@agent-native/core 0.78.1 → 0.78.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/core/CHANGELOG.md +6 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +269 -237
- package/corpus/core/src/server/request-context.ts +8 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +13 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +50 -29
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +6 -6
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +2 -2
- package/dist/server/request-context.d.ts +8 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.78.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8a6522a: fix(agent): make the durable background-function worker reliably claim its run for heavy apps (analytics). Two changes: (1) the per-run context now carries `isBackgroundWorker`, set before the system prompt is built, so template `extraContext`/prompt builders can skip heavy, hang-prone enrichment in the worker — the analytics data-dictionary read+render (which ran eagerly during prompt construction, before any pre-send timeout could arm) is now skipped in the worker, while the foreground keeps the full dictionary; (2) the pre-send context cap now takes thunks instead of eagerly-created promises, so each step runs inside an already-armed timeout (an eager promise could start and stall the event loop before the cap wrapped it) and a stalled step is recorded as `presend_timeout:<label>` for attribution. Foreground behavior is unchanged.
|
|
8
|
+
|
|
3
9
|
## 0.78.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.78.
|
|
3
|
+
"version": "0.78.2",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -3978,6 +3978,11 @@ export function createProductionAgentHandler(
|
|
|
3978
3978
|
if (requestRunCtx) {
|
|
3979
3979
|
requestRunCtx.browserTabId = requestBrowserTabId;
|
|
3980
3980
|
requestRunCtx.chatScope = requestChatScope;
|
|
3981
|
+
// Let template extraContext / system-prompt builders detect the durable
|
|
3982
|
+
// background worker so they can skip heavy hang-prone enrichment (e.g. the
|
|
3983
|
+
// analytics data-dictionary read) that otherwise stalls the worker before
|
|
3984
|
+
// it claims its run. Set early — before the system-prompt build runs.
|
|
3985
|
+
requestRunCtx.isBackgroundWorker = isBackgroundWorker;
|
|
3981
3986
|
}
|
|
3982
3987
|
const requestMode: AgentExecutionMode =
|
|
3983
3988
|
body.mode === "plan" ? "plan" : "act";
|
|
@@ -4212,268 +4217,288 @@ export function createProductionAgentHandler(
|
|
|
4212
4217
|
// Run all independent pre-send steps in parallel. Each of these hits
|
|
4213
4218
|
// the DB or invokes an action; running them sequentially was the
|
|
4214
4219
|
// single biggest contributor to pre-LLM latency.
|
|
4215
|
-
const
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
+
const enrichedMessageThunk = () =>
|
|
4221
|
+
enrichMessage(requestMessage, references);
|
|
4222
|
+
const loopSettingsThunk = () =>
|
|
4223
|
+
readAgentLoopSettings({
|
|
4224
|
+
userEmail: ownerEmail ?? getRequestUserEmail() ?? null,
|
|
4225
|
+
orgId: getRequestOrgId() ?? null,
|
|
4226
|
+
}).catch(() => readAgentLoopSettings({}));
|
|
4220
4227
|
|
|
4221
4228
|
let systemPromptError: any = null;
|
|
4222
|
-
const
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4229
|
+
const systemPromptThunk = (): Promise<string> =>
|
|
4230
|
+
(async (): Promise<string> => {
|
|
4231
|
+
const sysPromptStart = Date.now();
|
|
4232
|
+
try {
|
|
4233
|
+
return typeof options.systemPrompt === "function"
|
|
4234
|
+
? await options.systemPrompt(event)
|
|
4235
|
+
: options.systemPrompt;
|
|
4236
|
+
} catch (error) {
|
|
4237
|
+
systemPromptError = error;
|
|
4238
|
+
return "";
|
|
4239
|
+
} finally {
|
|
4240
|
+
setupMarks.sysPromptMs = Date.now() - sysPromptStart;
|
|
4241
|
+
}
|
|
4242
|
+
})();
|
|
4235
4243
|
|
|
4236
|
-
const
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4244
|
+
const screenContextThunk = (): Promise<string> =>
|
|
4245
|
+
(async (): Promise<string> => {
|
|
4246
|
+
const screenStart = Date.now();
|
|
4247
|
+
try {
|
|
4248
|
+
const viewScreenAction = resolvedActions["view-screen"];
|
|
4249
|
+
if (viewScreenAction) {
|
|
4250
|
+
const result = await viewScreenAction.run(
|
|
4251
|
+
{},
|
|
4252
|
+
{
|
|
4253
|
+
userEmail: getRequestUserEmail(),
|
|
4254
|
+
orgId: getRequestOrgId() ?? null,
|
|
4255
|
+
caller: "tool",
|
|
4256
|
+
},
|
|
4257
|
+
);
|
|
4258
|
+
if (result && result !== "(no output)") {
|
|
4259
|
+
const screenText =
|
|
4260
|
+
typeof result === "string"
|
|
4261
|
+
? result
|
|
4262
|
+
: JSON.stringify(result, null, 2);
|
|
4263
|
+
return `\n\n<current-screen>\n${capScreenContext(screenText)}\n</current-screen>`;
|
|
4264
|
+
}
|
|
4265
|
+
} else {
|
|
4266
|
+
const navigation = await readAppStateForBrowserTab(
|
|
4267
|
+
"navigation",
|
|
4268
|
+
requestBrowserTabId,
|
|
4269
|
+
);
|
|
4270
|
+
if (navigation) {
|
|
4271
|
+
return `\n\n<current-screen>\n${capScreenContext(JSON.stringify(navigation, null, 2))}\n</current-screen>`;
|
|
4272
|
+
}
|
|
4263
4273
|
}
|
|
4274
|
+
} catch {
|
|
4275
|
+
// DB not ready or no navigation state — skip silently
|
|
4276
|
+
} finally {
|
|
4277
|
+
setupMarks.screenMs = Date.now() - screenStart;
|
|
4264
4278
|
}
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
} finally {
|
|
4268
|
-
setupMarks.screenMs = Date.now() - screenStart;
|
|
4269
|
-
}
|
|
4270
|
-
return "";
|
|
4271
|
-
})();
|
|
4279
|
+
return "";
|
|
4280
|
+
})();
|
|
4272
4281
|
|
|
4273
|
-
const
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4282
|
+
const urlContextThunk = (): Promise<string> =>
|
|
4283
|
+
(async (): Promise<string> => {
|
|
4284
|
+
try {
|
|
4285
|
+
const url = (await readAppStateForBrowserTab(
|
|
4286
|
+
"__url__",
|
|
4287
|
+
requestBrowserTabId,
|
|
4288
|
+
)) as {
|
|
4289
|
+
pathname?: string;
|
|
4290
|
+
search?: string;
|
|
4291
|
+
hash?: string;
|
|
4292
|
+
searchParams?: Record<string, string>;
|
|
4293
|
+
} | null;
|
|
4294
|
+
if (url && (url.pathname || url.search || url.hash)) {
|
|
4295
|
+
const lines: string[] = [];
|
|
4296
|
+
if (url.pathname) lines.push(`pathname: ${url.pathname}`);
|
|
4297
|
+
const extensionId = url.pathname
|
|
4298
|
+
? extensionIdFromPathname(url.pathname)
|
|
4299
|
+
: null;
|
|
4300
|
+
if (extensionId) lines.push(`extensionId: ${extensionId}`);
|
|
4301
|
+
if (url.search) lines.push(`search: ${url.search}`);
|
|
4302
|
+
if (url.hash) lines.push(`hash: ${url.hash}`);
|
|
4303
|
+
if (url.searchParams && Object.keys(url.searchParams).length > 0) {
|
|
4304
|
+
lines.push("searchParams:");
|
|
4305
|
+
for (const [k, v] of Object.entries(url.searchParams)) {
|
|
4306
|
+
lines.push(` ${k}: ${v}`);
|
|
4307
|
+
}
|
|
4297
4308
|
}
|
|
4309
|
+
return `\n\n<current-url>\n${lines.join("\n")}\n</current-url>`;
|
|
4298
4310
|
}
|
|
4299
|
-
|
|
4311
|
+
} catch {
|
|
4312
|
+
// DB not ready — skip silently
|
|
4300
4313
|
}
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
}
|
|
4304
|
-
return "";
|
|
4305
|
-
})();
|
|
4314
|
+
return "";
|
|
4315
|
+
})();
|
|
4306
4316
|
|
|
4307
4317
|
// Selection context: written by the client when the user presses Cmd+I
|
|
4308
4318
|
// with text selected on the page. Treat anything older than 5 minutes
|
|
4309
4319
|
// as stale and ignore it.
|
|
4310
4320
|
const SELECTION_TTL_MS = 5 * 60 * 1000;
|
|
4311
|
-
const
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4321
|
+
const selectionContextThunk = (): Promise<string> =>
|
|
4322
|
+
(async (): Promise<string> => {
|
|
4323
|
+
try {
|
|
4324
|
+
const sel = (await readAppState("pending-selection-context")) as {
|
|
4325
|
+
text?: string;
|
|
4326
|
+
capturedAt?: number;
|
|
4327
|
+
} | null;
|
|
4328
|
+
if (!sel?.text) return "";
|
|
4329
|
+
const capturedAt =
|
|
4330
|
+
typeof sel.capturedAt === "number" ? sel.capturedAt : 0;
|
|
4331
|
+
if (Date.now() - capturedAt > SELECTION_TTL_MS) return "";
|
|
4332
|
+
return (
|
|
4333
|
+
`\n\nThe user has selected the following text and pressed Cmd+I to focus the agent. ` +
|
|
4334
|
+
`Treat this as the immediate context to act on:\n` +
|
|
4335
|
+
`<selection>\n${capSelectionContext(sel.text)}\n</selection>`
|
|
4336
|
+
);
|
|
4337
|
+
} catch {
|
|
4338
|
+
// DB not ready — skip silently
|
|
4339
|
+
}
|
|
4340
|
+
return "";
|
|
4341
|
+
})();
|
|
4331
4342
|
|
|
4332
4343
|
// On the first message of a conversation, inject workspace inventory
|
|
4333
4344
|
// so the agent knows what files, skills, jobs, and custom agents exist.
|
|
4334
4345
|
// Templates can opt out via `skipFilesContext: true` when the inventory
|
|
4335
4346
|
// is unrelated to the app's job (e.g. a voice-first macro tracker).
|
|
4336
|
-
const
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4347
|
+
const filesContextThunk = (): Promise<string> =>
|
|
4348
|
+
(async (): Promise<string> => {
|
|
4349
|
+
let filesContext = "";
|
|
4350
|
+
if (options.skipFilesContext) return filesContext;
|
|
4351
|
+
if (history.length === 0) {
|
|
4352
|
+
try {
|
|
4353
|
+
const {
|
|
4354
|
+
resourceListAccessible,
|
|
4355
|
+
SHARED_OWNER,
|
|
4356
|
+
WORKSPACE_OWNER,
|
|
4357
|
+
resourceGet,
|
|
4358
|
+
} = await import("../resources/store.js");
|
|
4359
|
+
const {
|
|
4360
|
+
getResourceKind,
|
|
4361
|
+
parseCustomAgentProfile,
|
|
4362
|
+
parseRemoteAgentManifest,
|
|
4363
|
+
parseSkillMetadata,
|
|
4364
|
+
} = await import("../resources/metadata.js");
|
|
4365
|
+
const ownerEmail = getRequestUserEmail();
|
|
4366
|
+
const orgId = getRequestOrgId();
|
|
4367
|
+
if (!ownerEmail) throw new Error("no authenticated user");
|
|
4368
|
+
const allResources = await resourceListAccessible(
|
|
4369
|
+
ownerEmail,
|
|
4370
|
+
undefined,
|
|
4371
|
+
{ userEmail: ownerEmail, orgId },
|
|
4372
|
+
);
|
|
4361
4373
|
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4374
|
+
if (allResources.length > 0) {
|
|
4375
|
+
const fileLines: string[] = [];
|
|
4376
|
+
const skillLines: string[] = [];
|
|
4377
|
+
const agentLines: string[] = [];
|
|
4378
|
+
const jobLines: string[] = [];
|
|
4379
|
+
for (const r of allResources) {
|
|
4380
|
+
const scope =
|
|
4381
|
+
r.owner === WORKSPACE_OWNER
|
|
4382
|
+
? "workspace"
|
|
4383
|
+
: r.owner === SHARED_OWNER
|
|
4384
|
+
? "shared"
|
|
4385
|
+
: "personal";
|
|
4386
|
+
const kind = getResourceKind(r.path);
|
|
4387
|
+
if (kind === "file") {
|
|
4388
|
+
fileLines.push(` ${r.path} (${scope})`);
|
|
4389
|
+
continue;
|
|
4390
|
+
}
|
|
4379
4391
|
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4392
|
+
if (kind === "job") {
|
|
4393
|
+
jobLines.push(` ${r.path} (${scope})`);
|
|
4394
|
+
continue;
|
|
4395
|
+
}
|
|
4384
4396
|
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4397
|
+
if (
|
|
4398
|
+
kind === "skill" ||
|
|
4399
|
+
kind === "agent" ||
|
|
4400
|
+
kind === "remote-agent"
|
|
4401
|
+
) {
|
|
4402
|
+
const full = await resourceGet(r.id, {
|
|
4403
|
+
userEmail: ownerEmail,
|
|
4404
|
+
orgId,
|
|
4405
|
+
});
|
|
4406
|
+
if (!full) continue;
|
|
4407
|
+
if (kind === "skill") {
|
|
4408
|
+
const skill = parseSkillMetadata(full.content, r.path);
|
|
4409
|
+
skillLines.push(
|
|
4410
|
+
` ${skill?.name || r.path} — ${compactInventoryDescription(skill?.description || r.path)} (${scope}, ${r.path})`,
|
|
4411
|
+
);
|
|
4412
|
+
} else if (kind === "agent") {
|
|
4413
|
+
const agent = parseCustomAgentProfile(full.content, r.path);
|
|
4414
|
+
agentLines.push(
|
|
4415
|
+
` ${agent?.name || r.path} — ${compactInventoryDescription(agent?.description || "Custom workspace agent")} (${scope}, ${r.path}${agent?.model ? `, model: ${agent.model}` : ""})`,
|
|
4416
|
+
);
|
|
4417
|
+
} else {
|
|
4418
|
+
const agent = parseRemoteAgentManifest(
|
|
4419
|
+
full.content,
|
|
4420
|
+
r.path,
|
|
4421
|
+
);
|
|
4422
|
+
agentLines.push(
|
|
4423
|
+
` ${agent?.name || r.path} — ${compactInventoryDescription(agent?.description || "Connected A2A agent")} (${scope}, remote via ${r.path})`,
|
|
4424
|
+
);
|
|
4425
|
+
}
|
|
4410
4426
|
}
|
|
4411
4427
|
}
|
|
4428
|
+
const blocks: string[] = [];
|
|
4429
|
+
if (fileLines.length > 0) {
|
|
4430
|
+
const lines = limitInventoryLines(fileLines, "files");
|
|
4431
|
+
blocks.push(
|
|
4432
|
+
`<available-files>\nFiles in the workspace:\n${lines.join("\n")}\n\nTo read a resource file's contents, use the resources tool with action "read" and the file path.\n</available-files>`,
|
|
4433
|
+
);
|
|
4434
|
+
}
|
|
4435
|
+
if (skillLines.length > 0) {
|
|
4436
|
+
const lines = limitInventoryLines(skillLines, "skills");
|
|
4437
|
+
blocks.push(
|
|
4438
|
+
`<available-skills>\nSkills in the workspace:\n${lines.join("\n")}\n\nBefore using a matching workspace skill, read its path with the resources tool using action "read"; slash-selected skills are inlined automatically when available.\n</available-skills>`,
|
|
4439
|
+
);
|
|
4440
|
+
}
|
|
4441
|
+
if (agentLines.length > 0) {
|
|
4442
|
+
const lines = limitInventoryLines(agentLines, "agents");
|
|
4443
|
+
blocks.push(
|
|
4444
|
+
`<available-agents>\nCustom and connected agents in the workspace:\n${lines.join("\n")}\n\nCustom agents under agents/*.md can be mentioned or used via agent-teams (action: "spawn") with the agent parameter.\n</available-agents>`,
|
|
4445
|
+
);
|
|
4446
|
+
}
|
|
4447
|
+
if (jobLines.length > 0) {
|
|
4448
|
+
const lines = limitInventoryLines(jobLines, "jobs");
|
|
4449
|
+
blocks.push(
|
|
4450
|
+
`<available-jobs>\nScheduled tasks in the workspace:\n${lines.join("\n")}\n</available-jobs>`,
|
|
4451
|
+
);
|
|
4452
|
+
}
|
|
4453
|
+
filesContext =
|
|
4454
|
+
blocks.length > 0 ? `\n\n${blocks.join("\n\n")}` : "";
|
|
4412
4455
|
}
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
const lines = limitInventoryLines(fileLines, "files");
|
|
4416
|
-
blocks.push(
|
|
4417
|
-
`<available-files>\nFiles in the workspace:\n${lines.join("\n")}\n\nTo read a resource file's contents, use the resources tool with action "read" and the file path.\n</available-files>`,
|
|
4418
|
-
);
|
|
4419
|
-
}
|
|
4420
|
-
if (skillLines.length > 0) {
|
|
4421
|
-
const lines = limitInventoryLines(skillLines, "skills");
|
|
4422
|
-
blocks.push(
|
|
4423
|
-
`<available-skills>\nSkills in the workspace:\n${lines.join("\n")}\n\nBefore using a matching workspace skill, read its path with the resources tool using action "read"; slash-selected skills are inlined automatically when available.\n</available-skills>`,
|
|
4424
|
-
);
|
|
4425
|
-
}
|
|
4426
|
-
if (agentLines.length > 0) {
|
|
4427
|
-
const lines = limitInventoryLines(agentLines, "agents");
|
|
4428
|
-
blocks.push(
|
|
4429
|
-
`<available-agents>\nCustom and connected agents in the workspace:\n${lines.join("\n")}\n\nCustom agents under agents/*.md can be mentioned or used via agent-teams (action: "spawn") with the agent parameter.\n</available-agents>`,
|
|
4430
|
-
);
|
|
4431
|
-
}
|
|
4432
|
-
if (jobLines.length > 0) {
|
|
4433
|
-
const lines = limitInventoryLines(jobLines, "jobs");
|
|
4434
|
-
blocks.push(
|
|
4435
|
-
`<available-jobs>\nScheduled tasks in the workspace:\n${lines.join("\n")}\n</available-jobs>`,
|
|
4436
|
-
);
|
|
4437
|
-
}
|
|
4438
|
-
filesContext =
|
|
4439
|
-
blocks.length > 0 ? `\n\n${blocks.join("\n\n")}` : "";
|
|
4456
|
+
} catch {
|
|
4457
|
+
// Resources not available — skip silently
|
|
4440
4458
|
}
|
|
4441
|
-
} catch {
|
|
4442
|
-
// Resources not available — skip silently
|
|
4443
4459
|
}
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
//
|
|
4449
|
-
//
|
|
4450
|
-
//
|
|
4451
|
-
//
|
|
4452
|
-
//
|
|
4453
|
-
//
|
|
4454
|
-
//
|
|
4455
|
-
//
|
|
4456
|
-
//
|
|
4457
|
-
//
|
|
4458
|
-
//
|
|
4460
|
+
return filesContext;
|
|
4461
|
+
})();
|
|
4462
|
+
|
|
4463
|
+
// Durable bg worker: a pre-send step that HANGS (rather than erroring) would
|
|
4464
|
+
// otherwise stall the worker until the foreground inline-recovery grace
|
|
4465
|
+
// (~16s) — wasting the entire 15-min durable budget and leaving the run
|
|
4466
|
+
// un-claimed (the exact analytics symptom: diag stuck at model_done,
|
|
4467
|
+
// preStart≈18s). `presendCap` takes a THUNK (not an eagerly-started promise):
|
|
4468
|
+
// the work runs INSIDE the cap, after the timer is armed, so a step whose
|
|
4469
|
+
// own synchronous prefix is heavy can still be timed out — an eagerly-created
|
|
4470
|
+
// promise would start (and could block the loop) before the cap ever wrapped
|
|
4471
|
+
// it. On timeout it records `presend_timeout:<label>` so a stalled phase is
|
|
4472
|
+
// attributable, then degrades to the fallback so the worker proceeds to
|
|
4473
|
+
// claim. Foreground keeps the un-capped path (thunk invoked immediately), so
|
|
4474
|
+
// its behaviour is unchanged. A rejected step (e.g. enrichMessage has no
|
|
4475
|
+
// .catch) resolves to the fallback instead of rejecting the whole batch.
|
|
4459
4476
|
const presendCap = <T>(
|
|
4460
|
-
|
|
4477
|
+
label: string,
|
|
4478
|
+
thunk: () => Promise<T>,
|
|
4461
4479
|
fallback: T,
|
|
4462
4480
|
ms: number,
|
|
4463
4481
|
): Promise<T> => {
|
|
4464
|
-
if (!isBackgroundWorker) return
|
|
4482
|
+
if (!isBackgroundWorker) return thunk();
|
|
4465
4483
|
return new Promise<T>((resolve) => {
|
|
4466
|
-
const timer = setTimeout(() =>
|
|
4467
|
-
|
|
4468
|
-
(
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4484
|
+
const timer = setTimeout(() => {
|
|
4485
|
+
workerStep(`presend_timeout:${label}`);
|
|
4486
|
+
resolve(fallback);
|
|
4487
|
+
}, ms);
|
|
4488
|
+
// Defer invocation one microtask so every sibling cap arms its timer
|
|
4489
|
+
// before any thunk's synchronous prefix runs.
|
|
4490
|
+
void Promise.resolve()
|
|
4491
|
+
.then(thunk)
|
|
4492
|
+
.then(
|
|
4493
|
+
(v) => {
|
|
4494
|
+
clearTimeout(timer);
|
|
4495
|
+
resolve(v);
|
|
4496
|
+
},
|
|
4497
|
+
() => {
|
|
4498
|
+
clearTimeout(timer);
|
|
4499
|
+
resolve(fallback);
|
|
4500
|
+
},
|
|
4501
|
+
);
|
|
4477
4502
|
});
|
|
4478
4503
|
};
|
|
4479
4504
|
const fallbackLoopSettings: AgentLoopSettings = {
|
|
@@ -4493,13 +4518,13 @@ export function createProductionAgentHandler(
|
|
|
4493
4518
|
loopSettings,
|
|
4494
4519
|
enrichedMessage,
|
|
4495
4520
|
] = await Promise.all([
|
|
4496
|
-
presendCap(
|
|
4497
|
-
presendCap(
|
|
4498
|
-
presendCap(
|
|
4499
|
-
presendCap(
|
|
4500
|
-
presendCap(
|
|
4501
|
-
presendCap(
|
|
4502
|
-
presendCap(
|
|
4521
|
+
presendCap("systemPrompt", systemPromptThunk, "", 13000),
|
|
4522
|
+
presendCap("screen", screenContextThunk, "", 9000),
|
|
4523
|
+
presendCap("url", urlContextThunk, "", 9000),
|
|
4524
|
+
presendCap("selection", selectionContextThunk, "", 9000),
|
|
4525
|
+
presendCap("files", filesContextThunk, "", 12000),
|
|
4526
|
+
presendCap("loopSettings", loopSettingsThunk, fallbackLoopSettings, 9000),
|
|
4527
|
+
presendCap("enrichedMessage", enrichedMessageThunk, requestMessage, 9000),
|
|
4503
4528
|
]);
|
|
4504
4529
|
setupMark("ctxAll");
|
|
4505
4530
|
// DIAGNOSTIC-ONLY: all parallel context gathering (system prompt, screen,
|
|
@@ -5066,6 +5091,13 @@ export function createProductionAgentHandler(
|
|
|
5066
5091
|
// DIAGNOSTIC-ONLY: last stage before startRun fires. A worker that reaches
|
|
5067
5092
|
// prestart but never workerStarted is hanging inside startRun itself.
|
|
5068
5093
|
workerStep("prestart");
|
|
5094
|
+
// DIAGNOSTIC-ONLY: peak-ish RSS (MB) + assembled system-prompt size (KB) at
|
|
5095
|
+
// prestart. The analytics bg worker dies right after model_done; if the
|
|
5096
|
+
// FOREGROUND (identical build, writes land) is already near the ~1024MB
|
|
5097
|
+
// Netlify function limit, an OOM kill in the heavier worker explains the
|
|
5098
|
+
// freeze. Both numbers ride along in the existing setup-timings detail.
|
|
5099
|
+
setupMarks.rssMB = Math.round(process.memoryUsage().rss / 1048576);
|
|
5100
|
+
setupMarks.promptKB = Math.round((systemPrompt?.length ?? 0) / 1024);
|
|
5069
5101
|
const setupDetail =
|
|
5070
5102
|
Object.entries(setupMarks)
|
|
5071
5103
|
.map(([k, v]) => `${k}=${v}`)
|
|
@@ -51,6 +51,14 @@ export interface RequestRunContext {
|
|
|
51
51
|
engine?: import("../agent/engine/types.js").AgentEngine;
|
|
52
52
|
/** Model name for this run (set by onEngineResolved). */
|
|
53
53
|
model?: string;
|
|
54
|
+
/**
|
|
55
|
+
* True when this run is executing inside the durable background-function
|
|
56
|
+
* worker (the `_process-run` self-dispatch), not the synchronous foreground
|
|
57
|
+
* request. Template `extraContext` / system-prompt builders can read this to
|
|
58
|
+
* skip heavy, hang-prone enrichment (large data-dictionary DB reads, etc.)
|
|
59
|
+
* in the worker so it reliably claims its run within the setup budget.
|
|
60
|
+
*/
|
|
61
|
+
isBackgroundWorker?: boolean;
|
|
54
62
|
/** Tool calls made so far in the current agent loop. */
|
|
55
63
|
toolCalls?: Array<{ name: string; input: unknown }>;
|
|
56
64
|
/** Tool results returned so far in the current agent loop. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getOrgContext } from "@agent-native/core/org";
|
|
2
2
|
import {
|
|
3
3
|
createAgentChatPlugin,
|
|
4
|
+
getRequestRunContext,
|
|
4
5
|
loadActionsFromStaticRegistry,
|
|
5
6
|
type AgentLoopFinalResponseGuardContext,
|
|
6
7
|
} from "@agent-native/core/server";
|
|
@@ -197,6 +198,18 @@ export default createAgentChatPlugin({
|
|
|
197
198
|
"After creating the extension, briefly tell the user that the request needed bespoke UI/code beyond the native Analytics dashboard or analysis format, so you built it as an extension.\n" +
|
|
198
199
|
"</analytics-artifact-guidance>";
|
|
199
200
|
|
|
201
|
+
// In the durable background-function worker, skip the data-dictionary read
|
|
202
|
+
// + render. That settings read + synchronous render is the heaviest, most
|
|
203
|
+
// hang-prone part of worker setup on a cold bg-fn instance, and it runs
|
|
204
|
+
// EAGERLY while the system prompt is built (before any pre-send timeout can
|
|
205
|
+
// arm), so a stall here is what kept the analytics worker from ever claiming
|
|
206
|
+
// its run. The agent can still pull dictionary entries on demand via
|
|
207
|
+
// `list-data-dictionary` / `search-bigquery-schema`; the static guidance is
|
|
208
|
+
// what it needs to know they exist. Foreground requests keep the full dict.
|
|
209
|
+
if (getRequestRunContext()?.isBackgroundWorker) {
|
|
210
|
+
return `${sourceGuidance}\n\n${artifactGuidance}`;
|
|
211
|
+
}
|
|
212
|
+
|
|
200
213
|
try {
|
|
201
214
|
const scope = await resolveSettingsScope(event);
|
|
202
215
|
const all = await listScopedSettingRecords(scope, DATA_DICT_PREFIX);
|