@botiverse/raft-daemon 0.72.3 → 0.72.4-play.20260710140617
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-GMOJFVE4.js → chunk-R56QPYOR.js} +2141 -401
- package/dist/cli/index.js +490 -142
- package/dist/core.js +37 -3
- package/dist/{dist-YOKZZD4F.js → dist-CRXPFW5L.js} +486 -142
- package/dist/index.js +1 -1
- package/package.json +3 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/core.ts
|
|
2
|
-
import
|
|
2
|
+
import path23 from "path";
|
|
3
3
|
import os8 from "os";
|
|
4
4
|
import { createRequire as createRequire3 } from "module";
|
|
5
5
|
import { accessSync } from "fs";
|
|
@@ -636,12 +636,12 @@ function parseModelConfig(runtimeModels, runtime, value, fallback, usesLegacyCla
|
|
|
636
636
|
if (value.kind === "custom" && typeof value.name === "string" && value.name.trim()) {
|
|
637
637
|
return { kind: "custom", name: value.name.trim() };
|
|
638
638
|
}
|
|
639
|
-
if (value.kind === "preset" && typeof value.id === "string" && value.id.trim() && isPresetRuntimeModel(runtimeModels, runtime, value.id.trim())) {
|
|
640
|
-
return { kind: "preset", id: value.id.trim() };
|
|
641
|
-
}
|
|
642
639
|
if (usesLegacyClaudeCustomProvider && runtime === "claude" && legacyCustomModel?.trim()) {
|
|
643
640
|
return { kind: "custom", name: legacyCustomModel.trim() };
|
|
644
641
|
}
|
|
642
|
+
if (value.kind === "preset" && typeof value.id === "string" && value.id.trim()) {
|
|
643
|
+
return { kind: "preset", id: value.id.trim() };
|
|
644
|
+
}
|
|
645
645
|
return modelConfigFromLegacy(runtimeModels, runtime, fallback);
|
|
646
646
|
}
|
|
647
647
|
function parseModeConfig(value) {
|
|
@@ -1366,6 +1366,9 @@ var PROMOTED_IDENTITY_ATTRS = [
|
|
|
1366
1366
|
"caller_kind"
|
|
1367
1367
|
];
|
|
1368
1368
|
var PROMOTED_CLOSED_ATTRS = [
|
|
1369
|
+
"router_reason",
|
|
1370
|
+
"stale_owner_cleanup_result",
|
|
1371
|
+
"stale_owner_cleanup_reason",
|
|
1369
1372
|
"outcome",
|
|
1370
1373
|
"reason",
|
|
1371
1374
|
"event_kind",
|
|
@@ -1374,6 +1377,8 @@ var PROMOTED_CLOSED_ATTRS = [
|
|
|
1374
1377
|
"activity_write_site",
|
|
1375
1378
|
"activity_source",
|
|
1376
1379
|
"hint_source",
|
|
1380
|
+
"weak_source",
|
|
1381
|
+
"competing_fact",
|
|
1377
1382
|
"resolved_activity",
|
|
1378
1383
|
"previous_activity",
|
|
1379
1384
|
"next_activity",
|
|
@@ -1459,11 +1464,20 @@ var TRACE_B0_FIELD_DEFINITIONS = defineTraceFields([
|
|
|
1459
1464
|
{ key: "served_from", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
|
|
1460
1465
|
{ key: "authority", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
|
|
1461
1466
|
{ key: "decided_by", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
|
|
1467
|
+
{ key: "router_reason", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
|
|
1468
|
+
{ key: "stale_owner_cleanup_result", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
|
|
1469
|
+
{ key: "stale_owner_cleanup_reason", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
|
|
1462
1470
|
{ key: "error_class", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
|
|
1463
1471
|
{ key: "query_name", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "query_registry" },
|
|
1464
1472
|
{ key: "phase", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
|
|
1465
1473
|
{ key: "eligibility_subcheck", fieldClass: "query_axis", placement: "event", valueKind: "closed_enum", enumBinding: "pending_457" },
|
|
1466
1474
|
{ key: "hint_source", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity" },
|
|
1475
|
+
{ key: "candidate_activity", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity" },
|
|
1476
|
+
{ key: "served_activity", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity" },
|
|
1477
|
+
{ key: "write_action", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity", enumBinding: "stable", enumValues: ["none"] },
|
|
1478
|
+
{ key: "arbitration_reason", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity", enumBinding: "stable", enumValues: ["trusted_snapshot", "owner_mirror_read_through"] },
|
|
1479
|
+
{ key: "weak_source", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity" },
|
|
1480
|
+
{ key: "competing_fact", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity" },
|
|
1467
1481
|
{ key: "resolved_activity", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity" },
|
|
1468
1482
|
{ key: "previous_activity", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity" },
|
|
1469
1483
|
{ key: "next_activity", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity" },
|
|
@@ -1989,23 +2003,13 @@ var integrationUpdateAppRegistrationOperationSchema = integrationAppDraftFieldsS
|
|
|
1989
2003
|
unsafeDemoUrlOverride: z.boolean().optional(),
|
|
1990
2004
|
draftHint: draftHintSchema
|
|
1991
2005
|
});
|
|
1992
|
-
var migrationExportOperationSchema = z.object({
|
|
1993
|
-
type: z.literal("migration:export"),
|
|
1994
|
-
targetComputer: idOrHandleSchema,
|
|
1995
|
-
mode: z.enum(["cooperative", "forensic"]).default("cooperative"),
|
|
1996
|
-
prepDeadlineMs: z.number().int().positive().optional(),
|
|
1997
|
-
transferDeadlineMs: z.number().int().positive().optional(),
|
|
1998
|
-
arrivalDeadlineMs: z.number().int().positive().optional(),
|
|
1999
|
-
draftHint: draftHintSchema
|
|
2000
|
-
});
|
|
2001
2006
|
var actionCardActionSchema = z.discriminatedUnion("type", [
|
|
2002
2007
|
channelCreateOperationSchema,
|
|
2003
2008
|
agentCreateOperationSchema,
|
|
2004
2009
|
channelAddMemberOperationSchema,
|
|
2005
2010
|
integrationApproveAgentLoginOperationSchema,
|
|
2006
2011
|
integrationRegisterAppOperationSchema,
|
|
2007
|
-
integrationUpdateAppRegistrationOperationSchema
|
|
2008
|
-
migrationExportOperationSchema
|
|
2012
|
+
integrationUpdateAppRegistrationOperationSchema
|
|
2009
2013
|
]);
|
|
2010
2014
|
|
|
2011
2015
|
// ../shared/src/agentApiContract.ts
|
|
@@ -2027,8 +2031,8 @@ var optionalIsoTimestampSchema = z2.string().datetime().optional();
|
|
|
2027
2031
|
var agentStatusSchema = z2.enum(["active", "inactive", "stopped"]);
|
|
2028
2032
|
var reminderStatusSchema = z2.enum(["scheduled", "fired", "canceled"]);
|
|
2029
2033
|
var reminderEventTypeSchema = z2.enum(["scheduled", "fired", "snoozed", "updated", "canceled"]);
|
|
2030
|
-
var reasoningEffortSchema = z2.enum(["low", "medium", "high", "xhigh"]);
|
|
2031
|
-
var profileVisibilityMembershipStatusSchema = z2.enum(["active", "removed"]);
|
|
2034
|
+
var reasoningEffortSchema = z2.enum(["low", "medium", "high", "xhigh", "max", "ultra"]);
|
|
2035
|
+
var profileVisibilityMembershipStatusSchema = z2.enum(["active", "left", "removed"]);
|
|
2032
2036
|
var passthroughObject = (shape) => z2.object(shape).passthrough();
|
|
2033
2037
|
var taskStatusSchema = z2.enum(["todo", "in_progress", "in_review", "done", "closed"]);
|
|
2034
2038
|
var agentApiEventsQuerySchema = passthroughObject({
|
|
@@ -2098,6 +2102,10 @@ var agentApiSendBodySchema = passthroughObject({
|
|
|
2098
2102
|
draftReplacedExisting: optionalBooleanSchema,
|
|
2099
2103
|
seenUpToSeq: optionalNumberSchema
|
|
2100
2104
|
});
|
|
2105
|
+
var legacyAgentSendBodySchema = agentApiSendBodySchema.extend({
|
|
2106
|
+
channel: z2.string().optional(),
|
|
2107
|
+
dm_to: z2.string().optional()
|
|
2108
|
+
});
|
|
2101
2109
|
var agentApiMessageResolveParamsSchema = passthroughObject({
|
|
2102
2110
|
msgId: z2.string().trim().min(1).transform(asMessageId)
|
|
2103
2111
|
});
|
|
@@ -2704,6 +2712,51 @@ var agentApiReminderResponseSchema = passthroughObject({
|
|
|
2704
2712
|
var agentApiReminderLogResponseSchema = passthroughObject({
|
|
2705
2713
|
events: z2.array(agentApiReminderEventSummarySchema)
|
|
2706
2714
|
});
|
|
2715
|
+
var agentApiMigrationStateSchema = z2.enum(["provisioning", "prep", "ready", "in_transit", "arriving", "completed", "aborted", "failed"]);
|
|
2716
|
+
var agentApiMigrationSummarySchema = passthroughObject({
|
|
2717
|
+
id: z2.string(),
|
|
2718
|
+
agentId: z2.string(),
|
|
2719
|
+
sourceMachineId: z2.string(),
|
|
2720
|
+
targetMachineId: z2.string(),
|
|
2721
|
+
state: agentApiMigrationStateSchema,
|
|
2722
|
+
manifestPath: nullableStringSchema,
|
|
2723
|
+
manifestSha256: nullableStringSchema,
|
|
2724
|
+
arrivalReportPath: nullableStringSchema,
|
|
2725
|
+
arrivalReportSha256: nullableStringSchema,
|
|
2726
|
+
abortReason: nullableStringSchema,
|
|
2727
|
+
failureReason: nullableStringSchema,
|
|
2728
|
+
prepDeadlineAt: z2.string().datetime(),
|
|
2729
|
+
transferDeadlineAt: z2.string().datetime(),
|
|
2730
|
+
arrivalDeadlineAt: z2.string().datetime(),
|
|
2731
|
+
readyAt: nullableStringSchema,
|
|
2732
|
+
flippedAt: nullableStringSchema,
|
|
2733
|
+
arrivedAt: nullableStringSchema,
|
|
2734
|
+
completedAt: nullableStringSchema,
|
|
2735
|
+
abortedAt: nullableStringSchema,
|
|
2736
|
+
revision: z2.number().int().positive(),
|
|
2737
|
+
createdAt: z2.string().datetime(),
|
|
2738
|
+
updatedAt: z2.string().datetime()
|
|
2739
|
+
});
|
|
2740
|
+
var agentApiMigrationBeginBodySchema = passthroughObject({
|
|
2741
|
+
targetMachineId: z2.string().trim().min(1),
|
|
2742
|
+
prepDeadlineMs: z2.number().int().positive().optional(),
|
|
2743
|
+
transferDeadlineMs: z2.number().int().positive().optional(),
|
|
2744
|
+
arrivalDeadlineMs: z2.number().int().positive().optional()
|
|
2745
|
+
});
|
|
2746
|
+
var agentApiMigrationReadyBodySchema = passthroughObject({
|
|
2747
|
+
manifestPath: z2.string().trim().min(1),
|
|
2748
|
+
manifestSha256: optionalStringSchema
|
|
2749
|
+
});
|
|
2750
|
+
var agentApiMigrationArrivedBodySchema = passthroughObject({
|
|
2751
|
+
reportPath: optionalStringSchema,
|
|
2752
|
+
reportSha256: optionalStringSchema
|
|
2753
|
+
});
|
|
2754
|
+
var agentApiMigrationResponseSchema = passthroughObject({
|
|
2755
|
+
migration: agentApiMigrationSummarySchema
|
|
2756
|
+
});
|
|
2757
|
+
var agentApiMigrationStatusResponseSchema = passthroughObject({
|
|
2758
|
+
migration: agentApiMigrationSummarySchema.nullable()
|
|
2759
|
+
});
|
|
2707
2760
|
function route(input) {
|
|
2708
2761
|
return {
|
|
2709
2762
|
...input,
|
|
@@ -2951,6 +3004,46 @@ var agentApiContract = {
|
|
|
2951
3004
|
request: { body: agentApiTaskUpdateStatusBodySchema },
|
|
2952
3005
|
response: { body: agentApiTaskUpdateStatusResponseSchema }
|
|
2953
3006
|
}),
|
|
3007
|
+
migrationBegin: route({
|
|
3008
|
+
key: "migrationBegin",
|
|
3009
|
+
method: "POST",
|
|
3010
|
+
path: "/migrations",
|
|
3011
|
+
client: { resource: "migrations", method: "begin" },
|
|
3012
|
+
capability: "server",
|
|
3013
|
+
description: "Begin a migration for the bound agent credential.",
|
|
3014
|
+
request: { body: agentApiMigrationBeginBodySchema },
|
|
3015
|
+
response: { body: agentApiMigrationResponseSchema }
|
|
3016
|
+
}),
|
|
3017
|
+
migrationStatus: route({
|
|
3018
|
+
key: "migrationStatus",
|
|
3019
|
+
method: "GET",
|
|
3020
|
+
path: "/migrations/current",
|
|
3021
|
+
client: { resource: "migrations", method: "status" },
|
|
3022
|
+
capability: "read",
|
|
3023
|
+
description: "Read the active migration for the bound agent credential, if any.",
|
|
3024
|
+
request: {},
|
|
3025
|
+
response: { body: agentApiMigrationStatusResponseSchema }
|
|
3026
|
+
}),
|
|
3027
|
+
migrationReady: route({
|
|
3028
|
+
key: "migrationReady",
|
|
3029
|
+
method: "POST",
|
|
3030
|
+
path: "/migrations/ready",
|
|
3031
|
+
client: { resource: "migrations", method: "ready" },
|
|
3032
|
+
capability: "read",
|
|
3033
|
+
description: "Mark the bound agent's active migration prep phase ready.",
|
|
3034
|
+
request: { body: agentApiMigrationReadyBodySchema },
|
|
3035
|
+
response: { body: agentApiMigrationResponseSchema }
|
|
3036
|
+
}),
|
|
3037
|
+
migrationArrived: route({
|
|
3038
|
+
key: "migrationArrived",
|
|
3039
|
+
method: "POST",
|
|
3040
|
+
path: "/migrations/arrived",
|
|
3041
|
+
client: { resource: "migrations", method: "arrived" },
|
|
3042
|
+
capability: "read",
|
|
3043
|
+
description: "Mark the bound agent's active migration arrival phase complete.",
|
|
3044
|
+
request: { body: agentApiMigrationArrivedBodySchema },
|
|
3045
|
+
response: { body: agentApiMigrationResponseSchema }
|
|
3046
|
+
}),
|
|
2954
3047
|
reminderList: route({
|
|
2955
3048
|
key: "reminderList",
|
|
2956
3049
|
method: "GET",
|
|
@@ -3258,6 +3351,14 @@ var AUTH_REFRESH_ROTATION_RETRY_RTT_BUDGET_MS = 1e3;
|
|
|
3258
3351
|
var AUTH_REFRESH_ROTATION_LOSER_WORST_CHAIN_MS = AUTH_REFRESH_ROTATION_DISCOVERY_LAG_BUDGET_MS + AUTH_REFRESH_ROTATED_TOKEN_WAIT_MS + AUTH_REFRESH_ROTATION_RETRY_RTT_BUDGET_MS;
|
|
3259
3352
|
var AUTH_REFRESH_ROTATION_REPLAY_GRACE_MARGIN_MS = AUTH_REFRESH_ROTATED_REPLAY_GRACE_MS - AUTH_REFRESH_ROTATION_LOSER_WORST_CHAIN_MS;
|
|
3260
3353
|
|
|
3354
|
+
// ../shared/src/clock.ts
|
|
3355
|
+
function currentTimeMs() {
|
|
3356
|
+
return Date.now();
|
|
3357
|
+
}
|
|
3358
|
+
function currentDate() {
|
|
3359
|
+
return new Date(currentTimeMs());
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3261
3362
|
// ../shared/src/agentInbox.ts
|
|
3262
3363
|
function formatAgentInboxDelta(rows, options = {}) {
|
|
3263
3364
|
const totalPendingMessages = options.totalPendingMessages;
|
|
@@ -3640,7 +3741,7 @@ var EXTERNAL_AGENT_RUNTIME_DISPLAY_NAME = "External agent";
|
|
|
3640
3741
|
var RUNTIMES = [
|
|
3641
3742
|
{ id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
|
|
3642
3743
|
{ id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
|
|
3643
|
-
{ id: "builtin", displayName: "
|
|
3744
|
+
{ id: "builtin", displayName: "Built-in Pi", binary: "", supported: true },
|
|
3644
3745
|
{ id: "antigravity", displayName: "Antigravity CLI", binary: "agy", supported: true },
|
|
3645
3746
|
// Kimi: prefer the in-process SDK (`kimi-sdk` → "Kimi Code") for new agents.
|
|
3646
3747
|
// The legacy `kimi` (kimi-cli child-process) entry stays for backward compat
|
|
@@ -3672,6 +3773,27 @@ var RUNTIME_MODELS = {
|
|
|
3672
3773
|
{ id: "claude-haiku-4-5", label: "Claude Haiku 4.5" }
|
|
3673
3774
|
],
|
|
3674
3775
|
codex: [
|
|
3776
|
+
{
|
|
3777
|
+
id: "gpt-5.6-sol",
|
|
3778
|
+
label: "GPT-5.6 Sol",
|
|
3779
|
+
supportedReasoningEfforts: ["low", "medium", "high", "xhigh", "max", "ultra"],
|
|
3780
|
+
defaultReasoningEffort: "medium",
|
|
3781
|
+
verified: "launchable"
|
|
3782
|
+
},
|
|
3783
|
+
{
|
|
3784
|
+
id: "gpt-5.6-terra",
|
|
3785
|
+
label: "GPT-5.6 Terra",
|
|
3786
|
+
supportedReasoningEfforts: ["low", "medium", "high", "xhigh", "max", "ultra"],
|
|
3787
|
+
defaultReasoningEffort: "medium",
|
|
3788
|
+
verified: "launchable"
|
|
3789
|
+
},
|
|
3790
|
+
{
|
|
3791
|
+
id: "gpt-5.6-luna",
|
|
3792
|
+
label: "GPT-5.6 Luna",
|
|
3793
|
+
supportedReasoningEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
3794
|
+
defaultReasoningEffort: "medium",
|
|
3795
|
+
verified: "launchable"
|
|
3796
|
+
},
|
|
3675
3797
|
{ id: "gpt-5.5", label: "GPT-5.5" },
|
|
3676
3798
|
{ id: "gpt-5.4", label: "GPT-5.4" },
|
|
3677
3799
|
{ id: "gpt-5.3-codex", label: "GPT-5.3 Codex" },
|
|
@@ -4338,8 +4460,8 @@ async function executeResponseRequest(url, init, {
|
|
|
4338
4460
|
}
|
|
4339
4461
|
|
|
4340
4462
|
// src/directUploadCapability.ts
|
|
4341
|
-
function joinUrl(base,
|
|
4342
|
-
return `${base.replace(/\/+$/, "")}${
|
|
4463
|
+
function joinUrl(base, path24) {
|
|
4464
|
+
return `${base.replace(/\/+$/, "")}${path24}`;
|
|
4343
4465
|
}
|
|
4344
4466
|
function jsonHeaders(apiKey) {
|
|
4345
4467
|
return {
|
|
@@ -5029,6 +5151,15 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
|
|
|
5029
5151
|
return candidates.filter((candidate) => existsSync(candidate.path));
|
|
5030
5152
|
}
|
|
5031
5153
|
|
|
5154
|
+
// src/authEnv.ts
|
|
5155
|
+
var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
|
|
5156
|
+
var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
|
|
5157
|
+
function scrubDaemonChildEnv(env) {
|
|
5158
|
+
delete env[DAEMON_API_KEY_ENV];
|
|
5159
|
+
delete env[SLOCK_AGENT_TOKEN_ENV];
|
|
5160
|
+
return env;
|
|
5161
|
+
}
|
|
5162
|
+
|
|
5032
5163
|
// src/agentCredentialProxy.ts
|
|
5033
5164
|
import { randomBytes } from "crypto";
|
|
5034
5165
|
import http from "http";
|
|
@@ -6610,7 +6741,9 @@ var LOOPBACK_NO_PROXY = "127.0.0.1,localhost";
|
|
|
6610
6741
|
var CLI_TRANSPORT_TRACE_DIR_ENV = "SLOCK_CLI_TRANSPORT_TRACE_DIR";
|
|
6611
6742
|
var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
6612
6743
|
var RAW_CREDENTIAL_ENV_DENYLIST = [
|
|
6613
|
-
"
|
|
6744
|
+
"SLOCK_AGENT_TOKEN",
|
|
6745
|
+
"SLOCK_AGENT_CREDENTIAL_KEY",
|
|
6746
|
+
"SLOCK_AGENT_CREDENTIAL_KEY_FILE"
|
|
6614
6747
|
];
|
|
6615
6748
|
var WORKSPACE_CLI_TRANSPORT_FILENAMES = [
|
|
6616
6749
|
"agent-token",
|
|
@@ -6965,7 +7098,7 @@ set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
|
|
|
6965
7098
|
SLOCK_SERVER_URL: ctx.config.serverUrl,
|
|
6966
7099
|
PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
|
|
6967
7100
|
};
|
|
6968
|
-
|
|
7101
|
+
scrubDaemonChildEnv(spawnEnv);
|
|
6969
7102
|
for (const key of RAW_CREDENTIAL_ENV_DENYLIST) {
|
|
6970
7103
|
delete spawnEnv[key];
|
|
6971
7104
|
}
|
|
@@ -7533,7 +7666,7 @@ function requiresWindowsShell(command, platform = process.platform) {
|
|
|
7533
7666
|
}
|
|
7534
7667
|
function resolveCommandOnPath(command, deps = {}) {
|
|
7535
7668
|
const platform = deps.platform ?? process.platform;
|
|
7536
|
-
const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
|
|
7669
|
+
const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
|
|
7537
7670
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
7538
7671
|
const existsSyncFn = deps.existsSyncFn ?? existsSync3;
|
|
7539
7672
|
if (platform === "win32") {
|
|
@@ -7559,7 +7692,7 @@ function firstExistingPath(candidates, deps = {}) {
|
|
|
7559
7692
|
return null;
|
|
7560
7693
|
}
|
|
7561
7694
|
function readCommandVersion(command, args = [], deps = {}) {
|
|
7562
|
-
const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
|
|
7695
|
+
const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
|
|
7563
7696
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
7564
7697
|
try {
|
|
7565
7698
|
const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
|
|
@@ -7985,7 +8118,7 @@ function codexNotificationDiagnosticEvent(message) {
|
|
|
7985
8118
|
return null;
|
|
7986
8119
|
}
|
|
7987
8120
|
const sessionId = codexMessageThreadId(message);
|
|
7988
|
-
const
|
|
8121
|
+
const path24 = boundedString(params.path);
|
|
7989
8122
|
return {
|
|
7990
8123
|
kind: "runtime_diagnostic",
|
|
7991
8124
|
severity: "warning",
|
|
@@ -7993,7 +8126,7 @@ function codexNotificationDiagnosticEvent(message) {
|
|
|
7993
8126
|
itemType: message.method,
|
|
7994
8127
|
message: diagnosticMessage,
|
|
7995
8128
|
...details ? { details } : {},
|
|
7996
|
-
...
|
|
8129
|
+
...path24 ? { path: path24 } : {},
|
|
7997
8130
|
...params.range !== void 0 ? { range: params.range } : {},
|
|
7998
8131
|
payloadBytes: payloadBytes(params),
|
|
7999
8132
|
...sessionId ? { sessionId } : {}
|
|
@@ -9643,11 +9776,11 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
|
|
|
9643
9776
|
return parseCursorModelsOutput(String(result.stdout || ""));
|
|
9644
9777
|
}
|
|
9645
9778
|
function buildCursorModelProbeEnv(deps = {}) {
|
|
9646
|
-
return withWindowsUserEnvironment({
|
|
9779
|
+
return scrubDaemonChildEnv(withWindowsUserEnvironment({
|
|
9647
9780
|
...deps.env ?? process.env,
|
|
9648
9781
|
FORCE_COLOR: "0",
|
|
9649
9782
|
NO_COLOR: "1"
|
|
9650
|
-
}, deps);
|
|
9783
|
+
}, deps));
|
|
9651
9784
|
}
|
|
9652
9785
|
function runCursorModelsCommand() {
|
|
9653
9786
|
return spawnSync("cursor-agent", ["models"], {
|
|
@@ -9703,7 +9836,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
|
|
|
9703
9836
|
}
|
|
9704
9837
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
|
|
9705
9838
|
const existsSyncFn = deps.existsSyncFn ?? existsSync5;
|
|
9706
|
-
const env = deps.env ?? process.env;
|
|
9839
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
9707
9840
|
const winPath = path8.win32;
|
|
9708
9841
|
let geminiEntry = null;
|
|
9709
9842
|
try {
|
|
@@ -9840,12 +9973,15 @@ var GeminiDriver = class {
|
|
|
9840
9973
|
// src/drivers/kimi.ts
|
|
9841
9974
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9842
9975
|
import { spawn as spawn7 } from "child_process";
|
|
9843
|
-
import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
9976
|
+
import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
9844
9977
|
import os4 from "os";
|
|
9845
9978
|
import path9 from "path";
|
|
9846
9979
|
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
9847
9980
|
var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
|
|
9848
9981
|
var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
|
|
9982
|
+
var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
|
|
9983
|
+
var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
|
|
9984
|
+
var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
|
|
9849
9985
|
function parseToolArguments(raw) {
|
|
9850
9986
|
if (typeof raw !== "string") return raw;
|
|
9851
9987
|
try {
|
|
@@ -9854,6 +9990,73 @@ function parseToolArguments(raw) {
|
|
|
9854
9990
|
return raw;
|
|
9855
9991
|
}
|
|
9856
9992
|
}
|
|
9993
|
+
function readKimiConfigSource(home = os4.homedir(), env = process.env) {
|
|
9994
|
+
const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
9995
|
+
if (inlineConfig && inlineConfig.trim()) {
|
|
9996
|
+
return {
|
|
9997
|
+
raw: inlineConfig,
|
|
9998
|
+
explicitPath: null,
|
|
9999
|
+
sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
|
|
10000
|
+
};
|
|
10001
|
+
}
|
|
10002
|
+
const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
10003
|
+
const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
|
|
10004
|
+
try {
|
|
10005
|
+
return {
|
|
10006
|
+
raw: readFileSync3(configPath, "utf8"),
|
|
10007
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
10008
|
+
sourcePath: configPath
|
|
10009
|
+
};
|
|
10010
|
+
} catch {
|
|
10011
|
+
return {
|
|
10012
|
+
raw: null,
|
|
10013
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
10014
|
+
sourcePath: configPath
|
|
10015
|
+
};
|
|
10016
|
+
}
|
|
10017
|
+
}
|
|
10018
|
+
function buildKimiSpawnEnv(env = process.env) {
|
|
10019
|
+
const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
|
|
10020
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
10021
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
10022
|
+
return scrubDaemonChildEnv(spawnEnv);
|
|
10023
|
+
}
|
|
10024
|
+
function buildKimiEffectiveEnv(ctx, overrideEnv) {
|
|
10025
|
+
return {
|
|
10026
|
+
...process.env,
|
|
10027
|
+
...ctx.config.envVars || {},
|
|
10028
|
+
...overrideEnv || {}
|
|
10029
|
+
};
|
|
10030
|
+
}
|
|
10031
|
+
function buildKimiLaunchOptions(ctx, opts = {}) {
|
|
10032
|
+
const env = buildKimiEffectiveEnv(ctx, opts.env);
|
|
10033
|
+
const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
|
|
10034
|
+
const args = [];
|
|
10035
|
+
let configFilePath = null;
|
|
10036
|
+
let configContent = null;
|
|
10037
|
+
if (source.explicitPath) {
|
|
10038
|
+
configFilePath = source.explicitPath;
|
|
10039
|
+
} else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
|
|
10040
|
+
configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
|
|
10041
|
+
configContent = source.raw;
|
|
10042
|
+
if (opts.writeGeneratedConfig !== false) {
|
|
10043
|
+
writeFileSync3(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
|
|
10044
|
+
chmodSync(configFilePath, 384);
|
|
10045
|
+
}
|
|
10046
|
+
}
|
|
10047
|
+
if (configFilePath) {
|
|
10048
|
+
args.push("--config-file", configFilePath);
|
|
10049
|
+
}
|
|
10050
|
+
if (ctx.config.model && ctx.config.model !== "default") {
|
|
10051
|
+
args.push("--model", ctx.config.model);
|
|
10052
|
+
}
|
|
10053
|
+
return {
|
|
10054
|
+
args,
|
|
10055
|
+
env: buildKimiSpawnEnv(env),
|
|
10056
|
+
configFilePath,
|
|
10057
|
+
configContent
|
|
10058
|
+
};
|
|
10059
|
+
}
|
|
9857
10060
|
function resolveKimiSpawn(commandArgs, deps = {}) {
|
|
9858
10061
|
return {
|
|
9859
10062
|
command: resolveCommandOnPath("kimi", deps) ?? "kimi",
|
|
@@ -9877,7 +10080,25 @@ var KimiDriver = class {
|
|
|
9877
10080
|
};
|
|
9878
10081
|
model = {
|
|
9879
10082
|
detectedModelsVerifiedAs: "launchable",
|
|
9880
|
-
toLaunchSpec: (modelId) =>
|
|
10083
|
+
toLaunchSpec: (modelId, ctx, opts) => {
|
|
10084
|
+
if (!ctx) return { args: ["--model", modelId] };
|
|
10085
|
+
const launchCtx = {
|
|
10086
|
+
...ctx,
|
|
10087
|
+
config: {
|
|
10088
|
+
...ctx.config,
|
|
10089
|
+
model: modelId
|
|
10090
|
+
}
|
|
10091
|
+
};
|
|
10092
|
+
const launch = buildKimiLaunchOptions(launchCtx, {
|
|
10093
|
+
home: opts?.home,
|
|
10094
|
+
writeGeneratedConfig: false
|
|
10095
|
+
});
|
|
10096
|
+
return {
|
|
10097
|
+
args: launch.args,
|
|
10098
|
+
env: launch.env,
|
|
10099
|
+
configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
|
|
10100
|
+
};
|
|
10101
|
+
}
|
|
9881
10102
|
};
|
|
9882
10103
|
supportsStdinNotification = true;
|
|
9883
10104
|
busyDeliveryMode = "direct";
|
|
@@ -9901,21 +10122,23 @@ var KimiDriver = class {
|
|
|
9901
10122
|
` system_prompt_path: ./${KIMI_SYSTEM_PROMPT_FILE}`,
|
|
9902
10123
|
""
|
|
9903
10124
|
].join("\n"), "utf8");
|
|
10125
|
+
const launch = buildKimiLaunchOptions(ctx);
|
|
9904
10126
|
const args = [
|
|
9905
10127
|
"--wire",
|
|
9906
10128
|
"--yolo",
|
|
9907
10129
|
"--agent-file",
|
|
9908
10130
|
agentFilePath,
|
|
9909
10131
|
"--session",
|
|
9910
|
-
this.sessionId
|
|
10132
|
+
this.sessionId,
|
|
10133
|
+
...launch.args
|
|
9911
10134
|
];
|
|
9912
10135
|
const launchRuntimeFields = runtimeConfigToLaunchFields(hydrateRuntimeConfig(ctx.config));
|
|
9913
10136
|
if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
|
|
9914
10137
|
args.push("--model", launchRuntimeFields.model);
|
|
9915
10138
|
}
|
|
9916
10139
|
const spawnEnv = (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
|
|
9917
|
-
const
|
|
9918
|
-
const proc = spawn7(
|
|
10140
|
+
const spawnTarget = resolveKimiSpawn(args);
|
|
10141
|
+
const proc = spawn7(spawnTarget.command, spawnTarget.args, {
|
|
9919
10142
|
cwd: ctx.workingDirectory,
|
|
9920
10143
|
stdio: ["pipe", "pipe", "pipe"],
|
|
9921
10144
|
env: spawnEnv,
|
|
@@ -9923,7 +10146,7 @@ var KimiDriver = class {
|
|
|
9923
10146
|
// and has an 8191-character command-line limit. Kimi's official
|
|
9924
10147
|
// installer/uv entrypoint is an executable, so launch it directly and
|
|
9925
10148
|
// keep prompts on stdin / files instead of routing through cmd.exe.
|
|
9926
|
-
shell:
|
|
10149
|
+
shell: spawnTarget.shell
|
|
9927
10150
|
});
|
|
9928
10151
|
proc.stdin?.write(JSON.stringify({
|
|
9929
10152
|
jsonrpc: "2.0",
|
|
@@ -10036,14 +10259,9 @@ var KimiDriver = class {
|
|
|
10036
10259
|
return detectKimiModels();
|
|
10037
10260
|
}
|
|
10038
10261
|
};
|
|
10039
|
-
function detectKimiModels(home = os4.homedir()) {
|
|
10040
|
-
const
|
|
10041
|
-
|
|
10042
|
-
try {
|
|
10043
|
-
raw = readFileSync3(configPath, "utf8");
|
|
10044
|
-
} catch {
|
|
10045
|
-
return null;
|
|
10046
|
-
}
|
|
10262
|
+
function detectKimiModels(home = os4.homedir(), opts = {}) {
|
|
10263
|
+
const raw = readKimiConfigSource(home, opts.env).raw;
|
|
10264
|
+
if (raw === null) return null;
|
|
10047
10265
|
const models = [];
|
|
10048
10266
|
const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
|
|
10049
10267
|
const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
|
|
@@ -10303,6 +10521,12 @@ var KimiSdkRuntimeSession = class {
|
|
|
10303
10521
|
get closed() {
|
|
10304
10522
|
return this.didClose;
|
|
10305
10523
|
}
|
|
10524
|
+
// In-process SDK session: there is no OS pid to probe with signal-0, so
|
|
10525
|
+
// liveness is unknowable via the pid-probe boundary. Return undefined, which
|
|
10526
|
+
// callers treat the same as the historic "no pid" probe result (RS-011).
|
|
10527
|
+
isAlive() {
|
|
10528
|
+
return void 0;
|
|
10529
|
+
}
|
|
10306
10530
|
on(event, cb) {
|
|
10307
10531
|
this.events.on(event, cb);
|
|
10308
10532
|
}
|
|
@@ -10722,7 +10946,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
|
|
|
10722
10946
|
const platform = deps.platform ?? process.platform;
|
|
10723
10947
|
const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
|
|
10724
10948
|
const result = spawnSyncFn("opencode", ["models"], {
|
|
10725
|
-
env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
10949
|
+
env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
|
|
10726
10950
|
encoding: "utf8",
|
|
10727
10951
|
timeout: 5e3,
|
|
10728
10952
|
shell: platform === "win32"
|
|
@@ -11018,7 +11242,9 @@ function createPiSdkEventMappingState(sessionId = null) {
|
|
|
11018
11242
|
return {
|
|
11019
11243
|
sessionId,
|
|
11020
11244
|
sessionAnnounced: false,
|
|
11021
|
-
sawTextDelta: false
|
|
11245
|
+
sawTextDelta: false,
|
|
11246
|
+
thinkingBuffers: /* @__PURE__ */ new Map(),
|
|
11247
|
+
announcedThinkingIndexes: /* @__PURE__ */ new Set()
|
|
11022
11248
|
};
|
|
11023
11249
|
}
|
|
11024
11250
|
function buildPiSessionDir(workingDirectory) {
|
|
@@ -11233,10 +11459,40 @@ function pushSessionInitIfNeeded2(state, events) {
|
|
|
11233
11459
|
state.sessionAnnounced = true;
|
|
11234
11460
|
}
|
|
11235
11461
|
}
|
|
11462
|
+
function piAssistantContentIndex(assistantEvent) {
|
|
11463
|
+
return "contentIndex" in assistantEvent && typeof assistantEvent.contentIndex === "number" ? assistantEvent.contentIndex : 0;
|
|
11464
|
+
}
|
|
11465
|
+
function announcePiThinkingIfNeeded(index, state) {
|
|
11466
|
+
if (state.announcedThinkingIndexes.has(index)) return [];
|
|
11467
|
+
state.announcedThinkingIndexes.add(index);
|
|
11468
|
+
return [{ kind: "thinking", text: "" }];
|
|
11469
|
+
}
|
|
11470
|
+
function resetPiThinking(index, state) {
|
|
11471
|
+
state.thinkingBuffers.delete(index);
|
|
11472
|
+
state.announcedThinkingIndexes.delete(index);
|
|
11473
|
+
}
|
|
11236
11474
|
function mapPiAssistantMessageEvent(assistantEvent, state) {
|
|
11237
11475
|
switch (assistantEvent.type) {
|
|
11238
|
-
case "
|
|
11239
|
-
|
|
11476
|
+
case "thinking_start": {
|
|
11477
|
+
const index = piAssistantContentIndex(assistantEvent);
|
|
11478
|
+
resetPiThinking(index, state);
|
|
11479
|
+
return announcePiThinkingIfNeeded(index, state);
|
|
11480
|
+
}
|
|
11481
|
+
case "thinking_delta": {
|
|
11482
|
+
const index = piAssistantContentIndex(assistantEvent);
|
|
11483
|
+
const events = announcePiThinkingIfNeeded(index, state);
|
|
11484
|
+
if (typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0) {
|
|
11485
|
+
state.thinkingBuffers.set(index, `${state.thinkingBuffers.get(index) ?? ""}${assistantEvent.delta}`);
|
|
11486
|
+
}
|
|
11487
|
+
return events;
|
|
11488
|
+
}
|
|
11489
|
+
case "thinking_end": {
|
|
11490
|
+
const index = piAssistantContentIndex(assistantEvent);
|
|
11491
|
+
const buffered = state.thinkingBuffers.get(index) ?? "";
|
|
11492
|
+
const text = typeof assistantEvent.content === "string" && assistantEvent.content.length > 0 ? assistantEvent.content : buffered;
|
|
11493
|
+
resetPiThinking(index, state);
|
|
11494
|
+
return text ? [{ kind: "thinking", text }] : [];
|
|
11495
|
+
}
|
|
11240
11496
|
case "text_delta":
|
|
11241
11497
|
if (typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0) {
|
|
11242
11498
|
state.sawTextDelta = true;
|
|
@@ -11247,8 +11503,6 @@ function mapPiAssistantMessageEvent(assistantEvent, state) {
|
|
|
11247
11503
|
return !state.sawTextDelta && typeof assistantEvent.content === "string" && assistantEvent.content.length > 0 ? [{ kind: "text", text: assistantEvent.content }] : [];
|
|
11248
11504
|
case "error":
|
|
11249
11505
|
return [{ kind: "error", message: piErrorMessage(assistantEvent.error.errorMessage || assistantEvent.error) }];
|
|
11250
|
-
case "thinking_start":
|
|
11251
|
-
case "thinking_end":
|
|
11252
11506
|
case "text_start":
|
|
11253
11507
|
case "toolcall_start":
|
|
11254
11508
|
case "toolcall_delta":
|
|
@@ -11292,6 +11546,8 @@ function mapPiSdkEventToParsedEvents(event, state) {
|
|
|
11292
11546
|
case "message_start":
|
|
11293
11547
|
if (event.message.role === "assistant") {
|
|
11294
11548
|
state.sawTextDelta = false;
|
|
11549
|
+
state.thinkingBuffers.clear();
|
|
11550
|
+
state.announcedThinkingIndexes.clear();
|
|
11295
11551
|
}
|
|
11296
11552
|
return events;
|
|
11297
11553
|
case "message_update":
|
|
@@ -11316,10 +11572,8 @@ function mapPiSdkEventToParsedEvents(event, state) {
|
|
|
11316
11572
|
case "agent_end":
|
|
11317
11573
|
events.push({ kind: "turn_end", sessionId: state.sessionId || void 0 });
|
|
11318
11574
|
return events;
|
|
11319
|
-
default:
|
|
11320
|
-
|
|
11321
|
-
return _exhaustive;
|
|
11322
|
-
}
|
|
11575
|
+
default:
|
|
11576
|
+
return events;
|
|
11323
11577
|
}
|
|
11324
11578
|
}
|
|
11325
11579
|
var PI_RUNTIME_SESSION_DESCRIPTOR = {
|
|
@@ -11504,15 +11758,21 @@ async function createPiAgentSessionForContext(ctx, sessionId, opts = {}) {
|
|
|
11504
11758
|
}
|
|
11505
11759
|
}
|
|
11506
11760
|
var PiSdkRuntimeSession = class {
|
|
11507
|
-
constructor(ctx, setCurrentSessionId, sessionFactory = createPiAgentSessionForContext) {
|
|
11761
|
+
constructor(ctx, setCurrentSessionId, sessionFactory = createPiAgentSessionForContext, opts = {}) {
|
|
11508
11762
|
this.ctx = ctx;
|
|
11509
11763
|
this.setCurrentSessionId = setCurrentSessionId;
|
|
11510
11764
|
this.sessionFactory = sessionFactory;
|
|
11511
11765
|
this.mappingState = createPiSdkEventMappingState(ctx.config.sessionId || null);
|
|
11766
|
+
const runtimeConfig = hydrateRuntimeConfig(ctx.config);
|
|
11767
|
+
const launchRuntimeFields = runtimeConfigToLaunchFields(runtimeConfig);
|
|
11768
|
+
this.sdkCallEnvPatch = buildPiSessionCreateEnvPatch(runtimeConfig, launchRuntimeFields.envVars);
|
|
11769
|
+
this.sdkCallEnvRemoveFirst = opts.isolateHostProviderEnv ? BUILTIN_BLOCKED_HOST_PROVIDER_ENV_KEYS : void 0;
|
|
11512
11770
|
}
|
|
11513
11771
|
descriptor = PI_RUNTIME_SESSION_DESCRIPTOR;
|
|
11514
11772
|
events = new EventEmitter2();
|
|
11515
11773
|
mappingState;
|
|
11774
|
+
sdkCallEnvPatch;
|
|
11775
|
+
sdkCallEnvRemoveFirst;
|
|
11516
11776
|
session = null;
|
|
11517
11777
|
unsubscribe = null;
|
|
11518
11778
|
started = false;
|
|
@@ -11534,6 +11794,12 @@ var PiSdkRuntimeSession = class {
|
|
|
11534
11794
|
get closed() {
|
|
11535
11795
|
return this.didClose;
|
|
11536
11796
|
}
|
|
11797
|
+
// In-process SDK session: there is no OS pid to probe with signal-0, so
|
|
11798
|
+
// liveness is unknowable via the pid-probe boundary. Return undefined, which
|
|
11799
|
+
// callers treat the same as the historic "no pid" probe result (RS-011).
|
|
11800
|
+
isAlive() {
|
|
11801
|
+
return void 0;
|
|
11802
|
+
}
|
|
11537
11803
|
on(event, cb) {
|
|
11538
11804
|
this.events.on(event, cb);
|
|
11539
11805
|
}
|
|
@@ -11642,7 +11908,7 @@ var PiSdkRuntimeSession = class {
|
|
|
11642
11908
|
setImmediate(() => {
|
|
11643
11909
|
if (this.didClose) return;
|
|
11644
11910
|
try {
|
|
11645
|
-
void invoke
|
|
11911
|
+
void withProcessEnvPatch(this.sdkCallEnvPatch, invoke, { removeFirst: this.sdkCallEnvRemoveFirst }).catch((error) => {
|
|
11646
11912
|
if (this.didClose) return;
|
|
11647
11913
|
this.events.emit("runtime_event", {
|
|
11648
11914
|
kind: "error",
|
|
@@ -11775,7 +12041,7 @@ var BuiltInDriver = class extends PiDriver {
|
|
|
11775
12041
|
exposeLaunchTraceEvidence: true,
|
|
11776
12042
|
exposeLaunchEnvToTools: false,
|
|
11777
12043
|
isolateHostProviderEnv: true
|
|
11778
|
-
}));
|
|
12044
|
+
}), { isolateHostProviderEnv: true });
|
|
11779
12045
|
}
|
|
11780
12046
|
buildSystemPrompt(config, _agentId) {
|
|
11781
12047
|
return buildCliTransportSystemPrompt(config, {
|
|
@@ -11852,6 +12118,25 @@ var ChildProcessRuntimeSession = class {
|
|
|
11852
12118
|
get closed() {
|
|
11853
12119
|
return this.process ? this.process.exitCode != null || this.process.signalCode != null : false;
|
|
11854
12120
|
}
|
|
12121
|
+
/**
|
|
12122
|
+
* Read-only OS liveness introspection (signal-0), NOT control IO — exposed so
|
|
12123
|
+
* managers query liveness through the session boundary (RS-011) instead of
|
|
12124
|
+
* poking the raw pid. Preserves the historic `probeRuntimeProcessLiveness`
|
|
12125
|
+
* semantics exactly: undefined when there is no pid, true on success or EPERM,
|
|
12126
|
+
* false otherwise. Deliberately does NOT short-circuit on `this.closed` — the
|
|
12127
|
+
* original probe checked only pid + signal-0.
|
|
12128
|
+
*/
|
|
12129
|
+
isAlive() {
|
|
12130
|
+
const pid = this.process?.pid;
|
|
12131
|
+
if (typeof pid !== "number") return void 0;
|
|
12132
|
+
try {
|
|
12133
|
+
process.kill(pid, 0);
|
|
12134
|
+
return true;
|
|
12135
|
+
} catch (err) {
|
|
12136
|
+
const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
|
|
12137
|
+
return code === "EPERM" ? true : false;
|
|
12138
|
+
}
|
|
12139
|
+
}
|
|
11855
12140
|
on(event, cb) {
|
|
11856
12141
|
this.events.on(event, cb);
|
|
11857
12142
|
}
|
|
@@ -11965,6 +12250,61 @@ function getDriver(runtimeId) {
|
|
|
11965
12250
|
return driver;
|
|
11966
12251
|
}
|
|
11967
12252
|
|
|
12253
|
+
// src/daemonOrphanReaper.ts
|
|
12254
|
+
async function reapOrphanProcesses(pids, logger2, recordTrace) {
|
|
12255
|
+
const survivors = pids.filter((pid) => {
|
|
12256
|
+
try {
|
|
12257
|
+
process.kill(pid, 0);
|
|
12258
|
+
return true;
|
|
12259
|
+
} catch {
|
|
12260
|
+
return false;
|
|
12261
|
+
}
|
|
12262
|
+
});
|
|
12263
|
+
if (survivors.length === 0) return false;
|
|
12264
|
+
for (const pid of survivors) {
|
|
12265
|
+
logger2.warn(`[Daemon] Agent subprocess ${pid} survived stopAll; sending SIGKILL`);
|
|
12266
|
+
try {
|
|
12267
|
+
process.kill(pid, "SIGKILL");
|
|
12268
|
+
} catch {
|
|
12269
|
+
}
|
|
12270
|
+
}
|
|
12271
|
+
recordTrace("daemon.agent.stop_all.survivor_reaped", {
|
|
12272
|
+
survivor_count: survivors.length,
|
|
12273
|
+
survivor_pids: survivors.join(","),
|
|
12274
|
+
reason: "shutdown_survivor",
|
|
12275
|
+
signal: "SIGKILL"
|
|
12276
|
+
});
|
|
12277
|
+
const deadline = Date.now() + 2e3;
|
|
12278
|
+
while (Date.now() < deadline) {
|
|
12279
|
+
const alive = survivors.filter((pid) => {
|
|
12280
|
+
try {
|
|
12281
|
+
process.kill(pid, 0);
|
|
12282
|
+
return true;
|
|
12283
|
+
} catch {
|
|
12284
|
+
return false;
|
|
12285
|
+
}
|
|
12286
|
+
});
|
|
12287
|
+
if (alive.length === 0) break;
|
|
12288
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
12289
|
+
}
|
|
12290
|
+
const stillAlive = survivors.filter((pid) => {
|
|
12291
|
+
try {
|
|
12292
|
+
process.kill(pid, 0);
|
|
12293
|
+
return true;
|
|
12294
|
+
} catch {
|
|
12295
|
+
return false;
|
|
12296
|
+
}
|
|
12297
|
+
});
|
|
12298
|
+
const outcome = stillAlive.length > 0 ? "survivors_still_alive" : "survivors_killed";
|
|
12299
|
+
recordTrace("daemon.agent.stop_all.completed", {
|
|
12300
|
+
survivor_count: survivors.length,
|
|
12301
|
+
reaped_count: survivors.length - stillAlive.length,
|
|
12302
|
+
still_alive_count: stillAlive.length,
|
|
12303
|
+
outcome
|
|
12304
|
+
}, stillAlive.length > 0 ? "error" : "ok");
|
|
12305
|
+
return true;
|
|
12306
|
+
}
|
|
12307
|
+
|
|
11968
12308
|
// src/workspaces.ts
|
|
11969
12309
|
import { readdir, rm, stat } from "fs/promises";
|
|
11970
12310
|
import path13 from "path";
|
|
@@ -13391,6 +13731,88 @@ var RuntimeNotificationState = class {
|
|
|
13391
13731
|
}
|
|
13392
13732
|
};
|
|
13393
13733
|
|
|
13734
|
+
// src/proxyFailureTrace.ts
|
|
13735
|
+
function failureReason(input) {
|
|
13736
|
+
if (input.lifecycleInvalidContext) return "local_daemon_state_invalid";
|
|
13737
|
+
const text = `${input.errorCause ?? ""} ${input.errorMessage}`.toLowerCase();
|
|
13738
|
+
if (/enotfound|eai_again|\bdns\b/.test(text)) return "dns";
|
|
13739
|
+
if (/econnrefused|econnreset|epipe|und_err_socket|\bsocket\b|other side closed|terminated/.test(text)) return "tcp";
|
|
13740
|
+
if (/certificate|\btls\b/.test(text)) return "tls";
|
|
13741
|
+
if (/und_err_headers_timeout|und_err_body_timeout|\btimeout\b/.test(text)) return "read_timeout";
|
|
13742
|
+
if (/\bproxy\b/.test(text)) return "proxy_connect";
|
|
13743
|
+
if (/\bfly\b/.test(text)) return "fly_edge";
|
|
13744
|
+
return "unknown";
|
|
13745
|
+
}
|
|
13746
|
+
function boundedErrorClass(value) {
|
|
13747
|
+
if (value === "TypeError") return "TypeError";
|
|
13748
|
+
if (value === "AbortError") return "AbortError";
|
|
13749
|
+
if (value === "Error") return "Error";
|
|
13750
|
+
return "OtherError";
|
|
13751
|
+
}
|
|
13752
|
+
function boundedMethod(value) {
|
|
13753
|
+
const method = value.toUpperCase();
|
|
13754
|
+
return ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"].includes(method) ? method : "OTHER";
|
|
13755
|
+
}
|
|
13756
|
+
function cannedExcerpt(reason) {
|
|
13757
|
+
switch (reason) {
|
|
13758
|
+
case "local_daemon_state_invalid":
|
|
13759
|
+
return "Local daemon lifecycle state rejected the proxy request";
|
|
13760
|
+
case "dns":
|
|
13761
|
+
return "Upstream DNS resolution failed";
|
|
13762
|
+
case "tcp":
|
|
13763
|
+
return "Upstream TCP connection failed";
|
|
13764
|
+
case "tls":
|
|
13765
|
+
return "Upstream TLS negotiation failed";
|
|
13766
|
+
case "read_timeout":
|
|
13767
|
+
return "Upstream response timed out";
|
|
13768
|
+
case "proxy_connect":
|
|
13769
|
+
return "Upstream proxy connection failed";
|
|
13770
|
+
case "fly_edge":
|
|
13771
|
+
return "Upstream edge transport failed";
|
|
13772
|
+
case "unknown":
|
|
13773
|
+
return "Proxy request failed with an unclassified cause";
|
|
13774
|
+
}
|
|
13775
|
+
}
|
|
13776
|
+
function daemonProxyFailureTraceAttrs(input) {
|
|
13777
|
+
const reason = failureReason(input);
|
|
13778
|
+
return {
|
|
13779
|
+
route_family: routeFamilyForPath(input.pathname),
|
|
13780
|
+
method: boundedMethod(input.method),
|
|
13781
|
+
outcome: "error",
|
|
13782
|
+
reason,
|
|
13783
|
+
error_class: boundedErrorClass(input.errorName),
|
|
13784
|
+
error_excerpt: cannedExcerpt(reason),
|
|
13785
|
+
...typeof input.responseStatusCode === "number" ? { response_status_code: input.responseStatusCode } : {},
|
|
13786
|
+
response_code_present: Boolean(input.responseCode)
|
|
13787
|
+
};
|
|
13788
|
+
}
|
|
13789
|
+
function daemonTransportErrorExcerpt(input) {
|
|
13790
|
+
if (input.normalizedCode === "local_daemon_state_invalid") {
|
|
13791
|
+
return "Local daemon lifecycle state rejected the proxy request";
|
|
13792
|
+
}
|
|
13793
|
+
if (input.normalizedCode === "server_5xx") return "Upstream server returned a 5xx response";
|
|
13794
|
+
switch (input.upstreamLayer) {
|
|
13795
|
+
case "dns":
|
|
13796
|
+
return "Upstream DNS resolution failed";
|
|
13797
|
+
case "tcp":
|
|
13798
|
+
return "Upstream TCP connection failed";
|
|
13799
|
+
case "tls":
|
|
13800
|
+
return "Upstream TLS negotiation failed";
|
|
13801
|
+
case "read_timeout":
|
|
13802
|
+
return "Upstream response timed out";
|
|
13803
|
+
case "proxy_connect":
|
|
13804
|
+
return "Upstream proxy connection failed";
|
|
13805
|
+
case "fly_edge":
|
|
13806
|
+
return "Upstream edge transport failed";
|
|
13807
|
+
case "body_decode_failure":
|
|
13808
|
+
return "Upstream response body could not be decoded";
|
|
13809
|
+
case "http_status":
|
|
13810
|
+
return "Upstream HTTP request failed";
|
|
13811
|
+
case "unknown":
|
|
13812
|
+
return "Proxy transport failed with an unclassified cause";
|
|
13813
|
+
}
|
|
13814
|
+
}
|
|
13815
|
+
|
|
13394
13816
|
// src/agentProcessManager.ts
|
|
13395
13817
|
var DEFAULT_MAX_CONCURRENT_AGENT_STARTS = 5;
|
|
13396
13818
|
var DEFAULT_AGENT_START_INTERVAL_MS = 500;
|
|
@@ -14768,8 +15190,9 @@ function classifyTerminalFailure(ap) {
|
|
|
14768
15190
|
if (lower.includes("usage limit") || lower.includes("quota exceeded") || lower.includes("quota limit") || lower.includes("budget limit exceeded") || lower.includes("usage not included in your plan") || lower.includes("modelnotfounderror") || lower.includes("requested entity was not found") || lower.includes("model deprecated") || lower.includes("model not found") || lower.includes("model is not supported") || lower.includes("unsupported model") || /\bmodel\b.*\bnot supported\b/i.test(text) || diagnostics.spanAttrs.runtime_error_action_required === true || isProviderStreamFailureText(text) || isRuntimeStartTimeoutText(text)) {
|
|
14769
15191
|
const actionRequired = diagnostics.spanAttrs.runtime_error_action_required === true;
|
|
14770
15192
|
return {
|
|
14771
|
-
detail: actionRequired ?
|
|
14772
|
-
actionRequired
|
|
15193
|
+
detail: actionRequired ? formatRuntimeActionRequiredMessage(ap) : text,
|
|
15194
|
+
actionRequired,
|
|
15195
|
+
...actionRequired ? { entries: buildRuntimeActionRequiredEntries(ap, text, diagnostics) } : {}
|
|
14773
15196
|
};
|
|
14774
15197
|
}
|
|
14775
15198
|
}
|
|
@@ -14785,6 +15208,52 @@ function classifyStickyTerminalFailure(ap) {
|
|
|
14785
15208
|
if (isRuntimeStartTimeoutText(terminalFailure.detail)) return null;
|
|
14786
15209
|
return null;
|
|
14787
15210
|
}
|
|
15211
|
+
function formatRuntimeActionRequiredMessage(ap) {
|
|
15212
|
+
if (ap.driver.id === "claude" && isClaudeCustomProviderConfig(ap.config)) {
|
|
15213
|
+
return "Claude Code custom provider authentication failed. Check this agent's custom Claude provider API key/API URL, then retry starting this agent.";
|
|
15214
|
+
}
|
|
15215
|
+
return formatRuntimeLoginRequiredMessage(ap.driver.id);
|
|
15216
|
+
}
|
|
15217
|
+
function buildRuntimeActionRequiredEntries(ap, text, diagnostics) {
|
|
15218
|
+
const excerpt = String(diagnostics.eventAttrs.runtime_error_message_excerpt ?? "").trim();
|
|
15219
|
+
const subtype = classifyRuntimeActionRequiredSubtype(ap, text);
|
|
15220
|
+
const detail = formatRuntimeActionRequiredMessage(ap);
|
|
15221
|
+
const entries = [
|
|
15222
|
+
{ kind: "text", text: `Error: ${detail}` },
|
|
15223
|
+
{ kind: "text", text: `Runtime auth diagnostic: ${subtype}` }
|
|
15224
|
+
];
|
|
15225
|
+
if (excerpt) {
|
|
15226
|
+
entries.push({ kind: "text", text: `Raw error excerpt (redacted): ${excerpt}` });
|
|
15227
|
+
}
|
|
15228
|
+
if (ap.driver.id === "claude") {
|
|
15229
|
+
entries.push({
|
|
15230
|
+
kind: "text",
|
|
15231
|
+
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."
|
|
15232
|
+
});
|
|
15233
|
+
}
|
|
15234
|
+
return entries;
|
|
15235
|
+
}
|
|
15236
|
+
function classifyRuntimeActionRequiredSubtype(ap, text) {
|
|
15237
|
+
const lower = text.toLowerCase();
|
|
15238
|
+
const customClaudeProvider = ap.driver.id === "claude" && isClaudeCustomProviderConfig(ap.config);
|
|
15239
|
+
if (customClaudeProvider) {
|
|
15240
|
+
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)) {
|
|
15241
|
+
return "custom_provider_missing_key";
|
|
15242
|
+
}
|
|
15243
|
+
if (/\binvalid\b.*\b(?:api\s*)?key\b|\bunauthorized\b|\b401\b|\b403\b|auth(?:entication)? failed/i.test(text)) {
|
|
15244
|
+
return "custom_provider_invalid_key";
|
|
15245
|
+
}
|
|
15246
|
+
return "custom_provider_auth_error";
|
|
15247
|
+
}
|
|
15248
|
+
if (ap.driver.id === "claude") {
|
|
15249
|
+
if (lower.includes("not logged in") || lower.includes("not signed in") || lower.includes("please log in") || lower.includes("login required")) {
|
|
15250
|
+
return "host_claude_login_required";
|
|
15251
|
+
}
|
|
15252
|
+
return "runtime_auth_error";
|
|
15253
|
+
}
|
|
15254
|
+
if (ap.driver.id === "builtin") return "builtin_provider_auth_error";
|
|
15255
|
+
return "runtime_auth_error";
|
|
15256
|
+
}
|
|
14788
15257
|
function isAuthClassTerminalLine(text) {
|
|
14789
15258
|
return buildRuntimeErrorDiagnosticEnvelope(text).spanAttrs.runtime_error_action_required === true;
|
|
14790
15259
|
}
|
|
@@ -15809,17 +16278,9 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
15809
16278
|
};
|
|
15810
16279
|
}
|
|
15811
16280
|
recordAgentProxyFailure(agentId, input) {
|
|
15812
|
-
this.recordDaemonTrace("daemon.
|
|
16281
|
+
this.recordDaemonTrace("daemon.proxy.failed", {
|
|
15813
16282
|
agentId,
|
|
15814
|
-
|
|
15815
|
-
path: input.pathname,
|
|
15816
|
-
query_keys: input.queryKeys,
|
|
15817
|
-
error_name: input.errorName,
|
|
15818
|
-
error_message: input.errorMessage,
|
|
15819
|
-
response_status_code: input.responseStatusCode,
|
|
15820
|
-
response_code: input.responseCode,
|
|
15821
|
-
lifecycle_invalid_agent_id: input.lifecycleInvalidAgentId,
|
|
15822
|
-
lifecycle_invalid_context: input.lifecycleInvalidContext
|
|
16283
|
+
...daemonProxyFailureTraceAttrs(input)
|
|
15823
16284
|
}, "error");
|
|
15824
16285
|
}
|
|
15825
16286
|
recordAgentProxyTransportNormalizedError(agentId, input) {
|
|
@@ -15831,7 +16292,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
15831
16292
|
response_started: input.responseStarted,
|
|
15832
16293
|
upstream_layer: input.upstreamLayer,
|
|
15833
16294
|
...typeof input.upstreamStatus === "number" ? { upstream_status: input.upstreamStatus } : {},
|
|
15834
|
-
|
|
16295
|
+
error_excerpt: daemonTransportErrorExcerpt(input),
|
|
15835
16296
|
launchId: input.launchId,
|
|
15836
16297
|
target_host_class: input.targetHostClass,
|
|
15837
16298
|
downstream_caller: input.downstreamCaller,
|
|
@@ -16986,7 +17447,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
16986
17447
|
agentId,
|
|
16987
17448
|
"error",
|
|
16988
17449
|
terminalFailureDetail.detail,
|
|
16989
|
-
[{ kind: "text", text: `Error: ${terminalFailureDetail.detail}` }],
|
|
17450
|
+
terminalFailureDetail.entries ?? [{ kind: "text", text: `Error: ${terminalFailureDetail.detail}` }],
|
|
16990
17451
|
ap.launchId,
|
|
16991
17452
|
"runtime_error"
|
|
16992
17453
|
);
|
|
@@ -17017,9 +17478,10 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
17017
17478
|
agentProcess.decisionErrorWindow.recordRuntimeError(startResult.error);
|
|
17018
17479
|
}
|
|
17019
17480
|
if (startResult.error && diagnostics?.spanAttrs.runtime_error_action_required === true) {
|
|
17020
|
-
const
|
|
17481
|
+
const terminalFailure = classifyTerminalFailure(agentProcess);
|
|
17482
|
+
const visibleErrorMessage = terminalFailure?.detail ?? formatRuntimeActionRequiredMessage(agentProcess);
|
|
17021
17483
|
this.broadcastActivity(agentId, "error", visibleErrorMessage, [
|
|
17022
|
-
{ kind: "text", text: `Error: ${visibleErrorMessage}` }
|
|
17484
|
+
...terminalFailure?.entries ?? [{ kind: "text", text: `Error: ${visibleErrorMessage}` }]
|
|
17023
17485
|
], agentProcess.launchId, "runtime_error");
|
|
17024
17486
|
}
|
|
17025
17487
|
throw new Error(`Runtime session failed to start: ${startResult.reason}${startResult.error ? ` (${startResult.error})` : ""}`);
|
|
@@ -17990,57 +18452,12 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
17990
18452
|
pids: pids.join(",")
|
|
17991
18453
|
});
|
|
17992
18454
|
await Promise.all(ids.map((id) => this.stopAgent(id, { wait: true, silent: true })));
|
|
17993
|
-
const
|
|
17994
|
-
|
|
17995
|
-
|
|
17996
|
-
|
|
17997
|
-
|
|
17998
|
-
|
|
17999
|
-
}
|
|
18000
|
-
});
|
|
18001
|
-
if (survivors.length > 0) {
|
|
18002
|
-
for (const pid of survivors) {
|
|
18003
|
-
logger.warn(`[Daemon] Agent subprocess ${pid} survived stopAll; sending SIGKILL`);
|
|
18004
|
-
try {
|
|
18005
|
-
process.kill(pid, "SIGKILL");
|
|
18006
|
-
} catch {
|
|
18007
|
-
}
|
|
18008
|
-
}
|
|
18009
|
-
this.recordDaemonTrace("daemon.agent.stop_all.survivor_reaped", {
|
|
18010
|
-
survivor_count: survivors.length,
|
|
18011
|
-
survivor_pids: survivors.join(","),
|
|
18012
|
-
reason: "shutdown_survivor",
|
|
18013
|
-
signal: "SIGKILL"
|
|
18014
|
-
});
|
|
18015
|
-
const deadline = Date.now() + 2e3;
|
|
18016
|
-
while (Date.now() < deadline) {
|
|
18017
|
-
const alive = survivors.filter((pid) => {
|
|
18018
|
-
try {
|
|
18019
|
-
process.kill(pid, 0);
|
|
18020
|
-
return true;
|
|
18021
|
-
} catch {
|
|
18022
|
-
return false;
|
|
18023
|
-
}
|
|
18024
|
-
});
|
|
18025
|
-
if (alive.length === 0) break;
|
|
18026
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
18027
|
-
}
|
|
18028
|
-
const stillAlive = survivors.filter((pid) => {
|
|
18029
|
-
try {
|
|
18030
|
-
process.kill(pid, 0);
|
|
18031
|
-
return true;
|
|
18032
|
-
} catch {
|
|
18033
|
-
return false;
|
|
18034
|
-
}
|
|
18035
|
-
});
|
|
18036
|
-
const outcome = stillAlive.length > 0 ? "survivors_still_alive" : "survivors_killed";
|
|
18037
|
-
this.recordDaemonTrace("daemon.agent.stop_all.completed", {
|
|
18038
|
-
survivor_count: survivors.length,
|
|
18039
|
-
reaped_count: survivors.length - stillAlive.length,
|
|
18040
|
-
still_alive_count: stillAlive.length,
|
|
18041
|
-
outcome
|
|
18042
|
-
}, stillAlive.length > 0 ? "error" : "ok");
|
|
18043
|
-
} else {
|
|
18455
|
+
const reapedSurvivors = await reapOrphanProcesses(
|
|
18456
|
+
pids,
|
|
18457
|
+
logger,
|
|
18458
|
+
(name, attrs, status) => this.recordDaemonTrace(name, attrs, status)
|
|
18459
|
+
);
|
|
18460
|
+
if (!reapedSurvivors) {
|
|
18044
18461
|
this.recordDaemonTrace("daemon.agent.stop_all.completed", {
|
|
18045
18462
|
agent_count: ids.length,
|
|
18046
18463
|
survivor_count: 0,
|
|
@@ -18782,7 +19199,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
18782
19199
|
const ap = this.agents.get(agentId);
|
|
18783
19200
|
const entries = [...extraTrajectory];
|
|
18784
19201
|
const hasToolStart = entries.some((e) => e.kind === "tool_start");
|
|
18785
|
-
|
|
19202
|
+
const isThinkingProgressFrame = activityKind === "thinking" && detail === "" && detailKind === "none" && entries.every((e) => e.kind === "thinking" || e.kind === "text");
|
|
19203
|
+
if (!hasToolStart && !isThinkingProgressFrame) {
|
|
18786
19204
|
entries.push({
|
|
18787
19205
|
kind: "status",
|
|
18788
19206
|
activity: activityKind,
|
|
@@ -19794,15 +20212,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
19794
20212
|
return true;
|
|
19795
20213
|
}
|
|
19796
20214
|
probeRuntimeProcessLiveness(ap) {
|
|
19797
|
-
|
|
19798
|
-
if (typeof pid !== "number") return void 0;
|
|
19799
|
-
try {
|
|
19800
|
-
process.kill(pid, 0);
|
|
19801
|
-
return true;
|
|
19802
|
-
} catch (err) {
|
|
19803
|
-
const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
|
|
19804
|
-
return code === "EPERM" ? true : false;
|
|
19805
|
-
}
|
|
20215
|
+
return ap.runtime.isAlive();
|
|
19806
20216
|
}
|
|
19807
20217
|
recoverStaleProcessForQueuedMessageIfNeeded(agentId, ap) {
|
|
19808
20218
|
const staleForMs = ap.runtimeProgress.ageMs();
|
|
@@ -20182,11 +20592,12 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
20182
20592
|
ap.decisionErrorWindow.recordRuntimeError(event.message);
|
|
20183
20593
|
}
|
|
20184
20594
|
let visibleErrorMessage = event.message;
|
|
20595
|
+
let visibleErrorEntries;
|
|
20185
20596
|
if (ap) {
|
|
20186
20597
|
const runtimeErrorDiagnostics = buildRuntimeErrorDiagnosticEnvelope(event.message);
|
|
20187
20598
|
const runtimeErrorFingerprint = typeof runtimeErrorDiagnostics.spanAttrs.runtime_error_fingerprint === "string" ? runtimeErrorDiagnostics.spanAttrs.runtime_error_fingerprint : null;
|
|
20188
20599
|
if (runtimeErrorDiagnostics.spanAttrs.runtime_error_action_required === true) {
|
|
20189
|
-
visibleErrorMessage =
|
|
20600
|
+
visibleErrorMessage = formatRuntimeActionRequiredMessage(ap);
|
|
20190
20601
|
}
|
|
20191
20602
|
const shouldDisableToolBoundaryFlush = ap.runtime.descriptor.busyDelivery === "gated" && this.isThinkingBlockMutationError(event.message);
|
|
20192
20603
|
const backoffFailPoint = this.runtimeErrorDeliveryBackoffFailPointForTesting?.({
|
|
@@ -20212,6 +20623,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
20212
20623
|
if (fingerprintFence) {
|
|
20213
20624
|
visibleErrorMessage = fingerprintFence.detail;
|
|
20214
20625
|
}
|
|
20626
|
+
visibleErrorEntries = terminalFailure?.entries;
|
|
20215
20627
|
this.noteRuntimeErrorDeliveryBackoff(agentId, ap, event.message, terminalFailure, stickyTerminalFailure, backoffReasonOverride);
|
|
20216
20628
|
if (reduction.shouldDisableToolBoundaryFlush) {
|
|
20217
20629
|
this.recordGatedSteeringEvent(agentId, ap, "disabled", {
|
|
@@ -20258,9 +20670,14 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
|
|
|
20258
20670
|
}
|
|
20259
20671
|
}
|
|
20260
20672
|
}
|
|
20261
|
-
this.broadcastActivity(
|
|
20262
|
-
|
|
20263
|
-
|
|
20673
|
+
this.broadcastActivity(
|
|
20674
|
+
agentId,
|
|
20675
|
+
"error",
|
|
20676
|
+
visibleErrorMessage,
|
|
20677
|
+
visibleErrorEntries ?? [{ kind: "text", text: `Error: ${visibleErrorMessage}` }],
|
|
20678
|
+
void 0,
|
|
20679
|
+
"runtime_error"
|
|
20680
|
+
);
|
|
20264
20681
|
break;
|
|
20265
20682
|
}
|
|
20266
20683
|
}
|
|
@@ -20927,8 +21344,11 @@ var DaemonConnection = class {
|
|
|
20927
21344
|
doConnect() {
|
|
20928
21345
|
if (!this.shouldConnect) return;
|
|
20929
21346
|
if (this.ws && this.ws.readyState !== WebSocket.CLOSED) return;
|
|
20930
|
-
const wsUrl = this.options.serverUrl.replace(/^http/, "ws") +
|
|
20931
|
-
const wsOptions =
|
|
21347
|
+
const wsUrl = this.options.serverUrl.replace(/^http/, "ws") + "/daemon/connect";
|
|
21348
|
+
const wsOptions = {
|
|
21349
|
+
...buildWebSocketOptions(wsUrl, this.options.proxyEnv ?? process.env),
|
|
21350
|
+
headers: { Authorization: `Bearer ${this.options.apiKey}` }
|
|
21351
|
+
};
|
|
20932
21352
|
logger.info(`[Daemon] Connecting to ${this.options.serverUrl}...`);
|
|
20933
21353
|
if (wsOptions?.agent) {
|
|
20934
21354
|
logger.info("[Daemon] Using configured proxy for WebSocket connection");
|
|
@@ -21865,45 +22285,212 @@ function readPositiveIntegerEnv2(name, fallback) {
|
|
|
21865
22285
|
}
|
|
21866
22286
|
|
|
21867
22287
|
// src/computerMigrationGuard.ts
|
|
21868
|
-
import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as
|
|
22288
|
+
import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "fs";
|
|
21869
22289
|
import path18 from "path";
|
|
21870
|
-
|
|
21871
|
-
|
|
21872
|
-
|
|
21873
|
-
|
|
21874
|
-
|
|
21875
|
-
|
|
21876
|
-
|
|
21877
|
-
|
|
21878
|
-
|
|
21879
|
-
|
|
21880
|
-
|
|
21881
|
-
|
|
21882
|
-
|
|
22290
|
+
|
|
22291
|
+
// src/legacySupervisor.ts
|
|
22292
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
22293
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
22294
|
+
function execFileText(file, args) {
|
|
22295
|
+
try {
|
|
22296
|
+
return execFileSync4(file, args, {
|
|
22297
|
+
encoding: "utf8",
|
|
22298
|
+
timeout: 1e3,
|
|
22299
|
+
maxBuffer: 256 * 1024,
|
|
22300
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
22301
|
+
});
|
|
22302
|
+
} catch {
|
|
22303
|
+
return void 0;
|
|
21883
22304
|
}
|
|
21884
|
-
};
|
|
21885
|
-
function daemonApiKeyFingerprint(apiKey) {
|
|
21886
|
-
return getDaemonMachineLockId(apiKey).slice("machine-".length);
|
|
21887
22305
|
}
|
|
21888
|
-
function
|
|
21889
|
-
const serversDir = path18.join(slockHome, "computer", "servers");
|
|
21890
|
-
if (!existsSync9(serversDir)) return null;
|
|
21891
|
-
let serverDirs;
|
|
22306
|
+
function readFileText(file) {
|
|
21892
22307
|
try {
|
|
21893
|
-
|
|
22308
|
+
return readFileSync8(file, "utf8");
|
|
21894
22309
|
} catch {
|
|
21895
|
-
return
|
|
22310
|
+
return void 0;
|
|
21896
22311
|
}
|
|
21897
|
-
|
|
21898
|
-
|
|
21899
|
-
|
|
22312
|
+
}
|
|
22313
|
+
function defaultProbe(pid) {
|
|
22314
|
+
return {
|
|
22315
|
+
pid,
|
|
22316
|
+
platform: process.platform,
|
|
22317
|
+
env: process.env,
|
|
22318
|
+
execFile: execFileText,
|
|
22319
|
+
readFile: readFileText
|
|
22320
|
+
};
|
|
22321
|
+
}
|
|
22322
|
+
function shellQuote(value) {
|
|
22323
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
22324
|
+
}
|
|
22325
|
+
function parentPid(probe, pid) {
|
|
22326
|
+
const stdout = probe.execFile("ps", ["-o", "ppid=", "-p", String(pid)]);
|
|
22327
|
+
const parsed = Number.parseInt(stdout?.trim() ?? "", 10);
|
|
22328
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
22329
|
+
}
|
|
22330
|
+
function processCommand(probe, pid) {
|
|
22331
|
+
return probe.execFile("ps", ["-o", "command=", "-p", String(pid)])?.trim().toLowerCase() ?? "";
|
|
22332
|
+
}
|
|
22333
|
+
function relatedProcessIds(probe) {
|
|
22334
|
+
const ids = [probe.pid];
|
|
22335
|
+
let current = probe.pid;
|
|
22336
|
+
for (let depth = 0; depth < 12; depth += 1) {
|
|
22337
|
+
const parent = parentPid(probe, current);
|
|
22338
|
+
if (!parent || ids.includes(parent)) break;
|
|
22339
|
+
ids.push(parent);
|
|
22340
|
+
if (parent === 1) break;
|
|
22341
|
+
current = parent;
|
|
22342
|
+
}
|
|
22343
|
+
return ids;
|
|
22344
|
+
}
|
|
22345
|
+
function detectPm2(probe, relatedPids) {
|
|
22346
|
+
const hasPm2Ancestor = relatedPids.slice(1).some((pid) => processCommand(probe, pid).includes("pm2"));
|
|
22347
|
+
const hasPm2Environment = Boolean(probe.env.PM2_HOME || probe.env.pm_id);
|
|
22348
|
+
if (!hasPm2Ancestor && !hasPm2Environment) return void 0;
|
|
22349
|
+
const pm2Json = probe.execFile("pm2", ["jlist"]);
|
|
22350
|
+
if (pm2Json) {
|
|
21900
22351
|
try {
|
|
21901
|
-
|
|
22352
|
+
const entries = JSON.parse(pm2Json);
|
|
22353
|
+
const entry = entries.find(
|
|
22354
|
+
(candidate) => typeof candidate.pid === "number" && relatedPids.includes(candidate.pid)
|
|
22355
|
+
);
|
|
22356
|
+
if (entry) {
|
|
22357
|
+
const name = typeof entry.name === "string" && entry.name.length > 0 ? entry.name : String(entry.pid);
|
|
22358
|
+
return {
|
|
22359
|
+
kind: "pm2",
|
|
22360
|
+
detail: `PM2 app ${JSON.stringify(name)} is supervising this legacy daemon.`,
|
|
22361
|
+
cleanupCommands: [`pm2 delete ${shellQuote(name)}`, "pm2 save"]
|
|
22362
|
+
};
|
|
22363
|
+
}
|
|
21902
22364
|
} catch {
|
|
21903
|
-
continue;
|
|
21904
22365
|
}
|
|
21905
|
-
|
|
21906
|
-
|
|
22366
|
+
}
|
|
22367
|
+
return {
|
|
22368
|
+
kind: "pm2",
|
|
22369
|
+
detail: "PM2 is supervising this legacy daemon, but its app name could not be resolved.",
|
|
22370
|
+
cleanupCommands: ["pm2 delete <legacy-daemon-app-name>", "pm2 save"]
|
|
22371
|
+
};
|
|
22372
|
+
}
|
|
22373
|
+
function systemdUnitFromCgroup(cgroup) {
|
|
22374
|
+
if (!cgroup) return void 0;
|
|
22375
|
+
for (const line of cgroup.split("\n")) {
|
|
22376
|
+
const pathPart = line.slice(line.lastIndexOf(":") + 1);
|
|
22377
|
+
let unit;
|
|
22378
|
+
for (const segment of pathPart.split("/")) {
|
|
22379
|
+
if (segment.endsWith(".service") && segment.length > ".service".length && !/^user@\d+\.service$/.test(segment)) {
|
|
22380
|
+
unit = segment;
|
|
22381
|
+
}
|
|
22382
|
+
}
|
|
22383
|
+
if (unit) return { unit, user: pathPart.includes("/user.slice/") };
|
|
22384
|
+
}
|
|
22385
|
+
return void 0;
|
|
22386
|
+
}
|
|
22387
|
+
function detectSystemd(probe) {
|
|
22388
|
+
if (probe.platform !== "linux") return void 0;
|
|
22389
|
+
const cgroupUnit = systemdUnitFromCgroup(
|
|
22390
|
+
probe.readFile(`/proc/${probe.pid}/cgroup`)
|
|
22391
|
+
);
|
|
22392
|
+
const hasSystemdEvidence = cgroupUnit !== void 0 || probe.env.INVOCATION_ID !== void 0;
|
|
22393
|
+
if (!hasSystemdEvidence) return void 0;
|
|
22394
|
+
if (cgroupUnit) {
|
|
22395
|
+
const prefix = cgroupUnit.user ? "systemctl --user" : "sudo systemctl";
|
|
22396
|
+
return {
|
|
22397
|
+
kind: "systemd",
|
|
22398
|
+
detail: `systemd unit ${JSON.stringify(cgroupUnit.unit)} is supervising this legacy daemon.`,
|
|
22399
|
+
cleanupCommands: [
|
|
22400
|
+
`${prefix} disable --now ${shellQuote(cgroupUnit.unit)}`
|
|
22401
|
+
]
|
|
22402
|
+
};
|
|
22403
|
+
}
|
|
22404
|
+
return {
|
|
22405
|
+
kind: "systemd",
|
|
22406
|
+
detail: "systemd is supervising this legacy daemon, but its unit name could not be resolved.",
|
|
22407
|
+
cleanupCommands: [
|
|
22408
|
+
"systemctl --user disable --now <legacy-daemon-unit>.service"
|
|
22409
|
+
]
|
|
22410
|
+
};
|
|
22411
|
+
}
|
|
22412
|
+
function detectLaunchd(probe) {
|
|
22413
|
+
if (probe.platform !== "darwin") return void 0;
|
|
22414
|
+
const label = probe.env.LAUNCH_JOBKEY_LABEL || probe.env.XPC_SERVICE_NAME;
|
|
22415
|
+
if (!label || label === "0") return void 0;
|
|
22416
|
+
return {
|
|
22417
|
+
kind: "launchd",
|
|
22418
|
+
detail: `launchd job ${JSON.stringify(label)} is supervising this legacy daemon.`,
|
|
22419
|
+
cleanupCommands: [
|
|
22420
|
+
`launchctl bootout gui/$(id -u)/${shellQuote(label)}`,
|
|
22421
|
+
`rm -f ~/Library/LaunchAgents/${shellQuote(`${label}.plist`)}`
|
|
22422
|
+
]
|
|
22423
|
+
};
|
|
22424
|
+
}
|
|
22425
|
+
function detectLegacyDaemonSupervisor(pid = process.pid) {
|
|
22426
|
+
return detectLegacyDaemonSupervisorWithProbe(defaultProbe(pid));
|
|
22427
|
+
}
|
|
22428
|
+
function detectLegacyDaemonSupervisorWithProbe(probe) {
|
|
22429
|
+
const relatedPids = relatedProcessIds(probe);
|
|
22430
|
+
return detectPm2(probe, relatedPids) ?? detectSystemd(probe) ?? detectLaunchd(probe);
|
|
22431
|
+
}
|
|
22432
|
+
var legacyDaemonSupervisorFallbackCommands = [
|
|
22433
|
+
"PM2: pm2 delete <legacy-daemon-app-name> && pm2 save",
|
|
22434
|
+
"systemd: systemctl --user disable --now <legacy-daemon-unit>.service",
|
|
22435
|
+
"launchd: launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/<legacy-daemon-label>.plist && rm -f ~/Library/LaunchAgents/<legacy-daemon-label>.plist"
|
|
22436
|
+
];
|
|
22437
|
+
|
|
22438
|
+
// src/computerMigrationGuard.ts
|
|
22439
|
+
var LegacyDaemonKeyAdoptedByComputerError = class extends Error {
|
|
22440
|
+
code = "LEGACY_DAEMON_KEY_ADOPTED_BY_COMPUTER";
|
|
22441
|
+
attachmentPath;
|
|
22442
|
+
serverId;
|
|
22443
|
+
constructor(match, supervisor = detectLegacyDaemonSupervisor() ?? null) {
|
|
22444
|
+
const serverDisplay = match.serverSlug ? `/${match.serverSlug}` : match.serverId;
|
|
22445
|
+
const startCommand = match.serverSlug ? `raft-computer start /${match.serverSlug}` : "raft-computer start";
|
|
22446
|
+
const statusCommand = match.serverSlug ? `raft-computer status /${match.serverSlug}` : "raft-computer status";
|
|
22447
|
+
const supervisorGuidance = formatSupervisorGuidance(
|
|
22448
|
+
supervisor ?? void 0
|
|
22449
|
+
);
|
|
22450
|
+
super(
|
|
22451
|
+
`Legacy Raft daemon startup refused: this machine key was already migrated to Raft Computer for ${serverDisplay}.
|
|
22452
|
+
Raft Computer now owns this connection; do not restart raft-daemon with the migrated key.
|
|
22453
|
+
${supervisorGuidance}
|
|
22454
|
+
Then start and verify Raft Computer:
|
|
22455
|
+
${startCommand}
|
|
22456
|
+
${statusCommand}`
|
|
22457
|
+
);
|
|
22458
|
+
this.name = "LegacyDaemonKeyAdoptedByComputerError";
|
|
22459
|
+
this.attachmentPath = match.attachmentPath;
|
|
22460
|
+
this.serverId = match.serverId;
|
|
22461
|
+
}
|
|
22462
|
+
};
|
|
22463
|
+
function formatSupervisorGuidance(supervisor) {
|
|
22464
|
+
if (supervisor) {
|
|
22465
|
+
return `${supervisor.detail}
|
|
22466
|
+
Remove the old supervisor entry so it cannot auto-restart:
|
|
22467
|
+
` + supervisor.cleanupCommands.map((command) => ` ${command}`).join("\n");
|
|
22468
|
+
}
|
|
22469
|
+
return `If a process manager keeps restarting the old daemon, remove the entry you configured:
|
|
22470
|
+
` + legacyDaemonSupervisorFallbackCommands.map((command) => ` ${command}`).join("\n");
|
|
22471
|
+
}
|
|
22472
|
+
function daemonApiKeyFingerprint(apiKey) {
|
|
22473
|
+
return getDaemonMachineLockId(apiKey).slice("machine-".length);
|
|
22474
|
+
}
|
|
22475
|
+
function findAdoptedComputerForLegacyFingerprint(slockHome, legacyApiKeyFingerprint) {
|
|
22476
|
+
const serversDir = path18.join(slockHome, "computer", "servers");
|
|
22477
|
+
if (!existsSync9(serversDir)) return null;
|
|
22478
|
+
let serverDirs;
|
|
22479
|
+
try {
|
|
22480
|
+
serverDirs = readdirSync6(serversDir);
|
|
22481
|
+
} catch {
|
|
22482
|
+
return null;
|
|
22483
|
+
}
|
|
22484
|
+
for (const serverId of serverDirs) {
|
|
22485
|
+
const attachmentPath = path18.join(serversDir, serverId, "runner.state.json");
|
|
22486
|
+
let raw;
|
|
22487
|
+
try {
|
|
22488
|
+
raw = readFileSync9(attachmentPath, "utf8");
|
|
22489
|
+
} catch {
|
|
22490
|
+
continue;
|
|
22491
|
+
}
|
|
22492
|
+
try {
|
|
22493
|
+
const attachment = JSON.parse(raw);
|
|
21907
22494
|
if (attachment.kind === "computer-attachment" && attachment.adoptedFromLegacy === true && attachment.legacyApiKeyFingerprint === legacyApiKeyFingerprint) {
|
|
21908
22495
|
return {
|
|
21909
22496
|
attachmentPath,
|
|
@@ -21912,20 +22499,543 @@ function findAdoptedComputerForLegacyFingerprint(slockHome, legacyApiKeyFingerpr
|
|
|
21912
22499
|
serverMachineId: typeof attachment.serverMachineId === "string" && attachment.serverMachineId.length > 0 ? attachment.serverMachineId : void 0
|
|
21913
22500
|
};
|
|
21914
22501
|
}
|
|
21915
|
-
} catch {
|
|
21916
|
-
continue;
|
|
22502
|
+
} catch {
|
|
22503
|
+
continue;
|
|
22504
|
+
}
|
|
22505
|
+
}
|
|
22506
|
+
return null;
|
|
22507
|
+
}
|
|
22508
|
+
function assertLegacyDaemonKeyNotAdoptedByComputer(options) {
|
|
22509
|
+
const legacyApiKeyFingerprint = daemonApiKeyFingerprint(options.apiKey);
|
|
22510
|
+
const match = findAdoptedComputerForLegacyFingerprint(
|
|
22511
|
+
options.slockHome,
|
|
22512
|
+
legacyApiKeyFingerprint
|
|
22513
|
+
);
|
|
22514
|
+
if (match) throw new LegacyDaemonKeyAdoptedByComputerError(match);
|
|
22515
|
+
}
|
|
22516
|
+
|
|
22517
|
+
// src/agentMigrationHttpTransport.ts
|
|
22518
|
+
import { createHash as createHash7, randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
22519
|
+
import http2 from "http";
|
|
22520
|
+
import { Readable } from "stream";
|
|
22521
|
+
var AGENT_MIGRATION_TRANSPORT_HOST_ENV = "SLOCK_AGENT_MIGRATION_TRANSPORT_HOST";
|
|
22522
|
+
var AGENT_MIGRATION_TRANSPORT_PORT_ENV = "SLOCK_AGENT_MIGRATION_TRANSPORT_PORT";
|
|
22523
|
+
var AGENT_MIGRATION_TRANSPORT_PUBLIC_URL_ENV = "SLOCK_AGENT_MIGRATION_TRANSPORT_PUBLIC_URL";
|
|
22524
|
+
var AGENT_MIGRATION_CONTROL_SEAM_ENV = "SLOCK_AGENT_MIGRATION_CONTROL_SEAM";
|
|
22525
|
+
var CONTROL_REQUEST_MAX_BYTES = 8 * 1024 * 1024;
|
|
22526
|
+
var MigrationBundleStreamError = class extends Error {
|
|
22527
|
+
constructor(code) {
|
|
22528
|
+
super(code);
|
|
22529
|
+
this.code = code;
|
|
22530
|
+
}
|
|
22531
|
+
};
|
|
22532
|
+
var AgentMigrationGrantRegistry = class {
|
|
22533
|
+
grants = /* @__PURE__ */ new Map();
|
|
22534
|
+
now;
|
|
22535
|
+
constructor(now = currentDate) {
|
|
22536
|
+
this.now = now;
|
|
22537
|
+
}
|
|
22538
|
+
createGrant(input) {
|
|
22539
|
+
const token = input.token ?? randomBytes2(32).toString("base64url");
|
|
22540
|
+
const manifestSha256 = sha256Buffer(canonicalJsonBuffer(input.manifest));
|
|
22541
|
+
const grant = {
|
|
22542
|
+
grantId: input.grantId ?? randomBytes2(16).toString("hex"),
|
|
22543
|
+
tokenHash: hashToken(token),
|
|
22544
|
+
expiresAt: input.expiresAt,
|
|
22545
|
+
oneTimeUse: input.oneTimeUse ?? true,
|
|
22546
|
+
state: "issued",
|
|
22547
|
+
manifest: input.manifest,
|
|
22548
|
+
manifestSha256,
|
|
22549
|
+
bundleEtag: `"agent-migration-${manifestSha256}"`,
|
|
22550
|
+
bundleSizeBytes: normalizeBundleSizeBytes(input.bundleSizeBytes)
|
|
22551
|
+
};
|
|
22552
|
+
this.grants.set(grant.grantId, grant);
|
|
22553
|
+
return { grant, token };
|
|
22554
|
+
}
|
|
22555
|
+
get(grantId) {
|
|
22556
|
+
return this.grants.get(grantId);
|
|
22557
|
+
}
|
|
22558
|
+
authenticate(grantId, token) {
|
|
22559
|
+
const grant = this.grants.get(grantId);
|
|
22560
|
+
if (!grant || !token || !verifyTokenHash(token, grant.tokenHash)) {
|
|
22561
|
+
return { ok: false, status: 401, code: "migration_grant_auth_failed" };
|
|
22562
|
+
}
|
|
22563
|
+
if (grant.state === "revoked" || grant.state === "consumed") {
|
|
22564
|
+
return { ok: false, status: 410, code: "migration_grant_unavailable" };
|
|
22565
|
+
}
|
|
22566
|
+
if (this.now().getTime() >= grant.expiresAt.getTime()) {
|
|
22567
|
+
grant.state = "expired";
|
|
22568
|
+
return { ok: false, status: 410, code: "migration_grant_unavailable" };
|
|
22569
|
+
}
|
|
22570
|
+
return { ok: true, grant };
|
|
22571
|
+
}
|
|
22572
|
+
revokeAll() {
|
|
22573
|
+
for (const grant of this.grants.values()) {
|
|
22574
|
+
if (grant.state !== "consumed" && grant.state !== "expired") {
|
|
22575
|
+
grant.state = "revoked";
|
|
22576
|
+
}
|
|
22577
|
+
}
|
|
22578
|
+
}
|
|
22579
|
+
beginBundleStream(grant) {
|
|
22580
|
+
const current = this.grants.get(grant.grantId);
|
|
22581
|
+
if (current !== grant) return false;
|
|
22582
|
+
if (current.state !== "issued" && current.state !== "interrupted") return false;
|
|
22583
|
+
current.state = "streaming";
|
|
22584
|
+
return true;
|
|
22585
|
+
}
|
|
22586
|
+
};
|
|
22587
|
+
function createAgentMigrationHttpTransport(options = {}) {
|
|
22588
|
+
const now = options.now ?? currentDate;
|
|
22589
|
+
const registry = new AgentMigrationGrantRegistry(now);
|
|
22590
|
+
const controlBundles = /* @__PURE__ */ new Map();
|
|
22591
|
+
const fallbackBundleStreamFactory = options.bundleStreamFactory ?? defaultBundleStream;
|
|
22592
|
+
const bundleStreamFactory = (grant, request) => {
|
|
22593
|
+
const bundle = controlBundles.get(grant.grantId);
|
|
22594
|
+
if (bundle) {
|
|
22595
|
+
const endExclusive = request.lengthBytes === null ? void 0 : request.offsetBytes + request.lengthBytes;
|
|
22596
|
+
return Readable.from([bundle.subarray(request.offsetBytes, endExclusive)]);
|
|
22597
|
+
}
|
|
22598
|
+
return fallbackBundleStreamFactory(grant, request);
|
|
22599
|
+
};
|
|
22600
|
+
const listenDefaults = options.listen ?? resolveAgentMigrationHttpTransportListenOptions();
|
|
22601
|
+
const controlSeamEnabled = options.controlSeam ?? resolveAgentMigrationControlSeamEnabled();
|
|
22602
|
+
const server = http2.createServer((req, res) => {
|
|
22603
|
+
const parsed = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
22604
|
+
if (parsed.pathname.startsWith("/migration-control")) {
|
|
22605
|
+
void handleMigrationControlRequest(req, res, parsed, {
|
|
22606
|
+
enabled: controlSeamEnabled,
|
|
22607
|
+
now,
|
|
22608
|
+
registry,
|
|
22609
|
+
controlBundles
|
|
22610
|
+
});
|
|
22611
|
+
return;
|
|
22612
|
+
}
|
|
22613
|
+
void handleMigrationRequest(req, res, registry, bundleStreamFactory);
|
|
22614
|
+
});
|
|
22615
|
+
return {
|
|
22616
|
+
grants: registry,
|
|
22617
|
+
server,
|
|
22618
|
+
listen: (port = listenDefaults.port ?? 0, host = listenDefaults.host ?? "127.0.0.1") => new Promise((resolve, reject) => {
|
|
22619
|
+
server.once("error", reject);
|
|
22620
|
+
server.listen(port, host, () => {
|
|
22621
|
+
server.off("error", reject);
|
|
22622
|
+
const address = server.address();
|
|
22623
|
+
if (!address || typeof address === "string") {
|
|
22624
|
+
reject(new Error("migration transport listen did not produce a TCP address"));
|
|
22625
|
+
return;
|
|
22626
|
+
}
|
|
22627
|
+
resolve({ url: listenDefaults.publicUrl ?? `http://${formatListenAddress(address.address)}:${address.port}` });
|
|
22628
|
+
});
|
|
22629
|
+
}),
|
|
22630
|
+
close: () => new Promise((resolve, reject) => {
|
|
22631
|
+
registry.revokeAll();
|
|
22632
|
+
server.close((err) => {
|
|
22633
|
+
if (err) reject(err);
|
|
22634
|
+
else resolve();
|
|
22635
|
+
});
|
|
22636
|
+
})
|
|
22637
|
+
};
|
|
22638
|
+
}
|
|
22639
|
+
function resolveAgentMigrationHttpTransportListenOptions(env = process.env) {
|
|
22640
|
+
const host = env[AGENT_MIGRATION_TRANSPORT_HOST_ENV]?.trim();
|
|
22641
|
+
const port = parseOptionalPort(env[AGENT_MIGRATION_TRANSPORT_PORT_ENV]);
|
|
22642
|
+
const publicUrl = env[AGENT_MIGRATION_TRANSPORT_PUBLIC_URL_ENV]?.trim();
|
|
22643
|
+
return {
|
|
22644
|
+
...host ? { host } : {},
|
|
22645
|
+
...port !== void 0 ? { port } : {},
|
|
22646
|
+
...publicUrl ? { publicUrl } : {}
|
|
22647
|
+
};
|
|
22648
|
+
}
|
|
22649
|
+
function resolveAgentMigrationControlSeamEnabled(env = process.env) {
|
|
22650
|
+
return env[AGENT_MIGRATION_CONTROL_SEAM_ENV]?.trim() !== "0";
|
|
22651
|
+
}
|
|
22652
|
+
async function handleMigrationControlRequest(req, res, parsed, context) {
|
|
22653
|
+
if (!context.enabled) {
|
|
22654
|
+
sendJson(res, 404, { code: "migration_route_not_found" });
|
|
22655
|
+
return;
|
|
22656
|
+
}
|
|
22657
|
+
if (parsed.pathname !== "/migration-control/grants") {
|
|
22658
|
+
sendJson(res, 404, { code: "migration_route_not_found" });
|
|
22659
|
+
return;
|
|
22660
|
+
}
|
|
22661
|
+
if (req.method !== "POST") {
|
|
22662
|
+
sendJson(res, 405, { code: "migration_method_not_allowed" });
|
|
22663
|
+
return;
|
|
22664
|
+
}
|
|
22665
|
+
let rawInput;
|
|
22666
|
+
try {
|
|
22667
|
+
rawInput = await readJsonRequestBody(req, CONTROL_REQUEST_MAX_BYTES);
|
|
22668
|
+
} catch (err) {
|
|
22669
|
+
if (err instanceof MigrationBundleStreamError) {
|
|
22670
|
+
sendJson(res, err.code === "migration_control_payload_too_large" ? 413 : 400, { code: err.code });
|
|
22671
|
+
return;
|
|
22672
|
+
}
|
|
22673
|
+
sendJson(res, 400, { code: "migration_control_invalid_json" });
|
|
22674
|
+
return;
|
|
22675
|
+
}
|
|
22676
|
+
const input = parseControlGrantInput(rawInput, context.now);
|
|
22677
|
+
if (!input.ok) {
|
|
22678
|
+
sendJson(res, 400, { code: input.code });
|
|
22679
|
+
return;
|
|
22680
|
+
}
|
|
22681
|
+
const { bundle, createGrantInput } = input;
|
|
22682
|
+
const { grant, token } = context.registry.createGrant({
|
|
22683
|
+
...createGrantInput,
|
|
22684
|
+
bundleSizeBytes: bundle.byteLength
|
|
22685
|
+
});
|
|
22686
|
+
context.controlBundles.set(grant.grantId, bundle);
|
|
22687
|
+
sendJson(res, 201, {
|
|
22688
|
+
grantId: grant.grantId,
|
|
22689
|
+
token,
|
|
22690
|
+
manifestSha256: grant.manifestSha256,
|
|
22691
|
+
bundleSizeBytes: grant.bundleSizeBytes,
|
|
22692
|
+
expiresAt: grant.expiresAt.toISOString(),
|
|
22693
|
+
manifestUrl: `/migration/${encodeURIComponent(grant.grantId)}/manifest`,
|
|
22694
|
+
bundleUrl: `/migration/${encodeURIComponent(grant.grantId)}/bundle.tar`
|
|
22695
|
+
});
|
|
22696
|
+
}
|
|
22697
|
+
async function handleMigrationRequest(req, res, registry, bundleStreamFactory) {
|
|
22698
|
+
const parsed = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
22699
|
+
const match = /^\/migration\/([^/]+)\/([^/]+)(?:\/([^/]+))?$/.exec(parsed.pathname);
|
|
22700
|
+
if (!match) {
|
|
22701
|
+
sendJson(res, 404, { code: "migration_route_not_found" });
|
|
22702
|
+
return;
|
|
22703
|
+
}
|
|
22704
|
+
const [, grantId, resource, resourceId] = match;
|
|
22705
|
+
const auth = registry.authenticate(decodeURIComponent(grantId), extractBearerToken(req));
|
|
22706
|
+
if (!auth.ok) {
|
|
22707
|
+
sendJson(res, auth.status, { code: auth.code });
|
|
22708
|
+
return;
|
|
22709
|
+
}
|
|
22710
|
+
if (resource === "manifest" && req.method === "GET" && !resourceId) {
|
|
22711
|
+
sendJson(res, 200, {
|
|
22712
|
+
manifestSha256: auth.grant.manifestSha256,
|
|
22713
|
+
manifest: auth.grant.manifest
|
|
22714
|
+
}, {
|
|
22715
|
+
"X-Raft-Manifest-Sha": auth.grant.manifestSha256,
|
|
22716
|
+
ETag: `"manifest-${auth.grant.manifestSha256}"`
|
|
22717
|
+
});
|
|
22718
|
+
return;
|
|
22719
|
+
}
|
|
22720
|
+
if (resource === "bundle.tar" && !resourceId) {
|
|
22721
|
+
if (req.method === "HEAD") {
|
|
22722
|
+
sendHead(res, auth.grant);
|
|
22723
|
+
return;
|
|
22724
|
+
}
|
|
22725
|
+
if (req.method === "GET") {
|
|
22726
|
+
await streamBundle(req, res, auth.grant, registry, bundleStreamFactory);
|
|
22727
|
+
return;
|
|
22728
|
+
}
|
|
22729
|
+
}
|
|
22730
|
+
if (resource === "chunk" && req.method === "GET" && resourceId) {
|
|
22731
|
+
sendJson(res, 501, { code: "migration_chunk_not_implemented" });
|
|
22732
|
+
return;
|
|
22733
|
+
}
|
|
22734
|
+
sendJson(res, 405, { code: "migration_method_not_allowed" });
|
|
22735
|
+
}
|
|
22736
|
+
function sendHead(res, grant) {
|
|
22737
|
+
res.statusCode = 200;
|
|
22738
|
+
res.setHeader("X-Raft-Manifest-Sha", grant.manifestSha256);
|
|
22739
|
+
res.setHeader("ETag", grant.bundleEtag);
|
|
22740
|
+
res.setHeader("Accept-Ranges", grant.bundleSizeBytes === null ? "none" : "bytes");
|
|
22741
|
+
res.setHeader("X-Raft-Bundle-Size", grant.bundleSizeBytes === null ? "unknown" : grant.bundleSizeBytes.toString());
|
|
22742
|
+
if (grant.bundleSizeBytes !== null) {
|
|
22743
|
+
res.setHeader("Content-Length", grant.bundleSizeBytes.toString());
|
|
22744
|
+
}
|
|
22745
|
+
res.end();
|
|
22746
|
+
}
|
|
22747
|
+
async function streamBundle(req, res, grant, registry, bundleStreamFactory) {
|
|
22748
|
+
if (grant.state === "streaming") {
|
|
22749
|
+
sendJson(res, 409, { code: "migration_bundle_stream_in_progress" });
|
|
22750
|
+
return;
|
|
22751
|
+
}
|
|
22752
|
+
if (grant.oneTimeUse && grant.state !== "issued" && grant.state !== "interrupted") {
|
|
22753
|
+
sendJson(res, 410, { code: "migration_grant_unavailable" });
|
|
22754
|
+
return;
|
|
22755
|
+
}
|
|
22756
|
+
const range = planRange(req, grant);
|
|
22757
|
+
if (!range.ok) {
|
|
22758
|
+
sendJson(res, 416, { code: range.code }, range.headers);
|
|
22759
|
+
return;
|
|
22760
|
+
}
|
|
22761
|
+
const previousState = grant.state;
|
|
22762
|
+
if (!registry.beginBundleStream(grant)) {
|
|
22763
|
+
sendJson(res, 409, { code: "migration_bundle_stream_in_progress" });
|
|
22764
|
+
return;
|
|
22765
|
+
}
|
|
22766
|
+
let stream;
|
|
22767
|
+
try {
|
|
22768
|
+
stream = bundleStreamFactory(grant, range.request);
|
|
22769
|
+
} catch (err) {
|
|
22770
|
+
grant.state = previousState;
|
|
22771
|
+
const code = err instanceof MigrationBundleStreamError ? err.code : "migration_bundle_stream_failed";
|
|
22772
|
+
sendJson(res, 500, { code });
|
|
22773
|
+
return;
|
|
22774
|
+
}
|
|
22775
|
+
let completed = false;
|
|
22776
|
+
res.on("close", () => {
|
|
22777
|
+
if (!completed && grant.state === "streaming") {
|
|
22778
|
+
grant.state = "interrupted";
|
|
22779
|
+
}
|
|
22780
|
+
});
|
|
22781
|
+
try {
|
|
22782
|
+
res.statusCode = 200;
|
|
22783
|
+
res.setHeader("Content-Type", "application/x-tar");
|
|
22784
|
+
res.setHeader("X-Raft-Manifest-Sha", grant.manifestSha256);
|
|
22785
|
+
res.setHeader("ETag", grant.bundleEtag);
|
|
22786
|
+
res.setHeader("Accept-Ranges", grant.bundleSizeBytes === null ? "none" : "bytes");
|
|
22787
|
+
if (range.request.range) {
|
|
22788
|
+
res.statusCode = 206;
|
|
22789
|
+
res.setHeader("Content-Range", `bytes ${range.request.range.start}-${range.request.range.end}/${grant.bundleSizeBytes}`);
|
|
22790
|
+
}
|
|
22791
|
+
if (range.request.lengthBytes !== null) {
|
|
22792
|
+
res.setHeader("Content-Length", range.request.lengthBytes.toString());
|
|
22793
|
+
}
|
|
22794
|
+
for await (const chunk of stream) {
|
|
22795
|
+
if (!res.write(chunk)) {
|
|
22796
|
+
await onceDrain(res);
|
|
22797
|
+
}
|
|
22798
|
+
}
|
|
22799
|
+
completed = true;
|
|
22800
|
+
grant.state = range.consumesGrant ? "consumed" : "interrupted";
|
|
22801
|
+
res.end();
|
|
22802
|
+
} catch {
|
|
22803
|
+
if (!completed) {
|
|
22804
|
+
grant.state = "interrupted";
|
|
22805
|
+
}
|
|
22806
|
+
if (!res.headersSent) {
|
|
22807
|
+
sendJson(res, 500, { code: "migration_bundle_stream_failed" });
|
|
22808
|
+
} else {
|
|
22809
|
+
res.destroy();
|
|
22810
|
+
}
|
|
22811
|
+
}
|
|
22812
|
+
}
|
|
22813
|
+
function planRange(req, grant) {
|
|
22814
|
+
const header = singleHeader(req.headers.range);
|
|
22815
|
+
if (!header) {
|
|
22816
|
+
return {
|
|
22817
|
+
ok: true,
|
|
22818
|
+
request: { range: null, offsetBytes: 0, lengthBytes: grant.bundleSizeBytes },
|
|
22819
|
+
consumesGrant: true
|
|
22820
|
+
};
|
|
22821
|
+
}
|
|
22822
|
+
if (grant.bundleSizeBytes === null) {
|
|
22823
|
+
return {
|
|
22824
|
+
ok: false,
|
|
22825
|
+
code: "migration_range_not_available",
|
|
22826
|
+
headers: { "Accept-Ranges": "none" }
|
|
22827
|
+
};
|
|
22828
|
+
}
|
|
22829
|
+
const parsed = parseSingleByteRange(header, grant.bundleSizeBytes);
|
|
22830
|
+
if (!parsed.ok) {
|
|
22831
|
+
return {
|
|
22832
|
+
ok: false,
|
|
22833
|
+
code: parsed.code,
|
|
22834
|
+
headers: {
|
|
22835
|
+
"Accept-Ranges": "bytes",
|
|
22836
|
+
"Content-Range": `bytes */${grant.bundleSizeBytes}`
|
|
22837
|
+
}
|
|
22838
|
+
};
|
|
22839
|
+
}
|
|
22840
|
+
return {
|
|
22841
|
+
ok: true,
|
|
22842
|
+
request: {
|
|
22843
|
+
range: parsed.range,
|
|
22844
|
+
offsetBytes: parsed.range.start,
|
|
22845
|
+
lengthBytes: parsed.range.end - parsed.range.start + 1
|
|
22846
|
+
},
|
|
22847
|
+
consumesGrant: parsed.range.end === grant.bundleSizeBytes - 1
|
|
22848
|
+
};
|
|
22849
|
+
}
|
|
22850
|
+
function parseSingleByteRange(header, sizeBytes) {
|
|
22851
|
+
const match = /^bytes=(\d+)-(\d*)$/.exec(header.trim());
|
|
22852
|
+
if (!match) {
|
|
22853
|
+
return { ok: false, code: "migration_range_not_satisfiable" };
|
|
22854
|
+
}
|
|
22855
|
+
const start = Number(match[1]);
|
|
22856
|
+
const requestedEnd = match[2] ? Number(match[2]) : sizeBytes - 1;
|
|
22857
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(requestedEnd) || start < 0 || requestedEnd < start || start >= sizeBytes) {
|
|
22858
|
+
return { ok: false, code: "migration_range_not_satisfiable" };
|
|
22859
|
+
}
|
|
22860
|
+
return {
|
|
22861
|
+
ok: true,
|
|
22862
|
+
range: {
|
|
22863
|
+
start,
|
|
22864
|
+
end: Math.min(requestedEnd, sizeBytes - 1)
|
|
22865
|
+
}
|
|
22866
|
+
};
|
|
22867
|
+
}
|
|
22868
|
+
function defaultBundleStream(grant) {
|
|
22869
|
+
void grant;
|
|
22870
|
+
throw new MigrationBundleStreamError("migration_bundle_stream_not_wired");
|
|
22871
|
+
}
|
|
22872
|
+
function parseControlGrantInput(rawInput, now) {
|
|
22873
|
+
if (!rawInput || typeof rawInput !== "object" || Array.isArray(rawInput)) {
|
|
22874
|
+
return { ok: false, code: "migration_control_invalid_payload" };
|
|
22875
|
+
}
|
|
22876
|
+
const input = rawInput;
|
|
22877
|
+
const manifest = input.manifest;
|
|
22878
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
22879
|
+
return { ok: false, code: "migration_control_manifest_required" };
|
|
22880
|
+
}
|
|
22881
|
+
const bundle = decodeControlBundle(input);
|
|
22882
|
+
if (!bundle.ok) return bundle;
|
|
22883
|
+
const expiresAt = parseControlGrantExpiry(input, now);
|
|
22884
|
+
if (!expiresAt.ok) return expiresAt;
|
|
22885
|
+
const grantId = optionalNonEmptyString(input.grantId);
|
|
22886
|
+
const token = optionalNonEmptyString(input.token);
|
|
22887
|
+
const oneTimeUse = typeof input.oneTimeUse === "boolean" ? input.oneTimeUse : void 0;
|
|
22888
|
+
return {
|
|
22889
|
+
ok: true,
|
|
22890
|
+
bundle: bundle.bundle,
|
|
22891
|
+
createGrantInput: {
|
|
22892
|
+
...grantId ? { grantId } : {},
|
|
22893
|
+
...token ? { token } : {},
|
|
22894
|
+
expiresAt: expiresAt.expiresAt,
|
|
22895
|
+
manifest,
|
|
22896
|
+
...oneTimeUse === void 0 ? {} : { oneTimeUse }
|
|
21917
22897
|
}
|
|
22898
|
+
};
|
|
22899
|
+
}
|
|
22900
|
+
function decodeControlBundle(input) {
|
|
22901
|
+
if (typeof input.bundleBase64 === "string") {
|
|
22902
|
+
const normalized = input.bundleBase64.trim();
|
|
22903
|
+
if (!normalized) return { ok: false, code: "migration_control_bundle_required" };
|
|
22904
|
+
const bundle = Buffer.from(normalized, "base64");
|
|
22905
|
+
if (bundle.byteLength === 0) return { ok: false, code: "migration_control_bundle_required" };
|
|
22906
|
+
return { ok: true, bundle };
|
|
21918
22907
|
}
|
|
21919
|
-
|
|
22908
|
+
if (typeof input.bundleText === "string") {
|
|
22909
|
+
const bundle = Buffer.from(input.bundleText, "utf8");
|
|
22910
|
+
if (bundle.byteLength === 0) return { ok: false, code: "migration_control_bundle_required" };
|
|
22911
|
+
return { ok: true, bundle };
|
|
22912
|
+
}
|
|
22913
|
+
return { ok: false, code: "migration_control_bundle_required" };
|
|
21920
22914
|
}
|
|
21921
|
-
function
|
|
21922
|
-
|
|
21923
|
-
|
|
21924
|
-
|
|
22915
|
+
function parseControlGrantExpiry(input, now) {
|
|
22916
|
+
if (typeof input.expiresAt === "string" && input.expiresAt.trim()) {
|
|
22917
|
+
const expiresAt = new Date(input.expiresAt);
|
|
22918
|
+
if (Number.isNaN(expiresAt.getTime())) {
|
|
22919
|
+
return { ok: false, code: "migration_control_invalid_expiry" };
|
|
22920
|
+
}
|
|
22921
|
+
return { ok: true, expiresAt };
|
|
22922
|
+
}
|
|
22923
|
+
if (input.expiresInSeconds !== void 0) {
|
|
22924
|
+
const expiresInSeconds = input.expiresInSeconds;
|
|
22925
|
+
if (typeof expiresInSeconds !== "number" || !Number.isSafeInteger(expiresInSeconds) || expiresInSeconds <= 0) {
|
|
22926
|
+
return { ok: false, code: "migration_control_invalid_expiry" };
|
|
22927
|
+
}
|
|
22928
|
+
return { ok: true, expiresAt: new Date(now().getTime() + expiresInSeconds * 1e3) };
|
|
22929
|
+
}
|
|
22930
|
+
return { ok: true, expiresAt: new Date(now().getTime() + 15 * 60 * 1e3) };
|
|
22931
|
+
}
|
|
22932
|
+
function optionalNonEmptyString(value) {
|
|
22933
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
22934
|
+
}
|
|
22935
|
+
function normalizeBundleSizeBytes(size) {
|
|
22936
|
+
if (size === null || size === void 0) return null;
|
|
22937
|
+
if (!Number.isSafeInteger(size) || size < 0) return null;
|
|
22938
|
+
return size;
|
|
22939
|
+
}
|
|
22940
|
+
function parseOptionalPort(raw) {
|
|
22941
|
+
const value = raw?.trim();
|
|
22942
|
+
if (!value) return void 0;
|
|
22943
|
+
const port = Number(value);
|
|
22944
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
22945
|
+
throw new Error(`${AGENT_MIGRATION_TRANSPORT_PORT_ENV} must be an integer from 0 to 65535`);
|
|
22946
|
+
}
|
|
22947
|
+
return port;
|
|
22948
|
+
}
|
|
22949
|
+
function formatListenAddress(address) {
|
|
22950
|
+
if (address.includes(":") && !address.startsWith("[")) return `[${address}]`;
|
|
22951
|
+
return address;
|
|
22952
|
+
}
|
|
22953
|
+
function extractBearerToken(req) {
|
|
22954
|
+
const directHeader = singleHeader(req.headers["x-raft-migration-token"]);
|
|
22955
|
+
if (directHeader) return directHeader;
|
|
22956
|
+
const authorization = singleHeader(req.headers.authorization);
|
|
22957
|
+
if (!authorization) return null;
|
|
22958
|
+
const match = /^Bearer\s+(.+)$/i.exec(authorization);
|
|
22959
|
+
return match?.[1] ?? null;
|
|
22960
|
+
}
|
|
22961
|
+
function singleHeader(value) {
|
|
22962
|
+
if (!value) return null;
|
|
22963
|
+
return Array.isArray(value) ? value[0] ?? null : value;
|
|
22964
|
+
}
|
|
22965
|
+
function sendJson(res, status, body, headers = {}) {
|
|
22966
|
+
const payload = JSON.stringify(body);
|
|
22967
|
+
res.writeHead(status, {
|
|
22968
|
+
"Content-Type": "application/json",
|
|
22969
|
+
"Content-Length": Buffer.byteLength(payload).toString(),
|
|
22970
|
+
...headers
|
|
22971
|
+
});
|
|
22972
|
+
res.end(payload);
|
|
22973
|
+
}
|
|
22974
|
+
function readJsonRequestBody(req, maxBytes) {
|
|
22975
|
+
return new Promise((resolve, reject) => {
|
|
22976
|
+
const chunks = [];
|
|
22977
|
+
let totalBytes = 0;
|
|
22978
|
+
let rejected = false;
|
|
22979
|
+
req.on("data", (chunk) => {
|
|
22980
|
+
if (rejected) return;
|
|
22981
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
22982
|
+
totalBytes += buffer.byteLength;
|
|
22983
|
+
if (totalBytes > maxBytes) {
|
|
22984
|
+
rejected = true;
|
|
22985
|
+
reject(new MigrationBundleStreamError("migration_control_payload_too_large"));
|
|
22986
|
+
return;
|
|
22987
|
+
}
|
|
22988
|
+
chunks.push(buffer);
|
|
22989
|
+
});
|
|
22990
|
+
req.on("error", reject);
|
|
22991
|
+
req.on("end", () => {
|
|
22992
|
+
if (rejected) return;
|
|
22993
|
+
try {
|
|
22994
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
22995
|
+
resolve(JSON.parse(text));
|
|
22996
|
+
} catch {
|
|
22997
|
+
reject(new MigrationBundleStreamError("migration_control_invalid_json"));
|
|
22998
|
+
}
|
|
22999
|
+
});
|
|
23000
|
+
});
|
|
23001
|
+
}
|
|
23002
|
+
function hashToken(token) {
|
|
23003
|
+
return sha256Buffer(Buffer.from(token, "utf8"));
|
|
23004
|
+
}
|
|
23005
|
+
function verifyTokenHash(token, expectedHashHex) {
|
|
23006
|
+
const actual = Buffer.from(hashToken(token), "hex");
|
|
23007
|
+
const expected = Buffer.from(expectedHashHex, "hex");
|
|
23008
|
+
if (actual.byteLength !== expected.byteLength) return false;
|
|
23009
|
+
return timingSafeEqual(actual, expected);
|
|
23010
|
+
}
|
|
23011
|
+
function sha256Buffer(buffer) {
|
|
23012
|
+
return createHash7("sha256").update(buffer).digest("hex");
|
|
23013
|
+
}
|
|
23014
|
+
function canonicalJsonBuffer(value) {
|
|
23015
|
+
return Buffer.from(canonicalJson(value), "utf8");
|
|
21925
23016
|
}
|
|
23017
|
+
function canonicalJson(value) {
|
|
23018
|
+
return JSON.stringify(sortJsonValue(value));
|
|
23019
|
+
}
|
|
23020
|
+
function sortJsonValue(value) {
|
|
23021
|
+
if (Array.isArray(value)) return value.map(sortJsonValue);
|
|
23022
|
+
if (!value || typeof value !== "object") return value;
|
|
23023
|
+
return Object.keys(value).sort().reduce((result, key) => {
|
|
23024
|
+
result[key] = sortJsonValue(value[key]);
|
|
23025
|
+
return result;
|
|
23026
|
+
}, {});
|
|
23027
|
+
}
|
|
23028
|
+
function onceDrain(res) {
|
|
23029
|
+
return new Promise((resolve) => res.once("drain", resolve));
|
|
23030
|
+
}
|
|
23031
|
+
|
|
23032
|
+
// src/agentMigrationObjectStoreBundle.ts
|
|
23033
|
+
import { createHash as createHash9 } from "crypto";
|
|
23034
|
+
import { lstat as lstat3, mkdir as mkdir3, readFile as readFile4, readlink as readlink2, rm as rm3, symlink, writeFile as writeFile3 } from "fs/promises";
|
|
23035
|
+
import path20 from "path";
|
|
21926
23036
|
|
|
21927
23037
|
// src/agentMigrationExport.ts
|
|
21928
|
-
import { createHash as
|
|
23038
|
+
import { createHash as createHash8 } from "crypto";
|
|
21929
23039
|
import { createReadStream } from "fs";
|
|
21930
23040
|
import { lstat as lstat2, readdir as readdir4, readFile as readFile3, readlink } from "fs/promises";
|
|
21931
23041
|
import path19 from "path";
|
|
@@ -22139,7 +23249,7 @@ async function buildCrossTreeRefs(refs) {
|
|
|
22139
23249
|
return result;
|
|
22140
23250
|
}
|
|
22141
23251
|
async function sha256File(filePath) {
|
|
22142
|
-
const hash =
|
|
23252
|
+
const hash = createHash8("sha256");
|
|
22143
23253
|
await new Promise((resolve, reject) => {
|
|
22144
23254
|
const stream = createReadStream(filePath);
|
|
22145
23255
|
stream.on("data", (chunk) => hash.update(chunk));
|
|
@@ -22245,240 +23355,362 @@ function sortByPath(entries) {
|
|
|
22245
23355
|
return [...entries].sort((a, b) => a.path.localeCompare(b.path));
|
|
22246
23356
|
}
|
|
22247
23357
|
|
|
22248
|
-
// src/
|
|
22249
|
-
|
|
22250
|
-
|
|
22251
|
-
|
|
22252
|
-
|
|
22253
|
-
|
|
22254
|
-
|
|
22255
|
-
|
|
22256
|
-
|
|
22257
|
-
|
|
22258
|
-
|
|
22259
|
-
|
|
22260
|
-
|
|
22261
|
-
|
|
22262
|
-
|
|
22263
|
-
|
|
22264
|
-
const
|
|
22265
|
-
|
|
22266
|
-
|
|
22267
|
-
|
|
22268
|
-
|
|
22269
|
-
|
|
22270
|
-
|
|
22271
|
-
|
|
22272
|
-
|
|
22273
|
-
|
|
22274
|
-
|
|
22275
|
-
|
|
22276
|
-
|
|
22277
|
-
|
|
23358
|
+
// src/agentMigrationObjectStoreBundle.ts
|
|
23359
|
+
var AGENT_MIGRATION_OBJECT_STORE_BUNDLE_SCHEMA_VERSION = "agent-object-store-bundle/v1";
|
|
23360
|
+
var AGENT_MIGRATION_OBJECT_STORE_CONTENT_TYPE = "application/vnd.slock.agent-migration-bundle";
|
|
23361
|
+
async function buildAgentMigrationObjectStoreBundle(input) {
|
|
23362
|
+
const workspacePath = path20.resolve(input.workspacePath);
|
|
23363
|
+
const manifest = await buildAgentMigrationExportManifest({
|
|
23364
|
+
agentId: input.agentId,
|
|
23365
|
+
slockHome: input.slockHome,
|
|
23366
|
+
workspacePath,
|
|
23367
|
+
mode: "forensic"
|
|
23368
|
+
});
|
|
23369
|
+
const files = [];
|
|
23370
|
+
for (const entry of manifest.files) {
|
|
23371
|
+
if (entry.source !== "workspace" || !entry.workspaceRelativePath) {
|
|
23372
|
+
throw new Error("MIGRATION_OBJECT_STORE_UNSUPPORTED_ENTRY");
|
|
23373
|
+
}
|
|
23374
|
+
const sourcePath = path20.resolve(entry.sourcePath);
|
|
23375
|
+
if (entry.kind === "symlink") {
|
|
23376
|
+
files.push({
|
|
23377
|
+
kind: "symlink",
|
|
23378
|
+
workspaceRelativePath: normalizeWorkspaceRelativePath(entry.workspaceRelativePath),
|
|
23379
|
+
linkTarget: entry.linkTarget ?? await readlink2(sourcePath)
|
|
23380
|
+
});
|
|
23381
|
+
continue;
|
|
23382
|
+
}
|
|
23383
|
+
const stat4 = await lstat3(sourcePath);
|
|
23384
|
+
if (!stat4.isFile()) throw new Error("MIGRATION_OBJECT_STORE_FILE_KIND_MISMATCH");
|
|
23385
|
+
files.push({
|
|
23386
|
+
kind: "file",
|
|
23387
|
+
workspaceRelativePath: normalizeWorkspaceRelativePath(entry.workspaceRelativePath),
|
|
23388
|
+
contentBase64: (await readFile4(sourcePath)).toString("base64")
|
|
23389
|
+
});
|
|
22278
23390
|
}
|
|
22279
|
-
|
|
22280
|
-
|
|
23391
|
+
const payload = {
|
|
23392
|
+
schemaVersion: AGENT_MIGRATION_OBJECT_STORE_BUNDLE_SCHEMA_VERSION,
|
|
23393
|
+
manifest,
|
|
23394
|
+
files
|
|
23395
|
+
};
|
|
23396
|
+
const bundle = Buffer.from(`${JSON.stringify(sortJsonValue2(payload))}
|
|
23397
|
+
`, "utf8");
|
|
23398
|
+
if (!Number.isSafeInteger(input.maxBytes) || input.maxBytes <= 0 || bundle.byteLength > input.maxBytes) {
|
|
23399
|
+
throw new Error("MIGRATION_OBJECT_STORE_BUNDLE_TOO_LARGE");
|
|
22281
23400
|
}
|
|
22282
|
-
|
|
22283
|
-
|
|
22284
|
-
|
|
22285
|
-
|
|
22286
|
-
|
|
22287
|
-
|
|
22288
|
-
|
|
22289
|
-
|
|
22290
|
-
|
|
22291
|
-
|
|
22292
|
-
|
|
23401
|
+
return {
|
|
23402
|
+
bundle,
|
|
23403
|
+
manifestSha256: sha256Buffer2(Buffer.from(canonicalJson2(manifest), "utf8"))
|
|
23404
|
+
};
|
|
23405
|
+
}
|
|
23406
|
+
async function stageAgentMigrationObjectStoreBundle(input) {
|
|
23407
|
+
const parsed = parseObjectStoreBundle(input.bundle);
|
|
23408
|
+
const stagingWorkspacePath = path20.join(
|
|
23409
|
+
path20.resolve(input.slockHome),
|
|
23410
|
+
"migrations",
|
|
23411
|
+
sanitizePathSegment(input.sessionId),
|
|
23412
|
+
"workspace"
|
|
23413
|
+
);
|
|
23414
|
+
await rm3(stagingWorkspacePath, { recursive: true, force: true });
|
|
23415
|
+
await mkdir3(stagingWorkspacePath, { recursive: true });
|
|
23416
|
+
for (const file of parsed.files) {
|
|
23417
|
+
const relativePath = normalizeWorkspaceRelativePath(file.workspaceRelativePath);
|
|
23418
|
+
const targetPath = path20.join(stagingWorkspacePath, relativePath);
|
|
23419
|
+
await mkdir3(path20.dirname(targetPath), { recursive: true });
|
|
23420
|
+
if (file.kind === "symlink") {
|
|
23421
|
+
if (!file.linkTarget) throw new Error("MIGRATION_OBJECT_STORE_LINK_TARGET_MISSING");
|
|
23422
|
+
await symlink(file.linkTarget, targetPath);
|
|
23423
|
+
continue;
|
|
22293
23424
|
}
|
|
22294
|
-
|
|
22295
|
-
|
|
22296
|
-
revokeAll() {
|
|
22297
|
-
for (const grant of this.grants.values()) {
|
|
22298
|
-
if (grant.state !== "consumed" && grant.state !== "expired") {
|
|
22299
|
-
grant.state = "revoked";
|
|
22300
|
-
}
|
|
23425
|
+
if (file.kind !== "file" || typeof file.contentBase64 !== "string") {
|
|
23426
|
+
throw new Error("MIGRATION_OBJECT_STORE_FILE_CONTENT_MISSING");
|
|
22301
23427
|
}
|
|
23428
|
+
await writeFile3(targetPath, Buffer.from(file.contentBase64, "base64"), { mode: 384 });
|
|
22302
23429
|
}
|
|
22303
|
-
};
|
|
22304
|
-
function createAgentMigrationHttpTransport(options = {}) {
|
|
22305
|
-
const registry = new AgentMigrationGrantRegistry(options.now);
|
|
22306
|
-
const bundleStreamFactory = options.bundleStreamFactory ?? defaultBundleStream;
|
|
22307
|
-
const server = http2.createServer((req, res) => {
|
|
22308
|
-
void handleMigrationRequest(req, res, registry, bundleStreamFactory);
|
|
22309
|
-
});
|
|
22310
23430
|
return {
|
|
22311
|
-
|
|
22312
|
-
|
|
22313
|
-
|
|
22314
|
-
server.once("error", reject);
|
|
22315
|
-
server.listen(port, host, () => {
|
|
22316
|
-
server.off("error", reject);
|
|
22317
|
-
const address = server.address();
|
|
22318
|
-
if (!address || typeof address === "string") {
|
|
22319
|
-
reject(new Error("migration transport listen did not produce a TCP address"));
|
|
22320
|
-
return;
|
|
22321
|
-
}
|
|
22322
|
-
resolve({ url: `http://${address.address}:${address.port}` });
|
|
22323
|
-
});
|
|
22324
|
-
}),
|
|
22325
|
-
close: () => new Promise((resolve, reject) => {
|
|
22326
|
-
registry.revokeAll();
|
|
22327
|
-
server.close((err) => {
|
|
22328
|
-
if (err) reject(err);
|
|
22329
|
-
else resolve();
|
|
22330
|
-
});
|
|
22331
|
-
})
|
|
23431
|
+
manifest: parsed.manifest,
|
|
23432
|
+
manifestSha256: sha256Buffer2(Buffer.from(canonicalJson2(parsed.manifest), "utf8")),
|
|
23433
|
+
stagingWorkspacePath
|
|
22332
23434
|
};
|
|
22333
23435
|
}
|
|
22334
|
-
|
|
22335
|
-
|
|
22336
|
-
|
|
22337
|
-
|
|
22338
|
-
|
|
22339
|
-
|
|
23436
|
+
function parseObjectStoreBundle(buffer) {
|
|
23437
|
+
let parsed;
|
|
23438
|
+
try {
|
|
23439
|
+
parsed = JSON.parse(buffer.toString("utf8"));
|
|
23440
|
+
} catch {
|
|
23441
|
+
throw new Error("MIGRATION_OBJECT_STORE_BUNDLE_INVALID_JSON");
|
|
22340
23442
|
}
|
|
22341
|
-
|
|
22342
|
-
|
|
22343
|
-
if (!auth.ok) {
|
|
22344
|
-
sendJson(res, auth.status, { code: auth.code });
|
|
22345
|
-
return;
|
|
23443
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
23444
|
+
throw new Error("MIGRATION_OBJECT_STORE_BUNDLE_INVALID");
|
|
22346
23445
|
}
|
|
22347
|
-
|
|
22348
|
-
|
|
22349
|
-
|
|
22350
|
-
|
|
22351
|
-
|
|
22352
|
-
|
|
22353
|
-
|
|
23446
|
+
const bundle = parsed;
|
|
23447
|
+
if (bundle.schemaVersion !== AGENT_MIGRATION_OBJECT_STORE_BUNDLE_SCHEMA_VERSION) {
|
|
23448
|
+
throw new Error("MIGRATION_OBJECT_STORE_BUNDLE_SCHEMA_UNSUPPORTED");
|
|
23449
|
+
}
|
|
23450
|
+
if (!bundle.manifest || typeof bundle.manifest !== "object" || Array.isArray(bundle.manifest)) {
|
|
23451
|
+
throw new Error("MIGRATION_OBJECT_STORE_MANIFEST_MISSING");
|
|
23452
|
+
}
|
|
23453
|
+
if (!Array.isArray(bundle.files)) throw new Error("MIGRATION_OBJECT_STORE_FILES_MISSING");
|
|
23454
|
+
return bundle;
|
|
23455
|
+
}
|
|
23456
|
+
function normalizeWorkspaceRelativePath(relativePath) {
|
|
23457
|
+
const normalized = path20.posix.normalize(relativePath.replaceAll("\\", "/"));
|
|
23458
|
+
if (normalized === "." || normalized.startsWith("../") || normalized === ".." || path20.isAbsolute(normalized)) {
|
|
23459
|
+
throw new Error("MIGRATION_OBJECT_STORE_UNSAFE_PATH");
|
|
23460
|
+
}
|
|
23461
|
+
return normalized;
|
|
23462
|
+
}
|
|
23463
|
+
function canonicalJson2(value) {
|
|
23464
|
+
return JSON.stringify(sortJsonValue2(value));
|
|
23465
|
+
}
|
|
23466
|
+
function sortJsonValue2(value) {
|
|
23467
|
+
if (Array.isArray(value)) return value.map(sortJsonValue2);
|
|
23468
|
+
if (!value || typeof value !== "object") return value;
|
|
23469
|
+
return Object.keys(value).sort().reduce((result, key) => {
|
|
23470
|
+
result[key] = sortJsonValue2(value[key]);
|
|
23471
|
+
return result;
|
|
23472
|
+
}, {});
|
|
23473
|
+
}
|
|
23474
|
+
function sha256Buffer2(buffer) {
|
|
23475
|
+
return createHash9("sha256").update(buffer).digest("hex");
|
|
23476
|
+
}
|
|
23477
|
+
function sanitizePathSegment(value) {
|
|
23478
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 128) || "migration";
|
|
23479
|
+
}
|
|
23480
|
+
|
|
23481
|
+
// src/agentMigrationImport.ts
|
|
23482
|
+
import { createHash as createHash10 } from "crypto";
|
|
23483
|
+
import { createReadStream as createReadStream2 } from "fs";
|
|
23484
|
+
import { access as access2, cp, lstat as lstat4, mkdir as mkdir4, readlink as readlink3, rename, writeFile as writeFile4 } from "fs/promises";
|
|
23485
|
+
import path21 from "path";
|
|
23486
|
+
async function buildAgentMigrationAdoptPlan(input) {
|
|
23487
|
+
const slockHome = path21.resolve(input.slockHome);
|
|
23488
|
+
let manifest;
|
|
23489
|
+
if (input.sourceKind === "orphan_directory" && !input.manifest) {
|
|
23490
|
+
manifest = await buildAgentMigrationExportManifest({
|
|
23491
|
+
agentId: input.agentId,
|
|
23492
|
+
slockHome,
|
|
23493
|
+
workspacePath: path21.resolve(input.orphanWorkspacePath),
|
|
23494
|
+
mode: "forensic",
|
|
23495
|
+
now: input.now
|
|
22354
23496
|
});
|
|
22355
|
-
|
|
23497
|
+
} else if (input.manifest) {
|
|
23498
|
+
manifest = input.manifest;
|
|
23499
|
+
} else {
|
|
23500
|
+
throw new Error("MIGRATION_MANIFEST_MISSING");
|
|
22356
23501
|
}
|
|
22357
|
-
|
|
22358
|
-
|
|
22359
|
-
|
|
22360
|
-
|
|
22361
|
-
|
|
22362
|
-
|
|
22363
|
-
|
|
22364
|
-
|
|
22365
|
-
|
|
23502
|
+
assertManifestIdentity({
|
|
23503
|
+
manifest,
|
|
23504
|
+
expectedAgentId: input.sourceKind === "orphan_directory" ? input.agentId : manifest.agentId
|
|
23505
|
+
});
|
|
23506
|
+
const sourceWorkspacePath = path21.resolve(
|
|
23507
|
+
input.sourceKind === "staged_bundle" ? input.stagingWorkspacePath : input.orphanWorkspacePath
|
|
23508
|
+
);
|
|
23509
|
+
const finalWorkspacePath = path21.resolve(input.finalWorkspacePath ?? path21.join(slockHome, "agents", manifest.agentId));
|
|
23510
|
+
const reportPath = path21.resolve(input.reportPath ?? path21.join(
|
|
23511
|
+
slockHome,
|
|
23512
|
+
"migrations",
|
|
23513
|
+
sanitizePathSegment2(input.generation.grantKey),
|
|
23514
|
+
"arrival-report.json"
|
|
23515
|
+
));
|
|
23516
|
+
const manifestSha256 = hashAgentMigrationManifest(manifest);
|
|
23517
|
+
if (input.manifestSha256 && input.manifestSha256 !== manifestSha256) {
|
|
23518
|
+
throw new Error("MIGRATION_MANIFEST_SHA_MISMATCH");
|
|
23519
|
+
}
|
|
23520
|
+
return {
|
|
23521
|
+
sourceKind: input.sourceKind,
|
|
23522
|
+
agentId: manifest.agentId,
|
|
23523
|
+
slockHome,
|
|
23524
|
+
sourceWorkspacePath,
|
|
23525
|
+
finalWorkspacePath,
|
|
23526
|
+
reportPath,
|
|
23527
|
+
manifest,
|
|
23528
|
+
manifestSha256,
|
|
23529
|
+
generation: input.generation,
|
|
23530
|
+
attestationNonce: input.attestationNonce,
|
|
23531
|
+
observedAttestationNonce: input.observedAttestationNonce
|
|
23532
|
+
};
|
|
23533
|
+
}
|
|
23534
|
+
async function verifyAgentMigrationAdoptPlan(plan) {
|
|
23535
|
+
assertGeneration(plan.generation);
|
|
23536
|
+
assertManifestIdentity({ manifest: plan.manifest, expectedAgentId: plan.agentId });
|
|
23537
|
+
const actualManifestSha256 = hashAgentMigrationManifest(plan.manifest);
|
|
23538
|
+
if (actualManifestSha256 !== plan.manifestSha256) {
|
|
23539
|
+
throw new Error("MIGRATION_MANIFEST_SHA_MISMATCH");
|
|
23540
|
+
}
|
|
23541
|
+
let totalBytes = 0;
|
|
23542
|
+
let fileCount = 0;
|
|
23543
|
+
for (const entry of plan.manifest.files) {
|
|
23544
|
+
await verifyManifestFileEntry(plan.sourceWorkspacePath, entry);
|
|
23545
|
+
if (entry.kind === "file") {
|
|
23546
|
+
fileCount += 1;
|
|
23547
|
+
totalBytes += entry.sizeBytes ?? 0;
|
|
23548
|
+
}
|
|
23549
|
+
}
|
|
23550
|
+
return { fileCount, totalBytes };
|
|
23551
|
+
}
|
|
23552
|
+
async function executeAgentMigrationAdoptPlan(plan, rebind, now = currentDate()) {
|
|
23553
|
+
assertGeneration(plan.generation);
|
|
23554
|
+
await assertFinalWorkspaceAvailable(plan.sourceWorkspacePath, plan.finalWorkspacePath);
|
|
23555
|
+
const verified = await verifyAgentMigrationAdoptPlan(plan);
|
|
23556
|
+
let migrationGeneration = plan.generation.migrationGeneration;
|
|
23557
|
+
migrationGeneration = await assertRebindResult(plan, migrationGeneration, await rebind.startTransfer({
|
|
23558
|
+
grantKey: plan.generation.grantKey,
|
|
23559
|
+
migrationGeneration
|
|
23560
|
+
}));
|
|
23561
|
+
await placeWorkspace(plan.sourceWorkspacePath, plan.finalWorkspacePath);
|
|
23562
|
+
migrationGeneration = await assertRebindResult(plan, migrationGeneration, await rebind.flipMachine({
|
|
23563
|
+
grantKey: plan.generation.grantKey,
|
|
23564
|
+
migrationGeneration
|
|
23565
|
+
}));
|
|
23566
|
+
const report = {
|
|
23567
|
+
schemaVersion: "agent-arrival/v1",
|
|
23568
|
+
agentId: plan.agentId,
|
|
23569
|
+
sourceKind: plan.sourceKind,
|
|
23570
|
+
grantKey: plan.generation.grantKey,
|
|
23571
|
+
migrationGeneration,
|
|
23572
|
+
sourceMachineId: plan.generation.sourceMachineId,
|
|
23573
|
+
targetMachineId: plan.generation.targetMachineId,
|
|
23574
|
+
manifestSha256: plan.manifestSha256,
|
|
23575
|
+
finalWorkspacePath: plan.finalWorkspacePath,
|
|
23576
|
+
verified,
|
|
23577
|
+
attestation: {
|
|
23578
|
+
noncePresent: Boolean(plan.attestationNonce),
|
|
23579
|
+
nonceVerified: Boolean(plan.attestationNonce && plan.observedAttestationNonce === plan.attestationNonce)
|
|
23580
|
+
},
|
|
23581
|
+
arrivedAt: now.toISOString()
|
|
23582
|
+
};
|
|
23583
|
+
const reportSha256 = await writeArrivalReport(plan.reportPath, report);
|
|
23584
|
+
await assertRebindResult(plan, migrationGeneration, await rebind.markArrived({
|
|
23585
|
+
grantKey: plan.generation.grantKey,
|
|
23586
|
+
migrationGeneration,
|
|
23587
|
+
reportPath: plan.reportPath,
|
|
23588
|
+
reportSha256
|
|
23589
|
+
}));
|
|
23590
|
+
return { report, reportPath: plan.reportPath, reportSha256 };
|
|
23591
|
+
}
|
|
23592
|
+
function hashAgentMigrationManifest(manifest) {
|
|
23593
|
+
return sha256Buffer3(Buffer.from(canonicalJson3(manifest), "utf8"));
|
|
23594
|
+
}
|
|
23595
|
+
function assertManifestIdentity(input) {
|
|
23596
|
+
if (input.manifest.schemaVersion !== AGENT_MIGRATION_BUNDLE_SCHEMA_VERSION) {
|
|
23597
|
+
throw new Error("MIGRATION_MANIFEST_SCHEMA_UNSUPPORTED");
|
|
22366
23598
|
}
|
|
22367
|
-
if (
|
|
22368
|
-
|
|
22369
|
-
return;
|
|
23599
|
+
if (input.manifest.agentId !== input.expectedAgentId) {
|
|
23600
|
+
throw new Error("MIGRATION_MANIFEST_AGENT_MISMATCH");
|
|
22370
23601
|
}
|
|
22371
|
-
sendJson(res, 405, { code: "migration_method_not_allowed" });
|
|
22372
23602
|
}
|
|
22373
|
-
function
|
|
22374
|
-
|
|
22375
|
-
|
|
22376
|
-
|
|
22377
|
-
|
|
22378
|
-
|
|
22379
|
-
|
|
23603
|
+
function assertGeneration(generation) {
|
|
23604
|
+
if (generation.localMachineId !== generation.targetMachineId) {
|
|
23605
|
+
throw new Error("MIGRATION_TARGET_MACHINE_MISMATCH");
|
|
23606
|
+
}
|
|
23607
|
+
if (generation.sourceMachineId === generation.targetMachineId) {
|
|
23608
|
+
throw new Error("MIGRATION_SOURCE_TARGET_MACHINE_MATCH");
|
|
23609
|
+
}
|
|
23610
|
+
if (!generation.grantKey || !generation.migrationGeneration) {
|
|
23611
|
+
throw new Error("MIGRATION_GENERATION_MISSING");
|
|
23612
|
+
}
|
|
22380
23613
|
}
|
|
22381
|
-
async function
|
|
22382
|
-
if (
|
|
22383
|
-
|
|
22384
|
-
|
|
23614
|
+
async function assertRebindResult(plan, expectedGeneration, result) {
|
|
23615
|
+
if (!result) return expectedGeneration;
|
|
23616
|
+
if (result.grantKey && result.grantKey !== plan.generation.grantKey) {
|
|
23617
|
+
throw new Error("MIGRATION_REBIND_GRANT_MISMATCH");
|
|
22385
23618
|
}
|
|
22386
|
-
if (
|
|
22387
|
-
|
|
22388
|
-
return;
|
|
23619
|
+
if (result.migrationGeneration !== void 0 && result.migrationGeneration.length === 0) {
|
|
23620
|
+
throw new Error("MIGRATION_REBIND_GENERATION_MISMATCH");
|
|
22389
23621
|
}
|
|
22390
|
-
|
|
22391
|
-
|
|
22392
|
-
stream = bundleStreamFactory(grant);
|
|
22393
|
-
} catch (err) {
|
|
22394
|
-
const code = err instanceof MigrationBundleStreamError ? err.code : "migration_bundle_stream_failed";
|
|
22395
|
-
sendJson(res, 500, { code });
|
|
22396
|
-
return;
|
|
23622
|
+
if (result.sourceMachineId && result.sourceMachineId !== plan.generation.sourceMachineId) {
|
|
23623
|
+
throw new Error("MIGRATION_REBIND_SOURCE_MACHINE_MISMATCH");
|
|
22397
23624
|
}
|
|
22398
|
-
|
|
22399
|
-
|
|
22400
|
-
res.on("close", () => {
|
|
22401
|
-
if (!completed && grant.state === "streaming") {
|
|
22402
|
-
grant.state = "interrupted";
|
|
22403
|
-
}
|
|
22404
|
-
});
|
|
22405
|
-
try {
|
|
22406
|
-
res.statusCode = 200;
|
|
22407
|
-
res.setHeader("Content-Type", "application/x-tar");
|
|
22408
|
-
res.setHeader("X-Raft-Manifest-Sha", grant.manifestSha256);
|
|
22409
|
-
res.setHeader("ETag", grant.bundleEtag);
|
|
22410
|
-
for await (const chunk of stream) {
|
|
22411
|
-
if (!res.write(chunk)) {
|
|
22412
|
-
await onceDrain(res);
|
|
22413
|
-
}
|
|
22414
|
-
}
|
|
22415
|
-
completed = true;
|
|
22416
|
-
grant.state = "consumed";
|
|
22417
|
-
res.end();
|
|
22418
|
-
} catch {
|
|
22419
|
-
if (!completed && grant.state === "streaming") {
|
|
22420
|
-
grant.state = "interrupted";
|
|
22421
|
-
}
|
|
22422
|
-
if (!res.headersSent) {
|
|
22423
|
-
sendJson(res, 500, { code: "migration_bundle_stream_failed" });
|
|
22424
|
-
} else {
|
|
22425
|
-
res.destroy();
|
|
22426
|
-
}
|
|
23625
|
+
if (result.targetMachineId && result.targetMachineId !== plan.generation.targetMachineId) {
|
|
23626
|
+
throw new Error("MIGRATION_REBIND_TARGET_MACHINE_MISMATCH");
|
|
22427
23627
|
}
|
|
23628
|
+
return result.migrationGeneration ?? expectedGeneration;
|
|
22428
23629
|
}
|
|
22429
|
-
function
|
|
22430
|
-
|
|
22431
|
-
|
|
22432
|
-
}
|
|
22433
|
-
|
|
22434
|
-
const
|
|
22435
|
-
|
|
22436
|
-
|
|
22437
|
-
if (
|
|
22438
|
-
|
|
22439
|
-
|
|
23630
|
+
async function verifyManifestFileEntry(sourceWorkspacePath, entry) {
|
|
23631
|
+
if (entry.source !== "workspace" || !entry.workspaceRelativePath) {
|
|
23632
|
+
throw new Error("MIGRATION_MANIFEST_ENTRY_NOT_WORKSPACE");
|
|
23633
|
+
}
|
|
23634
|
+
const relative = normalizeWorkspaceRelativePath2(entry.workspaceRelativePath);
|
|
23635
|
+
const entryPath = path21.join(sourceWorkspacePath, relative);
|
|
23636
|
+
const info = await lstat4(entryPath).catch(() => null);
|
|
23637
|
+
if (!info) throw new Error("MIGRATION_MANIFEST_FILE_MISSING");
|
|
23638
|
+
if (entry.kind === "symlink") {
|
|
23639
|
+
if (!info.isSymbolicLink()) throw new Error("MIGRATION_MANIFEST_KIND_MISMATCH");
|
|
23640
|
+
const linkTarget = await readlink3(entryPath);
|
|
23641
|
+
if (entry.linkTarget !== linkTarget) throw new Error("MIGRATION_MANIFEST_LINK_MISMATCH");
|
|
23642
|
+
return;
|
|
23643
|
+
}
|
|
23644
|
+
if (!info.isFile()) throw new Error("MIGRATION_MANIFEST_KIND_MISMATCH");
|
|
23645
|
+
if (entry.sizeBytes !== void 0 && entry.sizeBytes !== info.size) {
|
|
23646
|
+
throw new Error("MIGRATION_MANIFEST_FILE_SIZE_MISMATCH");
|
|
23647
|
+
}
|
|
23648
|
+
if (entry.sha256) {
|
|
23649
|
+
const actual = await sha256File2(entryPath);
|
|
23650
|
+
if (actual !== entry.sha256) throw new Error("MIGRATION_MANIFEST_FILE_SHA_MISMATCH");
|
|
23651
|
+
}
|
|
22440
23652
|
}
|
|
22441
|
-
function
|
|
22442
|
-
|
|
22443
|
-
|
|
23653
|
+
function normalizeWorkspaceRelativePath2(relativePath) {
|
|
23654
|
+
const normalized = path21.posix.normalize(relativePath.replaceAll("\\", "/"));
|
|
23655
|
+
if (normalized === "." || normalized.startsWith("../") || normalized === ".." || path21.isAbsolute(normalized)) {
|
|
23656
|
+
throw new Error("MIGRATION_MANIFEST_UNSAFE_PATH");
|
|
23657
|
+
}
|
|
23658
|
+
return normalized;
|
|
22444
23659
|
}
|
|
22445
|
-
function
|
|
22446
|
-
|
|
22447
|
-
|
|
22448
|
-
|
|
22449
|
-
"
|
|
22450
|
-
|
|
22451
|
-
|
|
22452
|
-
|
|
23660
|
+
async function assertFinalWorkspaceAvailable(sourceWorkspacePath, finalWorkspacePath) {
|
|
23661
|
+
if (path21.resolve(sourceWorkspacePath) === path21.resolve(finalWorkspacePath)) return;
|
|
23662
|
+
try {
|
|
23663
|
+
await access2(finalWorkspacePath);
|
|
23664
|
+
throw new Error("MIGRATION_WORKSPACE_ALREADY_EXISTS");
|
|
23665
|
+
} catch (err) {
|
|
23666
|
+
if (err instanceof Error && err.message === "MIGRATION_WORKSPACE_ALREADY_EXISTS") throw err;
|
|
23667
|
+
}
|
|
22453
23668
|
}
|
|
22454
|
-
function
|
|
22455
|
-
|
|
23669
|
+
async function placeWorkspace(sourceWorkspacePath, finalWorkspacePath) {
|
|
23670
|
+
if (path21.resolve(sourceWorkspacePath) === path21.resolve(finalWorkspacePath)) return;
|
|
23671
|
+
const stagingPath = `${finalWorkspacePath}.migration-${process.pid}-${Date.now()}`;
|
|
23672
|
+
await mkdir4(path21.dirname(finalWorkspacePath), { recursive: true });
|
|
23673
|
+
await cp(sourceWorkspacePath, stagingPath, { recursive: true, dereference: false, errorOnExist: true, force: false });
|
|
23674
|
+
await rename(stagingPath, finalWorkspacePath);
|
|
22456
23675
|
}
|
|
22457
|
-
function
|
|
22458
|
-
const
|
|
22459
|
-
|
|
22460
|
-
|
|
22461
|
-
|
|
23676
|
+
async function writeArrivalReport(reportPath, report) {
|
|
23677
|
+
const payload = `${canonicalJson3(report)}
|
|
23678
|
+
`;
|
|
23679
|
+
await mkdir4(path21.dirname(reportPath), { recursive: true });
|
|
23680
|
+
await writeFile4(reportPath, payload, { mode: 384 });
|
|
23681
|
+
return sha256Buffer3(Buffer.from(payload, "utf8"));
|
|
22462
23682
|
}
|
|
22463
|
-
function
|
|
22464
|
-
|
|
23683
|
+
async function sha256File2(filePath) {
|
|
23684
|
+
const hash = createHash10("sha256");
|
|
23685
|
+
const stream = createReadStream2(filePath);
|
|
23686
|
+
for await (const chunk of stream) {
|
|
23687
|
+
hash.update(chunk);
|
|
23688
|
+
}
|
|
23689
|
+
return hash.digest("hex");
|
|
22465
23690
|
}
|
|
22466
|
-
function
|
|
22467
|
-
return
|
|
23691
|
+
function sha256Buffer3(buffer) {
|
|
23692
|
+
return createHash10("sha256").update(buffer).digest("hex");
|
|
22468
23693
|
}
|
|
22469
|
-
function
|
|
22470
|
-
return JSON.stringify(
|
|
23694
|
+
function canonicalJson3(value) {
|
|
23695
|
+
return JSON.stringify(sortJsonValue3(value));
|
|
22471
23696
|
}
|
|
22472
|
-
function
|
|
22473
|
-
if (Array.isArray(value)) return value.map(
|
|
23697
|
+
function sortJsonValue3(value) {
|
|
23698
|
+
if (Array.isArray(value)) return value.map(sortJsonValue3);
|
|
22474
23699
|
if (!value || typeof value !== "object") return value;
|
|
22475
23700
|
return Object.keys(value).sort().reduce((result, key) => {
|
|
22476
|
-
result[key] =
|
|
23701
|
+
result[key] = sortJsonValue3(value[key]);
|
|
22477
23702
|
return result;
|
|
22478
23703
|
}, {});
|
|
22479
23704
|
}
|
|
22480
|
-
function
|
|
22481
|
-
return
|
|
23705
|
+
function sanitizePathSegment2(value) {
|
|
23706
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 128) || "migration";
|
|
23707
|
+
}
|
|
23708
|
+
|
|
23709
|
+
// src/secretFile.ts
|
|
23710
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
23711
|
+
import path22 from "path";
|
|
23712
|
+
function readSecretFileSync(filePath) {
|
|
23713
|
+
return readFileSync10(filePath, "utf8").trim();
|
|
22482
23714
|
}
|
|
22483
23715
|
|
|
22484
23716
|
// src/core.ts
|
|
@@ -22486,6 +23718,8 @@ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
|
|
|
22486
23718
|
var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels", "knowledge"];
|
|
22487
23719
|
var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
|
|
22488
23720
|
var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
|
|
23721
|
+
var MIGRATION_OBJECT_STORE_DOWNLOAD_RETRY_INITIAL_MS = 25;
|
|
23722
|
+
var MIGRATION_OBJECT_STORE_DOWNLOAD_RETRY_MAX_MS = 250;
|
|
22489
23723
|
var DAEMON_CORE_TRACE_ATTR_CONTRACTS = {
|
|
22490
23724
|
"daemon.lifecycle.start": {
|
|
22491
23725
|
spanAttrs: ["machine_dir_present", "local_trace_enabled"],
|
|
@@ -22607,7 +23841,7 @@ var DAEMON_CORE_TRACE_ATTR_CONTRACTS = {
|
|
|
22607
23841
|
spanAttrs: ["agentId", "event_kind", "runtime"]
|
|
22608
23842
|
}
|
|
22609
23843
|
};
|
|
22610
|
-
var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <
|
|
23844
|
+
var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key-file <path>";
|
|
22611
23845
|
var RunnerCredentialMintError2 = class extends Error {
|
|
22612
23846
|
code;
|
|
22613
23847
|
retryable;
|
|
@@ -22640,20 +23874,50 @@ function runnerCredentialErrorDetail2(error) {
|
|
|
22640
23874
|
retryable: true
|
|
22641
23875
|
};
|
|
22642
23876
|
}
|
|
23877
|
+
async function waitForAmbientBackoff(delayMs) {
|
|
23878
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
23879
|
+
}
|
|
22643
23880
|
async function waitForRunnerCredentialRetry2() {
|
|
22644
|
-
await
|
|
23881
|
+
await waitForAmbientBackoff(RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2);
|
|
23882
|
+
}
|
|
23883
|
+
function isRetryableMigrationObjectStoreDownloadStatus(status) {
|
|
23884
|
+
return status === 404 || status === 408 || status === 409 || status === 425 || status === 429 || status >= 500;
|
|
23885
|
+
}
|
|
23886
|
+
function migrationTransferFailureMessage(err) {
|
|
23887
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23888
|
+
return message.slice(0, 500);
|
|
23889
|
+
}
|
|
23890
|
+
async function migrationStepErrorSuffix(response) {
|
|
23891
|
+
try {
|
|
23892
|
+
const body = await response.clone().json();
|
|
23893
|
+
if (typeof body.code === "string" && body.code.length > 0) return body.code.slice(0, 120);
|
|
23894
|
+
if (typeof body.error === "string" && body.error.length > 0) return body.error.slice(0, 120);
|
|
23895
|
+
} catch {
|
|
23896
|
+
}
|
|
23897
|
+
return response.statusText || "unknown";
|
|
22645
23898
|
}
|
|
22646
23899
|
function parseDaemonCliArgs(args) {
|
|
22647
23900
|
let serverUrl = "";
|
|
22648
23901
|
let apiKey = "";
|
|
23902
|
+
let apiKeyFile = process.env.SLOCK_DAEMON_API_KEY_FILE ?? "";
|
|
22649
23903
|
for (let i = 0; i < args.length; i++) {
|
|
22650
23904
|
if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
|
|
22651
23905
|
if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
|
|
23906
|
+
if (args[i] === "--api-key-file" && args[i + 1]) apiKeyFile = args[++i];
|
|
23907
|
+
}
|
|
23908
|
+
if (!apiKey && apiKeyFile) {
|
|
23909
|
+
try {
|
|
23910
|
+
apiKey = readSecretFileSync(apiKeyFile);
|
|
23911
|
+
} catch {
|
|
23912
|
+
apiKey = "";
|
|
23913
|
+
}
|
|
22652
23914
|
}
|
|
22653
23915
|
if (!serverUrl || !apiKey) return null;
|
|
22654
23916
|
return { serverUrl, apiKey };
|
|
22655
23917
|
}
|
|
22656
23918
|
function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
23919
|
+
const baked = process.env.RAFT_DAEMON_VERSION;
|
|
23920
|
+
if (typeof baked === "string" && baked.length > 0) return baked;
|
|
22657
23921
|
try {
|
|
22658
23922
|
const require2 = createRequire3(moduleUrl);
|
|
22659
23923
|
return require2("../package.json").version;
|
|
@@ -22662,13 +23926,13 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
|
22662
23926
|
}
|
|
22663
23927
|
}
|
|
22664
23928
|
function resolveSlockCliPath(moduleUrl = import.meta.url) {
|
|
22665
|
-
const thisDir =
|
|
22666
|
-
const bundledDistPath =
|
|
23929
|
+
const thisDir = path23.dirname(fileURLToPath(moduleUrl));
|
|
23930
|
+
const bundledDistPath = path23.resolve(thisDir, "cli", "index.js");
|
|
22667
23931
|
try {
|
|
22668
23932
|
accessSync(bundledDistPath);
|
|
22669
23933
|
return bundledDistPath;
|
|
22670
23934
|
} catch {
|
|
22671
|
-
const workspaceDistPath =
|
|
23935
|
+
const workspaceDistPath = path23.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
|
|
22672
23936
|
accessSync(workspaceDistPath);
|
|
22673
23937
|
return workspaceDistPath;
|
|
22674
23938
|
}
|
|
@@ -22682,7 +23946,7 @@ function resolveSlockCliPathOrEmpty(moduleUrl = import.meta.url) {
|
|
|
22682
23946
|
}
|
|
22683
23947
|
async function runBundledSlockCli(argv) {
|
|
22684
23948
|
process.argv = [process.execPath, "slock", ...argv];
|
|
22685
|
-
await import("./dist-
|
|
23949
|
+
await import("./dist-CRXPFW5L.js");
|
|
22686
23950
|
}
|
|
22687
23951
|
function detectRuntimes(tracer = noopTracer) {
|
|
22688
23952
|
const ids = [];
|
|
@@ -22774,6 +24038,11 @@ function readPositiveIntegerEnv3(name, fallback) {
|
|
|
22774
24038
|
if (!Number.isFinite(parsed) || parsed < 1) return fallback;
|
|
22775
24039
|
return Math.floor(parsed);
|
|
22776
24040
|
}
|
|
24041
|
+
function hasAgentMigrationHttpTransportListenConfig(env = process.env) {
|
|
24042
|
+
return Boolean(
|
|
24043
|
+
env[AGENT_MIGRATION_TRANSPORT_HOST_ENV]?.trim() || env[AGENT_MIGRATION_TRANSPORT_PORT_ENV]?.trim() || env[AGENT_MIGRATION_TRANSPORT_PUBLIC_URL_ENV]?.trim()
|
|
24044
|
+
);
|
|
24045
|
+
}
|
|
22777
24046
|
function formatChannelTarget(msg) {
|
|
22778
24047
|
return msg.message.channel_type === "dm" ? `dm:@${msg.message.channel_name}` : `#${msg.message.channel_name}`;
|
|
22779
24048
|
}
|
|
@@ -22809,6 +24078,8 @@ function summarizeIncomingMessage(msg) {
|
|
|
22809
24078
|
return `(directory=${msg.directoryName})`;
|
|
22810
24079
|
case "machine:runtime_models:detect":
|
|
22811
24080
|
return `(runtime=${msg.runtime}, req=${msg.requestId})`;
|
|
24081
|
+
case "machine:migration_transport:lease":
|
|
24082
|
+
return `(agent=${msg.agentId}, migration=${msg.migrationId}, session=${msg.sessionId}, provider=${msg.provider}, role=${msg.role}, kind=${msg.transferKind}, url=${msg.url ? "set" : "missing"})`;
|
|
22812
24083
|
case "reminder.upsert":
|
|
22813
24084
|
return `(agent=${msg.agentId}, id=${msg.reminder.reminderId}, v${msg.reminder.version}, fireAt=${msg.reminder.fireAt})`;
|
|
22814
24085
|
case "reminder.cancel":
|
|
@@ -22838,6 +24109,12 @@ var DaemonCore = class {
|
|
|
22838
24109
|
agentManager;
|
|
22839
24110
|
connection;
|
|
22840
24111
|
reminderCache;
|
|
24112
|
+
migrationTransport;
|
|
24113
|
+
migrationTransportListenPromise = null;
|
|
24114
|
+
migrationTransportListening = false;
|
|
24115
|
+
migrationTransportUrl = null;
|
|
24116
|
+
migrationTransferLease = null;
|
|
24117
|
+
migrationTransferRuns = /* @__PURE__ */ new Set();
|
|
22841
24118
|
tracer;
|
|
22842
24119
|
injectedTracer;
|
|
22843
24120
|
machineLock = null;
|
|
@@ -22861,6 +24138,7 @@ var DaemonCore = class {
|
|
|
22861
24138
|
clock: options.reminderClock,
|
|
22862
24139
|
onFire: (job) => this.onReminderFire(job)
|
|
22863
24140
|
});
|
|
24141
|
+
this.migrationTransport = options.migrationTransport === void 0 ? hasAgentMigrationHttpTransportListenConfig() ? createAgentMigrationHttpTransport() : null : options.migrationTransport;
|
|
22864
24142
|
let connection;
|
|
22865
24143
|
this.agentsDataDir = options.dataDir ?? resolveSlockHomePath("agents", this.slockHome);
|
|
22866
24144
|
const traceUploadDisabled = process.env.SLOCK_DAEMON_TRACE_UPLOAD_DISABLED === "1";
|
|
@@ -22891,7 +24169,7 @@ var DaemonCore = class {
|
|
|
22891
24169
|
}
|
|
22892
24170
|
resolveMachineStateRoot() {
|
|
22893
24171
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
22894
|
-
if (this.options.dataDir) return
|
|
24172
|
+
if (this.options.dataDir) return path23.join(path23.dirname(this.options.dataDir), "machines");
|
|
22895
24173
|
return resolveDefaultMachineStateRoot();
|
|
22896
24174
|
}
|
|
22897
24175
|
shouldEnableLocalTrace() {
|
|
@@ -22919,7 +24197,7 @@ var DaemonCore = class {
|
|
|
22919
24197
|
sinks: [this.localTraceSink]
|
|
22920
24198
|
}));
|
|
22921
24199
|
this.agentManager.setTracer(this.tracer);
|
|
22922
|
-
this.agentManager.setCliTransportTraceDir(
|
|
24200
|
+
this.agentManager.setCliTransportTraceDir(path23.join(machineDir, "traces"));
|
|
22923
24201
|
}
|
|
22924
24202
|
installTraceBundleUploader(machineDir) {
|
|
22925
24203
|
if (!this.shouldEnableLocalTrace()) return;
|
|
@@ -22969,9 +24247,13 @@ var DaemonCore = class {
|
|
|
22969
24247
|
span.addEvent("daemon.machine_lock.acquired", { machine_dir_present: true });
|
|
22970
24248
|
span.end("ok");
|
|
22971
24249
|
}
|
|
24250
|
+
if (this.migrationTransport) {
|
|
24251
|
+
this.startMigrationTransport();
|
|
24252
|
+
}
|
|
22972
24253
|
try {
|
|
22973
24254
|
this.connection.connect();
|
|
22974
24255
|
} catch (err) {
|
|
24256
|
+
void this.stopMigrationTransport();
|
|
22975
24257
|
this.traceBundleUploader?.stop();
|
|
22976
24258
|
this.traceBundleUploader = null;
|
|
22977
24259
|
this.machineLock.release();
|
|
@@ -23010,6 +24292,7 @@ var DaemonCore = class {
|
|
|
23010
24292
|
shutdown_reason: shutdownReason
|
|
23011
24293
|
});
|
|
23012
24294
|
}
|
|
24295
|
+
await this.stopMigrationTransport();
|
|
23013
24296
|
this.connection.disconnect();
|
|
23014
24297
|
span.addEvent("daemon.connection.disconnect_requested");
|
|
23015
24298
|
this.machineLock?.release();
|
|
@@ -23035,6 +24318,432 @@ var DaemonCore = class {
|
|
|
23035
24318
|
});
|
|
23036
24319
|
span.end(status);
|
|
23037
24320
|
}
|
|
24321
|
+
getMigrationTransportReady() {
|
|
24322
|
+
const transferLease = this.getActiveMigrationTransferLease();
|
|
24323
|
+
if (transferLease) {
|
|
24324
|
+
return {
|
|
24325
|
+
provisioned: true,
|
|
24326
|
+
endpoint: transferLease.url,
|
|
24327
|
+
leaseSource: "server",
|
|
24328
|
+
provider: transferLease.provider,
|
|
24329
|
+
role: transferLease.role,
|
|
24330
|
+
transferKind: transferLease.transferKind,
|
|
24331
|
+
url: transferLease.url,
|
|
24332
|
+
expiresAt: transferLease.expiresAt,
|
|
24333
|
+
maxBytes: transferLease.maxBytes,
|
|
24334
|
+
observedAt: currentDate().toISOString()
|
|
24335
|
+
};
|
|
24336
|
+
}
|
|
24337
|
+
const endpoint = this.migrationTransportListening ? this.migrationTransportUrl : null;
|
|
24338
|
+
return {
|
|
24339
|
+
provisioned: Boolean(endpoint),
|
|
24340
|
+
endpoint,
|
|
24341
|
+
leaseSource: endpoint ? "env" : null,
|
|
24342
|
+
observedAt: currentDate().toISOString()
|
|
24343
|
+
};
|
|
24344
|
+
}
|
|
24345
|
+
startMigrationTransport() {
|
|
24346
|
+
if (!this.migrationTransport || this.migrationTransportListenPromise) return;
|
|
24347
|
+
this.migrationTransportListenPromise = this.migrationTransport.listen().then(({ url }) => {
|
|
24348
|
+
this.migrationTransportListening = true;
|
|
24349
|
+
this.migrationTransportUrl = url;
|
|
24350
|
+
logger.info(`[Slock Daemon] Agent migration HTTP transport listening: ${url}`);
|
|
24351
|
+
this.recordDaemonTrace("daemon.migration_transport.listen", {
|
|
24352
|
+
outcome: "listening",
|
|
24353
|
+
url_present: Boolean(url)
|
|
24354
|
+
});
|
|
24355
|
+
}).catch((err) => {
|
|
24356
|
+
this.migrationTransportListening = false;
|
|
24357
|
+
this.migrationTransportUrl = null;
|
|
24358
|
+
logger.error("[Slock Daemon] Agent migration HTTP transport failed to listen", err);
|
|
24359
|
+
this.recordDaemonTrace("daemon.migration_transport.listen", {
|
|
24360
|
+
outcome: "failed",
|
|
24361
|
+
error_class: err instanceof Error ? err.name : typeof err
|
|
24362
|
+
}, "error");
|
|
24363
|
+
});
|
|
24364
|
+
}
|
|
24365
|
+
async stopMigrationTransport() {
|
|
24366
|
+
if (!this.migrationTransport) return;
|
|
24367
|
+
const listenPromise = this.migrationTransportListenPromise;
|
|
24368
|
+
this.migrationTransportListenPromise = null;
|
|
24369
|
+
if (listenPromise) await listenPromise;
|
|
24370
|
+
if (!this.migrationTransportListening) return;
|
|
24371
|
+
try {
|
|
24372
|
+
await this.migrationTransport.close();
|
|
24373
|
+
this.migrationTransportUrl = null;
|
|
24374
|
+
logger.info("[Slock Daemon] Agent migration HTTP transport stopped");
|
|
24375
|
+
this.recordDaemonTrace("daemon.migration_transport.stop", { outcome: "stopped" });
|
|
24376
|
+
} catch (err) {
|
|
24377
|
+
logger.error("[Slock Daemon] Agent migration HTTP transport failed to stop", err);
|
|
24378
|
+
this.recordDaemonTrace("daemon.migration_transport.stop", {
|
|
24379
|
+
outcome: "failed",
|
|
24380
|
+
error_class: err instanceof Error ? err.name : typeof err
|
|
24381
|
+
}, "error");
|
|
24382
|
+
} finally {
|
|
24383
|
+
this.migrationTransportListening = false;
|
|
24384
|
+
this.migrationTransportUrl = null;
|
|
24385
|
+
}
|
|
24386
|
+
}
|
|
24387
|
+
getActiveMigrationTransferLease() {
|
|
24388
|
+
const lease = this.migrationTransferLease;
|
|
24389
|
+
if (!lease) return null;
|
|
24390
|
+
const expiresAtMs = Date.parse(lease.expiresAt);
|
|
24391
|
+
if (!Number.isFinite(expiresAtMs) || expiresAtMs <= currentTimeMs()) return null;
|
|
24392
|
+
return lease;
|
|
24393
|
+
}
|
|
24394
|
+
validateMigrationTransferLease(lease) {
|
|
24395
|
+
if (!lease.agentId.trim()) throw new Error("migration transfer lease agent id is required");
|
|
24396
|
+
if (!lease.migrationId.trim()) throw new Error("migration transfer lease migration id is required");
|
|
24397
|
+
if (!lease.migrationGeneration.trim()) throw new Error("migration transfer lease migrationGeneration is required");
|
|
24398
|
+
if (!lease.sessionId.trim()) throw new Error("migration transfer lease session id is required");
|
|
24399
|
+
if (lease.provider !== "object_store") throw new Error(`unsupported migration transfer provider: ${lease.provider}`);
|
|
24400
|
+
if (lease.leaseSource !== "server") throw new Error(`unsupported migration transfer lease source: ${lease.leaseSource}`);
|
|
24401
|
+
if (lease.role !== "source" && lease.role !== "target") throw new Error(`unsupported migration transfer role: ${lease.role}`);
|
|
24402
|
+
if (lease.transferKind !== "upload" && lease.transferKind !== "download") {
|
|
24403
|
+
throw new Error(`unsupported migration transfer kind: ${lease.transferKind}`);
|
|
24404
|
+
}
|
|
24405
|
+
if (!lease.url.trim()) throw new Error("migration transfer lease url is required");
|
|
24406
|
+
if (!lease.bearerToken.trim()) throw new Error("migration transfer lease bearer token is required");
|
|
24407
|
+
const expiresAtMs = Date.parse(lease.expiresAt);
|
|
24408
|
+
if (!lease.expiresAt.trim() || !Number.isFinite(expiresAtMs) || expiresAtMs <= currentTimeMs()) {
|
|
24409
|
+
throw new Error("migration transfer lease expiresAt must be a future timestamp");
|
|
24410
|
+
}
|
|
24411
|
+
if (!Number.isInteger(lease.maxBytes) || lease.maxBytes <= 0) {
|
|
24412
|
+
throw new Error("migration transfer lease maxBytes must be a positive integer");
|
|
24413
|
+
}
|
|
24414
|
+
}
|
|
24415
|
+
handleMigrationTransportLease(lease) {
|
|
24416
|
+
try {
|
|
24417
|
+
this.applyMigrationTransferLease(lease);
|
|
24418
|
+
} catch (err) {
|
|
24419
|
+
logger.error("[Slock Daemon] Failed to apply migration transport lease", err);
|
|
24420
|
+
this.recordDaemonTrace("daemon.migration_transport.lease", {
|
|
24421
|
+
outcome: "failed",
|
|
24422
|
+
lease_present: true,
|
|
24423
|
+
error_class: err instanceof Error ? err.name : typeof err
|
|
24424
|
+
}, "error");
|
|
24425
|
+
}
|
|
24426
|
+
}
|
|
24427
|
+
applyMigrationTransferLease(lease) {
|
|
24428
|
+
this.validateMigrationTransferLease(lease);
|
|
24429
|
+
if (this.migrationTransferLease?.agentId === lease.agentId && this.migrationTransferLease?.migrationId === lease.migrationId && this.migrationTransferLease?.migrationGeneration === lease.migrationGeneration && this.migrationTransferLease?.sessionId === lease.sessionId && this.migrationTransferLease?.role === lease.role) {
|
|
24430
|
+
this.migrationTransferLease = lease;
|
|
24431
|
+
this.emitReadyIfConnected();
|
|
24432
|
+
this.recordDaemonTrace("daemon.migration_transport.lease", {
|
|
24433
|
+
outcome: "unchanged",
|
|
24434
|
+
agent_id_present: true,
|
|
24435
|
+
migration_id_present: true,
|
|
24436
|
+
session_id_present: true,
|
|
24437
|
+
migration_generation: lease.migrationGeneration,
|
|
24438
|
+
provider: lease.provider,
|
|
24439
|
+
role: lease.role,
|
|
24440
|
+
transfer_kind: lease.transferKind
|
|
24441
|
+
});
|
|
24442
|
+
this.startMigrationTransferRun(lease);
|
|
24443
|
+
return;
|
|
24444
|
+
}
|
|
24445
|
+
this.migrationTransferLease = lease;
|
|
24446
|
+
this.emitReadyIfConnected();
|
|
24447
|
+
this.recordDaemonTrace("daemon.migration_transport.lease", {
|
|
24448
|
+
outcome: "applied",
|
|
24449
|
+
agent_id_present: true,
|
|
24450
|
+
migration_id_present: true,
|
|
24451
|
+
session_id_present: true,
|
|
24452
|
+
migration_generation: lease.migrationGeneration,
|
|
24453
|
+
provider: lease.provider,
|
|
24454
|
+
role: lease.role,
|
|
24455
|
+
transfer_kind: lease.transferKind,
|
|
24456
|
+
url_present: Boolean(lease.url),
|
|
24457
|
+
bearer_token_present: Boolean(lease.bearerToken)
|
|
24458
|
+
});
|
|
24459
|
+
this.startMigrationTransferRun(lease);
|
|
24460
|
+
}
|
|
24461
|
+
startMigrationTransferRun(lease) {
|
|
24462
|
+
const key = [
|
|
24463
|
+
lease.agentId,
|
|
24464
|
+
lease.migrationId,
|
|
24465
|
+
lease.migrationGeneration,
|
|
24466
|
+
lease.sessionId,
|
|
24467
|
+
lease.role
|
|
24468
|
+
].join(":");
|
|
24469
|
+
if (this.migrationTransferRuns.has(key)) return;
|
|
24470
|
+
this.migrationTransferRuns.add(key);
|
|
24471
|
+
void this.runMigrationTransferLease(lease).catch(async (err) => {
|
|
24472
|
+
logger.error("[Slock Daemon] Migration object-store transfer failed", err);
|
|
24473
|
+
this.recordDaemonTrace("daemon.migration_transport.object_store", {
|
|
24474
|
+
outcome: "failed",
|
|
24475
|
+
role: lease.role,
|
|
24476
|
+
transfer_kind: lease.transferKind,
|
|
24477
|
+
agent_id_present: Boolean(lease.agentId),
|
|
24478
|
+
migration_id_present: Boolean(lease.migrationId),
|
|
24479
|
+
session_id_present: Boolean(lease.sessionId),
|
|
24480
|
+
error_class: err instanceof Error ? err.name : typeof err
|
|
24481
|
+
}, "error");
|
|
24482
|
+
try {
|
|
24483
|
+
await this.reportMigrationTransportLost(lease, err);
|
|
24484
|
+
} catch (reportErr) {
|
|
24485
|
+
logger.error("[Slock Daemon] Failed to report migration transport loss", reportErr);
|
|
24486
|
+
this.recordDaemonTrace("daemon.migration_transport.object_store", {
|
|
24487
|
+
outcome: "transport_lost_report_failed",
|
|
24488
|
+
role: lease.role,
|
|
24489
|
+
transfer_kind: lease.transferKind,
|
|
24490
|
+
migration_id_present: Boolean(lease.migrationId),
|
|
24491
|
+
error_class: reportErr instanceof Error ? reportErr.name : typeof reportErr
|
|
24492
|
+
}, "error");
|
|
24493
|
+
}
|
|
24494
|
+
}).finally(() => {
|
|
24495
|
+
this.migrationTransferRuns.delete(key);
|
|
24496
|
+
});
|
|
24497
|
+
}
|
|
24498
|
+
async runMigrationTransferLease(lease) {
|
|
24499
|
+
if (lease.provider !== "object_store") return;
|
|
24500
|
+
if (lease.role === "source") {
|
|
24501
|
+
await this.uploadMigrationObjectStoreBundle(lease);
|
|
24502
|
+
return;
|
|
24503
|
+
}
|
|
24504
|
+
await this.downloadAndImportMigrationObjectStoreBundle(lease);
|
|
24505
|
+
}
|
|
24506
|
+
migrationObjectStoreHeaders(lease) {
|
|
24507
|
+
return {
|
|
24508
|
+
"X-Raft-Migration-Token": lease.bearerToken
|
|
24509
|
+
};
|
|
24510
|
+
}
|
|
24511
|
+
async uploadMigrationObjectStoreBundle(lease) {
|
|
24512
|
+
if (lease.transferKind !== "upload") {
|
|
24513
|
+
throw new Error("MIGRATION_OBJECT_STORE_SOURCE_KIND_MISMATCH");
|
|
24514
|
+
}
|
|
24515
|
+
const built = await buildAgentMigrationObjectStoreBundle({
|
|
24516
|
+
agentId: lease.agentId,
|
|
24517
|
+
slockHome: this.slockHome,
|
|
24518
|
+
workspacePath: path23.join(this.agentsDataDir, lease.agentId),
|
|
24519
|
+
maxBytes: lease.maxBytes
|
|
24520
|
+
});
|
|
24521
|
+
const response = await daemonFetch(lease.url, {
|
|
24522
|
+
method: "PUT",
|
|
24523
|
+
headers: {
|
|
24524
|
+
...this.migrationObjectStoreHeaders(lease),
|
|
24525
|
+
"Content-Type": AGENT_MIGRATION_OBJECT_STORE_CONTENT_TYPE
|
|
24526
|
+
},
|
|
24527
|
+
body: new Uint8Array(built.bundle)
|
|
24528
|
+
});
|
|
24529
|
+
if (!response.ok) {
|
|
24530
|
+
throw new Error(`MIGRATION_OBJECT_STORE_UPLOAD_FAILED:${response.status}`);
|
|
24531
|
+
}
|
|
24532
|
+
await this.reportMigrationSourceReady(lease, built.manifestSha256);
|
|
24533
|
+
this.recordDaemonTrace("daemon.migration_transport.object_store", {
|
|
24534
|
+
outcome: "uploaded",
|
|
24535
|
+
role: lease.role,
|
|
24536
|
+
transfer_kind: lease.transferKind,
|
|
24537
|
+
bundle_bytes: built.bundle.byteLength,
|
|
24538
|
+
max_bytes: lease.maxBytes
|
|
24539
|
+
});
|
|
24540
|
+
}
|
|
24541
|
+
async downloadAndImportMigrationObjectStoreBundle(lease) {
|
|
24542
|
+
if (lease.transferKind !== "download") {
|
|
24543
|
+
throw new Error("MIGRATION_OBJECT_STORE_TARGET_KIND_MISMATCH");
|
|
24544
|
+
}
|
|
24545
|
+
const bundle = await this.downloadMigrationObjectStoreBundleWithRetry(lease);
|
|
24546
|
+
if (bundle.byteLength > lease.maxBytes) {
|
|
24547
|
+
throw new Error("MIGRATION_OBJECT_STORE_BUNDLE_TOO_LARGE");
|
|
24548
|
+
}
|
|
24549
|
+
const staged = await stageAgentMigrationObjectStoreBundle({
|
|
24550
|
+
bundle,
|
|
24551
|
+
slockHome: this.slockHome,
|
|
24552
|
+
sessionId: lease.sessionId
|
|
24553
|
+
});
|
|
24554
|
+
const targetImport = await this.fetchMigrationTargetImportView(lease.migrationId);
|
|
24555
|
+
if (targetImport.agentId !== lease.agentId) {
|
|
24556
|
+
throw new Error("MIGRATION_TARGET_IMPORT_AGENT_MISMATCH");
|
|
24557
|
+
}
|
|
24558
|
+
if (targetImport.manifestSha256 && targetImport.manifestSha256 !== staged.manifestSha256) {
|
|
24559
|
+
throw new Error("MIGRATION_TARGET_IMPORT_MANIFEST_SHA_MISMATCH");
|
|
24560
|
+
}
|
|
24561
|
+
const plan = await buildAgentMigrationAdoptPlan({
|
|
24562
|
+
sourceKind: "staged_bundle",
|
|
24563
|
+
slockHome: this.slockHome,
|
|
24564
|
+
stagingWorkspacePath: staged.stagingWorkspacePath,
|
|
24565
|
+
finalWorkspacePath: path23.join(this.agentsDataDir, lease.agentId),
|
|
24566
|
+
manifest: staged.manifest,
|
|
24567
|
+
manifestSha256: staged.manifestSha256,
|
|
24568
|
+
generation: {
|
|
24569
|
+
grantKey: targetImport.grantKey,
|
|
24570
|
+
migrationGeneration: targetImport.migrationGeneration,
|
|
24571
|
+
sourceMachineId: targetImport.sourceMachineId,
|
|
24572
|
+
targetMachineId: targetImport.targetMachineId,
|
|
24573
|
+
localMachineId: targetImport.targetMachineId
|
|
24574
|
+
}
|
|
24575
|
+
});
|
|
24576
|
+
await executeAgentMigrationAdoptPlan(plan, this.migrationRebindClient(targetImport.grantKey));
|
|
24577
|
+
this.recordDaemonTrace("daemon.migration_transport.object_store", {
|
|
24578
|
+
outcome: "imported",
|
|
24579
|
+
role: lease.role,
|
|
24580
|
+
transfer_kind: lease.transferKind,
|
|
24581
|
+
bundle_bytes: bundle.byteLength,
|
|
24582
|
+
manifest_sha_present: true
|
|
24583
|
+
});
|
|
24584
|
+
}
|
|
24585
|
+
async downloadMigrationObjectStoreBundleWithRetry(lease) {
|
|
24586
|
+
const expiresAtMs = Date.parse(lease.expiresAt);
|
|
24587
|
+
let attempt = 0;
|
|
24588
|
+
let delayMs = MIGRATION_OBJECT_STORE_DOWNLOAD_RETRY_INITIAL_MS;
|
|
24589
|
+
let lastStatus = null;
|
|
24590
|
+
let lastErrorClass = null;
|
|
24591
|
+
while (currentTimeMs() < expiresAtMs) {
|
|
24592
|
+
attempt += 1;
|
|
24593
|
+
try {
|
|
24594
|
+
const response = await daemonFetch(lease.url, {
|
|
24595
|
+
method: "GET",
|
|
24596
|
+
headers: this.migrationObjectStoreHeaders(lease)
|
|
24597
|
+
});
|
|
24598
|
+
if (response.ok) {
|
|
24599
|
+
return Buffer.from(await response.arrayBuffer());
|
|
24600
|
+
}
|
|
24601
|
+
lastStatus = response.status;
|
|
24602
|
+
lastErrorClass = null;
|
|
24603
|
+
if (!isRetryableMigrationObjectStoreDownloadStatus(response.status)) {
|
|
24604
|
+
throw new Error(`MIGRATION_OBJECT_STORE_DOWNLOAD_FAILED:${response.status}`);
|
|
24605
|
+
}
|
|
24606
|
+
} catch (err) {
|
|
24607
|
+
if (err instanceof Error && err.message.startsWith("MIGRATION_OBJECT_STORE_DOWNLOAD_FAILED:")) {
|
|
24608
|
+
throw err;
|
|
24609
|
+
}
|
|
24610
|
+
lastErrorClass = err instanceof Error ? err.name : typeof err;
|
|
24611
|
+
}
|
|
24612
|
+
const remainingMs = expiresAtMs - currentTimeMs();
|
|
24613
|
+
if (remainingMs <= 0) break;
|
|
24614
|
+
const sleepMs = Math.min(delayMs, remainingMs);
|
|
24615
|
+
this.recordDaemonTrace("daemon.migration_transport.object_store", {
|
|
24616
|
+
outcome: "download_retry",
|
|
24617
|
+
role: lease.role,
|
|
24618
|
+
transfer_kind: lease.transferKind,
|
|
24619
|
+
attempt,
|
|
24620
|
+
status: lastStatus,
|
|
24621
|
+
error_class: lastErrorClass,
|
|
24622
|
+
retry_delay_ms: sleepMs
|
|
24623
|
+
});
|
|
24624
|
+
await waitForAmbientBackoff(sleepMs);
|
|
24625
|
+
delayMs = Math.min(delayMs * 2, MIGRATION_OBJECT_STORE_DOWNLOAD_RETRY_MAX_MS);
|
|
24626
|
+
}
|
|
24627
|
+
const suffix = lastStatus !== null ? String(lastStatus) : lastErrorClass ?? "unknown";
|
|
24628
|
+
throw new Error(`MIGRATION_OBJECT_STORE_DOWNLOAD_RETRY_EXHAUSTED:${suffix}`);
|
|
24629
|
+
}
|
|
24630
|
+
async reportMigrationTransportLost(lease, err) {
|
|
24631
|
+
const url = new URL(`/internal/computer/agent-migrations/by-id/${encodeURIComponent(lease.migrationId)}/transport-lost`, this.options.serverUrl);
|
|
24632
|
+
const response = await daemonFetch(url, {
|
|
24633
|
+
method: "POST",
|
|
24634
|
+
headers: {
|
|
24635
|
+
...this.internalComputerHeaders(),
|
|
24636
|
+
"Content-Type": "application/json"
|
|
24637
|
+
},
|
|
24638
|
+
body: JSON.stringify({
|
|
24639
|
+
role: lease.role,
|
|
24640
|
+
transferKind: lease.transferKind,
|
|
24641
|
+
message: migrationTransferFailureMessage(err)
|
|
24642
|
+
})
|
|
24643
|
+
});
|
|
24644
|
+
if (!response.ok) {
|
|
24645
|
+
throw new Error(`MIGRATION_TRANSPORT_LOST_REPORT_FAILED:${response.status}`);
|
|
24646
|
+
}
|
|
24647
|
+
this.recordDaemonTrace("daemon.migration_transport.object_store", {
|
|
24648
|
+
outcome: "transport_lost_reported",
|
|
24649
|
+
role: lease.role,
|
|
24650
|
+
transfer_kind: lease.transferKind,
|
|
24651
|
+
migration_id_present: Boolean(lease.migrationId)
|
|
24652
|
+
});
|
|
24653
|
+
}
|
|
24654
|
+
async reportMigrationSourceReady(lease, manifestSha256) {
|
|
24655
|
+
const url = new URL(`/internal/computer/agent-migrations/by-id/${encodeURIComponent(lease.migrationId)}/source-ready`, this.options.serverUrl);
|
|
24656
|
+
const response = await daemonFetch(url, {
|
|
24657
|
+
method: "POST",
|
|
24658
|
+
headers: {
|
|
24659
|
+
...this.internalComputerHeaders(),
|
|
24660
|
+
"Content-Type": "application/json"
|
|
24661
|
+
},
|
|
24662
|
+
body: JSON.stringify({
|
|
24663
|
+
manifestPath: `object-store:${lease.sessionId}/manifest.json`,
|
|
24664
|
+
manifestSha256
|
|
24665
|
+
})
|
|
24666
|
+
});
|
|
24667
|
+
if (!response.ok) {
|
|
24668
|
+
throw new Error(`MIGRATION_SOURCE_READY_REPORT_FAILED:${response.status}:${await migrationStepErrorSuffix(response)}`);
|
|
24669
|
+
}
|
|
24670
|
+
this.recordDaemonTrace("daemon.migration_transport.object_store", {
|
|
24671
|
+
outcome: "source_ready_reported",
|
|
24672
|
+
role: lease.role,
|
|
24673
|
+
transfer_kind: lease.transferKind,
|
|
24674
|
+
migration_id_present: Boolean(lease.migrationId)
|
|
24675
|
+
});
|
|
24676
|
+
}
|
|
24677
|
+
async fetchMigrationTargetImportView(migrationId) {
|
|
24678
|
+
const url = new URL(`/internal/computer/agent-migrations/by-id/${encodeURIComponent(migrationId)}`, this.options.serverUrl);
|
|
24679
|
+
const response = await daemonFetch(url, {
|
|
24680
|
+
method: "GET",
|
|
24681
|
+
headers: this.internalComputerHeaders()
|
|
24682
|
+
});
|
|
24683
|
+
if (!response.ok) {
|
|
24684
|
+
throw new Error(`MIGRATION_TARGET_IMPORT_LOOKUP_FAILED:${response.status}`);
|
|
24685
|
+
}
|
|
24686
|
+
const body = await response.json();
|
|
24687
|
+
if (!body.migration) throw new Error("MIGRATION_TARGET_IMPORT_VIEW_MISSING");
|
|
24688
|
+
return body.migration;
|
|
24689
|
+
}
|
|
24690
|
+
async fetchMigrationTargetImportViewByGrantKey(grantKey) {
|
|
24691
|
+
const url = new URL(`/internal/computer/agent-migrations/${encodeURIComponent(grantKey)}`, this.options.serverUrl);
|
|
24692
|
+
const response = await daemonFetch(url, {
|
|
24693
|
+
method: "GET",
|
|
24694
|
+
headers: this.internalComputerHeaders()
|
|
24695
|
+
});
|
|
24696
|
+
if (!response.ok) {
|
|
24697
|
+
throw new Error(`MIGRATION_TARGET_IMPORT_LOOKUP_FAILED:${response.status}`);
|
|
24698
|
+
}
|
|
24699
|
+
const body = await response.json();
|
|
24700
|
+
if (!body.migration) throw new Error("MIGRATION_TARGET_IMPORT_VIEW_MISSING");
|
|
24701
|
+
return body.migration;
|
|
24702
|
+
}
|
|
24703
|
+
migrationRebindClient(grantKey) {
|
|
24704
|
+
return {
|
|
24705
|
+
startTransfer: (input) => this.postMigrationTargetImportStep(grantKey, "start-transfer", input),
|
|
24706
|
+
flipMachine: (input) => this.postMigrationTargetImportStep(grantKey, "flip-machine", input),
|
|
24707
|
+
markArrived: (input) => this.postMigrationTargetImportStep(grantKey, "arrived", input)
|
|
24708
|
+
};
|
|
24709
|
+
}
|
|
24710
|
+
async postMigrationTargetImportStep(grantKey, step, body) {
|
|
24711
|
+
return await this.postMigrationTargetImportStepOnce(grantKey, step, body, true);
|
|
24712
|
+
}
|
|
24713
|
+
async postMigrationTargetImportStepOnce(grantKey, step, body, allowStartGenerationRefresh) {
|
|
24714
|
+
const url = new URL(`/internal/computer/agent-migrations/${encodeURIComponent(grantKey)}/${step}`, this.options.serverUrl);
|
|
24715
|
+
const response = await daemonFetch(url, {
|
|
24716
|
+
method: "POST",
|
|
24717
|
+
headers: {
|
|
24718
|
+
...this.internalComputerHeaders(),
|
|
24719
|
+
"Content-Type": "application/json"
|
|
24720
|
+
},
|
|
24721
|
+
body: JSON.stringify(body)
|
|
24722
|
+
});
|
|
24723
|
+
if (!response.ok) {
|
|
24724
|
+
const suffix = await migrationStepErrorSuffix(response);
|
|
24725
|
+
if (step === "start-transfer" && response.status === 409 && suffix === "migration_generation_stale" && allowStartGenerationRefresh) {
|
|
24726
|
+
const current = await this.fetchMigrationTargetImportViewByGrantKey(grantKey);
|
|
24727
|
+
if (current.state === "in_transit") return current;
|
|
24728
|
+
if (current.state === "ready" && current.migrationGeneration !== body.migrationGeneration) {
|
|
24729
|
+
return await this.postMigrationTargetImportStepOnce(grantKey, step, {
|
|
24730
|
+
...body,
|
|
24731
|
+
migrationGeneration: current.migrationGeneration
|
|
24732
|
+
}, false);
|
|
24733
|
+
}
|
|
24734
|
+
}
|
|
24735
|
+
throw new Error(`MIGRATION_TARGET_IMPORT_${step.toUpperCase().replace("-", "_")}_FAILED:${response.status}:${suffix}`);
|
|
24736
|
+
}
|
|
24737
|
+
const responseBody = await response.json();
|
|
24738
|
+
if (!responseBody.migration) throw new Error("MIGRATION_TARGET_IMPORT_VIEW_MISSING");
|
|
24739
|
+
return responseBody.migration;
|
|
24740
|
+
}
|
|
24741
|
+
internalComputerHeaders() {
|
|
24742
|
+
return {
|
|
24743
|
+
"Authorization": `Bearer ${this.options.apiKey}`,
|
|
24744
|
+
"X-Slock-Client": "daemon-migration-object-store"
|
|
24745
|
+
};
|
|
24746
|
+
}
|
|
23038
24747
|
withDaemonTraceScope(tracer) {
|
|
23039
24748
|
return {
|
|
23040
24749
|
startSpan: (name, options) => createTraceScopeTracer(tracer, this.daemonTraceScope(), {
|
|
@@ -23481,6 +25190,9 @@ var DaemonCore = class {
|
|
|
23481
25190
|
});
|
|
23482
25191
|
break;
|
|
23483
25192
|
}
|
|
25193
|
+
case "machine:migration_transport:lease":
|
|
25194
|
+
this.handleMigrationTransportLease(msg);
|
|
25195
|
+
break;
|
|
23484
25196
|
case "reminder.upsert":
|
|
23485
25197
|
this.reminderCache.upsert(msg.reminder);
|
|
23486
25198
|
break;
|
|
@@ -23534,27 +25246,20 @@ var DaemonCore = class {
|
|
|
23534
25246
|
agentId: job.ownerAgentId,
|
|
23535
25247
|
reminderId: job.reminderId,
|
|
23536
25248
|
version: job.version,
|
|
25249
|
+
// Accepted ambient clock: reminder telemetry reports the client wall-clock fire attempt.
|
|
23537
25250
|
firedAtClient: (/* @__PURE__ */ new Date()).toISOString()
|
|
23538
25251
|
});
|
|
23539
25252
|
}
|
|
23540
|
-
|
|
25253
|
+
emitReadyIfConnected() {
|
|
25254
|
+
if (this.connection.connected) this.emitReady();
|
|
25255
|
+
}
|
|
25256
|
+
emitReady() {
|
|
23541
25257
|
const { ids: runtimes, versions: runtimeVersions, diagnostics: runtimeDiagnostics = {} } = this.runtimeDetector();
|
|
23542
25258
|
const runtimeInfo = runtimes.map((id) => runtimeVersions[id] ? `${id} (${runtimeVersions[id]})` : id);
|
|
23543
25259
|
logger.info(`[Daemon] Detected runtimes: ${runtimeInfo.join(", ") || "none"}`);
|
|
23544
25260
|
for (const [runtime, diagnostic] of Object.entries(runtimeDiagnostics)) {
|
|
23545
25261
|
logger.warn(`[Daemon] Runtime ${runtime} diagnostic: ${diagnostic}`);
|
|
23546
25262
|
}
|
|
23547
|
-
if (!this.opencliWrappersRegenerated) {
|
|
23548
|
-
this.opencliWrappersRegenerated = true;
|
|
23549
|
-
try {
|
|
23550
|
-
const { scanned, rewritten } = regenerateExistingOpencliWrappers(this.agentsDataDir);
|
|
23551
|
-
if (scanned > 0) {
|
|
23552
|
-
logger.info(`[Daemon] Refreshed ${rewritten}/${scanned} opencli wrapper(s) to current self-healing form`);
|
|
23553
|
-
}
|
|
23554
|
-
} catch (err) {
|
|
23555
|
-
logger.warn(`[Daemon] opencli wrapper refresh skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
23556
|
-
}
|
|
23557
|
-
}
|
|
23558
25263
|
const runningAgentIds = this.agentManager.getRunningAgentIds();
|
|
23559
25264
|
const idleAgentSessions = this.agentManager.getIdleAgentSessionIds();
|
|
23560
25265
|
const runtimeProfileReports = this.agentManager.getAgentRuntimeProfileReports();
|
|
@@ -23566,7 +25271,8 @@ var DaemonCore = class {
|
|
|
23566
25271
|
hostname: this.options.hostname ?? os8.hostname(),
|
|
23567
25272
|
os: this.options.osDescription ?? `${os8.platform()} ${os8.arch()}`,
|
|
23568
25273
|
daemonVersion: this.daemonVersion,
|
|
23569
|
-
...this.computerVersion ? { computerVersion: this.computerVersion } : {}
|
|
25274
|
+
...this.computerVersion ? { computerVersion: this.computerVersion } : {},
|
|
25275
|
+
migrationTransport: this.getMigrationTransportReady()
|
|
23570
25276
|
});
|
|
23571
25277
|
this.recordDaemonTrace("daemon.ready.sent", {
|
|
23572
25278
|
runtimes_count: runtimes.length,
|
|
@@ -23574,6 +25280,23 @@ var DaemonCore = class {
|
|
|
23574
25280
|
idle_agents_count: idleAgentSessions.length,
|
|
23575
25281
|
runtime_profile_reports_count: runtimeProfileReports.length
|
|
23576
25282
|
});
|
|
25283
|
+
}
|
|
25284
|
+
handleConnect() {
|
|
25285
|
+
if (!this.opencliWrappersRegenerated) {
|
|
25286
|
+
this.opencliWrappersRegenerated = true;
|
|
25287
|
+
try {
|
|
25288
|
+
const { scanned, rewritten } = regenerateExistingOpencliWrappers(this.agentsDataDir);
|
|
25289
|
+
if (scanned > 0) {
|
|
25290
|
+
logger.info(`[Daemon] Refreshed ${rewritten}/${scanned} opencli wrapper(s) to current self-healing form`);
|
|
25291
|
+
}
|
|
25292
|
+
} catch (err) {
|
|
25293
|
+
logger.warn(`[Daemon] opencli wrapper refresh skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
25294
|
+
}
|
|
25295
|
+
}
|
|
25296
|
+
this.emitReady();
|
|
25297
|
+
const runningAgentIds = this.agentManager.getRunningAgentIds();
|
|
25298
|
+
const idleAgentSessions = this.agentManager.getIdleAgentSessionIds();
|
|
25299
|
+
const runtimeProfileReports = this.agentManager.getAgentRuntimeProfileReports();
|
|
23577
25300
|
if (this.options.onComputerUpgradeReconcile) {
|
|
23578
25301
|
void Promise.resolve().then(
|
|
23579
25302
|
() => this.options.onComputerUpgradeReconcile((done) => {
|
|
@@ -23651,10 +25374,27 @@ export {
|
|
|
23651
25374
|
resolveWorkspaceDirectoryPath,
|
|
23652
25375
|
scanWorkspaceDirectories,
|
|
23653
25376
|
deleteWorkspaceDirectory,
|
|
23654
|
-
|
|
23655
|
-
|
|
25377
|
+
detectLegacyDaemonSupervisor,
|
|
25378
|
+
detectLegacyDaemonSupervisorWithProbe,
|
|
25379
|
+
legacyDaemonSupervisorFallbackCommands,
|
|
25380
|
+
AGENT_MIGRATION_TRANSPORT_HOST_ENV,
|
|
25381
|
+
AGENT_MIGRATION_TRANSPORT_PORT_ENV,
|
|
25382
|
+
AGENT_MIGRATION_TRANSPORT_PUBLIC_URL_ENV,
|
|
25383
|
+
AGENT_MIGRATION_CONTROL_SEAM_ENV,
|
|
23656
25384
|
AgentMigrationGrantRegistry,
|
|
23657
25385
|
createAgentMigrationHttpTransport,
|
|
25386
|
+
resolveAgentMigrationHttpTransportListenOptions,
|
|
25387
|
+
resolveAgentMigrationControlSeamEnabled,
|
|
25388
|
+
AGENT_MIGRATION_BUNDLE_SCHEMA_VERSION,
|
|
25389
|
+
buildAgentMigrationExportManifest,
|
|
25390
|
+
AGENT_MIGRATION_OBJECT_STORE_BUNDLE_SCHEMA_VERSION,
|
|
25391
|
+
AGENT_MIGRATION_OBJECT_STORE_CONTENT_TYPE,
|
|
25392
|
+
buildAgentMigrationObjectStoreBundle,
|
|
25393
|
+
stageAgentMigrationObjectStoreBundle,
|
|
25394
|
+
buildAgentMigrationAdoptPlan,
|
|
25395
|
+
verifyAgentMigrationAdoptPlan,
|
|
25396
|
+
executeAgentMigrationAdoptPlan,
|
|
25397
|
+
hashAgentMigrationManifest,
|
|
23658
25398
|
DAEMON_CORE_TRACE_ATTR_CONTRACTS,
|
|
23659
25399
|
DAEMON_CLI_USAGE,
|
|
23660
25400
|
parseDaemonCliArgs,
|