@xfey/tutti 0.1.119 → 0.1.120
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/dist/approvals/decision-command.d.ts +2 -2
- package/dist/collaboration-state/clarification-snapshot.d.ts +9 -9
- package/dist/collaboration-state/task-compile-context.d.ts +1 -1
- package/dist/control-plane/workflows/openai.d.ts +3 -4
- package/dist/control-plane/workflows/openai.js +3 -2
- package/dist/providers/openai/app-server/invocation-coordinator.d.ts +1 -0
- package/dist/providers/openai/app-server/invocation-coordinator.js +4 -1
- package/dist/providers/openai/app-server/invocation-deadline.d.ts +3 -0
- package/dist/providers/openai/app-server/invocation-deadline.js +15 -0
- package/dist/providers/openai/app-server/read-only-procedure.d.ts +4 -2
- package/dist/providers/openai/app-server/read-only-procedure.js +16 -0
- package/dist/providers/openai/app-server/workspace-write-run.d.ts +4 -2
- package/dist/providers/openai/app-server/workspace-write-run.js +16 -0
- package/dist/providers/openai/chat-assistant.d.ts +3 -4
- package/dist/providers/openai/chat-assistant.js +2 -1
- package/dist/providers/openai/execution-access.d.ts +17 -0
- package/dist/providers/openai/execution-access.js +13 -0
- package/dist/providers/openai/sdk-procedure-runner.d.ts +2 -2
- package/dist/providers/openai/sdk-procedure-runner.js +17 -0
- package/dist/run-pipeline/artifact-applicability.d.ts +3 -4
- package/dist/run-pipeline/artifact-applicability.js +2 -1
- package/dist/run-pipeline/openai.d.ts +3 -4
- package/dist/run-pipeline/openai.js +3 -2
- package/dist/run-pipeline/task-run-invocation.js +6 -12
- package/dist/server-shell/cli/cli.js +3 -0
- package/dist/server-shell/cli/host-lifecycle.js +1 -0
- package/dist/server-shell/cli/host-server-runtime.d.ts +1 -0
- package/dist/server-shell/cli/host-server-runtime.js +83 -34
- package/dist/server-shell/cli/launch-command.js +1 -1
- package/dist/server-shell/cli/launch.d.ts +7 -3
- package/dist/server-shell/cli/launch.js +23 -4
- package/dist/server-shell/cli/machine-local.d.ts +9 -0
- package/dist/server-shell/cli/machine-local.js +60 -6
- package/dist/server-shell/cli/project-resolver.d.ts +2 -2
- package/dist/server-shell/cli/project-resolver.js +6 -3
- package/dist/server-shell/cli/relay-registration.js +38 -1
- package/dist/server-shell/http/routes/local-control.d.ts +1 -1
- package/dist/server-shell/http/routes/local-control.js +2 -46
- package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.d.ts +47 -137
- package/dist/server-shell/http/routes/project-api/openapi.d.ts +47 -137
- package/dist/server-shell/http/routes/project-api/schemas.d.ts +25 -136
- package/dist/server-shell/http/routes/project-api/schemas.js +4 -58
- package/dist/server-shell/http/routes/project-api/types.d.ts +4 -0
- package/dist/server-shell/local-console/managed-project-routes.d.ts +9 -0
- package/dist/server-shell/local-console/managed-project-routes.js +120 -0
- package/dist/server-shell/local-console/managed-project-service.d.ts +151 -0
- package/dist/server-shell/local-console/managed-project-service.js +163 -0
- package/dist/server-shell/local-console/operation-coordinator.d.ts +1 -1
- package/dist/server-shell/local-console/package-update-hosts.js +2 -1
- package/dist/server-shell/local-console/project-service.d.ts +5 -1
- package/dist/server-shell/local-console/project-service.js +63 -14
- package/dist/server-shell/local-console/server.js +22 -2
- package/dist/server-shell/managed-access/execution-binding.d.ts +9 -0
- package/dist/server-shell/managed-access/execution-binding.js +22 -0
- package/dist/server-shell/managed-access/existing-project.d.ts +15 -0
- package/dist/server-shell/managed-access/existing-project.js +41 -0
- package/dist/server-shell/managed-access/model-control-client.d.ts +66 -0
- package/dist/server-shell/managed-access/model-control-client.js +114 -0
- package/dist/server-shell/managed-access/private-store.d.ts +19 -0
- package/dist/server-shell/managed-access/private-store.js +275 -0
- package/dist/server-shell/managed-access/project-provider.d.ts +18 -0
- package/dist/server-shell/managed-access/project-provider.js +30 -0
- package/dist/server-shell/managed-access/records.d.ts +108 -0
- package/dist/server-shell/managed-access/records.js +73 -0
- package/dist/server-shell/managed-access/runners.d.ts +11 -0
- package/dist/server-shell/managed-access/runners.js +103 -0
- package/dist/server-shell/managed-access/runtime-records.d.ts +110 -0
- package/dist/server-shell/managed-access/runtime-records.js +65 -0
- package/dist/server-shell/managed-access/runtime.d.ts +48 -0
- package/dist/server-shell/managed-access/runtime.js +301 -0
- package/dist/server-shell/managed-access/state.d.ts +54 -0
- package/dist/server-shell/managed-access/state.js +287 -0
- package/dist/server-shell/managed-access/transport.d.ts +48 -0
- package/dist/server-shell/managed-access/transport.js +38 -0
- package/dist/server-shell/managed-access/workspace.d.ts +10 -0
- package/dist/server-shell/managed-access/workspace.js +69 -0
- package/dist/server-shell/smoke/e2e-client.d.ts +8 -3
- package/dist/server-shell/smoke/e2e-client.js +11 -11
- package/node_modules/@tutti/relay-client/dist/host-control.d.ts +4 -1
- package/node_modules/@tutti/relay-client/dist/host-control.js +26 -3
- package/node_modules/@tutti/relay-client/dist/index.d.ts +1 -0
- package/node_modules/@tutti/relay-client/dist/index.js +1 -0
- package/node_modules/@tutti/relay-client/dist/project-authority.d.ts +75 -0
- package/node_modules/@tutti/relay-client/dist/project-authority.js +140 -0
- package/node_modules/@tutti/shared/dist/ids/index.d.ts +7 -0
- package/node_modules/@tutti/shared/dist/ids/index.js +7 -0
- package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/managed-account.d.ts +35 -0
- package/node_modules/@tutti/shared/dist/schemas/api/managed-account.js +41 -0
- package/node_modules/@tutti/shared/dist/schemas/api/provider-config.d.ts +8 -4
- package/node_modules/@tutti/shared/dist/schemas/api/provider-config.js +6 -0
- package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.d.ts +4 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/creation-validation.d.ts +28 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/creation-validation.js +42 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/index.d.ts +5 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/index.js +5 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/managed-account.d.ts +12 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/managed-account.js +13 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/managed-models.d.ts +17 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/managed-models.js +17 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/managed-runtime.d.ts +116 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/managed-runtime.js +87 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/project-authority.d.ts +120 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/project-authority.js +110 -0
- package/package.json +1 -1
- package/web/assets/{homepage-motion-scene-B6lCCHBq.js → homepage-motion-scene-DyMFsYmT.js} +1 -1
- package/web/assets/index-DSlecDXC.js +70 -0
- package/web/index.html +1 -1
- package/web/assets/index-DepsYMLl.js +0 -70
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { Value } from "@sinclair/typebox/value";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import { IdempotencyKeySchema, ProjectIdSchema } from "@tutti/shared/schemas/domain";
|
|
4
|
+
import { ManagedTierSchema } from "@tutti/shared/schemas/internal";
|
|
5
|
+
import { resolveLaunchLocalContext } from "../cli/launch.js";
|
|
6
|
+
import { NativeManagedState } from "../managed-access/state.js";
|
|
7
|
+
import { findNativeManagedProject } from "../managed-access/state.js";
|
|
8
|
+
import { readExistingManagedTarget } from "../managed-access/existing-project.js";
|
|
9
|
+
import { ManagedLocalStateError } from "../managed-access/private-store.js";
|
|
10
|
+
import { reconcileNativePreparation, validateNativePreparation, } from "../managed-access/transport.js";
|
|
11
|
+
import { createLocalConsoleOperationEnvironment, } from "./invocation-context.js";
|
|
12
|
+
export const ManagedDraftInputSchema = Type.Object({
|
|
13
|
+
request_ref: IdempotencyKeySchema,
|
|
14
|
+
workspace_path: Type.String({ minLength: 1, maxLength: 4096 }),
|
|
15
|
+
display_name: Type.String({ minLength: 1, maxLength: 80 }),
|
|
16
|
+
description: Type.Optional(Type.String({ maxLength: 160 })),
|
|
17
|
+
surface: Type.Union([Type.Literal("standalone"), Type.Literal("feishu")]),
|
|
18
|
+
tier: ManagedTierSchema,
|
|
19
|
+
existing_project_id: Type.Optional(ProjectIdSchema),
|
|
20
|
+
}, { additionalProperties: false });
|
|
21
|
+
function projection(row) {
|
|
22
|
+
return {
|
|
23
|
+
request_ref: row.request_ref,
|
|
24
|
+
project_id: row.project_id,
|
|
25
|
+
display_name: row.display_name,
|
|
26
|
+
workspace_path: row.workspace_root,
|
|
27
|
+
...(row.description === undefined ? {} : { description: row.description }),
|
|
28
|
+
surface: row.surface,
|
|
29
|
+
tier: row.tier,
|
|
30
|
+
...(row.existing_relay_project_ref === undefined
|
|
31
|
+
? {}
|
|
32
|
+
: { existing_project_id: row.project_id }),
|
|
33
|
+
state: row.state,
|
|
34
|
+
expires_at: row.expires_at,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// Native orchestration only. No project SQLite, Git writes, model key or browser account authority.
|
|
38
|
+
export class LocalConsoleManagedProjectService {
|
|
39
|
+
options;
|
|
40
|
+
constructor(options) {
|
|
41
|
+
this.options = options;
|
|
42
|
+
}
|
|
43
|
+
environment(context) {
|
|
44
|
+
return createLocalConsoleOperationEnvironment({
|
|
45
|
+
serviceEnvironment: this.options.serviceEnvironment ?? process.env,
|
|
46
|
+
invocationEnvironment: context.environment,
|
|
47
|
+
tuttiHome: this.options.tuttiHome,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
state(context) {
|
|
51
|
+
const env = this.environment(context);
|
|
52
|
+
return new NativeManagedState({
|
|
53
|
+
tuttiHome: this.options.tuttiHome,
|
|
54
|
+
relayUrl: env.TUTTI_RELAY_URL ?? "https://tutti.now",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
prepare(input, context) {
|
|
58
|
+
return this.options.operations.runProjectOperation(() => {
|
|
59
|
+
if (!Value.Check(ManagedDraftInputSchema, input) || !input.display_name.trim())
|
|
60
|
+
throw new ManagedLocalStateError("managed_creation_input_invalid");
|
|
61
|
+
const local = resolveLaunchLocalContext({
|
|
62
|
+
workspacePath: input.workspace_path,
|
|
63
|
+
env: this.environment(context),
|
|
64
|
+
cwd: context.source === "terminal" ? context.currentDirectory : this.options.tuttiHome,
|
|
65
|
+
});
|
|
66
|
+
if (local.existing_identity.kind !== "missing" && input.existing_project_id === undefined)
|
|
67
|
+
throw new ManagedLocalStateError("managed_existing_project_requires_opt_in");
|
|
68
|
+
const state = this.state(context);
|
|
69
|
+
let existing;
|
|
70
|
+
if (input.existing_project_id !== undefined) {
|
|
71
|
+
const target = readExistingManagedTarget({
|
|
72
|
+
tuttiHome: this.options.tuttiHome,
|
|
73
|
+
relayOrigin: state.origin,
|
|
74
|
+
projectId: input.existing_project_id,
|
|
75
|
+
workspaceRoot: local.workspace_root,
|
|
76
|
+
surface: input.surface,
|
|
77
|
+
});
|
|
78
|
+
const previous = findNativeManagedProject({
|
|
79
|
+
tuttiHome: this.options.tuttiHome,
|
|
80
|
+
relayUrl: state.origin,
|
|
81
|
+
projectId: input.existing_project_id,
|
|
82
|
+
});
|
|
83
|
+
if (previous !== null &&
|
|
84
|
+
(previous.journal.state !== "registered" || previous.journal.tier !== input.tier))
|
|
85
|
+
throw new ManagedLocalStateError("managed_reauthorization_conflict");
|
|
86
|
+
existing = {
|
|
87
|
+
...target,
|
|
88
|
+
...(previous === null ? {} : { previousRequestRef: previous.journal.request_ref }),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const row = state.createDraft({
|
|
92
|
+
requestRef: input.request_ref,
|
|
93
|
+
workspaceRoot: local.workspace_root,
|
|
94
|
+
displayName: input.display_name.trim(),
|
|
95
|
+
...(input.description === undefined ? {} : { description: input.description.trim() }),
|
|
96
|
+
surface: input.surface,
|
|
97
|
+
tier: input.tier,
|
|
98
|
+
...(existing === undefined ? {} : { projectId: input.existing_project_id, existing }),
|
|
99
|
+
now: this.options.now?.() ?? new Date(),
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
draft: projection(row),
|
|
103
|
+
relay_origin: state.origin,
|
|
104
|
+
preparation: state.preparationBody(row.request_ref),
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
read(requestRef, context) {
|
|
109
|
+
const state = this.state(context);
|
|
110
|
+
return {
|
|
111
|
+
draft: projection(state.read(requestRef)),
|
|
112
|
+
relay_origin: state.origin,
|
|
113
|
+
preparation: state.preparationBody(requestRef),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async reconcile(requestRef, context) {
|
|
117
|
+
return this.options.operations.runProjectOperation(async () => {
|
|
118
|
+
const state = this.state(context);
|
|
119
|
+
await reconcileNativePreparation({
|
|
120
|
+
state,
|
|
121
|
+
requestRef,
|
|
122
|
+
now: this.options.now?.() ?? new Date(),
|
|
123
|
+
...(this.options.fetchImpl === undefined ? {} : { fetchImpl: this.options.fetchImpl }),
|
|
124
|
+
});
|
|
125
|
+
return this.read(requestRef, context);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
async validate(requestRef, context) {
|
|
129
|
+
return this.options.operations.runProjectOperation(async () => {
|
|
130
|
+
const state = this.state(context);
|
|
131
|
+
await reconcileNativePreparation({
|
|
132
|
+
state,
|
|
133
|
+
requestRef,
|
|
134
|
+
now: this.options.now?.() ?? new Date(),
|
|
135
|
+
...(this.options.fetchImpl === undefined ? {} : { fetchImpl: this.options.fetchImpl }),
|
|
136
|
+
});
|
|
137
|
+
const validation = await validateNativePreparation({
|
|
138
|
+
state,
|
|
139
|
+
requestRef,
|
|
140
|
+
...(this.options.fetchImpl === undefined ? {} : { fetchImpl: this.options.fetchImpl }),
|
|
141
|
+
});
|
|
142
|
+
return { ...this.read(requestRef, context), validation };
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
async launch(requestRef, context) {
|
|
146
|
+
return this.options.operations.runProjectOperation(async () => {
|
|
147
|
+
const projects = this.options.projects;
|
|
148
|
+
if (projects === undefined)
|
|
149
|
+
throw new ManagedLocalStateError("managed_launch_unavailable");
|
|
150
|
+
const state = this.state(context);
|
|
151
|
+
// Re-check current Relay authority immediately before any workspace mutation.
|
|
152
|
+
await reconcileNativePreparation({
|
|
153
|
+
state,
|
|
154
|
+
requestRef,
|
|
155
|
+
now: this.options.now?.() ?? new Date(),
|
|
156
|
+
...(this.options.fetchImpl === undefined ? {} : { fetchImpl: this.options.fetchImpl }),
|
|
157
|
+
});
|
|
158
|
+
state.bootstrap(requestRef, this.options.now?.() ?? new Date());
|
|
159
|
+
return projects.launchAuthorizedProject(state, requestRef, context);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=managed-project-service.js.map
|
|
@@ -10,7 +10,7 @@ export type LocalConsoleOperationState = "idle" | "project_busy" | "package_upda
|
|
|
10
10
|
export declare class LocalConsoleOperationCoordinator {
|
|
11
11
|
#private;
|
|
12
12
|
state(): LocalConsoleOperationState;
|
|
13
|
-
runProjectOperation<T>(operation: () => Promise<T>): Promise<T>;
|
|
13
|
+
runProjectOperation<T>(operation: () => T | Promise<T>): Promise<T>;
|
|
14
14
|
tryBeginPackageUpdate(): LocalConsolePackageUpdateOperationLease | null;
|
|
15
15
|
}
|
|
16
16
|
//# sourceMappingURL=operation-coordinator.d.ts.map
|
|
@@ -3,6 +3,7 @@ import { readHostLocalPackageUpdateReadiness, requestHostLocalIdleShutdown, } fr
|
|
|
3
3
|
import { createRuntimeEndpointProbe, waitForHostShutdown } from "../cli/host-runtime-endpoint.js";
|
|
4
4
|
import { spawnDetachedHost, waitForManagedHostReady } from "../cli/managed-host.js";
|
|
5
5
|
import { resolveManagedProjectContext } from "../cli/project-resolver.js";
|
|
6
|
+
import { hasProjectModelConfiguration } from "../managed-access/project-provider.js";
|
|
6
7
|
const PACKAGE_UPDATE_HOST_SHUTDOWN_WAIT_MS = 5_000;
|
|
7
8
|
export async function stopPackageUpdateHost(options) {
|
|
8
9
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
@@ -65,7 +66,7 @@ export async function restorePackageUpdateHosts(options) {
|
|
|
65
66
|
target: projectId,
|
|
66
67
|
env: { ...options.env, TUTTI_HOME: options.tuttiHome },
|
|
67
68
|
});
|
|
68
|
-
if (!project.workspace_exists || project.provider_config
|
|
69
|
+
if (!project.workspace_exists || !hasProjectModelConfiguration(project.provider_config)) {
|
|
69
70
|
failed.push(projectId);
|
|
70
71
|
continue;
|
|
71
72
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type ProjectSurface, type ProjectSurfaceProfile } from "@tutti/shared/domain";
|
|
2
2
|
import { type ProjectId, type RelayProjectRef } from "@tutti/shared/ids";
|
|
3
3
|
import type { FeishuSetupProjection } from "@tutti/shared/schemas/internal";
|
|
4
|
+
import type { ProviderConfigProjection } from "@tutti/shared/schemas/api";
|
|
5
|
+
import type { NativeManagedState } from "../managed-access/state.js";
|
|
4
6
|
import { type ConfigureProjectOpenAiProviderResult, type OpenAiProviderCredentialValidator } from "../../providers/openai/index.js";
|
|
5
7
|
import { type FetchLike } from "../cli/host-runtime-endpoint.js";
|
|
6
8
|
import type { HostLaunchRelaySummary } from "../cli/relay-registration.js";
|
|
@@ -13,8 +15,9 @@ export type LocalConsoleProject = {
|
|
|
13
15
|
display_name: string;
|
|
14
16
|
workspace_path: string;
|
|
15
17
|
status: RuntimeProjectRow["status"];
|
|
16
|
-
provider_status: "
|
|
18
|
+
provider_status: ProviderConfigProjection["status"];
|
|
17
19
|
provider_model?: string;
|
|
20
|
+
provider_tier?: "light" | "high" | "max";
|
|
18
21
|
relay_project_ref?: RelayProjectRef;
|
|
19
22
|
open_url?: string;
|
|
20
23
|
relay_connection_status?: string;
|
|
@@ -91,6 +94,7 @@ export declare class LocalConsoleProjectService {
|
|
|
91
94
|
provider?: LocalConsoleProviderInput;
|
|
92
95
|
surface?: ProjectSurface;
|
|
93
96
|
}, context: LocalConsoleInvocationContext, accountHandoff?: LocalConsoleAccountHandoff): Promise<LocalConsoleLaunchResult>;
|
|
97
|
+
launchAuthorizedProject(state: NativeManagedState, requestRef: string, context: LocalConsoleInvocationContext): Promise<LocalConsoleLaunchResult>;
|
|
94
98
|
openProject(projectId: string, context: LocalConsoleInvocationContext): Promise<LocalConsoleOpenResult>;
|
|
95
99
|
refreshInvite(projectId: string, context: LocalConsoleInvocationContext): Promise<{
|
|
96
100
|
join_url: string;
|
|
@@ -3,7 +3,9 @@ import { resolve } from "node:path";
|
|
|
3
3
|
import { readRelayProjectMembershipState } from "@tutti/relay-client";
|
|
4
4
|
import { createProjectSurfaceProfile, } from "@tutti/shared/domain";
|
|
5
5
|
import { relayProjectRouteSegment } from "@tutti/shared/ids";
|
|
6
|
-
import {
|
|
6
|
+
import { hasProjectModelConfiguration, readProjectModelAccess, } from "../managed-access/project-provider.js";
|
|
7
|
+
import { materializeManagedWorkspace } from "../managed-access/workspace.js";
|
|
8
|
+
import { configureProjectOpenAiProvider, discoverOpenAiModels, isOpenAiApiKey, validateOpenAiCredential, } from "../../providers/openai/index.js";
|
|
7
9
|
import { formatCliErrorReason, LaunchError } from "../cli/errors.js";
|
|
8
10
|
import { createRuntimeEndpointProbe, waitForHostShutdown, } from "../cli/host-runtime-endpoint.js";
|
|
9
11
|
import { prepareLaunchProject, resolveLaunchLocalContext, resolveRelayUrl } from "../cli/launch.js";
|
|
@@ -207,18 +209,28 @@ export class LocalConsoleProjectService {
|
|
|
207
209
|
// Damaged legacy metadata remains visible without an invented creation date.
|
|
208
210
|
}
|
|
209
211
|
}
|
|
210
|
-
const provider =
|
|
212
|
+
const provider = readProjectModelAccess({
|
|
211
213
|
tuttiHome: this.#tuttiHome,
|
|
212
214
|
projectId: row.project_id,
|
|
215
|
+
relayUrl: binding?.relay_url ?? resolveRelayUrl(operation.env),
|
|
213
216
|
});
|
|
214
|
-
const providerModel = provider.status === "not_configured"
|
|
217
|
+
const providerModel = provider.status === "not_configured"
|
|
218
|
+
? undefined
|
|
219
|
+
: provider.status === "managed"
|
|
220
|
+
? `Tutti ${provider.tier}`
|
|
221
|
+
: provider.default_model;
|
|
215
222
|
const activityAt = readProjectActivityAt(this.#tuttiHome, row, binding);
|
|
216
|
-
return
|
|
217
|
-
...(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
223
|
+
return {
|
|
224
|
+
...projectFromRuntimeRow(row, provider.status, providerModel, {
|
|
225
|
+
...(activityAt === undefined ? {} : { activityAt }),
|
|
226
|
+
...(createdAt === undefined ? {} : { createdAt }),
|
|
227
|
+
relayUrl: binding?.relay_url ??
|
|
228
|
+
relayUrlFromJoinUrl(row.join_url) ??
|
|
229
|
+
resolveRelayUrl(operation.env),
|
|
230
|
+
surfaceProfile: binding?.surface_profile ?? createProjectSurfaceProfile(),
|
|
231
|
+
}),
|
|
232
|
+
...(provider.status === "managed" ? { provider_tier: provider.tier } : {}),
|
|
233
|
+
};
|
|
222
234
|
});
|
|
223
235
|
}
|
|
224
236
|
async #readProject(projectId, context) {
|
|
@@ -325,12 +337,14 @@ export class LocalConsoleProjectService {
|
|
|
325
337
|
#requireStoredLaunchProvider(workspacePath, operation) {
|
|
326
338
|
const context = resolveLaunchLocalContext({ workspacePath, ...operation });
|
|
327
339
|
const provider = context.existing_identity.kind === "present"
|
|
328
|
-
?
|
|
340
|
+
? readProjectModelAccess({
|
|
329
341
|
tuttiHome: context.tutti_home,
|
|
330
342
|
projectId: context.existing_identity.project_id,
|
|
343
|
+
relayUrl: readMachineProjectBinding(context.tutti_home, context.existing_identity.project_id)
|
|
344
|
+
?.relay_url ?? resolveRelayUrl(operation.env),
|
|
331
345
|
})
|
|
332
346
|
: { status: "not_configured" };
|
|
333
|
-
if (provider
|
|
347
|
+
if (!hasProjectModelConfiguration(provider)) {
|
|
334
348
|
throw new LocalConsoleProjectError("provider_configuration_required", "This project does not have a valid saved Provider. Configure it in the launch form.");
|
|
335
349
|
}
|
|
336
350
|
}
|
|
@@ -359,6 +373,18 @@ export class LocalConsoleProjectService {
|
|
|
359
373
|
}
|
|
360
374
|
async #launchProject(input, context, accountHandoff) {
|
|
361
375
|
const operation = this.#operationOptions(context);
|
|
376
|
+
if (input.provider !== undefined) {
|
|
377
|
+
const local = resolveLaunchLocalContext({ workspacePath: input.workspacePath, ...operation });
|
|
378
|
+
if (local.existing_identity.kind === "present" &&
|
|
379
|
+
readProjectModelAccess({
|
|
380
|
+
tuttiHome: local.tutti_home,
|
|
381
|
+
projectId: local.existing_identity.project_id,
|
|
382
|
+
relayUrl: readMachineProjectBinding(local.tutti_home, local.existing_identity.project_id)
|
|
383
|
+
?.relay_url ?? resolveRelayUrl(operation.env),
|
|
384
|
+
}).status === "managed") {
|
|
385
|
+
throw new LocalConsoleProjectError("managed_model_fixed", "Managed projects cannot be changed through BYOK setup.", 409);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
362
388
|
if (input.provider === undefined) {
|
|
363
389
|
try {
|
|
364
390
|
this.#requireStoredLaunchProvider(input.workspacePath, operation);
|
|
@@ -388,6 +414,9 @@ export class LocalConsoleProjectService {
|
|
|
388
414
|
catch (error) {
|
|
389
415
|
throw this.#normalizeError(error);
|
|
390
416
|
}
|
|
417
|
+
if (provider !== undefined && preparation.provider_status === "managed") {
|
|
418
|
+
throw new LocalConsoleProjectError("managed_model_fixed", "Managed projects cannot be changed through BYOK setup.", 409);
|
|
419
|
+
}
|
|
391
420
|
if (provider !== undefined) {
|
|
392
421
|
const configured = await configureProjectOpenAiProvider({
|
|
393
422
|
tuttiHome: preparation.tutti_home,
|
|
@@ -401,10 +430,11 @@ export class LocalConsoleProjectService {
|
|
|
401
430
|
throw new LocalConsoleProjectError(configured.reason, localConsoleProviderValidationMessage(configured));
|
|
402
431
|
}
|
|
403
432
|
}
|
|
404
|
-
else if (preparation.
|
|
433
|
+
else if (!hasProjectModelConfiguration(preparation.provider_config)) {
|
|
405
434
|
throw new LocalConsoleProjectError("provider_configuration_required", "This project does not have a valid saved Provider. Configure it in the launch form.");
|
|
406
435
|
}
|
|
407
436
|
if (preparation.machine_local.binding.surface_profile.kind === "feishu" &&
|
|
437
|
+
preparation.provider_status !== "managed" &&
|
|
408
438
|
accountHandoff !== undefined) {
|
|
409
439
|
writeFeishuOwnerAccountHandoff({
|
|
410
440
|
tuttiHome: preparation.tutti_home,
|
|
@@ -414,12 +444,31 @@ export class LocalConsoleProjectService {
|
|
|
414
444
|
now: this.#now(),
|
|
415
445
|
});
|
|
416
446
|
}
|
|
447
|
+
return this.#finishLaunch(preparation, context, operation.env);
|
|
448
|
+
}
|
|
449
|
+
// Called only by the native authority service while holding the Console operation
|
|
450
|
+
// guard. It accepts a private, verified journal, never a browser-provided receipt.
|
|
451
|
+
async launchAuthorizedProject(state, requestRef, context) {
|
|
452
|
+
const operation = this.#operationOptions(context);
|
|
453
|
+
const preparation = await materializeManagedWorkspace({
|
|
454
|
+
state,
|
|
455
|
+
requestRef,
|
|
456
|
+
tuttiHome: this.#tuttiHome,
|
|
457
|
+
environment: operation.env,
|
|
458
|
+
now: this.#now,
|
|
459
|
+
});
|
|
460
|
+
return this.#finishLaunch(preparation, context, {
|
|
461
|
+
...operation.env,
|
|
462
|
+
TUTTI_RELAY_URL: state.origin,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
async #finishLaunch(preparation, context, environment) {
|
|
417
466
|
try {
|
|
418
467
|
const ready = await this.#startManagedHost({
|
|
419
468
|
workspaceRoot: preparation.workspace_root,
|
|
420
469
|
tuttiHome: preparation.tutti_home,
|
|
421
470
|
projectId: preparation.project_id,
|
|
422
|
-
env:
|
|
471
|
+
env: environment,
|
|
423
472
|
});
|
|
424
473
|
if (preparation.machine_local.binding.surface_profile.kind === "standalone" &&
|
|
425
474
|
ready.join_url === undefined) {
|
|
@@ -480,7 +529,7 @@ export class LocalConsoleProjectService {
|
|
|
480
529
|
}
|
|
481
530
|
if (!managed.workspace_exists ||
|
|
482
531
|
managed.runtime_endpoint === null ||
|
|
483
|
-
managed.provider_config
|
|
532
|
+
!hasProjectModelConfiguration(managed.provider_config)) {
|
|
484
533
|
return { project: current, hostUpdate: "deferred_unavailable" };
|
|
485
534
|
}
|
|
486
535
|
let shutdownDisposition;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { LocalConsoleManagedProjectService } from "./managed-project-service.js";
|
|
3
|
+
import { registerLocalConsoleManagedProjectRoutes } from "./managed-project-routes.js";
|
|
2
4
|
import { existsSync, statSync } from "node:fs";
|
|
3
5
|
import { resolve } from "node:path";
|
|
4
6
|
import fastify from "fastify";
|
|
7
|
+
import { createIdempotencyKey } from "@tutti/shared/ids";
|
|
5
8
|
import { resolveTuttiHome } from "../../providers/openai/index.js";
|
|
6
9
|
import { readCliVersion } from "../cli/version.js";
|
|
7
10
|
import { registerHostWebStaticRoutes, resolvePackagedWebStaticRoot } from "../http/static-web.js";
|
|
@@ -66,7 +69,11 @@ function requireLoopbackHost(request) {
|
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
71
|
export function createLocalConsoleServer(options) {
|
|
69
|
-
const app = fastify({
|
|
72
|
+
const app = fastify({
|
|
73
|
+
logger: false,
|
|
74
|
+
bodyLimit: 64 * 1024,
|
|
75
|
+
genReqId: () => createIdempotencyKey(),
|
|
76
|
+
});
|
|
70
77
|
const tuttiHome = options.tuttiHome ?? resolveTuttiHome(options.env?.TUTTI_HOME, options.cwd);
|
|
71
78
|
const sessions = new LocalConsoleSessionRegistry({
|
|
72
79
|
...(options.now === undefined ? {} : { now: options.now }),
|
|
@@ -119,6 +126,7 @@ export function createLocalConsoleServer(options) {
|
|
|
119
126
|
}
|
|
120
127
|
});
|
|
121
128
|
app.addHook("onRequest", (request, reply, done) => {
|
|
129
|
+
reply.header("x-tutti-request-id", request.id);
|
|
122
130
|
try {
|
|
123
131
|
requireLoopbackHost(request);
|
|
124
132
|
void reply
|
|
@@ -135,7 +143,7 @@ export function createLocalConsoleServer(options) {
|
|
|
135
143
|
done(error);
|
|
136
144
|
}
|
|
137
145
|
});
|
|
138
|
-
app.setErrorHandler((error,
|
|
146
|
+
app.setErrorHandler((error, request, reply) => {
|
|
139
147
|
const normalized = error instanceof LocalConsoleHttpError ||
|
|
140
148
|
error instanceof LocalConsoleProjectError ||
|
|
141
149
|
error instanceof LocalConsoleOperationConflictError
|
|
@@ -148,6 +156,7 @@ export function createLocalConsoleServer(options) {
|
|
|
148
156
|
code: normalized.code,
|
|
149
157
|
message: normalized.message,
|
|
150
158
|
retryable: normalized.statusCode >= 500,
|
|
159
|
+
request_id: request.id,
|
|
151
160
|
},
|
|
152
161
|
});
|
|
153
162
|
});
|
|
@@ -173,6 +182,17 @@ export function createLocalConsoleServer(options) {
|
|
|
173
182
|
options.onActivity?.();
|
|
174
183
|
return session;
|
|
175
184
|
}
|
|
185
|
+
registerLocalConsoleManagedProjectRoutes(app, {
|
|
186
|
+
tuttiHome,
|
|
187
|
+
requireSession,
|
|
188
|
+
service: new LocalConsoleManagedProjectService({
|
|
189
|
+
tuttiHome,
|
|
190
|
+
operations,
|
|
191
|
+
projects,
|
|
192
|
+
...(options.env === undefined ? {} : { serviceEnvironment: options.env }),
|
|
193
|
+
...(options.now === undefined ? {} : { now: options.now }),
|
|
194
|
+
}),
|
|
195
|
+
});
|
|
176
196
|
app.get(`${LOCAL_CONSOLE_API_BASE}/health`, () => ({
|
|
177
197
|
status: "ready",
|
|
178
198
|
protocol_version: 2,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { OpenAiCredentialLease, OpenAiExecutionConfig } from "../../providers/openai/execution-access.js";
|
|
2
|
+
import type { NativeManagedRuntime } from "./runtime.js";
|
|
3
|
+
import type { ModelSessionOwner } from "./runtime-records.js";
|
|
4
|
+
export type ManagedExecutionRuntime = Pick<NativeManagedRuntime, "selection" | "run">;
|
|
5
|
+
export declare function bindManagedExecution(runtime: ManagedExecutionRuntime, owner: ModelSessionOwner): Promise<{
|
|
6
|
+
config: OpenAiExecutionConfig;
|
|
7
|
+
credentialLease: OpenAiCredentialLease;
|
|
8
|
+
}>;
|
|
9
|
+
//# sourceMappingURL=execution-binding.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ManagedLocalStateError } from "./private-store.js";
|
|
2
|
+
export async function bindManagedExecution(runtime, owner) {
|
|
3
|
+
// This happens before provider factories open any durable Thread. Retaining
|
|
4
|
+
// this selection through retries forbids silent route/model replacement.
|
|
5
|
+
const selected = await runtime.selection(owner);
|
|
6
|
+
const config = {
|
|
7
|
+
default_model: selected.provider.model,
|
|
8
|
+
api_base_url: selected.provider.base_url,
|
|
9
|
+
};
|
|
10
|
+
return {
|
|
11
|
+
config,
|
|
12
|
+
credentialLease: (durationMs, invoke) => runtime.run(owner, Math.ceil(durationMs / 1000), async (access) => {
|
|
13
|
+
if (access.session_ref !== selected.session_ref ||
|
|
14
|
+
access.route_ref !== selected.route_ref ||
|
|
15
|
+
access.provider.model !== selected.provider.model ||
|
|
16
|
+
access.provider.base_url !== selected.provider.base_url)
|
|
17
|
+
throw new ManagedLocalStateError("managed_route_changed");
|
|
18
|
+
return invoke(access.provider.api_key);
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=execution-binding.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ProjectId } from "@tutti/shared/ids";
|
|
2
|
+
export declare function requireStoppedManagedTarget(tuttiHome: string, projectId: ProjectId): void;
|
|
3
|
+
export declare function readExistingManagedTarget(options: {
|
|
4
|
+
tuttiHome: string;
|
|
5
|
+
relayOrigin: string;
|
|
6
|
+
projectId: ProjectId;
|
|
7
|
+
workspaceRoot: string;
|
|
8
|
+
surface: "standalone" | "feishu";
|
|
9
|
+
requireStopped?: boolean;
|
|
10
|
+
}): {
|
|
11
|
+
relayProjectRef: `relay_project_${string}`;
|
|
12
|
+
workspacePath: string;
|
|
13
|
+
hostSecret: string;
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=existing-project.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { readHostRegistrationSecret, readMachineProjectBinding, readMachineRuntimeEndpoint, } from "../cli/machine-local.js";
|
|
3
|
+
import { readProjectIdentity } from "../cli/project-identity.js";
|
|
4
|
+
import { ManagedLocalStateError } from "./private-store.js";
|
|
5
|
+
import { managedRelayOrigin } from "./state.js";
|
|
6
|
+
// A provider cutover must never mutate the authority used by a live Host. A
|
|
7
|
+
// process we cannot prove stopped is rejected; network health failure is not proof.
|
|
8
|
+
export function requireStoppedManagedTarget(tuttiHome, projectId) {
|
|
9
|
+
const endpoint = readMachineRuntimeEndpoint(tuttiHome, projectId);
|
|
10
|
+
if (endpoint === null)
|
|
11
|
+
return;
|
|
12
|
+
try {
|
|
13
|
+
process.kill(endpoint.pid, 0);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (error.code === "ESRCH")
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
throw new ManagedLocalStateError("managed_project_must_be_stopped");
|
|
20
|
+
}
|
|
21
|
+
export function readExistingManagedTarget(options) {
|
|
22
|
+
if (options.requireStopped !== false)
|
|
23
|
+
requireStoppedManagedTarget(options.tuttiHome, options.projectId);
|
|
24
|
+
const binding = readMachineProjectBinding(options.tuttiHome, options.projectId);
|
|
25
|
+
const identity = readProjectIdentity(options.workspaceRoot);
|
|
26
|
+
if (binding === null ||
|
|
27
|
+
binding.relay_project_ref === undefined ||
|
|
28
|
+
binding.relay_url === undefined ||
|
|
29
|
+
managedRelayOrigin(binding.relay_url) !== options.relayOrigin ||
|
|
30
|
+
realpathSync(binding.workspace_root) !== realpathSync(options.workspaceRoot) ||
|
|
31
|
+
binding.surface_profile.kind !== options.surface ||
|
|
32
|
+
identity.kind !== "present" ||
|
|
33
|
+
identity.project_id !== options.projectId)
|
|
34
|
+
throw new ManagedLocalStateError("managed_existing_project_mismatch");
|
|
35
|
+
return {
|
|
36
|
+
relayProjectRef: binding.relay_project_ref,
|
|
37
|
+
workspacePath: binding.workspace_root,
|
|
38
|
+
hostSecret: readHostRegistrationSecret(options.tuttiHome, options.projectId).secret,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=existing-project.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ManagedLocalStateError } from "./private-store.js";
|
|
2
|
+
export declare class ManagedControlError extends ManagedLocalStateError {
|
|
3
|
+
readonly status?: number | undefined;
|
|
4
|
+
readonly requestId?: string | undefined;
|
|
5
|
+
constructor(code: string, status?: number | undefined, requestId?: string | undefined);
|
|
6
|
+
}
|
|
7
|
+
export declare class NativeModelControlClient {
|
|
8
|
+
private readonly options;
|
|
9
|
+
constructor(options: {
|
|
10
|
+
authorize: () => Promise<{
|
|
11
|
+
authorization: string;
|
|
12
|
+
control_origin: string;
|
|
13
|
+
}>;
|
|
14
|
+
fetchImpl?: typeof fetch;
|
|
15
|
+
});
|
|
16
|
+
private request;
|
|
17
|
+
pin(sessionRef: string, routeRef?: string): Promise<{
|
|
18
|
+
disposition: "created" | "existing";
|
|
19
|
+
tier: "light" | "high" | "max";
|
|
20
|
+
session_ref: string;
|
|
21
|
+
provider: {
|
|
22
|
+
model: string;
|
|
23
|
+
base_url: string;
|
|
24
|
+
};
|
|
25
|
+
route_ref: string;
|
|
26
|
+
}>;
|
|
27
|
+
issue(sessionRef: string, requestRef: string): Promise<{
|
|
28
|
+
disposition: "issued";
|
|
29
|
+
provider: {
|
|
30
|
+
api_key: string;
|
|
31
|
+
model: string;
|
|
32
|
+
base_url: string;
|
|
33
|
+
};
|
|
34
|
+
route_ref: string;
|
|
35
|
+
credential: {
|
|
36
|
+
state: "active" | "revoked" | "expired" | "creating" | "revoking";
|
|
37
|
+
session_ref: string;
|
|
38
|
+
expires_at: number;
|
|
39
|
+
credential_ref: string;
|
|
40
|
+
device_ref: string;
|
|
41
|
+
quota_cap: number;
|
|
42
|
+
};
|
|
43
|
+
} | {
|
|
44
|
+
disposition: "already_issued";
|
|
45
|
+
credential: {
|
|
46
|
+
state: "active" | "revoked" | "expired" | "creating" | "revoking";
|
|
47
|
+
session_ref: string;
|
|
48
|
+
expires_at: number;
|
|
49
|
+
credential_ref: string;
|
|
50
|
+
device_ref: string;
|
|
51
|
+
quota_cap: number;
|
|
52
|
+
};
|
|
53
|
+
}>;
|
|
54
|
+
revoke(credentialRef: string): Promise<{
|
|
55
|
+
disposition: "revoked";
|
|
56
|
+
credential: {
|
|
57
|
+
state: "active" | "revoked" | "expired" | "creating" | "revoking";
|
|
58
|
+
session_ref: string;
|
|
59
|
+
expires_at: number;
|
|
60
|
+
credential_ref: string;
|
|
61
|
+
device_ref: string;
|
|
62
|
+
quota_cap: number;
|
|
63
|
+
};
|
|
64
|
+
}>;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=model-control-client.d.ts.map
|