@xfey/tutti 0.1.105 → 0.1.107
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 +3 -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/control-plane/procedure-logging.d.ts +1 -0
- package/dist/control-plane/procedure-logging.js +16 -5
- package/dist/control-plane/reference-summary-refresh.js +2 -2
- package/dist/control-plane/workflows/openai.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/providers/openai/app-server/read-only-procedure.d.ts +10 -2
- package/dist/providers/openai/app-server/read-only-procedure.js +65 -15
- 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/reference-files-routes.js +19 -2
- 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/feishu-documents.d.ts +41 -0
- package/node_modules/@tutti/shared/dist/schemas/api/feishu-documents.js +40 -0
- package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +3 -2
- package/node_modules/@tutti/shared/dist/schemas/api/index.js +2 -1
- package/node_modules/@tutti/shared/dist/schemas/api/platform-sessions.d.ts +37 -0
- package/node_modules/@tutti/shared/dist/schemas/api/platform-sessions.js +45 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/feishu-documents.d.ts +38 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/feishu-documents.js +31 -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 +2 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/index.js +2 -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-CFs5rMWq.js} +1 -1
- package/web/assets/index-BmiLZmaV.js +69 -0
- package/web/assets/{index-CTMOjCgU.css → index-DvfZ57Nl.css} +1 -1
- package/web/index.html +2 -2
- package/web/assets/index-CmzEr4-V.js +0 -69
|
@@ -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,8 +4,9 @@ import { REFERENCE_DIRECTORY_PATH } from "../../../../workspace-ops/index.js";
|
|
|
4
4
|
import { decideCommandIdempotencyReplay, executeIdempotentCommand, readCommandIdempotencyRecord, } from "../../../command-idempotency/index.js";
|
|
5
5
|
import { HOST_PROJECT_API_BASE_PATH } from "./constants.js";
|
|
6
6
|
import { readReferenceFilesResponse, referenceSummaryProcedureInvalidates, } from "./reference-files.js";
|
|
7
|
-
import { requireControlPlane, requireHostProjectSession, requireProjectStore, requireWorkspaceRoot, } from "./session-requirements.js";
|
|
7
|
+
import { requireControlPlane, requireHostProjectSession, requireHostProjectOwnerSession, requireProjectStore, requireWorkspaceRoot, } from "./session-requirements.js";
|
|
8
8
|
import { abortDownloadedStagedUpload, cleanupDownloadedStagedUpload, downloadStagedUpload, } from "./staged-uploads.js";
|
|
9
|
+
import { HostProjectBadRequestError } from "./errors.js";
|
|
9
10
|
export function registerReferenceFilesRoutes(app, { options, workspaceEvents, }) {
|
|
10
11
|
app.get(`${HOST_PROJECT_API_BASE_PATH}/references/files`, {
|
|
11
12
|
schema: {
|
|
@@ -90,7 +91,16 @@ export function registerReferenceFilesRoutes(app, { options, workspaceEvents, })
|
|
|
90
91
|
},
|
|
91
92
|
},
|
|
92
93
|
}, (request) => {
|
|
93
|
-
|
|
94
|
+
if (options.surfaceProfile?.kind === "feishu") {
|
|
95
|
+
requireHostProjectOwnerSession(request, options);
|
|
96
|
+
const paths = request.body.payload.paths;
|
|
97
|
+
if (paths?.length !== 1 || !paths[0]?.startsWith(`${REFERENCE_DIRECTORY_PATH}/`)) {
|
|
98
|
+
throw new HostProjectBadRequestError("Select one project Reference to retry its summary");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
requireHostProjectSession(request, options);
|
|
103
|
+
}
|
|
94
104
|
const store = requireProjectStore(options);
|
|
95
105
|
const execution = executeIdempotentCommand({
|
|
96
106
|
db: store.db,
|
|
@@ -109,6 +119,13 @@ export function registerReferenceFilesRoutes(app, { options, workspaceEvents, })
|
|
|
109
119
|
if (execution.result !== undefined) {
|
|
110
120
|
response.result = execution.result;
|
|
111
121
|
}
|
|
122
|
+
request.log.info({
|
|
123
|
+
command_id: execution.command_id,
|
|
124
|
+
disposition: execution.disposition.kind,
|
|
125
|
+
activity_ref: execution.result?.activity_ref,
|
|
126
|
+
workflow_ref: execution.result?.workflow_ref,
|
|
127
|
+
target_count: execution.result?.target_count,
|
|
128
|
+
}, "Reference summary refresh requested");
|
|
112
129
|
return response;
|
|
113
130
|
});
|
|
114
131
|
}
|
|
@@ -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
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { extname, join, posix } from "node:path";
|
|
3
4
|
import { classifyViewerPath } from "@tutti/shared/utils";
|
|
4
|
-
import { MAX_REFERENCE_UPLOAD_BYTES, REFERENCE_DIRECTORY_PATH, REFERENCE_INDEX_PATH, } from "./constants.js";
|
|
5
|
+
import { MAX_REFERENCE_UPLOAD_BYTES, REFERENCE_DIRECTORY_PATH, REFERENCE_IMAGES_DIRECTORY_PATH, REFERENCE_INDEX_PATH, } from "./constants.js";
|
|
5
6
|
import { WorkspaceOpsError } from "./errors.js";
|
|
6
7
|
import { classifyReferenceUpload } from "./reference-metadata.js";
|
|
7
8
|
import { commitOwnedPaths, requireOwnedPathsWriteReady } from "./system-commits.js";
|
|
9
|
+
import { requireNormalizedViewerPath } from "./viewer-paths.js";
|
|
10
|
+
import { readBlob, requireFileEntry } from "./viewer-git.js";
|
|
8
11
|
import { readViewerFile } from "./viewer.js";
|
|
9
12
|
const MAX_REFERENCE_FILE_NAME_BYTES = 240;
|
|
10
13
|
const WINDOWS_RESERVED_FILE_STEMS = /^(?:con|prn|aux|nul|clock\$|com[1-9]|lpt[1-9])$/iu;
|
|
@@ -199,4 +202,166 @@ export function uploadReferenceFile(options) {
|
|
|
199
202
|
commit_oid: resolvedCommitOid,
|
|
200
203
|
};
|
|
201
204
|
}
|
|
205
|
+
function managedReferenceSnapshotPath(path) {
|
|
206
|
+
const repoPath = requireNormalizedViewerPath(path);
|
|
207
|
+
if (repoPath !== path || !repoPath.startsWith(`${REFERENCE_DIRECTORY_PATH}/`)) {
|
|
208
|
+
throw new WorkspaceOpsError("validation_failed", "Managed reference snapshot path must be normalized and inside the Reference directory");
|
|
209
|
+
}
|
|
210
|
+
if (repoPath === REFERENCE_INDEX_PATH) {
|
|
211
|
+
throw new WorkspaceOpsError("validation_failed", "Managed reference snapshot cannot replace the Reference index");
|
|
212
|
+
}
|
|
213
|
+
const fileName = posix.basename(repoPath);
|
|
214
|
+
if (normalizeReferenceFileName(fileName) !== fileName) {
|
|
215
|
+
throw new WorkspaceOpsError("validation_failed", "Managed reference snapshot file name is not normalized");
|
|
216
|
+
}
|
|
217
|
+
const directoryPath = posix.dirname(repoPath);
|
|
218
|
+
const category = repoPath.startsWith(`${REFERENCE_IMAGES_DIRECTORY_PATH}/`)
|
|
219
|
+
? "image"
|
|
220
|
+
: "file";
|
|
221
|
+
return { repoPath, directoryPath, fileName, category };
|
|
222
|
+
}
|
|
223
|
+
function readManagedSnapshotPathEntry(path) {
|
|
224
|
+
try {
|
|
225
|
+
return lstatSync(path);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
throw new WorkspaceOpsError("forbidden", "Managed reference snapshot path could not be inspected", { reason_code: "managed_reference_snapshot_path_unavailable" });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function inspectManagedReferenceSnapshotFilesystemPath(options) {
|
|
235
|
+
const segments = options.repoPath.split("/");
|
|
236
|
+
let currentPath = options.workspaceRoot;
|
|
237
|
+
for (const [index, segment] of segments.entries()) {
|
|
238
|
+
currentPath = join(currentPath, segment);
|
|
239
|
+
const entry = readManagedSnapshotPathEntry(currentPath);
|
|
240
|
+
if (entry === undefined) {
|
|
241
|
+
return {
|
|
242
|
+
filesystemPath: join(options.workspaceRoot, ...segments),
|
|
243
|
+
exists: false,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
if (entry.isSymbolicLink()) {
|
|
247
|
+
throw new WorkspaceOpsError("forbidden", "Managed reference snapshot path must not traverse a symbolic link", { reason_code: "managed_reference_snapshot_symlink" });
|
|
248
|
+
}
|
|
249
|
+
const isTarget = index === segments.length - 1;
|
|
250
|
+
if (!isTarget && !entry.isDirectory()) {
|
|
251
|
+
throw new WorkspaceOpsError("conflict", "Managed reference snapshot parent path is not a directory", { reason_code: "managed_reference_snapshot_parent_invalid" });
|
|
252
|
+
}
|
|
253
|
+
if (isTarget && !entry.isFile()) {
|
|
254
|
+
throw new WorkspaceOpsError("conflict", "Managed reference snapshot target is not a regular file", { reason_code: "managed_reference_snapshot_target_invalid" });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
filesystemPath: join(options.workspaceRoot, ...segments),
|
|
259
|
+
exists: true,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Writes a caller-managed logical Reference snapshot at one stable path.
|
|
264
|
+
*
|
|
265
|
+
* Platform adapters remain responsible for external source identity and
|
|
266
|
+
* revision discovery. Updates use the committed blob oid as a compare-and-set
|
|
267
|
+
* boundary so a stale adapter cannot overwrite a newer local snapshot.
|
|
268
|
+
*/
|
|
269
|
+
export function writeManagedReferenceSnapshot(options) {
|
|
270
|
+
if (options.content.byteLength > MAX_REFERENCE_UPLOAD_BYTES) {
|
|
271
|
+
throw new WorkspaceOpsError("validation_failed", `Managed reference snapshot cannot exceed ${MAX_REFERENCE_UPLOAD_BYTES} bytes`);
|
|
272
|
+
}
|
|
273
|
+
const { repoPath, directoryPath, fileName, category } = managedReferenceSnapshotPath(options.path);
|
|
274
|
+
const pathState = inspectManagedReferenceSnapshotFilesystemPath({
|
|
275
|
+
workspaceRoot: options.workspaceRoot,
|
|
276
|
+
repoPath,
|
|
277
|
+
});
|
|
278
|
+
const filesystemPath = pathState.filesystemPath;
|
|
279
|
+
const indexOwnedPaths = pathState.exists ? [repoPath] : [REFERENCE_INDEX_PATH, repoPath];
|
|
280
|
+
requireOwnedPathsWriteReady(options.workspaceRoot, indexOwnedPaths);
|
|
281
|
+
let previous;
|
|
282
|
+
if (pathState.exists) {
|
|
283
|
+
previous = readViewerFile({
|
|
284
|
+
workspaceRoot: options.workspaceRoot,
|
|
285
|
+
path: repoPath,
|
|
286
|
+
...(options.now === undefined ? {} : { now: options.now }),
|
|
287
|
+
});
|
|
288
|
+
if (options.expectedBlobOid === undefined) {
|
|
289
|
+
throw new WorkspaceOpsError("conflict", "Managed reference snapshot already exists", {
|
|
290
|
+
reason_code: "managed_reference_snapshot_exists",
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
if (previous.file.blob_oid !== options.expectedBlobOid) {
|
|
294
|
+
throw new WorkspaceOpsError("conflict", "Managed reference snapshot changed before this update", { reason_code: "managed_reference_snapshot_stale" });
|
|
295
|
+
}
|
|
296
|
+
if (sameFileContent(filesystemPath, options.content)) {
|
|
297
|
+
return {
|
|
298
|
+
disposition: "unchanged",
|
|
299
|
+
file: previous.file,
|
|
300
|
+
category,
|
|
301
|
+
index_path: REFERENCE_INDEX_PATH,
|
|
302
|
+
previous_blob_oid: previous.file.blob_oid,
|
|
303
|
+
commit_oid: previous.snapshot.head.oid,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
else if (options.expectedBlobOid !== undefined) {
|
|
308
|
+
throw new WorkspaceOpsError("conflict", "Managed reference snapshot no longer exists", {
|
|
309
|
+
reason_code: "managed_reference_snapshot_missing",
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
mkdirSync(join(options.workspaceRoot, ...directoryPath.split("/")), { recursive: true });
|
|
313
|
+
writeFileSync(filesystemPath, options.content);
|
|
314
|
+
if (previous === undefined) {
|
|
315
|
+
appendReferenceIndexEntry({
|
|
316
|
+
workspaceRoot: options.workspaceRoot,
|
|
317
|
+
fileName,
|
|
318
|
+
repoPath,
|
|
319
|
+
category,
|
|
320
|
+
...(options.note === undefined ? {} : { note: options.note }),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
const commitOid = commitOwnedPaths({
|
|
324
|
+
workspaceRoot: options.workspaceRoot,
|
|
325
|
+
paths: indexOwnedPaths,
|
|
326
|
+
message: previous === undefined
|
|
327
|
+
? `Add managed reference snapshot: ${fileName}`
|
|
328
|
+
: `Update managed reference snapshot: ${fileName}`,
|
|
329
|
+
});
|
|
330
|
+
const file = readViewerFile({
|
|
331
|
+
workspaceRoot: options.workspaceRoot,
|
|
332
|
+
path: repoPath,
|
|
333
|
+
...(options.now === undefined ? {} : { now: options.now }),
|
|
334
|
+
});
|
|
335
|
+
return {
|
|
336
|
+
disposition: previous === undefined ? "created" : "updated",
|
|
337
|
+
file: file.file,
|
|
338
|
+
category,
|
|
339
|
+
index_path: REFERENCE_INDEX_PATH,
|
|
340
|
+
...(previous === undefined ? {} : { previous_blob_oid: previous.file.blob_oid }),
|
|
341
|
+
...(commitOid === null ? {} : { commit_oid: commitOid }),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
export function readManagedReferenceSnapshotState(options) {
|
|
345
|
+
const { repoPath } = managedReferenceSnapshotPath(options.path);
|
|
346
|
+
const pathState = inspectManagedReferenceSnapshotFilesystemPath({
|
|
347
|
+
workspaceRoot: options.workspaceRoot,
|
|
348
|
+
repoPath,
|
|
349
|
+
});
|
|
350
|
+
requireOwnedPathsWriteReady(options.workspaceRoot, pathState.exists ? [repoPath] : [REFERENCE_INDEX_PATH, repoPath]);
|
|
351
|
+
if (!pathState.exists) {
|
|
352
|
+
return { exists: false, path: repoPath };
|
|
353
|
+
}
|
|
354
|
+
const entry = requireFileEntry({
|
|
355
|
+
workspaceRoot: options.workspaceRoot,
|
|
356
|
+
path: repoPath,
|
|
357
|
+
});
|
|
358
|
+
const content = readBlob(options.workspaceRoot, entry.oid, entry.size);
|
|
359
|
+
return {
|
|
360
|
+
exists: true,
|
|
361
|
+
path: repoPath,
|
|
362
|
+
blob_oid: entry.oid,
|
|
363
|
+
content_sha256: `sha256:${createHash("sha256").update(content).digest("hex")}`,
|
|
364
|
+
size_bytes: entry.size,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
202
367
|
//# sourceMappingURL=reference-files.js.map
|
|
@@ -123,6 +123,30 @@ export type UploadReferenceFileResult = {
|
|
|
123
123
|
index_path: typeof REFERENCE_INDEX_PATH;
|
|
124
124
|
commit_oid?: string;
|
|
125
125
|
};
|
|
126
|
+
export type WriteManagedReferenceSnapshotOptions = WorkspaceViewerOptions & {
|
|
127
|
+
path: string;
|
|
128
|
+
content: Buffer;
|
|
129
|
+
expectedBlobOid?: string;
|
|
130
|
+
note?: string;
|
|
131
|
+
};
|
|
132
|
+
export type WriteManagedReferenceSnapshotResult = {
|
|
133
|
+
disposition: "created" | "updated" | "unchanged";
|
|
134
|
+
file: ViewerFileResult["file"];
|
|
135
|
+
category: ReferenceFileCategory;
|
|
136
|
+
index_path: typeof REFERENCE_INDEX_PATH;
|
|
137
|
+
previous_blob_oid?: string;
|
|
138
|
+
commit_oid?: string;
|
|
139
|
+
};
|
|
140
|
+
export type ManagedReferenceSnapshotState = {
|
|
141
|
+
exists: false;
|
|
142
|
+
path: string;
|
|
143
|
+
} | {
|
|
144
|
+
exists: true;
|
|
145
|
+
path: string;
|
|
146
|
+
blob_oid: string;
|
|
147
|
+
content_sha256: string;
|
|
148
|
+
size_bytes: number;
|
|
149
|
+
};
|
|
126
150
|
export type UpdateReferenceSummariesOptions = WorkspaceViewerOptions & {
|
|
127
151
|
summaries: ReferenceSummaryRecord[];
|
|
128
152
|
};
|