@agent-native/core 0.220.0-nightly-20261024033422 → 0.220.0
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/handlers.js +7 -6
- package/dist/a2a/types.d.ts +1 -0
- package/dist/agent/engine/tool-call-journal-seed.js +24 -20
- package/dist/agent/execution-fence.d.ts +1 -1
- package/dist/agent/execution-fence.js +8 -1
- package/dist/agent/production-agent.js +31 -6
- package/dist/agent/reaped-run-thread.js +38 -3
- package/dist/agent/run-migrations.js +22 -0
- package/dist/agent/run-store.d.ts +19 -2
- package/dist/agent/run-store.fencing.worker.js +21 -1
- package/dist/agent/run-store.js +359 -35
- package/dist/agent/tool-approval-store.d.ts +13 -0
- package/dist/agent/tool-approval-store.js +64 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/client/background-agent-session.d.ts +4 -0
- package/dist/client/background-agent-session.js +17 -1
- package/dist/client/chat/runtime.js +38 -6
- package/dist/collab/routes.d.ts +2 -2
- package/dist/db/schema-snapshot.generated.js +14 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/jobs/automation-recovery.d.ts +4 -0
- package/dist/jobs/automation-recovery.js +7 -0
- package/dist/jobs/background-automation-runner.js +19 -2
- package/dist/jobs/scheduler.js +3 -1
- package/dist/mcp/org-directory.d.ts +8 -7
- package/dist/mcp/org-directory.js +82 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +7 -7
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/secrets/routes.d.ts +10 -10
- package/dist/server/agent-chat-plugin.d.ts +10 -1
- package/dist/server/agent-chat-plugin.js +178 -11
- package/dist/server/agent-discovery.js +11 -49
- package/dist/server/agent-engine-default-model-route.d.ts +1 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
- package/dist/usage/store.js +36 -33
- package/package.json +4 -4
|
@@ -87,6 +87,70 @@ export class AgentToolApprovalNotFoundError extends Error {
|
|
|
87
87
|
this.name = "AgentToolApprovalNotFoundError";
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
export async function hasPendingAgentToolApproval(tx, input) {
|
|
91
|
+
const result = await tx.execute({
|
|
92
|
+
sql: `SELECT 1 FROM agent_tool_approvals
|
|
93
|
+
WHERE owner_email = ?
|
|
94
|
+
AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = CAST(? AS TEXT))
|
|
95
|
+
AND thread_id = ?
|
|
96
|
+
AND status = 'pending'
|
|
97
|
+
AND expires_at > ?
|
|
98
|
+
LIMIT 1`,
|
|
99
|
+
args: [
|
|
100
|
+
input.ownerEmail,
|
|
101
|
+
input.orgId ?? null,
|
|
102
|
+
input.orgId ?? null,
|
|
103
|
+
input.threadId,
|
|
104
|
+
Date.now(),
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
return result.rows.length > 0;
|
|
108
|
+
}
|
|
109
|
+
/** Denial is a durable resolution so queued turns can resume after a pause. */
|
|
110
|
+
export async function denyAgentToolApproval(input) {
|
|
111
|
+
await ensureAgentToolApprovalTable();
|
|
112
|
+
const now = Date.now();
|
|
113
|
+
const updated = await withFencedExecutionWrite((tx) => tx.execute({
|
|
114
|
+
sql: `UPDATE agent_tool_approvals
|
|
115
|
+
SET status = 'denied', updated_at = ?
|
|
116
|
+
WHERE id = ?
|
|
117
|
+
AND owner_email = ?
|
|
118
|
+
AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = CAST(? AS TEXT))
|
|
119
|
+
AND thread_id = ?
|
|
120
|
+
AND status = 'pending'`,
|
|
121
|
+
args: [
|
|
122
|
+
now,
|
|
123
|
+
input.approvalId,
|
|
124
|
+
input.ownerEmail,
|
|
125
|
+
input.orgId ?? null,
|
|
126
|
+
input.orgId ?? null,
|
|
127
|
+
input.threadId,
|
|
128
|
+
],
|
|
129
|
+
}), { threadId: input.threadId });
|
|
130
|
+
if (updated.rowsAffected === 1)
|
|
131
|
+
return "denied";
|
|
132
|
+
const existing = await getDbExec().execute({
|
|
133
|
+
sql: `SELECT status FROM agent_tool_approvals
|
|
134
|
+
WHERE id = ?
|
|
135
|
+
AND owner_email = ?
|
|
136
|
+
AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = CAST(? AS TEXT))
|
|
137
|
+
AND thread_id = ?
|
|
138
|
+
LIMIT 1`,
|
|
139
|
+
args: [
|
|
140
|
+
input.approvalId,
|
|
141
|
+
input.ownerEmail,
|
|
142
|
+
input.orgId ?? null,
|
|
143
|
+
input.orgId ?? null,
|
|
144
|
+
input.threadId,
|
|
145
|
+
],
|
|
146
|
+
});
|
|
147
|
+
const status = existing.rows[0]?.status;
|
|
148
|
+
if (status === "denied")
|
|
149
|
+
return "denied";
|
|
150
|
+
if (status && status !== "pending")
|
|
151
|
+
return "already_resolved";
|
|
152
|
+
return "not_applied";
|
|
153
|
+
}
|
|
90
154
|
/**
|
|
91
155
|
* Saves "always allow" for one tool name. With `approvalId`, the grant takes
|
|
92
156
|
* the revision of that approval row, the card the user clicked, so it never
|
|
@@ -139,8 +139,8 @@ export declare function createAutomationCallbackHandler(runtime: AutomationRunti
|
|
|
139
139
|
duplicate?: undefined;
|
|
140
140
|
eventId?: undefined;
|
|
141
141
|
} | {
|
|
142
|
+
error?: undefined;
|
|
142
143
|
accepted: boolean;
|
|
143
144
|
duplicate: boolean;
|
|
144
145
|
eventId: string;
|
|
145
|
-
error?: undefined;
|
|
146
146
|
}>;
|
|
@@ -42,3 +42,7 @@ export declare function cancelBackgroundAgentSession(options: {
|
|
|
42
42
|
turnId: string;
|
|
43
43
|
reason?: string;
|
|
44
44
|
}): Promise<void>;
|
|
45
|
+
export type AgentRunThreadProjectionRetryState = "already_projected" | "projected" | "deferred";
|
|
46
|
+
export declare function retryAgentRunThreadProjection(runId: string): Promise<{
|
|
47
|
+
state: AgentRunThreadProjectionRetryState;
|
|
48
|
+
}>;
|
|
@@ -23,13 +23,16 @@ class BackgroundAgentSessionHttpError extends Error {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
async function responseError(response) {
|
|
26
|
+
return responseErrorFor(response, "Background agent session");
|
|
27
|
+
}
|
|
28
|
+
async function responseErrorFor(response, requestName) {
|
|
26
29
|
const body = await response
|
|
27
30
|
.json()
|
|
28
31
|
.catch(() => null);
|
|
29
32
|
const detail = body && typeof body.error === "string" && body.error.trim()
|
|
30
33
|
? `: ${body.error.trim()}`
|
|
31
34
|
: "";
|
|
32
|
-
return new BackgroundAgentSessionHttpError(response.status,
|
|
35
|
+
return new BackgroundAgentSessionHttpError(response.status, `${requestName} was rejected (HTTP ${response.status})${detail}`);
|
|
33
36
|
}
|
|
34
37
|
async function drainResponse(response) {
|
|
35
38
|
const reader = response.body?.getReader();
|
|
@@ -241,3 +244,16 @@ export async function cancelBackgroundAgentSession(options) {
|
|
|
241
244
|
throw await responseError(turnResponse);
|
|
242
245
|
}
|
|
243
246
|
}
|
|
247
|
+
export async function retryAgentRunThreadProjection(runId) {
|
|
248
|
+
const response = await fetch(agentNativePath(`/_agent-native/agent-chat/runs/${encodeURIComponent(runId)}/retry-projection`), { method: "POST", credentials: "same-origin" });
|
|
249
|
+
if (!response.ok) {
|
|
250
|
+
throw await responseErrorFor(response, "Run projection retry");
|
|
251
|
+
}
|
|
252
|
+
const body = (await response.json());
|
|
253
|
+
if (body.state !== "already_projected" &&
|
|
254
|
+
body.state !== "projected" &&
|
|
255
|
+
body.state !== "deferred") {
|
|
256
|
+
throw new Error("Run projection retry returned an invalid response");
|
|
257
|
+
}
|
|
258
|
+
return { state: body.state };
|
|
259
|
+
}
|
|
@@ -3474,12 +3474,44 @@ export function createAgentNativeChatRuntime(options = {}, telemetry = {}) {
|
|
|
3474
3474
|
});
|
|
3475
3475
|
}
|
|
3476
3476
|
if (!approval.approved) {
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3477
|
+
return (async () => {
|
|
3478
|
+
const threadId = session.threadId ?? options.threadId ?? session.id;
|
|
3479
|
+
const headers = await resolveHeaders(options.headers, {
|
|
3480
|
+
sessionId: session.id,
|
|
3481
|
+
turnId: continuation.turnId,
|
|
3482
|
+
});
|
|
3483
|
+
if (!headers.has("Content-Type")) {
|
|
3484
|
+
headers.set("Content-Type", "application/json");
|
|
3485
|
+
}
|
|
3486
|
+
const response = await fetchImpl(`${apiUrl.replace(/\/+$/, "")}/threads/${encodeURIComponent(threadId)}/approval-resolution`, {
|
|
3487
|
+
method: "POST",
|
|
3488
|
+
headers,
|
|
3489
|
+
credentials: "same-origin",
|
|
3490
|
+
body: JSON.stringify({
|
|
3491
|
+
approvalId: approval.id,
|
|
3492
|
+
decision: "deny",
|
|
3493
|
+
}),
|
|
3494
|
+
});
|
|
3495
|
+
let denialResolved = response.ok;
|
|
3496
|
+
if (!denialResolved && response.status === 409) {
|
|
3497
|
+
try {
|
|
3498
|
+
const body = asRecord(await response.clone().json());
|
|
3499
|
+
denialResolved = body?.code === "approval_not_pending";
|
|
3500
|
+
}
|
|
3501
|
+
catch {
|
|
3502
|
+
// coercion-ok: Preserve the original HTTP failure when conflict details are unreadable.
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
if (!denialResolved) {
|
|
3506
|
+
throw new Error(await readErrorText(response));
|
|
3507
|
+
}
|
|
3508
|
+
messageStates.delete(messageStateKey);
|
|
3509
|
+
return terminalRuntimeTurn({
|
|
3510
|
+
sessionId: session.id,
|
|
3511
|
+
turnId: continuation.turnId,
|
|
3512
|
+
reason: "complete",
|
|
3513
|
+
});
|
|
3514
|
+
})();
|
|
3483
3515
|
}
|
|
3484
3516
|
const messageState = messageStates.get(messageStateKey);
|
|
3485
3517
|
const approvalParts = [
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -13,12 +13,12 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
|
|
|
13
13
|
};
|
|
14
14
|
}>>;
|
|
15
15
|
export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
error: string;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error: string;
|
|
18
18
|
} | {
|
|
19
|
+
error?: undefined;
|
|
19
20
|
liveCopy: "derived" | "not_derived" | "unchanged";
|
|
20
21
|
liveCopyReason?: string;
|
|
21
|
-
error?: undefined;
|
|
22
22
|
ok: boolean;
|
|
23
23
|
} | {
|
|
24
24
|
error?: undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const SCHEMA_SNAPSHOT = {
|
|
2
2
|
statements: [
|
|
3
3
|
'CREATE SEQUENCE IF NOT EXISTS "agent_chat_submission_inbox_queue_order_seq";',
|
|
4
|
+
'CREATE SEQUENCE IF NOT EXISTS "agent_run_projection_retry_order_seq";',
|
|
4
5
|
'CREATE SEQUENCE IF NOT EXISTS "app_resource_change_seq";',
|
|
5
6
|
'CREATE SEQUENCE IF NOT EXISTS "automation_trigger_event_queue_sequence_id_seq";',
|
|
6
7
|
'CREATE TABLE "_better_auth_migrations" (\n "version" bigint NOT NULL\n);',
|
|
@@ -50,7 +51,7 @@ export const SCHEMA_SNAPSHOT = {
|
|
|
50
51
|
'CREATE TABLE "agent_review_thread_preferences" (\n "thread_id" text NOT NULL,\n "user_email" text NOT NULL,\n "muted" integer DEFAULT 0 NOT NULL,\n "unread" integer DEFAULT 0 NOT NULL,\n "updated_at" text NOT NULL\n);',
|
|
51
52
|
'CREATE TABLE "agent_run_events" (\n "run_id" text NOT NULL,\n "seq" bigint NOT NULL,\n "event_at" bigint,\n "event_data" text NOT NULL\n);',
|
|
52
53
|
'CREATE TABLE "agent_run_outcome_daily" (\n "day" text NOT NULL,\n "status" text NOT NULL,\n "terminal_reason" text DEFAULT \'\'::text NOT NULL,\n "run_count" bigint DEFAULT 0 NOT NULL\n);',
|
|
53
|
-
'CREATE TABLE "agent_runs" (\n "id" text NOT NULL,\n "thread_id" text NOT NULL,\n "status" text DEFAULT \'running\'::text NOT NULL,\n "abort_reason" text,\n "started_at" bigint NOT NULL,\n "completed_at" bigint,\n "heartbeat_at" bigint,\n "last_progress_at" bigint,\n "turn_id" text,\n "error_code" text,\n "error_detail" text,\n "terminal_reason" text,\n "dispatch_mode" text,\n "diag_stage" text,\n "dispatch_payload" text,\n "peak_rss_mb" bigint,\n "continuation_order" bigint,\n "thread_projection_pending" boolean DEFAULT false NOT NULL,\n "worker_stage" text,\n "in_flight_since" bigint,\n "auto_continue_of" text,\n "worker_generation" bigint\n);',
|
|
54
|
+
'CREATE TABLE "agent_runs" (\n "id" text NOT NULL,\n "thread_id" text NOT NULL,\n "status" text DEFAULT \'running\'::text NOT NULL,\n "abort_reason" text,\n "started_at" bigint NOT NULL,\n "completed_at" bigint,\n "heartbeat_at" bigint,\n "last_progress_at" bigint,\n "turn_id" text,\n "error_code" text,\n "error_detail" text,\n "terminal_reason" text,\n "dispatch_mode" text,\n "diag_stage" text,\n "dispatch_payload" text,\n "peak_rss_mb" bigint,\n "continuation_order" bigint,\n "thread_projection_pending" boolean DEFAULT false NOT NULL,\n "thread_projection_retry_order" bigint,\n "thread_projection_error_code" text,\n "worker_stage" text,\n "in_flight_since" bigint,\n "auto_continue_of" text,\n "worker_generation" bigint\n);',
|
|
54
55
|
'CREATE TABLE "agent_satisfaction_scores" (\n "id" text NOT NULL,\n "thread_id" text NOT NULL,\n "user_id" text,\n "frustration_score" real DEFAULT 0 NOT NULL,\n "rephrasing_score" real DEFAULT 0 NOT NULL,\n "abandonment_score" real DEFAULT 0 NOT NULL,\n "sentiment_score" real DEFAULT 0 NOT NULL,\n "length_trend_score" real DEFAULT 0 NOT NULL,\n "computed_at" bigint NOT NULL\n);',
|
|
55
56
|
'CREATE TABLE "agent_team_run_queue" (\n "task_id" text NOT NULL,\n "thread_id" text NOT NULL,\n "run_id" text NOT NULL,\n "status" text DEFAULT \'queued\'::text NOT NULL,\n "owner_email" text,\n "org_id" text,\n "payload" text NOT NULL,\n "continuation_count" bigint DEFAULT 0 NOT NULL,\n "attempts" bigint DEFAULT 0 NOT NULL,\n "created_at" bigint NOT NULL,\n "updated_at" bigint NOT NULL,\n "reconciliation_attempted_at" bigint\n);',
|
|
56
57
|
'CREATE TABLE "agent_tool_approval_policies" (\n "id" text NOT NULL,\n "owner_email" text NOT NULL,\n "org_id" text,\n "tool_name" text NOT NULL,\n "enabled" boolean NOT NULL,\n "created_at" bigint NOT NULL,\n "updated_at" bigint NOT NULL,\n "tool_revision" text\n);',
|
|
@@ -432,6 +433,7 @@ export const SCHEMA_SNAPSHOT = {
|
|
|
432
433
|
"CREATE INDEX idx_review_suggestion_proposal_members ON public.agent_review_suggestions USING btree (proposal_id, created_at);",
|
|
433
434
|
"CREATE INDEX idx_review_suggestions_resource ON public.agent_review_suggestions USING btree (resource_type, resource_id, created_at);",
|
|
434
435
|
"CREATE INDEX agent_runs_thread_projection_pending_idx ON public.agent_runs USING btree (completed_at, id) WHERE (thread_projection_pending IS TRUE);",
|
|
436
|
+
"CREATE INDEX agent_runs_thread_projection_retry_order_idx ON public.agent_runs USING btree (thread_projection_retry_order NULLS FIRST, completed_at, id) WHERE (thread_projection_pending IS TRUE);",
|
|
435
437
|
"CREATE INDEX idx_satisfaction_thread ON public.agent_satisfaction_scores USING btree (thread_id);",
|
|
436
438
|
"CREATE INDEX idx_satisfaction_user ON public.agent_satisfaction_scores USING btree (user_id, computed_at);",
|
|
437
439
|
"CREATE INDEX idx_agent_team_run_queue_reconciliation ON public.agent_team_run_queue USING btree (status, reconciliation_attempted_at, updated_at) WHERE (status = ANY (ARRAY['queued'::text, 'running'::text]));",
|
|
@@ -833,12 +835,23 @@ export const SCHEMA_SNAPSHOT = {
|
|
|
833
835
|
"agent-run-liveness-and-diagnostics-columns",
|
|
834
836
|
"2a9d24ed6ffbea78",
|
|
835
837
|
],
|
|
838
|
+
[
|
|
839
|
+
"agent_runs",
|
|
840
|
+
"agent-run-thread-projection-quarantine",
|
|
841
|
+
"8eb065e2325e20ea",
|
|
842
|
+
],
|
|
836
843
|
["agent_runs", "agent-run-thread-projection-retry", "d564128e21e3b424"],
|
|
844
|
+
[
|
|
845
|
+
"agent_runs",
|
|
846
|
+
"agent-run-thread-projection-retry-fairness",
|
|
847
|
+
"36150eae08f95f3d",
|
|
848
|
+
],
|
|
837
849
|
["agent_runs", "agent-tool-ledger-arguments-hash", "495946626eda9dc4"],
|
|
838
850
|
["agent_runs", "agent-tool-ledger-chat-ui-result", "288137f1126005f6"],
|
|
839
851
|
["agent_runs", "agent-tool-ledger-result-type", "d4fe90219a833db8"],
|
|
840
852
|
["agent_runs", "agent-turn-execution-leases-and-steps", "bc8cfdc3fc695e2a"],
|
|
841
853
|
["agent_runs", "baseline", null],
|
|
854
|
+
["agent_runs", "thread-projection-retry-quarantine-runtime-schema", null],
|
|
842
855
|
["agent_runs", "widen-bigint", null],
|
|
843
856
|
["agent_team_run_queue", "baseline", null],
|
|
844
857
|
[
|
|
@@ -16,6 +16,10 @@ export declare class AutomationRecoveryStorageError extends Error {
|
|
|
16
16
|
readonly errorCode = "automation_recovery_storage_unavailable";
|
|
17
17
|
constructor(cause: unknown);
|
|
18
18
|
}
|
|
19
|
+
export declare class AutomationRecoveryProjectionDeferredError extends Error {
|
|
20
|
+
readonly errorCode = "automation_recovery_projection_deferred";
|
|
21
|
+
constructor(runId: string);
|
|
22
|
+
}
|
|
19
23
|
export declare function withAutomationRecoveryStorage<T>(operation: () => Promise<T>): Promise<T>;
|
|
20
24
|
export declare function readAutomationRecoveryEvents(threadId: string, turnId: string): Promise<CurrentTurnRunEvent[]>;
|
|
21
25
|
export declare function readAutomationRunDeliveryNote(runId: string): Promise<string>;
|
|
@@ -16,6 +16,13 @@ export class AutomationRecoveryStorageError extends Error {
|
|
|
16
16
|
this.name = "AutomationRecoveryStorageError";
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
export class AutomationRecoveryProjectionDeferredError extends Error {
|
|
20
|
+
errorCode = "automation_recovery_projection_deferred";
|
|
21
|
+
constructor(runId) {
|
|
22
|
+
super(`Automation recovery is waiting for run ${runId} to project its thread`);
|
|
23
|
+
this.name = "AutomationRecoveryProjectionDeferredError";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
19
26
|
export async function withAutomationRecoveryStorage(operation) {
|
|
20
27
|
try {
|
|
21
28
|
return await operation();
|
|
@@ -8,7 +8,7 @@ import { actionsToEngineTools, filterInitialEngineTools, resolveOwnerEngineApiKe
|
|
|
8
8
|
import { foldReapedRunIntoThread } from "../agent/reaped-run-thread.js";
|
|
9
9
|
import { AgentLoopAbortedError, runAgentLoopDirectWithSoftTimeout, } from "../agent/run-loop-with-resume.js";
|
|
10
10
|
import { abortRun, resolveBackgroundAutomationSoftTimeoutMs, resolveBackgroundRunHardTimeoutMs, startRun, } from "../agent/run-manager.js";
|
|
11
|
-
import { claimBackgroundRun, beginAgentExecutionStep, completeAgentExecutionStep, markAgentExecutionStepDispatched, insertRun, tryClaimRunSlot, releaseBackgroundRunBeforeStart, } from "../agent/run-store.js";
|
|
11
|
+
import { claimBackgroundRun, beginAgentExecutionStep, completeAgentExecutionStep, markAgentExecutionStepDispatched, insertRun, tryClaimRunSlot, retryRunThreadProjection, releaseBackgroundRunBeforeStart, } from "../agent/run-store.js";
|
|
12
12
|
import { buildAssistantMessageFromRunJournal } from "../agent/run-thread-projector.js";
|
|
13
13
|
import { buildCurrentTimeUserContext, buildRuntimeContextPrompt, } from "../agent/runtime-context.js";
|
|
14
14
|
import { buildAssistantMessage, buildUserMessage, extractThreadMeta, foldAssistantTurn, upsertUserMessage, threadDataToEngineMessages, threadMessageTextForEngine, } from "../agent/thread-data-builder.js";
|
|
@@ -28,7 +28,7 @@ import { runWithRequestContext, } from "../server/request-context.js";
|
|
|
28
28
|
import { normalizeReasoningEffortForRequest } from "../shared/reasoning-effort.js";
|
|
29
29
|
import automationNoOpAction, { AUTOMATION_NO_OP_TOOL, automationNoOpSchema, } from "./actions/automation-no-op.js";
|
|
30
30
|
import { applyAutomationFailure, automationOwnerKind, classifyAutomationFailure, CONFIG_INVALID_ERROR_CODE, CONNECTION_REQUIRED_ERROR_CODE, MISSING_TOOLS_ERROR_CODE, OWNER_MISSING_ERROR_CODE, pausedMessage, withDeliveryNote, } from "./automation-outcome.js";
|
|
31
|
-
import { AutomationRecoveryStorageError, deliveryNoteForEvents, readAutomationRecoveryEvents, readAutomationRunDeliveryNote, withAutomationRecoveryStorage, } from "./automation-recovery.js";
|
|
31
|
+
import { AutomationRecoveryProjectionDeferredError, AutomationRecoveryStorageError, deliveryNoteForEvents, readAutomationRecoveryEvents, readAutomationRunDeliveryNote, withAutomationRecoveryStorage, } from "./automation-recovery.js";
|
|
32
32
|
import { automationRunTerminalError, } from "./automation-terminal-error.js";
|
|
33
33
|
import { inspectAutomationWork } from "./automation-work-evidence.js";
|
|
34
34
|
import { effectiveTimezone } from "./cron.js";
|
|
@@ -338,6 +338,7 @@ export async function runBackgroundAutomation(options, deps) {
|
|
|
338
338
|
catch (err) {
|
|
339
339
|
if (err instanceof StaleExecutionFenceError ||
|
|
340
340
|
err instanceof AutomationSchedulerLeaseLostError ||
|
|
341
|
+
err instanceof AutomationRecoveryProjectionDeferredError ||
|
|
341
342
|
err instanceof AutomationRecoveryStorageError ||
|
|
342
343
|
(err instanceof BackgroundAutomationRunError &&
|
|
343
344
|
(err.errorCode === "background_automation_claim_lost" ||
|
|
@@ -460,6 +461,8 @@ export async function runJobRunner(options) {
|
|
|
460
461
|
}
|
|
461
462
|
catch (err) {
|
|
462
463
|
if (err instanceof AutomationSchedulerLeaseLostError ||
|
|
464
|
+
err instanceof AutomationRecoveryProjectionDeferredError ||
|
|
465
|
+
err instanceof AutomationRecoveryStorageError ||
|
|
463
466
|
(err instanceof BackgroundAutomationRunError &&
|
|
464
467
|
err.errorCode === "background_automation_history_write_failed"))
|
|
465
468
|
throw err;
|
|
@@ -859,6 +862,20 @@ async function executeBackgroundAutomation(options, deps, historyId, runIdRef) {
|
|
|
859
862
|
const systemPrompt = `${baseSystemPrompt}${workspaceAppContext ? `\n\n${workspaceAppContext}` : ""}\n\n${messages.noOpInstruction}`;
|
|
860
863
|
assertHardDeadline(options.hardDeadlineAt);
|
|
861
864
|
const resume = options.resume;
|
|
865
|
+
if (resume) {
|
|
866
|
+
const projection = await withAutomationRecoveryStorage(() => retryRunThreadProjection(resume.previousRunId));
|
|
867
|
+
switch (projection.state) {
|
|
868
|
+
case "deferred":
|
|
869
|
+
throw new AutomationRecoveryProjectionDeferredError(resume.previousRunId);
|
|
870
|
+
case "untracked":
|
|
871
|
+
// Inspection found this run before retention pruned its cleared
|
|
872
|
+
// projection marker; a pending marker prevents that pruning.
|
|
873
|
+
break;
|
|
874
|
+
case "projected":
|
|
875
|
+
case "already_projected":
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
862
879
|
const thread = resume
|
|
863
880
|
? await withAutomationRecoveryStorage(() => getThread(resume.threadId))
|
|
864
881
|
: await createThread(ownerEmail, {
|
package/dist/jobs/scheduler.js
CHANGED
|
@@ -4,7 +4,7 @@ import { withDbExec } from "../db/client.js";
|
|
|
4
4
|
import { ensureTable as ensureResourcesTable, resourceGetByPath, resourceListAllOwners, resourcePutIfCurrent, resourcePutIfCurrentInTransaction, } from "../resources/store.js";
|
|
5
5
|
import { countAutomationCredentialState, trackAutomationPaused, trackAutomationResumed, } from "./automation-events.js";
|
|
6
6
|
import { applyAutomationFailure, classifyAutomationFailure, CLEAR_FAILURE_STATE, CONFIG_INVALID_ERROR_CODE, hasStalePause, isMissingCredentialCode, isPausedByFramework, isReservedIdentityBlocked, isTransientPauseProbeDue, OWNER_MISSING_ERROR_CODE, OWNER_RESERVED_ERROR_CODE, pauseNow, RESUME_AUTOMATION_PATCH, reservedIdentityMessage, runtimeFailureNextRun, TRANSIENT_PROBE_RESUME_PATCH, withDeliveryNote, } from "./automation-outcome.js";
|
|
7
|
-
import { AutomationRecoveryStorageError, deliveryNoteForEvents, inspectAutomationRecovery, } from "./automation-recovery.js";
|
|
7
|
+
import { AutomationRecoveryProjectionDeferredError, AutomationRecoveryStorageError, deliveryNoteForEvents, inspectAutomationRecovery, } from "./automation-recovery.js";
|
|
8
8
|
import { backgroundRunCutOffReason, checkBackgroundAutomationCredentials, isBackgroundAutomationRunActive, resolveBackgroundAutomationIdentity, runBackgroundAutomation, runJobRunner, startBackgroundAutomationHistory, } from "./background-automation-runner.js";
|
|
9
9
|
import { nextOccurrence, isValidCron, describeCron, effectiveTimezone, } from "./cron.js";
|
|
10
10
|
import { buildJobResourceContent, isRecoveredFactoryJob, jobBelongsToApp, parseJobResource, patchJobFrontmatterFields, recoveredFactoryOwnerOrgId, } from "./frontmatter.js";
|
|
@@ -993,6 +993,8 @@ async function executeJob(resource, meta, body, deps, now, options = {}) {
|
|
|
993
993
|
const failure = classifyAutomationFailure(err);
|
|
994
994
|
if (err instanceof AutomationSchedulerLeaseLostError)
|
|
995
995
|
throw err;
|
|
996
|
+
if (err instanceof AutomationRecoveryProjectionDeferredError)
|
|
997
|
+
throw err;
|
|
996
998
|
if (err instanceof AutomationRecoveryStorageError)
|
|
997
999
|
throw err;
|
|
998
1000
|
if (failure.code === "background_automation_history_write_failed")
|
|
@@ -21,17 +21,18 @@
|
|
|
21
21
|
*
|
|
22
22
|
* ## Resolution + safety model
|
|
23
23
|
*
|
|
24
|
-
* -
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
24
|
+
* - A selected org's bound workspace URL wins for org-scoped requests; the
|
|
25
|
+
* deployment's `AGENT_NATIVE_ORG_DIRECTORY_URL` or
|
|
26
|
+
* `AGENT_NATIVE_IDENTITY_HUB_URL` is the fallback when no binding exists.
|
|
27
|
+
* When neither is set the feature is inactive — `fetchOrgApps()` returns
|
|
28
|
+
* `[]` and nothing changes anywhere (asserted by a test).
|
|
29
29
|
* - `fetchOrgAppsResult()` preserves directory availability for callers
|
|
30
30
|
* that cannot mistake a failed lookup for an empty directory.
|
|
31
31
|
* `fetchOrgApps()` remains the best-effort compatibility wrapper: on ANY
|
|
32
32
|
* error it returns `[]` and NEVER throws, so cross-app verbs still degrade
|
|
33
33
|
* silently to their exact current local-only behavior.
|
|
34
|
-
* -
|
|
34
|
+
* - Mounted `/dispatch` requests retry at the host root only after a 404.
|
|
35
|
+
* Credentialed requests require HTTPS and use manual redirects. A short
|
|
35
36
|
* in-memory TTL cache (default 60s) is keyed by directory origin and
|
|
36
37
|
* caller identity/org scope so sibling app lists never cross tenants.
|
|
37
38
|
* Successful responses are cached for 60s; transient unavailable results
|
|
@@ -51,7 +52,7 @@ export interface OrgApp {
|
|
|
51
52
|
whenToUse?: string[];
|
|
52
53
|
aliases?: string[];
|
|
53
54
|
}
|
|
54
|
-
export type OrgDirectoryUnavailableReason = "not-configured" | "authentication" | "unauthorized" | "authorization" | "invalid-origin" | "missing-route" | "timeout" | "network" | "invalid-response" | "ssrf-blocked" | "server-error" | "http-error";
|
|
55
|
+
export type OrgDirectoryUnavailableReason = "not-configured" | "authentication" | "unauthorized" | "authorization" | "invalid-origin" | "missing-route" | "timeout" | "network" | "invalid-response" | "ssrf-blocked" | "workspace-url-unavailable" | "server-error" | "http-error";
|
|
55
56
|
export type OrgDirectoryFetchResult = {
|
|
56
57
|
status: "available";
|
|
57
58
|
apps: OrgApp[];
|
|
@@ -127,11 +127,92 @@ function serviceScopedCacheKey(origin, orgId, includeDirectoryApp = false, stric
|
|
|
127
127
|
].join("|");
|
|
128
128
|
}
|
|
129
129
|
async function fetchOrgAppsResultInternal(opts, strictValidation = true) {
|
|
130
|
+
const env = opts?.env ?? process.env;
|
|
131
|
+
const directoryBase = await resolveDirectoryBaseUrl(opts, env);
|
|
132
|
+
if (!directoryBase.ok)
|
|
133
|
+
return directoryBase.result;
|
|
134
|
+
const result = await fetchOrgAppsAtBaseUrl({ ...opts, directoryBaseUrl: directoryBase.url }, strictValidation);
|
|
135
|
+
if (result.status !== "unavailable" || result.reason !== "missing-route") {
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
const rootUrl = new URL(directoryBase.url).origin;
|
|
139
|
+
if (rootUrl === directoryBase.url.replace(/\/+$/, ""))
|
|
140
|
+
return result;
|
|
141
|
+
return fetchOrgAppsAtBaseUrl({ ...opts, directoryBaseUrl: rootUrl }, strictValidation);
|
|
142
|
+
}
|
|
143
|
+
async function resolveDirectoryBaseUrl(opts, env) {
|
|
144
|
+
if (opts?.directoryBaseUrl !== undefined) {
|
|
145
|
+
const url = normalizeDirectoryBaseUrl(opts.directoryBaseUrl);
|
|
146
|
+
return url
|
|
147
|
+
? { ok: true, url }
|
|
148
|
+
: {
|
|
149
|
+
ok: false,
|
|
150
|
+
result: { status: "unavailable", reason: "invalid-origin" },
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const serviceOrgId = opts?.serviceOrgId?.trim();
|
|
154
|
+
let orgId = serviceOrgId || undefined;
|
|
155
|
+
try {
|
|
156
|
+
if (!orgId) {
|
|
157
|
+
const { getRequestOrgId } = await import("../server/request-context.js");
|
|
158
|
+
orgId = getRequestOrgId();
|
|
159
|
+
}
|
|
160
|
+
if (orgId) {
|
|
161
|
+
const { getOrgWorkspaceUrl } = await import("../org/context.js");
|
|
162
|
+
const workspaceUrl = await getOrgWorkspaceUrl(orgId);
|
|
163
|
+
if (workspaceUrl) {
|
|
164
|
+
const url = normalizeOrgWorkspaceDirectoryBaseUrl(workspaceUrl);
|
|
165
|
+
return url
|
|
166
|
+
? { ok: true, url }
|
|
167
|
+
: {
|
|
168
|
+
ok: false,
|
|
169
|
+
result: { status: "unavailable", reason: "invalid-origin" },
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
result: { status: "unavailable", reason: "workspace-url-unavailable" },
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const configuredUrl = configuredOrgDirectoryUrl(env);
|
|
181
|
+
const url = resolveOrgDirectoryOrigin(env);
|
|
182
|
+
if (url)
|
|
183
|
+
return { ok: true, url };
|
|
184
|
+
return {
|
|
185
|
+
ok: false,
|
|
186
|
+
result: {
|
|
187
|
+
status: "unavailable",
|
|
188
|
+
reason: configuredUrl ? "invalid-origin" : "not-configured",
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function normalizeOrgWorkspaceDirectoryBaseUrl(value) {
|
|
193
|
+
try {
|
|
194
|
+
const url = new URL(value);
|
|
195
|
+
if (url.protocol !== "https:" ||
|
|
196
|
+
url.username ||
|
|
197
|
+
url.password ||
|
|
198
|
+
url.search ||
|
|
199
|
+
url.hash ||
|
|
200
|
+
(url.pathname !== "/" && url.pathname !== "")) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
return `${url.origin}/dispatch`;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// coercion-ok: Invalid bindings become typed invalid-origin failures above.
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function fetchOrgAppsAtBaseUrl(opts, strictValidation) {
|
|
130
211
|
const env = opts?.env ?? process.env;
|
|
131
212
|
const hasExplicitBaseUrl = opts?.directoryBaseUrl !== undefined;
|
|
132
213
|
const hasConfiguredEnvUrl = configuredOrgDirectoryUrl(env) !== null;
|
|
133
214
|
const origin = hasExplicitBaseUrl
|
|
134
|
-
? normalizeDirectoryBaseUrl(opts
|
|
215
|
+
? normalizeDirectoryBaseUrl(opts.directoryBaseUrl ?? "")
|
|
135
216
|
: resolveOrgDirectoryOrigin(env);
|
|
136
217
|
if (!origin) {
|
|
137
218
|
return {
|
|
@@ -27,43 +27,43 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
27
27
|
thumbsUpRate: number;
|
|
28
28
|
avgEvalScore: number;
|
|
29
29
|
} | {
|
|
30
|
-
ok?: undefined;
|
|
31
30
|
error: string;
|
|
32
31
|
message: string;
|
|
33
32
|
summary?: undefined;
|
|
34
33
|
spans?: undefined;
|
|
35
|
-
} | {
|
|
36
|
-
error?: undefined;
|
|
37
34
|
ok?: undefined;
|
|
35
|
+
} | {
|
|
38
36
|
message?: undefined;
|
|
39
37
|
summary: import("./types.js").TraceSummary;
|
|
40
38
|
spans: import("./types.js").TraceSpan[];
|
|
41
|
-
} | {
|
|
42
39
|
error?: undefined;
|
|
43
40
|
ok?: undefined;
|
|
41
|
+
} | {
|
|
44
42
|
message?: undefined;
|
|
45
43
|
summary?: undefined;
|
|
46
44
|
spans?: undefined;
|
|
47
45
|
traceMissing: true;
|
|
48
46
|
id: string;
|
|
49
|
-
} | {
|
|
50
47
|
error?: undefined;
|
|
51
48
|
ok?: undefined;
|
|
49
|
+
} | {
|
|
52
50
|
message?: undefined;
|
|
53
51
|
summary?: undefined;
|
|
54
52
|
spans?: undefined;
|
|
55
53
|
traceMissing?: undefined;
|
|
56
54
|
id: string;
|
|
57
|
-
|
|
55
|
+
error?: undefined;
|
|
58
56
|
ok?: undefined;
|
|
57
|
+
} | {
|
|
59
58
|
message?: undefined;
|
|
60
59
|
summary?: undefined;
|
|
61
60
|
spans?: undefined;
|
|
62
61
|
error: any;
|
|
62
|
+
ok?: undefined;
|
|
63
63
|
} | {
|
|
64
|
-
error?: undefined;
|
|
65
64
|
message?: undefined;
|
|
66
65
|
summary?: undefined;
|
|
67
66
|
spans?: undefined;
|
|
67
|
+
error?: undefined;
|
|
68
68
|
ok: boolean;
|
|
69
69
|
}>>;
|
|
@@ -42,8 +42,8 @@ export declare function handleDeleteResource(event: any): Promise<{
|
|
|
42
42
|
error: string;
|
|
43
43
|
ok?: undefined;
|
|
44
44
|
} | {
|
|
45
|
-
ok: boolean;
|
|
46
45
|
error?: undefined;
|
|
46
|
+
ok: boolean;
|
|
47
47
|
}>;
|
|
48
48
|
export declare function handleUploadResource(event: any): Promise<import("./store.js").Resource | {
|
|
49
49
|
error: string;
|
|
@@ -63,10 +63,10 @@ export declare function handleUploadResource(event: any): Promise<import("./stor
|
|
|
63
63
|
runId: string | null;
|
|
64
64
|
expiresAt: number | null;
|
|
65
65
|
metadata: string | null;
|
|
66
|
+
error?: undefined;
|
|
66
67
|
url: string;
|
|
67
68
|
provider: string;
|
|
68
69
|
storageSetupRequired?: undefined;
|
|
69
|
-
error?: undefined;
|
|
70
70
|
} | {
|
|
71
71
|
error: string;
|
|
72
72
|
storageSetupRequired: boolean;
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -57,39 +57,39 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
|
|
|
57
57
|
} | {
|
|
58
58
|
error: string;
|
|
59
59
|
errorCode?: undefined;
|
|
60
|
-
status?: undefined;
|
|
61
60
|
ok?: undefined;
|
|
61
|
+
status?: undefined;
|
|
62
62
|
} | {
|
|
63
63
|
error: string;
|
|
64
64
|
errorCode: string;
|
|
65
|
-
status?: undefined;
|
|
66
65
|
ok?: undefined;
|
|
66
|
+
status?: undefined;
|
|
67
67
|
} | {
|
|
68
|
+
error?: undefined;
|
|
68
69
|
errorCode?: undefined;
|
|
69
70
|
ok: boolean;
|
|
70
71
|
status: string;
|
|
71
|
-
error?: undefined;
|
|
72
72
|
} | {
|
|
73
|
+
ok?: undefined;
|
|
73
74
|
error: string;
|
|
74
75
|
removed?: undefined;
|
|
75
|
-
ok?: undefined;
|
|
76
76
|
} | {
|
|
77
|
+
error?: undefined;
|
|
77
78
|
ok: boolean;
|
|
78
79
|
removed: boolean;
|
|
79
|
-
error?: undefined;
|
|
80
80
|
}>>;
|
|
81
81
|
export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
82
|
+
ok?: undefined;
|
|
82
83
|
error: string;
|
|
83
84
|
note?: undefined;
|
|
84
|
-
ok?: undefined;
|
|
85
85
|
} | {
|
|
86
|
+
error?: undefined;
|
|
86
87
|
ok: boolean;
|
|
87
88
|
note?: undefined;
|
|
88
|
-
error?: undefined;
|
|
89
89
|
} | {
|
|
90
|
+
error?: undefined;
|
|
90
91
|
ok: boolean;
|
|
91
92
|
note: string;
|
|
92
|
-
error?: undefined;
|
|
93
93
|
} | {
|
|
94
94
|
note?: undefined;
|
|
95
95
|
ok: boolean;
|
|
@@ -117,11 +117,11 @@ export declare function createAdHocSecretHandler(): import("h3").EventHandlerWit
|
|
|
117
117
|
} | {
|
|
118
118
|
error: string;
|
|
119
119
|
} | {
|
|
120
|
+
error?: undefined;
|
|
120
121
|
ok: boolean;
|
|
121
122
|
key: string;
|
|
122
|
-
error?: undefined;
|
|
123
123
|
} | {
|
|
124
|
+
error?: undefined;
|
|
124
125
|
ok: boolean;
|
|
125
126
|
removed: boolean;
|
|
126
|
-
error?: undefined;
|
|
127
127
|
}>>;
|