@xfey/tutti 0.1.103 → 0.1.104
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 +9 -4
- package/dist/chat-assistant/index.d.ts +6 -1
- package/dist/chat-assistant/index.js +22 -12
- package/dist/collaboration-ingestion/external-source.d.ts +15 -0
- package/dist/collaboration-ingestion/external-source.js +120 -0
- package/dist/collaboration-ingestion/index.d.ts +5 -0
- package/dist/collaboration-ingestion/index.js +4 -0
- package/dist/collaboration-ingestion/invalidations.d.ts +4 -0
- package/dist/collaboration-ingestion/invalidations.js +15 -0
- package/dist/collaboration-ingestion/main-chat.d.ts +33 -0
- package/dist/collaboration-ingestion/main-chat.js +67 -0
- package/dist/collaboration-ingestion/reference-files.d.ts +29 -0
- package/dist/collaboration-ingestion/reference-files.js +158 -0
- package/dist/collaboration-ingestion/types.d.ts +19 -0
- package/dist/collaboration-ingestion/types.js +2 -0
- package/dist/platform-outbound/coordinator.d.ts +17 -0
- package/dist/platform-outbound/coordinator.js +48 -0
- package/dist/platform-outbound/index.d.ts +5 -0
- package/dist/platform-outbound/index.js +5 -0
- package/dist/platform-outbound/repository.d.ts +30 -0
- package/dist/platform-outbound/repository.js +108 -0
- package/dist/platform-outbound/types.d.ts +38 -0
- package/dist/platform-outbound/types.js +2 -0
- package/dist/platform-outbound/worker.d.ts +36 -0
- package/dist/platform-outbound/worker.js +164 -0
- package/dist/server-shell/cli/cli.js +9 -0
- package/dist/server-shell/cli/errors.d.ts +1 -1
- package/dist/server-shell/cli/host-lifecycle.d.ts +2 -0
- package/dist/server-shell/cli/host-lifecycle.js +34 -1
- package/dist/server-shell/cli/host-relay-connection-manager.js +22 -11
- package/dist/server-shell/cli/host-relay-status.js +1 -0
- package/dist/server-shell/cli/host-runtime-endpoint.js +17 -1
- package/dist/server-shell/cli/host-server-runtime.d.ts +2 -0
- package/dist/server-shell/cli/host-server-runtime.js +88 -14
- package/dist/server-shell/cli/launch-command.d.ts +7 -0
- package/dist/server-shell/cli/launch-command.js +39 -1
- package/dist/server-shell/cli/launch.d.ts +2 -0
- package/dist/server-shell/cli/launch.js +3 -0
- package/dist/server-shell/cli/machine-local.d.ts +20 -0
- package/dist/server-shell/cli/machine-local.js +61 -1
- package/dist/server-shell/cli/managed-host.d.ts +16 -1
- package/dist/server-shell/cli/managed-host.js +191 -23
- package/dist/server-shell/cli/relay-registration.d.ts +4 -1
- package/dist/server-shell/cli/relay-registration.js +36 -7
- package/dist/server-shell/cli/runtime-commands.d.ts +5 -1
- package/dist/server-shell/cli/runtime-commands.js +9 -3
- package/dist/server-shell/http/host-tunnel.d.ts +5 -0
- package/dist/server-shell/http/host-tunnel.js +109 -0
- package/dist/server-shell/http/routes/local-control.d.ts +2 -0
- package/dist/server-shell/http/routes/local-control.js +2 -0
- package/dist/server-shell/http/routes/project-api/invalidations.d.ts +0 -1
- package/dist/server-shell/http/routes/project-api/invalidations.js +0 -3
- package/dist/server-shell/http/routes/project-api/messages-routes.js +32 -63
- package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.d.ts +8 -4
- package/dist/server-shell/http/routes/project-api/openapi.d.ts +8 -4
- package/dist/server-shell/http/routes/project-api/reference-files-routes.js +25 -94
- package/dist/server-shell/http/routes/project-api/reference-files.d.ts +1 -11
- package/dist/server-shell/http/routes/project-api/reference-files.js +2 -87
- package/dist/server-shell/http/routes/project-api/schemas.d.ts +8 -4
- package/dist/server-shell/http/routes/project-api/schemas.js +8 -3
- package/dist/server-shell/http/routes/project-api/session-requirements.js +34 -2
- package/dist/server-shell/http/routes/project-api/types.d.ts +3 -1
- package/dist/server-shell/http/routes/project-api/workspace-projections.js +8 -2
- package/dist/server-shell/local-console/package-update-hosts.js +4 -1
- package/dist/server-shell/local-console/project-service.d.ts +11 -2
- package/dist/server-shell/local-console/project-service.js +36 -8
- package/dist/server-shell/local-console/server.js +6 -1
- package/dist/server-shell/local-console/session.d.ts +4 -0
- package/dist/server-shell/local-console/session.js +14 -0
- package/dist/server-shell/session/relay-session-context.d.ts +12 -1
- package/dist/server-shell/session/relay-session-context.js +19 -1
- package/migrations/0016_external_source_mappings.sql +141 -0
- package/migrations/0017_platform_outbound_intents.sql +31 -0
- package/migrations/README.md +3 -1
- package/node_modules/@tutti/relay-client/dist/host-control.d.ts +26 -1
- package/node_modules/@tutti/relay-client/dist/host-control.js +62 -0
- package/node_modules/@tutti/relay-client/dist/tunnel-frames.js +32 -6
- package/node_modules/@tutti/relay-client/dist/tunnel-types.d.ts +5 -1
- package/node_modules/@tutti/shared/dist/domain/index.d.ts +9 -0
- package/node_modules/@tutti/shared/dist/domain/index.js +14 -0
- package/node_modules/@tutti/shared/dist/ids/index.d.ts +6 -0
- package/node_modules/@tutti/shared/dist/ids/index.js +4 -0
- package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +4 -0
- package/node_modules/@tutti/shared/dist/schemas/api/index.js +2 -0
- package/node_modules/@tutti/shared/dist/schemas/api/local-console-account-handoffs.d.ts +13 -0
- package/node_modules/@tutti/shared/dist/schemas/api/local-console-account-handoffs.js +16 -0
- package/node_modules/@tutti/shared/dist/schemas/api/platform-sessions.d.ts +43 -0
- package/node_modules/@tutti/shared/dist/schemas/api/platform-sessions.js +44 -0
- package/node_modules/@tutti/shared/dist/schemas/domain/index.d.ts +7 -0
- package/node_modules/@tutti/shared/dist/schemas/domain/index.js +10 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/index.d.ts +29 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/index.js +24 -1
- package/node_modules/@tutti/shared/dist/schemas/internal/platform-integration.d.ts +1346 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/platform-integration.js +516 -0
- package/node_modules/@tutti/shared/dist/utils/feishu-diagnostics/index.d.ts +10 -0
- package/node_modules/@tutti/shared/dist/utils/feishu-diagnostics/index.js +19 -0
- package/node_modules/@tutti/shared/dist/utils/index.d.ts +1 -0
- package/node_modules/@tutti/shared/dist/utils/index.js +1 -0
- package/node_modules/@tutti/shared/dist/utils/redaction/index.d.ts +1 -1
- package/node_modules/@tutti/shared/dist/utils/redaction/index.js +18 -0
- package/package.json +1 -1
- package/web/assets/{homepage-motion-scene-CPs4EYQq.js → homepage-motion-scene-DEVrQ9aj.js} +1 -1
- package/web/assets/{index-CxCTqGFO.css → index-6i9Uk7fS.css} +1 -1
- package/web/assets/index-Lxtst2sl.js +69 -0
- package/web/index.html +2 -2
- package/web/assets/index-D3lwP_UR.js +0 -69
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { type ProjectSurface, type ProjectSurfaceProfile } from "@tutti/shared/domain";
|
|
1
2
|
import { type ProjectId, type RelayProjectRef } from "@tutti/shared/ids";
|
|
2
3
|
import { type ConfigureProjectOpenAiProviderResult, type OpenAiProviderCredentialValidator } from "../../providers/openai/index.js";
|
|
3
4
|
import { type FetchLike } from "../cli/host-runtime-endpoint.js";
|
|
5
|
+
import type { HostLaunchRelaySummary } from "../cli/relay-registration.js";
|
|
4
6
|
import { type RuntimeProjectRow } from "../cli/runtime-commands.js";
|
|
5
7
|
import { type LocalConsoleInvocationContext } from "./invocation-context.js";
|
|
6
8
|
import { LocalConsoleOperationCoordinator } from "./operation-coordinator.js";
|
|
7
9
|
export type LocalConsoleProject = {
|
|
8
10
|
project_id: ProjectId;
|
|
11
|
+
surface_profile: ProjectSurfaceProfile;
|
|
9
12
|
display_name: string;
|
|
10
13
|
workspace_path: string;
|
|
11
14
|
status: RuntimeProjectRow["status"];
|
|
@@ -31,8 +34,13 @@ export type LocalConsoleProjectMetadataInput = {
|
|
|
31
34
|
export type LocalConsoleLaunchResult = {
|
|
32
35
|
project: LocalConsoleProject;
|
|
33
36
|
project_created: boolean;
|
|
34
|
-
join_url
|
|
37
|
+
join_url?: string;
|
|
35
38
|
join_token_expires_at?: string;
|
|
39
|
+
feishu_setup?: HostLaunchRelaySummary["feishu_setup"];
|
|
40
|
+
};
|
|
41
|
+
export type LocalConsoleAccountHandoff = {
|
|
42
|
+
token: string;
|
|
43
|
+
expires_at: string;
|
|
36
44
|
};
|
|
37
45
|
export type LocalConsoleOpenResult = {
|
|
38
46
|
project: LocalConsoleProject;
|
|
@@ -79,7 +87,8 @@ export declare class LocalConsoleProjectService {
|
|
|
79
87
|
workspacePath: string;
|
|
80
88
|
project?: LocalConsoleProjectMetadataInput;
|
|
81
89
|
provider?: LocalConsoleProviderInput;
|
|
82
|
-
|
|
90
|
+
surface?: ProjectSurface;
|
|
91
|
+
}, context: LocalConsoleInvocationContext, accountHandoff?: LocalConsoleAccountHandoff): Promise<LocalConsoleLaunchResult>;
|
|
83
92
|
openProject(projectId: string, context: LocalConsoleInvocationContext): Promise<LocalConsoleOpenResult>;
|
|
84
93
|
refreshInvite(projectId: string, context: LocalConsoleInvocationContext): Promise<{
|
|
85
94
|
join_url: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, statSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { readRelayProjectMembershipState } from "@tutti/relay-client";
|
|
4
|
+
import { createProjectSurfaceProfile, } from "@tutti/shared/domain";
|
|
4
5
|
import { relayProjectRouteSegment } from "@tutti/shared/ids";
|
|
5
6
|
import { configureProjectOpenAiProvider, discoverOpenAiModels, isOpenAiApiKey, readOpenAiProviderConfigProjection, validateOpenAiCredential, } from "../../providers/openai/index.js";
|
|
6
7
|
import { formatCliErrorReason, LaunchError } from "../cli/errors.js";
|
|
@@ -8,7 +9,7 @@ import { createRuntimeEndpointProbe, waitForHostShutdown, } from "../cli/host-ru
|
|
|
8
9
|
import { prepareLaunchProject, resolveLaunchLocalContext, resolveRelayUrl } from "../cli/launch.js";
|
|
9
10
|
import { readHostLocalLaunchStatus, requestHostLocalIdleShutdown, rotateHostLocalInvite, } from "../cli/local-control-client.js";
|
|
10
11
|
import { readFreshVisibleJoinUrl } from "../cli/join-url.js";
|
|
11
|
-
import { readHostRegistrationSecret, readMachineProjectBinding, readMachineRuntimeEndpoint, } from "../cli/machine-local.js";
|
|
12
|
+
import { readHostRegistrationSecret, readMachineProjectBinding, readMachineRuntimeEndpoint, writeFeishuOwnerAccountHandoff, } from "../cli/machine-local.js";
|
|
12
13
|
import { spawnDetachedHost, waitForManagedHostReady } from "../cli/managed-host.js";
|
|
13
14
|
import { relayStatusFromLocalStatus } from "../cli/host-relay-status.js";
|
|
14
15
|
import { resolveManagedProjectContext } from "../cli/project-resolver.js";
|
|
@@ -88,9 +89,12 @@ function validateProjectMetadata(input) {
|
|
|
88
89
|
}
|
|
89
90
|
function projectFromRuntimeRow(row, providerStatus, providerModel, metadata) {
|
|
90
91
|
const currentVersion = readCliVersion();
|
|
91
|
-
const openUrl =
|
|
92
|
+
const openUrl = metadata.surfaceProfile.kind === "standalone"
|
|
93
|
+
? projectOpenUrl(metadata.relayUrl, row.relay_project_ref)
|
|
94
|
+
: undefined;
|
|
92
95
|
return {
|
|
93
96
|
project_id: row.project_id,
|
|
97
|
+
surface_profile: metadata.surfaceProfile,
|
|
94
98
|
display_name: row.display_name,
|
|
95
99
|
workspace_path: row.workspace_root,
|
|
96
100
|
status: row.status,
|
|
@@ -213,6 +217,7 @@ export class LocalConsoleProjectService {
|
|
|
213
217
|
...(activityAt === undefined ? {} : { activityAt }),
|
|
214
218
|
...(createdAt === undefined ? {} : { createdAt }),
|
|
215
219
|
relayUrl: binding?.relay_url ?? relayUrlFromJoinUrl(row.join_url) ?? resolveRelayUrl(operation.env),
|
|
220
|
+
surfaceProfile: binding?.surface_profile ?? createProjectSurfaceProfile(),
|
|
216
221
|
});
|
|
217
222
|
});
|
|
218
223
|
}
|
|
@@ -224,7 +229,9 @@ export class LocalConsoleProjectService {
|
|
|
224
229
|
return project;
|
|
225
230
|
}
|
|
226
231
|
async #startManagedHost(options) {
|
|
227
|
-
spawnDetachedHost({
|
|
232
|
+
const startup = spawnDetachedHost({
|
|
233
|
+
tuttiHome: options.tuttiHome,
|
|
234
|
+
projectId: options.projectId,
|
|
228
235
|
workspaceRoot: options.workspaceRoot,
|
|
229
236
|
env: options.env,
|
|
230
237
|
inheritProcessEnv: false,
|
|
@@ -234,6 +241,7 @@ export class LocalConsoleProjectService {
|
|
|
234
241
|
projectId: options.projectId,
|
|
235
242
|
workspaceRoot: options.workspaceRoot,
|
|
236
243
|
fetchImpl: this.#fetchImpl,
|
|
244
|
+
startupAttemptRef: startup.startup_attempt_ref,
|
|
237
245
|
...(options.inviteMode === undefined ? {} : { inviteMode: options.inviteMode }),
|
|
238
246
|
});
|
|
239
247
|
}
|
|
@@ -289,7 +297,7 @@ export class LocalConsoleProjectService {
|
|
|
289
297
|
throw new LocalConsoleProjectError("provider_configuration_required", "This project does not have a valid saved Provider. Configure it in the launch form.");
|
|
290
298
|
}
|
|
291
299
|
}
|
|
292
|
-
async launchProject(input, context) {
|
|
300
|
+
async launchProject(input, context, accountHandoff) {
|
|
293
301
|
const workspacePath = validateWorkspacePath(input.workspacePath);
|
|
294
302
|
const active = this.#launches.get(workspacePath);
|
|
295
303
|
if (active !== undefined) {
|
|
@@ -304,14 +312,15 @@ export class LocalConsoleProjectService {
|
|
|
304
312
|
...(input.provider === undefined
|
|
305
313
|
? {}
|
|
306
314
|
: { provider: validateProvider(input.provider) }),
|
|
307
|
-
|
|
315
|
+
...(input.surface === undefined ? {} : { surface: input.surface }),
|
|
316
|
+
}, context, accountHandoff))
|
|
308
317
|
.finally(() => {
|
|
309
318
|
this.#launches.delete(workspacePath);
|
|
310
319
|
});
|
|
311
320
|
this.#launches.set(workspacePath, launch);
|
|
312
321
|
return await launch;
|
|
313
322
|
}
|
|
314
|
-
async #launchProject(input, context) {
|
|
323
|
+
async #launchProject(input, context, accountHandoff) {
|
|
315
324
|
const operation = this.#operationOptions(context);
|
|
316
325
|
if (input.provider === undefined) {
|
|
317
326
|
try {
|
|
@@ -335,6 +344,7 @@ export class LocalConsoleProjectService {
|
|
|
335
344
|
? {}
|
|
336
345
|
: { projectDescription: input.project.description }),
|
|
337
346
|
}),
|
|
347
|
+
...(input.surface === undefined ? {} : { projectSurface: input.surface }),
|
|
338
348
|
...operation,
|
|
339
349
|
});
|
|
340
350
|
}
|
|
@@ -357,6 +367,16 @@ export class LocalConsoleProjectService {
|
|
|
357
367
|
else if (preparation.provider_status !== "configured") {
|
|
358
368
|
throw new LocalConsoleProjectError("provider_configuration_required", "This project does not have a valid saved Provider. Configure it in the launch form.");
|
|
359
369
|
}
|
|
370
|
+
if (preparation.machine_local.binding.surface_profile.kind === "feishu" &&
|
|
371
|
+
accountHandoff !== undefined) {
|
|
372
|
+
writeFeishuOwnerAccountHandoff({
|
|
373
|
+
tuttiHome: preparation.tutti_home,
|
|
374
|
+
projectId: preparation.project_id,
|
|
375
|
+
token: accountHandoff.token,
|
|
376
|
+
expiresAt: accountHandoff.expires_at,
|
|
377
|
+
now: this.#now(),
|
|
378
|
+
});
|
|
379
|
+
}
|
|
360
380
|
try {
|
|
361
381
|
const ready = await this.#startManagedHost({
|
|
362
382
|
workspaceRoot: preparation.workspace_root,
|
|
@@ -364,7 +384,8 @@ export class LocalConsoleProjectService {
|
|
|
364
384
|
projectId: preparation.project_id,
|
|
365
385
|
env: operation.env,
|
|
366
386
|
});
|
|
367
|
-
if (
|
|
387
|
+
if (preparation.machine_local.binding.surface_profile.kind === "standalone" &&
|
|
388
|
+
ready.join_url === undefined) {
|
|
368
389
|
throw new LocalConsoleProjectError("relay_registration_failed", "The host started, but Relay did not return a visible join link.");
|
|
369
390
|
}
|
|
370
391
|
const projects = await this.listProjects(context);
|
|
@@ -375,10 +396,11 @@ export class LocalConsoleProjectService {
|
|
|
375
396
|
return {
|
|
376
397
|
project,
|
|
377
398
|
project_created: preparation.project_identity_created,
|
|
378
|
-
join_url: ready.join_url,
|
|
399
|
+
...(ready.join_url === undefined ? {} : { join_url: ready.join_url }),
|
|
379
400
|
...(ready.join_token_expires_at === undefined
|
|
380
401
|
? {}
|
|
381
402
|
: { join_token_expires_at: ready.join_token_expires_at }),
|
|
403
|
+
...(ready.feishu_setup === undefined ? {} : { feishu_setup: ready.feishu_setup }),
|
|
382
404
|
};
|
|
383
405
|
}
|
|
384
406
|
catch (error) {
|
|
@@ -388,6 +410,9 @@ export class LocalConsoleProjectService {
|
|
|
388
410
|
async openProject(projectId, context) {
|
|
389
411
|
return await this.#operations.runProjectOperation(async () => {
|
|
390
412
|
const current = await this.#readProject(projectId, context);
|
|
413
|
+
if (current.surface_profile.kind !== "standalone") {
|
|
414
|
+
throw new LocalConsoleProjectError("project_surface_mismatch", "Feishu collaboration projects do not use the Standalone workspace entry.");
|
|
415
|
+
}
|
|
391
416
|
if (current.open_url === undefined) {
|
|
392
417
|
throw new LocalConsoleProjectError("project_open_unavailable", "This project does not have a Relay destination yet.");
|
|
393
418
|
}
|
|
@@ -534,6 +559,9 @@ export class LocalConsoleProjectService {
|
|
|
534
559
|
target: projectId,
|
|
535
560
|
...this.#operationOptions(context),
|
|
536
561
|
});
|
|
562
|
+
if (project.binding?.surface_profile.kind !== "standalone") {
|
|
563
|
+
throw new LocalConsoleProjectError("project_surface_mismatch", "Feishu collaboration projects do not use Standalone invite links.");
|
|
564
|
+
}
|
|
537
565
|
if (project.runtime_endpoint === null) {
|
|
538
566
|
throw new LocalConsoleProjectError("host_not_running", "Launch this project before creating an invite link.");
|
|
539
567
|
}
|
|
@@ -405,6 +405,10 @@ export function createLocalConsoleServer(options) {
|
|
|
405
405
|
model: { type: "string", minLength: 1, maxLength: 240 },
|
|
406
406
|
},
|
|
407
407
|
},
|
|
408
|
+
surface: {
|
|
409
|
+
type: "string",
|
|
410
|
+
enum: ["standalone", "feishu"],
|
|
411
|
+
},
|
|
408
412
|
},
|
|
409
413
|
},
|
|
410
414
|
},
|
|
@@ -414,7 +418,8 @@ export function createLocalConsoleServer(options) {
|
|
|
414
418
|
workspacePath: request.body.workspace_path,
|
|
415
419
|
...(request.body.project === undefined ? {} : { project: request.body.project }),
|
|
416
420
|
...(request.body.provider === undefined ? {} : { provider: request.body.provider }),
|
|
417
|
-
|
|
421
|
+
...(request.body.surface === undefined ? {} : { surface: request.body.surface }),
|
|
422
|
+
}, session.context, session.accountHandoff);
|
|
418
423
|
});
|
|
419
424
|
app.post(`${LOCAL_CONSOLE_API_BASE}/projects/:projectId/open`, async (request) => {
|
|
420
425
|
const session = requireSession(request, true);
|
|
@@ -13,6 +13,10 @@ export type ExchangedLocalConsoleSession = {
|
|
|
13
13
|
export type ReadLocalConsoleSession = {
|
|
14
14
|
accountReady: boolean;
|
|
15
15
|
accountDisplayName?: string;
|
|
16
|
+
accountHandoff?: {
|
|
17
|
+
token: string;
|
|
18
|
+
expires_at: string;
|
|
19
|
+
};
|
|
16
20
|
csrfToken: string;
|
|
17
21
|
context: LocalConsoleInvocationContext;
|
|
18
22
|
};
|
|
@@ -111,6 +111,12 @@ export class LocalConsoleSessionRegistry {
|
|
|
111
111
|
...(normalizedAccountDisplayName === undefined
|
|
112
112
|
? {}
|
|
113
113
|
: { accountDisplayName: normalizedAccountDisplayName }),
|
|
114
|
+
...(accessToken.accountReady
|
|
115
|
+
? {
|
|
116
|
+
accountHandoffToken: token,
|
|
117
|
+
accountHandoffExpiresAt: accessToken.expiresAt,
|
|
118
|
+
}
|
|
119
|
+
: {}),
|
|
114
120
|
hash: contextHash,
|
|
115
121
|
expiresAt: session.expiresAt,
|
|
116
122
|
sessionHash,
|
|
@@ -147,6 +153,14 @@ export class LocalConsoleSessionRegistry {
|
|
|
147
153
|
...(context.accountDisplayName === undefined
|
|
148
154
|
? {}
|
|
149
155
|
: { accountDisplayName: context.accountDisplayName }),
|
|
156
|
+
...(context.accountHandoffToken === undefined || context.accountHandoffExpiresAt === undefined
|
|
157
|
+
? {}
|
|
158
|
+
: {
|
|
159
|
+
accountHandoff: {
|
|
160
|
+
token: context.accountHandoffToken,
|
|
161
|
+
expires_at: new Date(context.accountHandoffExpiresAt).toISOString(),
|
|
162
|
+
},
|
|
163
|
+
}),
|
|
150
164
|
csrfToken: session.csrfToken,
|
|
151
165
|
context: context.context,
|
|
152
166
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type RelaySessionContext } from "@tutti/shared/schemas/internal";
|
|
1
|
+
import { type FeishuPlatformSessionContext, type RelaySessionContext } from "@tutti/shared/schemas/internal";
|
|
2
2
|
export declare const RELAY_SESSION_CONTEXT_METADATA_KEY = "relay_session_context";
|
|
3
|
+
export declare const FEISHU_PLATFORM_SESSION_CONTEXT_METADATA_KEY = "feishu_platform_session_context";
|
|
3
4
|
export declare const TRUSTED_RELAY_METADATA_REQUEST_HEADER = "x-tutti-internal-relay-metadata-id";
|
|
4
5
|
export type RelaySessionContextReadResult = {
|
|
5
6
|
kind: "present";
|
|
@@ -9,6 +10,14 @@ export type RelaySessionContextReadResult = {
|
|
|
9
10
|
} | {
|
|
10
11
|
kind: "invalid";
|
|
11
12
|
};
|
|
13
|
+
export type FeishuPlatformSessionContextReadResult = {
|
|
14
|
+
kind: "present";
|
|
15
|
+
context: FeishuPlatformSessionContext;
|
|
16
|
+
} | {
|
|
17
|
+
kind: "missing";
|
|
18
|
+
} | {
|
|
19
|
+
kind: "invalid";
|
|
20
|
+
};
|
|
12
21
|
export declare class RelaySessionInvalidError extends Error {
|
|
13
22
|
readonly statusCode = 401;
|
|
14
23
|
readonly code = "relay_session_invalid";
|
|
@@ -22,4 +31,6 @@ export declare class TrustedRelaySessionMetadataStore {
|
|
|
22
31
|
export declare function createTrustedRelaySessionMetadataStore(): TrustedRelaySessionMetadataStore;
|
|
23
32
|
export declare function readRelaySessionContext(metadata: unknown): RelaySessionContextReadResult;
|
|
24
33
|
export declare function requireRelaySessionContext(metadata: unknown): RelaySessionContext;
|
|
34
|
+
export declare function readFeishuPlatformSessionContext(metadata: unknown): FeishuPlatformSessionContextReadResult;
|
|
35
|
+
export declare function requireFeishuPlatformSessionContext(metadata: unknown): FeishuPlatformSessionContext;
|
|
25
36
|
//# sourceMappingURL=relay-session-context.d.ts.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { parseRelaySessionContext } from "@tutti/shared/schemas/internal";
|
|
2
|
+
import { parseFeishuPlatformSessionContext, parseRelaySessionContext, } from "@tutti/shared/schemas/internal";
|
|
3
3
|
export const RELAY_SESSION_CONTEXT_METADATA_KEY = "relay_session_context";
|
|
4
|
+
export const FEISHU_PLATFORM_SESSION_CONTEXT_METADATA_KEY = "feishu_platform_session_context";
|
|
4
5
|
export const TRUSTED_RELAY_METADATA_REQUEST_HEADER = "x-tutti-internal-relay-metadata-id";
|
|
5
6
|
export class RelaySessionInvalidError extends Error {
|
|
6
7
|
statusCode = 401;
|
|
@@ -46,4 +47,21 @@ export function requireRelaySessionContext(metadata) {
|
|
|
46
47
|
}
|
|
47
48
|
return result.context;
|
|
48
49
|
}
|
|
50
|
+
export function readFeishuPlatformSessionContext(metadata) {
|
|
51
|
+
if (!isRecord(metadata) || !(FEISHU_PLATFORM_SESSION_CONTEXT_METADATA_KEY in metadata)) {
|
|
52
|
+
return { kind: "missing" };
|
|
53
|
+
}
|
|
54
|
+
const context = parseFeishuPlatformSessionContext(metadata[FEISHU_PLATFORM_SESSION_CONTEXT_METADATA_KEY]);
|
|
55
|
+
if (context === null) {
|
|
56
|
+
return { kind: "invalid" };
|
|
57
|
+
}
|
|
58
|
+
return { kind: "present", context };
|
|
59
|
+
}
|
|
60
|
+
export function requireFeishuPlatformSessionContext(metadata) {
|
|
61
|
+
const result = readFeishuPlatformSessionContext(metadata);
|
|
62
|
+
if (result.kind !== "present") {
|
|
63
|
+
throw new RelaySessionInvalidError();
|
|
64
|
+
}
|
|
65
|
+
return result.context;
|
|
66
|
+
}
|
|
49
67
|
//# sourceMappingURL=relay-session-context.js.map
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
CREATE TABLE external_source_mappings (
|
|
2
|
+
platform TEXT NOT NULL,
|
|
3
|
+
installation_ref TEXT NOT NULL,
|
|
4
|
+
conversation_ref TEXT NOT NULL,
|
|
5
|
+
thread_ref TEXT,
|
|
6
|
+
parent_message_ref TEXT,
|
|
7
|
+
external_message_ref TEXT NOT NULL,
|
|
8
|
+
message_id TEXT NOT NULL UNIQUE,
|
|
9
|
+
binding_ref TEXT NOT NULL,
|
|
10
|
+
receipt_key TEXT NOT NULL UNIQUE,
|
|
11
|
+
external_subject_ref TEXT NOT NULL,
|
|
12
|
+
author_account_ref TEXT NOT NULL,
|
|
13
|
+
author_display_name TEXT NOT NULL,
|
|
14
|
+
external_created_at TEXT NOT NULL,
|
|
15
|
+
accepted_at TEXT NOT NULL,
|
|
16
|
+
initial_revision_ref TEXT NOT NULL,
|
|
17
|
+
initial_observed_at TEXT NOT NULL,
|
|
18
|
+
content_sha256 TEXT NOT NULL CHECK (
|
|
19
|
+
content_sha256 GLOB 'sha256:[0-9a-f]*' AND length(content_sha256) = 71
|
|
20
|
+
),
|
|
21
|
+
adapter_version TEXT NOT NULL,
|
|
22
|
+
latest_revision_ref TEXT NOT NULL,
|
|
23
|
+
latest_external_updated_at TEXT NOT NULL,
|
|
24
|
+
external_deleted_at TEXT,
|
|
25
|
+
PRIMARY KEY (platform, installation_ref, external_message_ref),
|
|
26
|
+
FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE RESTRICT
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
CREATE INDEX external_source_mappings_conversation_order
|
|
30
|
+
ON external_source_mappings (
|
|
31
|
+
platform,
|
|
32
|
+
installation_ref,
|
|
33
|
+
conversation_ref,
|
|
34
|
+
accepted_at,
|
|
35
|
+
message_id
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
CREATE TABLE external_source_revision_observations (
|
|
39
|
+
platform TEXT NOT NULL,
|
|
40
|
+
installation_ref TEXT NOT NULL,
|
|
41
|
+
external_message_ref TEXT NOT NULL,
|
|
42
|
+
revision_ref TEXT NOT NULL,
|
|
43
|
+
observation_kind TEXT NOT NULL CHECK (
|
|
44
|
+
observation_kind IN ('created', 'edited', 'deleted')
|
|
45
|
+
),
|
|
46
|
+
content_sha256 TEXT CHECK (
|
|
47
|
+
content_sha256 IS NULL
|
|
48
|
+
OR (content_sha256 GLOB 'sha256:[0-9a-f]*' AND length(content_sha256) = 71)
|
|
49
|
+
),
|
|
50
|
+
external_updated_at TEXT NOT NULL,
|
|
51
|
+
observed_at TEXT NOT NULL,
|
|
52
|
+
adapter_version TEXT NOT NULL,
|
|
53
|
+
PRIMARY KEY (
|
|
54
|
+
platform,
|
|
55
|
+
installation_ref,
|
|
56
|
+
external_message_ref,
|
|
57
|
+
revision_ref
|
|
58
|
+
),
|
|
59
|
+
FOREIGN KEY (platform, installation_ref, external_message_ref)
|
|
60
|
+
REFERENCES external_source_mappings (
|
|
61
|
+
platform,
|
|
62
|
+
installation_ref,
|
|
63
|
+
external_message_ref
|
|
64
|
+
)
|
|
65
|
+
ON DELETE RESTRICT,
|
|
66
|
+
CHECK (observation_kind != 'created' OR content_sha256 IS NOT NULL)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
CREATE INDEX external_source_revision_observations_order
|
|
70
|
+
ON external_source_revision_observations (
|
|
71
|
+
platform,
|
|
72
|
+
installation_ref,
|
|
73
|
+
external_message_ref,
|
|
74
|
+
external_updated_at,
|
|
75
|
+
revision_ref
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
CREATE TRIGGER external_source_mapping_identity_immutable
|
|
79
|
+
BEFORE UPDATE OF
|
|
80
|
+
platform,
|
|
81
|
+
installation_ref,
|
|
82
|
+
conversation_ref,
|
|
83
|
+
thread_ref,
|
|
84
|
+
parent_message_ref,
|
|
85
|
+
external_message_ref,
|
|
86
|
+
message_id,
|
|
87
|
+
binding_ref,
|
|
88
|
+
receipt_key,
|
|
89
|
+
external_subject_ref,
|
|
90
|
+
author_account_ref,
|
|
91
|
+
author_display_name,
|
|
92
|
+
external_created_at,
|
|
93
|
+
accepted_at,
|
|
94
|
+
initial_revision_ref,
|
|
95
|
+
initial_observed_at,
|
|
96
|
+
content_sha256,
|
|
97
|
+
adapter_version
|
|
98
|
+
ON external_source_mappings
|
|
99
|
+
BEGIN
|
|
100
|
+
SELECT RAISE(ABORT, 'external source mapping evidence is immutable');
|
|
101
|
+
END;
|
|
102
|
+
|
|
103
|
+
CREATE TRIGGER external_source_mapping_no_delete
|
|
104
|
+
BEFORE DELETE ON external_source_mappings
|
|
105
|
+
BEGIN
|
|
106
|
+
SELECT RAISE(ABORT, 'external source mapping evidence is immutable');
|
|
107
|
+
END;
|
|
108
|
+
|
|
109
|
+
CREATE TRIGGER external_source_revision_observation_no_update
|
|
110
|
+
BEFORE UPDATE ON external_source_revision_observations
|
|
111
|
+
BEGIN
|
|
112
|
+
SELECT RAISE(ABORT, 'external source revision observations are append-only');
|
|
113
|
+
END;
|
|
114
|
+
|
|
115
|
+
CREATE TRIGGER external_source_revision_observation_no_delete
|
|
116
|
+
BEFORE DELETE ON external_source_revision_observations
|
|
117
|
+
BEGIN
|
|
118
|
+
SELECT RAISE(ABORT, 'external source revision observations are append-only');
|
|
119
|
+
END;
|
|
120
|
+
|
|
121
|
+
CREATE TRIGGER external_source_message_evidence_immutable
|
|
122
|
+
BEFORE UPDATE OF
|
|
123
|
+
scope_kind,
|
|
124
|
+
clarification_round_id,
|
|
125
|
+
author_kind,
|
|
126
|
+
author_ref,
|
|
127
|
+
author_display_name,
|
|
128
|
+
author_avatar_json,
|
|
129
|
+
message_kind,
|
|
130
|
+
body,
|
|
131
|
+
refs_json,
|
|
132
|
+
created_at
|
|
133
|
+
ON messages
|
|
134
|
+
WHEN EXISTS (
|
|
135
|
+
SELECT 1
|
|
136
|
+
FROM external_source_mappings
|
|
137
|
+
WHERE external_source_mappings.message_id = OLD.id
|
|
138
|
+
)
|
|
139
|
+
BEGIN
|
|
140
|
+
SELECT RAISE(ABORT, 'external source message evidence is immutable');
|
|
141
|
+
END;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
CREATE TABLE platform_outbound_intents (
|
|
2
|
+
intent_ref TEXT PRIMARY KEY CHECK (intent_ref GLOB 'idem_*'),
|
|
3
|
+
platform TEXT NOT NULL CHECK (platform = 'feishu'),
|
|
4
|
+
installation_ref TEXT NOT NULL,
|
|
5
|
+
binding_ref TEXT NOT NULL,
|
|
6
|
+
trigger_message_id TEXT NOT NULL,
|
|
7
|
+
canonical_message_id TEXT NOT NULL UNIQUE,
|
|
8
|
+
disposition TEXT NOT NULL CHECK (
|
|
9
|
+
disposition IN ('pending', 'sending', 'retry_wait', 'handed_off', 'terminal')
|
|
10
|
+
),
|
|
11
|
+
attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
|
|
12
|
+
next_attempt_at TEXT,
|
|
13
|
+
last_attempt_at TEXT,
|
|
14
|
+
delivery_ref TEXT,
|
|
15
|
+
error_code TEXT,
|
|
16
|
+
created_at TEXT NOT NULL,
|
|
17
|
+
updated_at TEXT NOT NULL,
|
|
18
|
+
handed_off_at TEXT,
|
|
19
|
+
FOREIGN KEY (trigger_message_id) REFERENCES messages(id) ON DELETE RESTRICT,
|
|
20
|
+
FOREIGN KEY (canonical_message_id) REFERENCES messages(id) ON DELETE RESTRICT,
|
|
21
|
+
CHECK (
|
|
22
|
+
(disposition = 'handed_off' AND delivery_ref IS NOT NULL AND handed_off_at IS NOT NULL)
|
|
23
|
+
OR disposition != 'handed_off'
|
|
24
|
+
)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
CREATE INDEX platform_outbound_intents_due
|
|
28
|
+
ON platform_outbound_intents (disposition, next_attempt_at, created_at, intent_ref);
|
|
29
|
+
|
|
30
|
+
CREATE INDEX platform_outbound_intents_trigger
|
|
31
|
+
ON platform_outbound_intents (trigger_message_id, created_at, intent_ref);
|
package/migrations/README.md
CHANGED
|
@@ -28,10 +28,12 @@
|
|
|
28
28
|
- `0013_scratchpad_receipt_sources.sql`:新增当前 Scratchpad snapshot 与 submitted receipt 的 source message id 引用表,供 Timeline Task Accepted 追溯原始聊天依据。
|
|
29
29
|
- `0014_scratchpad_task_compile_context.sql`:把 Scratchpad source state 收敛为纯主群聊周期 cursor,新增 durable active Task Compile Context 与完整 source refs,并把 Current Scratchpad / receipt sources 限定为主群聊。该开发阶段 migration 不迁移旧调试数据,已有本地开发数据库需要 reset / recreate。
|
|
30
30
|
- `0015_control_plane_intents.sql`:新增 Control Plane durable dispatch intent,保存 bounded kind / owner / dedupe / lane、pending-to-running claim lease 与 terminal 状态,为 Clarification、Task Compile、Scratchpad、Reference Summary 和 Run 提供可恢复的 runtime handoff;同时用 immutable pending Task Compile submission row 固化 context capture 前的 workflow / main-chat cursor boundary,不保存 prompt、消息正文、provider output、secret 或 host path。
|
|
31
|
+
- `0016_external_source_mappings.sql`:新增external installation / message identity到既有`messages.id`的一对一mapping和append-only revision observation;mapped message evidence、mapping identity与observations由trigger / FK保护,不保存raw platform payload、credential或短期URL。
|
|
32
|
+
- `0017_platform_outbound_intents.sql`:新增Host-owned platform outbound intent;只引用canonical / trigger message与installation / binding locator,保存handoff attempt和terminal状态,不复制回复正文或保存conversation ref / credential。
|
|
31
33
|
|
|
32
34
|
## 当前实现状态
|
|
33
35
|
|
|
34
|
-
- `0001_init.sql` 至 `
|
|
36
|
+
- `0001_init.sql` 至 `0017_platform_outbound_intents.sql` 已存在,代表当前 host project SQLite schema contract。`0014` 明确采用开发数据库重建策略,不提供旧 Scratchpad source/cursor 数据兼容;`0016`–`0017`是additive migration,不要求为既有internal Chat message或agent response回填external mapping / outbound intent。
|
|
35
37
|
- SQLite adapter、migration runner、checksum 校验、store metadata 初始化、transaction helper 与 command recovery scan helper 已实现。
|
|
36
38
|
- 协作 repository 与 Control Plane 已实现第一批业务读写;Phase 7 首批已复用 `0001_init.sql` 中的 `tasks.latest_run_result_id`、`task_details.result_json` 与 `run_results` 表完成 Run result / checks / promotion 摘要持久化。Control Plane recovery resolver 与 task-bound continuation / retry 复用现有 schema;新 Run 的 accepted checkpoint 业务 truth 继续写入最终 Run result / task detail result,Git tag / commit trailer 承担 crash recovery evidence。Timeline 默认从 `tasks`、`run_results` 与历史主群聊 `refs.worklist_feedback` 投影任务级审计节点,当前 task terminal Chat feedback 写入 `refs.task_result` 消息;`project_timeline_events` 只保留缺少稳定 record 的轻量可观测性事件和历史 reconcile 兼容记录。project workspace、working diff 与 ignored runtime state 都不进入 SQLite truth。`provider_usage_events` 只保存 token 计数、调用来源、模型名、记录时间和可空 anchor,不保存 provider secret、prompt 或 raw response。store 基础设施本身已经可以创建并迁移 `tutti.sqlite`。
|
|
37
39
|
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { type HostConnectionRef, type IdempotencyKey, type ProjectId, type RelayProjectRef, type RelayUploadRef } from "@tutti/shared/ids";
|
|
2
|
+
import type { ProjectSurfaceProfile } from "@tutti/shared/domain";
|
|
2
3
|
import type { RelayUploadPurpose, Sha256Digest } from "@tutti/shared/schemas/api";
|
|
4
|
+
import { type FeishuSetupProjection, type PlatformOutboundHandoffResponse, type PlatformOutboundTextIntent } from "@tutti/shared/schemas/internal";
|
|
3
5
|
export type RelayHostControlFetch = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
|
4
6
|
export type RelayProjectProjection = {
|
|
5
7
|
relay_project_ref: RelayProjectRef;
|
|
6
8
|
display_name: string;
|
|
7
9
|
host_status: "online" | "offline" | "unknown";
|
|
10
|
+
surface_profile?: ProjectSurfaceProfile;
|
|
8
11
|
};
|
|
9
12
|
export type HostConnectionProjection = {
|
|
10
13
|
host_connection_ref: HostConnectionRef;
|
|
@@ -31,6 +34,7 @@ export type RegisterRelayHostConnectionPayload = {
|
|
|
31
34
|
secret: string;
|
|
32
35
|
};
|
|
33
36
|
display_name: string;
|
|
37
|
+
surface_profile?: ProjectSurfaceProfile;
|
|
34
38
|
host: {
|
|
35
39
|
server_version: string;
|
|
36
40
|
host_project_api_base_path: "/api/v1";
|
|
@@ -39,17 +43,24 @@ export type RegisterRelayHostConnectionPayload = {
|
|
|
39
43
|
provider_config_query: boolean;
|
|
40
44
|
browser_secret_write: false;
|
|
41
45
|
repo_viewer: boolean;
|
|
46
|
+
integration_source_ingestion_v1?: boolean;
|
|
47
|
+
integration_outbound_v1?: boolean;
|
|
42
48
|
};
|
|
43
49
|
};
|
|
44
50
|
join_token?: {
|
|
45
51
|
mode: "reuse_active" | "rotate";
|
|
46
52
|
ttl_seconds?: number;
|
|
47
53
|
};
|
|
54
|
+
owner_bootstrap?: {
|
|
55
|
+
kind: "local_console_account_handoff_v1";
|
|
56
|
+
account_handoff_token: string;
|
|
57
|
+
};
|
|
48
58
|
};
|
|
49
59
|
export type RegisterRelayHostConnectionResult = {
|
|
50
60
|
relay_project: RelayProjectProjection;
|
|
51
61
|
host_connection: HostConnectionProjection;
|
|
52
|
-
join_token
|
|
62
|
+
join_token?: JoinTokenProjection;
|
|
63
|
+
feishu_setup?: FeishuSetupProjection;
|
|
53
64
|
host_connection_token: string;
|
|
54
65
|
};
|
|
55
66
|
export type RegisterRelayHostConnectionResponse = {
|
|
@@ -122,18 +133,31 @@ export type ResolveRelayUploadResponse = {
|
|
|
122
133
|
export type CompleteRelayUploadResponse = {
|
|
123
134
|
upload: RelayUploadProjection;
|
|
124
135
|
};
|
|
136
|
+
export type DeliverRelayPlatformOutboundOptions = {
|
|
137
|
+
relayUrl: string;
|
|
138
|
+
projectId: ProjectId;
|
|
139
|
+
relayProjectRef: RelayProjectRef;
|
|
140
|
+
hostConnectionRef: HostConnectionRef;
|
|
141
|
+
hostRegistrationSecret: string;
|
|
142
|
+
intent: PlatformOutboundTextIntent;
|
|
143
|
+
fetch?: RelayHostControlFetch;
|
|
144
|
+
timeoutMs?: number;
|
|
145
|
+
signal?: AbortSignal;
|
|
146
|
+
};
|
|
125
147
|
export type RegisterRelayHostConnectionOptions = {
|
|
126
148
|
relayUrl: string;
|
|
127
149
|
projectId: ProjectId;
|
|
128
150
|
hostRegistrationSecret: string;
|
|
129
151
|
displayName: string;
|
|
130
152
|
serverVersion: string;
|
|
153
|
+
surfaceProfile?: ProjectSurfaceProfile;
|
|
131
154
|
commandId?: IdempotencyKey;
|
|
132
155
|
issuedAt?: Date;
|
|
133
156
|
joinToken?: {
|
|
134
157
|
mode: "reuse_active" | "rotate";
|
|
135
158
|
ttlSeconds?: number;
|
|
136
159
|
};
|
|
160
|
+
accountHandoffToken?: string;
|
|
137
161
|
capabilities?: Partial<RegisterRelayHostConnectionPayload["host"]["capabilities"]>;
|
|
138
162
|
fetch?: RelayHostControlFetch;
|
|
139
163
|
timeoutMs?: number;
|
|
@@ -198,4 +222,5 @@ export declare function archiveRelayProject(options: ArchiveRelayProjectOptions)
|
|
|
198
222
|
export declare function readRelayProjectMembershipState(options: ReadRelayProjectMembershipStateOptions): Promise<ReadRelayProjectMembershipStateResponse>;
|
|
199
223
|
export declare function resolveRelayUpload(options: ResolveRelayUploadOptions): Promise<ResolveRelayUploadResponse>;
|
|
200
224
|
export declare function completeRelayUpload(options: CompleteRelayUploadOptions): Promise<CompleteRelayUploadResponse>;
|
|
225
|
+
export declare function deliverRelayPlatformOutbound(options: DeliverRelayPlatformOutboundOptions): Promise<PlatformOutboundHandoffResponse>;
|
|
201
226
|
//# sourceMappingURL=host-control.d.ts.map
|