@botiverse/raft-daemon 0.72.2 → 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-7RB6FSFI.js → chunk-OEK5FGK6.js} +1250 -387
- package/dist/cli/index.js +424 -125
- package/dist/cli/package.json +1 -1
- package/dist/core.js +23 -3
- package/dist/{dist-V7E6IFYX.js → dist-NS7NN3BV.js} +420 -125
- 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";
|
|
@@ -484,7 +484,7 @@ var RUNTIME_PROVIDER_DISPLAY_NAMES = {
|
|
|
484
484
|
"openrouter": "OpenRouter",
|
|
485
485
|
"xai": "xAI",
|
|
486
486
|
"xiaomi": "Xiaomi MiMo",
|
|
487
|
-
"zai": "ZAI",
|
|
487
|
+
"zai": "ZAI Coding Plan (Global)",
|
|
488
488
|
"zai-coding-cn": "ZAI Coding Plan (China)"
|
|
489
489
|
};
|
|
490
490
|
|
|
@@ -2694,6 +2694,51 @@ var agentApiReminderResponseSchema = passthroughObject({
|
|
|
2694
2694
|
var agentApiReminderLogResponseSchema = passthroughObject({
|
|
2695
2695
|
events: z2.array(agentApiReminderEventSummarySchema)
|
|
2696
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
|
+
});
|
|
2697
2742
|
function route(input) {
|
|
2698
2743
|
return {
|
|
2699
2744
|
...input,
|
|
@@ -2941,6 +2986,46 @@ var agentApiContract = {
|
|
|
2941
2986
|
request: { body: agentApiTaskUpdateStatusBodySchema },
|
|
2942
2987
|
response: { body: agentApiTaskUpdateStatusResponseSchema }
|
|
2943
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
|
+
}),
|
|
2944
3029
|
reminderList: route({
|
|
2945
3030
|
key: "reminderList",
|
|
2946
3031
|
method: "GET",
|
|
@@ -3248,6 +3333,14 @@ var AUTH_REFRESH_ROTATION_RETRY_RTT_BUDGET_MS = 1e3;
|
|
|
3248
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;
|
|
3249
3334
|
var AUTH_REFRESH_ROTATION_REPLAY_GRACE_MARGIN_MS = AUTH_REFRESH_ROTATED_REPLAY_GRACE_MS - AUTH_REFRESH_ROTATION_LOSER_WORST_CHAIN_MS;
|
|
3250
3335
|
|
|
3336
|
+
// ../shared/src/clock.ts
|
|
3337
|
+
function currentTimeMs() {
|
|
3338
|
+
return Date.now();
|
|
3339
|
+
}
|
|
3340
|
+
function currentDate() {
|
|
3341
|
+
return new Date(currentTimeMs());
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3251
3344
|
// ../shared/src/agentInbox.ts
|
|
3252
3345
|
function formatAgentInboxDelta(rows, options = {}) {
|
|
3253
3346
|
const totalPendingMessages = options.totalPendingMessages;
|
|
@@ -3630,7 +3723,7 @@ var EXTERNAL_AGENT_RUNTIME_DISPLAY_NAME = "External agent";
|
|
|
3630
3723
|
var RUNTIMES = [
|
|
3631
3724
|
{ id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
|
|
3632
3725
|
{ id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
|
|
3633
|
-
{ id: "builtin", displayName: "
|
|
3726
|
+
{ id: "builtin", displayName: "Built-in Pi", binary: "", supported: true },
|
|
3634
3727
|
{ id: "antigravity", displayName: "Antigravity CLI", binary: "agy", supported: true },
|
|
3635
3728
|
// Kimi: prefer the in-process SDK (`kimi-sdk` → "Kimi Code") for new agents.
|
|
3636
3729
|
// The legacy `kimi` (kimi-cli child-process) entry stays for backward compat
|
|
@@ -4328,8 +4421,8 @@ async function executeResponseRequest(url, init, {
|
|
|
4328
4421
|
}
|
|
4329
4422
|
|
|
4330
4423
|
// src/directUploadCapability.ts
|
|
4331
|
-
function joinUrl(base,
|
|
4332
|
-
return `${base.replace(/\/+$/, "")}${
|
|
4424
|
+
function joinUrl(base, path23) {
|
|
4425
|
+
return `${base.replace(/\/+$/, "")}${path23}`;
|
|
4333
4426
|
}
|
|
4334
4427
|
function jsonHeaders(apiKey) {
|
|
4335
4428
|
return {
|
|
@@ -5019,6 +5112,15 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
|
|
|
5019
5112
|
return candidates.filter((candidate) => existsSync(candidate.path));
|
|
5020
5113
|
}
|
|
5021
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
|
+
|
|
5022
5124
|
// src/agentCredentialProxy.ts
|
|
5023
5125
|
import { randomBytes } from "crypto";
|
|
5024
5126
|
import http from "http";
|
|
@@ -6600,7 +6702,9 @@ var LOOPBACK_NO_PROXY = "127.0.0.1,localhost";
|
|
|
6600
6702
|
var CLI_TRANSPORT_TRACE_DIR_ENV = "SLOCK_CLI_TRANSPORT_TRACE_DIR";
|
|
6601
6703
|
var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
6602
6704
|
var RAW_CREDENTIAL_ENV_DENYLIST = [
|
|
6603
|
-
"
|
|
6705
|
+
"SLOCK_AGENT_TOKEN",
|
|
6706
|
+
"SLOCK_AGENT_CREDENTIAL_KEY",
|
|
6707
|
+
"SLOCK_AGENT_CREDENTIAL_KEY_FILE"
|
|
6604
6708
|
];
|
|
6605
6709
|
var WORKSPACE_CLI_TRANSPORT_FILENAMES = [
|
|
6606
6710
|
"agent-token",
|
|
@@ -6955,7 +7059,7 @@ set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
|
|
|
6955
7059
|
SLOCK_SERVER_URL: ctx.config.serverUrl,
|
|
6956
7060
|
PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
|
|
6957
7061
|
};
|
|
6958
|
-
|
|
7062
|
+
scrubDaemonChildEnv(spawnEnv);
|
|
6959
7063
|
for (const key of RAW_CREDENTIAL_ENV_DENYLIST) {
|
|
6960
7064
|
delete spawnEnv[key];
|
|
6961
7065
|
}
|
|
@@ -7523,7 +7627,7 @@ function requiresWindowsShell(command, platform = process.platform) {
|
|
|
7523
7627
|
}
|
|
7524
7628
|
function resolveCommandOnPath(command, deps = {}) {
|
|
7525
7629
|
const platform = deps.platform ?? process.platform;
|
|
7526
|
-
const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
|
|
7630
|
+
const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
|
|
7527
7631
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
7528
7632
|
const existsSyncFn = deps.existsSyncFn ?? existsSync3;
|
|
7529
7633
|
if (platform === "win32") {
|
|
@@ -7549,7 +7653,7 @@ function firstExistingPath(candidates, deps = {}) {
|
|
|
7549
7653
|
return null;
|
|
7550
7654
|
}
|
|
7551
7655
|
function readCommandVersion(command, args = [], deps = {}) {
|
|
7552
|
-
const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
|
|
7656
|
+
const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
|
|
7553
7657
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
7554
7658
|
try {
|
|
7555
7659
|
const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
|
|
@@ -7975,7 +8079,7 @@ function codexNotificationDiagnosticEvent(message) {
|
|
|
7975
8079
|
return null;
|
|
7976
8080
|
}
|
|
7977
8081
|
const sessionId = codexMessageThreadId(message);
|
|
7978
|
-
const
|
|
8082
|
+
const path23 = boundedString(params.path);
|
|
7979
8083
|
return {
|
|
7980
8084
|
kind: "runtime_diagnostic",
|
|
7981
8085
|
severity: "warning",
|
|
@@ -7983,7 +8087,7 @@ function codexNotificationDiagnosticEvent(message) {
|
|
|
7983
8087
|
itemType: message.method,
|
|
7984
8088
|
message: diagnosticMessage,
|
|
7985
8089
|
...details ? { details } : {},
|
|
7986
|
-
...
|
|
8090
|
+
...path23 ? { path: path23 } : {},
|
|
7987
8091
|
...params.range !== void 0 ? { range: params.range } : {},
|
|
7988
8092
|
payloadBytes: payloadBytes(params),
|
|
7989
8093
|
...sessionId ? { sessionId } : {}
|
|
@@ -9633,11 +9737,11 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
|
|
|
9633
9737
|
return parseCursorModelsOutput(String(result.stdout || ""));
|
|
9634
9738
|
}
|
|
9635
9739
|
function buildCursorModelProbeEnv(deps = {}) {
|
|
9636
|
-
return withWindowsUserEnvironment({
|
|
9740
|
+
return scrubDaemonChildEnv(withWindowsUserEnvironment({
|
|
9637
9741
|
...deps.env ?? process.env,
|
|
9638
9742
|
FORCE_COLOR: "0",
|
|
9639
9743
|
NO_COLOR: "1"
|
|
9640
|
-
}, deps);
|
|
9744
|
+
}, deps));
|
|
9641
9745
|
}
|
|
9642
9746
|
function runCursorModelsCommand() {
|
|
9643
9747
|
return spawnSync("cursor-agent", ["models"], {
|
|
@@ -9693,7 +9797,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
|
|
|
9693
9797
|
}
|
|
9694
9798
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
|
|
9695
9799
|
const existsSyncFn = deps.existsSyncFn ?? existsSync5;
|
|
9696
|
-
const env = deps.env ?? process.env;
|
|
9800
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
9697
9801
|
const winPath = path8.win32;
|
|
9698
9802
|
let geminiEntry = null;
|
|
9699
9803
|
try {
|
|
@@ -9830,12 +9934,15 @@ var GeminiDriver = class {
|
|
|
9830
9934
|
// src/drivers/kimi.ts
|
|
9831
9935
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9832
9936
|
import { spawn as spawn7 } from "child_process";
|
|
9833
|
-
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";
|
|
9834
9938
|
import os4 from "os";
|
|
9835
9939
|
import path9 from "path";
|
|
9836
9940
|
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
9837
9941
|
var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
|
|
9838
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";
|
|
9839
9946
|
function parseToolArguments(raw) {
|
|
9840
9947
|
if (typeof raw !== "string") return raw;
|
|
9841
9948
|
try {
|
|
@@ -9844,6 +9951,73 @@ function parseToolArguments(raw) {
|
|
|
9844
9951
|
return raw;
|
|
9845
9952
|
}
|
|
9846
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
|
+
}
|
|
9847
10021
|
function resolveKimiSpawn(commandArgs, deps = {}) {
|
|
9848
10022
|
return {
|
|
9849
10023
|
command: resolveCommandOnPath("kimi", deps) ?? "kimi",
|
|
@@ -9867,7 +10041,25 @@ var KimiDriver = class {
|
|
|
9867
10041
|
};
|
|
9868
10042
|
model = {
|
|
9869
10043
|
detectedModelsVerifiedAs: "launchable",
|
|
9870
|
-
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
|
+
}
|
|
9871
10063
|
};
|
|
9872
10064
|
supportsStdinNotification = true;
|
|
9873
10065
|
busyDeliveryMode = "direct";
|
|
@@ -9891,21 +10083,23 @@ var KimiDriver = class {
|
|
|
9891
10083
|
` system_prompt_path: ./${KIMI_SYSTEM_PROMPT_FILE}`,
|
|
9892
10084
|
""
|
|
9893
10085
|
].join("\n"), "utf8");
|
|
10086
|
+
const launch = buildKimiLaunchOptions(ctx);
|
|
9894
10087
|
const args = [
|
|
9895
10088
|
"--wire",
|
|
9896
10089
|
"--yolo",
|
|
9897
10090
|
"--agent-file",
|
|
9898
10091
|
agentFilePath,
|
|
9899
10092
|
"--session",
|
|
9900
|
-
this.sessionId
|
|
10093
|
+
this.sessionId,
|
|
10094
|
+
...launch.args
|
|
9901
10095
|
];
|
|
9902
10096
|
const launchRuntimeFields = runtimeConfigToLaunchFields(hydrateRuntimeConfig(ctx.config));
|
|
9903
10097
|
if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
|
|
9904
10098
|
args.push("--model", launchRuntimeFields.model);
|
|
9905
10099
|
}
|
|
9906
10100
|
const spawnEnv = (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
|
|
9907
|
-
const
|
|
9908
|
-
const proc = spawn7(
|
|
10101
|
+
const spawnTarget = resolveKimiSpawn(args);
|
|
10102
|
+
const proc = spawn7(spawnTarget.command, spawnTarget.args, {
|
|
9909
10103
|
cwd: ctx.workingDirectory,
|
|
9910
10104
|
stdio: ["pipe", "pipe", "pipe"],
|
|
9911
10105
|
env: spawnEnv,
|
|
@@ -9913,7 +10107,7 @@ var KimiDriver = class {
|
|
|
9913
10107
|
// and has an 8191-character command-line limit. Kimi's official
|
|
9914
10108
|
// installer/uv entrypoint is an executable, so launch it directly and
|
|
9915
10109
|
// keep prompts on stdin / files instead of routing through cmd.exe.
|
|
9916
|
-
shell:
|
|
10110
|
+
shell: spawnTarget.shell
|
|
9917
10111
|
});
|
|
9918
10112
|
proc.stdin?.write(JSON.stringify({
|
|
9919
10113
|
jsonrpc: "2.0",
|
|
@@ -10026,14 +10220,9 @@ var KimiDriver = class {
|
|
|
10026
10220
|
return detectKimiModels();
|
|
10027
10221
|
}
|
|
10028
10222
|
};
|
|
10029
|
-
function detectKimiModels(home = os4.homedir()) {
|
|
10030
|
-
const
|
|
10031
|
-
|
|
10032
|
-
try {
|
|
10033
|
-
raw = readFileSync3(configPath, "utf8");
|
|
10034
|
-
} catch {
|
|
10035
|
-
return null;
|
|
10036
|
-
}
|
|
10223
|
+
function detectKimiModels(home = os4.homedir(), opts = {}) {
|
|
10224
|
+
const raw = readKimiConfigSource(home, opts.env).raw;
|
|
10225
|
+
if (raw === null) return null;
|
|
10037
10226
|
const models = [];
|
|
10038
10227
|
const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
|
|
10039
10228
|
const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
|
|
@@ -10712,7 +10901,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
|
|
|
10712
10901
|
const platform = deps.platform ?? process.platform;
|
|
10713
10902
|
const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
|
|
10714
10903
|
const result = spawnSyncFn("opencode", ["models"], {
|
|
10715
|
-
env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
10904
|
+
env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
|
|
10716
10905
|
encoding: "utf8",
|
|
10717
10906
|
timeout: 5e3,
|
|
10718
10907
|
shell: platform === "win32"
|
|
@@ -11008,7 +11197,9 @@ function createPiSdkEventMappingState(sessionId = null) {
|
|
|
11008
11197
|
return {
|
|
11009
11198
|
sessionId,
|
|
11010
11199
|
sessionAnnounced: false,
|
|
11011
|
-
sawTextDelta: false
|
|
11200
|
+
sawTextDelta: false,
|
|
11201
|
+
thinkingBuffers: /* @__PURE__ */ new Map(),
|
|
11202
|
+
announcedThinkingIndexes: /* @__PURE__ */ new Set()
|
|
11012
11203
|
};
|
|
11013
11204
|
}
|
|
11014
11205
|
function buildPiSessionDir(workingDirectory) {
|
|
@@ -11223,10 +11414,40 @@ function pushSessionInitIfNeeded2(state, events) {
|
|
|
11223
11414
|
state.sessionAnnounced = true;
|
|
11224
11415
|
}
|
|
11225
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
|
+
}
|
|
11226
11429
|
function mapPiAssistantMessageEvent(assistantEvent, state) {
|
|
11227
11430
|
switch (assistantEvent.type) {
|
|
11228
|
-
case "
|
|
11229
|
-
|
|
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
|
+
}
|
|
11230
11451
|
case "text_delta":
|
|
11231
11452
|
if (typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0) {
|
|
11232
11453
|
state.sawTextDelta = true;
|
|
@@ -11237,8 +11458,6 @@ function mapPiAssistantMessageEvent(assistantEvent, state) {
|
|
|
11237
11458
|
return !state.sawTextDelta && typeof assistantEvent.content === "string" && assistantEvent.content.length > 0 ? [{ kind: "text", text: assistantEvent.content }] : [];
|
|
11238
11459
|
case "error":
|
|
11239
11460
|
return [{ kind: "error", message: piErrorMessage(assistantEvent.error.errorMessage || assistantEvent.error) }];
|
|
11240
|
-
case "thinking_start":
|
|
11241
|
-
case "thinking_end":
|
|
11242
11461
|
case "text_start":
|
|
11243
11462
|
case "toolcall_start":
|
|
11244
11463
|
case "toolcall_delta":
|
|
@@ -11282,6 +11501,8 @@ function mapPiSdkEventToParsedEvents(event, state) {
|
|
|
11282
11501
|
case "message_start":
|
|
11283
11502
|
if (event.message.role === "assistant") {
|
|
11284
11503
|
state.sawTextDelta = false;
|
|
11504
|
+
state.thinkingBuffers.clear();
|
|
11505
|
+
state.announcedThinkingIndexes.clear();
|
|
11285
11506
|
}
|
|
11286
11507
|
return events;
|
|
11287
11508
|
case "message_update":
|
|
@@ -14758,8 +14979,9 @@ function classifyTerminalFailure(ap) {
|
|
|
14758
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)) {
|
|
14759
14980
|
const actionRequired = diagnostics.spanAttrs.runtime_error_action_required === true;
|
|
14760
14981
|
return {
|
|
14761
|
-
detail: actionRequired ?
|
|
14762
|
-
actionRequired
|
|
14982
|
+
detail: actionRequired ? formatRuntimeActionRequiredMessage(ap) : text,
|
|
14983
|
+
actionRequired,
|
|
14984
|
+
...actionRequired ? { entries: buildRuntimeActionRequiredEntries(ap, text, diagnostics) } : {}
|
|
14763
14985
|
};
|
|
14764
14986
|
}
|
|
14765
14987
|
}
|
|
@@ -14775,6 +14997,52 @@ function classifyStickyTerminalFailure(ap) {
|
|
|
14775
14997
|
if (isRuntimeStartTimeoutText(terminalFailure.detail)) return null;
|
|
14776
14998
|
return null;
|
|
14777
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
|
+
}
|
|
14778
15046
|
function isAuthClassTerminalLine(text) {
|
|
14779
15047
|
return buildRuntimeErrorDiagnosticEnvelope(text).spanAttrs.runtime_error_action_required === true;
|
|
14780
15048
|
}
|
|
@@ -16976,7 +17244,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
16976
17244
|
agentId,
|
|
16977
17245
|
"error",
|
|
16978
17246
|
terminalFailureDetail.detail,
|
|
16979
|
-
[{ kind: "text", text: `Error: ${terminalFailureDetail.detail}` }],
|
|
17247
|
+
terminalFailureDetail.entries ?? [{ kind: "text", text: `Error: ${terminalFailureDetail.detail}` }],
|
|
16980
17248
|
ap.launchId,
|
|
16981
17249
|
"runtime_error"
|
|
16982
17250
|
);
|
|
@@ -17007,9 +17275,10 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
17007
17275
|
agentProcess.decisionErrorWindow.recordRuntimeError(startResult.error);
|
|
17008
17276
|
}
|
|
17009
17277
|
if (startResult.error && diagnostics?.spanAttrs.runtime_error_action_required === true) {
|
|
17010
|
-
const
|
|
17278
|
+
const terminalFailure = classifyTerminalFailure(agentProcess);
|
|
17279
|
+
const visibleErrorMessage = terminalFailure?.detail ?? formatRuntimeActionRequiredMessage(agentProcess);
|
|
17011
17280
|
this.broadcastActivity(agentId, "error", visibleErrorMessage, [
|
|
17012
|
-
{ kind: "text", text: `Error: ${visibleErrorMessage}` }
|
|
17281
|
+
...terminalFailure?.entries ?? [{ kind: "text", text: `Error: ${visibleErrorMessage}` }]
|
|
17013
17282
|
], agentProcess.launchId, "runtime_error");
|
|
17014
17283
|
}
|
|
17015
17284
|
throw new Error(`Runtime session failed to start: ${startResult.reason}${startResult.error ? ` (${startResult.error})` : ""}`);
|
|
@@ -20172,11 +20441,12 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
20172
20441
|
ap.decisionErrorWindow.recordRuntimeError(event.message);
|
|
20173
20442
|
}
|
|
20174
20443
|
let visibleErrorMessage = event.message;
|
|
20444
|
+
let visibleErrorEntries;
|
|
20175
20445
|
if (ap) {
|
|
20176
20446
|
const runtimeErrorDiagnostics = buildRuntimeErrorDiagnosticEnvelope(event.message);
|
|
20177
20447
|
const runtimeErrorFingerprint = typeof runtimeErrorDiagnostics.spanAttrs.runtime_error_fingerprint === "string" ? runtimeErrorDiagnostics.spanAttrs.runtime_error_fingerprint : null;
|
|
20178
20448
|
if (runtimeErrorDiagnostics.spanAttrs.runtime_error_action_required === true) {
|
|
20179
|
-
visibleErrorMessage =
|
|
20449
|
+
visibleErrorMessage = formatRuntimeActionRequiredMessage(ap);
|
|
20180
20450
|
}
|
|
20181
20451
|
const shouldDisableToolBoundaryFlush = ap.runtime.descriptor.busyDelivery === "gated" && this.isThinkingBlockMutationError(event.message);
|
|
20182
20452
|
const backoffFailPoint = this.runtimeErrorDeliveryBackoffFailPointForTesting?.({
|
|
@@ -20202,6 +20472,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
20202
20472
|
if (fingerprintFence) {
|
|
20203
20473
|
visibleErrorMessage = fingerprintFence.detail;
|
|
20204
20474
|
}
|
|
20475
|
+
visibleErrorEntries = terminalFailure?.entries;
|
|
20205
20476
|
this.noteRuntimeErrorDeliveryBackoff(agentId, ap, event.message, terminalFailure, stickyTerminalFailure, backoffReasonOverride);
|
|
20206
20477
|
if (reduction.shouldDisableToolBoundaryFlush) {
|
|
20207
20478
|
this.recordGatedSteeringEvent(agentId, ap, "disabled", {
|
|
@@ -20248,9 +20519,14 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
20248
20519
|
}
|
|
20249
20520
|
}
|
|
20250
20521
|
}
|
|
20251
|
-
this.broadcastActivity(
|
|
20252
|
-
|
|
20253
|
-
|
|
20522
|
+
this.broadcastActivity(
|
|
20523
|
+
agentId,
|
|
20524
|
+
"error",
|
|
20525
|
+
visibleErrorMessage,
|
|
20526
|
+
visibleErrorEntries ?? [{ kind: "text", text: `Error: ${visibleErrorMessage}` }],
|
|
20527
|
+
void 0,
|
|
20528
|
+
"runtime_error"
|
|
20529
|
+
);
|
|
20254
20530
|
break;
|
|
20255
20531
|
}
|
|
20256
20532
|
}
|
|
@@ -20917,8 +21193,11 @@ var DaemonConnection = class {
|
|
|
20917
21193
|
doConnect() {
|
|
20918
21194
|
if (!this.shouldConnect) return;
|
|
20919
21195
|
if (this.ws && this.ws.readyState !== WebSocket.CLOSED) return;
|
|
20920
|
-
const wsUrl = this.options.serverUrl.replace(/^http/, "ws") +
|
|
20921
|
-
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
|
+
};
|
|
20922
21201
|
logger.info(`[Daemon] Connecting to ${this.options.serverUrl}...`);
|
|
20923
21202
|
if (wsOptions?.agent) {
|
|
20924
21203
|
logger.info("[Daemon] Using configured proxy for WebSocket connection");
|
|
@@ -21914,138 +22193,653 @@ function assertLegacyDaemonKeyNotAdoptedByComputer(options) {
|
|
|
21914
22193
|
if (match) throw new LegacyDaemonKeyAdoptedByComputerError(match);
|
|
21915
22194
|
}
|
|
21916
22195
|
|
|
21917
|
-
// src/
|
|
21918
|
-
import { createHash as createHash7 } from "crypto";
|
|
21919
|
-
import
|
|
21920
|
-
import {
|
|
21921
|
-
|
|
21922
|
-
var
|
|
21923
|
-
var
|
|
21924
|
-
|
|
21925
|
-
|
|
21926
|
-
|
|
21927
|
-
|
|
21928
|
-
|
|
21929
|
-
|
|
21930
|
-
var SECRET_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
21931
|
-
".env",
|
|
21932
|
-
".env.local",
|
|
21933
|
-
".env.development",
|
|
21934
|
-
".env.production",
|
|
21935
|
-
".env.test",
|
|
21936
|
-
"credentials.json",
|
|
21937
|
-
"credential.json"
|
|
21938
|
-
]);
|
|
21939
|
-
var ENV_KEY_PATTERN = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/;
|
|
21940
|
-
async function buildAgentMigrationExportManifest(input) {
|
|
21941
|
-
const slockHome = path19.resolve(input.slockHome);
|
|
21942
|
-
const workspace = path19.resolve(input.workspacePath ?? path19.join(slockHome, "agents", input.agentId));
|
|
21943
|
-
const proposedIncludes = normalizeProposalPaths(input.cooperativeManifest?.include, workspace, "include");
|
|
21944
|
-
const proposedRegenerableExcludes = normalizeProposalPaths(input.cooperativeManifest?.exclude_regenerable, workspace, "exclude_regenerable");
|
|
21945
|
-
const cleanedProposal = normalizeProposalPaths(input.cooperativeManifest?.cleaned, workspace, "cleaned");
|
|
21946
|
-
const state = {
|
|
21947
|
-
workspace,
|
|
21948
|
-
files: [],
|
|
21949
|
-
excludedRegenerable: [],
|
|
21950
|
-
promotedIncludes: [],
|
|
21951
|
-
proposalRefusals: [
|
|
21952
|
-
...proposedIncludes.refusals,
|
|
21953
|
-
...proposedRegenerableExcludes.refusals,
|
|
21954
|
-
...cleanedProposal.refusals
|
|
21955
|
-
],
|
|
21956
|
-
unreachable: [],
|
|
21957
|
-
secretsDisclosed: [],
|
|
21958
|
-
seenWorkspacePaths: /* @__PURE__ */ new Set(),
|
|
21959
|
-
explicitIncludePaths: new Set(proposedIncludes.paths)
|
|
21960
|
-
};
|
|
21961
|
-
await walkWorkspace(workspace, "", new Set(proposedRegenerableExcludes.paths), state, { forceIncludeRegenerable: false });
|
|
21962
|
-
for (const requestedPath of proposedIncludes.paths) {
|
|
21963
|
-
if (!state.seenWorkspacePaths.has(requestedPath)) {
|
|
21964
|
-
await includeWorkspacePath(requestedPath, state, { forceIncludeRegenerable: true });
|
|
21965
|
-
}
|
|
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;
|
|
21966
22209
|
}
|
|
21967
|
-
|
|
21968
|
-
|
|
21969
|
-
|
|
21970
|
-
|
|
22210
|
+
};
|
|
22211
|
+
var AgentMigrationGrantRegistry = class {
|
|
22212
|
+
grants = /* @__PURE__ */ new Map();
|
|
22213
|
+
now;
|
|
22214
|
+
constructor(now = currentDate) {
|
|
22215
|
+
this.now = now;
|
|
21971
22216
|
}
|
|
21972
|
-
|
|
21973
|
-
|
|
21974
|
-
|
|
21975
|
-
|
|
21976
|
-
|
|
21977
|
-
|
|
21978
|
-
|
|
21979
|
-
|
|
21980
|
-
|
|
21981
|
-
|
|
21982
|
-
|
|
21983
|
-
|
|
21984
|
-
|
|
21985
|
-
|
|
21986
|
-
|
|
21987
|
-
|
|
21988
|
-
},
|
|
21989
|
-
files: sortBundleEntries(state.files),
|
|
21990
|
-
excludedRegenerable: sortByPath(state.excludedRegenerable),
|
|
21991
|
-
promotedIncludes: sortByPath(state.promotedIncludes),
|
|
21992
|
-
proposalRefusals: sortByPath(state.proposalRefusals),
|
|
21993
|
-
unreachable: sortByPath(state.unreachable),
|
|
21994
|
-
secretsDisclosed: sortByPath([
|
|
21995
|
-
...state.secretsDisclosed,
|
|
21996
|
-
...normalizeProvidedSecretDisclosures(secretDisclosureProposal.paths)
|
|
21997
|
-
]),
|
|
21998
|
-
cleaned: cleanedProposal.paths,
|
|
21999
|
-
crossTreeRefs: crossTreeRefs.sort((a, b) => a.bundlePath.localeCompare(b.bundlePath))
|
|
22000
|
-
};
|
|
22001
|
-
}
|
|
22002
|
-
async function walkWorkspace(root, relativeDir, proposedRegenerableExcludes, state, opts) {
|
|
22003
|
-
const absoluteDir = path19.join(root, relativeDir);
|
|
22004
|
-
let entries;
|
|
22005
|
-
try {
|
|
22006
|
-
entries = await readdir4(absoluteDir, { withFileTypes: true });
|
|
22007
|
-
} catch {
|
|
22008
|
-
state.unreachable.push({
|
|
22009
|
-
path: relativeDir || ".",
|
|
22010
|
-
sourcePath: absoluteDir,
|
|
22011
|
-
reason: "read_error"
|
|
22012
|
-
});
|
|
22013
|
-
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 };
|
|
22014
22233
|
}
|
|
22015
|
-
|
|
22016
|
-
|
|
22017
|
-
const relativePath = toPosixPath(path19.join(relativeDir, entry.name));
|
|
22018
|
-
if (entry.isDirectory()) {
|
|
22019
|
-
if (!opts.forceIncludeRegenerable && isRegenerablePath(relativePath)) {
|
|
22020
|
-
if (state.explicitIncludePaths.has(relativePath)) {
|
|
22021
|
-
await walkWorkspace(root, relativePath, proposedRegenerableExcludes, state, { forceIncludeRegenerable: true });
|
|
22022
|
-
continue;
|
|
22023
|
-
}
|
|
22024
|
-
if (hasExplicitIncludeDescendant(relativePath, state.explicitIncludePaths)) {
|
|
22025
|
-
await walkWorkspace(root, relativePath, proposedRegenerableExcludes, state, opts);
|
|
22026
|
-
continue;
|
|
22027
|
-
}
|
|
22028
|
-
state.excludedRegenerable.push({
|
|
22029
|
-
path: relativePath,
|
|
22030
|
-
reason: proposedRegenerableExcludes.has(relativePath) ? "cooperative_exclude_regenerable" : "regenerable_default",
|
|
22031
|
-
regenerableHint: `${entry.name} is treated as rebuildable/installable state and is not bundled by default`
|
|
22032
|
-
});
|
|
22033
|
-
continue;
|
|
22034
|
-
}
|
|
22035
|
-
await walkWorkspace(root, relativePath, proposedRegenerableExcludes, state, opts);
|
|
22036
|
-
continue;
|
|
22037
|
-
}
|
|
22038
|
-
await includeWorkspacePath(relativePath, state, { forceIncludeRegenerable: opts.forceIncludeRegenerable });
|
|
22234
|
+
get(grantId) {
|
|
22235
|
+
return this.grants.get(grantId);
|
|
22039
22236
|
}
|
|
22040
|
-
|
|
22041
|
-
|
|
22042
|
-
|
|
22043
|
-
|
|
22044
|
-
|
|
22045
|
-
|
|
22046
|
-
|
|
22047
|
-
|
|
22048
|
-
|
|
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 {
|
|
22841
|
+
stat4 = await lstat2(sourcePath);
|
|
22842
|
+
} catch {
|
|
22049
22843
|
state.unreachable.push({
|
|
22050
22844
|
path: normalizedRelativePath,
|
|
22051
22845
|
sourcePath,
|
|
@@ -22129,7 +22923,7 @@ async function buildCrossTreeRefs(refs) {
|
|
|
22129
22923
|
return result;
|
|
22130
22924
|
}
|
|
22131
22925
|
async function sha256File(filePath) {
|
|
22132
|
-
const hash =
|
|
22926
|
+
const hash = createHash8("sha256");
|
|
22133
22927
|
await new Promise((resolve, reject) => {
|
|
22134
22928
|
const stream = createReadStream(filePath);
|
|
22135
22929
|
stream.on("data", (chunk) => hash.update(chunk));
|
|
@@ -22235,240 +23029,239 @@ function sortByPath(entries) {
|
|
|
22235
23029
|
return [...entries].sort((a, b) => a.path.localeCompare(b.path));
|
|
22236
23030
|
}
|
|
22237
23031
|
|
|
22238
|
-
// src/
|
|
22239
|
-
import { createHash as
|
|
22240
|
-
import
|
|
22241
|
-
|
|
22242
|
-
|
|
22243
|
-
|
|
22244
|
-
|
|
22245
|
-
|
|
22246
|
-
|
|
22247
|
-
|
|
22248
|
-
|
|
22249
|
-
|
|
22250
|
-
|
|
22251
|
-
|
|
22252
|
-
|
|
22253
|
-
|
|
22254
|
-
|
|
22255
|
-
|
|
22256
|
-
|
|
22257
|
-
|
|
22258
|
-
tokenHash: hashToken(token),
|
|
22259
|
-
expiresAt: input.expiresAt,
|
|
22260
|
-
oneTimeUse: input.oneTimeUse ?? true,
|
|
22261
|
-
state: "issued",
|
|
22262
|
-
manifest: input.manifest,
|
|
22263
|
-
manifestSha256,
|
|
22264
|
-
bundleEtag: `"agent-migration-${manifestSha256}"`
|
|
22265
|
-
};
|
|
22266
|
-
this.grants.set(grant.grantId, grant);
|
|
22267
|
-
return { grant, token };
|
|
22268
|
-
}
|
|
22269
|
-
get(grantId) {
|
|
22270
|
-
return this.grants.get(grantId);
|
|
22271
|
-
}
|
|
22272
|
-
authenticate(grantId, token) {
|
|
22273
|
-
const grant = this.grants.get(grantId);
|
|
22274
|
-
if (!grant || !token || !verifyTokenHash(token, grant.tokenHash)) {
|
|
22275
|
-
return { ok: false, status: 401, code: "migration_grant_auth_failed" };
|
|
22276
|
-
}
|
|
22277
|
-
if (grant.state === "revoked" || grant.state === "consumed") {
|
|
22278
|
-
return { ok: false, status: 410, code: "migration_grant_unavailable" };
|
|
22279
|
-
}
|
|
22280
|
-
if (this.now().getTime() >= grant.expiresAt.getTime()) {
|
|
22281
|
-
grant.state = "expired";
|
|
22282
|
-
return { ok: false, status: 410, code: "migration_grant_unavailable" };
|
|
22283
|
-
}
|
|
22284
|
-
return { ok: true, grant };
|
|
22285
|
-
}
|
|
22286
|
-
revokeAll() {
|
|
22287
|
-
for (const grant of this.grants.values()) {
|
|
22288
|
-
if (grant.state !== "consumed" && grant.state !== "expired") {
|
|
22289
|
-
grant.state = "revoked";
|
|
22290
|
-
}
|
|
22291
|
-
}
|
|
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");
|
|
22292
23052
|
}
|
|
22293
|
-
|
|
22294
|
-
|
|
22295
|
-
|
|
22296
|
-
const bundleStreamFactory = options.bundleStreamFactory ?? defaultBundleStream;
|
|
22297
|
-
const server = http2.createServer((req, res) => {
|
|
22298
|
-
void handleMigrationRequest(req, res, registry, bundleStreamFactory);
|
|
23053
|
+
assertManifestIdentity({
|
|
23054
|
+
manifest,
|
|
23055
|
+
expectedAgentId: input.sourceKind === "orphan_directory" ? input.agentId : manifest.agentId
|
|
22299
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
|
+
}
|
|
22300
23071
|
return {
|
|
22301
|
-
|
|
22302
|
-
|
|
22303
|
-
|
|
22304
|
-
|
|
22305
|
-
|
|
22306
|
-
|
|
22307
|
-
|
|
22308
|
-
|
|
22309
|
-
|
|
22310
|
-
|
|
22311
|
-
|
|
22312
|
-
resolve({ url: `http://${address.address}:${address.port}` });
|
|
22313
|
-
});
|
|
22314
|
-
}),
|
|
22315
|
-
close: () => new Promise((resolve, reject) => {
|
|
22316
|
-
registry.revokeAll();
|
|
22317
|
-
server.close((err) => {
|
|
22318
|
-
if (err) reject(err);
|
|
22319
|
-
else resolve();
|
|
22320
|
-
});
|
|
22321
|
-
})
|
|
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
|
|
22322
23083
|
};
|
|
22323
23084
|
}
|
|
22324
|
-
async function
|
|
22325
|
-
|
|
22326
|
-
|
|
22327
|
-
|
|
22328
|
-
|
|
22329
|
-
|
|
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");
|
|
22330
23149
|
}
|
|
22331
|
-
|
|
22332
|
-
|
|
22333
|
-
if (!auth.ok) {
|
|
22334
|
-
sendJson(res, auth.status, { code: auth.code });
|
|
22335
|
-
return;
|
|
23150
|
+
if (input.manifest.agentId !== input.expectedAgentId) {
|
|
23151
|
+
throw new Error("MIGRATION_MANIFEST_AGENT_MISMATCH");
|
|
22336
23152
|
}
|
|
22337
|
-
|
|
22338
|
-
|
|
22339
|
-
|
|
22340
|
-
|
|
22341
|
-
}, {
|
|
22342
|
-
"X-Raft-Manifest-Sha": auth.grant.manifestSha256,
|
|
22343
|
-
ETag: `"manifest-${auth.grant.manifestSha256}"`
|
|
22344
|
-
});
|
|
22345
|
-
return;
|
|
23153
|
+
}
|
|
23154
|
+
function assertGeneration(generation) {
|
|
23155
|
+
if (generation.localMachineId !== generation.targetMachineId) {
|
|
23156
|
+
throw new Error("MIGRATION_TARGET_MACHINE_MISMATCH");
|
|
22346
23157
|
}
|
|
22347
|
-
if (
|
|
22348
|
-
|
|
22349
|
-
sendHead(res, auth.grant);
|
|
22350
|
-
return;
|
|
22351
|
-
}
|
|
22352
|
-
if (req.method === "GET") {
|
|
22353
|
-
await streamBundle(res, auth.grant, bundleStreamFactory);
|
|
22354
|
-
return;
|
|
22355
|
-
}
|
|
23158
|
+
if (generation.sourceMachineId === generation.targetMachineId) {
|
|
23159
|
+
throw new Error("MIGRATION_SOURCE_TARGET_MACHINE_MATCH");
|
|
22356
23160
|
}
|
|
22357
|
-
if (
|
|
22358
|
-
|
|
22359
|
-
return;
|
|
23161
|
+
if (!generation.grantKey || !generation.migrationGeneration) {
|
|
23162
|
+
throw new Error("MIGRATION_GENERATION_MISSING");
|
|
22360
23163
|
}
|
|
22361
|
-
sendJson(res, 405, { code: "migration_method_not_allowed" });
|
|
22362
23164
|
}
|
|
22363
|
-
function
|
|
22364
|
-
|
|
22365
|
-
|
|
22366
|
-
|
|
22367
|
-
|
|
22368
|
-
|
|
22369
|
-
|
|
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;
|
|
22370
23180
|
}
|
|
22371
|
-
async function
|
|
22372
|
-
if (
|
|
22373
|
-
|
|
22374
|
-
return;
|
|
23181
|
+
async function verifyManifestFileEntry(sourceWorkspacePath, entry) {
|
|
23182
|
+
if (entry.source !== "workspace" || !entry.workspaceRelativePath) {
|
|
23183
|
+
throw new Error("MIGRATION_MANIFEST_ENTRY_NOT_WORKSPACE");
|
|
22375
23184
|
}
|
|
22376
|
-
|
|
22377
|
-
|
|
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");
|
|
22378
23193
|
return;
|
|
22379
23194
|
}
|
|
22380
|
-
|
|
22381
|
-
|
|
22382
|
-
|
|
22383
|
-
} catch (err) {
|
|
22384
|
-
const code = err instanceof MigrationBundleStreamError ? err.code : "migration_bundle_stream_failed";
|
|
22385
|
-
sendJson(res, 500, { code });
|
|
22386
|
-
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");
|
|
22387
23198
|
}
|
|
22388
|
-
|
|
22389
|
-
|
|
22390
|
-
|
|
22391
|
-
if (!completed && grant.state === "streaming") {
|
|
22392
|
-
grant.state = "interrupted";
|
|
22393
|
-
}
|
|
22394
|
-
});
|
|
22395
|
-
try {
|
|
22396
|
-
res.statusCode = 200;
|
|
22397
|
-
res.setHeader("Content-Type", "application/x-tar");
|
|
22398
|
-
res.setHeader("X-Raft-Manifest-Sha", grant.manifestSha256);
|
|
22399
|
-
res.setHeader("ETag", grant.bundleEtag);
|
|
22400
|
-
for await (const chunk of stream) {
|
|
22401
|
-
if (!res.write(chunk)) {
|
|
22402
|
-
await onceDrain(res);
|
|
22403
|
-
}
|
|
22404
|
-
}
|
|
22405
|
-
completed = true;
|
|
22406
|
-
grant.state = "consumed";
|
|
22407
|
-
res.end();
|
|
22408
|
-
} catch {
|
|
22409
|
-
if (!completed && grant.state === "streaming") {
|
|
22410
|
-
grant.state = "interrupted";
|
|
22411
|
-
}
|
|
22412
|
-
if (!res.headersSent) {
|
|
22413
|
-
sendJson(res, 500, { code: "migration_bundle_stream_failed" });
|
|
22414
|
-
} else {
|
|
22415
|
-
res.destroy();
|
|
22416
|
-
}
|
|
23199
|
+
if (entry.sha256) {
|
|
23200
|
+
const actual = await sha256File2(entryPath);
|
|
23201
|
+
if (actual !== entry.sha256) throw new Error("MIGRATION_MANIFEST_FILE_SHA_MISMATCH");
|
|
22417
23202
|
}
|
|
22418
23203
|
}
|
|
22419
|
-
function
|
|
22420
|
-
|
|
22421
|
-
|
|
22422
|
-
|
|
22423
|
-
|
|
22424
|
-
|
|
22425
|
-
if (directHeader) return directHeader;
|
|
22426
|
-
const authorization = singleHeader(req.headers.authorization);
|
|
22427
|
-
if (!authorization) return null;
|
|
22428
|
-
const match = /^Bearer\s+(.+)$/i.exec(authorization);
|
|
22429
|
-
return match?.[1] ?? null;
|
|
22430
|
-
}
|
|
22431
|
-
function singleHeader(value) {
|
|
22432
|
-
if (!value) return null;
|
|
22433
|
-
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;
|
|
22434
23210
|
}
|
|
22435
|
-
function
|
|
22436
|
-
|
|
22437
|
-
|
|
22438
|
-
|
|
22439
|
-
"
|
|
22440
|
-
|
|
22441
|
-
|
|
22442
|
-
|
|
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
|
+
}
|
|
22443
23219
|
}
|
|
22444
|
-
function
|
|
22445
|
-
|
|
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);
|
|
22446
23226
|
}
|
|
22447
|
-
function
|
|
22448
|
-
const
|
|
22449
|
-
|
|
22450
|
-
|
|
22451
|
-
|
|
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"));
|
|
22452
23233
|
}
|
|
22453
|
-
function
|
|
22454
|
-
|
|
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");
|
|
22455
23241
|
}
|
|
22456
|
-
function
|
|
22457
|
-
return
|
|
23242
|
+
function sha256Buffer2(buffer) {
|
|
23243
|
+
return createHash9("sha256").update(buffer).digest("hex");
|
|
22458
23244
|
}
|
|
22459
|
-
function
|
|
22460
|
-
return JSON.stringify(
|
|
23245
|
+
function canonicalJson2(value) {
|
|
23246
|
+
return JSON.stringify(sortJsonValue2(value));
|
|
22461
23247
|
}
|
|
22462
|
-
function
|
|
22463
|
-
if (Array.isArray(value)) return value.map(
|
|
23248
|
+
function sortJsonValue2(value) {
|
|
23249
|
+
if (Array.isArray(value)) return value.map(sortJsonValue2);
|
|
22464
23250
|
if (!value || typeof value !== "object") return value;
|
|
22465
23251
|
return Object.keys(value).sort().reduce((result, key) => {
|
|
22466
|
-
result[key] =
|
|
23252
|
+
result[key] = sortJsonValue2(value[key]);
|
|
22467
23253
|
return result;
|
|
22468
23254
|
}, {});
|
|
22469
23255
|
}
|
|
22470
|
-
function
|
|
22471
|
-
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();
|
|
22472
23265
|
}
|
|
22473
23266
|
|
|
22474
23267
|
// src/core.ts
|
|
@@ -22597,7 +23390,7 @@ var DAEMON_CORE_TRACE_ATTR_CONTRACTS = {
|
|
|
22597
23390
|
spanAttrs: ["agentId", "event_kind", "runtime"]
|
|
22598
23391
|
}
|
|
22599
23392
|
};
|
|
22600
|
-
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>";
|
|
22601
23394
|
var RunnerCredentialMintError2 = class extends Error {
|
|
22602
23395
|
code;
|
|
22603
23396
|
retryable;
|
|
@@ -22636,9 +23429,18 @@ async function waitForRunnerCredentialRetry2() {
|
|
|
22636
23429
|
function parseDaemonCliArgs(args) {
|
|
22637
23430
|
let serverUrl = "";
|
|
22638
23431
|
let apiKey = "";
|
|
23432
|
+
let apiKeyFile = process.env.SLOCK_DAEMON_API_KEY_FILE ?? "";
|
|
22639
23433
|
for (let i = 0; i < args.length; i++) {
|
|
22640
23434
|
if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
|
|
22641
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
|
+
}
|
|
22642
23444
|
}
|
|
22643
23445
|
if (!serverUrl || !apiKey) return null;
|
|
22644
23446
|
return { serverUrl, apiKey };
|
|
@@ -22652,13 +23454,13 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
|
22652
23454
|
}
|
|
22653
23455
|
}
|
|
22654
23456
|
function resolveSlockCliPath(moduleUrl = import.meta.url) {
|
|
22655
|
-
const thisDir =
|
|
22656
|
-
const bundledDistPath =
|
|
23457
|
+
const thisDir = path22.dirname(fileURLToPath(moduleUrl));
|
|
23458
|
+
const bundledDistPath = path22.resolve(thisDir, "cli", "index.js");
|
|
22657
23459
|
try {
|
|
22658
23460
|
accessSync(bundledDistPath);
|
|
22659
23461
|
return bundledDistPath;
|
|
22660
23462
|
} catch {
|
|
22661
|
-
const workspaceDistPath =
|
|
23463
|
+
const workspaceDistPath = path22.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
|
|
22662
23464
|
accessSync(workspaceDistPath);
|
|
22663
23465
|
return workspaceDistPath;
|
|
22664
23466
|
}
|
|
@@ -22672,7 +23474,7 @@ function resolveSlockCliPathOrEmpty(moduleUrl = import.meta.url) {
|
|
|
22672
23474
|
}
|
|
22673
23475
|
async function runBundledSlockCli(argv) {
|
|
22674
23476
|
process.argv = [process.execPath, "slock", ...argv];
|
|
22675
|
-
await import("./dist-
|
|
23477
|
+
await import("./dist-NS7NN3BV.js");
|
|
22676
23478
|
}
|
|
22677
23479
|
function detectRuntimes(tracer = noopTracer) {
|
|
22678
23480
|
const ids = [];
|
|
@@ -22764,6 +23566,11 @@ function readPositiveIntegerEnv3(name, fallback) {
|
|
|
22764
23566
|
if (!Number.isFinite(parsed) || parsed < 1) return fallback;
|
|
22765
23567
|
return Math.floor(parsed);
|
|
22766
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
|
+
}
|
|
22767
23574
|
function formatChannelTarget(msg) {
|
|
22768
23575
|
return msg.message.channel_type === "dm" ? `dm:@${msg.message.channel_name}` : `#${msg.message.channel_name}`;
|
|
22769
23576
|
}
|
|
@@ -22828,6 +23635,9 @@ var DaemonCore = class {
|
|
|
22828
23635
|
agentManager;
|
|
22829
23636
|
connection;
|
|
22830
23637
|
reminderCache;
|
|
23638
|
+
migrationTransport;
|
|
23639
|
+
migrationTransportListenPromise = null;
|
|
23640
|
+
migrationTransportListening = false;
|
|
22831
23641
|
tracer;
|
|
22832
23642
|
injectedTracer;
|
|
22833
23643
|
machineLock = null;
|
|
@@ -22851,6 +23661,7 @@ var DaemonCore = class {
|
|
|
22851
23661
|
clock: options.reminderClock,
|
|
22852
23662
|
onFire: (job) => this.onReminderFire(job)
|
|
22853
23663
|
});
|
|
23664
|
+
this.migrationTransport = options.migrationTransport === void 0 ? hasAgentMigrationHttpTransportListenConfig() ? createAgentMigrationHttpTransport() : null : options.migrationTransport;
|
|
22854
23665
|
let connection;
|
|
22855
23666
|
this.agentsDataDir = options.dataDir ?? resolveSlockHomePath("agents", this.slockHome);
|
|
22856
23667
|
const traceUploadDisabled = process.env.SLOCK_DAEMON_TRACE_UPLOAD_DISABLED === "1";
|
|
@@ -22881,7 +23692,7 @@ var DaemonCore = class {
|
|
|
22881
23692
|
}
|
|
22882
23693
|
resolveMachineStateRoot() {
|
|
22883
23694
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
22884
|
-
if (this.options.dataDir) return
|
|
23695
|
+
if (this.options.dataDir) return path22.join(path22.dirname(this.options.dataDir), "machines");
|
|
22885
23696
|
return resolveDefaultMachineStateRoot();
|
|
22886
23697
|
}
|
|
22887
23698
|
shouldEnableLocalTrace() {
|
|
@@ -22909,7 +23720,7 @@ var DaemonCore = class {
|
|
|
22909
23720
|
sinks: [this.localTraceSink]
|
|
22910
23721
|
}));
|
|
22911
23722
|
this.agentManager.setTracer(this.tracer);
|
|
22912
|
-
this.agentManager.setCliTransportTraceDir(
|
|
23723
|
+
this.agentManager.setCliTransportTraceDir(path22.join(machineDir, "traces"));
|
|
22913
23724
|
}
|
|
22914
23725
|
installTraceBundleUploader(machineDir) {
|
|
22915
23726
|
if (!this.shouldEnableLocalTrace()) return;
|
|
@@ -22959,9 +23770,11 @@ var DaemonCore = class {
|
|
|
22959
23770
|
span.addEvent("daemon.machine_lock.acquired", { machine_dir_present: true });
|
|
22960
23771
|
span.end("ok");
|
|
22961
23772
|
}
|
|
23773
|
+
this.startMigrationTransport();
|
|
22962
23774
|
try {
|
|
22963
23775
|
this.connection.connect();
|
|
22964
23776
|
} catch (err) {
|
|
23777
|
+
void this.stopMigrationTransport();
|
|
22965
23778
|
this.traceBundleUploader?.stop();
|
|
22966
23779
|
this.traceBundleUploader = null;
|
|
22967
23780
|
this.machineLock.release();
|
|
@@ -23000,6 +23813,7 @@ var DaemonCore = class {
|
|
|
23000
23813
|
shutdown_reason: shutdownReason
|
|
23001
23814
|
});
|
|
23002
23815
|
}
|
|
23816
|
+
await this.stopMigrationTransport();
|
|
23003
23817
|
this.connection.disconnect();
|
|
23004
23818
|
span.addEvent("daemon.connection.disconnect_requested");
|
|
23005
23819
|
this.machineLock?.release();
|
|
@@ -23025,6 +23839,44 @@ var DaemonCore = class {
|
|
|
23025
23839
|
});
|
|
23026
23840
|
span.end(status);
|
|
23027
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
|
+
}
|
|
23028
23880
|
withDaemonTraceScope(tracer) {
|
|
23029
23881
|
return {
|
|
23030
23882
|
startSpan: (name, options) => createTraceScopeTracer(tracer, this.daemonTraceScope(), {
|
|
@@ -23524,6 +24376,7 @@ var DaemonCore = class {
|
|
|
23524
24376
|
agentId: job.ownerAgentId,
|
|
23525
24377
|
reminderId: job.reminderId,
|
|
23526
24378
|
version: job.version,
|
|
24379
|
+
// Accepted ambient clock: reminder telemetry reports the client wall-clock fire attempt.
|
|
23527
24380
|
firedAtClient: (/* @__PURE__ */ new Date()).toISOString()
|
|
23528
24381
|
});
|
|
23529
24382
|
}
|
|
@@ -23641,10 +24494,20 @@ export {
|
|
|
23641
24494
|
resolveWorkspaceDirectoryPath,
|
|
23642
24495
|
scanWorkspaceDirectories,
|
|
23643
24496
|
deleteWorkspaceDirectory,
|
|
23644
|
-
|
|
23645
|
-
|
|
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,
|
|
23646
24501
|
AgentMigrationGrantRegistry,
|
|
23647
24502
|
createAgentMigrationHttpTransport,
|
|
24503
|
+
resolveAgentMigrationHttpTransportListenOptions,
|
|
24504
|
+
resolveAgentMigrationControlSeamEnabled,
|
|
24505
|
+
AGENT_MIGRATION_BUNDLE_SCHEMA_VERSION,
|
|
24506
|
+
buildAgentMigrationExportManifest,
|
|
24507
|
+
buildAgentMigrationAdoptPlan,
|
|
24508
|
+
verifyAgentMigrationAdoptPlan,
|
|
24509
|
+
executeAgentMigrationAdoptPlan,
|
|
24510
|
+
hashAgentMigrationManifest,
|
|
23648
24511
|
DAEMON_CORE_TRACE_ATTR_CONTRACTS,
|
|
23649
24512
|
DAEMON_CLI_USAGE,
|
|
23650
24513
|
parseDaemonCliArgs,
|