@botiverse/raft-daemon 0.72.3 → 0.72.4-play.20260709142358
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/{chunk-GMOJFVE4.js → chunk-OEK5FGK6.js} +1248 -395
- package/dist/cli/index.js +420 -139
- package/dist/core.js +23 -3
- package/dist/{dist-YOKZZD4F.js → dist-NS7NN3BV.js} +418 -139
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/core.ts
|
|
2
|
-
import
|
|
2
|
+
import path22 from "path";
|
|
3
3
|
import os8 from "os";
|
|
4
4
|
import { createRequire as createRequire3 } from "module";
|
|
5
5
|
import { accessSync } from "fs";
|
|
@@ -1989,23 +1989,13 @@ var integrationUpdateAppRegistrationOperationSchema = integrationAppDraftFieldsS
|
|
|
1989
1989
|
unsafeDemoUrlOverride: z.boolean().optional(),
|
|
1990
1990
|
draftHint: draftHintSchema
|
|
1991
1991
|
});
|
|
1992
|
-
var migrationExportOperationSchema = z.object({
|
|
1993
|
-
type: z.literal("migration:export"),
|
|
1994
|
-
targetComputer: idOrHandleSchema,
|
|
1995
|
-
mode: z.enum(["cooperative", "forensic"]).default("cooperative"),
|
|
1996
|
-
prepDeadlineMs: z.number().int().positive().optional(),
|
|
1997
|
-
transferDeadlineMs: z.number().int().positive().optional(),
|
|
1998
|
-
arrivalDeadlineMs: z.number().int().positive().optional(),
|
|
1999
|
-
draftHint: draftHintSchema
|
|
2000
|
-
});
|
|
2001
1992
|
var actionCardActionSchema = z.discriminatedUnion("type", [
|
|
2002
1993
|
channelCreateOperationSchema,
|
|
2003
1994
|
agentCreateOperationSchema,
|
|
2004
1995
|
channelAddMemberOperationSchema,
|
|
2005
1996
|
integrationApproveAgentLoginOperationSchema,
|
|
2006
1997
|
integrationRegisterAppOperationSchema,
|
|
2007
|
-
integrationUpdateAppRegistrationOperationSchema
|
|
2008
|
-
migrationExportOperationSchema
|
|
1998
|
+
integrationUpdateAppRegistrationOperationSchema
|
|
2009
1999
|
]);
|
|
2010
2000
|
|
|
2011
2001
|
// ../shared/src/agentApiContract.ts
|
|
@@ -2704,6 +2694,51 @@ var agentApiReminderResponseSchema = passthroughObject({
|
|
|
2704
2694
|
var agentApiReminderLogResponseSchema = passthroughObject({
|
|
2705
2695
|
events: z2.array(agentApiReminderEventSummarySchema)
|
|
2706
2696
|
});
|
|
2697
|
+
var agentApiMigrationStateSchema = z2.enum(["prep", "ready", "in_transit", "arriving", "completed", "aborted", "failed"]);
|
|
2698
|
+
var agentApiMigrationSummarySchema = passthroughObject({
|
|
2699
|
+
id: z2.string(),
|
|
2700
|
+
agentId: z2.string(),
|
|
2701
|
+
sourceMachineId: z2.string(),
|
|
2702
|
+
targetMachineId: z2.string(),
|
|
2703
|
+
state: agentApiMigrationStateSchema,
|
|
2704
|
+
manifestPath: nullableStringSchema,
|
|
2705
|
+
manifestSha256: nullableStringSchema,
|
|
2706
|
+
arrivalReportPath: nullableStringSchema,
|
|
2707
|
+
arrivalReportSha256: nullableStringSchema,
|
|
2708
|
+
abortReason: nullableStringSchema,
|
|
2709
|
+
failureReason: nullableStringSchema,
|
|
2710
|
+
prepDeadlineAt: z2.string().datetime(),
|
|
2711
|
+
transferDeadlineAt: z2.string().datetime(),
|
|
2712
|
+
arrivalDeadlineAt: z2.string().datetime(),
|
|
2713
|
+
readyAt: nullableStringSchema,
|
|
2714
|
+
flippedAt: nullableStringSchema,
|
|
2715
|
+
arrivedAt: nullableStringSchema,
|
|
2716
|
+
completedAt: nullableStringSchema,
|
|
2717
|
+
abortedAt: nullableStringSchema,
|
|
2718
|
+
revision: z2.number().int().positive(),
|
|
2719
|
+
createdAt: z2.string().datetime(),
|
|
2720
|
+
updatedAt: z2.string().datetime()
|
|
2721
|
+
});
|
|
2722
|
+
var agentApiMigrationBeginBodySchema = passthroughObject({
|
|
2723
|
+
targetMachineId: z2.string().trim().min(1),
|
|
2724
|
+
prepDeadlineMs: z2.number().int().positive().optional(),
|
|
2725
|
+
transferDeadlineMs: z2.number().int().positive().optional(),
|
|
2726
|
+
arrivalDeadlineMs: z2.number().int().positive().optional()
|
|
2727
|
+
});
|
|
2728
|
+
var agentApiMigrationReadyBodySchema = passthroughObject({
|
|
2729
|
+
manifestPath: z2.string().trim().min(1),
|
|
2730
|
+
manifestSha256: optionalStringSchema
|
|
2731
|
+
});
|
|
2732
|
+
var agentApiMigrationArrivedBodySchema = passthroughObject({
|
|
2733
|
+
reportPath: optionalStringSchema,
|
|
2734
|
+
reportSha256: optionalStringSchema
|
|
2735
|
+
});
|
|
2736
|
+
var agentApiMigrationResponseSchema = passthroughObject({
|
|
2737
|
+
migration: agentApiMigrationSummarySchema
|
|
2738
|
+
});
|
|
2739
|
+
var agentApiMigrationStatusResponseSchema = passthroughObject({
|
|
2740
|
+
migration: agentApiMigrationSummarySchema.nullable()
|
|
2741
|
+
});
|
|
2707
2742
|
function route(input) {
|
|
2708
2743
|
return {
|
|
2709
2744
|
...input,
|
|
@@ -2951,6 +2986,46 @@ var agentApiContract = {
|
|
|
2951
2986
|
request: { body: agentApiTaskUpdateStatusBodySchema },
|
|
2952
2987
|
response: { body: agentApiTaskUpdateStatusResponseSchema }
|
|
2953
2988
|
}),
|
|
2989
|
+
migrationBegin: route({
|
|
2990
|
+
key: "migrationBegin",
|
|
2991
|
+
method: "POST",
|
|
2992
|
+
path: "/migrations",
|
|
2993
|
+
client: { resource: "migrations", method: "begin" },
|
|
2994
|
+
capability: "server",
|
|
2995
|
+
description: "Begin a migration for the bound agent credential.",
|
|
2996
|
+
request: { body: agentApiMigrationBeginBodySchema },
|
|
2997
|
+
response: { body: agentApiMigrationResponseSchema }
|
|
2998
|
+
}),
|
|
2999
|
+
migrationStatus: route({
|
|
3000
|
+
key: "migrationStatus",
|
|
3001
|
+
method: "GET",
|
|
3002
|
+
path: "/migrations/current",
|
|
3003
|
+
client: { resource: "migrations", method: "status" },
|
|
3004
|
+
capability: "read",
|
|
3005
|
+
description: "Read the active migration for the bound agent credential, if any.",
|
|
3006
|
+
request: {},
|
|
3007
|
+
response: { body: agentApiMigrationStatusResponseSchema }
|
|
3008
|
+
}),
|
|
3009
|
+
migrationReady: route({
|
|
3010
|
+
key: "migrationReady",
|
|
3011
|
+
method: "POST",
|
|
3012
|
+
path: "/migrations/ready",
|
|
3013
|
+
client: { resource: "migrations", method: "ready" },
|
|
3014
|
+
capability: "read",
|
|
3015
|
+
description: "Mark the bound agent's active migration prep phase ready.",
|
|
3016
|
+
request: { body: agentApiMigrationReadyBodySchema },
|
|
3017
|
+
response: { body: agentApiMigrationResponseSchema }
|
|
3018
|
+
}),
|
|
3019
|
+
migrationArrived: route({
|
|
3020
|
+
key: "migrationArrived",
|
|
3021
|
+
method: "POST",
|
|
3022
|
+
path: "/migrations/arrived",
|
|
3023
|
+
client: { resource: "migrations", method: "arrived" },
|
|
3024
|
+
capability: "read",
|
|
3025
|
+
description: "Mark the bound agent's active migration arrival phase complete.",
|
|
3026
|
+
request: { body: agentApiMigrationArrivedBodySchema },
|
|
3027
|
+
response: { body: agentApiMigrationResponseSchema }
|
|
3028
|
+
}),
|
|
2954
3029
|
reminderList: route({
|
|
2955
3030
|
key: "reminderList",
|
|
2956
3031
|
method: "GET",
|
|
@@ -3258,6 +3333,14 @@ var AUTH_REFRESH_ROTATION_RETRY_RTT_BUDGET_MS = 1e3;
|
|
|
3258
3333
|
var AUTH_REFRESH_ROTATION_LOSER_WORST_CHAIN_MS = AUTH_REFRESH_ROTATION_DISCOVERY_LAG_BUDGET_MS + AUTH_REFRESH_ROTATED_TOKEN_WAIT_MS + AUTH_REFRESH_ROTATION_RETRY_RTT_BUDGET_MS;
|
|
3259
3334
|
var AUTH_REFRESH_ROTATION_REPLAY_GRACE_MARGIN_MS = AUTH_REFRESH_ROTATED_REPLAY_GRACE_MS - AUTH_REFRESH_ROTATION_LOSER_WORST_CHAIN_MS;
|
|
3260
3335
|
|
|
3336
|
+
// ../shared/src/clock.ts
|
|
3337
|
+
function currentTimeMs() {
|
|
3338
|
+
return Date.now();
|
|
3339
|
+
}
|
|
3340
|
+
function currentDate() {
|
|
3341
|
+
return new Date(currentTimeMs());
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3261
3344
|
// ../shared/src/agentInbox.ts
|
|
3262
3345
|
function formatAgentInboxDelta(rows, options = {}) {
|
|
3263
3346
|
const totalPendingMessages = options.totalPendingMessages;
|
|
@@ -3640,7 +3723,7 @@ var EXTERNAL_AGENT_RUNTIME_DISPLAY_NAME = "External agent";
|
|
|
3640
3723
|
var RUNTIMES = [
|
|
3641
3724
|
{ id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
|
|
3642
3725
|
{ id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
|
|
3643
|
-
{ id: "builtin", displayName: "
|
|
3726
|
+
{ id: "builtin", displayName: "Built-in Pi", binary: "", supported: true },
|
|
3644
3727
|
{ id: "antigravity", displayName: "Antigravity CLI", binary: "agy", supported: true },
|
|
3645
3728
|
// Kimi: prefer the in-process SDK (`kimi-sdk` → "Kimi Code") for new agents.
|
|
3646
3729
|
// The legacy `kimi` (kimi-cli child-process) entry stays for backward compat
|
|
@@ -4338,8 +4421,8 @@ async function executeResponseRequest(url, init, {
|
|
|
4338
4421
|
}
|
|
4339
4422
|
|
|
4340
4423
|
// src/directUploadCapability.ts
|
|
4341
|
-
function joinUrl(base,
|
|
4342
|
-
return `${base.replace(/\/+$/, "")}${
|
|
4424
|
+
function joinUrl(base, path23) {
|
|
4425
|
+
return `${base.replace(/\/+$/, "")}${path23}`;
|
|
4343
4426
|
}
|
|
4344
4427
|
function jsonHeaders(apiKey) {
|
|
4345
4428
|
return {
|
|
@@ -5029,6 +5112,15 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
|
|
|
5029
5112
|
return candidates.filter((candidate) => existsSync(candidate.path));
|
|
5030
5113
|
}
|
|
5031
5114
|
|
|
5115
|
+
// src/authEnv.ts
|
|
5116
|
+
var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
|
|
5117
|
+
var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
|
|
5118
|
+
function scrubDaemonChildEnv(env) {
|
|
5119
|
+
delete env[DAEMON_API_KEY_ENV];
|
|
5120
|
+
delete env[SLOCK_AGENT_TOKEN_ENV];
|
|
5121
|
+
return env;
|
|
5122
|
+
}
|
|
5123
|
+
|
|
5032
5124
|
// src/agentCredentialProxy.ts
|
|
5033
5125
|
import { randomBytes } from "crypto";
|
|
5034
5126
|
import http from "http";
|
|
@@ -6610,7 +6702,9 @@ var LOOPBACK_NO_PROXY = "127.0.0.1,localhost";
|
|
|
6610
6702
|
var CLI_TRANSPORT_TRACE_DIR_ENV = "SLOCK_CLI_TRANSPORT_TRACE_DIR";
|
|
6611
6703
|
var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
6612
6704
|
var RAW_CREDENTIAL_ENV_DENYLIST = [
|
|
6613
|
-
"
|
|
6705
|
+
"SLOCK_AGENT_TOKEN",
|
|
6706
|
+
"SLOCK_AGENT_CREDENTIAL_KEY",
|
|
6707
|
+
"SLOCK_AGENT_CREDENTIAL_KEY_FILE"
|
|
6614
6708
|
];
|
|
6615
6709
|
var WORKSPACE_CLI_TRANSPORT_FILENAMES = [
|
|
6616
6710
|
"agent-token",
|
|
@@ -6965,7 +7059,7 @@ set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
|
|
|
6965
7059
|
SLOCK_SERVER_URL: ctx.config.serverUrl,
|
|
6966
7060
|
PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
|
|
6967
7061
|
};
|
|
6968
|
-
|
|
7062
|
+
scrubDaemonChildEnv(spawnEnv);
|
|
6969
7063
|
for (const key of RAW_CREDENTIAL_ENV_DENYLIST) {
|
|
6970
7064
|
delete spawnEnv[key];
|
|
6971
7065
|
}
|
|
@@ -7533,7 +7627,7 @@ function requiresWindowsShell(command, platform = process.platform) {
|
|
|
7533
7627
|
}
|
|
7534
7628
|
function resolveCommandOnPath(command, deps = {}) {
|
|
7535
7629
|
const platform = deps.platform ?? process.platform;
|
|
7536
|
-
const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
|
|
7630
|
+
const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
|
|
7537
7631
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
7538
7632
|
const existsSyncFn = deps.existsSyncFn ?? existsSync3;
|
|
7539
7633
|
if (platform === "win32") {
|
|
@@ -7559,7 +7653,7 @@ function firstExistingPath(candidates, deps = {}) {
|
|
|
7559
7653
|
return null;
|
|
7560
7654
|
}
|
|
7561
7655
|
function readCommandVersion(command, args = [], deps = {}) {
|
|
7562
|
-
const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
|
|
7656
|
+
const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
|
|
7563
7657
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
7564
7658
|
try {
|
|
7565
7659
|
const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
|
|
@@ -7985,7 +8079,7 @@ function codexNotificationDiagnosticEvent(message) {
|
|
|
7985
8079
|
return null;
|
|
7986
8080
|
}
|
|
7987
8081
|
const sessionId = codexMessageThreadId(message);
|
|
7988
|
-
const
|
|
8082
|
+
const path23 = boundedString(params.path);
|
|
7989
8083
|
return {
|
|
7990
8084
|
kind: "runtime_diagnostic",
|
|
7991
8085
|
severity: "warning",
|
|
@@ -7993,7 +8087,7 @@ function codexNotificationDiagnosticEvent(message) {
|
|
|
7993
8087
|
itemType: message.method,
|
|
7994
8088
|
message: diagnosticMessage,
|
|
7995
8089
|
...details ? { details } : {},
|
|
7996
|
-
...
|
|
8090
|
+
...path23 ? { path: path23 } : {},
|
|
7997
8091
|
...params.range !== void 0 ? { range: params.range } : {},
|
|
7998
8092
|
payloadBytes: payloadBytes(params),
|
|
7999
8093
|
...sessionId ? { sessionId } : {}
|
|
@@ -9643,11 +9737,11 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
|
|
|
9643
9737
|
return parseCursorModelsOutput(String(result.stdout || ""));
|
|
9644
9738
|
}
|
|
9645
9739
|
function buildCursorModelProbeEnv(deps = {}) {
|
|
9646
|
-
return withWindowsUserEnvironment({
|
|
9740
|
+
return scrubDaemonChildEnv(withWindowsUserEnvironment({
|
|
9647
9741
|
...deps.env ?? process.env,
|
|
9648
9742
|
FORCE_COLOR: "0",
|
|
9649
9743
|
NO_COLOR: "1"
|
|
9650
|
-
}, deps);
|
|
9744
|
+
}, deps));
|
|
9651
9745
|
}
|
|
9652
9746
|
function runCursorModelsCommand() {
|
|
9653
9747
|
return spawnSync("cursor-agent", ["models"], {
|
|
@@ -9703,7 +9797,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
|
|
|
9703
9797
|
}
|
|
9704
9798
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
|
|
9705
9799
|
const existsSyncFn = deps.existsSyncFn ?? existsSync5;
|
|
9706
|
-
const env = deps.env ?? process.env;
|
|
9800
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
9707
9801
|
const winPath = path8.win32;
|
|
9708
9802
|
let geminiEntry = null;
|
|
9709
9803
|
try {
|
|
@@ -9840,12 +9934,15 @@ var GeminiDriver = class {
|
|
|
9840
9934
|
// src/drivers/kimi.ts
|
|
9841
9935
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9842
9936
|
import { spawn as spawn7 } from "child_process";
|
|
9843
|
-
import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
9937
|
+
import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
9844
9938
|
import os4 from "os";
|
|
9845
9939
|
import path9 from "path";
|
|
9846
9940
|
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
9847
9941
|
var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
|
|
9848
9942
|
var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
|
|
9943
|
+
var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
|
|
9944
|
+
var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
|
|
9945
|
+
var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
|
|
9849
9946
|
function parseToolArguments(raw) {
|
|
9850
9947
|
if (typeof raw !== "string") return raw;
|
|
9851
9948
|
try {
|
|
@@ -9854,6 +9951,73 @@ function parseToolArguments(raw) {
|
|
|
9854
9951
|
return raw;
|
|
9855
9952
|
}
|
|
9856
9953
|
}
|
|
9954
|
+
function readKimiConfigSource(home = os4.homedir(), env = process.env) {
|
|
9955
|
+
const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
9956
|
+
if (inlineConfig && inlineConfig.trim()) {
|
|
9957
|
+
return {
|
|
9958
|
+
raw: inlineConfig,
|
|
9959
|
+
explicitPath: null,
|
|
9960
|
+
sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
|
|
9961
|
+
};
|
|
9962
|
+
}
|
|
9963
|
+
const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
9964
|
+
const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
|
|
9965
|
+
try {
|
|
9966
|
+
return {
|
|
9967
|
+
raw: readFileSync3(configPath, "utf8"),
|
|
9968
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
9969
|
+
sourcePath: configPath
|
|
9970
|
+
};
|
|
9971
|
+
} catch {
|
|
9972
|
+
return {
|
|
9973
|
+
raw: null,
|
|
9974
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
9975
|
+
sourcePath: configPath
|
|
9976
|
+
};
|
|
9977
|
+
}
|
|
9978
|
+
}
|
|
9979
|
+
function buildKimiSpawnEnv(env = process.env) {
|
|
9980
|
+
const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
|
|
9981
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
9982
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
9983
|
+
return scrubDaemonChildEnv(spawnEnv);
|
|
9984
|
+
}
|
|
9985
|
+
function buildKimiEffectiveEnv(ctx, overrideEnv) {
|
|
9986
|
+
return {
|
|
9987
|
+
...process.env,
|
|
9988
|
+
...ctx.config.envVars || {},
|
|
9989
|
+
...overrideEnv || {}
|
|
9990
|
+
};
|
|
9991
|
+
}
|
|
9992
|
+
function buildKimiLaunchOptions(ctx, opts = {}) {
|
|
9993
|
+
const env = buildKimiEffectiveEnv(ctx, opts.env);
|
|
9994
|
+
const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
|
|
9995
|
+
const args = [];
|
|
9996
|
+
let configFilePath = null;
|
|
9997
|
+
let configContent = null;
|
|
9998
|
+
if (source.explicitPath) {
|
|
9999
|
+
configFilePath = source.explicitPath;
|
|
10000
|
+
} else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
|
|
10001
|
+
configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
|
|
10002
|
+
configContent = source.raw;
|
|
10003
|
+
if (opts.writeGeneratedConfig !== false) {
|
|
10004
|
+
writeFileSync3(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
|
|
10005
|
+
chmodSync(configFilePath, 384);
|
|
10006
|
+
}
|
|
10007
|
+
}
|
|
10008
|
+
if (configFilePath) {
|
|
10009
|
+
args.push("--config-file", configFilePath);
|
|
10010
|
+
}
|
|
10011
|
+
if (ctx.config.model && ctx.config.model !== "default") {
|
|
10012
|
+
args.push("--model", ctx.config.model);
|
|
10013
|
+
}
|
|
10014
|
+
return {
|
|
10015
|
+
args,
|
|
10016
|
+
env: buildKimiSpawnEnv(env),
|
|
10017
|
+
configFilePath,
|
|
10018
|
+
configContent
|
|
10019
|
+
};
|
|
10020
|
+
}
|
|
9857
10021
|
function resolveKimiSpawn(commandArgs, deps = {}) {
|
|
9858
10022
|
return {
|
|
9859
10023
|
command: resolveCommandOnPath("kimi", deps) ?? "kimi",
|
|
@@ -9877,7 +10041,25 @@ var KimiDriver = class {
|
|
|
9877
10041
|
};
|
|
9878
10042
|
model = {
|
|
9879
10043
|
detectedModelsVerifiedAs: "launchable",
|
|
9880
|
-
toLaunchSpec: (modelId) =>
|
|
10044
|
+
toLaunchSpec: (modelId, ctx, opts) => {
|
|
10045
|
+
if (!ctx) return { args: ["--model", modelId] };
|
|
10046
|
+
const launchCtx = {
|
|
10047
|
+
...ctx,
|
|
10048
|
+
config: {
|
|
10049
|
+
...ctx.config,
|
|
10050
|
+
model: modelId
|
|
10051
|
+
}
|
|
10052
|
+
};
|
|
10053
|
+
const launch = buildKimiLaunchOptions(launchCtx, {
|
|
10054
|
+
home: opts?.home,
|
|
10055
|
+
writeGeneratedConfig: false
|
|
10056
|
+
});
|
|
10057
|
+
return {
|
|
10058
|
+
args: launch.args,
|
|
10059
|
+
env: launch.env,
|
|
10060
|
+
configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
|
|
10061
|
+
};
|
|
10062
|
+
}
|
|
9881
10063
|
};
|
|
9882
10064
|
supportsStdinNotification = true;
|
|
9883
10065
|
busyDeliveryMode = "direct";
|
|
@@ -9901,21 +10083,23 @@ var KimiDriver = class {
|
|
|
9901
10083
|
` system_prompt_path: ./${KIMI_SYSTEM_PROMPT_FILE}`,
|
|
9902
10084
|
""
|
|
9903
10085
|
].join("\n"), "utf8");
|
|
10086
|
+
const launch = buildKimiLaunchOptions(ctx);
|
|
9904
10087
|
const args = [
|
|
9905
10088
|
"--wire",
|
|
9906
10089
|
"--yolo",
|
|
9907
10090
|
"--agent-file",
|
|
9908
10091
|
agentFilePath,
|
|
9909
10092
|
"--session",
|
|
9910
|
-
this.sessionId
|
|
10093
|
+
this.sessionId,
|
|
10094
|
+
...launch.args
|
|
9911
10095
|
];
|
|
9912
10096
|
const launchRuntimeFields = runtimeConfigToLaunchFields(hydrateRuntimeConfig(ctx.config));
|
|
9913
10097
|
if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
|
|
9914
10098
|
args.push("--model", launchRuntimeFields.model);
|
|
9915
10099
|
}
|
|
9916
10100
|
const spawnEnv = (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
|
|
9917
|
-
const
|
|
9918
|
-
const proc = spawn7(
|
|
10101
|
+
const spawnTarget = resolveKimiSpawn(args);
|
|
10102
|
+
const proc = spawn7(spawnTarget.command, spawnTarget.args, {
|
|
9919
10103
|
cwd: ctx.workingDirectory,
|
|
9920
10104
|
stdio: ["pipe", "pipe", "pipe"],
|
|
9921
10105
|
env: spawnEnv,
|
|
@@ -9923,7 +10107,7 @@ var KimiDriver = class {
|
|
|
9923
10107
|
// and has an 8191-character command-line limit. Kimi's official
|
|
9924
10108
|
// installer/uv entrypoint is an executable, so launch it directly and
|
|
9925
10109
|
// keep prompts on stdin / files instead of routing through cmd.exe.
|
|
9926
|
-
shell:
|
|
10110
|
+
shell: spawnTarget.shell
|
|
9927
10111
|
});
|
|
9928
10112
|
proc.stdin?.write(JSON.stringify({
|
|
9929
10113
|
jsonrpc: "2.0",
|
|
@@ -10036,14 +10220,9 @@ var KimiDriver = class {
|
|
|
10036
10220
|
return detectKimiModels();
|
|
10037
10221
|
}
|
|
10038
10222
|
};
|
|
10039
|
-
function detectKimiModels(home = os4.homedir()) {
|
|
10040
|
-
const
|
|
10041
|
-
|
|
10042
|
-
try {
|
|
10043
|
-
raw = readFileSync3(configPath, "utf8");
|
|
10044
|
-
} catch {
|
|
10045
|
-
return null;
|
|
10046
|
-
}
|
|
10223
|
+
function detectKimiModels(home = os4.homedir(), opts = {}) {
|
|
10224
|
+
const raw = readKimiConfigSource(home, opts.env).raw;
|
|
10225
|
+
if (raw === null) return null;
|
|
10047
10226
|
const models = [];
|
|
10048
10227
|
const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
|
|
10049
10228
|
const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
|
|
@@ -10722,7 +10901,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
|
|
|
10722
10901
|
const platform = deps.platform ?? process.platform;
|
|
10723
10902
|
const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
|
|
10724
10903
|
const result = spawnSyncFn("opencode", ["models"], {
|
|
10725
|
-
env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
10904
|
+
env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
|
|
10726
10905
|
encoding: "utf8",
|
|
10727
10906
|
timeout: 5e3,
|
|
10728
10907
|
shell: platform === "win32"
|
|
@@ -11018,7 +11197,9 @@ function createPiSdkEventMappingState(sessionId = null) {
|
|
|
11018
11197
|
return {
|
|
11019
11198
|
sessionId,
|
|
11020
11199
|
sessionAnnounced: false,
|
|
11021
|
-
sawTextDelta: false
|
|
11200
|
+
sawTextDelta: false,
|
|
11201
|
+
thinkingBuffers: /* @__PURE__ */ new Map(),
|
|
11202
|
+
announcedThinkingIndexes: /* @__PURE__ */ new Set()
|
|
11022
11203
|
};
|
|
11023
11204
|
}
|
|
11024
11205
|
function buildPiSessionDir(workingDirectory) {
|
|
@@ -11233,10 +11414,40 @@ function pushSessionInitIfNeeded2(state, events) {
|
|
|
11233
11414
|
state.sessionAnnounced = true;
|
|
11234
11415
|
}
|
|
11235
11416
|
}
|
|
11417
|
+
function piAssistantContentIndex(assistantEvent) {
|
|
11418
|
+
return "contentIndex" in assistantEvent && typeof assistantEvent.contentIndex === "number" ? assistantEvent.contentIndex : 0;
|
|
11419
|
+
}
|
|
11420
|
+
function announcePiThinkingIfNeeded(index, state) {
|
|
11421
|
+
if (state.announcedThinkingIndexes.has(index)) return [];
|
|
11422
|
+
state.announcedThinkingIndexes.add(index);
|
|
11423
|
+
return [{ kind: "thinking", text: "" }];
|
|
11424
|
+
}
|
|
11425
|
+
function resetPiThinking(index, state) {
|
|
11426
|
+
state.thinkingBuffers.delete(index);
|
|
11427
|
+
state.announcedThinkingIndexes.delete(index);
|
|
11428
|
+
}
|
|
11236
11429
|
function mapPiAssistantMessageEvent(assistantEvent, state) {
|
|
11237
11430
|
switch (assistantEvent.type) {
|
|
11238
|
-
case "
|
|
11239
|
-
|
|
11431
|
+
case "thinking_start": {
|
|
11432
|
+
const index = piAssistantContentIndex(assistantEvent);
|
|
11433
|
+
resetPiThinking(index, state);
|
|
11434
|
+
return announcePiThinkingIfNeeded(index, state);
|
|
11435
|
+
}
|
|
11436
|
+
case "thinking_delta": {
|
|
11437
|
+
const index = piAssistantContentIndex(assistantEvent);
|
|
11438
|
+
const events = announcePiThinkingIfNeeded(index, state);
|
|
11439
|
+
if (typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0) {
|
|
11440
|
+
state.thinkingBuffers.set(index, `${state.thinkingBuffers.get(index) ?? ""}${assistantEvent.delta}`);
|
|
11441
|
+
}
|
|
11442
|
+
return events;
|
|
11443
|
+
}
|
|
11444
|
+
case "thinking_end": {
|
|
11445
|
+
const index = piAssistantContentIndex(assistantEvent);
|
|
11446
|
+
const buffered = state.thinkingBuffers.get(index) ?? "";
|
|
11447
|
+
const text = typeof assistantEvent.content === "string" && assistantEvent.content.length > 0 ? assistantEvent.content : buffered;
|
|
11448
|
+
resetPiThinking(index, state);
|
|
11449
|
+
return text ? [{ kind: "thinking", text }] : [];
|
|
11450
|
+
}
|
|
11240
11451
|
case "text_delta":
|
|
11241
11452
|
if (typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0) {
|
|
11242
11453
|
state.sawTextDelta = true;
|
|
@@ -11247,8 +11458,6 @@ function mapPiAssistantMessageEvent(assistantEvent, state) {
|
|
|
11247
11458
|
return !state.sawTextDelta && typeof assistantEvent.content === "string" && assistantEvent.content.length > 0 ? [{ kind: "text", text: assistantEvent.content }] : [];
|
|
11248
11459
|
case "error":
|
|
11249
11460
|
return [{ kind: "error", message: piErrorMessage(assistantEvent.error.errorMessage || assistantEvent.error) }];
|
|
11250
|
-
case "thinking_start":
|
|
11251
|
-
case "thinking_end":
|
|
11252
11461
|
case "text_start":
|
|
11253
11462
|
case "toolcall_start":
|
|
11254
11463
|
case "toolcall_delta":
|
|
@@ -11292,6 +11501,8 @@ function mapPiSdkEventToParsedEvents(event, state) {
|
|
|
11292
11501
|
case "message_start":
|
|
11293
11502
|
if (event.message.role === "assistant") {
|
|
11294
11503
|
state.sawTextDelta = false;
|
|
11504
|
+
state.thinkingBuffers.clear();
|
|
11505
|
+
state.announcedThinkingIndexes.clear();
|
|
11295
11506
|
}
|
|
11296
11507
|
return events;
|
|
11297
11508
|
case "message_update":
|
|
@@ -14768,8 +14979,9 @@ function classifyTerminalFailure(ap) {
|
|
|
14768
14979
|
if (lower.includes("usage limit") || lower.includes("quota exceeded") || lower.includes("quota limit") || lower.includes("budget limit exceeded") || lower.includes("usage not included in your plan") || lower.includes("modelnotfounderror") || lower.includes("requested entity was not found") || lower.includes("model deprecated") || lower.includes("model not found") || lower.includes("model is not supported") || lower.includes("unsupported model") || /\bmodel\b.*\bnot supported\b/i.test(text) || diagnostics.spanAttrs.runtime_error_action_required === true || isProviderStreamFailureText(text) || isRuntimeStartTimeoutText(text)) {
|
|
14769
14980
|
const actionRequired = diagnostics.spanAttrs.runtime_error_action_required === true;
|
|
14770
14981
|
return {
|
|
14771
|
-
detail: actionRequired ?
|
|
14772
|
-
actionRequired
|
|
14982
|
+
detail: actionRequired ? formatRuntimeActionRequiredMessage(ap) : text,
|
|
14983
|
+
actionRequired,
|
|
14984
|
+
...actionRequired ? { entries: buildRuntimeActionRequiredEntries(ap, text, diagnostics) } : {}
|
|
14773
14985
|
};
|
|
14774
14986
|
}
|
|
14775
14987
|
}
|
|
@@ -14785,6 +14997,52 @@ function classifyStickyTerminalFailure(ap) {
|
|
|
14785
14997
|
if (isRuntimeStartTimeoutText(terminalFailure.detail)) return null;
|
|
14786
14998
|
return null;
|
|
14787
14999
|
}
|
|
15000
|
+
function formatRuntimeActionRequiredMessage(ap) {
|
|
15001
|
+
if (ap.driver.id === "claude" && isClaudeCustomProviderConfig(ap.config)) {
|
|
15002
|
+
return "Claude Code custom provider authentication failed. Check this agent's custom Claude provider API key/API URL, then retry starting this agent.";
|
|
15003
|
+
}
|
|
15004
|
+
return formatRuntimeLoginRequiredMessage(ap.driver.id);
|
|
15005
|
+
}
|
|
15006
|
+
function buildRuntimeActionRequiredEntries(ap, text, diagnostics) {
|
|
15007
|
+
const excerpt = String(diagnostics.eventAttrs.runtime_error_message_excerpt ?? "").trim();
|
|
15008
|
+
const subtype = classifyRuntimeActionRequiredSubtype(ap, text);
|
|
15009
|
+
const detail = formatRuntimeActionRequiredMessage(ap);
|
|
15010
|
+
const entries = [
|
|
15011
|
+
{ kind: "text", text: `Error: ${detail}` },
|
|
15012
|
+
{ kind: "text", text: `Runtime auth diagnostic: ${subtype}` }
|
|
15013
|
+
];
|
|
15014
|
+
if (excerpt) {
|
|
15015
|
+
entries.push({ kind: "text", text: `Raw error excerpt (redacted): ${excerpt}` });
|
|
15016
|
+
}
|
|
15017
|
+
if (ap.driver.id === "claude") {
|
|
15018
|
+
entries.push({
|
|
15019
|
+
kind: "text",
|
|
15020
|
+
text: isClaudeCustomProviderConfig(ap.config) ? "Claude auth mode: custom provider. Local Claude Code login does not fix this branch; check this agent's provider API key/API URL." : "Claude auth mode: default host login. If Terminal works, set this agent's Claude command to the absolute path from `which claude` and restart Raft Computer."
|
|
15021
|
+
});
|
|
15022
|
+
}
|
|
15023
|
+
return entries;
|
|
15024
|
+
}
|
|
15025
|
+
function classifyRuntimeActionRequiredSubtype(ap, text) {
|
|
15026
|
+
const lower = text.toLowerCase();
|
|
15027
|
+
const customClaudeProvider = ap.driver.id === "claude" && isClaudeCustomProviderConfig(ap.config);
|
|
15028
|
+
if (customClaudeProvider) {
|
|
15029
|
+
if (/\bmissing\b.*\b(?:api\s*)?key\b|\b(?:api\s*)?key\b.*\bnot set\b|\bmissing\b.*\btoken\b|\bno\b.*\btoken\b/i.test(text)) {
|
|
15030
|
+
return "custom_provider_missing_key";
|
|
15031
|
+
}
|
|
15032
|
+
if (/\binvalid\b.*\b(?:api\s*)?key\b|\bunauthorized\b|\b401\b|\b403\b|auth(?:entication)? failed/i.test(text)) {
|
|
15033
|
+
return "custom_provider_invalid_key";
|
|
15034
|
+
}
|
|
15035
|
+
return "custom_provider_auth_error";
|
|
15036
|
+
}
|
|
15037
|
+
if (ap.driver.id === "claude") {
|
|
15038
|
+
if (lower.includes("not logged in") || lower.includes("not signed in") || lower.includes("please log in") || lower.includes("login required")) {
|
|
15039
|
+
return "host_claude_login_required";
|
|
15040
|
+
}
|
|
15041
|
+
return "runtime_auth_error";
|
|
15042
|
+
}
|
|
15043
|
+
if (ap.driver.id === "builtin") return "builtin_provider_auth_error";
|
|
15044
|
+
return "runtime_auth_error";
|
|
15045
|
+
}
|
|
14788
15046
|
function isAuthClassTerminalLine(text) {
|
|
14789
15047
|
return buildRuntimeErrorDiagnosticEnvelope(text).spanAttrs.runtime_error_action_required === true;
|
|
14790
15048
|
}
|
|
@@ -16986,7 +17244,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
16986
17244
|
agentId,
|
|
16987
17245
|
"error",
|
|
16988
17246
|
terminalFailureDetail.detail,
|
|
16989
|
-
[{ kind: "text", text: `Error: ${terminalFailureDetail.detail}` }],
|
|
17247
|
+
terminalFailureDetail.entries ?? [{ kind: "text", text: `Error: ${terminalFailureDetail.detail}` }],
|
|
16990
17248
|
ap.launchId,
|
|
16991
17249
|
"runtime_error"
|
|
16992
17250
|
);
|
|
@@ -17017,9 +17275,10 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
17017
17275
|
agentProcess.decisionErrorWindow.recordRuntimeError(startResult.error);
|
|
17018
17276
|
}
|
|
17019
17277
|
if (startResult.error && diagnostics?.spanAttrs.runtime_error_action_required === true) {
|
|
17020
|
-
const
|
|
17278
|
+
const terminalFailure = classifyTerminalFailure(agentProcess);
|
|
17279
|
+
const visibleErrorMessage = terminalFailure?.detail ?? formatRuntimeActionRequiredMessage(agentProcess);
|
|
17021
17280
|
this.broadcastActivity(agentId, "error", visibleErrorMessage, [
|
|
17022
|
-
{ kind: "text", text: `Error: ${visibleErrorMessage}` }
|
|
17281
|
+
...terminalFailure?.entries ?? [{ kind: "text", text: `Error: ${visibleErrorMessage}` }]
|
|
17023
17282
|
], agentProcess.launchId, "runtime_error");
|
|
17024
17283
|
}
|
|
17025
17284
|
throw new Error(`Runtime session failed to start: ${startResult.reason}${startResult.error ? ` (${startResult.error})` : ""}`);
|
|
@@ -20182,11 +20441,12 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
20182
20441
|
ap.decisionErrorWindow.recordRuntimeError(event.message);
|
|
20183
20442
|
}
|
|
20184
20443
|
let visibleErrorMessage = event.message;
|
|
20444
|
+
let visibleErrorEntries;
|
|
20185
20445
|
if (ap) {
|
|
20186
20446
|
const runtimeErrorDiagnostics = buildRuntimeErrorDiagnosticEnvelope(event.message);
|
|
20187
20447
|
const runtimeErrorFingerprint = typeof runtimeErrorDiagnostics.spanAttrs.runtime_error_fingerprint === "string" ? runtimeErrorDiagnostics.spanAttrs.runtime_error_fingerprint : null;
|
|
20188
20448
|
if (runtimeErrorDiagnostics.spanAttrs.runtime_error_action_required === true) {
|
|
20189
|
-
visibleErrorMessage =
|
|
20449
|
+
visibleErrorMessage = formatRuntimeActionRequiredMessage(ap);
|
|
20190
20450
|
}
|
|
20191
20451
|
const shouldDisableToolBoundaryFlush = ap.runtime.descriptor.busyDelivery === "gated" && this.isThinkingBlockMutationError(event.message);
|
|
20192
20452
|
const backoffFailPoint = this.runtimeErrorDeliveryBackoffFailPointForTesting?.({
|
|
@@ -20212,6 +20472,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
20212
20472
|
if (fingerprintFence) {
|
|
20213
20473
|
visibleErrorMessage = fingerprintFence.detail;
|
|
20214
20474
|
}
|
|
20475
|
+
visibleErrorEntries = terminalFailure?.entries;
|
|
20215
20476
|
this.noteRuntimeErrorDeliveryBackoff(agentId, ap, event.message, terminalFailure, stickyTerminalFailure, backoffReasonOverride);
|
|
20216
20477
|
if (reduction.shouldDisableToolBoundaryFlush) {
|
|
20217
20478
|
this.recordGatedSteeringEvent(agentId, ap, "disabled", {
|
|
@@ -20258,9 +20519,14 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
20258
20519
|
}
|
|
20259
20520
|
}
|
|
20260
20521
|
}
|
|
20261
|
-
this.broadcastActivity(
|
|
20262
|
-
|
|
20263
|
-
|
|
20522
|
+
this.broadcastActivity(
|
|
20523
|
+
agentId,
|
|
20524
|
+
"error",
|
|
20525
|
+
visibleErrorMessage,
|
|
20526
|
+
visibleErrorEntries ?? [{ kind: "text", text: `Error: ${visibleErrorMessage}` }],
|
|
20527
|
+
void 0,
|
|
20528
|
+
"runtime_error"
|
|
20529
|
+
);
|
|
20264
20530
|
break;
|
|
20265
20531
|
}
|
|
20266
20532
|
}
|
|
@@ -20927,8 +21193,11 @@ var DaemonConnection = class {
|
|
|
20927
21193
|
doConnect() {
|
|
20928
21194
|
if (!this.shouldConnect) return;
|
|
20929
21195
|
if (this.ws && this.ws.readyState !== WebSocket.CLOSED) return;
|
|
20930
|
-
const wsUrl = this.options.serverUrl.replace(/^http/, "ws") +
|
|
20931
|
-
const wsOptions =
|
|
21196
|
+
const wsUrl = this.options.serverUrl.replace(/^http/, "ws") + "/daemon/connect";
|
|
21197
|
+
const wsOptions = {
|
|
21198
|
+
...buildWebSocketOptions(wsUrl, this.options.proxyEnv ?? process.env),
|
|
21199
|
+
headers: { Authorization: `Bearer ${this.options.apiKey}` }
|
|
21200
|
+
};
|
|
20932
21201
|
logger.info(`[Daemon] Connecting to ${this.options.serverUrl}...`);
|
|
20933
21202
|
if (wsOptions?.agent) {
|
|
20934
21203
|
logger.info("[Daemon] Using configured proxy for WebSocket connection");
|
|
@@ -21924,136 +22193,651 @@ function assertLegacyDaemonKeyNotAdoptedByComputer(options) {
|
|
|
21924
22193
|
if (match) throw new LegacyDaemonKeyAdoptedByComputerError(match);
|
|
21925
22194
|
}
|
|
21926
22195
|
|
|
21927
|
-
// src/
|
|
21928
|
-
import { createHash as createHash7 } from "crypto";
|
|
21929
|
-
import
|
|
21930
|
-
import {
|
|
21931
|
-
|
|
21932
|
-
var
|
|
21933
|
-
var
|
|
21934
|
-
|
|
21935
|
-
|
|
21936
|
-
|
|
21937
|
-
|
|
21938
|
-
|
|
21939
|
-
|
|
21940
|
-
var SECRET_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
21941
|
-
".env",
|
|
21942
|
-
".env.local",
|
|
21943
|
-
".env.development",
|
|
21944
|
-
".env.production",
|
|
21945
|
-
".env.test",
|
|
21946
|
-
"credentials.json",
|
|
21947
|
-
"credential.json"
|
|
21948
|
-
]);
|
|
21949
|
-
var ENV_KEY_PATTERN = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/;
|
|
21950
|
-
async function buildAgentMigrationExportManifest(input) {
|
|
21951
|
-
const slockHome = path19.resolve(input.slockHome);
|
|
21952
|
-
const workspace = path19.resolve(input.workspacePath ?? path19.join(slockHome, "agents", input.agentId));
|
|
21953
|
-
const proposedIncludes = normalizeProposalPaths(input.cooperativeManifest?.include, workspace, "include");
|
|
21954
|
-
const proposedRegenerableExcludes = normalizeProposalPaths(input.cooperativeManifest?.exclude_regenerable, workspace, "exclude_regenerable");
|
|
21955
|
-
const cleanedProposal = normalizeProposalPaths(input.cooperativeManifest?.cleaned, workspace, "cleaned");
|
|
21956
|
-
const state = {
|
|
21957
|
-
workspace,
|
|
21958
|
-
files: [],
|
|
21959
|
-
excludedRegenerable: [],
|
|
21960
|
-
promotedIncludes: [],
|
|
21961
|
-
proposalRefusals: [
|
|
21962
|
-
...proposedIncludes.refusals,
|
|
21963
|
-
...proposedRegenerableExcludes.refusals,
|
|
21964
|
-
...cleanedProposal.refusals
|
|
21965
|
-
],
|
|
21966
|
-
unreachable: [],
|
|
21967
|
-
secretsDisclosed: [],
|
|
21968
|
-
seenWorkspacePaths: /* @__PURE__ */ new Set(),
|
|
21969
|
-
explicitIncludePaths: new Set(proposedIncludes.paths)
|
|
21970
|
-
};
|
|
21971
|
-
await walkWorkspace(workspace, "", new Set(proposedRegenerableExcludes.paths), state, { forceIncludeRegenerable: false });
|
|
21972
|
-
for (const requestedPath of proposedIncludes.paths) {
|
|
21973
|
-
if (!state.seenWorkspacePaths.has(requestedPath)) {
|
|
21974
|
-
await includeWorkspacePath(requestedPath, state, { forceIncludeRegenerable: true });
|
|
21975
|
-
}
|
|
22196
|
+
// src/agentMigrationHttpTransport.ts
|
|
22197
|
+
import { createHash as createHash7, randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
22198
|
+
import http2 from "http";
|
|
22199
|
+
import { Readable } from "stream";
|
|
22200
|
+
var AGENT_MIGRATION_TRANSPORT_HOST_ENV = "SLOCK_AGENT_MIGRATION_TRANSPORT_HOST";
|
|
22201
|
+
var AGENT_MIGRATION_TRANSPORT_PORT_ENV = "SLOCK_AGENT_MIGRATION_TRANSPORT_PORT";
|
|
22202
|
+
var AGENT_MIGRATION_TRANSPORT_PUBLIC_URL_ENV = "SLOCK_AGENT_MIGRATION_TRANSPORT_PUBLIC_URL";
|
|
22203
|
+
var AGENT_MIGRATION_CONTROL_SEAM_ENV = "SLOCK_AGENT_MIGRATION_CONTROL_SEAM";
|
|
22204
|
+
var CONTROL_REQUEST_MAX_BYTES = 8 * 1024 * 1024;
|
|
22205
|
+
var MigrationBundleStreamError = class extends Error {
|
|
22206
|
+
constructor(code) {
|
|
22207
|
+
super(code);
|
|
22208
|
+
this.code = code;
|
|
21976
22209
|
}
|
|
21977
|
-
|
|
21978
|
-
|
|
21979
|
-
|
|
21980
|
-
|
|
22210
|
+
};
|
|
22211
|
+
var AgentMigrationGrantRegistry = class {
|
|
22212
|
+
grants = /* @__PURE__ */ new Map();
|
|
22213
|
+
now;
|
|
22214
|
+
constructor(now = currentDate) {
|
|
22215
|
+
this.now = now;
|
|
21981
22216
|
}
|
|
21982
|
-
|
|
21983
|
-
|
|
21984
|
-
|
|
21985
|
-
|
|
21986
|
-
|
|
21987
|
-
|
|
21988
|
-
|
|
21989
|
-
|
|
21990
|
-
|
|
21991
|
-
|
|
21992
|
-
|
|
21993
|
-
|
|
21994
|
-
|
|
21995
|
-
|
|
21996
|
-
|
|
21997
|
-
|
|
21998
|
-
},
|
|
21999
|
-
files: sortBundleEntries(state.files),
|
|
22000
|
-
excludedRegenerable: sortByPath(state.excludedRegenerable),
|
|
22001
|
-
promotedIncludes: sortByPath(state.promotedIncludes),
|
|
22002
|
-
proposalRefusals: sortByPath(state.proposalRefusals),
|
|
22003
|
-
unreachable: sortByPath(state.unreachable),
|
|
22004
|
-
secretsDisclosed: sortByPath([
|
|
22005
|
-
...state.secretsDisclosed,
|
|
22006
|
-
...normalizeProvidedSecretDisclosures(secretDisclosureProposal.paths)
|
|
22007
|
-
]),
|
|
22008
|
-
cleaned: cleanedProposal.paths,
|
|
22009
|
-
crossTreeRefs: crossTreeRefs.sort((a, b) => a.bundlePath.localeCompare(b.bundlePath))
|
|
22010
|
-
};
|
|
22011
|
-
}
|
|
22012
|
-
async function walkWorkspace(root, relativeDir, proposedRegenerableExcludes, state, opts) {
|
|
22013
|
-
const absoluteDir = path19.join(root, relativeDir);
|
|
22014
|
-
let entries;
|
|
22015
|
-
try {
|
|
22016
|
-
entries = await readdir4(absoluteDir, { withFileTypes: true });
|
|
22017
|
-
} catch {
|
|
22018
|
-
state.unreachable.push({
|
|
22019
|
-
path: relativeDir || ".",
|
|
22020
|
-
sourcePath: absoluteDir,
|
|
22021
|
-
reason: "read_error"
|
|
22022
|
-
});
|
|
22023
|
-
return;
|
|
22217
|
+
createGrant(input) {
|
|
22218
|
+
const token = input.token ?? randomBytes2(32).toString("base64url");
|
|
22219
|
+
const manifestSha256 = sha256Buffer(canonicalJsonBuffer(input.manifest));
|
|
22220
|
+
const grant = {
|
|
22221
|
+
grantId: input.grantId ?? randomBytes2(16).toString("hex"),
|
|
22222
|
+
tokenHash: hashToken(token),
|
|
22223
|
+
expiresAt: input.expiresAt,
|
|
22224
|
+
oneTimeUse: input.oneTimeUse ?? true,
|
|
22225
|
+
state: "issued",
|
|
22226
|
+
manifest: input.manifest,
|
|
22227
|
+
manifestSha256,
|
|
22228
|
+
bundleEtag: `"agent-migration-${manifestSha256}"`,
|
|
22229
|
+
bundleSizeBytes: normalizeBundleSizeBytes(input.bundleSizeBytes)
|
|
22230
|
+
};
|
|
22231
|
+
this.grants.set(grant.grantId, grant);
|
|
22232
|
+
return { grant, token };
|
|
22024
22233
|
}
|
|
22025
|
-
|
|
22026
|
-
|
|
22027
|
-
const relativePath = toPosixPath(path19.join(relativeDir, entry.name));
|
|
22028
|
-
if (entry.isDirectory()) {
|
|
22029
|
-
if (!opts.forceIncludeRegenerable && isRegenerablePath(relativePath)) {
|
|
22030
|
-
if (state.explicitIncludePaths.has(relativePath)) {
|
|
22031
|
-
await walkWorkspace(root, relativePath, proposedRegenerableExcludes, state, { forceIncludeRegenerable: true });
|
|
22032
|
-
continue;
|
|
22033
|
-
}
|
|
22034
|
-
if (hasExplicitIncludeDescendant(relativePath, state.explicitIncludePaths)) {
|
|
22035
|
-
await walkWorkspace(root, relativePath, proposedRegenerableExcludes, state, opts);
|
|
22036
|
-
continue;
|
|
22037
|
-
}
|
|
22038
|
-
state.excludedRegenerable.push({
|
|
22039
|
-
path: relativePath,
|
|
22040
|
-
reason: proposedRegenerableExcludes.has(relativePath) ? "cooperative_exclude_regenerable" : "regenerable_default",
|
|
22041
|
-
regenerableHint: `${entry.name} is treated as rebuildable/installable state and is not bundled by default`
|
|
22042
|
-
});
|
|
22043
|
-
continue;
|
|
22044
|
-
}
|
|
22045
|
-
await walkWorkspace(root, relativePath, proposedRegenerableExcludes, state, opts);
|
|
22046
|
-
continue;
|
|
22047
|
-
}
|
|
22048
|
-
await includeWorkspacePath(relativePath, state, { forceIncludeRegenerable: opts.forceIncludeRegenerable });
|
|
22234
|
+
get(grantId) {
|
|
22235
|
+
return this.grants.get(grantId);
|
|
22049
22236
|
}
|
|
22050
|
-
|
|
22051
|
-
|
|
22052
|
-
|
|
22053
|
-
|
|
22054
|
-
|
|
22055
|
-
|
|
22056
|
-
|
|
22237
|
+
authenticate(grantId, token) {
|
|
22238
|
+
const grant = this.grants.get(grantId);
|
|
22239
|
+
if (!grant || !token || !verifyTokenHash(token, grant.tokenHash)) {
|
|
22240
|
+
return { ok: false, status: 401, code: "migration_grant_auth_failed" };
|
|
22241
|
+
}
|
|
22242
|
+
if (grant.state === "revoked" || grant.state === "consumed") {
|
|
22243
|
+
return { ok: false, status: 410, code: "migration_grant_unavailable" };
|
|
22244
|
+
}
|
|
22245
|
+
if (this.now().getTime() >= grant.expiresAt.getTime()) {
|
|
22246
|
+
grant.state = "expired";
|
|
22247
|
+
return { ok: false, status: 410, code: "migration_grant_unavailable" };
|
|
22248
|
+
}
|
|
22249
|
+
return { ok: true, grant };
|
|
22250
|
+
}
|
|
22251
|
+
revokeAll() {
|
|
22252
|
+
for (const grant of this.grants.values()) {
|
|
22253
|
+
if (grant.state !== "consumed" && grant.state !== "expired") {
|
|
22254
|
+
grant.state = "revoked";
|
|
22255
|
+
}
|
|
22256
|
+
}
|
|
22257
|
+
}
|
|
22258
|
+
beginBundleStream(grant) {
|
|
22259
|
+
const current = this.grants.get(grant.grantId);
|
|
22260
|
+
if (current !== grant) return false;
|
|
22261
|
+
if (current.state !== "issued" && current.state !== "interrupted") return false;
|
|
22262
|
+
current.state = "streaming";
|
|
22263
|
+
return true;
|
|
22264
|
+
}
|
|
22265
|
+
};
|
|
22266
|
+
function createAgentMigrationHttpTransport(options = {}) {
|
|
22267
|
+
const now = options.now ?? currentDate;
|
|
22268
|
+
const registry = new AgentMigrationGrantRegistry(now);
|
|
22269
|
+
const controlBundles = /* @__PURE__ */ new Map();
|
|
22270
|
+
const fallbackBundleStreamFactory = options.bundleStreamFactory ?? defaultBundleStream;
|
|
22271
|
+
const bundleStreamFactory = (grant, request) => {
|
|
22272
|
+
const bundle = controlBundles.get(grant.grantId);
|
|
22273
|
+
if (bundle) {
|
|
22274
|
+
const endExclusive = request.lengthBytes === null ? void 0 : request.offsetBytes + request.lengthBytes;
|
|
22275
|
+
return Readable.from([bundle.subarray(request.offsetBytes, endExclusive)]);
|
|
22276
|
+
}
|
|
22277
|
+
return fallbackBundleStreamFactory(grant, request);
|
|
22278
|
+
};
|
|
22279
|
+
const listenDefaults = options.listen ?? resolveAgentMigrationHttpTransportListenOptions();
|
|
22280
|
+
const controlSeamEnabled = options.controlSeam ?? resolveAgentMigrationControlSeamEnabled();
|
|
22281
|
+
const server = http2.createServer((req, res) => {
|
|
22282
|
+
const parsed = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
22283
|
+
if (parsed.pathname.startsWith("/migration-control")) {
|
|
22284
|
+
void handleMigrationControlRequest(req, res, parsed, {
|
|
22285
|
+
enabled: controlSeamEnabled,
|
|
22286
|
+
now,
|
|
22287
|
+
registry,
|
|
22288
|
+
controlBundles
|
|
22289
|
+
});
|
|
22290
|
+
return;
|
|
22291
|
+
}
|
|
22292
|
+
void handleMigrationRequest(req, res, registry, bundleStreamFactory);
|
|
22293
|
+
});
|
|
22294
|
+
return {
|
|
22295
|
+
grants: registry,
|
|
22296
|
+
server,
|
|
22297
|
+
listen: (port = listenDefaults.port ?? 0, host = listenDefaults.host ?? "127.0.0.1") => new Promise((resolve, reject) => {
|
|
22298
|
+
server.once("error", reject);
|
|
22299
|
+
server.listen(port, host, () => {
|
|
22300
|
+
server.off("error", reject);
|
|
22301
|
+
const address = server.address();
|
|
22302
|
+
if (!address || typeof address === "string") {
|
|
22303
|
+
reject(new Error("migration transport listen did not produce a TCP address"));
|
|
22304
|
+
return;
|
|
22305
|
+
}
|
|
22306
|
+
resolve({ url: listenDefaults.publicUrl ?? `http://${formatListenAddress(address.address)}:${address.port}` });
|
|
22307
|
+
});
|
|
22308
|
+
}),
|
|
22309
|
+
close: () => new Promise((resolve, reject) => {
|
|
22310
|
+
registry.revokeAll();
|
|
22311
|
+
server.close((err) => {
|
|
22312
|
+
if (err) reject(err);
|
|
22313
|
+
else resolve();
|
|
22314
|
+
});
|
|
22315
|
+
})
|
|
22316
|
+
};
|
|
22317
|
+
}
|
|
22318
|
+
function resolveAgentMigrationHttpTransportListenOptions(env = process.env) {
|
|
22319
|
+
const host = env[AGENT_MIGRATION_TRANSPORT_HOST_ENV]?.trim();
|
|
22320
|
+
const port = parseOptionalPort(env[AGENT_MIGRATION_TRANSPORT_PORT_ENV]);
|
|
22321
|
+
const publicUrl = env[AGENT_MIGRATION_TRANSPORT_PUBLIC_URL_ENV]?.trim();
|
|
22322
|
+
return {
|
|
22323
|
+
...host ? { host } : {},
|
|
22324
|
+
...port !== void 0 ? { port } : {},
|
|
22325
|
+
...publicUrl ? { publicUrl } : {}
|
|
22326
|
+
};
|
|
22327
|
+
}
|
|
22328
|
+
function resolveAgentMigrationControlSeamEnabled(env = process.env) {
|
|
22329
|
+
return env[AGENT_MIGRATION_CONTROL_SEAM_ENV]?.trim() !== "0";
|
|
22330
|
+
}
|
|
22331
|
+
async function handleMigrationControlRequest(req, res, parsed, context) {
|
|
22332
|
+
if (!context.enabled) {
|
|
22333
|
+
sendJson(res, 404, { code: "migration_route_not_found" });
|
|
22334
|
+
return;
|
|
22335
|
+
}
|
|
22336
|
+
if (parsed.pathname !== "/migration-control/grants") {
|
|
22337
|
+
sendJson(res, 404, { code: "migration_route_not_found" });
|
|
22338
|
+
return;
|
|
22339
|
+
}
|
|
22340
|
+
if (req.method !== "POST") {
|
|
22341
|
+
sendJson(res, 405, { code: "migration_method_not_allowed" });
|
|
22342
|
+
return;
|
|
22343
|
+
}
|
|
22344
|
+
let rawInput;
|
|
22345
|
+
try {
|
|
22346
|
+
rawInput = await readJsonRequestBody(req, CONTROL_REQUEST_MAX_BYTES);
|
|
22347
|
+
} catch (err) {
|
|
22348
|
+
if (err instanceof MigrationBundleStreamError) {
|
|
22349
|
+
sendJson(res, err.code === "migration_control_payload_too_large" ? 413 : 400, { code: err.code });
|
|
22350
|
+
return;
|
|
22351
|
+
}
|
|
22352
|
+
sendJson(res, 400, { code: "migration_control_invalid_json" });
|
|
22353
|
+
return;
|
|
22354
|
+
}
|
|
22355
|
+
const input = parseControlGrantInput(rawInput, context.now);
|
|
22356
|
+
if (!input.ok) {
|
|
22357
|
+
sendJson(res, 400, { code: input.code });
|
|
22358
|
+
return;
|
|
22359
|
+
}
|
|
22360
|
+
const { bundle, createGrantInput } = input;
|
|
22361
|
+
const { grant, token } = context.registry.createGrant({
|
|
22362
|
+
...createGrantInput,
|
|
22363
|
+
bundleSizeBytes: bundle.byteLength
|
|
22364
|
+
});
|
|
22365
|
+
context.controlBundles.set(grant.grantId, bundle);
|
|
22366
|
+
sendJson(res, 201, {
|
|
22367
|
+
grantId: grant.grantId,
|
|
22368
|
+
token,
|
|
22369
|
+
manifestSha256: grant.manifestSha256,
|
|
22370
|
+
bundleSizeBytes: grant.bundleSizeBytes,
|
|
22371
|
+
expiresAt: grant.expiresAt.toISOString(),
|
|
22372
|
+
manifestUrl: `/migration/${encodeURIComponent(grant.grantId)}/manifest`,
|
|
22373
|
+
bundleUrl: `/migration/${encodeURIComponent(grant.grantId)}/bundle.tar`
|
|
22374
|
+
});
|
|
22375
|
+
}
|
|
22376
|
+
async function handleMigrationRequest(req, res, registry, bundleStreamFactory) {
|
|
22377
|
+
const parsed = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
22378
|
+
const match = /^\/migration\/([^/]+)\/([^/]+)(?:\/([^/]+))?$/.exec(parsed.pathname);
|
|
22379
|
+
if (!match) {
|
|
22380
|
+
sendJson(res, 404, { code: "migration_route_not_found" });
|
|
22381
|
+
return;
|
|
22382
|
+
}
|
|
22383
|
+
const [, grantId, resource, resourceId] = match;
|
|
22384
|
+
const auth = registry.authenticate(decodeURIComponent(grantId), extractBearerToken(req));
|
|
22385
|
+
if (!auth.ok) {
|
|
22386
|
+
sendJson(res, auth.status, { code: auth.code });
|
|
22387
|
+
return;
|
|
22388
|
+
}
|
|
22389
|
+
if (resource === "manifest" && req.method === "GET" && !resourceId) {
|
|
22390
|
+
sendJson(res, 200, {
|
|
22391
|
+
manifestSha256: auth.grant.manifestSha256,
|
|
22392
|
+
manifest: auth.grant.manifest
|
|
22393
|
+
}, {
|
|
22394
|
+
"X-Raft-Manifest-Sha": auth.grant.manifestSha256,
|
|
22395
|
+
ETag: `"manifest-${auth.grant.manifestSha256}"`
|
|
22396
|
+
});
|
|
22397
|
+
return;
|
|
22398
|
+
}
|
|
22399
|
+
if (resource === "bundle.tar" && !resourceId) {
|
|
22400
|
+
if (req.method === "HEAD") {
|
|
22401
|
+
sendHead(res, auth.grant);
|
|
22402
|
+
return;
|
|
22403
|
+
}
|
|
22404
|
+
if (req.method === "GET") {
|
|
22405
|
+
await streamBundle(req, res, auth.grant, registry, bundleStreamFactory);
|
|
22406
|
+
return;
|
|
22407
|
+
}
|
|
22408
|
+
}
|
|
22409
|
+
if (resource === "chunk" && req.method === "GET" && resourceId) {
|
|
22410
|
+
sendJson(res, 501, { code: "migration_chunk_not_implemented" });
|
|
22411
|
+
return;
|
|
22412
|
+
}
|
|
22413
|
+
sendJson(res, 405, { code: "migration_method_not_allowed" });
|
|
22414
|
+
}
|
|
22415
|
+
function sendHead(res, grant) {
|
|
22416
|
+
res.statusCode = 200;
|
|
22417
|
+
res.setHeader("X-Raft-Manifest-Sha", grant.manifestSha256);
|
|
22418
|
+
res.setHeader("ETag", grant.bundleEtag);
|
|
22419
|
+
res.setHeader("Accept-Ranges", grant.bundleSizeBytes === null ? "none" : "bytes");
|
|
22420
|
+
res.setHeader("X-Raft-Bundle-Size", grant.bundleSizeBytes === null ? "unknown" : grant.bundleSizeBytes.toString());
|
|
22421
|
+
if (grant.bundleSizeBytes !== null) {
|
|
22422
|
+
res.setHeader("Content-Length", grant.bundleSizeBytes.toString());
|
|
22423
|
+
}
|
|
22424
|
+
res.end();
|
|
22425
|
+
}
|
|
22426
|
+
async function streamBundle(req, res, grant, registry, bundleStreamFactory) {
|
|
22427
|
+
if (grant.state === "streaming") {
|
|
22428
|
+
sendJson(res, 409, { code: "migration_bundle_stream_in_progress" });
|
|
22429
|
+
return;
|
|
22430
|
+
}
|
|
22431
|
+
if (grant.oneTimeUse && grant.state !== "issued" && grant.state !== "interrupted") {
|
|
22432
|
+
sendJson(res, 410, { code: "migration_grant_unavailable" });
|
|
22433
|
+
return;
|
|
22434
|
+
}
|
|
22435
|
+
const range = planRange(req, grant);
|
|
22436
|
+
if (!range.ok) {
|
|
22437
|
+
sendJson(res, 416, { code: range.code }, range.headers);
|
|
22438
|
+
return;
|
|
22439
|
+
}
|
|
22440
|
+
const previousState = grant.state;
|
|
22441
|
+
if (!registry.beginBundleStream(grant)) {
|
|
22442
|
+
sendJson(res, 409, { code: "migration_bundle_stream_in_progress" });
|
|
22443
|
+
return;
|
|
22444
|
+
}
|
|
22445
|
+
let stream;
|
|
22446
|
+
try {
|
|
22447
|
+
stream = bundleStreamFactory(grant, range.request);
|
|
22448
|
+
} catch (err) {
|
|
22449
|
+
grant.state = previousState;
|
|
22450
|
+
const code = err instanceof MigrationBundleStreamError ? err.code : "migration_bundle_stream_failed";
|
|
22451
|
+
sendJson(res, 500, { code });
|
|
22452
|
+
return;
|
|
22453
|
+
}
|
|
22454
|
+
let completed = false;
|
|
22455
|
+
res.on("close", () => {
|
|
22456
|
+
if (!completed && grant.state === "streaming") {
|
|
22457
|
+
grant.state = "interrupted";
|
|
22458
|
+
}
|
|
22459
|
+
});
|
|
22460
|
+
try {
|
|
22461
|
+
res.statusCode = 200;
|
|
22462
|
+
res.setHeader("Content-Type", "application/x-tar");
|
|
22463
|
+
res.setHeader("X-Raft-Manifest-Sha", grant.manifestSha256);
|
|
22464
|
+
res.setHeader("ETag", grant.bundleEtag);
|
|
22465
|
+
res.setHeader("Accept-Ranges", grant.bundleSizeBytes === null ? "none" : "bytes");
|
|
22466
|
+
if (range.request.range) {
|
|
22467
|
+
res.statusCode = 206;
|
|
22468
|
+
res.setHeader("Content-Range", `bytes ${range.request.range.start}-${range.request.range.end}/${grant.bundleSizeBytes}`);
|
|
22469
|
+
}
|
|
22470
|
+
if (range.request.lengthBytes !== null) {
|
|
22471
|
+
res.setHeader("Content-Length", range.request.lengthBytes.toString());
|
|
22472
|
+
}
|
|
22473
|
+
for await (const chunk of stream) {
|
|
22474
|
+
if (!res.write(chunk)) {
|
|
22475
|
+
await onceDrain(res);
|
|
22476
|
+
}
|
|
22477
|
+
}
|
|
22478
|
+
completed = true;
|
|
22479
|
+
grant.state = range.consumesGrant ? "consumed" : "interrupted";
|
|
22480
|
+
res.end();
|
|
22481
|
+
} catch {
|
|
22482
|
+
if (!completed) {
|
|
22483
|
+
grant.state = "interrupted";
|
|
22484
|
+
}
|
|
22485
|
+
if (!res.headersSent) {
|
|
22486
|
+
sendJson(res, 500, { code: "migration_bundle_stream_failed" });
|
|
22487
|
+
} else {
|
|
22488
|
+
res.destroy();
|
|
22489
|
+
}
|
|
22490
|
+
}
|
|
22491
|
+
}
|
|
22492
|
+
function planRange(req, grant) {
|
|
22493
|
+
const header = singleHeader(req.headers.range);
|
|
22494
|
+
if (!header) {
|
|
22495
|
+
return {
|
|
22496
|
+
ok: true,
|
|
22497
|
+
request: { range: null, offsetBytes: 0, lengthBytes: grant.bundleSizeBytes },
|
|
22498
|
+
consumesGrant: true
|
|
22499
|
+
};
|
|
22500
|
+
}
|
|
22501
|
+
if (grant.bundleSizeBytes === null) {
|
|
22502
|
+
return {
|
|
22503
|
+
ok: false,
|
|
22504
|
+
code: "migration_range_not_available",
|
|
22505
|
+
headers: { "Accept-Ranges": "none" }
|
|
22506
|
+
};
|
|
22507
|
+
}
|
|
22508
|
+
const parsed = parseSingleByteRange(header, grant.bundleSizeBytes);
|
|
22509
|
+
if (!parsed.ok) {
|
|
22510
|
+
return {
|
|
22511
|
+
ok: false,
|
|
22512
|
+
code: parsed.code,
|
|
22513
|
+
headers: {
|
|
22514
|
+
"Accept-Ranges": "bytes",
|
|
22515
|
+
"Content-Range": `bytes */${grant.bundleSizeBytes}`
|
|
22516
|
+
}
|
|
22517
|
+
};
|
|
22518
|
+
}
|
|
22519
|
+
return {
|
|
22520
|
+
ok: true,
|
|
22521
|
+
request: {
|
|
22522
|
+
range: parsed.range,
|
|
22523
|
+
offsetBytes: parsed.range.start,
|
|
22524
|
+
lengthBytes: parsed.range.end - parsed.range.start + 1
|
|
22525
|
+
},
|
|
22526
|
+
consumesGrant: parsed.range.end === grant.bundleSizeBytes - 1
|
|
22527
|
+
};
|
|
22528
|
+
}
|
|
22529
|
+
function parseSingleByteRange(header, sizeBytes) {
|
|
22530
|
+
const match = /^bytes=(\d+)-(\d*)$/.exec(header.trim());
|
|
22531
|
+
if (!match) {
|
|
22532
|
+
return { ok: false, code: "migration_range_not_satisfiable" };
|
|
22533
|
+
}
|
|
22534
|
+
const start = Number(match[1]);
|
|
22535
|
+
const requestedEnd = match[2] ? Number(match[2]) : sizeBytes - 1;
|
|
22536
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(requestedEnd) || start < 0 || requestedEnd < start || start >= sizeBytes) {
|
|
22537
|
+
return { ok: false, code: "migration_range_not_satisfiable" };
|
|
22538
|
+
}
|
|
22539
|
+
return {
|
|
22540
|
+
ok: true,
|
|
22541
|
+
range: {
|
|
22542
|
+
start,
|
|
22543
|
+
end: Math.min(requestedEnd, sizeBytes - 1)
|
|
22544
|
+
}
|
|
22545
|
+
};
|
|
22546
|
+
}
|
|
22547
|
+
function defaultBundleStream(grant) {
|
|
22548
|
+
void grant;
|
|
22549
|
+
throw new MigrationBundleStreamError("migration_bundle_stream_not_wired");
|
|
22550
|
+
}
|
|
22551
|
+
function parseControlGrantInput(rawInput, now) {
|
|
22552
|
+
if (!rawInput || typeof rawInput !== "object" || Array.isArray(rawInput)) {
|
|
22553
|
+
return { ok: false, code: "migration_control_invalid_payload" };
|
|
22554
|
+
}
|
|
22555
|
+
const input = rawInput;
|
|
22556
|
+
const manifest = input.manifest;
|
|
22557
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
22558
|
+
return { ok: false, code: "migration_control_manifest_required" };
|
|
22559
|
+
}
|
|
22560
|
+
const bundle = decodeControlBundle(input);
|
|
22561
|
+
if (!bundle.ok) return bundle;
|
|
22562
|
+
const expiresAt = parseControlGrantExpiry(input, now);
|
|
22563
|
+
if (!expiresAt.ok) return expiresAt;
|
|
22564
|
+
const grantId = optionalNonEmptyString(input.grantId);
|
|
22565
|
+
const token = optionalNonEmptyString(input.token);
|
|
22566
|
+
const oneTimeUse = typeof input.oneTimeUse === "boolean" ? input.oneTimeUse : void 0;
|
|
22567
|
+
return {
|
|
22568
|
+
ok: true,
|
|
22569
|
+
bundle: bundle.bundle,
|
|
22570
|
+
createGrantInput: {
|
|
22571
|
+
...grantId ? { grantId } : {},
|
|
22572
|
+
...token ? { token } : {},
|
|
22573
|
+
expiresAt: expiresAt.expiresAt,
|
|
22574
|
+
manifest,
|
|
22575
|
+
...oneTimeUse === void 0 ? {} : { oneTimeUse }
|
|
22576
|
+
}
|
|
22577
|
+
};
|
|
22578
|
+
}
|
|
22579
|
+
function decodeControlBundle(input) {
|
|
22580
|
+
if (typeof input.bundleBase64 === "string") {
|
|
22581
|
+
const normalized = input.bundleBase64.trim();
|
|
22582
|
+
if (!normalized) return { ok: false, code: "migration_control_bundle_required" };
|
|
22583
|
+
const bundle = Buffer.from(normalized, "base64");
|
|
22584
|
+
if (bundle.byteLength === 0) return { ok: false, code: "migration_control_bundle_required" };
|
|
22585
|
+
return { ok: true, bundle };
|
|
22586
|
+
}
|
|
22587
|
+
if (typeof input.bundleText === "string") {
|
|
22588
|
+
const bundle = Buffer.from(input.bundleText, "utf8");
|
|
22589
|
+
if (bundle.byteLength === 0) return { ok: false, code: "migration_control_bundle_required" };
|
|
22590
|
+
return { ok: true, bundle };
|
|
22591
|
+
}
|
|
22592
|
+
return { ok: false, code: "migration_control_bundle_required" };
|
|
22593
|
+
}
|
|
22594
|
+
function parseControlGrantExpiry(input, now) {
|
|
22595
|
+
if (typeof input.expiresAt === "string" && input.expiresAt.trim()) {
|
|
22596
|
+
const expiresAt = new Date(input.expiresAt);
|
|
22597
|
+
if (Number.isNaN(expiresAt.getTime())) {
|
|
22598
|
+
return { ok: false, code: "migration_control_invalid_expiry" };
|
|
22599
|
+
}
|
|
22600
|
+
return { ok: true, expiresAt };
|
|
22601
|
+
}
|
|
22602
|
+
if (input.expiresInSeconds !== void 0) {
|
|
22603
|
+
const expiresInSeconds = input.expiresInSeconds;
|
|
22604
|
+
if (typeof expiresInSeconds !== "number" || !Number.isSafeInteger(expiresInSeconds) || expiresInSeconds <= 0) {
|
|
22605
|
+
return { ok: false, code: "migration_control_invalid_expiry" };
|
|
22606
|
+
}
|
|
22607
|
+
return { ok: true, expiresAt: new Date(now().getTime() + expiresInSeconds * 1e3) };
|
|
22608
|
+
}
|
|
22609
|
+
return { ok: true, expiresAt: new Date(now().getTime() + 15 * 60 * 1e3) };
|
|
22610
|
+
}
|
|
22611
|
+
function optionalNonEmptyString(value) {
|
|
22612
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
22613
|
+
}
|
|
22614
|
+
function normalizeBundleSizeBytes(size) {
|
|
22615
|
+
if (size === null || size === void 0) return null;
|
|
22616
|
+
if (!Number.isSafeInteger(size) || size < 0) return null;
|
|
22617
|
+
return size;
|
|
22618
|
+
}
|
|
22619
|
+
function parseOptionalPort(raw) {
|
|
22620
|
+
const value = raw?.trim();
|
|
22621
|
+
if (!value) return void 0;
|
|
22622
|
+
const port = Number(value);
|
|
22623
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
22624
|
+
throw new Error(`${AGENT_MIGRATION_TRANSPORT_PORT_ENV} must be an integer from 0 to 65535`);
|
|
22625
|
+
}
|
|
22626
|
+
return port;
|
|
22627
|
+
}
|
|
22628
|
+
function formatListenAddress(address) {
|
|
22629
|
+
if (address.includes(":") && !address.startsWith("[")) return `[${address}]`;
|
|
22630
|
+
return address;
|
|
22631
|
+
}
|
|
22632
|
+
function extractBearerToken(req) {
|
|
22633
|
+
const directHeader = singleHeader(req.headers["x-raft-migration-token"]);
|
|
22634
|
+
if (directHeader) return directHeader;
|
|
22635
|
+
const authorization = singleHeader(req.headers.authorization);
|
|
22636
|
+
if (!authorization) return null;
|
|
22637
|
+
const match = /^Bearer\s+(.+)$/i.exec(authorization);
|
|
22638
|
+
return match?.[1] ?? null;
|
|
22639
|
+
}
|
|
22640
|
+
function singleHeader(value) {
|
|
22641
|
+
if (!value) return null;
|
|
22642
|
+
return Array.isArray(value) ? value[0] ?? null : value;
|
|
22643
|
+
}
|
|
22644
|
+
function sendJson(res, status, body, headers = {}) {
|
|
22645
|
+
const payload = JSON.stringify(body);
|
|
22646
|
+
res.writeHead(status, {
|
|
22647
|
+
"Content-Type": "application/json",
|
|
22648
|
+
"Content-Length": Buffer.byteLength(payload).toString(),
|
|
22649
|
+
...headers
|
|
22650
|
+
});
|
|
22651
|
+
res.end(payload);
|
|
22652
|
+
}
|
|
22653
|
+
function readJsonRequestBody(req, maxBytes) {
|
|
22654
|
+
return new Promise((resolve, reject) => {
|
|
22655
|
+
const chunks = [];
|
|
22656
|
+
let totalBytes = 0;
|
|
22657
|
+
let rejected = false;
|
|
22658
|
+
req.on("data", (chunk) => {
|
|
22659
|
+
if (rejected) return;
|
|
22660
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
22661
|
+
totalBytes += buffer.byteLength;
|
|
22662
|
+
if (totalBytes > maxBytes) {
|
|
22663
|
+
rejected = true;
|
|
22664
|
+
reject(new MigrationBundleStreamError("migration_control_payload_too_large"));
|
|
22665
|
+
return;
|
|
22666
|
+
}
|
|
22667
|
+
chunks.push(buffer);
|
|
22668
|
+
});
|
|
22669
|
+
req.on("error", reject);
|
|
22670
|
+
req.on("end", () => {
|
|
22671
|
+
if (rejected) return;
|
|
22672
|
+
try {
|
|
22673
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
22674
|
+
resolve(JSON.parse(text));
|
|
22675
|
+
} catch {
|
|
22676
|
+
reject(new MigrationBundleStreamError("migration_control_invalid_json"));
|
|
22677
|
+
}
|
|
22678
|
+
});
|
|
22679
|
+
});
|
|
22680
|
+
}
|
|
22681
|
+
function hashToken(token) {
|
|
22682
|
+
return sha256Buffer(Buffer.from(token, "utf8"));
|
|
22683
|
+
}
|
|
22684
|
+
function verifyTokenHash(token, expectedHashHex) {
|
|
22685
|
+
const actual = Buffer.from(hashToken(token), "hex");
|
|
22686
|
+
const expected = Buffer.from(expectedHashHex, "hex");
|
|
22687
|
+
if (actual.byteLength !== expected.byteLength) return false;
|
|
22688
|
+
return timingSafeEqual(actual, expected);
|
|
22689
|
+
}
|
|
22690
|
+
function sha256Buffer(buffer) {
|
|
22691
|
+
return createHash7("sha256").update(buffer).digest("hex");
|
|
22692
|
+
}
|
|
22693
|
+
function canonicalJsonBuffer(value) {
|
|
22694
|
+
return Buffer.from(canonicalJson(value), "utf8");
|
|
22695
|
+
}
|
|
22696
|
+
function canonicalJson(value) {
|
|
22697
|
+
return JSON.stringify(sortJsonValue(value));
|
|
22698
|
+
}
|
|
22699
|
+
function sortJsonValue(value) {
|
|
22700
|
+
if (Array.isArray(value)) return value.map(sortJsonValue);
|
|
22701
|
+
if (!value || typeof value !== "object") return value;
|
|
22702
|
+
return Object.keys(value).sort().reduce((result, key) => {
|
|
22703
|
+
result[key] = sortJsonValue(value[key]);
|
|
22704
|
+
return result;
|
|
22705
|
+
}, {});
|
|
22706
|
+
}
|
|
22707
|
+
function onceDrain(res) {
|
|
22708
|
+
return new Promise((resolve) => res.once("drain", resolve));
|
|
22709
|
+
}
|
|
22710
|
+
|
|
22711
|
+
// src/agentMigrationExport.ts
|
|
22712
|
+
import { createHash as createHash8 } from "crypto";
|
|
22713
|
+
import { createReadStream } from "fs";
|
|
22714
|
+
import { lstat as lstat2, readdir as readdir4, readFile as readFile3, readlink } from "fs/promises";
|
|
22715
|
+
import path19 from "path";
|
|
22716
|
+
var AGENT_MIGRATION_BUNDLE_SCHEMA_VERSION = "agent-bundle/v1";
|
|
22717
|
+
var REGENERABLE_DIRECTORY_NAMES = [
|
|
22718
|
+
".venv",
|
|
22719
|
+
"__pycache__",
|
|
22720
|
+
"dist",
|
|
22721
|
+
"node_modules",
|
|
22722
|
+
"target"
|
|
22723
|
+
];
|
|
22724
|
+
var SECRET_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
22725
|
+
".env",
|
|
22726
|
+
".env.local",
|
|
22727
|
+
".env.development",
|
|
22728
|
+
".env.production",
|
|
22729
|
+
".env.test",
|
|
22730
|
+
"credentials.json",
|
|
22731
|
+
"credential.json"
|
|
22732
|
+
]);
|
|
22733
|
+
var ENV_KEY_PATTERN = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/;
|
|
22734
|
+
async function buildAgentMigrationExportManifest(input) {
|
|
22735
|
+
const slockHome = path19.resolve(input.slockHome);
|
|
22736
|
+
const workspace = path19.resolve(input.workspacePath ?? path19.join(slockHome, "agents", input.agentId));
|
|
22737
|
+
const proposedIncludes = normalizeProposalPaths(input.cooperativeManifest?.include, workspace, "include");
|
|
22738
|
+
const proposedRegenerableExcludes = normalizeProposalPaths(input.cooperativeManifest?.exclude_regenerable, workspace, "exclude_regenerable");
|
|
22739
|
+
const cleanedProposal = normalizeProposalPaths(input.cooperativeManifest?.cleaned, workspace, "cleaned");
|
|
22740
|
+
const state = {
|
|
22741
|
+
workspace,
|
|
22742
|
+
files: [],
|
|
22743
|
+
excludedRegenerable: [],
|
|
22744
|
+
promotedIncludes: [],
|
|
22745
|
+
proposalRefusals: [
|
|
22746
|
+
...proposedIncludes.refusals,
|
|
22747
|
+
...proposedRegenerableExcludes.refusals,
|
|
22748
|
+
...cleanedProposal.refusals
|
|
22749
|
+
],
|
|
22750
|
+
unreachable: [],
|
|
22751
|
+
secretsDisclosed: [],
|
|
22752
|
+
seenWorkspacePaths: /* @__PURE__ */ new Set(),
|
|
22753
|
+
explicitIncludePaths: new Set(proposedIncludes.paths)
|
|
22754
|
+
};
|
|
22755
|
+
await walkWorkspace(workspace, "", new Set(proposedRegenerableExcludes.paths), state, { forceIncludeRegenerable: false });
|
|
22756
|
+
for (const requestedPath of proposedIncludes.paths) {
|
|
22757
|
+
if (!state.seenWorkspacePaths.has(requestedPath)) {
|
|
22758
|
+
await includeWorkspacePath(requestedPath, state, { forceIncludeRegenerable: true });
|
|
22759
|
+
}
|
|
22760
|
+
}
|
|
22761
|
+
for (const requestedPath of proposedRegenerableExcludes.paths) {
|
|
22762
|
+
if (isRegenerablePath(requestedPath)) continue;
|
|
22763
|
+
state.promotedIncludes.push({ path: requestedPath, reason: "exclude_not_regenerable" });
|
|
22764
|
+
await includeWorkspacePath(requestedPath, state, { forceIncludeRegenerable: true });
|
|
22765
|
+
}
|
|
22766
|
+
const crossTreeRefs = await buildCrossTreeRefs(input.runtimeSessionRefs ?? []);
|
|
22767
|
+
const secretDisclosureProposal = normalizeProposalPaths(input.cooperativeManifest?.secrets_disclosed, workspace, "secrets_disclosed");
|
|
22768
|
+
state.proposalRefusals.push(...secretDisclosureProposal.refusals);
|
|
22769
|
+
return {
|
|
22770
|
+
schemaVersion: AGENT_MIGRATION_BUNDLE_SCHEMA_VERSION,
|
|
22771
|
+
agentId: input.agentId,
|
|
22772
|
+
mode: input.mode,
|
|
22773
|
+
createdAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
22774
|
+
roots: {
|
|
22775
|
+
slockHome,
|
|
22776
|
+
workspace
|
|
22777
|
+
},
|
|
22778
|
+
defaults: {
|
|
22779
|
+
unknownFiles: "include",
|
|
22780
|
+
excludePolicy: "regenerable_only",
|
|
22781
|
+
regenerableDirectoryNames: [...REGENERABLE_DIRECTORY_NAMES]
|
|
22782
|
+
},
|
|
22783
|
+
files: sortBundleEntries(state.files),
|
|
22784
|
+
excludedRegenerable: sortByPath(state.excludedRegenerable),
|
|
22785
|
+
promotedIncludes: sortByPath(state.promotedIncludes),
|
|
22786
|
+
proposalRefusals: sortByPath(state.proposalRefusals),
|
|
22787
|
+
unreachable: sortByPath(state.unreachable),
|
|
22788
|
+
secretsDisclosed: sortByPath([
|
|
22789
|
+
...state.secretsDisclosed,
|
|
22790
|
+
...normalizeProvidedSecretDisclosures(secretDisclosureProposal.paths)
|
|
22791
|
+
]),
|
|
22792
|
+
cleaned: cleanedProposal.paths,
|
|
22793
|
+
crossTreeRefs: crossTreeRefs.sort((a, b) => a.bundlePath.localeCompare(b.bundlePath))
|
|
22794
|
+
};
|
|
22795
|
+
}
|
|
22796
|
+
async function walkWorkspace(root, relativeDir, proposedRegenerableExcludes, state, opts) {
|
|
22797
|
+
const absoluteDir = path19.join(root, relativeDir);
|
|
22798
|
+
let entries;
|
|
22799
|
+
try {
|
|
22800
|
+
entries = await readdir4(absoluteDir, { withFileTypes: true });
|
|
22801
|
+
} catch {
|
|
22802
|
+
state.unreachable.push({
|
|
22803
|
+
path: relativeDir || ".",
|
|
22804
|
+
sourcePath: absoluteDir,
|
|
22805
|
+
reason: "read_error"
|
|
22806
|
+
});
|
|
22807
|
+
return;
|
|
22808
|
+
}
|
|
22809
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
22810
|
+
for (const entry of entries) {
|
|
22811
|
+
const relativePath = toPosixPath(path19.join(relativeDir, entry.name));
|
|
22812
|
+
if (entry.isDirectory()) {
|
|
22813
|
+
if (!opts.forceIncludeRegenerable && isRegenerablePath(relativePath)) {
|
|
22814
|
+
if (state.explicitIncludePaths.has(relativePath)) {
|
|
22815
|
+
await walkWorkspace(root, relativePath, proposedRegenerableExcludes, state, { forceIncludeRegenerable: true });
|
|
22816
|
+
continue;
|
|
22817
|
+
}
|
|
22818
|
+
if (hasExplicitIncludeDescendant(relativePath, state.explicitIncludePaths)) {
|
|
22819
|
+
await walkWorkspace(root, relativePath, proposedRegenerableExcludes, state, opts);
|
|
22820
|
+
continue;
|
|
22821
|
+
}
|
|
22822
|
+
state.excludedRegenerable.push({
|
|
22823
|
+
path: relativePath,
|
|
22824
|
+
reason: proposedRegenerableExcludes.has(relativePath) ? "cooperative_exclude_regenerable" : "regenerable_default",
|
|
22825
|
+
regenerableHint: `${entry.name} is treated as rebuildable/installable state and is not bundled by default`
|
|
22826
|
+
});
|
|
22827
|
+
continue;
|
|
22828
|
+
}
|
|
22829
|
+
await walkWorkspace(root, relativePath, proposedRegenerableExcludes, state, opts);
|
|
22830
|
+
continue;
|
|
22831
|
+
}
|
|
22832
|
+
await includeWorkspacePath(relativePath, state, { forceIncludeRegenerable: opts.forceIncludeRegenerable });
|
|
22833
|
+
}
|
|
22834
|
+
}
|
|
22835
|
+
async function includeWorkspacePath(workspaceRelativePath, state, opts) {
|
|
22836
|
+
const normalizedRelativePath = normalizeRelativePath(workspaceRelativePath);
|
|
22837
|
+
if (state.seenWorkspacePaths.has(normalizedRelativePath)) return;
|
|
22838
|
+
const sourcePath = path19.join(state.workspace, normalizedRelativePath);
|
|
22839
|
+
let stat4;
|
|
22840
|
+
try {
|
|
22057
22841
|
stat4 = await lstat2(sourcePath);
|
|
22058
22842
|
} catch {
|
|
22059
22843
|
state.unreachable.push({
|
|
@@ -22139,7 +22923,7 @@ async function buildCrossTreeRefs(refs) {
|
|
|
22139
22923
|
return result;
|
|
22140
22924
|
}
|
|
22141
22925
|
async function sha256File(filePath) {
|
|
22142
|
-
const hash =
|
|
22926
|
+
const hash = createHash8("sha256");
|
|
22143
22927
|
await new Promise((resolve, reject) => {
|
|
22144
22928
|
const stream = createReadStream(filePath);
|
|
22145
22929
|
stream.on("data", (chunk) => hash.update(chunk));
|
|
@@ -22245,240 +23029,239 @@ function sortByPath(entries) {
|
|
|
22245
23029
|
return [...entries].sort((a, b) => a.path.localeCompare(b.path));
|
|
22246
23030
|
}
|
|
22247
23031
|
|
|
22248
|
-
// src/
|
|
22249
|
-
import { createHash as
|
|
22250
|
-
import
|
|
22251
|
-
|
|
22252
|
-
|
|
22253
|
-
|
|
22254
|
-
|
|
22255
|
-
|
|
22256
|
-
|
|
22257
|
-
|
|
22258
|
-
|
|
22259
|
-
|
|
22260
|
-
|
|
22261
|
-
|
|
22262
|
-
|
|
22263
|
-
|
|
22264
|
-
|
|
22265
|
-
|
|
22266
|
-
|
|
22267
|
-
|
|
22268
|
-
tokenHash: hashToken(token),
|
|
22269
|
-
expiresAt: input.expiresAt,
|
|
22270
|
-
oneTimeUse: input.oneTimeUse ?? true,
|
|
22271
|
-
state: "issued",
|
|
22272
|
-
manifest: input.manifest,
|
|
22273
|
-
manifestSha256,
|
|
22274
|
-
bundleEtag: `"agent-migration-${manifestSha256}"`
|
|
22275
|
-
};
|
|
22276
|
-
this.grants.set(grant.grantId, grant);
|
|
22277
|
-
return { grant, token };
|
|
22278
|
-
}
|
|
22279
|
-
get(grantId) {
|
|
22280
|
-
return this.grants.get(grantId);
|
|
22281
|
-
}
|
|
22282
|
-
authenticate(grantId, token) {
|
|
22283
|
-
const grant = this.grants.get(grantId);
|
|
22284
|
-
if (!grant || !token || !verifyTokenHash(token, grant.tokenHash)) {
|
|
22285
|
-
return { ok: false, status: 401, code: "migration_grant_auth_failed" };
|
|
22286
|
-
}
|
|
22287
|
-
if (grant.state === "revoked" || grant.state === "consumed") {
|
|
22288
|
-
return { ok: false, status: 410, code: "migration_grant_unavailable" };
|
|
22289
|
-
}
|
|
22290
|
-
if (this.now().getTime() >= grant.expiresAt.getTime()) {
|
|
22291
|
-
grant.state = "expired";
|
|
22292
|
-
return { ok: false, status: 410, code: "migration_grant_unavailable" };
|
|
22293
|
-
}
|
|
22294
|
-
return { ok: true, grant };
|
|
22295
|
-
}
|
|
22296
|
-
revokeAll() {
|
|
22297
|
-
for (const grant of this.grants.values()) {
|
|
22298
|
-
if (grant.state !== "consumed" && grant.state !== "expired") {
|
|
22299
|
-
grant.state = "revoked";
|
|
22300
|
-
}
|
|
22301
|
-
}
|
|
23032
|
+
// src/agentMigrationImport.ts
|
|
23033
|
+
import { createHash as createHash9 } from "crypto";
|
|
23034
|
+
import { createReadStream as createReadStream2 } from "fs";
|
|
23035
|
+
import { access as access2, cp, lstat as lstat3, mkdir as mkdir3, readlink as readlink2, rename, writeFile as writeFile3 } from "fs/promises";
|
|
23036
|
+
import path20 from "path";
|
|
23037
|
+
async function buildAgentMigrationAdoptPlan(input) {
|
|
23038
|
+
const slockHome = path20.resolve(input.slockHome);
|
|
23039
|
+
let manifest;
|
|
23040
|
+
if (input.sourceKind === "orphan_directory" && !input.manifest) {
|
|
23041
|
+
manifest = await buildAgentMigrationExportManifest({
|
|
23042
|
+
agentId: input.agentId,
|
|
23043
|
+
slockHome,
|
|
23044
|
+
workspacePath: path20.resolve(input.orphanWorkspacePath),
|
|
23045
|
+
mode: "forensic",
|
|
23046
|
+
now: input.now
|
|
23047
|
+
});
|
|
23048
|
+
} else if (input.manifest) {
|
|
23049
|
+
manifest = input.manifest;
|
|
23050
|
+
} else {
|
|
23051
|
+
throw new Error("MIGRATION_MANIFEST_MISSING");
|
|
22302
23052
|
}
|
|
22303
|
-
|
|
22304
|
-
|
|
22305
|
-
|
|
22306
|
-
const bundleStreamFactory = options.bundleStreamFactory ?? defaultBundleStream;
|
|
22307
|
-
const server = http2.createServer((req, res) => {
|
|
22308
|
-
void handleMigrationRequest(req, res, registry, bundleStreamFactory);
|
|
23053
|
+
assertManifestIdentity({
|
|
23054
|
+
manifest,
|
|
23055
|
+
expectedAgentId: input.sourceKind === "orphan_directory" ? input.agentId : manifest.agentId
|
|
22309
23056
|
});
|
|
23057
|
+
const sourceWorkspacePath = path20.resolve(
|
|
23058
|
+
input.sourceKind === "staged_bundle" ? input.stagingWorkspacePath : input.orphanWorkspacePath
|
|
23059
|
+
);
|
|
23060
|
+
const finalWorkspacePath = path20.resolve(input.finalWorkspacePath ?? path20.join(slockHome, "agents", manifest.agentId));
|
|
23061
|
+
const reportPath = path20.resolve(input.reportPath ?? path20.join(
|
|
23062
|
+
slockHome,
|
|
23063
|
+
"migrations",
|
|
23064
|
+
sanitizePathSegment(input.generation.grantKey),
|
|
23065
|
+
"arrival-report.json"
|
|
23066
|
+
));
|
|
23067
|
+
const manifestSha256 = hashAgentMigrationManifest(manifest);
|
|
23068
|
+
if (input.manifestSha256 && input.manifestSha256 !== manifestSha256) {
|
|
23069
|
+
throw new Error("MIGRATION_MANIFEST_SHA_MISMATCH");
|
|
23070
|
+
}
|
|
22310
23071
|
return {
|
|
22311
|
-
|
|
22312
|
-
|
|
22313
|
-
|
|
22314
|
-
|
|
22315
|
-
|
|
22316
|
-
|
|
22317
|
-
|
|
22318
|
-
|
|
22319
|
-
|
|
22320
|
-
|
|
22321
|
-
|
|
22322
|
-
resolve({ url: `http://${address.address}:${address.port}` });
|
|
22323
|
-
});
|
|
22324
|
-
}),
|
|
22325
|
-
close: () => new Promise((resolve, reject) => {
|
|
22326
|
-
registry.revokeAll();
|
|
22327
|
-
server.close((err) => {
|
|
22328
|
-
if (err) reject(err);
|
|
22329
|
-
else resolve();
|
|
22330
|
-
});
|
|
22331
|
-
})
|
|
23072
|
+
sourceKind: input.sourceKind,
|
|
23073
|
+
agentId: manifest.agentId,
|
|
23074
|
+
slockHome,
|
|
23075
|
+
sourceWorkspacePath,
|
|
23076
|
+
finalWorkspacePath,
|
|
23077
|
+
reportPath,
|
|
23078
|
+
manifest,
|
|
23079
|
+
manifestSha256,
|
|
23080
|
+
generation: input.generation,
|
|
23081
|
+
attestationNonce: input.attestationNonce,
|
|
23082
|
+
observedAttestationNonce: input.observedAttestationNonce
|
|
22332
23083
|
};
|
|
22333
23084
|
}
|
|
22334
|
-
async function
|
|
22335
|
-
|
|
22336
|
-
|
|
22337
|
-
|
|
22338
|
-
|
|
22339
|
-
|
|
23085
|
+
async function verifyAgentMigrationAdoptPlan(plan) {
|
|
23086
|
+
assertGeneration(plan.generation);
|
|
23087
|
+
assertManifestIdentity({ manifest: plan.manifest, expectedAgentId: plan.agentId });
|
|
23088
|
+
const actualManifestSha256 = hashAgentMigrationManifest(plan.manifest);
|
|
23089
|
+
if (actualManifestSha256 !== plan.manifestSha256) {
|
|
23090
|
+
throw new Error("MIGRATION_MANIFEST_SHA_MISMATCH");
|
|
23091
|
+
}
|
|
23092
|
+
let totalBytes = 0;
|
|
23093
|
+
let fileCount = 0;
|
|
23094
|
+
for (const entry of plan.manifest.files) {
|
|
23095
|
+
await verifyManifestFileEntry(plan.sourceWorkspacePath, entry);
|
|
23096
|
+
if (entry.kind === "file") {
|
|
23097
|
+
fileCount += 1;
|
|
23098
|
+
totalBytes += entry.sizeBytes ?? 0;
|
|
23099
|
+
}
|
|
23100
|
+
}
|
|
23101
|
+
return { fileCount, totalBytes };
|
|
23102
|
+
}
|
|
23103
|
+
async function executeAgentMigrationAdoptPlan(plan, rebind, now = currentDate()) {
|
|
23104
|
+
assertGeneration(plan.generation);
|
|
23105
|
+
await assertFinalWorkspaceAvailable(plan.sourceWorkspacePath, plan.finalWorkspacePath);
|
|
23106
|
+
const verified = await verifyAgentMigrationAdoptPlan(plan);
|
|
23107
|
+
let migrationGeneration = plan.generation.migrationGeneration;
|
|
23108
|
+
migrationGeneration = await assertRebindResult(plan, migrationGeneration, await rebind.startTransfer({
|
|
23109
|
+
grantKey: plan.generation.grantKey,
|
|
23110
|
+
migrationGeneration
|
|
23111
|
+
}));
|
|
23112
|
+
await placeWorkspace(plan.sourceWorkspacePath, plan.finalWorkspacePath);
|
|
23113
|
+
migrationGeneration = await assertRebindResult(plan, migrationGeneration, await rebind.flipMachine({
|
|
23114
|
+
grantKey: plan.generation.grantKey,
|
|
23115
|
+
migrationGeneration
|
|
23116
|
+
}));
|
|
23117
|
+
const report = {
|
|
23118
|
+
schemaVersion: "agent-arrival/v1",
|
|
23119
|
+
agentId: plan.agentId,
|
|
23120
|
+
sourceKind: plan.sourceKind,
|
|
23121
|
+
grantKey: plan.generation.grantKey,
|
|
23122
|
+
migrationGeneration,
|
|
23123
|
+
sourceMachineId: plan.generation.sourceMachineId,
|
|
23124
|
+
targetMachineId: plan.generation.targetMachineId,
|
|
23125
|
+
manifestSha256: plan.manifestSha256,
|
|
23126
|
+
finalWorkspacePath: plan.finalWorkspacePath,
|
|
23127
|
+
verified,
|
|
23128
|
+
attestation: {
|
|
23129
|
+
noncePresent: Boolean(plan.attestationNonce),
|
|
23130
|
+
nonceVerified: Boolean(plan.attestationNonce && plan.observedAttestationNonce === plan.attestationNonce)
|
|
23131
|
+
},
|
|
23132
|
+
arrivedAt: now.toISOString()
|
|
23133
|
+
};
|
|
23134
|
+
const reportSha256 = await writeArrivalReport(plan.reportPath, report);
|
|
23135
|
+
await assertRebindResult(plan, migrationGeneration, await rebind.markArrived({
|
|
23136
|
+
grantKey: plan.generation.grantKey,
|
|
23137
|
+
migrationGeneration,
|
|
23138
|
+
reportPath: plan.reportPath,
|
|
23139
|
+
reportSha256
|
|
23140
|
+
}));
|
|
23141
|
+
return { report, reportPath: plan.reportPath, reportSha256 };
|
|
23142
|
+
}
|
|
23143
|
+
function hashAgentMigrationManifest(manifest) {
|
|
23144
|
+
return sha256Buffer2(Buffer.from(canonicalJson2(manifest), "utf8"));
|
|
23145
|
+
}
|
|
23146
|
+
function assertManifestIdentity(input) {
|
|
23147
|
+
if (input.manifest.schemaVersion !== AGENT_MIGRATION_BUNDLE_SCHEMA_VERSION) {
|
|
23148
|
+
throw new Error("MIGRATION_MANIFEST_SCHEMA_UNSUPPORTED");
|
|
22340
23149
|
}
|
|
22341
|
-
|
|
22342
|
-
|
|
22343
|
-
if (!auth.ok) {
|
|
22344
|
-
sendJson(res, auth.status, { code: auth.code });
|
|
22345
|
-
return;
|
|
23150
|
+
if (input.manifest.agentId !== input.expectedAgentId) {
|
|
23151
|
+
throw new Error("MIGRATION_MANIFEST_AGENT_MISMATCH");
|
|
22346
23152
|
}
|
|
22347
|
-
|
|
22348
|
-
|
|
22349
|
-
|
|
22350
|
-
|
|
22351
|
-
}, {
|
|
22352
|
-
"X-Raft-Manifest-Sha": auth.grant.manifestSha256,
|
|
22353
|
-
ETag: `"manifest-${auth.grant.manifestSha256}"`
|
|
22354
|
-
});
|
|
22355
|
-
return;
|
|
23153
|
+
}
|
|
23154
|
+
function assertGeneration(generation) {
|
|
23155
|
+
if (generation.localMachineId !== generation.targetMachineId) {
|
|
23156
|
+
throw new Error("MIGRATION_TARGET_MACHINE_MISMATCH");
|
|
22356
23157
|
}
|
|
22357
|
-
if (
|
|
22358
|
-
|
|
22359
|
-
sendHead(res, auth.grant);
|
|
22360
|
-
return;
|
|
22361
|
-
}
|
|
22362
|
-
if (req.method === "GET") {
|
|
22363
|
-
await streamBundle(res, auth.grant, bundleStreamFactory);
|
|
22364
|
-
return;
|
|
22365
|
-
}
|
|
23158
|
+
if (generation.sourceMachineId === generation.targetMachineId) {
|
|
23159
|
+
throw new Error("MIGRATION_SOURCE_TARGET_MACHINE_MATCH");
|
|
22366
23160
|
}
|
|
22367
|
-
if (
|
|
22368
|
-
|
|
22369
|
-
return;
|
|
23161
|
+
if (!generation.grantKey || !generation.migrationGeneration) {
|
|
23162
|
+
throw new Error("MIGRATION_GENERATION_MISSING");
|
|
22370
23163
|
}
|
|
22371
|
-
sendJson(res, 405, { code: "migration_method_not_allowed" });
|
|
22372
23164
|
}
|
|
22373
|
-
function
|
|
22374
|
-
|
|
22375
|
-
|
|
22376
|
-
|
|
22377
|
-
|
|
22378
|
-
|
|
22379
|
-
|
|
23165
|
+
async function assertRebindResult(plan, expectedGeneration, result) {
|
|
23166
|
+
if (!result) return expectedGeneration;
|
|
23167
|
+
if (result.grantKey && result.grantKey !== plan.generation.grantKey) {
|
|
23168
|
+
throw new Error("MIGRATION_REBIND_GRANT_MISMATCH");
|
|
23169
|
+
}
|
|
23170
|
+
if (result.migrationGeneration !== void 0 && result.migrationGeneration.length === 0) {
|
|
23171
|
+
throw new Error("MIGRATION_REBIND_GENERATION_MISMATCH");
|
|
23172
|
+
}
|
|
23173
|
+
if (result.sourceMachineId && result.sourceMachineId !== plan.generation.sourceMachineId) {
|
|
23174
|
+
throw new Error("MIGRATION_REBIND_SOURCE_MACHINE_MISMATCH");
|
|
23175
|
+
}
|
|
23176
|
+
if (result.targetMachineId && result.targetMachineId !== plan.generation.targetMachineId) {
|
|
23177
|
+
throw new Error("MIGRATION_REBIND_TARGET_MACHINE_MISMATCH");
|
|
23178
|
+
}
|
|
23179
|
+
return result.migrationGeneration ?? expectedGeneration;
|
|
22380
23180
|
}
|
|
22381
|
-
async function
|
|
22382
|
-
if (
|
|
22383
|
-
|
|
22384
|
-
return;
|
|
23181
|
+
async function verifyManifestFileEntry(sourceWorkspacePath, entry) {
|
|
23182
|
+
if (entry.source !== "workspace" || !entry.workspaceRelativePath) {
|
|
23183
|
+
throw new Error("MIGRATION_MANIFEST_ENTRY_NOT_WORKSPACE");
|
|
22385
23184
|
}
|
|
22386
|
-
|
|
22387
|
-
|
|
23185
|
+
const relative = normalizeWorkspaceRelativePath(entry.workspaceRelativePath);
|
|
23186
|
+
const entryPath = path20.join(sourceWorkspacePath, relative);
|
|
23187
|
+
const info = await lstat3(entryPath).catch(() => null);
|
|
23188
|
+
if (!info) throw new Error("MIGRATION_MANIFEST_FILE_MISSING");
|
|
23189
|
+
if (entry.kind === "symlink") {
|
|
23190
|
+
if (!info.isSymbolicLink()) throw new Error("MIGRATION_MANIFEST_KIND_MISMATCH");
|
|
23191
|
+
const linkTarget = await readlink2(entryPath);
|
|
23192
|
+
if (entry.linkTarget !== linkTarget) throw new Error("MIGRATION_MANIFEST_LINK_MISMATCH");
|
|
22388
23193
|
return;
|
|
22389
23194
|
}
|
|
22390
|
-
|
|
22391
|
-
|
|
22392
|
-
|
|
22393
|
-
} catch (err) {
|
|
22394
|
-
const code = err instanceof MigrationBundleStreamError ? err.code : "migration_bundle_stream_failed";
|
|
22395
|
-
sendJson(res, 500, { code });
|
|
22396
|
-
return;
|
|
23195
|
+
if (!info.isFile()) throw new Error("MIGRATION_MANIFEST_KIND_MISMATCH");
|
|
23196
|
+
if (entry.sizeBytes !== void 0 && entry.sizeBytes !== info.size) {
|
|
23197
|
+
throw new Error("MIGRATION_MANIFEST_FILE_SIZE_MISMATCH");
|
|
22397
23198
|
}
|
|
22398
|
-
|
|
22399
|
-
|
|
22400
|
-
|
|
22401
|
-
if (!completed && grant.state === "streaming") {
|
|
22402
|
-
grant.state = "interrupted";
|
|
22403
|
-
}
|
|
22404
|
-
});
|
|
22405
|
-
try {
|
|
22406
|
-
res.statusCode = 200;
|
|
22407
|
-
res.setHeader("Content-Type", "application/x-tar");
|
|
22408
|
-
res.setHeader("X-Raft-Manifest-Sha", grant.manifestSha256);
|
|
22409
|
-
res.setHeader("ETag", grant.bundleEtag);
|
|
22410
|
-
for await (const chunk of stream) {
|
|
22411
|
-
if (!res.write(chunk)) {
|
|
22412
|
-
await onceDrain(res);
|
|
22413
|
-
}
|
|
22414
|
-
}
|
|
22415
|
-
completed = true;
|
|
22416
|
-
grant.state = "consumed";
|
|
22417
|
-
res.end();
|
|
22418
|
-
} catch {
|
|
22419
|
-
if (!completed && grant.state === "streaming") {
|
|
22420
|
-
grant.state = "interrupted";
|
|
22421
|
-
}
|
|
22422
|
-
if (!res.headersSent) {
|
|
22423
|
-
sendJson(res, 500, { code: "migration_bundle_stream_failed" });
|
|
22424
|
-
} else {
|
|
22425
|
-
res.destroy();
|
|
22426
|
-
}
|
|
23199
|
+
if (entry.sha256) {
|
|
23200
|
+
const actual = await sha256File2(entryPath);
|
|
23201
|
+
if (actual !== entry.sha256) throw new Error("MIGRATION_MANIFEST_FILE_SHA_MISMATCH");
|
|
22427
23202
|
}
|
|
22428
23203
|
}
|
|
22429
|
-
function
|
|
22430
|
-
|
|
22431
|
-
|
|
22432
|
-
|
|
22433
|
-
|
|
22434
|
-
|
|
22435
|
-
if (directHeader) return directHeader;
|
|
22436
|
-
const authorization = singleHeader(req.headers.authorization);
|
|
22437
|
-
if (!authorization) return null;
|
|
22438
|
-
const match = /^Bearer\s+(.+)$/i.exec(authorization);
|
|
22439
|
-
return match?.[1] ?? null;
|
|
22440
|
-
}
|
|
22441
|
-
function singleHeader(value) {
|
|
22442
|
-
if (!value) return null;
|
|
22443
|
-
return Array.isArray(value) ? value[0] ?? null : value;
|
|
23204
|
+
function normalizeWorkspaceRelativePath(relativePath) {
|
|
23205
|
+
const normalized = path20.posix.normalize(relativePath.replaceAll("\\", "/"));
|
|
23206
|
+
if (normalized === "." || normalized.startsWith("../") || normalized === ".." || path20.isAbsolute(normalized)) {
|
|
23207
|
+
throw new Error("MIGRATION_MANIFEST_UNSAFE_PATH");
|
|
23208
|
+
}
|
|
23209
|
+
return normalized;
|
|
22444
23210
|
}
|
|
22445
|
-
function
|
|
22446
|
-
|
|
22447
|
-
|
|
22448
|
-
|
|
22449
|
-
"
|
|
22450
|
-
|
|
22451
|
-
|
|
22452
|
-
|
|
23211
|
+
async function assertFinalWorkspaceAvailable(sourceWorkspacePath, finalWorkspacePath) {
|
|
23212
|
+
if (path20.resolve(sourceWorkspacePath) === path20.resolve(finalWorkspacePath)) return;
|
|
23213
|
+
try {
|
|
23214
|
+
await access2(finalWorkspacePath);
|
|
23215
|
+
throw new Error("MIGRATION_WORKSPACE_ALREADY_EXISTS");
|
|
23216
|
+
} catch (err) {
|
|
23217
|
+
if (err instanceof Error && err.message === "MIGRATION_WORKSPACE_ALREADY_EXISTS") throw err;
|
|
23218
|
+
}
|
|
22453
23219
|
}
|
|
22454
|
-
function
|
|
22455
|
-
|
|
23220
|
+
async function placeWorkspace(sourceWorkspacePath, finalWorkspacePath) {
|
|
23221
|
+
if (path20.resolve(sourceWorkspacePath) === path20.resolve(finalWorkspacePath)) return;
|
|
23222
|
+
const stagingPath = `${finalWorkspacePath}.migration-${process.pid}-${Date.now()}`;
|
|
23223
|
+
await mkdir3(path20.dirname(finalWorkspacePath), { recursive: true });
|
|
23224
|
+
await cp(sourceWorkspacePath, stagingPath, { recursive: true, dereference: false, errorOnExist: true, force: false });
|
|
23225
|
+
await rename(stagingPath, finalWorkspacePath);
|
|
22456
23226
|
}
|
|
22457
|
-
function
|
|
22458
|
-
const
|
|
22459
|
-
|
|
22460
|
-
|
|
22461
|
-
|
|
23227
|
+
async function writeArrivalReport(reportPath, report) {
|
|
23228
|
+
const payload = `${canonicalJson2(report)}
|
|
23229
|
+
`;
|
|
23230
|
+
await mkdir3(path20.dirname(reportPath), { recursive: true });
|
|
23231
|
+
await writeFile3(reportPath, payload, { mode: 384 });
|
|
23232
|
+
return sha256Buffer2(Buffer.from(payload, "utf8"));
|
|
22462
23233
|
}
|
|
22463
|
-
function
|
|
22464
|
-
|
|
23234
|
+
async function sha256File2(filePath) {
|
|
23235
|
+
const hash = createHash9("sha256");
|
|
23236
|
+
const stream = createReadStream2(filePath);
|
|
23237
|
+
for await (const chunk of stream) {
|
|
23238
|
+
hash.update(chunk);
|
|
23239
|
+
}
|
|
23240
|
+
return hash.digest("hex");
|
|
22465
23241
|
}
|
|
22466
|
-
function
|
|
22467
|
-
return
|
|
23242
|
+
function sha256Buffer2(buffer) {
|
|
23243
|
+
return createHash9("sha256").update(buffer).digest("hex");
|
|
22468
23244
|
}
|
|
22469
|
-
function
|
|
22470
|
-
return JSON.stringify(
|
|
23245
|
+
function canonicalJson2(value) {
|
|
23246
|
+
return JSON.stringify(sortJsonValue2(value));
|
|
22471
23247
|
}
|
|
22472
|
-
function
|
|
22473
|
-
if (Array.isArray(value)) return value.map(
|
|
23248
|
+
function sortJsonValue2(value) {
|
|
23249
|
+
if (Array.isArray(value)) return value.map(sortJsonValue2);
|
|
22474
23250
|
if (!value || typeof value !== "object") return value;
|
|
22475
23251
|
return Object.keys(value).sort().reduce((result, key) => {
|
|
22476
|
-
result[key] =
|
|
23252
|
+
result[key] = sortJsonValue2(value[key]);
|
|
22477
23253
|
return result;
|
|
22478
23254
|
}, {});
|
|
22479
23255
|
}
|
|
22480
|
-
function
|
|
22481
|
-
return
|
|
23256
|
+
function sanitizePathSegment(value) {
|
|
23257
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 128) || "migration";
|
|
23258
|
+
}
|
|
23259
|
+
|
|
23260
|
+
// src/secretFile.ts
|
|
23261
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
23262
|
+
import path21 from "path";
|
|
23263
|
+
function readSecretFileSync(filePath) {
|
|
23264
|
+
return readFileSync9(filePath, "utf8").trim();
|
|
22482
23265
|
}
|
|
22483
23266
|
|
|
22484
23267
|
// src/core.ts
|
|
@@ -22607,7 +23390,7 @@ var DAEMON_CORE_TRACE_ATTR_CONTRACTS = {
|
|
|
22607
23390
|
spanAttrs: ["agentId", "event_kind", "runtime"]
|
|
22608
23391
|
}
|
|
22609
23392
|
};
|
|
22610
|
-
var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <
|
|
23393
|
+
var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key-file <path>";
|
|
22611
23394
|
var RunnerCredentialMintError2 = class extends Error {
|
|
22612
23395
|
code;
|
|
22613
23396
|
retryable;
|
|
@@ -22646,9 +23429,18 @@ async function waitForRunnerCredentialRetry2() {
|
|
|
22646
23429
|
function parseDaemonCliArgs(args) {
|
|
22647
23430
|
let serverUrl = "";
|
|
22648
23431
|
let apiKey = "";
|
|
23432
|
+
let apiKeyFile = process.env.SLOCK_DAEMON_API_KEY_FILE ?? "";
|
|
22649
23433
|
for (let i = 0; i < args.length; i++) {
|
|
22650
23434
|
if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
|
|
22651
23435
|
if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
|
|
23436
|
+
if (args[i] === "--api-key-file" && args[i + 1]) apiKeyFile = args[++i];
|
|
23437
|
+
}
|
|
23438
|
+
if (!apiKey && apiKeyFile) {
|
|
23439
|
+
try {
|
|
23440
|
+
apiKey = readSecretFileSync(apiKeyFile);
|
|
23441
|
+
} catch {
|
|
23442
|
+
apiKey = "";
|
|
23443
|
+
}
|
|
22652
23444
|
}
|
|
22653
23445
|
if (!serverUrl || !apiKey) return null;
|
|
22654
23446
|
return { serverUrl, apiKey };
|
|
@@ -22662,13 +23454,13 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
|
22662
23454
|
}
|
|
22663
23455
|
}
|
|
22664
23456
|
function resolveSlockCliPath(moduleUrl = import.meta.url) {
|
|
22665
|
-
const thisDir =
|
|
22666
|
-
const bundledDistPath =
|
|
23457
|
+
const thisDir = path22.dirname(fileURLToPath(moduleUrl));
|
|
23458
|
+
const bundledDistPath = path22.resolve(thisDir, "cli", "index.js");
|
|
22667
23459
|
try {
|
|
22668
23460
|
accessSync(bundledDistPath);
|
|
22669
23461
|
return bundledDistPath;
|
|
22670
23462
|
} catch {
|
|
22671
|
-
const workspaceDistPath =
|
|
23463
|
+
const workspaceDistPath = path22.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
|
|
22672
23464
|
accessSync(workspaceDistPath);
|
|
22673
23465
|
return workspaceDistPath;
|
|
22674
23466
|
}
|
|
@@ -22682,7 +23474,7 @@ function resolveSlockCliPathOrEmpty(moduleUrl = import.meta.url) {
|
|
|
22682
23474
|
}
|
|
22683
23475
|
async function runBundledSlockCli(argv) {
|
|
22684
23476
|
process.argv = [process.execPath, "slock", ...argv];
|
|
22685
|
-
await import("./dist-
|
|
23477
|
+
await import("./dist-NS7NN3BV.js");
|
|
22686
23478
|
}
|
|
22687
23479
|
function detectRuntimes(tracer = noopTracer) {
|
|
22688
23480
|
const ids = [];
|
|
@@ -22774,6 +23566,11 @@ function readPositiveIntegerEnv3(name, fallback) {
|
|
|
22774
23566
|
if (!Number.isFinite(parsed) || parsed < 1) return fallback;
|
|
22775
23567
|
return Math.floor(parsed);
|
|
22776
23568
|
}
|
|
23569
|
+
function hasAgentMigrationHttpTransportListenConfig(env = process.env) {
|
|
23570
|
+
return Boolean(
|
|
23571
|
+
env[AGENT_MIGRATION_TRANSPORT_HOST_ENV]?.trim() || env[AGENT_MIGRATION_TRANSPORT_PORT_ENV]?.trim() || env[AGENT_MIGRATION_TRANSPORT_PUBLIC_URL_ENV]?.trim()
|
|
23572
|
+
);
|
|
23573
|
+
}
|
|
22777
23574
|
function formatChannelTarget(msg) {
|
|
22778
23575
|
return msg.message.channel_type === "dm" ? `dm:@${msg.message.channel_name}` : `#${msg.message.channel_name}`;
|
|
22779
23576
|
}
|
|
@@ -22838,6 +23635,9 @@ var DaemonCore = class {
|
|
|
22838
23635
|
agentManager;
|
|
22839
23636
|
connection;
|
|
22840
23637
|
reminderCache;
|
|
23638
|
+
migrationTransport;
|
|
23639
|
+
migrationTransportListenPromise = null;
|
|
23640
|
+
migrationTransportListening = false;
|
|
22841
23641
|
tracer;
|
|
22842
23642
|
injectedTracer;
|
|
22843
23643
|
machineLock = null;
|
|
@@ -22861,6 +23661,7 @@ var DaemonCore = class {
|
|
|
22861
23661
|
clock: options.reminderClock,
|
|
22862
23662
|
onFire: (job) => this.onReminderFire(job)
|
|
22863
23663
|
});
|
|
23664
|
+
this.migrationTransport = options.migrationTransport === void 0 ? hasAgentMigrationHttpTransportListenConfig() ? createAgentMigrationHttpTransport() : null : options.migrationTransport;
|
|
22864
23665
|
let connection;
|
|
22865
23666
|
this.agentsDataDir = options.dataDir ?? resolveSlockHomePath("agents", this.slockHome);
|
|
22866
23667
|
const traceUploadDisabled = process.env.SLOCK_DAEMON_TRACE_UPLOAD_DISABLED === "1";
|
|
@@ -22891,7 +23692,7 @@ var DaemonCore = class {
|
|
|
22891
23692
|
}
|
|
22892
23693
|
resolveMachineStateRoot() {
|
|
22893
23694
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
22894
|
-
if (this.options.dataDir) return
|
|
23695
|
+
if (this.options.dataDir) return path22.join(path22.dirname(this.options.dataDir), "machines");
|
|
22895
23696
|
return resolveDefaultMachineStateRoot();
|
|
22896
23697
|
}
|
|
22897
23698
|
shouldEnableLocalTrace() {
|
|
@@ -22919,7 +23720,7 @@ var DaemonCore = class {
|
|
|
22919
23720
|
sinks: [this.localTraceSink]
|
|
22920
23721
|
}));
|
|
22921
23722
|
this.agentManager.setTracer(this.tracer);
|
|
22922
|
-
this.agentManager.setCliTransportTraceDir(
|
|
23723
|
+
this.agentManager.setCliTransportTraceDir(path22.join(machineDir, "traces"));
|
|
22923
23724
|
}
|
|
22924
23725
|
installTraceBundleUploader(machineDir) {
|
|
22925
23726
|
if (!this.shouldEnableLocalTrace()) return;
|
|
@@ -22969,9 +23770,11 @@ var DaemonCore = class {
|
|
|
22969
23770
|
span.addEvent("daemon.machine_lock.acquired", { machine_dir_present: true });
|
|
22970
23771
|
span.end("ok");
|
|
22971
23772
|
}
|
|
23773
|
+
this.startMigrationTransport();
|
|
22972
23774
|
try {
|
|
22973
23775
|
this.connection.connect();
|
|
22974
23776
|
} catch (err) {
|
|
23777
|
+
void this.stopMigrationTransport();
|
|
22975
23778
|
this.traceBundleUploader?.stop();
|
|
22976
23779
|
this.traceBundleUploader = null;
|
|
22977
23780
|
this.machineLock.release();
|
|
@@ -23010,6 +23813,7 @@ var DaemonCore = class {
|
|
|
23010
23813
|
shutdown_reason: shutdownReason
|
|
23011
23814
|
});
|
|
23012
23815
|
}
|
|
23816
|
+
await this.stopMigrationTransport();
|
|
23013
23817
|
this.connection.disconnect();
|
|
23014
23818
|
span.addEvent("daemon.connection.disconnect_requested");
|
|
23015
23819
|
this.machineLock?.release();
|
|
@@ -23035,6 +23839,44 @@ var DaemonCore = class {
|
|
|
23035
23839
|
});
|
|
23036
23840
|
span.end(status);
|
|
23037
23841
|
}
|
|
23842
|
+
startMigrationTransport() {
|
|
23843
|
+
if (!this.migrationTransport || this.migrationTransportListenPromise) return;
|
|
23844
|
+
this.migrationTransportListenPromise = this.migrationTransport.listen().then(({ url }) => {
|
|
23845
|
+
this.migrationTransportListening = true;
|
|
23846
|
+
logger.info(`[Slock Daemon] Agent migration HTTP transport listening: ${url}`);
|
|
23847
|
+
this.recordDaemonTrace("daemon.migration_transport.listen", {
|
|
23848
|
+
outcome: "listening",
|
|
23849
|
+
url_present: Boolean(url)
|
|
23850
|
+
});
|
|
23851
|
+
}).catch((err) => {
|
|
23852
|
+
this.migrationTransportListening = false;
|
|
23853
|
+
logger.error("[Slock Daemon] Agent migration HTTP transport failed to listen", err);
|
|
23854
|
+
this.recordDaemonTrace("daemon.migration_transport.listen", {
|
|
23855
|
+
outcome: "failed",
|
|
23856
|
+
error_class: err instanceof Error ? err.name : typeof err
|
|
23857
|
+
}, "error");
|
|
23858
|
+
});
|
|
23859
|
+
}
|
|
23860
|
+
async stopMigrationTransport() {
|
|
23861
|
+
if (!this.migrationTransport) return;
|
|
23862
|
+
const listenPromise = this.migrationTransportListenPromise;
|
|
23863
|
+
this.migrationTransportListenPromise = null;
|
|
23864
|
+
if (listenPromise) await listenPromise;
|
|
23865
|
+
if (!this.migrationTransportListening) return;
|
|
23866
|
+
try {
|
|
23867
|
+
await this.migrationTransport.close();
|
|
23868
|
+
logger.info("[Slock Daemon] Agent migration HTTP transport stopped");
|
|
23869
|
+
this.recordDaemonTrace("daemon.migration_transport.stop", { outcome: "stopped" });
|
|
23870
|
+
} catch (err) {
|
|
23871
|
+
logger.error("[Slock Daemon] Agent migration HTTP transport failed to stop", err);
|
|
23872
|
+
this.recordDaemonTrace("daemon.migration_transport.stop", {
|
|
23873
|
+
outcome: "failed",
|
|
23874
|
+
error_class: err instanceof Error ? err.name : typeof err
|
|
23875
|
+
}, "error");
|
|
23876
|
+
} finally {
|
|
23877
|
+
this.migrationTransportListening = false;
|
|
23878
|
+
}
|
|
23879
|
+
}
|
|
23038
23880
|
withDaemonTraceScope(tracer) {
|
|
23039
23881
|
return {
|
|
23040
23882
|
startSpan: (name, options) => createTraceScopeTracer(tracer, this.daemonTraceScope(), {
|
|
@@ -23534,6 +24376,7 @@ var DaemonCore = class {
|
|
|
23534
24376
|
agentId: job.ownerAgentId,
|
|
23535
24377
|
reminderId: job.reminderId,
|
|
23536
24378
|
version: job.version,
|
|
24379
|
+
// Accepted ambient clock: reminder telemetry reports the client wall-clock fire attempt.
|
|
23537
24380
|
firedAtClient: (/* @__PURE__ */ new Date()).toISOString()
|
|
23538
24381
|
});
|
|
23539
24382
|
}
|
|
@@ -23651,10 +24494,20 @@ export {
|
|
|
23651
24494
|
resolveWorkspaceDirectoryPath,
|
|
23652
24495
|
scanWorkspaceDirectories,
|
|
23653
24496
|
deleteWorkspaceDirectory,
|
|
23654
|
-
|
|
23655
|
-
|
|
24497
|
+
AGENT_MIGRATION_TRANSPORT_HOST_ENV,
|
|
24498
|
+
AGENT_MIGRATION_TRANSPORT_PORT_ENV,
|
|
24499
|
+
AGENT_MIGRATION_TRANSPORT_PUBLIC_URL_ENV,
|
|
24500
|
+
AGENT_MIGRATION_CONTROL_SEAM_ENV,
|
|
23656
24501
|
AgentMigrationGrantRegistry,
|
|
23657
24502
|
createAgentMigrationHttpTransport,
|
|
24503
|
+
resolveAgentMigrationHttpTransportListenOptions,
|
|
24504
|
+
resolveAgentMigrationControlSeamEnabled,
|
|
24505
|
+
AGENT_MIGRATION_BUNDLE_SCHEMA_VERSION,
|
|
24506
|
+
buildAgentMigrationExportManifest,
|
|
24507
|
+
buildAgentMigrationAdoptPlan,
|
|
24508
|
+
verifyAgentMigrationAdoptPlan,
|
|
24509
|
+
executeAgentMigrationAdoptPlan,
|
|
24510
|
+
hashAgentMigrationManifest,
|
|
23658
24511
|
DAEMON_CORE_TRACE_ATTR_CONTRACTS,
|
|
23659
24512
|
DAEMON_CLI_USAGE,
|
|
23660
24513
|
parseDaemonCliArgs,
|