@botiverse/raft-daemon 0.69.0 → 0.70.1

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),
@@ -2225,7 +2362,12 @@ var agentApiIntegrationLoginResponseSchema = passthroughObject({
2225
2362
  status: z2.enum(["logged_in", "already_logged_in", "approval_required"]),
2226
2363
  service: agentApiIntegrationServiceSchema,
2227
2364
  scopes: z2.array(z2.string()),
2228
- requestId: z2.string(),
2365
+ requestId: z2.string().optional(),
2366
+ session: passthroughObject({
2367
+ status: z2.literal("stored"),
2368
+ source: z2.enum(["cache", "fresh"]),
2369
+ path: nullableStringSchema
2370
+ }).optional(),
2229
2371
  approval: passthroughObject({
2230
2372
  requestId: z2.string(),
2231
2373
  target: nullableStringSchema,
@@ -3182,6 +3324,55 @@ var SUPPORTED_TRANSLATION_LANGUAGE_SET = new Set(
3182
3324
  SUPPORTED_TRANSLATION_LANGUAGE_CODES
3183
3325
  );
3184
3326
 
3327
+ // ../shared/src/oauthScopes.ts
3328
+ var RAFT_OAUTH_SCOPE_CATALOG = {
3329
+ openid: {
3330
+ phase: "identity",
3331
+ defaultAllowed: true,
3332
+ publicDiscovery: true,
3333
+ requiresResource: false,
3334
+ label: "OpenID identity"
3335
+ },
3336
+ profile: {
3337
+ phase: "identity",
3338
+ defaultAllowed: true,
3339
+ publicDiscovery: true,
3340
+ requiresResource: false,
3341
+ label: "Basic profile"
3342
+ },
3343
+ identity: {
3344
+ phase: "identity",
3345
+ defaultAllowed: true,
3346
+ publicDiscovery: true,
3347
+ requiresResource: false,
3348
+ label: "Raft identity card"
3349
+ },
3350
+ "agent:event:write": {
3351
+ phase: "agent_inbound",
3352
+ defaultAllowed: false,
3353
+ publicDiscovery: true,
3354
+ requiresResource: true,
3355
+ label: "Send structured events to an agent"
3356
+ },
3357
+ "agent:notification:write": {
3358
+ phase: "agent_inbound",
3359
+ defaultAllowed: false,
3360
+ publicDiscovery: true,
3361
+ requiresResource: true,
3362
+ label: "Send notifications to an agent"
3363
+ },
3364
+ "agent:action_request:write": {
3365
+ phase: "agent_inbound",
3366
+ defaultAllowed: false,
3367
+ publicDiscovery: false,
3368
+ requiresResource: true,
3369
+ label: "Request agent action"
3370
+ }
3371
+ };
3372
+ var RAFT_OAUTH_SCOPES_SUPPORTED = Object.keys(RAFT_OAUTH_SCOPE_CATALOG);
3373
+ var RAFT_OAUTH_DEFAULT_ALLOWED_SCOPES = RAFT_OAUTH_SCOPES_SUPPORTED.filter((scope) => RAFT_OAUTH_SCOPE_CATALOG[scope].defaultAllowed);
3374
+ var RAFT_OAUTH_PUBLIC_DISCOVERY_SCOPES = RAFT_OAUTH_SCOPES_SUPPORTED.filter((scope) => RAFT_OAUTH_SCOPE_CATALOG[scope].publicDiscovery);
3375
+
3185
3376
  // ../shared/src/testing/failpoints.ts
3186
3377
  var NoopFailpointRegistry = class {
3187
3378
  get enabled() {
@@ -3739,6 +3930,8 @@ var DISPLAY_PLAN_CONFIG = {
3739
3930
  }
3740
3931
  };
3741
3932
  var FREE_MONTHLY_FILE_UPLOAD_LIMIT_BYTES = 100 * 1024 * 1024;
3933
+ var FREE_SINGLE_FILE_UPLOAD_LIMIT_BYTES = 50 * 1024 * 1024;
3934
+ var PRO_SINGLE_FILE_UPLOAD_LIMIT_BYTES = 200 * 1024 * 1024;
3742
3935
  var TRIAL_START_DATE = /* @__PURE__ */ new Date("2026-04-18T00:00:00Z");
3743
3936
  var TRIAL_END_DATE = /* @__PURE__ */ new Date("2026-06-23T12:00:00Z");
3744
3937
  var TRIAL_DURATION_DAYS = (TRIAL_END_DATE.getTime() - TRIAL_START_DATE.getTime()) / (24 * 60 * 60 * 1e3);
@@ -4018,8 +4211,8 @@ async function executeResponseRequest(url, init, {
4018
4211
  }
4019
4212
 
4020
4213
  // src/directUploadCapability.ts
4021
- function joinUrl(base, path20) {
4022
- return `${base.replace(/\/+$/, "")}${path20}`;
4214
+ function joinUrl(base, path21) {
4215
+ return `${base.replace(/\/+$/, "")}${path21}`;
4023
4216
  }
4024
4217
  function jsonHeaders(apiKey) {
4025
4218
  return {
@@ -4237,7 +4430,7 @@ Threads are sub-conversations attached to a specific message. They let you discu
4237
4430
  - 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
4431
  - **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
4432
  - 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"\`
4433
+ - You can read thread history: \`raft message read --target "#general:00000000"\`
4241
4434
  - 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
4435
  - Threads cannot be nested \u2014 you cannot start a thread inside a thread.`;
4243
4436
  }
@@ -4245,7 +4438,7 @@ function buildDiscoverySection() {
4245
4438
  return `### Discovering people and channels
4246
4439
 
4247
4440
  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"\`.
4441
+ 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
4442
  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
4443
  }
4251
4444
  function buildChannelAwarenessSection() {
@@ -4259,9 +4452,9 @@ Each channel has a **name** and optionally a **description** that define its pur
4259
4452
  function buildReadingHistorySection() {
4260
4453
  return `### Reading history
4261
4454
 
4262
- \`raft message read --channel "#channel-name"\` or \`raft message read --channel dm:@peer-name\` or \`raft message read --channel "#channel:shortid"\`
4455
+ \`raft message read --target "#channel-name"\` or \`raft message read --target dm:@peer-name\` or \`raft message read --target "#channel:shortid"\`
4263
4456
 
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\`.`;
4457
+ 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
4458
  }
4266
4459
  function buildHistoricalReferencesSection() {
4267
4460
  return `### Historical references
@@ -4290,7 +4483,7 @@ Only top-level channel / DM messages can become tasks. Messages inside threads a
4290
4483
  **Assignee** is independent from status \u2014 a task can be claimed or unclaimed at any status except \`done\`.
4291
4484
 
4292
4485
  **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\`.
4486
+ 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
4487
  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
4488
  3. Post updates in the task's thread: \`raft message send --target "#channel:msgShortId" <<'${D}'\` followed by the message body and \`${D}\`
4296
4489
  4. When done, set status to \`in_review\` so a human can validate via \`raft task update\`
@@ -4302,7 +4495,7 @@ Only top-level channel / DM messages can become tasks. Messages inside threads a
4302
4495
  - \`raft task create\` only creates the task \u2014 to own it, call \`raft task claim\` afterward.
4303
4496
  - 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
4497
  - 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\`.
4498
+ - If the work already exists as a message, reuse it via \`raft task claim --target "#channel" --message-id abc12345\`.
4306
4499
 
4307
4500
  **Creating new tasks:**
4308
4501
  - 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 +4714,7 @@ function buildPrompt(config, opts) {
4521
4714
  const channelAwarenessSection = cliGuideSections.channelAwareness;
4522
4715
  const thirdPartyIntegrationsSection = `### Third-party integrations
4523
4716
 
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.`;
4717
+ 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
4718
  const readingHistorySection = cliGuideSections.readingHistory;
4526
4719
  const historicalReferenceSection = cliGuideSections.historicalReferences;
4527
4720
  const tasksSection = cliGuideSections.tasks;
@@ -5652,7 +5845,7 @@ function shortMessageId2(value) {
5652
5845
  return value.slice(0, 8);
5653
5846
  }
5654
5847
  function normalizeSenderType(value) {
5655
- return value === "human" || value === "agent" || value === "system" ? value : void 0;
5848
+ return value === "human" || value === "agent" || value === "system" || value === "third_party_app" ? value : void 0;
5656
5849
  }
5657
5850
  function nonEmptyString(value) {
5658
5851
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
@@ -6040,6 +6233,10 @@ async function readRequestBody(req) {
6040
6233
  function messageSeq3(message) {
6041
6234
  return Number(message.seq ?? 0);
6042
6235
  }
6236
+ function isThirdPartyAgentEvent(message) {
6237
+ const thirdPartyEvent = message.third_party_event;
6238
+ return typeof thirdPartyEvent?.id === "string" && thirdPartyEvent.id.length > 0;
6239
+ }
6043
6240
  function localAgentApiInboxResponse(registration) {
6044
6241
  const coordinator = registration.inboxCoordinator;
6045
6242
  if (!coordinator) return void 0;
@@ -6090,6 +6287,7 @@ function localAgentApiEventsResponse(registration, target) {
6090
6287
  const normalized = sortInboxMessagesBySeq(normalizeInboxVisibleMessages(pending));
6091
6288
  const filtered = parsedQuery.sinceSeq !== null ? normalized.filter((message) => {
6092
6289
  const seq = messageSeq3(message);
6290
+ if ((!Number.isFinite(seq) || seq <= 0) && isThirdPartyAgentEvent(message)) return true;
6093
6291
  return Number.isFinite(seq) && seq > parsedQuery.sinceSeq;
6094
6292
  }) : normalized;
6095
6293
  const events = filtered.slice(0, parsedQuery.limit);
@@ -7658,7 +7856,7 @@ function codexNotificationDiagnosticEvent(message) {
7658
7856
  return null;
7659
7857
  }
7660
7858
  const sessionId = codexMessageThreadId(message);
7661
- const path20 = boundedString(params.path);
7859
+ const path21 = boundedString(params.path);
7662
7860
  return {
7663
7861
  kind: "runtime_diagnostic",
7664
7862
  severity: "warning",
@@ -7666,7 +7864,7 @@ function codexNotificationDiagnosticEvent(message) {
7666
7864
  itemType: message.method,
7667
7865
  message: diagnosticMessage,
7668
7866
  ...details ? { details } : {},
7669
- ...path20 ? { path: path20 } : {},
7867
+ ...path21 ? { path: path21 } : {},
7670
7868
  ...params.range !== void 0 ? { range: params.range } : {},
7671
7869
  payloadBytes: payloadBytes(params),
7672
7870
  ...sessionId ? { sessionId } : {}
@@ -12524,6 +12722,9 @@ function computeTarget(channelType, channelName, parentChannelName, parentChanne
12524
12722
  return `#${channelName}`;
12525
12723
  }
12526
12724
  function formatAgentMessageVisibleTarget(message) {
12725
+ if (message.third_party_event) {
12726
+ return `agent-event:${getMessageShortId(message.third_party_event.id)}`;
12727
+ }
12527
12728
  return computeTarget(message.channel_type, message.channel_name, message.parent_channel_name, message.parent_channel_type);
12528
12729
  }
12529
12730
  function formatProxyVisibleMessageTarget(message) {
@@ -13371,24 +13572,38 @@ function safeSessionFilename(value) {
13371
13572
  const normalized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
13372
13573
  return normalized || "unknown-session";
13373
13574
  }
13374
- function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
13575
+ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir, join, resolve) {
13375
13576
  try {
13376
13577
  const dir = path14.join(fallbackDir, ".slock", "runtime-sessions");
13377
13578
  mkdirSync4(dir, { recursive: true });
13378
13579
  const filePath = path14.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
13580
+ const searchedPaths = resolve?.searchedPaths ?? [];
13379
13581
  writeFileSync4(filePath, JSON.stringify({
13380
13582
  type: "runtime_session_handoff",
13381
13583
  runtime,
13382
13584
  sessionId,
13585
+ // Persisted L1<->L2 join keys (#460 V3): without these the
13586
+ // session->launch mapping lives only in daemon memory and old L1
13587
+ // transcripts can never re-join their launch after restart/adopt.
13588
+ // ids-only, closed fields (no content payload).
13589
+ launchId: join?.launchId ?? null,
13590
+ processInstanceId: join?.processInstanceId ?? null,
13591
+ // Negative-space (fruit#0 pt2): a resolve-miss is explicit evidence, not a
13592
+ // silent "not found". Recording the lookup method + the directories checked
13593
+ // makes "looked in the wrong place" distinguishable from "looked in the right
13594
+ // place and it is genuinely absent". ids/paths only — no transcript content.
13595
+ resolveStatus: "transcript_resolve_missing",
13596
+ lookupMethod: resolve?.lookupMethod ?? "none",
13597
+ searchedPaths,
13383
13598
  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."
13599
+ 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
13600
  }) + "\n", { mode: 384 });
13386
13601
  return {
13387
13602
  label: sessionId,
13388
13603
  path: filePath,
13389
13604
  runtime,
13390
13605
  reachable: true,
13391
- reason: "native session file path not found; using daemon handoff file"
13606
+ reason: `native session file path not found; using daemon handoff file; searched=[${searchedPaths.join(", ")}]`
13392
13607
  };
13393
13608
  } catch {
13394
13609
  return null;
@@ -13413,12 +13628,16 @@ function ensureRuntimeHomeDir(config, defaultHomeDir, workspacePath, opts = {})
13413
13628
  function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir, opts) {
13414
13629
  let resolvedPath = null;
13415
13630
  let lookupMethod = "none";
13631
+ const searchedPaths = [];
13416
13632
  if (runtime === "claude") {
13417
13633
  lookupMethod = "claude_jsonl";
13418
- resolvedPath = findSessionJsonl(path14.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`);
13634
+ const claudeRoot = path14.join(homeDir, ".claude", "projects");
13635
+ searchedPaths.push(claudeRoot);
13636
+ resolvedPath = findSessionJsonl(claudeRoot, (filename) => filename === `${sessionId}.jsonl`);
13419
13637
  } else if (runtime === "codex") {
13420
13638
  lookupMethod = "codex_jsonl";
13421
13639
  for (const root of codexSessionRootCandidates(homeDir)) {
13640
+ searchedPaths.push(root);
13422
13641
  resolvedPath = findSessionJsonl(root, (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId));
13423
13642
  if (resolvedPath) break;
13424
13643
  }
@@ -13430,7 +13649,13 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
13430
13649
  resolvedPath = findPiSessionFile2(sessionId, opts?.workingDirectory, homeDir);
13431
13650
  }
13432
13651
  if (!resolvedPath && fallbackDir) {
13433
- const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
13652
+ const fallback = writeRuntimeSessionHandoff(
13653
+ runtime,
13654
+ sessionId,
13655
+ fallbackDir,
13656
+ { launchId: opts?.launchId, processInstanceId: opts?.processInstanceId },
13657
+ { lookupMethod, searchedPaths }
13658
+ );
13434
13659
  if (fallback) {
13435
13660
  return {
13436
13661
  ...fallback,
@@ -13445,7 +13670,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
13445
13670
  reachable: Boolean(resolvedPath)
13446
13671
  };
13447
13672
  if (!resolvedPath) {
13448
- ref.reason = `session file path not found; attempted_lookup=${lookupMethod}`;
13673
+ ref.reason = `session file path not found; attempted_lookup=${lookupMethod}; searched=[${searchedPaths.join(", ")}]`;
13449
13674
  }
13450
13675
  return ref;
13451
13676
  }
@@ -13499,7 +13724,7 @@ function formatIncomingMessage(message, options = {}) {
13499
13724
  `[Slock thread context: you were added to a new thread via @mention.]`,
13500
13725
  `parent: ${message.thread_join_context.parent_target}`,
13501
13726
  `thread: ${message.thread_join_context.thread_target}`,
13502
- `suggested next step: raft message read --channel "${message.thread_join_context.suggested_read_history_target}"`,
13727
+ `suggested next step: raft message read --target "${message.thread_join_context.suggested_read_history_target}"`,
13503
13728
  "",
13504
13729
  "Parent message:",
13505
13730
  formatThreadContextMessage(message.thread_join_context.parent_message),
@@ -15329,7 +15554,8 @@ var AgentProcessManager = class _AgentProcessManager {
15329
15554
  detailKind: activity.statusEntry.detailKind ?? "daemon_activity",
15330
15555
  entries: activity.entries,
15331
15556
  launchId: ap?.launchId || void 0,
15332
- clientSeq: ap ? this.nextActivityClientSeq(agentId) : void 0
15557
+ clientSeq: ap ? this.nextActivityClientSeq(agentId) : void 0,
15558
+ isHeartbeat: false
15333
15559
  });
15334
15560
  }
15335
15561
  recordRuntimeDiagnosticActivity(agentId, ap, event) {
@@ -15342,7 +15568,8 @@ var AgentProcessManager = class _AgentProcessManager {
15342
15568
  detailKind: ap.lastActivityDetailKind || "none",
15343
15569
  entries: [runtimeDiagnosticTrajectoryEntry(event)],
15344
15570
  launchId: ap.launchId || void 0,
15345
- clientSeq: this.nextActivityClientSeq(agentId)
15571
+ clientSeq: this.nextActivityClientSeq(agentId),
15572
+ isHeartbeat: false
15346
15573
  });
15347
15574
  this.recordDaemonTrace("daemon.runtime.diagnostic", {
15348
15575
  agentId,
@@ -15393,7 +15620,8 @@ var AgentProcessManager = class _AgentProcessManager {
15393
15620
  detailKind: ap.lastActivityDetailKind || "none",
15394
15621
  entries: [runtimeRecoveryTrajectoryEntry(event)],
15395
15622
  launchId: ap.launchId || void 0,
15396
- clientSeq: this.nextActivityClientSeq(agentId)
15623
+ clientSeq: this.nextActivityClientSeq(agentId),
15624
+ isHeartbeat: false
15397
15625
  });
15398
15626
  this.recordDaemonTrace("daemon.runtime.recovery.visible", {
15399
15627
  agentId,
@@ -15641,7 +15869,8 @@ var AgentProcessManager = class _AgentProcessManager {
15641
15869
  this.lifecycleRecords.setRestartSnapshot(agentId, {
15642
15870
  config: this.buildRestartSafeConfig(ap.config, nextConfigSessionId),
15643
15871
  sessionId: nextConfigSessionId,
15644
- launchId: nextLaunchId
15872
+ launchId: nextLaunchId,
15873
+ processInstanceId: ap.processInstanceId
15645
15874
  });
15646
15875
  this.recordStartRebind(agentId, start, reason, previousLaunchId, nextLaunchId, nextSessionId);
15647
15876
  this.sendAgentStatus(agentId, "active", nextLaunchId);
@@ -16072,6 +16301,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16072
16301
  config: this.buildRestartSafeConfig(runtimeConfig, runtimeConfig.sessionId || null),
16073
16302
  sessionId: runtimeConfig.sessionId || null,
16074
16303
  launchId: effectiveLaunchId
16304
+ // No live AgentProcess in this adoption branch — omit rather than
16305
+ // fabricate; the marker's processInstanceId is nullable by contract.
16075
16306
  });
16076
16307
  this.sendAgentStatus(agentId, "active", effectiveLaunchId);
16077
16308
  this.broadcastActivity(agentId, "online", "Process idle", [], void 0, "idle");
@@ -16161,7 +16392,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16161
16392
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16162
16393
  config: this.buildRestartSafeConfig(runtimeConfig, restartSessionId),
16163
16394
  sessionId: restartSessionId,
16164
- launchId: effectiveLaunchId
16395
+ launchId: effectiveLaunchId,
16396
+ processInstanceId: agentProcess.processInstanceId
16165
16397
  });
16166
16398
  if (pendingStartRebind) {
16167
16399
  this.recordStartRebind(agentId, pendingStartRebind, "startup_registered", originalLaunchId, effectiveLaunchId, agentProcess.sessionId);
@@ -16363,7 +16595,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16363
16595
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16364
16596
  config: nextConfig,
16365
16597
  sessionId: ap.sessionId,
16366
- launchId: ap.launchId
16598
+ launchId: ap.launchId,
16599
+ processInstanceId: ap.processInstanceId
16367
16600
  });
16368
16601
  this.broadcastMessageReceivedActivity(agentId);
16369
16602
  this.lifecycleRecords.deleteRestartSnapshot(agentId);
@@ -16381,7 +16614,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16381
16614
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16382
16615
  config: nextConfig,
16383
16616
  sessionId: ap.sessionId,
16384
- launchId: ap.launchId
16617
+ launchId: ap.launchId,
16618
+ processInstanceId: ap.processInstanceId
16385
16619
  });
16386
16620
  const report = this.recordSpawnFailure(agentId, "runner_credential_mint");
16387
16621
  this.assertStartPendingDeliveryInvariants("queued-continuation-runner-credential-mint-failure");
@@ -16401,7 +16635,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16401
16635
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16402
16636
  config: nextConfig,
16403
16637
  sessionId: ap.sessionId,
16404
- launchId: ap.launchId
16638
+ launchId: ap.launchId,
16639
+ processInstanceId: ap.processInstanceId
16405
16640
  });
16406
16641
  this.broadcastActivity(agentId, "online", "Process idle", [], void 0, "idle");
16407
16642
  });
@@ -16410,7 +16645,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16410
16645
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16411
16646
  config: this.buildRestartSafeConfig(ap.config, ap.sessionId),
16412
16647
  sessionId: ap.sessionId,
16413
- launchId: ap.launchId
16648
+ launchId: ap.launchId,
16649
+ processInstanceId: ap.processInstanceId
16414
16650
  });
16415
16651
  if (!ap.driver.supportsStdinNotification) {
16416
16652
  logger.info(`[Agent ${agentId}] Turn completed; cached idle state for future restart`);
@@ -16422,7 +16658,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16422
16658
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16423
16659
  config: this.buildRestartSafeConfig(ap.config, ap.sessionId),
16424
16660
  sessionId: ap.sessionId,
16425
- launchId: ap.launchId
16661
+ launchId: ap.launchId,
16662
+ processInstanceId: ap.processInstanceId
16426
16663
  });
16427
16664
  logger.warn(`[Agent ${agentId}] Recoverable provider stream failure (${reason}) \u2014 keeping agent wakeable`);
16428
16665
  this.sendAgentStatus(agentId, "active", ap.launchId);
@@ -16638,7 +16875,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16638
16875
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16639
16876
  config: retryConfig,
16640
16877
  sessionId: retrySessionId,
16641
- launchId: ap.launchId
16878
+ launchId: ap.launchId,
16879
+ processInstanceId: ap.processInstanceId
16642
16880
  });
16643
16881
  this.recordDaemonTrace("daemon.agent.startup_timeout.retry_config_cached", {
16644
16882
  agentId,
@@ -17552,7 +17790,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
17552
17790
  }
17553
17791
  return result;
17554
17792
  }
17555
- buildRuntimeProfileReport(agentId, config, sessionId, launchId, observedRuntimeHomeDir) {
17793
+ buildRuntimeProfileReport(agentId, config, sessionId, launchId, observedRuntimeHomeDir, processInstanceId) {
17556
17794
  const workspacePath = path14.join(this.dataDir, agentId);
17557
17795
  const runtimeHomeDir = observedRuntimeHomeDir || resolveRuntimeHomeDir(config, this.runtimeSessionHomeDir, workspacePath, { agentId, slockHome: this.slockHome });
17558
17796
  return {
@@ -17568,7 +17806,16 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
17568
17806
  path: workspacePath,
17569
17807
  reachable: true
17570
17808
  },
17571
- sessionRef: sessionId ? resolveRuntimeSessionRef(config.runtime, sessionId, runtimeHomeDir, workspacePath, { agentId, workingDirectory: workspacePath }) : null
17809
+ sessionRef: sessionId ? resolveRuntimeSessionRef(config.runtime, sessionId, runtimeHomeDir, workspacePath, {
17810
+ agentId,
17811
+ workingDirectory: workspacePath,
17812
+ // Join keys come from the CALLER's context (running agent OR idle
17813
+ // restart snapshot) — reading only live state here left the
17814
+ // restart/adopt path writing launchId:null markers, exactly the
17815
+ // V3 class this exists to close (Leiysky review on #3863).
17816
+ launchId: launchId || void 0,
17817
+ processInstanceId: processInstanceId ?? this.agents.get(agentId)?.processInstanceId
17818
+ }) : null
17572
17819
  }
17573
17820
  };
17574
17821
  }
@@ -17580,12 +17827,13 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
17580
17827
  running.config,
17581
17828
  running.sessionId,
17582
17829
  running.launchId,
17583
- running.runtime.currentRuntimeHomeDir
17830
+ running.runtime.currentRuntimeHomeDir,
17831
+ running.processInstanceId
17584
17832
  );
17585
17833
  }
17586
17834
  const idle = this.lifecycleRecords.getRestartSnapshot(agentId);
17587
17835
  if (idle) {
17588
- return this.buildRuntimeProfileReport(agentId, idle.config, idle.sessionId, idle.launchId);
17836
+ return this.buildRuntimeProfileReport(agentId, idle.config, idle.sessionId, idle.launchId, null, idle.processInstanceId);
17589
17837
  }
17590
17838
  return null;
17591
17839
  }
@@ -17983,7 +18231,9 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
17983
18231
  }
17984
18232
  const ref = resolveRuntimeSessionRef(runtime, actualSessionId, homeDir, workspaceDir, {
17985
18233
  agentId,
17986
- workingDirectory: workspaceDir
18234
+ workingDirectory: workspaceDir,
18235
+ launchId: this.agents.get(agentId)?.launchId || void 0,
18236
+ processInstanceId: this.agents.get(agentId)?.processInstanceId
17987
18237
  });
17988
18238
  const tier = runtimeTier(runtime);
17989
18239
  const span = this.tracer.startSpan("daemon.session_transcript.read", {
@@ -18241,6 +18491,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18241
18491
  const launchId = launchIdOverride || ap?.launchId || void 0;
18242
18492
  const clientSeq = this.nextActivityClientSeq(agentId);
18243
18493
  const producerFactId = this.buildActivityProducerFactId(agentId, launchId, clientSeq);
18494
+ const observedAtMs = Date.now();
18244
18495
  this.sendToServer({
18245
18496
  type: "agent:activity",
18246
18497
  agentId,
@@ -18251,9 +18502,11 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18251
18502
  entries,
18252
18503
  launchId,
18253
18504
  clientSeq,
18254
- producerFactId
18505
+ producerFactId,
18506
+ observedAtMs,
18507
+ isHeartbeat: false
18255
18508
  });
18256
- this.recordActivityProducedTrace(agentId, activityKind, detail, detailKind, entries, ap, launchId, clientSeq, producerFactId);
18509
+ this.recordActivityProducedTrace(agentId, activityKind, detail, detailKind, entries, ap, launchId, clientSeq, producerFactId, false);
18257
18510
  if (ap) {
18258
18511
  ap.lastActivityKind = activityKind;
18259
18512
  ap.lastActivity = activityDisplay;
@@ -18270,6 +18523,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18270
18523
  const heartbeatLaunchId = launchIdOverride || ap.launchId || void 0;
18271
18524
  const heartbeatClientSeq = this.nextActivityClientSeq(agentId);
18272
18525
  const heartbeatProducerFactId = this.buildActivityProducerFactId(agentId, heartbeatLaunchId, heartbeatClientSeq);
18526
+ const heartbeatObservedAtMs = Date.now();
18273
18527
  this.sendToServer({
18274
18528
  type: "agent:activity",
18275
18529
  agentId,
@@ -18279,9 +18533,13 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18279
18533
  detailKind: ap.lastActivityDetailKind,
18280
18534
  launchId: heartbeatLaunchId,
18281
18535
  clientSeq: heartbeatClientSeq,
18282
- producerFactId: heartbeatProducerFactId
18536
+ producerFactId: heartbeatProducerFactId,
18537
+ observedAtMs: heartbeatObservedAtMs,
18538
+ // The one knowing site: this timer re-broadcasts stale
18539
+ // lastActivity, so it declares its replay provenance.
18540
+ isHeartbeat: true
18283
18541
  });
18284
- this.recordActivityProducedTrace(agentId, ap.lastActivityKind, ap.lastActivityDetail, ap.lastActivityDetailKind, [], ap, heartbeatLaunchId, heartbeatClientSeq, heartbeatProducerFactId);
18542
+ this.recordActivityProducedTrace(agentId, ap.lastActivityKind, ap.lastActivityDetail, ap.lastActivityDetailKind, [], ap, heartbeatLaunchId, heartbeatClientSeq, heartbeatProducerFactId, true);
18285
18543
  }, ACTIVITY_HEARTBEAT_MS);
18286
18544
  ap.activityHeartbeat = { kind: "active", timer };
18287
18545
  }
@@ -18290,7 +18548,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18290
18548
  }
18291
18549
  }
18292
18550
  }
18293
- recordActivityProducedTrace(agentId, activityKind, detail, detailKind, entries, ap, launchId, clientSeq, producerFactId) {
18551
+ recordActivityProducedTrace(agentId, activityKind, detail, detailKind, entries, ap, launchId, clientSeq, producerFactId, isHeartbeat) {
18294
18552
  const runtimeContext = ap?.config.runtimeContext;
18295
18553
  this.recordDaemonTrace("daemon.agent.activity.produced", {
18296
18554
  agentId,
@@ -18312,6 +18570,11 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18312
18570
  client_seq_present: typeof clientSeq === "number",
18313
18571
  producerFactId,
18314
18572
  producer_fact_id: producerFactId,
18573
+ // #460 V1: the wire's producer-declared replay-provenance bit must be
18574
+ // independently verifiable from daemon-side evidence (L2<->L3 seam);
18575
+ // closed boolean, mirrors the agent:activity isHeartbeat field exactly.
18576
+ isHeartbeat,
18577
+ is_heartbeat: isHeartbeat,
18315
18578
  correlation_id: `agent:${agentId}:daemonActivity:${launchId ?? "legacy"}:${clientSeq ?? "unsequenced"}`,
18316
18579
  session_id_present: Boolean(ap?.sessionId),
18317
18580
  runtime: ap?.config.runtime
@@ -18360,9 +18623,10 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18360
18623
  launchId,
18361
18624
  probeId,
18362
18625
  clientSeq,
18363
- producerFactId
18626
+ producerFactId,
18627
+ isHeartbeat: false
18364
18628
  });
18365
- this.recordActivityProducedTrace(agentId, activityKind, detail, detailKind, [], ap, launchId, clientSeq, producerFactId);
18629
+ this.recordActivityProducedTrace(agentId, activityKind, detail, detailKind, [], ap, launchId, clientSeq, producerFactId, false);
18366
18630
  }
18367
18631
  flushPendingTrajectory(agentId) {
18368
18632
  const ap = this.agents.get(agentId);
@@ -21302,6 +21566,563 @@ function assertLegacyDaemonKeyNotAdoptedByComputer(options) {
21302
21566
  if (match) throw new LegacyDaemonKeyAdoptedByComputerError(match);
21303
21567
  }
21304
21568
 
21569
+ // src/agentMigrationExport.ts
21570
+ import { createHash as createHash7 } from "crypto";
21571
+ import { createReadStream } from "fs";
21572
+ import { lstat as lstat2, readdir as readdir4, readFile as readFile3, readlink } from "fs/promises";
21573
+ import path19 from "path";
21574
+ var AGENT_MIGRATION_BUNDLE_SCHEMA_VERSION = "agent-bundle/v1";
21575
+ var REGENERABLE_DIRECTORY_NAMES = [
21576
+ ".venv",
21577
+ "__pycache__",
21578
+ "dist",
21579
+ "node_modules",
21580
+ "target"
21581
+ ];
21582
+ var SECRET_FILE_NAMES = /* @__PURE__ */ new Set([
21583
+ ".env",
21584
+ ".env.local",
21585
+ ".env.development",
21586
+ ".env.production",
21587
+ ".env.test",
21588
+ "credentials.json",
21589
+ "credential.json"
21590
+ ]);
21591
+ var ENV_KEY_PATTERN = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/;
21592
+ async function buildAgentMigrationExportManifest(input) {
21593
+ const slockHome = path19.resolve(input.slockHome);
21594
+ const workspace = path19.resolve(input.workspacePath ?? path19.join(slockHome, "agents", input.agentId));
21595
+ const proposedIncludes = normalizeProposalPaths(input.cooperativeManifest?.include, workspace, "include");
21596
+ const proposedRegenerableExcludes = normalizeProposalPaths(input.cooperativeManifest?.exclude_regenerable, workspace, "exclude_regenerable");
21597
+ const cleanedProposal = normalizeProposalPaths(input.cooperativeManifest?.cleaned, workspace, "cleaned");
21598
+ const state = {
21599
+ workspace,
21600
+ files: [],
21601
+ excludedRegenerable: [],
21602
+ promotedIncludes: [],
21603
+ proposalRefusals: [
21604
+ ...proposedIncludes.refusals,
21605
+ ...proposedRegenerableExcludes.refusals,
21606
+ ...cleanedProposal.refusals
21607
+ ],
21608
+ unreachable: [],
21609
+ secretsDisclosed: [],
21610
+ seenWorkspacePaths: /* @__PURE__ */ new Set(),
21611
+ explicitIncludePaths: new Set(proposedIncludes.paths)
21612
+ };
21613
+ await walkWorkspace(workspace, "", new Set(proposedRegenerableExcludes.paths), state, { forceIncludeRegenerable: false });
21614
+ for (const requestedPath of proposedIncludes.paths) {
21615
+ if (!state.seenWorkspacePaths.has(requestedPath)) {
21616
+ await includeWorkspacePath(requestedPath, state, { forceIncludeRegenerable: true });
21617
+ }
21618
+ }
21619
+ for (const requestedPath of proposedRegenerableExcludes.paths) {
21620
+ if (isRegenerablePath(requestedPath)) continue;
21621
+ state.promotedIncludes.push({ path: requestedPath, reason: "exclude_not_regenerable" });
21622
+ await includeWorkspacePath(requestedPath, state, { forceIncludeRegenerable: true });
21623
+ }
21624
+ const crossTreeRefs = await buildCrossTreeRefs(input.runtimeSessionRefs ?? []);
21625
+ const secretDisclosureProposal = normalizeProposalPaths(input.cooperativeManifest?.secrets_disclosed, workspace, "secrets_disclosed");
21626
+ state.proposalRefusals.push(...secretDisclosureProposal.refusals);
21627
+ return {
21628
+ schemaVersion: AGENT_MIGRATION_BUNDLE_SCHEMA_VERSION,
21629
+ agentId: input.agentId,
21630
+ mode: input.mode,
21631
+ createdAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
21632
+ roots: {
21633
+ slockHome,
21634
+ workspace
21635
+ },
21636
+ defaults: {
21637
+ unknownFiles: "include",
21638
+ excludePolicy: "regenerable_only",
21639
+ regenerableDirectoryNames: [...REGENERABLE_DIRECTORY_NAMES]
21640
+ },
21641
+ files: sortBundleEntries(state.files),
21642
+ excludedRegenerable: sortByPath(state.excludedRegenerable),
21643
+ promotedIncludes: sortByPath(state.promotedIncludes),
21644
+ proposalRefusals: sortByPath(state.proposalRefusals),
21645
+ unreachable: sortByPath(state.unreachable),
21646
+ secretsDisclosed: sortByPath([
21647
+ ...state.secretsDisclosed,
21648
+ ...normalizeProvidedSecretDisclosures(secretDisclosureProposal.paths)
21649
+ ]),
21650
+ cleaned: cleanedProposal.paths,
21651
+ crossTreeRefs: crossTreeRefs.sort((a, b) => a.bundlePath.localeCompare(b.bundlePath))
21652
+ };
21653
+ }
21654
+ async function walkWorkspace(root, relativeDir, proposedRegenerableExcludes, state, opts) {
21655
+ const absoluteDir = path19.join(root, relativeDir);
21656
+ let entries;
21657
+ try {
21658
+ entries = await readdir4(absoluteDir, { withFileTypes: true });
21659
+ } catch {
21660
+ state.unreachable.push({
21661
+ path: relativeDir || ".",
21662
+ sourcePath: absoluteDir,
21663
+ reason: "read_error"
21664
+ });
21665
+ return;
21666
+ }
21667
+ entries.sort((a, b) => a.name.localeCompare(b.name));
21668
+ for (const entry of entries) {
21669
+ const relativePath = toPosixPath(path19.join(relativeDir, entry.name));
21670
+ if (entry.isDirectory()) {
21671
+ if (!opts.forceIncludeRegenerable && isRegenerablePath(relativePath)) {
21672
+ if (state.explicitIncludePaths.has(relativePath)) {
21673
+ await walkWorkspace(root, relativePath, proposedRegenerableExcludes, state, { forceIncludeRegenerable: true });
21674
+ continue;
21675
+ }
21676
+ if (hasExplicitIncludeDescendant(relativePath, state.explicitIncludePaths)) {
21677
+ await walkWorkspace(root, relativePath, proposedRegenerableExcludes, state, opts);
21678
+ continue;
21679
+ }
21680
+ state.excludedRegenerable.push({
21681
+ path: relativePath,
21682
+ reason: proposedRegenerableExcludes.has(relativePath) ? "cooperative_exclude_regenerable" : "regenerable_default",
21683
+ regenerableHint: `${entry.name} is treated as rebuildable/installable state and is not bundled by default`
21684
+ });
21685
+ continue;
21686
+ }
21687
+ await walkWorkspace(root, relativePath, proposedRegenerableExcludes, state, opts);
21688
+ continue;
21689
+ }
21690
+ await includeWorkspacePath(relativePath, state, { forceIncludeRegenerable: opts.forceIncludeRegenerable });
21691
+ }
21692
+ }
21693
+ async function includeWorkspacePath(workspaceRelativePath, state, opts) {
21694
+ const normalizedRelativePath = normalizeRelativePath(workspaceRelativePath);
21695
+ if (state.seenWorkspacePaths.has(normalizedRelativePath)) return;
21696
+ const sourcePath = path19.join(state.workspace, normalizedRelativePath);
21697
+ let stat4;
21698
+ try {
21699
+ stat4 = await lstat2(sourcePath);
21700
+ } catch {
21701
+ state.unreachable.push({
21702
+ path: normalizedRelativePath,
21703
+ sourcePath,
21704
+ reason: "missing"
21705
+ });
21706
+ return;
21707
+ }
21708
+ if (stat4.isDirectory()) {
21709
+ if (!opts.forceIncludeRegenerable && isRegenerablePath(normalizedRelativePath)) {
21710
+ state.excludedRegenerable.push({
21711
+ path: normalizedRelativePath,
21712
+ reason: "cooperative_exclude_regenerable",
21713
+ regenerableHint: `${path19.basename(normalizedRelativePath)} is treated as rebuildable/installable state and is not bundled by default`
21714
+ });
21715
+ return;
21716
+ }
21717
+ await walkWorkspace(state.workspace, normalizedRelativePath, /* @__PURE__ */ new Set(), state, opts);
21718
+ return;
21719
+ }
21720
+ state.seenWorkspacePaths.add(normalizedRelativePath);
21721
+ const baseEntry = {
21722
+ source: "workspace",
21723
+ sourcePath,
21724
+ workspaceRelativePath: normalizedRelativePath,
21725
+ bundlePath: `workspace/${normalizedRelativePath}`,
21726
+ mode: stat4.mode,
21727
+ mtimeMs: stat4.mtimeMs
21728
+ };
21729
+ if (stat4.isSymbolicLink()) {
21730
+ state.files.push({
21731
+ ...baseEntry,
21732
+ kind: "symlink",
21733
+ linkTarget: await readlink(sourcePath)
21734
+ });
21735
+ return;
21736
+ }
21737
+ if (!stat4.isFile()) {
21738
+ state.unreachable.push({
21739
+ path: normalizedRelativePath,
21740
+ sourcePath,
21741
+ reason: "unsupported_file_type"
21742
+ });
21743
+ return;
21744
+ }
21745
+ const secretShapes = await detectSecretShapes(sourcePath, normalizedRelativePath);
21746
+ if (secretShapes.length > 0) {
21747
+ state.secretsDisclosed.push({ path: normalizedRelativePath, shapes: secretShapes });
21748
+ }
21749
+ state.files.push({
21750
+ ...baseEntry,
21751
+ kind: "file",
21752
+ sizeBytes: stat4.size,
21753
+ sha256: await sha256File(sourcePath),
21754
+ secretShapes: secretShapes.length > 0 ? secretShapes : void 0
21755
+ });
21756
+ }
21757
+ async function buildCrossTreeRefs(refs) {
21758
+ const result = [];
21759
+ for (const ref of refs) {
21760
+ const sourcePath = path19.resolve(ref.path);
21761
+ const bundlePath = `runtime/${safeBundleSegment(ref.runtime)}/${safeBundleSegment(ref.label)}/${safeBundleSegment(path19.basename(sourcePath) || "session")}`;
21762
+ const entry = {
21763
+ runtime: ref.runtime,
21764
+ label: ref.label,
21765
+ sourcePath,
21766
+ bundlePath,
21767
+ reachable: ref.reachable !== false,
21768
+ reason: ref.reason
21769
+ };
21770
+ try {
21771
+ const stat4 = await lstat2(sourcePath);
21772
+ if (stat4.isFile()) {
21773
+ entry.sizeBytes = stat4.size;
21774
+ entry.sha256 = await sha256File(sourcePath);
21775
+ }
21776
+ } catch {
21777
+ entry.reachable = false;
21778
+ }
21779
+ result.push(entry);
21780
+ }
21781
+ return result;
21782
+ }
21783
+ async function sha256File(filePath) {
21784
+ const hash = createHash7("sha256");
21785
+ await new Promise((resolve, reject) => {
21786
+ const stream = createReadStream(filePath);
21787
+ stream.on("data", (chunk) => hash.update(chunk));
21788
+ stream.on("error", reject);
21789
+ stream.on("end", resolve);
21790
+ });
21791
+ return hash.digest("hex");
21792
+ }
21793
+ async function detectSecretShapes(sourcePath, relativePath) {
21794
+ const basename = path19.basename(relativePath);
21795
+ const lowerBasename = basename.toLowerCase();
21796
+ const shapes = /* @__PURE__ */ new Set();
21797
+ if (SECRET_FILE_NAMES.has(lowerBasename) || lowerBasename.startsWith(".env.")) {
21798
+ shapes.add(`file:${basename}`);
21799
+ try {
21800
+ const text = await readFile3(sourcePath, "utf8");
21801
+ for (const line of text.split(/\r?\n/)) {
21802
+ const match = ENV_KEY_PATTERN.exec(line);
21803
+ if (match) shapes.add(`env:${match[1]}`);
21804
+ }
21805
+ } catch {
21806
+ shapes.add("content:unreadable");
21807
+ }
21808
+ }
21809
+ if (/(?:secret|token|credential|api[-_]?key)/i.test(relativePath)) {
21810
+ shapes.add(`path:${basename}`);
21811
+ }
21812
+ return [...shapes].sort();
21813
+ }
21814
+ function normalizeProvidedSecretDisclosures(disclosures) {
21815
+ return disclosures.map((entry) => ({
21816
+ path: entry,
21817
+ shapes: ["provided"]
21818
+ }));
21819
+ }
21820
+ function normalizeProposalPaths(paths, workspace, source) {
21821
+ if (!paths) return { paths: [], refusals: [] };
21822
+ const result = [];
21823
+ const refusals = [];
21824
+ for (const value of paths) {
21825
+ const trimmed = value.trim();
21826
+ if (!trimmed) continue;
21827
+ if (path19.isAbsolute(trimmed)) {
21828
+ const relative = path19.relative(workspace, trimmed);
21829
+ if (relative.startsWith("..") || path19.isAbsolute(relative)) {
21830
+ refusals.push({
21831
+ path: trimmed,
21832
+ reason: "outside_workspace",
21833
+ source
21834
+ });
21835
+ continue;
21836
+ }
21837
+ try {
21838
+ result.push(normalizeRelativePath(relative));
21839
+ } catch {
21840
+ refusals.push({
21841
+ path: trimmed,
21842
+ reason: "unsafe_path",
21843
+ source
21844
+ });
21845
+ }
21846
+ continue;
21847
+ }
21848
+ try {
21849
+ result.push(normalizeRelativePath(trimmed));
21850
+ } catch {
21851
+ refusals.push({
21852
+ path: trimmed,
21853
+ reason: "unsafe_path",
21854
+ source
21855
+ });
21856
+ }
21857
+ }
21858
+ return { paths: [...new Set(result)], refusals };
21859
+ }
21860
+ function normalizeRelativePath(value) {
21861
+ const normalized = path19.normalize(value).replace(/^[\\/]+/, "");
21862
+ if (!normalized || normalized === "." || normalized.startsWith("..") || path19.isAbsolute(normalized)) {
21863
+ throw new Error(`unsafe migration export path: ${value}`);
21864
+ }
21865
+ return toPosixPath(normalized);
21866
+ }
21867
+ function isRegenerablePath(relativePath) {
21868
+ return normalizeRelativePath(relativePath).split("/").some((segment) => REGENERABLE_DIRECTORY_NAMES.includes(segment));
21869
+ }
21870
+ function hasExplicitIncludeDescendant(relativePath, explicitIncludePaths) {
21871
+ const prefix = `${relativePath}/`;
21872
+ for (const includePath of explicitIncludePaths) {
21873
+ if (includePath.startsWith(prefix)) return true;
21874
+ }
21875
+ return false;
21876
+ }
21877
+ function safeBundleSegment(value) {
21878
+ return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
21879
+ }
21880
+ function toPosixPath(value) {
21881
+ return value.split(path19.sep).join("/");
21882
+ }
21883
+ function sortBundleEntries(entries) {
21884
+ return [...entries].sort((a, b) => a.bundlePath.localeCompare(b.bundlePath));
21885
+ }
21886
+ function sortByPath(entries) {
21887
+ return [...entries].sort((a, b) => a.path.localeCompare(b.path));
21888
+ }
21889
+
21890
+ // src/agentMigrationHttpTransport.ts
21891
+ import { createHash as createHash8, randomBytes as randomBytes2, timingSafeEqual } from "crypto";
21892
+ import http2 from "http";
21893
+ var MigrationBundleStreamError = class extends Error {
21894
+ constructor(code) {
21895
+ super(code);
21896
+ this.code = code;
21897
+ }
21898
+ };
21899
+ var AgentMigrationGrantRegistry = class {
21900
+ grants = /* @__PURE__ */ new Map();
21901
+ now;
21902
+ constructor(now = () => /* @__PURE__ */ new Date()) {
21903
+ this.now = now;
21904
+ }
21905
+ createGrant(input) {
21906
+ const token = input.token ?? randomBytes2(32).toString("base64url");
21907
+ const manifestSha256 = sha256Buffer(canonicalJsonBuffer(input.manifest));
21908
+ const grant = {
21909
+ grantId: input.grantId ?? randomBytes2(16).toString("hex"),
21910
+ tokenHash: hashToken(token),
21911
+ expiresAt: input.expiresAt,
21912
+ oneTimeUse: input.oneTimeUse ?? true,
21913
+ state: "issued",
21914
+ manifest: input.manifest,
21915
+ manifestSha256,
21916
+ bundleEtag: `"agent-migration-${manifestSha256}"`
21917
+ };
21918
+ this.grants.set(grant.grantId, grant);
21919
+ return { grant, token };
21920
+ }
21921
+ get(grantId) {
21922
+ return this.grants.get(grantId);
21923
+ }
21924
+ authenticate(grantId, token) {
21925
+ const grant = this.grants.get(grantId);
21926
+ if (!grant || !token || !verifyTokenHash(token, grant.tokenHash)) {
21927
+ return { ok: false, status: 401, code: "migration_grant_auth_failed" };
21928
+ }
21929
+ if (grant.state === "revoked" || grant.state === "consumed") {
21930
+ return { ok: false, status: 410, code: "migration_grant_unavailable" };
21931
+ }
21932
+ if (this.now().getTime() >= grant.expiresAt.getTime()) {
21933
+ grant.state = "expired";
21934
+ return { ok: false, status: 410, code: "migration_grant_unavailable" };
21935
+ }
21936
+ return { ok: true, grant };
21937
+ }
21938
+ revokeAll() {
21939
+ for (const grant of this.grants.values()) {
21940
+ if (grant.state !== "consumed" && grant.state !== "expired") {
21941
+ grant.state = "revoked";
21942
+ }
21943
+ }
21944
+ }
21945
+ };
21946
+ function createAgentMigrationHttpTransport(options = {}) {
21947
+ const registry = new AgentMigrationGrantRegistry(options.now);
21948
+ const bundleStreamFactory = options.bundleStreamFactory ?? defaultBundleStream;
21949
+ const server = http2.createServer((req, res) => {
21950
+ void handleMigrationRequest(req, res, registry, bundleStreamFactory);
21951
+ });
21952
+ return {
21953
+ grants: registry,
21954
+ server,
21955
+ listen: (port = 0, host = "127.0.0.1") => new Promise((resolve, reject) => {
21956
+ server.once("error", reject);
21957
+ server.listen(port, host, () => {
21958
+ server.off("error", reject);
21959
+ const address = server.address();
21960
+ if (!address || typeof address === "string") {
21961
+ reject(new Error("migration transport listen did not produce a TCP address"));
21962
+ return;
21963
+ }
21964
+ resolve({ url: `http://${address.address}:${address.port}` });
21965
+ });
21966
+ }),
21967
+ close: () => new Promise((resolve, reject) => {
21968
+ registry.revokeAll();
21969
+ server.close((err) => {
21970
+ if (err) reject(err);
21971
+ else resolve();
21972
+ });
21973
+ })
21974
+ };
21975
+ }
21976
+ async function handleMigrationRequest(req, res, registry, bundleStreamFactory) {
21977
+ const parsed = new URL(req.url ?? "/", "http://127.0.0.1");
21978
+ const match = /^\/migration\/([^/]+)\/([^/]+)(?:\/([^/]+))?$/.exec(parsed.pathname);
21979
+ if (!match) {
21980
+ sendJson(res, 404, { code: "migration_route_not_found" });
21981
+ return;
21982
+ }
21983
+ const [, grantId, resource, resourceId] = match;
21984
+ const auth = registry.authenticate(decodeURIComponent(grantId), extractBearerToken(req));
21985
+ if (!auth.ok) {
21986
+ sendJson(res, auth.status, { code: auth.code });
21987
+ return;
21988
+ }
21989
+ if (resource === "manifest" && req.method === "GET" && !resourceId) {
21990
+ sendJson(res, 200, {
21991
+ manifestSha256: auth.grant.manifestSha256,
21992
+ manifest: auth.grant.manifest
21993
+ }, {
21994
+ "X-Raft-Manifest-Sha": auth.grant.manifestSha256,
21995
+ ETag: `"manifest-${auth.grant.manifestSha256}"`
21996
+ });
21997
+ return;
21998
+ }
21999
+ if (resource === "bundle.tar" && !resourceId) {
22000
+ if (req.method === "HEAD") {
22001
+ sendHead(res, auth.grant);
22002
+ return;
22003
+ }
22004
+ if (req.method === "GET") {
22005
+ await streamBundle(res, auth.grant, bundleStreamFactory);
22006
+ return;
22007
+ }
22008
+ }
22009
+ if (resource === "chunk" && req.method === "GET" && resourceId) {
22010
+ sendJson(res, 501, { code: "migration_chunk_not_implemented" });
22011
+ return;
22012
+ }
22013
+ sendJson(res, 405, { code: "migration_method_not_allowed" });
22014
+ }
22015
+ function sendHead(res, grant) {
22016
+ res.statusCode = 200;
22017
+ res.setHeader("X-Raft-Manifest-Sha", grant.manifestSha256);
22018
+ res.setHeader("ETag", grant.bundleEtag);
22019
+ res.setHeader("Accept-Ranges", "none");
22020
+ res.setHeader("X-Raft-Bundle-Size", "unknown");
22021
+ res.end();
22022
+ }
22023
+ async function streamBundle(res, grant, bundleStreamFactory) {
22024
+ if (grant.state === "streaming") {
22025
+ sendJson(res, 409, { code: "migration_bundle_stream_in_progress" });
22026
+ return;
22027
+ }
22028
+ if (grant.oneTimeUse && grant.state !== "issued" && grant.state !== "interrupted") {
22029
+ sendJson(res, 410, { code: "migration_grant_unavailable" });
22030
+ return;
22031
+ }
22032
+ let stream;
22033
+ try {
22034
+ stream = bundleStreamFactory(grant);
22035
+ } catch (err) {
22036
+ const code = err instanceof MigrationBundleStreamError ? err.code : "migration_bundle_stream_failed";
22037
+ sendJson(res, 500, { code });
22038
+ return;
22039
+ }
22040
+ grant.state = "streaming";
22041
+ let completed = false;
22042
+ res.on("close", () => {
22043
+ if (!completed && grant.state === "streaming") {
22044
+ grant.state = "interrupted";
22045
+ }
22046
+ });
22047
+ try {
22048
+ res.statusCode = 200;
22049
+ res.setHeader("Content-Type", "application/x-tar");
22050
+ res.setHeader("X-Raft-Manifest-Sha", grant.manifestSha256);
22051
+ res.setHeader("ETag", grant.bundleEtag);
22052
+ for await (const chunk of stream) {
22053
+ if (!res.write(chunk)) {
22054
+ await onceDrain(res);
22055
+ }
22056
+ }
22057
+ completed = true;
22058
+ grant.state = "consumed";
22059
+ res.end();
22060
+ } catch {
22061
+ if (!completed && grant.state === "streaming") {
22062
+ grant.state = "interrupted";
22063
+ }
22064
+ if (!res.headersSent) {
22065
+ sendJson(res, 500, { code: "migration_bundle_stream_failed" });
22066
+ } else {
22067
+ res.destroy();
22068
+ }
22069
+ }
22070
+ }
22071
+ function defaultBundleStream(grant) {
22072
+ void grant;
22073
+ throw new MigrationBundleStreamError("migration_bundle_stream_not_wired");
22074
+ }
22075
+ function extractBearerToken(req) {
22076
+ const directHeader = singleHeader(req.headers["x-raft-migration-token"]);
22077
+ if (directHeader) return directHeader;
22078
+ const authorization = singleHeader(req.headers.authorization);
22079
+ if (!authorization) return null;
22080
+ const match = /^Bearer\s+(.+)$/i.exec(authorization);
22081
+ return match?.[1] ?? null;
22082
+ }
22083
+ function singleHeader(value) {
22084
+ if (!value) return null;
22085
+ return Array.isArray(value) ? value[0] ?? null : value;
22086
+ }
22087
+ function sendJson(res, status, body, headers = {}) {
22088
+ const payload = JSON.stringify(body);
22089
+ res.writeHead(status, {
22090
+ "Content-Type": "application/json",
22091
+ "Content-Length": Buffer.byteLength(payload).toString(),
22092
+ ...headers
22093
+ });
22094
+ res.end(payload);
22095
+ }
22096
+ function hashToken(token) {
22097
+ return sha256Buffer(Buffer.from(token, "utf8"));
22098
+ }
22099
+ function verifyTokenHash(token, expectedHashHex) {
22100
+ const actual = Buffer.from(hashToken(token), "hex");
22101
+ const expected = Buffer.from(expectedHashHex, "hex");
22102
+ if (actual.byteLength !== expected.byteLength) return false;
22103
+ return timingSafeEqual(actual, expected);
22104
+ }
22105
+ function sha256Buffer(buffer) {
22106
+ return createHash8("sha256").update(buffer).digest("hex");
22107
+ }
22108
+ function canonicalJsonBuffer(value) {
22109
+ return Buffer.from(canonicalJson(value), "utf8");
22110
+ }
22111
+ function canonicalJson(value) {
22112
+ return JSON.stringify(sortJsonValue(value));
22113
+ }
22114
+ function sortJsonValue(value) {
22115
+ if (Array.isArray(value)) return value.map(sortJsonValue);
22116
+ if (!value || typeof value !== "object") return value;
22117
+ return Object.keys(value).sort().reduce((result, key) => {
22118
+ result[key] = sortJsonValue(value[key]);
22119
+ return result;
22120
+ }, {});
22121
+ }
22122
+ function onceDrain(res) {
22123
+ return new Promise((resolve) => res.once("drain", resolve));
22124
+ }
22125
+
21305
22126
  // src/core.ts
21306
22127
  var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
21307
22128
  var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels", "knowledge"];
@@ -21409,7 +22230,17 @@ var DAEMON_CORE_TRACE_ATTR_CONTRACTS = {
21409
22230
  spanAttrs: ["running_agents_count", "idle_agents_count"]
21410
22231
  },
21411
22232
  "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"]
22233
+ // isHeartbeat/is_heartbeat (#460 V1) and process_instance_id (#460 V3)
22234
+ // were emitted by agentProcessManager but scrubbed here (pilot violation
22235
+ // V4): this list is a RUNTIME allowlist (SpanAttrContractTracer), so an
22236
+ // emission-site key that is not added here silently dies before disk.
22237
+ // Witness for the pair lives in agentProcessManager.builtin.e2e.test.ts
22238
+ // behind a contract-wrapped tracer (production-isomorphic oracle).
22239
+ // producerFactId/producer_fact_id and activity_kind/detail_kind are ALSO
22240
+ // emitted-and-scrubbed today; deliberately NOT added here — banned-join-
22241
+ // key discipline for the fact id (#460 classification ruling) means
22242
+ // widening needs its own ruling, not a drive-by.
22243
+ 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
22244
  },
21414
22245
  "daemon.agent.activity.skipped": {
21415
22246
  spanAttrs: ["agentId", "event_kind", "reason", "text_length"]
@@ -21473,13 +22304,13 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
21473
22304
  }
21474
22305
  }
21475
22306
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
21476
- const thisDir = path19.dirname(fileURLToPath(moduleUrl));
21477
- const bundledDistPath = path19.resolve(thisDir, "cli", "index.js");
22307
+ const thisDir = path20.dirname(fileURLToPath(moduleUrl));
22308
+ const bundledDistPath = path20.resolve(thisDir, "cli", "index.js");
21478
22309
  try {
21479
22310
  accessSync(bundledDistPath);
21480
22311
  return bundledDistPath;
21481
22312
  } catch {
21482
- const workspaceDistPath = path19.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
22313
+ const workspaceDistPath = path20.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
21483
22314
  accessSync(workspaceDistPath);
21484
22315
  return workspaceDistPath;
21485
22316
  }
@@ -21493,7 +22324,7 @@ function resolveSlockCliPathOrEmpty(moduleUrl = import.meta.url) {
21493
22324
  }
21494
22325
  async function runBundledSlockCli(argv) {
21495
22326
  process.argv = [process.execPath, "slock", ...argv];
21496
- await import("./dist-P3SAWND7.js");
22327
+ await import("./dist-D77BWHH4.js");
21497
22328
  }
21498
22329
  function detectRuntimes(tracer = noopTracer) {
21499
22330
  const ids = [];
@@ -21701,7 +22532,7 @@ var DaemonCore = class {
21701
22532
  }
21702
22533
  resolveMachineStateRoot() {
21703
22534
  if (this.options.machineStateDir) return this.options.machineStateDir;
21704
- if (this.options.dataDir) return path19.join(path19.dirname(this.options.dataDir), "machines");
22535
+ if (this.options.dataDir) return path20.join(path20.dirname(this.options.dataDir), "machines");
21705
22536
  return resolveDefaultMachineStateRoot();
21706
22537
  }
21707
22538
  shouldEnableLocalTrace() {
@@ -21729,7 +22560,7 @@ var DaemonCore = class {
21729
22560
  sinks: [this.localTraceSink]
21730
22561
  }));
21731
22562
  this.agentManager.setTracer(this.tracer);
21732
- this.agentManager.setCliTransportTraceDir(path19.join(machineDir, "traces"));
22563
+ this.agentManager.setCliTransportTraceDir(path20.join(machineDir, "traces"));
21733
22564
  }
21734
22565
  installTraceBundleUploader(machineDir) {
21735
22566
  if (!this.shouldEnableLocalTrace()) return;
@@ -22018,7 +22849,7 @@ var DaemonCore = class {
22018
22849
  session_id_present: Boolean(msg.config.sessionId)
22019
22850
  }, "error");
22020
22851
  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 });
22852
+ this.connection.send({ type: "agent:activity", agentId: msg.agentId, activity: "offline", detail: classification.userMessage, launchId: msg.launchId, observedAtMs: Date.now() });
22022
22853
  });
22023
22854
  break;
22024
22855
  case "agent:stop":
@@ -22438,6 +23269,10 @@ export {
22438
23269
  resolveWorkspaceDirectoryPath,
22439
23270
  scanWorkspaceDirectories,
22440
23271
  deleteWorkspaceDirectory,
23272
+ AGENT_MIGRATION_BUNDLE_SCHEMA_VERSION,
23273
+ buildAgentMigrationExportManifest,
23274
+ AgentMigrationGrantRegistry,
23275
+ createAgentMigrationHttpTransport,
22441
23276
  DAEMON_CORE_TRACE_ATTR_CONTRACTS,
22442
23277
  DAEMON_CLI_USAGE,
22443
23278
  parseDaemonCliArgs,