@botiverse/raft-daemon 0.69.0 → 0.70.0-play.20260706142959

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.
@@ -1,5 +1,5 @@
1
1
  // src/core.ts
2
- import path19 from "path";
2
+ import path20 from "path";
3
3
  import os8 from "os";
4
4
  import { createRequire as createRequire3 } from "module";
5
5
  import { accessSync } from "fs";
@@ -1105,6 +1105,7 @@ function projectTraceScopeAttrs(scope) {
1105
1105
  addOptionalStringAttr(attrs, "route_pattern", scope.request?.routePattern);
1106
1106
  addOptionalStringAttr(attrs, "method", scope.request?.method);
1107
1107
  addOptionalStringAttr(attrs, "caller_kind", scope.request?.callerKind);
1108
+ addOptionalStringAttr(attrs, "user_id", scope.request?.userId);
1108
1109
  if (typeof scope.request?.userIdPresent === "boolean") {
1109
1110
  attrs.user_id_present = scope.request.userIdPresent;
1110
1111
  }
@@ -1280,10 +1281,24 @@ var RecordingActiveSpan = class {
1280
1281
  }
1281
1282
  addEvent(name, attrs) {
1282
1283
  if (this.ended) return;
1283
- this.events.push({
1284
+ const event = {
1284
1285
  name,
1285
1286
  timeMs: this.clock(),
1286
1287
  ...attrs ? { attrs } : {}
1288
+ };
1289
+ const eventIndex = this.events.length;
1290
+ this.events.push(event);
1291
+ this.sink.recordEvent?.({
1292
+ span: {
1293
+ context: this.context,
1294
+ name: this.name,
1295
+ surface: this.surface,
1296
+ kind: this.kind,
1297
+ startTimeMs: this.startTimeMs,
1298
+ ...this.attrs ? { attrs: this.attrs } : {}
1299
+ },
1300
+ event,
1301
+ eventIndex
1287
1302
  });
1288
1303
  }
1289
1304
  end(status = "ok", options = {}) {
@@ -1291,7 +1306,7 @@ var RecordingActiveSpan = class {
1291
1306
  this.ended = true;
1292
1307
  const endTimeMs = this.clock();
1293
1308
  const attrs = mergeAttrs(this.attrs, options.attrs);
1294
- this.sink.record({
1309
+ const completed = {
1295
1310
  context: this.context,
1296
1311
  name: this.name,
1297
1312
  surface: this.surface,
@@ -1302,7 +1317,9 @@ var RecordingActiveSpan = class {
1302
1317
  durationMs: Math.max(0, endTimeMs - this.startTimeMs),
1303
1318
  ...attrs ? { attrs } : {},
1304
1319
  events: [...this.events]
1305
- });
1320
+ };
1321
+ this.sink.recordSpanFact?.({ span: completed });
1322
+ this.sink.record(completed);
1306
1323
  }
1307
1324
  };
1308
1325
  function mergeAttrs(base, extra) {
@@ -1337,6 +1354,125 @@ function randomHex(length) {
1337
1354
  return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
1338
1355
  }
1339
1356
 
1357
+ // ../shared/src/tracing/eventRows.ts
1358
+ var PROMOTED_IDENTITY_ATTRS = [
1359
+ "server_id",
1360
+ "machine_id",
1361
+ "agent_id",
1362
+ "launch_id",
1363
+ "session_id",
1364
+ "request_id",
1365
+ "route_pattern",
1366
+ "caller_kind"
1367
+ ];
1368
+ var PROMOTED_CLOSED_ATTRS = [
1369
+ "outcome",
1370
+ "reason",
1371
+ "event_kind",
1372
+ "source",
1373
+ "authority",
1374
+ "activity_write_site",
1375
+ "activity_source",
1376
+ "hint_source",
1377
+ "resolved_activity",
1378
+ "previous_activity",
1379
+ "next_activity",
1380
+ "repair_kind",
1381
+ "action",
1382
+ "error_class",
1383
+ "status_bucket",
1384
+ "shadow_agent_id",
1385
+ "shadow_signal_site",
1386
+ "shadow_observation_class",
1387
+ "shadow_prior_projection",
1388
+ "shadow_projection",
1389
+ "shadow_legacy_outcome",
1390
+ "shadow_action",
1391
+ "shadow_reason",
1392
+ "shadow_direction",
1393
+ "shadow_plan_kind"
1394
+ ];
1395
+ var TRACE_EVENT_ROW_PROMOTED_ATTRS = [
1396
+ ...PROMOTED_IDENTITY_ATTRS,
1397
+ ...PROMOTED_CLOSED_ATTRS
1398
+ ];
1399
+
1400
+ // ../shared/src/tracing/fields.ts
1401
+ function defineTraceFields(definitions) {
1402
+ for (const definition of definitions) {
1403
+ if (definition.fieldClass === "content_safety" && !definition.contentRule) {
1404
+ throw new Error(`Trace content-safety field "${definition.key}" must define a contentRule`);
1405
+ }
1406
+ if ((definition.fieldClass === "query_axis" || definition.fieldClass === "family_query_axis") && definition.valueKind === "content") {
1407
+ throw new Error(`Trace query-axis field "${definition.key}" cannot be content`);
1408
+ }
1409
+ if (definition.fieldClass === "family_query_axis" && !definition.scope) {
1410
+ throw new Error(`Trace family query-axis field "${definition.key}" must define a family scope`);
1411
+ }
1412
+ }
1413
+ return definitions;
1414
+ }
1415
+ var TRACE_B0_FIELD_DEFINITIONS = defineTraceFields([
1416
+ { key: "row_kind", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "stable" },
1417
+ { key: "trace_id", fieldClass: "query_axis", placement: "span", valueKind: "identity", highCardinality: true },
1418
+ { key: "span_id", fieldClass: "query_axis", placement: "span", valueKind: "identity", highCardinality: true },
1419
+ { key: "parent_span_id", fieldClass: "query_axis", placement: "span", valueKind: "identity", highCardinality: true },
1420
+ { key: "span_name", fieldClass: "query_axis", placement: "span", valueKind: "closed_enum", enumBinding: "pending_457" },
1421
+ { key: "span_surface", fieldClass: "query_axis", placement: "span", valueKind: "closed_enum", enumBinding: "stable" },
1422
+ { key: "span_kind", fieldClass: "query_axis", placement: "span", valueKind: "closed_enum", enumBinding: "stable" },
1423
+ { key: "span_status", fieldClass: "query_axis", placement: "span", valueKind: "closed_enum", enumBinding: "stable" },
1424
+ { key: "event_name", fieldClass: "query_axis", placement: "event", valueKind: "closed_enum", enumBinding: "pending_457" },
1425
+ { key: "event_kind", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
1426
+ { key: "event_index", fieldClass: "query_axis", placement: "event", valueKind: "identity" },
1427
+ { key: "event_time", fieldClass: "query_axis", placement: "event", valueKind: "timestamp" },
1428
+ { key: "event_time_ms", fieldClass: "query_axis", placement: "event", valueKind: "numeric" },
1429
+ { key: "lifecycle_observed_at_ms", fieldClass: "query_axis", placement: "span_or_event", valueKind: "numeric" },
1430
+ { key: "activity_observed_at_ms", fieldClass: "query_axis", placement: "span_or_event", valueKind: "numeric" },
1431
+ { key: "advances_observed_clock", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
1432
+ { key: "duration_ms", fieldClass: "query_axis", placement: "span_or_event", valueKind: "numeric" },
1433
+ { key: "request_id", fieldClass: "query_axis", placement: "span", valueKind: "identity", highCardinality: true },
1434
+ { key: "route_pattern", fieldClass: "query_axis", placement: "span", valueKind: "route" },
1435
+ { key: "caller_kind", fieldClass: "query_axis", placement: "span", valueKind: "closed_enum", enumBinding: "stable" },
1436
+ { key: "server_id", fieldClass: "query_axis", placement: "span", valueKind: "identity", highCardinality: true },
1437
+ { key: "machine_id", fieldClass: "query_axis", placement: "span", valueKind: "identity", highCardinality: true },
1438
+ { key: "agent_id", fieldClass: "query_axis", placement: "span_or_event", valueKind: "identity", highCardinality: true },
1439
+ { key: "launch_id", fieldClass: "query_axis", placement: "span", valueKind: "identity", highCardinality: true },
1440
+ { key: "session_id", fieldClass: "query_axis", placement: "span", valueKind: "identity", highCardinality: true },
1441
+ { key: "observation_class", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
1442
+ { key: "action", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
1443
+ { key: "source", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
1444
+ { key: "served_from", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
1445
+ { key: "authority", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
1446
+ { key: "decided_by", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
1447
+ { key: "error_class", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
1448
+ { key: "query_name", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "query_registry" },
1449
+ { key: "phase", fieldClass: "query_axis", placement: "span_or_event", valueKind: "closed_enum", enumBinding: "pending_457" },
1450
+ { key: "hint_source", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity" },
1451
+ { key: "resolved_activity", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity" },
1452
+ { key: "previous_activity", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity" },
1453
+ { key: "next_activity", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity" },
1454
+ { key: "repair_kind", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity" },
1455
+ { key: "activity_write_site", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity" },
1456
+ { key: "activity_source", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity" },
1457
+ { key: "status_bucket", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:activity" },
1458
+ { key: "shadow_agent_id", fieldClass: "family_query_axis", placement: "event", valueKind: "identity", scope: "family:lifecycle_shadow", highCardinality: true },
1459
+ { key: "shadow_signal_site", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:lifecycle_shadow", enumBinding: "pending_457" },
1460
+ { key: "shadow_observation_class", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:lifecycle_shadow", enumBinding: "pending_457" },
1461
+ { key: "shadow_prior_projection", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:lifecycle_shadow", enumBinding: "pending_457" },
1462
+ { key: "shadow_projection", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:lifecycle_shadow", enumBinding: "pending_457" },
1463
+ { key: "shadow_legacy_outcome", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:lifecycle_shadow", enumBinding: "pending_457" },
1464
+ { key: "shadow_action", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:lifecycle_shadow", enumBinding: "pending_457" },
1465
+ { key: "shadow_reason", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:lifecycle_shadow", enumBinding: "pending_457" },
1466
+ { key: "shadow_direction", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:lifecycle_shadow", enumBinding: "pending_457" },
1467
+ { key: "shadow_plan_kind", fieldClass: "family_query_axis", placement: "event", valueKind: "closed_enum", scope: "family:lifecycle_shadow", enumBinding: "pending_457" },
1468
+ { key: "row_count", fieldClass: "detail", placement: "span_or_event", valueKind: "detail" },
1469
+ { key: "retry_count", fieldClass: "detail", placement: "span_or_event", valueKind: "detail" },
1470
+ { key: "error_message", fieldClass: "detail", placement: "span_or_event", valueKind: "detail", contentRule: "scrub_and_short_retention" },
1471
+ { key: "query_text", fieldClass: "content_safety", placement: "span_or_event", valueKind: "content", contentRule: "scrub_and_short_retention" },
1472
+ { key: "raw_payload", fieldClass: "content_safety", placement: "span_or_event", valueKind: "content", contentRule: "drop" },
1473
+ { key: "token", fieldClass: "content_safety", placement: "span_or_event", valueKind: "content", contentRule: "drop" }
1474
+ ]);
1475
+
1340
1476
  // ../shared/src/toolDisplay.ts
1341
1477
  var TOOL_DISPLAY_METADATA = {
1342
1478
  send_message: { logLabel: "Sending message", activityLabel: "Sending message\u2026", summaryKind: "message_target" },
@@ -1986,7 +2122,8 @@ var agentApiResolveChannelResponseSchema = passthroughObject({
1986
2122
  channelId: z2.string().trim().min(1)
1987
2123
  });
1988
2124
  var agentApiThreadUnfollowBodySchema = passthroughObject({
1989
- thread: z2.string().trim().min(1)
2125
+ thread: z2.string().trim().min(1),
2126
+ reason: z2.string().trim().min(1).max(200).optional()
1990
2127
  });
1991
2128
  var agentApiTaskClaimBodySchema = passthroughObject({
1992
2129
  channel: z2.string().trim().min(1),
@@ -2146,6 +2283,7 @@ var agentApiServerInfoResponseSchema = passthroughObject({
2146
2283
  serverId: z2.string(),
2147
2284
  machineId: z2.string().nullable().optional(),
2148
2285
  machineName: z2.string().nullable().optional(),
2286
+ machineDescription: z2.string().nullable().optional(),
2149
2287
  machineHostname: z2.string().nullable().optional(),
2150
2288
  machineOs: z2.string().nullable().optional(),
2151
2289
  daemonVersion: z2.string().nullable().optional(),
@@ -2225,7 +2363,12 @@ var agentApiIntegrationLoginResponseSchema = passthroughObject({
2225
2363
  status: z2.enum(["logged_in", "already_logged_in", "approval_required"]),
2226
2364
  service: agentApiIntegrationServiceSchema,
2227
2365
  scopes: z2.array(z2.string()),
2228
- requestId: z2.string(),
2366
+ requestId: z2.string().optional(),
2367
+ session: passthroughObject({
2368
+ status: z2.literal("stored"),
2369
+ source: z2.enum(["cache", "fresh"]),
2370
+ path: nullableStringSchema
2371
+ }).optional(),
2229
2372
  approval: passthroughObject({
2230
2373
  requestId: z2.string(),
2231
2374
  target: nullableStringSchema,
@@ -3003,6 +3146,14 @@ var daemonApiContract = {
3003
3146
  })
3004
3147
  };
3005
3148
 
3149
+ // ../shared/src/authRefreshTiming.ts
3150
+ var AUTH_REFRESH_ROTATED_REPLAY_GRACE_MS = 1e4;
3151
+ var AUTH_REFRESH_ROTATION_DISCOVERY_LAG_BUDGET_MS = 1e3;
3152
+ var AUTH_REFRESH_ROTATED_TOKEN_WAIT_MS = 4e3;
3153
+ var AUTH_REFRESH_ROTATION_RETRY_RTT_BUDGET_MS = 1e3;
3154
+ 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;
3155
+ var AUTH_REFRESH_ROTATION_REPLAY_GRACE_MARGIN_MS = AUTH_REFRESH_ROTATED_REPLAY_GRACE_MS - AUTH_REFRESH_ROTATION_LOSER_WORST_CHAIN_MS;
3156
+
3006
3157
  // ../shared/src/agentInbox.ts
3007
3158
  function formatAgentInboxDelta(rows, options = {}) {
3008
3159
  const totalPendingMessages = options.totalPendingMessages;
@@ -3182,6 +3333,55 @@ var SUPPORTED_TRANSLATION_LANGUAGE_SET = new Set(
3182
3333
  SUPPORTED_TRANSLATION_LANGUAGE_CODES
3183
3334
  );
3184
3335
 
3336
+ // ../shared/src/oauthScopes.ts
3337
+ var RAFT_OAUTH_SCOPE_CATALOG = {
3338
+ openid: {
3339
+ phase: "identity",
3340
+ defaultAllowed: true,
3341
+ publicDiscovery: true,
3342
+ requiresResource: false,
3343
+ label: "OpenID identity"
3344
+ },
3345
+ profile: {
3346
+ phase: "identity",
3347
+ defaultAllowed: true,
3348
+ publicDiscovery: true,
3349
+ requiresResource: false,
3350
+ label: "Basic profile"
3351
+ },
3352
+ identity: {
3353
+ phase: "identity",
3354
+ defaultAllowed: true,
3355
+ publicDiscovery: true,
3356
+ requiresResource: false,
3357
+ label: "Raft identity card"
3358
+ },
3359
+ "agent:event:write": {
3360
+ phase: "agent_inbound",
3361
+ defaultAllowed: false,
3362
+ publicDiscovery: true,
3363
+ requiresResource: true,
3364
+ label: "Send structured events to an agent"
3365
+ },
3366
+ "agent:notification:write": {
3367
+ phase: "agent_inbound",
3368
+ defaultAllowed: false,
3369
+ publicDiscovery: true,
3370
+ requiresResource: true,
3371
+ label: "Send notifications to an agent"
3372
+ },
3373
+ "agent:action_request:write": {
3374
+ phase: "agent_inbound",
3375
+ defaultAllowed: false,
3376
+ publicDiscovery: false,
3377
+ requiresResource: true,
3378
+ label: "Request agent action"
3379
+ }
3380
+ };
3381
+ var RAFT_OAUTH_SCOPES_SUPPORTED = Object.keys(RAFT_OAUTH_SCOPE_CATALOG);
3382
+ var RAFT_OAUTH_DEFAULT_ALLOWED_SCOPES = RAFT_OAUTH_SCOPES_SUPPORTED.filter((scope) => RAFT_OAUTH_SCOPE_CATALOG[scope].defaultAllowed);
3383
+ var RAFT_OAUTH_PUBLIC_DISCOVERY_SCOPES = RAFT_OAUTH_SCOPES_SUPPORTED.filter((scope) => RAFT_OAUTH_SCOPE_CATALOG[scope].publicDiscovery);
3384
+
3185
3385
  // ../shared/src/testing/failpoints.ts
3186
3386
  var NoopFailpointRegistry = class {
3187
3387
  get enabled() {
@@ -3739,6 +3939,8 @@ var DISPLAY_PLAN_CONFIG = {
3739
3939
  }
3740
3940
  };
3741
3941
  var FREE_MONTHLY_FILE_UPLOAD_LIMIT_BYTES = 100 * 1024 * 1024;
3942
+ var FREE_SINGLE_FILE_UPLOAD_LIMIT_BYTES = 50 * 1024 * 1024;
3943
+ var PRO_SINGLE_FILE_UPLOAD_LIMIT_BYTES = 200 * 1024 * 1024;
3742
3944
  var TRIAL_START_DATE = /* @__PURE__ */ new Date("2026-04-18T00:00:00Z");
3743
3945
  var TRIAL_END_DATE = /* @__PURE__ */ new Date("2026-06-23T12:00:00Z");
3744
3946
  var TRIAL_DURATION_DAYS = (TRIAL_END_DATE.getTime() - TRIAL_START_DATE.getTime()) / (24 * 60 * 60 * 1e3);
@@ -4018,8 +4220,8 @@ async function executeResponseRequest(url, init, {
4018
4220
  }
4019
4221
 
4020
4222
  // src/directUploadCapability.ts
4021
- function joinUrl(base, path20) {
4022
- return `${base.replace(/\/+$/, "")}${path20}`;
4223
+ function joinUrl(base, path21) {
4224
+ return `${base.replace(/\/+$/, "")}${path21}`;
4023
4225
  }
4024
4226
  function jsonHeaders(apiKey) {
4025
4227
  return {
@@ -4237,7 +4439,7 @@ Threads are sub-conversations attached to a specific message. They let you discu
4237
4439
  - When you receive a message from a thread (the target has a \`:shortid\` suffix), **always reply using that same target** to keep the conversation in the thread.
4238
4440
  - **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=00000000 ...]\`, reply with \`raft message send --target "#general:00000000" <<'${D}'\` followed by the message body and \`${D}\`. The thread will be auto-created if it doesn't exist yet. Example IDs like \`00000000\` are placeholders; real message IDs come from received messages.
4239
4441
  - When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
4240
- - You can read thread history: \`raft message read --channel "#general:00000000"\`
4442
+ - You can read thread history: \`raft message read --target "#general:00000000"\`
4241
4443
  - You can stop receiving ordinary delivery for a thread with \`raft thread unfollow --target "#general:00000000"\`. Only do this when your work in that thread is clearly complete or no longer relevant.
4242
4444
  - Threads cannot be nested \u2014 you cannot start a thread inside a thread.`;
4243
4445
  }
@@ -4245,7 +4447,7 @@ function buildDiscoverySection() {
4245
4447
  return `### Discovering people and channels
4246
4448
 
4247
4449
  Call \`raft server info\` to see all channels in this server, which ones you have joined, other agents, and humans.
4248
- Visible public channels may appear even when \`joined=false\`. In that state you can still inspect them with \`raft message read\` and \`raft channel members\`, but you cannot send messages there or receive ordinary channel delivery until you join with \`raft channel join --target "#channel-name"\`. Private channels require a human with access to add you. To leave a regular channel you have joined, use \`raft channel leave --target "#channel-name"\`. To mute ordinary Activity delivery without leaving a regular channel, use \`raft channel mute "#channel-name"\`; personal @mentions and DMs still pierce (a task pierces only when it personally @mentions you), and thread following is separate. To reverse that setting, use \`raft channel unmute "#channel-name"\`. To stop following a thread without leaving its parent channel, use \`raft thread unfollow --target "#channel-name:shortid"\`.
4450
+ Visible public channels may appear even when \`joined=false\`. In that state you can still inspect them with \`raft message read\` and \`raft channel members\`, but you cannot send messages there or receive ordinary channel delivery until you join with \`raft channel join --target "#channel-name"\`. Private channels require a human with access to add you. To leave a regular channel you have joined, use \`raft channel leave --target "#channel-name"\`. To mute ordinary Activity delivery without leaving a regular channel, use \`raft channel mute --target "#channel-name"\`; personal @mentions and DMs still pierce (a task pierces only when it personally @mentions you), and thread following is separate. To reverse that setting, use \`raft channel unmute --target "#channel-name"\`. To stop following a thread without leaving its parent channel, use \`raft thread unfollow --target "#channel-name:shortid"\`.
4249
4451
  Private channels are membership-gated. If \`raft server info\` shows a channel as private, treat its name, members, and content as private to that channel; do not disclose that information in other channels, DMs, summaries, or task reports unless a human explicitly asks within an authorized context. In \`raft channel members\`, human role labels such as owner/admin show server-level authority; no role label means ordinary member.`;
4250
4452
  }
4251
4453
  function buildChannelAwarenessSection() {
@@ -4259,9 +4461,9 @@ Each channel has a **name** and optionally a **description** that define its pur
4259
4461
  function buildReadingHistorySection() {
4260
4462
  return `### Reading history
4261
4463
 
4262
- \`raft message read --channel "#channel-name"\` or \`raft message read --channel dm:@peer-name\` or \`raft message read --channel "#channel:shortid"\`
4464
+ \`raft message read --target "#channel-name"\` or \`raft message read --target dm:@peer-name\` or \`raft message read --target "#channel:shortid"\`
4263
4465
 
4264
- To jump directly to a specific hit with nearby context, use \`raft message read --channel "..." --around "messageId"\` or \`raft message read --channel "..." --around 12345\`.`;
4466
+ To jump directly to a specific hit with nearby context, use \`raft message read --target "..." --around "messageId"\` or \`raft message read --target "..." --around 12345\`.`;
4265
4467
  }
4266
4468
  function buildHistoricalReferencesSection() {
4267
4469
  return `### Historical references
@@ -4290,7 +4492,7 @@ Only top-level channel / DM messages can become tasks. Messages inside threads a
4290
4492
  **Assignee** is independent from status \u2014 a task can be claimed or unclaimed at any status except \`done\`.
4291
4493
 
4292
4494
  **Workflow:**
4293
- 1. Receive a message that requires action \u2192 claim it first (by task number if already a task, or by message ID if it's a regular message). Use repeat flags: \`raft task claim --channel "#channel" --number 1 --number 2\` or \`raft task claim --channel "#channel" --message-id abc12345\`.
4495
+ 1. Receive a message that requires action \u2192 claim it first (by task number if already a task, or by message ID if it's a regular message). Use repeat flags: \`raft task claim --target "#channel" --number 1 --number 2\` or \`raft task claim --target "#channel" --message-id abc12345\`.
4294
4496
  2. If the claim fails, someone else is working on it \u2014 do not work on that task unless an owner/admin explicitly redirects it to you
4295
4497
  3. Post updates in the task's thread: \`raft message send --target "#channel:msgShortId" <<'${D}'\` followed by the message body and \`${D}\`
4296
4498
  4. When done, set status to \`in_review\` so a human can validate via \`raft task update\`
@@ -4302,7 +4504,7 @@ Only top-level channel / DM messages can become tasks. Messages inside threads a
4302
4504
  - \`raft task create\` only creates the task \u2014 to own it, call \`raft task claim\` afterward.
4303
4505
  - Typical uses for \`raft task create\` are breaking down a larger task into parallel subtasks, or batch-creating genuinely new work for others to claim.
4304
4506
  - If someone already sent the work item as a message, just claim that existing message/task instead of creating a new one.
4305
- - If the work already exists as a message, reuse it via \`raft task claim --channel "#channel" --message-id abc12345\`.
4507
+ - If the work already exists as a message, reuse it via \`raft task claim --target "#channel" --message-id abc12345\`.
4306
4508
 
4307
4509
  **Creating new tasks:**
4308
4510
  - The task system exists to prevent duplicate work. If you see an existing task for the work, either claim that task or leave it alone.
@@ -4521,7 +4723,7 @@ function buildPrompt(config, opts) {
4521
4723
  const channelAwarenessSection = cliGuideSections.channelAwareness;
4522
4724
  const thirdPartyIntegrationsSection = `### Third-party integrations
4523
4725
 
4524
- If a built-in Slock app or registered third-party service requires login, use Slock Agent Login through the CLI instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app or built-in Slock app, first run \`raft integration list\` and match the app to a listed service before browsing the app. Use \`raft integration login --service <service>\` to provision or reuse your agent login for that service. If the service exposes manifest-backed HTTP API actions, prefer \`raft integration invoke --service <service> --list-actions\` and then \`raft integration invoke --service <service> --action <name>\`; the CLI performs the stateless Agent Login callback handoff and service-session setup for you. If the service exposes an agent behavior manifest and you need to run its local CLI, run \`raft integration env --service <service>\` before invoking that CLI; if it prints exports, apply them first so service credentials stay under a per-agent profile HOME/XDG tree instead of the host user's global HOME. If it reports that no local env is required, do not invent HOME/XDG overrides; for HTTP API action services, use \`raft integration invoke\` instead. If it fails, do not run that local CLI with the host user's HOME; report that the service manifest is unsupported. Slock does not execute local commands from remote manifests automatically. If the CLI reports that the \`integration\` command is unknown, the local daemon/CLI is too old for Slock Agent Login; report that the machine must be upgraded/restarted instead of calling internal HTTP endpoints yourself. When \`raft integration login\` returns \`Agent login ready\` or \`Already logged in\`, the agent-side login is ready. If the output includes a service callback handoff URL, treat it as a stateless Login with Raft API-action session handoff URL; prefer \`raft integration invoke\` over manually opening or curling it. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow, use internal request IDs as OAuth callback codes outside the documented CLI flow, call internal Slock integration endpoints directly, or call third-party exchange endpoints unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use \`raft profile show\`. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the listed service / Slock Agent Login path.`;
4726
+ If a built-in Slock app or registered third-party service requires login, use Slock Agent Login through the CLI instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app or built-in Slock app, first run \`raft integration list\` and match the app to a listed service before browsing the app. Use \`raft integration login --service <service>\` to provision or reuse your agent login for that service; the CLI consumes the one-time Agent Login handoff internally and stores the service-owned session for this agent, so the successful output should be \`Agent login ready\` / \`Already logged in\`, not a raw request code you need to keep. If the service exposes manifest-backed HTTP API actions, prefer \`raft integration invoke --service <service> --list-actions\` and then \`raft integration invoke --service <service> --action <name>\`; the CLI uses the stored service session or refreshes it internally. If the service exposes an agent behavior manifest and you need to run its local CLI, run \`raft integration env --service <service>\` before invoking that CLI; if it prints exports, apply them first so service credentials stay under a per-agent profile HOME/XDG tree instead of the host user's global HOME. If it reports that no local env is required, do not invent HOME/XDG overrides; for HTTP API action services, use \`raft integration invoke\` instead. If it fails, do not run that local CLI with the host user's HOME; report that the service manifest is unsupported. Slock does not execute local commands from remote manifests automatically. If the CLI reports that the \`integration\` command is unknown, the local daemon/CLI is too old for Slock Agent Login; report that the machine must be upgraded/restarted instead of calling internal HTTP endpoints yourself. Do not store or reuse raw \`oauth_access_request\` request codes; those are one-time compatibility handoffs for services to exchange once for their own session/token. Prefer \`raft integration invoke\` over manually opening or curling callback URLs. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow, use internal request IDs as OAuth callback codes outside the documented CLI flow, call internal Slock integration endpoints directly, or call third-party exchange endpoints unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use \`raft profile show\`. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the listed service / Slock Agent Login path.`;
4525
4727
  const readingHistorySection = cliGuideSections.readingHistory;
4526
4728
  const historicalReferenceSection = cliGuideSections.historicalReferences;
4527
4729
  const tasksSection = cliGuideSections.tasks;
@@ -4709,6 +4911,19 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
4709
4911
  return candidates.filter((candidate) => existsSync(candidate.path));
4710
4912
  }
4711
4913
 
4914
+ // src/authEnv.ts
4915
+ var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
4916
+ var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
4917
+ function scrubDaemonAuthEnv(env) {
4918
+ delete env[DAEMON_API_KEY_ENV];
4919
+ return env;
4920
+ }
4921
+ function scrubDaemonChildEnv(env) {
4922
+ delete env[DAEMON_API_KEY_ENV];
4923
+ delete env[SLOCK_AGENT_TOKEN_ENV];
4924
+ return env;
4925
+ }
4926
+
4712
4927
  // src/agentCredentialProxy.ts
4713
4928
  import { randomBytes } from "crypto";
4714
4929
  import http from "http";
@@ -5652,7 +5867,7 @@ function shortMessageId2(value) {
5652
5867
  return value.slice(0, 8);
5653
5868
  }
5654
5869
  function normalizeSenderType(value) {
5655
- return value === "human" || value === "agent" || value === "system" ? value : void 0;
5870
+ return value === "human" || value === "agent" || value === "system" || value === "third_party_app" ? value : void 0;
5656
5871
  }
5657
5872
  function nonEmptyString(value) {
5658
5873
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
@@ -6040,6 +6255,10 @@ async function readRequestBody(req) {
6040
6255
  function messageSeq3(message) {
6041
6256
  return Number(message.seq ?? 0);
6042
6257
  }
6258
+ function isThirdPartyAgentEvent(message) {
6259
+ const thirdPartyEvent = message.third_party_event;
6260
+ return typeof thirdPartyEvent?.id === "string" && thirdPartyEvent.id.length > 0;
6261
+ }
6043
6262
  function localAgentApiInboxResponse(registration) {
6044
6263
  const coordinator = registration.inboxCoordinator;
6045
6264
  if (!coordinator) return void 0;
@@ -6090,6 +6309,7 @@ function localAgentApiEventsResponse(registration, target) {
6090
6309
  const normalized = sortInboxMessagesBySeq(normalizeInboxVisibleMessages(pending));
6091
6310
  const filtered = parsedQuery.sinceSeq !== null ? normalized.filter((message) => {
6092
6311
  const seq = messageSeq3(message);
6312
+ if ((!Number.isFinite(seq) || seq <= 0) && isThirdPartyAgentEvent(message)) return true;
6093
6313
  return Number.isFinite(seq) && seq > parsedQuery.sinceSeq;
6094
6314
  }) : normalized;
6095
6315
  const events = filtered.slice(0, parsedQuery.limit);
@@ -6283,7 +6503,9 @@ var LOOPBACK_NO_PROXY = "127.0.0.1,localhost";
6283
6503
  var CLI_TRANSPORT_TRACE_DIR_ENV = "SLOCK_CLI_TRANSPORT_TRACE_DIR";
6284
6504
  var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
6285
6505
  var RAW_CREDENTIAL_ENV_DENYLIST = [
6286
- "SLOCK_AGENT_CREDENTIAL_KEY"
6506
+ "SLOCK_AGENT_TOKEN",
6507
+ "SLOCK_AGENT_CREDENTIAL_KEY",
6508
+ "SLOCK_AGENT_CREDENTIAL_KEY_FILE"
6287
6509
  ];
6288
6510
  var WORKSPACE_CLI_TRANSPORT_FILENAMES = [
6289
6511
  "agent-token",
@@ -6638,7 +6860,7 @@ set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
6638
6860
  SLOCK_SERVER_URL: ctx.config.serverUrl,
6639
6861
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
6640
6862
  };
6641
- delete spawnEnv.SLOCK_AGENT_TOKEN;
6863
+ scrubDaemonChildEnv(spawnEnv);
6642
6864
  for (const key of RAW_CREDENTIAL_ENV_DENYLIST) {
6643
6865
  delete spawnEnv[key];
6644
6866
  }
@@ -7206,7 +7428,7 @@ function requiresWindowsShell(command, platform = process.platform) {
7206
7428
  }
7207
7429
  function resolveCommandOnPath(command, deps = {}) {
7208
7430
  const platform = deps.platform ?? process.platform;
7209
- const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
7431
+ const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
7210
7432
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
7211
7433
  const existsSyncFn = deps.existsSyncFn ?? existsSync3;
7212
7434
  if (platform === "win32") {
@@ -7232,7 +7454,7 @@ function firstExistingPath(candidates, deps = {}) {
7232
7454
  return null;
7233
7455
  }
7234
7456
  function readCommandVersion(command, args = [], deps = {}) {
7235
- const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
7457
+ const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
7236
7458
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
7237
7459
  try {
7238
7460
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -7658,7 +7880,7 @@ function codexNotificationDiagnosticEvent(message) {
7658
7880
  return null;
7659
7881
  }
7660
7882
  const sessionId = codexMessageThreadId(message);
7661
- const path20 = boundedString(params.path);
7883
+ const path21 = boundedString(params.path);
7662
7884
  return {
7663
7885
  kind: "runtime_diagnostic",
7664
7886
  severity: "warning",
@@ -7666,7 +7888,7 @@ function codexNotificationDiagnosticEvent(message) {
7666
7888
  itemType: message.method,
7667
7889
  message: diagnosticMessage,
7668
7890
  ...details ? { details } : {},
7669
- ...path20 ? { path: path20 } : {},
7891
+ ...path21 ? { path: path21 } : {},
7670
7892
  ...params.range !== void 0 ? { range: params.range } : {},
7671
7893
  payloadBytes: payloadBytes(params),
7672
7894
  ...sessionId ? { sessionId } : {}
@@ -9316,11 +9538,11 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
9316
9538
  return parseCursorModelsOutput(String(result.stdout || ""));
9317
9539
  }
9318
9540
  function buildCursorModelProbeEnv(deps = {}) {
9319
- return withWindowsUserEnvironment({
9541
+ return scrubDaemonChildEnv(withWindowsUserEnvironment({
9320
9542
  ...deps.env ?? process.env,
9321
9543
  FORCE_COLOR: "0",
9322
9544
  NO_COLOR: "1"
9323
- }, deps);
9545
+ }, deps));
9324
9546
  }
9325
9547
  function runCursorModelsCommand() {
9326
9548
  return spawnSync("cursor-agent", ["models"], {
@@ -9376,7 +9598,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
9376
9598
  }
9377
9599
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
9378
9600
  const existsSyncFn = deps.existsSyncFn ?? existsSync5;
9379
- const env = deps.env ?? process.env;
9601
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
9380
9602
  const winPath = path8.win32;
9381
9603
  let geminiEntry = null;
9382
9604
  try {
@@ -9513,12 +9735,15 @@ var GeminiDriver = class {
9513
9735
  // src/drivers/kimi.ts
9514
9736
  import { randomUUID as randomUUID2 } from "crypto";
9515
9737
  import { spawn as spawn7 } from "child_process";
9516
- import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
9738
+ import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
9517
9739
  import os4 from "os";
9518
9740
  import path9 from "path";
9519
9741
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
9520
9742
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
9521
9743
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
9744
+ var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
9745
+ var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
9746
+ var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
9522
9747
  function parseToolArguments(raw) {
9523
9748
  if (typeof raw !== "string") return raw;
9524
9749
  try {
@@ -9527,6 +9752,73 @@ function parseToolArguments(raw) {
9527
9752
  return raw;
9528
9753
  }
9529
9754
  }
9755
+ function readKimiConfigSource(home = os4.homedir(), env = process.env) {
9756
+ const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
9757
+ if (inlineConfig && inlineConfig.trim()) {
9758
+ return {
9759
+ raw: inlineConfig,
9760
+ explicitPath: null,
9761
+ sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
9762
+ };
9763
+ }
9764
+ const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
9765
+ const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
9766
+ try {
9767
+ return {
9768
+ raw: readFileSync3(configPath, "utf8"),
9769
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
9770
+ sourcePath: configPath
9771
+ };
9772
+ } catch {
9773
+ return {
9774
+ raw: null,
9775
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
9776
+ sourcePath: configPath
9777
+ };
9778
+ }
9779
+ }
9780
+ function buildKimiSpawnEnv(env = process.env) {
9781
+ const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
9782
+ delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
9783
+ delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
9784
+ return scrubDaemonChildEnv(spawnEnv);
9785
+ }
9786
+ function buildKimiEffectiveEnv(ctx, overrideEnv) {
9787
+ return {
9788
+ ...process.env,
9789
+ ...ctx.config.envVars || {},
9790
+ ...overrideEnv || {}
9791
+ };
9792
+ }
9793
+ function buildKimiLaunchOptions(ctx, opts = {}) {
9794
+ const env = buildKimiEffectiveEnv(ctx, opts.env);
9795
+ const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
9796
+ const args = [];
9797
+ let configFilePath = null;
9798
+ let configContent = null;
9799
+ if (source.explicitPath) {
9800
+ configFilePath = source.explicitPath;
9801
+ } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
9802
+ configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
9803
+ configContent = source.raw;
9804
+ if (opts.writeGeneratedConfig !== false) {
9805
+ writeFileSync3(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
9806
+ chmodSync(configFilePath, 384);
9807
+ }
9808
+ }
9809
+ if (configFilePath) {
9810
+ args.push("--config-file", configFilePath);
9811
+ }
9812
+ if (ctx.config.model && ctx.config.model !== "default") {
9813
+ args.push("--model", ctx.config.model);
9814
+ }
9815
+ return {
9816
+ args,
9817
+ env: buildKimiSpawnEnv(env),
9818
+ configFilePath,
9819
+ configContent
9820
+ };
9821
+ }
9530
9822
  function resolveKimiSpawn(commandArgs, deps = {}) {
9531
9823
  return {
9532
9824
  command: resolveCommandOnPath("kimi", deps) ?? "kimi",
@@ -9550,7 +9842,25 @@ var KimiDriver = class {
9550
9842
  };
9551
9843
  model = {
9552
9844
  detectedModelsVerifiedAs: "launchable",
9553
- toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
9845
+ toLaunchSpec: (modelId, ctx, opts) => {
9846
+ if (!ctx) return { args: ["--model", modelId] };
9847
+ const launchCtx = {
9848
+ ...ctx,
9849
+ config: {
9850
+ ...ctx.config,
9851
+ model: modelId
9852
+ }
9853
+ };
9854
+ const launch = buildKimiLaunchOptions(launchCtx, {
9855
+ home: opts?.home,
9856
+ writeGeneratedConfig: false
9857
+ });
9858
+ return {
9859
+ args: launch.args,
9860
+ env: launch.env,
9861
+ configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
9862
+ };
9863
+ }
9554
9864
  };
9555
9865
  supportsStdinNotification = true;
9556
9866
  busyDeliveryMode = "direct";
@@ -9574,21 +9884,23 @@ var KimiDriver = class {
9574
9884
  ` system_prompt_path: ./${KIMI_SYSTEM_PROMPT_FILE}`,
9575
9885
  ""
9576
9886
  ].join("\n"), "utf8");
9887
+ const launch = buildKimiLaunchOptions(ctx);
9577
9888
  const args = [
9578
9889
  "--wire",
9579
9890
  "--yolo",
9580
9891
  "--agent-file",
9581
9892
  agentFilePath,
9582
9893
  "--session",
9583
- this.sessionId
9894
+ this.sessionId,
9895
+ ...launch.args
9584
9896
  ];
9585
9897
  const launchRuntimeFields = runtimeConfigToLaunchFields(hydrateRuntimeConfig(ctx.config));
9586
9898
  if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
9587
9899
  args.push("--model", launchRuntimeFields.model);
9588
9900
  }
9589
9901
  const spawnEnv = (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
9590
- const launch = resolveKimiSpawn(args);
9591
- const proc = spawn7(launch.command, launch.args, {
9902
+ const spawnTarget = resolveKimiSpawn(args);
9903
+ const proc = spawn7(spawnTarget.command, spawnTarget.args, {
9592
9904
  cwd: ctx.workingDirectory,
9593
9905
  stdio: ["pipe", "pipe", "pipe"],
9594
9906
  env: spawnEnv,
@@ -9596,7 +9908,7 @@ var KimiDriver = class {
9596
9908
  // and has an 8191-character command-line limit. Kimi's official
9597
9909
  // installer/uv entrypoint is an executable, so launch it directly and
9598
9910
  // keep prompts on stdin / files instead of routing through cmd.exe.
9599
- shell: launch.shell
9911
+ shell: spawnTarget.shell
9600
9912
  });
9601
9913
  proc.stdin?.write(JSON.stringify({
9602
9914
  jsonrpc: "2.0",
@@ -9709,14 +10021,9 @@ var KimiDriver = class {
9709
10021
  return detectKimiModels();
9710
10022
  }
9711
10023
  };
9712
- function detectKimiModels(home = os4.homedir()) {
9713
- const configPath = path9.join(home, ".kimi", "config.toml");
9714
- let raw;
9715
- try {
9716
- raw = readFileSync3(configPath, "utf8");
9717
- } catch {
9718
- return null;
9719
- }
10024
+ function detectKimiModels(home = os4.homedir(), opts = {}) {
10025
+ const raw = readKimiConfigSource(home, opts.env).raw;
10026
+ if (raw === null) return null;
9720
10027
  const models = [];
9721
10028
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
9722
10029
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -10382,7 +10689,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
10382
10689
  const platform = deps.platform ?? process.platform;
10383
10690
  const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
10384
10691
  const result = spawnSyncFn("opencode", ["models"], {
10385
- env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
10692
+ env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
10386
10693
  encoding: "utf8",
10387
10694
  timeout: 5e3,
10388
10695
  shell: platform === "win32"
@@ -12524,6 +12831,9 @@ function computeTarget(channelType, channelName, parentChannelName, parentChanne
12524
12831
  return `#${channelName}`;
12525
12832
  }
12526
12833
  function formatAgentMessageVisibleTarget(message) {
12834
+ if (message.third_party_event) {
12835
+ return `agent-event:${getMessageShortId(message.third_party_event.id)}`;
12836
+ }
12527
12837
  return computeTarget(message.channel_type, message.channel_name, message.parent_channel_name, message.parent_channel_type);
12528
12838
  }
12529
12839
  function formatProxyVisibleMessageTarget(message) {
@@ -13371,24 +13681,38 @@ function safeSessionFilename(value) {
13371
13681
  const normalized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
13372
13682
  return normalized || "unknown-session";
13373
13683
  }
13374
- function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
13684
+ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir, join, resolve) {
13375
13685
  try {
13376
13686
  const dir = path14.join(fallbackDir, ".slock", "runtime-sessions");
13377
13687
  mkdirSync4(dir, { recursive: true });
13378
13688
  const filePath = path14.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
13689
+ const searchedPaths = resolve?.searchedPaths ?? [];
13379
13690
  writeFileSync4(filePath, JSON.stringify({
13380
13691
  type: "runtime_session_handoff",
13381
13692
  runtime,
13382
13693
  sessionId,
13694
+ // Persisted L1<->L2 join keys (#460 V3): without these the
13695
+ // session->launch mapping lives only in daemon memory and old L1
13696
+ // transcripts can never re-join their launch after restart/adopt.
13697
+ // ids-only, closed fields (no content payload).
13698
+ launchId: join?.launchId ?? null,
13699
+ processInstanceId: join?.processInstanceId ?? null,
13700
+ // Negative-space (fruit#0 pt2): a resolve-miss is explicit evidence, not a
13701
+ // silent "not found". Recording the lookup method + the directories checked
13702
+ // makes "looked in the wrong place" distinguishable from "looked in the right
13703
+ // place and it is genuinely absent". ids/paths only — no transcript content.
13704
+ resolveStatus: "transcript_resolve_missing",
13705
+ lookupMethod: resolve?.lookupMethod ?? "none",
13706
+ searchedPaths,
13383
13707
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
13384
- note: "The native runtime transcript file was not found on this machine; this daemon-created handoff records the runtime session identity for diagnostics."
13708
+ note: "The native runtime transcript file was not found on this machine; this daemon-created handoff records the runtime session identity + the directories checked for diagnostics."
13385
13709
  }) + "\n", { mode: 384 });
13386
13710
  return {
13387
13711
  label: sessionId,
13388
13712
  path: filePath,
13389
13713
  runtime,
13390
13714
  reachable: true,
13391
- reason: "native session file path not found; using daemon handoff file"
13715
+ reason: `native session file path not found; using daemon handoff file; searched=[${searchedPaths.join(", ")}]`
13392
13716
  };
13393
13717
  } catch {
13394
13718
  return null;
@@ -13413,12 +13737,16 @@ function ensureRuntimeHomeDir(config, defaultHomeDir, workspacePath, opts = {})
13413
13737
  function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir, opts) {
13414
13738
  let resolvedPath = null;
13415
13739
  let lookupMethod = "none";
13740
+ const searchedPaths = [];
13416
13741
  if (runtime === "claude") {
13417
13742
  lookupMethod = "claude_jsonl";
13418
- resolvedPath = findSessionJsonl(path14.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`);
13743
+ const claudeRoot = path14.join(homeDir, ".claude", "projects");
13744
+ searchedPaths.push(claudeRoot);
13745
+ resolvedPath = findSessionJsonl(claudeRoot, (filename) => filename === `${sessionId}.jsonl`);
13419
13746
  } else if (runtime === "codex") {
13420
13747
  lookupMethod = "codex_jsonl";
13421
13748
  for (const root of codexSessionRootCandidates(homeDir)) {
13749
+ searchedPaths.push(root);
13422
13750
  resolvedPath = findSessionJsonl(root, (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId));
13423
13751
  if (resolvedPath) break;
13424
13752
  }
@@ -13430,7 +13758,13 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
13430
13758
  resolvedPath = findPiSessionFile2(sessionId, opts?.workingDirectory, homeDir);
13431
13759
  }
13432
13760
  if (!resolvedPath && fallbackDir) {
13433
- const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
13761
+ const fallback = writeRuntimeSessionHandoff(
13762
+ runtime,
13763
+ sessionId,
13764
+ fallbackDir,
13765
+ { launchId: opts?.launchId, processInstanceId: opts?.processInstanceId },
13766
+ { lookupMethod, searchedPaths }
13767
+ );
13434
13768
  if (fallback) {
13435
13769
  return {
13436
13770
  ...fallback,
@@ -13445,7 +13779,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
13445
13779
  reachable: Boolean(resolvedPath)
13446
13780
  };
13447
13781
  if (!resolvedPath) {
13448
- ref.reason = `session file path not found; attempted_lookup=${lookupMethod}`;
13782
+ ref.reason = `session file path not found; attempted_lookup=${lookupMethod}; searched=[${searchedPaths.join(", ")}]`;
13449
13783
  }
13450
13784
  return ref;
13451
13785
  }
@@ -13499,7 +13833,7 @@ function formatIncomingMessage(message, options = {}) {
13499
13833
  `[Slock thread context: you were added to a new thread via @mention.]`,
13500
13834
  `parent: ${message.thread_join_context.parent_target}`,
13501
13835
  `thread: ${message.thread_join_context.thread_target}`,
13502
- `suggested next step: raft message read --channel "${message.thread_join_context.suggested_read_history_target}"`,
13836
+ `suggested next step: raft message read --target "${message.thread_join_context.suggested_read_history_target}"`,
13503
13837
  "",
13504
13838
  "Parent message:",
13505
13839
  formatThreadContextMessage(message.thread_join_context.parent_message),
@@ -15329,7 +15663,8 @@ var AgentProcessManager = class _AgentProcessManager {
15329
15663
  detailKind: activity.statusEntry.detailKind ?? "daemon_activity",
15330
15664
  entries: activity.entries,
15331
15665
  launchId: ap?.launchId || void 0,
15332
- clientSeq: ap ? this.nextActivityClientSeq(agentId) : void 0
15666
+ clientSeq: ap ? this.nextActivityClientSeq(agentId) : void 0,
15667
+ isHeartbeat: false
15333
15668
  });
15334
15669
  }
15335
15670
  recordRuntimeDiagnosticActivity(agentId, ap, event) {
@@ -15342,7 +15677,8 @@ var AgentProcessManager = class _AgentProcessManager {
15342
15677
  detailKind: ap.lastActivityDetailKind || "none",
15343
15678
  entries: [runtimeDiagnosticTrajectoryEntry(event)],
15344
15679
  launchId: ap.launchId || void 0,
15345
- clientSeq: this.nextActivityClientSeq(agentId)
15680
+ clientSeq: this.nextActivityClientSeq(agentId),
15681
+ isHeartbeat: false
15346
15682
  });
15347
15683
  this.recordDaemonTrace("daemon.runtime.diagnostic", {
15348
15684
  agentId,
@@ -15393,7 +15729,8 @@ var AgentProcessManager = class _AgentProcessManager {
15393
15729
  detailKind: ap.lastActivityDetailKind || "none",
15394
15730
  entries: [runtimeRecoveryTrajectoryEntry(event)],
15395
15731
  launchId: ap.launchId || void 0,
15396
- clientSeq: this.nextActivityClientSeq(agentId)
15732
+ clientSeq: this.nextActivityClientSeq(agentId),
15733
+ isHeartbeat: false
15397
15734
  });
15398
15735
  this.recordDaemonTrace("daemon.runtime.recovery.visible", {
15399
15736
  agentId,
@@ -15641,7 +15978,8 @@ var AgentProcessManager = class _AgentProcessManager {
15641
15978
  this.lifecycleRecords.setRestartSnapshot(agentId, {
15642
15979
  config: this.buildRestartSafeConfig(ap.config, nextConfigSessionId),
15643
15980
  sessionId: nextConfigSessionId,
15644
- launchId: nextLaunchId
15981
+ launchId: nextLaunchId,
15982
+ processInstanceId: ap.processInstanceId
15645
15983
  });
15646
15984
  this.recordStartRebind(agentId, start, reason, previousLaunchId, nextLaunchId, nextSessionId);
15647
15985
  this.sendAgentStatus(agentId, "active", nextLaunchId);
@@ -16072,6 +16410,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16072
16410
  config: this.buildRestartSafeConfig(runtimeConfig, runtimeConfig.sessionId || null),
16073
16411
  sessionId: runtimeConfig.sessionId || null,
16074
16412
  launchId: effectiveLaunchId
16413
+ // No live AgentProcess in this adoption branch — omit rather than
16414
+ // fabricate; the marker's processInstanceId is nullable by contract.
16075
16415
  });
16076
16416
  this.sendAgentStatus(agentId, "active", effectiveLaunchId);
16077
16417
  this.broadcastActivity(agentId, "online", "Process idle", [], void 0, "idle");
@@ -16161,7 +16501,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16161
16501
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16162
16502
  config: this.buildRestartSafeConfig(runtimeConfig, restartSessionId),
16163
16503
  sessionId: restartSessionId,
16164
- launchId: effectiveLaunchId
16504
+ launchId: effectiveLaunchId,
16505
+ processInstanceId: agentProcess.processInstanceId
16165
16506
  });
16166
16507
  if (pendingStartRebind) {
16167
16508
  this.recordStartRebind(agentId, pendingStartRebind, "startup_registered", originalLaunchId, effectiveLaunchId, agentProcess.sessionId);
@@ -16363,7 +16704,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16363
16704
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16364
16705
  config: nextConfig,
16365
16706
  sessionId: ap.sessionId,
16366
- launchId: ap.launchId
16707
+ launchId: ap.launchId,
16708
+ processInstanceId: ap.processInstanceId
16367
16709
  });
16368
16710
  this.broadcastMessageReceivedActivity(agentId);
16369
16711
  this.lifecycleRecords.deleteRestartSnapshot(agentId);
@@ -16381,7 +16723,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16381
16723
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16382
16724
  config: nextConfig,
16383
16725
  sessionId: ap.sessionId,
16384
- launchId: ap.launchId
16726
+ launchId: ap.launchId,
16727
+ processInstanceId: ap.processInstanceId
16385
16728
  });
16386
16729
  const report = this.recordSpawnFailure(agentId, "runner_credential_mint");
16387
16730
  this.assertStartPendingDeliveryInvariants("queued-continuation-runner-credential-mint-failure");
@@ -16401,7 +16744,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16401
16744
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16402
16745
  config: nextConfig,
16403
16746
  sessionId: ap.sessionId,
16404
- launchId: ap.launchId
16747
+ launchId: ap.launchId,
16748
+ processInstanceId: ap.processInstanceId
16405
16749
  });
16406
16750
  this.broadcastActivity(agentId, "online", "Process idle", [], void 0, "idle");
16407
16751
  });
@@ -16410,7 +16754,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16410
16754
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16411
16755
  config: this.buildRestartSafeConfig(ap.config, ap.sessionId),
16412
16756
  sessionId: ap.sessionId,
16413
- launchId: ap.launchId
16757
+ launchId: ap.launchId,
16758
+ processInstanceId: ap.processInstanceId
16414
16759
  });
16415
16760
  if (!ap.driver.supportsStdinNotification) {
16416
16761
  logger.info(`[Agent ${agentId}] Turn completed; cached idle state for future restart`);
@@ -16422,7 +16767,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16422
16767
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16423
16768
  config: this.buildRestartSafeConfig(ap.config, ap.sessionId),
16424
16769
  sessionId: ap.sessionId,
16425
- launchId: ap.launchId
16770
+ launchId: ap.launchId,
16771
+ processInstanceId: ap.processInstanceId
16426
16772
  });
16427
16773
  logger.warn(`[Agent ${agentId}] Recoverable provider stream failure (${reason}) \u2014 keeping agent wakeable`);
16428
16774
  this.sendAgentStatus(agentId, "active", ap.launchId);
@@ -16638,7 +16984,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16638
16984
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16639
16985
  config: retryConfig,
16640
16986
  sessionId: retrySessionId,
16641
- launchId: ap.launchId
16987
+ launchId: ap.launchId,
16988
+ processInstanceId: ap.processInstanceId
16642
16989
  });
16643
16990
  this.recordDaemonTrace("daemon.agent.startup_timeout.retry_config_cached", {
16644
16991
  agentId,
@@ -17552,7 +17899,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
17552
17899
  }
17553
17900
  return result;
17554
17901
  }
17555
- buildRuntimeProfileReport(agentId, config, sessionId, launchId, observedRuntimeHomeDir) {
17902
+ buildRuntimeProfileReport(agentId, config, sessionId, launchId, observedRuntimeHomeDir, processInstanceId) {
17556
17903
  const workspacePath = path14.join(this.dataDir, agentId);
17557
17904
  const runtimeHomeDir = observedRuntimeHomeDir || resolveRuntimeHomeDir(config, this.runtimeSessionHomeDir, workspacePath, { agentId, slockHome: this.slockHome });
17558
17905
  return {
@@ -17568,7 +17915,16 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
17568
17915
  path: workspacePath,
17569
17916
  reachable: true
17570
17917
  },
17571
- sessionRef: sessionId ? resolveRuntimeSessionRef(config.runtime, sessionId, runtimeHomeDir, workspacePath, { agentId, workingDirectory: workspacePath }) : null
17918
+ sessionRef: sessionId ? resolveRuntimeSessionRef(config.runtime, sessionId, runtimeHomeDir, workspacePath, {
17919
+ agentId,
17920
+ workingDirectory: workspacePath,
17921
+ // Join keys come from the CALLER's context (running agent OR idle
17922
+ // restart snapshot) — reading only live state here left the
17923
+ // restart/adopt path writing launchId:null markers, exactly the
17924
+ // V3 class this exists to close (Leiysky review on #3863).
17925
+ launchId: launchId || void 0,
17926
+ processInstanceId: processInstanceId ?? this.agents.get(agentId)?.processInstanceId
17927
+ }) : null
17572
17928
  }
17573
17929
  };
17574
17930
  }
@@ -17580,12 +17936,13 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
17580
17936
  running.config,
17581
17937
  running.sessionId,
17582
17938
  running.launchId,
17583
- running.runtime.currentRuntimeHomeDir
17939
+ running.runtime.currentRuntimeHomeDir,
17940
+ running.processInstanceId
17584
17941
  );
17585
17942
  }
17586
17943
  const idle = this.lifecycleRecords.getRestartSnapshot(agentId);
17587
17944
  if (idle) {
17588
- return this.buildRuntimeProfileReport(agentId, idle.config, idle.sessionId, idle.launchId);
17945
+ return this.buildRuntimeProfileReport(agentId, idle.config, idle.sessionId, idle.launchId, null, idle.processInstanceId);
17589
17946
  }
17590
17947
  return null;
17591
17948
  }
@@ -17983,7 +18340,9 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
17983
18340
  }
17984
18341
  const ref = resolveRuntimeSessionRef(runtime, actualSessionId, homeDir, workspaceDir, {
17985
18342
  agentId,
17986
- workingDirectory: workspaceDir
18343
+ workingDirectory: workspaceDir,
18344
+ launchId: this.agents.get(agentId)?.launchId || void 0,
18345
+ processInstanceId: this.agents.get(agentId)?.processInstanceId
17987
18346
  });
17988
18347
  const tier = runtimeTier(runtime);
17989
18348
  const span = this.tracer.startSpan("daemon.session_transcript.read", {
@@ -18241,6 +18600,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18241
18600
  const launchId = launchIdOverride || ap?.launchId || void 0;
18242
18601
  const clientSeq = this.nextActivityClientSeq(agentId);
18243
18602
  const producerFactId = this.buildActivityProducerFactId(agentId, launchId, clientSeq);
18603
+ const observedAtMs = Date.now();
18244
18604
  this.sendToServer({
18245
18605
  type: "agent:activity",
18246
18606
  agentId,
@@ -18251,9 +18611,11 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18251
18611
  entries,
18252
18612
  launchId,
18253
18613
  clientSeq,
18254
- producerFactId
18614
+ producerFactId,
18615
+ observedAtMs,
18616
+ isHeartbeat: false
18255
18617
  });
18256
- this.recordActivityProducedTrace(agentId, activityKind, detail, detailKind, entries, ap, launchId, clientSeq, producerFactId);
18618
+ this.recordActivityProducedTrace(agentId, activityKind, detail, detailKind, entries, ap, launchId, clientSeq, producerFactId, false);
18257
18619
  if (ap) {
18258
18620
  ap.lastActivityKind = activityKind;
18259
18621
  ap.lastActivity = activityDisplay;
@@ -18270,6 +18632,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18270
18632
  const heartbeatLaunchId = launchIdOverride || ap.launchId || void 0;
18271
18633
  const heartbeatClientSeq = this.nextActivityClientSeq(agentId);
18272
18634
  const heartbeatProducerFactId = this.buildActivityProducerFactId(agentId, heartbeatLaunchId, heartbeatClientSeq);
18635
+ const heartbeatObservedAtMs = Date.now();
18273
18636
  this.sendToServer({
18274
18637
  type: "agent:activity",
18275
18638
  agentId,
@@ -18279,9 +18642,13 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18279
18642
  detailKind: ap.lastActivityDetailKind,
18280
18643
  launchId: heartbeatLaunchId,
18281
18644
  clientSeq: heartbeatClientSeq,
18282
- producerFactId: heartbeatProducerFactId
18645
+ producerFactId: heartbeatProducerFactId,
18646
+ observedAtMs: heartbeatObservedAtMs,
18647
+ // The one knowing site: this timer re-broadcasts stale
18648
+ // lastActivity, so it declares its replay provenance.
18649
+ isHeartbeat: true
18283
18650
  });
18284
- this.recordActivityProducedTrace(agentId, ap.lastActivityKind, ap.lastActivityDetail, ap.lastActivityDetailKind, [], ap, heartbeatLaunchId, heartbeatClientSeq, heartbeatProducerFactId);
18651
+ this.recordActivityProducedTrace(agentId, ap.lastActivityKind, ap.lastActivityDetail, ap.lastActivityDetailKind, [], ap, heartbeatLaunchId, heartbeatClientSeq, heartbeatProducerFactId, true);
18285
18652
  }, ACTIVITY_HEARTBEAT_MS);
18286
18653
  ap.activityHeartbeat = { kind: "active", timer };
18287
18654
  }
@@ -18290,7 +18657,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18290
18657
  }
18291
18658
  }
18292
18659
  }
18293
- recordActivityProducedTrace(agentId, activityKind, detail, detailKind, entries, ap, launchId, clientSeq, producerFactId) {
18660
+ recordActivityProducedTrace(agentId, activityKind, detail, detailKind, entries, ap, launchId, clientSeq, producerFactId, isHeartbeat) {
18294
18661
  const runtimeContext = ap?.config.runtimeContext;
18295
18662
  this.recordDaemonTrace("daemon.agent.activity.produced", {
18296
18663
  agentId,
@@ -18312,6 +18679,11 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18312
18679
  client_seq_present: typeof clientSeq === "number",
18313
18680
  producerFactId,
18314
18681
  producer_fact_id: producerFactId,
18682
+ // #460 V1: the wire's producer-declared replay-provenance bit must be
18683
+ // independently verifiable from daemon-side evidence (L2<->L3 seam);
18684
+ // closed boolean, mirrors the agent:activity isHeartbeat field exactly.
18685
+ isHeartbeat,
18686
+ is_heartbeat: isHeartbeat,
18315
18687
  correlation_id: `agent:${agentId}:daemonActivity:${launchId ?? "legacy"}:${clientSeq ?? "unsequenced"}`,
18316
18688
  session_id_present: Boolean(ap?.sessionId),
18317
18689
  runtime: ap?.config.runtime
@@ -18360,9 +18732,10 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18360
18732
  launchId,
18361
18733
  probeId,
18362
18734
  clientSeq,
18363
- producerFactId
18735
+ producerFactId,
18736
+ isHeartbeat: false
18364
18737
  });
18365
- this.recordActivityProducedTrace(agentId, activityKind, detail, detailKind, [], ap, launchId, clientSeq, producerFactId);
18738
+ this.recordActivityProducedTrace(agentId, activityKind, detail, detailKind, [], ap, launchId, clientSeq, producerFactId, false);
18366
18739
  }
18367
18740
  flushPendingTrajectory(agentId) {
18368
18741
  const ap = this.agents.get(agentId);
@@ -18546,12 +18919,29 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18546
18919
  }
18547
18920
  this.commitApmIdleState(agentId, ap, false);
18548
18921
  }
18549
- interruptCompactionIfActive(agentId) {
18922
+ interruptCompactionIfActive(agentId, options = {}) {
18550
18923
  const ap = this.agents.get(agentId);
18551
- if (!ap || ap.compaction.kind !== "active" && !ap.gatedSteering.compacting) return;
18924
+ if (!ap || ap.compaction.kind !== "active" && !ap.gatedSteering.compacting) return false;
18552
18925
  this.clearCompactionWatchdog(ap);
18553
18926
  const reduction = reduceApmGatedCompaction(ap.gatedSteering, { kind: "compaction_interrupted" });
18554
- this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState);
18927
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, { event: "compaction_interrupted" });
18928
+ if (options.traceAttrs) {
18929
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime.context_compaction.interrupted", options.traceAttrs);
18930
+ }
18931
+ if (options.detail) {
18932
+ this.broadcastActivity(
18933
+ agentId,
18934
+ "error",
18935
+ options.detail,
18936
+ options.entries ?? [],
18937
+ void 0,
18938
+ options.detailKind ?? "runtime_error"
18939
+ );
18940
+ }
18941
+ if (options.flushBoundaryMessages) {
18942
+ this.flushCompactionBoundaryMessages(agentId, ap);
18943
+ }
18944
+ return true;
18555
18945
  }
18556
18946
  flushReviewBoundaryMessages(agentId, ap) {
18557
18947
  const reduction = reduceApmGatedReviewBoundaryFlush(ap.gatedSteering, {
@@ -19279,6 +19669,17 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
19279
19669
  if (event.kind === "delivery_error") {
19280
19670
  if (ap) {
19281
19671
  this.restoreRuntimeDeliveryAfterAsyncRejection(agentId, ap, event);
19672
+ this.interruptCompactionIfActive(agentId, {
19673
+ detail: `Context compaction interrupted after runtime delivery failed: ${event.message}`,
19674
+ entries: [{ kind: "text", text: `Error: ${event.message}` }],
19675
+ flushBoundaryMessages: true,
19676
+ traceAttrs: {
19677
+ reason: "delivery_error",
19678
+ request_method: event.requestMethod,
19679
+ source: event.source,
19680
+ payloadBytes: event.payloadBytes
19681
+ }
19682
+ });
19282
19683
  } else {
19283
19684
  this.recordDaemonTrace("daemon.agent.delivery_error.received_without_process", {
19284
19685
  agentId,
@@ -21302,6 +21703,563 @@ function assertLegacyDaemonKeyNotAdoptedByComputer(options) {
21302
21703
  if (match) throw new LegacyDaemonKeyAdoptedByComputerError(match);
21303
21704
  }
21304
21705
 
21706
+ // src/agentMigrationExport.ts
21707
+ import { createHash as createHash7 } from "crypto";
21708
+ import { createReadStream } from "fs";
21709
+ import { lstat as lstat2, readdir as readdir4, readFile as readFile3, readlink } from "fs/promises";
21710
+ import path19 from "path";
21711
+ var AGENT_MIGRATION_BUNDLE_SCHEMA_VERSION = "agent-bundle/v1";
21712
+ var REGENERABLE_DIRECTORY_NAMES = [
21713
+ ".venv",
21714
+ "__pycache__",
21715
+ "dist",
21716
+ "node_modules",
21717
+ "target"
21718
+ ];
21719
+ var SECRET_FILE_NAMES = /* @__PURE__ */ new Set([
21720
+ ".env",
21721
+ ".env.local",
21722
+ ".env.development",
21723
+ ".env.production",
21724
+ ".env.test",
21725
+ "credentials.json",
21726
+ "credential.json"
21727
+ ]);
21728
+ var ENV_KEY_PATTERN = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/;
21729
+ async function buildAgentMigrationExportManifest(input) {
21730
+ const slockHome = path19.resolve(input.slockHome);
21731
+ const workspace = path19.resolve(input.workspacePath ?? path19.join(slockHome, "agents", input.agentId));
21732
+ const proposedIncludes = normalizeProposalPaths(input.cooperativeManifest?.include, workspace, "include");
21733
+ const proposedRegenerableExcludes = normalizeProposalPaths(input.cooperativeManifest?.exclude_regenerable, workspace, "exclude_regenerable");
21734
+ const cleanedProposal = normalizeProposalPaths(input.cooperativeManifest?.cleaned, workspace, "cleaned");
21735
+ const state = {
21736
+ workspace,
21737
+ files: [],
21738
+ excludedRegenerable: [],
21739
+ promotedIncludes: [],
21740
+ proposalRefusals: [
21741
+ ...proposedIncludes.refusals,
21742
+ ...proposedRegenerableExcludes.refusals,
21743
+ ...cleanedProposal.refusals
21744
+ ],
21745
+ unreachable: [],
21746
+ secretsDisclosed: [],
21747
+ seenWorkspacePaths: /* @__PURE__ */ new Set(),
21748
+ explicitIncludePaths: new Set(proposedIncludes.paths)
21749
+ };
21750
+ await walkWorkspace(workspace, "", new Set(proposedRegenerableExcludes.paths), state, { forceIncludeRegenerable: false });
21751
+ for (const requestedPath of proposedIncludes.paths) {
21752
+ if (!state.seenWorkspacePaths.has(requestedPath)) {
21753
+ await includeWorkspacePath(requestedPath, state, { forceIncludeRegenerable: true });
21754
+ }
21755
+ }
21756
+ for (const requestedPath of proposedRegenerableExcludes.paths) {
21757
+ if (isRegenerablePath(requestedPath)) continue;
21758
+ state.promotedIncludes.push({ path: requestedPath, reason: "exclude_not_regenerable" });
21759
+ await includeWorkspacePath(requestedPath, state, { forceIncludeRegenerable: true });
21760
+ }
21761
+ const crossTreeRefs = await buildCrossTreeRefs(input.runtimeSessionRefs ?? []);
21762
+ const secretDisclosureProposal = normalizeProposalPaths(input.cooperativeManifest?.secrets_disclosed, workspace, "secrets_disclosed");
21763
+ state.proposalRefusals.push(...secretDisclosureProposal.refusals);
21764
+ return {
21765
+ schemaVersion: AGENT_MIGRATION_BUNDLE_SCHEMA_VERSION,
21766
+ agentId: input.agentId,
21767
+ mode: input.mode,
21768
+ createdAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
21769
+ roots: {
21770
+ slockHome,
21771
+ workspace
21772
+ },
21773
+ defaults: {
21774
+ unknownFiles: "include",
21775
+ excludePolicy: "regenerable_only",
21776
+ regenerableDirectoryNames: [...REGENERABLE_DIRECTORY_NAMES]
21777
+ },
21778
+ files: sortBundleEntries(state.files),
21779
+ excludedRegenerable: sortByPath(state.excludedRegenerable),
21780
+ promotedIncludes: sortByPath(state.promotedIncludes),
21781
+ proposalRefusals: sortByPath(state.proposalRefusals),
21782
+ unreachable: sortByPath(state.unreachable),
21783
+ secretsDisclosed: sortByPath([
21784
+ ...state.secretsDisclosed,
21785
+ ...normalizeProvidedSecretDisclosures(secretDisclosureProposal.paths)
21786
+ ]),
21787
+ cleaned: cleanedProposal.paths,
21788
+ crossTreeRefs: crossTreeRefs.sort((a, b) => a.bundlePath.localeCompare(b.bundlePath))
21789
+ };
21790
+ }
21791
+ async function walkWorkspace(root, relativeDir, proposedRegenerableExcludes, state, opts) {
21792
+ const absoluteDir = path19.join(root, relativeDir);
21793
+ let entries;
21794
+ try {
21795
+ entries = await readdir4(absoluteDir, { withFileTypes: true });
21796
+ } catch {
21797
+ state.unreachable.push({
21798
+ path: relativeDir || ".",
21799
+ sourcePath: absoluteDir,
21800
+ reason: "read_error"
21801
+ });
21802
+ return;
21803
+ }
21804
+ entries.sort((a, b) => a.name.localeCompare(b.name));
21805
+ for (const entry of entries) {
21806
+ const relativePath = toPosixPath(path19.join(relativeDir, entry.name));
21807
+ if (entry.isDirectory()) {
21808
+ if (!opts.forceIncludeRegenerable && isRegenerablePath(relativePath)) {
21809
+ if (state.explicitIncludePaths.has(relativePath)) {
21810
+ await walkWorkspace(root, relativePath, proposedRegenerableExcludes, state, { forceIncludeRegenerable: true });
21811
+ continue;
21812
+ }
21813
+ if (hasExplicitIncludeDescendant(relativePath, state.explicitIncludePaths)) {
21814
+ await walkWorkspace(root, relativePath, proposedRegenerableExcludes, state, opts);
21815
+ continue;
21816
+ }
21817
+ state.excludedRegenerable.push({
21818
+ path: relativePath,
21819
+ reason: proposedRegenerableExcludes.has(relativePath) ? "cooperative_exclude_regenerable" : "regenerable_default",
21820
+ regenerableHint: `${entry.name} is treated as rebuildable/installable state and is not bundled by default`
21821
+ });
21822
+ continue;
21823
+ }
21824
+ await walkWorkspace(root, relativePath, proposedRegenerableExcludes, state, opts);
21825
+ continue;
21826
+ }
21827
+ await includeWorkspacePath(relativePath, state, { forceIncludeRegenerable: opts.forceIncludeRegenerable });
21828
+ }
21829
+ }
21830
+ async function includeWorkspacePath(workspaceRelativePath, state, opts) {
21831
+ const normalizedRelativePath = normalizeRelativePath(workspaceRelativePath);
21832
+ if (state.seenWorkspacePaths.has(normalizedRelativePath)) return;
21833
+ const sourcePath = path19.join(state.workspace, normalizedRelativePath);
21834
+ let stat4;
21835
+ try {
21836
+ stat4 = await lstat2(sourcePath);
21837
+ } catch {
21838
+ state.unreachable.push({
21839
+ path: normalizedRelativePath,
21840
+ sourcePath,
21841
+ reason: "missing"
21842
+ });
21843
+ return;
21844
+ }
21845
+ if (stat4.isDirectory()) {
21846
+ if (!opts.forceIncludeRegenerable && isRegenerablePath(normalizedRelativePath)) {
21847
+ state.excludedRegenerable.push({
21848
+ path: normalizedRelativePath,
21849
+ reason: "cooperative_exclude_regenerable",
21850
+ regenerableHint: `${path19.basename(normalizedRelativePath)} is treated as rebuildable/installable state and is not bundled by default`
21851
+ });
21852
+ return;
21853
+ }
21854
+ await walkWorkspace(state.workspace, normalizedRelativePath, /* @__PURE__ */ new Set(), state, opts);
21855
+ return;
21856
+ }
21857
+ state.seenWorkspacePaths.add(normalizedRelativePath);
21858
+ const baseEntry = {
21859
+ source: "workspace",
21860
+ sourcePath,
21861
+ workspaceRelativePath: normalizedRelativePath,
21862
+ bundlePath: `workspace/${normalizedRelativePath}`,
21863
+ mode: stat4.mode,
21864
+ mtimeMs: stat4.mtimeMs
21865
+ };
21866
+ if (stat4.isSymbolicLink()) {
21867
+ state.files.push({
21868
+ ...baseEntry,
21869
+ kind: "symlink",
21870
+ linkTarget: await readlink(sourcePath)
21871
+ });
21872
+ return;
21873
+ }
21874
+ if (!stat4.isFile()) {
21875
+ state.unreachable.push({
21876
+ path: normalizedRelativePath,
21877
+ sourcePath,
21878
+ reason: "unsupported_file_type"
21879
+ });
21880
+ return;
21881
+ }
21882
+ const secretShapes = await detectSecretShapes(sourcePath, normalizedRelativePath);
21883
+ if (secretShapes.length > 0) {
21884
+ state.secretsDisclosed.push({ path: normalizedRelativePath, shapes: secretShapes });
21885
+ }
21886
+ state.files.push({
21887
+ ...baseEntry,
21888
+ kind: "file",
21889
+ sizeBytes: stat4.size,
21890
+ sha256: await sha256File(sourcePath),
21891
+ secretShapes: secretShapes.length > 0 ? secretShapes : void 0
21892
+ });
21893
+ }
21894
+ async function buildCrossTreeRefs(refs) {
21895
+ const result = [];
21896
+ for (const ref of refs) {
21897
+ const sourcePath = path19.resolve(ref.path);
21898
+ const bundlePath = `runtime/${safeBundleSegment(ref.runtime)}/${safeBundleSegment(ref.label)}/${safeBundleSegment(path19.basename(sourcePath) || "session")}`;
21899
+ const entry = {
21900
+ runtime: ref.runtime,
21901
+ label: ref.label,
21902
+ sourcePath,
21903
+ bundlePath,
21904
+ reachable: ref.reachable !== false,
21905
+ reason: ref.reason
21906
+ };
21907
+ try {
21908
+ const stat4 = await lstat2(sourcePath);
21909
+ if (stat4.isFile()) {
21910
+ entry.sizeBytes = stat4.size;
21911
+ entry.sha256 = await sha256File(sourcePath);
21912
+ }
21913
+ } catch {
21914
+ entry.reachable = false;
21915
+ }
21916
+ result.push(entry);
21917
+ }
21918
+ return result;
21919
+ }
21920
+ async function sha256File(filePath) {
21921
+ const hash = createHash7("sha256");
21922
+ await new Promise((resolve, reject) => {
21923
+ const stream = createReadStream(filePath);
21924
+ stream.on("data", (chunk) => hash.update(chunk));
21925
+ stream.on("error", reject);
21926
+ stream.on("end", resolve);
21927
+ });
21928
+ return hash.digest("hex");
21929
+ }
21930
+ async function detectSecretShapes(sourcePath, relativePath) {
21931
+ const basename = path19.basename(relativePath);
21932
+ const lowerBasename = basename.toLowerCase();
21933
+ const shapes = /* @__PURE__ */ new Set();
21934
+ if (SECRET_FILE_NAMES.has(lowerBasename) || lowerBasename.startsWith(".env.")) {
21935
+ shapes.add(`file:${basename}`);
21936
+ try {
21937
+ const text = await readFile3(sourcePath, "utf8");
21938
+ for (const line of text.split(/\r?\n/)) {
21939
+ const match = ENV_KEY_PATTERN.exec(line);
21940
+ if (match) shapes.add(`env:${match[1]}`);
21941
+ }
21942
+ } catch {
21943
+ shapes.add("content:unreadable");
21944
+ }
21945
+ }
21946
+ if (/(?:secret|token|credential|api[-_]?key)/i.test(relativePath)) {
21947
+ shapes.add(`path:${basename}`);
21948
+ }
21949
+ return [...shapes].sort();
21950
+ }
21951
+ function normalizeProvidedSecretDisclosures(disclosures) {
21952
+ return disclosures.map((entry) => ({
21953
+ path: entry,
21954
+ shapes: ["provided"]
21955
+ }));
21956
+ }
21957
+ function normalizeProposalPaths(paths, workspace, source) {
21958
+ if (!paths) return { paths: [], refusals: [] };
21959
+ const result = [];
21960
+ const refusals = [];
21961
+ for (const value of paths) {
21962
+ const trimmed = value.trim();
21963
+ if (!trimmed) continue;
21964
+ if (path19.isAbsolute(trimmed)) {
21965
+ const relative = path19.relative(workspace, trimmed);
21966
+ if (relative.startsWith("..") || path19.isAbsolute(relative)) {
21967
+ refusals.push({
21968
+ path: trimmed,
21969
+ reason: "outside_workspace",
21970
+ source
21971
+ });
21972
+ continue;
21973
+ }
21974
+ try {
21975
+ result.push(normalizeRelativePath(relative));
21976
+ } catch {
21977
+ refusals.push({
21978
+ path: trimmed,
21979
+ reason: "unsafe_path",
21980
+ source
21981
+ });
21982
+ }
21983
+ continue;
21984
+ }
21985
+ try {
21986
+ result.push(normalizeRelativePath(trimmed));
21987
+ } catch {
21988
+ refusals.push({
21989
+ path: trimmed,
21990
+ reason: "unsafe_path",
21991
+ source
21992
+ });
21993
+ }
21994
+ }
21995
+ return { paths: [...new Set(result)], refusals };
21996
+ }
21997
+ function normalizeRelativePath(value) {
21998
+ const normalized = path19.normalize(value).replace(/^[\\/]+/, "");
21999
+ if (!normalized || normalized === "." || normalized.startsWith("..") || path19.isAbsolute(normalized)) {
22000
+ throw new Error(`unsafe migration export path: ${value}`);
22001
+ }
22002
+ return toPosixPath(normalized);
22003
+ }
22004
+ function isRegenerablePath(relativePath) {
22005
+ return normalizeRelativePath(relativePath).split("/").some((segment) => REGENERABLE_DIRECTORY_NAMES.includes(segment));
22006
+ }
22007
+ function hasExplicitIncludeDescendant(relativePath, explicitIncludePaths) {
22008
+ const prefix = `${relativePath}/`;
22009
+ for (const includePath of explicitIncludePaths) {
22010
+ if (includePath.startsWith(prefix)) return true;
22011
+ }
22012
+ return false;
22013
+ }
22014
+ function safeBundleSegment(value) {
22015
+ return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
22016
+ }
22017
+ function toPosixPath(value) {
22018
+ return value.split(path19.sep).join("/");
22019
+ }
22020
+ function sortBundleEntries(entries) {
22021
+ return [...entries].sort((a, b) => a.bundlePath.localeCompare(b.bundlePath));
22022
+ }
22023
+ function sortByPath(entries) {
22024
+ return [...entries].sort((a, b) => a.path.localeCompare(b.path));
22025
+ }
22026
+
22027
+ // src/agentMigrationHttpTransport.ts
22028
+ import { createHash as createHash8, randomBytes as randomBytes2, timingSafeEqual } from "crypto";
22029
+ import http2 from "http";
22030
+ var MigrationBundleStreamError = class extends Error {
22031
+ constructor(code) {
22032
+ super(code);
22033
+ this.code = code;
22034
+ }
22035
+ };
22036
+ var AgentMigrationGrantRegistry = class {
22037
+ grants = /* @__PURE__ */ new Map();
22038
+ now;
22039
+ constructor(now = () => /* @__PURE__ */ new Date()) {
22040
+ this.now = now;
22041
+ }
22042
+ createGrant(input) {
22043
+ const token = input.token ?? randomBytes2(32).toString("base64url");
22044
+ const manifestSha256 = sha256Buffer(canonicalJsonBuffer(input.manifest));
22045
+ const grant = {
22046
+ grantId: input.grantId ?? randomBytes2(16).toString("hex"),
22047
+ tokenHash: hashToken(token),
22048
+ expiresAt: input.expiresAt,
22049
+ oneTimeUse: input.oneTimeUse ?? true,
22050
+ state: "issued",
22051
+ manifest: input.manifest,
22052
+ manifestSha256,
22053
+ bundleEtag: `"agent-migration-${manifestSha256}"`
22054
+ };
22055
+ this.grants.set(grant.grantId, grant);
22056
+ return { grant, token };
22057
+ }
22058
+ get(grantId) {
22059
+ return this.grants.get(grantId);
22060
+ }
22061
+ authenticate(grantId, token) {
22062
+ const grant = this.grants.get(grantId);
22063
+ if (!grant || !token || !verifyTokenHash(token, grant.tokenHash)) {
22064
+ return { ok: false, status: 401, code: "migration_grant_auth_failed" };
22065
+ }
22066
+ if (grant.state === "revoked" || grant.state === "consumed") {
22067
+ return { ok: false, status: 410, code: "migration_grant_unavailable" };
22068
+ }
22069
+ if (this.now().getTime() >= grant.expiresAt.getTime()) {
22070
+ grant.state = "expired";
22071
+ return { ok: false, status: 410, code: "migration_grant_unavailable" };
22072
+ }
22073
+ return { ok: true, grant };
22074
+ }
22075
+ revokeAll() {
22076
+ for (const grant of this.grants.values()) {
22077
+ if (grant.state !== "consumed" && grant.state !== "expired") {
22078
+ grant.state = "revoked";
22079
+ }
22080
+ }
22081
+ }
22082
+ };
22083
+ function createAgentMigrationHttpTransport(options = {}) {
22084
+ const registry = new AgentMigrationGrantRegistry(options.now);
22085
+ const bundleStreamFactory = options.bundleStreamFactory ?? defaultBundleStream;
22086
+ const server = http2.createServer((req, res) => {
22087
+ void handleMigrationRequest(req, res, registry, bundleStreamFactory);
22088
+ });
22089
+ return {
22090
+ grants: registry,
22091
+ server,
22092
+ listen: (port = 0, host = "127.0.0.1") => new Promise((resolve, reject) => {
22093
+ server.once("error", reject);
22094
+ server.listen(port, host, () => {
22095
+ server.off("error", reject);
22096
+ const address = server.address();
22097
+ if (!address || typeof address === "string") {
22098
+ reject(new Error("migration transport listen did not produce a TCP address"));
22099
+ return;
22100
+ }
22101
+ resolve({ url: `http://${address.address}:${address.port}` });
22102
+ });
22103
+ }),
22104
+ close: () => new Promise((resolve, reject) => {
22105
+ registry.revokeAll();
22106
+ server.close((err) => {
22107
+ if (err) reject(err);
22108
+ else resolve();
22109
+ });
22110
+ })
22111
+ };
22112
+ }
22113
+ async function handleMigrationRequest(req, res, registry, bundleStreamFactory) {
22114
+ const parsed = new URL(req.url ?? "/", "http://127.0.0.1");
22115
+ const match = /^\/migration\/([^/]+)\/([^/]+)(?:\/([^/]+))?$/.exec(parsed.pathname);
22116
+ if (!match) {
22117
+ sendJson(res, 404, { code: "migration_route_not_found" });
22118
+ return;
22119
+ }
22120
+ const [, grantId, resource, resourceId] = match;
22121
+ const auth = registry.authenticate(decodeURIComponent(grantId), extractBearerToken(req));
22122
+ if (!auth.ok) {
22123
+ sendJson(res, auth.status, { code: auth.code });
22124
+ return;
22125
+ }
22126
+ if (resource === "manifest" && req.method === "GET" && !resourceId) {
22127
+ sendJson(res, 200, {
22128
+ manifestSha256: auth.grant.manifestSha256,
22129
+ manifest: auth.grant.manifest
22130
+ }, {
22131
+ "X-Raft-Manifest-Sha": auth.grant.manifestSha256,
22132
+ ETag: `"manifest-${auth.grant.manifestSha256}"`
22133
+ });
22134
+ return;
22135
+ }
22136
+ if (resource === "bundle.tar" && !resourceId) {
22137
+ if (req.method === "HEAD") {
22138
+ sendHead(res, auth.grant);
22139
+ return;
22140
+ }
22141
+ if (req.method === "GET") {
22142
+ await streamBundle(res, auth.grant, bundleStreamFactory);
22143
+ return;
22144
+ }
22145
+ }
22146
+ if (resource === "chunk" && req.method === "GET" && resourceId) {
22147
+ sendJson(res, 501, { code: "migration_chunk_not_implemented" });
22148
+ return;
22149
+ }
22150
+ sendJson(res, 405, { code: "migration_method_not_allowed" });
22151
+ }
22152
+ function sendHead(res, grant) {
22153
+ res.statusCode = 200;
22154
+ res.setHeader("X-Raft-Manifest-Sha", grant.manifestSha256);
22155
+ res.setHeader("ETag", grant.bundleEtag);
22156
+ res.setHeader("Accept-Ranges", "none");
22157
+ res.setHeader("X-Raft-Bundle-Size", "unknown");
22158
+ res.end();
22159
+ }
22160
+ async function streamBundle(res, grant, bundleStreamFactory) {
22161
+ if (grant.state === "streaming") {
22162
+ sendJson(res, 409, { code: "migration_bundle_stream_in_progress" });
22163
+ return;
22164
+ }
22165
+ if (grant.oneTimeUse && grant.state !== "issued" && grant.state !== "interrupted") {
22166
+ sendJson(res, 410, { code: "migration_grant_unavailable" });
22167
+ return;
22168
+ }
22169
+ let stream;
22170
+ try {
22171
+ stream = bundleStreamFactory(grant);
22172
+ } catch (err) {
22173
+ const code = err instanceof MigrationBundleStreamError ? err.code : "migration_bundle_stream_failed";
22174
+ sendJson(res, 500, { code });
22175
+ return;
22176
+ }
22177
+ grant.state = "streaming";
22178
+ let completed = false;
22179
+ res.on("close", () => {
22180
+ if (!completed && grant.state === "streaming") {
22181
+ grant.state = "interrupted";
22182
+ }
22183
+ });
22184
+ try {
22185
+ res.statusCode = 200;
22186
+ res.setHeader("Content-Type", "application/x-tar");
22187
+ res.setHeader("X-Raft-Manifest-Sha", grant.manifestSha256);
22188
+ res.setHeader("ETag", grant.bundleEtag);
22189
+ for await (const chunk of stream) {
22190
+ if (!res.write(chunk)) {
22191
+ await onceDrain(res);
22192
+ }
22193
+ }
22194
+ completed = true;
22195
+ grant.state = "consumed";
22196
+ res.end();
22197
+ } catch {
22198
+ if (!completed && grant.state === "streaming") {
22199
+ grant.state = "interrupted";
22200
+ }
22201
+ if (!res.headersSent) {
22202
+ sendJson(res, 500, { code: "migration_bundle_stream_failed" });
22203
+ } else {
22204
+ res.destroy();
22205
+ }
22206
+ }
22207
+ }
22208
+ function defaultBundleStream(grant) {
22209
+ void grant;
22210
+ throw new MigrationBundleStreamError("migration_bundle_stream_not_wired");
22211
+ }
22212
+ function extractBearerToken(req) {
22213
+ const directHeader = singleHeader(req.headers["x-raft-migration-token"]);
22214
+ if (directHeader) return directHeader;
22215
+ const authorization = singleHeader(req.headers.authorization);
22216
+ if (!authorization) return null;
22217
+ const match = /^Bearer\s+(.+)$/i.exec(authorization);
22218
+ return match?.[1] ?? null;
22219
+ }
22220
+ function singleHeader(value) {
22221
+ if (!value) return null;
22222
+ return Array.isArray(value) ? value[0] ?? null : value;
22223
+ }
22224
+ function sendJson(res, status, body, headers = {}) {
22225
+ const payload = JSON.stringify(body);
22226
+ res.writeHead(status, {
22227
+ "Content-Type": "application/json",
22228
+ "Content-Length": Buffer.byteLength(payload).toString(),
22229
+ ...headers
22230
+ });
22231
+ res.end(payload);
22232
+ }
22233
+ function hashToken(token) {
22234
+ return sha256Buffer(Buffer.from(token, "utf8"));
22235
+ }
22236
+ function verifyTokenHash(token, expectedHashHex) {
22237
+ const actual = Buffer.from(hashToken(token), "hex");
22238
+ const expected = Buffer.from(expectedHashHex, "hex");
22239
+ if (actual.byteLength !== expected.byteLength) return false;
22240
+ return timingSafeEqual(actual, expected);
22241
+ }
22242
+ function sha256Buffer(buffer) {
22243
+ return createHash8("sha256").update(buffer).digest("hex");
22244
+ }
22245
+ function canonicalJsonBuffer(value) {
22246
+ return Buffer.from(canonicalJson(value), "utf8");
22247
+ }
22248
+ function canonicalJson(value) {
22249
+ return JSON.stringify(sortJsonValue(value));
22250
+ }
22251
+ function sortJsonValue(value) {
22252
+ if (Array.isArray(value)) return value.map(sortJsonValue);
22253
+ if (!value || typeof value !== "object") return value;
22254
+ return Object.keys(value).sort().reduce((result, key) => {
22255
+ result[key] = sortJsonValue(value[key]);
22256
+ return result;
22257
+ }, {});
22258
+ }
22259
+ function onceDrain(res) {
22260
+ return new Promise((resolve) => res.once("drain", resolve));
22261
+ }
22262
+
21305
22263
  // src/core.ts
21306
22264
  var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
21307
22265
  var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels", "knowledge"];
@@ -21409,7 +22367,17 @@ var DAEMON_CORE_TRACE_ATTR_CONTRACTS = {
21409
22367
  spanAttrs: ["running_agents_count", "idle_agents_count"]
21410
22368
  },
21411
22369
  "daemon.agent.activity.produced": {
21412
- spanAttrs: ["agentId", "agent_id", "server_id", "machine_id", "activity", "detail_present", "entry_kinds", "ap_present", "launchId", "launch_id", "launch_id_present", "clientSeq", "client_seq", "client_seq_present", "correlation_id", "session_id_present", "runtime"]
22370
+ // isHeartbeat/is_heartbeat (#460 V1) and process_instance_id (#460 V3)
22371
+ // were emitted by agentProcessManager but scrubbed here (pilot violation
22372
+ // V4): this list is a RUNTIME allowlist (SpanAttrContractTracer), so an
22373
+ // emission-site key that is not added here silently dies before disk.
22374
+ // Witness for the pair lives in agentProcessManager.builtin.e2e.test.ts
22375
+ // behind a contract-wrapped tracer (production-isomorphic oracle).
22376
+ // producerFactId/producer_fact_id and activity_kind/detail_kind are ALSO
22377
+ // emitted-and-scrubbed today; deliberately NOT added here — banned-join-
22378
+ // key discipline for the fact id (#460 classification ruling) means
22379
+ // widening needs its own ruling, not a drive-by.
22380
+ spanAttrs: ["agentId", "agent_id", "server_id", "machine_id", "activity", "detail_present", "entry_kinds", "ap_present", "launchId", "launch_id", "launch_id_present", "clientSeq", "client_seq", "client_seq_present", "correlation_id", "session_id_present", "runtime", "isHeartbeat", "is_heartbeat", "process_instance_id"]
21413
22381
  },
21414
22382
  "daemon.agent.activity.skipped": {
21415
22383
  spanAttrs: ["agentId", "event_kind", "reason", "text_length"]
@@ -21418,7 +22386,7 @@ var DAEMON_CORE_TRACE_ATTR_CONTRACTS = {
21418
22386
  spanAttrs: ["agentId", "event_kind", "runtime"]
21419
22387
  }
21420
22388
  };
21421
- var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
22389
+ var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
21422
22390
  var RunnerCredentialMintError2 = class extends Error {
21423
22391
  code;
21424
22392
  retryable;
@@ -21454,9 +22422,9 @@ function runnerCredentialErrorDetail2(error) {
21454
22422
  async function waitForRunnerCredentialRetry2() {
21455
22423
  await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
21456
22424
  }
21457
- function parseDaemonCliArgs(args) {
22425
+ function parseDaemonCliArgs(args, env = {}) {
21458
22426
  let serverUrl = "";
21459
- let apiKey = "";
22427
+ let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
21460
22428
  for (let i = 0; i < args.length; i++) {
21461
22429
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
21462
22430
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -21473,13 +22441,13 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
21473
22441
  }
21474
22442
  }
21475
22443
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
21476
- const thisDir = path19.dirname(fileURLToPath(moduleUrl));
21477
- const bundledDistPath = path19.resolve(thisDir, "cli", "index.js");
22444
+ const thisDir = path20.dirname(fileURLToPath(moduleUrl));
22445
+ const bundledDistPath = path20.resolve(thisDir, "cli", "index.js");
21478
22446
  try {
21479
22447
  accessSync(bundledDistPath);
21480
22448
  return bundledDistPath;
21481
22449
  } catch {
21482
- const workspaceDistPath = path19.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
22450
+ const workspaceDistPath = path20.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
21483
22451
  accessSync(workspaceDistPath);
21484
22452
  return workspaceDistPath;
21485
22453
  }
@@ -21493,7 +22461,7 @@ function resolveSlockCliPathOrEmpty(moduleUrl = import.meta.url) {
21493
22461
  }
21494
22462
  async function runBundledSlockCli(argv) {
21495
22463
  process.argv = [process.execPath, "slock", ...argv];
21496
- await import("./dist-P3SAWND7.js");
22464
+ await import("./dist-PAW4LJQD.js");
21497
22465
  }
21498
22466
  function detectRuntimes(tracer = noopTracer) {
21499
22467
  const ids = [];
@@ -21701,7 +22669,7 @@ var DaemonCore = class {
21701
22669
  }
21702
22670
  resolveMachineStateRoot() {
21703
22671
  if (this.options.machineStateDir) return this.options.machineStateDir;
21704
- if (this.options.dataDir) return path19.join(path19.dirname(this.options.dataDir), "machines");
22672
+ if (this.options.dataDir) return path20.join(path20.dirname(this.options.dataDir), "machines");
21705
22673
  return resolveDefaultMachineStateRoot();
21706
22674
  }
21707
22675
  shouldEnableLocalTrace() {
@@ -21729,7 +22697,7 @@ var DaemonCore = class {
21729
22697
  sinks: [this.localTraceSink]
21730
22698
  }));
21731
22699
  this.agentManager.setTracer(this.tracer);
21732
- this.agentManager.setCliTransportTraceDir(path19.join(machineDir, "traces"));
22700
+ this.agentManager.setCliTransportTraceDir(path20.join(machineDir, "traces"));
21733
22701
  }
21734
22702
  installTraceBundleUploader(machineDir) {
21735
22703
  if (!this.shouldEnableLocalTrace()) return;
@@ -22018,7 +22986,7 @@ var DaemonCore = class {
22018
22986
  session_id_present: Boolean(msg.config.sessionId)
22019
22987
  }, "error");
22020
22988
  this.connection.send({ type: "agent:status", agentId: msg.agentId, status: "inactive", launchId: msg.launchId });
22021
- this.connection.send({ type: "agent:activity", agentId: msg.agentId, activity: "offline", detail: classification.userMessage, launchId: msg.launchId });
22989
+ this.connection.send({ type: "agent:activity", agentId: msg.agentId, activity: "offline", detail: classification.userMessage, launchId: msg.launchId, observedAtMs: Date.now() });
22022
22990
  });
22023
22991
  break;
22024
22992
  case "agent:stop":
@@ -22435,9 +23403,15 @@ var DaemonCore = class {
22435
23403
 
22436
23404
  export {
22437
23405
  subscribeDaemonLogs,
23406
+ DAEMON_API_KEY_ENV,
23407
+ scrubDaemonAuthEnv,
22438
23408
  resolveWorkspaceDirectoryPath,
22439
23409
  scanWorkspaceDirectories,
22440
23410
  deleteWorkspaceDirectory,
23411
+ AGENT_MIGRATION_BUNDLE_SCHEMA_VERSION,
23412
+ buildAgentMigrationExportManifest,
23413
+ AgentMigrationGrantRegistry,
23414
+ createAgentMigrationHttpTransport,
22441
23415
  DAEMON_CORE_TRACE_ATTR_CONTRACTS,
22442
23416
  DAEMON_CLI_USAGE,
22443
23417
  parseDaemonCliArgs,