@xfey/tutti 0.1.105 → 0.1.106
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 +2 -2
- package/dist/approvals/decision-command.d.ts +124 -0
- package/dist/approvals/decision-command.js +20 -0
- package/dist/approvals/index.d.ts +1 -0
- package/dist/approvals/index.js +1 -0
- package/dist/approvals/manager.d.ts +3 -1
- package/dist/approvals/manager.js +9 -0
- package/dist/collaboration-ingestion/index.d.ts +3 -0
- package/dist/collaboration-ingestion/index.js +3 -0
- package/dist/collaboration-ingestion/managed-reference-snapshots.d.ts +27 -0
- package/dist/collaboration-ingestion/managed-reference-snapshots.js +45 -0
- package/dist/collaboration-ingestion/managed-reference-sources.d.ts +20 -0
- package/dist/collaboration-ingestion/managed-reference-sources.js +250 -0
- package/dist/collaboration-ingestion/reference-attachments.d.ts +22 -0
- package/dist/collaboration-ingestion/reference-attachments.js +132 -0
- package/dist/collaboration-ingestion/reference-files.d.ts +3 -2
- package/dist/collaboration-ingestion/reference-files.js +1 -0
- package/dist/platform-outbound/coordinator.d.ts +5 -2
- package/dist/platform-outbound/coordinator.js +95 -2
- package/dist/platform-outbound/repository.d.ts +17 -0
- package/dist/platform-outbound/repository.js +190 -5
- package/dist/platform-outbound/types.d.ts +13 -3
- package/dist/platform-outbound/worker.js +30 -14
- package/dist/server-shell/cli/host-lifecycle.js +3 -0
- package/dist/server-shell/cli/host-server-runtime.d.ts +4 -1
- package/dist/server-shell/cli/host-server-runtime.js +128 -40
- package/dist/server-shell/cli/relay-registration.js +3 -0
- package/dist/server-shell/http/host-tunnel.d.ts +4 -1
- package/dist/server-shell/http/host-tunnel.js +174 -3
- package/dist/server-shell/http/routes/project-api/approval-routes.js +13 -20
- package/dist/server-shell/http/routes/project-api/staged-uploads.d.ts +6 -0
- package/dist/server-shell/http/routes/project-api/staged-uploads.js +9 -2
- package/dist/workspace-ops/index.d.ts +2 -2
- package/dist/workspace-ops/index.js +1 -1
- package/dist/workspace-ops/reference-files.d.ts +14 -1
- package/dist/workspace-ops/reference-files.js +167 -2
- package/dist/workspace-ops/types.d.ts +24 -0
- package/migrations/0018_platform_outbound_events.sql +111 -0
- package/migrations/0019_platform_outbound_card_content.sql +76 -0
- package/migrations/0020_managed_reference_sources.sql +34 -0
- package/migrations/README.md +4 -1
- package/node_modules/@tutti/relay-client/dist/host-control.d.ts +5 -2
- package/node_modules/@tutti/relay-client/dist/host-control.js +3 -0
- package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +2 -2
- package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/platform-sessions.d.ts +34 -0
- package/node_modules/@tutti/shared/dist/schemas/api/platform-sessions.js +37 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/feishu-user-credentials.d.ts +22 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/feishu-user-credentials.js +37 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/index.d.ts +1 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/index.js +1 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/platform-integration.d.ts +1005 -49
- package/node_modules/@tutti/shared/dist/schemas/internal/platform-integration.js +434 -14
- package/package.json +1 -1
- package/prompts/skills/read-references/README.md +1 -1
- package/prompts/skills/read-references/SKILL.md +4 -2
- package/web/assets/{homepage-motion-scene-BnRTgItL.js → homepage-motion-scene-tfFEdV9Y.js} +1 -1
- package/web/assets/index-B7ZGIvOv.js +69 -0
- package/web/assets/{index-CTMOjCgU.css → index-C2Mmj25S.css} +1 -1
- package/web/index.html +2 -2
- package/web/assets/index-CmzEr4-V.js +0 -69
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FastifyInstance } from "fastify";
|
|
2
2
|
import { type ExecutionStatusProjection } from "@tutti/shared/schemas/api";
|
|
3
|
-
import type { IntegrationDeliveryDisposition, TrustedConversationSourceIngestionCommand } from "@tutti/shared/schemas/internal";
|
|
3
|
+
import type { IntegrationApprovalDecisionDisposition, IntegrationDeliveryDisposition, IntegrationManagedReferenceDisposition, TrustedApprovalDecisionCommand, TrustedConversationSourceIngestionCommand, TrustedManagedReferenceIngestionCommand, TrustedReferenceAttachmentIngestionCommand } from "@tutti/shared/schemas/internal";
|
|
4
4
|
import { type HostProjectStore } from "../../store/index.js";
|
|
5
5
|
import type { createHostServer } from "../http/create-server.js";
|
|
6
6
|
import type { HostLocalLaunchStatus } from "../http/routes/local-control.js";
|
|
@@ -19,6 +19,9 @@ export type HostServerHandle = {
|
|
|
19
19
|
runtime_endpoint: MachineRuntimeEndpointRecord;
|
|
20
20
|
trusted_relay_metadata_store: TrustedRelaySessionMetadataStore;
|
|
21
21
|
integration_delivery: (command: TrustedConversationSourceIngestionCommand) => IntegrationDeliveryDisposition;
|
|
22
|
+
integration_approval_decision: (command: TrustedApprovalDecisionCommand) => IntegrationApprovalDecisionDisposition;
|
|
23
|
+
integration_reference_attachment: (command: TrustedReferenceAttachmentIngestionCommand) => Promise<IntegrationDeliveryDisposition>;
|
|
24
|
+
integration_managed_reference: (command: TrustedManagedReferenceIngestionCommand) => Promise<IntegrationManagedReferenceDisposition>;
|
|
22
25
|
attachRelayConnectionManager: (manager: HostRelayConnectionManager) => void;
|
|
23
26
|
closed: Promise<void>;
|
|
24
27
|
close: () => Promise<void>;
|
|
@@ -2,11 +2,11 @@ import { basename, join } from "node:path";
|
|
|
2
2
|
import { MAX_PROVIDER_MODEL_NAME_LENGTH, } from "@tutti/shared/schemas/api";
|
|
3
3
|
import { serializeRuntimeLogLine } from "@tutti/shared/utils";
|
|
4
4
|
import { completeRelayUpload, deliverRelayPlatformOutbound, RelayHostControlClientError, resolveRelayUpload, } from "@tutti/relay-client";
|
|
5
|
-
import { ApprovalRequestManager } from "../../approvals/index.js";
|
|
5
|
+
import { ApprovalRequestManager, executeApprovalDecisionCommand } from "../../approvals/index.js";
|
|
6
6
|
import { ArtifactPreviewManager } from "../../artifacts/index.js";
|
|
7
7
|
import { ReadOnlyChatAssistant } from "../../chat-assistant/index.js";
|
|
8
8
|
import { CheckProcessCoordinator } from "../../checks/index.js";
|
|
9
|
-
import { ingestTrustedConversationSource } from "../../collaboration-ingestion/index.js";
|
|
9
|
+
import { ingestTrustedConversationSource, ingestTrustedManagedReference, ingestTrustedReferenceAttachment, } from "../../collaboration-ingestion/index.js";
|
|
10
10
|
import { ensureInitialProjectWelcomeMessage } from "../../collaboration-state/index.js";
|
|
11
11
|
import { Phase5ControlPlane } from "../../control-plane/index.js";
|
|
12
12
|
import { resolveProjectOpenAiProcedureWorkflowRunner } from "../../control-plane/workflows/index.js";
|
|
@@ -21,6 +21,7 @@ import { openHostProjectStore } from "../../store/index.js";
|
|
|
21
21
|
import { resolvePackagedWebStaticRoot } from "../http/static-web.js";
|
|
22
22
|
import { AGENT_CONTEXT_BASE_PATH, AgentContextTokenRegistry, } from "../http/routes/agent-context.js";
|
|
23
23
|
import { readHostRepoProjection } from "../http/routes/project-api.js";
|
|
24
|
+
import { abortDownloadedStagedUpload, cleanupDownloadedStagedUpload, downloadStagedUploadWithResolver, } from "../http/routes/project-api/staged-uploads.js";
|
|
24
25
|
import { WorkspaceEventBus } from "../http/workspace-events.js";
|
|
25
26
|
import { createTrustedRelaySessionMetadataStore, } from "../session/relay-session-context.js";
|
|
26
27
|
import { LaunchError } from "./errors.js";
|
|
@@ -308,9 +309,60 @@ export async function startForegroundHostServer(options) {
|
|
|
308
309
|
},
|
|
309
310
|
});
|
|
310
311
|
controlPlaneLogger.info(staleTempCleanup, "Codex app-server stale temp cleanup completed");
|
|
312
|
+
const platformOutboundWorker = options.preparation.machine_local.binding.surface_profile.kind === "feishu"
|
|
313
|
+
? new HostPlatformOutboundWorker(store, {
|
|
314
|
+
deliver: async (item) => {
|
|
315
|
+
const connectionState = relayConnectionManager?.getState();
|
|
316
|
+
if (connectionState?.status !== "connected") {
|
|
317
|
+
throw new RelayHostControlClientError("relay_unavailable", "Relay is not connected for platform outbound handoff", true);
|
|
318
|
+
}
|
|
319
|
+
const hostSecret = readHostRegistrationSecret(options.preparation.tutti_home, options.preparation.project_id);
|
|
320
|
+
const relayProjectRef = connectionState.connection.relay_project_ref;
|
|
321
|
+
return await deliverRelayPlatformOutbound({
|
|
322
|
+
relayUrl: options.preparation.relay_url,
|
|
323
|
+
projectId: options.preparation.project_id,
|
|
324
|
+
relayProjectRef,
|
|
325
|
+
hostConnectionRef: connectionState.connection.host_connection_ref,
|
|
326
|
+
hostRegistrationSecret: hostSecret.secret,
|
|
327
|
+
intent: {
|
|
328
|
+
protocol_version: 1,
|
|
329
|
+
intent_ref: item.intent.intent_ref,
|
|
330
|
+
relay_project_ref: relayProjectRef,
|
|
331
|
+
installation: {
|
|
332
|
+
platform: item.intent.platform,
|
|
333
|
+
installation_ref: item.intent.installation_ref,
|
|
334
|
+
},
|
|
335
|
+
binding_ref: item.intent.binding_ref,
|
|
336
|
+
...(item.intent.canonical_message_id === null
|
|
337
|
+
? {}
|
|
338
|
+
: { canonical_message_ref: item.intent.canonical_message_id }),
|
|
339
|
+
...(item.intent.trigger_message_id === null
|
|
340
|
+
? {}
|
|
341
|
+
: { trigger_message_ref: item.intent.trigger_message_id }),
|
|
342
|
+
content: item.content,
|
|
343
|
+
event: {
|
|
344
|
+
kind: item.intent.event_kind,
|
|
345
|
+
event_key: item.intent.event_key,
|
|
346
|
+
subject: {
|
|
347
|
+
kind: item.intent.subject_kind,
|
|
348
|
+
ref: item.intent.subject_ref,
|
|
349
|
+
},
|
|
350
|
+
target: { kind: item.intent.target_kind },
|
|
351
|
+
},
|
|
352
|
+
created_at: item.intent.created_at,
|
|
353
|
+
attempt: item.intent.attempt_count,
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
},
|
|
357
|
+
}, platformOutboundLogger, { now })
|
|
358
|
+
: undefined;
|
|
359
|
+
const platformOutboundCoordinator = platformOutboundWorker === undefined
|
|
360
|
+
? undefined
|
|
361
|
+
: new HostPlatformOutboundCoordinator(store, platformOutboundWorker, platformOutboundLogger, now);
|
|
311
362
|
const approvalManager = new ApprovalRequestManager({
|
|
312
363
|
events: workspaceEvents,
|
|
313
364
|
now,
|
|
365
|
+
onPending: (request) => platformOutboundCoordinator?.captureApprovalRequest(request),
|
|
314
366
|
});
|
|
315
367
|
const codexInvocationCoordinator = new CodexAppServerInvocationCoordinator();
|
|
316
368
|
const checkProcessCoordinator = new CheckProcessCoordinator();
|
|
@@ -364,43 +416,6 @@ export async function startForegroundHostServer(options) {
|
|
|
364
416
|
onLifecycleIncident: procedureLifecycleIncident,
|
|
365
417
|
}),
|
|
366
418
|
});
|
|
367
|
-
const platformOutboundWorker = options.preparation.machine_local.binding.surface_profile.kind === "feishu"
|
|
368
|
-
? new HostPlatformOutboundWorker(store, {
|
|
369
|
-
deliver: async (item) => {
|
|
370
|
-
const connectionState = relayConnectionManager?.getState();
|
|
371
|
-
if (connectionState?.status !== "connected") {
|
|
372
|
-
throw new RelayHostControlClientError("relay_unavailable", "Relay is not connected for platform outbound handoff", true);
|
|
373
|
-
}
|
|
374
|
-
const hostSecret = readHostRegistrationSecret(options.preparation.tutti_home, options.preparation.project_id);
|
|
375
|
-
const relayProjectRef = connectionState.connection.relay_project_ref;
|
|
376
|
-
return await deliverRelayPlatformOutbound({
|
|
377
|
-
relayUrl: options.preparation.relay_url,
|
|
378
|
-
projectId: options.preparation.project_id,
|
|
379
|
-
relayProjectRef,
|
|
380
|
-
hostConnectionRef: connectionState.connection.host_connection_ref,
|
|
381
|
-
hostRegistrationSecret: hostSecret.secret,
|
|
382
|
-
intent: {
|
|
383
|
-
protocol_version: 1,
|
|
384
|
-
intent_ref: item.intent.intent_ref,
|
|
385
|
-
relay_project_ref: relayProjectRef,
|
|
386
|
-
installation: {
|
|
387
|
-
platform: item.intent.platform,
|
|
388
|
-
installation_ref: item.intent.installation_ref,
|
|
389
|
-
},
|
|
390
|
-
binding_ref: item.intent.binding_ref,
|
|
391
|
-
canonical_message_ref: item.intent.canonical_message_id,
|
|
392
|
-
trigger_message_ref: item.intent.trigger_message_id,
|
|
393
|
-
content: { kind: "text", text: item.body },
|
|
394
|
-
created_at: item.intent.created_at,
|
|
395
|
-
attempt: item.intent.attempt_count,
|
|
396
|
-
},
|
|
397
|
-
});
|
|
398
|
-
},
|
|
399
|
-
}, platformOutboundLogger, { now })
|
|
400
|
-
: undefined;
|
|
401
|
-
const platformOutboundCoordinator = platformOutboundWorker === undefined
|
|
402
|
-
? undefined
|
|
403
|
-
: new HostPlatformOutboundCoordinator(platformOutboundWorker, platformOutboundLogger, now);
|
|
404
419
|
const chatAssistant = new ReadOnlyChatAssistant({
|
|
405
420
|
project: chatAssistantProject,
|
|
406
421
|
store,
|
|
@@ -508,9 +523,10 @@ export async function startForegroundHostServer(options) {
|
|
|
508
523
|
...(options.getLaunchStatus === undefined ? {} : { getLaunchStatus: options.getLaunchStatus }),
|
|
509
524
|
};
|
|
510
525
|
let app;
|
|
526
|
+
let resolveStagedUpload;
|
|
511
527
|
try {
|
|
512
528
|
const webStaticRoot = resolvePackagedWebStaticRoot();
|
|
513
|
-
|
|
529
|
+
resolveStagedUpload = createStagedUploadResolver({
|
|
514
530
|
preparation: options.preparation,
|
|
515
531
|
...(options.getLaunchStatus === undefined
|
|
516
532
|
? {}
|
|
@@ -688,6 +704,78 @@ export async function startForegroundHostServer(options) {
|
|
|
688
704
|
chatAssistant,
|
|
689
705
|
now,
|
|
690
706
|
}),
|
|
707
|
+
integration_reference_attachment: async (command) => await ingestTrustedReferenceAttachment({
|
|
708
|
+
store,
|
|
709
|
+
workspaceRoot: options.preparation.workspace_root,
|
|
710
|
+
events: workspaceEvents,
|
|
711
|
+
command,
|
|
712
|
+
resolveContent: async () => {
|
|
713
|
+
if (resolveStagedUpload === undefined) {
|
|
714
|
+
throw new Error("Staged upload resolver is unavailable");
|
|
715
|
+
}
|
|
716
|
+
const downloaded = await downloadStagedUploadWithResolver({
|
|
717
|
+
resolveStagedUpload,
|
|
718
|
+
expected: command.staged_upload,
|
|
719
|
+
purpose: "reference_file",
|
|
720
|
+
});
|
|
721
|
+
return {
|
|
722
|
+
content: downloaded.content,
|
|
723
|
+
complete: async () => await cleanupDownloadedStagedUpload(downloaded),
|
|
724
|
+
abort: async () => await abortDownloadedStagedUpload(downloaded),
|
|
725
|
+
};
|
|
726
|
+
},
|
|
727
|
+
scratchpadSource: {
|
|
728
|
+
notify: (message) => controlPlane.notifyScratchpadSourceChanged(message),
|
|
729
|
+
},
|
|
730
|
+
referenceSummaries: {
|
|
731
|
+
start: (paths) => controlPlane.startReferenceSummaryRefresh({ paths }),
|
|
732
|
+
},
|
|
733
|
+
now,
|
|
734
|
+
}),
|
|
735
|
+
integration_managed_reference: async (command) => await ingestTrustedManagedReference({
|
|
736
|
+
store,
|
|
737
|
+
workspaceRoot: options.preparation.workspace_root,
|
|
738
|
+
events: workspaceEvents,
|
|
739
|
+
command,
|
|
740
|
+
resolveContent: async () => {
|
|
741
|
+
if (resolveStagedUpload === undefined) {
|
|
742
|
+
throw new Error("Staged upload resolver is unavailable");
|
|
743
|
+
}
|
|
744
|
+
const downloaded = await downloadStagedUploadWithResolver({
|
|
745
|
+
resolveStagedUpload,
|
|
746
|
+
expected: command.staged_upload,
|
|
747
|
+
purpose: "reference_file",
|
|
748
|
+
});
|
|
749
|
+
return {
|
|
750
|
+
content: downloaded.content,
|
|
751
|
+
complete: async () => await cleanupDownloadedStagedUpload(downloaded),
|
|
752
|
+
abort: async () => await abortDownloadedStagedUpload(downloaded),
|
|
753
|
+
};
|
|
754
|
+
},
|
|
755
|
+
referenceSummaries: {
|
|
756
|
+
start: (paths) => controlPlane.startReferenceSummaryRefresh({ paths }),
|
|
757
|
+
},
|
|
758
|
+
now,
|
|
759
|
+
}),
|
|
760
|
+
integration_approval_decision: (command) => {
|
|
761
|
+
try {
|
|
762
|
+
return executeApprovalDecisionCommand({
|
|
763
|
+
db: store.db,
|
|
764
|
+
approvalManager,
|
|
765
|
+
commandId: command.command_id,
|
|
766
|
+
approvalId: command.approval_id,
|
|
767
|
+
decisionId: command.decision_id,
|
|
768
|
+
actor: {
|
|
769
|
+
account_ref: command.actor.account_ref,
|
|
770
|
+
display_name: command.actor.display_name,
|
|
771
|
+
},
|
|
772
|
+
now,
|
|
773
|
+
}).disposition;
|
|
774
|
+
}
|
|
775
|
+
catch {
|
|
776
|
+
return { kind: "invalid", reason_code: "approval_command_rejected" };
|
|
777
|
+
}
|
|
778
|
+
},
|
|
691
779
|
attachRelayConnectionManager: (manager) => {
|
|
692
780
|
relayConnectionManager = manager;
|
|
693
781
|
platformOutboundCoordinator?.start();
|
|
@@ -37,7 +37,10 @@ export async function registerHostWithRelay(options) {
|
|
|
37
37
|
joinToken: { mode: options.joinTokenMode ?? "rotate" },
|
|
38
38
|
capabilities: {
|
|
39
39
|
integration_source_ingestion_v1: true,
|
|
40
|
+
integration_attachment_ingestion_v1: true,
|
|
41
|
+
integration_managed_reference_ingestion_v1: true,
|
|
40
42
|
integration_outbound_v1: true,
|
|
43
|
+
integration_approval_decision_v1: true,
|
|
41
44
|
},
|
|
42
45
|
fetch: options.fetchImpl,
|
|
43
46
|
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FastifyInstance } from "fastify";
|
|
2
2
|
import type { RelayHostTunnelRequestHandler, RelayHostTunnelStreamRequestHandler } from "@tutti/relay-client";
|
|
3
|
-
import type { IntegrationDeliveryDisposition, TrustedConversationSourceIngestionCommand } from "@tutti/shared/schemas/internal";
|
|
3
|
+
import type { IntegrationApprovalDecisionDisposition, IntegrationDeliveryDisposition, IntegrationManagedReferenceDisposition, TrustedApprovalDecisionCommand, TrustedConversationSourceIngestionCommand, TrustedManagedReferenceIngestionCommand, TrustedReferenceAttachmentIngestionCommand } from "@tutti/shared/schemas/internal";
|
|
4
4
|
import type { ProjectSurface } from "@tutti/shared/domain";
|
|
5
5
|
import { type TrustedRelaySessionMetadataStore } from "../session/relay-session-context.js";
|
|
6
6
|
export type HostProjectApiTunnelDispatchOptions = {
|
|
@@ -8,6 +8,9 @@ export type HostProjectApiTunnelDispatchOptions = {
|
|
|
8
8
|
projectSurface?: ProjectSurface;
|
|
9
9
|
trustedMetadataStore: TrustedRelaySessionMetadataStore;
|
|
10
10
|
integrationDelivery?: (command: TrustedConversationSourceIngestionCommand) => IntegrationDeliveryDisposition;
|
|
11
|
+
integrationApprovalDecision?: (command: TrustedApprovalDecisionCommand) => IntegrationApprovalDecisionDisposition;
|
|
12
|
+
integrationReferenceAttachment?: (command: TrustedReferenceAttachmentIngestionCommand) => Promise<IntegrationDeliveryDisposition>;
|
|
13
|
+
integrationManagedReference?: (command: TrustedManagedReferenceIngestionCommand) => Promise<IntegrationManagedReferenceDisposition>;
|
|
11
14
|
};
|
|
12
15
|
export type HostProjectApiTunnelStreamDispatchOptions = {
|
|
13
16
|
baseUrl: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isTrustedConversationSourceIngestionCommand } from "@tutti/shared/schemas/internal";
|
|
2
|
-
import { TRUSTED_INTEGRATION_INGESTION_PATH } from "../../collaboration-ingestion/index.js";
|
|
1
|
+
import { INTEGRATION_APPROVAL_DECISION_TUNNEL_PATH, INTEGRATION_MANAGED_REFERENCE_TUNNEL_PATH, INTEGRATION_REFERENCE_ATTACHMENT_TUNNEL_PATH, isTrustedApprovalDecisionCommand, isTrustedConversationSourceIngestionCommand, isTrustedManagedReferenceIngestionCommand, isTrustedReferenceAttachmentIngestionCommand, } from "@tutti/shared/schemas/internal";
|
|
2
|
+
import { TRUSTED_INTEGRATION_INGESTION_PATH, TRUSTED_INTEGRATION_REFERENCE_ATTACHMENT_PATH, } from "../../collaboration-ingestion/index.js";
|
|
3
3
|
import { TRUSTED_RELAY_METADATA_REQUEST_HEADER, } from "../session/relay-session-context.js";
|
|
4
4
|
function browserMetadataMatchesSurface(projectSurface, request) {
|
|
5
5
|
return projectSurface === "standalone"
|
|
@@ -38,6 +38,30 @@ function integrationResponse(requestId, disposition) {
|
|
|
38
38
|
body_base64: Buffer.from(JSON.stringify(body), "utf8").toString("base64"),
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
+
function integrationApprovalResponse(requestId, disposition) {
|
|
42
|
+
const body = {
|
|
43
|
+
protocol_version: 1,
|
|
44
|
+
disposition,
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
request_id: requestId,
|
|
48
|
+
status_code: 200,
|
|
49
|
+
headers: { "content-type": "application/json; charset=utf-8" },
|
|
50
|
+
body_base64: Buffer.from(JSON.stringify(body), "utf8").toString("base64"),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function integrationManagedReferenceResponse(requestId, disposition) {
|
|
54
|
+
const body = {
|
|
55
|
+
protocol_version: 1,
|
|
56
|
+
disposition,
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
request_id: requestId,
|
|
60
|
+
status_code: 200,
|
|
61
|
+
headers: { "content-type": "application/json; charset=utf-8" },
|
|
62
|
+
body_base64: Buffer.from(JSON.stringify(body), "utf8").toString("base64"),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
41
65
|
function sameInstallation(left, right) {
|
|
42
66
|
return left.platform === right.platform && left.installation_ref === right.installation_ref;
|
|
43
67
|
}
|
|
@@ -51,6 +75,15 @@ function dispatchIntegrationDelivery(options, request) {
|
|
|
51
75
|
if (!("integration_delivery" in request.metadata)) {
|
|
52
76
|
throw new TypeError("Integration delivery metadata is required");
|
|
53
77
|
}
|
|
78
|
+
if (request.path === INTEGRATION_APPROVAL_DECISION_TUNNEL_PATH) {
|
|
79
|
+
return dispatchIntegrationApprovalDecision(options, request);
|
|
80
|
+
}
|
|
81
|
+
if (request.path === INTEGRATION_REFERENCE_ATTACHMENT_TUNNEL_PATH) {
|
|
82
|
+
return dispatchIntegrationReferenceAttachment(options, request);
|
|
83
|
+
}
|
|
84
|
+
if (request.path === INTEGRATION_MANAGED_REFERENCE_TUNNEL_PATH) {
|
|
85
|
+
return dispatchIntegrationManagedReference(options, request);
|
|
86
|
+
}
|
|
54
87
|
if (request.method.toUpperCase() !== "POST" ||
|
|
55
88
|
request.path !== TRUSTED_INTEGRATION_INGESTION_PATH) {
|
|
56
89
|
return Promise.resolve(integrationResponse(request.request_id, {
|
|
@@ -73,7 +106,8 @@ function dispatchIntegrationDelivery(options, request) {
|
|
|
73
106
|
}));
|
|
74
107
|
}
|
|
75
108
|
const metadata = request.metadata.integration_delivery;
|
|
76
|
-
if (
|
|
109
|
+
if (!("receipt_key" in metadata) ||
|
|
110
|
+
command.relay_project_ref !== metadata.relay_project_ref ||
|
|
77
111
|
command.binding_ref !== metadata.binding_ref ||
|
|
78
112
|
command.receipt.receipt_key !== metadata.receipt_key ||
|
|
79
113
|
command.actor.verified_at !== metadata.verified_at ||
|
|
@@ -92,6 +126,143 @@ function dispatchIntegrationDelivery(options, request) {
|
|
|
92
126
|
}
|
|
93
127
|
return Promise.resolve(integrationResponse(request.request_id, options.integrationDelivery(command)));
|
|
94
128
|
}
|
|
129
|
+
async function dispatchIntegrationReferenceAttachment(options, request) {
|
|
130
|
+
if (!("integration_delivery" in request.metadata)) {
|
|
131
|
+
throw new TypeError("Integration delivery metadata is required");
|
|
132
|
+
}
|
|
133
|
+
if (request.method.toUpperCase() !== "POST" ||
|
|
134
|
+
request.path !== TRUSTED_INTEGRATION_REFERENCE_ATTACHMENT_PATH) {
|
|
135
|
+
return integrationResponse(request.request_id, {
|
|
136
|
+
kind: "invalid",
|
|
137
|
+
reason_code: "internal_route_invalid",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const payload = decodeRequestBody(request.body_base64);
|
|
141
|
+
let command;
|
|
142
|
+
try {
|
|
143
|
+
command = payload === undefined ? undefined : JSON.parse(payload.toString("utf8"));
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
command = undefined;
|
|
147
|
+
}
|
|
148
|
+
if (!isTrustedReferenceAttachmentIngestionCommand(command)) {
|
|
149
|
+
return integrationResponse(request.request_id, {
|
|
150
|
+
kind: "invalid",
|
|
151
|
+
reason_code: "trusted_attachment_command_invalid",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
const metadata = request.metadata.integration_delivery;
|
|
155
|
+
if (!("receipt_key" in metadata) ||
|
|
156
|
+
command.relay_project_ref !== metadata.relay_project_ref ||
|
|
157
|
+
command.binding_ref !== metadata.binding_ref ||
|
|
158
|
+
command.receipt.receipt_key !== metadata.receipt_key ||
|
|
159
|
+
command.actor.verified_at !== metadata.verified_at ||
|
|
160
|
+
!sameInstallation(command.receipt.installation, metadata.installation)) {
|
|
161
|
+
return integrationResponse(request.request_id, {
|
|
162
|
+
kind: "invalid",
|
|
163
|
+
reason_code: "trusted_metadata_mismatch",
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (options.integrationReferenceAttachment === undefined) {
|
|
167
|
+
return integrationResponse(request.request_id, {
|
|
168
|
+
kind: "host_incompatible",
|
|
169
|
+
retryable: true,
|
|
170
|
+
reason_code: "integration_attachment_ingestion_unsupported",
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return integrationResponse(request.request_id, await options.integrationReferenceAttachment(command));
|
|
174
|
+
}
|
|
175
|
+
async function dispatchIntegrationManagedReference(options, request) {
|
|
176
|
+
if (!("integration_delivery" in request.metadata)) {
|
|
177
|
+
throw new TypeError("Integration delivery metadata is required");
|
|
178
|
+
}
|
|
179
|
+
if (request.method.toUpperCase() !== "POST" ||
|
|
180
|
+
request.path !== INTEGRATION_MANAGED_REFERENCE_TUNNEL_PATH) {
|
|
181
|
+
return integrationManagedReferenceResponse(request.request_id, {
|
|
182
|
+
kind: "invalid",
|
|
183
|
+
reason_code: "internal_route_invalid",
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
const payload = decodeRequestBody(request.body_base64);
|
|
187
|
+
let command;
|
|
188
|
+
try {
|
|
189
|
+
command = payload === undefined ? undefined : JSON.parse(payload.toString("utf8"));
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
command = undefined;
|
|
193
|
+
}
|
|
194
|
+
if (!isTrustedManagedReferenceIngestionCommand(command)) {
|
|
195
|
+
return integrationManagedReferenceResponse(request.request_id, {
|
|
196
|
+
kind: "invalid",
|
|
197
|
+
reason_code: "trusted_managed_reference_command_invalid",
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
const metadata = request.metadata.integration_delivery;
|
|
201
|
+
if (!("operation_ref" in metadata) ||
|
|
202
|
+
command.relay_project_ref !== metadata.relay_project_ref ||
|
|
203
|
+
command.binding_ref !== metadata.binding_ref ||
|
|
204
|
+
command.operation_ref !== metadata.operation_ref ||
|
|
205
|
+
command.actor.verified_at !== metadata.verified_at ||
|
|
206
|
+
!sameInstallation(command.source.installation, metadata.installation)) {
|
|
207
|
+
return integrationManagedReferenceResponse(request.request_id, {
|
|
208
|
+
kind: "invalid",
|
|
209
|
+
reason_code: "trusted_metadata_mismatch",
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
if (options.integrationManagedReference === undefined) {
|
|
213
|
+
return integrationManagedReferenceResponse(request.request_id, {
|
|
214
|
+
kind: "host_incompatible",
|
|
215
|
+
retryable: true,
|
|
216
|
+
reason_code: "integration_managed_reference_ingestion_unsupported",
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return integrationManagedReferenceResponse(request.request_id, await options.integrationManagedReference(command));
|
|
220
|
+
}
|
|
221
|
+
function dispatchIntegrationApprovalDecision(options, request) {
|
|
222
|
+
if (!("integration_delivery" in request.metadata)) {
|
|
223
|
+
throw new TypeError("Integration delivery metadata is required");
|
|
224
|
+
}
|
|
225
|
+
if (request.method.toUpperCase() !== "POST") {
|
|
226
|
+
return Promise.resolve(integrationApprovalResponse(request.request_id, {
|
|
227
|
+
kind: "invalid",
|
|
228
|
+
reason_code: "internal_route_invalid",
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
const payload = decodeRequestBody(request.body_base64);
|
|
232
|
+
let command;
|
|
233
|
+
try {
|
|
234
|
+
command = payload === undefined ? undefined : JSON.parse(payload.toString("utf8"));
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
command = undefined;
|
|
238
|
+
}
|
|
239
|
+
if (!isTrustedApprovalDecisionCommand(command)) {
|
|
240
|
+
return Promise.resolve(integrationApprovalResponse(request.request_id, {
|
|
241
|
+
kind: "invalid",
|
|
242
|
+
reason_code: "trusted_command_invalid",
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
const metadata = request.metadata.integration_delivery;
|
|
246
|
+
if (!("receipt_key" in metadata) ||
|
|
247
|
+
command.relay_project_ref !== metadata.relay_project_ref ||
|
|
248
|
+
command.binding_ref !== metadata.binding_ref ||
|
|
249
|
+
command.receipt.receipt_key !== metadata.receipt_key ||
|
|
250
|
+
command.actor.verified_at !== metadata.verified_at ||
|
|
251
|
+
!sameInstallation(command.receipt.installation, metadata.installation)) {
|
|
252
|
+
return Promise.resolve(integrationApprovalResponse(request.request_id, {
|
|
253
|
+
kind: "invalid",
|
|
254
|
+
reason_code: "trusted_metadata_mismatch",
|
|
255
|
+
}));
|
|
256
|
+
}
|
|
257
|
+
if (options.integrationApprovalDecision === undefined) {
|
|
258
|
+
return Promise.resolve(integrationApprovalResponse(request.request_id, {
|
|
259
|
+
kind: "host_incompatible",
|
|
260
|
+
retryable: true,
|
|
261
|
+
reason_code: "integration_approval_decision_unsupported",
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
return Promise.resolve(integrationApprovalResponse(request.request_id, options.integrationApprovalDecision(command)));
|
|
265
|
+
}
|
|
95
266
|
function injectHeaders(request) {
|
|
96
267
|
const headers = {};
|
|
97
268
|
for (const [name, value] of Object.entries(request.headers)) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ActiveApprovalProjectionSchema, DecideApprovalBodySchema, DecideApprovalResponseSchema, } from "@tutti/shared/schemas/api";
|
|
2
|
-
import {
|
|
2
|
+
import { executeApprovalDecisionCommand } from "../../../../approvals/index.js";
|
|
3
3
|
import { HOST_PROJECT_API_BASE_PATH } from "./constants.js";
|
|
4
4
|
import { approvalInvalidates } from "./invalidations.js";
|
|
5
|
-
import { ApprovalRequestParamsSchema
|
|
6
|
-
import { requireHostProjectSession, requireProjectStore
|
|
5
|
+
import { ApprovalRequestParamsSchema } from "./schemas.js";
|
|
6
|
+
import { requireHostProjectSession, requireProjectStore } from "./session-requirements.js";
|
|
7
7
|
function readActiveApproval(options) {
|
|
8
8
|
return options.approvalManager?.getActiveApproval() ?? { state: "none" };
|
|
9
9
|
}
|
|
@@ -29,26 +29,19 @@ export function registerApprovalRoutes(app, options) {
|
|
|
29
29
|
}, (request) => {
|
|
30
30
|
const session = requireHostProjectSession(request, options);
|
|
31
31
|
const store = requireProjectStore(options);
|
|
32
|
-
const execution =
|
|
32
|
+
const execution = executeApprovalDecisionCommand({
|
|
33
33
|
db: store.db,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
...(options.approvalManager === undefined
|
|
35
|
+
? {}
|
|
36
|
+
: { approvalManager: options.approvalManager }),
|
|
37
|
+
commandId: request.body.command_id,
|
|
38
|
+
approvalId: request.params.approvalId,
|
|
39
|
+
decisionId: request.body.payload.decision_id,
|
|
40
|
+
actor: {
|
|
41
|
+
account_ref: session.account.account_ref,
|
|
42
|
+
display_name: session.account.display_name,
|
|
39
43
|
},
|
|
40
44
|
...(options.now === undefined ? {} : { now: options.now }),
|
|
41
|
-
execute: () => options.approvalManager?.decide({
|
|
42
|
-
approvalId: request.params.approvalId,
|
|
43
|
-
payload: request.body.payload,
|
|
44
|
-
actor: {
|
|
45
|
-
account_ref: session.account.account_ref,
|
|
46
|
-
display_name: session.account.display_name,
|
|
47
|
-
},
|
|
48
|
-
}) ?? {
|
|
49
|
-
disposition: { kind: "stale_approval" },
|
|
50
|
-
result: { approval: { state: "none" } },
|
|
51
|
-
},
|
|
52
45
|
});
|
|
53
46
|
const response = {
|
|
54
47
|
command_id: execution.command_id,
|
|
@@ -4,6 +4,12 @@ export type DownloadedStagedUpload = {
|
|
|
4
4
|
resolved: ResolvedStagedUpload;
|
|
5
5
|
content: Buffer;
|
|
6
6
|
};
|
|
7
|
+
export type StagedUploadResolver = NonNullable<HostProjectApiRouteOptions["resolveStagedUpload"]>;
|
|
8
|
+
export declare function downloadStagedUploadWithResolver(options: {
|
|
9
|
+
resolveStagedUpload: StagedUploadResolver;
|
|
10
|
+
expected: StagedUploadReference;
|
|
11
|
+
purpose: RelayUploadPurpose;
|
|
12
|
+
}): Promise<DownloadedStagedUpload>;
|
|
7
13
|
export declare function downloadStagedUpload(options: {
|
|
8
14
|
routeOptions: HostProjectApiRouteOptions;
|
|
9
15
|
expected: StagedUploadReference;
|
|
@@ -104,14 +104,14 @@ async function disposeResolvedStagedUpload(upload, status) {
|
|
|
104
104
|
// Completion metadata is best-effort after the host has decided the terminal status.
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
|
-
export async function
|
|
107
|
+
export async function downloadStagedUploadWithResolver(options) {
|
|
108
108
|
const maxBytes = maxStagedUploadBytesForPurpose(options.purpose);
|
|
109
109
|
if (options.expected.size_bytes > maxBytes) {
|
|
110
110
|
throw new HostProjectValidationError(`Upload content cannot exceed ${maxBytes} bytes`);
|
|
111
111
|
}
|
|
112
112
|
let resolved;
|
|
113
113
|
try {
|
|
114
|
-
resolved = await
|
|
114
|
+
resolved = await options.resolveStagedUpload(options.expected.upload_id);
|
|
115
115
|
}
|
|
116
116
|
catch {
|
|
117
117
|
throw new HostProjectValidationError("Upload session could not be resolved");
|
|
@@ -140,6 +140,13 @@ export async function downloadStagedUpload(options) {
|
|
|
140
140
|
content,
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
|
+
export async function downloadStagedUpload(options) {
|
|
144
|
+
return downloadStagedUploadWithResolver({
|
|
145
|
+
resolveStagedUpload: requireStagedUploadResolver(options.routeOptions),
|
|
146
|
+
expected: options.expected,
|
|
147
|
+
purpose: options.purpose,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
143
150
|
export async function cleanupDownloadedStagedUpload(upload) {
|
|
144
151
|
await disposeResolvedStagedUpload(upload.resolved, "completed");
|
|
145
152
|
}
|
|
@@ -3,11 +3,11 @@ export { WorkspaceOpsError } from "./errors.js";
|
|
|
3
3
|
export { commitOwnedPaths, requireOwnedPathsWriteReady } from "./system-commits.js";
|
|
4
4
|
export { collectProjectSourceDiff, createAcceptedCheckpoint, inspectProjectWorkspace, recoverAcceptedCheckpoint, } from "./project-workspace.js";
|
|
5
5
|
export { initializeProjectDocs, readProjectDocsInitializationStatus, syncProjectDocs, } from "./project-docs.js";
|
|
6
|
-
export { uploadReferenceFile } from "./reference-files.js";
|
|
6
|
+
export { readManagedReferenceSnapshotState, uploadReferenceFile, writeManagedReferenceSnapshot, } from "./reference-files.js";
|
|
7
7
|
export { isReferenceFilePath, referenceFileCategoryForPath, referenceFolderGroupForPath, referenceMediaTypeForPath, } from "./reference-metadata.js";
|
|
8
8
|
export { readReferenceFileEntries } from "./reference-tree.js";
|
|
9
9
|
export { readReferenceSummaryEntries, readReferenceSummaryIndex, readReferenceSummaryTargets, referenceSummaryProjectionForEntry, updateReferenceSummaries, } from "./reference-summaries.js";
|
|
10
10
|
export { readViewerAsset, readViewerFile, readViewerSnapshot, readViewerTree } from "./viewer.js";
|
|
11
11
|
export type { WorkspaceOpsErrorCode } from "./errors.js";
|
|
12
|
-
export type { AcceptedCheckpointRecoveryResult, AcceptedCheckpointResult, CreateAcceptedCheckpointOptions, InitializeProjectDocsOptions, InitializeProjectDocsResult, ProjectDocsInitializationContext, ProjectDocsInitializationDocument, ProjectDocsInitializationStatus, ProjectWorkspaceInspection, ReadViewerAssetOptions, ReadViewerFileOptions, ReadViewerTreeOptions, ReferenceFileCategory, ReferenceSummaryProjection, ReferenceSummaryRecord, ReferenceSummaryStatus, ReferenceSummaryTarget, RunDiffSummary, SyncProjectDocsResult, SyncProjectDocsUpdate, UpdateReferenceSummariesOptions, UpdateReferenceSummariesResult, UploadReferenceFileOptions, UploadReferenceFileResult, ViewerAssetResult, ViewerAssetVariant, ViewerEntryKind, ViewerFileContent, ViewerFileResult, ViewerRepoSnapshot, ViewerTreeEntry, ViewerTreeResult, WorkspaceViewerOptions, } from "./types.js";
|
|
12
|
+
export type { AcceptedCheckpointRecoveryResult, AcceptedCheckpointResult, CreateAcceptedCheckpointOptions, InitializeProjectDocsOptions, InitializeProjectDocsResult, ProjectDocsInitializationContext, ProjectDocsInitializationDocument, ProjectDocsInitializationStatus, ProjectWorkspaceInspection, ReadViewerAssetOptions, ReadViewerFileOptions, ReadViewerTreeOptions, ReferenceFileCategory, ReferenceSummaryProjection, ReferenceSummaryRecord, ReferenceSummaryStatus, ReferenceSummaryTarget, RunDiffSummary, SyncProjectDocsResult, SyncProjectDocsUpdate, UpdateReferenceSummariesOptions, UpdateReferenceSummariesResult, UploadReferenceFileOptions, UploadReferenceFileResult, WriteManagedReferenceSnapshotOptions, WriteManagedReferenceSnapshotResult, ManagedReferenceSnapshotState, ViewerAssetResult, ViewerAssetVariant, ViewerEntryKind, ViewerFileContent, ViewerFileResult, ViewerRepoSnapshot, ViewerTreeEntry, ViewerTreeResult, WorkspaceViewerOptions, } from "./types.js";
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -3,7 +3,7 @@ export { WorkspaceOpsError } from "./errors.js";
|
|
|
3
3
|
export { commitOwnedPaths, requireOwnedPathsWriteReady } from "./system-commits.js";
|
|
4
4
|
export { collectProjectSourceDiff, createAcceptedCheckpoint, inspectProjectWorkspace, recoverAcceptedCheckpoint, } from "./project-workspace.js";
|
|
5
5
|
export { initializeProjectDocs, readProjectDocsInitializationStatus, syncProjectDocs, } from "./project-docs.js";
|
|
6
|
-
export { uploadReferenceFile } from "./reference-files.js";
|
|
6
|
+
export { readManagedReferenceSnapshotState, uploadReferenceFile, writeManagedReferenceSnapshot, } from "./reference-files.js";
|
|
7
7
|
export { isReferenceFilePath, referenceFileCategoryForPath, referenceFolderGroupForPath, referenceMediaTypeForPath, } from "./reference-metadata.js";
|
|
8
8
|
export { readReferenceFileEntries } from "./reference-tree.js";
|
|
9
9
|
export { readReferenceSummaryEntries, readReferenceSummaryIndex, readReferenceSummaryTargets, referenceSummaryProjectionForEntry, updateReferenceSummaries, } from "./reference-summaries.js";
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
-
import type { UploadReferenceFileOptions, UploadReferenceFileResult } from "./types.js";
|
|
1
|
+
import type { UploadReferenceFileOptions, UploadReferenceFileResult, WriteManagedReferenceSnapshotOptions, WriteManagedReferenceSnapshotResult, ManagedReferenceSnapshotState } from "./types.js";
|
|
2
2
|
export declare function uploadReferenceFile(options: UploadReferenceFileOptions): UploadReferenceFileResult;
|
|
3
|
+
/**
|
|
4
|
+
* Writes a caller-managed logical Reference snapshot at one stable path.
|
|
5
|
+
*
|
|
6
|
+
* Platform adapters remain responsible for external source identity and
|
|
7
|
+
* revision discovery. Updates use the committed blob oid as a compare-and-set
|
|
8
|
+
* boundary so a stale adapter cannot overwrite a newer local snapshot.
|
|
9
|
+
*/
|
|
10
|
+
export declare function writeManagedReferenceSnapshot(options: WriteManagedReferenceSnapshotOptions): WriteManagedReferenceSnapshotResult;
|
|
11
|
+
export declare function readManagedReferenceSnapshotState(options: {
|
|
12
|
+
workspaceRoot: string;
|
|
13
|
+
path: string;
|
|
14
|
+
now?: () => Date;
|
|
15
|
+
}): ManagedReferenceSnapshotState;
|
|
3
16
|
//# sourceMappingURL=reference-files.d.ts.map
|