@agent-native/core 0.77.14 → 0.77.15
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 +15 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +305 -193
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +89 -12
- 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
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.77.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d5ec2d0: Diagnostic + defensive instrumentation for the durable background-agent worker's
|
|
8
|
+
pre-send setup. Adds stdout breadcrumbs (`bgLog`, gated on the worker → Netlify
|
|
9
|
+
function logs, independent of DB writes which stall in the failing case) across
|
|
10
|
+
the post-`model_done` / pre-claim sequence and each parallel pre-send branch
|
|
11
|
+
(enrich, loop settings, system prompt, view-screen, url, selection, files
|
|
12
|
+
inventory) with start/done/error/timeout. The OPTIONAL context reads
|
|
13
|
+
(view-screen, url, selection, files) now race a short timeout with a safe `""`
|
|
14
|
+
fallback so one stuck read cannot block the worker from reaching
|
|
15
|
+
`claimBackgroundRun`. Used to localize the analytics-only worker freeze that the
|
|
16
|
+
DB-based diagnostic can't see.
|
|
17
|
+
|
|
3
18
|
## 0.77.14
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.77.
|
|
3
|
+
"version": "0.77.15",
|
|
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": {
|
|
@@ -3950,6 +3950,58 @@ 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 + DEFENSIVE: race an optional context read against a short
|
|
3967
|
+
// timeout and fall back to a safe default, logging start/done/error/timeout.
|
|
3968
|
+
// A single stuck optional read can no longer block the worker from reaching
|
|
3969
|
+
// claim, and the TIMEOUT breadcrumb names the culprit in the function logs.
|
|
3970
|
+
const withBgFallback = <T>(
|
|
3971
|
+
label: string,
|
|
3972
|
+
ms: number,
|
|
3973
|
+
fallback: T,
|
|
3974
|
+
fn: () => Promise<T>,
|
|
3975
|
+
): Promise<T> => {
|
|
3976
|
+
bgLog(`${label}:start`);
|
|
3977
|
+
return new Promise<T>((resolve) => {
|
|
3978
|
+
let done = false;
|
|
3979
|
+
const timer = setTimeout(() => {
|
|
3980
|
+
if (done) return;
|
|
3981
|
+
done = true;
|
|
3982
|
+
bgLog(`${label}:TIMEOUT@${ms}ms`);
|
|
3983
|
+
resolve(fallback);
|
|
3984
|
+
}, ms);
|
|
3985
|
+
fn().then(
|
|
3986
|
+
(v) => {
|
|
3987
|
+
if (done) return;
|
|
3988
|
+
done = true;
|
|
3989
|
+
clearTimeout(timer);
|
|
3990
|
+
bgLog(`${label}:done`);
|
|
3991
|
+
resolve(v);
|
|
3992
|
+
},
|
|
3993
|
+
(e) => {
|
|
3994
|
+
if (done) return;
|
|
3995
|
+
done = true;
|
|
3996
|
+
clearTimeout(timer);
|
|
3997
|
+
bgLog(
|
|
3998
|
+
`${label}:error ${(e as { message?: string })?.message ?? e}`,
|
|
3999
|
+
);
|
|
4000
|
+
resolve(fallback);
|
|
4001
|
+
},
|
|
4002
|
+
);
|
|
4003
|
+
});
|
|
4004
|
+
};
|
|
3953
4005
|
// Whether this worker is REALLY executing inside a 15-min Netlify
|
|
3954
4006
|
// `-background` function (proven by the runtime function name), not merely a
|
|
3955
4007
|
// `_process-run` re-entry that may have landed on the ~60s synchronous
|
|
@@ -4160,6 +4212,7 @@ export function createProductionAgentHandler(
|
|
|
4160
4212
|
engine.defaultModel;
|
|
4161
4213
|
// DIAGNOSTIC-ONLY: stored-model resolution finished.
|
|
4162
4214
|
workerStep("model_done");
|
|
4215
|
+
bgLog("model_done");
|
|
4163
4216
|
const model = normalizeModelForEngine(engine, modelCandidate);
|
|
4164
4217
|
const reasoningEffort = normalizeReasoningEffortForModel(
|
|
4165
4218
|
model,
|
|
@@ -4168,7 +4221,9 @@ export function createProductionAgentHandler(
|
|
|
4168
4221
|
: options.reasoningEffort,
|
|
4169
4222
|
);
|
|
4170
4223
|
|
|
4224
|
+
bgLog("onEngineResolved:before");
|
|
4171
4225
|
options.onEngineResolved?.(engine, model);
|
|
4226
|
+
bgLog("onEngineResolved:after");
|
|
4172
4227
|
|
|
4173
4228
|
// One-line per-turn resolution log so it's obvious in dev which engine
|
|
4174
4229
|
// is actually handling the request. `requestEngine` is what the client
|
|
@@ -4206,241 +4261,290 @@ export function createProductionAgentHandler(
|
|
|
4206
4261
|
// reached db_request_ctx but not env_config hung in attachment upload or
|
|
4207
4262
|
// engine/model resolution.
|
|
4208
4263
|
workerStep("env_config");
|
|
4264
|
+
bgLog("env_config");
|
|
4265
|
+
bgLog("presend:creating");
|
|
4209
4266
|
// Run all independent pre-send steps in parallel. Each of these hits
|
|
4210
4267
|
// the DB or invokes an action; running them sequentially was the
|
|
4211
|
-
// single biggest contributor to pre-LLM latency.
|
|
4212
|
-
|
|
4268
|
+
// single biggest contributor to pre-LLM latency. Each branch is bracketed
|
|
4269
|
+
// with a log-drain breadcrumb (bgLog) so the function logs name any branch
|
|
4270
|
+
// that starts but never finishes; the OPTIONAL context reads additionally
|
|
4271
|
+
// race a short timeout + "" fallback (withBgFallback) so one stuck read
|
|
4272
|
+
// cannot block the worker from reaching claim.
|
|
4273
|
+
bgLog("enrich:start");
|
|
4274
|
+
const enrichedMessagePromise = Promise.resolve(
|
|
4275
|
+
enrichMessage(requestMessage, references),
|
|
4276
|
+
).then((v) => {
|
|
4277
|
+
bgLog("enrich:done");
|
|
4278
|
+
return v;
|
|
4279
|
+
});
|
|
4280
|
+
bgLog("loop:start");
|
|
4213
4281
|
const loopSettingsPromise = readAgentLoopSettings({
|
|
4214
4282
|
userEmail: ownerEmail ?? getRequestUserEmail() ?? null,
|
|
4215
4283
|
orgId: getRequestOrgId() ?? null,
|
|
4216
|
-
})
|
|
4284
|
+
})
|
|
4285
|
+
.catch(() => readAgentLoopSettings({}))
|
|
4286
|
+
.then((v) => {
|
|
4287
|
+
bgLog("loop:done");
|
|
4288
|
+
return v;
|
|
4289
|
+
});
|
|
4217
4290
|
|
|
4218
4291
|
let systemPromptError: any = null;
|
|
4219
4292
|
const systemPromptPromise = (async (): Promise<string> => {
|
|
4220
4293
|
const sysPromptStart = Date.now();
|
|
4294
|
+
bgLog("sysprompt:start");
|
|
4221
4295
|
try {
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4296
|
+
const sp =
|
|
4297
|
+
typeof options.systemPrompt === "function"
|
|
4298
|
+
? await options.systemPrompt(event)
|
|
4299
|
+
: options.systemPrompt;
|
|
4300
|
+
bgLog("sysprompt:done");
|
|
4301
|
+
return sp;
|
|
4225
4302
|
} catch (error) {
|
|
4226
4303
|
systemPromptError = error;
|
|
4304
|
+
bgLog(
|
|
4305
|
+
"sysprompt:error " +
|
|
4306
|
+
((error as { message?: string })?.message ?? error),
|
|
4307
|
+
);
|
|
4227
4308
|
return "";
|
|
4228
4309
|
} finally {
|
|
4229
4310
|
setupMarks.sysPromptMs = Date.now() - sysPromptStart;
|
|
4230
4311
|
}
|
|
4231
4312
|
})();
|
|
4232
4313
|
|
|
4233
|
-
const screenContextPromise = (
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4314
|
+
const screenContextPromise = withBgFallback(
|
|
4315
|
+
"screen",
|
|
4316
|
+
6000,
|
|
4317
|
+
"",
|
|
4318
|
+
async (): Promise<string> => {
|
|
4319
|
+
const screenStart = Date.now();
|
|
4320
|
+
try {
|
|
4321
|
+
const viewScreenAction = resolvedActions["view-screen"];
|
|
4322
|
+
if (viewScreenAction) {
|
|
4323
|
+
const result = await viewScreenAction.run(
|
|
4324
|
+
{},
|
|
4325
|
+
{
|
|
4326
|
+
userEmail: getRequestUserEmail(),
|
|
4327
|
+
orgId: getRequestOrgId() ?? null,
|
|
4328
|
+
caller: "tool",
|
|
4329
|
+
},
|
|
4330
|
+
);
|
|
4331
|
+
if (result && result !== "(no output)") {
|
|
4332
|
+
const screenText =
|
|
4333
|
+
typeof result === "string"
|
|
4334
|
+
? result
|
|
4335
|
+
: JSON.stringify(result, null, 2);
|
|
4336
|
+
return `\n\n<current-screen>\n${capScreenContext(screenText)}\n</current-screen>`;
|
|
4337
|
+
}
|
|
4338
|
+
} else {
|
|
4339
|
+
const navigation = await readAppStateForBrowserTab(
|
|
4340
|
+
"navigation",
|
|
4341
|
+
requestBrowserTabId,
|
|
4342
|
+
);
|
|
4343
|
+
if (navigation) {
|
|
4344
|
+
return `\n\n<current-screen>\n${capScreenContext(JSON.stringify(navigation, null, 2))}\n</current-screen>`;
|
|
4345
|
+
}
|
|
4260
4346
|
}
|
|
4347
|
+
} catch {
|
|
4348
|
+
// DB not ready or no navigation state — skip silently
|
|
4349
|
+
} finally {
|
|
4350
|
+
setupMarks.screenMs = Date.now() - screenStart;
|
|
4261
4351
|
}
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
setupMarks.screenMs = Date.now() - screenStart;
|
|
4266
|
-
}
|
|
4267
|
-
return "";
|
|
4268
|
-
})();
|
|
4352
|
+
return "";
|
|
4353
|
+
},
|
|
4354
|
+
);
|
|
4269
4355
|
|
|
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
|
-
|
|
4356
|
+
const urlContextPromise = withBgFallback(
|
|
4357
|
+
"url",
|
|
4358
|
+
6000,
|
|
4359
|
+
"",
|
|
4360
|
+
async (): Promise<string> => {
|
|
4361
|
+
try {
|
|
4362
|
+
const url = (await readAppStateForBrowserTab(
|
|
4363
|
+
"__url__",
|
|
4364
|
+
requestBrowserTabId,
|
|
4365
|
+
)) as {
|
|
4366
|
+
pathname?: string;
|
|
4367
|
+
search?: string;
|
|
4368
|
+
hash?: string;
|
|
4369
|
+
searchParams?: Record<string, string>;
|
|
4370
|
+
} | null;
|
|
4371
|
+
if (url && (url.pathname || url.search || url.hash)) {
|
|
4372
|
+
const lines: string[] = [];
|
|
4373
|
+
if (url.pathname) lines.push(`pathname: ${url.pathname}`);
|
|
4374
|
+
const extensionId = url.pathname
|
|
4375
|
+
? extensionIdFromPathname(url.pathname)
|
|
4376
|
+
: null;
|
|
4377
|
+
if (extensionId) lines.push(`extensionId: ${extensionId}`);
|
|
4378
|
+
if (url.search) lines.push(`search: ${url.search}`);
|
|
4379
|
+
if (url.hash) lines.push(`hash: ${url.hash}`);
|
|
4380
|
+
if (url.searchParams && Object.keys(url.searchParams).length > 0) {
|
|
4381
|
+
lines.push("searchParams:");
|
|
4382
|
+
for (const [k, v] of Object.entries(url.searchParams)) {
|
|
4383
|
+
lines.push(` ${k}: ${v}`);
|
|
4384
|
+
}
|
|
4294
4385
|
}
|
|
4386
|
+
return `\n\n<current-url>\n${lines.join("\n")}\n</current-url>`;
|
|
4295
4387
|
}
|
|
4296
|
-
|
|
4388
|
+
} catch {
|
|
4389
|
+
// DB not ready — skip silently
|
|
4297
4390
|
}
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
return "";
|
|
4302
|
-
})();
|
|
4391
|
+
return "";
|
|
4392
|
+
},
|
|
4393
|
+
);
|
|
4303
4394
|
|
|
4304
4395
|
// Selection context: written by the client when the user presses Cmd+I
|
|
4305
4396
|
// with text selected on the page. Treat anything older than 5 minutes
|
|
4306
4397
|
// as stale and ignore it.
|
|
4307
4398
|
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
|
-
|
|
4399
|
+
const selectionContextPromise = withBgFallback(
|
|
4400
|
+
"selection",
|
|
4401
|
+
6000,
|
|
4402
|
+
"",
|
|
4403
|
+
async (): Promise<string> => {
|
|
4404
|
+
try {
|
|
4405
|
+
const sel = (await readAppState("pending-selection-context")) as {
|
|
4406
|
+
text?: string;
|
|
4407
|
+
capturedAt?: number;
|
|
4408
|
+
} | null;
|
|
4409
|
+
if (!sel?.text) return "";
|
|
4410
|
+
const capturedAt =
|
|
4411
|
+
typeof sel.capturedAt === "number" ? sel.capturedAt : 0;
|
|
4412
|
+
if (Date.now() - capturedAt > SELECTION_TTL_MS) return "";
|
|
4413
|
+
return (
|
|
4414
|
+
`\n\nThe user has selected the following text and pressed Cmd+I to focus the agent. ` +
|
|
4415
|
+
`Treat this as the immediate context to act on:\n` +
|
|
4416
|
+
`<selection>\n${capSelectionContext(sel.text)}\n</selection>`
|
|
4417
|
+
);
|
|
4418
|
+
} catch {
|
|
4419
|
+
// DB not ready — skip silently
|
|
4420
|
+
}
|
|
4421
|
+
return "";
|
|
4422
|
+
},
|
|
4423
|
+
);
|
|
4328
4424
|
|
|
4329
4425
|
// On the first message of a conversation, inject workspace inventory
|
|
4330
4426
|
// so the agent knows what files, skills, jobs, and custom agents exist.
|
|
4331
4427
|
// Templates can opt out via `skipFilesContext: true` when the inventory
|
|
4332
4428
|
// 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
|
-
|
|
4429
|
+
const filesContextPromise = withBgFallback(
|
|
4430
|
+
"files",
|
|
4431
|
+
8000,
|
|
4432
|
+
"",
|
|
4433
|
+
async (): Promise<string> => {
|
|
4434
|
+
let filesContext = "";
|
|
4435
|
+
if (options.skipFilesContext) return filesContext;
|
|
4436
|
+
if (history.length === 0) {
|
|
4437
|
+
try {
|
|
4438
|
+
const {
|
|
4439
|
+
resourceListAccessible,
|
|
4440
|
+
SHARED_OWNER,
|
|
4441
|
+
WORKSPACE_OWNER,
|
|
4442
|
+
resourceGet,
|
|
4443
|
+
} = await import("../resources/store.js");
|
|
4444
|
+
const {
|
|
4445
|
+
getResourceKind,
|
|
4446
|
+
parseCustomAgentProfile,
|
|
4447
|
+
parseRemoteAgentManifest,
|
|
4448
|
+
parseSkillMetadata,
|
|
4449
|
+
} = await import("../resources/metadata.js");
|
|
4450
|
+
const ownerEmail = getRequestUserEmail();
|
|
4451
|
+
const orgId = getRequestOrgId();
|
|
4452
|
+
if (!ownerEmail) throw new Error("no authenticated user");
|
|
4453
|
+
const allResources = await resourceListAccessible(
|
|
4454
|
+
ownerEmail,
|
|
4455
|
+
undefined,
|
|
4456
|
+
{ userEmail: ownerEmail, orgId },
|
|
4457
|
+
);
|
|
4358
4458
|
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4459
|
+
if (allResources.length > 0) {
|
|
4460
|
+
const fileLines: string[] = [];
|
|
4461
|
+
const skillLines: string[] = [];
|
|
4462
|
+
const agentLines: string[] = [];
|
|
4463
|
+
const jobLines: string[] = [];
|
|
4464
|
+
for (const r of allResources) {
|
|
4465
|
+
const scope =
|
|
4466
|
+
r.owner === WORKSPACE_OWNER
|
|
4467
|
+
? "workspace"
|
|
4468
|
+
: r.owner === SHARED_OWNER
|
|
4469
|
+
? "shared"
|
|
4470
|
+
: "personal";
|
|
4471
|
+
const kind = getResourceKind(r.path);
|
|
4472
|
+
if (kind === "file") {
|
|
4473
|
+
fileLines.push(` ${r.path} (${scope})`);
|
|
4474
|
+
continue;
|
|
4475
|
+
}
|
|
4376
4476
|
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4477
|
+
if (kind === "job") {
|
|
4478
|
+
jobLines.push(` ${r.path} (${scope})`);
|
|
4479
|
+
continue;
|
|
4480
|
+
}
|
|
4381
4481
|
|
|
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
|
-
|
|
4482
|
+
if (
|
|
4483
|
+
kind === "skill" ||
|
|
4484
|
+
kind === "agent" ||
|
|
4485
|
+
kind === "remote-agent"
|
|
4486
|
+
) {
|
|
4487
|
+
const full = await resourceGet(r.id, {
|
|
4488
|
+
userEmail: ownerEmail,
|
|
4489
|
+
orgId,
|
|
4490
|
+
});
|
|
4491
|
+
if (!full) continue;
|
|
4492
|
+
if (kind === "skill") {
|
|
4493
|
+
const skill = parseSkillMetadata(full.content, r.path);
|
|
4494
|
+
skillLines.push(
|
|
4495
|
+
` ${skill?.name || r.path} — ${compactInventoryDescription(skill?.description || r.path)} (${scope}, ${r.path})`,
|
|
4496
|
+
);
|
|
4497
|
+
} else if (kind === "agent") {
|
|
4498
|
+
const agent = parseCustomAgentProfile(full.content, r.path);
|
|
4499
|
+
agentLines.push(
|
|
4500
|
+
` ${agent?.name || r.path} — ${compactInventoryDescription(agent?.description || "Custom workspace agent")} (${scope}, ${r.path}${agent?.model ? `, model: ${agent.model}` : ""})`,
|
|
4501
|
+
);
|
|
4502
|
+
} else {
|
|
4503
|
+
const agent = parseRemoteAgentManifest(
|
|
4504
|
+
full.content,
|
|
4505
|
+
r.path,
|
|
4506
|
+
);
|
|
4507
|
+
agentLines.push(
|
|
4508
|
+
` ${agent?.name || r.path} — ${compactInventoryDescription(agent?.description || "Connected A2A agent")} (${scope}, remote via ${r.path})`,
|
|
4509
|
+
);
|
|
4510
|
+
}
|
|
4407
4511
|
}
|
|
4408
4512
|
}
|
|
4513
|
+
const blocks: string[] = [];
|
|
4514
|
+
if (fileLines.length > 0) {
|
|
4515
|
+
const lines = limitInventoryLines(fileLines, "files");
|
|
4516
|
+
blocks.push(
|
|
4517
|
+
`<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>`,
|
|
4518
|
+
);
|
|
4519
|
+
}
|
|
4520
|
+
if (skillLines.length > 0) {
|
|
4521
|
+
const lines = limitInventoryLines(skillLines, "skills");
|
|
4522
|
+
blocks.push(
|
|
4523
|
+
`<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>`,
|
|
4524
|
+
);
|
|
4525
|
+
}
|
|
4526
|
+
if (agentLines.length > 0) {
|
|
4527
|
+
const lines = limitInventoryLines(agentLines, "agents");
|
|
4528
|
+
blocks.push(
|
|
4529
|
+
`<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>`,
|
|
4530
|
+
);
|
|
4531
|
+
}
|
|
4532
|
+
if (jobLines.length > 0) {
|
|
4533
|
+
const lines = limitInventoryLines(jobLines, "jobs");
|
|
4534
|
+
blocks.push(
|
|
4535
|
+
`<available-jobs>\nScheduled tasks in the workspace:\n${lines.join("\n")}\n</available-jobs>`,
|
|
4536
|
+
);
|
|
4537
|
+
}
|
|
4538
|
+
filesContext =
|
|
4539
|
+
blocks.length > 0 ? `\n\n${blocks.join("\n\n")}` : "";
|
|
4409
4540
|
}
|
|
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")}` : "";
|
|
4541
|
+
} catch {
|
|
4542
|
+
// Resources not available — skip silently
|
|
4437
4543
|
}
|
|
4438
|
-
} catch {
|
|
4439
|
-
// Resources not available — skip silently
|
|
4440
4544
|
}
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4545
|
+
return filesContext;
|
|
4546
|
+
},
|
|
4547
|
+
);
|
|
4444
4548
|
|
|
4445
4549
|
// DIAGNOSTIC-ONLY: the background worker freezes between `env_config` and
|
|
4446
4550
|
// `context_all` when one of the parallel pre-send promises hangs (observed
|
|
@@ -4468,6 +4572,7 @@ export function createProductionAgentHandler(
|
|
|
4468
4572
|
.finally(() => __psMark("enrich"))
|
|
4469
4573
|
.catch(() => {});
|
|
4470
4574
|
|
|
4575
|
+
bgLog("presend:awaiting Promise.all");
|
|
4471
4576
|
const [
|
|
4472
4577
|
systemPrompt,
|
|
4473
4578
|
screenBlock,
|
|
@@ -4489,6 +4594,7 @@ export function createProductionAgentHandler(
|
|
|
4489
4594
|
// DIAGNOSTIC-ONLY: all parallel context gathering (system prompt, screen,
|
|
4490
4595
|
// files, loop settings, enriched message) resolved.
|
|
4491
4596
|
workerStep("context_all");
|
|
4597
|
+
bgLog("context_all");
|
|
4492
4598
|
|
|
4493
4599
|
if (systemPromptError) {
|
|
4494
4600
|
setResponseHeader(event, "Content-Type", "text/event-stream");
|
|
@@ -4519,6 +4625,7 @@ export function createProductionAgentHandler(
|
|
|
4519
4625
|
setupMark("actions");
|
|
4520
4626
|
// DIAGNOSTIC-ONLY: action/tool resolution + engine-tool filtering finished.
|
|
4521
4627
|
workerStep("action_tool_setup");
|
|
4628
|
+
bgLog("action_tool_setup");
|
|
4522
4629
|
const requestSystemPrompt =
|
|
4523
4630
|
requestMode === "plan"
|
|
4524
4631
|
? `${systemPrompt}\n\n${PLAN_MODE_SYSTEM_PROMPT}`
|
|
@@ -4633,6 +4740,7 @@ export function createProductionAgentHandler(
|
|
|
4633
4740
|
// DIAGNOSTIC-ONLY: owner/thread resolution + runId/effectiveThreadId +
|
|
4634
4741
|
// chained-continuation thread fetch finished.
|
|
4635
4742
|
workerStep("owner_thread");
|
|
4743
|
+
bgLog("owner_thread");
|
|
4636
4744
|
|
|
4637
4745
|
// Persist the user's turn exactly once. The foreground POST does this
|
|
4638
4746
|
// before dispatching; the background worker must NOT repeat it (it re-enters
|
|
@@ -5025,7 +5133,9 @@ export function createProductionAgentHandler(
|
|
|
5025
5133
|
dispatchMode: "background",
|
|
5026
5134
|
}).catch(() => {});
|
|
5027
5135
|
}
|
|
5136
|
+
bgLog("claim:before");
|
|
5028
5137
|
const won = await claimBackgroundRun(runId);
|
|
5138
|
+
bgLog("claim:after won=" + won);
|
|
5029
5139
|
if (!won) {
|
|
5030
5140
|
// Already claimed by an earlier delivery — return a benign ack so
|
|
5031
5141
|
// Netlify doesn't retry a successful handoff.
|
|
@@ -5050,11 +5160,13 @@ export function createProductionAgentHandler(
|
|
|
5050
5160
|
// DIAGNOSTIC-ONLY: last stage before startRun fires. A worker that reaches
|
|
5051
5161
|
// prestart but never workerStarted is hanging inside startRun itself.
|
|
5052
5162
|
workerStep("prestart");
|
|
5163
|
+
bgLog("prestart");
|
|
5053
5164
|
const setupDetail =
|
|
5054
5165
|
Object.entries(setupMarks)
|
|
5055
5166
|
.map(([k, v]) => `${k}=${v}`)
|
|
5056
5167
|
.join(" ") + ` total=${Date.now() - setupT0}`;
|
|
5057
5168
|
|
|
5169
|
+
bgLog("startRun:invoked");
|
|
5058
5170
|
startRun(
|
|
5059
5171
|
runId,
|
|
5060
5172
|
effectiveThreadId,
|