@agent-native/core 0.222.0 → 0.222.1-nightly-20261029092708
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/agent/production-agent.d.ts +2 -0
- package/dist/agent/production-agent.js +86 -6
- package/dist/agent/run-store.js +3 -2
- package/dist/app-config/agent.d.ts +9 -0
- package/dist/app-config/agent.js +31 -0
- package/dist/app-config/schema.d.ts +9 -0
- package/dist/app-config/store.js +48 -5
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +4 -4
- package/dist/jobs/run-history.js +3 -2
- package/dist/observability/hosted-model-experiment.d.ts +1 -1
- package/dist/observability/hosted-model-experiment.js +7 -1
- package/dist/observability/question-intake-experiment.d.ts +34 -0
- package/dist/observability/question-intake-experiment.js +114 -0
- package/dist/observability/traces.d.ts +4 -0
- package/dist/observability/traces.js +5 -4
- package/dist/observability/tracing.d.ts +1 -0
- package/dist/observability/tracing.js +14 -4
- package/dist/org/index.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
- package/dist/provider-api/actions/provider-api.d.ts +7 -7
- package/dist/server/agent-chat/browser-team-tools.js +2 -2
- package/dist/server/agent-engine-api-key-route.d.ts +3 -3
- package/dist/server/better-auth-instance.js +43 -0
- package/dist/server/builder-branch-files.d.ts +3 -1
- package/dist/server/builder-branch-files.js +74 -33
- package/dist/server/builder-branch-git.d.ts +1 -1
- package/dist/server/builder-branch-git.js +2 -0
- package/dist/server/builder-browser.d.ts +17 -0
- package/dist/server/builder-browser.js +33 -0
- package/dist/server/core-routes-plugin.js +42 -8
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/request-context.d.ts +8 -0
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/first-run-builder-credits-experiment.d.ts +1 -0
- package/dist/shared/first-run-builder-credits-experiment.js +19 -0
- package/dist/tracking/index.d.ts +1 -1
- package/dist/tracking/index.js +1 -1
- package/dist/tracking/registry.d.ts +7 -0
- package/dist/tracking/registry.js +84 -4
- package/package.json +4 -4
|
@@ -279,6 +279,7 @@ export declare function isUnavailableActionEntry(entry: ActionEntry | undefined)
|
|
|
279
279
|
*/
|
|
280
280
|
export declare function pinAdditionalActionsToTurn(resolved: Record<string, ActionEntry>, turnNames: readonly string[], registered: Record<string, ActionEntry>): Record<string, ActionEntry>;
|
|
281
281
|
export declare function filterActionsByAllowedNames(actions: Record<string, ActionEntry>, allowedActionNames: readonly string[]): Record<string, ActionEntry>;
|
|
282
|
+
export declare function filterActionsByExcludedNames(actions: Record<string, ActionEntry>, excludedActionNames: readonly string[]): Record<string, ActionEntry>;
|
|
282
283
|
export declare const PLAN_MODE_SYSTEM_PROMPT = "## Plan Mode Active\n\nYou are in Plan mode. This turn is for research, clarification, and a proposed approach only.\n\nHard rules:\n- Use only read-only tools. Do not edit files, write resources, run mutating bash commands, mutate SQL rows, navigate the UI, send notifications, create jobs, create tools, send messages or tasks to external agents, or change external systems.\n- If a needed detail is unclear, ask a concise clarifying question before proposing a plan.\n- When ready, present a concrete plan with the files/tools you expect to touch, the intended changes, validation steps, and notable risks.\n- Plan mode covers only this message. End with the plan and do not carry it out; the user starts implementation with the Implement button or their next message.";
|
|
283
284
|
export declare function isPlanModeToolCallAllowed(name: string, input: unknown, entry: ActionEntry): boolean;
|
|
284
285
|
export declare function createPlanModeActionRegistry(actions: Record<string, ActionEntry>): Record<string, ActionEntry>;
|
|
@@ -791,6 +792,7 @@ export declare function createRunSafeBoundaryGuards(input: {
|
|
|
791
792
|
beforeModelDispatch: BeforeModelDispatch;
|
|
792
793
|
beforeWrite: () => Promise<void>;
|
|
793
794
|
};
|
|
795
|
+
export declare function trackExperimentExposureAtDispatch(beforeModelDispatch: BeforeModelDispatch, trackExposure: () => void): BeforeModelDispatch;
|
|
794
796
|
export declare function confirmQueuedRunInterrupt(input: {
|
|
795
797
|
abort: () => Promise<boolean>;
|
|
796
798
|
defer: () => Promise<unknown>;
|
|
@@ -33,7 +33,8 @@ import { isMcpActionResult } from "../mcp-client/app-result.js";
|
|
|
33
33
|
import { extractMcpToolResultImages } from "../mcp-client/index.js";
|
|
34
34
|
import { isMcpToolAllowedForRequest } from "../mcp-client/visibility.js";
|
|
35
35
|
import { describeSettingsViewForAgent, SETTINGS_VIEW_STATE_KEY, } from "../navigation/settings-redirects.js";
|
|
36
|
-
import { isHostedModelExperimentEnabled, resolveHostedModelExperiment, } from "../observability/hosted-model-experiment.js";
|
|
36
|
+
import { isHostedModelExperimentEnabled, resolveHostedModelExperiment, singleNonHostedAssignment, } from "../observability/hosted-model-experiment.js";
|
|
37
|
+
import { InvalidQuestionIntakeExperimentAssignmentError, resolveQuestionIntakeExperimentAssignment, treatmentInstructionsForQuestionIntake, } from "../observability/question-intake-experiment.js";
|
|
37
38
|
import { isFirstPartyHostedAgentNative, shouldInferSentimentForTurn, } from "../observability/sentiment.js";
|
|
38
39
|
import { parseServiceIdentityEmail } from "../org/service-identity.js";
|
|
39
40
|
import { ServicePrincipalRefusedError } from "../org/service-principal-guard.js";
|
|
@@ -61,6 +62,7 @@ import { stripDiagnosticSnippets } from "../shared/diagnostic-snippet.js";
|
|
|
61
62
|
import { DurableAttachmentReferenceRequiredError, stripInlineBytes, } from "../shared/inline-bytes.js";
|
|
62
63
|
import { isReasoningEffort, normalizeReasoningEffortForRequest, stepDownReasoningEffort, } from "../shared/reasoning-effort.js";
|
|
63
64
|
import { SYNTHETIC_TRAFFIC_BETA_E2E, SYNTHETIC_TRAFFIC_HEADER, } from "../shared/test-traffic.js";
|
|
65
|
+
import { trackQuestionIntakeExposure } from "../tracking/index.js";
|
|
64
66
|
import { actionPreparationContinuationNote } from "./action-continuation-guidance.js";
|
|
65
67
|
import { agentErrorDetails } from "./action-error-details.js";
|
|
66
68
|
import { drainAgentWarnings, formatAgentWarningsForToolResult, } from "./action-warnings.js";
|
|
@@ -901,6 +903,12 @@ export function filterActionsByAllowedNames(actions, allowedActionNames) {
|
|
|
901
903
|
}
|
|
902
904
|
return filtered;
|
|
903
905
|
}
|
|
906
|
+
export function filterActionsByExcludedNames(actions, excludedActionNames) {
|
|
907
|
+
if (excludedActionNames.length === 0)
|
|
908
|
+
return actions;
|
|
909
|
+
const excludedNames = new Set(excludedActionNames);
|
|
910
|
+
return Object.fromEntries(Object.entries(actions).filter(([name]) => !excludedNames.has(name)));
|
|
911
|
+
}
|
|
904
912
|
export const PLAN_MODE_SYSTEM_PROMPT = `## Plan Mode Active
|
|
905
913
|
|
|
906
914
|
You are in Plan mode. This turn is for research, clarification, and a proposed approach only.
|
|
@@ -4996,6 +5004,16 @@ export function createRunSafeBoundaryGuards(input) {
|
|
|
4996
5004
|
},
|
|
4997
5005
|
};
|
|
4998
5006
|
}
|
|
5007
|
+
export function trackExperimentExposureAtDispatch(beforeModelDispatch, trackExposure) {
|
|
5008
|
+
let tracked = false;
|
|
5009
|
+
return async (dispatch) => beforeModelDispatch(() => {
|
|
5010
|
+
if (!tracked) {
|
|
5011
|
+
tracked = true;
|
|
5012
|
+
trackExposure();
|
|
5013
|
+
}
|
|
5014
|
+
return dispatch();
|
|
5015
|
+
});
|
|
5016
|
+
}
|
|
4999
5017
|
export async function confirmQueuedRunInterrupt(input) {
|
|
5000
5018
|
if (await input.abort())
|
|
5001
5019
|
return "aborted";
|
|
@@ -9646,7 +9664,7 @@ export function createProductionAgentHandler(options) {
|
|
|
9646
9664
|
}
|
|
9647
9665
|
filtered[name] = entry;
|
|
9648
9666
|
}
|
|
9649
|
-
return filtered;
|
|
9667
|
+
return filterActionsByExcludedNames(filtered, getRequestRunContext()?.excludedActionNames ?? []);
|
|
9650
9668
|
};
|
|
9651
9669
|
const getEngineTools = (actions) => actionsToEngineTools(getRequestActions(actions));
|
|
9652
9670
|
return defineEventHandler(async (event) => {
|
|
@@ -9970,6 +9988,40 @@ export function createProductionAgentHandler(options) {
|
|
|
9970
9988
|
});
|
|
9971
9989
|
const ownerEmail = await resolveAgentOwnerEmail(options, event);
|
|
9972
9990
|
const runRequestContext = getRequestContext();
|
|
9991
|
+
const questionIntakeConfig = getAppConfig().agent.questionIntake;
|
|
9992
|
+
let questionIntakeAssignment = null;
|
|
9993
|
+
try {
|
|
9994
|
+
// raw-sql-ok: the assignment store binds the authenticated user ID as a query argument.
|
|
9995
|
+
questionIntakeAssignment =
|
|
9996
|
+
await resolveQuestionIntakeExperimentAssignment(questionIntakeConfig, runRequestContext?.agentRunAnonymous
|
|
9997
|
+
? undefined
|
|
9998
|
+
: runRequestContext?.authUserId);
|
|
9999
|
+
}
|
|
10000
|
+
catch (error) {
|
|
10001
|
+
const invalidAssignment = error instanceof InvalidQuestionIntakeExperimentAssignmentError
|
|
10002
|
+
? error
|
|
10003
|
+
: null;
|
|
10004
|
+
console.error(invalidAssignment
|
|
10005
|
+
? "[agent-chat] question-intake assignment is invalid"
|
|
10006
|
+
: "[agent-chat] question-intake assignment lookup failed");
|
|
10007
|
+
setResponseStatus(event, invalidAssignment ? 500 : 503);
|
|
10008
|
+
return {
|
|
10009
|
+
error: "The agent run failed.",
|
|
10010
|
+
code: invalidAssignment?.code ?? "question_intake_assignment_unavailable",
|
|
10011
|
+
retryable: invalidAssignment === null,
|
|
10012
|
+
};
|
|
10013
|
+
}
|
|
10014
|
+
if (questionIntakeAssignment) {
|
|
10015
|
+
const runContext = ensureRequestRunContext();
|
|
10016
|
+
if (runContext) {
|
|
10017
|
+
runContext.questionIntakeExperimentAssignment =
|
|
10018
|
+
questionIntakeAssignment;
|
|
10019
|
+
runContext.excludedActionNames =
|
|
10020
|
+
questionIntakeAssignment.variantId === "no_questions"
|
|
10021
|
+
? (questionIntakeConfig?.questionToolNames ?? [])
|
|
10022
|
+
: [];
|
|
10023
|
+
}
|
|
10024
|
+
}
|
|
9973
10025
|
const turnInitiator = ownerEmail
|
|
9974
10026
|
? {
|
|
9975
10027
|
email: ownerEmail,
|
|
@@ -10858,7 +10910,15 @@ export function createProductionAgentHandler(options) {
|
|
|
10858
10910
|
let effectiveModel = model;
|
|
10859
10911
|
let modelSelectionSource = modelSelection.source;
|
|
10860
10912
|
const turnUsageLabel = normalizeUsageLabel(body.usageLabel);
|
|
10861
|
-
let experimentAssignments =
|
|
10913
|
+
let experimentAssignments = questionIntakeAssignment
|
|
10914
|
+
? [
|
|
10915
|
+
{
|
|
10916
|
+
experimentId: questionIntakeAssignment.experimentId,
|
|
10917
|
+
variantId: questionIntakeAssignment.variantId,
|
|
10918
|
+
},
|
|
10919
|
+
]
|
|
10920
|
+
: [];
|
|
10921
|
+
let singularExperimentAssignment;
|
|
10862
10922
|
try {
|
|
10863
10923
|
// A pinned request model suppresses the experiment's model override, so
|
|
10864
10924
|
// resolving would assign (and persist) a variant this turn never ran.
|
|
@@ -10873,7 +10933,11 @@ export function createProductionAgentHandler(options) {
|
|
|
10873
10933
|
: undefined,
|
|
10874
10934
|
assignments: expConfig.assignments,
|
|
10875
10935
|
});
|
|
10876
|
-
|
|
10936
|
+
singularExperimentAssignment = singleNonHostedAssignment(experimentSelection.assignments);
|
|
10937
|
+
experimentAssignments = [
|
|
10938
|
+
...experimentAssignments,
|
|
10939
|
+
...experimentSelection.assignments,
|
|
10940
|
+
];
|
|
10877
10941
|
if (experimentSelection.model) {
|
|
10878
10942
|
effectiveModel = normalizeModelForEngine(engine, experimentSelection.model);
|
|
10879
10943
|
modelSelectionSource = "experiment";
|
|
@@ -11013,7 +11077,10 @@ export function createProductionAgentHandler(options) {
|
|
|
11013
11077
|
const built = typeof options.systemPrompt === "function"
|
|
11014
11078
|
? await options.systemPrompt(event)
|
|
11015
11079
|
: options.systemPrompt;
|
|
11016
|
-
|
|
11080
|
+
const treatmentInstructions = treatmentInstructionsForQuestionIntake(questionIntakeConfig, questionIntakeAssignment);
|
|
11081
|
+
return treatmentInstructions
|
|
11082
|
+
? [built, treatmentInstructions].filter(Boolean).join("\n\n")
|
|
11083
|
+
: built;
|
|
11017
11084
|
}
|
|
11018
11085
|
catch (error) {
|
|
11019
11086
|
systemPromptError =
|
|
@@ -12254,6 +12321,7 @@ export function createProductionAgentHandler(options) {
|
|
|
12254
12321
|
if (backgroundSetupDeadlineExceeded?.()) {
|
|
12255
12322
|
return { ok: true, skipped: "setup-deadline" };
|
|
12256
12323
|
}
|
|
12324
|
+
// raw-sql-ok: run callback IDs and prompts reach static queries as bound arguments.
|
|
12257
12325
|
const startedRun = startRun(runId, effectiveThreadId, async (rawSend, signal, control) => {
|
|
12258
12326
|
if (backgroundSetupDeadlineExceeded?.())
|
|
12259
12327
|
return;
|
|
@@ -12327,11 +12395,22 @@ export function createProductionAgentHandler(options) {
|
|
|
12327
12395
|
}
|
|
12328
12396
|
return "none";
|
|
12329
12397
|
};
|
|
12330
|
-
const { beforeModelDispatch, beforeWrite } = createRunSafeBoundaryGuards({
|
|
12398
|
+
const { beforeModelDispatch: guardedBeforeModelDispatch, beforeWrite } = createRunSafeBoundaryGuards({
|
|
12331
12399
|
signal,
|
|
12332
12400
|
flushEvents: control.flushEvents,
|
|
12333
12401
|
interruptRequestedAtBoundary: interruptQueuedRunAtBoundary,
|
|
12334
12402
|
});
|
|
12403
|
+
const exposureAssignment = questionIntakeAssignment && !internalContinuation && !continueOf
|
|
12404
|
+
? questionIntakeAssignment
|
|
12405
|
+
: null;
|
|
12406
|
+
const beforeModelDispatch = exposureAssignment
|
|
12407
|
+
? trackExperimentExposureAtDispatch(guardedBeforeModelDispatch, () => trackQuestionIntakeExposure(exposureAssignment, {
|
|
12408
|
+
appId: options.appId ?? "app",
|
|
12409
|
+
...(threadId === undefined ? {} : { threadId }),
|
|
12410
|
+
turnId: effectiveTurnId,
|
|
12411
|
+
...(ownerEmail ? { userId: ownerEmail } : {}),
|
|
12412
|
+
}))
|
|
12413
|
+
: guardedBeforeModelDispatch;
|
|
12335
12414
|
if (selfChainBudget?.skipToBoundary) {
|
|
12336
12415
|
await recordRunDiagnostic(runId, RUN_DIAG_STAGE.workerSetupStep, `self_chain_budget_exhausted elapsed=${Date.now() - setupT0}ms`).catch(() => { });
|
|
12337
12416
|
send({ type: "auto_continue", reason: "run_timeout" });
|
|
@@ -12809,6 +12888,7 @@ export function createProductionAgentHandler(options) {
|
|
|
12809
12888
|
: {}),
|
|
12810
12889
|
},
|
|
12811
12890
|
experimentAssignments,
|
|
12891
|
+
singularExperimentAssignment,
|
|
12812
12892
|
modelSelectionSource,
|
|
12813
12893
|
sentimentInput: shouldInferSentimentForTurn({
|
|
12814
12894
|
internalContinuation: Boolean(internalContinuation),
|
package/dist/agent/run-store.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MAX_BACKGROUND_RUN_CONTINUATIONS, TURN_RUN_LEDGER_SLACK, } from "../app-config/run-lifecycle-invariants.js";
|
|
2
2
|
import { isArtifactReceipt, } from "../artifacts/detect.js";
|
|
3
|
-
import { getDbExec, getScopedTransactionDbExec } from "../db/client.js";
|
|
3
|
+
import { getDbExec, getScopedDbExec, getScopedTransactionDbExec, } from "../db/client.js";
|
|
4
4
|
import { ensureColumnExists, ensureIndexExists, ensureTableExists, } from "../db/ddl-guard.js";
|
|
5
5
|
import { defineStore } from "../db/store-registry.js";
|
|
6
6
|
import { fromMigrationEntries } from "../db/versioned-store-migrations.js";
|
|
@@ -1320,7 +1320,8 @@ function earlierTurnProjectionPendingSql() {
|
|
|
1320
1320
|
)`;
|
|
1321
1321
|
}
|
|
1322
1322
|
export async function claimBackgroundRun(runId) {
|
|
1323
|
-
|
|
1323
|
+
if (getScopedDbExec() === undefined)
|
|
1324
|
+
await ensureRunTables();
|
|
1324
1325
|
const client = getDbExec();
|
|
1325
1326
|
const { rowsAffected } = await client.execute({
|
|
1326
1327
|
sql: `UPDATE agent_runs
|
|
@@ -2,6 +2,15 @@ import { z } from "zod";
|
|
|
2
2
|
export declare const agentConfig: z.ZodObject<{
|
|
3
3
|
engine: z.ZodOptional<z.ZodString>;
|
|
4
4
|
model: z.ZodOptional<z.ZodString>;
|
|
5
|
+
questionIntake: z.ZodOptional<z.ZodObject<{
|
|
6
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
7
|
+
disabled: z.ZodDefault<z.ZodBoolean>;
|
|
8
|
+
experimentId: z.ZodString;
|
|
9
|
+
questionToolNames: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
10
|
+
treatmentInstructions: z.ZodString;
|
|
11
|
+
trackedEventNames: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
12
|
+
clientTrackedEventNames: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
13
|
+
}, z.core.$strip>>;
|
|
5
14
|
builtInEngines: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
6
15
|
buildEnginePackages: z.ZodOptional<z.ZodString>;
|
|
7
16
|
mode: z.ZodOptional<z.ZodString>;
|
package/dist/app-config/agent.js
CHANGED
|
@@ -16,6 +16,37 @@ export const agentConfig = z.object({
|
|
|
16
16
|
env: ["AGENT_MODEL"],
|
|
17
17
|
doc: "Model the agent runs with, when the caller does not pass one.",
|
|
18
18
|
}),
|
|
19
|
+
questionIntake: z
|
|
20
|
+
.object({
|
|
21
|
+
enabled: z
|
|
22
|
+
.boolean()
|
|
23
|
+
.default(false)
|
|
24
|
+
.meta({
|
|
25
|
+
env: ["AGENT_QUESTION_INTAKE_EXPERIMENT_ENABLED"],
|
|
26
|
+
doc: "Enable signup-time assignment and run-level question-intake experimentation for this app.",
|
|
27
|
+
}),
|
|
28
|
+
disabled: z
|
|
29
|
+
.boolean()
|
|
30
|
+
.default(false)
|
|
31
|
+
.meta({
|
|
32
|
+
env: ["AGENT_QUESTION_INTAKE_EXPERIMENT_DISABLED"],
|
|
33
|
+
doc: "Emergency switch that disables this app's question-intake experiment, including when enabled by app config.",
|
|
34
|
+
}),
|
|
35
|
+
experimentId: z.string().trim().min(1),
|
|
36
|
+
questionToolNames: z.array(z.string().trim().min(1)).default([]),
|
|
37
|
+
treatmentInstructions: z.string().trim().min(1),
|
|
38
|
+
trackedEventNames: z.array(z.string().trim().min(1)).default([]),
|
|
39
|
+
clientTrackedEventNames: z
|
|
40
|
+
.array(z.string().trim().min(1))
|
|
41
|
+
.default([])
|
|
42
|
+
.meta({
|
|
43
|
+
doc: "Client-originated event names allowed to receive experiment assignment metadata. Keep outcome events restricted to server-confirmed behavior.",
|
|
44
|
+
}),
|
|
45
|
+
})
|
|
46
|
+
.optional()
|
|
47
|
+
.meta({
|
|
48
|
+
doc: "App-specific settings for the reusable question-intake experiment. Only new users created while enabled receive an assignment.",
|
|
49
|
+
}),
|
|
19
50
|
builtInEngines: z
|
|
20
51
|
.array(z.string().min(1))
|
|
21
52
|
.min(1)
|
|
@@ -26,6 +26,15 @@ export declare const appConfigSchema: z.ZodObject<{
|
|
|
26
26
|
agent: z.ZodPrefault<z.ZodObject<{
|
|
27
27
|
engine: z.ZodOptional<z.ZodString>;
|
|
28
28
|
model: z.ZodOptional<z.ZodString>;
|
|
29
|
+
questionIntake: z.ZodOptional<z.ZodObject<{
|
|
30
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
31
|
+
disabled: z.ZodDefault<z.ZodBoolean>;
|
|
32
|
+
experimentId: z.ZodString;
|
|
33
|
+
questionToolNames: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
34
|
+
treatmentInstructions: z.ZodString;
|
|
35
|
+
trackedEventNames: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
36
|
+
clientTrackedEventNames: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
37
|
+
}, z.core.$strip>>;
|
|
29
38
|
builtInEngines: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
30
39
|
buildEnginePackages: z.ZodOptional<z.ZodString>;
|
|
31
40
|
mode: z.ZodOptional<z.ZodString>;
|
package/dist/app-config/store.js
CHANGED
|
@@ -26,8 +26,29 @@ function mergeLayers(base, override) {
|
|
|
26
26
|
}
|
|
27
27
|
return result;
|
|
28
28
|
}
|
|
29
|
-
function resolve(envLayer) {
|
|
30
|
-
|
|
29
|
+
function resolve(envLayer, questionIntakeEnvironment) {
|
|
30
|
+
const nonEnvironmentConfig = LAYER_ORDER.filter((layer) => layer !== "env").reduce((merged, layer) => state.layers[layer]
|
|
31
|
+
? mergeLayers(merged, state.layers[layer])
|
|
32
|
+
: merged, {});
|
|
33
|
+
const configuredAgent = nonEnvironmentConfig.agent;
|
|
34
|
+
const configuredQuestionIntake = isPlainObject(configuredAgent)
|
|
35
|
+
? configuredAgent.questionIntake
|
|
36
|
+
: undefined;
|
|
37
|
+
const hasQuestionIntakeConfig = isPlainObject(configuredQuestionIntake) &&
|
|
38
|
+
typeof configuredQuestionIntake.experimentId === "string" &&
|
|
39
|
+
typeof configuredQuestionIntake.treatmentInstructions === "string";
|
|
40
|
+
const resolvedEnvLayer = mergeLayers({}, envLayer);
|
|
41
|
+
if (hasQuestionIntakeConfig &&
|
|
42
|
+
questionIntakeEnvironment?.enabled !== undefined) {
|
|
43
|
+
const agentLayer = isPlainObject(resolvedEnvLayer.agent)
|
|
44
|
+
? resolvedEnvLayer.agent
|
|
45
|
+
: (resolvedEnvLayer.agent = {});
|
|
46
|
+
const questionIntakeLayer = isPlainObject(agentLayer.questionIntake)
|
|
47
|
+
? agentLayer.questionIntake
|
|
48
|
+
: (agentLayer.questionIntake = {});
|
|
49
|
+
questionIntakeLayer.enabled = questionIntakeEnvironment.enabled;
|
|
50
|
+
}
|
|
51
|
+
state.layers.env = resolvedEnvLayer;
|
|
31
52
|
let merged = {};
|
|
32
53
|
for (const layer of LAYER_ORDER) {
|
|
33
54
|
const value = state.layers[layer];
|
|
@@ -35,6 +56,20 @@ function resolve(envLayer) {
|
|
|
35
56
|
merged = mergeLayers(merged, value);
|
|
36
57
|
}
|
|
37
58
|
const parsed = appConfigSchema.parse(merged);
|
|
59
|
+
if (questionIntakeEnvironment?.enabled !== undefined &&
|
|
60
|
+
parsed.agent.questionIntake) {
|
|
61
|
+
parsed.agent.questionIntake = {
|
|
62
|
+
...parsed.agent.questionIntake,
|
|
63
|
+
enabled: questionIntakeEnvironment.enabled,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (questionIntakeEnvironment?.disabled === true &&
|
|
67
|
+
parsed.agent.questionIntake) {
|
|
68
|
+
parsed.agent.questionIntake = {
|
|
69
|
+
...parsed.agent.questionIntake,
|
|
70
|
+
disabled: true,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
38
73
|
assertRunLifecycleInvariants(parsed.agent);
|
|
39
74
|
parsed.app = deriveAppIdentity(parsed.app);
|
|
40
75
|
return parsed;
|
|
@@ -68,12 +103,20 @@ export function defineAppConfig(config) {
|
|
|
68
103
|
return () => { };
|
|
69
104
|
}
|
|
70
105
|
export function getAppConfig() {
|
|
71
|
-
const
|
|
72
|
-
const
|
|
106
|
+
const environment = readConfigEnvironment();
|
|
107
|
+
const questionIntakeEnvironment = readEnvConfigLayer(appConfigSchema, {
|
|
108
|
+
AGENT_QUESTION_INTAKE_EXPERIMENT_ENABLED: environment.AGENT_QUESTION_INTAKE_EXPERIMENT_ENABLED,
|
|
109
|
+
AGENT_QUESTION_INTAKE_EXPERIMENT_DISABLED: environment.AGENT_QUESTION_INTAKE_EXPERIMENT_DISABLED,
|
|
110
|
+
}).agent?.questionIntake;
|
|
111
|
+
const environmentWithoutQuestionIntakeSwitches = { ...environment };
|
|
112
|
+
delete environmentWithoutQuestionIntakeSwitches.AGENT_QUESTION_INTAKE_EXPERIMENT_ENABLED;
|
|
113
|
+
delete environmentWithoutQuestionIntakeSwitches.AGENT_QUESTION_INTAKE_EXPERIMENT_DISABLED;
|
|
114
|
+
const envLayer = readEnvConfigLayer(appConfigSchema, environmentWithoutQuestionIntakeSwitches);
|
|
115
|
+
const signature = JSON.stringify({ envLayer, questionIntakeEnvironment });
|
|
73
116
|
if (state.resolved && state.envSignature === signature)
|
|
74
117
|
return state.resolved;
|
|
75
118
|
state.envSignature = signature;
|
|
76
|
-
return (state.resolved = resolve(envLayer));
|
|
119
|
+
return (state.resolved = resolve(envLayer, questionIntakeEnvironment));
|
|
77
120
|
}
|
|
78
121
|
export function resetAppConfigForTests() {
|
|
79
122
|
state.layers = {};
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -25,8 +25,8 @@ export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import
|
|
|
25
25
|
ok: boolean;
|
|
26
26
|
}>>;
|
|
27
27
|
export declare const postCollabText: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
28
|
-
ok?: undefined;
|
|
29
28
|
text?: undefined;
|
|
29
|
+
ok?: undefined;
|
|
30
30
|
error: string;
|
|
31
31
|
} | {
|
|
32
32
|
error?: undefined;
|
|
@@ -13,9 +13,9 @@ export declare const postCollabPatch: import("h3").EventHandlerWithFetch<import(
|
|
|
13
13
|
ok: boolean;
|
|
14
14
|
}>>;
|
|
15
15
|
export declare const getCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
data?: undefined;
|
|
17
16
|
error: string;
|
|
18
17
|
docId?: undefined;
|
|
18
|
+
data?: undefined;
|
|
19
19
|
} | {
|
|
20
20
|
error?: undefined;
|
|
21
21
|
docId: string;
|
|
@@ -33,10 +33,10 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
33
33
|
idempotencyKey: string;
|
|
34
34
|
deleted: boolean;
|
|
35
35
|
} | {
|
|
36
|
-
|
|
36
|
+
provider?: undefined;
|
|
37
37
|
error: string;
|
|
38
38
|
url?: undefined;
|
|
39
|
-
|
|
39
|
+
id?: undefined;
|
|
40
40
|
configured?: undefined;
|
|
41
41
|
} | {
|
|
42
42
|
error?: undefined;
|
|
@@ -45,9 +45,9 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
45
45
|
provider: string;
|
|
46
46
|
configured?: undefined;
|
|
47
47
|
} | {
|
|
48
|
-
id?: undefined;
|
|
49
|
-
url?: undefined;
|
|
50
48
|
provider?: undefined;
|
|
49
|
+
url?: undefined;
|
|
50
|
+
id?: undefined;
|
|
51
51
|
error: string;
|
|
52
52
|
configured: boolean;
|
|
53
53
|
}>;
|
package/dist/jobs/run-history.js
CHANGED
|
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { currentExecutionFence, StaleExecutionFenceError, withFencedExecutionWrite, } from "../agent/execution-fence.js";
|
|
4
4
|
import { resolveBackgroundRunHardTimeoutMs } from "../agent/run-manager.js";
|
|
5
|
-
import { getDbExec } from "../db/client.js";
|
|
5
|
+
import { getDbExec, getScopedDbExec, } from "../db/client.js";
|
|
6
6
|
import { ensureColumnExists, ensureIndexExists, ensureTableExists, } from "../db/ddl-guard.js";
|
|
7
7
|
import { defineStore } from "../db/store-registry.js";
|
|
8
8
|
import { fromMigrationEntries } from "../db/versioned-store-migrations.js";
|
|
@@ -717,7 +717,8 @@ export async function finishAutomationRun(id, status, error, errorCode, options
|
|
|
717
717
|
}
|
|
718
718
|
}
|
|
719
719
|
export async function attachAutomationRunThread(id, threadId, runId, options = {}) {
|
|
720
|
-
|
|
720
|
+
if (getScopedDbExec() === undefined)
|
|
721
|
+
await ensureTable();
|
|
721
722
|
const fence = currentExecutionFence();
|
|
722
723
|
if (fence && (fence.runId !== runId || fence.threadId !== threadId)) {
|
|
723
724
|
throw new StaleExecutionFenceError(fence);
|
|
@@ -60,4 +60,4 @@ export declare function hostedModelExperimentVariant(assignments: readonly Hoste
|
|
|
60
60
|
* The singular experiment tags predate the hosted test. They keep naming only
|
|
61
61
|
* the other experiment on a turn, so the hosted arm never changes them.
|
|
62
62
|
*/
|
|
63
|
-
export declare function singleNonHostedAssignment(assignments: readonly HostedModelExperimentAssignment[] | undefined): HostedModelExperimentAssignment | undefined;
|
|
63
|
+
export declare function singleNonHostedAssignment(assignments: readonly HostedModelExperimentAssignment[] | undefined, selectedAssignment?: HostedModelExperimentAssignment): HostedModelExperimentAssignment | undefined;
|
|
@@ -86,7 +86,13 @@ export function hostedModelExperimentVariant(assignments) {
|
|
|
86
86
|
* The singular experiment tags predate the hosted test. They keep naming only
|
|
87
87
|
* the other experiment on a turn, so the hosted arm never changes them.
|
|
88
88
|
*/
|
|
89
|
-
export function singleNonHostedAssignment(assignments) {
|
|
89
|
+
export function singleNonHostedAssignment(assignments, selectedAssignment) {
|
|
90
|
+
if (selectedAssignment &&
|
|
91
|
+
selectedAssignment.experimentId !== HOSTED_MODEL_EXPERIMENT_ID &&
|
|
92
|
+
assignments?.some((assignment) => assignment.experimentId === selectedAssignment.experimentId &&
|
|
93
|
+
assignment.variantId === selectedAssignment.variantId)) {
|
|
94
|
+
return selectedAssignment;
|
|
95
|
+
}
|
|
90
96
|
const others = assignments?.filter((assignment) => assignment.experimentId !== HOSTED_MODEL_EXPERIMENT_ID);
|
|
91
97
|
return others?.length === 1 ? others[0] : undefined;
|
|
92
98
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export declare const QUESTION_INTAKE_CONTROL_VARIANT: "control";
|
|
2
|
+
export declare const QUESTION_INTAKE_TREATMENT_VARIANT: "no_questions";
|
|
3
|
+
export interface QuestionIntakeExperimentConfig {
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
experimentId: string;
|
|
7
|
+
questionToolNames: string[];
|
|
8
|
+
treatmentInstructions: string;
|
|
9
|
+
trackedEventNames: string[];
|
|
10
|
+
clientTrackedEventNames?: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface QuestionIntakeExperimentAssignment {
|
|
13
|
+
authUserId: string;
|
|
14
|
+
experimentId: string;
|
|
15
|
+
variantId: typeof QUESTION_INTAKE_CONTROL_VARIANT | typeof QUESTION_INTAKE_TREATMENT_VARIANT;
|
|
16
|
+
}
|
|
17
|
+
export declare function questionIntakeExposureProperties(assignment: QuestionIntakeExperimentAssignment, context: {
|
|
18
|
+
appId: string;
|
|
19
|
+
threadId?: string;
|
|
20
|
+
turnId: string;
|
|
21
|
+
}): Record<string, string>;
|
|
22
|
+
export declare class QuestionIntakeAssignmentError extends Error {
|
|
23
|
+
readonly code = "question_intake_assignment_failed";
|
|
24
|
+
constructor(experimentId: string, cause: unknown);
|
|
25
|
+
}
|
|
26
|
+
export declare class InvalidQuestionIntakeExperimentAssignmentError extends Error {
|
|
27
|
+
readonly code = "question_intake_assignment_invalid";
|
|
28
|
+
constructor(experimentId: string);
|
|
29
|
+
}
|
|
30
|
+
export declare function treatmentInstructionsForQuestionIntake(config: QuestionIntakeExperimentConfig | undefined, assignment: QuestionIntakeExperimentAssignment | null): string | undefined;
|
|
31
|
+
/** Assigns only from the user-create hook while the app config is enabled. */
|
|
32
|
+
export declare function assignQuestionIntakeExperimentOnSignup(config: QuestionIntakeExperimentConfig | undefined, authUserId: string | undefined): Promise<QuestionIntakeExperimentAssignment | null>;
|
|
33
|
+
/** Reads an existing signup assignment; agent runs never enroll older users. */
|
|
34
|
+
export declare function resolveQuestionIntakeExperimentAssignment(config: QuestionIntakeExperimentConfig | undefined, authUserId: string | undefined): Promise<QuestionIntakeExperimentAssignment | null>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { getAssignment, upsertAssignment } from "./store.js";
|
|
3
|
+
export const QUESTION_INTAKE_CONTROL_VARIANT = "control";
|
|
4
|
+
export const QUESTION_INTAKE_TREATMENT_VARIANT = "no_questions";
|
|
5
|
+
export function questionIntakeExposureProperties(assignment, context) {
|
|
6
|
+
return {
|
|
7
|
+
experiment_id: assignment.experimentId,
|
|
8
|
+
experiment_variant: assignment.variantId,
|
|
9
|
+
exposure_surface: "agent_run",
|
|
10
|
+
app_id: context.appId,
|
|
11
|
+
templateId: context.appId,
|
|
12
|
+
agent_native_app: context.appId,
|
|
13
|
+
...(context.threadId ? { thread_id: context.threadId } : {}),
|
|
14
|
+
turn_id: context.turnId,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export class QuestionIntakeAssignmentError extends Error {
|
|
18
|
+
code = "question_intake_assignment_failed";
|
|
19
|
+
constructor(experimentId, cause) {
|
|
20
|
+
super(`Question-intake assignment failed after retry for ${experimentId}.`, { cause });
|
|
21
|
+
this.name = "QuestionIntakeAssignmentError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export class InvalidQuestionIntakeExperimentAssignmentError extends Error {
|
|
25
|
+
code = "question_intake_assignment_invalid";
|
|
26
|
+
constructor(experimentId) {
|
|
27
|
+
super(`Question-intake assignment has an invalid stored variant for ${experimentId}.`);
|
|
28
|
+
this.name = "InvalidQuestionIntakeExperimentAssignmentError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function treatmentInstructionsForQuestionIntake(config, assignment) {
|
|
32
|
+
if (!configured(config) ||
|
|
33
|
+
assignment?.experimentId !== config.experimentId ||
|
|
34
|
+
assignment.variantId !== QUESTION_INTAKE_TREATMENT_VARIANT) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return [
|
|
38
|
+
"For this assigned experiment treatment, the instructions below override conflicting intake-question directives in the app prompt or the user's current message, including app-injected directives.",
|
|
39
|
+
config.treatmentInstructions.trim(),
|
|
40
|
+
].join("\n");
|
|
41
|
+
}
|
|
42
|
+
function configured(config) {
|
|
43
|
+
return Boolean(config?.enabled &&
|
|
44
|
+
!config.disabled &&
|
|
45
|
+
config.experimentId.trim() &&
|
|
46
|
+
config.questionToolNames.length > 0 &&
|
|
47
|
+
config.treatmentInstructions.trim());
|
|
48
|
+
}
|
|
49
|
+
function variantForUser(experimentId, authUserId) {
|
|
50
|
+
const bucket = createHash("sha256")
|
|
51
|
+
.update(`${experimentId}\0${authUserId}`)
|
|
52
|
+
.digest()
|
|
53
|
+
.readUInt8(0);
|
|
54
|
+
return bucket < 128
|
|
55
|
+
? QUESTION_INTAKE_CONTROL_VARIANT
|
|
56
|
+
: QUESTION_INTAKE_TREATMENT_VARIANT;
|
|
57
|
+
}
|
|
58
|
+
function assignmentFromStored(config, authUserId, variantId) {
|
|
59
|
+
if (variantId !== QUESTION_INTAKE_CONTROL_VARIANT &&
|
|
60
|
+
variantId !== QUESTION_INTAKE_TREATMENT_VARIANT) {
|
|
61
|
+
throw new InvalidQuestionIntakeExperimentAssignmentError(config.experimentId);
|
|
62
|
+
}
|
|
63
|
+
return { authUserId, experimentId: config.experimentId, variantId };
|
|
64
|
+
}
|
|
65
|
+
/** Assigns only from the user-create hook while the app config is enabled. */
|
|
66
|
+
// raw-sql-ok: authUserId is persisted only through the assignment store's bound arguments.
|
|
67
|
+
export async function assignQuestionIntakeExperimentOnSignup(config, authUserId) {
|
|
68
|
+
if (!configured(config) || !authUserId?.trim())
|
|
69
|
+
return null;
|
|
70
|
+
const assign = async () => {
|
|
71
|
+
// raw-sql-ok: the assignment store binds the experiment and user IDs as query arguments.
|
|
72
|
+
const existing = await getAssignment(config.experimentId, authUserId);
|
|
73
|
+
if (existing) {
|
|
74
|
+
return assignmentFromStored(config, authUserId, existing.variantId);
|
|
75
|
+
}
|
|
76
|
+
const variantId = variantForUser(config.experimentId, authUserId);
|
|
77
|
+
await upsertAssignment({
|
|
78
|
+
experimentId: config.experimentId,
|
|
79
|
+
userId: authUserId,
|
|
80
|
+
variantId,
|
|
81
|
+
assignedAt: Date.now(),
|
|
82
|
+
});
|
|
83
|
+
return { authUserId, experimentId: config.experimentId, variantId };
|
|
84
|
+
};
|
|
85
|
+
try {
|
|
86
|
+
return await assign();
|
|
87
|
+
}
|
|
88
|
+
catch (firstError) {
|
|
89
|
+
if (firstError instanceof InvalidQuestionIntakeExperimentAssignmentError) {
|
|
90
|
+
throw firstError;
|
|
91
|
+
}
|
|
92
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
93
|
+
try {
|
|
94
|
+
return await assign();
|
|
95
|
+
}
|
|
96
|
+
catch (retryError) {
|
|
97
|
+
if (retryError instanceof InvalidQuestionIntakeExperimentAssignmentError) {
|
|
98
|
+
throw retryError;
|
|
99
|
+
}
|
|
100
|
+
throw new QuestionIntakeAssignmentError(config.experimentId, new AggregateError([firstError, retryError], "Question-intake assignment failed twice."));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** Reads an existing signup assignment; agent runs never enroll older users. */
|
|
105
|
+
// raw-sql-ok: authUserId is read only through the assignment store's bound arguments.
|
|
106
|
+
export async function resolveQuestionIntakeExperimentAssignment(config, authUserId) {
|
|
107
|
+
if (!configured(config) || !authUserId?.trim())
|
|
108
|
+
return null;
|
|
109
|
+
// raw-sql-ok: the assignment store binds the experiment and user IDs as query arguments.
|
|
110
|
+
const existing = await getAssignment(config.experimentId, authUserId);
|
|
111
|
+
if (!existing)
|
|
112
|
+
return null;
|
|
113
|
+
return assignmentFromStored(config, authUserId, existing.variantId);
|
|
114
|
+
}
|
|
@@ -49,6 +49,10 @@ export declare function instrumentAgentLoop(opts: {
|
|
|
49
49
|
experimentId: string;
|
|
50
50
|
variantId: string;
|
|
51
51
|
}>;
|
|
52
|
+
singularExperimentAssignment?: {
|
|
53
|
+
experimentId: string;
|
|
54
|
+
variantId: string;
|
|
55
|
+
};
|
|
52
56
|
modelSelectionSource?: string;
|
|
53
57
|
delegation?: {
|
|
54
58
|
protocol: "a2a" | "mcp" | "agent-team";
|
|
@@ -311,7 +311,7 @@ function emitLlmGenerationTrackingEvent(args) {
|
|
|
311
311
|
properties.experiment_variants = args.experimentAssignments
|
|
312
312
|
.map((assignment) => assignment.variantId)
|
|
313
313
|
.join(",");
|
|
314
|
-
const singular = singleNonHostedAssignment(args.experimentAssignments);
|
|
314
|
+
const singular = singleNonHostedAssignment(args.experimentAssignments, args.singularExperimentAssignment);
|
|
315
315
|
if (singular) {
|
|
316
316
|
properties.experiment_id = singular.experimentId;
|
|
317
317
|
properties.experiment_variant = singular.variantId;
|
|
@@ -404,6 +404,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
404
404
|
.catch(() => null)
|
|
405
405
|
: Promise.resolve(null);
|
|
406
406
|
const browserSessionId = opts.browserSessionId ?? getRequestContext()?.browserSessionId;
|
|
407
|
+
const singularExperimentAssignment = singleNonHostedAssignment(opts.experimentAssignments, opts.singularExperimentAssignment);
|
|
407
408
|
const otelRunSpanPromise = startAgentSpan("invoke_agent", {
|
|
408
409
|
"gen_ai.operation.name": "invoke_agent",
|
|
409
410
|
"gen_ai.provider.name": engineName,
|
|
@@ -412,9 +413,8 @@ export async function instrumentAgentLoop(opts) {
|
|
|
412
413
|
"agent.run_id": runId,
|
|
413
414
|
"agent.user_id": userId ?? undefined,
|
|
414
415
|
"agent.model_selection_source": opts.modelSelectionSource,
|
|
415
|
-
"agent.experiment_id":
|
|
416
|
-
|
|
417
|
-
"agent.experiment_variant": singleNonHostedAssignment(opts.experimentAssignments)?.variantId,
|
|
416
|
+
"agent.experiment_id": singularExperimentAssignment?.experimentId,
|
|
417
|
+
"agent.experiment_variant": singularExperimentAssignment?.variantId,
|
|
418
418
|
"agent.hosted_model_experiment_variant": hostedModelExperimentVariant(opts.experimentAssignments),
|
|
419
419
|
});
|
|
420
420
|
let otelRunSpan = null;
|
|
@@ -1265,6 +1265,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
1265
1265
|
createdAt: generation.createdAt,
|
|
1266
1266
|
endedAt: generation.endedAt,
|
|
1267
1267
|
experimentAssignments: opts.experimentAssignments,
|
|
1268
|
+
singularExperimentAssignment: opts.singularExperimentAssignment,
|
|
1268
1269
|
modelSelectionSource: opts.modelSelectionSource,
|
|
1269
1270
|
browserSessionId,
|
|
1270
1271
|
...generationContent,
|