@k2b/cloud 0.43.0 → 0.45.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/package.json +11 -2
- package/src/_internal/app-access-request.ts +13 -0
- package/src/_internal/app-access-resources.ts +141 -0
- package/src/_internal/app-event-catalog.ts +40 -0
- package/src/_internal/app-event-schemas.ts +127 -0
- package/src/_internal/application-package.ts +40 -0
- package/src/_internal/built-in-app-ids.json +27 -0
- package/src/_internal/capabilities.ts +158 -12
- package/src/_internal/capability-origin.ts +4 -0
- package/src/_internal/capability-streams.ts +7 -5
- package/src/_internal/define-app.ts +57 -6
- package/src/_internal/object-store-area.ts +9 -0
- package/src/_internal/object-store-config.ts +101 -0
- package/src/_internal/object-store-key.ts +14 -0
- package/src/_internal/process-identity.ts +7 -2
- package/src/_internal/registry-validation.ts +11 -1
- package/src/_internal/registry.ts +8 -1
- package/src/_internal/runtime-context.ts +8 -1
- package/src/access/AppAccessEditor.tsx +2 -0
- package/src/access/GroupCoverage.tsx +20 -5
- package/src/access/PermissionEditor.tsx +20 -5
- package/src/access/PrincipalAvatar.tsx +30 -21
- package/src/access/PrincipalPicker.tsx +14 -4
- package/src/access/ResourceApiKeys.tsx +3 -4
- package/src/access/app-access-messages.ts +6 -0
- package/src/access/messages.ts +4 -4
- package/src/account/EntitySearch.tsx +12 -0
- package/src/account/ProfilePictureDialog.tsx +699 -0
- package/src/account/profile-picture-messages.ts +269 -0
- package/src/account/ui.ts +7 -0
- package/src/ai/admin.ts +3 -0
- package/src/ai/app-frame.ts +12 -0
- package/src/ai/application-tasks.ts +101 -0
- package/src/ai/assistant-models.ts +126 -64
- package/src/ai/capabilities.ts +19 -4
- package/src/ai/chart-block.ts +63 -30
- package/src/ai/chat/chart-block.tsx +12 -3
- package/src/ai/chat/composer-adapter.ts +8 -7
- package/src/ai/chat/message-actions.tsx +1 -1
- package/src/ai/chat/message-utils.ts +2 -2
- package/src/ai/chat/presentation.tsx +22 -1
- package/src/ai/chat-quotas.ts +5 -4
- package/src/ai/chat-tasks.ts +2 -2
- package/src/ai/client/controller.ts +11 -10
- package/src/ai/client/projection.ts +3 -3
- package/src/ai/compaction.ts +3 -3
- package/src/ai/credentials.ts +13 -114
- package/src/ai/default-tool-contracts.ts +4 -3
- package/src/ai/dictation-runtime.ts +10 -6
- package/src/ai/enrich.ts +2 -2
- package/src/ai/executor.ts +40 -25
- package/src/ai/http.ts +17 -29
- package/src/ai/index.ts +12 -5
- package/src/ai/inference-calls.ts +5 -4
- package/src/ai/maintenance.ts +1 -1
- package/src/ai/memory-learning-runs.ts +5 -5
- package/src/ai/memory-learning.ts +3 -3
- package/src/ai/message-queue.ts +1 -2
- package/src/ai/migrate.ts +75 -42
- package/src/ai/model-access-migrate.ts +3 -17
- package/src/ai/model-access.ts +44 -85
- package/src/ai/model-card.tsx +134 -0
- package/src/ai/model-catalog.ts +359 -0
- package/src/ai/model-chooser-messages.ts +88 -0
- package/src/ai/model-chooser.tsx +577 -0
- package/src/ai/model-delete.ts +234 -0
- package/src/ai/model-fixture.ts +39 -0
- package/src/ai/model-icon.tsx +36 -0
- package/src/ai/model-overview.ts +53 -0
- package/src/ai/model-usage.ts +45 -0
- package/src/ai/openai-api-messages.ts +0 -4
- package/src/ai/openai-api.ts +26 -9
- package/src/ai/personal-agent.ts +1 -1
- package/src/ai/prefs.ts +12 -10
- package/src/ai/project-tool.ts +4 -4
- package/src/ai/projects-authorization.ts +53 -0
- package/src/ai/projects-routes.ts +58 -47
- package/src/ai/projects.ts +110 -88
- package/src/ai/protocol.ts +2 -2
- package/src/ai/provider-connections.ts +161 -0
- package/src/ai/provider-errors.ts +55 -6
- package/src/ai/provider-fetch.ts +4 -0
- package/src/ai/provider.ts +1 -1
- package/src/ai/quota-limits.ts +36 -0
- package/src/ai/quota-provider.ts +4 -2
- package/src/ai/quota-report.ts +7 -4
- package/src/ai/quotas.ts +19 -8
- package/src/ai/recent-resources.ts +9 -3
- package/src/ai/request-headers.ts +14 -87
- package/src/ai/resource-refs.ts +6 -2
- package/src/ai/routes.ts +47 -48
- package/src/ai/runtime.ts +50 -22
- package/src/ai/settings.ts +93 -580
- package/src/ai/skill-tool.ts +4 -4
- package/src/ai/skills-routes.ts +27 -26
- package/src/ai/skills.ts +120 -81
- package/src/ai/store.ts +45 -23
- package/src/ai/stream.ts +1 -1
- package/src/ai/structured-runs.ts +2 -2
- package/src/ai/structured.ts +21 -44
- package/src/ai/task-contracts.ts +25 -5
- package/src/ai/task-execution.ts +15 -6
- package/src/ai/task-model.ts +9 -0
- package/src/ai/transcription.ts +27 -22
- package/src/ai/types.ts +50 -62
- package/src/ai/ui.tsx +13 -0
- package/src/ai/usage.ts +8 -8
- package/src/ai/validate.ts +8 -7
- package/src/ai/view-block.ts +2 -2
- package/src/api/accounts-entities.ts +22 -3
- package/src/api/admin-ai-quotas.ts +34 -56
- package/src/api/admin-ai-skills.ts +4 -1
- package/src/api/admin-ai-usage.ts +1 -1
- package/src/api/admin-ai.ts +173 -0
- package/src/api/admin-core-settings.ts +68 -244
- package/src/api/app-approval.ts +3 -1
- package/src/api/app-event-delivery.ts +51 -0
- package/src/api/app-events.ts +123 -0
- package/src/api/app-resource-access.ts +48 -0
- package/src/api/auth/account-mismatch.ts +24 -0
- package/src/api/auth.ts +35 -5
- package/src/api/capabilities.ts +28 -4
- package/src/api/index.ts +7 -1
- package/src/api/mcp.ts +175 -2
- package/src/api/me.ts +6 -4
- package/src/api/pwa-phone.ts +75 -26
- package/src/api/pwa.ts +34 -8
- package/src/api/resource-links.ts +83 -6
- package/src/api/resource-resolve.ts +50 -0
- package/src/api/search/schemas.ts +3 -1
- package/src/api/search.ts +9 -7
- package/src/app-pwa/contracts.ts +4 -0
- package/src/app-pwa/core-query-error.ts +20 -0
- package/src/app-pwa/index.ts +1 -0
- package/src/app-pwa/service.ts +91 -0
- package/src/app-pwa/transport.ts +22 -0
- package/src/approvals/messages.ts +2 -2
- package/src/browser/cloud-id.ts +86 -0
- package/src/browser/resource-clipboard.ts +4 -0
- package/src/browser/resource-links.ts +52 -0
- package/src/browser/session.ts +39 -0
- package/src/browser/shared-text-history-validation.ts +151 -0
- package/src/browser/shared-text-history.ts +464 -0
- package/src/browser/shared-text-offline-authorized.ts +29 -0
- package/src/browser/shared-text-offline.ts +351 -0
- package/src/browser/shared-text-provider.ts +714 -0
- package/src/browser/shared-text-table-contract.ts +150 -0
- package/src/browser/shared-text.ts +19 -0
- package/src/capabilities/server-invoke.ts +2 -11
- package/src/capabilities/testing.ts +5 -2
- package/src/cli/access.ts +6 -5
- package/src/cli/admin/ai-models.ts +199 -0
- package/src/cli/admin/ai-quotas.ts +6 -102
- package/src/cli/admin/ai-usage.ts +4 -4
- package/src/cli/admin/app-credentials.ts +5 -3
- package/src/cli/admin/index.ts +5 -2
- package/src/cli/apps.ts +89 -26
- package/src/cli/capabilities.ts +22 -1
- package/src/cli/events.ts +320 -0
- package/src/config/env.ts +40 -0
- package/src/contracts/app-access.ts +26 -2
- package/src/contracts/app-details.ts +89 -0
- package/src/contracts/app-events.ts +51 -0
- package/src/contracts/app.ts +21 -5
- package/src/contracts/capabilities.ts +143 -3
- package/src/contracts/index.ts +2 -0
- package/src/contracts/notification-types.ts +36 -9
- package/src/contracts/pwa.ts +17 -1
- package/src/contracts/registry.ts +6 -0
- package/src/contracts/resource-clipboard.ts +35 -0
- package/src/contracts/resource-pages.ts +140 -0
- package/src/contracts/resource-resolution.ts +13 -0
- package/src/contracts/shared.ts +6 -1
- package/src/contracts/user-notifications.ts +2 -0
- package/src/events/app-event-delivery.ts +100 -0
- package/src/events/app-events.testing.ts +65 -0
- package/src/events/app-events.ts +134 -0
- package/src/events/core-user-created.ts +24 -0
- package/src/events/index.ts +9 -0
- package/src/events/testing.ts +2 -0
- package/src/index.ts +1 -1
- package/src/server/api/respond.ts +17 -4
- package/src/server/index.ts +2 -0
- package/src/server/middleware/app-access.ts +1 -1
- package/src/server/middleware/auth.ts +21 -7
- package/src/server/middleware/index.ts +1 -0
- package/src/server/middleware/invocation.ts +2 -0
- package/src/server/public-storage.ts +106 -0
- package/src/server/services/access.ts +31 -10
- package/src/server/services/index.ts +1 -0
- package/src/services/accounts/app.ts +36 -13
- package/src/services/accounts/entities.ts +43 -19
- package/src/services/accounts/group-sql.ts +23 -0
- package/src/services/accounts/groups.ts +19 -14
- package/src/services/accounts/identities.ts +70 -11
- package/src/services/accounts/identity-reconciliation.ts +3 -1
- package/src/services/accounts/local-groups.ts +69 -4
- package/src/services/accounts/users.ts +45 -14
- package/src/services/app-access.ts +25 -7
- package/src/services/app-details.ts +224 -0
- package/src/services/app-discovery.ts +2 -1
- package/src/services/app-event-shapes.ts +139 -0
- package/src/services/app-events/accept.ts +116 -0
- package/src/services/app-events/admin.ts +250 -0
- package/src/services/app-events/declarations.ts +187 -0
- package/src/services/app-events/deliver.ts +218 -0
- package/src/services/app-events.ts +92 -0
- package/src/services/config-conflict.ts +11 -0
- package/src/services/core-user-events.ts +1 -0
- package/src/services/freeipa-config.ts +9 -0
- package/src/services/geo/ledger.ts +28 -10
- package/src/services/geo/service.ts +43 -24
- package/src/services/identity/core-origin.ts +12 -0
- package/src/services/identity/invocation-actor.ts +20 -2
- package/src/services/identity/invocation-authority.ts +4 -0
- package/src/services/identity/invocation-token.ts +238 -56
- package/src/services/identity/issue-invocation.ts +6 -1
- package/src/services/image-metadata.ts +65 -1
- package/src/services/index.ts +13 -1
- package/src/services/ipa/sync.ts +6 -2
- package/src/services/ipa/users.ts +4 -1
- package/src/services/mandates/index.ts +2 -2
- package/src/services/mandates/policy.ts +20 -12
- package/src/services/notifications/browser.ts +4 -0
- package/src/services/notifications/catalog.ts +5 -2
- package/src/services/notifications/platform.ts +7 -2
- package/src/services/notifications/user.ts +18 -7
- package/src/services/oauth-tokens.ts +28 -2
- package/src/services/object-store.ts +217 -0
- package/src/services/providers/local/users.ts +5 -0
- package/src/services/pwa-devices.ts +147 -37
- package/src/services/session/index.ts +26 -3
- package/src/services/settings/core-settings.ts +71 -43
- package/src/services/settings/index.ts +8 -0
- package/src/services/settings/versions.ts +26 -0
- package/src/shared/ai-models.ts +274 -0
- package/src/shared/ai-quotas.ts +2 -2
- package/src/shared/ai-usage.ts +2 -2
- package/src/shared/email-html.ts +1 -0
- package/src/shared/index.ts +2 -0
- package/src/shared/markdown/extensions/katex.ts +8 -1
- package/src/shared/markdown/extensions/tables.ts +14 -1
- package/src/shared/markdown/index.ts +22 -11
- package/src/shared-text/anchors.ts +29 -0
- package/src/shared-text/awareness.ts +87 -0
- package/src/shared-text/changes.ts +27 -0
- package/src/shared-text/documents.ts +433 -65
- package/src/shared-text/events.ts +12 -2
- package/src/shared-text/index.ts +16 -1
- package/src/shared-text/log.ts +24 -5
- package/src/shared-text/snapshots.ts +69 -23
- package/src/shared-text/structural-history.ts +377 -0
- package/src/shared-text/table-actions.ts +432 -0
- package/src/shared-text/text-diff.ts +75 -0
- package/src/shared-text/types.ts +24 -5
- package/src/ssr/AdminArea.tsx +131 -0
- package/src/ssr/AdminLayout.tsx +9 -3
- package/src/ssr/AdminSidebar.tsx +7 -7
- package/src/ssr/AppAccessDenied.tsx +2 -0
- package/src/ssr/AppBadge.tsx +14 -0
- package/src/ssr/AppLaunchpadPanel.tsx +2 -2
- package/src/ssr/CloudShell.island.tsx +44 -7
- package/src/ssr/Layout.tsx +9 -7
- package/src/ssr/MobileProfileActions.tsx +2 -2
- package/src/ssr/ProfilePreferences.tsx +2 -1
- package/src/ssr/PwaLayout.tsx +16 -1
- package/src/ssr/PwaPartCount.island.tsx +23 -0
- package/src/ssr/SettingsSaveOutcome.tsx +58 -0
- package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
- package/src/ssr/admin-active-link.ts +16 -0
- package/src/ssr/admin-navigation.ts +26 -31
- package/src/ssr/app-badges.ts +31 -7
- package/src/ssr/app-launchpad.tsx +6 -1
- package/src/ssr/index.ts +8 -0
- package/src/ssr/page-trail-runtime.ts +3 -1
- package/src/ssr/platform-messages.ts +11 -1
- package/src/ssr/profile-actions.ts +3 -6
- package/src/ssr/pwa-messages.ts +2 -2
- package/src/ssr/rail-navigation.ts +5 -1
- package/src/ssr/settings-draft.ts +153 -0
- package/src/ssr/shell-data.tsx +3 -1
- package/src/styles/global.css +1 -0
- package/src/styles/profile-picture.css +270 -0
- package/src/styles/utilities-navigation.css +6 -0
- package/src/workflows/ai/policy.ts +49 -0
- package/src/workflows/ai/runtime.ts +131 -21
- package/src/workflows/ai/store.ts +283 -35
- package/src/workflows/ai/types.ts +8 -2
- package/src/workflows/ai-actions.ts +148 -120
- package/src/workflows/authoring.ts +31 -0
- package/src/workflows/contracts.ts +1 -0
- package/src/workflows/definition.ts +2 -0
- package/src/workflows/editor.ts +15 -0
- package/src/workflows/index.ts +2 -1
- package/src/workflows/language/compiler.ts +34 -3
- package/src/workflows/language/expressions.ts +3 -2
- package/src/workflows/runtime/executor.ts +134 -34
- package/src/workflows/runtime/index.ts +2 -0
- package/src/workflows/runtime/ports.ts +5 -1
- package/src/workflows/store/actions.ts +1 -1
- package/src/workflows/store/definitions.ts +5 -0
- package/src/workflows/store/runs.ts +5 -3
- package/src/workflows/store/worker.ts +7 -2
- package/src/ai/model-pricing.ts +0 -43
- package/src/ai/model-request-settings.ts +0 -98
- package/src/ai/settings-messages.ts +0 -129
- package/src/shared/ai-model-request-settings.ts +0 -21
package/src/ai/projects.ts
CHANGED
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
import { type SQL, type SQLQuery, sql } from "bun";
|
|
2
2
|
import type { CloudResourceRef } from "../contracts/capabilities";
|
|
3
3
|
import { AuthenticatedPrincipalSchema, type ServiceAccountKind } from "../contracts/shared";
|
|
4
|
-
import type { AccessSubject } from "../server";
|
|
5
4
|
import {
|
|
6
5
|
buildAccessPrincipalCondition,
|
|
7
6
|
createAccess,
|
|
8
7
|
deleteAccess,
|
|
8
|
+
ensureManagerRemains,
|
|
9
9
|
hasPermission,
|
|
10
10
|
type PermissionLevel,
|
|
11
11
|
type Principal,
|
|
12
12
|
} from "../server/services/access";
|
|
13
13
|
import { stripImageMetadata } from "../services/image-metadata";
|
|
14
14
|
import { toPgUuidArray } from "../services/postgres";
|
|
15
|
+
import { type AiModelRequest, AiModelRequestSchema } from "../shared/ai-models";
|
|
15
16
|
import { AiFileVersionConflict, AiFileWriteError, aiFileContentVersion } from "./file-content-version";
|
|
16
17
|
import { isAiImage } from "./file-media-type";
|
|
17
18
|
import { mountAiProjectFilePath } from "./file-mount";
|
|
18
19
|
import { pickStoredAiFilePath } from "./files-store";
|
|
20
|
+
import {
|
|
21
|
+
type AiResourceContext,
|
|
22
|
+
aiResourceAccessSubject,
|
|
23
|
+
aiResourcePermissionCap,
|
|
24
|
+
capAiResourcePermission,
|
|
25
|
+
requireAiResourceCreation,
|
|
26
|
+
} from "./projects-authorization";
|
|
19
27
|
import { withAiShortIdForDb } from "./short-id";
|
|
20
28
|
import type { AiProjectPromptSnapshot } from "./types";
|
|
21
29
|
|
|
@@ -42,7 +50,7 @@ export type AiProject = {
|
|
|
42
50
|
description: string;
|
|
43
51
|
icon: string;
|
|
44
52
|
instructions: string;
|
|
45
|
-
|
|
53
|
+
defaultModel: AiModelRequest | null;
|
|
46
54
|
permission: AiProjectPermission;
|
|
47
55
|
revision: number;
|
|
48
56
|
createdAt: string;
|
|
@@ -85,7 +93,7 @@ export type AiProjectAccess = {
|
|
|
85
93
|
permission: AiProjectPermission;
|
|
86
94
|
displayName?: string;
|
|
87
95
|
avatarHash?: string | null;
|
|
88
|
-
/** Kind of a `service_account` principal
|
|
96
|
+
/** Kind of a `service_account` principal, including manager eligibility. */
|
|
89
97
|
serviceAccountKind?: ServiceAccountKind;
|
|
90
98
|
createdAt: string;
|
|
91
99
|
};
|
|
@@ -115,7 +123,7 @@ type ProjectRow = {
|
|
|
115
123
|
description: string;
|
|
116
124
|
icon: string;
|
|
117
125
|
instructions: string;
|
|
118
|
-
|
|
126
|
+
default_model: AiModelRequest | null;
|
|
119
127
|
revision: number;
|
|
120
128
|
created_at: Date | string;
|
|
121
129
|
updated_at: Date | string;
|
|
@@ -170,15 +178,21 @@ type AdminProjectRow = ProjectRow & {
|
|
|
170
178
|
};
|
|
171
179
|
|
|
172
180
|
const iso = (value: Date | string): string => (value instanceof Date ? value.toISOString() : new Date(value).toISOString());
|
|
173
|
-
const actorUserId = (
|
|
181
|
+
const actorUserId = (input: AiResourceContext | null): string | null => {
|
|
182
|
+
const subject = aiResourceAccessSubject(input);
|
|
183
|
+
return subject?.type === "user" ? subject.userId : null;
|
|
184
|
+
};
|
|
174
185
|
|
|
175
|
-
const principalForSubject = (
|
|
176
|
-
subject
|
|
186
|
+
const principalForSubject = (input: AiResourceContext): Principal => {
|
|
187
|
+
const subject = aiResourceAccessSubject(input)!;
|
|
188
|
+
return subject.type === "user"
|
|
177
189
|
? { type: "user", userId: subject.userId }
|
|
178
190
|
: { type: "service_account", serviceAccountId: subject.serviceAccountId };
|
|
191
|
+
};
|
|
179
192
|
|
|
180
|
-
const accessMatch = (
|
|
181
|
-
subject
|
|
193
|
+
const accessMatch = (input: AiResourceContext | null): SQLQuery => {
|
|
194
|
+
const subject = aiResourceAccessSubject(input);
|
|
195
|
+
return subject === null || aiResourcePermissionCap(input) === "none"
|
|
182
196
|
? sql`FALSE`
|
|
183
197
|
: buildAccessPrincipalCondition({
|
|
184
198
|
subject,
|
|
@@ -189,8 +203,9 @@ const accessMatch = (subject: AccessSubject | null): SQLQuery =>
|
|
|
189
203
|
authenticatedOnly: sql`access.authenticated_only`,
|
|
190
204
|
},
|
|
191
205
|
});
|
|
206
|
+
};
|
|
192
207
|
|
|
193
|
-
const permissionFor = async (row: ProjectRow, subject:
|
|
208
|
+
const permissionFor = async (row: ProjectRow, subject: AiResourceContext | null, db: SQL = sql): Promise<AiProjectPermission | "none"> => {
|
|
194
209
|
const match = accessMatch(subject);
|
|
195
210
|
const rows = await db<{ permission: AiProjectPermission }[]>`
|
|
196
211
|
SELECT access.permission
|
|
@@ -200,10 +215,10 @@ const permissionFor = async (row: ProjectRow, subject: AccessSubject | null, db:
|
|
|
200
215
|
ORDER BY CASE access.permission WHEN 'admin' THEN 3 WHEN 'write' THEN 2 WHEN 'read' THEN 1 ELSE 0 END DESC
|
|
201
216
|
LIMIT 1
|
|
202
217
|
`;
|
|
203
|
-
return rows[0]?.permission ?? "none";
|
|
218
|
+
return capAiResourcePermission(rows[0]?.permission ?? "none", subject);
|
|
204
219
|
};
|
|
205
220
|
|
|
206
|
-
const toProject = async (row: ProjectRow, subject:
|
|
221
|
+
const toProject = async (row: ProjectRow, subject: AiResourceContext | null, db: SQL = sql): Promise<AiProject | null> => {
|
|
207
222
|
const permission = await permissionFor(row, subject, db);
|
|
208
223
|
if (permission === "none") return null;
|
|
209
224
|
return {
|
|
@@ -213,7 +228,7 @@ const toProject = async (row: ProjectRow, subject: AccessSubject | null, db: SQL
|
|
|
213
228
|
description: row.description,
|
|
214
229
|
icon: row.icon,
|
|
215
230
|
instructions: row.instructions,
|
|
216
|
-
|
|
231
|
+
defaultModel: row.default_model ? AiModelRequestSchema.parse(row.default_model) : null,
|
|
217
232
|
permission,
|
|
218
233
|
revision: Number(row.revision),
|
|
219
234
|
createdAt: iso(row.created_at),
|
|
@@ -330,41 +345,34 @@ const createProjectAccess = async (
|
|
|
330
345
|
};
|
|
331
346
|
|
|
332
347
|
const updateProjectAccess = async (projectId: string, accessId: string, permission: AiProjectPermission, db: SQL): Promise<boolean> => {
|
|
333
|
-
const [bound] = await db<{ id: string
|
|
334
|
-
SELECT access.id
|
|
348
|
+
const [bound] = await db<{ id: string }[]>`
|
|
349
|
+
SELECT access.id
|
|
335
350
|
FROM ai.project_access project_access
|
|
336
351
|
JOIN auth.access access ON access.id = project_access.access_id
|
|
337
352
|
WHERE project_access.project_id = ${projectId}::uuid AND project_access.short_id = ${accessId}
|
|
338
353
|
`;
|
|
339
354
|
if (!bound) return false;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if ((admins?.count ?? 0) <= 1) throw new AiProjectLastAdminError();
|
|
347
|
-
}
|
|
355
|
+
const before = await listProjectAccess(projectId, db);
|
|
356
|
+
const guarded = ensureManagerRemains({
|
|
357
|
+
before,
|
|
358
|
+
after: before.map((entry) => (entry.shortId === accessId ? { ...entry, permission } : entry)),
|
|
359
|
+
});
|
|
360
|
+
if (!guarded.ok) throw new AiProjectLastAdminError();
|
|
348
361
|
const result = await db`UPDATE auth.access SET permission = ${permission}::auth.permission_level WHERE id = ${bound.id}::uuid`;
|
|
349
362
|
return result.count === 1;
|
|
350
363
|
};
|
|
351
364
|
|
|
352
365
|
const revokeProjectAccess = async (projectId: string, accessId: string, db: SQL): Promise<boolean> => {
|
|
353
|
-
const [bound] = await db<{ access_id: string
|
|
354
|
-
SELECT project_access.access_id
|
|
366
|
+
const [bound] = await db<{ access_id: string }[]>`
|
|
367
|
+
SELECT project_access.access_id
|
|
355
368
|
FROM ai.project_access project_access
|
|
356
369
|
JOIN auth.access access ON access.id = project_access.access_id
|
|
357
370
|
WHERE project_access.project_id = ${projectId}::uuid AND project_access.short_id = ${accessId}
|
|
358
371
|
`;
|
|
359
372
|
if (!bound) return false;
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
JOIN auth.access access ON access.id = project_access.access_id
|
|
364
|
-
WHERE project_access.project_id = ${projectId}::uuid AND access.permission = 'admin'
|
|
365
|
-
`;
|
|
366
|
-
if ((admins?.count ?? 0) <= 1) throw new AiProjectLastAdminError();
|
|
367
|
-
}
|
|
373
|
+
const before = await listProjectAccess(projectId, db);
|
|
374
|
+
const guarded = ensureManagerRemains({ before, after: before.filter((entry) => entry.shortId !== accessId) });
|
|
375
|
+
if (!guarded.ok) throw new AiProjectLastAdminError();
|
|
368
376
|
const deleted = await deleteAccess({ id: bound.access_id }, db);
|
|
369
377
|
if (!deleted.ok) throw new Error(deleted.error.message);
|
|
370
378
|
return true;
|
|
@@ -372,7 +380,7 @@ const revokeProjectAccess = async (projectId: string, accessId: string, db: SQL)
|
|
|
372
380
|
|
|
373
381
|
const requireProject = async (
|
|
374
382
|
projectId: string,
|
|
375
|
-
subject:
|
|
383
|
+
subject: AiResourceContext | null,
|
|
376
384
|
permission: AiProjectPermission = "read",
|
|
377
385
|
): Promise<AiProject | null> => {
|
|
378
386
|
const row = await getRow(projectId);
|
|
@@ -383,7 +391,7 @@ const requireProject = async (
|
|
|
383
391
|
|
|
384
392
|
const requireProjectByShortId = async (
|
|
385
393
|
shortId: string,
|
|
386
|
-
subject:
|
|
394
|
+
subject: AiResourceContext | null,
|
|
387
395
|
permission: AiProjectPermission = "read",
|
|
388
396
|
): Promise<AiProject | null> => {
|
|
389
397
|
const row = await getRowByShortId(shortId);
|
|
@@ -412,22 +420,23 @@ const projectSearchPattern = (value: string | undefined): string | null => {
|
|
|
412
420
|
|
|
413
421
|
export const aiProjects = {
|
|
414
422
|
async create(input: {
|
|
415
|
-
subject:
|
|
423
|
+
subject: AiResourceContext;
|
|
416
424
|
name: string;
|
|
417
425
|
description?: string;
|
|
418
426
|
icon?: string;
|
|
419
427
|
instructions?: string;
|
|
420
|
-
|
|
428
|
+
defaultModel?: AiModelRequest | null;
|
|
421
429
|
}): Promise<AiProject> {
|
|
430
|
+
requireAiResourceCreation(input.subject);
|
|
422
431
|
return sql.begin(async (tx) => {
|
|
423
432
|
const rows = await withAiShortIdForDb(
|
|
424
433
|
tx,
|
|
425
434
|
"idx_ai_projects_short_id",
|
|
426
435
|
(attempt, shortId) => attempt<ProjectRow[]>`
|
|
427
|
-
INSERT INTO ai.projects (short_id, name, description, icon, instructions,
|
|
436
|
+
INSERT INTO ai.projects (short_id, name, description, icon, instructions, default_model)
|
|
428
437
|
VALUES (
|
|
429
438
|
${shortId}, ${input.name.trim()}, ${input.description?.trim() ?? ""}, ${input.icon?.trim() || "ti ti-folders"},
|
|
430
|
-
${input.instructions?.trim() ?? ""}, ${input.
|
|
439
|
+
${input.instructions?.trim() ?? ""}, ${input.defaultModel ? JSON.stringify(AiModelRequestSchema.parse(input.defaultModel)) : null}::text::jsonb
|
|
431
440
|
)
|
|
432
441
|
RETURNING *
|
|
433
442
|
`,
|
|
@@ -442,11 +451,11 @@ export const aiProjects = {
|
|
|
442
451
|
VALUES (${rows[0]!.id}::uuid, ${created.data.id}::uuid, ${shortId})
|
|
443
452
|
`,
|
|
444
453
|
);
|
|
445
|
-
return
|
|
454
|
+
return (await toProject(rows[0]!, input.subject, tx))!;
|
|
446
455
|
});
|
|
447
456
|
},
|
|
448
457
|
|
|
449
|
-
async list(subject:
|
|
458
|
+
async list(subject: AiResourceContext | null): Promise<AiProject[]> {
|
|
450
459
|
const match = accessMatch(subject);
|
|
451
460
|
const rows = await sql<ProjectRow[]>`
|
|
452
461
|
SELECT DISTINCT project.*, lower(project.name) AS sort_name
|
|
@@ -459,7 +468,7 @@ export const aiProjects = {
|
|
|
459
468
|
return (await Promise.all(rows.map((row) => toProject(row, subject)))).filter((project): project is AiProject => Boolean(project));
|
|
460
469
|
},
|
|
461
470
|
|
|
462
|
-
async resolveShortIds(projectIds: readonly string[], subject:
|
|
471
|
+
async resolveShortIds(projectIds: readonly string[], subject: AiResourceContext | null): Promise<Map<string, string>> {
|
|
463
472
|
const ids = [...new Set(projectIds)];
|
|
464
473
|
if (!ids.length) return new Map();
|
|
465
474
|
const match = accessMatch(subject);
|
|
@@ -474,7 +483,7 @@ export const aiProjects = {
|
|
|
474
483
|
return new Map(rows.map((row) => [row.id, row.short_id]));
|
|
475
484
|
},
|
|
476
485
|
|
|
477
|
-
async scopeVersion(subject:
|
|
486
|
+
async scopeVersion(subject: AiResourceContext | null): Promise<string> {
|
|
478
487
|
const projects = await this.list(subject);
|
|
479
488
|
return projects
|
|
480
489
|
.map((project) => `${project.shortId}:${project.permission}`)
|
|
@@ -487,30 +496,34 @@ export const aiProjects = {
|
|
|
487
496
|
|
|
488
497
|
async update(
|
|
489
498
|
projectId: string,
|
|
490
|
-
subject:
|
|
491
|
-
input: Partial<Pick<AiProject, "name" | "description" | "icon" | "instructions" | "
|
|
499
|
+
subject: AiResourceContext | null,
|
|
500
|
+
input: Partial<Pick<AiProject, "name" | "description" | "icon" | "instructions" | "defaultModel">>,
|
|
492
501
|
): Promise<AiProject | null> {
|
|
493
|
-
const changesTrustedInstructions = Object.hasOwn(input, "instructions") || Object.hasOwn(input, "
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
502
|
+
const changesTrustedInstructions = Object.hasOwn(input, "instructions") || Object.hasOwn(input, "defaultModel");
|
|
503
|
+
return sql.begin(async (tx) => {
|
|
504
|
+
const [project] = await tx<ProjectRow[]>`SELECT * FROM ai.projects WHERE id = ${projectId}::uuid FOR UPDATE`;
|
|
505
|
+
if (!project || !hasPermission(await permissionFor(project, subject, tx), changesTrustedInstructions ? "admin" : "write"))
|
|
506
|
+
return null;
|
|
507
|
+
const rows = await tx<ProjectRow[]>`
|
|
508
|
+
UPDATE ai.projects
|
|
509
|
+
SET name = COALESCE(${input.name?.trim() ?? null}, name),
|
|
510
|
+
description = COALESCE(${input.description?.trim() ?? null}, description),
|
|
511
|
+
icon = COALESCE(${input.icon?.trim() ?? null}, icon),
|
|
512
|
+
instructions = COALESCE(${input.instructions?.trim() ?? null}, instructions),
|
|
513
|
+
default_model = CASE
|
|
514
|
+
WHEN ${Object.hasOwn(input, "defaultModel")} THEN ${input.defaultModel ? JSON.stringify(AiModelRequestSchema.parse(input.defaultModel)) : null}::text::jsonb
|
|
515
|
+
ELSE default_model
|
|
516
|
+
END,
|
|
517
|
+
revision = revision + 1,
|
|
518
|
+
updated_at = now()
|
|
519
|
+
WHERE id = ${projectId}::uuid
|
|
520
|
+
RETURNING *
|
|
521
|
+
`;
|
|
522
|
+
return rows[0] ? toProject(rows[0], subject, tx) : null;
|
|
523
|
+
});
|
|
511
524
|
},
|
|
512
525
|
|
|
513
|
-
async delete(projectId: string, subject:
|
|
526
|
+
async delete(projectId: string, subject: AiResourceContext | null): Promise<boolean> {
|
|
514
527
|
return sql.begin(async (tx) => {
|
|
515
528
|
const [project] = await tx<ProjectRow[]>`
|
|
516
529
|
SELECT * FROM ai.projects WHERE id = ${projectId}::uuid FOR UPDATE
|
|
@@ -525,7 +538,7 @@ export const aiProjects = {
|
|
|
525
538
|
});
|
|
526
539
|
},
|
|
527
540
|
|
|
528
|
-
async snapshot(projectId: string, subject:
|
|
541
|
+
async snapshot(projectId: string, subject: AiResourceContext | null): Promise<AiProjectPromptSnapshot | null> {
|
|
529
542
|
return sql.begin(async (tx) => {
|
|
530
543
|
const [row] = await tx<ProjectRow[]>`SELECT * FROM ai.projects WHERE id = ${projectId}::uuid FOR SHARE`;
|
|
531
544
|
if (!row || !hasPermission(await permissionFor(row, subject, tx), "read")) return null;
|
|
@@ -563,19 +576,19 @@ export const aiProjects = {
|
|
|
563
576
|
instructions: project.instructions,
|
|
564
577
|
context: lines.join("\n").slice(0, AI_PROJECT_PROMPT_MANIFEST_MAX_CHARS),
|
|
565
578
|
references: references.map((reference) => reference.ref),
|
|
566
|
-
|
|
579
|
+
defaultModel: project.defaultModel,
|
|
567
580
|
};
|
|
568
581
|
});
|
|
569
582
|
},
|
|
570
583
|
|
|
571
|
-
async listAccess(projectId: string, subject:
|
|
584
|
+
async listAccess(projectId: string, subject: AiResourceContext | null): Promise<AiProjectAccess[] | null> {
|
|
572
585
|
if (!(await requireProject(projectId, subject, "admin"))) return null;
|
|
573
586
|
return listProjectAccess(projectId);
|
|
574
587
|
},
|
|
575
588
|
|
|
576
589
|
async grantAccess(
|
|
577
590
|
projectId: string,
|
|
578
|
-
subject:
|
|
591
|
+
subject: AiResourceContext | null,
|
|
579
592
|
input: { principal: Principal; permission: AiProjectPermission },
|
|
580
593
|
): Promise<AiProjectAccess | null> {
|
|
581
594
|
return sql.begin(async (tx) => {
|
|
@@ -590,7 +603,7 @@ export const aiProjects = {
|
|
|
590
603
|
async updateAccess(
|
|
591
604
|
projectId: string,
|
|
592
605
|
accessId: string,
|
|
593
|
-
subject:
|
|
606
|
+
subject: AiResourceContext | null,
|
|
594
607
|
permission: AiProjectPermission,
|
|
595
608
|
): Promise<boolean> {
|
|
596
609
|
return sql.begin(async (tx) => {
|
|
@@ -602,7 +615,7 @@ export const aiProjects = {
|
|
|
602
615
|
});
|
|
603
616
|
},
|
|
604
617
|
|
|
605
|
-
async revokeAccess(projectId: string, accessId: string, subject:
|
|
618
|
+
async revokeAccess(projectId: string, accessId: string, subject: AiResourceContext | null): Promise<boolean> {
|
|
606
619
|
return sql.begin(async (tx) => {
|
|
607
620
|
const [project] = await tx<ProjectRow[]>`
|
|
608
621
|
SELECT * FROM ai.projects WHERE id = ${projectId}::uuid FOR UPDATE
|
|
@@ -726,7 +739,7 @@ export const aiProjects = {
|
|
|
726
739
|
},
|
|
727
740
|
},
|
|
728
741
|
|
|
729
|
-
async listKnowledge(projectId: string, subject:
|
|
742
|
+
async listKnowledge(projectId: string, subject: AiResourceContext | null, query?: string): Promise<AiProjectKnowledge[]> {
|
|
730
743
|
if (!(await requireProject(projectId, subject, "read"))) return [];
|
|
731
744
|
const q = query?.trim() || null;
|
|
732
745
|
const pattern = q ? `%${q.toLowerCase().replace(/[\\%_]/g, (char) => `\\${char}`)}%` : null;
|
|
@@ -741,7 +754,7 @@ export const aiProjects = {
|
|
|
741
754
|
return rows.map(toKnowledge);
|
|
742
755
|
},
|
|
743
756
|
|
|
744
|
-
async getKnowledgeByShortId(projectId: string, shortId: string, subject:
|
|
757
|
+
async getKnowledgeByShortId(projectId: string, shortId: string, subject: AiResourceContext | null): Promise<AiProjectKnowledge | null> {
|
|
745
758
|
if (!(await requireProject(projectId, subject, "read"))) return null;
|
|
746
759
|
const rows = await sql<KnowledgeRow[]>`
|
|
747
760
|
SELECT * FROM ai.project_knowledge
|
|
@@ -753,11 +766,12 @@ export const aiProjects = {
|
|
|
753
766
|
|
|
754
767
|
async createKnowledge(
|
|
755
768
|
projectId: string,
|
|
756
|
-
subject:
|
|
769
|
+
subject: AiResourceContext | null,
|
|
757
770
|
input: { title: string; content: string },
|
|
758
771
|
): Promise<AiProjectKnowledge | null> {
|
|
759
|
-
if (!(await requireProject(projectId, subject, "write"))) return null;
|
|
760
772
|
return sql.begin(async (tx) => {
|
|
773
|
+
const [project] = await tx<ProjectRow[]>`SELECT * FROM ai.projects WHERE id = ${projectId}::uuid FOR UPDATE`;
|
|
774
|
+
if (!project || !hasPermission(await permissionFor(project, subject, tx), "write")) return null;
|
|
761
775
|
const rows = await withAiShortIdForDb(
|
|
762
776
|
tx,
|
|
763
777
|
"idx_ai_project_knowledge_short_id",
|
|
@@ -775,11 +789,12 @@ export const aiProjects = {
|
|
|
775
789
|
async updateKnowledge(
|
|
776
790
|
projectId: string,
|
|
777
791
|
knowledgeId: string,
|
|
778
|
-
subject:
|
|
792
|
+
subject: AiResourceContext | null,
|
|
779
793
|
input: { title?: string; content?: string },
|
|
780
794
|
): Promise<AiProjectKnowledge | null> {
|
|
781
|
-
if (!(await requireProject(projectId, subject, "write"))) return null;
|
|
782
795
|
return sql.begin(async (tx) => {
|
|
796
|
+
const [project] = await tx<ProjectRow[]>`SELECT * FROM ai.projects WHERE id = ${projectId}::uuid FOR UPDATE`;
|
|
797
|
+
if (!project || !hasPermission(await permissionFor(project, subject, tx), "write")) return null;
|
|
783
798
|
const rows = await tx<KnowledgeRow[]>`
|
|
784
799
|
UPDATE ai.project_knowledge
|
|
785
800
|
SET title = COALESCE(${input.title?.trim() ?? null}, title), content = COALESCE(${input.content?.trim() ?? null}, content), updated_at = now()
|
|
@@ -791,9 +806,10 @@ export const aiProjects = {
|
|
|
791
806
|
});
|
|
792
807
|
},
|
|
793
808
|
|
|
794
|
-
async deleteKnowledge(projectId: string, knowledgeId: string, subject:
|
|
795
|
-
if (!(await requireProject(projectId, subject, "write"))) return false;
|
|
809
|
+
async deleteKnowledge(projectId: string, knowledgeId: string, subject: AiResourceContext | null): Promise<boolean> {
|
|
796
810
|
return sql.begin(async (tx) => {
|
|
811
|
+
const [project] = await tx<ProjectRow[]>`SELECT * FROM ai.projects WHERE id = ${projectId}::uuid FOR UPDATE`;
|
|
812
|
+
if (!project || !hasPermission(await permissionFor(project, subject, tx), "write")) return false;
|
|
797
813
|
const rows = await tx<{ id: string }[]>`
|
|
798
814
|
DELETE FROM ai.project_knowledge WHERE short_id = ${knowledgeId} AND project_id = ${projectId}::uuid RETURNING id
|
|
799
815
|
`;
|
|
@@ -802,7 +818,11 @@ export const aiProjects = {
|
|
|
802
818
|
});
|
|
803
819
|
},
|
|
804
820
|
|
|
805
|
-
async listFiles(
|
|
821
|
+
async listFiles(
|
|
822
|
+
projectId: string,
|
|
823
|
+
subject: AiResourceContext | null,
|
|
824
|
+
page?: { after?: string; limit: number },
|
|
825
|
+
): Promise<AiProjectFile[]> {
|
|
806
826
|
if (!(await requireProject(projectId, subject, "read"))) return [];
|
|
807
827
|
if (page && (!Number.isInteger(page.limit) || page.limit < 1 || page.limit > 1000)) throw new Error("Invalid file page limit");
|
|
808
828
|
return (
|
|
@@ -814,7 +834,7 @@ export const aiProjects = {
|
|
|
814
834
|
|
|
815
835
|
async writeFile(
|
|
816
836
|
projectId: string,
|
|
817
|
-
subject:
|
|
837
|
+
subject: AiResourceContext | null,
|
|
818
838
|
input: { path: string; mediaType: string; bytes: Uint8Array; expectedVersion?: string | null },
|
|
819
839
|
): Promise<AiProjectFile | null> {
|
|
820
840
|
if (!(await requireProject(projectId, subject, "write"))) return null;
|
|
@@ -853,7 +873,7 @@ export const aiProjects = {
|
|
|
853
873
|
async readFile(
|
|
854
874
|
projectId: string,
|
|
855
875
|
fileId: string,
|
|
856
|
-
subject:
|
|
876
|
+
subject: AiResourceContext | null,
|
|
857
877
|
): Promise<(AiProjectFile & { bytes: Uint8Array }) | null> {
|
|
858
878
|
if (!(await requireProject(projectId, subject, "read"))) return null;
|
|
859
879
|
const rows = await sql<(FileRow & { bytes: Uint8Array })[]>`
|
|
@@ -866,7 +886,7 @@ export const aiProjects = {
|
|
|
866
886
|
async readFileByPath(
|
|
867
887
|
projectId: string,
|
|
868
888
|
path: string,
|
|
869
|
-
subject:
|
|
889
|
+
subject: AiResourceContext | null,
|
|
870
890
|
): Promise<(AiProjectFile & { bytes: Uint8Array }) | null> {
|
|
871
891
|
if (!(await requireProject(projectId, subject, "read"))) return null;
|
|
872
892
|
const normalized = normalizeProjectPath(path);
|
|
@@ -883,7 +903,7 @@ export const aiProjects = {
|
|
|
883
903
|
return rows[0] ? { ...toFile(rows[0]), bytes: rows[0].bytes } : null;
|
|
884
904
|
},
|
|
885
905
|
|
|
886
|
-
async deleteFile(projectId: string, fileId: string, subject:
|
|
906
|
+
async deleteFile(projectId: string, fileId: string, subject: AiResourceContext | null): Promise<boolean> {
|
|
887
907
|
if (!(await requireProject(projectId, subject, "write"))) return false;
|
|
888
908
|
return sql.begin(async (tx) => {
|
|
889
909
|
const [row] = await tx<ProjectRow[]>`SELECT * FROM ai.projects WHERE id=${projectId}::uuid FOR UPDATE`;
|
|
@@ -897,7 +917,7 @@ export const aiProjects = {
|
|
|
897
917
|
});
|
|
898
918
|
},
|
|
899
919
|
|
|
900
|
-
async listReferences(projectId: string, subject:
|
|
920
|
+
async listReferences(projectId: string, subject: AiResourceContext | null): Promise<AiProjectReference[]> {
|
|
901
921
|
if (!(await requireProject(projectId, subject, "read"))) return [];
|
|
902
922
|
return (
|
|
903
923
|
await sql<
|
|
@@ -908,11 +928,12 @@ export const aiProjects = {
|
|
|
908
928
|
|
|
909
929
|
async createReference(
|
|
910
930
|
projectId: string,
|
|
911
|
-
subject:
|
|
931
|
+
subject: AiResourceContext | null,
|
|
912
932
|
input: { ref: CloudResourceRef; label?: string },
|
|
913
933
|
): Promise<AiProjectReference | null> {
|
|
914
|
-
if (!(await requireProject(projectId, subject, "write"))) return null;
|
|
915
934
|
return sql.begin(async (tx) => {
|
|
935
|
+
const [project] = await tx<ProjectRow[]>`SELECT * FROM ai.projects WHERE id = ${projectId}::uuid FOR UPDATE`;
|
|
936
|
+
if (!project || !hasPermission(await permissionFor(project, subject, tx), "write")) return null;
|
|
916
937
|
const rows = await withAiShortIdForDb(
|
|
917
938
|
tx,
|
|
918
939
|
"idx_ai_project_resource_refs_short_id",
|
|
@@ -927,9 +948,10 @@ export const aiProjects = {
|
|
|
927
948
|
});
|
|
928
949
|
},
|
|
929
950
|
|
|
930
|
-
async deleteReference(projectId: string, referenceId: string, subject:
|
|
931
|
-
if (!(await requireProject(projectId, subject, "write"))) return false;
|
|
951
|
+
async deleteReference(projectId: string, referenceId: string, subject: AiResourceContext | null): Promise<boolean> {
|
|
932
952
|
return sql.begin(async (tx) => {
|
|
953
|
+
const [project] = await tx<ProjectRow[]>`SELECT * FROM ai.projects WHERE id = ${projectId}::uuid FOR UPDATE`;
|
|
954
|
+
if (!project || !hasPermission(await permissionFor(project, subject, tx), "write")) return false;
|
|
933
955
|
const rows = await tx<{ id: string }[]>`
|
|
934
956
|
DELETE FROM ai.project_resource_refs WHERE short_id = ${referenceId} AND project_id = ${projectId}::uuid RETURNING id
|
|
935
957
|
`;
|
package/src/ai/protocol.ts
CHANGED
|
@@ -90,7 +90,7 @@ export type AiTurnFinishedStatus = "completed" | "failed" | "aborted";
|
|
|
90
90
|
export type AiWireEvent =
|
|
91
91
|
| (AiWireEventBase & {
|
|
92
92
|
type: "turn_started";
|
|
93
|
-
|
|
93
|
+
modelId: string;
|
|
94
94
|
providerModel: string;
|
|
95
95
|
/** Authoritative ordered baseline for this attempt. Optional for older senders. */
|
|
96
96
|
blocks?: AiTurnBlock[];
|
|
@@ -116,7 +116,7 @@ export type AiTurnSnapshot = {
|
|
|
116
116
|
/** Last wire seq reflected in `blocks` (attempt-scoped). */
|
|
117
117
|
seq: number;
|
|
118
118
|
blocks: AiTurnBlock[];
|
|
119
|
-
|
|
119
|
+
modelId: string | null;
|
|
120
120
|
createdAt: string;
|
|
121
121
|
/** Time the turn already waited for answered user actions such as approvals. Absent from older servers. */
|
|
122
122
|
actionWaitMs?: number;
|