@agent-native/core 0.205.0 → 0.206.0-nightly-20261009011246
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a2a/client.d.ts +2 -2
- package/dist/agent/engine/anthropic-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.js +23 -8
- package/dist/agent/engine/error-telemetry.d.ts +5 -0
- package/dist/agent/engine/error-telemetry.js +41 -0
- package/dist/agent/engine/registry.d.ts +1 -6
- package/dist/agent/engine/registry.js +2 -31
- package/dist/agent/model-config.d.ts +26 -14
- package/dist/agent/model-config.js +131 -8
- package/dist/agent/model-version.d.ts +19 -0
- package/dist/agent/model-version.js +153 -0
- package/dist/agent/production-agent.d.ts +1 -0
- package/dist/agent/production-agent.js +24 -6
- package/dist/agent/provider-model-selection.js +6 -4
- package/dist/agent/run-manager.js +5 -11
- package/dist/agent/thread-data-builder.js +14 -6
- package/dist/authorization/check-action.js +8 -4
- package/dist/chat-threads/store.js +22 -9
- package/dist/client/analytics.d.ts +1 -0
- package/dist/client/analytics.js +10 -2
- package/dist/client/chat/runtime.js +18 -0
- package/dist/client/chat-model-groups.d.ts +3 -0
- package/dist/client/error-format.js +10 -7
- package/dist/client/onboarding/use-onboarding.d.ts +12 -0
- package/dist/client/onboarding/use-onboarding.js +491 -4
- package/dist/client/session-replay-privacy.d.ts +8 -0
- package/dist/client/session-replay-privacy.js +8 -0
- package/dist/client/session-replay.d.ts +1 -0
- package/dist/client/session-replay.js +18 -9
- package/dist/client/sse-event-processor.js +10 -6
- package/dist/client/use-chat-models.d.ts +5 -0
- package/dist/client/use-chat-models.js +51 -5
- package/dist/client/use-session.d.ts +8 -1
- package/dist/client/use-session.js +20 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +353 -3
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/jobs/backfill-run-history-ownership.d.ts +12 -0
- package/dist/jobs/backfill-run-history-ownership.js +87 -0
- package/dist/jobs/background-automation-runner.js +3 -9
- package/dist/jobs/remote-execution.js +3 -9
- package/dist/jobs/run-history-ownership.d.ts +5 -0
- package/dist/jobs/run-history-ownership.js +16 -0
- package/dist/jobs/run-now.js +2 -3
- package/dist/localization/agent-resources-messages.d.ts +3 -0
- package/dist/localization/agent-resources-messages.js +33 -0
- package/dist/mcp/actions/create-org-service-token.js +5 -7
- package/dist/mcp/build-server.d.ts +23 -18
- package/dist/mcp/build-server.js +220 -212
- package/dist/mcp/connect-route.d.ts +11 -2
- package/dist/mcp/connect-route.js +34 -24
- package/dist/mcp/connect-store.d.ts +9 -13
- package/dist/mcp/connect-store.js +11 -24
- package/dist/mcp/credential-membership.js +17 -6
- package/dist/mcp/oauth-route.d.ts +11 -1
- package/dist/mcp/oauth-route.js +28 -6
- package/dist/mcp/oauth-token.d.ts +12 -2
- package/dist/mcp/oauth-token.js +15 -4
- package/dist/mcp/server.js +9 -5
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/posthog-ai.d.ts +1 -0
- package/dist/observability/posthog-ai.js +20 -8
- package/dist/observability/routes.d.ts +6 -6
- package/dist/observability/traces.js +61 -39
- package/dist/org/app-roles.d.ts +8 -1
- package/dist/org/app-roles.js +29 -9
- package/dist/org/context.js +24 -8
- package/dist/org/index.d.ts +2 -2
- package/dist/org/workspace-app-identity.d.ts +1 -0
- package/dist/org/workspace-app-identity.js +25 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +3 -3
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/scripts/agent-engines/list-agent-engines.js +1 -0
- package/dist/server/action-routes.js +8 -1
- package/dist/server/agent-chat-plugin.js +1 -0
- package/dist/server/agent-engine-default-model-route.d.ts +1 -1
- package/dist/server/auth.js +33 -17
- package/dist/server/bearer-credential-refusal.d.ts +23 -0
- package/dist/server/bearer-credential-refusal.js +67 -0
- package/dist/server/better-auth-instance.js +13 -6
- package/dist/server/builder-browser.d.ts +2 -0
- package/dist/server/builder-browser.js +16 -3
- package/dist/server/capture-error.d.ts +1 -0
- package/dist/server/capture-error.js +28 -4
- package/dist/server/complete-text.js +16 -3
- package/dist/server/core-routes-plugin.js +2 -0
- package/dist/server/dev-action-bridge.js +7 -1
- package/dist/server/google-oauth.js +6 -3
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/recap-image-route.js +4 -0
- package/dist/server/request-context.d.ts +16 -0
- package/dist/server/request-context.js +17 -0
- package/dist/shared/agent-chat-context.d.ts +1 -0
- package/dist/shared/agent-chat-context.js +26 -0
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/index.js +1 -1
- package/dist/shared/reasoning-effort.js +10 -1
- package/dist/shared/ssr-session-bootstrap.d.ts +7 -0
- package/dist/shared/ssr-session-bootstrap.js +21 -0
- package/dist/sharing/access.js +86 -5
- package/dist/templates/builder-code-starter/server/routes/index.get.ts +2 -0
- package/dist/triggers/routes.js +2 -1
- package/docs/content/automation-connectors.mdx +5 -3
- package/docs/content/automations.mdx +78 -49
- package/docs/content/observability.mdx +16 -5
- package/docs/content/organizations-teams-permissions.mdx +44 -2
- package/docs/content/recurring-jobs.mdx +4 -8
- package/docs/content/template-forms.mdx +6 -0
- package/package.json +5 -4
- package/src/templates/builder-code-starter/server/routes/index.get.ts +2 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { credentialStateTrackingProperties } from "../agent/engine/credential-state.js";
|
|
2
|
+
import { runErrorTelemetryProperties } from "../agent/engine/error-telemetry.js";
|
|
2
3
|
import { isToolDoneFailure } from "../agent/tool-done-error.js";
|
|
3
4
|
import { captureError } from "../server/capture-error.js";
|
|
4
5
|
import { getRequestContext, getRequestOrgId, } from "../server/request-context.js";
|
|
@@ -9,6 +10,28 @@ export { redactSensitiveFields } from "./trace-redaction.js";
|
|
|
9
10
|
import { recordAgentToolCall, recordGenAiChat, recordTraceWriteFailure, } from "./metrics.js";
|
|
10
11
|
import { endAgentSpan, startAgentSpan, withAgentSpanContext, } from "./tracing.js";
|
|
11
12
|
import { trackingIdentityProperties } from "./tracking-identity.js";
|
|
13
|
+
function endRunTelemetrySpan(span, { errorCode, ...result }) {
|
|
14
|
+
if (result.status !== "error") {
|
|
15
|
+
endAgentSpan(span, result);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const code = errorCode ?? result.attributes?.["agent.terminal_code"];
|
|
19
|
+
const { error_code, error_cause } = runErrorTelemetryProperties(typeof code === "string" ? code : undefined, result.errorMessage);
|
|
20
|
+
endAgentSpan(span, {
|
|
21
|
+
...result,
|
|
22
|
+
errorMessage: `Agent run failed (${error_code})`,
|
|
23
|
+
attributes: {
|
|
24
|
+
...result.attributes,
|
|
25
|
+
"agent.error_code": error_code,
|
|
26
|
+
"agent.error_cause": error_cause,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function endToolTelemetrySpan(span, result) {
|
|
31
|
+
endAgentSpan(span, result.status === "error"
|
|
32
|
+
? { ...result, errorMessage: "Tool call failed" }
|
|
33
|
+
: result);
|
|
34
|
+
}
|
|
12
35
|
function spanId() {
|
|
13
36
|
return `span-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
14
37
|
}
|
|
@@ -136,6 +159,8 @@ function aiTraceMetadataProperties(metadata) {
|
|
|
136
159
|
return {};
|
|
137
160
|
const properties = {};
|
|
138
161
|
for (const [key, value] of Object.entries(metadata)) {
|
|
162
|
+
if (key === "error_message" || key === "errorMessage")
|
|
163
|
+
continue;
|
|
139
164
|
if (value === undefined || value === null)
|
|
140
165
|
continue;
|
|
141
166
|
const scalar = typeof value === "string"
|
|
@@ -234,7 +259,7 @@ function emitLlmGenerationTrackingEvent(args) {
|
|
|
234
259
|
$ai_is_error: args.status === "error",
|
|
235
260
|
$ai_error: resolveAiError(args.status === "error", toAiErrorDetail(error, {
|
|
236
261
|
state: args.terminalOutcome?.state,
|
|
237
|
-
code: terminalCode,
|
|
262
|
+
code: terminalCode ?? args.errorCode,
|
|
238
263
|
retryable: terminalRetryable,
|
|
239
264
|
})),
|
|
240
265
|
$ai_error_type: args.status === "error" ? (terminalCode ?? "llm_error") : undefined,
|
|
@@ -267,8 +292,6 @@ function emitLlmGenerationTrackingEvent(args) {
|
|
|
267
292
|
properties.experiment_variant = args.experimentAssignments[0].variantId;
|
|
268
293
|
}
|
|
269
294
|
}
|
|
270
|
-
if (error)
|
|
271
|
-
properties.error_message = error;
|
|
272
295
|
for (const key of Object.keys(properties)) {
|
|
273
296
|
if (properties[key] === undefined)
|
|
274
297
|
delete properties[key];
|
|
@@ -435,7 +458,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
435
458
|
return;
|
|
436
459
|
if (entry.endResult) {
|
|
437
460
|
entry.ended = true;
|
|
438
|
-
|
|
461
|
+
endRunTelemetrySpan(span, entry.endResult);
|
|
439
462
|
}
|
|
440
463
|
else {
|
|
441
464
|
entry.span = span;
|
|
@@ -458,7 +481,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
458
481
|
return;
|
|
459
482
|
entry.ended = true;
|
|
460
483
|
openOtelModelSpans.delete(entry.span);
|
|
461
|
-
|
|
484
|
+
endRunTelemetrySpan(entry.span, safeResult);
|
|
462
485
|
};
|
|
463
486
|
const recordPreStreamModelFailure = (start, end, input, errorMessage) => {
|
|
464
487
|
const tripIndex = modelRoundTrips.length;
|
|
@@ -479,7 +502,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
479
502
|
endTime: end,
|
|
480
503
|
});
|
|
481
504
|
};
|
|
482
|
-
const finishAwaitingOtelModelSpans = (finalErrorMessage = null, markUnresolvedFailed = false) => {
|
|
505
|
+
const finishAwaitingOtelModelSpans = (finalErrorMessage = null, markUnresolvedFailed = false, finalErrorCode) => {
|
|
483
506
|
for (const tripIndex of modelSpansAwaitingFinalError) {
|
|
484
507
|
const trip = modelRoundTrips[tripIndex];
|
|
485
508
|
const modelCallFailed = trip?.stopReason === "error" ||
|
|
@@ -500,6 +523,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
500
523
|
finishOtelModelSpan(tripIndex, {
|
|
501
524
|
status: modelCallFailed ? "error" : "success",
|
|
502
525
|
errorMessage: modelErrorMessage,
|
|
526
|
+
errorCode: finalErrorCode,
|
|
503
527
|
attributes: modelSpanAttributes(tripIndex),
|
|
504
528
|
endTime: modelRoundTrips[tripIndex]?.end,
|
|
505
529
|
});
|
|
@@ -516,6 +540,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
516
540
|
let usage;
|
|
517
541
|
let runStatus = "success";
|
|
518
542
|
let errorMessage = null;
|
|
543
|
+
let errorCode;
|
|
519
544
|
let runMetadata = opts.metadata ?? null;
|
|
520
545
|
let terminalOutcome;
|
|
521
546
|
let errorHttpStatus;
|
|
@@ -566,6 +591,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
566
591
|
finishAwaitingOtelModelSpans(null, event.type === "clear");
|
|
567
592
|
runStatus = "success";
|
|
568
593
|
errorMessage = null;
|
|
594
|
+
errorCode = undefined;
|
|
569
595
|
cutOffReason = null;
|
|
570
596
|
}
|
|
571
597
|
else if (event.type === "auto_continue") {
|
|
@@ -579,6 +605,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
579
605
|
else if (event.type === "error") {
|
|
580
606
|
runStatus = "error";
|
|
581
607
|
errorMessage = redactCapturedError(event.error);
|
|
608
|
+
errorCode = event.errorCode;
|
|
582
609
|
}
|
|
583
610
|
else if (event.type === "tripwire") {
|
|
584
611
|
runStatus = "error";
|
|
@@ -652,7 +679,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
652
679
|
if (!span)
|
|
653
680
|
return;
|
|
654
681
|
if (entry.endResult) {
|
|
655
|
-
|
|
682
|
+
endToolTelemetrySpan(span, {
|
|
656
683
|
status: entry.endResult.status,
|
|
657
684
|
errorMessage: entry.endResult.errorMessage,
|
|
658
685
|
});
|
|
@@ -705,8 +732,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
705
732
|
}
|
|
706
733
|
else
|
|
707
734
|
successfulTools++;
|
|
708
|
-
//
|
|
709
|
-
// on; the persisted span always keeps at least the signature.
|
|
735
|
+
// Local spans keep the error text; tracking keeps only its class.
|
|
710
736
|
const toolErrorMessage = isError && config.captureToolResults
|
|
711
737
|
? sanitizeToolErrorMessage(event.result)
|
|
712
738
|
: null;
|
|
@@ -726,7 +752,6 @@ export async function instrumentAgentLoop(opts) {
|
|
|
726
752
|
: explicitError
|
|
727
753
|
? "tool_error"
|
|
728
754
|
: "legacy_inferred_error",
|
|
729
|
-
error_message: toolErrorMessage ?? undefined,
|
|
730
755
|
});
|
|
731
756
|
}
|
|
732
757
|
recordAgentToolCall({
|
|
@@ -743,7 +768,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
743
768
|
};
|
|
744
769
|
if (pending?.otelSpan) {
|
|
745
770
|
openOtelToolSpans.delete(pending.otelSpan);
|
|
746
|
-
|
|
771
|
+
endToolTelemetrySpan(pending.otelSpan, {
|
|
747
772
|
status: otelEndResult.status,
|
|
748
773
|
errorMessage: otelEndResult.errorMessage,
|
|
749
774
|
attributes: { "gen_ai.tool.name": event.tool },
|
|
@@ -839,6 +864,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
839
864
|
? (err?.message ?? String(err))
|
|
840
865
|
: classification.errorMessage);
|
|
841
866
|
errorHttpStatus = httpStatusFromError(err);
|
|
867
|
+
errorCode = typeof err?.errorCode === "string" ? err.errorCode : undefined;
|
|
842
868
|
const errorMetadata = classification?.metadata ?? null;
|
|
843
869
|
runMetadata =
|
|
844
870
|
runMetadata || errorMetadata
|
|
@@ -899,12 +925,11 @@ export async function instrumentAgentLoop(opts) {
|
|
|
899
925
|
duration_ms: Math.max(0, runEnd - pending.startMs),
|
|
900
926
|
status: "error",
|
|
901
927
|
error_class: "interrupted",
|
|
902
|
-
error_message: capturedInterruptedMessage ?? undefined,
|
|
903
928
|
});
|
|
904
929
|
}
|
|
905
930
|
if (pending.otelSpan) {
|
|
906
931
|
openOtelToolSpans.delete(pending.otelSpan);
|
|
907
|
-
|
|
932
|
+
endToolTelemetrySpan(pending.otelSpan, {
|
|
908
933
|
status: "error",
|
|
909
934
|
errorMessage: capturedInterruptedMessage,
|
|
910
935
|
attributes: { "gen_ai.tool.name": pending.toolName },
|
|
@@ -1149,6 +1174,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
1149
1174
|
status: generationStatus,
|
|
1150
1175
|
errorMessage: capturedGenerationError,
|
|
1151
1176
|
httpStatus: generationStatus === "error" ? errorHttpStatus : HTTP_STATUS_OK,
|
|
1177
|
+
errorCode: generationStatus === "error" ? errorCode : undefined,
|
|
1152
1178
|
toolCalls: generation.toolSpans.length,
|
|
1153
1179
|
successfulTools: generation.toolSpans.filter((span) => span.status === "success").length,
|
|
1154
1180
|
failedTools: generation.toolSpans.filter((span) => span.status === "error").length,
|
|
@@ -1196,7 +1222,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
1196
1222
|
code: effectiveTerminalOutcome?.state === "failed" ||
|
|
1197
1223
|
effectiveTerminalOutcome?.state === "input_required"
|
|
1198
1224
|
? effectiveTerminalOutcome.code
|
|
1199
|
-
:
|
|
1225
|
+
: errorCode,
|
|
1200
1226
|
retryable: effectiveTerminalOutcome?.state === "failed"
|
|
1201
1227
|
? effectiveTerminalOutcome.retryable
|
|
1202
1228
|
: undefined,
|
|
@@ -1263,24 +1289,16 @@ export async function instrumentAgentLoop(opts) {
|
|
|
1263
1289
|
},
|
|
1264
1290
|
});
|
|
1265
1291
|
for (const span of emittedToolSpans) {
|
|
1266
|
-
const toolErrorMessage = span.status === "error" &&
|
|
1267
|
-
span.errorMessage &&
|
|
1268
|
-
config.captureToolResults
|
|
1269
|
-
? sanitizeToolErrorMessage(span.errorMessage)
|
|
1270
|
-
: undefined;
|
|
1271
1292
|
const toolErrorDetail = span.status !== "error"
|
|
1272
1293
|
? undefined
|
|
1273
|
-
:
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
? (toolErrorMessage ??
|
|
1282
|
-
span.metadata?.output)
|
|
1283
|
-
: "[tool result withheld: captureToolResults is off for this app]";
|
|
1294
|
+
: toAiErrorDetail(undefined, {
|
|
1295
|
+
code: toolSpanErrorClass.get(span.id) ?? "tool_error",
|
|
1296
|
+
});
|
|
1297
|
+
const toolOutputState = span.status === "error"
|
|
1298
|
+
? "[tool error message omitted from telemetry]"
|
|
1299
|
+
: config.captureToolResults
|
|
1300
|
+
? span.metadata?.output
|
|
1301
|
+
: "[tool result withheld: captureToolResults is off for this app]";
|
|
1284
1302
|
const requestingGeneration = toolSpanRoundTrip.get(span.id);
|
|
1285
1303
|
emitAiSpanEvent({
|
|
1286
1304
|
runId,
|
|
@@ -1329,6 +1347,10 @@ export async function instrumentAgentLoop(opts) {
|
|
|
1329
1347
|
createdAt: runStart,
|
|
1330
1348
|
};
|
|
1331
1349
|
writeTraceData(spans, summary, runId, config).catch((error) => reportTraceWriteFailure(runId, "write", error));
|
|
1350
|
+
const runTerminalCode = effectiveTerminalOutcome?.state === "failed" ||
|
|
1351
|
+
effectiveTerminalOutcome?.state === "input_required"
|
|
1352
|
+
? effectiveTerminalOutcome.code
|
|
1353
|
+
: errorCode;
|
|
1332
1354
|
try {
|
|
1333
1355
|
// Metrics are unsampled and independent of trace export.
|
|
1334
1356
|
for (const [tripIndex, trip] of modelRoundTrips.entries()) {
|
|
@@ -1365,6 +1387,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
1365
1387
|
finishOtelModelSpan(interruptedModelRoundTrip, {
|
|
1366
1388
|
status: "error",
|
|
1367
1389
|
errorMessage: errorMessage ?? "Model stream interrupted before completion.",
|
|
1390
|
+
errorCode: runTerminalCode,
|
|
1368
1391
|
attributes: modelSpanAttributes(interruptedModelRoundTrip),
|
|
1369
1392
|
endTime: runEnd,
|
|
1370
1393
|
});
|
|
@@ -1379,7 +1402,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
1379
1402
|
});
|
|
1380
1403
|
}
|
|
1381
1404
|
}
|
|
1382
|
-
finishAwaitingOtelModelSpans(errorMessage);
|
|
1405
|
+
finishAwaitingOtelModelSpans(errorMessage, false, runTerminalCode);
|
|
1383
1406
|
await Promise.all([...pendingOtelModelSpans.values()].map((entry) => entry.spanPromise));
|
|
1384
1407
|
if (usage && modelRoundTrips.length === 0) {
|
|
1385
1408
|
const aggregateLlmSpan = await withAgentSpanContext(otelRunSpan, () => startAgentSpan(`chat ${loopOpts.model}`, {
|
|
@@ -1387,9 +1410,10 @@ export async function instrumentAgentLoop(opts) {
|
|
|
1387
1410
|
"gen_ai.provider.name": engineName,
|
|
1388
1411
|
"gen_ai.request.model": loopOpts.model,
|
|
1389
1412
|
}, otelRunSpan));
|
|
1390
|
-
|
|
1413
|
+
endRunTelemetrySpan(aggregateLlmSpan, {
|
|
1391
1414
|
status: runStatus,
|
|
1392
1415
|
errorMessage: errorMessage === null ? null : redactCapturedString(errorMessage),
|
|
1416
|
+
errorCode: runTerminalCode,
|
|
1393
1417
|
attributes: {
|
|
1394
1418
|
"gen_ai.response.model": usage.model,
|
|
1395
1419
|
...(usage.usageReported
|
|
@@ -1405,20 +1429,21 @@ export async function instrumentAgentLoop(opts) {
|
|
|
1405
1429
|
});
|
|
1406
1430
|
}
|
|
1407
1431
|
for (const modelSpan of openOtelModelSpans) {
|
|
1408
|
-
|
|
1432
|
+
endRunTelemetrySpan(modelSpan, {
|
|
1409
1433
|
status: "error",
|
|
1410
1434
|
errorMessage: "Agent run ended before model_stream completed.",
|
|
1435
|
+
errorCode: runTerminalCode,
|
|
1411
1436
|
});
|
|
1412
1437
|
}
|
|
1413
1438
|
openOtelModelSpans.clear();
|
|
1414
1439
|
for (const toolSpan of openOtelToolSpans) {
|
|
1415
|
-
|
|
1440
|
+
endToolTelemetrySpan(toolSpan, {
|
|
1416
1441
|
status: "error",
|
|
1417
1442
|
errorMessage: "Agent run ended before tool_done.",
|
|
1418
1443
|
});
|
|
1419
1444
|
}
|
|
1420
1445
|
openOtelToolSpans.clear();
|
|
1421
|
-
|
|
1446
|
+
endRunTelemetrySpan(otelRunSpan, {
|
|
1422
1447
|
status: runStatus,
|
|
1423
1448
|
errorMessage: redactCapturedError(errorMessage),
|
|
1424
1449
|
attributes: {
|
|
@@ -1437,10 +1462,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
1437
1462
|
: undefined,
|
|
1438
1463
|
"agent.cost_cents_x100": costCentsX100,
|
|
1439
1464
|
"agent.terminal_state": effectiveTerminalOutcome?.state,
|
|
1440
|
-
"agent.terminal_code":
|
|
1441
|
-
effectiveTerminalOutcome?.state === "input_required"
|
|
1442
|
-
? effectiveTerminalOutcome.code
|
|
1443
|
-
: undefined,
|
|
1465
|
+
"agent.terminal_code": runTerminalCode,
|
|
1444
1466
|
},
|
|
1445
1467
|
});
|
|
1446
1468
|
// coercion-ok: OTel export must never break the run.
|
package/dist/org/app-roles.d.ts
CHANGED
|
@@ -12,9 +12,12 @@ export type AppRoleLookup<R extends string = string> = {
|
|
|
12
12
|
status: "assigned";
|
|
13
13
|
roles: R[];
|
|
14
14
|
orgId: string;
|
|
15
|
+
orgRole?: string;
|
|
15
16
|
} | {
|
|
16
17
|
status: "unassigned";
|
|
17
18
|
orgId: string;
|
|
19
|
+
orgRole?: string;
|
|
20
|
+
hasAssignments?: boolean;
|
|
18
21
|
} | {
|
|
19
22
|
status: "not-a-member";
|
|
20
23
|
orgId: string;
|
|
@@ -27,6 +30,10 @@ export interface AppRoleCaller {
|
|
|
27
30
|
userEmail?: string | null;
|
|
28
31
|
orgId?: string | null;
|
|
29
32
|
}
|
|
33
|
+
export interface AppPermissionPolicy<R extends string> {
|
|
34
|
+
unassignedRole?: R;
|
|
35
|
+
allowOrgAdmins?: boolean;
|
|
36
|
+
}
|
|
30
37
|
export interface AppAuthorizationContext {
|
|
31
38
|
appId: string;
|
|
32
39
|
roles: string[];
|
|
@@ -44,7 +51,7 @@ export interface AppRoles<R extends string = string, P extends string = string>
|
|
|
44
51
|
}
|
|
45
52
|
export declare function getRegisteredAppRoles(appId: string): AppRolesDescriptor<string, string> | undefined;
|
|
46
53
|
export declare function listRegisteredAppRoles(): AppRolesDescriptor<string, string>[];
|
|
47
|
-
export declare function defineAppRoles<const R extends string, const P extends string = string>(descriptor: AppRolesDescriptor<R, P>): AppRoles<R, P>;
|
|
54
|
+
export declare function defineAppRoles<const R extends string, const P extends string = string>(descriptor: AppRolesDescriptor<R, P>, permissionPolicy?: AppPermissionPolicy<R>): AppRoles<R, P>;
|
|
48
55
|
export declare function resolveAppRole<R extends string>(descriptor: AppRolesDescriptor<R>, caller?: AppRoleCaller): Promise<AppRoleLookup<R>>;
|
|
49
56
|
export interface AppMemberRoleRow {
|
|
50
57
|
email: string;
|
package/dist/org/app-roles.js
CHANGED
|
@@ -18,13 +18,17 @@ function callerIdentity(caller) {
|
|
|
18
18
|
orgId: orgId && orgId.trim() ? orgId.trim() : null,
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
-
export function defineAppRoles(descriptor) {
|
|
21
|
+
export function defineAppRoles(descriptor, permissionPolicy = {}) {
|
|
22
22
|
const appId = descriptor.appId.trim();
|
|
23
23
|
if (!appId)
|
|
24
24
|
throw new Error("defineAppRoles: appId is required");
|
|
25
25
|
if (!descriptor.roles.length) {
|
|
26
26
|
throw new Error(`defineAppRoles(${appId}): at least one role is required`);
|
|
27
27
|
}
|
|
28
|
+
if (permissionPolicy.unassignedRole &&
|
|
29
|
+
!descriptor.roles.includes(permissionPolicy.unassignedRole)) {
|
|
30
|
+
throw new Error(`defineAppRoles(${appId}): unassignedRole is not in roles`);
|
|
31
|
+
}
|
|
28
32
|
if (descriptor.defaultRole &&
|
|
29
33
|
!descriptor.roles.includes(descriptor.defaultRole)) {
|
|
30
34
|
throw new Error(`defineAppRoles(${appId}): defaultRole "${descriptor.defaultRole}" is not in roles`);
|
|
@@ -61,11 +65,19 @@ export function defineAppRoles(descriptor) {
|
|
|
61
65
|
if (!identity.email || !identity.orgId)
|
|
62
66
|
throw new ForbiddenError(`Requires ${appId} permission ${permissions.join(" or ")}`);
|
|
63
67
|
const lookup = await resolve(caller);
|
|
64
|
-
if (lookup.status !== "assigned")
|
|
68
|
+
if (lookup.status !== "assigned" && lookup.status !== "unassigned")
|
|
65
69
|
throw new ForbiddenError(`Requires ${appId} permission ${permissions.join(" or ")}`);
|
|
70
|
+
if (permissionPolicy.allowOrgAdmins &&
|
|
71
|
+
(lookup.orgRole === "owner" || lookup.orgRole === "admin"))
|
|
72
|
+
return;
|
|
73
|
+
const roles = lookup.status === "assigned"
|
|
74
|
+
? lookup.roles
|
|
75
|
+
: permissionPolicy.unassignedRole && !lookup.hasAssignments
|
|
76
|
+
? [permissionPolicy.unassignedRole]
|
|
77
|
+
: [];
|
|
66
78
|
const overrides = await getAppPermissionOverrides(appId, identity.orgId);
|
|
67
79
|
const grants = permissions.flatMap((permission) => overrides[permission] ?? descriptor.permissions?.[permission] ?? []);
|
|
68
|
-
if (!
|
|
80
|
+
if (!roles.some((role) => grants.includes(role))) {
|
|
69
81
|
throw new ForbiddenError(`Requires ${appId} permission ${permissions.join(" or ")}`);
|
|
70
82
|
}
|
|
71
83
|
};
|
|
@@ -128,7 +140,7 @@ export async function resolveAppRole(descriptor, caller) {
|
|
|
128
140
|
let rows;
|
|
129
141
|
try {
|
|
130
142
|
rows = (await getDbExec().execute({
|
|
131
|
-
sql: `SELECT array_agg(r.role ORDER BY r.role) FILTER (WHERE r.role IS NOT NULL) AS roles,
|
|
143
|
+
sql: `SELECT m.role AS "orgRole", array_agg(r.role ORDER BY r.role) FILTER (WHERE r.role IS NOT NULL) AS roles,
|
|
132
144
|
o.identity_authority AS "identityAuthority",
|
|
133
145
|
o.identity_id AS "identityId"
|
|
134
146
|
FROM org_members m
|
|
@@ -139,7 +151,7 @@ export async function resolveAppRole(descriptor, caller) {
|
|
|
139
151
|
AND LOWER(r.email) = LOWER(m.email)
|
|
140
152
|
WHERE m.org_id = ? AND LOWER(m.email) = ?
|
|
141
153
|
AND m.federation_removal_pending_at IS NULL
|
|
142
|
-
GROUP BY o.identity_authority, o.identity_id`,
|
|
154
|
+
GROUP BY m.role, o.identity_authority, o.identity_id`,
|
|
143
155
|
args: [descriptor.appId, orgId, normalizeEmail(email)],
|
|
144
156
|
})).rows;
|
|
145
157
|
}
|
|
@@ -147,7 +159,7 @@ export async function resolveAppRole(descriptor, caller) {
|
|
|
147
159
|
if (!isMissingOrganizationTableError(error))
|
|
148
160
|
throw error;
|
|
149
161
|
rows = (await getDbExec().execute({
|
|
150
|
-
sql: `SELECT array_agg(r.role ORDER BY r.role) FILTER (WHERE r.role IS NOT NULL) AS roles
|
|
162
|
+
sql: `SELECT m.role AS "orgRole", array_agg(r.role ORDER BY r.role) FILTER (WHERE r.role IS NOT NULL) AS roles
|
|
151
163
|
FROM org_members m
|
|
152
164
|
LEFT JOIN app_member_roles r
|
|
153
165
|
ON r.org_id = m.org_id
|
|
@@ -155,7 +167,7 @@ export async function resolveAppRole(descriptor, caller) {
|
|
|
155
167
|
AND LOWER(r.email) = LOWER(m.email)
|
|
156
168
|
WHERE m.org_id = ? AND LOWER(m.email) = ?
|
|
157
169
|
AND m.federation_removal_pending_at IS NULL
|
|
158
|
-
GROUP BY m.org_id, m.email`,
|
|
170
|
+
GROUP BY m.org_id, m.email, m.role`,
|
|
159
171
|
args: [descriptor.appId, orgId, normalizeEmail(email)],
|
|
160
172
|
})).rows;
|
|
161
173
|
}
|
|
@@ -164,6 +176,7 @@ export async function resolveAppRole(descriptor, caller) {
|
|
|
164
176
|
return { status: "not-a-member", orgId };
|
|
165
177
|
const identityAuthority = String(row.identityAuthority ?? row.identity_authority ?? "").trim();
|
|
166
178
|
const identityId = String(row.identityId ?? row.identity_id ?? "").trim();
|
|
179
|
+
let orgRole = typeof row.orgRole === "string" ? row.orgRole : undefined;
|
|
167
180
|
if (identityAuthority || identityId) {
|
|
168
181
|
const { validateFederatedOrganizationMembershipForCurrentRequest } = await import("./federation.js");
|
|
169
182
|
const membership = await validateFederatedOrganizationMembershipForCurrentRequest({
|
|
@@ -172,15 +185,22 @@ export async function resolveAppRole(descriptor, caller) {
|
|
|
172
185
|
});
|
|
173
186
|
if (!membership.active)
|
|
174
187
|
return { status: "not-a-member", orgId };
|
|
188
|
+
orgRole = membership.role;
|
|
175
189
|
}
|
|
176
190
|
const raw = row.roles ?? row.appRoles ?? row.approles;
|
|
177
191
|
const roles = Array.isArray(raw) ? raw.map(String) : [];
|
|
178
192
|
const validRoles = [
|
|
179
193
|
...new Set(roles.filter((role) => descriptor.roles.includes(role))),
|
|
180
194
|
];
|
|
195
|
+
const membership = orgRole ? { orgRole } : {};
|
|
181
196
|
if (!validRoles.length)
|
|
182
|
-
return {
|
|
183
|
-
|
|
197
|
+
return {
|
|
198
|
+
status: "unassigned",
|
|
199
|
+
orgId,
|
|
200
|
+
...membership,
|
|
201
|
+
...(roles.length ? { hasAssignments: true } : {}),
|
|
202
|
+
};
|
|
203
|
+
return { status: "assigned", roles: validRoles, orgId, ...membership };
|
|
184
204
|
}
|
|
185
205
|
export async function listAppMemberRoles(appId, orgId) {
|
|
186
206
|
const { rows } = await getDbExec().execute({
|
package/dist/org/context.js
CHANGED
|
@@ -7,7 +7,7 @@ import { getDbExec, isTransientDatabaseError } from "../db/client.js";
|
|
|
7
7
|
import { isAnonymousWaitlistSessionEmail } from "../server/anonymous-identity.js";
|
|
8
8
|
import { crossSiteCookieAttrs, getSession } from "../server/auth.js";
|
|
9
9
|
import { shouldWriteFirstRunOnboardingEligibility } from "../server/first-run-onboarding-build-mode.js";
|
|
10
|
-
import { getRequestContext, hasExplicitPersonalOrgScope, } from "../server/request-context.js";
|
|
10
|
+
import { getVerifiedServiceIdentityFromEvent, getRequestContext, hasExplicitPersonalOrgScope, } from "../server/request-context.js";
|
|
11
11
|
import { getSetting } from "../settings/store.js";
|
|
12
12
|
import { getUserSetting } from "../settings/user-settings.js";
|
|
13
13
|
import { FIRST_RUN_ONBOARDING_ELIGIBLE_KEY } from "../shared/first-run-onboarding.js";
|
|
@@ -170,6 +170,7 @@ async function resolveOrgContextUncached(event) {
|
|
|
170
170
|
: null;
|
|
171
171
|
const sessionOrgRole = normalizeOrgRole(session.orgRole);
|
|
172
172
|
const requestContext = getRequestContext();
|
|
173
|
+
const verifiedServiceIdentity = getVerifiedServiceIdentityFromEvent(event);
|
|
173
174
|
const serviceRole = implicitServiceOrgRole({
|
|
174
175
|
email,
|
|
175
176
|
orgId: sessionOrgId,
|
|
@@ -177,13 +178,28 @@ async function resolveOrgContextUncached(event) {
|
|
|
177
178
|
? null
|
|
178
179
|
: (requestContext?.orgId ?? sessionOrgId),
|
|
179
180
|
});
|
|
180
|
-
if (serviceRole &&
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
181
|
+
if (serviceRole &&
|
|
182
|
+
sessionOrgId &&
|
|
183
|
+
verifiedServiceIdentity &&
|
|
184
|
+
verifiedServiceIdentity.userEmail.trim().toLowerCase() ===
|
|
185
|
+
email.trim().toLowerCase() &&
|
|
186
|
+
verifiedServiceIdentity.orgId === sessionOrgId) {
|
|
187
|
+
const organization = await getDbExec().execute({
|
|
188
|
+
sql: `SELECT identity_authority, identity_id
|
|
189
|
+
FROM organizations WHERE id = ? LIMIT 1`,
|
|
190
|
+
args: [sessionOrgId],
|
|
191
|
+
});
|
|
192
|
+
const metadata = organization.rows[0];
|
|
193
|
+
if (metadata &&
|
|
194
|
+
!String(metadata.identity_authority ?? "").trim() &&
|
|
195
|
+
!String(metadata.identity_id ?? "").trim()) {
|
|
196
|
+
return {
|
|
197
|
+
email,
|
|
198
|
+
orgId: sessionOrgId,
|
|
199
|
+
orgName: null,
|
|
200
|
+
role: serviceRole,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
187
203
|
}
|
|
188
204
|
const exec = getDbExec();
|
|
189
205
|
const activeOrgSettingPromise = loadActiveOrgSettingForEvent(event, email);
|
package/dist/org/index.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export { registerIdentityColumns } from "../identity/rekey.js";
|
|
|
23
23
|
export type { AppIdentityColumn, IdentityEmailChange, IdentityOffboard, IdentityOrgScope, } from "../identity/rekey.js";
|
|
24
24
|
export { claimWorkspaceAppForOrganization, isStandaloneDispatchRuntime, isWorkspaceAppAccessAllowed, } from "./workspace-app-access.js";
|
|
25
25
|
export { defineAppRoles, getRegisteredAppRoles, listRegisteredAppRoles, listAppMemberRoles, resolveAppRole, setAppMemberRole, setAppMemberRoles, applyInvitationAppRoles, getAppPermissionOverrides, setAppPermissionRoles, resolveAppAuthorizationContext, } from "./app-roles.js";
|
|
26
|
-
export type { AppRoles, AppRolesDescriptor, AppRoleCaller, AppRoleLookup, AppMemberRoleRow, AppAuthorizationContext, } from "./app-roles.js";
|
|
26
|
+
export type { AppRoles, AppRolesDescriptor, AppRoleCaller, AppPermissionPolicy, AppRoleLookup, AppMemberRoleRow, AppAuthorizationContext, } from "./app-roles.js";
|
|
27
27
|
export { ORG_MIGRATIONS } from "./migrations.js";
|
|
28
28
|
export { CROSS_APP_ORG_FEDERATION_FLAG, CROSS_APP_ORG_FEDERATION_SCOPE, } from "./feature-flags.js";
|
|
29
29
|
export declare const addFederatedOrganizationMember: typeof import("./federation.js").addFederatedOrganizationMember;
|
|
@@ -179,8 +179,8 @@ export declare const getMyOrgHandler: import("h3").EventHandlerWithFetch<import(
|
|
|
179
179
|
export declare const createOrgHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
180
180
|
name?: undefined;
|
|
181
181
|
success: boolean;
|
|
182
|
-
id?: undefined;
|
|
183
182
|
role?: undefined;
|
|
183
|
+
id?: undefined;
|
|
184
184
|
} | {
|
|
185
185
|
success?: undefined;
|
|
186
186
|
id: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveWorkspaceAccessAppId(targetAppId?: string): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getAppConfig } from "../app-config/index.js";
|
|
2
|
+
export function resolveWorkspaceAccessAppId(targetAppId) {
|
|
3
|
+
const app = getAppConfig().app;
|
|
4
|
+
const identities = [
|
|
5
|
+
app.id,
|
|
6
|
+
app.legacyId,
|
|
7
|
+
app.template,
|
|
8
|
+
app.slug,
|
|
9
|
+
app.packageName,
|
|
10
|
+
app.workspaceId,
|
|
11
|
+
]
|
|
12
|
+
.map((value) => value?.trim())
|
|
13
|
+
.filter(Boolean);
|
|
14
|
+
// Other apps still require their own workspace grant; only this deployment's
|
|
15
|
+
// aliases resolve to the identity used by its HTTP access gate.
|
|
16
|
+
const target = targetAppId?.trim();
|
|
17
|
+
if (target && !identities.includes(target))
|
|
18
|
+
return target;
|
|
19
|
+
const workspaceId = app.workspaceId?.trim();
|
|
20
|
+
if (workspaceId)
|
|
21
|
+
return workspaceId;
|
|
22
|
+
return identities.some((value) => value?.toLowerCase() === "dispatch")
|
|
23
|
+
? "dispatch"
|
|
24
|
+
: "";
|
|
25
|
+
}
|
|
@@ -75,9 +75,9 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
75
75
|
user: "user";
|
|
76
76
|
}>>;
|
|
77
77
|
}, z.core.$strip>>, {
|
|
78
|
+
deleted?: undefined;
|
|
78
79
|
message?: undefined;
|
|
79
80
|
id?: undefined;
|
|
80
|
-
found?: undefined;
|
|
81
81
|
providers: {
|
|
82
82
|
id: string;
|
|
83
83
|
label: string;
|
|
@@ -89,45 +89,45 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
89
89
|
}[];
|
|
90
90
|
count: number;
|
|
91
91
|
provider?: undefined;
|
|
92
|
-
|
|
92
|
+
found?: undefined;
|
|
93
93
|
registered?: undefined;
|
|
94
94
|
label?: undefined;
|
|
95
95
|
} | {
|
|
96
|
+
deleted?: undefined;
|
|
96
97
|
message?: undefined;
|
|
97
98
|
id?: undefined;
|
|
98
|
-
count?: undefined;
|
|
99
99
|
providers?: undefined;
|
|
100
|
+
count?: undefined;
|
|
100
101
|
found: boolean;
|
|
101
102
|
provider: import("../custom-registry.js").CustomProviderConfig;
|
|
102
|
-
deleted?: undefined;
|
|
103
103
|
registered?: undefined;
|
|
104
104
|
label?: undefined;
|
|
105
105
|
} | {
|
|
106
|
+
deleted?: undefined;
|
|
106
107
|
message?: undefined;
|
|
107
|
-
count?: undefined;
|
|
108
108
|
providers?: undefined;
|
|
109
|
+
count?: undefined;
|
|
109
110
|
provider?: undefined;
|
|
110
111
|
found: boolean;
|
|
111
112
|
id: string;
|
|
112
|
-
deleted?: undefined;
|
|
113
113
|
registered?: undefined;
|
|
114
114
|
label?: undefined;
|
|
115
115
|
} | {
|
|
116
116
|
message?: undefined;
|
|
117
|
-
count?: undefined;
|
|
118
|
-
found?: undefined;
|
|
119
117
|
providers?: undefined;
|
|
118
|
+
count?: undefined;
|
|
120
119
|
provider?: undefined;
|
|
120
|
+
found?: undefined;
|
|
121
121
|
deleted: boolean;
|
|
122
122
|
id: string;
|
|
123
123
|
registered?: undefined;
|
|
124
124
|
label?: undefined;
|
|
125
125
|
} | {
|
|
126
|
-
|
|
127
|
-
found?: undefined;
|
|
126
|
+
deleted?: undefined;
|
|
128
127
|
providers?: undefined;
|
|
128
|
+
count?: undefined;
|
|
129
129
|
provider?: undefined;
|
|
130
|
-
|
|
130
|
+
found?: undefined;
|
|
131
131
|
registered: boolean;
|
|
132
132
|
id: string;
|
|
133
133
|
label: string;
|
|
@@ -315,7 +315,6 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
315
315
|
provider?: undefined;
|
|
316
316
|
message?: undefined;
|
|
317
317
|
deleted?: undefined;
|
|
318
|
-
found?: undefined;
|
|
319
318
|
providers: {
|
|
320
319
|
id: string;
|
|
321
320
|
label: string;
|
|
@@ -326,6 +325,7 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
326
325
|
updatedAt: number;
|
|
327
326
|
}[];
|
|
328
327
|
count: number;
|
|
328
|
+
found?: undefined;
|
|
329
329
|
registered?: undefined;
|
|
330
330
|
label?: undefined;
|
|
331
331
|
} | {
|
|
@@ -352,8 +352,8 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
352
352
|
provider?: undefined;
|
|
353
353
|
count?: undefined;
|
|
354
354
|
message?: undefined;
|
|
355
|
-
found?: undefined;
|
|
356
355
|
providers?: undefined;
|
|
356
|
+
found?: undefined;
|
|
357
357
|
deleted: boolean;
|
|
358
358
|
id: string;
|
|
359
359
|
registered?: undefined;
|
|
@@ -362,8 +362,8 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
362
362
|
provider?: undefined;
|
|
363
363
|
count?: undefined;
|
|
364
364
|
deleted?: undefined;
|
|
365
|
-
found?: undefined;
|
|
366
365
|
providers?: undefined;
|
|
366
|
+
found?: undefined;
|
|
367
367
|
registered: boolean;
|
|
368
368
|
id: string;
|
|
369
369
|
label: string;
|