@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.
- package/corpus/core/CHANGELOG.md +26 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +329 -204
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +97 -15
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
})
|
|
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 = (
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
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
|
-
|
|
4259
|
-
|
|
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
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
setupMarks.screenMs = Date.now() - screenStart;
|
|
4266
|
-
}
|
|
4267
|
-
return "";
|
|
4268
|
-
})();
|
|
4363
|
+
return "";
|
|
4364
|
+
},
|
|
4365
|
+
);
|
|
4269
4366
|
|
|
4270
|
-
const urlContextPromise = (
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
:
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
lines.push(
|
|
4292
|
-
|
|
4293
|
-
|
|
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
|
-
|
|
4399
|
+
} catch {
|
|
4400
|
+
// DB not ready — skip silently
|
|
4297
4401
|
}
|
|
4298
|
-
|
|
4299
|
-
|
|
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 = (
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
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 = (
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
ownerEmail
|
|
4355
|
-
|
|
4356
|
-
|
|
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
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
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
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4488
|
+
if (kind === "job") {
|
|
4489
|
+
jobLines.push(` ${r.path} (${scope})`);
|
|
4490
|
+
continue;
|
|
4491
|
+
}
|
|
4381
4492
|
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
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
|
-
|
|
4411
|
-
|
|
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
|
-
|
|
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(" ") +
|
|
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,
|