@agent-native/core 0.171.1 → 0.172.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/corpus/README.md +1 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +4 -5
- package/corpus/templates/calendar/actions/create-event.ts +11 -3
- package/corpus/templates/calendar/app/components/calendar/AddCalendarDialog.tsx +56 -51
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +3 -0
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +1 -1
- package/corpus/templates/calendar/app/components/calendar/PeopleSearchDialog.tsx +54 -51
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +1 -1
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +4 -1
- package/corpus/templates/calendar/app/hooks/use-people.ts +23 -13
- package/corpus/templates/calendar/app/lib/event-layout.ts +21 -6
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +3 -0
- package/corpus/templates/calendar/changelog/2026-08-24-overlapping-events-use-space-better.md +5 -0
- package/corpus/templates/calendar/server/lib/google-api.ts +9 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +59 -2
- package/corpus/templates/calendar/server/lib/people-search.ts +43 -28
- package/corpus/templates/calendar/shared/api.ts +2 -0
- package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +3 -3
- package/corpus/templates/mail/app/root.tsx +29 -0
- package/dist/agent/actions/set-tool-approval-policy.d.ts +8 -0
- package/dist/agent/actions/set-tool-approval-policy.js +26 -0
- package/dist/agent/harness/migrations.js +8 -0
- package/dist/agent/harness/runner.js +114 -33
- package/dist/agent/harness/store.d.ts +9 -0
- package/dist/agent/harness/store.js +87 -23
- package/dist/agent/production-agent.d.ts +3 -0
- package/dist/agent/production-agent.js +23 -1
- package/dist/agent/run-manager.js +40 -9
- package/dist/agent/tool-approval-migrations.d.ts +21 -0
- package/dist/agent/tool-approval-migrations.js +32 -0
- package/dist/agent/tool-approval-store.d.ts +11 -0
- package/dist/agent/tool-approval-store.js +76 -1
- package/dist/cli/code-agent-executor.js +20 -3
- package/dist/client/AgentPanel.d.ts +4 -1
- package/dist/client/AgentPanel.js +11 -3
- package/dist/client/AssistantChat.d.ts +5 -5
- package/dist/client/AssistantChat.js +24 -11
- package/dist/client/DefaultSpinner.d.ts +3 -1
- package/dist/client/DefaultSpinner.js +2 -2
- package/dist/client/chat/tool-call-display.d.ts +5 -5
- package/dist/client/chat/tool-call-display.js +27 -11
- package/dist/client/chat-first/apps-rail.d.ts +1 -1
- package/dist/client/chat-first/apps-rail.js +11 -8
- package/dist/client/chat-first/copy.js +1 -0
- package/dist/client/chat-first/types.d.ts +1 -0
- package/dist/client/resources/McpIntegrationDialog.js +5 -5
- package/dist/client/resources/mcp-integration-catalog.js +1 -1
- package/dist/coding-tools/index.d.ts +31 -0
- package/dist/coding-tools/index.js +139 -3
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.js +3 -0
- package/dist/localization/core-messages/de-DE.js +3 -0
- package/dist/localization/core-messages/en-US.d.ts +3 -0
- package/dist/localization/core-messages/en-US.js +3 -0
- package/dist/localization/core-messages/es-ES.js +3 -0
- package/dist/localization/core-messages/fr-FR.js +3 -0
- package/dist/localization/core-messages/hi-IN.js +3 -0
- package/dist/localization/core-messages/ja-JP.js +3 -0
- package/dist/localization/core-messages/ko-KR.js +3 -0
- package/dist/localization/core-messages/pt-BR.js +3 -0
- package/dist/localization/core-messages/zh-CN.js +3 -0
- package/dist/localization/core-messages/zh-TW.js +3 -0
- package/dist/localization/core-messages.d.ts +3 -0
- package/dist/localization/default-messages.d.ts +1 -0
- package/dist/localization/default-messages.js +7 -6
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/progress/routes.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 +11 -11
- package/dist/server/action-discovery.js +5 -0
- package/dist/server/agent-chat/prompt-resources.d.ts +9 -0
- package/dist/server/agent-chat/prompt-resources.js +12 -2
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/release-schema.js +4 -1
- package/dist/triggers/dispatcher.d.ts +24 -0
- package/dist/triggers/dispatcher.js +76 -18
- package/dist/vite/action-types-plugin.js +4 -0
- package/package.json +2 -1
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { getDbExec, intType, isPostgres } from "../../db/client.js";
|
|
2
2
|
import { ensureColumnExists, ensureIndexExists, ensureTableExists, } from "../../db/ddl-guard.js";
|
|
3
|
+
export class AgentHarnessSessionConflictError extends Error {
|
|
4
|
+
constructor(sessionId) {
|
|
5
|
+
super(`Harness session ${sessionId} changed concurrently; retry the operation.`);
|
|
6
|
+
this.name = "AgentHarnessSessionConflictError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function isAgentHarnessSessionConflictError(error) {
|
|
10
|
+
return (error instanceof AgentHarnessSessionConflictError ||
|
|
11
|
+
(error instanceof Error &&
|
|
12
|
+
error.name === "AgentHarnessSessionConflictError"));
|
|
13
|
+
}
|
|
3
14
|
let initPromise;
|
|
4
15
|
export async function ensureAgentHarnessSessionTables() {
|
|
5
16
|
if (!initPromise) {
|
|
@@ -19,6 +30,7 @@ export async function ensureAgentHarnessSessionTables() {
|
|
|
19
30
|
resolved_approval_ids TEXT,
|
|
20
31
|
owner_email TEXT,
|
|
21
32
|
org_id TEXT,
|
|
33
|
+
generation ${intType()} NOT NULL DEFAULT 0,
|
|
22
34
|
created_at ${intType()} NOT NULL,
|
|
23
35
|
updated_at ${intType()} NOT NULL,
|
|
24
36
|
stopped_at ${intType()}
|
|
@@ -41,6 +53,7 @@ export async function ensureAgentHarnessSessionTables() {
|
|
|
41
53
|
await ensureColumnExists("agent_harness_sessions", col, `ALTER TABLE agent_harness_sessions ADD COLUMN IF NOT EXISTS ${col} TEXT`);
|
|
42
54
|
}
|
|
43
55
|
await ensureColumnExists("agent_harness_sessions", "stopped_at", `ALTER TABLE agent_harness_sessions ADD COLUMN IF NOT EXISTS stopped_at ${intType()}`);
|
|
56
|
+
await ensureColumnExists("agent_harness_sessions", "generation", `ALTER TABLE agent_harness_sessions ADD COLUMN IF NOT EXISTS generation ${intType()} NOT NULL DEFAULT 0`);
|
|
44
57
|
await ensureIndexExists("idx_agent_harness_sessions_thread", `CREATE INDEX IF NOT EXISTS idx_agent_harness_sessions_thread ON agent_harness_sessions(thread_id, updated_at)`);
|
|
45
58
|
await ensureIndexExists("idx_agent_harness_sessions_status", `CREATE INDEX IF NOT EXISTS idx_agent_harness_sessions_status ON agent_harness_sessions(status, updated_at)`);
|
|
46
59
|
await ensureIndexExists("idx_agent_harness_sessions_owner", `CREATE INDEX IF NOT EXISTS idx_agent_harness_sessions_owner ON agent_harness_sessions(owner_email, updated_at)`);
|
|
@@ -48,6 +61,13 @@ export async function ensureAgentHarnessSessionTables() {
|
|
|
48
61
|
}
|
|
49
62
|
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
50
63
|
await client.execute(createSql);
|
|
64
|
+
try {
|
|
65
|
+
await client.execute(`ALTER TABLE agent_harness_sessions ADD COLUMN generation ${intType()} NOT NULL DEFAULT 0`);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (!isDuplicateColumnError(error))
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
51
71
|
for (const col of [
|
|
52
72
|
"run_id",
|
|
53
73
|
"provider_session_id",
|
|
@@ -99,7 +119,9 @@ export async function ensureAgentHarnessSessionTables() {
|
|
|
99
119
|
export async function saveAgentHarnessSession(input) {
|
|
100
120
|
await ensureAgentHarnessSessionTables();
|
|
101
121
|
const now = Date.now();
|
|
102
|
-
const existing =
|
|
122
|
+
const existing = input.existingSession === undefined
|
|
123
|
+
? await getAgentHarnessSession(input.id)
|
|
124
|
+
: input.existingSession;
|
|
103
125
|
const record = {
|
|
104
126
|
id: input.id,
|
|
105
127
|
harnessName: input.harnessName,
|
|
@@ -117,33 +139,60 @@ export async function saveAgentHarnessSession(input) {
|
|
|
117
139
|
resolvedApprovalIds: input.resolvedApprovalIds ?? existing?.resolvedApprovalIds ?? [],
|
|
118
140
|
ownerEmail: input.ownerEmail ?? existing?.ownerEmail ?? null,
|
|
119
141
|
orgId: input.orgId ?? existing?.orgId ?? null,
|
|
142
|
+
generation: (input.expectedGeneration ?? existing?.generation ?? 0) + 1,
|
|
120
143
|
createdAt: existing?.createdAt ?? now,
|
|
121
144
|
updatedAt: now,
|
|
122
145
|
stoppedAt: input.stoppedAt ?? existing?.stoppedAt ?? null,
|
|
123
146
|
};
|
|
124
147
|
const client = getDbExec();
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
148
|
+
const values = [
|
|
149
|
+
record.id,
|
|
150
|
+
record.harnessName,
|
|
151
|
+
record.threadId,
|
|
152
|
+
record.runId ?? null,
|
|
153
|
+
record.providerSessionId ?? null,
|
|
154
|
+
record.status,
|
|
155
|
+
serializeJson(record.resumeState),
|
|
156
|
+
record.workspaceRef ?? null,
|
|
157
|
+
serializeJson(record.pendingApproval),
|
|
158
|
+
serializeJson(record.resolvedApprovalIds),
|
|
159
|
+
record.ownerEmail ?? null,
|
|
160
|
+
record.orgId ?? null,
|
|
161
|
+
record.generation,
|
|
162
|
+
record.createdAt,
|
|
163
|
+
record.updatedAt,
|
|
164
|
+
record.stoppedAt ?? null,
|
|
165
|
+
];
|
|
166
|
+
if (!existing) {
|
|
167
|
+
const inserted = await client.execute({
|
|
168
|
+
sql: `INSERT INTO agent_harness_sessions (
|
|
169
|
+
id, harness_name, thread_id, run_id, provider_session_id, status,
|
|
170
|
+
resume_state, workspace_ref, pending_approval, resolved_approval_ids,
|
|
171
|
+
owner_email, org_id, generation, created_at, updated_at, stopped_at
|
|
172
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
173
|
+
ON CONFLICT(id) DO NOTHING`,
|
|
174
|
+
args: values,
|
|
175
|
+
});
|
|
176
|
+
if (inserted.rowsAffected === 1)
|
|
177
|
+
return record;
|
|
178
|
+
const current = await getAgentHarnessSession(input.id);
|
|
179
|
+
if (!current)
|
|
180
|
+
throw new Error("Harness session insert did not persist.");
|
|
181
|
+
return saveAgentHarnessSession({
|
|
182
|
+
...input,
|
|
183
|
+
expectedGeneration: undefined,
|
|
184
|
+
existingSession: undefined,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
const expectedGeneration = input.expectedGeneration ?? existing.generation;
|
|
188
|
+
const updated = await client.execute({
|
|
189
|
+
sql: `UPDATE agent_harness_sessions SET
|
|
190
|
+
harness_name = ?, thread_id = ?, run_id = ?, provider_session_id = ?,
|
|
191
|
+
status = ?, resume_state = ?, workspace_ref = ?, pending_approval = ?,
|
|
192
|
+
resolved_approval_ids = ?, owner_email = ?, org_id = ?, generation = ?,
|
|
193
|
+
updated_at = ?, stopped_at = ?
|
|
194
|
+
WHERE id = ? AND generation = ?`,
|
|
145
195
|
args: [
|
|
146
|
-
record.id,
|
|
147
196
|
record.harnessName,
|
|
148
197
|
record.threadId,
|
|
149
198
|
record.runId ?? null,
|
|
@@ -155,11 +204,16 @@ export async function saveAgentHarnessSession(input) {
|
|
|
155
204
|
serializeJson(record.resolvedApprovalIds),
|
|
156
205
|
record.ownerEmail ?? null,
|
|
157
206
|
record.orgId ?? null,
|
|
158
|
-
record.
|
|
207
|
+
record.generation,
|
|
159
208
|
record.updatedAt,
|
|
160
209
|
record.stoppedAt ?? null,
|
|
210
|
+
record.id,
|
|
211
|
+
expectedGeneration,
|
|
161
212
|
],
|
|
162
213
|
});
|
|
214
|
+
if (updated.rowsAffected !== 1) {
|
|
215
|
+
throw new AgentHarnessSessionConflictError(record.id);
|
|
216
|
+
}
|
|
163
217
|
return record;
|
|
164
218
|
}
|
|
165
219
|
export async function updateAgentHarnessSession(id, patch) {
|
|
@@ -184,6 +238,8 @@ export async function updateAgentHarnessSession(id, patch) {
|
|
|
184
238
|
ownerEmail: patch.ownerEmail ?? existing.ownerEmail ?? null,
|
|
185
239
|
orgId: patch.orgId ?? existing.orgId ?? null,
|
|
186
240
|
stoppedAt: patch.stoppedAt ?? existing.stoppedAt ?? null,
|
|
241
|
+
expectedGeneration: existing.generation,
|
|
242
|
+
existingSession: existing,
|
|
187
243
|
});
|
|
188
244
|
}
|
|
189
245
|
export async function getAgentHarnessSession(id) {
|
|
@@ -298,6 +354,7 @@ function rowToHarnessSession(row) {
|
|
|
298
354
|
resolvedApprovalIds: stringArray(record.resolved_approval_ids),
|
|
299
355
|
ownerEmail: stringOrNull(record.owner_email),
|
|
300
356
|
orgId: stringOrNull(record.org_id),
|
|
357
|
+
generation: numberValue(record.generation),
|
|
301
358
|
createdAt: numberValue(record.created_at),
|
|
302
359
|
updatedAt: numberValue(record.updated_at),
|
|
303
360
|
stoppedAt: record.stopped_at === null || record.stopped_at === undefined
|
|
@@ -344,3 +401,10 @@ function stringOrNull(value) {
|
|
|
344
401
|
function numberValue(value) {
|
|
345
402
|
return typeof value === "number" ? value : Number(value ?? 0);
|
|
346
403
|
}
|
|
404
|
+
function isDuplicateColumnError(error) {
|
|
405
|
+
if (!error || typeof error !== "object")
|
|
406
|
+
return false;
|
|
407
|
+
const message = error.message;
|
|
408
|
+
return (typeof message === "string" &&
|
|
409
|
+
/duplicate column name|column .* already exists/i.test(message));
|
|
410
|
+
}
|
|
@@ -730,6 +730,7 @@ export interface ExecuteAgentToolCallOptions {
|
|
|
730
730
|
approvedToolCalls?: string[];
|
|
731
731
|
onApprovalRequired?: (binding: AgentApprovalBinding) => Promise<string | void>;
|
|
732
732
|
consumeApproval?: (binding: AgentApprovalBinding) => Promise<boolean>;
|
|
733
|
+
isToolAlwaysAllowed?: (binding: AgentApprovalBinding) => Promise<boolean>;
|
|
733
734
|
send?: (event: AgentChatEvent) => void;
|
|
734
735
|
}
|
|
735
736
|
export interface AgentApprovalBinding {
|
|
@@ -915,6 +916,8 @@ export declare function runAgentLoop(opts: {
|
|
|
915
916
|
*/
|
|
916
917
|
onApprovalRequired?: (binding: AgentApprovalBinding) => Promise<string | void>;
|
|
917
918
|
consumeApproval?: (binding: AgentApprovalBinding) => Promise<boolean>;
|
|
919
|
+
/** User-scoped action-type policy, checked only after needsApproval. */
|
|
920
|
+
isToolAlwaysAllowed?: (binding: AgentApprovalBinding) => Promise<boolean>;
|
|
918
921
|
/**
|
|
919
922
|
* In-loop processor seam (see `processors.ts`). Each processor can observe
|
|
920
923
|
* streamed chunks, observe model responses around tool execution, and
|
|
@@ -43,7 +43,7 @@ import { ProcessorChain, TripWire, toolCallsFromContent, } from "./processors.js
|
|
|
43
43
|
import { startRun, subscribeToRun, getActiveRunForThread, getActiveRunForThreadAsync, getRun, abortRun, abortRunDurably, abortTurnDurably, tryClaimRunSlot, isHostedRuntime, resolveRunSoftTimeoutMs, resolveRunToolTimeoutCeilingMs, endsAfterCompletedToolWithoutAssistantFinal, } from "./run-manager.js";
|
|
44
44
|
import { writeLedgerEntry, readLedgerEntry, clearLedgerForThread, insertRun, insertRunEvent, isTurnAborted, markRunAborted, updateRunHeartbeat, updateRunStatusIfRunning, setRunError, setRunTerminalReason, claimBackgroundRun, readBackgroundRunClaim, recordRunDiagnostic, countRunsForTurn, RUN_DIAG_STAGE, UNCLAIMED_BACKGROUND_RUN_GRACE_MS, turnRunLedgerExhausted, } from "./run-store.js";
|
|
45
45
|
import { buildCurrentTimeUserContext } from "./runtime-context.js";
|
|
46
|
-
import { consumeAgentToolApproval, createAgentToolApproval, resolveAgentToolApprovalTurnId, } from "./tool-approval-store.js";
|
|
46
|
+
import { consumeAgentToolApproval, createAgentToolApproval, isAgentToolAlwaysAllowed, resolveAgentToolApprovalTurnId, } from "./tool-approval-store.js";
|
|
47
47
|
import { findCompletedJournalEntry, } from "./tool-call-journal.js";
|
|
48
48
|
import { redactSensitiveFields, sanitizeToolErrorText, sanitizeToolErrorValue, } from "./tool-error-redaction.js";
|
|
49
49
|
import { describeToolResultImages, extractAgentImagesFromActionResult, } from "./tool-result-images.js";
|
|
@@ -2274,6 +2274,7 @@ export async function executeAgentToolCall(options) {
|
|
|
2274
2274
|
approvedToolCalls: options.approvedToolCalls,
|
|
2275
2275
|
onApprovalRequired: options.onApprovalRequired,
|
|
2276
2276
|
consumeApproval: options.consumeApproval,
|
|
2277
|
+
isToolAlwaysAllowed: options.isToolAlwaysAllowed,
|
|
2277
2278
|
});
|
|
2278
2279
|
}
|
|
2279
2280
|
catch (error) {
|
|
@@ -4359,6 +4360,16 @@ export async function runAgentLoop(opts) {
|
|
|
4359
4360
|
// than silently running a high-consequence action.
|
|
4360
4361
|
mustApprove = true;
|
|
4361
4362
|
}
|
|
4363
|
+
if (mustApprove && opts.isToolAlwaysAllowed) {
|
|
4364
|
+
try {
|
|
4365
|
+
mustApprove = !(await opts.isToolAlwaysAllowed(approvalBinding));
|
|
4366
|
+
}
|
|
4367
|
+
catch {
|
|
4368
|
+
// Fail closed: an unreadable policy must leave the approval gate in
|
|
4369
|
+
// place instead of turning a storage outage into authorization.
|
|
4370
|
+
mustApprove = true;
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
4362
4373
|
if (mustApprove) {
|
|
4363
4374
|
// `askId` identifies THIS gate hit, distinct from any earlier ask
|
|
4364
4375
|
// for the same approvalKey/toolCallId. A failed resume (expired
|
|
@@ -7226,6 +7237,15 @@ export function createProductionAgentHandler(options) {
|
|
|
7226
7237
|
return false;
|
|
7227
7238
|
return consumeAgentToolApproval(approvalStoreBinding(binding));
|
|
7228
7239
|
},
|
|
7240
|
+
isToolAlwaysAllowed: async (binding) => {
|
|
7241
|
+
if (!ownerEmail)
|
|
7242
|
+
return false;
|
|
7243
|
+
return isAgentToolAlwaysAllowed({
|
|
7244
|
+
ownerEmail,
|
|
7245
|
+
orgId: getRequestOrgId() ?? null,
|
|
7246
|
+
toolName: binding.toolName,
|
|
7247
|
+
});
|
|
7248
|
+
},
|
|
7229
7249
|
};
|
|
7230
7250
|
const approvedToolCallsForExecution = requestedApprovedToolCalls;
|
|
7231
7251
|
if (isBackgroundWorker &&
|
|
@@ -8074,6 +8094,7 @@ export function createProductionAgentHandler(options) {
|
|
|
8074
8094
|
: {}),
|
|
8075
8095
|
onApprovalRequired: approvalHooks.onApprovalRequired,
|
|
8076
8096
|
consumeApproval: approvalHooks.consumeApproval,
|
|
8097
|
+
isToolAlwaysAllowed: approvalHooks.isToolAlwaysAllowed,
|
|
8077
8098
|
// A worker PROVEN to be running inside the real 15-min Netlify
|
|
8078
8099
|
// `-background` function (`runsInBackgroundFunction`) has minutes of
|
|
8079
8100
|
// budget left on THIS invocation. Let it catch a recoverable
|
|
@@ -8119,6 +8140,7 @@ export function createProductionAgentHandler(options) {
|
|
|
8119
8140
|
approvedToolCalls: approvedToolCallsForExecution,
|
|
8120
8141
|
onApprovalRequired: approvalHooks.onApprovalRequired,
|
|
8121
8142
|
consumeApproval: approvalHooks.consumeApproval,
|
|
8143
|
+
isToolAlwaysAllowed: approvalHooks.isToolAlwaysAllowed,
|
|
8122
8144
|
send,
|
|
8123
8145
|
});
|
|
8124
8146
|
return;
|
|
@@ -663,6 +663,9 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
663
663
|
? new AbortController()
|
|
664
664
|
: null;
|
|
665
665
|
let chunkBoundaryReason = null;
|
|
666
|
+
let chunkSoftTimeoutTimer = null;
|
|
667
|
+
let armChunkSoftTimeout = () => { };
|
|
668
|
+
let recoverableRunDeadlineAt = null;
|
|
666
669
|
let recoveredChunkBoundaries = 0;
|
|
667
670
|
/** A boundary that has been reached but not yet proven recovered. */
|
|
668
671
|
let pendingBoundary = null;
|
|
@@ -706,7 +709,10 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
706
709
|
return abort.signal;
|
|
707
710
|
settleBoundary(true);
|
|
708
711
|
chunkBoundaryReason = null;
|
|
712
|
+
if (chunkSoftTimeoutTimer)
|
|
713
|
+
clearTimeout(chunkSoftTimeoutTimer);
|
|
709
714
|
chunkAbort = new AbortController();
|
|
715
|
+
armChunkSoftTimeout();
|
|
710
716
|
// The boundary is behind us; the silence clock restarts with the chunk,
|
|
711
717
|
// or the backstop fires again on the elapsed time of the chunk it just
|
|
712
718
|
// ended and every recovery round dies instantly.
|
|
@@ -1079,8 +1085,12 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
1079
1085
|
const reachRunBoundary = (reason, diagnostic = {}) => {
|
|
1080
1086
|
if (run.status !== "running" || abort.signal.aborted)
|
|
1081
1087
|
return;
|
|
1082
|
-
|
|
1083
|
-
|
|
1088
|
+
const activeChunkAbort = chunkAbort;
|
|
1089
|
+
const canRecoverChunk = activeChunkAbort !== null &&
|
|
1090
|
+
(recoverableRunDeadlineAt === null ||
|
|
1091
|
+
Date.now() < recoverableRunDeadlineAt);
|
|
1092
|
+
if (canRecoverChunk) {
|
|
1093
|
+
if (activeChunkAbort.signal.aborted)
|
|
1084
1094
|
return;
|
|
1085
1095
|
recoveredChunkBoundaries += 1;
|
|
1086
1096
|
console.warn(`[run-manager] chunk boundary (${reason}) — recovering in-invocation`, runId, diagnostic);
|
|
@@ -1092,9 +1102,11 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
1092
1102
|
// direction that hides the failure.
|
|
1093
1103
|
pendingBoundary = { reason, diagnostic, index: recoveredChunkBoundaries };
|
|
1094
1104
|
chunkBoundaryReason = reason;
|
|
1095
|
-
|
|
1105
|
+
activeChunkAbort.abort(reason);
|
|
1096
1106
|
return;
|
|
1097
1107
|
}
|
|
1108
|
+
if (chunkAbort)
|
|
1109
|
+
chunkBoundaryReason = reason;
|
|
1098
1110
|
// Mirror the soft-timeout semantics exactly: the chunk completes (not
|
|
1099
1111
|
// aborts) at an auto_continue boundary, so the continuation machinery —
|
|
1100
1112
|
// server-chained for background workers, client-driven for foreground —
|
|
@@ -1277,12 +1289,10 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
1277
1289
|
overrideMs: options?.noProgressTimeoutMs,
|
|
1278
1290
|
backgroundOverrideMs: options?.backgroundNoProgressTimeoutMs,
|
|
1279
1291
|
});
|
|
1280
|
-
//
|
|
1281
|
-
//
|
|
1282
|
-
//
|
|
1283
|
-
//
|
|
1284
|
-
// recoverable in name only and burning the tail of the budget on nothing.
|
|
1285
|
-
// One wall, one clock; the caller's hard abort still backstops it.
|
|
1292
|
+
// A recoverable run uses the cumulative timer below instead of this one-shot
|
|
1293
|
+
// timer. Each successor chunk gets only the remaining invocation budget, so
|
|
1294
|
+
// recovery cannot extend the hosted wall. The caller's hard abort still
|
|
1295
|
+
// backstops both timer paths.
|
|
1286
1296
|
const softTimeoutTimer = softTimeoutMs > 0 && !recoverChunkBoundaries
|
|
1287
1297
|
? setTimeout(() => {
|
|
1288
1298
|
reachRunBoundary("run_timeout", {
|
|
@@ -1290,6 +1300,25 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
1290
1300
|
});
|
|
1291
1301
|
}, softTimeoutMs)
|
|
1292
1302
|
: null;
|
|
1303
|
+
if (recoverChunkBoundaries && softTimeoutMs > 0) {
|
|
1304
|
+
const runDeadlineAt = Date.now() + softTimeoutMs;
|
|
1305
|
+
recoverableRunDeadlineAt = runDeadlineAt;
|
|
1306
|
+
armChunkSoftTimeout = () => {
|
|
1307
|
+
if (chunkSoftTimeoutTimer)
|
|
1308
|
+
clearTimeout(chunkSoftTimeoutTimer);
|
|
1309
|
+
if (abort.signal.aborted || run.status !== "running")
|
|
1310
|
+
return;
|
|
1311
|
+
chunkSoftTimeoutTimer = setTimeout(() => {
|
|
1312
|
+
chunkSoftTimeoutTimer = null;
|
|
1313
|
+
reachRunBoundary("run_timeout", {
|
|
1314
|
+
lastEventType: run.events.at(-1)?.event.type,
|
|
1315
|
+
});
|
|
1316
|
+
}, Math.max(0, runDeadlineAt - Date.now()));
|
|
1317
|
+
};
|
|
1318
|
+
// Boundary recovery disables the ordinary one-shot timer, so arm the
|
|
1319
|
+
// cumulative invocation deadline on the initial chunk and each successor.
|
|
1320
|
+
armChunkSoftTimeout();
|
|
1321
|
+
}
|
|
1293
1322
|
let pendingTerminalEvent = null;
|
|
1294
1323
|
const captureRunError = (error, phase) => {
|
|
1295
1324
|
const errorCode = getRunErrorCode(error);
|
|
@@ -1638,6 +1667,8 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
1638
1667
|
clearInterval(heartbeatTimer);
|
|
1639
1668
|
if (softTimeoutTimer)
|
|
1640
1669
|
clearTimeout(softTimeoutTimer);
|
|
1670
|
+
if (chunkSoftTimeoutTimer)
|
|
1671
|
+
clearTimeout(chunkSoftTimeoutTimer);
|
|
1641
1672
|
if (progressWriteTimer)
|
|
1642
1673
|
clearTimeout(progressWriteTimer);
|
|
1643
1674
|
progressWriteTimer = null;
|
|
@@ -35,6 +35,27 @@ export declare const AGENT_TOOL_APPROVAL_TABLE_SQL: {
|
|
|
35
35
|
export declare const AGENT_TOOL_APPROVAL_INDEX_SQL = "CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_binding\n ON agent_tool_approvals(owner_email, org_id, thread_id, tool_name, call_id, status)";
|
|
36
36
|
export declare const AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL = "CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_logical\n ON agent_tool_approvals(owner_email, org_id, thread_id, turn_id, tool_name, approval_key_hash, status)";
|
|
37
37
|
export declare const AGENT_TOOL_APPROVAL_RECOVERY_INDEX_SQL = "CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_recovery\n ON agent_tool_approvals(owner_email, org_id, thread_id, approval_key_hash, status, turn_id)";
|
|
38
|
+
export declare const AGENT_TOOL_APPROVAL_POLICY_TABLE_SQL: {
|
|
39
|
+
readonly postgres: `CREATE TABLE IF NOT EXISTS agent_tool_approval_policies (
|
|
40
|
+
id TEXT PRIMARY KEY,
|
|
41
|
+
owner_email TEXT NOT NULL,
|
|
42
|
+
org_id TEXT,
|
|
43
|
+
tool_name TEXT NOT NULL,
|
|
44
|
+
enabled BOOLEAN NOT NULL,
|
|
45
|
+
created_at BIGINT NOT NULL,
|
|
46
|
+
updated_at BIGINT NOT NULL
|
|
47
|
+
)`;
|
|
48
|
+
readonly sqlite: `CREATE TABLE IF NOT EXISTS agent_tool_approval_policies (
|
|
49
|
+
id TEXT PRIMARY KEY,
|
|
50
|
+
owner_email TEXT NOT NULL,
|
|
51
|
+
org_id TEXT,
|
|
52
|
+
tool_name TEXT NOT NULL,
|
|
53
|
+
enabled INTEGER NOT NULL,
|
|
54
|
+
created_at INTEGER NOT NULL,
|
|
55
|
+
updated_at INTEGER NOT NULL
|
|
56
|
+
)`;
|
|
57
|
+
};
|
|
58
|
+
export declare const AGENT_TOOL_APPROVAL_POLICY_INDEX_SQL = "CREATE INDEX IF NOT EXISTS idx_agent_tool_approval_policies_scope\n ON agent_tool_approval_policies(owner_email, org_id, tool_name, enabled)";
|
|
38
59
|
/**
|
|
39
60
|
* Durable approval grants are created and consumed on request paths, but their
|
|
40
61
|
* schema belongs to the release migration boundary in production.
|
|
@@ -37,6 +37,28 @@ export const AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL = `CREATE INDEX IF NOT EXISTS
|
|
|
37
37
|
ON agent_tool_approvals(owner_email, org_id, thread_id, turn_id, tool_name, approval_key_hash, status)`;
|
|
38
38
|
export const AGENT_TOOL_APPROVAL_RECOVERY_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_recovery
|
|
39
39
|
ON agent_tool_approvals(owner_email, org_id, thread_id, approval_key_hash, status, turn_id)`;
|
|
40
|
+
export const AGENT_TOOL_APPROVAL_POLICY_TABLE_SQL = {
|
|
41
|
+
postgres: `CREATE TABLE IF NOT EXISTS agent_tool_approval_policies (
|
|
42
|
+
id TEXT PRIMARY KEY,
|
|
43
|
+
owner_email TEXT NOT NULL,
|
|
44
|
+
org_id TEXT,
|
|
45
|
+
tool_name TEXT NOT NULL,
|
|
46
|
+
enabled BOOLEAN NOT NULL,
|
|
47
|
+
created_at BIGINT NOT NULL,
|
|
48
|
+
updated_at BIGINT NOT NULL
|
|
49
|
+
)`,
|
|
50
|
+
sqlite: `CREATE TABLE IF NOT EXISTS agent_tool_approval_policies (
|
|
51
|
+
id TEXT PRIMARY KEY,
|
|
52
|
+
owner_email TEXT NOT NULL,
|
|
53
|
+
org_id TEXT,
|
|
54
|
+
tool_name TEXT NOT NULL,
|
|
55
|
+
enabled INTEGER NOT NULL,
|
|
56
|
+
created_at INTEGER NOT NULL,
|
|
57
|
+
updated_at INTEGER NOT NULL
|
|
58
|
+
)`,
|
|
59
|
+
};
|
|
60
|
+
export const AGENT_TOOL_APPROVAL_POLICY_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_agent_tool_approval_policies_scope
|
|
61
|
+
ON agent_tool_approval_policies(owner_email, org_id, tool_name, enabled)`;
|
|
40
62
|
/**
|
|
41
63
|
* Durable approval grants are created and consumed on request paths, but their
|
|
42
64
|
* schema belongs to the release migration boundary in production.
|
|
@@ -68,4 +90,14 @@ ${AGENT_TOOL_APPROVAL_INDEX_SQL}`,
|
|
|
68
90
|
sqlite: AGENT_TOOL_APPROVAL_RECOVERY_INDEX_SQL,
|
|
69
91
|
},
|
|
70
92
|
},
|
|
93
|
+
{
|
|
94
|
+
version: 4,
|
|
95
|
+
name: "agent-tool-approval-policies-table-and-index",
|
|
96
|
+
sql: {
|
|
97
|
+
postgres: `${AGENT_TOOL_APPROVAL_POLICY_TABLE_SQL.postgres};
|
|
98
|
+
${AGENT_TOOL_APPROVAL_POLICY_INDEX_SQL}`,
|
|
99
|
+
sqlite: `${AGENT_TOOL_APPROVAL_POLICY_TABLE_SQL.sqlite};
|
|
100
|
+
${AGENT_TOOL_APPROVAL_POLICY_INDEX_SQL}`,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
71
103
|
];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare function ensureAgentToolApprovalTable(): Promise<void>;
|
|
2
|
+
export declare function ensureAgentToolApprovalPolicyTable(): Promise<void>;
|
|
2
3
|
export interface AgentToolApprovalBinding {
|
|
3
4
|
ownerEmail: string;
|
|
4
5
|
orgId?: string | null;
|
|
@@ -10,6 +11,16 @@ export interface AgentToolApprovalBinding {
|
|
|
10
11
|
expiresAt?: number;
|
|
11
12
|
}
|
|
12
13
|
export declare function hashAgentToolApprovalKey(approvalKey: string): string;
|
|
14
|
+
export interface AgentToolApprovalPolicyBinding {
|
|
15
|
+
ownerEmail: string;
|
|
16
|
+
orgId?: string | null;
|
|
17
|
+
toolName: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function setAgentToolApprovalPolicy(input: {
|
|
20
|
+
binding: AgentToolApprovalPolicyBinding;
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
export declare function isAgentToolAlwaysAllowed(input: AgentToolApprovalPolicyBinding): Promise<boolean>;
|
|
13
24
|
/**
|
|
14
25
|
* Recover the durable scope for an approval continuation when a transport
|
|
15
26
|
* drops the original turn id. A single pending logical turn is safe to
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { getDbExec, isPostgres, retryOnDdlRace } from "../db/client.js";
|
|
3
3
|
import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
4
|
-
import { AGENT_TOOL_APPROVAL_INDEX_SQL, AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL, AGENT_TOOL_APPROVAL_RECOVERY_INDEX_SQL, AGENT_TOOL_APPROVAL_TABLE_SQL, } from "./tool-approval-migrations.js";
|
|
4
|
+
import { AGENT_TOOL_APPROVAL_INDEX_SQL, AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL, AGENT_TOOL_APPROVAL_POLICY_INDEX_SQL, AGENT_TOOL_APPROVAL_POLICY_TABLE_SQL, AGENT_TOOL_APPROVAL_RECOVERY_INDEX_SQL, AGENT_TOOL_APPROVAL_TABLE_SQL, } from "./tool-approval-migrations.js";
|
|
5
5
|
// 15 minutes was too short in practice: a user who steps away mid-approval
|
|
6
6
|
// (switching tabs to update their client, checking something else) comes back
|
|
7
7
|
// to a silently expired grant with no visible error — clicking Approve just
|
|
@@ -11,6 +11,7 @@ import { AGENT_TOOL_APPROVAL_INDEX_SQL, AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL, A
|
|
|
11
11
|
const APPROVAL_TTL_MS = 60 * 60_000;
|
|
12
12
|
const APPROVAL_CLEANUP_AGE_MS = 24 * 60 * 60_000;
|
|
13
13
|
let initPromise;
|
|
14
|
+
let policyInitPromise;
|
|
14
15
|
export async function ensureAgentToolApprovalTable() {
|
|
15
16
|
if (!initPromise) {
|
|
16
17
|
initPromise = (async () => {
|
|
@@ -34,9 +35,83 @@ export async function ensureAgentToolApprovalTable() {
|
|
|
34
35
|
}
|
|
35
36
|
return initPromise;
|
|
36
37
|
}
|
|
38
|
+
export async function ensureAgentToolApprovalPolicyTable() {
|
|
39
|
+
if (!policyInitPromise) {
|
|
40
|
+
policyInitPromise = (async () => {
|
|
41
|
+
const createSql = AGENT_TOOL_APPROVAL_POLICY_TABLE_SQL[isPostgres() ? "postgres" : "sqlite"];
|
|
42
|
+
if (isPostgres()) {
|
|
43
|
+
await ensureTableExists("agent_tool_approval_policies", createSql);
|
|
44
|
+
await ensureIndexExists("idx_agent_tool_approval_policies_scope", AGENT_TOOL_APPROVAL_POLICY_INDEX_SQL);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const client = getDbExec();
|
|
48
|
+
await retryOnDdlRace(() => client.execute(createSql));
|
|
49
|
+
await retryOnDdlRace(() => client.execute(AGENT_TOOL_APPROVAL_POLICY_INDEX_SQL));
|
|
50
|
+
})().catch((error) => {
|
|
51
|
+
policyInitPromise = undefined;
|
|
52
|
+
throw error;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return policyInitPromise;
|
|
56
|
+
}
|
|
37
57
|
export function hashAgentToolApprovalKey(approvalKey) {
|
|
38
58
|
return createHash("sha256").update(approvalKey).digest("hex");
|
|
39
59
|
}
|
|
60
|
+
function normalizeApprovalPolicyBinding(binding) {
|
|
61
|
+
const ownerEmail = binding.ownerEmail.trim().toLowerCase();
|
|
62
|
+
const toolName = binding.toolName.trim();
|
|
63
|
+
if (!ownerEmail)
|
|
64
|
+
throw new Error("Approval policy owner is required.");
|
|
65
|
+
if (!toolName)
|
|
66
|
+
throw new Error("Approval policy tool name is required.");
|
|
67
|
+
return {
|
|
68
|
+
ownerEmail,
|
|
69
|
+
orgId: binding.orgId?.trim() || null,
|
|
70
|
+
toolName,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function approvalPolicyId(binding) {
|
|
74
|
+
return `agent-tool-approval-policy-${createHash("sha256")
|
|
75
|
+
.update(`${binding.ownerEmail}\u0000${binding.orgId ?? ""}\u0000${binding.toolName}`)
|
|
76
|
+
.digest("hex")}`;
|
|
77
|
+
}
|
|
78
|
+
export async function setAgentToolApprovalPolicy(input) {
|
|
79
|
+
const binding = normalizeApprovalPolicyBinding(input.binding);
|
|
80
|
+
await ensureAgentToolApprovalPolicyTable();
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
const id = approvalPolicyId(binding);
|
|
83
|
+
await getDbExec().execute({
|
|
84
|
+
sql: `INSERT INTO agent_tool_approval_policies
|
|
85
|
+
(id, owner_email, org_id, tool_name, enabled, created_at, updated_at)
|
|
86
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
87
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
88
|
+
enabled = excluded.enabled,
|
|
89
|
+
updated_at = excluded.updated_at`,
|
|
90
|
+
args: [
|
|
91
|
+
id,
|
|
92
|
+
binding.ownerEmail,
|
|
93
|
+
binding.orgId,
|
|
94
|
+
binding.toolName,
|
|
95
|
+
input.enabled,
|
|
96
|
+
now,
|
|
97
|
+
now,
|
|
98
|
+
],
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
export async function isAgentToolAlwaysAllowed(input) {
|
|
102
|
+
const binding = normalizeApprovalPolicyBinding(input);
|
|
103
|
+
await ensureAgentToolApprovalPolicyTable();
|
|
104
|
+
const result = await getDbExec().execute({
|
|
105
|
+
sql: `SELECT 1 FROM agent_tool_approval_policies
|
|
106
|
+
WHERE owner_email = ?
|
|
107
|
+
AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
|
|
108
|
+
AND tool_name = ?
|
|
109
|
+
AND enabled = TRUE
|
|
110
|
+
LIMIT 1`,
|
|
111
|
+
args: [binding.ownerEmail, binding.orgId, binding.orgId, binding.toolName],
|
|
112
|
+
});
|
|
113
|
+
return (result.rows ?? []).length > 0;
|
|
114
|
+
}
|
|
40
115
|
/**
|
|
41
116
|
* Recover the durable scope for an approval continuation when a transport
|
|
42
117
|
* drops the original turn id. A single pending logical turn is safe to
|
|
@@ -9,7 +9,7 @@ import { actionsToEngineTools, runAgentLoop, } from "../agent/production-agent.j
|
|
|
9
9
|
import { createToolSearchEntry, TOOL_SEARCH_ACTION_NAME, } from "../agent/tool-search.js";
|
|
10
10
|
import { getAppConfig } from "../app-config/index.js";
|
|
11
11
|
import { formatPromptWithAttachments, } from "../code-agents/prompt-attachments.js";
|
|
12
|
-
import { createCodingToolRegistry, isReadOnlyShellCommand, runCodingCommand, truncateBashOutput, truncateCodingOutput, } from "../coding-tools/index.js";
|
|
12
|
+
import { createCodingToolRegistry, canonicalizeShellCommand, isReadOnlyShellCommand, runCodingCommand, truncateBashOutput, truncateCodingOutput, } from "../coding-tools/index.js";
|
|
13
13
|
import { buildMergedConfig, McpClientManager, mcpToolsToActionEntries, } from "../mcp-client/index.js";
|
|
14
14
|
import { readAgentsBundleFromFs, generateDevelopmentSkillsPromptBlock, } from "../server/agents-bundle.js";
|
|
15
15
|
import { getAmbientOrgId, getAmbientUserEmail, runWithRequestContext, } from "../server/request-context.js";
|
|
@@ -2303,6 +2303,13 @@ export function classifyCodeAgentCommandPermission(command) {
|
|
|
2303
2303
|
const normalized = command.trim().toLowerCase();
|
|
2304
2304
|
if (!normalized)
|
|
2305
2305
|
return { kind: "read" };
|
|
2306
|
+
// Match every rule below against the quote-stripped form as well as the raw
|
|
2307
|
+
// one. The shell removes quoting before the command word exists, so
|
|
2308
|
+
// `git 'checkout' main` runs the branch operation these rules forbid while
|
|
2309
|
+
// matching none of them verbatim.
|
|
2310
|
+
const { canonical, unanalyzable } = canonicalizeShellCommand(command);
|
|
2311
|
+
const canonicalized = canonical.trim().toLowerCase();
|
|
2312
|
+
const matches = (pattern) => pattern.test(normalized) || pattern.test(canonicalized);
|
|
2306
2313
|
const blockedPatterns = [
|
|
2307
2314
|
[
|
|
2308
2315
|
/\bgit\s+(checkout|switch|reset|rebase|stash|clean|worktree)\b/,
|
|
@@ -2315,7 +2322,7 @@ export function classifyCodeAgentCommandPermission(command) {
|
|
|
2315
2322
|
[/\bdrizzle-kit\s+push\b/, "drizzle-kit push is not allowed"],
|
|
2316
2323
|
];
|
|
2317
2324
|
for (const [pattern, reason] of blockedPatterns) {
|
|
2318
|
-
if (pattern
|
|
2325
|
+
if (matches(pattern))
|
|
2319
2326
|
return { kind: "forbidden", reason };
|
|
2320
2327
|
}
|
|
2321
2328
|
const approvalPatterns = [
|
|
@@ -2331,10 +2338,20 @@ export function classifyCodeAgentCommandPermission(command) {
|
|
|
2331
2338
|
[/\bdelete\s+from\b(?![\s\S]*\bwhere\b)/, "unscoped delete command"],
|
|
2332
2339
|
];
|
|
2333
2340
|
for (const [pattern, reason] of approvalPatterns) {
|
|
2334
|
-
if (pattern
|
|
2341
|
+
if (matches(pattern)) {
|
|
2335
2342
|
return { kind: "approval-required", reason };
|
|
2336
2343
|
}
|
|
2337
2344
|
}
|
|
2345
|
+
// A command whose real text this pass cannot recover is not a command we can
|
|
2346
|
+
// clear. `$(printf git) $(printf checkout) main` runs the forbidden operation
|
|
2347
|
+
// while containing neither token, so a rule that did not match proves nothing.
|
|
2348
|
+
// Ask rather than fall through to `write` on a guess.
|
|
2349
|
+
if (unanalyzable) {
|
|
2350
|
+
return {
|
|
2351
|
+
kind: "approval-required",
|
|
2352
|
+
reason: "command builds its text at runtime (command substitution or $'…' escaping), so policy matching cannot see what will run",
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2338
2355
|
if (isReadOnlyShellCommand(command)) {
|
|
2339
2356
|
return { kind: "read" };
|
|
2340
2357
|
}
|
|
@@ -26,6 +26,7 @@ export declare function preloadAgentChatSurface(): Promise<void>;
|
|
|
26
26
|
import type { AgentChatSurfaceKind } from "./agent-chat-adapter.js";
|
|
27
27
|
import type { AssistantChatProps } from "./AssistantChat.js";
|
|
28
28
|
import type { MultiTabAssistantChatHeaderProps, MultiTabAssistantChatProps } from "./MultiTabAssistantChat.js";
|
|
29
|
+
export declare function shouldHandleAgentSidebarToggle(event: Event, toggleScopeId?: string | null): boolean;
|
|
29
30
|
type PanelMode = "chat" | "cli" | "resources" | "settings";
|
|
30
31
|
export declare function normalizeAgentPanelModeForSurface(mode: PanelMode, allowSettingsMode: boolean, chatOnly?: boolean): PanelMode;
|
|
31
32
|
export declare function deferAgentPanelOverlayOpen(event: {
|
|
@@ -249,6 +250,8 @@ export interface AgentSidebarProps {
|
|
|
249
250
|
onFullscreenRequest?: () => void;
|
|
250
251
|
/** Ambient resource context rendered as a composer chip. */
|
|
251
252
|
scope?: import("./use-chat-threads.js").ChatThreadScope | null;
|
|
253
|
+
/** Identity used to route host-scoped sidebar toggle events. */
|
|
254
|
+
toggleScopeId?: string;
|
|
252
255
|
/** Keep app-owned chat history isolated to the supplied scope. */
|
|
253
256
|
isolateHistoryByScope?: boolean;
|
|
254
257
|
/** @deprecated Scope context now appears inside the composer. */
|
|
@@ -266,7 +269,7 @@ export interface AgentSidebarProps {
|
|
|
266
269
|
* Wraps app content with a toggleable agent sidebar.
|
|
267
270
|
* Use AgentToggleButton in your header to open/close it.
|
|
268
271
|
*/
|
|
269
|
-
export declare function AgentSidebar({ children, enabled, emptyStateText, suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, onConnectProvider, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position, defaultOpen, animateMobile, animateDesktop, chatViewTransition, chatViewTransitionHandoff, storageKey, openStorageKey, restoreActiveThread, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning, onFullscreenRequest, scope, isolateHistoryByScope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding, }: AgentSidebarProps): React.JSX.Element;
|
|
272
|
+
export declare function AgentSidebar({ children, enabled, emptyStateText, suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, onConnectProvider, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position, defaultOpen, animateMobile, animateDesktop, chatViewTransition, chatViewTransitionHandoff, storageKey, openStorageKey, restoreActiveThread, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning, onFullscreenRequest, scope, toggleScopeId, isolateHistoryByScope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding, }: AgentSidebarProps): React.JSX.Element;
|
|
270
273
|
/**
|
|
271
274
|
* Focus the agent chat composer input.
|
|
272
275
|
* Opens the sidebar if closed, then focuses the text input.
|