@dxos/agent-runtime 0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264 → 0.13.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/dist/lib/chunk-AgentService.mjs +577 -319
- package/dist/lib/chunk-AgentService.mjs.map +1 -1
- package/dist/lib/index.mjs +2 -2
- package/dist/lib/testing.mjs +40 -28
- package/dist/lib/testing.mjs.map +1 -1
- package/dist/types/src/agent-service/AgentService.d.ts +20 -14
- package/dist/types/src/agent-service/AgentService.d.ts.map +1 -1
- package/dist/types/src/agent-service/agent-process.d.ts +560 -204
- package/dist/types/src/agent-service/agent-process.d.ts.map +1 -1
- package/dist/types/src/agent-service/delegation-strategy.d.ts +12 -4
- package/dist/types/src/agent-service/delegation-strategy.d.ts.map +1 -1
- package/dist/types/src/agent-service/index.d.ts +4 -4
- package/dist/types/src/agent-service/index.d.ts.map +1 -1
- package/dist/types/src/agent-service/mcp-servers.d.ts +10 -0
- package/dist/types/src/agent-service/mcp-servers.d.ts.map +1 -0
- package/dist/types/src/agent-service/mcp-servers.test.d.ts +2 -0
- package/dist/types/src/agent-service/mcp-servers.test.d.ts.map +1 -0
- package/dist/types/src/agent-service/queue-scripted.test.d.ts +2 -0
- package/dist/types/src/agent-service/queue-scripted.test.d.ts.map +1 -0
- package/dist/types/src/agent-service/tool-backgrounding.test.d.ts +2 -0
- package/dist/types/src/agent-service/tool-backgrounding.test.d.ts.map +1 -0
- package/dist/types/src/agent-service/turn-producer.d.ts +5 -1
- package/dist/types/src/agent-service/turn-producer.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/testing/assistant-test-layer.d.ts +12 -10
- package/dist/types/src/testing/assistant-test-layer.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +4 -4
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/test-agent.d.ts +4 -5
- package/dist/types/src/testing/test-agent.d.ts.map +1 -1
- package/dist/types/src/testing/trace-pretty-print.d.ts +1 -1
- package/dist/types/src/testing/trace-pretty-print.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +20 -18
- package/src/agent-service/AgentService.test.ts +348 -61
- package/src/agent-service/AgentService.ts +263 -147
- package/src/agent-service/README.md +6 -4
- package/src/agent-service/agent-process.test.ts +63 -131
- package/src/agent-service/agent-process.ts +549 -292
- package/src/agent-service/delegation-scripted.test.ts +5 -5
- package/src/agent-service/delegation-strategy.ts +16 -4
- package/src/agent-service/index.ts +12 -4
- package/src/agent-service/mcp-servers.test.ts +99 -0
- package/src/agent-service/mcp-servers.ts +62 -0
- package/src/agent-service/queue-scripted.test.ts +422 -0
- package/src/agent-service/redelivery.test.ts +30 -28
- package/src/agent-service/tool-backgrounding.test.ts +249 -0
- package/src/agent-service/turn-producer.ts +6 -2
- package/src/assistant-session-tests/format.test.ts +1 -1
- package/src/assistant-session-tests/request.test.ts +3 -3
- package/src/assistant-session-tests/scripted-loop.test.ts +99 -3
- package/src/assistant-session-tests/session-ollama.test.ts +1 -1
- package/src/assistant-session-tests/xml-response.test.ts +1 -1
- package/src/functions.test.ts +1 -1
- package/src/index.ts +1 -1
- package/src/testing/assistant-test-layer.ts +81 -33
- package/src/testing/index.ts +4 -4
- package/src/testing/test-agent.ts +8 -9
- package/src/testing/trace-pretty-print.ts +3 -4
|
@@ -1,33 +1,37 @@
|
|
|
1
1
|
import * as Cause from "effect/Cause";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
3
|
import * as Layer from "effect/Layer";
|
|
4
|
-
import * as
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
4
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
5
|
+
import * as Semaphore from "effect/Semaphore";
|
|
6
|
+
import { AiContext, AiSession, Alarm, HarnessControl, SessionStore, SkillHooks, getOperationFromTool, makeToolExecutionService, makeToolResolverFromOperations } from "@dxos/assistant";
|
|
7
|
+
import * as Chat from "@dxos/assistant/Chat";
|
|
8
|
+
import * as AgentService from "@dxos/compute/AgentService";
|
|
8
9
|
import * as Process from "@dxos/compute/Process";
|
|
9
10
|
import * as Skill from "@dxos/compute/Skill";
|
|
10
|
-
import { Annotation, Database, Feed, Obj, Ref, Registry } from "@dxos/echo";
|
|
11
|
-
import
|
|
11
|
+
import { Annotation, Database, Feed, Filter, Obj, Ref, Registry } from "@dxos/echo";
|
|
12
|
+
import * as EffectEx from "@dxos/effect/EffectEx";
|
|
12
13
|
import { DXN, EID } from "@dxos/keys";
|
|
13
14
|
import { log } from "@dxos/log";
|
|
15
|
+
import * as Tool from "effect/ai/Tool";
|
|
16
|
+
import * as Toolkit from "effect/ai/Toolkit";
|
|
14
17
|
import * as Clock from "effect/Clock";
|
|
15
18
|
import * as DateTime from "effect/DateTime";
|
|
16
19
|
import * as Duration from "effect/Duration";
|
|
17
20
|
import * as Exit from "effect/Exit";
|
|
18
|
-
import * as
|
|
21
|
+
import * as Option from "effect/Option";
|
|
19
22
|
import * as Schema from "effect/Schema";
|
|
20
23
|
import * as Struct from "effect/Struct";
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
import { AiService, OpaqueToolkit } from "@dxos/ai";
|
|
24
|
+
import { AiService, Model, OpaqueToolkit } from "@dxos/ai";
|
|
25
|
+
import { ProcessManager } from "@dxos/compute-runtime";
|
|
24
26
|
import * as Credential from "@dxos/compute/Credential";
|
|
25
|
-
import * as
|
|
27
|
+
import * as McpServer from "@dxos/compute/McpServer";
|
|
26
28
|
import * as Operation from "@dxos/compute/Operation";
|
|
27
29
|
import * as StorageService from "@dxos/compute/StorageService";
|
|
28
30
|
import * as Trace from "@dxos/compute/Trace";
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
+
import { AccessToken } from "@dxos/link";
|
|
32
|
+
import { Actor, ContentBlock, Message } from "@dxos/types";
|
|
33
|
+
import { markWork, trim } from "@dxos/util";
|
|
34
|
+
import { McpOAuth } from "@dxos/mcp-client";
|
|
31
35
|
//#region \0rolldown/runtime.js
|
|
32
36
|
var __defProp = Object.defineProperty;
|
|
33
37
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -40,6 +44,51 @@ var __exportAll = (all, no_symbols) => {
|
|
|
40
44
|
return target;
|
|
41
45
|
};
|
|
42
46
|
//#endregion
|
|
47
|
+
//#region src/agent-service/mcp-servers.ts
|
|
48
|
+
var __dxlog_file$2 = "/__w/dxos/dxos/packages/core/compute/agent-runtime/src/agent-service/mcp-servers.ts";
|
|
49
|
+
/**
|
|
50
|
+
* The space's enabled MCP servers as connection options, read at the start of every turn so a server
|
|
51
|
+
* added, toggled or signed in to from the chat's options applies from the next turn on.
|
|
52
|
+
*/
|
|
53
|
+
var loadSpaceMcpServers = () => Effect.gen(function* () {
|
|
54
|
+
const servers = yield* Database.query(Filter.type(McpServer.McpServer)).run;
|
|
55
|
+
return yield* Effect.forEach(servers.filter((server) => server.enabled !== false), toOptions);
|
|
56
|
+
});
|
|
57
|
+
var toOptions = (server) => Effect.gen(function* () {
|
|
58
|
+
const options = {
|
|
59
|
+
url: server.url,
|
|
60
|
+
protocol: server.protocol,
|
|
61
|
+
apiKey: server.apiKey
|
|
62
|
+
};
|
|
63
|
+
if (server.oauth?.tokens) return {
|
|
64
|
+
...options,
|
|
65
|
+
authProvider: McpOAuth.makeProvider({ store: McpServer.oauthStore(server) })
|
|
66
|
+
};
|
|
67
|
+
if (server.accessToken) {
|
|
68
|
+
const apiKey = yield* resolveAccessToken(server.accessToken);
|
|
69
|
+
return Option.isSome(apiKey) ? {
|
|
70
|
+
...options,
|
|
71
|
+
apiKey: apiKey.value
|
|
72
|
+
} : options;
|
|
73
|
+
}
|
|
74
|
+
return options;
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* Resolves through {@link Credential.CredentialsService} rather than reading `token` directly, so a
|
|
78
|
+
* token EDGE custodies (a managed one) resolves the same way a pasted one does.
|
|
79
|
+
*/
|
|
80
|
+
var resolveAccessToken = (ref) => Effect.gen(function* () {
|
|
81
|
+
const accessToken = yield* Database.load(ref);
|
|
82
|
+
const credentials = yield* Credential.CredentialsService;
|
|
83
|
+
const credential = yield* Effect.tryPromise(() => credentials.getCredential({ accessTokenId: accessToken.id }));
|
|
84
|
+
return Option.fromNullishOr(credential.apiKey);
|
|
85
|
+
}).pipe(Effect.tapError((error) => Effect.sync(() => log.warn("failed to resolve MCP server credential", { error }, {
|
|
86
|
+
"~LogMeta": "~LogMeta",
|
|
87
|
+
F: __dxlog_file$2,
|
|
88
|
+
L: 60,
|
|
89
|
+
S: void 0
|
|
90
|
+
}))), Effect.orElseSucceed(() => Option.none()));
|
|
91
|
+
//#endregion
|
|
43
92
|
//#region src/agent-service/turn-producer.ts
|
|
44
93
|
/**
|
|
45
94
|
* The default producer: `AiSession.Session`, which resolves the model, binds skills and context
|
|
@@ -57,14 +106,90 @@ var makeAiSessionTurnProducer = ({ feed, runtime, instructions }) => EffectEx.ac
|
|
|
57
106
|
//#region src/agent-service/agent-process.ts
|
|
58
107
|
var __dxlog_file$1 = "/__w/dxos/dxos/packages/core/compute/agent-runtime/src/agent-service/agent-process.ts";
|
|
59
108
|
var AGENT_PROCESS_KEY = "org.dxos.testing.process.agent";
|
|
109
|
+
var AgentPrompt = Schema.Union([Schema.String, Schema.Array(ContentBlock.Any)]);
|
|
110
|
+
/**
|
|
111
|
+
* Who sent a prompt: the plain-data subset of `Actor`, since the input crosses a JSON boundary
|
|
112
|
+
* (EDGE decodes it with the schema's type side) where a `Ref` cannot be supplied.
|
|
113
|
+
*/
|
|
114
|
+
var AgentInputSender = Actor.Actor.mapFields(Struct.pick([
|
|
115
|
+
"role",
|
|
116
|
+
"name",
|
|
117
|
+
"identityDid",
|
|
118
|
+
"email"
|
|
119
|
+
]));
|
|
120
|
+
/**
|
|
121
|
+
* Input accepted by {@link AgentProcess}: a bare prompt, or a prompt attributed to a sender (e.g. a
|
|
122
|
+
* relayed Discord author) whose identity the appended `Message` records.
|
|
123
|
+
*/
|
|
124
|
+
var AgentInput = Schema.Union([AgentPrompt, Schema.Struct({
|
|
125
|
+
prompt: AgentPrompt,
|
|
126
|
+
sender: Schema.optional(AgentInputSender),
|
|
127
|
+
/** Foreign identity and other source metadata copied onto the message (e.g. `{ discord: { userId } }`). */
|
|
128
|
+
properties: Schema.optional(Schema.Record(Schema.String, Schema.Any))
|
|
129
|
+
})]);
|
|
130
|
+
/** Builds the feed message for an input; the sender defaults to the plain user role. */
|
|
131
|
+
var makeInputMessage = (input) => {
|
|
132
|
+
const { prompt, sender, properties } = typeof input === "object" && "prompt" in input ? input : {
|
|
133
|
+
prompt: input,
|
|
134
|
+
sender: void 0,
|
|
135
|
+
properties: void 0
|
|
136
|
+
};
|
|
137
|
+
const blocks = typeof prompt === "string" ? [ContentBlock.Text.make({ text: prompt })] : [...prompt];
|
|
138
|
+
return Message.make({
|
|
139
|
+
sender: {
|
|
140
|
+
role: "user",
|
|
141
|
+
...sender
|
|
142
|
+
},
|
|
143
|
+
blocks,
|
|
144
|
+
properties
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* How long to wait before re-reading a queue that contradicts a write this process just made, and
|
|
149
|
+
* how many times. A hosted runtime serves the read from an eventually-consistent index, and the lag
|
|
150
|
+
* measured on EDGE is single-digit milliseconds — the cap is what keeps a write that never lands
|
|
151
|
+
* from waking the agent forever.
|
|
152
|
+
*/
|
|
153
|
+
var UNSEEN_WRITE_RETRY_MS = 250;
|
|
154
|
+
/** How much of a sub-agent's result the trace keeps: enough to read, never the whole payload. */
|
|
155
|
+
var RESULT_PREVIEW_LENGTH = 200;
|
|
156
|
+
/**
|
|
157
|
+
* A short, safe rendering of a sub-agent's result for the trace.
|
|
158
|
+
*
|
|
159
|
+
* Guarded because this sits between removing the child from `delegations` and running the strategy's
|
|
160
|
+
* `onComplete`: a `BigInt` or a cycle would throw here, become a defect, and take the whole return
|
|
161
|
+
* path with it — the work item would never be updated and work waiting on it never reconciled.
|
|
162
|
+
* Losing the preview is survivable; losing the return is not.
|
|
163
|
+
*/
|
|
164
|
+
var resultPreview = (value) => {
|
|
165
|
+
let text;
|
|
166
|
+
try {
|
|
167
|
+
text = typeof value === "string" ? value : JSON.stringify(value);
|
|
168
|
+
} catch {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (text === void 0) return;
|
|
172
|
+
return text.length > RESULT_PREVIEW_LENGTH ? `${text.slice(0, RESULT_PREVIEW_LENGTH)}…` : text;
|
|
173
|
+
};
|
|
174
|
+
var MAX_UNSEEN_WRITE_WAKES = 20;
|
|
60
175
|
/**
|
|
61
176
|
* Hosts a persistent, suspendible AiAgent that can process a number of prompts.
|
|
62
177
|
* The process target is a queue DXN string.
|
|
63
178
|
*/
|
|
64
|
-
var AgentProcess = (options) =>
|
|
179
|
+
var AgentProcess = (options) => Operation.makeDurable({
|
|
65
180
|
key: AGENT_PROCESS_KEY,
|
|
66
|
-
input:
|
|
181
|
+
input: AgentInput,
|
|
67
182
|
output: Schema.Void,
|
|
183
|
+
types: [
|
|
184
|
+
Chat.Chat,
|
|
185
|
+
Feed.Feed,
|
|
186
|
+
Message.Message,
|
|
187
|
+
Alarm.Alarm,
|
|
188
|
+
AiContext.Binding,
|
|
189
|
+
Skill.Skill,
|
|
190
|
+
McpServer.McpServer,
|
|
191
|
+
AccessToken.AccessToken
|
|
192
|
+
],
|
|
68
193
|
services: [
|
|
69
194
|
Database.Service,
|
|
70
195
|
OpaqueToolkit.OpaqueToolkitProvider,
|
|
@@ -77,32 +202,46 @@ var AgentProcess = (options) => Process.make({
|
|
|
77
202
|
],
|
|
78
203
|
rpcs: HarnessControl
|
|
79
204
|
}, (ctx) => Effect.gen(function* () {
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const instructions =
|
|
205
|
+
const chatDxn = Annotation.getDictionary(ctx.params.annotations, Process.TargetAnnotation).pipe(Option.getOrUndefined);
|
|
206
|
+
if (chatDxn == null) return yield* Effect.die(/* @__PURE__ */ new Error("Agent executable requires spawn options.target set to a Chat DXN."));
|
|
207
|
+
const chat = yield* Database.resolve(chatDxn, Chat.Chat).pipe(Effect.orDie);
|
|
208
|
+
const feed = yield* Database.load(chat.feed).pipe(Effect.orDie);
|
|
209
|
+
const instructions = chat.instructions ? yield* Database.load(chat.instructions).pipe(Effect.orElseSucceed(() => void 0)) : void 0;
|
|
85
210
|
const runtime = yield* Effect.context();
|
|
86
211
|
const session = yield* (options.makeTurnProducer ?? makeAiSessionTurnProducer)({
|
|
212
|
+
chat,
|
|
87
213
|
feed,
|
|
88
214
|
runtime,
|
|
89
215
|
instructions: instructions ? [instructions] : []
|
|
90
216
|
});
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
217
|
+
const sessionStore = new SessionStore();
|
|
218
|
+
let toolResults = [...yield* ToolResultsCell.get];
|
|
219
|
+
let ackedEntries = [...yield* AckedEntriesCell.get];
|
|
220
|
+
let selfWakes = yield* SelfWakesCell.get;
|
|
221
|
+
const toolCallManager = new ToolCallManager(yield* StorageService.StorageService);
|
|
94
222
|
yield* toolCallManager.load();
|
|
95
223
|
const clock = yield* Clock.Clock;
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
224
|
+
const now = () => clock.currentTimeMillisUnsafe();
|
|
225
|
+
yield* toolCallManager.reconcileWithInputQueue(toolResults);
|
|
226
|
+
const unseenAlarms = /* @__PURE__ */ new Map();
|
|
227
|
+
const reconcileAlarmWith = (state) => {
|
|
228
|
+
for (const alarm of state.pendingAlarms) unseenAlarms.delete(alarm.id);
|
|
229
|
+
const observed = state.pendingAlarms[0]?.wakeAt;
|
|
230
|
+
const wakeAts = [...observed != null ? [observed] : [], ...[...unseenAlarms.values()].map(({ wakeAt }) => wakeAt)];
|
|
231
|
+
const delay = computeAlarmDelay({
|
|
232
|
+
hasPendingWork: toolResults.length > 0 || state.pendingMessages.length > 0,
|
|
233
|
+
wakeAt: wakeAts.length > 0 ? Math.min(...wakeAts) : null,
|
|
234
|
+
now: now()
|
|
235
|
+
});
|
|
236
|
+
return delay != null ? ctx.setAlarm(delay) : Effect.void;
|
|
237
|
+
};
|
|
238
|
+
const reconcileAlarm = Effect.flatMap(sessionStore.loadPending(feed), reconcileAlarmWith);
|
|
239
|
+
const startupPending = yield* sessionStore.loadPending(feed);
|
|
240
|
+
yield* reconcileAlarmWith(startupPending);
|
|
103
241
|
const strategy = Option.fromNullishOr(options.delegationStrategy);
|
|
104
242
|
let delegations = [...yield* DelegationsCell.get];
|
|
105
|
-
const
|
|
243
|
+
const model = chat.session?.model ?? options.defaultModel;
|
|
244
|
+
const requestModelLayer = AiService.languageModel(DXN.getName(model ?? Model.claudeSonnet5.id), { provider: options.provider });
|
|
106
245
|
const operationInvoker = yield* ProcessManager.ProcessOperationInvoker.Service;
|
|
107
246
|
const runEndRequestHooks = Effect.gen(function* () {
|
|
108
247
|
yield* SkillHooks.runHooks({
|
|
@@ -117,164 +256,293 @@ var AgentProcess = (options) => Process.make({
|
|
|
117
256
|
}).pipe(Effect.asVoid, Effect.orDie)
|
|
118
257
|
});
|
|
119
258
|
});
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
259
|
+
let turnRan = false;
|
|
260
|
+
const unseenWriteIds = /* @__PURE__ */ new Set();
|
|
261
|
+
let unseenWriteWakes = 0;
|
|
262
|
+
const pendingWork = (state) => isAgentWorkPending({
|
|
263
|
+
toolResults,
|
|
264
|
+
pendingMessages: state.pendingMessages,
|
|
265
|
+
pendingAlarms: state.pendingAlarms,
|
|
266
|
+
delegations,
|
|
267
|
+
toolCallManager
|
|
268
|
+
});
|
|
269
|
+
const reportSleeping = (state) => state.pendingAlarms.length > 0 ? Trace.emitRequestPhase("sleeping") : Effect.void;
|
|
270
|
+
const maybeCompleteWith = (state) => Effect.gen(function* () {
|
|
271
|
+
for (const pid of dropReportedToolResults(toolResults, (pid) => toolCallManager.isReported(pid))) log("drop tool result reported within its turn", { pid }, {
|
|
272
|
+
"~LogMeta": "~LogMeta",
|
|
273
|
+
F: __dxlog_file$1,
|
|
274
|
+
L: 364,
|
|
275
|
+
S: this
|
|
276
|
+
});
|
|
277
|
+
if (pendingWork(state)) {
|
|
278
|
+
yield* reportSleeping(state);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (!turnRan) {
|
|
282
|
+
log("agent idle before its first turn, staying resident", void 0, {
|
|
283
|
+
"~LogMeta": "~LogMeta",
|
|
284
|
+
F: __dxlog_file$1,
|
|
285
|
+
L: 375,
|
|
286
|
+
S: this
|
|
287
|
+
});
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
127
290
|
yield* runEndRequestHooks;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
alarmManager,
|
|
131
|
-
delegations,
|
|
132
|
-
toolCallManager
|
|
133
|
-
})) {
|
|
291
|
+
const after = yield* sessionStore.loadPending(feed);
|
|
292
|
+
if (pendingWork(after)) {
|
|
134
293
|
log("agent work enqueued by end-request hook, continuing", void 0, {
|
|
135
294
|
"~LogMeta": "~LogMeta",
|
|
136
295
|
F: __dxlog_file$1,
|
|
137
|
-
L:
|
|
296
|
+
L: 384,
|
|
138
297
|
S: this
|
|
139
298
|
});
|
|
140
|
-
|
|
299
|
+
yield* reconcileAlarmWith(after);
|
|
300
|
+
yield* reportSleeping(after);
|
|
141
301
|
return;
|
|
142
302
|
}
|
|
143
303
|
log("agent work complete, succeeding", void 0, {
|
|
144
304
|
"~LogMeta": "~LogMeta",
|
|
145
305
|
F: __dxlog_file$1,
|
|
146
|
-
L:
|
|
306
|
+
L: 390,
|
|
147
307
|
S: this
|
|
148
308
|
});
|
|
149
309
|
ctx.succeed();
|
|
150
310
|
});
|
|
311
|
+
const maybeComplete = Effect.flatMap(sessionStore.loadPending(feed), maybeCompleteWith);
|
|
151
312
|
return {
|
|
313
|
+
onSpawn: Effect.fnUntraced(function* () {
|
|
314
|
+
if (startupPending.pendingMessages.length > 0) {
|
|
315
|
+
log.info("discarding queue entries left by a previous process", { count: startupPending.pendingMessages.length }, {
|
|
316
|
+
"~LogMeta": "~LogMeta",
|
|
317
|
+
F: __dxlog_file$1,
|
|
318
|
+
L: 403,
|
|
319
|
+
S: this
|
|
320
|
+
});
|
|
321
|
+
yield* Effect.forEach(startupPending.pendingMessages, (message) => sessionStore.ack(feed, message), { discard: true });
|
|
322
|
+
}
|
|
323
|
+
}),
|
|
152
324
|
rpcHandlers: yield* HarnessControl.toHandlers({
|
|
153
325
|
setAlarm: Effect.fn(function* ({ at, message }) {
|
|
154
|
-
yield*
|
|
155
|
-
|
|
326
|
+
const alarm = yield* sessionStore.setAlarm(feed, {
|
|
327
|
+
wakeAt: DateTime.toEpochMillis(at),
|
|
328
|
+
message: message ?? void 0
|
|
329
|
+
});
|
|
330
|
+
unseenAlarms.set(alarm.id, {
|
|
331
|
+
wakeAt: alarm.wakeAt,
|
|
332
|
+
wakes: 0
|
|
333
|
+
});
|
|
334
|
+
yield* reconcileAlarm;
|
|
156
335
|
}),
|
|
157
336
|
enqueueMessage: Effect.fn(function* ({ content }) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
content
|
|
337
|
+
const message = Message.make({
|
|
338
|
+
sender: { role: "user" },
|
|
339
|
+
blocks: [...content]
|
|
161
340
|
});
|
|
162
|
-
yield*
|
|
163
|
-
|
|
341
|
+
yield* sessionStore.enqueueMessage(feed, message);
|
|
342
|
+
unseenWriteIds.add(message.id);
|
|
343
|
+
yield* ctx.setAlarm(0);
|
|
164
344
|
})
|
|
165
345
|
}),
|
|
166
|
-
onInput: Effect.fnUntraced(function* (
|
|
167
|
-
log("agent onInput received", { backlog:
|
|
168
|
-
"~LogMeta": "~LogMeta",
|
|
169
|
-
F: __dxlog_file$1,
|
|
170
|
-
L: 229,
|
|
171
|
-
S: this
|
|
172
|
-
});
|
|
173
|
-
const content = typeof prompt === "string" ? [ContentBlock.Text.make({ text: prompt })] : [...prompt];
|
|
174
|
-
inputQueue.push({
|
|
175
|
-
_tag: "prompt",
|
|
176
|
-
content
|
|
177
|
-
});
|
|
178
|
-
log("agent onInput persisting queue", { depth: inputQueue.length }, {
|
|
346
|
+
onInput: Effect.fnUntraced(function* (input) {
|
|
347
|
+
log("agent onInput received", { backlog: toolResults.length }, {
|
|
179
348
|
"~LogMeta": "~LogMeta",
|
|
180
349
|
F: __dxlog_file$1,
|
|
181
|
-
L:
|
|
350
|
+
L: 432,
|
|
182
351
|
S: this
|
|
183
352
|
});
|
|
184
|
-
|
|
185
|
-
|
|
353
|
+
const message = makeInputMessage(input);
|
|
354
|
+
yield* sessionStore.enqueueMessage(feed, message);
|
|
355
|
+
markWork("agent.prompt-queued");
|
|
356
|
+
unseenWriteIds.add(message.id);
|
|
357
|
+
yield* ctx.setAlarm(0);
|
|
358
|
+
log("agent onInput enqueued to feed", void 0, {
|
|
186
359
|
"~LogMeta": "~LogMeta",
|
|
187
360
|
F: __dxlog_file$1,
|
|
188
|
-
L:
|
|
361
|
+
L: 438,
|
|
189
362
|
S: this
|
|
190
363
|
});
|
|
191
|
-
|
|
192
|
-
|
|
364
|
+
}),
|
|
365
|
+
onAlarm: Effect.fnUntraced(function* () {
|
|
366
|
+
log("agent onAlarm fired", { backlog: toolResults.length }, {
|
|
193
367
|
"~LogMeta": "~LogMeta",
|
|
194
368
|
F: __dxlog_file$1,
|
|
195
|
-
L:
|
|
369
|
+
L: 442,
|
|
196
370
|
S: this
|
|
197
371
|
});
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
log("
|
|
372
|
+
markWork("agent.wake");
|
|
373
|
+
yield* Trace.emitRequestPhase("preparing");
|
|
374
|
+
for (const pid of dropReportedToolResults(toolResults, (pid) => toolCallManager.isReported(pid))) log.info("skip tool result that was reported synchronously", { pid }, {
|
|
201
375
|
"~LogMeta": "~LogMeta",
|
|
202
376
|
F: __dxlog_file$1,
|
|
203
|
-
L:
|
|
377
|
+
L: 451,
|
|
204
378
|
S: this
|
|
205
379
|
});
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
380
|
+
const toolResult = toolResults.shift();
|
|
381
|
+
let prompt;
|
|
382
|
+
let dequeued;
|
|
383
|
+
if (toolResult !== void 0) {
|
|
384
|
+
log("agent onAlarm handling", { tag: toolResult._tag }, {
|
|
209
385
|
"~LogMeta": "~LogMeta",
|
|
210
386
|
F: __dxlog_file$1,
|
|
211
|
-
L:
|
|
387
|
+
L: 459,
|
|
212
388
|
S: this
|
|
213
389
|
});
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
390
|
+
prompt = toolResultPrompt(toolResult);
|
|
391
|
+
} else {
|
|
392
|
+
const state = yield* sessionStore.loadPending(feed);
|
|
393
|
+
markWork("agent.pending-loaded");
|
|
394
|
+
const stillPending = /* @__PURE__ */ new Set([...state.pendingMessages.map((message) => message.id), ...state.pendingAlarms.map((alarm) => alarm.id)]);
|
|
395
|
+
if (ackedEntries.some((id) => !stillPending.has(id))) {
|
|
396
|
+
ackedEntries = ackedEntries.filter((id) => stillPending.has(id));
|
|
397
|
+
yield* AckedEntriesCell.set(ackedEntries);
|
|
398
|
+
}
|
|
399
|
+
const acked = new Set(ackedEntries);
|
|
400
|
+
const message = state.pendingMessages.find((candidate) => !acked.has(candidate.id));
|
|
401
|
+
const dueAlarm = state.pendingAlarms.find((alarm) => alarm.wakeAt <= now() && !acked.has(alarm.id));
|
|
402
|
+
for (const [id, entry] of unseenAlarms) {
|
|
403
|
+
if (state.pendingAlarms.some((alarm) => alarm.id === id)) continue;
|
|
404
|
+
entry.wakes++;
|
|
405
|
+
if (entry.wakes >= MAX_UNSEEN_WRITE_WAKES) unseenAlarms.delete(id);
|
|
406
|
+
}
|
|
407
|
+
if (message !== void 0) {
|
|
408
|
+
log("agent onAlarm handling", {
|
|
409
|
+
tag: "message",
|
|
410
|
+
id: message.id
|
|
411
|
+
}, {
|
|
412
|
+
"~LogMeta": "~LogMeta",
|
|
413
|
+
F: __dxlog_file$1,
|
|
414
|
+
L: 491,
|
|
415
|
+
S: this
|
|
416
|
+
});
|
|
417
|
+
unseenWriteIds.delete(message.id);
|
|
418
|
+
unseenWriteWakes = 0;
|
|
419
|
+
if (isUserPrompt(message) && selfWakes > 0) {
|
|
420
|
+
selfWakes = 0;
|
|
421
|
+
yield* SelfWakesCell.set(selfWakes);
|
|
422
|
+
}
|
|
423
|
+
dequeued = message;
|
|
424
|
+
prompt = [...message.blocks];
|
|
425
|
+
} else if (dueAlarm !== void 0) {
|
|
426
|
+
unseenAlarms.delete(dueAlarm.id);
|
|
427
|
+
if (selfWakes >= Alarm.MAX_SELF_WAKES) {
|
|
428
|
+
log.warn("agent self-wake budget spent, dropping alarm", { wakes: selfWakes }, {
|
|
429
|
+
"~LogMeta": "~LogMeta",
|
|
430
|
+
F: __dxlog_file$1,
|
|
431
|
+
L: 504,
|
|
432
|
+
S: this
|
|
433
|
+
});
|
|
434
|
+
yield* sessionStore.ack(feed, dueAlarm);
|
|
435
|
+
ackedEntries = [...ackedEntries, dueAlarm.id];
|
|
436
|
+
yield* AckedEntriesCell.set(ackedEntries);
|
|
437
|
+
const after = yield* sessionStore.loadPending(feed);
|
|
438
|
+
yield* reconcileAlarmWith(after);
|
|
439
|
+
yield* maybeCompleteWith(after);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
selfWakes++;
|
|
443
|
+
yield* SelfWakesCell.set(selfWakes);
|
|
444
|
+
log("agent onAlarm self-wake", {
|
|
445
|
+
firedAt: dueAlarm.wakeAt,
|
|
446
|
+
wakes: selfWakes
|
|
447
|
+
}, {
|
|
448
|
+
"~LogMeta": "~LogMeta",
|
|
449
|
+
F: __dxlog_file$1,
|
|
450
|
+
L: 515,
|
|
451
|
+
S: this
|
|
452
|
+
});
|
|
453
|
+
dequeued = dueAlarm;
|
|
454
|
+
prompt = [ContentBlock.Text.make({
|
|
455
|
+
text: wakeUpPrompt(dueAlarm.wakeAt, dueAlarm.message ?? null, {
|
|
456
|
+
wake: selfWakes,
|
|
457
|
+
max: Alarm.MAX_SELF_WAKES
|
|
458
|
+
}),
|
|
459
|
+
disposition: "synthetic"
|
|
460
|
+
})];
|
|
461
|
+
} else if (unseenWriteIds.size > 0 && unseenWriteWakes < MAX_UNSEEN_WRITE_WAKES) {
|
|
462
|
+
unseenWriteWakes++;
|
|
463
|
+
markWork("agent.unseen-write-retry");
|
|
464
|
+
log("agent onAlarm empty queue with an unread write, waking again", {
|
|
465
|
+
unseenWrites: unseenWriteIds.size,
|
|
466
|
+
attempt: unseenWriteWakes
|
|
467
|
+
}, {
|
|
468
|
+
"~LogMeta": "~LogMeta",
|
|
469
|
+
F: __dxlog_file$1,
|
|
470
|
+
L: 533,
|
|
471
|
+
S: this
|
|
472
|
+
});
|
|
473
|
+
yield* ctx.setAlarm(UNSEEN_WRITE_RETRY_MS);
|
|
474
|
+
return;
|
|
475
|
+
} else if (unseenWriteIds.size > 0) {
|
|
476
|
+
log.warn("agent giving up on an unread write, staying resident", {
|
|
477
|
+
unseenWrites: unseenWriteIds.size,
|
|
478
|
+
wakes: unseenWriteWakes
|
|
479
|
+
}, {
|
|
480
|
+
"~LogMeta": "~LogMeta",
|
|
481
|
+
F: __dxlog_file$1,
|
|
482
|
+
L: 544,
|
|
483
|
+
S: this
|
|
484
|
+
});
|
|
485
|
+
yield* reconcileAlarmWith(state);
|
|
486
|
+
return;
|
|
487
|
+
} else {
|
|
488
|
+
log("agent onAlarm empty queue", {}, {
|
|
489
|
+
"~LogMeta": "~LogMeta",
|
|
490
|
+
F: __dxlog_file$1,
|
|
491
|
+
L: 551,
|
|
492
|
+
S: this
|
|
493
|
+
});
|
|
494
|
+
yield* reconcileAlarmWith(state);
|
|
495
|
+
yield* maybeCompleteWith(state);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
220
498
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
L: 251,
|
|
225
|
-
S: this
|
|
226
|
-
});
|
|
227
|
-
const item = inputQueue.shift();
|
|
228
|
-
if (!item) {
|
|
229
|
-
log("agent onAlarm empty queue", {}, {
|
|
499
|
+
const [mcpServers] = yield* Effect.all([loadSpaceMcpServers(), Effect.gen(function* () {
|
|
500
|
+
if (dequeued !== void 0) yield* sessionStore.markInFlight(feed, dequeued);
|
|
501
|
+
log("begin request", { prompt }, {
|
|
230
502
|
"~LogMeta": "~LogMeta",
|
|
231
503
|
F: __dxlog_file$1,
|
|
232
|
-
L:
|
|
504
|
+
L: 570,
|
|
233
505
|
S: this
|
|
234
506
|
});
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
S: this
|
|
245
|
-
});
|
|
246
|
-
const prompt = agentEventToPrompt(item);
|
|
247
|
-
log("begin request", { prompt }, {
|
|
248
|
-
"~LogMeta": "~LogMeta",
|
|
249
|
-
F: __dxlog_file$1,
|
|
250
|
-
L: 267,
|
|
251
|
-
S: this
|
|
252
|
-
});
|
|
253
|
-
log("trace agent request begin", void 0, {
|
|
254
|
-
"~LogMeta": "~LogMeta",
|
|
255
|
-
F: __dxlog_file$1,
|
|
256
|
-
L: 268,
|
|
257
|
-
S: this
|
|
258
|
-
});
|
|
259
|
-
yield* Trace.write(AgentRequestBegin, {});
|
|
507
|
+
log("trace agent request begin", void 0, {
|
|
508
|
+
"~LogMeta": "~LogMeta",
|
|
509
|
+
F: __dxlog_file$1,
|
|
510
|
+
L: 571,
|
|
511
|
+
S: this
|
|
512
|
+
});
|
|
513
|
+
yield* Trace.write(Trace.AgentRequestBegin, {});
|
|
514
|
+
})], { concurrency: "unbounded" });
|
|
515
|
+
markWork("agent.turn-begin");
|
|
260
516
|
yield* session.runTurn({
|
|
261
517
|
prompt,
|
|
518
|
+
sender: dequeued !== void 0 && Obj.instanceOf(Message.Message, dequeued) ? dequeued.sender : void 0,
|
|
262
519
|
system: options.systemPrompt,
|
|
263
|
-
mcpServers
|
|
264
|
-
}).pipe(Effect.onExit((exit) =>
|
|
265
|
-
|
|
266
|
-
|
|
520
|
+
mcpServers
|
|
521
|
+
}).pipe(Effect.onExit((exit) => Effect.gen(function* () {
|
|
522
|
+
yield* Trace.write(Trace.AgentRequestEnd, {
|
|
523
|
+
status: Exit.isSuccess(exit) ? "success" : Exit.hasInterrupts(exit) ? "interrupted" : "error",
|
|
524
|
+
error: Exit.isFailure(exit) ? Cause.pretty(exit.cause) : void 0
|
|
525
|
+
});
|
|
526
|
+
const onTurnFailed = Option.isSome(strategy) ? strategy.value.onTurnFailed : void 0;
|
|
527
|
+
if (onTurnFailed && Exit.isFailure(exit) && !Cause.hasInterrupts(exit.cause)) yield* onTurnFailed(chat, exit.cause);
|
|
267
528
|
})));
|
|
268
529
|
log("end request", void 0, {
|
|
269
530
|
"~LogMeta": "~LogMeta",
|
|
270
531
|
F: __dxlog_file$1,
|
|
271
|
-
L:
|
|
532
|
+
L: 605,
|
|
272
533
|
S: this
|
|
273
534
|
});
|
|
274
|
-
|
|
535
|
+
turnRan = true;
|
|
536
|
+
yield* ToolResultsCell.set(toolResults);
|
|
537
|
+
if (dequeued !== void 0) {
|
|
538
|
+
yield* sessionStore.ack(feed, dequeued);
|
|
539
|
+
ackedEntries = [...ackedEntries, dequeued.id];
|
|
540
|
+
yield* AckedEntriesCell.set(ackedEntries);
|
|
541
|
+
}
|
|
542
|
+
const after = yield* sessionStore.loadPending(feed);
|
|
275
543
|
if (Option.isSome(strategy)) {
|
|
276
544
|
const activeIds = new Set(delegations.map((delegation) => delegation.id));
|
|
277
|
-
const pending = yield* strategy.value.reconcile(
|
|
545
|
+
const pending = yield* strategy.value.reconcile(chat, activeIds);
|
|
278
546
|
for (const delegation of pending) {
|
|
279
547
|
const pid = yield* delegation.spawn;
|
|
280
548
|
delegations.push({
|
|
@@ -287,14 +555,18 @@ var AgentProcess = (options) => Process.make({
|
|
|
287
555
|
}, {
|
|
288
556
|
"~LogMeta": "~LogMeta",
|
|
289
557
|
F: __dxlog_file$1,
|
|
290
|
-
L:
|
|
558
|
+
L: 626,
|
|
291
559
|
S: this
|
|
292
560
|
});
|
|
561
|
+
yield* Trace.write(Trace.DelegationSpawned, {
|
|
562
|
+
taskId: delegation.id,
|
|
563
|
+
pid: String(pid)
|
|
564
|
+
});
|
|
293
565
|
}
|
|
294
566
|
if (pending.length > 0) yield* DelegationsCell.set(delegations);
|
|
295
567
|
}
|
|
296
|
-
|
|
297
|
-
yield*
|
|
568
|
+
yield* reconcileAlarmWith(after);
|
|
569
|
+
yield* maybeCompleteWith(after);
|
|
298
570
|
}, Effect.orDie, Effect.provide(Layer.mergeAll(makeToolResolverFromOperations(), ToolExecutionService({
|
|
299
571
|
toolCallManager,
|
|
300
572
|
feed,
|
|
@@ -304,7 +576,7 @@ var AgentProcess = (options) => Process.make({
|
|
|
304
576
|
log("childEvent", { event }, {
|
|
305
577
|
"~LogMeta": "~LogMeta",
|
|
306
578
|
F: __dxlog_file$1,
|
|
307
|
-
L:
|
|
579
|
+
L: 653,
|
|
308
580
|
S: this
|
|
309
581
|
});
|
|
310
582
|
if (event._tag === "exited") {
|
|
@@ -313,10 +585,17 @@ var AgentProcess = (options) => Process.make({
|
|
|
313
585
|
delegations = delegations.filter((other) => other.pid !== event.pid);
|
|
314
586
|
yield* DelegationsCell.set(delegations);
|
|
315
587
|
const exit = yield* (yield* (yield* ProcessManager.ProcessOperationInvoker.Service).attachFiber(event.pid).pipe(Effect.orDie)).await;
|
|
588
|
+
const result = Exit.isSuccess(exit) ? resultPreview(exit.value) : void 0;
|
|
589
|
+
yield* Trace.write(Trace.DelegationCompleted, {
|
|
590
|
+
taskId: delegation.id,
|
|
591
|
+
pid: String(event.pid),
|
|
592
|
+
status: Exit.isSuccess(exit) ? "success" : "failure",
|
|
593
|
+
...result === void 0 ? {} : { result }
|
|
594
|
+
});
|
|
316
595
|
if (Option.isSome(strategy)) {
|
|
317
|
-
yield* strategy.value.onComplete(
|
|
596
|
+
yield* strategy.value.onComplete(chat, delegation.id, exit);
|
|
318
597
|
const activeIds = new Set(delegations.map((delegation) => delegation.id));
|
|
319
|
-
const pending = yield* strategy.value.reconcile(
|
|
598
|
+
const pending = yield* strategy.value.reconcile(chat, activeIds);
|
|
320
599
|
for (const next of pending) {
|
|
321
600
|
const pid = yield* next.spawn;
|
|
322
601
|
delegations.push({
|
|
@@ -329,9 +608,13 @@ var AgentProcess = (options) => Process.make({
|
|
|
329
608
|
}, {
|
|
330
609
|
"~LogMeta": "~LogMeta",
|
|
331
610
|
F: __dxlog_file$1,
|
|
332
|
-
L:
|
|
611
|
+
L: 685,
|
|
333
612
|
S: this
|
|
334
613
|
});
|
|
614
|
+
yield* Trace.write(Trace.DelegationSpawned, {
|
|
615
|
+
taskId: next.id,
|
|
616
|
+
pid: String(pid)
|
|
617
|
+
});
|
|
335
618
|
}
|
|
336
619
|
if (pending.length > 0) yield* DelegationsCell.set(delegations);
|
|
337
620
|
}
|
|
@@ -342,18 +625,18 @@ var AgentProcess = (options) => Process.make({
|
|
|
342
625
|
}, {
|
|
343
626
|
"~LogMeta": "~LogMeta",
|
|
344
627
|
F: __dxlog_file$1,
|
|
345
|
-
L:
|
|
628
|
+
L: 692,
|
|
346
629
|
S: this
|
|
347
630
|
});
|
|
348
631
|
yield* maybeComplete;
|
|
349
632
|
} else if (toolCallManager.isToolCall(event.pid)) {
|
|
350
633
|
const attachExit = yield* (yield* ProcessManager.ProcessOperationInvoker.Service).attachFiber(event.pid).pipe(Effect.exit);
|
|
351
634
|
if (Exit.isFailure(attachExit)) {
|
|
352
|
-
if (toolCallManager.isToolCall(event.pid) ||
|
|
635
|
+
if (toolCallManager.isToolCall(event.pid) || toolResults.some((item) => item.pid === event.pid) || toolCallManager.isReported(event.pid)) {
|
|
353
636
|
log.verbose("childEvent skipped (process gone, result already handled)", { pid: event.pid }, {
|
|
354
637
|
"~LogMeta": "~LogMeta",
|
|
355
638
|
F: __dxlog_file$1,
|
|
356
|
-
L:
|
|
639
|
+
L: 705,
|
|
357
640
|
S: this
|
|
358
641
|
});
|
|
359
642
|
return;
|
|
@@ -374,47 +657,40 @@ var AgentProcess = (options) => Process.make({
|
|
|
374
657
|
isError: true
|
|
375
658
|
})
|
|
376
659
|
})));
|
|
377
|
-
|
|
660
|
+
toolResults.push(result);
|
|
378
661
|
log("agent onChildEvent persisted tool result", {
|
|
379
|
-
depth:
|
|
662
|
+
depth: toolResults.length,
|
|
380
663
|
childPid: event.pid
|
|
381
664
|
}, {
|
|
382
665
|
"~LogMeta": "~LogMeta",
|
|
383
666
|
F: __dxlog_file$1,
|
|
384
|
-
L:
|
|
667
|
+
L: 730,
|
|
385
668
|
S: this
|
|
386
669
|
});
|
|
387
|
-
yield*
|
|
388
|
-
|
|
389
|
-
log("agent onChildEvent alarm scheduled", { depth:
|
|
670
|
+
yield* ToolResultsCell.set(toolResults);
|
|
671
|
+
yield* ctx.setAlarm(0);
|
|
672
|
+
log("agent onChildEvent alarm scheduled", { depth: toolResults.length }, {
|
|
390
673
|
"~LogMeta": "~LogMeta",
|
|
391
674
|
F: __dxlog_file$1,
|
|
392
|
-
L:
|
|
675
|
+
L: 733,
|
|
393
676
|
S: this
|
|
394
677
|
});
|
|
395
678
|
} else log.verbose("childEvent ignored non-tool call and not a delegation", { pid: event.pid }, {
|
|
396
679
|
"~LogMeta": "~LogMeta",
|
|
397
680
|
F: __dxlog_file$1,
|
|
398
|
-
L:
|
|
681
|
+
L: 735,
|
|
399
682
|
S: this
|
|
400
683
|
});
|
|
401
684
|
}
|
|
402
685
|
})
|
|
403
686
|
};
|
|
404
687
|
}));
|
|
405
|
-
var
|
|
406
|
-
|
|
407
|
-
Schema.
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}),
|
|
412
|
-
Schema.TaggedStruct("alarm", {
|
|
413
|
-
firedAt: Schema.Number,
|
|
414
|
-
message: Schema.NullOr(Schema.String)
|
|
415
|
-
})
|
|
416
|
-
]);
|
|
417
|
-
var AgentEventsCell = StorageService.cell(Schema.fromJsonString(Schema.Array(AgentEvent).pipe(Schema.mutable)), "inputQueue").pipe(StorageService.withDefault(() => []));
|
|
688
|
+
var ToolResultEvent = Schema.TaggedStruct("tool_result", {
|
|
689
|
+
pid: Process.ID,
|
|
690
|
+
result: Schema.Unknown,
|
|
691
|
+
isError: Schema.Boolean
|
|
692
|
+
});
|
|
693
|
+
var ToolResultsCell = StorageService.cell(Schema.fromJsonString(Schema.Array(ToolResultEvent).pipe(Schema.mutable)), "toolResults").pipe(StorageService.withDefault(() => []));
|
|
418
694
|
/**
|
|
419
695
|
* Tracks delegated sub-agent child processes (pid -> correlation id) so that, after a hibernation,
|
|
420
696
|
* a delegated child's exit can be matched back to the work it was fulfilling.
|
|
@@ -424,6 +700,25 @@ var Delegation = Schema.Struct({
|
|
|
424
700
|
id: Schema.String
|
|
425
701
|
}).mapFields(Struct.map(Schema.mutableKey));
|
|
426
702
|
var DelegationsCell = StorageService.cell(Schema.fromJsonString(Schema.Array(Delegation).pipe(Schema.mutable)), "delegations").pipe(StorageService.withDefault(() => []));
|
|
703
|
+
/**
|
|
704
|
+
* Ids of queue entries this process has ACKED, held until the ack is visible in the feed read.
|
|
705
|
+
*
|
|
706
|
+
* The ack is a feed append and a hosted process reads the feed back through the eventually
|
|
707
|
+
* consistent space index, so `loadPending` keeps returning an entry whose ack has not landed yet —
|
|
708
|
+
* and `loadPending` deliberately includes in-flight entries, so that an interrupted turn is
|
|
709
|
+
* redelivered. Without this the two combine into a redelivery loop: the agent re-runs the same turn
|
|
710
|
+
* on every wake, forever. Durable rather than in-memory because the isolate does not survive
|
|
711
|
+
* between turns, and pruned as soon as the entry leaves the pending set, so it cannot grow
|
|
712
|
+
* unbounded.
|
|
713
|
+
*
|
|
714
|
+
* This does NOT weaken at-least-once delivery: an entry is recorded only after its turn ran, which
|
|
715
|
+
* is the same point the ack is written, so a crash before that still redelivers.
|
|
716
|
+
*/
|
|
717
|
+
var AckedEntriesCell = StorageService.cell(Schema.fromJsonString(Schema.Array(Schema.String).pipe(Schema.mutable)), "ackedEntries").pipe(StorageService.withDefault(() => []));
|
|
718
|
+
/** Alarms that have woken the agent since the last user prompt; bounded by {@link Alarm.MAX_SELF_WAKES}. */
|
|
719
|
+
var SelfWakesCell = StorageService.cell(Schema.fromJsonString(Schema.Number), "selfWakes").pipe(StorageService.withDefault(() => 0));
|
|
720
|
+
/** A prompt someone typed, as opposed to one the system wrote (a report, a tool result). */
|
|
721
|
+
var isUserPrompt = (message) => message.sender.role === "user" && message.blocks.some((block) => block._tag === "text" && block.disposition !== "synthetic");
|
|
427
722
|
var ToolCallState = Schema.Struct({ activeCalls: Schema.Array(Schema.Struct({
|
|
428
723
|
pid: Process.ID,
|
|
429
724
|
reported: Schema.Boolean
|
|
@@ -475,7 +770,6 @@ var ToolCallManager = class {
|
|
|
475
770
|
return Effect.gen({ self: this }, function* () {
|
|
476
771
|
let changed = false;
|
|
477
772
|
for (const item of queue) {
|
|
478
|
-
if (item._tag !== "tool_result") continue;
|
|
479
773
|
const call = this.#state.activeCalls.find((entry) => entry.pid === item.pid);
|
|
480
774
|
if (call?.reported) {
|
|
481
775
|
call.reported = false;
|
|
@@ -483,7 +777,7 @@ var ToolCallManager = class {
|
|
|
483
777
|
log("reconcile queued tool result", { pid: item.pid }, {
|
|
484
778
|
"~LogMeta": "~LogMeta",
|
|
485
779
|
F: __dxlog_file$1,
|
|
486
|
-
L:
|
|
780
|
+
L: 890,
|
|
487
781
|
S: this
|
|
488
782
|
});
|
|
489
783
|
}
|
|
@@ -492,8 +786,8 @@ var ToolCallManager = class {
|
|
|
492
786
|
}).pipe(Effect.provideService(StorageService.StorageService, this.#storageService));
|
|
493
787
|
}
|
|
494
788
|
};
|
|
495
|
-
/** True while the agent still has queued work, a
|
|
496
|
-
var isAgentWorkPending = ({
|
|
789
|
+
/** True while the agent still has queued work, a pending alarm, subprocesses, or undelivered tool results. */
|
|
790
|
+
var isAgentWorkPending = ({ toolResults, pendingMessages, pendingAlarms, delegations, toolCallManager }) => toolResults.length > 0 || pendingMessages.length > 0 || pendingAlarms.length > 0 || delegations.length > 0 || toolCallManager.hasPendingToolResults();
|
|
497
791
|
/**
|
|
498
792
|
* Discards tool results at the head of the queue whose values already reached the agent.
|
|
499
793
|
*
|
|
@@ -507,37 +801,24 @@ var dropReportedToolResults = (queue, isReported) => {
|
|
|
507
801
|
const dropped = [];
|
|
508
802
|
while (queue.length > 0) {
|
|
509
803
|
const head = queue[0];
|
|
510
|
-
if (
|
|
804
|
+
if (!isReported(head.pid)) break;
|
|
511
805
|
queue.shift();
|
|
512
806
|
dropped.push(head.pid);
|
|
513
807
|
}
|
|
514
808
|
return dropped;
|
|
515
809
|
};
|
|
516
810
|
/**
|
|
517
|
-
* Renders a
|
|
518
|
-
*
|
|
519
|
-
* A tool result recovered across a reload is redelivered as a synthetic `<result pid=N>` TEXT block
|
|
811
|
+
* Renders a recovered tool result as the next turn's prompt: a synthetic `<result pid=N>` TEXT block
|
|
520
812
|
* rather than a tool-result part, because the request it belonged to is gone and its tool-call id
|
|
521
813
|
* cannot be answered. `disposition: 'synthetic'` keeps it out of the user-visible transcript.
|
|
522
814
|
*/
|
|
523
|
-
var
|
|
524
|
-
text: event.isError ? toolErrorResponse(event.pid, event.result) : toolResultResponse(event.pid, event.result),
|
|
815
|
+
var toolResultPrompt = (event) => [ContentBlock.Text.make({
|
|
816
|
+
text: event.isError ? toolErrorResponse(event.pid, String(event.result)) : toolResultResponse(event.pid, event.result),
|
|
525
817
|
disposition: "synthetic"
|
|
526
|
-
})]
|
|
527
|
-
text: wakeUpPrompt(event.firedAt, event.message),
|
|
528
|
-
disposition: "synthetic"
|
|
529
|
-
})]), Match.exhaustive);
|
|
530
|
-
/**
|
|
531
|
-
* Persisted next agent-scheduled self-wake: the UNIX timestamp (ms) and an optional reminder
|
|
532
|
-
* message delivered when it fires, or `null` when no self-wake is set.
|
|
533
|
-
*/
|
|
534
|
-
var AgentAlarmCell = StorageService.cell(Schema.fromJsonString(Schema.NullOr(Schema.Struct({
|
|
535
|
-
wakeAt: Schema.Number,
|
|
536
|
-
message: Schema.NullOr(Schema.String)
|
|
537
|
-
}))), "agentAlarm").pipe(StorageService.withDefault(() => null));
|
|
818
|
+
})];
|
|
538
819
|
/**
|
|
539
|
-
* Computes the timeout to pass to `ctx.setAlarm`, reconciling pending queue work with the
|
|
540
|
-
*
|
|
820
|
+
* Computes the timeout to pass to `ctx.setAlarm`, reconciling pending queue work with the earliest
|
|
821
|
+
* pending feed alarm. Returns `null` when no alarm should be scheduled (process can go idle).
|
|
541
822
|
*/
|
|
542
823
|
var computeAlarmDelay = ({ hasPendingWork, wakeAt, now }) => {
|
|
543
824
|
if (hasPendingWork) return 0;
|
|
@@ -545,94 +826,17 @@ var computeAlarmDelay = ({ hasPendingWork, wakeAt, now }) => {
|
|
|
545
826
|
return null;
|
|
546
827
|
};
|
|
547
828
|
/**
|
|
548
|
-
* Tracks the next agent-scheduled self-wake and keeps it in sync with the process alarm
|
|
549
|
-
* (`ctx.setAlarm`). The agent sets alarms via the alarm blueprint, which dispatches to this manager
|
|
550
|
-
* through the `HarnessControl` RPC surface; the process reconciles the underlying single-shot alarm
|
|
551
|
-
* timer to fire at the earliest of pending work or the self-wake.
|
|
552
|
-
*/
|
|
553
|
-
var AlarmManager = class {
|
|
554
|
-
#storageService;
|
|
555
|
-
#setAlarm;
|
|
556
|
-
#now;
|
|
557
|
-
#wakeAt = null;
|
|
558
|
-
#message = null;
|
|
559
|
-
constructor({ storageService, setAlarm, now = () => Date.now() }) {
|
|
560
|
-
this.#storageService = storageService;
|
|
561
|
-
this.#setAlarm = setAlarm;
|
|
562
|
-
this.#now = now;
|
|
563
|
-
}
|
|
564
|
-
/** Currently scheduled self-wake timestamp (ms), or `null` when none is set. */
|
|
565
|
-
get wakeAt() {
|
|
566
|
-
return this.#wakeAt;
|
|
567
|
-
}
|
|
568
|
-
/** Reminder message delivered when the current self-wake fires, or `null` when none is set. */
|
|
569
|
-
get message() {
|
|
570
|
-
return this.#message;
|
|
571
|
-
}
|
|
572
|
-
now() {
|
|
573
|
-
return this.#now();
|
|
574
|
-
}
|
|
575
|
-
/** Restores the persisted alarm state. */
|
|
576
|
-
load() {
|
|
577
|
-
return Effect.gen({ self: this }, function* () {
|
|
578
|
-
const persisted = yield* AgentAlarmCell.get;
|
|
579
|
-
this.#wakeAt = persisted?.wakeAt ?? null;
|
|
580
|
-
this.#message = persisted?.message ?? null;
|
|
581
|
-
}).pipe(Effect.provideService(StorageService.StorageService, this.#storageService));
|
|
582
|
-
}
|
|
583
|
-
/** Records a new self-wake target (with an optional reminder message) and persists it. */
|
|
584
|
-
setWakeAt(wakeAt, message = null) {
|
|
585
|
-
return Effect.gen({ self: this }, function* () {
|
|
586
|
-
this.#wakeAt = wakeAt;
|
|
587
|
-
this.#message = message;
|
|
588
|
-
yield* AgentAlarmCell.set({
|
|
589
|
-
wakeAt,
|
|
590
|
-
message
|
|
591
|
-
});
|
|
592
|
-
}).pipe(Effect.provideService(StorageService.StorageService, this.#storageService));
|
|
593
|
-
}
|
|
594
|
-
/**
|
|
595
|
-
* Clears the self-wake alarm if it is due, returning the timestamp it was scheduled for and the
|
|
596
|
-
* reminder message it carried (or `null` if no alarm was due).
|
|
597
|
-
*/
|
|
598
|
-
takeFiredAlarm() {
|
|
599
|
-
return Effect.gen({ self: this }, function* () {
|
|
600
|
-
if (this.#wakeAt == null || this.#now() < this.#wakeAt) return null;
|
|
601
|
-
const firedAt = this.#wakeAt;
|
|
602
|
-
const message = this.#message;
|
|
603
|
-
this.#wakeAt = null;
|
|
604
|
-
this.#message = null;
|
|
605
|
-
yield* AgentAlarmCell.set(null);
|
|
606
|
-
return {
|
|
607
|
-
firedAt,
|
|
608
|
-
message
|
|
609
|
-
};
|
|
610
|
-
}).pipe(Effect.provideService(StorageService.StorageService, this.#storageService));
|
|
611
|
-
}
|
|
612
|
-
/**
|
|
613
|
-
* Reconciles the process alarm timer with pending work and the tracked self-wake, scheduling the
|
|
614
|
-
* earliest of the two. Does nothing when there is neither pending work nor a self-wake.
|
|
615
|
-
*/
|
|
616
|
-
reconcile(hasPendingWork) {
|
|
617
|
-
const delay = computeAlarmDelay({
|
|
618
|
-
hasPendingWork,
|
|
619
|
-
wakeAt: this.#wakeAt,
|
|
620
|
-
now: this.#now()
|
|
621
|
-
});
|
|
622
|
-
if (delay != null) this.#setAlarm(delay);
|
|
623
|
-
}
|
|
624
|
-
};
|
|
625
|
-
/**
|
|
626
829
|
* Prompt delivered to the agent when a self-scheduled alarm fires. When the alarm carried a
|
|
627
830
|
* reminder message it is surfaced verbatim, otherwise a generic continuation prompt is used.
|
|
831
|
+
* Exported so the prompt shape stays pinned by tests without spawning an agent.
|
|
628
832
|
*/
|
|
629
|
-
var wakeUpPrompt = (firedAt, message) =>
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
833
|
+
var wakeUpPrompt = (firedAt, message, budget) => {
|
|
834
|
+
return [
|
|
835
|
+
`Scheduled alarm fired (it was set for ${new Date(firedAt).toISOString()}).`,
|
|
836
|
+
message ?? "Continue with whatever you intended to do when you scheduled this wake-up.",
|
|
837
|
+
budget == null ? void 0 : budget.wake >= budget.max ? `This is self-wake ${budget.wake} of ${budget.max}: further alarms will not wake you until the user writes again, so finish or report where you are now.` : `This is self-wake ${budget.wake} of ${budget.max} before the user must write again.`
|
|
838
|
+
].filter((line) => line != null).join("\n");
|
|
839
|
+
};
|
|
636
840
|
var ToolExecutionService = ({ enableBackgrounding, backgroundThreshold = Duration.seconds(1), toolCallManager, feed }) => Layer.unwrap(Effect.gen(function* () {
|
|
637
841
|
const operationInvoker = yield* ProcessManager.ProcessOperationInvoker.Service;
|
|
638
842
|
return makeToolExecutionService({ invoke: (tool, input) => Effect.gen(function* () {
|
|
@@ -643,14 +847,16 @@ var ToolExecutionService = ({ enableBackgrounding, backgroundThreshold = Duratio
|
|
|
643
847
|
}, {
|
|
644
848
|
"~LogMeta": "~LogMeta",
|
|
645
849
|
F: __dxlog_file$1,
|
|
646
|
-
L:
|
|
850
|
+
L: 1024,
|
|
647
851
|
S: this
|
|
648
852
|
});
|
|
649
853
|
const fiber = yield* operationInvoker.invokeFiber(operationDef, input, {
|
|
650
854
|
environment: { conversation: Obj.getURI(feed) },
|
|
651
855
|
traceMeta: { conversation: Ref.make(feed) }
|
|
652
856
|
});
|
|
857
|
+
markWork("tool.spawned");
|
|
653
858
|
yield* toolCallManager.beginCall(fiber.pid);
|
|
859
|
+
markWork("tool.call-recorded");
|
|
654
860
|
log("invoked operation", {
|
|
655
861
|
operationDef,
|
|
656
862
|
input,
|
|
@@ -658,15 +864,16 @@ var ToolExecutionService = ({ enableBackgrounding, backgroundThreshold = Duratio
|
|
|
658
864
|
}, {
|
|
659
865
|
"~LogMeta": "~LogMeta",
|
|
660
866
|
F: __dxlog_file$1,
|
|
661
|
-
L:
|
|
867
|
+
L: 1036,
|
|
662
868
|
S: this
|
|
663
869
|
});
|
|
664
870
|
const awaitWithReport = fiber.await.pipe(Effect.tap(() => toolCallManager.markAsReported(fiber.pid)));
|
|
665
871
|
const result = enableBackgrounding ? yield* awaitWithReport.pipe(Effect.timeout(backgroundThreshold), Effect.catchTag("TimeoutError", () => Effect.succeed(Exit.succeed(toolIsRunningInBackgroundResponse(fiber.pid))))) : yield* awaitWithReport;
|
|
872
|
+
markWork("tool.settled");
|
|
666
873
|
log("result", { result }, {
|
|
667
874
|
"~LogMeta": "~LogMeta",
|
|
668
875
|
F: __dxlog_file$1,
|
|
669
|
-
L:
|
|
876
|
+
L: 1048,
|
|
670
877
|
S: this
|
|
671
878
|
});
|
|
672
879
|
return yield* result;
|
|
@@ -714,9 +921,11 @@ var AgentService_exports = /* @__PURE__ */ __exportAll({
|
|
|
714
921
|
layer: () => layer
|
|
715
922
|
});
|
|
716
923
|
var __dxlog_file = "/__w/dxos/dxos/packages/core/compute/agent-runtime/src/agent-service/AgentService.ts";
|
|
717
|
-
|
|
924
|
+
/**
|
|
925
|
+
* Creates a session on a fresh feed and chat, with `opts.skills` and `opts.context` bound to it.
|
|
926
|
+
*/
|
|
718
927
|
var createSession = Effect.fn("createSession")(function* (opts) {
|
|
719
|
-
const skills =
|
|
928
|
+
const skills = (opts?.skills ?? []).map(Skill.makeRef);
|
|
720
929
|
const feed = yield* Database.add(Feed.make());
|
|
721
930
|
const runtime = yield* Effect.context();
|
|
722
931
|
const binder = yield* EffectEx.acquireReleaseResource(() => new AiContext.Binder({
|
|
@@ -727,86 +936,129 @@ var createSession = Effect.fn("createSession")(function* (opts) {
|
|
|
727
936
|
skills,
|
|
728
937
|
objects: opts?.context ?? []
|
|
729
938
|
}));
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
});
|
|
939
|
+
const chat = yield* Database.add(Chat.make({
|
|
940
|
+
feed: Ref.make(feed),
|
|
941
|
+
...opts?.model ? { session: { model: opts.model } } : {}
|
|
942
|
+
}));
|
|
943
|
+
return yield* AgentService.getSession(chat, { provider: opts?.provider });
|
|
734
944
|
}, Effect.scoped);
|
|
735
|
-
|
|
736
|
-
|
|
945
|
+
/**
|
|
946
|
+
* The `AgentService` layer.
|
|
947
|
+
*
|
|
948
|
+
* Requires {@link Process.ManagerService}, which runs a session locally or, for `location: 'edge'`,
|
|
949
|
+
* on the remote runtime. A host without EDGE satisfies its remote half with
|
|
950
|
+
* `RemoteProcessManager.layerNoop`, so an edge session fails at spawn rather than silently running
|
|
951
|
+
* locally.
|
|
952
|
+
*/
|
|
953
|
+
var layer = (opts) => Layer.effect(AgentService.AgentService, Effect.gen(function* () {
|
|
954
|
+
const processManager = yield* Process.ManagerService;
|
|
955
|
+
const remoteSpaces = /* @__PURE__ */ new Set();
|
|
956
|
+
/**
|
|
957
|
+
* Where a session's agent runs. `edge` needs the space, since one remote runtime spans them,
|
|
958
|
+
* and a chat with no space cannot name where its agent would run.
|
|
959
|
+
*/
|
|
960
|
+
const locationFor = (location, spaceId) => {
|
|
961
|
+
if (location !== "edge") return { kind: "local" };
|
|
962
|
+
if (!spaceId) throw new Error("Agent requested on edge, but its conversation has no space.");
|
|
963
|
+
remoteSpaces.add(spaceId);
|
|
964
|
+
return {
|
|
965
|
+
kind: "edge",
|
|
966
|
+
space: spaceId
|
|
967
|
+
};
|
|
968
|
+
};
|
|
737
969
|
const sessionCache = /* @__PURE__ */ new Map();
|
|
738
|
-
const
|
|
970
|
+
const sessionLocks = /* @__PURE__ */ new Map();
|
|
971
|
+
const lockFor = (chatId) => {
|
|
972
|
+
const existing = sessionLocks.get(chatId);
|
|
973
|
+
if (existing) return existing;
|
|
974
|
+
const lock = Effect.runSync(Semaphore.make(1));
|
|
975
|
+
sessionLocks.set(chatId, lock);
|
|
976
|
+
return lock;
|
|
977
|
+
};
|
|
978
|
+
const makeExecutable = (provider) => AgentProcess({
|
|
739
979
|
systemPrompt: opts?.systemPrompt,
|
|
740
980
|
makeTurnProducer: opts?.makeTurnProducer,
|
|
741
|
-
|
|
981
|
+
defaultModel: opts?.defaultModel,
|
|
742
982
|
provider: provider ?? opts?.provider,
|
|
743
|
-
getMcpServers: opts?.getMcpServers,
|
|
744
983
|
enableToolBackgrounding: opts?.enableToolBackgrounding,
|
|
745
984
|
delegationStrategy: opts?.delegationStrategy
|
|
746
985
|
});
|
|
747
|
-
|
|
748
|
-
getSession: (
|
|
749
|
-
const model = options?.model ?? opts?.model;
|
|
986
|
+
const service = {
|
|
987
|
+
getSession: (chat, options) => Effect.suspend(() => lockFor(chat.id).withPermits(1)(Effect.gen(function* () {
|
|
750
988
|
const provider = options?.provider ?? opts?.provider;
|
|
751
|
-
const
|
|
752
|
-
const
|
|
989
|
+
const model = chat.session?.model;
|
|
990
|
+
const instructions = chat.instructions?.uri;
|
|
991
|
+
const location = options?.location ?? "local";
|
|
992
|
+
const cached = sessionCache.get(chat.id);
|
|
753
993
|
if (cached) {
|
|
754
|
-
if (cached.model === model && cached.provider === provider && cached.instructions === instructions && !
|
|
755
|
-
if (!
|
|
756
|
-
sessionCache.delete(
|
|
994
|
+
if (cached.model === model && cached.provider === provider && cached.instructions === instructions && cached.location === location && !Process.isTerminal(cached.handle.status.state)) return cached.session;
|
|
995
|
+
if (!Process.isTerminal(cached.handle.status.state)) yield* cached.handle.terminate();
|
|
996
|
+
sessionCache.delete(chat.id);
|
|
757
997
|
}
|
|
758
|
-
const
|
|
998
|
+
const feed = yield* Database.load(chat.feed).pipe(Effect.orDie);
|
|
999
|
+
const target = Obj.getURI(chat);
|
|
759
1000
|
const parsedEchoUri = EID.tryParse(target);
|
|
760
1001
|
const spaceId = parsedEchoUri ? EID.getSpaceId(parsedEchoUri) : void 0;
|
|
761
|
-
const
|
|
762
|
-
|
|
1002
|
+
const processLocation = locationFor(options?.location, spaceId);
|
|
1003
|
+
const executable = makeExecutable(provider);
|
|
1004
|
+
let activeProcess = (yield* processManager.handles({
|
|
763
1005
|
target,
|
|
764
|
-
key: executable.key
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
if (Option.getOrUndefined(Annotation.getDictionary(activeProcess.params.annotations, Process.InstructionsAnnotation)) !== instructions) {
|
|
768
|
-
yield* activeProcess.terminate();
|
|
769
|
-
activeProcess = void 0;
|
|
770
|
-
}
|
|
771
|
-
}
|
|
1006
|
+
key: executable.key,
|
|
1007
|
+
location: processLocation
|
|
1008
|
+
})).find((process) => !Process.isTerminal(process.status.state));
|
|
772
1009
|
let handle;
|
|
773
|
-
if (activeProcess)
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
} else handle = yield* processManager.spawn(executable, {
|
|
1010
|
+
if (activeProcess) handle = yield* activeProcess.hydrate(executable);
|
|
1011
|
+
else handle = yield* processManager.spawn(executable, {
|
|
1012
|
+
location: processLocation,
|
|
777
1013
|
name: "Agent",
|
|
778
1014
|
target,
|
|
779
1015
|
annotations: Annotation.buildDictionary((dictionary) => {
|
|
780
1016
|
Annotation.setDictionary(dictionary, Process.HarnessHostAnnotation, true);
|
|
781
|
-
if (options?.instructions) Annotation.setDictionary(dictionary, Process.InstructionsAnnotation, options.instructions.uri);
|
|
782
1017
|
}),
|
|
783
1018
|
environment: {
|
|
784
1019
|
...spaceId !== void 0 ? { space: spaceId } : {},
|
|
785
|
-
conversation:
|
|
1020
|
+
conversation: Obj.getURI(feed)
|
|
786
1021
|
},
|
|
787
1022
|
traceMeta: { conversation: Ref.make(feed) }
|
|
788
1023
|
});
|
|
789
1024
|
const releaseSession = () => {
|
|
790
|
-
sessionCache.delete(
|
|
1025
|
+
sessionCache.delete(chat.id);
|
|
791
1026
|
};
|
|
792
|
-
const
|
|
793
|
-
|
|
1027
|
+
const databaseContext = yield* Effect.context();
|
|
1028
|
+
const isFinished = Effect.suspend(() => Process.isTerminal(handle.status.state) || location !== "edge" ? Effect.succeed(Process.isTerminal(handle.status.state)) : processManager.handles({
|
|
1029
|
+
target,
|
|
1030
|
+
key: executable.key,
|
|
1031
|
+
location: processLocation
|
|
1032
|
+
}).pipe(Effect.map((live) => {
|
|
1033
|
+
const current = live.find((process) => process.pid === handle.pid);
|
|
1034
|
+
return current === void 0 || Process.isTerminal(current.status.state);
|
|
1035
|
+
}), Effect.orElseSucceed(() => false)));
|
|
1036
|
+
const resubmit = (prompt, submitOptions) => Effect.sync(releaseSession).pipe(Effect.andThen(service.getSession(chat, options)), Effect.flatMap((next) => next.submitPrompt(prompt, submitOptions)), Effect.provide(databaseContext));
|
|
1037
|
+
const session = makeSession(handle, chat, feed, releaseSession, isFinished, resubmit);
|
|
1038
|
+
sessionCache.set(chat.id, {
|
|
794
1039
|
model,
|
|
795
1040
|
provider,
|
|
796
1041
|
instructions,
|
|
1042
|
+
location,
|
|
797
1043
|
handle,
|
|
798
1044
|
session
|
|
799
1045
|
});
|
|
800
1046
|
return session;
|
|
801
|
-
}),
|
|
1047
|
+
}))),
|
|
802
1048
|
hydrate: Effect.fnUntraced(function* () {
|
|
803
1049
|
sessionCache.clear();
|
|
804
1050
|
const executable = makeExecutable();
|
|
805
|
-
const agents = yield* processManager.
|
|
1051
|
+
const agents = [...yield* processManager.handles({ key: AGENT_PROCESS_KEY }), ...(yield* Effect.forEach([...remoteSpaces], (space) => processManager.handles({
|
|
1052
|
+
key: AGENT_PROCESS_KEY,
|
|
1053
|
+
location: {
|
|
1054
|
+
kind: "edge",
|
|
1055
|
+
space
|
|
1056
|
+
}
|
|
1057
|
+
}))).flat()];
|
|
806
1058
|
log("agent hydrate", { count: agents.length }, {
|
|
807
1059
|
"~LogMeta": "~LogMeta",
|
|
808
1060
|
F: __dxlog_file,
|
|
809
|
-
L:
|
|
1061
|
+
L: 207,
|
|
810
1062
|
S: this
|
|
811
1063
|
});
|
|
812
1064
|
for (const agent of agents) yield* agent.hydrate(executable).pipe(Effect.catchCause((cause) => Effect.sync(() => log.warn("agent hydrate skipped", {
|
|
@@ -815,13 +1067,15 @@ var layer = (opts) => Layer.effect(AgentService, Effect.gen(function* () {
|
|
|
815
1067
|
}, {
|
|
816
1068
|
"~LogMeta": "~LogMeta",
|
|
817
1069
|
F: __dxlog_file,
|
|
818
|
-
L:
|
|
1070
|
+
L: 213,
|
|
819
1071
|
S: this
|
|
820
1072
|
}))));
|
|
821
1073
|
})
|
|
822
1074
|
};
|
|
1075
|
+
return service;
|
|
823
1076
|
}));
|
|
824
|
-
var makeSession = (process, feed, releaseSession) => ({
|
|
1077
|
+
var makeSession = (process, chat, feed, releaseSession, isFinished, resubmit) => ({
|
|
1078
|
+
chat,
|
|
825
1079
|
feed,
|
|
826
1080
|
getContext: () => Effect.gen(function* () {
|
|
827
1081
|
const runtime = yield* Effect.context();
|
|
@@ -841,12 +1095,16 @@ var makeSession = (process, feed, releaseSession) => ({
|
|
|
841
1095
|
objects: context
|
|
842
1096
|
}));
|
|
843
1097
|
}).pipe(Effect.scoped),
|
|
844
|
-
submitPrompt: (prompt) => process.submitInput(
|
|
1098
|
+
submitPrompt: (prompt, options) => Effect.flatMap(isFinished, (finished) => finished ? resubmit(prompt, options) : process.submitInput(options?.sender ? {
|
|
1099
|
+
prompt,
|
|
1100
|
+
sender: options.sender
|
|
1101
|
+
} : prompt)),
|
|
1102
|
+
running: Atom.make((get) => get(process.statusAtom).state === Process.State.RUNNING || get(process.statusAtom).state === Process.State.HYBERNATING),
|
|
845
1103
|
waitForCompletion: () => process.runUntilSettled(),
|
|
846
1104
|
terminate: () => process.terminate().pipe(Effect.tap(() => Effect.sync(releaseSession))),
|
|
847
1105
|
subscribeEphemeral: () => process.subscribeEphemeral()
|
|
848
1106
|
});
|
|
849
1107
|
//#endregion
|
|
850
|
-
export {
|
|
1108
|
+
export { AgentProcess as a, __exportAll as c, AgentInput as i, layer as n, makeInputMessage as o, AGENT_PROCESS_KEY as r, makeAiSessionTurnProducer as s, AgentService_exports as t };
|
|
851
1109
|
|
|
852
1110
|
//# sourceMappingURL=chunk-AgentService.mjs.map
|