@agent-native/core 0.77.14 → 0.77.16

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.
@@ -3950,6 +3950,65 @@ export function createProductionAgentHandler(
3950
3950
  `${s}=${Date.now() - setupT0}ms`,
3951
3951
  ).catch(() => {});
3952
3952
  };
3953
+ // DIAGNOSTIC-ONLY: non-DB breadcrumb to the function log drain. `workerStep`
3954
+ // writes to the DB, which is exactly what stalls after `model_done` in the bg
3955
+ // worker (no diag write lands), so this logs to stdout (Netlify function
3956
+ // logs) instead — used to name the exact post-model_done / pre-claim async
3957
+ // branch that hangs. Gated on the worker so foreground logs stay clean.
3958
+ const bgLog = (label: string) => {
3959
+ if (!isBackgroundWorker) return;
3960
+ try {
3961
+ console.log(
3962
+ `[bg-presend] +${Date.now() - setupT0}ms run=${(bgRunId ?? "").slice(-6)} ${label}`,
3963
+ );
3964
+ } catch {}
3965
+ };
3966
+ // DIAGNOSTIC: pre-send branches that hit their timeout/error fallback,
3967
+ // recorded in memory so the FINAL run diag (setupDetail) can name them even
3968
+ // when the Netlify function-log drain is unreadable. Readable via
3969
+ // /runs/active once the worker claims past the stuck branch.
3970
+ const bgTimedOut: string[] = [];
3971
+ // DIAGNOSTIC + DEFENSIVE: race a pre-send read against a short timeout and
3972
+ // fall back to a safe default, logging start/done/error/timeout. A single
3973
+ // stuck read can no longer block the worker from reaching claim, and the
3974
+ // recorded `bgTimedOut` set names the culprit in the run diag.
3975
+ const withBgFallback = <T>(
3976
+ label: string,
3977
+ ms: number,
3978
+ fallback: T,
3979
+ fn: () => Promise<T>,
3980
+ ): Promise<T> => {
3981
+ bgLog(`${label}:start`);
3982
+ return new Promise<T>((resolve) => {
3983
+ let done = false;
3984
+ const timer = setTimeout(() => {
3985
+ if (done) return;
3986
+ done = true;
3987
+ bgLog(`${label}:TIMEOUT@${ms}ms`);
3988
+ bgTimedOut.push(`${label}:timeout`);
3989
+ resolve(fallback);
3990
+ }, ms);
3991
+ fn().then(
3992
+ (v) => {
3993
+ if (done) return;
3994
+ done = true;
3995
+ clearTimeout(timer);
3996
+ bgLog(`${label}:done`);
3997
+ resolve(v);
3998
+ },
3999
+ (e) => {
4000
+ if (done) return;
4001
+ done = true;
4002
+ clearTimeout(timer);
4003
+ bgLog(
4004
+ `${label}:error ${(e as { message?: string })?.message ?? e}`,
4005
+ );
4006
+ bgTimedOut.push(`${label}:error`);
4007
+ resolve(fallback);
4008
+ },
4009
+ );
4010
+ });
4011
+ };
3953
4012
  // Whether this worker is REALLY executing inside a 15-min Netlify
3954
4013
  // `-background` function (proven by the runtime function name), not merely a
3955
4014
  // `_process-run` re-entry that may have landed on the ~60s synchronous
@@ -4160,6 +4219,7 @@ export function createProductionAgentHandler(
4160
4219
  engine.defaultModel;
4161
4220
  // DIAGNOSTIC-ONLY: stored-model resolution finished.
4162
4221
  workerStep("model_done");
4222
+ bgLog("model_done");
4163
4223
  const model = normalizeModelForEngine(engine, modelCandidate);
4164
4224
  const reasoningEffort = normalizeReasoningEffortForModel(
4165
4225
  model,
@@ -4168,7 +4228,9 @@ export function createProductionAgentHandler(
4168
4228
  : options.reasoningEffort,
4169
4229
  );
4170
4230
 
4231
+ bgLog("onEngineResolved:before");
4171
4232
  options.onEngineResolved?.(engine, model);
4233
+ bgLog("onEngineResolved:after");
4172
4234
 
4173
4235
  // One-line per-turn resolution log so it's obvious in dev which engine
4174
4236
  // is actually handling the request. `requestEngine` is what the client
@@ -4206,241 +4268,294 @@ export function createProductionAgentHandler(
4206
4268
  // reached db_request_ctx but not env_config hung in attachment upload or
4207
4269
  // engine/model resolution.
4208
4270
  workerStep("env_config");
4271
+ bgLog("env_config");
4272
+ bgLog("presend:creating");
4209
4273
  // Run all independent pre-send steps in parallel. Each of these hits
4210
4274
  // the DB or invokes an action; running them sequentially was the
4211
- // single biggest contributor to pre-LLM latency.
4212
- const enrichedMessagePromise = enrichMessage(requestMessage, references);
4275
+ // single biggest contributor to pre-LLM latency. Each branch is bracketed
4276
+ // with a log-drain breadcrumb (bgLog) so the function logs name any branch
4277
+ // that starts but never finishes; the OPTIONAL context reads additionally
4278
+ // race a short timeout + "" fallback (withBgFallback) so one stuck read
4279
+ // cannot block the worker from reaching claim.
4280
+ const enrichedMessagePromise = withBgFallback(
4281
+ "enrich",
4282
+ 5000,
4283
+ requestMessage,
4284
+ () => Promise.resolve(enrichMessage(requestMessage, references)),
4285
+ );
4286
+ bgLog("loop:start");
4213
4287
  const loopSettingsPromise = readAgentLoopSettings({
4214
4288
  userEmail: ownerEmail ?? getRequestUserEmail() ?? null,
4215
4289
  orgId: getRequestOrgId() ?? null,
4216
- }).catch(() => readAgentLoopSettings({}));
4290
+ })
4291
+ .catch(() => readAgentLoopSettings({}))
4292
+ .then((v) => {
4293
+ bgLog("loop:done");
4294
+ return v;
4295
+ });
4217
4296
 
4218
4297
  let systemPromptError: any = null;
4219
- const systemPromptPromise = (async (): Promise<string> => {
4220
- const sysPromptStart = Date.now();
4221
- try {
4222
- return typeof options.systemPrompt === "function"
4223
- ? await options.systemPrompt(event)
4224
- : options.systemPrompt;
4225
- } catch (error) {
4226
- systemPromptError = error;
4227
- return "";
4228
- } finally {
4229
- setupMarks.sysPromptMs = Date.now() - sysPromptStart;
4230
- }
4231
- })();
4232
-
4233
- const screenContextPromise = (async (): Promise<string> => {
4234
- const screenStart = Date.now();
4235
- try {
4236
- const viewScreenAction = resolvedActions["view-screen"];
4237
- if (viewScreenAction) {
4238
- const result = await viewScreenAction.run(
4239
- {},
4240
- {
4241
- userEmail: getRequestUserEmail(),
4242
- orgId: getRequestOrgId() ?? null,
4243
- caller: "tool",
4244
- },
4245
- );
4246
- if (result && result !== "(no output)") {
4247
- const screenText =
4248
- typeof result === "string"
4249
- ? result
4250
- : JSON.stringify(result, null, 2);
4251
- return `\n\n<current-screen>\n${capScreenContext(screenText)}\n</current-screen>`;
4252
- }
4253
- } else {
4254
- const navigation = await readAppStateForBrowserTab(
4255
- "navigation",
4256
- requestBrowserTabId,
4298
+ const systemPromptPromise = withBgFallback(
4299
+ "sysprompt",
4300
+ 5000,
4301
+ "",
4302
+ async (): Promise<string> => {
4303
+ const sysPromptStart = Date.now();
4304
+ bgLog("sysprompt:start");
4305
+ try {
4306
+ const sp =
4307
+ typeof options.systemPrompt === "function"
4308
+ ? await options.systemPrompt(event)
4309
+ : options.systemPrompt;
4310
+ bgLog("sysprompt:done");
4311
+ return sp;
4312
+ } catch (error) {
4313
+ systemPromptError = error;
4314
+ bgLog(
4315
+ "sysprompt:error " +
4316
+ ((error as { message?: string })?.message ?? error),
4257
4317
  );
4258
- if (navigation) {
4259
- return `\n\n<current-screen>\n${capScreenContext(JSON.stringify(navigation, null, 2))}\n</current-screen>`;
4318
+ return "";
4319
+ } finally {
4320
+ setupMarks.sysPromptMs = Date.now() - sysPromptStart;
4321
+ }
4322
+ },
4323
+ );
4324
+
4325
+ const screenContextPromise = withBgFallback(
4326
+ "screen",
4327
+ 6000,
4328
+ "",
4329
+ async (): Promise<string> => {
4330
+ const screenStart = Date.now();
4331
+ try {
4332
+ const viewScreenAction = resolvedActions["view-screen"];
4333
+ if (viewScreenAction) {
4334
+ const result = await viewScreenAction.run(
4335
+ {},
4336
+ {
4337
+ userEmail: getRequestUserEmail(),
4338
+ orgId: getRequestOrgId() ?? null,
4339
+ caller: "tool",
4340
+ },
4341
+ );
4342
+ if (result && result !== "(no output)") {
4343
+ const screenText =
4344
+ typeof result === "string"
4345
+ ? result
4346
+ : JSON.stringify(result, null, 2);
4347
+ return `\n\n<current-screen>\n${capScreenContext(screenText)}\n</current-screen>`;
4348
+ }
4349
+ } else {
4350
+ const navigation = await readAppStateForBrowserTab(
4351
+ "navigation",
4352
+ requestBrowserTabId,
4353
+ );
4354
+ if (navigation) {
4355
+ return `\n\n<current-screen>\n${capScreenContext(JSON.stringify(navigation, null, 2))}\n</current-screen>`;
4356
+ }
4260
4357
  }
4358
+ } catch {
4359
+ // DB not ready or no navigation state — skip silently
4360
+ } finally {
4361
+ setupMarks.screenMs = Date.now() - screenStart;
4261
4362
  }
4262
- } catch {
4263
- // DB not ready or no navigation state — skip silently
4264
- } finally {
4265
- setupMarks.screenMs = Date.now() - screenStart;
4266
- }
4267
- return "";
4268
- })();
4363
+ return "";
4364
+ },
4365
+ );
4269
4366
 
4270
- const urlContextPromise = (async (): Promise<string> => {
4271
- try {
4272
- const url = (await readAppStateForBrowserTab(
4273
- "__url__",
4274
- requestBrowserTabId,
4275
- )) as {
4276
- pathname?: string;
4277
- search?: string;
4278
- hash?: string;
4279
- searchParams?: Record<string, string>;
4280
- } | null;
4281
- if (url && (url.pathname || url.search || url.hash)) {
4282
- const lines: string[] = [];
4283
- if (url.pathname) lines.push(`pathname: ${url.pathname}`);
4284
- const extensionId = url.pathname
4285
- ? extensionIdFromPathname(url.pathname)
4286
- : null;
4287
- if (extensionId) lines.push(`extensionId: ${extensionId}`);
4288
- if (url.search) lines.push(`search: ${url.search}`);
4289
- if (url.hash) lines.push(`hash: ${url.hash}`);
4290
- if (url.searchParams && Object.keys(url.searchParams).length > 0) {
4291
- lines.push("searchParams:");
4292
- for (const [k, v] of Object.entries(url.searchParams)) {
4293
- lines.push(` ${k}: ${v}`);
4367
+ const urlContextPromise = withBgFallback(
4368
+ "url",
4369
+ 6000,
4370
+ "",
4371
+ async (): Promise<string> => {
4372
+ try {
4373
+ const url = (await readAppStateForBrowserTab(
4374
+ "__url__",
4375
+ requestBrowserTabId,
4376
+ )) as {
4377
+ pathname?: string;
4378
+ search?: string;
4379
+ hash?: string;
4380
+ searchParams?: Record<string, string>;
4381
+ } | null;
4382
+ if (url && (url.pathname || url.search || url.hash)) {
4383
+ const lines: string[] = [];
4384
+ if (url.pathname) lines.push(`pathname: ${url.pathname}`);
4385
+ const extensionId = url.pathname
4386
+ ? extensionIdFromPathname(url.pathname)
4387
+ : null;
4388
+ if (extensionId) lines.push(`extensionId: ${extensionId}`);
4389
+ if (url.search) lines.push(`search: ${url.search}`);
4390
+ if (url.hash) lines.push(`hash: ${url.hash}`);
4391
+ if (url.searchParams && Object.keys(url.searchParams).length > 0) {
4392
+ lines.push("searchParams:");
4393
+ for (const [k, v] of Object.entries(url.searchParams)) {
4394
+ lines.push(` ${k}: ${v}`);
4395
+ }
4294
4396
  }
4397
+ return `\n\n<current-url>\n${lines.join("\n")}\n</current-url>`;
4295
4398
  }
4296
- return `\n\n<current-url>\n${lines.join("\n")}\n</current-url>`;
4399
+ } catch {
4400
+ // DB not ready — skip silently
4297
4401
  }
4298
- } catch {
4299
- // DB not ready — skip silently
4300
- }
4301
- return "";
4302
- })();
4402
+ return "";
4403
+ },
4404
+ );
4303
4405
 
4304
4406
  // Selection context: written by the client when the user presses Cmd+I
4305
4407
  // with text selected on the page. Treat anything older than 5 minutes
4306
4408
  // as stale and ignore it.
4307
4409
  const SELECTION_TTL_MS = 5 * 60 * 1000;
4308
- const selectionContextPromise = (async (): Promise<string> => {
4309
- try {
4310
- const sel = (await readAppState("pending-selection-context")) as {
4311
- text?: string;
4312
- capturedAt?: number;
4313
- } | null;
4314
- if (!sel?.text) return "";
4315
- const capturedAt =
4316
- typeof sel.capturedAt === "number" ? sel.capturedAt : 0;
4317
- if (Date.now() - capturedAt > SELECTION_TTL_MS) return "";
4318
- return (
4319
- `\n\nThe user has selected the following text and pressed Cmd+I to focus the agent. ` +
4320
- `Treat this as the immediate context to act on:\n` +
4321
- `<selection>\n${capSelectionContext(sel.text)}\n</selection>`
4322
- );
4323
- } catch {
4324
- // DB not ready skip silently
4325
- }
4326
- return "";
4327
- })();
4410
+ const selectionContextPromise = withBgFallback(
4411
+ "selection",
4412
+ 6000,
4413
+ "",
4414
+ async (): Promise<string> => {
4415
+ try {
4416
+ const sel = (await readAppState("pending-selection-context")) as {
4417
+ text?: string;
4418
+ capturedAt?: number;
4419
+ } | null;
4420
+ if (!sel?.text) return "";
4421
+ const capturedAt =
4422
+ typeof sel.capturedAt === "number" ? sel.capturedAt : 0;
4423
+ if (Date.now() - capturedAt > SELECTION_TTL_MS) return "";
4424
+ return (
4425
+ `\n\nThe user has selected the following text and pressed Cmd+I to focus the agent. ` +
4426
+ `Treat this as the immediate context to act on:\n` +
4427
+ `<selection>\n${capSelectionContext(sel.text)}\n</selection>`
4428
+ );
4429
+ } catch {
4430
+ // DB not ready — skip silently
4431
+ }
4432
+ return "";
4433
+ },
4434
+ );
4328
4435
 
4329
4436
  // On the first message of a conversation, inject workspace inventory
4330
4437
  // so the agent knows what files, skills, jobs, and custom agents exist.
4331
4438
  // Templates can opt out via `skipFilesContext: true` when the inventory
4332
4439
  // is unrelated to the app's job (e.g. a voice-first macro tracker).
4333
- const filesContextPromise = (async (): Promise<string> => {
4334
- let filesContext = "";
4335
- if (options.skipFilesContext) return filesContext;
4336
- if (history.length === 0) {
4337
- try {
4338
- const {
4339
- resourceListAccessible,
4340
- SHARED_OWNER,
4341
- WORKSPACE_OWNER,
4342
- resourceGet,
4343
- } = await import("../resources/store.js");
4344
- const {
4345
- getResourceKind,
4346
- parseCustomAgentProfile,
4347
- parseRemoteAgentManifest,
4348
- parseSkillMetadata,
4349
- } = await import("../resources/metadata.js");
4350
- const ownerEmail = getRequestUserEmail();
4351
- const orgId = getRequestOrgId();
4352
- if (!ownerEmail) throw new Error("no authenticated user");
4353
- const allResources = await resourceListAccessible(
4354
- ownerEmail,
4355
- undefined,
4356
- { userEmail: ownerEmail, orgId },
4357
- );
4440
+ const filesContextPromise = withBgFallback(
4441
+ "files",
4442
+ 8000,
4443
+ "",
4444
+ async (): Promise<string> => {
4445
+ let filesContext = "";
4446
+ if (options.skipFilesContext) return filesContext;
4447
+ if (history.length === 0) {
4448
+ try {
4449
+ const {
4450
+ resourceListAccessible,
4451
+ SHARED_OWNER,
4452
+ WORKSPACE_OWNER,
4453
+ resourceGet,
4454
+ } = await import("../resources/store.js");
4455
+ const {
4456
+ getResourceKind,
4457
+ parseCustomAgentProfile,
4458
+ parseRemoteAgentManifest,
4459
+ parseSkillMetadata,
4460
+ } = await import("../resources/metadata.js");
4461
+ const ownerEmail = getRequestUserEmail();
4462
+ const orgId = getRequestOrgId();
4463
+ if (!ownerEmail) throw new Error("no authenticated user");
4464
+ const allResources = await resourceListAccessible(
4465
+ ownerEmail,
4466
+ undefined,
4467
+ { userEmail: ownerEmail, orgId },
4468
+ );
4358
4469
 
4359
- if (allResources.length > 0) {
4360
- const fileLines: string[] = [];
4361
- const skillLines: string[] = [];
4362
- const agentLines: string[] = [];
4363
- const jobLines: string[] = [];
4364
- for (const r of allResources) {
4365
- const scope =
4366
- r.owner === WORKSPACE_OWNER
4367
- ? "workspace"
4368
- : r.owner === SHARED_OWNER
4369
- ? "shared"
4370
- : "personal";
4371
- const kind = getResourceKind(r.path);
4372
- if (kind === "file") {
4373
- fileLines.push(` ${r.path} (${scope})`);
4374
- continue;
4375
- }
4470
+ if (allResources.length > 0) {
4471
+ const fileLines: string[] = [];
4472
+ const skillLines: string[] = [];
4473
+ const agentLines: string[] = [];
4474
+ const jobLines: string[] = [];
4475
+ for (const r of allResources) {
4476
+ const scope =
4477
+ r.owner === WORKSPACE_OWNER
4478
+ ? "workspace"
4479
+ : r.owner === SHARED_OWNER
4480
+ ? "shared"
4481
+ : "personal";
4482
+ const kind = getResourceKind(r.path);
4483
+ if (kind === "file") {
4484
+ fileLines.push(` ${r.path} (${scope})`);
4485
+ continue;
4486
+ }
4376
4487
 
4377
- if (kind === "job") {
4378
- jobLines.push(` ${r.path} (${scope})`);
4379
- continue;
4380
- }
4488
+ if (kind === "job") {
4489
+ jobLines.push(` ${r.path} (${scope})`);
4490
+ continue;
4491
+ }
4381
4492
 
4382
- if (
4383
- kind === "skill" ||
4384
- kind === "agent" ||
4385
- kind === "remote-agent"
4386
- ) {
4387
- const full = await resourceGet(r.id, {
4388
- userEmail: ownerEmail,
4389
- orgId,
4390
- });
4391
- if (!full) continue;
4392
- if (kind === "skill") {
4393
- const skill = parseSkillMetadata(full.content, r.path);
4394
- skillLines.push(
4395
- ` ${skill?.name || r.path} — ${compactInventoryDescription(skill?.description || r.path)} (${scope}, ${r.path})`,
4396
- );
4397
- } else if (kind === "agent") {
4398
- const agent = parseCustomAgentProfile(full.content, r.path);
4399
- agentLines.push(
4400
- ` ${agent?.name || r.path} — ${compactInventoryDescription(agent?.description || "Custom workspace agent")} (${scope}, ${r.path}${agent?.model ? `, model: ${agent.model}` : ""})`,
4401
- );
4402
- } else {
4403
- const agent = parseRemoteAgentManifest(full.content, r.path);
4404
- agentLines.push(
4405
- ` ${agent?.name || r.path} — ${compactInventoryDescription(agent?.description || "Connected A2A agent")} (${scope}, remote via ${r.path})`,
4406
- );
4493
+ if (
4494
+ kind === "skill" ||
4495
+ kind === "agent" ||
4496
+ kind === "remote-agent"
4497
+ ) {
4498
+ const full = await resourceGet(r.id, {
4499
+ userEmail: ownerEmail,
4500
+ orgId,
4501
+ });
4502
+ if (!full) continue;
4503
+ if (kind === "skill") {
4504
+ const skill = parseSkillMetadata(full.content, r.path);
4505
+ skillLines.push(
4506
+ ` ${skill?.name || r.path} — ${compactInventoryDescription(skill?.description || r.path)} (${scope}, ${r.path})`,
4507
+ );
4508
+ } else if (kind === "agent") {
4509
+ const agent = parseCustomAgentProfile(full.content, r.path);
4510
+ agentLines.push(
4511
+ ` ${agent?.name || r.path} — ${compactInventoryDescription(agent?.description || "Custom workspace agent")} (${scope}, ${r.path}${agent?.model ? `, model: ${agent.model}` : ""})`,
4512
+ );
4513
+ } else {
4514
+ const agent = parseRemoteAgentManifest(
4515
+ full.content,
4516
+ r.path,
4517
+ );
4518
+ agentLines.push(
4519
+ ` ${agent?.name || r.path} — ${compactInventoryDescription(agent?.description || "Connected A2A agent")} (${scope}, remote via ${r.path})`,
4520
+ );
4521
+ }
4407
4522
  }
4408
4523
  }
4524
+ const blocks: string[] = [];
4525
+ if (fileLines.length > 0) {
4526
+ const lines = limitInventoryLines(fileLines, "files");
4527
+ blocks.push(
4528
+ `<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>`,
4529
+ );
4530
+ }
4531
+ if (skillLines.length > 0) {
4532
+ const lines = limitInventoryLines(skillLines, "skills");
4533
+ blocks.push(
4534
+ `<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>`,
4535
+ );
4536
+ }
4537
+ if (agentLines.length > 0) {
4538
+ const lines = limitInventoryLines(agentLines, "agents");
4539
+ blocks.push(
4540
+ `<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>`,
4541
+ );
4542
+ }
4543
+ if (jobLines.length > 0) {
4544
+ const lines = limitInventoryLines(jobLines, "jobs");
4545
+ blocks.push(
4546
+ `<available-jobs>\nScheduled tasks in the workspace:\n${lines.join("\n")}\n</available-jobs>`,
4547
+ );
4548
+ }
4549
+ filesContext =
4550
+ blocks.length > 0 ? `\n\n${blocks.join("\n\n")}` : "";
4409
4551
  }
4410
- const blocks: string[] = [];
4411
- if (fileLines.length > 0) {
4412
- const lines = limitInventoryLines(fileLines, "files");
4413
- blocks.push(
4414
- `<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>`,
4415
- );
4416
- }
4417
- if (skillLines.length > 0) {
4418
- const lines = limitInventoryLines(skillLines, "skills");
4419
- blocks.push(
4420
- `<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>`,
4421
- );
4422
- }
4423
- if (agentLines.length > 0) {
4424
- const lines = limitInventoryLines(agentLines, "agents");
4425
- blocks.push(
4426
- `<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>`,
4427
- );
4428
- }
4429
- if (jobLines.length > 0) {
4430
- const lines = limitInventoryLines(jobLines, "jobs");
4431
- blocks.push(
4432
- `<available-jobs>\nScheduled tasks in the workspace:\n${lines.join("\n")}\n</available-jobs>`,
4433
- );
4434
- }
4435
- filesContext =
4436
- blocks.length > 0 ? `\n\n${blocks.join("\n\n")}` : "";
4552
+ } catch {
4553
+ // Resources not available — skip silently
4437
4554
  }
4438
- } catch {
4439
- // Resources not available — skip silently
4440
4555
  }
4441
- }
4442
- return filesContext;
4443
- })();
4556
+ return filesContext;
4557
+ },
4558
+ );
4444
4559
 
4445
4560
  // DIAGNOSTIC-ONLY: the background worker freezes between `env_config` and
4446
4561
  // `context_all` when one of the parallel pre-send promises hangs (observed
@@ -4468,6 +4583,7 @@ export function createProductionAgentHandler(
4468
4583
  .finally(() => __psMark("enrich"))
4469
4584
  .catch(() => {});
4470
4585
 
4586
+ bgLog("presend:awaiting Promise.all");
4471
4587
  const [
4472
4588
  systemPrompt,
4473
4589
  screenBlock,
@@ -4489,6 +4605,7 @@ export function createProductionAgentHandler(
4489
4605
  // DIAGNOSTIC-ONLY: all parallel context gathering (system prompt, screen,
4490
4606
  // files, loop settings, enriched message) resolved.
4491
4607
  workerStep("context_all");
4608
+ bgLog("context_all");
4492
4609
 
4493
4610
  if (systemPromptError) {
4494
4611
  setResponseHeader(event, "Content-Type", "text/event-stream");
@@ -4519,6 +4636,7 @@ export function createProductionAgentHandler(
4519
4636
  setupMark("actions");
4520
4637
  // DIAGNOSTIC-ONLY: action/tool resolution + engine-tool filtering finished.
4521
4638
  workerStep("action_tool_setup");
4639
+ bgLog("action_tool_setup");
4522
4640
  const requestSystemPrompt =
4523
4641
  requestMode === "plan"
4524
4642
  ? `${systemPrompt}\n\n${PLAN_MODE_SYSTEM_PROMPT}`
@@ -4633,6 +4751,7 @@ export function createProductionAgentHandler(
4633
4751
  // DIAGNOSTIC-ONLY: owner/thread resolution + runId/effectiveThreadId +
4634
4752
  // chained-continuation thread fetch finished.
4635
4753
  workerStep("owner_thread");
4754
+ bgLog("owner_thread");
4636
4755
 
4637
4756
  // Persist the user's turn exactly once. The foreground POST does this
4638
4757
  // before dispatching; the background worker must NOT repeat it (it re-enters
@@ -5025,7 +5144,9 @@ export function createProductionAgentHandler(
5025
5144
  dispatchMode: "background",
5026
5145
  }).catch(() => {});
5027
5146
  }
5147
+ bgLog("claim:before");
5028
5148
  const won = await claimBackgroundRun(runId);
5149
+ bgLog("claim:after won=" + won);
5029
5150
  if (!won) {
5030
5151
  // Already claimed by an earlier delivery — return a benign ack so
5031
5152
  // Netlify doesn't retry a successful handoff.
@@ -5050,11 +5171,15 @@ export function createProductionAgentHandler(
5050
5171
  // DIAGNOSTIC-ONLY: last stage before startRun fires. A worker that reaches
5051
5172
  // prestart but never workerStarted is hanging inside startRun itself.
5052
5173
  workerStep("prestart");
5174
+ bgLog("prestart");
5053
5175
  const setupDetail =
5054
5176
  Object.entries(setupMarks)
5055
5177
  .map(([k, v]) => `${k}=${v}`)
5056
- .join(" ") + ` total=${Date.now() - setupT0}`;
5178
+ .join(" ") +
5179
+ ` total=${Date.now() - setupT0}` +
5180
+ ` to=${bgTimedOut.join(",") || "none"}`;
5057
5181
 
5182
+ bgLog("startRun:invoked");
5058
5183
  startRun(
5059
5184
  runId,
5060
5185
  effectiveThreadId,