@xfey/tutti 0.1.1 → 0.1.3
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 +1 -0
- package/dist/server-shell/cli/cli.js +1 -1
- package/dist/server-shell/cli/host-lifecycle.d.ts +4 -1
- package/dist/server-shell/cli/host-lifecycle.js +25 -20
- package/dist/server-shell/cli/host-server-runtime.d.ts +1 -0
- package/dist/server-shell/cli/host-server-runtime.js +22 -2
- package/dist/server-shell/cli/machine-local.d.ts +3 -0
- package/dist/server-shell/cli/machine-local.js +14 -1
- package/dist/server-shell/http/create-server.d.ts +9 -1
- package/dist/server-shell/http/create-server.js +4 -1
- package/dist/server-shell/http/logger.d.ts +3 -1
- package/dist/server-shell/http/logger.js +2 -1
- package/dist/server-shell/http/routes/health.d.ts +4 -0
- package/dist/server-shell/http/routes/health.js +2 -2
- package/node_modules/@tutti/shared/dist/utils/redaction/index.d.ts +2 -1
- package/node_modules/@tutti/shared/dist/utils/redaction/index.js +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
- packaged / production `tutti launch` 默认连接 `https://tutti.now`;`TUTTI_RELAY_URL` 可覆盖 Relay URL,本地开发 dev runner 默认使用 `http://127.0.0.1:4370`。
|
|
44
44
|
- host 注册使用 machine-local `host_registration_secret`;该 secret 明文只可写入受限权限的 machine-local secret store,不写入目标项目 repo、Git local config 项目身份、SQLite 协作真相或日志,`binding.json` 只保存引用。
|
|
45
45
|
- Relay registration 成功后只把 `relay_project_ref` 写回 machine-local binding;join URL 只在 host 本机终端或当前 host-local control 内存状态中展示,不写入 binding、runtime endpoint、SQLite 协作真相或日志。
|
|
46
|
+
- 普通 `tutti launch` 的前台 stdout 只展示运行提示、setup URL 和 join URL;Host HTTP request log 与 Control Plane / Run Pipeline runtime log 写入 `TUTTI_HOME/logs/host.log`,不写入目标项目 repo 或常规 CLI stdout。
|
|
46
47
|
- Reference 和 Skills 上传的最终写入由 Host Project API 完成;Host 只通过 Relay host-control resolve 获取短期 R2 URL 下载 staged object,不接收浏览器 base64 文件正文,也不持久化 presigned URL。
|
|
47
48
|
- Host Project API command 的 browser session context 只信任 Relay tunnel metadata 中的 `relay_session_context`;route handler 不读取浏览器身份 header,也不接受 payload 内身份字段。
|
|
48
49
|
- Fastify request log、debug log、SSE payload、activity event 和 run result 都必须经过 redaction,不得泄露 provider secret、host registration secret、host connection token、join token、cookie、host 绝对路径或 run workspace path。
|
|
@@ -44,7 +44,7 @@ try {
|
|
|
44
44
|
yes: args.yes,
|
|
45
45
|
confirm: confirmFromTty,
|
|
46
46
|
});
|
|
47
|
-
process.stdout.write(`${formatLaunchLifecycleResult(result)}\n`);
|
|
47
|
+
process.stdout.write(`${formatLaunchLifecycleResult(result, { hyperlinks: process.stdout.isTTY })}\n`);
|
|
48
48
|
if (result.kind === "hosting") {
|
|
49
49
|
await waitForForegroundHostShutdown(result.host);
|
|
50
50
|
}
|
|
@@ -39,7 +39,10 @@ export type StartLaunchProjectOptions = LaunchPreparationOptions & {
|
|
|
39
39
|
registerRelayHostConnection?: RelayHostConnectionRegistrar;
|
|
40
40
|
shutdownWaitMs?: number;
|
|
41
41
|
};
|
|
42
|
+
export type FormatLaunchLifecycleResultOptions = {
|
|
43
|
+
hyperlinks?: boolean;
|
|
44
|
+
};
|
|
42
45
|
export declare function startLaunchProject(options: StartLaunchProjectOptions): Promise<StartLaunchProjectResult>;
|
|
43
|
-
export declare function formatLaunchLifecycleResult(result: StartLaunchProjectResult): string;
|
|
46
|
+
export declare function formatLaunchLifecycleResult(result: StartLaunchProjectResult, options?: FormatLaunchLifecycleResultOptions): string;
|
|
44
47
|
export declare function waitForForegroundHostShutdown(handle: HostServerHandle, processLike?: NodeJS.Process): Promise<void>;
|
|
45
48
|
//# sourceMappingURL=host-lifecycle.d.ts.map
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
-
import { redactText } from "@tutti/shared/utils";
|
|
3
2
|
import { connectRelayHostTunnel, RelayHostControlClientError, refreshRelayJoinToken as refreshRelayJoinTokenWithRelay, registerRelayHostConnection as registerRelayHostConnectionWithRelay, } from "@tutti/relay-client";
|
|
4
3
|
import { createHostServer } from "../http/create-server.js";
|
|
5
4
|
import { createHostProjectApiTunnelRequestHandler, createHostProjectApiTunnelStreamRequestHandler, } from "../http/host-tunnel.js";
|
|
@@ -13,6 +12,26 @@ import { prepareLaunchProject, resolveLaunchLocalContext, } from "./launch.js";
|
|
|
13
12
|
import { registerHostWithRelay, relayErrorToLaunchError, } from "./relay-registration.js";
|
|
14
13
|
export { createRuntimeEndpointProbe } from "./host-runtime-endpoint.js";
|
|
15
14
|
const DEFAULT_SHUTDOWN_WAIT_MS = 2_000;
|
|
15
|
+
const OSC8_START = "\u001B]8;;";
|
|
16
|
+
const OSC8_END = "\u001B]8;;\u001B\\";
|
|
17
|
+
function isTerminalSafeHttpUrl(value) {
|
|
18
|
+
if (!value.startsWith("http://") && !value.startsWith("https://")) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return Array.from(value).every((character) => {
|
|
22
|
+
const codePoint = character.codePointAt(0);
|
|
23
|
+
return codePoint !== undefined && codePoint > 0x1f && codePoint !== 0x7f;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function terminalLink(value, options) {
|
|
27
|
+
if (options.hyperlinks !== true || !isTerminalSafeHttpUrl(value)) {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
return `${OSC8_START}${value}\u001B\\${value}${OSC8_END}`;
|
|
31
|
+
}
|
|
32
|
+
function urlLine(label, value, options) {
|
|
33
|
+
return `${label}: ${terminalLink(value, options)}`;
|
|
34
|
+
}
|
|
16
35
|
function endpointWithoutToken(endpoint) {
|
|
17
36
|
return {
|
|
18
37
|
project_id: endpoint.project_id,
|
|
@@ -246,28 +265,18 @@ export async function startLaunchProject(options) {
|
|
|
246
265
|
}),
|
|
247
266
|
};
|
|
248
267
|
}
|
|
249
|
-
export function formatLaunchLifecycleResult(result) {
|
|
268
|
+
export function formatLaunchLifecycleResult(result, options = {}) {
|
|
250
269
|
const summary = result.summary;
|
|
251
270
|
const fields = [
|
|
252
271
|
result.kind === "connected"
|
|
253
|
-
? "Tutti
|
|
254
|
-
: "Tutti
|
|
255
|
-
"",
|
|
256
|
-
`Status: ${summary.status}`,
|
|
257
|
-
`Workspace: ${redactText(summary.workspace_root)}`,
|
|
258
|
-
`Project: ${summary.project_id}`,
|
|
259
|
-
`Branch: ${summary.branch}`,
|
|
260
|
-
`Relay: ${summary.relay_url}`,
|
|
272
|
+
? "Tutti is already running. Stop it from the original host terminal."
|
|
273
|
+
: "Tutti is running. Press Ctrl-C to stop.",
|
|
261
274
|
];
|
|
262
|
-
if (summary.provider_status !== undefined) {
|
|
263
|
-
fields.push(`Provider: ${summary.provider_status}`);
|
|
264
|
-
}
|
|
265
|
-
fields.push(`Local diagnostic endpoint: ${summary.local_diagnostic_endpoint}`);
|
|
266
275
|
if (summary.setup_url !== undefined) {
|
|
267
|
-
fields.push(
|
|
276
|
+
fields.push(urlLine("Setup URL", summary.setup_url, options));
|
|
268
277
|
}
|
|
269
278
|
if (summary.relay?.join_url !== undefined) {
|
|
270
|
-
fields.push(
|
|
279
|
+
fields.push(urlLine("Join URL", summary.relay.join_url, options));
|
|
271
280
|
}
|
|
272
281
|
else if (summary.relay?.join_url_visibility === "not_recoverable") {
|
|
273
282
|
fields.push("Join URL: not recoverable; rotate a fresh invite from the host.");
|
|
@@ -275,10 +284,6 @@ export function formatLaunchLifecycleResult(result) {
|
|
|
275
284
|
else {
|
|
276
285
|
fields.push("Join URL: unavailable from this local host process");
|
|
277
286
|
}
|
|
278
|
-
fields.push("");
|
|
279
|
-
fields.push(result.kind === "connected"
|
|
280
|
-
? "An existing host server is handling this workspace; no second server was started."
|
|
281
|
-
: "This terminal is hosting the project server. Press Ctrl-C to stop.");
|
|
282
287
|
return fields.join("\n");
|
|
283
288
|
}
|
|
284
289
|
export async function waitForForegroundHostShutdown(handle, processLike = process) {
|
|
@@ -12,6 +12,7 @@ export type HostServerFactory = typeof createHostServer;
|
|
|
12
12
|
export type HostServerHandle = {
|
|
13
13
|
app: FastifyInstance;
|
|
14
14
|
store: HostProjectStore;
|
|
15
|
+
log_file_path: string;
|
|
15
16
|
runtime_endpoint: MachineRuntimeEndpointRecord;
|
|
16
17
|
trusted_relay_metadata_store: TrustedRelaySessionMetadataStore;
|
|
17
18
|
attachRelayTunnel: (tunnel: RelayHostTunnelHandle) => void;
|
|
@@ -20,7 +20,7 @@ import { WorkspaceEventBus } from "../http/workspace-events.js";
|
|
|
20
20
|
import { createTrustedRelaySessionMetadataStore, } from "../session/relay-session-context.js";
|
|
21
21
|
import { LaunchError } from "./errors.js";
|
|
22
22
|
import { relayStateFromLaunchStatus } from "./host-relay-status.js";
|
|
23
|
-
import { readHostRegistrationSecret, createMachineRuntimeEndpointToken, deleteMachineRuntimeEndpoint, writeMachineRuntimeEndpoint, } from "./machine-local.js";
|
|
23
|
+
import { appendHostLogLine, readHostRegistrationSecret, createMachineRuntimeEndpointToken, deleteMachineRuntimeEndpoint, ensureHostLogFile, getHostLogFilePath, writeMachineRuntimeEndpoint, } from "./machine-local.js";
|
|
24
24
|
import { readProjectDisplayName, writeProjectDisplayName } from "./project-identity.js";
|
|
25
25
|
export const HOST_SERVER_VERSION = "0.0.0";
|
|
26
26
|
function createHostRuntimeLogger(options) {
|
|
@@ -34,7 +34,7 @@ function createHostRuntimeLogger(options) {
|
|
|
34
34
|
fields,
|
|
35
35
|
now: options.now,
|
|
36
36
|
});
|
|
37
|
-
|
|
37
|
+
appendHostLogLine(options.logFilePath, line);
|
|
38
38
|
},
|
|
39
39
|
};
|
|
40
40
|
}
|
|
@@ -125,6 +125,8 @@ class HostLocalProviderConfigureError extends Error {
|
|
|
125
125
|
export async function startForegroundHostServer(options) {
|
|
126
126
|
const token = createMachineRuntimeEndpointToken();
|
|
127
127
|
const now = options.now ?? (() => new Date());
|
|
128
|
+
const logFilePath = getHostLogFilePath(options.preparation.tutti_home);
|
|
129
|
+
ensureHostLogFile(logFilePath);
|
|
128
130
|
const startedAt = now().toISOString();
|
|
129
131
|
const trustedMetadataStore = createTrustedRelaySessionMetadataStore();
|
|
130
132
|
const agentContextTokenRegistry = new AgentContextTokenRegistry({ now });
|
|
@@ -183,10 +185,12 @@ export async function startForegroundHostServer(options) {
|
|
|
183
185
|
};
|
|
184
186
|
const controlPlaneLogger = createHostRuntimeLogger({
|
|
185
187
|
component: "control-plane",
|
|
188
|
+
logFilePath,
|
|
186
189
|
now,
|
|
187
190
|
});
|
|
188
191
|
const runPipelineLogger = createHostRuntimeLogger({
|
|
189
192
|
component: "run-pipeline",
|
|
193
|
+
logFilePath,
|
|
190
194
|
now,
|
|
191
195
|
});
|
|
192
196
|
const approvalManager = new ApprovalRequestManager({
|
|
@@ -246,6 +250,7 @@ export async function startForegroundHostServer(options) {
|
|
|
246
250
|
});
|
|
247
251
|
let close = async () => { };
|
|
248
252
|
let relayTunnel;
|
|
253
|
+
let relayTunnelConnected = false;
|
|
249
254
|
const localControl = {
|
|
250
255
|
token,
|
|
251
256
|
requestShutdown: () => {
|
|
@@ -306,6 +311,13 @@ export async function startForegroundHostServer(options) {
|
|
|
306
311
|
});
|
|
307
312
|
app = options.createServer({
|
|
308
313
|
agentContext: { tokenRegistry: agentContextTokenRegistry },
|
|
314
|
+
health: {
|
|
315
|
+
readiness: {
|
|
316
|
+
relay: () => (relayTunnelConnected ? "connected" : "not_connected"),
|
|
317
|
+
store: () => "ready",
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
logger: { file: logFilePath },
|
|
309
321
|
localControl,
|
|
310
322
|
now,
|
|
311
323
|
projectApi: {
|
|
@@ -397,6 +409,7 @@ export async function startForegroundHostServer(options) {
|
|
|
397
409
|
close = () => {
|
|
398
410
|
closePromise ??= (async () => {
|
|
399
411
|
try {
|
|
412
|
+
relayTunnelConnected = false;
|
|
400
413
|
relayTunnel?.close(1000, "host closing");
|
|
401
414
|
await app.close();
|
|
402
415
|
}
|
|
@@ -420,10 +433,17 @@ export async function startForegroundHostServer(options) {
|
|
|
420
433
|
return {
|
|
421
434
|
app,
|
|
422
435
|
store,
|
|
436
|
+
log_file_path: logFilePath,
|
|
423
437
|
runtime_endpoint: runtimeEndpoint,
|
|
424
438
|
trusted_relay_metadata_store: trustedMetadataStore,
|
|
425
439
|
attachRelayTunnel: (tunnel) => {
|
|
426
440
|
relayTunnel = tunnel;
|
|
441
|
+
relayTunnelConnected = true;
|
|
442
|
+
void tunnel.closed.finally(() => {
|
|
443
|
+
if (relayTunnel === tunnel) {
|
|
444
|
+
relayTunnelConnected = false;
|
|
445
|
+
}
|
|
446
|
+
});
|
|
427
447
|
},
|
|
428
448
|
closed,
|
|
429
449
|
close,
|
|
@@ -38,6 +38,9 @@ export type MachineProjectBindingResult = {
|
|
|
38
38
|
export declare function getProjectLocalStoreRoot(tuttiHome: string, projectId: ProjectId): string;
|
|
39
39
|
export declare function getMachineProjectBindingPath(tuttiHome: string, projectId: ProjectId): string;
|
|
40
40
|
export declare function getMachineRuntimeEndpointPath(tuttiHome: string, projectId: ProjectId): string;
|
|
41
|
+
export declare function getHostLogFilePath(tuttiHome: string): string;
|
|
42
|
+
export declare function ensureHostLogFile(logFilePath: string): void;
|
|
43
|
+
export declare function appendHostLogLine(logFilePath: string, line: string): void;
|
|
41
44
|
export declare function createHostRegistrationSecretRef(projectId: ProjectId): string;
|
|
42
45
|
export declare function getHostRegistrationSecretPath(tuttiHome: string, projectId: ProjectId): string;
|
|
43
46
|
export declare function createHostRegistrationSecret(): string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { appendFileSync, chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { ID_PREFIXES, isPrefixedId } from "@tutti/shared/ids";
|
|
5
5
|
import { LaunchError } from "./errors.js";
|
|
@@ -36,6 +36,19 @@ export function getMachineProjectBindingPath(tuttiHome, projectId) {
|
|
|
36
36
|
export function getMachineRuntimeEndpointPath(tuttiHome, projectId) {
|
|
37
37
|
return join(getProjectLocalStoreRoot(tuttiHome, projectId), "runtime", "endpoint.json");
|
|
38
38
|
}
|
|
39
|
+
export function getHostLogFilePath(tuttiHome) {
|
|
40
|
+
return join(tuttiHome, "logs", "host.log");
|
|
41
|
+
}
|
|
42
|
+
export function ensureHostLogFile(logFilePath) {
|
|
43
|
+
ensurePrivateDirectory(dirname(logFilePath));
|
|
44
|
+
const fileDescriptor = openSync(logFilePath, "a", 0o600);
|
|
45
|
+
closeSync(fileDescriptor);
|
|
46
|
+
chmodSync(logFilePath, 0o600);
|
|
47
|
+
}
|
|
48
|
+
export function appendHostLogLine(logFilePath, line) {
|
|
49
|
+
ensureHostLogFile(logFilePath);
|
|
50
|
+
appendFileSync(logFilePath, `${line}\n`, { encoding: "utf8", mode: 0o600 });
|
|
51
|
+
}
|
|
39
52
|
export function createHostRegistrationSecretRef(projectId) {
|
|
40
53
|
return `host_registration:${projectId}`;
|
|
41
54
|
}
|
|
@@ -7,7 +7,15 @@ export type HostServerOptions = {
|
|
|
7
7
|
agentContext?: false | {
|
|
8
8
|
tokenRegistry?: AgentContextTokenRegistry;
|
|
9
9
|
};
|
|
10
|
-
|
|
10
|
+
health?: {
|
|
11
|
+
readiness?: {
|
|
12
|
+
relay?: () => "not_connected" | "connected";
|
|
13
|
+
store?: () => "not_configured" | "ready";
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
logger?: false | {
|
|
17
|
+
file?: string;
|
|
18
|
+
};
|
|
11
19
|
localControl?: HostLocalControlOptions;
|
|
12
20
|
now?: () => Date;
|
|
13
21
|
projectApi?: false | HostProjectApiRouteOptions;
|
|
@@ -9,9 +9,12 @@ import { registerApiErrorHandler } from "./validation.js";
|
|
|
9
9
|
export function createHostServer(options = {}) {
|
|
10
10
|
const app = options.logger === false
|
|
11
11
|
? fastify({ logger: false })
|
|
12
|
-
: fastify({ logger: createRedactedLoggerOptions("tutti-host") });
|
|
12
|
+
: fastify({ logger: createRedactedLoggerOptions("tutti-host", options.logger) });
|
|
13
13
|
registerApiErrorHandler(app);
|
|
14
14
|
registerHostHealthRoute(app, {
|
|
15
|
+
...(options.health?.readiness === undefined
|
|
16
|
+
? {}
|
|
17
|
+
: { readiness: options.health.readiness }),
|
|
15
18
|
now: options.now ?? (() => new Date()),
|
|
16
19
|
serviceVersion: options.serviceVersion ?? "0.0.0",
|
|
17
20
|
});
|
|
@@ -3,5 +3,7 @@ import type { FastifyError, FastifyServerOptions } from "fastify";
|
|
|
3
3
|
export declare const serializeRequestLog: typeof serializeRuntimeRequestLog;
|
|
4
4
|
export declare const serializeResponseLog: typeof serializeRuntimeResponseLog;
|
|
5
5
|
export declare function serializeErrorLog(error: FastifyError): RuntimeSerializedErrorLog;
|
|
6
|
-
export declare function createRedactedLoggerOptions(service: string
|
|
6
|
+
export declare function createRedactedLoggerOptions(service: string, options?: {
|
|
7
|
+
file?: string;
|
|
8
|
+
}): Exclude<FastifyServerOptions["logger"], undefined>;
|
|
7
9
|
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -4,11 +4,12 @@ export const serializeResponseLog = serializeRuntimeResponseLog;
|
|
|
4
4
|
export function serializeErrorLog(error) {
|
|
5
5
|
return serializeRuntimeErrorLog(error);
|
|
6
6
|
}
|
|
7
|
-
export function createRedactedLoggerOptions(service) {
|
|
7
|
+
export function createRedactedLoggerOptions(service, options = {}) {
|
|
8
8
|
return {
|
|
9
9
|
level: "info",
|
|
10
10
|
messageKey: "message",
|
|
11
11
|
base: { service, component: "http" },
|
|
12
|
+
...(options.file === undefined ? {} : { file: options.file }),
|
|
12
13
|
serializers: {
|
|
13
14
|
req: serializeRequestLog,
|
|
14
15
|
res: serializeResponseLog,
|
|
@@ -33,8 +33,8 @@ export function registerHostHealthRoute(app, options) {
|
|
|
33
33
|
time: options.now().toISOString(),
|
|
34
34
|
readiness: {
|
|
35
35
|
api: "ready",
|
|
36
|
-
store: "not_configured",
|
|
37
|
-
relay: "not_connected",
|
|
36
|
+
store: options.readiness?.store?.() ?? "not_configured",
|
|
37
|
+
relay: options.readiness?.relay?.() ?? "not_connected",
|
|
38
38
|
},
|
|
39
39
|
}));
|
|
40
40
|
}
|
|
@@ -3,10 +3,11 @@ export declare const REDACTION_LABELS: {
|
|
|
3
3
|
readonly bearerToken: "[REDACTED:bearer_token]";
|
|
4
4
|
readonly cookie: "[REDACTED:cookie]";
|
|
5
5
|
readonly joinToken: "[REDACTED:join_token]";
|
|
6
|
+
readonly runtimeToken: "[REDACTED:runtime_token]";
|
|
6
7
|
readonly hostSecret: "[REDACTED:host_secret]";
|
|
7
8
|
readonly localPath: "[REDACTED:local_path]";
|
|
8
9
|
};
|
|
9
|
-
export declare const REDACTION_FIXTURES: readonly ["sk-proj-example-secret", "Authorization: Bearer example-token", "Cookie: tutti_session=example-session", "http://127.0.0.1:4370/join/example-join-token", "http://127.0.0.1:4370/api/v1/join/example-join-token/accept", "host_connection_token=example-host-token", "host_registration_secret=example-host-secret", "/Users/example/.tutti/auth.json", "/Users/example/project/.git/config", "/Users/example/.tutti/projects/proj_123/runs/act_123", "OPENAI_API_KEY=example"];
|
|
10
|
+
export declare const REDACTION_FIXTURES: readonly ["sk-proj-example-secret", "Authorization: Bearer example-token", "Cookie: tutti_session=example-session", "http://127.0.0.1:4370/join/example-join-token", "http://127.0.0.1:4370/api/v1/join/example-join-token/accept", "http://127.0.0.1:4371/setup?token=example-runtime-token", "host_connection_token=example-host-token", "host_registration_secret=example-host-secret", "/Users/example/.tutti/auth.json", "/Users/example/project/.git/config", "/Users/example/.tutti/projects/proj_123/runs/act_123", "OPENAI_API_KEY=example"];
|
|
10
11
|
export declare function redactText(value: string): string;
|
|
11
12
|
export declare function redactValue(value: unknown): unknown;
|
|
12
13
|
export type RedactedError = {
|
|
@@ -3,6 +3,7 @@ export const REDACTION_LABELS = {
|
|
|
3
3
|
bearerToken: "[REDACTED:bearer_token]",
|
|
4
4
|
cookie: "[REDACTED:cookie]",
|
|
5
5
|
joinToken: "[REDACTED:join_token]",
|
|
6
|
+
runtimeToken: "[REDACTED:runtime_token]",
|
|
6
7
|
hostSecret: "[REDACTED:host_secret]",
|
|
7
8
|
localPath: "[REDACTED:local_path]",
|
|
8
9
|
};
|
|
@@ -12,6 +13,7 @@ export const REDACTION_FIXTURES = [
|
|
|
12
13
|
"Cookie: tutti_session=example-session",
|
|
13
14
|
"http://127.0.0.1:4370/join/example-join-token",
|
|
14
15
|
"http://127.0.0.1:4370/api/v1/join/example-join-token/accept",
|
|
16
|
+
"http://127.0.0.1:4371/setup?token=example-runtime-token",
|
|
15
17
|
"host_connection_token=example-host-token",
|
|
16
18
|
"host_registration_secret=example-host-secret",
|
|
17
19
|
"/Users/example/.tutti/auth.json",
|
|
@@ -56,6 +58,14 @@ const TEXT_REDACTION_RULES = [
|
|
|
56
58
|
pattern: /(^|[\s"'`])\/join\/[A-Za-z0-9._~+/-]+/giu,
|
|
57
59
|
replacement: `$1/join/${REDACTION_LABELS.joinToken}`,
|
|
58
60
|
},
|
|
61
|
+
{
|
|
62
|
+
pattern: /(https?:\/\/[^\s"'`]+\/setup)\?token=[A-Za-z0-9._~+/-]+/giu,
|
|
63
|
+
replacement: `$1?token=${REDACTION_LABELS.runtimeToken}`,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
pattern: /(^|[\s"'`])\/setup\?token=[A-Za-z0-9._~+/-]+/giu,
|
|
67
|
+
replacement: `$1/setup?token=${REDACTION_LABELS.runtimeToken}`,
|
|
68
|
+
},
|
|
59
69
|
{
|
|
60
70
|
pattern: /\bhost_connection_token\s*=\s*[^\s"'`&]+/giu,
|
|
61
71
|
replacement: `host_connection_token=${REDACTION_LABELS.hostSecret}`,
|