@k2b/cloud 0.39.0 → 0.41.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/README.md +8 -0
- package/package.json +11 -4
- package/src/_internal/app-favicon.ts +4 -2
- package/src/_internal/capabilities.ts +72 -48
- package/src/_internal/capability-reviews.ts +81 -0
- package/src/_internal/capability-schemas.ts +29 -0
- package/src/_internal/capability-sentences.ts +217 -1
- package/src/_internal/capability-streams.ts +7 -0
- package/src/_internal/define-app.ts +49 -18
- package/src/_internal/font-preloads.ts +2 -1
- package/src/_internal/heartbeat.ts +203 -14
- package/src/_internal/registry.ts +28 -1
- package/src/_internal/runtime-watcher.ts +6 -0
- package/src/_internal/setup-lock.ts +103 -0
- package/src/_internal/static-assets.ts +52 -3
- package/src/_internal/status-preserving-ssr.ts +2 -0
- package/src/access/PermissionEditor.tsx +2 -2
- package/src/account/EntitySearch.tsx +1 -1
- package/src/ai/action-review.ts +101 -0
- package/src/ai/background-review.ts +72 -0
- package/src/ai/capabilities.ts +29 -11
- package/src/ai/capability-execution.ts +76 -43
- package/src/ai/capability-result.ts +95 -0
- package/src/ai/chat/action-card.ts +464 -0
- package/src/ai/chat/action-messages.ts +129 -0
- package/src/ai/chat/action-question.browser-harness.tsx +245 -0
- package/src/ai/chat/action-question.fixtures.ts +733 -0
- package/src/ai/chat/action-question.tsx +680 -0
- package/src/ai/chat/action-text.ts +288 -0
- package/src/ai/chat/blocks.tsx +19 -25
- package/src/ai/chat/message-actions.tsx +6 -0
- package/src/ai/chat/tool-action-words.ts +31 -0
- package/src/ai/chat/tool-action.tsx +456 -0
- package/src/ai/chat/turn-view.tsx +10 -160
- package/src/ai/chat/view-block.tsx +15 -5
- package/src/ai/chat-task-contracts.ts +4 -1
- package/src/ai/chat-task-reasons-migrate.ts +7 -0
- package/src/ai/chat-task-reasons.ts +1 -1
- package/src/ai/chat-tasks.ts +55 -25
- package/src/ai/client/controller.ts +59 -0
- package/src/ai/default-tools.ts +9 -1
- package/src/ai/executor.ts +198 -39
- package/src/ai/firecrawl-tools.ts +15 -2
- package/src/ai/index.ts +3 -0
- package/src/ai/migrate.ts +26 -2
- package/src/ai/open-questions.ts +172 -0
- package/src/ai/projects-routes.ts +3 -0
- package/src/ai/protocol.ts +14 -2
- package/src/ai/question-text.ts +8 -0
- package/src/ai/recent-resources.ts +240 -0
- package/src/ai/recent-sent-refs.ts +38 -0
- package/src/ai/routes.ts +195 -5
- package/src/ai/runtime.ts +1 -0
- package/src/ai/skills-routes.ts +3 -0
- package/src/ai/skills.ts +10 -1
- package/src/ai/store.ts +245 -24
- package/src/ai/testing.ts +36 -0
- package/src/ai/tool-audit.ts +8 -0
- package/src/ai/tool-sentences.ts +152 -34
- package/src/ai/tool-wording.ts +36 -8
- package/src/ai/tools.ts +8 -3
- package/src/ai/types.ts +43 -4
- package/src/api/accounts-entities.ts +2 -1
- package/src/api/admin-ai-projects.ts +2 -0
- package/src/api/admin-ai-skills.ts +2 -0
- package/src/api/admin-ai-usage.ts +2 -0
- package/src/api/admin-geoip.ts +80 -0
- package/src/api/admin-identity.ts +3 -0
- package/src/api/admin-lifecycle.ts +3 -0
- package/src/api/admin-outgoing-mail.ts +2 -0
- package/src/api/app-approval-actions.ts +31 -7
- package/src/api/app-approval.ts +46 -5
- package/src/api/approvals-loader.ts +23 -1
- package/src/api/approvals.ts +2 -0
- package/src/api/auth/schemas.ts +3 -0
- package/src/api/auth.ts +67 -12
- package/src/api/capabilities.ts +44 -11
- package/src/api/capability-streams.ts +12 -0
- package/src/api/help.ts +4 -28
- package/src/api/index.ts +6 -1
- package/src/api/lasting-access-messages.ts +17 -0
- package/src/api/mcp.ts +30 -3
- package/src/api/me-panel-consent.ts +71 -0
- package/src/api/me.ts +159 -36
- package/src/api/search.ts +3 -1
- package/src/api/sync-ops.ts +2 -0
- package/src/api/widgets.ts +3 -0
- package/src/approvals/Approvals.tsx +166 -262
- package/src/approvals/approval-card.ts +68 -0
- package/src/approvals/messages.ts +12 -24
- package/src/browser/CloudResourceSearch.browser-harness.tsx +10 -2
- package/src/browser/app-approval.ts +54 -5
- package/src/browser/commands.ts +22 -6
- package/src/browser/composer-catalog.ts +45 -3
- package/src/browser/composer-insert.ts +10 -0
- package/src/browser/composer-open-messages.ts +17 -0
- package/src/browser/composer-open.ts +110 -0
- package/src/browser/discoverable-command.ts +6 -0
- package/src/browser/hints.ts +6 -0
- package/src/browser/live-connection.ts +26 -2
- package/src/browser/navigation-apps.ts +7 -0
- package/src/browser/panel-context-bridge.ts +30 -0
- package/src/browser/panel-context-receiver.ts +53 -0
- package/src/browser/panel-menu.tsx +56 -0
- package/src/browser/panel-messages.ts +11 -0
- package/src/browser/panel-shortcuts.ts +31 -0
- package/src/browser/panel-slot.ts +412 -10
- package/src/browser/panel.ts +37 -0
- package/src/browser/search-commands.ts +6 -3
- package/src/capabilities/client.ts +8 -2
- package/src/capabilities/reviews.ts +205 -0
- package/src/capabilities/server.ts +10 -3
- package/src/capabilities/testing.ts +47 -0
- package/src/capabilities/types.ts +2 -2
- package/src/cli/account.ts +13 -3
- package/src/cli/admin/geoip.ts +115 -0
- package/src/cli/admin/index.ts +3 -0
- package/src/contracts/app-approval.ts +123 -16
- package/src/contracts/approvals.ts +2 -2
- package/src/contracts/capabilities.ts +410 -18
- package/src/contracts/geoip.ts +42 -0
- package/src/contracts/index.ts +3 -0
- package/src/contracts/panel-context-plain.ts +99 -0
- package/src/contracts/panel-context.ts +93 -0
- package/src/contracts/seen-hints.ts +35 -0
- package/src/contracts/shared.ts +3 -0
- package/src/server/client-ip.ts +5 -0
- package/src/server/help-payload.ts +38 -0
- package/src/server/middleware/auth.ts +13 -1
- package/src/server/middleware/middleware.ts +9 -0
- package/src/server/middleware/no-transform.ts +19 -0
- package/src/server/middleware/rate-limit.ts +4 -6
- package/src/server/request-context/address.ts +45 -0
- package/src/server/request-context/geoip.ts +380 -0
- package/src/server/request-context/index.ts +33 -0
- package/src/server/request-context/user-agent.ts +95 -0
- package/src/services/accounts/app.ts +23 -3
- package/src/services/accounts/users.ts +12 -1
- package/src/services/app-approval.ts +382 -59
- package/src/services/approvals/device.ts +49 -31
- package/src/services/approvals/index.ts +7 -5
- package/src/services/approvals/list.ts +27 -6
- package/src/services/approvals/question.ts +68 -0
- package/src/services/audit/index.ts +1 -0
- package/src/services/auth-flows/ipa.ts +39 -2
- package/src/services/auth-flows/notification-sender.ts +3 -0
- package/src/services/auth-flows/password-reset.ts +32 -6
- package/src/services/geoip/index.ts +164 -0
- package/src/services/geoip/store.ts +30 -0
- package/src/services/geoip/upload.ts +150 -0
- package/src/services/identity/routes.ts +5 -1
- package/src/services/index.ts +13 -0
- package/src/services/known-browsers.ts +312 -0
- package/src/services/login-address-store.ts +51 -0
- package/src/services/login-context.ts +118 -0
- package/src/services/mandates/index.ts +3 -0
- package/src/services/mandates/policy.ts +15 -3
- package/src/services/notifications/email-frame.ts +1 -1
- package/src/services/oauth-tokens.ts +16 -2
- package/src/services/panel-context-consent.ts +58 -0
- package/src/services/pdf/markdown.ts +160 -2
- package/src/services/service-account-credentials.ts +21 -0
- package/src/services/session/index.ts +296 -41
- package/src/services/session/recent.ts +13 -0
- package/src/services/webauthn.ts +12 -0
- package/src/shared/email-html.ts +2 -0
- package/src/shared/help.ts +3 -0
- package/src/shared-text/cursor.ts +22 -0
- package/src/shared-text/documents.ts +142 -0
- package/src/shared-text/events.ts +43 -0
- package/src/shared-text/index.ts +12 -0
- package/src/shared-text/log.ts +48 -0
- package/src/shared-text/text-diff.ts +23 -0
- package/src/shared-text/types.ts +41 -0
- package/src/ssr/AdminLayout.tsx +2 -0
- package/src/ssr/AppLaunchpadPanel.tsx +2 -2
- package/src/ssr/CloudShell.island.tsx +10 -2
- package/src/ssr/HelpPanelArticle.island.tsx +308 -0
- package/src/ssr/HelpPanelBack.island.tsx +32 -0
- package/src/ssr/HelpPanelMenu.island.tsx +133 -0
- package/src/ssr/HelpPanelSearch.island.tsx +123 -0
- package/src/ssr/HelpPanelShortcuts.island.tsx +22 -0
- package/src/ssr/HelpPanelView.island.tsx +27 -0
- package/src/ssr/Layout.tsx +25 -2
- package/src/ssr/LayoutHelp.browser-harness.tsx +2 -0
- package/src/ssr/LayoutHelp.tsx +222 -419
- package/src/ssr/LayoutHelpPanel.browser-harness.tsx +135 -0
- package/src/ssr/LayoutHelpPanel.server.browser-harness.tsx +131 -0
- package/src/ssr/LayoutHelpPanel.server.tsx +115 -0
- package/src/ssr/LayoutHelpTrigger.tsx +27 -2
- package/src/ssr/MobileNavigation.tsx +2 -2
- package/src/ssr/PanelLayout.tsx +9 -1
- package/src/ssr/PanelRuntime.island.tsx +7 -0
- package/src/ssr/PanelSlot.tsx +53 -3
- package/src/ssr/ShellRuntime.island.tsx +11 -2
- package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +29 -0
- package/src/ssr/app-color.ts +14 -9
- package/src/ssr/app-navigation.ts +16 -3
- package/src/ssr/compact-shell.browser-harness.ts +30 -22
- package/src/ssr/dialog-app-colors.browser-harness.tsx +43 -0
- package/src/ssr/help-messages.ts +4 -0
- package/src/ssr/help.ts +21 -0
- package/src/ssr/hints.ts +25 -0
- package/src/ssr/index.ts +3 -0
- package/src/ssr/layout-context.ts +2 -0
- package/src/ssr/layout-help-article.server.ts +50 -0
- package/src/ssr/layout-help-panel-controls.tsx +43 -0
- package/src/ssr/layout-help-panel-document.ts +67 -0
- package/src/ssr/layout-help-panel-lifecycle.ts +32 -0
- package/src/ssr/layout-help-panel-menu.tsx +113 -0
- package/src/ssr/layout-help-panel-modules.ts +7 -0
- package/src/ssr/layout-help-panel-reader.tsx +159 -0
- package/src/ssr/layout-help-panel-search.tsx +69 -0
- package/src/ssr/layout-help-panel-shortcuts.ts +7 -0
- package/src/ssr/layout-help-panel-state.ts +141 -0
- package/src/ssr/layout-help-panel-types.ts +19 -0
- package/src/ssr/layout-help-start.ts +33 -7
- package/src/ssr/layout-help-views.tsx +484 -0
- package/src/ssr/panel-consent.ts +29 -0
- package/src/ssr/panel-context.ts +10 -0
- package/src/ssr/panel-idle.ts +9 -0
- package/src/ssr/panel-slot.browser-harness.tsx +53 -4
- package/src/ssr/platform-messages.ts +18 -0
- package/src/ssr/shell-data.tsx +2 -1
- package/src/styles/approvals.css +6 -0
- package/src/styles/effects.css +213 -9
- package/src/styles/file-chooser.css +2 -1
- package/src/styles/global.css +49 -12
- package/src/styles/panel.css +47 -4
- package/src/styles/resource-search.css +6 -2
- package/src/styles/tokens.css +17 -11
- package/src/styles/utilities-buttons.css +3 -2
- package/src/styles/utilities-data.css +2 -1
- package/src/styles/utilities-feedback.css +2 -2
- package/src/styles/utilities-layout.css +3 -13
- package/src/styles/utilities-markdown-editor.css +6 -6
- package/src/styles/utilities-navigation.css +17 -22
package/src/ai/executor.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { AssistantMessage, CompactEvent, LoopAggregate, Message, NessiLoop, OutboundEvent } from "@k2b/nessi";
|
|
2
2
|
import { compact, nessi } from "@k2b/nessi";
|
|
3
|
+
import { capabilityReviewRememberScope } from "../_internal/capability-reviews";
|
|
3
4
|
import { listCapabilities } from "../_internal/registry";
|
|
4
|
-
import type {
|
|
5
|
+
import type { CapabilityActionReviewData } from "../contracts/capabilities";
|
|
6
|
+
import { capabilityActionPolicy } from "../contracts/capabilities";
|
|
5
7
|
import type { AccessSubject, RequestActor } from "../server";
|
|
6
8
|
import { approvalAssurance, approvalScopeFromToolName, approvals } from "../services/approvals";
|
|
7
9
|
import { createHelpReader } from "../services/help";
|
|
@@ -10,6 +12,7 @@ import { getMandate } from "../services/mandates";
|
|
|
10
12
|
import type { MandatePolicyV1 } from "../services/mandates/policy";
|
|
11
13
|
import { coreSettings } from "../services/settings/api";
|
|
12
14
|
import { normalizeLocale } from "../shared/locale";
|
|
15
|
+
import { type AiActionCheck, type AiStoredActionReview, storedActionReview } from "./action-review";
|
|
13
16
|
import {
|
|
14
17
|
AI_WEBSITE_APPROVAL_TOOL,
|
|
15
18
|
type AiToolApprovalContext,
|
|
@@ -19,20 +22,23 @@ import {
|
|
|
19
22
|
hasRememberedAiToolApproval,
|
|
20
23
|
} from "./approvals";
|
|
21
24
|
import { aiChatAccessSubject, isAssistantChatTurn, resolveAssistantAudioModel } from "./assistant-models";
|
|
25
|
+
import { authorizeBackgroundActionWith } from "./background-review";
|
|
22
26
|
import { CODE_RUNTIME_TOOL_NAMES } from "./browser-code-contracts";
|
|
23
|
-
import { type AiCapabilityCatalogEntry, createAiToolResolver, createRunToolStore } from "./capabilities";
|
|
24
|
-
import {
|
|
27
|
+
import { type AiCapabilityCatalogEntry, buildAiCapabilityCatalog, createAiToolResolver, createRunToolStore } from "./capabilities";
|
|
28
|
+
import {
|
|
29
|
+
AiCapabilityExecutionError,
|
|
30
|
+
aiCapabilityCheckResult,
|
|
31
|
+
checkAiCapability,
|
|
32
|
+
executeAiCapability,
|
|
33
|
+
resolveAiCapabilityActor,
|
|
34
|
+
reviewAiCapability,
|
|
35
|
+
} from "./capability-execution";
|
|
25
36
|
import { aiChatMessages } from "./chat/messages";
|
|
26
37
|
import { isFailedTool } from "./chat/tool-groups";
|
|
27
38
|
import { aiTurnErrorText } from "./chat/turn-error";
|
|
28
39
|
import { AiChatTaskAuthorityError, aiChatTasks } from "./chat-tasks";
|
|
29
40
|
import { createCloudCompactFn } from "./compaction";
|
|
30
|
-
import {
|
|
31
|
-
createCloudAiCodeTools,
|
|
32
|
-
createCloudAiLocalBashTool,
|
|
33
|
-
createCloudAiLocalTools,
|
|
34
|
-
createConfiguredDefaultCloudAiTools,
|
|
35
|
-
} from "./default-tools";
|
|
41
|
+
import { createAiTurnCodeAndClientTools, createConfiguredDefaultCloudAiTools } from "./default-tools";
|
|
36
42
|
import { aiFileStore } from "./files-store";
|
|
37
43
|
import { AI_LOCAL_TOOL_NAMES } from "./local-tool-contracts";
|
|
38
44
|
import {
|
|
@@ -72,7 +78,15 @@ import { isAiVisionModelConfigured, type resolveAiModel } from "./settings";
|
|
|
72
78
|
import { mergeAiSkillCatalog, selectAiSkillCatalog } from "./skill-catalog";
|
|
73
79
|
import { createCloudAiLoadSkillTool, createCloudAiSearchSkillsTool, loadSelectedAiSkills } from "./skill-tool";
|
|
74
80
|
import { aiSkills } from "./skills";
|
|
75
|
-
import {
|
|
81
|
+
import {
|
|
82
|
+
AI_TOOL_CHECK_STALE_MS,
|
|
83
|
+
aiConversations,
|
|
84
|
+
claimAiToolCheck,
|
|
85
|
+
readAiToolCheck,
|
|
86
|
+
recordAiToolReview,
|
|
87
|
+
recordAiWebsiteReceipts,
|
|
88
|
+
settleAiToolCheck,
|
|
89
|
+
} from "./store";
|
|
76
90
|
import { AI_LIVE_SNAPSHOT_INTERVAL_MS, publishAiWireEvent } from "./stream";
|
|
77
91
|
import { composeAiSystemPrompt } from "./system-prompt";
|
|
78
92
|
import { aiToolAudit } from "./tool-audit";
|
|
@@ -427,9 +441,9 @@ type ToolBlockPatch = Partial<Extract<AiTurnBlock, { kind: "tool" }>> & { name?:
|
|
|
427
441
|
const customApprovalParentCallId = (callId: string): string | undefined => /^(.*)-approval-\d+$/.exec(callId)?.[1];
|
|
428
442
|
|
|
429
443
|
const approvalReviewForCallId = (
|
|
430
|
-
reviews: ReadonlyMap<string,
|
|
444
|
+
reviews: ReadonlyMap<string, CapabilityActionReviewData>,
|
|
431
445
|
callId: string,
|
|
432
|
-
):
|
|
446
|
+
): CapabilityActionReviewData | undefined => {
|
|
433
447
|
const direct = reviews.get(callId);
|
|
434
448
|
if (direct) return direct;
|
|
435
449
|
const parentCallId = customApprovalParentCallId(callId);
|
|
@@ -479,9 +493,11 @@ const createEventMapper = (attempt: number, seedBlocks: AiTurnBlock[], allowReme
|
|
|
479
493
|
const setCanonicalNames = (items: Map<string, string>) => {
|
|
480
494
|
canonicalNames = items;
|
|
481
495
|
};
|
|
482
|
-
let approvalReviews = new Map<string,
|
|
483
|
-
|
|
496
|
+
let approvalReviews = new Map<string, CapabilityActionReviewData>();
|
|
497
|
+
let capabilityScopes: ReadonlyMap<string, string> = new Map();
|
|
498
|
+
const setApprovalReviews = (items: Map<string, CapabilityActionReviewData>, scopes: ReadonlyMap<string, string> = new Map()) => {
|
|
484
499
|
approvalReviews = items;
|
|
500
|
+
capabilityScopes = scopes;
|
|
485
501
|
};
|
|
486
502
|
let approvalPolicies: PreparedAiTools["approvalPolicies"] = new Map();
|
|
487
503
|
const setApprovalPolicies = (items: PreparedAiTools["approvalPolicies"]) => {
|
|
@@ -505,7 +521,7 @@ const createEventMapper = (attempt: number, seedBlocks: AiTurnBlock[], allowReme
|
|
|
505
521
|
const streamKinds = new Map<string, "text" | "thinking">();
|
|
506
522
|
|
|
507
523
|
const setTool = (callId: string, patch: ToolBlockPatch, displayCallId = callId): BlockOp => {
|
|
508
|
-
const existing = toolBlocks.get(callId);
|
|
524
|
+
const existing = toolBlocks.get(callId) ?? toolBlocks.get(displayCallId);
|
|
509
525
|
const rawName = patch.name ?? existing?.name ?? "tool";
|
|
510
526
|
const name = canonicalNames.get(rawName) ?? rawName;
|
|
511
527
|
const approval = patch.clearApproval ? undefined : "approval" in patch ? patch.approval : existing?.approval;
|
|
@@ -520,6 +536,8 @@ const createEventMapper = (attempt: number, seedBlocks: AiTurnBlock[], allowReme
|
|
|
520
536
|
result: "result" in patch ? patch.result : existing?.result,
|
|
521
537
|
isError: "isError" in patch ? patch.isError : existing?.isError,
|
|
522
538
|
approval: approval && !allowRememberedApprovals ? { ...approval, allowAlways: false, allowChat: false } : approval,
|
|
539
|
+
review: patch.review ?? existing?.review,
|
|
540
|
+
check: patch.check ?? existing?.check,
|
|
523
541
|
frontendMode: patch.frontendMode ?? existing?.frontendMode,
|
|
524
542
|
presentation: patch.presentation ?? existing?.presentation ?? presentations.get(rawName) ?? presentations.get(name),
|
|
525
543
|
...(approvedCallIds.has(callId) || approvedCallIds.has(displayCallId) || existing?.approved ? { approved: true } : {}),
|
|
@@ -572,7 +590,8 @@ const createEventMapper = (attempt: number, seedBlocks: AiTurnBlock[], allowReme
|
|
|
572
590
|
const target = event.kind === "custom_approval" ? approvalTargets.get(event.callId) : undefined;
|
|
573
591
|
const allowAlways = target
|
|
574
592
|
? target.always
|
|
575
|
-
:
|
|
593
|
+
: capabilityScopes.has(customApprovalParentCallId(event.callId) ?? event.callId) ||
|
|
594
|
+
aiToolAllowsAlways(approvalPolicies.get(event.name));
|
|
576
595
|
return [
|
|
577
596
|
setTool(
|
|
578
597
|
event.callId,
|
|
@@ -651,6 +670,7 @@ const createEventMapper = (attempt: number, seedBlocks: AiTurnBlock[], allowReme
|
|
|
651
670
|
|
|
652
671
|
return {
|
|
653
672
|
translate,
|
|
673
|
+
setTool,
|
|
654
674
|
storedMessage,
|
|
655
675
|
compaction,
|
|
656
676
|
setFrontendModes,
|
|
@@ -690,10 +710,7 @@ const materializeChatConfig = async (config: AiChatTurnRunConfig, signal: AbortS
|
|
|
690
710
|
accessSubject: aiChatAccessSubject(config.actor),
|
|
691
711
|
allowedDataBoundaries: config.modelPolicy?.allowedDataBoundaries,
|
|
692
712
|
})),
|
|
693
|
-
|
|
694
|
-
...[createCloudAiLocalBashTool(), ...createCloudAiLocalTools(), ...createCloudAiCodeTools()].filter(
|
|
695
|
-
(tool) => tool.location === "server" || config.clientToolIds?.some((name) => name === tool.def.name),
|
|
696
|
-
),
|
|
713
|
+
...createAiTurnCodeAndClientTools(config),
|
|
697
714
|
]
|
|
698
715
|
: [],
|
|
699
716
|
toolApprovalContext: config.toolApprovalContext,
|
|
@@ -997,11 +1014,7 @@ export class AiTurnExecutor {
|
|
|
997
1014
|
];
|
|
998
1015
|
const toolsSupported = resolved.profile.capabilities.includes("tools");
|
|
999
1016
|
const allowed = allowedTools === null ? null : new Set(allowedTools);
|
|
1000
|
-
const activeTools = toolsSupported
|
|
1001
|
-
? runtimeTools.filter(
|
|
1002
|
-
(tool) => (!allowed || allowed.has(tool.def.name)) && !(config.mandate && ["code_open", "code_secret"].includes(tool.def.name)),
|
|
1003
|
-
)
|
|
1004
|
-
: [];
|
|
1017
|
+
const activeTools = toolsSupported ? runtimeTools.filter((tool) => !allowed || allowed.has(tool.def.name)) : [];
|
|
1005
1018
|
let audioUnavailableInstruction: string | undefined;
|
|
1006
1019
|
if (
|
|
1007
1020
|
defaultToolSource &&
|
|
@@ -1042,6 +1055,8 @@ export class AiTurnExecutor {
|
|
|
1042
1055
|
const approvalTargets = new Map<string, AiApprovalTarget>();
|
|
1043
1056
|
const linkedApprovals = new Map<string, string>();
|
|
1044
1057
|
const freshApprovalRequired = new Set<string>();
|
|
1058
|
+
const capabilityActionReviews = new Map<string, CapabilityActionReviewData>();
|
|
1059
|
+
const storedReviews = new Map<string, AiStoredActionReview>();
|
|
1045
1060
|
const dynamicToolRuntimeContext = {
|
|
1046
1061
|
turnId,
|
|
1047
1062
|
reportToolProgress: (callId: string, message: string) => pipeline.reportToolProgress(callId, message),
|
|
@@ -1083,6 +1098,14 @@ export class AiTurnExecutor {
|
|
|
1083
1098
|
return "changed";
|
|
1084
1099
|
},
|
|
1085
1100
|
describeApproval: (approvalCallId: string, target: AiApprovalTarget) => approvalTargets.set(approvalCallId, target),
|
|
1101
|
+
describeReview: async (approvalCallId: string, review: CapabilityActionReviewData) => {
|
|
1102
|
+
capabilityActionReviews.set(approvalCallId, review);
|
|
1103
|
+
const callId = customApprovalParentCallId(approvalCallId) ?? approvalCallId;
|
|
1104
|
+
const stored = storedActionReview(review);
|
|
1105
|
+
await recordAiToolReview({ conversationId, turnId, callId, review: stored });
|
|
1106
|
+
storedReviews.set(callId, stored);
|
|
1107
|
+
await pipeline.setToolReview(callId, stored);
|
|
1108
|
+
},
|
|
1086
1109
|
};
|
|
1087
1110
|
const prepared = prepareAiTools({
|
|
1088
1111
|
tools: activeTools,
|
|
@@ -1092,7 +1115,21 @@ export class AiTurnExecutor {
|
|
|
1092
1115
|
});
|
|
1093
1116
|
let backgroundError: string | null = null;
|
|
1094
1117
|
const rememberableCapabilityApprovals = new Map<string, string>();
|
|
1095
|
-
const
|
|
1118
|
+
const onCapabilityReview = async (
|
|
1119
|
+
callId: string,
|
|
1120
|
+
review: CapabilityActionReviewData,
|
|
1121
|
+
approval: "none" | "rememberable" | undefined,
|
|
1122
|
+
entry: AiCapabilityCatalogEntry,
|
|
1123
|
+
) => {
|
|
1124
|
+
capabilityActionReviews.set(callId, review);
|
|
1125
|
+
const scope = capabilityReviewRememberScope(review, approval);
|
|
1126
|
+
if (scope) rememberableCapabilityApprovals.set(callId, scope);
|
|
1127
|
+
else rememberableCapabilityApprovals.delete(callId);
|
|
1128
|
+
const stored = storedActionReview(review, capabilityActionPolicy(entry.app.manifest, entry.operation.localId));
|
|
1129
|
+
await recordAiToolReview({ conversationId, turnId, callId, review: stored });
|
|
1130
|
+
storedReviews.set(callId, stored);
|
|
1131
|
+
await pipeline.setToolReview(callId, stored);
|
|
1132
|
+
};
|
|
1096
1133
|
pipeline.setApprovalTargets(approvalTargets);
|
|
1097
1134
|
pipeline.setFrontendModes(prepared.frontendModes);
|
|
1098
1135
|
pipeline.setCanonicalNames(prepared.canonicalNames);
|
|
@@ -1101,7 +1138,7 @@ export class AiTurnExecutor {
|
|
|
1101
1138
|
const rejectedToolCallIds = new Set<string>();
|
|
1102
1139
|
const approvedToolCallIds = new Set<string>();
|
|
1103
1140
|
pipeline.setPresentations(toolPresentations);
|
|
1104
|
-
pipeline.setApprovalReviews(capabilityActionReviews);
|
|
1141
|
+
pipeline.setApprovalReviews(capabilityActionReviews, rememberableCapabilityApprovals);
|
|
1105
1142
|
let turnInput = config.input;
|
|
1106
1143
|
try {
|
|
1107
1144
|
if (resolved.profile.capabilities.includes("vision") && config.files?.attached.some((file) => isAiImageMediaType(file.mediaType))) {
|
|
@@ -1192,7 +1229,7 @@ export class AiTurnExecutor {
|
|
|
1192
1229
|
);
|
|
1193
1230
|
return;
|
|
1194
1231
|
}
|
|
1195
|
-
const
|
|
1232
|
+
const authorizeBackgroundMandate =
|
|
1196
1233
|
config.background && config.mandate
|
|
1197
1234
|
? async (entry: AiCapabilityCatalogEntry, args: unknown) => {
|
|
1198
1235
|
try {
|
|
@@ -1202,6 +1239,7 @@ export class AiTurnExecutor {
|
|
|
1202
1239
|
capabilityId: entry.operation.localId,
|
|
1203
1240
|
kind: entry.kind,
|
|
1204
1241
|
input: args,
|
|
1242
|
+
policy: capabilityActionPolicy(entry.app.manifest, entry.operation.localId),
|
|
1205
1243
|
approval: entry.kind === "action" && "approval" in entry.operation ? entry.operation.approval : undefined,
|
|
1206
1244
|
});
|
|
1207
1245
|
} catch (error) {
|
|
@@ -1245,16 +1283,51 @@ export class AiTurnExecutor {
|
|
|
1245
1283
|
}),
|
|
1246
1284
|
}
|
|
1247
1285
|
: {}),
|
|
1248
|
-
authorizeBackground
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1286
|
+
authorizeBackground: authorizeBackgroundMandate
|
|
1287
|
+
? async (entry, args, context) => {
|
|
1288
|
+
const review = await authorizeBackgroundActionWith(
|
|
1289
|
+
{
|
|
1290
|
+
turnId,
|
|
1291
|
+
mandate: config.mandate!,
|
|
1292
|
+
appId: entry.appId,
|
|
1293
|
+
capabilityId: entry.operation.localId,
|
|
1294
|
+
kind: entry.kind,
|
|
1295
|
+
args,
|
|
1296
|
+
policy: capabilityActionPolicy(entry.app.manifest, entry.operation.localId),
|
|
1297
|
+
approval: entry.kind === "action" && "approval" in entry.operation ? entry.operation.approval : undefined,
|
|
1298
|
+
hasReview: entry.kind === "action" && "review" in entry.operation && entry.operation.review === true,
|
|
1299
|
+
action: entry.display.title,
|
|
1300
|
+
review: () =>
|
|
1301
|
+
capabilityAuthority
|
|
1302
|
+
? reviewAiCapability({
|
|
1303
|
+
conversationId,
|
|
1304
|
+
authority: capabilityAuthority,
|
|
1305
|
+
mandate: config.mandate,
|
|
1306
|
+
locale: promptLocale,
|
|
1307
|
+
entry,
|
|
1308
|
+
args,
|
|
1309
|
+
context,
|
|
1310
|
+
})
|
|
1311
|
+
: Promise.resolve(null),
|
|
1312
|
+
signal: context.signal,
|
|
1313
|
+
},
|
|
1314
|
+
() => authorizeBackgroundMandate(entry, args),
|
|
1315
|
+
);
|
|
1316
|
+
if (review && context.callId)
|
|
1317
|
+
await onCapabilityReview(
|
|
1318
|
+
context.callId,
|
|
1319
|
+
review,
|
|
1320
|
+
"approval" in entry.operation ? entry.operation.approval : undefined,
|
|
1321
|
+
entry,
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1324
|
+
: undefined,
|
|
1325
|
+
onReview: onCapabilityReview,
|
|
1253
1326
|
...(capabilityAuthority
|
|
1254
1327
|
? {
|
|
1255
1328
|
execute: async (entry, args, context) => {
|
|
1256
1329
|
try {
|
|
1257
|
-
await
|
|
1330
|
+
await authorizeBackgroundMandate?.(entry, args);
|
|
1258
1331
|
const result = await executeAiCapability({
|
|
1259
1332
|
conversationId,
|
|
1260
1333
|
turnId,
|
|
@@ -1268,9 +1341,78 @@ export class AiTurnExecutor {
|
|
|
1268
1341
|
entry,
|
|
1269
1342
|
args,
|
|
1270
1343
|
context,
|
|
1344
|
+
}).catch(async (error: unknown) => {
|
|
1345
|
+
const review = context.callId ? storedReviews.get(context.callId) : undefined;
|
|
1346
|
+
if (entry.kind !== "action" || !(error instanceof AiCapabilityExecutionError)) throw error;
|
|
1347
|
+
// Only an answer from the app about the request itself (a 4xx) proves that nothing happened. A 5xx,
|
|
1348
|
+
// a timeout, or a cancellation leaves the outcome open only once Cloud sent the request to the app;
|
|
1349
|
+
// before that, nothing ran and the error stays as it is. The idempotency codes speak of an earlier
|
|
1350
|
+
// attempt with the same key, so they leave it open even when this one sent nothing.
|
|
1351
|
+
const uncertain =
|
|
1352
|
+
["ACTION_OUTCOME_UNKNOWN", "IDEMPOTENCY_UNCERTAIN", "IDEMPOTENCY_IN_PROGRESS"].includes(error.code) ||
|
|
1353
|
+
(error.dispatched && (error.status >= 500 || error.status === 499));
|
|
1354
|
+
if (!uncertain) throw error;
|
|
1355
|
+
const unknown =
|
|
1356
|
+
error.code === "ACTION_OUTCOME_UNKNOWN"
|
|
1357
|
+
? error
|
|
1358
|
+
: new AiCapabilityExecutionError(
|
|
1359
|
+
"ACTION_OUTCOME_UNKNOWN",
|
|
1360
|
+
error.status,
|
|
1361
|
+
`The outcome is unknown after ${error.code}; do not retry automatically.`,
|
|
1362
|
+
error.dispatched,
|
|
1363
|
+
);
|
|
1364
|
+
if (!review || !("sentence" in review) || !review.check || !context.callId) throw unknown;
|
|
1365
|
+
let check: AiActionCheck;
|
|
1366
|
+
try {
|
|
1367
|
+
// Durable first: the claim stores `checking` before the Query runs. If this worker stops, the
|
|
1368
|
+
// claim goes stale and the person can check again; while it holds, no re-check runs beside it.
|
|
1369
|
+
const target = { conversationId, turnId, callId: context.callId };
|
|
1370
|
+
const claimedAt = new Date().toISOString();
|
|
1371
|
+
const claimed = await claimAiToolCheck({
|
|
1372
|
+
...target,
|
|
1373
|
+
at: claimedAt,
|
|
1374
|
+
staleBefore: new Date(Date.now() - AI_TOOL_CHECK_STALE_MS).toISOString(),
|
|
1375
|
+
first: true,
|
|
1376
|
+
});
|
|
1377
|
+
if (!claimed) {
|
|
1378
|
+
// A person's check holds or settled the call already; its stored result is the outcome.
|
|
1379
|
+
const stored = await readAiToolCheck(target);
|
|
1380
|
+
const known = stored && stored.state !== "checking" ? stored : { state: "pending" as const };
|
|
1381
|
+
check = known;
|
|
1382
|
+
} else {
|
|
1383
|
+
await pipeline.setToolCheck(context.callId, { state: "checking" });
|
|
1384
|
+
const answered = await checkAiCapability({
|
|
1385
|
+
conversationId,
|
|
1386
|
+
turnId,
|
|
1387
|
+
authority: capabilityAuthority!,
|
|
1388
|
+
mandate: config.mandate,
|
|
1389
|
+
// The receipt joins the review's sentence, so it is asked for in the review's language.
|
|
1390
|
+
locale: review.locale,
|
|
1391
|
+
entry,
|
|
1392
|
+
check: review.check,
|
|
1393
|
+
catalog: buildAiCapabilityCatalog([entry.app], promptLocale),
|
|
1394
|
+
context,
|
|
1395
|
+
});
|
|
1396
|
+
// A claim that went stale and was taken over loses to the newer check's stored result.
|
|
1397
|
+
check = (await settleAiToolCheck({ ...target, claimedAt, check: answered }))
|
|
1398
|
+
? answered
|
|
1399
|
+
: ((await readAiToolCheck(target)) ?? answered);
|
|
1400
|
+
}
|
|
1401
|
+
await pipeline.setToolCheck(context.callId, check);
|
|
1402
|
+
} catch (recoveryError) {
|
|
1403
|
+
log.warn("AI Action outcome recovery failed", {
|
|
1404
|
+
code: "action_outcome_recovery_failed",
|
|
1405
|
+
turnId,
|
|
1406
|
+
callId: context.callId,
|
|
1407
|
+
error: recoveryError instanceof Error ? recoveryError.message : String(recoveryError),
|
|
1408
|
+
});
|
|
1409
|
+
throw unknown;
|
|
1410
|
+
}
|
|
1411
|
+
return aiCapabilityCheckResult(check);
|
|
1271
1412
|
});
|
|
1272
1413
|
const resources = collectConversationResourceObservations(args, result);
|
|
1273
1414
|
if (resources.length) {
|
|
1415
|
+
// Bookkeeping after a call that ran never turns its outcome into a failure: the effect happened.
|
|
1274
1416
|
await Promise.all([
|
|
1275
1417
|
indexConversationResources({ conversationId, turnId, callId: context.callId, resources }),
|
|
1276
1418
|
recordMemoryWorkflowEvidence({
|
|
@@ -1280,7 +1422,14 @@ export class AiTurnExecutor {
|
|
|
1280
1422
|
capabilityId: entry.name,
|
|
1281
1423
|
resources,
|
|
1282
1424
|
}),
|
|
1283
|
-
])
|
|
1425
|
+
]).catch((error: unknown) =>
|
|
1426
|
+
log.warn("AI capability bookkeeping failed after the call ran", {
|
|
1427
|
+
code: "capability_bookkeeping_failed",
|
|
1428
|
+
turnId,
|
|
1429
|
+
callId: context.callId,
|
|
1430
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1431
|
+
}),
|
|
1432
|
+
);
|
|
1284
1433
|
}
|
|
1285
1434
|
return result;
|
|
1286
1435
|
} catch (error) {
|
|
@@ -1593,7 +1742,7 @@ export class AiTurnExecutor {
|
|
|
1593
1742
|
approvalContext?: AiToolApprovalContext;
|
|
1594
1743
|
allowRememberedApprovals: boolean;
|
|
1595
1744
|
rememberableCapabilityApprovals: ReadonlyMap<string, string>;
|
|
1596
|
-
capabilityActionReviews: ReadonlyMap<string,
|
|
1745
|
+
capabilityActionReviews: ReadonlyMap<string, CapabilityActionReviewData>;
|
|
1597
1746
|
approvalTargets: ReadonlyMap<string, AiApprovalTarget>;
|
|
1598
1747
|
linkedApprovals: ReadonlyMap<string, string>;
|
|
1599
1748
|
freshApprovalRequired?: ReadonlySet<string>;
|
|
@@ -1778,7 +1927,7 @@ export class AiTurnExecutor {
|
|
|
1778
1927
|
approvalContext?: AiToolApprovalContext;
|
|
1779
1928
|
allowRememberedApprovals: boolean;
|
|
1780
1929
|
rememberableCapabilityApprovals: ReadonlyMap<string, string>;
|
|
1781
|
-
capabilityActionReviews: ReadonlyMap<string,
|
|
1930
|
+
capabilityActionReviews: ReadonlyMap<string, CapabilityActionReviewData>;
|
|
1782
1931
|
approvalTargets: ReadonlyMap<string, AiApprovalTarget>;
|
|
1783
1932
|
linkedApprovals: ReadonlyMap<string, string>;
|
|
1784
1933
|
freshApprovalRequired?: ReadonlySet<string>;
|
|
@@ -2207,8 +2356,8 @@ class StreamPipeline {
|
|
|
2207
2356
|
this.mapper.setCanonicalNames(names);
|
|
2208
2357
|
}
|
|
2209
2358
|
|
|
2210
|
-
setApprovalReviews(reviews: Map<string,
|
|
2211
|
-
this.mapper.setApprovalReviews(reviews);
|
|
2359
|
+
setApprovalReviews(reviews: Map<string, CapabilityActionReviewData>, scopes: ReadonlyMap<string, string>): void {
|
|
2360
|
+
this.mapper.setApprovalReviews(reviews, scopes);
|
|
2212
2361
|
}
|
|
2213
2362
|
|
|
2214
2363
|
setApprovalPolicies(policies: PreparedAiTools["approvalPolicies"]): void {
|
|
@@ -2290,6 +2439,16 @@ class StreamPipeline {
|
|
|
2290
2439
|
await this.maybeSnapshot();
|
|
2291
2440
|
}
|
|
2292
2441
|
|
|
2442
|
+
async setToolReview(callId: string, review: AiStoredActionReview): Promise<void> {
|
|
2443
|
+
await this.emitOp(this.mapper.setTool(callId, { review }));
|
|
2444
|
+
await this.maybeSnapshot();
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
async setToolCheck(callId: string, check: AiActionCheck): Promise<void> {
|
|
2448
|
+
await this.emitOp(this.mapper.setTool(callId, { check, status: "running" }));
|
|
2449
|
+
await this.maybeSnapshot();
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2293
2452
|
async reportToolProgress(callId: string, progress: string): Promise<void> {
|
|
2294
2453
|
const block = this.blocks.find((block) => block.kind === "tool" && block.callId === callId);
|
|
2295
2454
|
if (!block || block.kind !== "tool" || block.progress === progress) return;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { type ToolContext, truncateMiddle } from "@k2b/nessi";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import type { CapabilityActionCallReview } from "../contracts/capabilities";
|
|
3
4
|
import type { RequestActor } from "../server";
|
|
4
5
|
import { coreSettings } from "../services";
|
|
5
6
|
import { isUnsafeNetworkAddress, isUnsafeNetworkHostname, parseNetworkAddress } from "../shared/network-address";
|
|
6
7
|
import { AI_WEBSITE_APPROVAL_TOOL, aiTurnAllowsWebsiteApprovals, aiWebsiteApprovalScope, findRememberedAiToolApproval } from "./approvals";
|
|
7
8
|
import { aiConversations } from "./store";
|
|
9
|
+
import { cloudToolReview } from "./tool-sentences";
|
|
8
10
|
import { defineAiTool } from "./tools";
|
|
9
11
|
import type { AiApprovalTarget, AiWebsiteReceipt } from "./types";
|
|
10
12
|
import { hasWebAddressProvenance, recordWebAddresses, webAddressesIn } from "./web-provenance";
|
|
@@ -280,7 +282,7 @@ export type WebReadContext = Pick<ToolContext, "requestApproval"> & {
|
|
|
280
282
|
conversationId?: string;
|
|
281
283
|
turnId?: string;
|
|
282
284
|
locale?: string;
|
|
283
|
-
requestApprovalFor?: (message: string, target: AiApprovalTarget) => Promise<boolean>;
|
|
285
|
+
requestApprovalFor?: (message: string, target: AiApprovalTarget, review?: CapabilityActionCallReview) => Promise<boolean>;
|
|
284
286
|
reportWebsiteReceipts?: (receipts: AiWebsiteReceipt[]) => Promise<void>;
|
|
285
287
|
};
|
|
286
288
|
|
|
@@ -306,7 +308,18 @@ export const authorizeWebRead = async (kind: "page" | "file", url: string, ctx:
|
|
|
306
308
|
return true;
|
|
307
309
|
}
|
|
308
310
|
const message = webReadApprovalMessage(kind, url, ctx.locale);
|
|
309
|
-
const
|
|
311
|
+
const review = cloudToolReview(kind === "page" ? "web_extract" : "fetch_file", { url }, ctx.locale ?? "en");
|
|
312
|
+
const question: CapabilityActionCallReview | undefined = review
|
|
313
|
+
? {
|
|
314
|
+
...review,
|
|
315
|
+
ask: "sends",
|
|
316
|
+
consequences: message
|
|
317
|
+
.split("\n")
|
|
318
|
+
.slice(1)
|
|
319
|
+
.map((text) => ({ text })),
|
|
320
|
+
}
|
|
321
|
+
: undefined;
|
|
322
|
+
const approved = ctx.requestApprovalFor ? await ctx.requestApprovalFor(message, target, question) : await ctx.requestApproval(message);
|
|
310
323
|
if (!approved) throw new Error("The person did not allow reading this address. Use an address from their message or a search result.");
|
|
311
324
|
return false;
|
|
312
325
|
};
|
package/src/ai/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ export {
|
|
|
22
22
|
export { assistantAiSettingsState, listAssistantAiModels, selectAssistantAiModelId } from "./assistant-models";
|
|
23
23
|
export { type AiAttachmentRef, aiAttachmentMarker, formatAiFileSize, parseAiAttachmentMarkers } from "./attachments";
|
|
24
24
|
export { CloudAiTranscribeAudioInputSchema, CloudAiTranscribeAudioOutputSchema, createCloudAiTranscribeAudioTool } from "./audio-tool";
|
|
25
|
+
export { AiBackgroundApprovalRequiredError, authorizeBackgroundAction } from "./background-review";
|
|
25
26
|
export { CODE_RUNTIME_TOOL_NAMES, CodeResourceId, CodeRuntimeInput, parseCodeToolInput } from "./browser-code-contracts";
|
|
26
27
|
export { aiCapabilityId, aiCapabilityToolName } from "./capabilities";
|
|
27
28
|
export { getAiChatQuotas } from "./chat-quotas";
|
|
@@ -261,6 +262,7 @@ export {
|
|
|
261
262
|
createCloudAiMemoryTool,
|
|
262
263
|
} from "./memory-tool";
|
|
263
264
|
export { migrateCloudAi } from "./migrate";
|
|
265
|
+
export { type AiOpenQuestion, type AiOpenQuestions, listAiOpenQuestions } from "./open-questions";
|
|
264
266
|
export { personalAiModelPolicy } from "./personal-agent";
|
|
265
267
|
export { type AiUserPrefs, aiActorUser, aiPrefsUserId, aiUserPrefs } from "./prefs";
|
|
266
268
|
export {
|
|
@@ -419,6 +421,7 @@ export type {
|
|
|
419
421
|
AiProjectPromptSnapshot,
|
|
420
422
|
AiProviderId,
|
|
421
423
|
AiPublicModelProfile,
|
|
424
|
+
AiRecentResource,
|
|
422
425
|
AiResolvedModel,
|
|
423
426
|
AiRuntimeTool,
|
|
424
427
|
AiSettingsError,
|
package/src/ai/migrate.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { sql } from "bun";
|
|
2
|
-
import {
|
|
2
|
+
import { CapabilityActionReviewDataSchema } from "../contracts/capabilities";
|
|
3
3
|
import { APPROVAL_MAX_WAIT_MS, approvalAssurance, approvalScopeFromToolName, approvals, migrateApprovals } from "../services/approvals";
|
|
4
4
|
import { migrateChatTaskReasons } from "./chat-task-reasons-migrate";
|
|
5
5
|
import { migrateAiMessageQueue } from "./message-queue";
|
|
@@ -404,6 +404,15 @@ export const migrateCloudAi = async (): Promise<void> => {
|
|
|
404
404
|
ON ai.messages(conversation_id, loop_id, seq ASC)
|
|
405
405
|
WHERE loop_id IS NOT NULL
|
|
406
406
|
`.simple();
|
|
407
|
+
// "Recent" in the slash menu reads the newest messages a person sent in each of their latest chats. Without it the
|
|
408
|
+
// read walks every Assistant and tool row of those chats. The predicate matches the store query word for word.
|
|
409
|
+
await sql`
|
|
410
|
+
CREATE INDEX IF NOT EXISTS idx_ai_messages_sent_by_person
|
|
411
|
+
ON ai.messages(conversation_id, seq DESC) INCLUDE (created_at, id)
|
|
412
|
+
WHERE role = 'user' AND kind = 'message'
|
|
413
|
+
AND NOT (COALESCE(meta, '{}'::jsonb) ? 'scheduledTask')
|
|
414
|
+
AND NOT (COALESCE(meta, '{}'::jsonb) ? 'agentMessage')
|
|
415
|
+
`.simple();
|
|
407
416
|
|
|
408
417
|
// Optional pg_textsearch ranking. Native weighted FTS above is always the
|
|
409
418
|
// correctness path; missing extension support must never block startup.
|
|
@@ -2288,6 +2297,21 @@ export const migrateCloudAi = async (): Promise<void> => {
|
|
|
2288
2297
|
AFTER UPDATE OF status, error, completed_at, cancel_requested_at ON ai.turns
|
|
2289
2298
|
FOR EACH ROW EXECUTE FUNCTION ai.live_conversation_child_changed('conversation-list,conversation-detail')
|
|
2290
2299
|
`.simple();
|
|
2300
|
+
// Only questions for the person change the list of open questions; browser tool calls come and go per turn.
|
|
2301
|
+
await sql`DROP TRIGGER IF EXISTS ai_live_pending_actions_changed ON ai.pending_actions`.simple();
|
|
2302
|
+
await sql`DROP TRIGGER IF EXISTS ai_live_pending_actions_removed ON ai.pending_actions`.simple();
|
|
2303
|
+
await sql`
|
|
2304
|
+
CREATE TRIGGER ai_live_pending_actions_changed
|
|
2305
|
+
AFTER INSERT OR UPDATE OF status ON ai.pending_actions
|
|
2306
|
+
FOR EACH ROW WHEN (NEW.kind IN ('approval', 'custom_approval'))
|
|
2307
|
+
EXECUTE FUNCTION ai.live_conversation_child_changed('conversation-list')
|
|
2308
|
+
`.simple();
|
|
2309
|
+
await sql`
|
|
2310
|
+
CREATE TRIGGER ai_live_pending_actions_removed
|
|
2311
|
+
AFTER DELETE ON ai.pending_actions
|
|
2312
|
+
FOR EACH ROW WHEN (OLD.kind IN ('approval', 'custom_approval'))
|
|
2313
|
+
EXECUTE FUNCTION ai.live_conversation_child_changed('conversation-list')
|
|
2314
|
+
`.simple();
|
|
2291
2315
|
await sql`DROP TRIGGER IF EXISTS ai_live_chat_tasks_changed ON ai.chat_tasks`.simple();
|
|
2292
2316
|
await sql`
|
|
2293
2317
|
CREATE TRIGGER ai_live_chat_tasks_changed AFTER INSERT OR UPDATE OR DELETE ON ai.chat_tasks
|
|
@@ -2667,7 +2691,7 @@ const backfillApprovalRequests = async (): Promise<void> => {
|
|
|
2667
2691
|
WHERE r.requester_kind='assistant_turn' AND r.requester_ref=a.turn_id::text || '/' || a.call_id)
|
|
2668
2692
|
ORDER BY a.created_at, a.id LIMIT 200 FOR UPDATE OF a SKIP LOCKED`;
|
|
2669
2693
|
for (const row of rows) {
|
|
2670
|
-
const review =
|
|
2694
|
+
const review = CapabilityActionReviewDataSchema.safeParse(row.review == null ? null : decoded(row.review));
|
|
2671
2695
|
// request computes both canonical digests; preserve the original wait deadline, including expiry.
|
|
2672
2696
|
const record = await approvals.request(
|
|
2673
2697
|
{
|