@p4code/cli 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.mjs CHANGED
@@ -29,15 +29,15 @@ import * as Duration from "effect/Duration";
29
29
  import * as Option from "effect/Option";
30
30
  import * as SchemaTransformation from "effect/SchemaTransformation";
31
31
  import * as HttpApiSchema from "effect/unstable/httpapi/HttpApiSchema";
32
- import * as HttpApi from "effect/unstable/httpapi/HttpApi";
33
- import * as HttpApiEndpoint from "effect/unstable/httpapi/HttpApiEndpoint";
34
- import * as HttpApiGroup from "effect/unstable/httpapi/HttpApiGroup";
35
- import * as HttpApiMiddleware from "effect/unstable/httpapi/HttpApiMiddleware";
36
32
  import * as HttpServerRespondable$1 from "effect/unstable/http/HttpServerRespondable";
37
33
  import * as HttpServerResponse$1 from "effect/unstable/http/HttpServerResponse";
38
34
  import * as SchemaIssue from "effect/SchemaIssue";
39
35
  import * as SchemaGetter from "effect/SchemaGetter";
40
36
  import * as Struct from "effect/Struct";
37
+ import * as HttpApi from "effect/unstable/httpapi/HttpApi";
38
+ import * as HttpApiEndpoint from "effect/unstable/httpapi/HttpApiEndpoint";
39
+ import * as HttpApiGroup from "effect/unstable/httpapi/HttpApiGroup";
40
+ import * as HttpApiMiddleware from "effect/unstable/httpapi/HttpApiMiddleware";
41
41
  import { Schema } from "effect";
42
42
  import * as Rpc from "effect/unstable/rpc/Rpc";
43
43
  import * as RpcGroup from "effect/unstable/rpc/RpcGroup";
@@ -263,10 +263,10 @@ const make$122 = () => {
263
263
  })
264
264
  };
265
265
  };
266
- const layer$122 = Layer.sync(NetService, make$122);
266
+ const layer$123 = Layer.sync(NetService, make$122);
267
267
  //#endregion
268
268
  //#region package.json
269
- var version$1 = "0.6.2";
269
+ var version$1 = "0.6.3";
270
270
  //#endregion
271
271
  //#region src/config.ts
272
272
  /**
@@ -288,7 +288,7 @@ var ServerConfig$1 = class extends Context.Service()("@p4code/cli/config/ServerC
288
288
  static layerTest = (cwd, baseDirOrPrefix) => layerTest$4(cwd, baseDirOrPrefix);
289
289
  };
290
290
  const make$121 = (config) => ServerConfig$1.of(config);
291
- const layer$121 = (config) => Layer.succeed(ServerConfig$1, make$121(config));
291
+ const layer$122 = (config) => Layer.succeed(ServerConfig$1, make$121(config));
292
292
  const deriveServerPaths = Effect.fn(function* (baseDir, devUrl, options = {}) {
293
293
  const { join } = yield* Path$1.Path;
294
294
  const stateDir = join(baseDir, devUrl !== void 0 && !options.baseDirIsExplicit ? "dev" : "userdata");
@@ -1377,276 +1377,6 @@ Schema$1.Struct({
1377
1377
  expiresAt: Schema$1.Number
1378
1378
  });
1379
1379
  //#endregion
1380
- //#region ../../packages/contracts/src/hubNotifications.ts
1381
- const MAX_NOTIFICATION_ID_CHARS = 128;
1382
- const MAX_PUSH_TOKEN_CHARS = 4096;
1383
- const MAX_HUB_ORIGIN_CHARS = 2048;
1384
- const MAX_ACTIVITY_TITLE_CHARS = 160;
1385
- const MAX_ACTIVITY_DETAIL_CHARS = 240;
1386
- const identifier = Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_NOTIFICATION_ID_CHARS), Schema$1.isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]*$(?![\s\S])/));
1387
- const pushToken = Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_PUSH_TOKEN_CHARS), Schema$1.isPattern(/^[\x21-\x7e]+$(?![\s\S])/));
1388
- /** Server emission is bound to the authenticated environment, never a supplied account. */
1389
- const HubAgentActivityInput = Schema$1.Struct({
1390
- eventId: identifier,
1391
- threadId: ThreadId,
1392
- revision: Schema$1.Int.check(Schema$1.isGreaterThanOrEqualTo(0)),
1393
- state: Schema$1.Literals([
1394
- "running",
1395
- "approval_required",
1396
- "question_required",
1397
- "completed",
1398
- "failed",
1399
- "stopped"
1400
- ]),
1401
- title: Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_ACTIVITY_TITLE_CHARS)),
1402
- detail: Schema$1.String.check(Schema$1.isMaxLength(MAX_ACTIVITY_DETAIL_CHARS))
1403
- });
1404
- /** Each authenticated client owns its registration; tokens never belong in public client state. */
1405
- const HubPushRegistrationInput = Schema$1.Union([Schema$1.Struct({
1406
- platform: Schema$1.Literal("ios"),
1407
- token: pushToken,
1408
- sandbox: Schema$1.Boolean
1409
- }), Schema$1.Struct({
1410
- platform: Schema$1.Literal("android"),
1411
- token: pushToken
1412
- })]);
1413
- /** Explicit opt-in, independently reversible. Omission never implies permission. */
1414
- const HubNotificationPreferences = Schema$1.Struct({
1415
- attention: Schema$1.Boolean,
1416
- completion: Schema$1.Boolean,
1417
- liveActivity: Schema$1.Boolean
1418
- });
1419
- Schema$1.Struct({
1420
- registered: Schema$1.Boolean,
1421
- registrationGeneration: Schema$1.NullOr(identifier),
1422
- preferences: HubNotificationPreferences
1423
- });
1424
- /** ActivityKit update token, distinct from the app's APNs registration token. */
1425
- const HubActivityOwnership = Schema$1.Struct({
1426
- origin: Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_HUB_ORIGIN_CHARS)),
1427
- environmentId: EnvironmentId,
1428
- clientId: HubClientId,
1429
- registrationGeneration: identifier,
1430
- threadId: ThreadId,
1431
- activityId: identifier
1432
- });
1433
- const HubLiveActivityRegistrationInput = Schema$1.Struct({
1434
- threadId: ThreadId,
1435
- registrationGeneration: identifier,
1436
- activityId: identifier,
1437
- token: Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_PUSH_TOKEN_CHARS), Schema$1.isPattern(/^(?:[a-fA-F0-9]{2})+$(?![\s\S])/)),
1438
- sandbox: Schema$1.Boolean,
1439
- p4Ownership: Schema$1.optional(HubActivityOwnership)
1440
- });
1441
- /** Close only this presentation; delayed requests must not retire a newer activity. */
1442
- const HubLiveActivityRemoveInput = Schema$1.Struct({
1443
- threadId: ThreadId,
1444
- registrationGeneration: identifier,
1445
- activityId: identifier
1446
- });
1447
- const HUB_TUNNEL_WEBSOCKET_PROTOCOL = "p4-hub-tunnel-v1";
1448
- const HUB_TUNNEL_INITIAL_WINDOW_BYTES = 256 * 1024;
1449
- const HUB_TUNNEL_MAX_WINDOW_BYTES = 1024 * 1024;
1450
- const MAX_HEADERS = 64;
1451
- const MAX_HEADER_NAME_CHARS = 128;
1452
- const MAX_HEADER_VALUE_CHARS = 8192;
1453
- const MAX_PATH_CHARS = 8192;
1454
- const MAX_PROTOCOLS = 16;
1455
- const MAX_PROTOCOL_CHARS = 128;
1456
- const HeaderName = Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_HEADER_NAME_CHARS), Schema$1.isPattern(/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$(?![\s\S])/));
1457
- const HeaderValue = Schema$1.String.check(Schema$1.isMaxLength(MAX_HEADER_VALUE_CHARS), Schema$1.isPattern(/^[\t\x20-\x7e]*$(?![\s\S])/));
1458
- const Headers$2 = Schema$1.Array(Schema$1.Tuple([HeaderName, HeaderValue])).check(Schema$1.isMaxLength(MAX_HEADERS));
1459
- const Path = Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_PATH_CHARS), Schema$1.isPattern(/^\/(?!\/)[^\s\\#\x00-\x1f\x7f]*$(?![\s\S])/));
1460
- const Protocol = Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_PROTOCOL_CHARS), Schema$1.isPattern(/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$(?![\s\S])/));
1461
- const FailureReason = Schema$1.Literals([
1462
- "cancelled",
1463
- "unavailable",
1464
- "protocol_error"
1465
- ]);
1466
- const CloseCode = Schema$1.Int.check(Schema$1.makeFilter((code) => code >= 1e3 && code <= 1014 && code !== 1004 && code !== 1005 && code !== 1006 || code >= 3e3 && code <= 4999));
1467
- /** No caller-supplied account, environment, origin, or connector ownership authority. */
1468
- const HubTunnelControl = Schema$1.Union([
1469
- Schema$1.Struct({
1470
- type: Schema$1.Literal("connector_ready"),
1471
- protocolVersion: Schema$1.Literal(1)
1472
- }),
1473
- Schema$1.Struct({
1474
- type: Schema$1.Literal("http_request_start"),
1475
- method: Schema$1.Literals([
1476
- "GET",
1477
- "HEAD",
1478
- "POST",
1479
- "PUT",
1480
- "PATCH",
1481
- "DELETE",
1482
- "OPTIONS"
1483
- ]),
1484
- path: Path,
1485
- headers: Headers$2
1486
- }),
1487
- Schema$1.Struct({ type: Schema$1.Literal("http_request_end") }),
1488
- Schema$1.Struct({
1489
- type: Schema$1.Literal("http_request_abort"),
1490
- reason: FailureReason
1491
- }),
1492
- Schema$1.Struct({
1493
- type: Schema$1.Literal("http_response_start"),
1494
- status: Schema$1.Int.check(Schema$1.isGreaterThanOrEqualTo(200), Schema$1.isLessThanOrEqualTo(599)),
1495
- headers: Headers$2
1496
- }),
1497
- Schema$1.Struct({ type: Schema$1.Literal("http_response_end") }),
1498
- Schema$1.Struct({
1499
- type: Schema$1.Literal("http_response_abort"),
1500
- reason: FailureReason
1501
- }),
1502
- Schema$1.Struct({
1503
- type: Schema$1.Literal("websocket_open"),
1504
- path: Path,
1505
- headers: Headers$2,
1506
- protocols: Schema$1.Array(Protocol).check(Schema$1.isMaxLength(MAX_PROTOCOLS))
1507
- }),
1508
- Schema$1.Struct({
1509
- type: Schema$1.Literal("websocket_accept"),
1510
- protocol: Schema$1.optional(Protocol)
1511
- }),
1512
- Schema$1.Struct({
1513
- type: Schema$1.Literal("websocket_reject"),
1514
- status: Schema$1.Int.check(Schema$1.isGreaterThanOrEqualTo(400), Schema$1.isLessThanOrEqualTo(599)),
1515
- reason: FailureReason
1516
- }),
1517
- Schema$1.Struct({
1518
- type: Schema$1.Literal("websocket_close"),
1519
- code: CloseCode,
1520
- reason: FailureReason
1521
- }),
1522
- Schema$1.Struct({
1523
- type: Schema$1.Literal("window_update"),
1524
- creditBytes: Schema$1.Int.check(Schema$1.isGreaterThan(0), Schema$1.isLessThanOrEqualTo(HUB_TUNNEL_MAX_WINDOW_BYTES))
1525
- })
1526
- ]);
1527
- //#endregion
1528
- //#region ../../packages/contracts/src/environment.ts
1529
- const ExecutionEnvironmentPlatformOs = Schema$1.Literals([
1530
- "darwin",
1531
- "linux",
1532
- "windows",
1533
- "unknown"
1534
- ]);
1535
- const ExecutionEnvironmentPlatformArch = Schema$1.Literals([
1536
- "arm64",
1537
- "x64",
1538
- "other"
1539
- ]);
1540
- const ExecutionEnvironmentPlatform = Schema$1.Struct({
1541
- os: ExecutionEnvironmentPlatformOs,
1542
- arch: ExecutionEnvironmentPlatformArch
1543
- });
1544
- /** How a server can replace itself with another version when asked over RPC:
1545
- "boot-service" rewrites the systemd user unit and restarts it; "respawn"
1546
- installs the target version and respawns the foreground process. */
1547
- const ServerSelfUpdateMethod = Schema$1.Literals(["boot-service", "respawn"]);
1548
- /** What update path a client should offer for a server: one of the RPC
1549
- self-update methods above, or "desktop-managed" when the backend's
1550
- version belongs to the P4Code desktop app supervising it — updating the
1551
- app on that machine is the only way to update the server. */
1552
- const ServerSelfUpdateCapability = Schema$1.Literals([
1553
- "boot-service",
1554
- "respawn",
1555
- "desktop-managed"
1556
- ]);
1557
- const ExecutionEnvironmentCapabilities = Schema$1.Struct({
1558
- computerUseControls: Schema$1.optionalKey(Schema$1.Boolean),
1559
- usagePriceOverrides: Schema$1.optionalKey(Schema$1.Boolean),
1560
- projectAutoPull: Schema$1.optionalKey(Schema$1.Boolean),
1561
- sharedProjectModelDefault: Schema$1.optionalKey(Schema$1.Boolean),
1562
- projectScriptDefaults: Schema$1.optionalKey(Schema$1.Boolean),
1563
- customModelDefinitions: Schema$1.optionalKey(Schema$1.Boolean),
1564
- threadLifecycleV2: Schema$1.optionalKey(Schema$1.Boolean),
1565
- repositoryIdentity: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(false))),
1566
- connectionProbe: Schema$1.optionalKey(Schema$1.Boolean),
1567
- /** Server understands thread.settle / thread.unsettle commands. Absent on
1568
- pre-settlement servers, so clients treat missing as unsupported and
1569
- never send the commands under version skew. */
1570
- threadSettlement: Schema$1.optionalKey(Schema$1.Boolean),
1571
- /** Server understands thread.snooze / thread.unsnooze commands. Same
1572
- version-skew contract as threadSettlement. */
1573
- threadSnooze: Schema$1.optionalKey(Schema$1.Boolean),
1574
- /** Server understands thread.pin / thread.unpin / thread.pin.reorder
1575
- commands. Same version-skew contract as threadSettlement. */
1576
- threadPinning: Schema$1.optionalKey(Schema$1.Boolean),
1577
- /** Server understands the thread.fork command. Same version-skew contract
1578
- as threadSettlement. */
1579
- threadFork: Schema$1.optionalKey(Schema$1.Boolean),
1580
- /** Server understands thread.scheduled-task.create / cancel and fires
1581
- pending tasks. Same version-skew contract as threadSettlement. */
1582
- threadScheduledTasks: Schema$1.optionalKey(Schema$1.Boolean),
1583
- /** Server exposes the pull-request list, detail, activity, diff, and mutation APIs. Absent on
1584
- servers from before the pull-request workspace shipped, so clients must not probe them. */
1585
- pullRequests: Schema$1.optionalKey(Schema$1.Boolean),
1586
- /** Whole-host capacity snapshots for automatic draft routing. */
1587
- hostResources: Schema$1.optionalKey(Schema$1.Boolean),
1588
- /** Device open returns lease IDs and close honors expectedLeaseId. */
1589
- deviceSessionLeases: Schema$1.optionalKey(Schema$1.Boolean),
1590
- /** Manual detail/action requests can require an exact viewing lifetime. */
1591
- deviceActionLeases: Schema$1.optionalKey(Schema$1.Boolean),
1592
- /** Stack-scoped mutations preserve expected heads. Require true before sending stack payloads;
1593
- older servers may otherwise interpret the request as a single-PR action. */
1594
- pullRequestStackActions: Schema$1.optionalKey(Schema$1.Boolean),
1595
- /** The update path clients should offer for this server. Absent on
1596
- servers that must be relaunched manually (dev checkouts, Windows
1597
- foreground runs, pre-update servers). */
1598
- serverSelfUpdate: Schema$1.optionalKey(ServerSelfUpdateCapability),
1599
- /**
1600
- * Set when a service supervises the server, so a client offering a manual
1601
- * update must say `service update` rather than a bare relaunch — which would
1602
- * start a second server beside the supervised one.
1603
- *
1604
- * A new optional field rather than a new `ServerSelfUpdateCapability` member:
1605
- * adding a literal to that union makes older clients fail to decode the whole
1606
- * descriptor, while an unknown key is ignored.
1607
- */
1608
- serverServiceSupervised: Schema$1.optionalKey(Schema$1.Boolean)
1609
- });
1610
- const ExecutionEnvironmentDescriptor = Schema$1.Struct({
1611
- environmentId: EnvironmentId,
1612
- label: TrimmedNonEmptyString,
1613
- platform: ExecutionEnvironmentPlatform,
1614
- serverVersion: TrimmedNonEmptyString,
1615
- capabilities: ExecutionEnvironmentCapabilities
1616
- });
1617
- Schema$1.Literals([
1618
- "connecting",
1619
- "connected",
1620
- "disconnected",
1621
- "error"
1622
- ]);
1623
- const RepositoryIdentityLocator = Schema$1.Struct({
1624
- source: Schema$1.Literal("git-remote"),
1625
- remoteName: TrimmedNonEmptyString,
1626
- remoteUrl: TrimmedNonEmptyString
1627
- });
1628
- const RepositoryIdentity = Schema$1.Struct({
1629
- canonicalKey: TrimmedNonEmptyString,
1630
- locator: RepositoryIdentityLocator,
1631
- rootPath: Schema$1.optionalKey(TrimmedNonEmptyString),
1632
- displayName: Schema$1.optionalKey(TrimmedNonEmptyString),
1633
- provider: Schema$1.optionalKey(TrimmedNonEmptyString),
1634
- owner: Schema$1.optionalKey(TrimmedNonEmptyString),
1635
- name: Schema$1.optionalKey(TrimmedNonEmptyString)
1636
- });
1637
- Schema$1.Struct({
1638
- environmentId: EnvironmentId,
1639
- projectId: ProjectId
1640
- });
1641
- Schema$1.Struct({
1642
- environmentId: EnvironmentId,
1643
- threadId: ThreadId
1644
- });
1645
- Schema$1.Struct({
1646
- environmentId: EnvironmentId,
1647
- threadId: ThreadId
1648
- });
1649
- //#endregion
1650
1380
  //#region ../../packages/contracts/src/vcs.ts
1651
1381
  const VcsDriverKind = Schema$1.Literals([
1652
1382
  "git",
@@ -3215,6 +2945,128 @@ const MODEL_SLUG_ALIASES_BY_PROVIDER = {
3215
2945
  [OPENCODE_DRIVER_KIND]: {}
3216
2946
  };
3217
2947
  //#endregion
2948
+ //#region ../../packages/contracts/src/environment.ts
2949
+ const ExecutionEnvironmentPlatformOs = Schema$1.Literals([
2950
+ "darwin",
2951
+ "linux",
2952
+ "windows",
2953
+ "unknown"
2954
+ ]);
2955
+ const ExecutionEnvironmentPlatformArch = Schema$1.Literals([
2956
+ "arm64",
2957
+ "x64",
2958
+ "other"
2959
+ ]);
2960
+ const ExecutionEnvironmentPlatform = Schema$1.Struct({
2961
+ os: ExecutionEnvironmentPlatformOs,
2962
+ arch: ExecutionEnvironmentPlatformArch
2963
+ });
2964
+ /** How a server can replace itself with another version when asked over RPC:
2965
+ "boot-service" rewrites the systemd user unit and restarts it; "respawn"
2966
+ installs the target version and respawns the foreground process. */
2967
+ const ServerSelfUpdateMethod = Schema$1.Literals(["boot-service", "respawn"]);
2968
+ /** What update path a client should offer for a server: one of the RPC
2969
+ self-update methods above, or "desktop-managed" when the backend's
2970
+ version belongs to the P4Code desktop app supervising it — updating the
2971
+ app on that machine is the only way to update the server. */
2972
+ const ServerSelfUpdateCapability = Schema$1.Literals([
2973
+ "boot-service",
2974
+ "respawn",
2975
+ "desktop-managed"
2976
+ ]);
2977
+ const ExecutionEnvironmentCapabilities = Schema$1.Struct({
2978
+ computerUseControls: Schema$1.optionalKey(Schema$1.Boolean),
2979
+ usagePriceOverrides: Schema$1.optionalKey(Schema$1.Boolean),
2980
+ projectAutoPull: Schema$1.optionalKey(Schema$1.Boolean),
2981
+ sharedProjectModelDefault: Schema$1.optionalKey(Schema$1.Boolean),
2982
+ projectScriptDefaults: Schema$1.optionalKey(Schema$1.Boolean),
2983
+ customModelDefinitions: Schema$1.optionalKey(Schema$1.Boolean),
2984
+ threadLifecycleV2: Schema$1.optionalKey(Schema$1.Boolean),
2985
+ repositoryIdentity: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(false))),
2986
+ connectionProbe: Schema$1.optionalKey(Schema$1.Boolean),
2987
+ /** Server understands thread.settle / thread.unsettle commands. Absent on
2988
+ pre-settlement servers, so clients treat missing as unsupported and
2989
+ never send the commands under version skew. */
2990
+ threadSettlement: Schema$1.optionalKey(Schema$1.Boolean),
2991
+ /** Server understands thread.snooze / thread.unsnooze commands. Same
2992
+ version-skew contract as threadSettlement. */
2993
+ threadSnooze: Schema$1.optionalKey(Schema$1.Boolean),
2994
+ /** Server understands thread.pin / thread.unpin / thread.pin.reorder
2995
+ commands. Same version-skew contract as threadSettlement. */
2996
+ threadPinning: Schema$1.optionalKey(Schema$1.Boolean),
2997
+ /** Server understands the thread.fork command. Same version-skew contract
2998
+ as threadSettlement. */
2999
+ threadFork: Schema$1.optionalKey(Schema$1.Boolean),
3000
+ /** Server understands thread.scheduled-task.create / cancel and fires
3001
+ pending tasks. Same version-skew contract as threadSettlement. */
3002
+ threadScheduledTasks: Schema$1.optionalKey(Schema$1.Boolean),
3003
+ /** Server exposes the pull-request list, detail, activity, diff, and mutation APIs. Absent on
3004
+ servers from before the pull-request workspace shipped, so clients must not probe them. */
3005
+ pullRequests: Schema$1.optionalKey(Schema$1.Boolean),
3006
+ /** Whole-host capacity snapshots for automatic draft routing. */
3007
+ hostResources: Schema$1.optionalKey(Schema$1.Boolean),
3008
+ /** Device open returns lease IDs and close honors expectedLeaseId. */
3009
+ deviceSessionLeases: Schema$1.optionalKey(Schema$1.Boolean),
3010
+ /** Manual detail/action requests can require an exact viewing lifetime. */
3011
+ deviceActionLeases: Schema$1.optionalKey(Schema$1.Boolean),
3012
+ /** Stack-scoped mutations preserve expected heads. Require true before sending stack payloads;
3013
+ older servers may otherwise interpret the request as a single-PR action. */
3014
+ pullRequestStackActions: Schema$1.optionalKey(Schema$1.Boolean),
3015
+ /** The update path clients should offer for this server. Absent on
3016
+ servers that must be relaunched manually (dev checkouts, Windows
3017
+ foreground runs, pre-update servers). */
3018
+ serverSelfUpdate: Schema$1.optionalKey(ServerSelfUpdateCapability),
3019
+ /**
3020
+ * Set when a service supervises the server, so a client offering a manual
3021
+ * update must say `service update` rather than a bare relaunch — which would
3022
+ * start a second server beside the supervised one.
3023
+ *
3024
+ * A new optional field rather than a new `ServerSelfUpdateCapability` member:
3025
+ * adding a literal to that union makes older clients fail to decode the whole
3026
+ * descriptor, while an unknown key is ignored.
3027
+ */
3028
+ serverServiceSupervised: Schema$1.optionalKey(Schema$1.Boolean)
3029
+ });
3030
+ const ExecutionEnvironmentDescriptor = Schema$1.Struct({
3031
+ environmentId: EnvironmentId,
3032
+ label: TrimmedNonEmptyString,
3033
+ platform: ExecutionEnvironmentPlatform,
3034
+ serverVersion: TrimmedNonEmptyString,
3035
+ capabilities: ExecutionEnvironmentCapabilities
3036
+ });
3037
+ Schema$1.Literals([
3038
+ "connecting",
3039
+ "connected",
3040
+ "disconnected",
3041
+ "error"
3042
+ ]);
3043
+ const RepositoryIdentityLocator = Schema$1.Struct({
3044
+ source: Schema$1.Literal("git-remote"),
3045
+ remoteName: TrimmedNonEmptyString,
3046
+ remoteUrl: TrimmedNonEmptyString
3047
+ });
3048
+ const RepositoryIdentity = Schema$1.Struct({
3049
+ canonicalKey: TrimmedNonEmptyString,
3050
+ locator: RepositoryIdentityLocator,
3051
+ rootPath: Schema$1.optionalKey(TrimmedNonEmptyString),
3052
+ displayName: Schema$1.optionalKey(TrimmedNonEmptyString),
3053
+ provider: Schema$1.optionalKey(TrimmedNonEmptyString),
3054
+ owner: Schema$1.optionalKey(TrimmedNonEmptyString),
3055
+ name: Schema$1.optionalKey(TrimmedNonEmptyString)
3056
+ });
3057
+ Schema$1.Struct({
3058
+ environmentId: EnvironmentId,
3059
+ projectId: ProjectId
3060
+ });
3061
+ Schema$1.Struct({
3062
+ environmentId: EnvironmentId,
3063
+ threadId: ThreadId
3064
+ });
3065
+ Schema$1.Struct({
3066
+ environmentId: EnvironmentId,
3067
+ threadId: ThreadId
3068
+ });
3069
+ //#endregion
3218
3070
  //#region ../../packages/contracts/src/orchestration.ts
3219
3071
  const ORCHESTRATION_WS_METHODS = {
3220
3072
  dispatchCommand: "orchestration.dispatchCommand",
@@ -3296,6 +3148,12 @@ const COMPRESS_MODE_VALUES = [
3296
3148
  "ultra",
3297
3149
  "wenyan"
3298
3150
  ];
3151
+ /**
3152
+ * The strict mode set, for a reader that must not accept a mode it cannot
3153
+ * name. `CompressMode` below stays tolerant for wire compatibility; anything
3154
+ * storing a mode as a fact uses this one instead, because coercing an unknown
3155
+ * mode to the default would record a compression pass that never happened.
3156
+ */
3299
3157
  const CompressModeLiteral = Schema$1.Literals(COMPRESS_MODE_VALUES);
3300
3158
  /**
3301
3159
  * Tolerant on the wire: an unknown mode decodes to the default instead of
@@ -5347,6 +5205,579 @@ var OrchestrationSearchThreadsError = class extends Schema$1.TaggedErrorClass()(
5347
5205
  message: TrimmedNonEmptyString,
5348
5206
  cause: Schema$1.optional(Schema$1.Defect())
5349
5207
  }) {};
5208
+ const ThreadWatchEventsInput = Schema$1.Struct({
5209
+ threadId: ThreadId.annotate({ description: "The thread to read events from. Any thread on this server, not only this session's own." }),
5210
+ afterSequence: Schema$1.optional(NonNegativeInt.annotate({ description: "Exclusive sequence cursor. Events with a higher sequence are returned. Omit to read from the beginning." })),
5211
+ limit: Schema$1.optional(NonNegativeInt.check(Schema$1.isLessThanOrEqualTo(200)).annotate({ description: `Maximum events to return (default 50, max 200).` }))
5212
+ });
5213
+ const ThreadWatchEventsResult = Schema$1.Struct({
5214
+ threadId: ThreadId,
5215
+ /** Ordered by sequence ascending; resume with the last event's sequence. */
5216
+ events: Schema$1.Array(OrchestrationEvent),
5217
+ /**
5218
+ * The engine's latest persisted sequence at read time. Equal to the last
5219
+ * returned event's sequence when the watcher is caught up.
5220
+ */
5221
+ headSequence: NonNegativeInt,
5222
+ /** True when more events for this thread exist past the returned page. */
5223
+ hasMore: Schema$1.Boolean
5224
+ });
5225
+ /** Compact review limits are server-owned, independent of raw event paging. */
5226
+ const THREAD_REVIEW_SCAN_LIMIT = 2e3;
5227
+ const THREAD_REVIEW_ITEM_MAX_BYTES = 8e3;
5228
+ const ThreadWatchReviewInput = Schema$1.Struct({
5229
+ threadId: ThreadId,
5230
+ afterSequence: Schema$1.optional(NonNegativeInt),
5231
+ throughSequence: Schema$1.optional(NonNegativeInt.annotate({ description: "Inclusive fixed review boundary. Omit on the first page to capture the head; reuse the returned boundary on every subsequent page." }))
5232
+ });
5233
+ const ReviewSourceFields = {
5234
+ firstSequence: NonNegativeInt,
5235
+ lastSequence: NonNegativeInt,
5236
+ truncated: Schema$1.Boolean
5237
+ };
5238
+ const ThreadReviewItem = Schema$1.Union([Schema$1.Struct({
5239
+ ...ReviewSourceFields,
5240
+ kind: Schema$1.Literal("message"),
5241
+ messageId: Schema$1.String,
5242
+ turnId: Schema$1.NullOr(Schema$1.String),
5243
+ role: Schema$1.String,
5244
+ /** Apply append/replace by message identity across page boundaries. */
5245
+ operation: Schema$1.Literals(["append", "replace"]),
5246
+ complete: Schema$1.Boolean,
5247
+ text: Schema$1.String,
5248
+ /**
5249
+ * What the message carried besides text.
5250
+ *
5251
+ * Present only when there was something: a message whose fragments are
5252
+ * merged by identity would otherwise lose every trace of an image or a
5253
+ * document the builder attached. Bounded by
5254
+ * {@link THREAD_REVIEW_ITEM_MAX_ATTACHMENTS} so the item stays trimmable -
5255
+ * only its text shrinks under the byte cap, so nothing else may be able to
5256
+ * grow without limit.
5257
+ */
5258
+ attachments: Schema$1.optionalKey(Schema$1.Array(Schema$1.Struct({
5259
+ type: Schema$1.String,
5260
+ name: Schema$1.String,
5261
+ sizeBytes: NonNegativeInt
5262
+ })))
5263
+ }), Schema$1.Struct({
5264
+ ...ReviewSourceFields,
5265
+ kind: Schema$1.Literal("event"),
5266
+ type: Schema$1.String,
5267
+ /** JSON evidence excerpt. Truncated excerpts require targeted raw recovery. */
5268
+ payload: Schema$1.String
5269
+ })]);
5270
+ /**
5271
+ * Compressed evidence standing in for the items it replaces.
5272
+ *
5273
+ * The supervisor reads this instead of paging the range event by event. The
5274
+ * sequence bounds are what make that safe: any claim can be verified at source
5275
+ * with a targeted `thread_watch_events` read over exactly this range, so the
5276
+ * summary is a starting point rather than the only record.
5277
+ *
5278
+ * `degraded` is set whenever the items came back unsummarized, and names why.
5279
+ * Most reasons are failures - unset model, provider error, timeout, empty or
5280
+ * unusable output - but `page-under-threshold` is not: it means the page was
5281
+ * small enough that summarizing it would have cost more than reading it. The
5282
+ * supervisor is never left guessing whether it is reading everything.
5283
+ */
5284
+ const ThreadReviewSummary = Schema$1.Struct({
5285
+ text: Schema$1.String,
5286
+ firstSequence: NonNegativeInt,
5287
+ lastSequence: NonNegativeInt,
5288
+ /** How many builder turns the range covered. */
5289
+ turnCount: NonNegativeInt,
5290
+ degraded: Schema$1.NullOr(Schema$1.String)
5291
+ });
5292
+ const ThreadWatchReviewResult = Schema$1.Struct({
5293
+ threadId: ThreadId,
5294
+ items: Schema$1.Array(ThreadReviewItem),
5295
+ throughSequence: NonNegativeInt,
5296
+ /** Resume from the source scan cursor, even when items is empty. */
5297
+ nextAfterSequence: NonNegativeInt,
5298
+ hasMore: Schema$1.Boolean,
5299
+ scannedEvents: NonNegativeInt,
5300
+ /**
5301
+ * Present only when summarization ran. When its `degraded` is null the
5302
+ * `items` for the summarized range are omitted and this stands in for them;
5303
+ * otherwise the items are intact and this explains why.
5304
+ */
5305
+ summary: Schema$1.optionalKey(Schema$1.NullOr(ThreadReviewSummary))
5306
+ });
5307
+ /**
5308
+ * How a review ended, from the supervisor's own account of it.
5309
+ *
5310
+ * Three states rather than a boolean because they are acted on differently: a
5311
+ * summary that arrived is evidence, a fallback is a working review that cost
5312
+ * full context, and a failure is a misconfiguration worth fixing. The fourth
5313
+ * state - a read no report ever answered - is not sayable here: only the
5314
+ * server can observe it, through `ThreadEvidenceActivityOutcome`.
5315
+ */
5316
+ const ThreadEvidenceReportOutcome = Schema$1.Literals([
5317
+ "summarized",
5318
+ "fallback",
5319
+ "failed"
5320
+ ]);
5321
+ /** Bounded so one report cannot dominate a thread's activity storage. */
5322
+ const THREAD_EVIDENCE_REPORT_MAX_CHARS = 8e3;
5323
+ const ThreadEvidenceReportInput = Schema$1.Struct({
5324
+ /** The watched thread the summary covers, not the reporting thread. */
5325
+ threadId: ThreadId,
5326
+ firstSequence: NonNegativeInt,
5327
+ lastSequence: NonNegativeInt,
5328
+ /**
5329
+ * The fixed review boundary the pages were read against.
5330
+ *
5331
+ * Identity, not description: the evidence-read rows are keyed on it, so a
5332
+ * report that guessed from its own last sequence would look for a baseline
5333
+ * that never existed whenever the range ended on a filtered event.
5334
+ */
5335
+ throughSequence: NonNegativeInt,
5336
+ outcome: ThreadEvidenceReportOutcome,
5337
+ /** The summarizer's exact text. Required when the outcome is summarized. */
5338
+ summary: Schema$1.optional(Schema$1.String),
5339
+ /** The model the subagent ran on, when one was spawned. */
5340
+ model: Schema$1.optional(Schema$1.String),
5341
+ /** Why the summary is missing or degraded. Required unless summarized. */
5342
+ reason: Schema$1.optional(Schema$1.String)
5343
+ });
5344
+ /**
5345
+ * The supervisor's own context around the read, correlated by the server.
5346
+ *
5347
+ * Self-reported numbers would be worth little, so these are read from the
5348
+ * reporting thread's projected context-window rows: `before` is what was
5349
+ * recorded when the evidence read happened, `after` what is recorded when the
5350
+ * report lands. `delta` can be negative when a compaction ran in between, and
5351
+ * is null whenever no read-time baseline exists.
5352
+ */
5353
+ const ThreadEvidenceReportContext = Schema$1.Struct({
5354
+ /**
5355
+ * Null when no reading was recorded at read time.
5356
+ *
5357
+ * Never the report-time value standing in for itself: a delta of zero read
5358
+ * as measurement would be a lie about a review that cost context.
5359
+ */
5360
+ usedTokensBefore: Schema$1.NullOr(NonNegativeInt),
5361
+ usedTokensAfter: NonNegativeInt,
5362
+ /** Null exactly when `usedTokensBefore` is. */
5363
+ deltaTokens: Schema$1.NullOr(Schema$1.Number),
5364
+ maxTokens: Schema$1.NullOr(NonNegativeInt),
5365
+ capacityPercent: Schema$1.NullOr(Schema$1.Number)
5366
+ });
5367
+ /**
5368
+ * How the recorded row ended, which is the supervisor's three outcomes plus the
5369
+ * one only the server can state: the review closed with no report at all.
5370
+ *
5371
+ * `unreported` is never accepted from a tool call. The server writes it when a
5372
+ * supervisor turn ends over an evidence read that nothing answered, so the
5373
+ * absence survives outside a live client that could infer it from two rows.
5374
+ */
5375
+ const ThreadEvidenceActivityOutcome = Schema$1.Literals([
5376
+ "summarized",
5377
+ "fallback",
5378
+ "failed",
5379
+ "unreported"
5380
+ ]);
5381
+ /**
5382
+ * Server clock around one review, never the supervisor's own account of it.
5383
+ *
5384
+ * `readAt` is when the evidence-read row was recorded and `closedAt` when the
5385
+ * row that closes the review was: the report for a reported review, the
5386
+ * completed supervisor turn for an unreported one. A supervisor turn that
5387
+ * failed or was interrupted closes nothing, so its read keeps no timing until
5388
+ * some later turn reports on it.
5389
+ */
5390
+ const ThreadEvidenceReportTiming = Schema$1.Struct({
5391
+ readAt: IsoDateTime,
5392
+ closedAt: IsoDateTime,
5393
+ durationMs: NonNegativeInt
5394
+ });
5395
+ /** Payload of the single `fusion.evidence.summary` activity a report records. */
5396
+ const ThreadEvidenceReportActivity = Schema$1.Struct({
5397
+ watchedThreadId: ThreadId,
5398
+ firstSequence: NonNegativeInt,
5399
+ lastSequence: NonNegativeInt,
5400
+ throughSequence: NonNegativeInt,
5401
+ outcome: ThreadEvidenceActivityOutcome,
5402
+ summary: Schema$1.NullOr(Schema$1.String),
5403
+ /** True when a snapshot dropped the text of a superseded report. */
5404
+ summaryDropped: Schema$1.optionalKey(Schema$1.Boolean),
5405
+ model: Schema$1.NullOr(Schema$1.String),
5406
+ reason: Schema$1.NullOr(Schema$1.String),
5407
+ /** Null when no context-window row exists yet for the reporting thread. */
5408
+ context: Schema$1.NullOr(ThreadEvidenceReportContext),
5409
+ /**
5410
+ * Null when the evidence-read row behind this review is no longer readable,
5411
+ * and absent on rows stored before the server measured a review at all.
5412
+ */
5413
+ timing: Schema$1.optionalKey(Schema$1.NullOr(ThreadEvidenceReportTiming))
5414
+ });
5415
+ const ThreadEvidenceReportResult = Schema$1.Struct({
5416
+ recorded: Schema$1.Boolean,
5417
+ context: Schema$1.NullOr(ThreadEvidenceReportContext)
5418
+ });
5419
+ var WatchToolUnavailableError = class extends Schema$1.TaggedErrorClass()("WatchToolUnavailableError", {
5420
+ capability: Schema$1.Literal("watch"),
5421
+ environmentId: EnvironmentId,
5422
+ threadId: ThreadId,
5423
+ providerSessionId: TrimmedNonEmptyString,
5424
+ providerInstanceId: ProviderInstanceId
5425
+ }) {
5426
+ get message() {
5427
+ return `MCP credential does not grant the ${this.capability} capability.`;
5428
+ }
5429
+ };
5430
+ /**
5431
+ * The capability is present but the requested thread is not in the granted
5432
+ * set. Separate from {@link WatchToolUnavailableError} so a watcher can tell
5433
+ * "you cannot watch at all" apart from "not this thread".
5434
+ */
5435
+ var ThreadWatchNotPermittedError = class extends Schema$1.TaggedErrorClass()("ThreadWatchNotPermittedError", { threadId: ThreadId }) {
5436
+ get message() {
5437
+ return `This session was not granted watch access to thread ${this.threadId}.`;
5438
+ }
5439
+ };
5440
+ var ThreadWatchFailedError = class extends Schema$1.TaggedErrorClass()("ThreadWatchFailedError", {
5441
+ threadId: ThreadId,
5442
+ detail: Schema$1.String
5443
+ }) {
5444
+ get message() {
5445
+ return `Reading events for thread ${this.threadId} failed: ${this.detail}`;
5446
+ }
5447
+ };
5448
+ const ThreadWatchToolError = Schema$1.Union([
5449
+ WatchToolUnavailableError,
5450
+ ThreadWatchNotPermittedError,
5451
+ ThreadWatchFailedError
5452
+ ]);
5453
+ //#endregion
5454
+ //#region ../../packages/contracts/src/hubAnalytics.ts
5455
+ /**
5456
+ * Account-scoped analytics facts the hub stores for its own account holder.
5457
+ *
5458
+ * Append-only and versioned: a fact is what happened once, never a row that is
5459
+ * later corrected, so a changed shape ships as a new `version` beside the old
5460
+ * one rather than a rewrite of stored rows. Every payload is bounded, because
5461
+ * these travel from environments the hub does not control and a fact that can
5462
+ * grow without limit is a store that can be filled by one caller.
5463
+ *
5464
+ * Nothing here leaves the account: the hub is the only sink, and no field is
5465
+ * shaped for an external telemetry vendor.
5466
+ *
5467
+ * @module hubAnalytics
5468
+ */
5469
+ const MAX_FACT_ID_CHARS = 128;
5470
+ const MAX_QUESTION_CHARS$2 = 2e3;
5471
+ const MAX_HEADER_CHARS$2 = 120;
5472
+ const MAX_OPTION_LABEL_CHARS$1 = 200;
5473
+ const MAX_OPTION_DESCRIPTION_CHARS$1 = 600;
5474
+ const MAX_PRESENTED_OPTIONS$1 = 12;
5475
+ const MAX_CUSTOM_ANSWER_CHARS$1 = 2e3;
5476
+ const MAX_SELECTED_ANSWERS = MAX_PRESENTED_OPTIONS$1;
5477
+ const MAX_VERSION_CHARS = 40;
5478
+ const MAX_REASON_CHARS$1 = 200;
5479
+ const MAX_MODEL_CHARS$1 = 120;
5480
+ const MAX_QUERY_KINDS = 3;
5481
+ const MAX_QUERY_LIMIT = 200;
5482
+ const DEFAULT_QUERY_LIMIT = 50;
5483
+ const MAX_CURSOR_CHARS = 160;
5484
+ const factId = Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_FACT_ID_CHARS), Schema$1.isPattern(/^[A-Za-z0-9][A-Za-z0-9._:-]*$(?![\s\S])/));
5485
+ const nonNegativeInt$1 = Schema$1.Int.check(Schema$1.isGreaterThanOrEqualTo(0));
5486
+ /**
5487
+ * One option exactly as the user saw it, so a later preference read has its
5488
+ * context. Deliberately its own shape rather than the live question contract:
5489
+ * a fact records what was shown that day, and must not change meaning when the
5490
+ * question schema does.
5491
+ */
5492
+ const PresentedOption = Schema$1.Struct({
5493
+ label: Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_OPTION_LABEL_CHARS$1)),
5494
+ description: Schema$1.String.check(Schema$1.isMaxLength(MAX_OPTION_DESCRIPTION_CHARS$1))
5495
+ });
5496
+ /**
5497
+ * What the user actually answered.
5498
+ *
5499
+ * The custom text is kept verbatim: an answer nobody offered is the most
5500
+ * informative one there is, and summarizing it away would leave the fact
5501
+ * saying only that the offered options were all wrong.
5502
+ */
5503
+ const QuestionAnswer = Schema$1.Union([
5504
+ Schema$1.Struct({
5505
+ kind: Schema$1.Literal("option"),
5506
+ optionIndexes: Schema$1.Array(nonNegativeInt$1).check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_SELECTED_ANSWERS)),
5507
+ labels: Schema$1.Array(Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_OPTION_LABEL_CHARS$1))).check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_SELECTED_ANSWERS))
5508
+ }),
5509
+ Schema$1.Struct({
5510
+ kind: Schema$1.Literal("custom"),
5511
+ text: Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_CUSTOM_ANSWER_CHARS$1))
5512
+ }),
5513
+ Schema$1.Struct({ kind: Schema$1.Literal("dismissed") })
5514
+ ]);
5515
+ /** An `ask_user_question` as presented, and what came back. */
5516
+ const HubQuestionFactV1 = Schema$1.Struct({
5517
+ questionId: factId,
5518
+ threadId: Schema$1.optional(ThreadId),
5519
+ header: Schema$1.String.check(Schema$1.isMaxLength(MAX_HEADER_CHARS$2)),
5520
+ question: Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_QUESTION_CHARS$2)),
5521
+ multiSelect: Schema$1.Boolean,
5522
+ options: Schema$1.Array(PresentedOption).check(Schema$1.isMaxLength(MAX_PRESENTED_OPTIONS$1)),
5523
+ answer: QuestionAnswer,
5524
+ answeredInMs: Schema$1.optional(nonNegativeInt$1)
5525
+ });
5526
+ /**
5527
+ * One Fusion handoff, from one implementer thread.
5528
+ *
5529
+ * Efficiency is reported as what the handoff carried and what it cost, never
5530
+ * as a score: the counts are comparable across versions, a verdict is not.
5531
+ */
5532
+ const HubFusionHandoffFactV1 = Schema$1.Struct({
5533
+ fusionVersion: Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_VERSION_CHARS)),
5534
+ threadId: ThreadId,
5535
+ /**
5536
+ * The recorded review outcome, in the server's own vocabulary: `unreported`
5537
+ * is a real result - a review that closed answering nothing - and collapsing
5538
+ * it into a failure would lose the only row that says so.
5539
+ */
5540
+ status: ThreadEvidenceActivityOutcome,
5541
+ /** The model the report names, or null when the review recorded none. */
5542
+ model: Schema$1.NullOr(Schema$1.String.check(Schema$1.isMaxLength(MAX_MODEL_CHARS$1))),
5543
+ reason: Schema$1.NullOr(Schema$1.String.check(Schema$1.isMaxLength(MAX_REASON_CHARS$1))),
5544
+ transcriptEvents: nonNegativeInt$1,
5545
+ transcriptIncluded: nonNegativeInt$1,
5546
+ transcriptDropped: nonNegativeInt$1,
5547
+ transcriptClippedFields: nonNegativeInt$1,
5548
+ ledgerRows: nonNegativeInt$1,
5549
+ ledgerTotalRows: nonNegativeInt$1,
5550
+ summaryChars: Schema$1.NullOr(nonNegativeInt$1),
5551
+ contextDeltaTokens: Schema$1.NullOr(Schema$1.Int),
5552
+ durationMs: Schema$1.NullOr(nonNegativeInt$1)
5553
+ });
5554
+ /**
5555
+ * One compression pass: what it started from, what it left, and what it cost.
5556
+ *
5557
+ * The mode is the product's own strict mode set, so a mode added there is
5558
+ * recordable here the day it ships; a local copy of the literals would quietly
5559
+ * reject the new one and lose exactly the passes worth measuring. The strict
5560
+ * schema rather than the tolerant wire one: a fact that relabelled an unknown
5561
+ * mode as the default would be a record of something that never ran.
5562
+ */
5563
+ const HubCompressionFactV1 = Schema$1.Struct({
5564
+ mode: CompressModeLiteral,
5565
+ threadId: Schema$1.optional(ThreadId),
5566
+ beforeTokens: nonNegativeInt$1,
5567
+ afterTokens: nonNegativeInt$1,
5568
+ /** Negative when the pass cost more than it saved; the sign is the finding. */
5569
+ deltaTokens: Schema$1.Int,
5570
+ durationMs: nonNegativeInt$1
5571
+ });
5572
+ const HubAnalyticsFactKind = Schema$1.Literals([
5573
+ "question.answered",
5574
+ "fusion.handoff",
5575
+ "compression.applied"
5576
+ ]);
5577
+ const factBase = {
5578
+ /** Dedupe identity: the same id appended twice stores one fact. */
5579
+ eventId: factId,
5580
+ occurredAt: IsoDateTime
5581
+ };
5582
+ /** One append. The kind fixes the payload shape, and the version fixes its meaning. */
5583
+ const HubAnalyticsFactInput = Schema$1.Union([
5584
+ Schema$1.Struct({
5585
+ ...factBase,
5586
+ kind: Schema$1.Literal("question.answered"),
5587
+ version: Schema$1.Literal(1),
5588
+ payload: HubQuestionFactV1
5589
+ }),
5590
+ Schema$1.Struct({
5591
+ ...factBase,
5592
+ kind: Schema$1.Literal("fusion.handoff"),
5593
+ version: Schema$1.Literal(1),
5594
+ payload: HubFusionHandoffFactV1
5595
+ }),
5596
+ Schema$1.Struct({
5597
+ ...factBase,
5598
+ kind: Schema$1.Literal("compression.applied"),
5599
+ version: Schema$1.Literal(1),
5600
+ payload: HubCompressionFactV1
5601
+ })
5602
+ ]);
5603
+ /** A stored fact as it is read back, with the environment that reported it. */
5604
+ const HubAnalyticsFact = Schema$1.Struct({
5605
+ eventId: factId,
5606
+ environmentId: EnvironmentId,
5607
+ kind: HubAnalyticsFactKind,
5608
+ version: Schema$1.Int.check(Schema$1.isGreaterThanOrEqualTo(1)),
5609
+ occurredAt: IsoDateTime,
5610
+ payload: Schema$1.Unknown
5611
+ });
5612
+ /**
5613
+ * An opaque page position: the occurrence millisecond and the event id that
5614
+ * produced it, which together order every row exactly once.
5615
+ */
5616
+ const HubAnalyticsCursor = Schema$1.String.check(Schema$1.isMaxLength(MAX_CURSOR_CHARS), Schema$1.isPattern(/^\d+\|[A-Za-z0-9][A-Za-z0-9._:-]*$(?![\s\S])/));
5617
+ const HubAnalyticsQuery = Schema$1.Struct({
5618
+ kinds: Schema$1.optional(Schema$1.Array(HubAnalyticsFactKind).check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_QUERY_KINDS))),
5619
+ since: Schema$1.optional(IsoDateTime),
5620
+ until: Schema$1.optional(IsoDateTime),
5621
+ limit: Schema$1.optional(Schema$1.Int.check(Schema$1.isGreaterThanOrEqualTo(1), Schema$1.isLessThanOrEqualTo(MAX_QUERY_LIMIT))),
5622
+ cursor: Schema$1.optional(HubAnalyticsCursor)
5623
+ });
5624
+ const HubAnalyticsPage = Schema$1.Struct({
5625
+ facts: Schema$1.Array(HubAnalyticsFact),
5626
+ /** Null when the page reached the end, never an empty string. */
5627
+ nextCursor: Schema$1.NullOr(HubAnalyticsCursor)
5628
+ });
5629
+ const HUB_ANALYTICS_QUERY_LIMITS = {
5630
+ defaultLimit: DEFAULT_QUERY_LIMIT,
5631
+ maxLimit: MAX_QUERY_LIMIT
5632
+ };
5633
+ //#endregion
5634
+ //#region ../../packages/contracts/src/hubNotifications.ts
5635
+ const MAX_NOTIFICATION_ID_CHARS = 128;
5636
+ const MAX_PUSH_TOKEN_CHARS = 4096;
5637
+ const MAX_HUB_ORIGIN_CHARS = 2048;
5638
+ const MAX_ACTIVITY_TITLE_CHARS = 160;
5639
+ const MAX_ACTIVITY_DETAIL_CHARS = 240;
5640
+ const identifier = Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_NOTIFICATION_ID_CHARS), Schema$1.isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]*$(?![\s\S])/));
5641
+ const pushToken = Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_PUSH_TOKEN_CHARS), Schema$1.isPattern(/^[\x21-\x7e]+$(?![\s\S])/));
5642
+ /** Server emission is bound to the authenticated environment, never a supplied account. */
5643
+ const HubAgentActivityInput = Schema$1.Struct({
5644
+ eventId: identifier,
5645
+ threadId: ThreadId,
5646
+ revision: Schema$1.Int.check(Schema$1.isGreaterThanOrEqualTo(0)),
5647
+ state: Schema$1.Literals([
5648
+ "running",
5649
+ "approval_required",
5650
+ "question_required",
5651
+ "completed",
5652
+ "failed",
5653
+ "stopped"
5654
+ ]),
5655
+ title: Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_ACTIVITY_TITLE_CHARS)),
5656
+ detail: Schema$1.String.check(Schema$1.isMaxLength(MAX_ACTIVITY_DETAIL_CHARS))
5657
+ });
5658
+ /** Each authenticated client owns its registration; tokens never belong in public client state. */
5659
+ const HubPushRegistrationInput = Schema$1.Union([Schema$1.Struct({
5660
+ platform: Schema$1.Literal("ios"),
5661
+ token: pushToken,
5662
+ sandbox: Schema$1.Boolean
5663
+ }), Schema$1.Struct({
5664
+ platform: Schema$1.Literal("android"),
5665
+ token: pushToken
5666
+ })]);
5667
+ /** Explicit opt-in, independently reversible. Omission never implies permission. */
5668
+ const HubNotificationPreferences = Schema$1.Struct({
5669
+ attention: Schema$1.Boolean,
5670
+ completion: Schema$1.Boolean,
5671
+ liveActivity: Schema$1.Boolean
5672
+ });
5673
+ Schema$1.Struct({
5674
+ registered: Schema$1.Boolean,
5675
+ registrationGeneration: Schema$1.NullOr(identifier),
5676
+ preferences: HubNotificationPreferences
5677
+ });
5678
+ /** ActivityKit update token, distinct from the app's APNs registration token. */
5679
+ const HubActivityOwnership = Schema$1.Struct({
5680
+ origin: Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_HUB_ORIGIN_CHARS)),
5681
+ environmentId: EnvironmentId,
5682
+ clientId: HubClientId,
5683
+ registrationGeneration: identifier,
5684
+ threadId: ThreadId,
5685
+ activityId: identifier
5686
+ });
5687
+ const HubLiveActivityRegistrationInput = Schema$1.Struct({
5688
+ threadId: ThreadId,
5689
+ registrationGeneration: identifier,
5690
+ activityId: identifier,
5691
+ token: Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_PUSH_TOKEN_CHARS), Schema$1.isPattern(/^(?:[a-fA-F0-9]{2})+$(?![\s\S])/)),
5692
+ sandbox: Schema$1.Boolean,
5693
+ p4Ownership: Schema$1.optional(HubActivityOwnership)
5694
+ });
5695
+ /** Close only this presentation; delayed requests must not retire a newer activity. */
5696
+ const HubLiveActivityRemoveInput = Schema$1.Struct({
5697
+ threadId: ThreadId,
5698
+ registrationGeneration: identifier,
5699
+ activityId: identifier
5700
+ });
5701
+ const HUB_TUNNEL_WEBSOCKET_PROTOCOL = "p4-hub-tunnel-v1";
5702
+ const HUB_TUNNEL_INITIAL_WINDOW_BYTES = 256 * 1024;
5703
+ const HUB_TUNNEL_MAX_WINDOW_BYTES = 1024 * 1024;
5704
+ const MAX_HEADERS = 64;
5705
+ const MAX_HEADER_NAME_CHARS = 128;
5706
+ const MAX_HEADER_VALUE_CHARS = 8192;
5707
+ const MAX_PATH_CHARS = 8192;
5708
+ const MAX_PROTOCOLS = 16;
5709
+ const MAX_PROTOCOL_CHARS = 128;
5710
+ const HeaderName = Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_HEADER_NAME_CHARS), Schema$1.isPattern(/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$(?![\s\S])/));
5711
+ const HeaderValue = Schema$1.String.check(Schema$1.isMaxLength(MAX_HEADER_VALUE_CHARS), Schema$1.isPattern(/^[\t\x20-\x7e]*$(?![\s\S])/));
5712
+ const Headers$2 = Schema$1.Array(Schema$1.Tuple([HeaderName, HeaderValue])).check(Schema$1.isMaxLength(MAX_HEADERS));
5713
+ const Path = Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_PATH_CHARS), Schema$1.isPattern(/^\/(?!\/)[^\s\\#\x00-\x1f\x7f]*$(?![\s\S])/));
5714
+ const Protocol = Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_PROTOCOL_CHARS), Schema$1.isPattern(/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$(?![\s\S])/));
5715
+ const FailureReason = Schema$1.Literals([
5716
+ "cancelled",
5717
+ "unavailable",
5718
+ "protocol_error"
5719
+ ]);
5720
+ const CloseCode = Schema$1.Int.check(Schema$1.makeFilter((code) => code >= 1e3 && code <= 1014 && code !== 1004 && code !== 1005 && code !== 1006 || code >= 3e3 && code <= 4999));
5721
+ /** No caller-supplied account, environment, origin, or connector ownership authority. */
5722
+ const HubTunnelControl = Schema$1.Union([
5723
+ Schema$1.Struct({
5724
+ type: Schema$1.Literal("connector_ready"),
5725
+ protocolVersion: Schema$1.Literal(1)
5726
+ }),
5727
+ Schema$1.Struct({
5728
+ type: Schema$1.Literal("http_request_start"),
5729
+ method: Schema$1.Literals([
5730
+ "GET",
5731
+ "HEAD",
5732
+ "POST",
5733
+ "PUT",
5734
+ "PATCH",
5735
+ "DELETE",
5736
+ "OPTIONS"
5737
+ ]),
5738
+ path: Path,
5739
+ headers: Headers$2
5740
+ }),
5741
+ Schema$1.Struct({ type: Schema$1.Literal("http_request_end") }),
5742
+ Schema$1.Struct({
5743
+ type: Schema$1.Literal("http_request_abort"),
5744
+ reason: FailureReason
5745
+ }),
5746
+ Schema$1.Struct({
5747
+ type: Schema$1.Literal("http_response_start"),
5748
+ status: Schema$1.Int.check(Schema$1.isGreaterThanOrEqualTo(200), Schema$1.isLessThanOrEqualTo(599)),
5749
+ headers: Headers$2
5750
+ }),
5751
+ Schema$1.Struct({ type: Schema$1.Literal("http_response_end") }),
5752
+ Schema$1.Struct({
5753
+ type: Schema$1.Literal("http_response_abort"),
5754
+ reason: FailureReason
5755
+ }),
5756
+ Schema$1.Struct({
5757
+ type: Schema$1.Literal("websocket_open"),
5758
+ path: Path,
5759
+ headers: Headers$2,
5760
+ protocols: Schema$1.Array(Protocol).check(Schema$1.isMaxLength(MAX_PROTOCOLS))
5761
+ }),
5762
+ Schema$1.Struct({
5763
+ type: Schema$1.Literal("websocket_accept"),
5764
+ protocol: Schema$1.optional(Protocol)
5765
+ }),
5766
+ Schema$1.Struct({
5767
+ type: Schema$1.Literal("websocket_reject"),
5768
+ status: Schema$1.Int.check(Schema$1.isGreaterThanOrEqualTo(400), Schema$1.isLessThanOrEqualTo(599)),
5769
+ reason: FailureReason
5770
+ }),
5771
+ Schema$1.Struct({
5772
+ type: Schema$1.Literal("websocket_close"),
5773
+ code: CloseCode,
5774
+ reason: FailureReason
5775
+ }),
5776
+ Schema$1.Struct({
5777
+ type: Schema$1.Literal("window_update"),
5778
+ creditBytes: Schema$1.Int.check(Schema$1.isGreaterThan(0), Schema$1.isLessThanOrEqualTo(HUB_TUNNEL_MAX_WINDOW_BYTES))
5779
+ })
5780
+ ]);
5350
5781
  //#endregion
5351
5782
  //#region ../../packages/contracts/src/environmentHttp.ts
5352
5783
  const OptionalBearerHeaders = Schema$1.Struct({
@@ -7866,9 +8297,16 @@ const UserInputResolvedPayload = Schema$1.Struct({ answers: UnknownRecordSchema
7866
8297
  * that outlives its turn is in practice a watch loop). Canonical single copy,
7867
8298
  * so the server liveness registry and any client fold classify identically.
7868
8299
  */
8300
+ const MONITOR_TOOL_TASK_TYPES = /* @__PURE__ */ new Set(["monitor", "monitor_mcp"]);
8301
+ /**
8302
+ * A watch loop the agent asked for by name, without the background shells.
8303
+ *
8304
+ * A backgrounded shell is only a watch loop in practice; it can equally run a
8305
+ * build or edit a file, so a consumer that needs proof of waiting rather than
8306
+ * a liveness hint must use this narrower set.
8307
+ */
7869
8308
  const MONITOR_TASK_TYPES = /* @__PURE__ */ new Set([
7870
- "monitor",
7871
- "monitor_mcp",
8309
+ ...MONITOR_TOOL_TASK_TYPES,
7872
8310
  "local_bash",
7873
8311
  "shell"
7874
8312
  ]);
@@ -12568,251 +13006,6 @@ var ThreadAskFailedError = class extends Schema$1.TaggedErrorClass()("ThreadAskF
12568
13006
  }
12569
13007
  };
12570
13008
  const ThreadAskToolError = Schema$1.Union([ThreadAskNotPermittedError, ThreadAskFailedError]);
12571
- const ThreadWatchEventsInput = Schema$1.Struct({
12572
- threadId: ThreadId.annotate({ description: "The thread to read events from. Any thread on this server, not only this session's own." }),
12573
- afterSequence: Schema$1.optional(NonNegativeInt.annotate({ description: "Exclusive sequence cursor. Events with a higher sequence are returned. Omit to read from the beginning." })),
12574
- limit: Schema$1.optional(NonNegativeInt.check(Schema$1.isLessThanOrEqualTo(200)).annotate({ description: `Maximum events to return (default 50, max 200).` }))
12575
- });
12576
- const ThreadWatchEventsResult = Schema$1.Struct({
12577
- threadId: ThreadId,
12578
- /** Ordered by sequence ascending; resume with the last event's sequence. */
12579
- events: Schema$1.Array(OrchestrationEvent),
12580
- /**
12581
- * The engine's latest persisted sequence at read time. Equal to the last
12582
- * returned event's sequence when the watcher is caught up.
12583
- */
12584
- headSequence: NonNegativeInt,
12585
- /** True when more events for this thread exist past the returned page. */
12586
- hasMore: Schema$1.Boolean
12587
- });
12588
- /** Compact review limits are server-owned, independent of raw event paging. */
12589
- const THREAD_REVIEW_SCAN_LIMIT = 2e3;
12590
- const THREAD_REVIEW_ITEM_MAX_BYTES = 8e3;
12591
- const ThreadWatchReviewInput = Schema$1.Struct({
12592
- threadId: ThreadId,
12593
- afterSequence: Schema$1.optional(NonNegativeInt),
12594
- throughSequence: Schema$1.optional(NonNegativeInt.annotate({ description: "Inclusive fixed review boundary. Omit on the first page to capture the head; reuse the returned boundary on every subsequent page." }))
12595
- });
12596
- const ReviewSourceFields = {
12597
- firstSequence: NonNegativeInt,
12598
- lastSequence: NonNegativeInt,
12599
- truncated: Schema$1.Boolean
12600
- };
12601
- const ThreadReviewItem = Schema$1.Union([Schema$1.Struct({
12602
- ...ReviewSourceFields,
12603
- kind: Schema$1.Literal("message"),
12604
- messageId: Schema$1.String,
12605
- turnId: Schema$1.NullOr(Schema$1.String),
12606
- role: Schema$1.String,
12607
- /** Apply append/replace by message identity across page boundaries. */
12608
- operation: Schema$1.Literals(["append", "replace"]),
12609
- complete: Schema$1.Boolean,
12610
- text: Schema$1.String,
12611
- /**
12612
- * What the message carried besides text.
12613
- *
12614
- * Present only when there was something: a message whose fragments are
12615
- * merged by identity would otherwise lose every trace of an image or a
12616
- * document the builder attached. Bounded by
12617
- * {@link THREAD_REVIEW_ITEM_MAX_ATTACHMENTS} so the item stays trimmable -
12618
- * only its text shrinks under the byte cap, so nothing else may be able to
12619
- * grow without limit.
12620
- */
12621
- attachments: Schema$1.optionalKey(Schema$1.Array(Schema$1.Struct({
12622
- type: Schema$1.String,
12623
- name: Schema$1.String,
12624
- sizeBytes: NonNegativeInt
12625
- })))
12626
- }), Schema$1.Struct({
12627
- ...ReviewSourceFields,
12628
- kind: Schema$1.Literal("event"),
12629
- type: Schema$1.String,
12630
- /** JSON evidence excerpt. Truncated excerpts require targeted raw recovery. */
12631
- payload: Schema$1.String
12632
- })]);
12633
- /**
12634
- * Compressed evidence standing in for the items it replaces.
12635
- *
12636
- * The supervisor reads this instead of paging the range event by event. The
12637
- * sequence bounds are what make that safe: any claim can be verified at source
12638
- * with a targeted `thread_watch_events` read over exactly this range, so the
12639
- * summary is a starting point rather than the only record.
12640
- *
12641
- * `degraded` is set whenever the items came back unsummarized, and names why.
12642
- * Most reasons are failures - unset model, provider error, timeout, empty or
12643
- * unusable output - but `page-under-threshold` is not: it means the page was
12644
- * small enough that summarizing it would have cost more than reading it. The
12645
- * supervisor is never left guessing whether it is reading everything.
12646
- */
12647
- const ThreadReviewSummary = Schema$1.Struct({
12648
- text: Schema$1.String,
12649
- firstSequence: NonNegativeInt,
12650
- lastSequence: NonNegativeInt,
12651
- /** How many builder turns the range covered. */
12652
- turnCount: NonNegativeInt,
12653
- degraded: Schema$1.NullOr(Schema$1.String)
12654
- });
12655
- const ThreadWatchReviewResult = Schema$1.Struct({
12656
- threadId: ThreadId,
12657
- items: Schema$1.Array(ThreadReviewItem),
12658
- throughSequence: NonNegativeInt,
12659
- /** Resume from the source scan cursor, even when items is empty. */
12660
- nextAfterSequence: NonNegativeInt,
12661
- hasMore: Schema$1.Boolean,
12662
- scannedEvents: NonNegativeInt,
12663
- /**
12664
- * Present only when summarization ran. When its `degraded` is null the
12665
- * `items` for the summarized range are omitted and this stands in for them;
12666
- * otherwise the items are intact and this explains why.
12667
- */
12668
- summary: Schema$1.optionalKey(Schema$1.NullOr(ThreadReviewSummary))
12669
- });
12670
- /**
12671
- * How a review ended, from the supervisor's own account of it.
12672
- *
12673
- * Three states rather than a boolean because they are acted on differently: a
12674
- * summary that arrived is evidence, a fallback is a working review that cost
12675
- * full context, and a failure is a misconfiguration worth fixing. The fourth
12676
- * state - a read no report ever answered - is not sayable here: only the
12677
- * server can observe it, through `ThreadEvidenceActivityOutcome`.
12678
- */
12679
- const ThreadEvidenceReportOutcome = Schema$1.Literals([
12680
- "summarized",
12681
- "fallback",
12682
- "failed"
12683
- ]);
12684
- /** Bounded so one report cannot dominate a thread's activity storage. */
12685
- const THREAD_EVIDENCE_REPORT_MAX_CHARS = 8e3;
12686
- const ThreadEvidenceReportInput = Schema$1.Struct({
12687
- /** The watched thread the summary covers, not the reporting thread. */
12688
- threadId: ThreadId,
12689
- firstSequence: NonNegativeInt,
12690
- lastSequence: NonNegativeInt,
12691
- /**
12692
- * The fixed review boundary the pages were read against.
12693
- *
12694
- * Identity, not description: the evidence-read rows are keyed on it, so a
12695
- * report that guessed from its own last sequence would look for a baseline
12696
- * that never existed whenever the range ended on a filtered event.
12697
- */
12698
- throughSequence: NonNegativeInt,
12699
- outcome: ThreadEvidenceReportOutcome,
12700
- /** The summarizer's exact text. Required when the outcome is summarized. */
12701
- summary: Schema$1.optional(Schema$1.String),
12702
- /** The model the subagent ran on, when one was spawned. */
12703
- model: Schema$1.optional(Schema$1.String),
12704
- /** Why the summary is missing or degraded. Required unless summarized. */
12705
- reason: Schema$1.optional(Schema$1.String)
12706
- });
12707
- /**
12708
- * The supervisor's own context around the read, correlated by the server.
12709
- *
12710
- * Self-reported numbers would be worth little, so these are read from the
12711
- * reporting thread's projected context-window rows: `before` is what was
12712
- * recorded when the evidence read happened, `after` what is recorded when the
12713
- * report lands. `delta` can be negative when a compaction ran in between, and
12714
- * is null whenever no read-time baseline exists.
12715
- */
12716
- const ThreadEvidenceReportContext = Schema$1.Struct({
12717
- /**
12718
- * Null when no reading was recorded at read time.
12719
- *
12720
- * Never the report-time value standing in for itself: a delta of zero read
12721
- * as measurement would be a lie about a review that cost context.
12722
- */
12723
- usedTokensBefore: Schema$1.NullOr(NonNegativeInt),
12724
- usedTokensAfter: NonNegativeInt,
12725
- /** Null exactly when `usedTokensBefore` is. */
12726
- deltaTokens: Schema$1.NullOr(Schema$1.Number),
12727
- maxTokens: Schema$1.NullOr(NonNegativeInt),
12728
- capacityPercent: Schema$1.NullOr(Schema$1.Number)
12729
- });
12730
- /**
12731
- * How the recorded row ended, which is the supervisor's three outcomes plus the
12732
- * one only the server can state: the review closed with no report at all.
12733
- *
12734
- * `unreported` is never accepted from a tool call. The server writes it when a
12735
- * supervisor turn ends over an evidence read that nothing answered, so the
12736
- * absence survives outside a live client that could infer it from two rows.
12737
- */
12738
- const ThreadEvidenceActivityOutcome = Schema$1.Literals([
12739
- "summarized",
12740
- "fallback",
12741
- "failed",
12742
- "unreported"
12743
- ]);
12744
- /**
12745
- * Server clock around one review, never the supervisor's own account of it.
12746
- *
12747
- * `readAt` is when the evidence-read row was recorded and `closedAt` when the
12748
- * row that closes the review was: the report for a reported review, the
12749
- * completed supervisor turn for an unreported one. A supervisor turn that
12750
- * failed or was interrupted closes nothing, so its read keeps no timing until
12751
- * some later turn reports on it.
12752
- */
12753
- const ThreadEvidenceReportTiming = Schema$1.Struct({
12754
- readAt: IsoDateTime,
12755
- closedAt: IsoDateTime,
12756
- durationMs: NonNegativeInt
12757
- });
12758
- /** Payload of the single `fusion.evidence.summary` activity a report records. */
12759
- const ThreadEvidenceReportActivity = Schema$1.Struct({
12760
- watchedThreadId: ThreadId,
12761
- firstSequence: NonNegativeInt,
12762
- lastSequence: NonNegativeInt,
12763
- throughSequence: NonNegativeInt,
12764
- outcome: ThreadEvidenceActivityOutcome,
12765
- summary: Schema$1.NullOr(Schema$1.String),
12766
- /** True when a snapshot dropped the text of a superseded report. */
12767
- summaryDropped: Schema$1.optionalKey(Schema$1.Boolean),
12768
- model: Schema$1.NullOr(Schema$1.String),
12769
- reason: Schema$1.NullOr(Schema$1.String),
12770
- /** Null when no context-window row exists yet for the reporting thread. */
12771
- context: Schema$1.NullOr(ThreadEvidenceReportContext),
12772
- /**
12773
- * Null when the evidence-read row behind this review is no longer readable,
12774
- * and absent on rows stored before the server measured a review at all.
12775
- */
12776
- timing: Schema$1.optionalKey(Schema$1.NullOr(ThreadEvidenceReportTiming))
12777
- });
12778
- const ThreadEvidenceReportResult = Schema$1.Struct({
12779
- recorded: Schema$1.Boolean,
12780
- context: Schema$1.NullOr(ThreadEvidenceReportContext)
12781
- });
12782
- var WatchToolUnavailableError = class extends Schema$1.TaggedErrorClass()("WatchToolUnavailableError", {
12783
- capability: Schema$1.Literal("watch"),
12784
- environmentId: EnvironmentId,
12785
- threadId: ThreadId,
12786
- providerSessionId: TrimmedNonEmptyString,
12787
- providerInstanceId: ProviderInstanceId
12788
- }) {
12789
- get message() {
12790
- return `MCP credential does not grant the ${this.capability} capability.`;
12791
- }
12792
- };
12793
- /**
12794
- * The capability is present but the requested thread is not in the granted
12795
- * set. Separate from {@link WatchToolUnavailableError} so a watcher can tell
12796
- * "you cannot watch at all" apart from "not this thread".
12797
- */
12798
- var ThreadWatchNotPermittedError = class extends Schema$1.TaggedErrorClass()("ThreadWatchNotPermittedError", { threadId: ThreadId }) {
12799
- get message() {
12800
- return `This session was not granted watch access to thread ${this.threadId}.`;
12801
- }
12802
- };
12803
- var ThreadWatchFailedError = class extends Schema$1.TaggedErrorClass()("ThreadWatchFailedError", {
12804
- threadId: ThreadId,
12805
- detail: Schema$1.String
12806
- }) {
12807
- get message() {
12808
- return `Reading events for thread ${this.threadId} failed: ${this.detail}`;
12809
- }
12810
- };
12811
- const ThreadWatchToolError = Schema$1.Union([
12812
- WatchToolUnavailableError,
12813
- ThreadWatchNotPermittedError,
12814
- ThreadWatchFailedError
12815
- ]);
12816
13009
  //#endregion
12817
13010
  //#region ../../packages/contracts/src/threadControl.ts
12818
13011
  /**
@@ -14412,10 +14605,19 @@ const UserInputRankingInput = Schema$1.Struct({
14412
14605
  requestId: ApprovalRequestId,
14413
14606
  questionId: Schema$1.String
14414
14607
  });
14415
- const UserInputRankingResult = Schema$1.Struct({ ranks: Schema$1.Array(Schema$1.Struct({
14416
- optionIndex: Schema$1.Int,
14417
- rank: Schema$1.Int
14418
- })) });
14608
+ /**
14609
+ * `probability` is the option's share of the ranker's distribution and
14610
+ * `confidence` how concentrated that distribution is. Both come from the
14611
+ * TypeSafe answer; neither is invented when the answer carries none.
14612
+ */
14613
+ const UserInputRankingResult = Schema$1.Struct({
14614
+ confidence: Schema$1.Number,
14615
+ ranks: Schema$1.Array(Schema$1.Struct({
14616
+ optionIndex: Schema$1.Int,
14617
+ rank: Schema$1.Int,
14618
+ probability: Schema$1.Number
14619
+ }))
14620
+ });
14419
14621
  var UserInputRankingError = class extends Schema$1.TaggedErrorClass()("UserInputRankingError", { detail: Schema$1.String }) {};
14420
14622
  //#endregion
14421
14623
  //#region ../../packages/contracts/src/providerSetup.ts
@@ -14928,6 +15130,7 @@ const WS_METHODS = {
14928
15130
  hubSetSyncMode: "hub.setSyncMode",
14929
15131
  hubSetShareMode: "hub.setShareMode",
14930
15132
  hubMintToken: "hub.mintToken",
15133
+ hubAnalyticsQuery: "hub.analyticsQuery",
14931
15134
  settingsSyncNow: "settingsSync.now",
14932
15135
  storageCleanupPreview: "storageCleanup.preview",
14933
15136
  storageCleanupSweep: "storageCleanup.sweep",
@@ -15141,6 +15344,19 @@ const WsWorktreeCleanupSweepRpc = Rpc.make(WS_METHODS.worktreeCleanupSweep, {
15141
15344
  success: WorktreeCleanupPreview,
15142
15345
  error: Schema$1.Union([EnvironmentAuthorizationError])
15143
15346
  });
15347
+ /**
15348
+ * The account's own analytics facts, read through this server.
15349
+ *
15350
+ * The hub credential belongs to the environment, not to a browser tab, so the
15351
+ * client never holds it: it asks its own server, which is already the only
15352
+ * thing that can authenticate against the hub. Read-only, and the query is the
15353
+ * hub's own contract, so a filter the hub rejects is rejected here too.
15354
+ */
15355
+ const WsHubAnalyticsQueryRpc = Rpc.make(WS_METHODS.hubAnalyticsQuery, {
15356
+ payload: HubAnalyticsQuery,
15357
+ success: HubAnalyticsPage,
15358
+ error: HubRpcError
15359
+ });
15144
15360
  const WsHubGetSyncStatusRpc = Rpc.make(WS_METHODS.hubGetSyncStatus, {
15145
15361
  payload: Schema$1.Struct({}),
15146
15362
  success: AgentSyncStatus,
@@ -16092,7 +16308,7 @@ const WsSubscribeAuthAccessRpc = Rpc.make(WS_METHODS.subscribeAuthAccess, {
16092
16308
  error: Schema$1.Union([AuthAccessStreamError, EnvironmentAuthorizationError]),
16093
16309
  stream: true
16094
16310
  });
16095
- const WsRpcGroup = RpcGroup.make(WsDeviceGetStateRpc, WsDeviceListRpc, WsDeviceConfigureRpc, WsDeviceOpenRpc, WsDeviceCloseRpc, WsDeviceDetailRpc, WsDeviceActionRpc, WsSubscribeDevicesRpc, WsServerProbeRpc, WsServerGetConfigRpc, WsServerRefreshProvidersRpc, WsServerProviderAuthStartRpc, WsServerProviderAuthCompleteRpc, WsServerProviderAuthCancelRpc, WsServerProviderAuthLogoutRpc, WsServerProviderAuthSubscribeRpc, WsServerProviderInstallStartRpc, WsServerProviderInstallCancelRpc, WsServerProviderInstallRemoveRpc, WsServerProviderInstallSubscribeRpc, WsServerUpdateProviderRpc, WsServerUpdateServerRpc, WsServerUpsertKeybindingRpc, WsServerRemoveKeybindingRpc, WsServerGetSettingsRpc, WsServerGetFusionDefaultPromptsRpc, WsServerUpdateSettingsRpc, WsServerDiscoverSourceControlRpc, WsServerGetProviderUsageRpc, WsServerGetHostResourcesRpc, WsServerGetResourceTelemetryRpc, WsServerGetResourceTelemetryHistoryRpc, WsServerRetryResourceTelemetryRpc, WsSubscribeResourceTelemetryRpc, WsThreadHandoffRpc, WsUserInputRankRpc, WsBtwAskRpc, WsBtwCancelRpc, WsServerGetUsageSummaryRpc, WsServerGetTraceDiagnosticsRpc, WsServerGetProcessDiagnosticsRpc, WsServerGetProcessResourceHistoryRpc, WsServerSignalProcessRpc, WsPullRequestsListRpc, WsPullRequestsListStatsRpc, WsPullRequestsDetailRpc, WsPullRequestsStackRpc, WsPullRequestsActivityRpc, WsPullRequestsDiffFileContentsRpc, WsPullRequestsRunActionRpc, WsPullRequestsUpdateRpc, WsPullRequestsCommentRpc, WsPullRequestsUpdateCommentRpc, WsPullRequestsSubmitReviewRpc, WsPullRequestsReplyToThreadRpc, WsPullRequestsSetThreadResolutionRpc, WsPullRequestsSetReactionRpc, WsPullRequestsInvalidateRpc, WsPullRequestsReviewerCandidatesRpc, WsPullRequestsRequestReviewersRpc, WsSourceControlLookupRepositoryRpc, WsSourceControlCloneRepositoryRpc, WsSourceControlPublishRepositoryRpc, WsProjectsListEntriesRpc, WsProjectsReadFileRpc, WsProjectsSearchContentsRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsShellOpenInEditorRpc, WsFilesystemBrowseRpc, WsAssetsCreateUrlRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsVcsListRefsRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsReviewGetDiffPreviewRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsPreviewOpenRpc, WsPreviewNavigateRpc, WsPreviewResizeRpc, WsPreviewRefreshRpc, WsPreviewCloseRpc, WsPreviewListRpc, WsPreviewReportStatusRpc, WsComputerUseConnectRpc, WsComputerUseListRpc, WsComputerUseControlsRpc, WsComputerUseControlRpc, WsComputerUsePreviewRpc, WsComputerUseRespondRpc, WsPreviewAutomationConnectRpc, WsPreviewAutomationRespondRpc, WsPreviewAutomationFocusHostRpc, WsSubscribePreviewEventsRpc, WsSubscribeDiscoveredLocalServersRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsTasksListRpc, WsTasksGetRpc, WsTasksCreateRpc, WsTasksUpdateRpc, WsTasksDeleteRpc, WsTasksStartThreadRpc, WsFeedListRpc, WsFeedRefreshRpc, WsFeedSourcesListRpc, WsFeedSourcesUpsertRpc, WsFeedSourcesDeleteRpc, WsFeedMarkReadRpc, WsFeedCleanupRpc, WsSubscribeTasksRpc, WsStorageCleanupPreviewRpc, WsStorageCleanupSweepRpc, WsWorktreeCleanupPreviewRpc, WsWorktreeCleanupSweepRpc, WsHubGetSyncStatusRpc, WsHubConnectRpc, WsHubDisconnectRpc, WsHubSetSyncModeRpc, WsHubSetShareModeRpc, WsHubMintTokenRpc, WsSettingsSyncNowRpc, WsSkillsSyncRpc, WsSkillsPublishRpc, WsSkillsPublishAllRpc, WsSkillsUnpublishRpc, WsAssetsReadRpc, WsAssetsSaveRpc, WsAssetsDeleteRpc, WsAssetsCreateLocalRpc, WsAssetsRemoveLocalRpc, WsSkillRegistrySearchRpc, WsSkillRegistryFetchRpc, WsMcpListRpc, WsMcpSaveRpc, WsMcpRemoveRpc, WsMcpSetSecretRpc, WsMcpOAuthBeginRpc, WsMcpOAuthDisconnectRpc, WsTrackerStatusRpc, WsTrackerSetApiKeyRpc, WsSupervisorSecretStatusRpc, WsSupervisorSetApiKeyRpc, WsSupervisorClearApiKeyRpc, WsTrackerClearApiKeyRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetWorkflowScriptRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationSearchThreadsRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
16311
+ const WsRpcGroup = RpcGroup.make(WsDeviceGetStateRpc, WsDeviceListRpc, WsDeviceConfigureRpc, WsDeviceOpenRpc, WsDeviceCloseRpc, WsDeviceDetailRpc, WsDeviceActionRpc, WsSubscribeDevicesRpc, WsServerProbeRpc, WsServerGetConfigRpc, WsServerRefreshProvidersRpc, WsServerProviderAuthStartRpc, WsServerProviderAuthCompleteRpc, WsServerProviderAuthCancelRpc, WsServerProviderAuthLogoutRpc, WsServerProviderAuthSubscribeRpc, WsServerProviderInstallStartRpc, WsServerProviderInstallCancelRpc, WsServerProviderInstallRemoveRpc, WsServerProviderInstallSubscribeRpc, WsServerUpdateProviderRpc, WsServerUpdateServerRpc, WsServerUpsertKeybindingRpc, WsServerRemoveKeybindingRpc, WsServerGetSettingsRpc, WsServerGetFusionDefaultPromptsRpc, WsServerUpdateSettingsRpc, WsServerDiscoverSourceControlRpc, WsServerGetProviderUsageRpc, WsServerGetHostResourcesRpc, WsServerGetResourceTelemetryRpc, WsServerGetResourceTelemetryHistoryRpc, WsServerRetryResourceTelemetryRpc, WsSubscribeResourceTelemetryRpc, WsThreadHandoffRpc, WsUserInputRankRpc, WsBtwAskRpc, WsBtwCancelRpc, WsServerGetUsageSummaryRpc, WsServerGetTraceDiagnosticsRpc, WsServerGetProcessDiagnosticsRpc, WsServerGetProcessResourceHistoryRpc, WsServerSignalProcessRpc, WsPullRequestsListRpc, WsPullRequestsListStatsRpc, WsPullRequestsDetailRpc, WsPullRequestsStackRpc, WsPullRequestsActivityRpc, WsPullRequestsDiffFileContentsRpc, WsPullRequestsRunActionRpc, WsPullRequestsUpdateRpc, WsPullRequestsCommentRpc, WsPullRequestsUpdateCommentRpc, WsPullRequestsSubmitReviewRpc, WsPullRequestsReplyToThreadRpc, WsPullRequestsSetThreadResolutionRpc, WsPullRequestsSetReactionRpc, WsPullRequestsInvalidateRpc, WsPullRequestsReviewerCandidatesRpc, WsPullRequestsRequestReviewersRpc, WsSourceControlLookupRepositoryRpc, WsSourceControlCloneRepositoryRpc, WsSourceControlPublishRepositoryRpc, WsProjectsListEntriesRpc, WsProjectsReadFileRpc, WsProjectsSearchContentsRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsShellOpenInEditorRpc, WsFilesystemBrowseRpc, WsAssetsCreateUrlRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsVcsListRefsRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsReviewGetDiffPreviewRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsPreviewOpenRpc, WsPreviewNavigateRpc, WsPreviewResizeRpc, WsPreviewRefreshRpc, WsPreviewCloseRpc, WsPreviewListRpc, WsPreviewReportStatusRpc, WsComputerUseConnectRpc, WsComputerUseListRpc, WsComputerUseControlsRpc, WsComputerUseControlRpc, WsComputerUsePreviewRpc, WsComputerUseRespondRpc, WsPreviewAutomationConnectRpc, WsPreviewAutomationRespondRpc, WsPreviewAutomationFocusHostRpc, WsSubscribePreviewEventsRpc, WsSubscribeDiscoveredLocalServersRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsTasksListRpc, WsTasksGetRpc, WsTasksCreateRpc, WsTasksUpdateRpc, WsTasksDeleteRpc, WsTasksStartThreadRpc, WsFeedListRpc, WsFeedRefreshRpc, WsFeedSourcesListRpc, WsFeedSourcesUpsertRpc, WsFeedSourcesDeleteRpc, WsFeedMarkReadRpc, WsFeedCleanupRpc, WsSubscribeTasksRpc, WsStorageCleanupPreviewRpc, WsStorageCleanupSweepRpc, WsWorktreeCleanupPreviewRpc, WsWorktreeCleanupSweepRpc, WsHubAnalyticsQueryRpc, WsHubGetSyncStatusRpc, WsHubConnectRpc, WsHubDisconnectRpc, WsHubSetSyncModeRpc, WsHubSetShareModeRpc, WsHubMintTokenRpc, WsSettingsSyncNowRpc, WsSkillsSyncRpc, WsSkillsPublishRpc, WsSkillsPublishAllRpc, WsSkillsUnpublishRpc, WsAssetsReadRpc, WsAssetsSaveRpc, WsAssetsDeleteRpc, WsAssetsCreateLocalRpc, WsAssetsRemoveLocalRpc, WsSkillRegistrySearchRpc, WsSkillRegistryFetchRpc, WsMcpListRpc, WsMcpSaveRpc, WsMcpRemoveRpc, WsMcpSetSecretRpc, WsMcpOAuthBeginRpc, WsMcpOAuthDisconnectRpc, WsTrackerStatusRpc, WsTrackerSetApiKeyRpc, WsSupervisorSecretStatusRpc, WsSupervisorSetApiKeyRpc, WsSupervisorClearApiKeyRpc, WsTrackerClearApiKeyRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetWorkflowScriptRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationSearchThreadsRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
16096
16312
  //#endregion
16097
16313
  //#region ../../packages/shared/src/oauthScope.ts
16098
16314
  const OAUTH_SCOPE_TOKEN = /^[\u0021\u0023-\u005b\u005d-\u007e]+$/u;
@@ -16256,7 +16472,7 @@ const make$120 = Effect.gen(function* () {
16256
16472
  };
16257
16473
  return EnvironmentAuthPolicy.of({ getDescriptor: () => Effect.succeed(descriptor).pipe(Effect.withSpan("EnvironmentAuthPolicy.getDescriptor")) });
16258
16474
  });
16259
- const layer$120 = Layer.effect(EnvironmentAuthPolicy, make$120);
16475
+ const layer$121 = Layer.effect(EnvironmentAuthPolicy, make$120);
16260
16476
  //#endregion
16261
16477
  //#region src/persistence/Errors.ts
16262
16478
  function summarizeSchemaIssue(issue) {
@@ -16571,7 +16787,7 @@ const make$119 = Effect.gen(function* () {
16571
16787
  setLastConnectedAt
16572
16788
  };
16573
16789
  });
16574
- const layer$119 = Layer.effect(AuthSessionRepository, make$119);
16790
+ const layer$120 = Layer.effect(AuthSessionRepository, make$119);
16575
16791
  //#endregion
16576
16792
  //#region src/auth/ServerSecretStore.ts
16577
16793
  const secretStoreErrorContext = {
@@ -16717,7 +16933,7 @@ const make$118 = Effect.gen(function* () {
16717
16933
  const config = yield* ServerConfig$1;
16718
16934
  return yield* makeForDirectory(config.secretsDir);
16719
16935
  });
16720
- const layer$118 = Layer.effect(ServerSecretStore, make$118);
16936
+ const layer$119 = Layer.effect(ServerSecretStore, make$118);
16721
16937
  //#endregion
16722
16938
  //#region src/auth/SessionStore.ts
16723
16939
  var MalformedSessionTokenError = class extends Schema$1.TaggedErrorClass()("MalformedSessionTokenError", {}) {
@@ -17270,7 +17486,7 @@ const make$117 = Effect.gen(function* () {
17270
17486
  markDisconnected
17271
17487
  });
17272
17488
  });
17273
- const layer$117 = Layer.effect(SessionStore, make$117).pipe(Layer.provideMerge(layer$119));
17489
+ const layer$118 = Layer.effect(SessionStore, make$117).pipe(Layer.provideMerge(layer$120));
17274
17490
  //#endregion
17275
17491
  //#region src/persistence/AuthPairingLinks.ts
17276
17492
  const AuthPairingLinkRecord = Schema$1.Struct({
@@ -17466,7 +17682,7 @@ const make$116 = Effect.gen(function* () {
17466
17682
  getByCredential
17467
17683
  };
17468
17684
  });
17469
- const layer$116 = Layer.effect(AuthPairingLinkRepository, make$116);
17685
+ const layer$117 = Layer.effect(AuthPairingLinkRepository, make$116);
17470
17686
  //#endregion
17471
17687
  //#region src/auth/PairingGrantStore.ts
17472
17688
  var UnknownBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("UnknownBootstrapCredentialError", {}) {
@@ -17759,7 +17975,7 @@ const make$115 = Effect.gen(function* () {
17759
17975
  consume
17760
17976
  });
17761
17977
  });
17762
- const layer$115 = Layer.effect(PairingGrantStore, make$115).pipe(Layer.provideMerge(layer$116));
17978
+ const layer$116 = Layer.effect(PairingGrantStore, make$115).pipe(Layer.provideMerge(layer$117));
17763
17979
  //#endregion
17764
17980
  //#region src/persistence/DatabaseSnapshot.ts
17765
17981
  /**
@@ -17901,6 +18117,21 @@ var _060_ThreadPairTriageCursor_default = Effect.gen(function* () {
17901
18117
  yield* (yield* SqlClient.SqlClient)`ALTER TABLE thread_pairs ADD COLUMN triaged_from_sequence INTEGER`;
17902
18118
  });
17903
18119
  //#endregion
18120
+ //#region src/persistence/Migrations/061_AnalyticsFactOutbox.ts
18121
+ var _061_AnalyticsFactOutbox_default = Effect.gen(function* () {
18122
+ const sql = yield* SqlClient.SqlClient;
18123
+ yield* sql`CREATE TABLE analytics_fact_outbox (
18124
+ event_id TEXT PRIMARY KEY, kind TEXT NOT NULL, version INTEGER NOT NULL,
18125
+ occurred_at TEXT NOT NULL, payload TEXT NOT NULL,
18126
+ pending INTEGER NOT NULL DEFAULT 1, created_at INTEGER NOT NULL
18127
+ )`;
18128
+ yield* sql`CREATE INDEX analytics_fact_outbox_pending
18129
+ ON analytics_fact_outbox(pending, created_at, event_id)`;
18130
+ yield* sql`CREATE TABLE analytics_fact_cursors (
18131
+ source TEXT PRIMARY KEY, after_sequence INTEGER NOT NULL
18132
+ )`;
18133
+ });
18134
+ //#endregion
17904
18135
  //#region src/persistence/Migrations/001_OrchestrationEvents.ts
17905
18136
  var _001_OrchestrationEvents_default = Effect.gen(function* () {
17906
18137
  const sql = yield* SqlClient.SqlClient;
@@ -20082,6 +20313,11 @@ const migrationEntries = [
20082
20313
  60,
20083
20314
  "ThreadPairTriageCursor",
20084
20315
  _060_ThreadPairTriageCursor_default
20316
+ ],
20317
+ [
20318
+ 61,
20319
+ "AnalyticsFactOutbox",
20320
+ _061_AnalyticsFactOutbox_default
20085
20321
  ]
20086
20322
  ];
20087
20323
  const makeMigrationLoader = (throughId) => Migrator.fromRecord(Object.fromEntries(migrationEntries.filter(([id]) => throughId === void 0 || id <= throughId).map(([id, name, migration]) => [`${id}_${name}`, migration])));
@@ -20429,22 +20665,22 @@ function classifyToolCategory(input) {
20429
20665
  if (normalized.includes("image")) return "image_view";
20430
20666
  return "tool";
20431
20667
  }
20432
- function asRecord$10(value) {
20668
+ function asRecord$12(value) {
20433
20669
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
20434
20670
  }
20435
20671
  /** Classify from a runtime item payload's `data` (`{ toolName, input }`). */
20436
20672
  function classifyToolCategoryFromToolData(data) {
20437
- const record = asRecord$10(data);
20673
+ const record = asRecord$12(data);
20438
20674
  const toolName = record?.toolName;
20439
20675
  if (typeof toolName !== "string" || toolName.trim().length === 0) return;
20440
20676
  return classifyToolCategory({
20441
20677
  toolName,
20442
- toolInput: asRecord$10(record?.input)
20678
+ toolInput: asRecord$12(record?.input)
20443
20679
  });
20444
20680
  }
20445
20681
  //#endregion
20446
20682
  //#region src/orchestration/ActivityPayloadProjection.ts
20447
- function asRecord$9(value) {
20683
+ function asRecord$11(value) {
20448
20684
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
20449
20685
  }
20450
20686
  function asTrimmedString$2(value) {
@@ -20474,7 +20710,7 @@ function compactMcpRecord(record) {
20474
20710
  const entries = Object.entries(record);
20475
20711
  const projected = {};
20476
20712
  for (const [key, value] of entries.slice(0, MCP_TOOL_ACTIVITY_FIELD_LIMIT)) {
20477
- const nestedItem = key === "item" ? asRecord$9(value) : null;
20713
+ const nestedItem = key === "item" ? asRecord$11(value) : null;
20478
20714
  projected[key] = nestedItem === null ? compactMcpField(value) : compactMcpRecord(nestedItem);
20479
20715
  }
20480
20716
  if (entries.length > MCP_TOOL_ACTIVITY_FIELD_LIMIT) projected.truncatedFields = entries.length - MCP_TOOL_ACTIVITY_FIELD_LIMIT;
@@ -20500,7 +20736,7 @@ function collectChangedFiles(value, target, seen, depth) {
20500
20736
  }
20501
20737
  return;
20502
20738
  }
20503
- const record = asRecord$9(value);
20739
+ const record = asRecord$11(value);
20504
20740
  if (!record) return;
20505
20741
  pushChangedFile(target, seen, record.path);
20506
20742
  pushChangedFile(target, seen, record.filePath);
@@ -20526,13 +20762,13 @@ function collectChangedFiles(value, target, seen, depth) {
20526
20762
  }
20527
20763
  }
20528
20764
  function projectCommandData(data) {
20529
- const item = asRecord$9(data.item);
20765
+ const item = asRecord$11(data.item);
20530
20766
  if (!item) return;
20531
20767
  const projectedItem = {};
20532
20768
  if ("command" in item) projectedItem.command = item.command;
20533
- const input = asRecord$9(item.input);
20769
+ const input = asRecord$11(item.input);
20534
20770
  if (input && "command" in input) projectedItem.input = { command: input.command };
20535
- const result = asRecord$9(item.result);
20771
+ const result = asRecord$11(item.result);
20536
20772
  if (result && "command" in result) projectedItem.result = { command: result.command };
20537
20773
  return Object.keys(projectedItem).length > 0 ? projectedItem : void 0;
20538
20774
  }
@@ -20556,7 +20792,7 @@ function summarizeToolTextOutput(value) {
20556
20792
  return meaningfulLineCount > 1 ? `${meaningfulLineCount.toLocaleString()} lines` : null;
20557
20793
  }
20558
20794
  function projectRawOutput(value) {
20559
- const rawOutput = asRecord$9(value);
20795
+ const rawOutput = asRecord$11(value);
20560
20796
  if (!rawOutput) return;
20561
20797
  if (typeof rawOutput.totalFiles === "number" && Number.isFinite(rawOutput.totalFiles)) return {
20562
20798
  totalFiles: rawOutput.totalFiles,
@@ -20579,8 +20815,8 @@ function projectRawOutput(value) {
20579
20815
  * tool outputs do not accumulate twice in the event store and activity projection.
20580
20816
  */
20581
20817
  function projectActivityPayload(activity) {
20582
- const payload = asRecord$9(activity.payload);
20583
- const data = asRecord$9(payload?.data);
20818
+ const payload = asRecord$11(activity.payload);
20819
+ const data = asRecord$11(payload?.data);
20584
20820
  if (!payload || !data) return activity;
20585
20821
  if (payload.itemType === "mcp_tool_call") {
20586
20822
  const projectedMcpData = projectMcpToolCallData(data);
@@ -20597,7 +20833,7 @@ function projectActivityPayload(activity) {
20597
20833
  const item = projectCommandData(data);
20598
20834
  if (item) projectedData.item = item;
20599
20835
  if ("command" in data) projectedData.command = data.command;
20600
- const input = asRecord$9(data.input);
20836
+ const input = asRecord$11(data.input);
20601
20837
  if (input) {
20602
20838
  const projectedInput = {};
20603
20839
  if ("command" in input) projectedInput.command = input.command;
@@ -20637,7 +20873,7 @@ function projectActivityPayload(activity) {
20637
20873
  */
20638
20874
  function isResolvableContextWindowActivity(activity) {
20639
20875
  if (activity.kind !== "context-window.updated") return false;
20640
- const usedTokens = asRecord$9(activity.payload)?.usedTokens;
20876
+ const usedTokens = asRecord$11(activity.payload)?.usedTokens;
20641
20877
  return typeof usedTokens === "number" && Number.isFinite(usedTokens) && usedTokens >= 0;
20642
20878
  }
20643
20879
  /**
@@ -20653,7 +20889,7 @@ function isResolvableContextWindowActivity(activity) {
20653
20889
  * client.
20654
20890
  */
20655
20891
  function withoutContextWindowBreakdown$1(activity) {
20656
- const payload = asRecord$9(activity.payload);
20892
+ const payload = asRecord$11(activity.payload);
20657
20893
  if (!payload || payload.breakdown === void 0) return activity;
20658
20894
  const { breakdown: _breakdown, ...rest } = payload;
20659
20895
  return {
@@ -20668,7 +20904,7 @@ function dropStaleContextWindowActivities(activities) {
20668
20904
  const retainedIndexes = new Set(latestIndexByTurn.values());
20669
20905
  let breakdownIndex = null;
20670
20906
  for (const index of retainedIndexes) {
20671
- if (asRecord$9(activities[index].payload)?.breakdown === void 0) continue;
20907
+ if (asRecord$11(activities[index].payload)?.breakdown === void 0) continue;
20672
20908
  if (breakdownIndex === null || index > breakdownIndex) breakdownIndex = index;
20673
20909
  }
20674
20910
  return activities.flatMap((activity, index) => {
@@ -20678,12 +20914,12 @@ function dropStaleContextWindowActivities(activities) {
20678
20914
  });
20679
20915
  }
20680
20916
  function toolLifecycleIdentity(activity) {
20681
- return asTrimmedString$2(asRecord$9(asRecord$9(activity.payload)?.data)?.toolCallId);
20917
+ return asTrimmedString$2(asRecord$11(asRecord$11(activity.payload)?.data)?.toolCallId);
20682
20918
  }
20683
20919
  /** Clients retain fields missing from a completion when folding an update. */
20684
20920
  function completionPreservesUpdate(update, completion) {
20685
- const updatePayload = asRecord$9(update.payload);
20686
- const completionPayload = asRecord$9(completion.payload);
20921
+ const updatePayload = asRecord$11(update.payload);
20922
+ const completionPayload = asRecord$11(completion.payload);
20687
20923
  if (!updatePayload || !completionPayload) return false;
20688
20924
  return Object.entries(updatePayload).every(([key, value]) => key === "status" || NodeUtil.isDeepStrictEqual(value, completionPayload[key]));
20689
20925
  }
@@ -20722,13 +20958,13 @@ function dropSupersededEvidenceSummaries(activities) {
20722
20958
  for (let index = 0; index < activities.length; index += 1) {
20723
20959
  const activity = activities[index];
20724
20960
  if (activity.kind !== "fusion.evidence.summary") continue;
20725
- const watchedThreadId = asRecord$9(activity.payload)?.watchedThreadId;
20961
+ const watchedThreadId = asRecord$11(activity.payload)?.watchedThreadId;
20726
20962
  if (typeof watchedThreadId === "string") latestIndexByWatched.set(watchedThreadId, index);
20727
20963
  }
20728
20964
  if (latestIndexByWatched.size === 0) return activities;
20729
20965
  return activities.map((activity, index) => {
20730
20966
  if (activity.kind !== "fusion.evidence.summary") return activity;
20731
- const payload = asRecord$9(activity.payload);
20967
+ const payload = asRecord$11(activity.payload);
20732
20968
  const watchedThreadId = payload?.watchedThreadId;
20733
20969
  if (!payload || typeof watchedThreadId !== "string" || latestIndexByWatched.get(watchedThreadId) === index || payload.summary === null || payload.summary === void 0) return activity;
20734
20970
  return {
@@ -21339,9 +21575,9 @@ const make$114 = Effect.gen(function* () {
21339
21575
  issueStartupPairingUrl
21340
21576
  });
21341
21577
  });
21342
- const layer$114 = Layer.effect(EnvironmentAuth, make$114).pipe(Layer.provideMerge(layer$115), Layer.provideMerge(layer$117), Layer.provideMerge(layer$120));
21343
- const storageLayer = Layer.mergeAll(layer$118, layerConfig);
21344
- const runtimeLayer = layer$114.pipe(Layer.provideMerge(storageLayer));
21578
+ const layer$115 = Layer.effect(EnvironmentAuth, make$114).pipe(Layer.provideMerge(layer$116), Layer.provideMerge(layer$118), Layer.provideMerge(layer$121));
21579
+ const storageLayer = Layer.mergeAll(layer$119, layerConfig);
21580
+ const runtimeLayer = layer$115.pipe(Layer.provideMerge(storageLayer));
21345
21581
  //#endregion
21346
21582
  //#region ../../packages/shared/src/Struct.ts
21347
21583
  function deepMerge(current, patch) {
@@ -23189,7 +23425,7 @@ const runWithEnvironmentAuth = (flags, run, options) => Effect.gen(function* ()
23189
23425
  }
23190
23426
  return yield* Effect.gen(function* () {
23191
23427
  return yield* run(yield* EnvironmentAuth);
23192
- }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer).pipe(Layer.provide(layer$121(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
23428
+ }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer).pipe(Layer.provide(layer$122(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
23193
23429
  });
23194
23430
  const ttlFlag = Flag.string("ttl").pipe(Flag.withSchema(DurationFromString), Flag.withDescription("TTL, for example `5m`, `1h`, `30d`, or `15 minutes`."), Flag.optional);
23195
23431
  const jsonFlag = Flag.boolean("json").pipe(Flag.withDescription("Emit JSON instead of human-readable output."), Flag.withDefault(false));
@@ -23349,7 +23585,7 @@ const make$113 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_L
23349
23585
  })
23350
23586
  });
23351
23587
  });
23352
- const layer$113 = Layer.effect(HubAuthThrottle, make$113());
23588
+ const layer$114 = Layer.effect(HubAuthThrottle, make$113());
23353
23589
  //#endregion
23354
23590
  //#region src/hub/HubAuth.ts
23355
23591
  /**
@@ -24122,6 +24358,88 @@ const makeTaskRepository = Effect.gen(function* () {
24122
24358
  });
24123
24359
  const TaskRepositoryLive = Layer.effect(TaskRepository, makeTaskRepository);
24124
24360
  //#endregion
24361
+ //#region src/hub/HubClientCors.ts
24362
+ const MAX_ORIGIN_CHARS = 2048;
24363
+ const DESKTOP_RENDERER_ORIGINS$1 = /* @__PURE__ */ new Set(["p4code://app", "p4code-dev://app"]);
24364
+ const MAX_PREFLIGHT_HEADER_CHARS = 128;
24365
+ const ALLOWED_HEADERS = /* @__PURE__ */ new Set([
24366
+ "authorization",
24367
+ "content-type",
24368
+ "accept"
24369
+ ]);
24370
+ const CLIENT_ROUTES = {
24371
+ "/v1/client-enrollments/exchange": ["POST"],
24372
+ "/v1/clients/me": ["GET", "DELETE"],
24373
+ "/v1/clients/me/events": ["GET"],
24374
+ "/v1/clients/me/socket-ticket": ["POST"],
24375
+ "/v1/clients/me/asset-grant": ["POST"],
24376
+ "/v1/clients/me/notifications": [
24377
+ "GET",
24378
+ "PUT",
24379
+ "DELETE"
24380
+ ],
24381
+ "/v1/clients/me/notifications/preferences": ["PUT"],
24382
+ "/v1/clients/me/live-activities": ["PUT", "DELETE"]
24383
+ };
24384
+ const routeMethods = (path) => Object.hasOwn(CLIENT_ROUTES, path) ? CLIENT_ROUTES[path] : void 0;
24385
+ const denied$2 = () => HttpServerResponse.jsonUnsafe({ error: "invalid_client_origin" }, {
24386
+ status: 403,
24387
+ headers: {
24388
+ "cache-control": "no-store",
24389
+ vary: "Origin"
24390
+ }
24391
+ });
24392
+ /** Arbitrary self-hosted browser clients are allowed; authentication uses explicit credentials, never cookies. */
24393
+ function hubClientCorsOrigin(value) {
24394
+ if (DESKTOP_RENDERER_ORIGINS$1.has(value)) return value;
24395
+ if (value.length > MAX_ORIGIN_CHARS) return void 0;
24396
+ try {
24397
+ const url = new URL(value);
24398
+ if (url.protocol !== "https:" && url.protocol !== "http:" || value !== url.origin || url.username || url.password) return void 0;
24399
+ return url.origin;
24400
+ } catch {
24401
+ return;
24402
+ }
24403
+ }
24404
+ /** Wrap only client-authenticated routes, after their generic failure mapping. */
24405
+ const withHubClientCors = (operation) => Effect.gen(function* () {
24406
+ const request = yield* HttpServerRequest.HttpServerRequest;
24407
+ const path = request.url.split("?")[0];
24408
+ if (!routeMethods(path)?.includes(request.method)) return denied$2();
24409
+ const raw = request.headers.origin;
24410
+ if (raw === void 0) return yield* operation;
24411
+ const origin = hubClientCorsOrigin(raw);
24412
+ if (!origin) return denied$2();
24413
+ const response = yield* operation;
24414
+ return response.pipe(HttpServerResponse.setHeaders({
24415
+ "access-control-allow-origin": origin,
24416
+ "access-control-expose-headers": "retry-after",
24417
+ "cache-control": "no-store",
24418
+ vary: response.headers.vary ? `${response.headers.vary}, Origin` : "Origin"
24419
+ }));
24420
+ });
24421
+ const hubClientPreflight = Effect.gen(function* () {
24422
+ const request = yield* HttpServerRequest.HttpServerRequest;
24423
+ const origin = hubClientCorsOrigin(request.headers.origin ?? "");
24424
+ const methods = routeMethods(request.url);
24425
+ const method = request.headers["access-control-request-method"];
24426
+ const raw = request.headers["access-control-request-headers"] ?? "";
24427
+ if (raw.length > MAX_PREFLIGHT_HEADER_CHARS) return denied$2();
24428
+ const headers = raw === "" ? [] : raw.split(",").map((value) => value.trim().toLowerCase());
24429
+ if (!origin || !methods || !method || !methods.includes(method) || raw.length > MAX_PREFLIGHT_HEADER_CHARS || headers.length > ALLOWED_HEADERS.size || new Set(headers).size !== headers.length || headers.some((value) => !ALLOWED_HEADERS.has(value))) return denied$2();
24430
+ return HttpServerResponse.empty({
24431
+ status: 204,
24432
+ headers: {
24433
+ "access-control-allow-origin": origin,
24434
+ "access-control-allow-methods": methods.join(", "),
24435
+ "access-control-allow-headers": "authorization, content-type, accept",
24436
+ "cache-control": "no-store",
24437
+ vary: "Origin, Access-Control-Request-Method, Access-Control-Request-Headers"
24438
+ }
24439
+ });
24440
+ });
24441
+ const layer$113 = Layer.mergeAll(Layer.empty, ...Object.keys(CLIENT_ROUTES).map((path) => HttpRouter.add("OPTIONS", path, hubClientPreflight)));
24442
+ //#endregion
24125
24443
  //#region src/hub/HubClientRevocations.ts
24126
24444
  var HubClientRevocations = class extends Context.Service()("@p4code/cli/hub/HubClientRevocations") {};
24127
24445
  /** Coalesced invalidations carry no identity or credential; consumers recheck durable state. */
@@ -24282,88 +24600,6 @@ const signOutHubClient = Effect.fn("HubClients.signOut")(function* (credential)
24282
24600
  WHERE id = ${principal.clientId} AND credential_hash = ${hashHubToken(credential)}`;
24283
24601
  })).pipe(Effect.ensuring(notifications.changed));
24284
24602
  });
24285
- //#endregion
24286
- //#region src/hub/HubClientCors.ts
24287
- const MAX_ORIGIN_CHARS = 2048;
24288
- const DESKTOP_RENDERER_ORIGINS$1 = /* @__PURE__ */ new Set(["p4code://app", "p4code-dev://app"]);
24289
- const MAX_PREFLIGHT_HEADER_CHARS = 128;
24290
- const ALLOWED_HEADERS = /* @__PURE__ */ new Set([
24291
- "authorization",
24292
- "content-type",
24293
- "accept"
24294
- ]);
24295
- const CLIENT_ROUTES = {
24296
- "/v1/client-enrollments/exchange": ["POST"],
24297
- "/v1/clients/me": ["GET", "DELETE"],
24298
- "/v1/clients/me/events": ["GET"],
24299
- "/v1/clients/me/socket-ticket": ["POST"],
24300
- "/v1/clients/me/asset-grant": ["POST"],
24301
- "/v1/clients/me/notifications": [
24302
- "GET",
24303
- "PUT",
24304
- "DELETE"
24305
- ],
24306
- "/v1/clients/me/notifications/preferences": ["PUT"],
24307
- "/v1/clients/me/live-activities": ["PUT", "DELETE"]
24308
- };
24309
- const routeMethods = (path) => Object.hasOwn(CLIENT_ROUTES, path) ? CLIENT_ROUTES[path] : void 0;
24310
- const denied$2 = () => HttpServerResponse.jsonUnsafe({ error: "invalid_client_origin" }, {
24311
- status: 403,
24312
- headers: {
24313
- "cache-control": "no-store",
24314
- vary: "Origin"
24315
- }
24316
- });
24317
- /** Arbitrary self-hosted browser clients are allowed; authentication uses explicit credentials, never cookies. */
24318
- function hubClientCorsOrigin(value) {
24319
- if (DESKTOP_RENDERER_ORIGINS$1.has(value)) return value;
24320
- if (value.length > MAX_ORIGIN_CHARS) return void 0;
24321
- try {
24322
- const url = new URL(value);
24323
- if (url.protocol !== "https:" && url.protocol !== "http:" || value !== url.origin || url.username || url.password) return void 0;
24324
- return url.origin;
24325
- } catch {
24326
- return;
24327
- }
24328
- }
24329
- /** Wrap only client-authenticated routes, after their generic failure mapping. */
24330
- const withHubClientCors = (operation) => Effect.gen(function* () {
24331
- const request = yield* HttpServerRequest.HttpServerRequest;
24332
- const path = request.url.split("?")[0];
24333
- if (!routeMethods(path)?.includes(request.method)) return denied$2();
24334
- const raw = request.headers.origin;
24335
- if (raw === void 0) return yield* operation;
24336
- const origin = hubClientCorsOrigin(raw);
24337
- if (!origin) return denied$2();
24338
- const response = yield* operation;
24339
- return response.pipe(HttpServerResponse.setHeaders({
24340
- "access-control-allow-origin": origin,
24341
- "access-control-expose-headers": "retry-after",
24342
- "cache-control": "no-store",
24343
- vary: response.headers.vary ? `${response.headers.vary}, Origin` : "Origin"
24344
- }));
24345
- });
24346
- const hubClientPreflight = Effect.gen(function* () {
24347
- const request = yield* HttpServerRequest.HttpServerRequest;
24348
- const origin = hubClientCorsOrigin(request.headers.origin ?? "");
24349
- const methods = routeMethods(request.url);
24350
- const method = request.headers["access-control-request-method"];
24351
- const raw = request.headers["access-control-request-headers"] ?? "";
24352
- if (raw.length > MAX_PREFLIGHT_HEADER_CHARS) return denied$2();
24353
- const headers = raw === "" ? [] : raw.split(",").map((value) => value.trim().toLowerCase());
24354
- if (!origin || !methods || !method || !methods.includes(method) || raw.length > MAX_PREFLIGHT_HEADER_CHARS || headers.length > ALLOWED_HEADERS.size || new Set(headers).size !== headers.length || headers.some((value) => !ALLOWED_HEADERS.has(value))) return denied$2();
24355
- return HttpServerResponse.empty({
24356
- status: 204,
24357
- headers: {
24358
- "access-control-allow-origin": origin,
24359
- "access-control-allow-methods": methods.join(", "),
24360
- "access-control-allow-headers": "authorization, content-type, accept",
24361
- "cache-control": "no-store",
24362
- vary: "Origin, Access-Control-Request-Method, Access-Control-Request-Headers"
24363
- }
24364
- });
24365
- });
24366
- const layer$111 = Layer.mergeAll(Layer.empty, ...Object.keys(CLIENT_ROUTES).map((path) => HttpRouter.add("OPTIONS", path, hubClientPreflight)));
24367
24603
  const HUB_TUNNEL_MAX_FRAME_PAYLOAD_BYTES = 64 * 1024;
24368
24604
  const HUB_TUNNEL_MAX_MESSAGE_BYTES = 128 * 1024 * 1024;
24369
24605
  const HUB_TUNNEL_FRAME_MAGIC = 1345606705;
@@ -24650,7 +24886,7 @@ var HubTunnelConnectorBudget = class {
24650
24886
  const HUB_TUNNEL_RETIRE_TIMEOUT_MS = 1e4;
24651
24887
  var HubTunnelUnavailable = class extends Data.TaggedError("HubTunnelUnavailable") {};
24652
24888
  var HubTunnelRegistry = class extends Context.Service()("@p4code/cli/hub/HubTunnelRegistry") {};
24653
- const layer$110 = Layer.effect(HubTunnelRegistry, Effect.gen(function* () {
24889
+ const layer$111 = Layer.effect(HubTunnelRegistry, Effect.gen(function* () {
24654
24890
  const config = yield* HubConfig;
24655
24891
  const lock = yield* Semaphore.make(1);
24656
24892
  const entries = /* @__PURE__ */ new Map();
@@ -24903,7 +25139,7 @@ const makeHubClientSocketTickets = Effect.gen(function* () {
24903
25139
  };
24904
25140
  });
24905
25141
  var HubClientSocketTickets = class extends Context.Service()("@p4code/cli/hub/HubClientSocketTickets") {};
24906
- const layer$109 = Layer.effect(HubClientSocketTickets, makeHubClientSocketTickets);
25142
+ const layer$110 = Layer.effect(HubClientSocketTickets, makeHubClientSocketTickets);
24907
25143
  //#endregion
24908
25144
  //#region src/hub/HubClientLease.ts
24909
25145
  /** Other Hub processes sharing the database are observed within this interval. */
@@ -24992,7 +25228,7 @@ const readBody$1 = Effect.fn("HubClientRoutes.readBody")(function* (schema, maxi
24992
25228
  }) : request.stream.pipe(Stream.mapError(() => new InvalidClientRequest()))).pipe(Stream.runFoldEffect(() => Buffer.alloc(0), (buffer, chunk) => buffer.byteLength + chunk.byteLength > maximumBytes ? Effect.fail(new InvalidClientRequest()) : Effect.succeed(Buffer.concat([buffer, chunk]))));
24993
25229
  return yield* Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(schema))(body.toString("utf8"), { onExcessProperty: "error" }).pipe(Effect.mapError(() => new InvalidClientRequest()));
24994
25230
  });
24995
- const json$1 = (body) => HttpServerResponse.jsonUnsafe(body, { headers: { "cache-control": "no-store" } });
25231
+ const json$2 = (body) => HttpServerResponse.jsonUnsafe(body, { headers: { "cache-control": "no-store" } });
24996
25232
  const handle$1 = (operation, accountingOwner) => operation.pipe(Effect.timeout(HUB_CLIENT_REQUEST_TIMEOUT_MS), Effect.catch((error) => Effect.gen(function* () {
24997
25233
  if (error instanceof HubThrottledError) return hubThrottledResponse(error.retryAfterSeconds);
24998
25234
  if (error instanceof HubUnauthorizedError) {
@@ -25013,15 +25249,15 @@ const handle$1 = (operation, accountingOwner) => operation.pipe(Effect.timeout(H
25013
25249
  const issue$1 = handle$1(Effect.gen(function* () {
25014
25250
  const principal = yield* authenticateHubServerMembership();
25015
25251
  const input = yield* readBody$1(HubClientEnrollmentInput);
25016
- return json$1(yield* issueHubClientEnrollment(principal, input));
25252
+ return json$2(yield* issueHubClientEnrollment(principal, input));
25017
25253
  }), "server-auth");
25018
25254
  const exchange$1 = handle$1(Effect.gen(function* () {
25019
25255
  const input = yield* readBody$1(HubClientEnrollmentExchange);
25020
- return json$1(yield* consumeHubClientEnrollment(input.secret));
25256
+ return json$2(yield* consumeHubClientEnrollment(input.secret));
25021
25257
  }), "client-route");
25022
25258
  const identity$2 = handle$1(Effect.gen(function* () {
25023
25259
  const request = yield* HttpServerRequest.HttpServerRequest;
25024
- return json$1(yield* authenticateHubClient(readBearerToken(request.headers.authorization)));
25260
+ return json$2(yield* authenticateHubClient(readBearerToken(request.headers.authorization)));
25025
25261
  }), "client-route");
25026
25262
  const events = handle$1(Effect.gen(function* () {
25027
25263
  return yield* hubClientEvents(readBearerToken((yield* HttpServerRequest.HttpServerRequest).headers.authorization));
@@ -25046,7 +25282,7 @@ const socketTicket = handle$1(Effect.gen(function* () {
25046
25282
  yield* authenticateHubClient(credential);
25047
25283
  const input = yield* readBody$1(HubClientSocketTicketInput, MAX_SOCKET_TICKET_REQUEST_BYTES);
25048
25284
  const tickets = yield* HubClientSocketTickets;
25049
- return json$1(yield* tickets.issue(credential, {
25285
+ return json$2(yield* tickets.issue(credential, {
25050
25286
  method: "GET",
25051
25287
  url: `${HUB_TUNNEL_CLIENT_SOCKET_PATH}${input.path}`,
25052
25288
  headers: {
@@ -25059,7 +25295,143 @@ const socketTicket = handle$1(Effect.gen(function* () {
25059
25295
  }
25060
25296
  }));
25061
25297
  }), "client-route");
25062
- const layer$108 = Layer.mergeAll(layer$111, HttpRouter.add("POST", "/v1/clients/me/socket-ticket", withHubClientCors(socketTicket)), HttpRouter.add("POST", "/v1/client-enrollments", issue$1), HttpRouter.add("POST", "/v1/client-enrollments/exchange", withHubClientCors(exchange$1)), HttpRouter.add("GET", "/v1/clients/me", withHubClientCors(identity$2)), HttpRouter.add("GET", "/v1/clients/me/events", withHubClientCors(events)), HttpRouter.add("DELETE", "/v1/clients/me", withHubClientCors(signOut)), HttpRouter.add("POST", "/v1/clients/revoke", revoke));
25298
+ const layer$109 = Layer.mergeAll(layer$113, HttpRouter.add("POST", "/v1/clients/me/socket-ticket", withHubClientCors(socketTicket)), HttpRouter.add("POST", "/v1/client-enrollments", issue$1), HttpRouter.add("POST", "/v1/client-enrollments/exchange", withHubClientCors(exchange$1)), HttpRouter.add("GET", "/v1/clients/me", withHubClientCors(identity$2)), HttpRouter.add("GET", "/v1/clients/me/events", withHubClientCors(events)), HttpRouter.add("DELETE", "/v1/clients/me", withHubClientCors(signOut)), HttpRouter.add("POST", "/v1/clients/revoke", revoke));
25299
+ //#endregion
25300
+ //#region src/hub/HubAnalytics.ts
25301
+ /**
25302
+ * Account-scoped analytics facts: append and read.
25303
+ *
25304
+ * Every row belongs to the authenticated account, and every read is filtered
25305
+ * by it, so one account's facts can never appear in another's page. Appends
25306
+ * are idempotent on the caller's own event id rather than deduplicated by
25307
+ * content: a retry after a dropped response must not double-count, and a fact
25308
+ * is never rewritten once stored.
25309
+ *
25310
+ * @module HubAnalytics
25311
+ */
25312
+ const CURSOR_SEPARATOR = "|";
25313
+ const decodeFact = Schema$1.decodeUnknownEffect(HubAnalyticsFactInput);
25314
+ const decodeQuery = Schema$1.decodeUnknownEffect(HubAnalyticsQuery);
25315
+ const encodePayload$1 = Schema$1.encodeEffect(Schema$1.fromJsonString(Schema$1.Unknown));
25316
+ const decodePayload$1 = Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(Schema$1.Unknown));
25317
+ /** The page position of a row, in the exact order the index reads. */
25318
+ const cursorOf = (row) => `${row.occurred_at}${CURSOR_SEPARATOR}${row.event_id}`;
25319
+ const parseCursor = (cursor) => {
25320
+ const separator = cursor.indexOf(CURSOR_SEPARATOR);
25321
+ if (separator <= 0) return null;
25322
+ const occurredAt = Number.parseInt(cursor.slice(0, separator), 10);
25323
+ const eventId = cursor.slice(separator + 1);
25324
+ return Number.isFinite(occurredAt) && eventId.length > 0 ? {
25325
+ occurredAt,
25326
+ eventId
25327
+ } : null;
25328
+ };
25329
+ /**
25330
+ * Append one fact for the authenticated account.
25331
+ *
25332
+ * The principal is rechecked inside the transaction, the way every other hub
25333
+ * write does it: a credential revoked while the body was being read must not
25334
+ * still land a row.
25335
+ */
25336
+ const appendHubAnalyticsFact = Effect.fn("HubAnalytics.append")(function* (principal, input) {
25337
+ const config = yield* HubConfig;
25338
+ if (!currentPrincipal(config, principal)) return yield* Effect.fail(new HubUnauthorizedError());
25339
+ const fact = yield* decodeFact(input, { onExcessProperty: "error" });
25340
+ const payload = yield* encodePayload$1(fact.payload);
25341
+ const occurredAt = Date.parse(fact.occurredAt);
25342
+ const sql = yield* SqlClient.SqlClient;
25343
+ const now = yield* Clock.currentTimeMillis;
25344
+ return yield* sql.withTransaction(Effect.gen(function* () {
25345
+ if (!currentPrincipal(config, principal)) return yield* Effect.fail(new HubUnauthorizedError());
25346
+ if ((yield* sql`SELECT event_id FROM hub_analytics_facts
25347
+ WHERE account_id = ${principal.accountId} AND event_id = ${fact.eventId}`).length > 0) return { accepted: false };
25348
+ yield* sql`INSERT INTO hub_analytics_facts
25349
+ (account_id, environment_id, event_id, kind, version, occurred_at, payload, created_at)
25350
+ VALUES (${principal.accountId}, ${principal.environmentId}, ${fact.eventId}, ${fact.kind},
25351
+ ${fact.version}, ${occurredAt}, ${payload}, ${now})
25352
+ ON CONFLICT(account_id, event_id) DO NOTHING`;
25353
+ return { accepted: true };
25354
+ }));
25355
+ });
25356
+ /**
25357
+ * Read this account's facts, newest first.
25358
+ *
25359
+ * Paged by keyset rather than offset, so a fact appended between two pages
25360
+ * cannot shift a row into being read twice or skipped. The limit is bounded by
25361
+ * the contract, and one extra row is read to decide whether a next page
25362
+ * exists without a second count query.
25363
+ */
25364
+ const queryHubAnalyticsFacts = Effect.fn("HubAnalytics.query")(function* (principal, input) {
25365
+ if (!currentPrincipal(yield* HubConfig, principal)) return yield* Effect.fail(new HubUnauthorizedError());
25366
+ const query = yield* decodeQuery(input, { onExcessProperty: "error" });
25367
+ const limit = query.limit ?? HUB_ANALYTICS_QUERY_LIMITS.defaultLimit;
25368
+ const kinds = query.kinds === void 0 ? null : `,${query.kinds.join(",")},`;
25369
+ const since = query.since === void 0 ? null : Date.parse(query.since);
25370
+ const until = query.until === void 0 ? null : Date.parse(query.until);
25371
+ const cursor = query.cursor === void 0 ? null : parseCursor(query.cursor);
25372
+ const cursorOccurredAt = cursor === null ? null : cursor.occurredAt;
25373
+ const cursorEventId = cursor === null ? null : cursor.eventId;
25374
+ const rows = yield* (yield* SqlClient.SqlClient)`
25375
+ SELECT environment_id, event_id, kind, version, occurred_at, payload
25376
+ FROM hub_analytics_facts
25377
+ WHERE account_id = ${principal.accountId}
25378
+ AND (${kinds} IS NULL OR instr(${kinds}, ',' || kind || ',') > 0)
25379
+ AND (${since} IS NULL OR occurred_at >= ${since})
25380
+ AND (${until} IS NULL OR occurred_at <= ${until})
25381
+ AND (
25382
+ ${cursorOccurredAt} IS NULL
25383
+ OR occurred_at < ${cursorOccurredAt}
25384
+ OR (occurred_at = ${cursorOccurredAt} AND event_id < ${cursorEventId})
25385
+ )
25386
+ ORDER BY occurred_at DESC, event_id DESC
25387
+ LIMIT ${limit + 1}`;
25388
+ const page = rows.slice(0, limit);
25389
+ const facts = [];
25390
+ for (const row of page) facts.push({
25391
+ eventId: row.event_id,
25392
+ environmentId: row.environment_id,
25393
+ kind: row.kind,
25394
+ version: row.version,
25395
+ occurredAt: new Date(row.occurred_at).toISOString(),
25396
+ payload: yield* decodePayload$1(row.payload)
25397
+ });
25398
+ const last = page[page.length - 1];
25399
+ return {
25400
+ facts,
25401
+ nextCursor: rows.length > limit && last !== void 0 ? cursorOf(last) : null
25402
+ };
25403
+ });
25404
+ //#endregion
25405
+ //#region src/hub/HubAnalyticsRoutes.ts
25406
+ /**
25407
+ * The account's own analytics endpoints.
25408
+ *
25409
+ * Both routes require the same server membership every other environment-owned
25410
+ * hub write uses, and neither forwards anything anywhere: the hub is the only
25411
+ * sink for these facts.
25412
+ *
25413
+ * @module HubAnalyticsRoutes
25414
+ */
25415
+ const MAX_ANALYTICS_BODY_BYTES = 16 * 1024;
25416
+ const json$1 = (value) => HttpServerResponse.jsonUnsafe(value, { headers: { "cache-control": "no-store" } });
25417
+ /** Only the filters the query contract knows; anything else is a decode failure. */
25418
+ const queryFromUrl = (url) => Object.fromEntries([
25419
+ ["kinds", url.searchParams.getAll("kind")],
25420
+ ["since", url.searchParams.get("since")],
25421
+ ["until", url.searchParams.get("until")],
25422
+ ["limit", url.searchParams.get("limit")],
25423
+ ["cursor", url.searchParams.get("cursor")]
25424
+ ].filter(([, value]) => Array.isArray(value) ? value.length > 0 : value !== null).map(([key, value]) => key === "limit" && typeof value === "string" ? [key, Number.parseInt(value, 10)] : [key, value]));
25425
+ const layer$108 = Layer.mergeAll(HttpRouter.add("POST", "/v1/analytics/facts", handle$1(Effect.gen(function* () {
25426
+ const principal = yield* authenticateHubServerMembership();
25427
+ const input = yield* readBody$1(HubAnalyticsFactInput, MAX_ANALYTICS_BODY_BYTES);
25428
+ return json$1(yield* appendHubAnalyticsFact(principal, input));
25429
+ }), "server-auth")), HttpRouter.add("GET", "/v1/analytics/facts", handle$1(Effect.gen(function* () {
25430
+ const principal = yield* authenticateHubServerMembership();
25431
+ const request = yield* HttpServerRequest.HttpServerRequest;
25432
+ const url = new URL(request.url, "http://hub.invalid");
25433
+ return json$1(yield* queryHubAnalyticsFacts(principal, queryFromUrl(url)));
25434
+ }), "server-auth")));
25063
25435
  //#endregion
25064
25436
  //#region src/hub/HubPushTokenProtection.ts
25065
25437
  const KEY_NAME = "hub-push-token-encryption-v1";
@@ -26812,7 +27184,7 @@ const layer$100 = Layer.effect(HubTunnelPublished, Effect.gen(function* () {
26812
27184
  }));
26813
27185
  //#endregion
26814
27186
  //#region src/hub/HubTunnelCors.ts
26815
- const MAX_HEADER_CHARS = 256;
27187
+ const MAX_HEADER_CHARS$1 = 256;
26816
27188
  const METHODS = [
26817
27189
  "GET",
26818
27190
  "HEAD",
@@ -26842,7 +27214,7 @@ function hubTunnelCors(method, headers) {
26842
27214
  };
26843
27215
  if (method === "OPTIONS" && headers["access-control-request-method"] !== void 0) {
26844
27216
  const raw = headers["access-control-request-headers"] ?? "";
26845
- if (raw.length > MAX_HEADER_CHARS) return null;
27217
+ if (raw.length > MAX_HEADER_CHARS$1) return null;
26846
27218
  const requested = raw ? raw.split(",").map((name) => name.trim().toLowerCase()) : [];
26847
27219
  if (!METHODS.includes(headers["access-control-request-method"]) || requested.length > HEADERS.length || new Set(requested).size !== requested.length || requested.some((name) => !HEADERS.includes(name))) return null;
26848
27220
  result["access-control-allow-methods"] = METHODS.join(", ");
@@ -28597,8 +28969,8 @@ const releaseFeedLeaseRoute = HttpRouter.add("DELETE", "/feed/lease", respondToH
28597
28969
  return HttpServerResponse.empty({ status: 204 });
28598
28970
  })));
28599
28971
  /** Provide around HttpRouter.serve so request fibers share these process-owned services. */
28600
- const routingServices = Layer.mergeAll(layer$112, layer$109, layer$101, layer$100).pipe(Layer.provideMerge(layer$110));
28601
- const layer$94 = Layer.mergeAll(layer$99, layer$98, layer$102, layer$96, layer$95, layer$108, layer$97, healthRoute, identityRoute, listTasksRoute, getTaskRoute, createTaskRoute, putTaskRoute, updateTaskRoute, deleteTaskRoute, listAssetsRoute, getAssetRoute, putAssetRoute, deleteAssetRoute, listFeedSourcesRoute, upsertFeedSourceRoute, deleteFeedSourceRoute, listFeedRoute, putFeedArticleRoute, putFeedEngagementRoute, markFeedReadRoute, cleanupFeedRoute, setFeedRefreshedAtRoute, acquireFeedLeaseRoute, renewFeedLeaseRoute, releaseFeedLeaseRoute).pipe(Layer.provideMerge(layer$112), Layer.provideMerge(layer$109), Layer.provideMerge(layer$101));
28972
+ const routingServices = Layer.mergeAll(layer$112, layer$110, layer$101, layer$100).pipe(Layer.provideMerge(layer$111));
28973
+ const layer$94 = Layer.mergeAll(layer$99, layer$98, layer$102, layer$96, layer$95, layer$109, layer$97, healthRoute, identityRoute, listTasksRoute, getTaskRoute, createTaskRoute, putTaskRoute, updateTaskRoute, deleteTaskRoute, listAssetsRoute, getAssetRoute, putAssetRoute, deleteAssetRoute, listFeedSourcesRoute, upsertFeedSourceRoute, deleteFeedSourceRoute, listFeedRoute, putFeedArticleRoute, putFeedEngagementRoute, markFeedReadRoute, cleanupFeedRoute, setFeedRefreshedAtRoute, acquireFeedLeaseRoute, renewFeedLeaseRoute, releaseFeedLeaseRoute).pipe(Layer.provideMerge(layer$112), Layer.provideMerge(layer$110), Layer.provideMerge(layer$101));
28602
28974
  //#endregion
28603
28975
  //#region src/hub/Migrations/001_Tasks.ts
28604
28976
  /**
@@ -29080,6 +29452,21 @@ var _021_NotificationDeliveryProgress_default = Effect.gen(function* () {
29080
29452
  yield* (yield* SqlClient.SqlClient)`ALTER TABLE hub_notification_deliveries ADD COLUMN app_delivered INTEGER NOT NULL DEFAULT 0`;
29081
29453
  });
29082
29454
  //#endregion
29455
+ //#region src/hub/Migrations/022_AnalyticsFacts.ts
29456
+ var _022_AnalyticsFacts_default = Effect.gen(function* () {
29457
+ const sql = yield* SqlClient.SqlClient;
29458
+ yield* sql`CREATE TABLE hub_analytics_facts (
29459
+ account_id TEXT NOT NULL, environment_id TEXT NOT NULL, event_id TEXT NOT NULL,
29460
+ kind TEXT NOT NULL, version INTEGER NOT NULL, occurred_at INTEGER NOT NULL,
29461
+ payload TEXT NOT NULL, created_at INTEGER NOT NULL,
29462
+ PRIMARY KEY(account_id, event_id)
29463
+ )`;
29464
+ yield* sql`CREATE INDEX hub_analytics_facts_recent
29465
+ ON hub_analytics_facts(account_id, occurred_at DESC, event_id DESC)`;
29466
+ yield* sql`CREATE INDEX hub_analytics_facts_kind
29467
+ ON hub_analytics_facts(account_id, kind, occurred_at DESC, event_id DESC)`;
29468
+ });
29469
+ //#endregion
29083
29470
  //#region src/hub/Migrations.ts
29084
29471
  /**
29085
29472
  * Hub migrations.
@@ -29195,6 +29582,11 @@ const hubMigrationEntries = [
29195
29582
  21,
29196
29583
  "NotificationDeliveryProgress",
29197
29584
  _021_NotificationDeliveryProgress_default
29585
+ ],
29586
+ [
29587
+ 22,
29588
+ "AnalyticsFacts",
29589
+ _022_AnalyticsFacts_default
29198
29590
  ]
29199
29591
  ];
29200
29592
  const hubMigrationLoader = Migrator.fromRecord(Object.fromEntries(hubMigrationEntries.map(([id, name, migration]) => [`${id}_${name}`, migration])));
@@ -29286,7 +29678,7 @@ const announce = Layer.effectDiscard(Effect.gen(function* () {
29286
29678
  });
29287
29679
  if (isRemoteReachableHost(config.host)) yield* Effect.logWarning("Hub is reachable beyond loopback. The bearer token is the only control — repeated auth failures are not rate limited.", { host: config.host });
29288
29680
  }));
29289
- const makeHubLayer = HttpRouter.serve(Layer.mergeAll(layer$94, layer$106)).pipe(Layer.provideMerge(routingServices), Layer.provideMerge(layer$104), Layer.provideMerge(layer$105), Layer.provideMerge(layer$103), Layer.provideMerge(announce), Layer.provideMerge(HubHttpServerLive), Layer.provideMerge(layer$113), Layer.provideMerge(TaskRepositoryLive), Layer.provideMerge(AgentAssetRepositoryLive), Layer.provideMerge(HubPersistenceLive));
29681
+ const makeHubLayer = HttpRouter.serve(Layer.mergeAll(layer$94, layer$106, layer$108)).pipe(Layer.provideMerge(routingServices), Layer.provideMerge(layer$104), Layer.provideMerge(layer$105), Layer.provideMerge(layer$103), Layer.provideMerge(announce), Layer.provideMerge(HubHttpServerLive), Layer.provideMerge(layer$114), Layer.provideMerge(TaskRepositoryLive), Layer.provideMerge(AgentAssetRepositoryLive), Layer.provideMerge(HubPersistenceLive));
29290
29682
  const runHub = Layer.launch(makeHubLayer);
29291
29683
  //#endregion
29292
29684
  //#region src/atomicWrite.ts
@@ -30944,6 +31336,16 @@ const decodeRegistration$1 = Schema$1.decodeUnknownExit(RegistrationFromJson$1);
30944
31336
  const encodeRegistration = Schema$1.encodeSync(RegistrationFromJson$1);
30945
31337
  var McpRegistry = class extends Context.Service()("@p4code/cli/mcp/McpRegistry") {};
30946
31338
  const slotsOf = (registration) => registration.secrets ?? [];
31339
+ /**
31340
+ * What a session is told when a server is left out for want of a credential.
31341
+ *
31342
+ * The name and where the row came from, and nothing else: a token, a url query
31343
+ * or a header would end up in a log file that outlives the session. The action
31344
+ * names the one place a person can fix it, because "absent" on its own reads as
31345
+ * a bug rather than as a sign-in that has not happened.
31346
+ */
31347
+ const OMITTED_FOR_SIGN_IN = "mcp server omitted: sign-in required";
31348
+ const SIGN_IN_ACTION = "Connect it in Settings > MCP servers.";
30947
31349
  const make$108 = Effect.gen(function* () {
30948
31350
  const config = yield* ServerConfig$1;
30949
31351
  const secrets = yield* ServerSecretStore;
@@ -31060,6 +31462,14 @@ const make$108 = Effect.gen(function* () {
31060
31462
  if (registration.transport !== "stdio" && !hasExplicitAuthorization) {
31061
31463
  const token = yield* oauth.accessTokenFor(registration);
31062
31464
  if (Option.isSome(token)) headers["Authorization"] = `Bearer ${token.value}`;
31465
+ else if (Option.isSome(yield* oauth.statusFor(registration))) {
31466
+ yield* Effect.logWarning(OMITTED_FOR_SIGN_IN, {
31467
+ server: registration.name,
31468
+ source: "registration",
31469
+ action: SIGN_IN_ACTION
31470
+ });
31471
+ continue;
31472
+ }
31063
31473
  }
31064
31474
  resolved[registration.name] = registration.transport === "stdio" ? {
31065
31475
  type: "stdio",
@@ -31095,7 +31505,15 @@ const make$108 = Effect.gen(function* () {
31095
31505
  if (!Object.keys(headers).some((key) => key.toLowerCase() === "authorization")) {
31096
31506
  const token = yield* oauth.accessTokenFor(userScope);
31097
31507
  if (Option.isSome(token)) headers["Authorization"] = `Bearer ${token.value}`;
31098
- else if (Option.isSome(yield* oauth.statusFor(userScope))) continue;
31508
+ else if (Option.isSome(yield* oauth.statusFor(userScope))) {
31509
+ yield* Effect.logWarning(OMITTED_FOR_SIGN_IN, {
31510
+ server: userScope.name,
31511
+ source: "user-scope",
31512
+ sessionName,
31513
+ action: SIGN_IN_ACTION
31514
+ });
31515
+ continue;
31516
+ }
31099
31517
  }
31100
31518
  resolved[sessionName] = {
31101
31519
  type: userScope.transport,
@@ -33858,7 +34276,7 @@ const findAgentPreset = (requested) => {
33858
34276
  };
33859
34277
  //#endregion
33860
34278
  //#region src/cli/assets.ts
33861
- const SyncServicesLive = layer$87.pipe(Layer.provideMerge(layer$88), Layer.provideMerge(layer$89), Layer.provideMerge(layer$93.pipe(Layer.provide(layer$118))), Layer.provideMerge(layer$118), Layer.provideMerge(FetchHttpClient.layer));
34279
+ const SyncServicesLive = layer$87.pipe(Layer.provideMerge(layer$88), Layer.provideMerge(layer$89), Layer.provideMerge(layer$93.pipe(Layer.provide(layer$119))), Layer.provideMerge(layer$119), Layer.provideMerge(FetchHttpClient.layer));
33862
34280
  const runWithSync = (flags, run) => Effect.gen(function* () {
33863
34281
  const config = yield* resolveCliAuthConfig(flags, yield* GlobalFlag.LogLevel);
33864
34282
  return yield* Effect.gen(function* () {
@@ -33866,7 +34284,7 @@ const runWithSync = (flags, run) => Effect.gen(function* () {
33866
34284
  sync: yield* AssetSync,
33867
34285
  link: yield* HubLink
33868
34286
  });
33869
- }).pipe(Effect.provide(SyncServicesLive.pipe(Layer.provide(layer$121(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, "Error")))));
34287
+ }).pipe(Effect.provide(SyncServicesLive.pipe(Layer.provide(layer$122(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, "Error")))));
33870
34288
  });
33871
34289
  const STATE_LABEL = {
33872
34290
  synced: "synced",
@@ -36240,7 +36658,11 @@ function projectEvent(model, event) {
36240
36658
  case "thread.activity-appended": return decodeForEvent(ThreadActivityAppendedPayload, event.payload, event.type, "payload").pipe(Effect.map((payload) => {
36241
36659
  const thread = nextBase.threads.find((entry) => entry.id === payload.threadId);
36242
36660
  if (!thread) return nextBase;
36243
- const activities = [...thread.activities.filter((entry) => entry.id !== payload.activity.id), payload.activity].toSorted(compareThreadActivities).slice(-500);
36661
+ const appended = payload.activity.sequence === void 0 ? {
36662
+ ...payload.activity,
36663
+ sequence: event.sequence
36664
+ } : payload.activity;
36665
+ const activities = [...thread.activities.filter((entry) => entry.id !== appended.id), appended].toSorted(compareThreadActivities).slice(-500);
36244
36666
  return {
36245
36667
  ...nextBase,
36246
36668
  threads: updateThread(nextBase.threads, payload.threadId, {
@@ -40666,7 +41088,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
40666
41088
  kind: activity.kind,
40667
41089
  summary: activity.summary,
40668
41090
  payload: activity.payload,
40669
- ...activity.sequence !== void 0 ? { sequence: activity.sequence } : {},
41091
+ sequence: activity.sequence ?? event.sequence,
40670
41092
  createdAt: activity.createdAt
40671
41093
  });
40672
41094
  return;
@@ -47399,7 +47821,7 @@ const startDeviceHub = Effect.fn("DeviceHubProcess.start")(function* (input) {
47399
47821
  });
47400
47822
  //#endregion
47401
47823
  //#region src/device/AgentDeviceProcessLive.ts
47402
- const decodeJson$2 = Schema$1.decodeUnknownSync(Schema$1.UnknownFromJsonString);
47824
+ const decodeJson$3 = Schema$1.decodeUnknownSync(Schema$1.UnknownFromJsonString);
47403
47825
  var AgentDeviceProcessLiveFailure = class extends Data.TaggedError("AgentDeviceProcessLiveFailure") {};
47404
47826
  const READY_TIMEOUT_MS = 3e4;
47405
47827
  const MAX_READY_LINE = 256;
@@ -47511,7 +47933,7 @@ const make$93 = Effect.fn("AgentDeviceProcess.make")(function* () {
47511
47933
  maxBytes: MAX_RESPONSE_BYTES$7
47512
47934
  });
47513
47935
  if (response.status !== 200 || body.truncated || body.invalidUtf8) return yield* new AgentDeviceProcessLiveFailure({ message: "Agent authorization failed" });
47514
- const result = yield* Effect.try(() => decodeJson$2(body.text));
47936
+ const result = yield* Effect.try(() => decodeJson$3(body.text));
47515
47937
  if (!result || typeof result !== "object" || !("id" in result) || result.id !== "p4-readiness" || !("result" in result) || !result.result || typeof result.result !== "object" || !("ok" in result.result) || result.result.ok !== true) return yield* new AgentDeviceProcessLiveFailure({ message: "Agent readiness rejected" });
47516
47938
  endpoint = {
47517
47939
  baseUrl,
@@ -49511,7 +49933,7 @@ main().catch(() => { if (!stopping) emit({ phase: 'failed', message: 'Remote dev
49511
49933
  }
49512
49934
  //#endregion
49513
49935
  //#region src/device/SshDeviceConnection.ts
49514
- const decodeJson$1 = Schema$1.decodeUnknownSync(Schema$1.UnknownFromJsonString);
49936
+ const decodeJson$2 = Schema$1.decodeUnknownSync(Schema$1.UnknownFromJsonString);
49515
49937
  const START_TIMEOUT_MS = 66e4;
49516
49938
  const FORWARD_TIMEOUT_MS = 15e3;
49517
49939
  const VERIFY_TIMEOUT_MS = 3e4;
@@ -49666,7 +50088,7 @@ const connectSshDevice = Effect.fn("SshDeviceConnection.connect")(function* (inp
49666
50088
  maxBytes: MAX_RESPONSE_BYTES$4
49667
50089
  });
49668
50090
  if (response.status !== 200 || body.truncated || body.invalidUtf8) return yield* fail("verifying forwarded agent")(void 0);
49669
- const result = yield* Effect.try(() => decodeJson$1(body.text));
50091
+ const result = yield* Effect.try(() => decodeJson$2(body.text));
49670
50092
  if (!result || typeof result !== "object" || !("id" in result) || result.id !== identity.nonce || !("result" in result) || !result.result || typeof result.result !== "object" || !("ok" in result.result) || result.result.ok !== true) return yield* fail("authenticating forwarded agent")(void 0);
49671
50093
  }).pipe(Effect.scoped, Effect.timeout(Duration.millis(VERIFY_TIMEOUT_MS)), Effect.mapError(fail("verifying forwarded agent")));
49672
50094
  return {
@@ -51095,7 +51517,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
51095
51517
  });
51096
51518
  yield* Console.log(output);
51097
51519
  }));
51098
- const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(Layer.provide(layer$121(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)));
51520
+ const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(Layer.provide(layer$122(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)));
51099
51521
  return yield* Effect.gen(function* () {
51100
51522
  const snapshot = yield* getOfflineSnapshot();
51101
51523
  const orchestrationEngine = yield* OrchestrationEngineService;
@@ -51106,7 +51528,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
51106
51528
  });
51107
51529
  yield* Console.log(output);
51108
51530
  }).pipe(Effect.provide(offlineRuntimeLayer));
51109
- }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer, layer$70).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provide(layer$121(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
51531
+ }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer, layer$70).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provide(layer$122(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
51110
51532
  });
51111
51533
  const projectAddCommand = Command.make("add", {
51112
51534
  ...projectLocationFlags,
@@ -59682,7 +60104,7 @@ const TYPESAFE_REQUEST_BUDGET_TOKENS = 32e3;
59682
60104
  const TYPESAFE_BUDGET_RESERVE_TOKENS = 2e3;
59683
60105
  var TypeSafeRequestError = class extends Data.TaggedError("TypeSafeRequestError") {};
59684
60106
  var TypeSafeClient = class extends Context.Service()("@p4code/cli/orchestration/triage/TypeSafeClient") {};
59685
- const asRecord$8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
60107
+ const asRecord$10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
59686
60108
  /**
59687
60109
  * The finite numbers out of a probability map, or nothing.
59688
60110
  *
@@ -59691,7 +60113,7 @@ const asRecord$8 = (value) => typeof value === "object" && value !== null && !Ar
59691
60113
  * invented number is worse than one built on fewer.
59692
60114
  */
59693
60115
  function readProbabilities(value) {
59694
- const record = asRecord$8(value);
60116
+ const record = asRecord$10(value);
59695
60117
  if (record === void 0) return void 0;
59696
60118
  const probabilities = {};
59697
60119
  for (const [key, entry] of Object.entries(record)) if (typeof entry === "number" && Number.isFinite(entry)) probabilities[key] = entry;
@@ -59705,7 +60127,7 @@ function readProbabilities(value) {
59705
60127
  * can never reach the caller wearing a confidence it did not have.
59706
60128
  */
59707
60129
  function parseTypeSafeAnswer(value) {
59708
- const record = asRecord$8(value);
60130
+ const record = asRecord$10(value);
59709
60131
  if (record === void 0) return void 0;
59710
60132
  const confidence = record["confidence"];
59711
60133
  switch (record["type"]) {
@@ -59746,7 +60168,7 @@ function parseTypeSafeAnswer(value) {
59746
60168
  }
59747
60169
  /** The one answer for the question that was asked, out of the answers map. */
59748
60170
  function readAnswerFor(body, questionId) {
59749
- const answers = asRecord$8(asRecord$8(body)?.["answers"]);
60171
+ const answers = asRecord$10(asRecord$10(body)?.["answers"]);
59750
60172
  return answers === void 0 ? void 0 : parseTypeSafeAnswer(answers[questionId]);
59751
60173
  }
59752
60174
  const makeTypeSafeClient = Effect.gen(function* () {
@@ -59850,19 +60272,19 @@ const estimateTokens$1 = (text) => Math.ceil(text.length / 4);
59850
60272
  /** One line per event is plenty to answer "did anything reviewable happen". */
59851
60273
  const MAX_LINE_CHARS = 400;
59852
60274
  const oneLine = (text) => text.replace(/\s+/gu, " ").trim();
59853
- const clip$1 = (text) => text.length <= MAX_LINE_CHARS ? text : `${text.slice(0, MAX_LINE_CHARS)}…`;
60275
+ const clip$2 = (text) => text.length <= MAX_LINE_CHARS ? text : `${text.slice(0, MAX_LINE_CHARS)}…`;
59854
60276
  /** The line an event contributes, or null when it says nothing about the work. */
59855
60277
  function describeEvent(event, implementerThreadId) {
59856
60278
  switch (event.type) {
59857
60279
  case "thread.message-sent": {
59858
60280
  if (event.payload.threadId !== implementerThreadId) return null;
59859
60281
  const text = oneLine(event.payload.text);
59860
- return text.length === 0 ? null : clip$1(`${event.payload.role}: ${text}`);
60282
+ return text.length === 0 ? null : clip$2(`${event.payload.role}: ${text}`);
59861
60283
  }
59862
60284
  case "thread.activity-appended": {
59863
60285
  if (event.payload.threadId !== implementerThreadId) return null;
59864
60286
  const activity = event.payload.activity;
59865
- return clip$1(oneLine(`${activity.kind}: ${activity.summary}`));
60287
+ return clip$2(oneLine(`${activity.kind}: ${activity.summary}`));
59866
60288
  }
59867
60289
  case "thread.turn-completed":
59868
60290
  if (event.payload.threadId !== implementerThreadId) return null;
@@ -60085,7 +60507,7 @@ function renderPrefetchHint(selected) {
60085
60507
  //#endregion
60086
60508
  //#region src/orchestration/userInputRanking.ts
60087
60509
  const MAX_OPTIONS = 255;
60088
- const MAX_QUESTION_CHARS = 16e3;
60510
+ const MAX_QUESTION_CHARS$1 = 16e3;
60089
60511
  const RANKING_TIMEOUT_MS = 1e4;
60090
60512
  const PROBABILITY_SUM_TOLERANCE = .01;
60091
60513
  /** Require a complete distribution; never invent missing option ranks. Equal scores share rank. */
@@ -60103,13 +60525,17 @@ function ranksFromAnswer(answer, count) {
60103
60525
  })).sort((a, b) => b.probability - a.probability || a.optionIndex - b.optionIndex);
60104
60526
  if (Math.abs(ranked.reduce((sum, item) => sum + item.probability, 0) - 1) > PROBABILITY_SUM_TOLERANCE) return null;
60105
60527
  let rank = 1;
60106
- return { ranks: ranked.map((item, index) => {
60107
- if (index > 0 && item.probability !== ranked[index - 1]?.probability) rank = index + 1;
60108
- return {
60109
- optionIndex: item.optionIndex,
60110
- rank
60111
- };
60112
- }) };
60528
+ return {
60529
+ confidence: answer.confidence,
60530
+ ranks: ranked.map((item, index) => {
60531
+ if (index > 0 && item.probability !== ranked[index - 1]?.probability) rank = index + 1;
60532
+ return {
60533
+ optionIndex: item.optionIndex,
60534
+ rank,
60535
+ probability: item.probability
60536
+ };
60537
+ })
60538
+ };
60113
60539
  }
60114
60540
  const makeUserInputRanker = Effect.gen(function* () {
60115
60541
  const query = yield* ProjectionSnapshotQuery;
@@ -60128,7 +60554,7 @@ const makeUserInputRanker = Effect.gen(function* () {
60128
60554
  const decoded = Schema$1.decodeUnknownOption(Schema$1.Struct({ questions: Schema$1.Array(UserInputQuestion) }))(payload);
60129
60555
  const question = Option.isSome(decoded) ? decoded.value.questions.find((question) => question.id === input.questionId) : void 0;
60130
60556
  if (!question) return yield* fail("This question is no longer pending.");
60131
- if (question.options.length < 2 || question.options.length > MAX_OPTIONS || question.question.length + question.options.reduce((sum, option) => sum + option.label.length + option.description.length, 0) > MAX_QUESTION_CHARS) return yield* fail("This question cannot be ranked.");
60557
+ if (question.options.length < 2 || question.options.length > MAX_OPTIONS || question.question.length + question.options.reduce((sum, option) => sum + option.label.length + option.description.length, 0) > MAX_QUESTION_CHARS$1) return yield* fail("This question cannot be ranked.");
60132
60558
  const context = query.getHandoffContext ? yield* query.getHandoffContext(input.threadId).pipe(Effect.mapError(() => fail("Could not read question context."))) : Option.none();
60133
60559
  if (Option.isNone(context)) return yield* fail("Question context is unavailable.");
60134
60560
  const state = context.value.transcript;
@@ -60818,7 +61244,7 @@ const forceStopOwnedProcess = (threadId, options) => threadLocks.withPermit(thre
60818
61244
  }));
60819
61245
  //#endregion
60820
61246
  //#region ../../packages/shared/src/toolActivity.ts
60821
- function asRecord$7(value) {
61247
+ function asRecord$9(value) {
60822
61248
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
60823
61249
  }
60824
61250
  function asTrimmedString$1(value) {
@@ -60848,10 +61274,10 @@ function extractCommandFromTitle$1(title) {
60848
61274
  return /`([^`]+)`/u.exec(title)?.[1]?.trim() || void 0;
60849
61275
  }
60850
61276
  function extractToolCommand(data, title) {
60851
- const item = asRecord$7(data?.item);
60852
- const itemInput = asRecord$7(item?.input);
60853
- const itemResult = asRecord$7(item?.result);
60854
- const rawInput = asRecord$7(data?.rawInput);
61277
+ const item = asRecord$9(data?.item);
61278
+ const itemInput = asRecord$9(item?.input);
61279
+ const itemResult = asRecord$9(item?.result);
61280
+ const rawInput = asRecord$9(data?.rawInput);
60855
61281
  const direct = [
60856
61282
  normalizeCommandValue$1(item?.command),
60857
61283
  normalizeCommandValue$1(itemInput?.command),
@@ -60879,7 +61305,7 @@ function collectPaths(value, paths, seen, depth) {
60879
61305
  }
60880
61306
  return;
60881
61307
  }
60882
- const record = asRecord$7(value);
61308
+ const record = asRecord$9(value);
60883
61309
  if (!record) return;
60884
61310
  for (const key of [
60885
61311
  "path",
@@ -60938,7 +61364,7 @@ function deriveToolActivityPresentation(input) {
60938
61364
  const title = asTrimmedString$1(input.title);
60939
61365
  const detail = stripTrailingExitCode(asTrimmedString$1(input.detail));
60940
61366
  const fallbackSummary = asTrimmedString$1(input.fallbackSummary) ?? "Tool";
60941
- const data = asRecord$7(input.data);
61367
+ const data = asRecord$9(input.data);
60942
61368
  const command = extractToolCommand(data, title);
60943
61369
  const primaryPath = extractPrimaryPath(data);
60944
61370
  const action = classifyToolAction({
@@ -60962,7 +61388,7 @@ function deriveToolActivityPresentation(input) {
60962
61388
  ...primaryPath ? { detail: primaryPath } : {}
60963
61389
  };
60964
61390
  if (action === "search") {
60965
- const query = asTrimmedString$1(asRecord$7(data?.rawInput)?.query) ?? asTrimmedString$1(asRecord$7(data?.rawInput)?.pattern) ?? asTrimmedString$1(asRecord$7(data?.rawInput)?.searchTerm);
61391
+ const query = asTrimmedString$1(asRecord$9(data?.rawInput)?.query) ?? asTrimmedString$1(asRecord$9(data?.rawInput)?.pattern) ?? asTrimmedString$1(asRecord$9(data?.rawInput)?.searchTerm);
60966
61392
  return {
60967
61393
  summary: "Searched files",
60968
61394
  ...query ? { detail: query } : {}
@@ -98870,7 +99296,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
98870
99296
  };
98871
99297
  });
98872
99298
  var OpenCodeRuntime = class extends Context.Service()("@p4code/cli/provider/opencodeRuntime") {};
98873
- const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(Layer.provide(layer$122));
99299
+ const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(Layer.provide(layer$123));
98874
99300
  //#endregion
98875
99301
  //#region src/textGeneration/OpenCodeTextGeneration.ts
98876
99302
  const OPENCODE_TEXT_GENERATION_IDLE_TTL = "30 seconds";
@@ -103776,6 +104202,136 @@ const openForActiveThread = Effect.fn("DeviceThreadAdmission.open")(function* (i
103776
104202
  function isRegisteredProviderWorkspace(cwd, projects, threads) {
103777
104203
  return projects.some((project) => project.workspaceRoot === cwd) || threads.some((thread) => thread.worktreePath === cwd);
103778
104204
  }
104205
+ //#endregion
104206
+ //#region src/hub/HubActivityTransport.ts
104207
+ const REQUEST_TIMEOUT_MS$3 = 8e3;
104208
+ const CLEANUP_TIMEOUT_MS = 1e3;
104209
+ const MAX_RESPONSE_BYTES = 4096;
104210
+ const MAX_REQUEST_BYTES = 16 * 1024;
104211
+ const MAX_BEARER_CHARS = 8192;
104212
+ var HubActivityUnavailable = class extends Data.TaggedError("HubActivityUnavailable") {};
104213
+ var HubActivityUnauthorized = class extends Data.TaggedError("HubActivityUnauthorized") {};
104214
+ /** All errors are payload-free; late responses still cancel their bodies after owner interruption. */
104215
+ const makeHubActivityTransport = Effect.fn("HubActivityTransport.make")(function* (settings, fetcher = fetch) {
104216
+ const origin = yield* Effect.try({
104217
+ try: () => {
104218
+ const url = new URL(settings.baseUrl);
104219
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash || url.pathname !== "/" || settings.token.length === 0 || settings.token.length > MAX_BEARER_CHARS || !/^[\x21-\x7e]+$/.test(settings.token)) throw new HubActivityUnavailable();
104220
+ return url.origin;
104221
+ },
104222
+ catch: () => new HubActivityUnavailable()
104223
+ });
104224
+ const request = Effect.fn("HubActivityTransport.request")(function* (path, body, membership) {
104225
+ return yield* Effect.tryPromise({
104226
+ try: async (signal) => {
104227
+ const controller = new AbortController();
104228
+ const abort = () => controller.abort();
104229
+ signal.addEventListener("abort", abort, { once: true });
104230
+ if (signal.aborted) abort();
104231
+ const timer = setTimeout(abort, REQUEST_TIMEOUT_MS$3);
104232
+ let reader;
104233
+ const cancel = async (work) => {
104234
+ let timeout;
104235
+ try {
104236
+ await Promise.race([Promise.resolve().then(work).catch(() => void 0), new Promise((resolve) => {
104237
+ timeout = setTimeout(resolve, CLEANUP_TIMEOUT_MS);
104238
+ })]);
104239
+ } finally {
104240
+ if (timeout) clearTimeout(timeout);
104241
+ }
104242
+ };
104243
+ try {
104244
+ if (controller.signal.aborted) throw new HubActivityUnavailable();
104245
+ const response = await fetcher(`${origin}${path}`, {
104246
+ method: body === void 0 ? "GET" : "POST",
104247
+ headers: {
104248
+ authorization: `Bearer ${settings.token}`,
104249
+ ...membership ? {
104250
+ "x-p4-hub-account": membership.accountId,
104251
+ "x-p4-hub-environment": membership.environmentId
104252
+ } : {},
104253
+ ...body === void 0 ? {} : { "content-type": "application/json" }
104254
+ },
104255
+ body,
104256
+ signal: controller.signal,
104257
+ redirect: "error",
104258
+ credentials: "omit",
104259
+ cache: "no-store"
104260
+ });
104261
+ if (controller.signal.aborted) {
104262
+ await cancel(() => response.body?.cancel() ?? Promise.resolve());
104263
+ throw new HubActivityUnavailable();
104264
+ }
104265
+ reader = response.body?.getReader();
104266
+ if (response.status === 401 || response.status === 403) throw new HubActivityUnauthorized();
104267
+ if (response.status !== 200 || !/^application\/json(?:\s*;|$)/i.test(response.headers.get("content-type") ?? "") || !reader) throw new HubActivityUnavailable();
104268
+ let length = 0;
104269
+ const chunks = [];
104270
+ while (true) {
104271
+ const item = await reader.read();
104272
+ if (controller.signal.aborted) throw new HubActivityUnavailable();
104273
+ if (item.done) break;
104274
+ length += item.value.byteLength;
104275
+ if (length > MAX_RESPONSE_BYTES) throw new HubActivityUnavailable();
104276
+ if (item.value.byteLength) chunks.push(item.value);
104277
+ }
104278
+ const bytes = new Uint8Array(length);
104279
+ let offset = 0;
104280
+ for (const chunk of chunks) {
104281
+ bytes.set(chunk, offset);
104282
+ offset += chunk.byteLength;
104283
+ }
104284
+ return Schema$1.decodeUnknownSync(Schema$1.UnknownFromJsonString)(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
104285
+ } finally {
104286
+ controller.abort();
104287
+ clearTimeout(timer);
104288
+ signal.removeEventListener("abort", abort);
104289
+ if (reader) await cancel(() => reader.cancel());
104290
+ }
104291
+ },
104292
+ catch: (error) => error instanceof HubActivityUnauthorized ? error : new HubActivityUnavailable()
104293
+ }).pipe(Effect.timeout(9e3), Effect.mapError((error) => error instanceof HubActivityUnauthorized ? error : new HubActivityUnavailable()));
104294
+ });
104295
+ return {
104296
+ identity: Effect.fn("HubActivityTransport.identity")(function* () {
104297
+ const membership = yield* request("/v1/identity").pipe(Effect.flatMap(Schema$1.decodeUnknownEffect(HubServerMembership, { onExcessProperty: "error" })), Effect.mapError((error) => error instanceof HubActivityUnauthorized ? error : new HubActivityUnavailable()));
104298
+ const destination = yield* Schema$1.encodeUnknownEffect(Schema$1.UnknownFromJsonString)([
104299
+ origin,
104300
+ membership.accountId,
104301
+ membership.environmentId
104302
+ ]);
104303
+ return {
104304
+ ...membership,
104305
+ destination: NodeCrypto.createHash("sha256").update(destination).digest("hex")
104306
+ };
104307
+ }),
104308
+ post: Effect.fn("HubActivityTransport.post")(function* (input, membership) {
104309
+ const activity = yield* Schema$1.decodeUnknownEffect(HubAgentActivityInput)(input, { onExcessProperty: "error" }).pipe(Effect.mapError(() => new HubActivityUnavailable()));
104310
+ const body = yield* Schema$1.encodeEffect(Schema$1.fromJsonString(HubAgentActivityInput))(activity);
104311
+ if (Buffer.byteLength(body) > MAX_REQUEST_BYTES) return yield* Effect.fail(new HubActivityUnavailable());
104312
+ const result = yield* request("/v1/agent-activity", body, membership);
104313
+ return yield* Schema$1.decodeUnknownEffect(Schema$1.Struct({ accepted: Schema$1.Boolean }))(result, { onExcessProperty: "error" }).pipe(Effect.mapError(() => new HubActivityUnavailable()));
104314
+ }),
104315
+ postFact: Effect.fn("HubActivityTransport.postFact")(function* (input, membership) {
104316
+ const fact = yield* Schema$1.decodeUnknownEffect(HubAnalyticsFactInput)(input, { onExcessProperty: "error" }).pipe(Effect.mapError(() => new HubActivityUnavailable()));
104317
+ const body = yield* Schema$1.encodeEffect(Schema$1.fromJsonString(HubAnalyticsFactInput))(fact);
104318
+ if (Buffer.byteLength(body) > MAX_REQUEST_BYTES) return yield* Effect.fail(new HubActivityUnavailable());
104319
+ const result = yield* request("/v1/analytics/facts", body, membership);
104320
+ return yield* Schema$1.decodeUnknownEffect(Schema$1.Struct({ accepted: Schema$1.Boolean }))(result, { onExcessProperty: "error" }).pipe(Effect.mapError(() => new HubActivityUnavailable()));
104321
+ }),
104322
+ queryFacts: Effect.fn("HubActivityTransport.queryFacts")(function* (query, membership) {
104323
+ const search = new URLSearchParams();
104324
+ for (const kind of query.kinds ?? []) search.append("kind", kind);
104325
+ if (query.since !== void 0) search.set("since", query.since);
104326
+ if (query.until !== void 0) search.set("until", query.until);
104327
+ if (query.limit !== void 0) search.set("limit", String(query.limit));
104328
+ if (query.cursor !== void 0) search.set("cursor", query.cursor);
104329
+ const suffix = search.size === 0 ? "" : `?${search.toString()}`;
104330
+ const result = yield* request(`/v1/analytics/facts${suffix}`, void 0, membership);
104331
+ return yield* Schema$1.decodeUnknownEffect(HubAnalyticsPage)(result, { onExcessProperty: "error" }).pipe(Effect.mapError(() => new HubActivityUnavailable()));
104332
+ })
104333
+ };
104334
+ });
103779
104335
  (() => {
103780
104336
  try {
103781
104337
  return process.env.NODE_ENV === "development";
@@ -104486,7 +105042,7 @@ const NESTED_PAYLOAD_KEYS = [
104486
105042
  "operations"
104487
105043
  ];
104488
105044
  const MAX_COLLECT_DEPTH = 4;
104489
- function asRecord$6(value) {
105045
+ function asRecord$8(value) {
104490
105046
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
104491
105047
  }
104492
105048
  function pushChangedFilePath(target, value) {
@@ -104501,7 +105057,7 @@ function collectChangedFilePaths(value, target, depth) {
104501
105057
  for (const entry of value) collectChangedFilePaths(entry, target, depth + 1);
104502
105058
  return;
104503
105059
  }
104504
- const record = asRecord$6(value);
105060
+ const record = asRecord$8(value);
104505
105061
  if (!record) return;
104506
105062
  for (const field of CHANGED_FILE_FIELDS) pushChangedFilePath(target, record[field]);
104507
105063
  for (const nestedKey of NESTED_PAYLOAD_KEYS) if (nestedKey in record) collectChangedFilePaths(record[nestedKey], target, depth + 1);
@@ -104512,7 +105068,7 @@ function collectChangedFilePaths(value, target, depth) {
104512
105068
  */
104513
105069
  function collectActivityChangedFilePaths(payload) {
104514
105070
  const target = /* @__PURE__ */ new Set();
104515
- collectChangedFilePaths(asRecord$6(asRecord$6(payload)?.data), target, 0);
105071
+ collectChangedFilePaths(asRecord$8(asRecord$8(payload)?.data), target, 0);
104516
105072
  return target;
104517
105073
  }
104518
105074
  /**
@@ -122485,7 +123041,7 @@ const makeBtwRequestCoordinator = (dependencies) => Effect.gen(function* () {
122485
123041
  * already negotiated.
122486
123042
  */
122487
123043
  const MCP_PROTOCOL_VERSION = "2025-06-18";
122488
- const REQUEST_TIMEOUT_MS$3 = 15e3;
123044
+ const REQUEST_TIMEOUT_MS$2 = 15e3;
122489
123045
  var McpToolCallError = class extends Schema$1.TaggedErrorClass()("McpToolCallError", {
122490
123046
  detail: Schema$1.String,
122491
123047
  status: Schema$1.NullOr(Schema$1.Number)
@@ -122549,7 +123105,7 @@ const callMcpTool = Effect.fn("McpToolClient.callMcpTool")(function* (input) {
122549
123105
  ...input.headers,
122550
123106
  ...extraHeaders,
122551
123107
  accept: "application/json, text/event-stream"
122552
- }), body)).pipe(Effect.timeout(REQUEST_TIMEOUT_MS$3), Effect.catchCause((cause) => Effect.fail(new McpToolCallError({
123108
+ }), body)).pipe(Effect.timeout(REQUEST_TIMEOUT_MS$2), Effect.catchCause((cause) => Effect.fail(new McpToolCallError({
122553
123109
  detail: `Could not reach ${input.url}: ${String(cause)}`,
122554
123110
  status: null
122555
123111
  }))));
@@ -122643,7 +123199,7 @@ var LinearUnavailable = class extends Schema$1.TaggedErrorClass()("LinearUnavail
122643
123199
  return this.detail;
122644
123200
  }
122645
123201
  };
122646
- const asRecord$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
123202
+ const asRecord$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
122647
123203
  const asArray$1 = (value) => Array.isArray(value) ? value : void 0;
122648
123204
  /**
122649
123205
  * Find the payload inside whatever the tool returned.
@@ -122673,22 +123229,22 @@ const readPayload = (result) => {
122673
123229
  */
122674
123230
  const readRows = (payload) => {
122675
123231
  const direct = asArray$1(payload);
122676
- if (direct !== void 0) return direct.map(asRecord$5).filter((row) => row !== void 0);
122677
- const record = asRecord$5(payload);
123232
+ if (direct !== void 0) return direct.map(asRecord$7).filter((row) => row !== void 0);
123233
+ const record = asRecord$7(payload);
122678
123234
  if (record === void 0) return [];
122679
123235
  for (const value of Object.values(record)) {
122680
123236
  const rows = asArray$1(value);
122681
- if (rows !== void 0) return rows.map(asRecord$5).filter((row) => row !== void 0);
123237
+ if (rows !== void 0) return rows.map(asRecord$7).filter((row) => row !== void 0);
122682
123238
  }
122683
123239
  return [];
122684
123240
  };
122685
123241
  /** The single object out of a result, unwrapping one level of nesting. */
122686
123242
  const readOne = (payload) => {
122687
- const record = asRecord$5(payload);
123243
+ const record = asRecord$7(payload);
122688
123244
  if (record === void 0) return;
122689
123245
  if (record["id"] !== void 0 || record["identifier"] !== void 0) return record;
122690
123246
  for (const value of Object.values(record)) {
122691
- const nested = asRecord$5(value);
123247
+ const nested = asRecord$7(value);
122692
123248
  if (nested?.["id"] !== void 0) return nested;
122693
123249
  }
122694
123250
  };
@@ -122798,7 +123354,7 @@ const TrackerSecretsLive = Layer.effect(TrackerSecrets, makeTrackerSecrets);
122798
123354
  *
122799
123355
  * @module tracker/graphqlRequest
122800
123356
  */
122801
- const REQUEST_TIMEOUT_MS$2 = 15e3;
123357
+ const REQUEST_TIMEOUT_MS$1 = 15e3;
122802
123358
  var GraphqlRequestError = class extends Schema$1.TaggedErrorClass()("GraphqlRequestError", {
122803
123359
  detail: Schema$1.String,
122804
123360
  status: Schema$1.NullOr(Schema$1.Number)
@@ -122807,14 +123363,14 @@ var GraphqlRequestError = class extends Schema$1.TaggedErrorClass()("GraphqlRequ
122807
123363
  return this.detail;
122808
123364
  }
122809
123365
  };
122810
- const asRecord$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
123366
+ const asRecord$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
122811
123367
  const errorMessages = (value) => {
122812
123368
  if (!Array.isArray(value)) return [];
122813
- return value.map((entry) => asRecord$4(entry)?.["message"]).filter((message) => typeof message === "string");
123369
+ return value.map((entry) => asRecord$6(entry)?.["message"]).filter((message) => typeof message === "string");
122814
123370
  };
122815
123371
  const parseJsonRecord = (text) => {
122816
123372
  try {
122817
- return asRecord$4(JSON.parse(text));
123373
+ return asRecord$6(JSON.parse(text));
122818
123374
  } catch {
122819
123375
  return;
122820
123376
  }
@@ -122823,7 +123379,7 @@ const graphqlRequest = Effect.fn("tracker/graphqlRequest")(function* (input) {
122823
123379
  const response = yield* (yield* HttpClient$1.HttpClient).execute(HttpClientRequest$1.bodyJsonUnsafe(HttpClientRequest$1.setHeaders(HttpClientRequest$1.post(input.url), input.headers), {
122824
123380
  query: input.query,
122825
123381
  variables: input.variables ?? {}
122826
- })).pipe(Effect.timeout(REQUEST_TIMEOUT_MS$2), Effect.catchCause((cause) => Effect.fail(new GraphqlRequestError({
123382
+ })).pipe(Effect.timeout(REQUEST_TIMEOUT_MS$1), Effect.catchCause((cause) => Effect.fail(new GraphqlRequestError({
122827
123383
  detail: `Could not reach ${input.url}: ${String(cause)}`,
122828
123384
  status: null
122829
123385
  }))));
@@ -122838,7 +123394,7 @@ const graphqlRequest = Effect.fn("tracker/graphqlRequest")(function* (input) {
122838
123394
  status: response.status
122839
123395
  });
122840
123396
  return {
122841
- data: asRecord$4(record["data"]),
123397
+ data: asRecord$6(record["data"]),
122842
123398
  errors: errorMessages(record["errors"])
122843
123399
  };
122844
123400
  });
@@ -122891,7 +123447,7 @@ const STATE_TYPES = /* @__PURE__ */ new Set([
122891
123447
  "completed",
122892
123448
  "canceled"
122893
123449
  ]);
122894
- const asRecord$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
123450
+ const asRecord$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
122895
123451
  const asArray = (value) => Array.isArray(value) ? value : [];
122896
123452
  const asString = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
122897
123453
  /**
@@ -122901,9 +123457,9 @@ const asString = (value) => typeof value === "string" && value.trim().length > 0
122901
123457
  * `labels` stay structured - the mapping already accepts both forms.
122902
123458
  */
122903
123459
  const toIssueRow = (node) => {
122904
- const state = asRecord$3(node["state"]);
122905
- const parent = asRecord$3(node["parent"]);
122906
- const labels = asRecord$3(node["labels"]);
123460
+ const state = asRecord$5(node["state"]);
123461
+ const parent = asRecord$5(node["parent"]);
123462
+ const labels = asRecord$5(node["labels"]);
122907
123463
  return {
122908
123464
  id: node["id"],
122909
123465
  identifier: node["identifier"],
@@ -122943,7 +123499,7 @@ const makeLinearApiTransport = Effect.gen(function* () {
122943
123499
  })));
122944
123500
  /** The one entity out of `data`, or a failure carrying Linear's own words. */
122945
123501
  const readEntity = (response, key) => {
122946
- const entity = asRecord$3(response.data?.[key]);
123502
+ const entity = asRecord$5(response.data?.[key]);
122947
123503
  if (entity !== void 0) return Effect.succeed(entity);
122948
123504
  if (response.errors.length === 0) return Effect.void.pipe(Effect.as(void 0));
122949
123505
  if (response.errors.some((message) => /not found|does not exist/iu.test(message))) return Effect.void.pipe(Effect.as(void 0));
@@ -122952,7 +123508,7 @@ const makeLinearApiTransport = Effect.gen(function* () {
122952
123508
  detail: response.errors.join("; ")
122953
123509
  }));
122954
123510
  };
122955
- const nodesOf = (response, key) => asArray(asRecord$3(response.data?.[key])?.["nodes"]).map(asRecord$3).filter((node) => node !== void 0);
123511
+ const nodesOf = (response, key) => asArray(asRecord$5(response.data?.[key])?.["nodes"]).map(asRecord$5).filter((node) => node !== void 0);
122956
123512
  const resolveTeamId = (apiKey, team) => Effect.gen(function* () {
122957
123513
  const response = yield* request(apiKey, `query($filter: TeamFilter) { teams(filter: $filter, first: 2) { nodes { id } } }`, { filter: { or: [{ key: { eqIgnoreCase: team } }, { name: { eqIgnoreCase: team } }] } });
122958
123514
  const id = asString(nodesOf(response, "teams")[0]?.["id"]);
@@ -122965,7 +123521,7 @@ const makeLinearApiTransport = Effect.gen(function* () {
122965
123521
  const teamIdOfIssue = (apiKey, issueId) => Effect.gen(function* () {
122966
123522
  const response = yield* request(apiKey, `query($id: String!) { issue(id: $id) { team { id } } }`, { id: issueId });
122967
123523
  const issue = yield* readEntity(response, "issue");
122968
- const id = asString(asRecord$3(issue?.["team"])?.["id"]);
123524
+ const id = asString(asRecord$5(issue?.["team"])?.["id"]);
122969
123525
  if (id === void 0) return yield* new LinearUnavailable({
122970
123526
  reason: "failed",
122971
123527
  detail: `Linear has no issue "${issueId}" to read a team from.`
@@ -123085,7 +123641,7 @@ const makeLinearApiTransport = Effect.gen(function* () {
123085
123641
  input
123086
123642
  });
123087
123643
  const payload = yield* readEntity(response, issueId === void 0 ? "issueCreate" : "issueUpdate");
123088
- const issue = asRecord$3(payload?.["issue"]);
123644
+ const issue = asRecord$5(payload?.["issue"]);
123089
123645
  if (issue === void 0) return yield* new LinearUnavailable({
123090
123646
  reason: "failed",
123091
123647
  detail: response.errors.length > 0 ? response.errors.join("; ") : "Linear accepted the write but returned no issue."
@@ -123115,11 +123671,11 @@ const makeLinearApiTransport = Effect.gen(function* () {
123115
123671
  reason: "not_authorized",
123116
123672
  detail: "Linear rejected this API key."
123117
123673
  }) : error));
123118
- if (asString(asRecord$3(response.data?.["viewer"])?.["id"]) === void 0) return yield* new LinearUnavailable({
123674
+ if (asString(asRecord$5(response.data?.["viewer"])?.["id"]) === void 0) return yield* new LinearUnavailable({
123119
123675
  reason: "not_authorized",
123120
123676
  detail: response.errors.length > 0 ? response.errors.join("; ") : "Linear rejected this API key."
123121
123677
  });
123122
- const organization = asRecord$3(response.data?.["organization"]);
123678
+ const organization = asRecord$5(response.data?.["organization"]);
123123
123679
  return { workspace: asString(organization?.["name"]) ?? asString(organization?.["urlKey"]) ?? null };
123124
123680
  });
123125
123681
  return {
@@ -123340,7 +123896,7 @@ const STATUS_FROM_LINEAR_TYPE = {
123340
123896
  completed: "done",
123341
123897
  canceled: "cancelled"
123342
123898
  };
123343
- const asRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
123899
+ const asRecord$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
123344
123900
  const text = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
123345
123901
  /**
123346
123902
  * A person's name out of whatever Linear put in the field: an object when the
@@ -123349,14 +123905,14 @@ const text = (value) => typeof value === "string" && value.trim().length > 0 ? v
123349
123905
  const personName = (value) => {
123350
123906
  const direct = text(value);
123351
123907
  if (direct !== void 0) return direct;
123352
- const record = asRecord$2(value);
123908
+ const record = asRecord$4(value);
123353
123909
  if (record === void 0) return null;
123354
123910
  return text(record["displayName"]) ?? text(record["name"]) ?? text(record["email"]) ?? null;
123355
123911
  };
123356
123912
  /** Label names, from either `["Bug"]` or `[{ name: "Bug" }]`. */
123357
123913
  const labelNames = (value) => {
123358
123914
  if (!Array.isArray(value)) return [];
123359
- return value.map((entry) => text(entry) ?? text(asRecord$2(entry)?.["name"])).filter((name) => name !== void 0);
123915
+ return value.map((entry) => text(entry) ?? text(asRecord$4(entry)?.["name"])).filter((name) => name !== void 0);
123360
123916
  };
123361
123917
  /**
123362
123918
  * The p4code status an issue is in.
@@ -123401,7 +123957,7 @@ const statusToLinearState = (status) => {
123401
123957
  * Medium, and quietly rewrite the local row with it.
123402
123958
  */
123403
123959
  const priorityFromLinear = (value) => {
123404
- const numeric = typeof value === "number" ? value : asRecord$2(value)?.["value"];
123960
+ const numeric = typeof value === "number" ? value : asRecord$4(value)?.["value"];
123405
123961
  return typeof numeric === "number" ? PRIORITY_FROM_LINEAR[numeric] ?? "none" : "none";
123406
123962
  };
123407
123963
  const priorityToLinear = (priority) => PRIORITY_TO_LINEAR[priority];
@@ -126920,7 +127476,7 @@ function toAssetFilePath(repositoryPath, root) {
126920
127476
  const REGISTRY_SEARCH_URL = "https://www.skills.sh/api/search";
126921
127477
  const GITHUB_API_URL = "https://api.github.com";
126922
127478
  const GITHUB_RAW_URL = "https://raw.githubusercontent.com";
126923
- const REQUEST_TIMEOUT_MS$1 = 15e3;
127479
+ const REQUEST_TIMEOUT_MS = 15e3;
126924
127480
  /** More than a person reads, few enough that the panel is a list and not a feed. */
126925
127481
  const MAX_SEARCH_RESULTS = 25;
126926
127482
  /**
@@ -126960,7 +127516,7 @@ const emptyFetch = (id, unavailable) => ({
126960
127516
  const make$29 = Effect.gen(function* () {
126961
127517
  const http = yield* HttpClient.HttpClient;
126962
127518
  const request = Effect.fn("SkillRegistry.request")(function* (url) {
126963
- return yield* http.execute(HttpClientRequest.get(url).pipe(HttpClientRequest.setHeader("accept", "application/json"), HttpClientRequest.setHeader("user-agent", "p4code"))).pipe(Effect.timeout(REQUEST_TIMEOUT_MS$1));
127519
+ return yield* http.execute(HttpClientRequest.get(url).pipe(HttpClientRequest.setHeader("accept", "application/json"), HttpClientRequest.setHeader("user-agent", "p4code"))).pipe(Effect.timeout(REQUEST_TIMEOUT_MS));
126964
127520
  });
126965
127521
  const search = (query) => Effect.gen(function* () {
126966
127522
  const response = yield* request(`${REGISTRY_SEARCH_URL}?q=${encodeURIComponent(query)}`).pipe(Effect.orElseSucceed(() => null));
@@ -130108,6 +130664,7 @@ const RPC_REQUIRED_SCOPE = /* @__PURE__ */ new Map([
130108
130664
  [WS_METHODS.storageCleanupSweep, AuthOrchestrationOperateScope],
130109
130665
  [WS_METHODS.worktreeCleanupPreview, AuthOrchestrationReadScope],
130110
130666
  [WS_METHODS.worktreeCleanupSweep, AuthOrchestrationOperateScope],
130667
+ [WS_METHODS.hubAnalyticsQuery, AuthOrchestrationReadScope],
130111
130668
  [WS_METHODS.hubGetSyncStatus, AuthOrchestrationReadScope],
130112
130669
  [WS_METHODS.hubConnect, AuthOrchestrationOperateScope],
130113
130670
  [WS_METHODS.hubDisconnect, AuthOrchestrationOperateScope],
@@ -131135,6 +131692,13 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker, computerUseBrok
131135
131692
  [WS_METHODS.storageCleanupSweep]: (_input) => observeRpcEffect$1(WS_METHODS.storageCleanupSweep, storageCleanup.sweep, { "rpc.aggregate": "storageCleanup" }),
131136
131693
  [WS_METHODS.worktreeCleanupPreview]: (_input) => observeRpcEffect$1(WS_METHODS.worktreeCleanupPreview, worktreeCleanup.preview, { "rpc.aggregate": "worktreeCleanup" }),
131137
131694
  [WS_METHODS.worktreeCleanupSweep]: (_input) => observeRpcEffect$1(WS_METHODS.worktreeCleanupSweep, worktreeCleanup.sweep, { "rpc.aggregate": "worktreeCleanup" }),
131695
+ [WS_METHODS.hubAnalyticsQuery]: (query) => observeRpcEffect$1(WS_METHODS.hubAnalyticsQuery, Effect.gen(function* () {
131696
+ const state = yield* hubLink.current;
131697
+ if (Option.isNone(state.settings)) return yield* new HubLinkError({ detail: "No hub is configured." });
131698
+ const transport = yield* makeHubActivityTransport(state.settings.value);
131699
+ const membership = yield* transport.identity();
131700
+ return yield* transport.queryFacts(query, membership);
131701
+ }).pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause instanceof HubLinkError ? cause.detail : "The hub could not be reached." }))), { "rpc.aggregate": "hub" }),
131138
131702
  [WS_METHODS.hubGetSyncStatus]: (_input) => observeRpcEffect$1(WS_METHODS.hubGetSyncStatus, assetSync.status, { "rpc.aggregate": "hub" }),
131139
131703
  [WS_METHODS.hubConnect]: (input) => observeRpcEffect$1(WS_METHODS.hubConnect, hubLink.connect(input).pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(assetSync.status)), { "rpc.aggregate": "hub" }),
131140
131704
  [WS_METHODS.hubDisconnect]: (_input) => observeRpcEffect$1(WS_METHODS.hubDisconnect, hubLink.disconnect.pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(assetSync.status)), { "rpc.aggregate": "hub" }),
@@ -132332,6 +132896,23 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (options)
132332
132896
  ...event,
132333
132897
  requestId: RuntimeRequestId.make(String(pending.requestId))
132334
132898
  };
132899
+ /**
132900
+ * Stamp a compacted event with how long its pass actually took.
132901
+ *
132902
+ * The service owns both ends of a compaction, so it is the only place that
132903
+ * can measure one; every reader downstream would be guessing from
132904
+ * surrounding timestamps instead.
132905
+ */
132906
+ const withCompactionMeasurement = (event, pending, nowMs) => event.type === "thread.state.changed" && event.payload.state === "compacted" ? {
132907
+ ...event,
132908
+ payload: {
132909
+ ...event.payload,
132910
+ detail: {
132911
+ ...typeof event.payload.detail === "object" && event.payload.detail !== null ? event.payload.detail : {},
132912
+ durationMs: Math.max(0, nowMs - pending.startedAtMs)
132913
+ }
132914
+ }
132915
+ } : event;
132335
132916
  const compactionTerminal = (event) => event.type === "turn.completed" ? event.payload.state : event.type === "runtime.error" || event.type === "turn.aborted" || event.type === "session.exited" ? event.type : null;
132336
132917
  const processFallbackCompactionEvent = (pending, event) => Effect.gen(function* () {
132337
132918
  if (pendingCompactions.get(event.threadId) !== pending) {
@@ -132341,7 +132922,7 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (options)
132341
132922
  const matchesTurn = event.turnId !== void 0 && event.turnId === pending.expectedTurnId;
132342
132923
  if (matchesTurn && isCompactedEvent(event)) {
132343
132924
  pending.compactedEventObserved = true;
132344
- yield* publishRuntimeEvent(withCompactionRequestId(event, pending));
132925
+ yield* publishRuntimeEvent(withCompactionMeasurement(withCompactionRequestId(event, pending), pending, yield* Clock.currentTimeMillis));
132345
132926
  return;
132346
132927
  }
132347
132928
  yield* publishRuntimeEvent(event);
@@ -132354,7 +132935,10 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (options)
132354
132935
  type: "thread.state.changed",
132355
132936
  payload: {
132356
132937
  state: "compacted",
132357
- detail: { source: "provider-native-command" }
132938
+ detail: {
132939
+ source: "provider-native-command",
132940
+ durationMs: Math.max(0, (yield* Clock.currentTimeMillis) - pending.startedAtMs)
132941
+ }
132358
132942
  },
132359
132943
  ...pending.requestId !== void 0 ? { requestId: RuntimeRequestId.make(String(pending.requestId)) } : {}
132360
132944
  };
@@ -132386,7 +132970,7 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (options)
132386
132970
  if (pendingCompaction.native) {
132387
132971
  const compacted = isCompactedEvent(canonicalEvent);
132388
132972
  const terminal = compacted ? "completed" : canonicalEvent.type === "turn.completed" && canonicalEvent.payload.state === "completed" ? null : compactionTerminal(canonicalEvent);
132389
- yield* publishRuntimeEvent(compacted ? withCompactionRequestId(canonicalEvent, pendingCompaction) : canonicalEvent);
132973
+ yield* publishRuntimeEvent(compacted ? withCompactionMeasurement(withCompactionRequestId(canonicalEvent, pendingCompaction), pendingCompaction, yield* Clock.currentTimeMillis) : canonicalEvent);
132390
132974
  if (terminal !== null) yield* settleCompaction(canonicalEvent.threadId, pendingCompaction, terminal);
132391
132975
  return;
132392
132976
  }
@@ -132685,6 +133269,7 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (options)
132685
133269
  const completion = yield* Deferred.make();
132686
133270
  const pending = {
132687
133271
  completion,
133272
+ startedAtMs: yield* Clock.currentTimeMillis,
132688
133273
  native: compaction.type === "native",
132689
133274
  providerInstanceId: routed.instanceId,
132690
133275
  requestId,
@@ -136107,7 +136692,7 @@ const watchesAsFusionSupervisor = Effect.fn("mcp.watch.watchesAsFusionSupervisor
136107
136692
  return (yield* (yield* ProjectionSnapshotQuery).getActiveThreadPairs().pipe(Effect.orElseSucceed(() => []))).some((pair) => pair.watcherThreadId === readerThreadId && pair.implementerThreadId === watchedThreadId);
136108
136693
  });
136109
136694
  /** Bounded so one report cannot dominate a thread's activity storage. */
136110
- const clip = (value, limit) => {
136695
+ const clip$1 = (value, limit) => {
136111
136696
  const trimmed = value?.trim();
136112
136697
  if (!trimmed) return null;
136113
136698
  return trimmed.length <= limit ? trimmed : `${trimmed.slice(0, limit)}…`;
@@ -136143,8 +136728,8 @@ const WatchToolkitHandlersLive = WatchToolkit.toLayer({
136143
136728
  serverReadActivityId: serverEvidenceReadActivityId(input.threadId, input.throughSequence),
136144
136729
  closedAt: createdAt
136145
136730
  });
136146
- const summary = clip(input.summary, THREAD_EVIDENCE_REPORT_MAX_CHARS);
136147
- const reason = clip(input.reason, 500);
136731
+ const summary = clip$1(input.summary, THREAD_EVIDENCE_REPORT_MAX_CHARS);
136732
+ const reason = clip$1(input.reason, 500);
136148
136733
  const outcome = summary === null && input.outcome === "summarized" ? "failed" : input.outcome;
136149
136734
  return {
136150
136735
  recorded: yield* engine.dispatch({
@@ -136166,7 +136751,7 @@ const WatchToolkitHandlersLive = WatchToolkit.toLayer({
136166
136751
  throughSequence: input.throughSequence,
136167
136752
  outcome,
136168
136753
  summary,
136169
- model: clip(input.model, MODEL_ID_MAX_CHARS),
136754
+ model: clip$1(input.model, MODEL_ID_MAX_CHARS),
136170
136755
  reason: reason ?? (outcome === "summarized" ? null : "the supervisor gave no reason"),
136171
136756
  context,
136172
136757
  timing
@@ -136354,118 +136939,6 @@ const ComputerUseMcpLive = ComputerUseToolkitRegistrationLive.pipe(Layer.provide
136354
136939
  }).pipe(Layer.provide(McpAuthMiddlewareLive))));
136355
136940
  const layer$4 = Layer.mergeAll(Layer.fresh(StandardMcpLive), Layer.fresh(ComputerUseMcpLive));
136356
136941
  //#endregion
136357
- //#region src/hub/HubActivityTransport.ts
136358
- const REQUEST_TIMEOUT_MS = 8e3;
136359
- const CLEANUP_TIMEOUT_MS = 1e3;
136360
- const MAX_RESPONSE_BYTES = 4096;
136361
- const MAX_REQUEST_BYTES = 16 * 1024;
136362
- const MAX_BEARER_CHARS = 8192;
136363
- var HubActivityUnavailable = class extends Data.TaggedError("HubActivityUnavailable") {};
136364
- var HubActivityUnauthorized = class extends Data.TaggedError("HubActivityUnauthorized") {};
136365
- /** All errors are payload-free; late responses still cancel their bodies after owner interruption. */
136366
- const makeHubActivityTransport = Effect.fn("HubActivityTransport.make")(function* (settings, fetcher = fetch) {
136367
- const origin = yield* Effect.try({
136368
- try: () => {
136369
- const url = new URL(settings.baseUrl);
136370
- if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash || url.pathname !== "/" || settings.token.length === 0 || settings.token.length > MAX_BEARER_CHARS || !/^[\x21-\x7e]+$/.test(settings.token)) throw new HubActivityUnavailable();
136371
- return url.origin;
136372
- },
136373
- catch: () => new HubActivityUnavailable()
136374
- });
136375
- const request = Effect.fn("HubActivityTransport.request")(function* (path, body, membership) {
136376
- return yield* Effect.tryPromise({
136377
- try: async (signal) => {
136378
- const controller = new AbortController();
136379
- const abort = () => controller.abort();
136380
- signal.addEventListener("abort", abort, { once: true });
136381
- if (signal.aborted) abort();
136382
- const timer = setTimeout(abort, REQUEST_TIMEOUT_MS);
136383
- let reader;
136384
- const cancel = async (work) => {
136385
- let timeout;
136386
- try {
136387
- await Promise.race([Promise.resolve().then(work).catch(() => void 0), new Promise((resolve) => {
136388
- timeout = setTimeout(resolve, CLEANUP_TIMEOUT_MS);
136389
- })]);
136390
- } finally {
136391
- if (timeout) clearTimeout(timeout);
136392
- }
136393
- };
136394
- try {
136395
- if (controller.signal.aborted) throw new HubActivityUnavailable();
136396
- const response = await fetcher(`${origin}${path}`, {
136397
- method: body === void 0 ? "GET" : "POST",
136398
- headers: {
136399
- authorization: `Bearer ${settings.token}`,
136400
- ...membership ? {
136401
- "x-p4-hub-account": membership.accountId,
136402
- "x-p4-hub-environment": membership.environmentId
136403
- } : {},
136404
- ...body === void 0 ? {} : { "content-type": "application/json" }
136405
- },
136406
- body,
136407
- signal: controller.signal,
136408
- redirect: "error",
136409
- credentials: "omit",
136410
- cache: "no-store"
136411
- });
136412
- if (controller.signal.aborted) {
136413
- await cancel(() => response.body?.cancel() ?? Promise.resolve());
136414
- throw new HubActivityUnavailable();
136415
- }
136416
- reader = response.body?.getReader();
136417
- if (response.status === 401 || response.status === 403) throw new HubActivityUnauthorized();
136418
- if (response.status !== 200 || !/^application\/json(?:\s*;|$)/i.test(response.headers.get("content-type") ?? "") || !reader) throw new HubActivityUnavailable();
136419
- let length = 0;
136420
- const chunks = [];
136421
- while (true) {
136422
- const item = await reader.read();
136423
- if (controller.signal.aborted) throw new HubActivityUnavailable();
136424
- if (item.done) break;
136425
- length += item.value.byteLength;
136426
- if (length > MAX_RESPONSE_BYTES) throw new HubActivityUnavailable();
136427
- if (item.value.byteLength) chunks.push(item.value);
136428
- }
136429
- const bytes = new Uint8Array(length);
136430
- let offset = 0;
136431
- for (const chunk of chunks) {
136432
- bytes.set(chunk, offset);
136433
- offset += chunk.byteLength;
136434
- }
136435
- return Schema$1.decodeUnknownSync(Schema$1.UnknownFromJsonString)(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
136436
- } finally {
136437
- controller.abort();
136438
- clearTimeout(timer);
136439
- signal.removeEventListener("abort", abort);
136440
- if (reader) await cancel(() => reader.cancel());
136441
- }
136442
- },
136443
- catch: (error) => error instanceof HubActivityUnauthorized ? error : new HubActivityUnavailable()
136444
- }).pipe(Effect.timeout(9e3), Effect.mapError((error) => error instanceof HubActivityUnauthorized ? error : new HubActivityUnavailable()));
136445
- });
136446
- return {
136447
- identity: Effect.fn("HubActivityTransport.identity")(function* () {
136448
- const membership = yield* request("/v1/identity").pipe(Effect.flatMap(Schema$1.decodeUnknownEffect(HubServerMembership, { onExcessProperty: "error" })), Effect.mapError((error) => error instanceof HubActivityUnauthorized ? error : new HubActivityUnavailable()));
136449
- const destination = yield* Schema$1.encodeUnknownEffect(Schema$1.UnknownFromJsonString)([
136450
- origin,
136451
- membership.accountId,
136452
- membership.environmentId
136453
- ]);
136454
- return {
136455
- ...membership,
136456
- destination: NodeCrypto.createHash("sha256").update(destination).digest("hex")
136457
- };
136458
- }),
136459
- post: Effect.fn("HubActivityTransport.post")(function* (input, membership) {
136460
- const activity = yield* Schema$1.decodeUnknownEffect(HubAgentActivityInput)(input, { onExcessProperty: "error" }).pipe(Effect.mapError(() => new HubActivityUnavailable()));
136461
- const body = yield* Schema$1.encodeEffect(Schema$1.fromJsonString(HubAgentActivityInput))(activity);
136462
- if (Buffer.byteLength(body) > MAX_REQUEST_BYTES) return yield* Effect.fail(new HubActivityUnavailable());
136463
- const result = yield* request("/v1/agent-activity", body, membership);
136464
- return yield* Schema$1.decodeUnknownEffect(Schema$1.Struct({ accepted: Schema$1.Boolean }))(result, { onExcessProperty: "error" }).pipe(Effect.mapError(() => new HubActivityUnavailable()));
136465
- })
136466
- };
136467
- });
136468
- //#endregion
136469
136942
  //#region src/hub/HubActivityProjection.ts
136470
136943
  const titles = {
136471
136944
  running: "Agent working",
@@ -136596,6 +137069,407 @@ const makeHubActivityOutbound = Effect.fn("HubActivityOutbound.make")(function*
136596
137069
  })
136597
137070
  };
136598
137071
  });
137072
+ const encodeFact = Schema$1.encodeEffect(Schema$1.fromJsonString(HubAnalyticsFactInput));
137073
+ const decodeStoredFact = Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(HubAnalyticsFactInput));
137074
+ /** Stage one derived fact. Returns false when this id was already staged. */
137075
+ const stageAnalyticsFact = Effect.fn("AnalyticsFactOutbox.stage")(function* (fact) {
137076
+ const sql = yield* SqlClient.SqlClient;
137077
+ const payload = yield* encodeFact(fact);
137078
+ const now = yield* Clock.currentTimeMillis;
137079
+ return (yield* sql`
137080
+ INSERT INTO analytics_fact_outbox (event_id, kind, version, occurred_at, payload, pending, created_at)
137081
+ VALUES (${fact.eventId}, ${fact.kind}, ${fact.version}, ${fact.occurredAt}, ${payload}, 1, ${now})
137082
+ ON CONFLICT(event_id) DO NOTHING
137083
+ RETURNING event_id`).length === 1;
137084
+ });
137085
+ /** The oldest undelivered facts, in the order they were captured. */
137086
+ const pendingAnalyticsFacts = Effect.fn("AnalyticsFactOutbox.pending")(function* (limit = 16) {
137087
+ return yield* (yield* SqlClient.SqlClient)`
137088
+ SELECT event_id, payload FROM analytics_fact_outbox WHERE pending = 1
137089
+ ORDER BY created_at, event_id LIMIT ${limit}`;
137090
+ });
137091
+ /**
137092
+ * Mark one fact delivered.
137093
+ *
137094
+ * The hub answers `accepted: false` for a fact it already holds, which is the
137095
+ * same outcome as a fresh accept from here: the row is delivered either way,
137096
+ * and a retry that raced an earlier success must not stay pending forever.
137097
+ */
137098
+ const acknowledgeAnalyticsFact = Effect.fn("AnalyticsFactOutbox.acknowledge")(function* (eventId) {
137099
+ return (yield* (yield* SqlClient.SqlClient)`UPDATE analytics_fact_outbox SET pending = 0
137100
+ WHERE event_id = ${eventId} AND pending = 1 RETURNING event_id`).length === 1;
137101
+ });
137102
+ /** Where a source's scan left off, so a restart resumes instead of replaying. */
137103
+ const readAnalyticsCursor = Effect.fn("AnalyticsFactOutbox.readCursor")(function* (source) {
137104
+ return (yield* (yield* SqlClient.SqlClient)`SELECT after_sequence FROM analytics_fact_cursors WHERE source = ${source}`)[0]?.after_sequence ?? 0;
137105
+ });
137106
+ const writeAnalyticsCursor = Effect.fn("AnalyticsFactOutbox.writeCursor")(function* (source, afterSequence) {
137107
+ yield* (yield* SqlClient.SqlClient)`INSERT INTO analytics_fact_cursors (source, after_sequence)
137108
+ VALUES (${source}, ${afterSequence})
137109
+ ON CONFLICT(source) DO UPDATE SET after_sequence = excluded.after_sequence`;
137110
+ });
137111
+ //#endregion
137112
+ //#region src/analytics/analyticsFacts.ts
137113
+ const MAX_QUESTION_CHARS = 2e3;
137114
+ const MAX_HEADER_CHARS = 120;
137115
+ const MAX_OPTION_LABEL_CHARS = 200;
137116
+ const MAX_OPTION_DESCRIPTION_CHARS = 600;
137117
+ const MAX_PRESENTED_OPTIONS = 12;
137118
+ const MAX_CUSTOM_ANSWER_CHARS = 2e3;
137119
+ const MAX_REASON_CHARS = 200;
137120
+ const MAX_MODEL_CHARS = 120;
137121
+ const clip = (value, max) => value.length <= max ? value : value.slice(0, max);
137122
+ const asRecord$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
137123
+ const asFiniteInt$1 = (value) => typeof value === "number" && Number.isFinite(value) ? Math.round(value) : null;
137124
+ /** `question:<requestId>:<questionId>`, so a replayed resolution derives the same id. */
137125
+ const questionFactId = (requestId, questionId) => `question:${requestId}:${questionId}`;
137126
+ /**
137127
+ * One fact per question in a resolved request.
137128
+ *
137129
+ * The presented options travel with the answer because the answer alone says
137130
+ * nothing: "Patch" only means something beside what else was offered, and a
137131
+ * custom answer only means something beside what it refused.
137132
+ */
137133
+ function questionFactsFrom(input) {
137134
+ const questions = Array.isArray(input.questions) ? input.questions : [];
137135
+ const answers = asRecord$3(input.answers) ?? {};
137136
+ const facts = [];
137137
+ for (const raw of questions) {
137138
+ const question = asRecord$3(raw);
137139
+ if (question === null) continue;
137140
+ const questionId = typeof question.id === "string" ? question.id : null;
137141
+ const prompt = typeof question.question === "string" ? question.question : null;
137142
+ if (questionId === null || prompt === null || prompt.length === 0) continue;
137143
+ const options = (Array.isArray(question.options) ? question.options : []).slice(0, MAX_PRESENTED_OPTIONS).flatMap((entry) => {
137144
+ const option = asRecord$3(entry);
137145
+ const label = typeof option?.label === "string" ? option.label : null;
137146
+ return label === null || label.length === 0 ? [] : [{
137147
+ label: clip(label, MAX_OPTION_LABEL_CHARS),
137148
+ description: clip(typeof option?.description === "string" ? option.description : "", MAX_OPTION_DESCRIPTION_CHARS)
137149
+ }];
137150
+ });
137151
+ const given = answers[questionId];
137152
+ const selected = (Array.isArray(given) ? given : given === void 0 ? [] : [given]).flatMap((value) => typeof value === "string" && value.length > 0 ? [value] : []);
137153
+ const indexes = selected.map((label) => options.findIndex((option) => option.label === label));
137154
+ const custom = selected.find((_, position) => indexes[position] === -1);
137155
+ const answer = selected.length === 0 ? { kind: "dismissed" } : custom !== void 0 ? {
137156
+ kind: "custom",
137157
+ text: clip(custom, MAX_CUSTOM_ANSWER_CHARS)
137158
+ } : {
137159
+ kind: "option",
137160
+ optionIndexes: indexes,
137161
+ labels: selected.map((label) => clip(label, MAX_OPTION_LABEL_CHARS))
137162
+ };
137163
+ facts.push({
137164
+ eventId: questionFactId(input.requestId, questionId),
137165
+ occurredAt: input.occurredAt,
137166
+ kind: "question.answered",
137167
+ version: 1,
137168
+ payload: {
137169
+ questionId,
137170
+ threadId: input.threadId,
137171
+ header: clip(typeof question.header === "string" ? question.header : "", MAX_HEADER_CHARS),
137172
+ question: clip(prompt, MAX_QUESTION_CHARS),
137173
+ multiSelect: question.multiSelect === true,
137174
+ options,
137175
+ answer
137176
+ }
137177
+ });
137178
+ }
137179
+ return facts;
137180
+ }
137181
+ /** `fusion:<threadId>:<throughSequence>`: one handoff covers one range, once. */
137182
+ const fusionFactId = (threadId, throughSequence) => `fusion:${threadId}:${throughSequence}`;
137183
+ /**
137184
+ * One review, read from the two rows that recorded it.
137185
+ *
137186
+ * The outcome, the model and the duration come from the report, because that
137187
+ * is the row the server wrote them on; the coverage and the summary come from
137188
+ * the handoff, because that is the row that carried them. Neither is guessed
137189
+ * from the other, and a handoff with no report yields nothing at all.
137190
+ */
137191
+ function fusionHandoffFactFrom(input) {
137192
+ const payload = asRecord$3(input.payload);
137193
+ const report = asRecord$3(input.reportPayload);
137194
+ if (payload === null || report === null) return null;
137195
+ const outcome = report.outcome;
137196
+ if (outcome !== "summarized" && outcome !== "fallback" && outcome !== "failed" && outcome !== "unreported") return null;
137197
+ const coverage = asRecord$3(payload.coverage) ?? {};
137198
+ const context = asRecord$3(report.context);
137199
+ const timing = asRecord$3(report.timing);
137200
+ const summary = typeof payload.summary === "string" ? payload.summary : null;
137201
+ const reason = typeof report.reason === "string" ? clip(report.reason, MAX_REASON_CHARS) : null;
137202
+ return {
137203
+ eventId: fusionFactId(input.threadId, input.throughSequence),
137204
+ occurredAt: input.occurredAt,
137205
+ kind: "fusion.handoff",
137206
+ version: 1,
137207
+ payload: {
137208
+ fusionVersion: input.fusionVersion,
137209
+ threadId: input.threadId,
137210
+ status: outcome,
137211
+ model: typeof report.model === "string" ? clip(report.model, MAX_MODEL_CHARS) : null,
137212
+ reason,
137213
+ transcriptEvents: asFiniteInt$1(coverage.transcriptEvents) ?? 0,
137214
+ transcriptIncluded: asFiniteInt$1(coverage.transcriptIncluded) ?? 0,
137215
+ transcriptDropped: asFiniteInt$1(coverage.transcriptDropped) ?? 0,
137216
+ transcriptClippedFields: asFiniteInt$1(coverage.transcriptClippedFields) ?? 0,
137217
+ ledgerRows: asFiniteInt$1(coverage.ledgerRows) ?? 0,
137218
+ ledgerTotalRows: asFiniteInt$1(coverage.ledgerTotalRows) ?? 0,
137219
+ summaryChars: summary === null ? null : summary.length,
137220
+ contextDeltaTokens: asFiniteInt$1(context?.deltaTokens),
137221
+ durationMs: asFiniteInt$1(timing?.durationMs)
137222
+ }
137223
+ };
137224
+ }
137225
+ /** `compression:<threadId>:<turnId>`: one pass per turn, once. */
137226
+ const compressionFactId = (threadId, turnId) => `compression:${threadId}:${turnId}`;
137227
+ /**
137228
+ * A compression pass, only when it was actually measured.
137229
+ *
137230
+ * Both token readings and the duration have to come from recorded state: a
137231
+ * pass whose before or after was never written is not reported as zero, and
137232
+ * the delta is derived from the two readings rather than supplied beside them.
137233
+ */
137234
+ function compressionFactFrom(input) {
137235
+ const before = asFiniteInt$1(input.beforeTokens);
137236
+ const after = asFiniteInt$1(input.afterTokens);
137237
+ const durationMs = asFiniteInt$1(input.durationMs);
137238
+ if (before === null || after === null || durationMs === null || durationMs < 0) return null;
137239
+ if (before < 0 || after < 0) return null;
137240
+ return {
137241
+ eventId: compressionFactId(input.threadId, input.turnId),
137242
+ occurredAt: input.occurredAt,
137243
+ kind: "compression.applied",
137244
+ version: 1,
137245
+ payload: {
137246
+ mode: input.mode,
137247
+ threadId: input.threadId,
137248
+ beforeTokens: before,
137249
+ afterTokens: after,
137250
+ deltaTokens: before - after,
137251
+ durationMs
137252
+ }
137253
+ };
137254
+ }
137255
+ //#endregion
137256
+ //#region src/analytics/AnalyticsFactCapture.ts
137257
+ const ANALYTICS_QUESTION_SOURCE = "events.user-input";
137258
+ const ANALYTICS_FUSION_SOURCE = "events.fusion-evidence";
137259
+ const ANALYTICS_COMPRESSION_SOURCE = "events.context-compaction";
137260
+ const decodeJson$1 = Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(Schema$1.Unknown));
137261
+ const asRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
137262
+ const asFiniteInt = (value) => typeof value === "number" && Number.isFinite(value) ? Math.round(value) : null;
137263
+ const asUsedTokens = (payload) => asFiniteInt(asRecord$2(payload)?.usedTokens);
137264
+ /** One appended activity, read out of its event envelope. */
137265
+ const readActivityEvent = Effect.fn("AnalyticsFactCapture.activity")(function* (row) {
137266
+ const payload = asRecord$2(yield* decodeJson$1(row.payload_json));
137267
+ const activity = asRecord$2(payload?.activity);
137268
+ const threadId = typeof payload?.threadId === "string" ? payload.threadId : null;
137269
+ if (activity === null || threadId === null) return null;
137270
+ return {
137271
+ sequence: row.sequence,
137272
+ threadId,
137273
+ turnId: typeof activity.turnId === "string" ? activity.turnId : null,
137274
+ createdAt: typeof activity.createdAt === "string" ? activity.createdAt : "",
137275
+ payload: asRecord$2(activity.payload) ?? {}
137276
+ };
137277
+ });
137278
+ /** Candidate rows of one activity kind, after a source's own cursor. */
137279
+ const readCandidates = Effect.fn("AnalyticsFactCapture.candidates")(function* (kind, after, limit) {
137280
+ return yield* (yield* SqlClient.SqlClient)`
137281
+ SELECT sequence, payload_json FROM orchestration_events
137282
+ WHERE sequence > ${after} AND event_type = 'thread.activity-appended'
137283
+ AND json_extract(payload_json, '$.activity.kind') = ${kind}
137284
+ ORDER BY sequence ASC LIMIT ${limit}`;
137285
+ });
137286
+ /**
137287
+ * The request a resolution answers, matched on the exact request id.
137288
+ *
137289
+ * A newer question in the same thread must never lend its options to an older
137290
+ * answer, which is what a "most recent request" lookup would do whenever two
137291
+ * requests were open at once.
137292
+ */
137293
+ const readQuestionRequest = Effect.fn("AnalyticsFactCapture.questionRequest")(function* (threadId, requestId, throughSequence) {
137294
+ const [row] = yield* (yield* SqlClient.SqlClient)`
137295
+ SELECT sequence, payload_json FROM orchestration_events
137296
+ WHERE sequence <= ${throughSequence} AND event_type = 'thread.activity-appended'
137297
+ AND json_extract(payload_json, '$.activity.kind') = 'user-input.requested'
137298
+ AND json_extract(payload_json, '$.threadId') = ${threadId}
137299
+ AND json_extract(payload_json, '$.activity.payload.requestId') = ${requestId}
137300
+ ORDER BY sequence DESC LIMIT 1`;
137301
+ if (row === void 0) return null;
137302
+ return (yield* readActivityEvent(row))?.payload ?? null;
137303
+ });
137304
+ /** The handoff a report closes, found by the range the report names. */
137305
+ const readHandoff = Effect.fn("AnalyticsFactCapture.handoff")(function* (threadId, throughSequence, beforeSequence) {
137306
+ const [row] = yield* (yield* SqlClient.SqlClient)`
137307
+ SELECT sequence, payload_json FROM orchestration_events
137308
+ WHERE sequence <= ${beforeSequence} AND event_type = 'thread.activity-appended'
137309
+ AND json_extract(payload_json, '$.activity.kind') = 'fusion.evidence.handoff'
137310
+ AND json_extract(payload_json, '$.activity.payload.watchedThreadId') = ${threadId}
137311
+ AND json_extract(payload_json, '$.activity.payload.throughSequence') = ${throughSequence}
137312
+ ORDER BY sequence DESC LIMIT 1`;
137313
+ if (row === void 0) return null;
137314
+ return (yield* readActivityEvent(row))?.payload ?? null;
137315
+ });
137316
+ /**
137317
+ * The nearest context reading on one side of a compaction, in the same turn.
137318
+ *
137319
+ * Correlating on the turn is the point: a later turn's context update must
137320
+ * never be able to close an old pass, which is exactly what "nearest reading
137321
+ * in this thread" would let it do once the pass itself was never completed.
137322
+ */
137323
+ const readContextReading = Effect.fn("AnalyticsFactCapture.contextReading")(function* (threadId, turnId, sequence, side) {
137324
+ const sql = yield* SqlClient.SqlClient;
137325
+ const row = (side === "before" ? yield* sql`
137326
+ SELECT sequence, payload_json FROM orchestration_events
137327
+ WHERE sequence < ${sequence} AND event_type = 'thread.activity-appended'
137328
+ AND json_extract(payload_json, '$.activity.kind') = 'context-window.updated'
137329
+ AND json_extract(payload_json, '$.threadId') = ${threadId}
137330
+ AND json_extract(payload_json, '$.activity.turnId') = ${turnId}
137331
+ ORDER BY sequence DESC LIMIT 1` : yield* sql`
137332
+ SELECT sequence, payload_json FROM orchestration_events
137333
+ WHERE sequence > ${sequence} AND event_type = 'thread.activity-appended'
137334
+ AND json_extract(payload_json, '$.activity.kind') = 'context-window.updated'
137335
+ AND json_extract(payload_json, '$.threadId') = ${threadId}
137336
+ AND json_extract(payload_json, '$.activity.turnId') = ${turnId}
137337
+ ORDER BY sequence ASC LIMIT 1`)[0];
137338
+ return row === void 0 ? null : yield* readActivityEvent(row);
137339
+ });
137340
+ const stageAll = Effect.fn("AnalyticsFactCapture.stageAll")(function* (facts) {
137341
+ let staged = 0;
137342
+ for (const fact of facts) if (yield* stageAnalyticsFact(fact)) staged += 1;
137343
+ return staged;
137344
+ });
137345
+ /** Questions: one fact per question in each resolved request. */
137346
+ const captureQuestionFacts = Effect.fn("AnalyticsFactCapture.questions")(function* (limit = 128) {
137347
+ const after = yield* readAnalyticsCursor(ANALYTICS_QUESTION_SOURCE);
137348
+ const rows = yield* readCandidates("user-input.resolved", after, limit);
137349
+ let cursor = after;
137350
+ let staged = 0;
137351
+ for (const row of rows) {
137352
+ const event = yield* readActivityEvent(row);
137353
+ const requestId = typeof event?.payload.requestId === "string" ? event.payload.requestId : null;
137354
+ if (event !== null && requestId !== null) {
137355
+ const request = yield* readQuestionRequest(event.threadId, requestId, event.sequence);
137356
+ if (request !== null) staged += yield* stageAll(questionFactsFrom({
137357
+ requestId,
137358
+ threadId: event.threadId,
137359
+ occurredAt: event.createdAt,
137360
+ questions: request.questions,
137361
+ answers: event.payload.answers
137362
+ }));
137363
+ }
137364
+ cursor = row.sequence;
137365
+ }
137366
+ if (cursor !== after) yield* writeAnalyticsCursor(ANALYTICS_QUESTION_SOURCE, cursor);
137367
+ return {
137368
+ staged,
137369
+ cursor
137370
+ };
137371
+ });
137372
+ /**
137373
+ * Fusion: staged from the evidence report, never before it.
137374
+ *
137375
+ * The report is what carries the model, the context delta and the measured
137376
+ * duration, so a fact staged at handoff time would be missing exactly the
137377
+ * numbers it exists to carry - and could never gain them, because the fact is
137378
+ * append-only.
137379
+ */
137380
+ const captureFusionFacts = Effect.fn("AnalyticsFactCapture.fusion")(function* (input) {
137381
+ const after = yield* readAnalyticsCursor(ANALYTICS_FUSION_SOURCE);
137382
+ const rows = yield* readCandidates("fusion.evidence.summary", after, input.limit ?? 128);
137383
+ let cursor = after;
137384
+ let staged = 0;
137385
+ for (const row of rows) {
137386
+ const event = yield* readActivityEvent(row);
137387
+ const watchedThreadId = typeof event?.payload.watchedThreadId === "string" ? event.payload.watchedThreadId : null;
137388
+ const throughSequence = asFiniteInt(event?.payload.throughSequence);
137389
+ if (event !== null && watchedThreadId !== null && throughSequence !== null) {
137390
+ const handoff = yield* readHandoff(watchedThreadId, throughSequence, event.sequence);
137391
+ if (handoff !== null) {
137392
+ const fact = fusionHandoffFactFrom({
137393
+ threadId: watchedThreadId,
137394
+ throughSequence,
137395
+ occurredAt: event.createdAt,
137396
+ fusionVersion: input.fusionVersion,
137397
+ payload: handoff,
137398
+ reportPayload: event.payload
137399
+ });
137400
+ if (fact !== null) staged += yield* stageAll([fact]);
137401
+ }
137402
+ }
137403
+ cursor = row.sequence;
137404
+ }
137405
+ if (cursor !== after) yield* writeAnalyticsCursor(ANALYTICS_FUSION_SOURCE, cursor);
137406
+ return {
137407
+ staged,
137408
+ cursor
137409
+ };
137410
+ });
137411
+ /**
137412
+ * Compression: only a pass the service measured and the provider reported.
137413
+ *
137414
+ * The duration comes from the compaction event the provider service stamped,
137415
+ * the tokens from the recorded context readings either side. A pass whose
137416
+ * later reading has not landed yet holds back only this cursor, so a question
137417
+ * or a handoff behind it is still captured on the same tick.
137418
+ */
137419
+ const captureCompressionFacts = Effect.fn("AnalyticsFactCapture.compression")(function* (limit = 128) {
137420
+ const after = yield* readAnalyticsCursor(ANALYTICS_COMPRESSION_SOURCE);
137421
+ const rows = yield* readCandidates("context-compaction", after, limit);
137422
+ let cursor = after;
137423
+ let staged = 0;
137424
+ for (const row of rows) {
137425
+ const event = yield* readActivityEvent(row);
137426
+ if (event === null || event.turnId === null) {
137427
+ cursor = row.sequence;
137428
+ continue;
137429
+ }
137430
+ const durationMs = asFiniteInt(asRecord$2(event.payload.detail)?.durationMs);
137431
+ const before = yield* readContextReading(event.threadId, event.turnId, event.sequence, "before");
137432
+ const afterReading = yield* readContextReading(event.threadId, event.turnId, event.sequence, "after");
137433
+ if (afterReading === null) break;
137434
+ const mode = asRecord$2(before?.payload)?.compressMode ?? afterReading.payload.compressMode;
137435
+ const fact = typeof mode === "string" ? compressionFactFrom({
137436
+ threadId: event.threadId,
137437
+ turnId: event.turnId,
137438
+ occurredAt: event.createdAt,
137439
+ mode,
137440
+ beforeTokens: asUsedTokens(before?.payload),
137441
+ afterTokens: asUsedTokens(afterReading.payload),
137442
+ durationMs
137443
+ }) : null;
137444
+ if (fact !== null) staged += yield* stageAll([fact]);
137445
+ cursor = row.sequence;
137446
+ }
137447
+ if (cursor !== after) yield* writeAnalyticsCursor(ANALYTICS_COMPRESSION_SOURCE, cursor);
137448
+ return {
137449
+ staged,
137450
+ cursor
137451
+ };
137452
+ });
137453
+ /** One tick over every source. A source that stalls never blocks another. */
137454
+ const captureAnalyticsFacts = Effect.fn("AnalyticsFactCapture.run")(function* (input) {
137455
+ const questions = yield* captureQuestionFacts(input.limit);
137456
+ const fusion = yield* captureFusionFacts(input);
137457
+ const compression = yield* captureCompressionFacts(input.limit);
137458
+ return { staged: questions.staged + fusion.staged + compression.staged };
137459
+ });
137460
+ //#endregion
137461
+ //#region src/analytics/AnalyticsDelivery.ts
137462
+ const runAnalyticsTick = Effect.fn("AnalyticsDelivery.tick")(function* (transport, identity, fusionVersion) {
137463
+ yield* captureAnalyticsFacts({ fusionVersion });
137464
+ let delivered = 0;
137465
+ for (const row of yield* pendingAnalyticsFacts()) {
137466
+ const fact = yield* decodeStoredFact(row.payload);
137467
+ yield* transport.postFact(fact, identity);
137468
+ yield* acknowledgeAnalyticsFact(row.event_id);
137469
+ delivered += 1;
137470
+ }
137471
+ return { delivered };
137472
+ });
136599
137473
  //#endregion
136600
137474
  //#region src/hub/HubTunnelReconnect.ts
136601
137475
  const HUB_TUNNEL_RETRY_INITIAL_MS = 1e3;
@@ -136634,6 +137508,7 @@ const runHubActivityTick = Effect.fn("HubActivityRuntime.tick")(function* (outbo
136634
137508
  yield* transport.post(activity, identity);
136635
137509
  yield* outbound.acknowledge(pending.thread_id, pending.revision);
136636
137510
  }
137511
+ yield* runAnalyticsTick(transport, identity, version$1).pipe(Effect.catchCause((cause) => Effect.logDebug("analytics tick deferred", { cause })));
136637
137512
  });
136638
137513
  /** A credential generation owns every request, staging page and delay. Old destinations remain isolated. */
136639
137514
  const runHubActivityEmission = Effect.fn("HubActivityRuntime.run")(function* (settings, ready) {
@@ -141591,22 +142466,95 @@ function describeHandoffEvent(event, implementerThreadId, clip) {
141591
142466
  }
141592
142467
  }
141593
142468
  /**
142469
+ * An activity line's evidence fields, its kind dropped and its title read the
142470
+ * way the ledger reads it: a provider spells the opening fragment's title
142471
+ * "<title> started", which is the same call rather than a different one.
142472
+ */
142473
+ function activityFields(content) {
142474
+ const [, ...fields] = content.split(" | ");
142475
+ const [title, ...rest] = fields;
142476
+ return title === void 0 ? fields : [title.replace(/ started$/u, ""), ...rest];
142477
+ }
142478
+ /** What a line is a fragment of, or null when it stands alone. */
142479
+ function fragmentIdentity(event) {
142480
+ if (event.type === "thread.message-sent") {
142481
+ const messageId = asTrimmed(event.payload.messageId);
142482
+ return messageId === null ? null : `message:${messageId}`;
142483
+ }
142484
+ if (event.type === "thread.activity-appended") {
142485
+ const data = asRecord(asRecord(event.payload.activity.payload)?.data);
142486
+ const toolCallId = asTrimmed(data?.toolCallId);
142487
+ return toolCallId === null ? null : `tool:${toolCallId}`;
142488
+ }
142489
+ return null;
142490
+ }
142491
+ /**
142492
+ * Fragments whose evidence a later line of the same message or call already
142493
+ * carries, so the transcript spends its budget on evidence rather than on the
142494
+ * same text streamed twenty times.
142495
+ *
142496
+ * A streaming message is folded only when a later fragment literally contains
142497
+ * it, and a tool fragment only when every field it carries reappears later, so
142498
+ * nothing unique is ever folded away. What a folded line did carry alone - the
142499
+ * call's identity, its status and the sequence it started at - is exactly what
142500
+ * the ledger records beside this transcript.
142501
+ */
142502
+ function supersededLines(lines) {
142503
+ const groups = /* @__PURE__ */ new Map();
142504
+ for (const line of lines) {
142505
+ if (line.identity === null) continue;
142506
+ const group = groups.get(line.identity);
142507
+ if (group === void 0) groups.set(line.identity, [line]);
142508
+ else group.push(line);
142509
+ }
142510
+ const superseded = /* @__PURE__ */ new Set();
142511
+ for (const group of groups.values()) {
142512
+ if (group.length < 2) continue;
142513
+ const last = group[group.length - 1];
142514
+ for (let index = 0; index < group.length - 1; index += 1) {
142515
+ const line = group[index];
142516
+ if (line.shape === "message") {
142517
+ if (last.shape === "message" && last.content.includes(line.content)) superseded.add(line);
142518
+ continue;
142519
+ }
142520
+ const laterFields = new Set(group.slice(index + 1).flatMap((later) => later.fields));
142521
+ if (line.fields.every((field) => laterFields.has(field))) superseded.add(line);
142522
+ }
142523
+ }
142524
+ return superseded;
142525
+ }
142526
+ /**
141594
142527
  * Flatten the range, newest kept when the bound bites.
141595
142528
  *
141596
- * Dropping the oldest is the lesser evil for the same reason it is in the
141597
- * triage window, but unlike there the loss is reported rather than absorbed:
141598
- * a range that did not fit is a range this handoff must not call complete.
142529
+ * Duplicate fragments are folded before the bound is applied, because dropping
142530
+ * a unique event to make room for the twelfth copy of a streaming message is a
142531
+ * loss this handoff can simply avoid. Dropping the oldest is still the lesser
142532
+ * evil once the bound does bite, and unlike in the triage window the loss is
142533
+ * reported rather than absorbed: a range that did not fit is a range this
142534
+ * handoff must not call complete. Clipped fields are counted on the lines
142535
+ * actually handed over, so a field clipped inside a folded or dropped line is
142536
+ * not charged against coverage twice.
141599
142537
  */
141600
142538
  function buildHandoffEvidence(events, implementerThreadId, maxChars) {
141601
- const clip = makeClipper();
141602
- const lines = [];
142539
+ const rendered = [];
141603
142540
  for (const event of events) {
142541
+ const clip = makeClipper();
141604
142542
  const text = describeHandoffEvent(event, implementerThreadId, clip);
141605
- if (text !== null) lines.push({
142543
+ if (text === null) continue;
142544
+ const content = text.replace(/^#\d+ /u, "");
142545
+ const shape = event.type === "thread.message-sent" ? "message" : "activity";
142546
+ rendered.push({
141606
142547
  sequence: event.sequence,
141607
- text
142548
+ text,
142549
+ clippedFields: clip.clipped,
142550
+ identity: fragmentIdentity(event),
142551
+ shape,
142552
+ content,
142553
+ fields: shape === "activity" ? activityFields(content) : []
141608
142554
  });
141609
142555
  }
142556
+ const superseded = supersededLines(rendered);
142557
+ const lines = rendered.filter((line) => !superseded.has(line));
141610
142558
  const kept = [];
141611
142559
  let used = 0;
141612
142560
  for (let index = lines.length - 1; index >= 0; index -= 1) {
@@ -141618,10 +142566,11 @@ function buildHandoffEvidence(events, implementerThreadId, maxChars) {
141618
142566
  }
141619
142567
  return {
141620
142568
  transcript: kept.map((line) => line.text).join("\n"),
141621
- eventCount: lines.length,
142569
+ eventCount: rendered.length,
141622
142570
  includedCount: kept.length,
141623
142571
  droppedCount: lines.length - kept.length,
141624
- clippedFields: clip.clipped,
142572
+ coalescedCount: superseded.size,
142573
+ clippedFields: kept.reduce((total, line) => total + line.clippedFields, 0),
141625
142574
  firstSequence: kept[0]?.sequence ?? null,
141626
142575
  lastSequence: kept[kept.length - 1]?.sequence ?? null,
141627
142576
  includedSequences: new Set(kept.map((line) => line.sequence))
@@ -141677,6 +142626,29 @@ const HANDOFF_SUMMARY_INSTRUCTIONS = [
141677
142626
  "cite at least one sequence, and never a sequence absent from the transcript",
141678
142627
  "name the user's requirements and corrections, and quote the exact text of any failure or error"
141679
142628
  ];
142629
+ /** Enough sequences to show the shape of a citation without dictating the summary. */
142630
+ const CITATION_EXAMPLE_COUNT = 2;
142631
+ /** The lowest and highest sequence handed over, as the citations they must be copied as. */
142632
+ function citationExamples(includedSequences) {
142633
+ const sorted = [...includedSequences].sort((left, right) => left - right);
142634
+ return (sorted.length <= CITATION_EXAMPLE_COUNT ? sorted : [sorted[0], sorted[sorted.length - 1]]).map((sequence) => `#${sequence}`);
142635
+ }
142636
+ /**
142637
+ * The citation rules carrying real sequences, because `#<sequence>` read as a
142638
+ * placeholder is what a summarizer answers without any number at all.
142639
+ */
142640
+ function handoffSummaryInstructions(input) {
142641
+ const examples = citationExamples(input.includedSequences);
142642
+ return examples.length === 0 ? HANDOFF_SUMMARY_INSTRUCTIONS : [...HANDOFF_SUMMARY_INSTRUCTIONS, `copy citations from the line starts of this transcript, for example ${examples.join(" and ")}`];
142643
+ }
142644
+ /**
142645
+ * The single repair attempt: the same rules, plus what the discarded answer
142646
+ * got wrong. Nothing here relaxes the rule - a second miss falls back.
142647
+ */
142648
+ function handoffSummaryRepairInstructions(input) {
142649
+ const invented = citedSequences(input.summary).filter((sequence) => !input.includedSequences.has(sequence)).slice(0, CITATION_EXAMPLE_COUNT).map((sequence) => `#${sequence}`);
142650
+ return [...handoffSummaryInstructions(input), input.rejection === "summary-uncited" || invented.length === 0 ? "your previous answer was discarded because it cited no sequence from the transcript; every claim needs one" : `your previous answer was discarded because it cited ${invented.join(" and ")}, which this transcript does not contain; never write a number that is not at a line start`];
142651
+ }
141680
142652
  /** How a summarized handoff opens, so a caller can tell one from a fallback. */
141681
142653
  const SERVER_HANDOFF_SUMMARY_PREFIX = "Server evidence handoff for builder thread";
141682
142654
  /**
@@ -141706,6 +142678,7 @@ const EMPTY_EVIDENCE = {
141706
142678
  eventCount: 0,
141707
142679
  includedCount: 0,
141708
142680
  droppedCount: 0,
142681
+ coalescedCount: 0,
141709
142682
  clippedFields: 0,
141710
142683
  firstSequence: null,
141711
142684
  lastSequence: null,
@@ -141765,19 +142738,20 @@ function resolveHandoffModel(settings) {
141765
142738
  };
141766
142739
  }
141767
142740
  /**
141768
- * Whether a summary may stand in for reading the range.
142741
+ * Why a summary may not stand in for reading the range, or null when it may.
141769
142742
  *
141770
- * Empty is refused for the obvious reason. Uncited is refused because a claim
141771
- * with no sequence behind it cannot be checked against the ledger, and a
141772
- * citation outside the window is refused because it is evidence of a model
141773
- * describing something other than this turn.
142743
+ * Empty and uncited are refused because a claim with no sequence behind it
142744
+ * cannot be checked against the ledger, and a citation outside the window is
142745
+ * refused because it is evidence of a model describing something other than
142746
+ * this turn. The two are named apart because they fail differently: one is a
142747
+ * summarizer that ignored the rule, the other one that read something else.
141774
142748
  */
141775
- function summaryIsUsable(input) {
142749
+ function summaryRejection(input) {
141776
142750
  const trimmed = input.summary.trim();
141777
- if (trimmed.length === 0) return false;
142751
+ if (trimmed.length === 0) return "summary-uncited";
141778
142752
  const cited = citedSequences(trimmed);
141779
- if (cited.length === 0) return false;
141780
- return cited.every((sequence) => input.includedSequences.has(sequence));
142753
+ if (cited.length === 0) return "summary-uncited";
142754
+ return cited.every((sequence) => input.includedSequences.has(sequence)) ? null : "summary-citation-invalid";
141781
142755
  }
141782
142756
  const buildFusionEvidenceHandoff = Effect.fn("fusionEvidenceHandoff.build")(function* (input) {
141783
142757
  const engine = yield* OrchestrationEngineService;
@@ -141833,20 +142807,51 @@ const buildFusionEvidenceHandoff = Effect.fn("fusionEvidenceHandoff.build")(func
141833
142807
  if (driver === void 0 || !HARDENED_SUMMARY_DRIVERS.has(driver)) return yield* fallback("provider-profile-unhardened");
141834
142808
  if (textGeneration.summarizeFusionEvidence === void 0) return yield* fallback("provider-cannot-summarize");
141835
142809
  const summarize = textGeneration.summarizeFusionEvidence;
141836
- const produced = yield* Effect.suspend(() => summarize({
142810
+ const runSummary = (instructions) => Effect.suspend(() => summarize({
141837
142811
  cwd: serverConfig.cwd,
141838
142812
  transcript: evidence.transcript,
141839
142813
  maxTranscriptChars: HANDOFF_TRANSCRIPT_MAX_CHARS,
141840
- instructions: HANDOFF_SUMMARY_INSTRUCTIONS,
142814
+ instructions,
141841
142815
  modelSelection: model
141842
- })).pipe(Effect.map((result) => Option.some(result)), Effect.timeoutOption(HANDOFF_TIMEOUT), Effect.map(Option.flatten), Effect.catchCause(() => Effect.succeed(Option.none())));
141843
- if (Option.isNone(produced)) return yield* fallback("summary-unavailable");
141844
- if (produced.value.agentName !== P4_SUMMARIZER_AGENT.name && produced.value.agentName !== "summarizer") return yield* fallback("summarizer-agent-mismatch");
141845
- if (produced.value.requestedModel !== model.model) return yield* fallback("summarizer-model-mismatch");
141846
- if (!summaryIsUsable({
141847
- summary: produced.value.summary,
141848
- includedSequences: evidence.includedSequences
141849
- })) return yield* fallback("summary-uncited");
142816
+ })).pipe(Effect.map((result) => Option.some(result)), Effect.catchCause(() => Effect.succeed(Option.none())));
142817
+ /** The hardened identity checks, which a repair may never talk its way past. */
142818
+ const identityRejection = (result) => result.agentName !== P4_SUMMARIZER_AGENT.name && result.agentName !== "summarizer" ? "summarizer-agent-mismatch" : result.requestedModel !== model.model ? "summarizer-model-mismatch" : null;
142819
+ const noSummary = (reason) => ({
142820
+ produced: Option.none(),
142821
+ reason
142822
+ });
142823
+ const attempt = yield* Effect.gen(function* () {
142824
+ const first = yield* runSummary(handoffSummaryInstructions({ includedSequences: evidence.includedSequences }));
142825
+ if (Option.isNone(first)) return noSummary("summary-unavailable");
142826
+ const identity = identityRejection(first.value);
142827
+ if (identity !== null) return noSummary(identity);
142828
+ const rejection = summaryRejection({
142829
+ summary: first.value.summary,
142830
+ includedSequences: evidence.includedSequences
142831
+ });
142832
+ if (rejection === null) return {
142833
+ produced: first,
142834
+ reason: null
142835
+ };
142836
+ const repaired = yield* runSummary(handoffSummaryRepairInstructions({
142837
+ rejection,
142838
+ summary: first.value.summary,
142839
+ includedSequences: evidence.includedSequences
142840
+ }));
142841
+ if (Option.isNone(repaired)) return noSummary(rejection);
142842
+ const repairedIdentity = identityRejection(repaired.value);
142843
+ if (repairedIdentity !== null) return noSummary(repairedIdentity);
142844
+ const repairedRejection = summaryRejection({
142845
+ summary: repaired.value.summary,
142846
+ includedSequences: evidence.includedSequences
142847
+ });
142848
+ return repairedRejection === null ? {
142849
+ produced: repaired,
142850
+ reason: null
142851
+ } : noSummary(repairedRejection);
142852
+ }).pipe(Effect.timeoutOption(HANDOFF_TIMEOUT), Effect.map(Option.getOrElse(() => noSummary("summary-unavailable"))));
142853
+ const produced = attempt.produced;
142854
+ if (Option.isNone(produced)) return yield* fallback(attempt.reason ?? "summary-unavailable");
141850
142855
  const preparedAt = DateTime.formatIso(yield* DateTime.now);
141851
142856
  return {
141852
142857
  status: "summarized",
@@ -142133,6 +143138,25 @@ const make$3 = Effect.gen(function* () {
142133
143138
  */
142134
143139
  const triageSkipStreak = /* @__PURE__ */ new Map();
142135
143140
  /**
143141
+ * Consecutive status turns coalesced per pair. In-memory like the counters
143142
+ * above, and safe there: the classification is recomputed from the same
143143
+ * events on replay, so a restart costs at most one extra wake rather than a
143144
+ * wrong one.
143145
+ */
143146
+ const statusCoalesceStreak = /* @__PURE__ */ new Map();
143147
+ /**
143148
+ * The monitor tasks each pair currently has open.
143149
+ *
143150
+ * A progress row carries no task type, so the only thing that makes it a
143151
+ * wait rather than unclassified work is the `task.started` that named it -
143152
+ * and that row belongs to an earlier turn, outside the window this turn is
143153
+ * described over. Held per pair so an open watch stays recognisable across
143154
+ * the turns it reports through, and dropped the moment the task ends.
143155
+ * In-memory like the counters above, and safe the same way: a restart
143156
+ * forgets the watch and costs one extra wake rather than a wrong one.
143157
+ */
143158
+ const openMonitorTaskIds = /* @__PURE__ */ new Map();
143159
+ /**
142136
143160
  * Gate detection and human-override resolution act on the CURRENT persisted
142137
143161
  * gate state, so replaying them against historical events is wrong twice
142138
143162
  * over: an old plan event would open a fresh gate, and an old user message
@@ -142551,15 +143575,28 @@ const make$3 = Effect.gen(function* () {
142551
143575
  /**
142552
143576
  * What the builder did between two review boundaries, read from the event
142553
143577
  * window rather than remembered: whether supervisor advice started the turn,
142554
- * whether a human spoke into it, and whether any tool ran. A text-only
142555
- * builder question counts as idle on purpose: one or two such rounds pass
142556
- * unhindered, and a longer tool-free exchange is a debate the user should
142557
- * see rather than fund.
143578
+ * whether a human spoke into it, whether any tool ran, and whether anything
143579
+ * happened that a supervisor has to see. A text-only builder question counts
143580
+ * as idle on purpose: one or two such rounds pass unhindered, and a longer
143581
+ * tool-free exchange is a debate the user should see rather than fund.
143582
+ *
143583
+ * `waitMarked`, `changedState`, `failedTool` and `gateActivity` are read from
143584
+ * activity kinds and typed task classifications alone; nothing here inspects
143585
+ * prose. Only a live, named wait marks a turn as waiting: a wake scheduled
143586
+ * for a real time, or a Monitor-tool task and its own progress. A command, a
143587
+ * background shell and an unnamed task all count as work, because any of
143588
+ * them can change the repository, and every ending - a cancelled wake, a
143589
+ * completed task, a failure, a decline - is a result rather than a wait.
142558
143590
  */
142559
143591
  const describeBuilderTurn = (input) => {
142560
143592
  let startedByAdvice = false;
142561
143593
  let humanMessage = false;
142562
143594
  let ranTools = false;
143595
+ let changedState = false;
143596
+ let failedTool = false;
143597
+ let gateActivity = false;
143598
+ let waitMarked = false;
143599
+ const monitorTaskIds = input.monitorTaskIds;
142563
143600
  let summary;
142564
143601
  const assistantMessages = /* @__PURE__ */ new Map();
142565
143602
  for (const event of input.events) {
@@ -142573,12 +143610,39 @@ const make$3 = Effect.gen(function* () {
142573
143610
  if (event.payload.text.startsWith("[fusion-advice]")) startedByAdvice = true;
142574
143611
  else if (!isFusionProtocolText(event.payload.text)) humanMessage = true;
142575
143612
  }
142576
- if (event.type === "thread.activity-appended" && event.payload.threadId === input.implementerThreadId && event.payload.activity.kind.startsWith("tool.")) ranTools = true;
143613
+ if (event.type === "thread.activity-appended" && event.payload.threadId === input.implementerThreadId) {
143614
+ const activity = event.payload.activity;
143615
+ const activityPayload = activity.payload ?? {};
143616
+ const unsuccessful = activityPayload.status === "failed" || activityPayload.status === "declined";
143617
+ if (activity.kind.startsWith("tool.")) {
143618
+ ranTools = true;
143619
+ if (activity.kind === "tool.denied" || unsuccessful) failedTool = true;
143620
+ else changedState = true;
143621
+ } else if (activity.kind === "wake.scheduled") if (activityPayload.wakeAt === null || activityPayload.wakeAt === void 0) changedState = true;
143622
+ else waitMarked = true;
143623
+ else if (activity.kind.startsWith("task.")) {
143624
+ if ((unsuccessful || activity.kind === "task.completed") && activityPayload.taskId !== void 0) monitorTaskIds.delete(activityPayload.taskId);
143625
+ if (unsuccessful) failedTool = true;
143626
+ else if (activity.kind === "task.completed") changedState = true;
143627
+ else if (activity.kind === "task.started") {
143628
+ const taskType = activityPayload.taskType;
143629
+ if (taskType !== void 0 && MONITOR_TOOL_TASK_TYPES.has(taskType)) {
143630
+ waitMarked = true;
143631
+ if (activityPayload.taskId !== void 0) monitorTaskIds.add(activityPayload.taskId);
143632
+ } else if (taskType === void 0 || !INERT_TASK_TYPES.has(taskType)) changedState = true;
143633
+ } else if (activity.kind === "task.progress" && activityPayload.taskId !== void 0 && monitorTaskIds.has(activityPayload.taskId)) waitMarked = true;
143634
+ }
143635
+ }
143636
+ if ((event.type === "thread-pair.gate-opened" || event.type === "thread-pair.gate-advanced" || event.type === "thread-pair.gate-resolved") && event.payload.pairId === input.pairId) gateActivity = true;
142577
143637
  }
142578
143638
  return {
142579
143639
  startedByAdvice,
142580
143640
  humanMessage,
142581
143641
  ranTools,
143642
+ changedState,
143643
+ failedTool,
143644
+ gateActivity,
143645
+ waitMarked,
142582
143646
  summary
142583
143647
  };
142584
143648
  };
@@ -143158,7 +144222,7 @@ const make$3 = Effect.gen(function* () {
143158
144222
  });
143159
144223
  return;
143160
144224
  }
143161
- const idleCycles = turn.startedByAdvice && !turn.humanMessage && !turn.ranTools ? (idleAdviceCycles.get(currentPair.id) ?? 0) + 1 : 0;
144225
+ const idleCycles = turn.startedByAdvice && !turn.humanMessage && !turn.ranTools && !turn.waitMarked && !turn.changedState && !turn.failedTool ? (idleAdviceCycles.get(currentPair.id) ?? 0) + 1 : 0;
143162
144226
  idleAdviceCycles.set(currentPair.id, idleCycles);
143163
144227
  if (idleCycles >= 3) {
143164
144228
  idleAdviceCycles.delete(currentPair.id);
@@ -143170,6 +144234,18 @@ const make$3 = Effect.gen(function* () {
143170
144234
  });
143171
144235
  return;
143172
144236
  }
144237
+ const statusOnly = turn.startedByAdvice && !turn.humanMessage && turn.waitMarked && !turn.changedState && !turn.failedTool && !turn.gateActivity;
144238
+ const streak = statusCoalesceStreak.get(currentPair.id);
144239
+ const alreadyCounted = streak !== void 0 && completion.sequence <= streak.throughSequence;
144240
+ const coalesced = statusOnly ? alreadyCounted ? streak.count : (streak?.count ?? 0) + 1 : 0;
144241
+ if (statusOnly && coalesced < 5) {
144242
+ statusCoalesceStreak.set(currentPair.id, {
144243
+ count: coalesced,
144244
+ throughSequence: Math.max(streak?.throughSequence ?? 0, completion.sequence)
144245
+ });
144246
+ return;
144247
+ }
144248
+ statusCoalesceStreak.delete(currentPair.id);
143173
144249
  const triageSettings = yield* readTriageSettings;
143174
144250
  if (triageSettings !== null) {
143175
144251
  if (currentPair.triagedFromSequence !== null && completion.sequence <= currentPair.triagedFromSequence) return;
@@ -143256,13 +144332,18 @@ const make$3 = Effect.gen(function* () {
143256
144332
  if (pair.reviewExperiment) completions = completions.filter((completion) => completion.payload.state === "completed").slice(-1);
143257
144333
  let boundary = activationCompletion?.sequence ?? pair.lastReviewedImplementerSequence;
143258
144334
  for (const completion of completions.filter((candidate) => candidate.payload.state === "completed")) {
144335
+ const monitorTaskIds = openMonitorTaskIds.get(pair.id) ?? /* @__PURE__ */ new Set();
143259
144336
  const turn = describeBuilderTurn({
143260
144337
  events,
143261
144338
  implementerThreadId: pair.implementerThreadId,
144339
+ pairId: pair.id,
143262
144340
  afterSequence: boundary,
143263
144341
  throughSequence: completion.sequence,
143264
- turnId: completion.payload.turnId
144342
+ turnId: completion.payload.turnId,
144343
+ monitorTaskIds
143265
144344
  });
144345
+ if (monitorTaskIds.size === 0) openMonitorTaskIds.delete(pair.id);
144346
+ else openMonitorTaskIds.set(pair.id, monitorTaskIds);
143266
144347
  boundary = completion.sequence;
143267
144348
  yield* processReview(pair, completion, turn);
143268
144349
  }
@@ -144891,11 +145972,11 @@ const WorkspaceEntriesLayerLive = layer$17.pipe(Layer.provide(layer$70));
144891
145972
  const WorkspaceFileSystemLayerLive = layer$16.pipe(Layer.provide(layer$70), Layer.provide(WorkspaceEntriesLayerLive));
144892
145973
  const WorkspaceLayerLive = Layer.mergeAll(layer$70, WorkspaceEntriesLayerLive, WorkspaceFileSystemLayerLive);
144893
145974
  const ProjectFaviconResolverLayerLive = layer$65.pipe(Layer.provide(layer$70), Layer.provide(layer$66));
144894
- const AuthLayerLive = layer$114.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$118));
145975
+ const AuthLayerLive = layer$115.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$119));
144895
145976
  const ProviderRuntimeLayerLive = Layer.mergeAll(ProviderSessionReaperLive, ProviderHealthRefresherLive).pipe(Layer.provideMerge(ProviderLayerLive), Layer.provideMerge(OrchestrationLayerLive));
144896
- const RuntimeCoreDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(layer$27), Layer.provideMerge(Layer.mergeAll(PullRequestServiceLive, CheckpointingLayerLive, SourceControlProviderRegistryLayerLive)), Layer.provideMerge(GitLayerLive), Layer.provideMerge(VcsLayerLive), Layer.provideMerge(ProviderAuthServiceLive), Layer.provideMerge(ProviderRuntimeLayerLive), Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive, RoutedTaskRepositoryLive)), Layer.provideMerge(Layer.mergeAll(PersistenceLayerLive, layer$89)), Layer.provideMerge(layer$83), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(layer$60), Layer.provideMerge(Layer.mergeAll(layer$59, layer$72.pipe(Layer.provide(layer$85)), layer$74, layer$69)), Layer.provideMerge(layer$54), Layer.provideMerge(layer$55), Layer.provideMerge(layer$56), Layer.provideMerge(layer$57), Layer.provideMerge(layer$58)).pipe(Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(AntigravityInstallation.layer), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$62), Layer.provideMerge(JevWorkspaceAdvisorLive.pipe(Layer.provide(TypeSafeClientLive))), Layer.provideMerge(layer$93.pipe(Layer.provide(layer$118))), Layer.provideMerge(SupervisorSecretsLive.pipe(Layer.provide(layer$118))), Layer.provideMerge(WorkspaceLayerLive), Layer.provideMerge(ProjectFaviconResolverLayerLive), Layer.provideMerge(layer$84), Layer.provideMerge(layer$68), Layer.provideMerge(remoteComputerUseBindingsLayer.pipe(Layer.provide(layer$89))), Layer.provideMerge(layer$77), Layer.provideMerge(AuthLayerLive), Layer.provideMerge(layer$5.pipe(Layer.provideMerge(LinearClientLive), Layer.provideMerge(Layer.mergeAll(LinearMcpClientLive, TrackerSecretsLive)), Layer.provideMerge(Layer.mergeAll(layer$90, layer$21).pipe(Layer.provideMerge(layer$91), Layer.provideMerge(layer$92))))), Layer.provideMerge(layer$118));
144897
- const UsageLayerLive = layer$25.pipe(Layer.provide(layer$93.pipe(Layer.provide(layer$118))));
144898
- const RuntimeDependenciesLive = layer$24.pipe(Layer.provideMerge(RuntimeCoreDependenciesLive)).pipe(Layer.provideMerge(UsageLayerLive), Layer.provideMerge(layer$12), Layer.provideMerge(layer$11), Layer.provideMerge(layer$10), Layer.provideMerge(layer$80), Layer.provideMerge(layer$82), Layer.provideMerge(layer$81), Layer.provide(layer$122));
145977
+ const RuntimeCoreDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(layer$27), Layer.provideMerge(Layer.mergeAll(PullRequestServiceLive, CheckpointingLayerLive, SourceControlProviderRegistryLayerLive)), Layer.provideMerge(GitLayerLive), Layer.provideMerge(VcsLayerLive), Layer.provideMerge(ProviderAuthServiceLive), Layer.provideMerge(ProviderRuntimeLayerLive), Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive, RoutedTaskRepositoryLive)), Layer.provideMerge(Layer.mergeAll(PersistenceLayerLive, layer$89)), Layer.provideMerge(layer$83), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(layer$60), Layer.provideMerge(Layer.mergeAll(layer$59, layer$72.pipe(Layer.provide(layer$85)), layer$74, layer$69)), Layer.provideMerge(layer$54), Layer.provideMerge(layer$55), Layer.provideMerge(layer$56), Layer.provideMerge(layer$57), Layer.provideMerge(layer$58)).pipe(Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(AntigravityInstallation.layer), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$62), Layer.provideMerge(JevWorkspaceAdvisorLive.pipe(Layer.provide(TypeSafeClientLive))), Layer.provideMerge(layer$93.pipe(Layer.provide(layer$119))), Layer.provideMerge(SupervisorSecretsLive.pipe(Layer.provide(layer$119))), Layer.provideMerge(WorkspaceLayerLive), Layer.provideMerge(ProjectFaviconResolverLayerLive), Layer.provideMerge(layer$84), Layer.provideMerge(layer$68), Layer.provideMerge(remoteComputerUseBindingsLayer.pipe(Layer.provide(layer$89))), Layer.provideMerge(layer$77), Layer.provideMerge(AuthLayerLive), Layer.provideMerge(layer$5.pipe(Layer.provideMerge(LinearClientLive), Layer.provideMerge(Layer.mergeAll(LinearMcpClientLive, TrackerSecretsLive)), Layer.provideMerge(Layer.mergeAll(layer$90, layer$21).pipe(Layer.provideMerge(layer$91), Layer.provideMerge(layer$92))))), Layer.provideMerge(layer$119));
145978
+ const UsageLayerLive = layer$25.pipe(Layer.provide(layer$93.pipe(Layer.provide(layer$119))));
145979
+ const RuntimeDependenciesLive = layer$24.pipe(Layer.provideMerge(RuntimeCoreDependenciesLive)).pipe(Layer.provideMerge(UsageLayerLive), Layer.provideMerge(layer$12), Layer.provideMerge(layer$11), Layer.provideMerge(layer$10), Layer.provideMerge(layer$80), Layer.provideMerge(layer$82), Layer.provideMerge(layer$81), Layer.provide(layer$123));
144899
145980
  /**
144900
145981
  * Hub asset sync.
144901
145982
  *
@@ -145547,7 +146628,7 @@ const serviceCommand = Command.make("service").pipe(Command.withDescription("Man
145547
146628
  ]));
145548
146629
  //#endregion
145549
146630
  //#region src/bin.ts
145550
- const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$122, FetchHttpClient.layer);
146631
+ const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$123, FetchHttpClient.layer);
145551
146632
  /**
145552
146633
  * Prints an update reminder before dispatching, at most once a day.
145553
146634
  *