@p4code/cli 0.6.2 → 0.6.4
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 +2354 -923
- package/dist/client/assets/{DiffPanel-Dr0Q8nc0.js → DiffPanel-DfuqXVpG.js} +3 -3
- package/dist/client/assets/{FilePreviewPanel-DF3Qm8mq.js → FilePreviewPanel-BT2cVEr7.js} +14 -14
- package/dist/client/assets/PreviewPanel-Cvzdihek.js +2 -0
- package/dist/client/assets/{PullRequestCodeTab-CmXZgH9P.js → PullRequestCodeTab-BWT2wgTE.js} +3 -3
- package/dist/client/assets/{fileCommentAnnotations-C24vUPt7.js → fileCommentAnnotations-j7Nl3tNy.js} +2 -2
- package/dist/client/assets/index-DE12bQJs.js +2982 -0
- package/dist/client/assets/{index-B_qrDEeL.css → index-mpULC1Of.css} +1 -1
- package/dist/client/assets/previewAssetResource-x8WpJWsS.js +48 -0
- package/dist/client/assets/renderFileChildren-BvcSiOwh.js +2 -0
- package/dist/client/assets/toggle-group-zU_XUcK4.js +2 -0
- package/dist/client/index.html +3 -3
- package/package.json +1 -1
- package/dist/client/assets/PreviewPanel-fURDlBeN.js +0 -2
- package/dist/client/assets/index-Bu7C-0KP.js +0 -2968
- package/dist/client/assets/previewAssetResource-CYZbubRz.js +0 -48
- package/dist/client/assets/renderFileChildren-CSWS4Las.js +0 -2
- package/dist/client/assets/toggle-group-DOifOCJp.js +0 -2
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$
|
|
266
|
+
const layer$123 = Layer.sync(NetService, make$122);
|
|
267
267
|
//#endregion
|
|
268
268
|
//#region package.json
|
|
269
|
-
var version$1 = "0.6.
|
|
269
|
+
var version$1 = "0.6.4";
|
|
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$
|
|
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
|
-
|
|
7871
|
-
"monitor_mcp",
|
|
8309
|
+
...MONITOR_TOOL_TASK_TYPES,
|
|
7872
8310
|
"local_bash",
|
|
7873
8311
|
"shell"
|
|
7874
8312
|
]);
|
|
@@ -12210,6 +12648,44 @@ const TicketResolveResult = Schema$1.Struct({
|
|
|
12210
12648
|
*/
|
|
12211
12649
|
description: Schema$1.optional(TrimmedNonEmptyString)
|
|
12212
12650
|
});
|
|
12651
|
+
/** One link filed alongside a comment: the proof, and what to call it. */
|
|
12652
|
+
const TicketCommentAttachment = Schema$1.Struct({
|
|
12653
|
+
url: TrimmedNonEmptyString.pipe(Schema$1.annotateEncoded({ description: "Publicly reachable URL of the proof to file against the ticket. Local file paths cannot be attached." })),
|
|
12654
|
+
title: Schema$1.optional(TrimmedNonEmptyString.pipe(Schema$1.annotateEncoded({ description: "Label for the link. Defaults to the URL itself." })))
|
|
12655
|
+
});
|
|
12656
|
+
/**
|
|
12657
|
+
* One comment on a ticket, with the links that belong to it.
|
|
12658
|
+
*
|
|
12659
|
+
* Commenting rather than editing: closeout writes what happened, and the
|
|
12660
|
+
* description belongs to whoever filed the ticket. The attachments ride along
|
|
12661
|
+
* because proof without the sentence explaining it is an orphan link.
|
|
12662
|
+
*/
|
|
12663
|
+
const TicketCommentInput = Schema$1.Struct({
|
|
12664
|
+
reference: TrimmedNonEmptyString.pipe(Schema$1.annotateEncoded({ description: "The ticket as it was written: an identifier like MOBILE-12241, or a tracker URL." })),
|
|
12665
|
+
body: TrimmedNonEmptyString.pipe(Schema$1.annotateEncoded({ description: "The comment, in Markdown, exactly as it should appear on the ticket." })),
|
|
12666
|
+
attachments: Schema$1.optional(Schema$1.Array(TicketCommentAttachment))
|
|
12667
|
+
});
|
|
12668
|
+
/** A link the tracker would not file, and the sentence explaining why. */
|
|
12669
|
+
const TicketCommentAttachmentFailure = Schema$1.Struct({
|
|
12670
|
+
url: TrimmedNonEmptyString,
|
|
12671
|
+
detail: Schema$1.String
|
|
12672
|
+
});
|
|
12673
|
+
const TicketCommentResult = Schema$1.Struct({
|
|
12674
|
+
externalRef: TaskExternalRef,
|
|
12675
|
+
/** Absent when the tracker filed the comment and reported no link to it. */
|
|
12676
|
+
commentUrl: Schema$1.optional(TrimmedNonEmptyString),
|
|
12677
|
+
/** The attachments that landed, in the order they were asked for. */
|
|
12678
|
+
attachedUrls: Schema$1.Array(TrimmedNonEmptyString),
|
|
12679
|
+
/**
|
|
12680
|
+
* The attachments that did not land.
|
|
12681
|
+
*
|
|
12682
|
+
* Reported rather than thrown, because the comment is already on the ticket
|
|
12683
|
+
* by the time a link can fail: a caller told only "it failed" retries the
|
|
12684
|
+
* whole call and files the comment twice. With this it can retry the links
|
|
12685
|
+
* alone, or say in the thread which proof is missing.
|
|
12686
|
+
*/
|
|
12687
|
+
failedAttachments: Schema$1.Array(TicketCommentAttachmentFailure)
|
|
12688
|
+
});
|
|
12213
12689
|
/**
|
|
12214
12690
|
* Why a reference could not be turned into a ticket.
|
|
12215
12691
|
*
|
|
@@ -12568,251 +13044,6 @@ var ThreadAskFailedError = class extends Schema$1.TaggedErrorClass()("ThreadAskF
|
|
|
12568
13044
|
}
|
|
12569
13045
|
};
|
|
12570
13046
|
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
13047
|
//#endregion
|
|
12817
13048
|
//#region ../../packages/contracts/src/threadControl.ts
|
|
12818
13049
|
/**
|
|
@@ -14412,10 +14643,19 @@ const UserInputRankingInput = Schema$1.Struct({
|
|
|
14412
14643
|
requestId: ApprovalRequestId,
|
|
14413
14644
|
questionId: Schema$1.String
|
|
14414
14645
|
});
|
|
14415
|
-
|
|
14416
|
-
|
|
14417
|
-
|
|
14418
|
-
|
|
14646
|
+
/**
|
|
14647
|
+
* `probability` is the option's share of the ranker's distribution and
|
|
14648
|
+
* `confidence` how concentrated that distribution is. Both come from the
|
|
14649
|
+
* TypeSafe answer; neither is invented when the answer carries none.
|
|
14650
|
+
*/
|
|
14651
|
+
const UserInputRankingResult = Schema$1.Struct({
|
|
14652
|
+
confidence: Schema$1.Number,
|
|
14653
|
+
ranks: Schema$1.Array(Schema$1.Struct({
|
|
14654
|
+
optionIndex: Schema$1.Int,
|
|
14655
|
+
rank: Schema$1.Int,
|
|
14656
|
+
probability: Schema$1.Number
|
|
14657
|
+
}))
|
|
14658
|
+
});
|
|
14419
14659
|
var UserInputRankingError = class extends Schema$1.TaggedErrorClass()("UserInputRankingError", { detail: Schema$1.String }) {};
|
|
14420
14660
|
//#endregion
|
|
14421
14661
|
//#region ../../packages/contracts/src/providerSetup.ts
|
|
@@ -14928,6 +15168,7 @@ const WS_METHODS = {
|
|
|
14928
15168
|
hubSetSyncMode: "hub.setSyncMode",
|
|
14929
15169
|
hubSetShareMode: "hub.setShareMode",
|
|
14930
15170
|
hubMintToken: "hub.mintToken",
|
|
15171
|
+
hubAnalyticsQuery: "hub.analyticsQuery",
|
|
14931
15172
|
settingsSyncNow: "settingsSync.now",
|
|
14932
15173
|
storageCleanupPreview: "storageCleanup.preview",
|
|
14933
15174
|
storageCleanupSweep: "storageCleanup.sweep",
|
|
@@ -15141,6 +15382,19 @@ const WsWorktreeCleanupSweepRpc = Rpc.make(WS_METHODS.worktreeCleanupSweep, {
|
|
|
15141
15382
|
success: WorktreeCleanupPreview,
|
|
15142
15383
|
error: Schema$1.Union([EnvironmentAuthorizationError])
|
|
15143
15384
|
});
|
|
15385
|
+
/**
|
|
15386
|
+
* The account's own analytics facts, read through this server.
|
|
15387
|
+
*
|
|
15388
|
+
* The hub credential belongs to the environment, not to a browser tab, so the
|
|
15389
|
+
* client never holds it: it asks its own server, which is already the only
|
|
15390
|
+
* thing that can authenticate against the hub. Read-only, and the query is the
|
|
15391
|
+
* hub's own contract, so a filter the hub rejects is rejected here too.
|
|
15392
|
+
*/
|
|
15393
|
+
const WsHubAnalyticsQueryRpc = Rpc.make(WS_METHODS.hubAnalyticsQuery, {
|
|
15394
|
+
payload: HubAnalyticsQuery,
|
|
15395
|
+
success: HubAnalyticsPage,
|
|
15396
|
+
error: HubRpcError
|
|
15397
|
+
});
|
|
15144
15398
|
const WsHubGetSyncStatusRpc = Rpc.make(WS_METHODS.hubGetSyncStatus, {
|
|
15145
15399
|
payload: Schema$1.Struct({}),
|
|
15146
15400
|
success: AgentSyncStatus,
|
|
@@ -16092,7 +16346,7 @@ const WsSubscribeAuthAccessRpc = Rpc.make(WS_METHODS.subscribeAuthAccess, {
|
|
|
16092
16346
|
error: Schema$1.Union([AuthAccessStreamError, EnvironmentAuthorizationError]),
|
|
16093
16347
|
stream: true
|
|
16094
16348
|
});
|
|
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);
|
|
16349
|
+
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
16350
|
//#endregion
|
|
16097
16351
|
//#region ../../packages/shared/src/oauthScope.ts
|
|
16098
16352
|
const OAUTH_SCOPE_TOKEN = /^[\u0021\u0023-\u005b\u005d-\u007e]+$/u;
|
|
@@ -16256,7 +16510,7 @@ const make$120 = Effect.gen(function* () {
|
|
|
16256
16510
|
};
|
|
16257
16511
|
return EnvironmentAuthPolicy.of({ getDescriptor: () => Effect.succeed(descriptor).pipe(Effect.withSpan("EnvironmentAuthPolicy.getDescriptor")) });
|
|
16258
16512
|
});
|
|
16259
|
-
const layer$
|
|
16513
|
+
const layer$121 = Layer.effect(EnvironmentAuthPolicy, make$120);
|
|
16260
16514
|
//#endregion
|
|
16261
16515
|
//#region src/persistence/Errors.ts
|
|
16262
16516
|
function summarizeSchemaIssue(issue) {
|
|
@@ -16571,7 +16825,7 @@ const make$119 = Effect.gen(function* () {
|
|
|
16571
16825
|
setLastConnectedAt
|
|
16572
16826
|
};
|
|
16573
16827
|
});
|
|
16574
|
-
const layer$
|
|
16828
|
+
const layer$120 = Layer.effect(AuthSessionRepository, make$119);
|
|
16575
16829
|
//#endregion
|
|
16576
16830
|
//#region src/auth/ServerSecretStore.ts
|
|
16577
16831
|
const secretStoreErrorContext = {
|
|
@@ -16717,7 +16971,7 @@ const make$118 = Effect.gen(function* () {
|
|
|
16717
16971
|
const config = yield* ServerConfig$1;
|
|
16718
16972
|
return yield* makeForDirectory(config.secretsDir);
|
|
16719
16973
|
});
|
|
16720
|
-
const layer$
|
|
16974
|
+
const layer$119 = Layer.effect(ServerSecretStore, make$118);
|
|
16721
16975
|
//#endregion
|
|
16722
16976
|
//#region src/auth/SessionStore.ts
|
|
16723
16977
|
var MalformedSessionTokenError = class extends Schema$1.TaggedErrorClass()("MalformedSessionTokenError", {}) {
|
|
@@ -17270,7 +17524,7 @@ const make$117 = Effect.gen(function* () {
|
|
|
17270
17524
|
markDisconnected
|
|
17271
17525
|
});
|
|
17272
17526
|
});
|
|
17273
|
-
const layer$
|
|
17527
|
+
const layer$118 = Layer.effect(SessionStore, make$117).pipe(Layer.provideMerge(layer$120));
|
|
17274
17528
|
//#endregion
|
|
17275
17529
|
//#region src/persistence/AuthPairingLinks.ts
|
|
17276
17530
|
const AuthPairingLinkRecord = Schema$1.Struct({
|
|
@@ -17466,7 +17720,7 @@ const make$116 = Effect.gen(function* () {
|
|
|
17466
17720
|
getByCredential
|
|
17467
17721
|
};
|
|
17468
17722
|
});
|
|
17469
|
-
const layer$
|
|
17723
|
+
const layer$117 = Layer.effect(AuthPairingLinkRepository, make$116);
|
|
17470
17724
|
//#endregion
|
|
17471
17725
|
//#region src/auth/PairingGrantStore.ts
|
|
17472
17726
|
var UnknownBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("UnknownBootstrapCredentialError", {}) {
|
|
@@ -17759,7 +18013,7 @@ const make$115 = Effect.gen(function* () {
|
|
|
17759
18013
|
consume
|
|
17760
18014
|
});
|
|
17761
18015
|
});
|
|
17762
|
-
const layer$
|
|
18016
|
+
const layer$116 = Layer.effect(PairingGrantStore, make$115).pipe(Layer.provideMerge(layer$117));
|
|
17763
18017
|
//#endregion
|
|
17764
18018
|
//#region src/persistence/DatabaseSnapshot.ts
|
|
17765
18019
|
/**
|
|
@@ -17901,6 +18155,21 @@ var _060_ThreadPairTriageCursor_default = Effect.gen(function* () {
|
|
|
17901
18155
|
yield* (yield* SqlClient.SqlClient)`ALTER TABLE thread_pairs ADD COLUMN triaged_from_sequence INTEGER`;
|
|
17902
18156
|
});
|
|
17903
18157
|
//#endregion
|
|
18158
|
+
//#region src/persistence/Migrations/061_AnalyticsFactOutbox.ts
|
|
18159
|
+
var _061_AnalyticsFactOutbox_default = Effect.gen(function* () {
|
|
18160
|
+
const sql = yield* SqlClient.SqlClient;
|
|
18161
|
+
yield* sql`CREATE TABLE analytics_fact_outbox (
|
|
18162
|
+
event_id TEXT PRIMARY KEY, kind TEXT NOT NULL, version INTEGER NOT NULL,
|
|
18163
|
+
occurred_at TEXT NOT NULL, payload TEXT NOT NULL,
|
|
18164
|
+
pending INTEGER NOT NULL DEFAULT 1, created_at INTEGER NOT NULL
|
|
18165
|
+
)`;
|
|
18166
|
+
yield* sql`CREATE INDEX analytics_fact_outbox_pending
|
|
18167
|
+
ON analytics_fact_outbox(pending, created_at, event_id)`;
|
|
18168
|
+
yield* sql`CREATE TABLE analytics_fact_cursors (
|
|
18169
|
+
source TEXT PRIMARY KEY, after_sequence INTEGER NOT NULL
|
|
18170
|
+
)`;
|
|
18171
|
+
});
|
|
18172
|
+
//#endregion
|
|
17904
18173
|
//#region src/persistence/Migrations/001_OrchestrationEvents.ts
|
|
17905
18174
|
var _001_OrchestrationEvents_default = Effect.gen(function* () {
|
|
17906
18175
|
const sql = yield* SqlClient.SqlClient;
|
|
@@ -20082,6 +20351,11 @@ const migrationEntries = [
|
|
|
20082
20351
|
60,
|
|
20083
20352
|
"ThreadPairTriageCursor",
|
|
20084
20353
|
_060_ThreadPairTriageCursor_default
|
|
20354
|
+
],
|
|
20355
|
+
[
|
|
20356
|
+
61,
|
|
20357
|
+
"AnalyticsFactOutbox",
|
|
20358
|
+
_061_AnalyticsFactOutbox_default
|
|
20085
20359
|
]
|
|
20086
20360
|
];
|
|
20087
20361
|
const makeMigrationLoader = (throughId) => Migrator.fromRecord(Object.fromEntries(migrationEntries.filter(([id]) => throughId === void 0 || id <= throughId).map(([id, name, migration]) => [`${id}_${name}`, migration])));
|
|
@@ -20429,22 +20703,22 @@ function classifyToolCategory(input) {
|
|
|
20429
20703
|
if (normalized.includes("image")) return "image_view";
|
|
20430
20704
|
return "tool";
|
|
20431
20705
|
}
|
|
20432
|
-
function asRecord$
|
|
20706
|
+
function asRecord$12(value) {
|
|
20433
20707
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
20434
20708
|
}
|
|
20435
20709
|
/** Classify from a runtime item payload's `data` (`{ toolName, input }`). */
|
|
20436
20710
|
function classifyToolCategoryFromToolData(data) {
|
|
20437
|
-
const record = asRecord$
|
|
20711
|
+
const record = asRecord$12(data);
|
|
20438
20712
|
const toolName = record?.toolName;
|
|
20439
20713
|
if (typeof toolName !== "string" || toolName.trim().length === 0) return;
|
|
20440
20714
|
return classifyToolCategory({
|
|
20441
20715
|
toolName,
|
|
20442
|
-
toolInput: asRecord$
|
|
20716
|
+
toolInput: asRecord$12(record?.input)
|
|
20443
20717
|
});
|
|
20444
20718
|
}
|
|
20445
20719
|
//#endregion
|
|
20446
20720
|
//#region src/orchestration/ActivityPayloadProjection.ts
|
|
20447
|
-
function asRecord$
|
|
20721
|
+
function asRecord$11(value) {
|
|
20448
20722
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
20449
20723
|
}
|
|
20450
20724
|
function asTrimmedString$2(value) {
|
|
@@ -20474,7 +20748,7 @@ function compactMcpRecord(record) {
|
|
|
20474
20748
|
const entries = Object.entries(record);
|
|
20475
20749
|
const projected = {};
|
|
20476
20750
|
for (const [key, value] of entries.slice(0, MCP_TOOL_ACTIVITY_FIELD_LIMIT)) {
|
|
20477
|
-
const nestedItem = key === "item" ? asRecord$
|
|
20751
|
+
const nestedItem = key === "item" ? asRecord$11(value) : null;
|
|
20478
20752
|
projected[key] = nestedItem === null ? compactMcpField(value) : compactMcpRecord(nestedItem);
|
|
20479
20753
|
}
|
|
20480
20754
|
if (entries.length > MCP_TOOL_ACTIVITY_FIELD_LIMIT) projected.truncatedFields = entries.length - MCP_TOOL_ACTIVITY_FIELD_LIMIT;
|
|
@@ -20500,7 +20774,7 @@ function collectChangedFiles(value, target, seen, depth) {
|
|
|
20500
20774
|
}
|
|
20501
20775
|
return;
|
|
20502
20776
|
}
|
|
20503
|
-
const record = asRecord$
|
|
20777
|
+
const record = asRecord$11(value);
|
|
20504
20778
|
if (!record) return;
|
|
20505
20779
|
pushChangedFile(target, seen, record.path);
|
|
20506
20780
|
pushChangedFile(target, seen, record.filePath);
|
|
@@ -20526,13 +20800,13 @@ function collectChangedFiles(value, target, seen, depth) {
|
|
|
20526
20800
|
}
|
|
20527
20801
|
}
|
|
20528
20802
|
function projectCommandData(data) {
|
|
20529
|
-
const item = asRecord$
|
|
20803
|
+
const item = asRecord$11(data.item);
|
|
20530
20804
|
if (!item) return;
|
|
20531
20805
|
const projectedItem = {};
|
|
20532
20806
|
if ("command" in item) projectedItem.command = item.command;
|
|
20533
|
-
const input = asRecord$
|
|
20807
|
+
const input = asRecord$11(item.input);
|
|
20534
20808
|
if (input && "command" in input) projectedItem.input = { command: input.command };
|
|
20535
|
-
const result = asRecord$
|
|
20809
|
+
const result = asRecord$11(item.result);
|
|
20536
20810
|
if (result && "command" in result) projectedItem.result = { command: result.command };
|
|
20537
20811
|
return Object.keys(projectedItem).length > 0 ? projectedItem : void 0;
|
|
20538
20812
|
}
|
|
@@ -20556,7 +20830,7 @@ function summarizeToolTextOutput(value) {
|
|
|
20556
20830
|
return meaningfulLineCount > 1 ? `${meaningfulLineCount.toLocaleString()} lines` : null;
|
|
20557
20831
|
}
|
|
20558
20832
|
function projectRawOutput(value) {
|
|
20559
|
-
const rawOutput = asRecord$
|
|
20833
|
+
const rawOutput = asRecord$11(value);
|
|
20560
20834
|
if (!rawOutput) return;
|
|
20561
20835
|
if (typeof rawOutput.totalFiles === "number" && Number.isFinite(rawOutput.totalFiles)) return {
|
|
20562
20836
|
totalFiles: rawOutput.totalFiles,
|
|
@@ -20579,8 +20853,8 @@ function projectRawOutput(value) {
|
|
|
20579
20853
|
* tool outputs do not accumulate twice in the event store and activity projection.
|
|
20580
20854
|
*/
|
|
20581
20855
|
function projectActivityPayload(activity) {
|
|
20582
|
-
const payload = asRecord$
|
|
20583
|
-
const data = asRecord$
|
|
20856
|
+
const payload = asRecord$11(activity.payload);
|
|
20857
|
+
const data = asRecord$11(payload?.data);
|
|
20584
20858
|
if (!payload || !data) return activity;
|
|
20585
20859
|
if (payload.itemType === "mcp_tool_call") {
|
|
20586
20860
|
const projectedMcpData = projectMcpToolCallData(data);
|
|
@@ -20597,7 +20871,7 @@ function projectActivityPayload(activity) {
|
|
|
20597
20871
|
const item = projectCommandData(data);
|
|
20598
20872
|
if (item) projectedData.item = item;
|
|
20599
20873
|
if ("command" in data) projectedData.command = data.command;
|
|
20600
|
-
const input = asRecord$
|
|
20874
|
+
const input = asRecord$11(data.input);
|
|
20601
20875
|
if (input) {
|
|
20602
20876
|
const projectedInput = {};
|
|
20603
20877
|
if ("command" in input) projectedInput.command = input.command;
|
|
@@ -20637,7 +20911,7 @@ function projectActivityPayload(activity) {
|
|
|
20637
20911
|
*/
|
|
20638
20912
|
function isResolvableContextWindowActivity(activity) {
|
|
20639
20913
|
if (activity.kind !== "context-window.updated") return false;
|
|
20640
|
-
const usedTokens = asRecord$
|
|
20914
|
+
const usedTokens = asRecord$11(activity.payload)?.usedTokens;
|
|
20641
20915
|
return typeof usedTokens === "number" && Number.isFinite(usedTokens) && usedTokens >= 0;
|
|
20642
20916
|
}
|
|
20643
20917
|
/**
|
|
@@ -20653,7 +20927,7 @@ function isResolvableContextWindowActivity(activity) {
|
|
|
20653
20927
|
* client.
|
|
20654
20928
|
*/
|
|
20655
20929
|
function withoutContextWindowBreakdown$1(activity) {
|
|
20656
|
-
const payload = asRecord$
|
|
20930
|
+
const payload = asRecord$11(activity.payload);
|
|
20657
20931
|
if (!payload || payload.breakdown === void 0) return activity;
|
|
20658
20932
|
const { breakdown: _breakdown, ...rest } = payload;
|
|
20659
20933
|
return {
|
|
@@ -20668,7 +20942,7 @@ function dropStaleContextWindowActivities(activities) {
|
|
|
20668
20942
|
const retainedIndexes = new Set(latestIndexByTurn.values());
|
|
20669
20943
|
let breakdownIndex = null;
|
|
20670
20944
|
for (const index of retainedIndexes) {
|
|
20671
|
-
if (asRecord$
|
|
20945
|
+
if (asRecord$11(activities[index].payload)?.breakdown === void 0) continue;
|
|
20672
20946
|
if (breakdownIndex === null || index > breakdownIndex) breakdownIndex = index;
|
|
20673
20947
|
}
|
|
20674
20948
|
return activities.flatMap((activity, index) => {
|
|
@@ -20678,12 +20952,12 @@ function dropStaleContextWindowActivities(activities) {
|
|
|
20678
20952
|
});
|
|
20679
20953
|
}
|
|
20680
20954
|
function toolLifecycleIdentity(activity) {
|
|
20681
|
-
return asTrimmedString$2(asRecord$
|
|
20955
|
+
return asTrimmedString$2(asRecord$11(asRecord$11(activity.payload)?.data)?.toolCallId);
|
|
20682
20956
|
}
|
|
20683
20957
|
/** Clients retain fields missing from a completion when folding an update. */
|
|
20684
20958
|
function completionPreservesUpdate(update, completion) {
|
|
20685
|
-
const updatePayload = asRecord$
|
|
20686
|
-
const completionPayload = asRecord$
|
|
20959
|
+
const updatePayload = asRecord$11(update.payload);
|
|
20960
|
+
const completionPayload = asRecord$11(completion.payload);
|
|
20687
20961
|
if (!updatePayload || !completionPayload) return false;
|
|
20688
20962
|
return Object.entries(updatePayload).every(([key, value]) => key === "status" || NodeUtil.isDeepStrictEqual(value, completionPayload[key]));
|
|
20689
20963
|
}
|
|
@@ -20722,13 +20996,13 @@ function dropSupersededEvidenceSummaries(activities) {
|
|
|
20722
20996
|
for (let index = 0; index < activities.length; index += 1) {
|
|
20723
20997
|
const activity = activities[index];
|
|
20724
20998
|
if (activity.kind !== "fusion.evidence.summary") continue;
|
|
20725
|
-
const watchedThreadId = asRecord$
|
|
20999
|
+
const watchedThreadId = asRecord$11(activity.payload)?.watchedThreadId;
|
|
20726
21000
|
if (typeof watchedThreadId === "string") latestIndexByWatched.set(watchedThreadId, index);
|
|
20727
21001
|
}
|
|
20728
21002
|
if (latestIndexByWatched.size === 0) return activities;
|
|
20729
21003
|
return activities.map((activity, index) => {
|
|
20730
21004
|
if (activity.kind !== "fusion.evidence.summary") return activity;
|
|
20731
|
-
const payload = asRecord$
|
|
21005
|
+
const payload = asRecord$11(activity.payload);
|
|
20732
21006
|
const watchedThreadId = payload?.watchedThreadId;
|
|
20733
21007
|
if (!payload || typeof watchedThreadId !== "string" || latestIndexByWatched.get(watchedThreadId) === index || payload.summary === null || payload.summary === void 0) return activity;
|
|
20734
21008
|
return {
|
|
@@ -21339,9 +21613,9 @@ const make$114 = Effect.gen(function* () {
|
|
|
21339
21613
|
issueStartupPairingUrl
|
|
21340
21614
|
});
|
|
21341
21615
|
});
|
|
21342
|
-
const layer$
|
|
21343
|
-
const storageLayer = Layer.mergeAll(layer$
|
|
21344
|
-
const runtimeLayer = layer$
|
|
21616
|
+
const layer$115 = Layer.effect(EnvironmentAuth, make$114).pipe(Layer.provideMerge(layer$116), Layer.provideMerge(layer$118), Layer.provideMerge(layer$121));
|
|
21617
|
+
const storageLayer = Layer.mergeAll(layer$119, layerConfig);
|
|
21618
|
+
const runtimeLayer = layer$115.pipe(Layer.provideMerge(storageLayer));
|
|
21345
21619
|
//#endregion
|
|
21346
21620
|
//#region ../../packages/shared/src/Struct.ts
|
|
21347
21621
|
function deepMerge(current, patch) {
|
|
@@ -23189,7 +23463,7 @@ const runWithEnvironmentAuth = (flags, run, options) => Effect.gen(function* ()
|
|
|
23189
23463
|
}
|
|
23190
23464
|
return yield* Effect.gen(function* () {
|
|
23191
23465
|
return yield* run(yield* EnvironmentAuth);
|
|
23192
|
-
}).pipe(Effect.provide(Layer.mergeAll(runtimeLayer).pipe(Layer.provide(layer$
|
|
23466
|
+
}).pipe(Effect.provide(Layer.mergeAll(runtimeLayer).pipe(Layer.provide(layer$122(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
|
|
23193
23467
|
});
|
|
23194
23468
|
const ttlFlag = Flag.string("ttl").pipe(Flag.withSchema(DurationFromString), Flag.withDescription("TTL, for example `5m`, `1h`, `30d`, or `15 minutes`."), Flag.optional);
|
|
23195
23469
|
const jsonFlag = Flag.boolean("json").pipe(Flag.withDescription("Emit JSON instead of human-readable output."), Flag.withDefault(false));
|
|
@@ -23349,7 +23623,7 @@ const make$113 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_L
|
|
|
23349
23623
|
})
|
|
23350
23624
|
});
|
|
23351
23625
|
});
|
|
23352
|
-
const layer$
|
|
23626
|
+
const layer$114 = Layer.effect(HubAuthThrottle, make$113());
|
|
23353
23627
|
//#endregion
|
|
23354
23628
|
//#region src/hub/HubAuth.ts
|
|
23355
23629
|
/**
|
|
@@ -24122,6 +24396,88 @@ const makeTaskRepository = Effect.gen(function* () {
|
|
|
24122
24396
|
});
|
|
24123
24397
|
const TaskRepositoryLive = Layer.effect(TaskRepository, makeTaskRepository);
|
|
24124
24398
|
//#endregion
|
|
24399
|
+
//#region src/hub/HubClientCors.ts
|
|
24400
|
+
const MAX_ORIGIN_CHARS = 2048;
|
|
24401
|
+
const DESKTOP_RENDERER_ORIGINS$1 = /* @__PURE__ */ new Set(["p4code://app", "p4code-dev://app"]);
|
|
24402
|
+
const MAX_PREFLIGHT_HEADER_CHARS = 128;
|
|
24403
|
+
const ALLOWED_HEADERS = /* @__PURE__ */ new Set([
|
|
24404
|
+
"authorization",
|
|
24405
|
+
"content-type",
|
|
24406
|
+
"accept"
|
|
24407
|
+
]);
|
|
24408
|
+
const CLIENT_ROUTES = {
|
|
24409
|
+
"/v1/client-enrollments/exchange": ["POST"],
|
|
24410
|
+
"/v1/clients/me": ["GET", "DELETE"],
|
|
24411
|
+
"/v1/clients/me/events": ["GET"],
|
|
24412
|
+
"/v1/clients/me/socket-ticket": ["POST"],
|
|
24413
|
+
"/v1/clients/me/asset-grant": ["POST"],
|
|
24414
|
+
"/v1/clients/me/notifications": [
|
|
24415
|
+
"GET",
|
|
24416
|
+
"PUT",
|
|
24417
|
+
"DELETE"
|
|
24418
|
+
],
|
|
24419
|
+
"/v1/clients/me/notifications/preferences": ["PUT"],
|
|
24420
|
+
"/v1/clients/me/live-activities": ["PUT", "DELETE"]
|
|
24421
|
+
};
|
|
24422
|
+
const routeMethods = (path) => Object.hasOwn(CLIENT_ROUTES, path) ? CLIENT_ROUTES[path] : void 0;
|
|
24423
|
+
const denied$2 = () => HttpServerResponse.jsonUnsafe({ error: "invalid_client_origin" }, {
|
|
24424
|
+
status: 403,
|
|
24425
|
+
headers: {
|
|
24426
|
+
"cache-control": "no-store",
|
|
24427
|
+
vary: "Origin"
|
|
24428
|
+
}
|
|
24429
|
+
});
|
|
24430
|
+
/** Arbitrary self-hosted browser clients are allowed; authentication uses explicit credentials, never cookies. */
|
|
24431
|
+
function hubClientCorsOrigin(value) {
|
|
24432
|
+
if (DESKTOP_RENDERER_ORIGINS$1.has(value)) return value;
|
|
24433
|
+
if (value.length > MAX_ORIGIN_CHARS) return void 0;
|
|
24434
|
+
try {
|
|
24435
|
+
const url = new URL(value);
|
|
24436
|
+
if (url.protocol !== "https:" && url.protocol !== "http:" || value !== url.origin || url.username || url.password) return void 0;
|
|
24437
|
+
return url.origin;
|
|
24438
|
+
} catch {
|
|
24439
|
+
return;
|
|
24440
|
+
}
|
|
24441
|
+
}
|
|
24442
|
+
/** Wrap only client-authenticated routes, after their generic failure mapping. */
|
|
24443
|
+
const withHubClientCors = (operation) => Effect.gen(function* () {
|
|
24444
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
24445
|
+
const path = request.url.split("?")[0];
|
|
24446
|
+
if (!routeMethods(path)?.includes(request.method)) return denied$2();
|
|
24447
|
+
const raw = request.headers.origin;
|
|
24448
|
+
if (raw === void 0) return yield* operation;
|
|
24449
|
+
const origin = hubClientCorsOrigin(raw);
|
|
24450
|
+
if (!origin) return denied$2();
|
|
24451
|
+
const response = yield* operation;
|
|
24452
|
+
return response.pipe(HttpServerResponse.setHeaders({
|
|
24453
|
+
"access-control-allow-origin": origin,
|
|
24454
|
+
"access-control-expose-headers": "retry-after",
|
|
24455
|
+
"cache-control": "no-store",
|
|
24456
|
+
vary: response.headers.vary ? `${response.headers.vary}, Origin` : "Origin"
|
|
24457
|
+
}));
|
|
24458
|
+
});
|
|
24459
|
+
const hubClientPreflight = Effect.gen(function* () {
|
|
24460
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
24461
|
+
const origin = hubClientCorsOrigin(request.headers.origin ?? "");
|
|
24462
|
+
const methods = routeMethods(request.url);
|
|
24463
|
+
const method = request.headers["access-control-request-method"];
|
|
24464
|
+
const raw = request.headers["access-control-request-headers"] ?? "";
|
|
24465
|
+
if (raw.length > MAX_PREFLIGHT_HEADER_CHARS) return denied$2();
|
|
24466
|
+
const headers = raw === "" ? [] : raw.split(",").map((value) => value.trim().toLowerCase());
|
|
24467
|
+
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();
|
|
24468
|
+
return HttpServerResponse.empty({
|
|
24469
|
+
status: 204,
|
|
24470
|
+
headers: {
|
|
24471
|
+
"access-control-allow-origin": origin,
|
|
24472
|
+
"access-control-allow-methods": methods.join(", "),
|
|
24473
|
+
"access-control-allow-headers": "authorization, content-type, accept",
|
|
24474
|
+
"cache-control": "no-store",
|
|
24475
|
+
vary: "Origin, Access-Control-Request-Method, Access-Control-Request-Headers"
|
|
24476
|
+
}
|
|
24477
|
+
});
|
|
24478
|
+
});
|
|
24479
|
+
const layer$113 = Layer.mergeAll(Layer.empty, ...Object.keys(CLIENT_ROUTES).map((path) => HttpRouter.add("OPTIONS", path, hubClientPreflight)));
|
|
24480
|
+
//#endregion
|
|
24125
24481
|
//#region src/hub/HubClientRevocations.ts
|
|
24126
24482
|
var HubClientRevocations = class extends Context.Service()("@p4code/cli/hub/HubClientRevocations") {};
|
|
24127
24483
|
/** Coalesced invalidations carry no identity or credential; consumers recheck durable state. */
|
|
@@ -24282,88 +24638,6 @@ const signOutHubClient = Effect.fn("HubClients.signOut")(function* (credential)
|
|
|
24282
24638
|
WHERE id = ${principal.clientId} AND credential_hash = ${hashHubToken(credential)}`;
|
|
24283
24639
|
})).pipe(Effect.ensuring(notifications.changed));
|
|
24284
24640
|
});
|
|
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
24641
|
const HUB_TUNNEL_MAX_FRAME_PAYLOAD_BYTES = 64 * 1024;
|
|
24368
24642
|
const HUB_TUNNEL_MAX_MESSAGE_BYTES = 128 * 1024 * 1024;
|
|
24369
24643
|
const HUB_TUNNEL_FRAME_MAGIC = 1345606705;
|
|
@@ -24650,7 +24924,7 @@ var HubTunnelConnectorBudget = class {
|
|
|
24650
24924
|
const HUB_TUNNEL_RETIRE_TIMEOUT_MS = 1e4;
|
|
24651
24925
|
var HubTunnelUnavailable = class extends Data.TaggedError("HubTunnelUnavailable") {};
|
|
24652
24926
|
var HubTunnelRegistry = class extends Context.Service()("@p4code/cli/hub/HubTunnelRegistry") {};
|
|
24653
|
-
const layer$
|
|
24927
|
+
const layer$111 = Layer.effect(HubTunnelRegistry, Effect.gen(function* () {
|
|
24654
24928
|
const config = yield* HubConfig;
|
|
24655
24929
|
const lock = yield* Semaphore.make(1);
|
|
24656
24930
|
const entries = /* @__PURE__ */ new Map();
|
|
@@ -24903,7 +25177,7 @@ const makeHubClientSocketTickets = Effect.gen(function* () {
|
|
|
24903
25177
|
};
|
|
24904
25178
|
});
|
|
24905
25179
|
var HubClientSocketTickets = class extends Context.Service()("@p4code/cli/hub/HubClientSocketTickets") {};
|
|
24906
|
-
const layer$
|
|
25180
|
+
const layer$110 = Layer.effect(HubClientSocketTickets, makeHubClientSocketTickets);
|
|
24907
25181
|
//#endregion
|
|
24908
25182
|
//#region src/hub/HubClientLease.ts
|
|
24909
25183
|
/** Other Hub processes sharing the database are observed within this interval. */
|
|
@@ -24992,7 +25266,7 @@ const readBody$1 = Effect.fn("HubClientRoutes.readBody")(function* (schema, maxi
|
|
|
24992
25266
|
}) : 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
25267
|
return yield* Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(schema))(body.toString("utf8"), { onExcessProperty: "error" }).pipe(Effect.mapError(() => new InvalidClientRequest()));
|
|
24994
25268
|
});
|
|
24995
|
-
const json$
|
|
25269
|
+
const json$2 = (body) => HttpServerResponse.jsonUnsafe(body, { headers: { "cache-control": "no-store" } });
|
|
24996
25270
|
const handle$1 = (operation, accountingOwner) => operation.pipe(Effect.timeout(HUB_CLIENT_REQUEST_TIMEOUT_MS), Effect.catch((error) => Effect.gen(function* () {
|
|
24997
25271
|
if (error instanceof HubThrottledError) return hubThrottledResponse(error.retryAfterSeconds);
|
|
24998
25272
|
if (error instanceof HubUnauthorizedError) {
|
|
@@ -25013,15 +25287,15 @@ const handle$1 = (operation, accountingOwner) => operation.pipe(Effect.timeout(H
|
|
|
25013
25287
|
const issue$1 = handle$1(Effect.gen(function* () {
|
|
25014
25288
|
const principal = yield* authenticateHubServerMembership();
|
|
25015
25289
|
const input = yield* readBody$1(HubClientEnrollmentInput);
|
|
25016
|
-
return json$
|
|
25290
|
+
return json$2(yield* issueHubClientEnrollment(principal, input));
|
|
25017
25291
|
}), "server-auth");
|
|
25018
25292
|
const exchange$1 = handle$1(Effect.gen(function* () {
|
|
25019
25293
|
const input = yield* readBody$1(HubClientEnrollmentExchange);
|
|
25020
|
-
return json$
|
|
25294
|
+
return json$2(yield* consumeHubClientEnrollment(input.secret));
|
|
25021
25295
|
}), "client-route");
|
|
25022
25296
|
const identity$2 = handle$1(Effect.gen(function* () {
|
|
25023
25297
|
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
25024
|
-
return json$
|
|
25298
|
+
return json$2(yield* authenticateHubClient(readBearerToken(request.headers.authorization)));
|
|
25025
25299
|
}), "client-route");
|
|
25026
25300
|
const events = handle$1(Effect.gen(function* () {
|
|
25027
25301
|
return yield* hubClientEvents(readBearerToken((yield* HttpServerRequest.HttpServerRequest).headers.authorization));
|
|
@@ -25046,7 +25320,7 @@ const socketTicket = handle$1(Effect.gen(function* () {
|
|
|
25046
25320
|
yield* authenticateHubClient(credential);
|
|
25047
25321
|
const input = yield* readBody$1(HubClientSocketTicketInput, MAX_SOCKET_TICKET_REQUEST_BYTES);
|
|
25048
25322
|
const tickets = yield* HubClientSocketTickets;
|
|
25049
|
-
return json$
|
|
25323
|
+
return json$2(yield* tickets.issue(credential, {
|
|
25050
25324
|
method: "GET",
|
|
25051
25325
|
url: `${HUB_TUNNEL_CLIENT_SOCKET_PATH}${input.path}`,
|
|
25052
25326
|
headers: {
|
|
@@ -25059,7 +25333,143 @@ const socketTicket = handle$1(Effect.gen(function* () {
|
|
|
25059
25333
|
}
|
|
25060
25334
|
}));
|
|
25061
25335
|
}), "client-route");
|
|
25062
|
-
const layer$
|
|
25336
|
+
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));
|
|
25337
|
+
//#endregion
|
|
25338
|
+
//#region src/hub/HubAnalytics.ts
|
|
25339
|
+
/**
|
|
25340
|
+
* Account-scoped analytics facts: append and read.
|
|
25341
|
+
*
|
|
25342
|
+
* Every row belongs to the authenticated account, and every read is filtered
|
|
25343
|
+
* by it, so one account's facts can never appear in another's page. Appends
|
|
25344
|
+
* are idempotent on the caller's own event id rather than deduplicated by
|
|
25345
|
+
* content: a retry after a dropped response must not double-count, and a fact
|
|
25346
|
+
* is never rewritten once stored.
|
|
25347
|
+
*
|
|
25348
|
+
* @module HubAnalytics
|
|
25349
|
+
*/
|
|
25350
|
+
const CURSOR_SEPARATOR = "|";
|
|
25351
|
+
const decodeFact = Schema$1.decodeUnknownEffect(HubAnalyticsFactInput);
|
|
25352
|
+
const decodeQuery = Schema$1.decodeUnknownEffect(HubAnalyticsQuery);
|
|
25353
|
+
const encodePayload$1 = Schema$1.encodeEffect(Schema$1.fromJsonString(Schema$1.Unknown));
|
|
25354
|
+
const decodePayload$1 = Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(Schema$1.Unknown));
|
|
25355
|
+
/** The page position of a row, in the exact order the index reads. */
|
|
25356
|
+
const cursorOf = (row) => `${row.occurred_at}${CURSOR_SEPARATOR}${row.event_id}`;
|
|
25357
|
+
const parseCursor = (cursor) => {
|
|
25358
|
+
const separator = cursor.indexOf(CURSOR_SEPARATOR);
|
|
25359
|
+
if (separator <= 0) return null;
|
|
25360
|
+
const occurredAt = Number.parseInt(cursor.slice(0, separator), 10);
|
|
25361
|
+
const eventId = cursor.slice(separator + 1);
|
|
25362
|
+
return Number.isFinite(occurredAt) && eventId.length > 0 ? {
|
|
25363
|
+
occurredAt,
|
|
25364
|
+
eventId
|
|
25365
|
+
} : null;
|
|
25366
|
+
};
|
|
25367
|
+
/**
|
|
25368
|
+
* Append one fact for the authenticated account.
|
|
25369
|
+
*
|
|
25370
|
+
* The principal is rechecked inside the transaction, the way every other hub
|
|
25371
|
+
* write does it: a credential revoked while the body was being read must not
|
|
25372
|
+
* still land a row.
|
|
25373
|
+
*/
|
|
25374
|
+
const appendHubAnalyticsFact = Effect.fn("HubAnalytics.append")(function* (principal, input) {
|
|
25375
|
+
const config = yield* HubConfig;
|
|
25376
|
+
if (!currentPrincipal(config, principal)) return yield* Effect.fail(new HubUnauthorizedError());
|
|
25377
|
+
const fact = yield* decodeFact(input, { onExcessProperty: "error" });
|
|
25378
|
+
const payload = yield* encodePayload$1(fact.payload);
|
|
25379
|
+
const occurredAt = Date.parse(fact.occurredAt);
|
|
25380
|
+
const sql = yield* SqlClient.SqlClient;
|
|
25381
|
+
const now = yield* Clock.currentTimeMillis;
|
|
25382
|
+
return yield* sql.withTransaction(Effect.gen(function* () {
|
|
25383
|
+
if (!currentPrincipal(config, principal)) return yield* Effect.fail(new HubUnauthorizedError());
|
|
25384
|
+
if ((yield* sql`SELECT event_id FROM hub_analytics_facts
|
|
25385
|
+
WHERE account_id = ${principal.accountId} AND event_id = ${fact.eventId}`).length > 0) return { accepted: false };
|
|
25386
|
+
yield* sql`INSERT INTO hub_analytics_facts
|
|
25387
|
+
(account_id, environment_id, event_id, kind, version, occurred_at, payload, created_at)
|
|
25388
|
+
VALUES (${principal.accountId}, ${principal.environmentId}, ${fact.eventId}, ${fact.kind},
|
|
25389
|
+
${fact.version}, ${occurredAt}, ${payload}, ${now})
|
|
25390
|
+
ON CONFLICT(account_id, event_id) DO NOTHING`;
|
|
25391
|
+
return { accepted: true };
|
|
25392
|
+
}));
|
|
25393
|
+
});
|
|
25394
|
+
/**
|
|
25395
|
+
* Read this account's facts, newest first.
|
|
25396
|
+
*
|
|
25397
|
+
* Paged by keyset rather than offset, so a fact appended between two pages
|
|
25398
|
+
* cannot shift a row into being read twice or skipped. The limit is bounded by
|
|
25399
|
+
* the contract, and one extra row is read to decide whether a next page
|
|
25400
|
+
* exists without a second count query.
|
|
25401
|
+
*/
|
|
25402
|
+
const queryHubAnalyticsFacts = Effect.fn("HubAnalytics.query")(function* (principal, input) {
|
|
25403
|
+
if (!currentPrincipal(yield* HubConfig, principal)) return yield* Effect.fail(new HubUnauthorizedError());
|
|
25404
|
+
const query = yield* decodeQuery(input, { onExcessProperty: "error" });
|
|
25405
|
+
const limit = query.limit ?? HUB_ANALYTICS_QUERY_LIMITS.defaultLimit;
|
|
25406
|
+
const kinds = query.kinds === void 0 ? null : `,${query.kinds.join(",")},`;
|
|
25407
|
+
const since = query.since === void 0 ? null : Date.parse(query.since);
|
|
25408
|
+
const until = query.until === void 0 ? null : Date.parse(query.until);
|
|
25409
|
+
const cursor = query.cursor === void 0 ? null : parseCursor(query.cursor);
|
|
25410
|
+
const cursorOccurredAt = cursor === null ? null : cursor.occurredAt;
|
|
25411
|
+
const cursorEventId = cursor === null ? null : cursor.eventId;
|
|
25412
|
+
const rows = yield* (yield* SqlClient.SqlClient)`
|
|
25413
|
+
SELECT environment_id, event_id, kind, version, occurred_at, payload
|
|
25414
|
+
FROM hub_analytics_facts
|
|
25415
|
+
WHERE account_id = ${principal.accountId}
|
|
25416
|
+
AND (${kinds} IS NULL OR instr(${kinds}, ',' || kind || ',') > 0)
|
|
25417
|
+
AND (${since} IS NULL OR occurred_at >= ${since})
|
|
25418
|
+
AND (${until} IS NULL OR occurred_at <= ${until})
|
|
25419
|
+
AND (
|
|
25420
|
+
${cursorOccurredAt} IS NULL
|
|
25421
|
+
OR occurred_at < ${cursorOccurredAt}
|
|
25422
|
+
OR (occurred_at = ${cursorOccurredAt} AND event_id < ${cursorEventId})
|
|
25423
|
+
)
|
|
25424
|
+
ORDER BY occurred_at DESC, event_id DESC
|
|
25425
|
+
LIMIT ${limit + 1}`;
|
|
25426
|
+
const page = rows.slice(0, limit);
|
|
25427
|
+
const facts = [];
|
|
25428
|
+
for (const row of page) facts.push({
|
|
25429
|
+
eventId: row.event_id,
|
|
25430
|
+
environmentId: row.environment_id,
|
|
25431
|
+
kind: row.kind,
|
|
25432
|
+
version: row.version,
|
|
25433
|
+
occurredAt: new Date(row.occurred_at).toISOString(),
|
|
25434
|
+
payload: yield* decodePayload$1(row.payload)
|
|
25435
|
+
});
|
|
25436
|
+
const last = page[page.length - 1];
|
|
25437
|
+
return {
|
|
25438
|
+
facts,
|
|
25439
|
+
nextCursor: rows.length > limit && last !== void 0 ? cursorOf(last) : null
|
|
25440
|
+
};
|
|
25441
|
+
});
|
|
25442
|
+
//#endregion
|
|
25443
|
+
//#region src/hub/HubAnalyticsRoutes.ts
|
|
25444
|
+
/**
|
|
25445
|
+
* The account's own analytics endpoints.
|
|
25446
|
+
*
|
|
25447
|
+
* Both routes require the same server membership every other environment-owned
|
|
25448
|
+
* hub write uses, and neither forwards anything anywhere: the hub is the only
|
|
25449
|
+
* sink for these facts.
|
|
25450
|
+
*
|
|
25451
|
+
* @module HubAnalyticsRoutes
|
|
25452
|
+
*/
|
|
25453
|
+
const MAX_ANALYTICS_BODY_BYTES = 16 * 1024;
|
|
25454
|
+
const json$1 = (value) => HttpServerResponse.jsonUnsafe(value, { headers: { "cache-control": "no-store" } });
|
|
25455
|
+
/** Only the filters the query contract knows; anything else is a decode failure. */
|
|
25456
|
+
const queryFromUrl = (url) => Object.fromEntries([
|
|
25457
|
+
["kinds", url.searchParams.getAll("kind")],
|
|
25458
|
+
["since", url.searchParams.get("since")],
|
|
25459
|
+
["until", url.searchParams.get("until")],
|
|
25460
|
+
["limit", url.searchParams.get("limit")],
|
|
25461
|
+
["cursor", url.searchParams.get("cursor")]
|
|
25462
|
+
].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]));
|
|
25463
|
+
const layer$108 = Layer.mergeAll(HttpRouter.add("POST", "/v1/analytics/facts", handle$1(Effect.gen(function* () {
|
|
25464
|
+
const principal = yield* authenticateHubServerMembership();
|
|
25465
|
+
const input = yield* readBody$1(HubAnalyticsFactInput, MAX_ANALYTICS_BODY_BYTES);
|
|
25466
|
+
return json$1(yield* appendHubAnalyticsFact(principal, input));
|
|
25467
|
+
}), "server-auth")), HttpRouter.add("GET", "/v1/analytics/facts", handle$1(Effect.gen(function* () {
|
|
25468
|
+
const principal = yield* authenticateHubServerMembership();
|
|
25469
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
25470
|
+
const url = new URL(request.url, "http://hub.invalid");
|
|
25471
|
+
return json$1(yield* queryHubAnalyticsFacts(principal, queryFromUrl(url)));
|
|
25472
|
+
}), "server-auth")));
|
|
25063
25473
|
//#endregion
|
|
25064
25474
|
//#region src/hub/HubPushTokenProtection.ts
|
|
25065
25475
|
const KEY_NAME = "hub-push-token-encryption-v1";
|
|
@@ -26812,7 +27222,7 @@ const layer$100 = Layer.effect(HubTunnelPublished, Effect.gen(function* () {
|
|
|
26812
27222
|
}));
|
|
26813
27223
|
//#endregion
|
|
26814
27224
|
//#region src/hub/HubTunnelCors.ts
|
|
26815
|
-
const MAX_HEADER_CHARS = 256;
|
|
27225
|
+
const MAX_HEADER_CHARS$1 = 256;
|
|
26816
27226
|
const METHODS = [
|
|
26817
27227
|
"GET",
|
|
26818
27228
|
"HEAD",
|
|
@@ -26842,7 +27252,7 @@ function hubTunnelCors(method, headers) {
|
|
|
26842
27252
|
};
|
|
26843
27253
|
if (method === "OPTIONS" && headers["access-control-request-method"] !== void 0) {
|
|
26844
27254
|
const raw = headers["access-control-request-headers"] ?? "";
|
|
26845
|
-
if (raw.length > MAX_HEADER_CHARS) return null;
|
|
27255
|
+
if (raw.length > MAX_HEADER_CHARS$1) return null;
|
|
26846
27256
|
const requested = raw ? raw.split(",").map((name) => name.trim().toLowerCase()) : [];
|
|
26847
27257
|
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
27258
|
result["access-control-allow-methods"] = METHODS.join(", ");
|
|
@@ -28597,8 +29007,8 @@ const releaseFeedLeaseRoute = HttpRouter.add("DELETE", "/feed/lease", respondToH
|
|
|
28597
29007
|
return HttpServerResponse.empty({ status: 204 });
|
|
28598
29008
|
})));
|
|
28599
29009
|
/** Provide around HttpRouter.serve so request fibers share these process-owned services. */
|
|
28600
|
-
const routingServices = Layer.mergeAll(layer$112, layer$
|
|
28601
|
-
const layer$94 = Layer.mergeAll(layer$99, layer$98, layer$102, layer$96, layer$95, layer$
|
|
29010
|
+
const routingServices = Layer.mergeAll(layer$112, layer$110, layer$101, layer$100).pipe(Layer.provideMerge(layer$111));
|
|
29011
|
+
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
29012
|
//#endregion
|
|
28603
29013
|
//#region src/hub/Migrations/001_Tasks.ts
|
|
28604
29014
|
/**
|
|
@@ -29080,6 +29490,21 @@ var _021_NotificationDeliveryProgress_default = Effect.gen(function* () {
|
|
|
29080
29490
|
yield* (yield* SqlClient.SqlClient)`ALTER TABLE hub_notification_deliveries ADD COLUMN app_delivered INTEGER NOT NULL DEFAULT 0`;
|
|
29081
29491
|
});
|
|
29082
29492
|
//#endregion
|
|
29493
|
+
//#region src/hub/Migrations/022_AnalyticsFacts.ts
|
|
29494
|
+
var _022_AnalyticsFacts_default = Effect.gen(function* () {
|
|
29495
|
+
const sql = yield* SqlClient.SqlClient;
|
|
29496
|
+
yield* sql`CREATE TABLE hub_analytics_facts (
|
|
29497
|
+
account_id TEXT NOT NULL, environment_id TEXT NOT NULL, event_id TEXT NOT NULL,
|
|
29498
|
+
kind TEXT NOT NULL, version INTEGER NOT NULL, occurred_at INTEGER NOT NULL,
|
|
29499
|
+
payload TEXT NOT NULL, created_at INTEGER NOT NULL,
|
|
29500
|
+
PRIMARY KEY(account_id, event_id)
|
|
29501
|
+
)`;
|
|
29502
|
+
yield* sql`CREATE INDEX hub_analytics_facts_recent
|
|
29503
|
+
ON hub_analytics_facts(account_id, occurred_at DESC, event_id DESC)`;
|
|
29504
|
+
yield* sql`CREATE INDEX hub_analytics_facts_kind
|
|
29505
|
+
ON hub_analytics_facts(account_id, kind, occurred_at DESC, event_id DESC)`;
|
|
29506
|
+
});
|
|
29507
|
+
//#endregion
|
|
29083
29508
|
//#region src/hub/Migrations.ts
|
|
29084
29509
|
/**
|
|
29085
29510
|
* Hub migrations.
|
|
@@ -29195,6 +29620,11 @@ const hubMigrationEntries = [
|
|
|
29195
29620
|
21,
|
|
29196
29621
|
"NotificationDeliveryProgress",
|
|
29197
29622
|
_021_NotificationDeliveryProgress_default
|
|
29623
|
+
],
|
|
29624
|
+
[
|
|
29625
|
+
22,
|
|
29626
|
+
"AnalyticsFacts",
|
|
29627
|
+
_022_AnalyticsFacts_default
|
|
29198
29628
|
]
|
|
29199
29629
|
];
|
|
29200
29630
|
const hubMigrationLoader = Migrator.fromRecord(Object.fromEntries(hubMigrationEntries.map(([id, name, migration]) => [`${id}_${name}`, migration])));
|
|
@@ -29286,7 +29716,7 @@ const announce = Layer.effectDiscard(Effect.gen(function* () {
|
|
|
29286
29716
|
});
|
|
29287
29717
|
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
29718
|
}));
|
|
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$
|
|
29719
|
+
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
29720
|
const runHub = Layer.launch(makeHubLayer);
|
|
29291
29721
|
//#endregion
|
|
29292
29722
|
//#region src/atomicWrite.ts
|
|
@@ -30502,6 +30932,34 @@ Layer.succeed(ClaudeMcpFiles, {
|
|
|
30502
30932
|
const PROBE_CACHE_TTL_MS = 300 * 1e3;
|
|
30503
30933
|
/** An authorize URL nobody returned from within this window is abandoned. */
|
|
30504
30934
|
const PENDING_FLOW_TTL_MS = 600 * 1e3;
|
|
30935
|
+
/**
|
|
30936
|
+
* The two answers that mean the credential, not the request, was refused.
|
|
30937
|
+
*
|
|
30938
|
+
* The same pair `McpToolClient` reports as "the server rejected p4code's
|
|
30939
|
+
* credentials": 401 for a token the server will not accept, 403 for one it
|
|
30940
|
+
* accepts and will not act on. Both leave the session with a server it cannot
|
|
30941
|
+
* use, so both take the one refresh and, failing that, the sign-in.
|
|
30942
|
+
*/
|
|
30943
|
+
const credentialsRefused = (status) => status === 401 || status === 403;
|
|
30944
|
+
/**
|
|
30945
|
+
* Whether a token endpoint's answer means the grant itself is gone.
|
|
30946
|
+
*
|
|
30947
|
+
* RFC 6749 gives a retired or rejected refresh token one spelling: 400 with
|
|
30948
|
+
* `error: "invalid_grant"`. The other 400s - `invalid_request`,
|
|
30949
|
+
* `unsupported_grant_type`, `invalid_scope` - are complaints about the request
|
|
30950
|
+
* this code sent, and erasing the user's credential over a malformed request
|
|
30951
|
+
* of ours would charge them a sign-in for a bug. 401 and 403 stay terminal:
|
|
30952
|
+
* the client credential itself was refused, and no retry of this shape fixes
|
|
30953
|
+
* that. Everything else - 429, a 5xx, whatever a proxy invents - is the server
|
|
30954
|
+
* having a bad minute.
|
|
30955
|
+
*/
|
|
30956
|
+
const terminalRefreshAnswer = (status, json) => {
|
|
30957
|
+
if (status === 401 || status === 403) return true;
|
|
30958
|
+
if (status !== 400) return false;
|
|
30959
|
+
return (typeof json === "object" && json !== null ? json["error"] : void 0) === "invalid_grant";
|
|
30960
|
+
};
|
|
30961
|
+
/** Why one refresh attempt failed, and whether the stored grant survives it. */
|
|
30962
|
+
var RefreshFailure = class extends Data.TaggedError("RefreshFailure") {};
|
|
30505
30963
|
/** A token this close to expiry is refreshed rather than handed to a session. */
|
|
30506
30964
|
const TOKEN_EXPIRY_MARGIN_MS = 60 * 1e3;
|
|
30507
30965
|
/** Outbound discovery, registration and token requests all use this cap. */
|
|
@@ -30613,6 +31071,7 @@ const make$109 = Effect.gen(function* () {
|
|
|
30613
31071
|
const crypto = yield* Crypto.Crypto;
|
|
30614
31072
|
const pendingFlows = yield* Ref.make(/* @__PURE__ */ new Map());
|
|
30615
31073
|
const probeCache = yield* Ref.make(/* @__PURE__ */ new Map());
|
|
31074
|
+
const tokenVerdicts = yield* Ref.make(/* @__PURE__ */ new Map());
|
|
30616
31075
|
const redirectUri = `http://127.0.0.1:${config.port}${OAUTH_CALLBACK_PATH}`;
|
|
30617
31076
|
const nowMillis = Effect.map(DateTime.now, DateTime.toEpochMillis);
|
|
30618
31077
|
const readRecord = Effect.fn("McpOAuth.readRecord")(function* (server) {
|
|
@@ -30644,26 +31103,28 @@ const make$109 = Effect.gen(function* () {
|
|
|
30644
31103
|
* does — while answering the initialize POST with the 401 challenge this
|
|
30645
31104
|
* probe exists to see.
|
|
30646
31105
|
*/
|
|
31106
|
+
const initializeRequest = (registration, accessToken) => HttpClientRequest.bodyJsonUnsafe(HttpClientRequest.setHeaders(HttpClientRequest.post(registration.url), {
|
|
31107
|
+
...registration.headers,
|
|
31108
|
+
...accessToken === void 0 ? {} : { authorization: `Bearer ${accessToken}` },
|
|
31109
|
+
accept: "application/json, text/event-stream"
|
|
31110
|
+
}), {
|
|
31111
|
+
jsonrpc: "2.0",
|
|
31112
|
+
id: 0,
|
|
31113
|
+
method: "initialize",
|
|
31114
|
+
params: {
|
|
31115
|
+
protocolVersion: MCP_PROBE_PROTOCOL_VERSION,
|
|
31116
|
+
capabilities: {},
|
|
31117
|
+
clientInfo: {
|
|
31118
|
+
name: "p4code",
|
|
31119
|
+
version: "0.0.0"
|
|
31120
|
+
}
|
|
31121
|
+
}
|
|
31122
|
+
});
|
|
30647
31123
|
const probe = Effect.fn("McpOAuth.probe")(function* (registration) {
|
|
30648
31124
|
const now = yield* nowMillis;
|
|
30649
31125
|
const cached = (yield* Ref.get(probeCache)).get(registration.url);
|
|
30650
31126
|
if (cached !== void 0 && now - cached.at < PROBE_CACHE_TTL_MS) return cached;
|
|
30651
|
-
const verdict = yield* http.execute(
|
|
30652
|
-
...registration.headers,
|
|
30653
|
-
accept: "application/json, text/event-stream"
|
|
30654
|
-
}), {
|
|
30655
|
-
jsonrpc: "2.0",
|
|
30656
|
-
id: 0,
|
|
30657
|
-
method: "initialize",
|
|
30658
|
-
params: {
|
|
30659
|
-
protocolVersion: MCP_PROBE_PROTOCOL_VERSION,
|
|
30660
|
-
capabilities: {},
|
|
30661
|
-
clientInfo: {
|
|
30662
|
-
name: "p4code",
|
|
30663
|
-
version: "0.0.0"
|
|
30664
|
-
}
|
|
30665
|
-
}
|
|
30666
|
-
})).pipe(Effect.timeout(OAUTH_REQUEST_TIMEOUT_MS), Effect.map((response) => {
|
|
31127
|
+
const verdict = yield* http.execute(initializeRequest(registration)).pipe(Effect.timeout(OAUTH_REQUEST_TIMEOUT_MS), Effect.map((response) => {
|
|
30667
31128
|
const challengeHeader = response.headers["www-authenticate"];
|
|
30668
31129
|
const challenge = response.status === 401 && challengeHeader !== void 0 ? parseWwwAuthenticate(challengeHeader) : void 0;
|
|
30669
31130
|
return {
|
|
@@ -30690,12 +31151,53 @@ const make$109 = Effect.gen(function* () {
|
|
|
30690
31151
|
next.delete(url);
|
|
30691
31152
|
return next;
|
|
30692
31153
|
});
|
|
31154
|
+
/**
|
|
31155
|
+
* Ask the server whether the token this machine holds still works.
|
|
31156
|
+
*
|
|
31157
|
+
* `probe` cannot answer this. It asks without credentials, so its 401 is the
|
|
31158
|
+
* one every protected server sends and says nothing about the token sitting
|
|
31159
|
+
* in the record. A token revoked at the other end, or issued for a workspace
|
|
31160
|
+
* the user has since left, stays well inside its stored expiry and is
|
|
31161
|
+
* refused on every call - the state this catches.
|
|
31162
|
+
*
|
|
31163
|
+
* Unreachable counts as usable, exactly as `probe` treats it: a network
|
|
31164
|
+
* fault is not a revocation, and signing someone out of a server that is
|
|
31165
|
+
* merely down helps nobody. Only a real answer is remembered.
|
|
31166
|
+
*/
|
|
31167
|
+
const validateToken = Effect.fn("McpOAuth.validateToken")(function* (registration, accessToken) {
|
|
31168
|
+
const now = yield* nowMillis;
|
|
31169
|
+
const cached = (yield* Ref.get(tokenVerdicts)).get(registration.name);
|
|
31170
|
+
if (cached !== void 0 && cached.accessToken === accessToken) {
|
|
31171
|
+
if (!cached.accepted) return false;
|
|
31172
|
+
if (now - cached.at < PROBE_CACHE_TTL_MS) return true;
|
|
31173
|
+
}
|
|
31174
|
+
const answered = yield* http.execute(initializeRequest(registration, accessToken)).pipe(Effect.timeout(OAUTH_REQUEST_TIMEOUT_MS), Effect.map((response) => Option.some(!credentialsRefused(response.status))), Effect.orElseSucceed(() => Option.none()));
|
|
31175
|
+
if (Option.isNone(answered)) return true;
|
|
31176
|
+
yield* Ref.update(tokenVerdicts, (cache) => {
|
|
31177
|
+
const next = new Map(cache);
|
|
31178
|
+
next.set(registration.name, {
|
|
31179
|
+
accessToken,
|
|
31180
|
+
at: now,
|
|
31181
|
+
accepted: answered.value
|
|
31182
|
+
});
|
|
31183
|
+
return next;
|
|
31184
|
+
});
|
|
31185
|
+
return answered.value;
|
|
31186
|
+
});
|
|
31187
|
+
/** Whether the server has refused the exact token the record still holds. */
|
|
31188
|
+
const rejectedAlready = (server, accessToken) => Effect.map(Ref.get(tokenVerdicts), (cache) => {
|
|
31189
|
+
const verdict = cache.get(server);
|
|
31190
|
+
return verdict !== void 0 && verdict.accessToken === accessToken && !verdict.accepted;
|
|
31191
|
+
});
|
|
30693
31192
|
const statusFor = (registration) => Effect.gen(function* () {
|
|
30694
31193
|
const record = yield* readRecord(registration.name);
|
|
30695
|
-
if (Option.isSome(record) && record.value.tokens !== void 0)
|
|
30696
|
-
|
|
30697
|
-
|
|
30698
|
-
|
|
31194
|
+
if (Option.isSome(record) && record.value.tokens !== void 0) {
|
|
31195
|
+
const rejected = yield* rejectedAlready(registration.name, record.value.tokens.accessToken);
|
|
31196
|
+
return Option.some({
|
|
31197
|
+
required: true,
|
|
31198
|
+
connected: !rejected
|
|
31199
|
+
});
|
|
31200
|
+
}
|
|
30699
31201
|
if ((yield* probe(registration)).requiresAuth || Option.isSome(record)) return Option.some({
|
|
30700
31202
|
required: true,
|
|
30701
31203
|
connected: false
|
|
@@ -30870,6 +31372,24 @@ const make$109 = Effect.gen(function* () {
|
|
|
30870
31372
|
ok: false,
|
|
30871
31373
|
detail: "The sign-in failed. See the server log."
|
|
30872
31374
|
}))));
|
|
31375
|
+
/**
|
|
31376
|
+
* Drop the tokens the server will not accept, keeping the client
|
|
31377
|
+
* registration so the sign-in that follows skips re-registration.
|
|
31378
|
+
*
|
|
31379
|
+
* Compare-and-delete rather than a blind write, because two sessions can
|
|
31380
|
+
* resolve the same server at once. The loser of that race spends a refresh
|
|
31381
|
+
* token the winner has already rotated, is told `invalid_grant`, and would
|
|
31382
|
+
* otherwise erase the credential the winner just stored - turning a refresh
|
|
31383
|
+
* that worked into a sign-in prompt. A record whose access token has moved on
|
|
31384
|
+
* belongs to somebody else's successful refresh and is left alone.
|
|
31385
|
+
*/
|
|
31386
|
+
const forgetTokens = Effect.fn("McpOAuth.forgetTokens")(function* (server, refusedAccessToken) {
|
|
31387
|
+
const current = yield* readRecord(server);
|
|
31388
|
+
if (Option.isNone(current)) return;
|
|
31389
|
+
if (current.value.tokens?.accessToken !== refusedAccessToken) return;
|
|
31390
|
+
const { tokens: _refused, ...rest } = current.value;
|
|
31391
|
+
yield* writeRecord(server, rest);
|
|
31392
|
+
});
|
|
30873
31393
|
const disconnect = (name) => Effect.gen(function* () {
|
|
30874
31394
|
const record = yield* readRecord(name);
|
|
30875
31395
|
if (Option.isNone(record)) return;
|
|
@@ -30887,9 +31407,15 @@ const make$109 = Effect.gen(function* () {
|
|
|
30887
31407
|
...record.clientSecret === void 0 ? {} : { client_secret: record.clientSecret },
|
|
30888
31408
|
...record.resource === void 0 ? {} : { resource: record.resource }
|
|
30889
31409
|
}));
|
|
30890
|
-
if (status !== 200) return yield* Effect.fail(
|
|
31410
|
+
if (status !== 200) return yield* Effect.fail(new RefreshFailure({
|
|
31411
|
+
terminal: terminalRefreshAnswer(status, json),
|
|
31412
|
+
detail: `status ${status}`
|
|
31413
|
+
}));
|
|
30891
31414
|
const decoded = decodeTokenResponse(json);
|
|
30892
|
-
if (decoded._tag !== "Success") return yield* Effect.fail(
|
|
31415
|
+
if (decoded._tag !== "Success") return yield* Effect.fail(new RefreshFailure({
|
|
31416
|
+
terminal: false,
|
|
31417
|
+
detail: "unexpected shape"
|
|
31418
|
+
}));
|
|
30893
31419
|
const response = decoded.value;
|
|
30894
31420
|
const now = yield* nowMillis;
|
|
30895
31421
|
const tokens = {
|
|
@@ -30908,9 +31434,16 @@ const make$109 = Effect.gen(function* () {
|
|
|
30908
31434
|
if (Option.isNone(record) || record.value.tokens === void 0) return Option.none();
|
|
30909
31435
|
const tokens = record.value.tokens;
|
|
30910
31436
|
const now = yield* nowMillis;
|
|
30911
|
-
if (!(tokens.expiresAt !== void 0 && now >= tokens.expiresAt - TOKEN_EXPIRY_MARGIN_MS)) return Option.some(tokens.accessToken);
|
|
30912
|
-
if (tokens.refreshToken === void 0)
|
|
30913
|
-
|
|
31437
|
+
if (!(tokens.expiresAt !== void 0 && now >= tokens.expiresAt - TOKEN_EXPIRY_MARGIN_MS) && (yield* validateToken(registration, tokens.accessToken))) return Option.some(tokens.accessToken);
|
|
31438
|
+
if (tokens.refreshToken === void 0) {
|
|
31439
|
+
yield* forgetTokens(registration.name, tokens.accessToken);
|
|
31440
|
+
return Option.none();
|
|
31441
|
+
}
|
|
31442
|
+
return yield* refreshTokens(registration.name, record.value, tokens.refreshToken).pipe(Effect.map(Option.some), Effect.catchTag("RefreshFailure", (failure) => Effect.logWarning("mcp oauth refresh failed", {
|
|
31443
|
+
server: registration.name,
|
|
31444
|
+
detail: failure.detail,
|
|
31445
|
+
terminal: failure.terminal
|
|
31446
|
+
}).pipe(Effect.andThen(failure.terminal ? forgetTokens(registration.name, tokens.accessToken) : Effect.void), Effect.as(Option.none()))), Effect.catchCause((cause) => Effect.logWarning("mcp oauth refresh could not be attempted", {
|
|
30914
31447
|
cause,
|
|
30915
31448
|
server: registration.name
|
|
30916
31449
|
}).pipe(Effect.as(Option.none()))));
|
|
@@ -30944,6 +31477,16 @@ const decodeRegistration$1 = Schema$1.decodeUnknownExit(RegistrationFromJson$1);
|
|
|
30944
31477
|
const encodeRegistration = Schema$1.encodeSync(RegistrationFromJson$1);
|
|
30945
31478
|
var McpRegistry = class extends Context.Service()("@p4code/cli/mcp/McpRegistry") {};
|
|
30946
31479
|
const slotsOf = (registration) => registration.secrets ?? [];
|
|
31480
|
+
/**
|
|
31481
|
+
* What a session is told when a server is left out for want of a credential.
|
|
31482
|
+
*
|
|
31483
|
+
* The name and where the row came from, and nothing else: a token, a url query
|
|
31484
|
+
* or a header would end up in a log file that outlives the session. The action
|
|
31485
|
+
* names the one place a person can fix it, because "absent" on its own reads as
|
|
31486
|
+
* a bug rather than as a sign-in that has not happened.
|
|
31487
|
+
*/
|
|
31488
|
+
const OMITTED_FOR_SIGN_IN = "mcp server omitted: sign-in required";
|
|
31489
|
+
const SIGN_IN_ACTION = "Connect it in Settings > MCP servers.";
|
|
30947
31490
|
const make$108 = Effect.gen(function* () {
|
|
30948
31491
|
const config = yield* ServerConfig$1;
|
|
30949
31492
|
const secrets = yield* ServerSecretStore;
|
|
@@ -31060,6 +31603,14 @@ const make$108 = Effect.gen(function* () {
|
|
|
31060
31603
|
if (registration.transport !== "stdio" && !hasExplicitAuthorization) {
|
|
31061
31604
|
const token = yield* oauth.accessTokenFor(registration);
|
|
31062
31605
|
if (Option.isSome(token)) headers["Authorization"] = `Bearer ${token.value}`;
|
|
31606
|
+
else if (Option.isSome(yield* oauth.statusFor(registration))) {
|
|
31607
|
+
yield* Effect.logWarning(OMITTED_FOR_SIGN_IN, {
|
|
31608
|
+
server: registration.name,
|
|
31609
|
+
source: "registration",
|
|
31610
|
+
action: SIGN_IN_ACTION
|
|
31611
|
+
});
|
|
31612
|
+
continue;
|
|
31613
|
+
}
|
|
31063
31614
|
}
|
|
31064
31615
|
resolved[registration.name] = registration.transport === "stdio" ? {
|
|
31065
31616
|
type: "stdio",
|
|
@@ -31095,7 +31646,15 @@ const make$108 = Effect.gen(function* () {
|
|
|
31095
31646
|
if (!Object.keys(headers).some((key) => key.toLowerCase() === "authorization")) {
|
|
31096
31647
|
const token = yield* oauth.accessTokenFor(userScope);
|
|
31097
31648
|
if (Option.isSome(token)) headers["Authorization"] = `Bearer ${token.value}`;
|
|
31098
|
-
else if (Option.isSome(yield* oauth.statusFor(userScope)))
|
|
31649
|
+
else if (Option.isSome(yield* oauth.statusFor(userScope))) {
|
|
31650
|
+
yield* Effect.logWarning(OMITTED_FOR_SIGN_IN, {
|
|
31651
|
+
server: userScope.name,
|
|
31652
|
+
source: "user-scope",
|
|
31653
|
+
sessionName,
|
|
31654
|
+
action: SIGN_IN_ACTION
|
|
31655
|
+
});
|
|
31656
|
+
continue;
|
|
31657
|
+
}
|
|
31099
31658
|
}
|
|
31100
31659
|
resolved[sessionName] = {
|
|
31101
31660
|
type: userScope.transport,
|
|
@@ -33858,7 +34417,7 @@ const findAgentPreset = (requested) => {
|
|
|
33858
34417
|
};
|
|
33859
34418
|
//#endregion
|
|
33860
34419
|
//#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$
|
|
34420
|
+
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
34421
|
const runWithSync = (flags, run) => Effect.gen(function* () {
|
|
33863
34422
|
const config = yield* resolveCliAuthConfig(flags, yield* GlobalFlag.LogLevel);
|
|
33864
34423
|
return yield* Effect.gen(function* () {
|
|
@@ -33866,7 +34425,7 @@ const runWithSync = (flags, run) => Effect.gen(function* () {
|
|
|
33866
34425
|
sync: yield* AssetSync,
|
|
33867
34426
|
link: yield* HubLink
|
|
33868
34427
|
});
|
|
33869
|
-
}).pipe(Effect.provide(SyncServicesLive.pipe(Layer.provide(layer$
|
|
34428
|
+
}).pipe(Effect.provide(SyncServicesLive.pipe(Layer.provide(layer$122(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, "Error")))));
|
|
33870
34429
|
});
|
|
33871
34430
|
const STATE_LABEL = {
|
|
33872
34431
|
synced: "synced",
|
|
@@ -36240,7 +36799,11 @@ function projectEvent(model, event) {
|
|
|
36240
36799
|
case "thread.activity-appended": return decodeForEvent(ThreadActivityAppendedPayload, event.payload, event.type, "payload").pipe(Effect.map((payload) => {
|
|
36241
36800
|
const thread = nextBase.threads.find((entry) => entry.id === payload.threadId);
|
|
36242
36801
|
if (!thread) return nextBase;
|
|
36243
|
-
const
|
|
36802
|
+
const appended = payload.activity.sequence === void 0 ? {
|
|
36803
|
+
...payload.activity,
|
|
36804
|
+
sequence: event.sequence
|
|
36805
|
+
} : payload.activity;
|
|
36806
|
+
const activities = [...thread.activities.filter((entry) => entry.id !== appended.id), appended].toSorted(compareThreadActivities).slice(-500);
|
|
36244
36807
|
return {
|
|
36245
36808
|
...nextBase,
|
|
36246
36809
|
threads: updateThread(nextBase.threads, payload.threadId, {
|
|
@@ -40666,7 +41229,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
|
|
|
40666
41229
|
kind: activity.kind,
|
|
40667
41230
|
summary: activity.summary,
|
|
40668
41231
|
payload: activity.payload,
|
|
40669
|
-
|
|
41232
|
+
sequence: activity.sequence ?? event.sequence,
|
|
40670
41233
|
createdAt: activity.createdAt
|
|
40671
41234
|
});
|
|
40672
41235
|
return;
|
|
@@ -47399,7 +47962,7 @@ const startDeviceHub = Effect.fn("DeviceHubProcess.start")(function* (input) {
|
|
|
47399
47962
|
});
|
|
47400
47963
|
//#endregion
|
|
47401
47964
|
//#region src/device/AgentDeviceProcessLive.ts
|
|
47402
|
-
const decodeJson$
|
|
47965
|
+
const decodeJson$3 = Schema$1.decodeUnknownSync(Schema$1.UnknownFromJsonString);
|
|
47403
47966
|
var AgentDeviceProcessLiveFailure = class extends Data.TaggedError("AgentDeviceProcessLiveFailure") {};
|
|
47404
47967
|
const READY_TIMEOUT_MS = 3e4;
|
|
47405
47968
|
const MAX_READY_LINE = 256;
|
|
@@ -47511,7 +48074,7 @@ const make$93 = Effect.fn("AgentDeviceProcess.make")(function* () {
|
|
|
47511
48074
|
maxBytes: MAX_RESPONSE_BYTES$7
|
|
47512
48075
|
});
|
|
47513
48076
|
if (response.status !== 200 || body.truncated || body.invalidUtf8) return yield* new AgentDeviceProcessLiveFailure({ message: "Agent authorization failed" });
|
|
47514
|
-
const result = yield* Effect.try(() => decodeJson$
|
|
48077
|
+
const result = yield* Effect.try(() => decodeJson$3(body.text));
|
|
47515
48078
|
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
48079
|
endpoint = {
|
|
47517
48080
|
baseUrl,
|
|
@@ -49511,7 +50074,7 @@ main().catch(() => { if (!stopping) emit({ phase: 'failed', message: 'Remote dev
|
|
|
49511
50074
|
}
|
|
49512
50075
|
//#endregion
|
|
49513
50076
|
//#region src/device/SshDeviceConnection.ts
|
|
49514
|
-
const decodeJson$
|
|
50077
|
+
const decodeJson$2 = Schema$1.decodeUnknownSync(Schema$1.UnknownFromJsonString);
|
|
49515
50078
|
const START_TIMEOUT_MS = 66e4;
|
|
49516
50079
|
const FORWARD_TIMEOUT_MS = 15e3;
|
|
49517
50080
|
const VERIFY_TIMEOUT_MS = 3e4;
|
|
@@ -49666,7 +50229,7 @@ const connectSshDevice = Effect.fn("SshDeviceConnection.connect")(function* (inp
|
|
|
49666
50229
|
maxBytes: MAX_RESPONSE_BYTES$4
|
|
49667
50230
|
});
|
|
49668
50231
|
if (response.status !== 200 || body.truncated || body.invalidUtf8) return yield* fail("verifying forwarded agent")(void 0);
|
|
49669
|
-
const result = yield* Effect.try(() => decodeJson$
|
|
50232
|
+
const result = yield* Effect.try(() => decodeJson$2(body.text));
|
|
49670
50233
|
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
50234
|
}).pipe(Effect.scoped, Effect.timeout(Duration.millis(VERIFY_TIMEOUT_MS)), Effect.mapError(fail("verifying forwarded agent")));
|
|
49672
50235
|
return {
|
|
@@ -51095,7 +51658,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
|
|
|
51095
51658
|
});
|
|
51096
51659
|
yield* Console.log(output);
|
|
51097
51660
|
}));
|
|
51098
|
-
const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(Layer.provide(layer$
|
|
51661
|
+
const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(Layer.provide(layer$122(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)));
|
|
51099
51662
|
return yield* Effect.gen(function* () {
|
|
51100
51663
|
const snapshot = yield* getOfflineSnapshot();
|
|
51101
51664
|
const orchestrationEngine = yield* OrchestrationEngineService;
|
|
@@ -51106,7 +51669,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
|
|
|
51106
51669
|
});
|
|
51107
51670
|
yield* Console.log(output);
|
|
51108
51671
|
}).pipe(Effect.provide(offlineRuntimeLayer));
|
|
51109
|
-
}).pipe(Effect.provide(Layer.mergeAll(runtimeLayer, layer$70).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provide(layer$
|
|
51672
|
+
}).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
51673
|
});
|
|
51111
51674
|
const projectAddCommand = Command.make("add", {
|
|
51112
51675
|
...projectLocationFlags,
|
|
@@ -59682,7 +60245,7 @@ const TYPESAFE_REQUEST_BUDGET_TOKENS = 32e3;
|
|
|
59682
60245
|
const TYPESAFE_BUDGET_RESERVE_TOKENS = 2e3;
|
|
59683
60246
|
var TypeSafeRequestError = class extends Data.TaggedError("TypeSafeRequestError") {};
|
|
59684
60247
|
var TypeSafeClient = class extends Context.Service()("@p4code/cli/orchestration/triage/TypeSafeClient") {};
|
|
59685
|
-
const asRecord$
|
|
60248
|
+
const asRecord$10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
59686
60249
|
/**
|
|
59687
60250
|
* The finite numbers out of a probability map, or nothing.
|
|
59688
60251
|
*
|
|
@@ -59691,7 +60254,7 @@ const asRecord$8 = (value) => typeof value === "object" && value !== null && !Ar
|
|
|
59691
60254
|
* invented number is worse than one built on fewer.
|
|
59692
60255
|
*/
|
|
59693
60256
|
function readProbabilities(value) {
|
|
59694
|
-
const record = asRecord$
|
|
60257
|
+
const record = asRecord$10(value);
|
|
59695
60258
|
if (record === void 0) return void 0;
|
|
59696
60259
|
const probabilities = {};
|
|
59697
60260
|
for (const [key, entry] of Object.entries(record)) if (typeof entry === "number" && Number.isFinite(entry)) probabilities[key] = entry;
|
|
@@ -59705,7 +60268,7 @@ function readProbabilities(value) {
|
|
|
59705
60268
|
* can never reach the caller wearing a confidence it did not have.
|
|
59706
60269
|
*/
|
|
59707
60270
|
function parseTypeSafeAnswer(value) {
|
|
59708
|
-
const record = asRecord$
|
|
60271
|
+
const record = asRecord$10(value);
|
|
59709
60272
|
if (record === void 0) return void 0;
|
|
59710
60273
|
const confidence = record["confidence"];
|
|
59711
60274
|
switch (record["type"]) {
|
|
@@ -59746,7 +60309,7 @@ function parseTypeSafeAnswer(value) {
|
|
|
59746
60309
|
}
|
|
59747
60310
|
/** The one answer for the question that was asked, out of the answers map. */
|
|
59748
60311
|
function readAnswerFor(body, questionId) {
|
|
59749
|
-
const answers = asRecord$
|
|
60312
|
+
const answers = asRecord$10(asRecord$10(body)?.["answers"]);
|
|
59750
60313
|
return answers === void 0 ? void 0 : parseTypeSafeAnswer(answers[questionId]);
|
|
59751
60314
|
}
|
|
59752
60315
|
const makeTypeSafeClient = Effect.gen(function* () {
|
|
@@ -59850,19 +60413,19 @@ const estimateTokens$1 = (text) => Math.ceil(text.length / 4);
|
|
|
59850
60413
|
/** One line per event is plenty to answer "did anything reviewable happen". */
|
|
59851
60414
|
const MAX_LINE_CHARS = 400;
|
|
59852
60415
|
const oneLine = (text) => text.replace(/\s+/gu, " ").trim();
|
|
59853
|
-
const clip$
|
|
60416
|
+
const clip$2 = (text) => text.length <= MAX_LINE_CHARS ? text : `${text.slice(0, MAX_LINE_CHARS)}…`;
|
|
59854
60417
|
/** The line an event contributes, or null when it says nothing about the work. */
|
|
59855
60418
|
function describeEvent(event, implementerThreadId) {
|
|
59856
60419
|
switch (event.type) {
|
|
59857
60420
|
case "thread.message-sent": {
|
|
59858
60421
|
if (event.payload.threadId !== implementerThreadId) return null;
|
|
59859
60422
|
const text = oneLine(event.payload.text);
|
|
59860
|
-
return text.length === 0 ? null : clip$
|
|
60423
|
+
return text.length === 0 ? null : clip$2(`${event.payload.role}: ${text}`);
|
|
59861
60424
|
}
|
|
59862
60425
|
case "thread.activity-appended": {
|
|
59863
60426
|
if (event.payload.threadId !== implementerThreadId) return null;
|
|
59864
60427
|
const activity = event.payload.activity;
|
|
59865
|
-
return clip$
|
|
60428
|
+
return clip$2(oneLine(`${activity.kind}: ${activity.summary}`));
|
|
59866
60429
|
}
|
|
59867
60430
|
case "thread.turn-completed":
|
|
59868
60431
|
if (event.payload.threadId !== implementerThreadId) return null;
|
|
@@ -60085,7 +60648,7 @@ function renderPrefetchHint(selected) {
|
|
|
60085
60648
|
//#endregion
|
|
60086
60649
|
//#region src/orchestration/userInputRanking.ts
|
|
60087
60650
|
const MAX_OPTIONS = 255;
|
|
60088
|
-
const MAX_QUESTION_CHARS = 16e3;
|
|
60651
|
+
const MAX_QUESTION_CHARS$1 = 16e3;
|
|
60089
60652
|
const RANKING_TIMEOUT_MS = 1e4;
|
|
60090
60653
|
const PROBABILITY_SUM_TOLERANCE = .01;
|
|
60091
60654
|
/** Require a complete distribution; never invent missing option ranks. Equal scores share rank. */
|
|
@@ -60103,13 +60666,17 @@ function ranksFromAnswer(answer, count) {
|
|
|
60103
60666
|
})).sort((a, b) => b.probability - a.probability || a.optionIndex - b.optionIndex);
|
|
60104
60667
|
if (Math.abs(ranked.reduce((sum, item) => sum + item.probability, 0) - 1) > PROBABILITY_SUM_TOLERANCE) return null;
|
|
60105
60668
|
let rank = 1;
|
|
60106
|
-
return {
|
|
60107
|
-
|
|
60108
|
-
|
|
60109
|
-
|
|
60110
|
-
|
|
60111
|
-
|
|
60112
|
-
|
|
60669
|
+
return {
|
|
60670
|
+
confidence: answer.confidence,
|
|
60671
|
+
ranks: ranked.map((item, index) => {
|
|
60672
|
+
if (index > 0 && item.probability !== ranked[index - 1]?.probability) rank = index + 1;
|
|
60673
|
+
return {
|
|
60674
|
+
optionIndex: item.optionIndex,
|
|
60675
|
+
rank,
|
|
60676
|
+
probability: item.probability
|
|
60677
|
+
};
|
|
60678
|
+
})
|
|
60679
|
+
};
|
|
60113
60680
|
}
|
|
60114
60681
|
const makeUserInputRanker = Effect.gen(function* () {
|
|
60115
60682
|
const query = yield* ProjectionSnapshotQuery;
|
|
@@ -60128,7 +60695,7 @@ const makeUserInputRanker = Effect.gen(function* () {
|
|
|
60128
60695
|
const decoded = Schema$1.decodeUnknownOption(Schema$1.Struct({ questions: Schema$1.Array(UserInputQuestion) }))(payload);
|
|
60129
60696
|
const question = Option.isSome(decoded) ? decoded.value.questions.find((question) => question.id === input.questionId) : void 0;
|
|
60130
60697
|
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.");
|
|
60698
|
+
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
60699
|
const context = query.getHandoffContext ? yield* query.getHandoffContext(input.threadId).pipe(Effect.mapError(() => fail("Could not read question context."))) : Option.none();
|
|
60133
60700
|
if (Option.isNone(context)) return yield* fail("Question context is unavailable.");
|
|
60134
60701
|
const state = context.value.transcript;
|
|
@@ -60568,8 +61135,15 @@ const FUSION_SAFETY_KERNEL = [
|
|
|
60568
61135
|
* `task_propose` is deliberately absent: it tells its caller to stop and wait
|
|
60569
61136
|
* for the user, and a supervisor that waits is one the builder sits idle
|
|
60570
61137
|
* behind. Asking the user is what `ask_user_question` is for.
|
|
61138
|
+
*
|
|
61139
|
+
* `thread_spawn` is here because starting a thread is a coordination act: when
|
|
61140
|
+
* the authorized work needs a separate checkout, a different provider or its
|
|
61141
|
+
* own pair, the half that owns the plan is the half that decides. It stays in
|
|
61142
|
+
* the builder's denial list, so the pair still has exactly one thread that can
|
|
61143
|
+
* open new work.
|
|
60571
61144
|
*/
|
|
60572
61145
|
const FUSION_SUPERVISOR_P4_TOOLS = /* @__PURE__ */ new Set([
|
|
61146
|
+
"thread_spawn",
|
|
60573
61147
|
"thread_watch_review",
|
|
60574
61148
|
"thread_watch_events",
|
|
60575
61149
|
"thread_evidence_report",
|
|
@@ -60587,7 +61161,8 @@ const FUSION_SUPERVISOR_P4_TOOLS = /* @__PURE__ */ new Set([
|
|
|
60587
61161
|
"task_list",
|
|
60588
61162
|
"task_create",
|
|
60589
61163
|
"task_update",
|
|
60590
|
-
"ticket_resolve"
|
|
61164
|
+
"ticket_resolve",
|
|
61165
|
+
"ticket_comment"
|
|
60591
61166
|
]);
|
|
60592
61167
|
/**
|
|
60593
61168
|
* What the builder half must not reach.
|
|
@@ -60597,8 +61172,9 @@ const FUSION_SUPERVISOR_P4_TOOLS = /* @__PURE__ */ new Set([
|
|
|
60597
61172
|
* those should reach it without an edit here. What it must never have is the
|
|
60598
61173
|
* pair's own machinery - spawning threads, creating pairs, advising, answering
|
|
60599
61174
|
* its own gate - the phase plan, which the supervisor owns outright, plus
|
|
60600
|
-
* task-board mutation
|
|
60601
|
-
*
|
|
61175
|
+
* task-board mutation and the tracker comment that closes a task. Both roles
|
|
61176
|
+
* may read paired history and task-relevant external evidence; only Supervisor
|
|
61177
|
+
* writes review reports and the phase plan.
|
|
60602
61178
|
*
|
|
60603
61179
|
* Both roles retain `thread_pair_detach` as their way out. Its handler limits
|
|
60604
61180
|
* detaching to a pair containing the caller whose other thread this session
|
|
@@ -60620,6 +61196,7 @@ const FUSION_BUILDER_DENIED_P4_TOOLS = /* @__PURE__ */ new Set([
|
|
|
60620
61196
|
"task_create",
|
|
60621
61197
|
"task_update",
|
|
60622
61198
|
"task_propose",
|
|
61199
|
+
"ticket_comment",
|
|
60623
61200
|
"memory_append"
|
|
60624
61201
|
]);
|
|
60625
61202
|
/**
|
|
@@ -60818,7 +61395,7 @@ const forceStopOwnedProcess = (threadId, options) => threadLocks.withPermit(thre
|
|
|
60818
61395
|
}));
|
|
60819
61396
|
//#endregion
|
|
60820
61397
|
//#region ../../packages/shared/src/toolActivity.ts
|
|
60821
|
-
function asRecord$
|
|
61398
|
+
function asRecord$9(value) {
|
|
60822
61399
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
60823
61400
|
}
|
|
60824
61401
|
function asTrimmedString$1(value) {
|
|
@@ -60848,10 +61425,10 @@ function extractCommandFromTitle$1(title) {
|
|
|
60848
61425
|
return /`([^`]+)`/u.exec(title)?.[1]?.trim() || void 0;
|
|
60849
61426
|
}
|
|
60850
61427
|
function extractToolCommand(data, title) {
|
|
60851
|
-
const item = asRecord$
|
|
60852
|
-
const itemInput = asRecord$
|
|
60853
|
-
const itemResult = asRecord$
|
|
60854
|
-
const rawInput = asRecord$
|
|
61428
|
+
const item = asRecord$9(data?.item);
|
|
61429
|
+
const itemInput = asRecord$9(item?.input);
|
|
61430
|
+
const itemResult = asRecord$9(item?.result);
|
|
61431
|
+
const rawInput = asRecord$9(data?.rawInput);
|
|
60855
61432
|
const direct = [
|
|
60856
61433
|
normalizeCommandValue$1(item?.command),
|
|
60857
61434
|
normalizeCommandValue$1(itemInput?.command),
|
|
@@ -60879,7 +61456,7 @@ function collectPaths(value, paths, seen, depth) {
|
|
|
60879
61456
|
}
|
|
60880
61457
|
return;
|
|
60881
61458
|
}
|
|
60882
|
-
const record = asRecord$
|
|
61459
|
+
const record = asRecord$9(value);
|
|
60883
61460
|
if (!record) return;
|
|
60884
61461
|
for (const key of [
|
|
60885
61462
|
"path",
|
|
@@ -60938,7 +61515,7 @@ function deriveToolActivityPresentation(input) {
|
|
|
60938
61515
|
const title = asTrimmedString$1(input.title);
|
|
60939
61516
|
const detail = stripTrailingExitCode(asTrimmedString$1(input.detail));
|
|
60940
61517
|
const fallbackSummary = asTrimmedString$1(input.fallbackSummary) ?? "Tool";
|
|
60941
|
-
const data = asRecord$
|
|
61518
|
+
const data = asRecord$9(input.data);
|
|
60942
61519
|
const command = extractToolCommand(data, title);
|
|
60943
61520
|
const primaryPath = extractPrimaryPath(data);
|
|
60944
61521
|
const action = classifyToolAction({
|
|
@@ -60962,7 +61539,7 @@ function deriveToolActivityPresentation(input) {
|
|
|
60962
61539
|
...primaryPath ? { detail: primaryPath } : {}
|
|
60963
61540
|
};
|
|
60964
61541
|
if (action === "search") {
|
|
60965
|
-
const query = asTrimmedString$1(asRecord$
|
|
61542
|
+
const query = asTrimmedString$1(asRecord$9(data?.rawInput)?.query) ?? asTrimmedString$1(asRecord$9(data?.rawInput)?.pattern) ?? asTrimmedString$1(asRecord$9(data?.rawInput)?.searchTerm);
|
|
60966
61543
|
return {
|
|
60967
61544
|
summary: "Searched files",
|
|
60968
61545
|
...query ? { detail: query } : {}
|
|
@@ -63215,6 +63792,7 @@ const P4CODE_MCP_TOOL_NAMES = [
|
|
|
63215
63792
|
"thread_spawn",
|
|
63216
63793
|
"thread_watch_events",
|
|
63217
63794
|
"thread_watch_review",
|
|
63795
|
+
"ticket_comment",
|
|
63218
63796
|
"ticket_resolve"
|
|
63219
63797
|
];
|
|
63220
63798
|
/** The catalogue as the adapters read it. */
|
|
@@ -98870,7 +99448,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
|
|
|
98870
99448
|
};
|
|
98871
99449
|
});
|
|
98872
99450
|
var OpenCodeRuntime = class extends Context.Service()("@p4code/cli/provider/opencodeRuntime") {};
|
|
98873
|
-
const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(Layer.provide(layer$
|
|
99451
|
+
const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(Layer.provide(layer$123));
|
|
98874
99452
|
//#endregion
|
|
98875
99453
|
//#region src/textGeneration/OpenCodeTextGeneration.ts
|
|
98876
99454
|
const OPENCODE_TEXT_GENERATION_IDLE_TTL = "30 seconds";
|
|
@@ -103776,6 +104354,136 @@ const openForActiveThread = Effect.fn("DeviceThreadAdmission.open")(function* (i
|
|
|
103776
104354
|
function isRegisteredProviderWorkspace(cwd, projects, threads) {
|
|
103777
104355
|
return projects.some((project) => project.workspaceRoot === cwd) || threads.some((thread) => thread.worktreePath === cwd);
|
|
103778
104356
|
}
|
|
104357
|
+
//#endregion
|
|
104358
|
+
//#region src/hub/HubActivityTransport.ts
|
|
104359
|
+
const REQUEST_TIMEOUT_MS$3 = 8e3;
|
|
104360
|
+
const CLEANUP_TIMEOUT_MS = 1e3;
|
|
104361
|
+
const MAX_RESPONSE_BYTES = 4096;
|
|
104362
|
+
const MAX_REQUEST_BYTES = 16 * 1024;
|
|
104363
|
+
const MAX_BEARER_CHARS = 8192;
|
|
104364
|
+
var HubActivityUnavailable = class extends Data.TaggedError("HubActivityUnavailable") {};
|
|
104365
|
+
var HubActivityUnauthorized = class extends Data.TaggedError("HubActivityUnauthorized") {};
|
|
104366
|
+
/** All errors are payload-free; late responses still cancel their bodies after owner interruption. */
|
|
104367
|
+
const makeHubActivityTransport = Effect.fn("HubActivityTransport.make")(function* (settings, fetcher = fetch) {
|
|
104368
|
+
const origin = yield* Effect.try({
|
|
104369
|
+
try: () => {
|
|
104370
|
+
const url = new URL(settings.baseUrl);
|
|
104371
|
+
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();
|
|
104372
|
+
return url.origin;
|
|
104373
|
+
},
|
|
104374
|
+
catch: () => new HubActivityUnavailable()
|
|
104375
|
+
});
|
|
104376
|
+
const request = Effect.fn("HubActivityTransport.request")(function* (path, body, membership) {
|
|
104377
|
+
return yield* Effect.tryPromise({
|
|
104378
|
+
try: async (signal) => {
|
|
104379
|
+
const controller = new AbortController();
|
|
104380
|
+
const abort = () => controller.abort();
|
|
104381
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
104382
|
+
if (signal.aborted) abort();
|
|
104383
|
+
const timer = setTimeout(abort, REQUEST_TIMEOUT_MS$3);
|
|
104384
|
+
let reader;
|
|
104385
|
+
const cancel = async (work) => {
|
|
104386
|
+
let timeout;
|
|
104387
|
+
try {
|
|
104388
|
+
await Promise.race([Promise.resolve().then(work).catch(() => void 0), new Promise((resolve) => {
|
|
104389
|
+
timeout = setTimeout(resolve, CLEANUP_TIMEOUT_MS);
|
|
104390
|
+
})]);
|
|
104391
|
+
} finally {
|
|
104392
|
+
if (timeout) clearTimeout(timeout);
|
|
104393
|
+
}
|
|
104394
|
+
};
|
|
104395
|
+
try {
|
|
104396
|
+
if (controller.signal.aborted) throw new HubActivityUnavailable();
|
|
104397
|
+
const response = await fetcher(`${origin}${path}`, {
|
|
104398
|
+
method: body === void 0 ? "GET" : "POST",
|
|
104399
|
+
headers: {
|
|
104400
|
+
authorization: `Bearer ${settings.token}`,
|
|
104401
|
+
...membership ? {
|
|
104402
|
+
"x-p4-hub-account": membership.accountId,
|
|
104403
|
+
"x-p4-hub-environment": membership.environmentId
|
|
104404
|
+
} : {},
|
|
104405
|
+
...body === void 0 ? {} : { "content-type": "application/json" }
|
|
104406
|
+
},
|
|
104407
|
+
body,
|
|
104408
|
+
signal: controller.signal,
|
|
104409
|
+
redirect: "error",
|
|
104410
|
+
credentials: "omit",
|
|
104411
|
+
cache: "no-store"
|
|
104412
|
+
});
|
|
104413
|
+
if (controller.signal.aborted) {
|
|
104414
|
+
await cancel(() => response.body?.cancel() ?? Promise.resolve());
|
|
104415
|
+
throw new HubActivityUnavailable();
|
|
104416
|
+
}
|
|
104417
|
+
reader = response.body?.getReader();
|
|
104418
|
+
if (response.status === 401 || response.status === 403) throw new HubActivityUnauthorized();
|
|
104419
|
+
if (response.status !== 200 || !/^application\/json(?:\s*;|$)/i.test(response.headers.get("content-type") ?? "") || !reader) throw new HubActivityUnavailable();
|
|
104420
|
+
let length = 0;
|
|
104421
|
+
const chunks = [];
|
|
104422
|
+
while (true) {
|
|
104423
|
+
const item = await reader.read();
|
|
104424
|
+
if (controller.signal.aborted) throw new HubActivityUnavailable();
|
|
104425
|
+
if (item.done) break;
|
|
104426
|
+
length += item.value.byteLength;
|
|
104427
|
+
if (length > MAX_RESPONSE_BYTES) throw new HubActivityUnavailable();
|
|
104428
|
+
if (item.value.byteLength) chunks.push(item.value);
|
|
104429
|
+
}
|
|
104430
|
+
const bytes = new Uint8Array(length);
|
|
104431
|
+
let offset = 0;
|
|
104432
|
+
for (const chunk of chunks) {
|
|
104433
|
+
bytes.set(chunk, offset);
|
|
104434
|
+
offset += chunk.byteLength;
|
|
104435
|
+
}
|
|
104436
|
+
return Schema$1.decodeUnknownSync(Schema$1.UnknownFromJsonString)(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
104437
|
+
} finally {
|
|
104438
|
+
controller.abort();
|
|
104439
|
+
clearTimeout(timer);
|
|
104440
|
+
signal.removeEventListener("abort", abort);
|
|
104441
|
+
if (reader) await cancel(() => reader.cancel());
|
|
104442
|
+
}
|
|
104443
|
+
},
|
|
104444
|
+
catch: (error) => error instanceof HubActivityUnauthorized ? error : new HubActivityUnavailable()
|
|
104445
|
+
}).pipe(Effect.timeout(9e3), Effect.mapError((error) => error instanceof HubActivityUnauthorized ? error : new HubActivityUnavailable()));
|
|
104446
|
+
});
|
|
104447
|
+
return {
|
|
104448
|
+
identity: Effect.fn("HubActivityTransport.identity")(function* () {
|
|
104449
|
+
const membership = yield* request("/v1/identity").pipe(Effect.flatMap(Schema$1.decodeUnknownEffect(HubServerMembership, { onExcessProperty: "error" })), Effect.mapError((error) => error instanceof HubActivityUnauthorized ? error : new HubActivityUnavailable()));
|
|
104450
|
+
const destination = yield* Schema$1.encodeUnknownEffect(Schema$1.UnknownFromJsonString)([
|
|
104451
|
+
origin,
|
|
104452
|
+
membership.accountId,
|
|
104453
|
+
membership.environmentId
|
|
104454
|
+
]);
|
|
104455
|
+
return {
|
|
104456
|
+
...membership,
|
|
104457
|
+
destination: NodeCrypto.createHash("sha256").update(destination).digest("hex")
|
|
104458
|
+
};
|
|
104459
|
+
}),
|
|
104460
|
+
post: Effect.fn("HubActivityTransport.post")(function* (input, membership) {
|
|
104461
|
+
const activity = yield* Schema$1.decodeUnknownEffect(HubAgentActivityInput)(input, { onExcessProperty: "error" }).pipe(Effect.mapError(() => new HubActivityUnavailable()));
|
|
104462
|
+
const body = yield* Schema$1.encodeEffect(Schema$1.fromJsonString(HubAgentActivityInput))(activity);
|
|
104463
|
+
if (Buffer.byteLength(body) > MAX_REQUEST_BYTES) return yield* Effect.fail(new HubActivityUnavailable());
|
|
104464
|
+
const result = yield* request("/v1/agent-activity", body, membership);
|
|
104465
|
+
return yield* Schema$1.decodeUnknownEffect(Schema$1.Struct({ accepted: Schema$1.Boolean }))(result, { onExcessProperty: "error" }).pipe(Effect.mapError(() => new HubActivityUnavailable()));
|
|
104466
|
+
}),
|
|
104467
|
+
postFact: Effect.fn("HubActivityTransport.postFact")(function* (input, membership) {
|
|
104468
|
+
const fact = yield* Schema$1.decodeUnknownEffect(HubAnalyticsFactInput)(input, { onExcessProperty: "error" }).pipe(Effect.mapError(() => new HubActivityUnavailable()));
|
|
104469
|
+
const body = yield* Schema$1.encodeEffect(Schema$1.fromJsonString(HubAnalyticsFactInput))(fact);
|
|
104470
|
+
if (Buffer.byteLength(body) > MAX_REQUEST_BYTES) return yield* Effect.fail(new HubActivityUnavailable());
|
|
104471
|
+
const result = yield* request("/v1/analytics/facts", body, membership);
|
|
104472
|
+
return yield* Schema$1.decodeUnknownEffect(Schema$1.Struct({ accepted: Schema$1.Boolean }))(result, { onExcessProperty: "error" }).pipe(Effect.mapError(() => new HubActivityUnavailable()));
|
|
104473
|
+
}),
|
|
104474
|
+
queryFacts: Effect.fn("HubActivityTransport.queryFacts")(function* (query, membership) {
|
|
104475
|
+
const search = new URLSearchParams();
|
|
104476
|
+
for (const kind of query.kinds ?? []) search.append("kind", kind);
|
|
104477
|
+
if (query.since !== void 0) search.set("since", query.since);
|
|
104478
|
+
if (query.until !== void 0) search.set("until", query.until);
|
|
104479
|
+
if (query.limit !== void 0) search.set("limit", String(query.limit));
|
|
104480
|
+
if (query.cursor !== void 0) search.set("cursor", query.cursor);
|
|
104481
|
+
const suffix = search.size === 0 ? "" : `?${search.toString()}`;
|
|
104482
|
+
const result = yield* request(`/v1/analytics/facts${suffix}`, void 0, membership);
|
|
104483
|
+
return yield* Schema$1.decodeUnknownEffect(HubAnalyticsPage)(result, { onExcessProperty: "error" }).pipe(Effect.mapError(() => new HubActivityUnavailable()));
|
|
104484
|
+
})
|
|
104485
|
+
};
|
|
104486
|
+
});
|
|
103779
104487
|
(() => {
|
|
103780
104488
|
try {
|
|
103781
104489
|
return process.env.NODE_ENV === "development";
|
|
@@ -104486,7 +105194,7 @@ const NESTED_PAYLOAD_KEYS = [
|
|
|
104486
105194
|
"operations"
|
|
104487
105195
|
];
|
|
104488
105196
|
const MAX_COLLECT_DEPTH = 4;
|
|
104489
|
-
function asRecord$
|
|
105197
|
+
function asRecord$8(value) {
|
|
104490
105198
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
104491
105199
|
}
|
|
104492
105200
|
function pushChangedFilePath(target, value) {
|
|
@@ -104501,7 +105209,7 @@ function collectChangedFilePaths(value, target, depth) {
|
|
|
104501
105209
|
for (const entry of value) collectChangedFilePaths(entry, target, depth + 1);
|
|
104502
105210
|
return;
|
|
104503
105211
|
}
|
|
104504
|
-
const record = asRecord$
|
|
105212
|
+
const record = asRecord$8(value);
|
|
104505
105213
|
if (!record) return;
|
|
104506
105214
|
for (const field of CHANGED_FILE_FIELDS) pushChangedFilePath(target, record[field]);
|
|
104507
105215
|
for (const nestedKey of NESTED_PAYLOAD_KEYS) if (nestedKey in record) collectChangedFilePaths(record[nestedKey], target, depth + 1);
|
|
@@ -104512,7 +105220,7 @@ function collectChangedFilePaths(value, target, depth) {
|
|
|
104512
105220
|
*/
|
|
104513
105221
|
function collectActivityChangedFilePaths(payload) {
|
|
104514
105222
|
const target = /* @__PURE__ */ new Set();
|
|
104515
|
-
collectChangedFilePaths(asRecord$
|
|
105223
|
+
collectChangedFilePaths(asRecord$8(asRecord$8(payload)?.data), target, 0);
|
|
104516
105224
|
return target;
|
|
104517
105225
|
}
|
|
104518
105226
|
/**
|
|
@@ -122485,7 +123193,7 @@ const makeBtwRequestCoordinator = (dependencies) => Effect.gen(function* () {
|
|
|
122485
123193
|
* already negotiated.
|
|
122486
123194
|
*/
|
|
122487
123195
|
const MCP_PROTOCOL_VERSION = "2025-06-18";
|
|
122488
|
-
const REQUEST_TIMEOUT_MS$
|
|
123196
|
+
const REQUEST_TIMEOUT_MS$2 = 15e3;
|
|
122489
123197
|
var McpToolCallError = class extends Schema$1.TaggedErrorClass()("McpToolCallError", {
|
|
122490
123198
|
detail: Schema$1.String,
|
|
122491
123199
|
status: Schema$1.NullOr(Schema$1.Number)
|
|
@@ -122549,7 +123257,7 @@ const callMcpTool = Effect.fn("McpToolClient.callMcpTool")(function* (input) {
|
|
|
122549
123257
|
...input.headers,
|
|
122550
123258
|
...extraHeaders,
|
|
122551
123259
|
accept: "application/json, text/event-stream"
|
|
122552
|
-
}), body)).pipe(Effect.timeout(REQUEST_TIMEOUT_MS$
|
|
123260
|
+
}), body)).pipe(Effect.timeout(REQUEST_TIMEOUT_MS$2), Effect.catchCause((cause) => Effect.fail(new McpToolCallError({
|
|
122553
123261
|
detail: `Could not reach ${input.url}: ${String(cause)}`,
|
|
122554
123262
|
status: null
|
|
122555
123263
|
}))));
|
|
@@ -122630,6 +123338,9 @@ const callMcpTool = Effect.fn("McpToolClient.callMcpTool")(function* (input) {
|
|
|
122630
123338
|
* name that drifts between them fails at runtime with a shrug from the server.
|
|
122631
123339
|
*/
|
|
122632
123340
|
const LINEAR_GET_ISSUE_TOOL = "get_issue";
|
|
123341
|
+
/** The write half: one comment, and the links filed beside it. */
|
|
123342
|
+
const LINEAR_SAVE_COMMENT_TOOL = "save_comment";
|
|
123343
|
+
const LINEAR_CREATE_ATTACHMENT_TOOL = "create_attachment";
|
|
122633
123344
|
/** Why a Linear call could not be made, in terms a person can act on. */
|
|
122634
123345
|
var LinearUnavailable = class extends Schema$1.TaggedErrorClass()("LinearUnavailable", {
|
|
122635
123346
|
reason: Schema$1.Literals([
|
|
@@ -122643,7 +123354,7 @@ var LinearUnavailable = class extends Schema$1.TaggedErrorClass()("LinearUnavail
|
|
|
122643
123354
|
return this.detail;
|
|
122644
123355
|
}
|
|
122645
123356
|
};
|
|
122646
|
-
const asRecord$
|
|
123357
|
+
const asRecord$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
122647
123358
|
const asArray$1 = (value) => Array.isArray(value) ? value : void 0;
|
|
122648
123359
|
/**
|
|
122649
123360
|
* Find the payload inside whatever the tool returned.
|
|
@@ -122673,22 +123384,22 @@ const readPayload = (result) => {
|
|
|
122673
123384
|
*/
|
|
122674
123385
|
const readRows = (payload) => {
|
|
122675
123386
|
const direct = asArray$1(payload);
|
|
122676
|
-
if (direct !== void 0) return direct.map(asRecord$
|
|
122677
|
-
const record = asRecord$
|
|
123387
|
+
if (direct !== void 0) return direct.map(asRecord$7).filter((row) => row !== void 0);
|
|
123388
|
+
const record = asRecord$7(payload);
|
|
122678
123389
|
if (record === void 0) return [];
|
|
122679
123390
|
for (const value of Object.values(record)) {
|
|
122680
123391
|
const rows = asArray$1(value);
|
|
122681
|
-
if (rows !== void 0) return rows.map(asRecord$
|
|
123392
|
+
if (rows !== void 0) return rows.map(asRecord$7).filter((row) => row !== void 0);
|
|
122682
123393
|
}
|
|
122683
123394
|
return [];
|
|
122684
123395
|
};
|
|
122685
123396
|
/** The single object out of a result, unwrapping one level of nesting. */
|
|
122686
123397
|
const readOne = (payload) => {
|
|
122687
|
-
const record = asRecord$
|
|
123398
|
+
const record = asRecord$7(payload);
|
|
122688
123399
|
if (record === void 0) return;
|
|
122689
123400
|
if (record["id"] !== void 0 || record["identifier"] !== void 0) return record;
|
|
122690
123401
|
for (const value of Object.values(record)) {
|
|
122691
|
-
const nested = asRecord$
|
|
123402
|
+
const nested = asRecord$7(value);
|
|
122692
123403
|
if (nested?.["id"] !== void 0) return nested;
|
|
122693
123404
|
}
|
|
122694
123405
|
};
|
|
@@ -122798,7 +123509,7 @@ const TrackerSecretsLive = Layer.effect(TrackerSecrets, makeTrackerSecrets);
|
|
|
122798
123509
|
*
|
|
122799
123510
|
* @module tracker/graphqlRequest
|
|
122800
123511
|
*/
|
|
122801
|
-
const REQUEST_TIMEOUT_MS$
|
|
123512
|
+
const REQUEST_TIMEOUT_MS$1 = 15e3;
|
|
122802
123513
|
var GraphqlRequestError = class extends Schema$1.TaggedErrorClass()("GraphqlRequestError", {
|
|
122803
123514
|
detail: Schema$1.String,
|
|
122804
123515
|
status: Schema$1.NullOr(Schema$1.Number)
|
|
@@ -122807,14 +123518,14 @@ var GraphqlRequestError = class extends Schema$1.TaggedErrorClass()("GraphqlRequ
|
|
|
122807
123518
|
return this.detail;
|
|
122808
123519
|
}
|
|
122809
123520
|
};
|
|
122810
|
-
const asRecord$
|
|
123521
|
+
const asRecord$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
122811
123522
|
const errorMessages = (value) => {
|
|
122812
123523
|
if (!Array.isArray(value)) return [];
|
|
122813
|
-
return value.map((entry) => asRecord$
|
|
123524
|
+
return value.map((entry) => asRecord$6(entry)?.["message"]).filter((message) => typeof message === "string");
|
|
122814
123525
|
};
|
|
122815
123526
|
const parseJsonRecord = (text) => {
|
|
122816
123527
|
try {
|
|
122817
|
-
return asRecord$
|
|
123528
|
+
return asRecord$6(JSON.parse(text));
|
|
122818
123529
|
} catch {
|
|
122819
123530
|
return;
|
|
122820
123531
|
}
|
|
@@ -122823,7 +123534,7 @@ const graphqlRequest = Effect.fn("tracker/graphqlRequest")(function* (input) {
|
|
|
122823
123534
|
const response = yield* (yield* HttpClient$1.HttpClient).execute(HttpClientRequest$1.bodyJsonUnsafe(HttpClientRequest$1.setHeaders(HttpClientRequest$1.post(input.url), input.headers), {
|
|
122824
123535
|
query: input.query,
|
|
122825
123536
|
variables: input.variables ?? {}
|
|
122826
|
-
})).pipe(Effect.timeout(REQUEST_TIMEOUT_MS$
|
|
123537
|
+
})).pipe(Effect.timeout(REQUEST_TIMEOUT_MS$1), Effect.catchCause((cause) => Effect.fail(new GraphqlRequestError({
|
|
122827
123538
|
detail: `Could not reach ${input.url}: ${String(cause)}`,
|
|
122828
123539
|
status: null
|
|
122829
123540
|
}))));
|
|
@@ -122838,7 +123549,7 @@ const graphqlRequest = Effect.fn("tracker/graphqlRequest")(function* (input) {
|
|
|
122838
123549
|
status: response.status
|
|
122839
123550
|
});
|
|
122840
123551
|
return {
|
|
122841
|
-
data: asRecord$
|
|
123552
|
+
data: asRecord$6(record["data"]),
|
|
122842
123553
|
errors: errorMessages(record["errors"])
|
|
122843
123554
|
};
|
|
122844
123555
|
});
|
|
@@ -122847,10 +123558,11 @@ const graphqlRequest = Effect.fn("tracker/graphqlRequest")(function* (input) {
|
|
|
122847
123558
|
/**
|
|
122848
123559
|
* Linear over its own GraphQL API, wearing the MCP tool vocabulary.
|
|
122849
123560
|
*
|
|
122850
|
-
* This speaks the same
|
|
122851
|
-
* `
|
|
122852
|
-
*
|
|
122853
|
-
*
|
|
123561
|
+
* This speaks the same "tools" the Linear MCP server offers - `list_issues`,
|
|
123562
|
+
* `get_issue`, `save_issue`, `list_comments`, `save_comment` and
|
|
123563
|
+
* `create_attachment` - and answers in the same row shapes, so `LinearTasks`,
|
|
123564
|
+
* the resolver and the closeout path cannot tell which transport served
|
|
123565
|
+
* them. That is the point: the MCP server remains the fallback, and a
|
|
122854
123566
|
* consumer that could tell the two apart would need two code paths.
|
|
122855
123567
|
*
|
|
122856
123568
|
* The one real difference is hidden here: the MCP tools accept names where
|
|
@@ -122891,7 +123603,7 @@ const STATE_TYPES = /* @__PURE__ */ new Set([
|
|
|
122891
123603
|
"completed",
|
|
122892
123604
|
"canceled"
|
|
122893
123605
|
]);
|
|
122894
|
-
const asRecord$
|
|
123606
|
+
const asRecord$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
122895
123607
|
const asArray = (value) => Array.isArray(value) ? value : [];
|
|
122896
123608
|
const asString = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
122897
123609
|
/**
|
|
@@ -122901,9 +123613,9 @@ const asString = (value) => typeof value === "string" && value.trim().length > 0
|
|
|
122901
123613
|
* `labels` stay structured - the mapping already accepts both forms.
|
|
122902
123614
|
*/
|
|
122903
123615
|
const toIssueRow = (node) => {
|
|
122904
|
-
const state = asRecord$
|
|
122905
|
-
const parent = asRecord$
|
|
122906
|
-
const labels = asRecord$
|
|
123616
|
+
const state = asRecord$5(node["state"]);
|
|
123617
|
+
const parent = asRecord$5(node["parent"]);
|
|
123618
|
+
const labels = asRecord$5(node["labels"]);
|
|
122907
123619
|
return {
|
|
122908
123620
|
id: node["id"],
|
|
122909
123621
|
identifier: node["identifier"],
|
|
@@ -122920,6 +123632,29 @@ const toIssueRow = (node) => {
|
|
|
122920
123632
|
parentId: parent?.["id"]
|
|
122921
123633
|
};
|
|
122922
123634
|
};
|
|
123635
|
+
/** Everything a closeout comment's caller renders back: who, when, and where. */
|
|
123636
|
+
const COMMENT_SELECTION = `
|
|
123637
|
+
id
|
|
123638
|
+
body
|
|
123639
|
+
url
|
|
123640
|
+
createdAt
|
|
123641
|
+
user { id name displayName }
|
|
123642
|
+
`;
|
|
123643
|
+
/** An attachment as Linear files it: a link with a label, against one issue. */
|
|
123644
|
+
const ATTACHMENT_SELECTION = `
|
|
123645
|
+
id
|
|
123646
|
+
url
|
|
123647
|
+
title
|
|
123648
|
+
subtitle
|
|
123649
|
+
`;
|
|
123650
|
+
/** One comment node, flattened the way `toIssueRow` flattens an issue. */
|
|
123651
|
+
const toCommentRow = (node) => ({
|
|
123652
|
+
id: node["id"],
|
|
123653
|
+
body: node["body"],
|
|
123654
|
+
url: node["url"],
|
|
123655
|
+
createdAt: node["createdAt"],
|
|
123656
|
+
user: node["user"]
|
|
123657
|
+
});
|
|
122923
123658
|
/** A case-insensitive equality filter over the ways a person is named. */
|
|
122924
123659
|
const personFilter = (person) => ({ or: [
|
|
122925
123660
|
{ displayName: { eqIgnoreCase: person } },
|
|
@@ -122943,7 +123678,7 @@ const makeLinearApiTransport = Effect.gen(function* () {
|
|
|
122943
123678
|
})));
|
|
122944
123679
|
/** The one entity out of `data`, or a failure carrying Linear's own words. */
|
|
122945
123680
|
const readEntity = (response, key) => {
|
|
122946
|
-
const entity = asRecord$
|
|
123681
|
+
const entity = asRecord$5(response.data?.[key]);
|
|
122947
123682
|
if (entity !== void 0) return Effect.succeed(entity);
|
|
122948
123683
|
if (response.errors.length === 0) return Effect.void.pipe(Effect.as(void 0));
|
|
122949
123684
|
if (response.errors.some((message) => /not found|does not exist/iu.test(message))) return Effect.void.pipe(Effect.as(void 0));
|
|
@@ -122952,7 +123687,7 @@ const makeLinearApiTransport = Effect.gen(function* () {
|
|
|
122952
123687
|
detail: response.errors.join("; ")
|
|
122953
123688
|
}));
|
|
122954
123689
|
};
|
|
122955
|
-
const nodesOf = (response, key) => asArray(asRecord$
|
|
123690
|
+
const nodesOf = (response, key) => asArray(asRecord$5(response.data?.[key])?.["nodes"]).map(asRecord$5).filter((node) => node !== void 0);
|
|
122956
123691
|
const resolveTeamId = (apiKey, team) => Effect.gen(function* () {
|
|
122957
123692
|
const response = yield* request(apiKey, `query($filter: TeamFilter) { teams(filter: $filter, first: 2) { nodes { id } } }`, { filter: { or: [{ key: { eqIgnoreCase: team } }, { name: { eqIgnoreCase: team } }] } });
|
|
122958
123693
|
const id = asString(nodesOf(response, "teams")[0]?.["id"]);
|
|
@@ -122965,7 +123700,7 @@ const makeLinearApiTransport = Effect.gen(function* () {
|
|
|
122965
123700
|
const teamIdOfIssue = (apiKey, issueId) => Effect.gen(function* () {
|
|
122966
123701
|
const response = yield* request(apiKey, `query($id: String!) { issue(id: $id) { team { id } } }`, { id: issueId });
|
|
122967
123702
|
const issue = yield* readEntity(response, "issue");
|
|
122968
|
-
const id = asString(asRecord$
|
|
123703
|
+
const id = asString(asRecord$5(issue?.["team"])?.["id"]);
|
|
122969
123704
|
if (id === void 0) return yield* new LinearUnavailable({
|
|
122970
123705
|
reason: "failed",
|
|
122971
123706
|
detail: `Linear has no issue "${issueId}" to read a team from.`
|
|
@@ -123085,13 +123820,114 @@ const makeLinearApiTransport = Effect.gen(function* () {
|
|
|
123085
123820
|
input
|
|
123086
123821
|
});
|
|
123087
123822
|
const payload = yield* readEntity(response, issueId === void 0 ? "issueCreate" : "issueUpdate");
|
|
123088
|
-
const issue = asRecord$
|
|
123823
|
+
const issue = asRecord$5(payload?.["issue"]);
|
|
123089
123824
|
if (issue === void 0) return yield* new LinearUnavailable({
|
|
123090
123825
|
reason: "failed",
|
|
123091
123826
|
detail: response.errors.length > 0 ? response.errors.join("; ") : "Linear accepted the write but returned no issue."
|
|
123092
123827
|
});
|
|
123093
123828
|
return toIssueRow(issue);
|
|
123094
123829
|
});
|
|
123830
|
+
/**
|
|
123831
|
+
* The issue's own id, from whatever a caller had - an identifier, a URL's
|
|
123832
|
+
* tail, or the id itself.
|
|
123833
|
+
*
|
|
123834
|
+
* The comment and attachment mutations take the UUID only, while every
|
|
123835
|
+
* caller here starts from what a person typed. One lookup closes that gap
|
|
123836
|
+
* and gives the "no such issue" sentence a name to report.
|
|
123837
|
+
*/
|
|
123838
|
+
const resolveIssueNodeId = (apiKey, reference) => Effect.gen(function* () {
|
|
123839
|
+
const response = yield* request(apiKey, `query($id: String!) { issue(id: $id) { id } }`, { id: reference });
|
|
123840
|
+
const issue = yield* readEntity(response, "issue");
|
|
123841
|
+
const id = asString(issue?.["id"]);
|
|
123842
|
+
if (id === void 0) return yield* new LinearUnavailable({
|
|
123843
|
+
reason: "failed",
|
|
123844
|
+
detail: `Linear has no issue "${reference}".`
|
|
123845
|
+
});
|
|
123846
|
+
return id;
|
|
123847
|
+
});
|
|
123848
|
+
const listComments = (apiKey, args) => Effect.gen(function* () {
|
|
123849
|
+
const reference = asString(args["issueId"]) ?? asString(args["id"]);
|
|
123850
|
+
if (reference === void 0) return yield* new LinearUnavailable({
|
|
123851
|
+
reason: "failed",
|
|
123852
|
+
detail: "list_comments needs an issueId."
|
|
123853
|
+
});
|
|
123854
|
+
const response = yield* request(apiKey, `query($id: String!, $first: Int!) {
|
|
123855
|
+
issue(id: $id) {
|
|
123856
|
+
comments(first: $first) {
|
|
123857
|
+
nodes { ${COMMENT_SELECTION} }
|
|
123858
|
+
}
|
|
123859
|
+
}
|
|
123860
|
+
}`, {
|
|
123861
|
+
id: reference,
|
|
123862
|
+
first: typeof args["limit"] === "number" ? args["limit"] : 50
|
|
123863
|
+
});
|
|
123864
|
+
const issue = yield* readEntity(response, "issue");
|
|
123865
|
+
if (issue === void 0) return yield* new LinearUnavailable({
|
|
123866
|
+
reason: "failed",
|
|
123867
|
+
detail: `Linear has no issue "${reference}".`
|
|
123868
|
+
});
|
|
123869
|
+
return { comments: asArray(asRecord$5(issue["comments"])?.["nodes"]).map(asRecord$5).filter((node) => node !== void 0).map(toCommentRow) };
|
|
123870
|
+
});
|
|
123871
|
+
const saveComment = (apiKey, args) => Effect.gen(function* () {
|
|
123872
|
+
const reference = asString(args["issueId"]) ?? asString(args["id"]);
|
|
123873
|
+
const body = typeof args["body"] === "string" ? args["body"] : void 0;
|
|
123874
|
+
if (reference === void 0 || body === void 0) return yield* new LinearUnavailable({
|
|
123875
|
+
reason: "failed",
|
|
123876
|
+
detail: "save_comment needs an issueId and a body."
|
|
123877
|
+
});
|
|
123878
|
+
const issueNodeId = yield* resolveIssueNodeId(apiKey, reference);
|
|
123879
|
+
const parentId = asString(args["parentId"]);
|
|
123880
|
+
const response = yield* request(apiKey, `mutation($input: CommentCreateInput!) {
|
|
123881
|
+
commentCreate(input: $input) { comment { ${COMMENT_SELECTION} } }
|
|
123882
|
+
}`, { input: {
|
|
123883
|
+
issueId: issueNodeId,
|
|
123884
|
+
body,
|
|
123885
|
+
...parentId === void 0 ? {} : { parentId }
|
|
123886
|
+
} });
|
|
123887
|
+
const payload = yield* readEntity(response, "commentCreate");
|
|
123888
|
+
const comment = asRecord$5(payload?.["comment"]);
|
|
123889
|
+
if (comment === void 0) return yield* new LinearUnavailable({
|
|
123890
|
+
reason: "failed",
|
|
123891
|
+
detail: response.errors.length > 0 ? response.errors.join("; ") : "Linear accepted the comment and returned nothing."
|
|
123892
|
+
});
|
|
123893
|
+
return toCommentRow(comment);
|
|
123894
|
+
});
|
|
123895
|
+
/**
|
|
123896
|
+
* A link filed against an issue, which is every attachment this transport
|
|
123897
|
+
* can make.
|
|
123898
|
+
*
|
|
123899
|
+
* Linear's own file upload is a signed-URL handshake followed by a PUT of
|
|
123900
|
+
* the bytes, and nothing here has bytes to send: the proof a closeout links
|
|
123901
|
+
* is already hosted somewhere. A caller holding a local file gets the same
|
|
123902
|
+
* answer it would get from the MCP server's `create_attachment`, which also
|
|
123903
|
+
* takes a URL.
|
|
123904
|
+
*/
|
|
123905
|
+
const createAttachment = (apiKey, args) => Effect.gen(function* () {
|
|
123906
|
+
const reference = asString(args["issueId"]) ?? asString(args["id"]);
|
|
123907
|
+
const url = asString(args["url"]);
|
|
123908
|
+
if (reference === void 0 || url === void 0) return yield* new LinearUnavailable({
|
|
123909
|
+
reason: "failed",
|
|
123910
|
+
detail: "create_attachment needs an issueId and a url."
|
|
123911
|
+
});
|
|
123912
|
+
const issueNodeId = yield* resolveIssueNodeId(apiKey, reference);
|
|
123913
|
+
const title = asString(args["title"]);
|
|
123914
|
+
const subtitle = asString(args["subtitle"]);
|
|
123915
|
+
const response = yield* request(apiKey, `mutation($input: AttachmentCreateInput!) {
|
|
123916
|
+
attachmentCreate(input: $input) { attachment { ${ATTACHMENT_SELECTION} } }
|
|
123917
|
+
}`, { input: {
|
|
123918
|
+
issueId: issueNodeId,
|
|
123919
|
+
url,
|
|
123920
|
+
title: title ?? url,
|
|
123921
|
+
...subtitle === void 0 ? {} : { subtitle }
|
|
123922
|
+
} });
|
|
123923
|
+
const payload = yield* readEntity(response, "attachmentCreate");
|
|
123924
|
+
const attachment = asRecord$5(payload?.["attachment"]);
|
|
123925
|
+
if (attachment === void 0) return yield* new LinearUnavailable({
|
|
123926
|
+
reason: "failed",
|
|
123927
|
+
detail: response.errors.length > 0 ? response.errors.join("; ") : "Linear accepted the attachment and returned nothing."
|
|
123928
|
+
});
|
|
123929
|
+
return attachment;
|
|
123930
|
+
});
|
|
123095
123931
|
const isConfigured = secrets.read("linear").pipe(Effect.map((credential) => Option.isSome(credential)));
|
|
123096
123932
|
const call = (toolName, args) => Effect.gen(function* () {
|
|
123097
123933
|
const credential = yield* secrets.read("linear");
|
|
@@ -123104,6 +123940,9 @@ const makeLinearApiTransport = Effect.gen(function* () {
|
|
|
123104
123940
|
case "list_issues": return yield* listIssues(apiKey, args);
|
|
123105
123941
|
case "get_issue": return yield* getIssue(apiKey, args);
|
|
123106
123942
|
case "save_issue": return yield* saveIssue(apiKey, args);
|
|
123943
|
+
case "list_comments": return yield* listComments(apiKey, args);
|
|
123944
|
+
case "save_comment": return yield* saveComment(apiKey, args);
|
|
123945
|
+
case "create_attachment": return yield* createAttachment(apiKey, args);
|
|
123107
123946
|
default: return yield* new LinearUnavailable({
|
|
123108
123947
|
reason: "failed",
|
|
123109
123948
|
detail: `The Linear API transport has no translation for "${toolName}".`
|
|
@@ -123115,11 +123954,11 @@ const makeLinearApiTransport = Effect.gen(function* () {
|
|
|
123115
123954
|
reason: "not_authorized",
|
|
123116
123955
|
detail: "Linear rejected this API key."
|
|
123117
123956
|
}) : error));
|
|
123118
|
-
if (asString(asRecord$
|
|
123957
|
+
if (asString(asRecord$5(response.data?.["viewer"])?.["id"]) === void 0) return yield* new LinearUnavailable({
|
|
123119
123958
|
reason: "not_authorized",
|
|
123120
123959
|
detail: response.errors.length > 0 ? response.errors.join("; ") : "Linear rejected this API key."
|
|
123121
123960
|
});
|
|
123122
|
-
const organization = asRecord$
|
|
123961
|
+
const organization = asRecord$5(response.data?.["organization"]);
|
|
123123
123962
|
return { workspace: asString(organization?.["name"]) ?? asString(organization?.["urlKey"]) ?? null };
|
|
123124
123963
|
});
|
|
123125
123964
|
return {
|
|
@@ -123340,7 +124179,7 @@ const STATUS_FROM_LINEAR_TYPE = {
|
|
|
123340
124179
|
completed: "done",
|
|
123341
124180
|
canceled: "cancelled"
|
|
123342
124181
|
};
|
|
123343
|
-
const asRecord$
|
|
124182
|
+
const asRecord$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
123344
124183
|
const text = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
123345
124184
|
/**
|
|
123346
124185
|
* A person's name out of whatever Linear put in the field: an object when the
|
|
@@ -123349,14 +124188,14 @@ const text = (value) => typeof value === "string" && value.trim().length > 0 ? v
|
|
|
123349
124188
|
const personName = (value) => {
|
|
123350
124189
|
const direct = text(value);
|
|
123351
124190
|
if (direct !== void 0) return direct;
|
|
123352
|
-
const record = asRecord$
|
|
124191
|
+
const record = asRecord$4(value);
|
|
123353
124192
|
if (record === void 0) return null;
|
|
123354
124193
|
return text(record["displayName"]) ?? text(record["name"]) ?? text(record["email"]) ?? null;
|
|
123355
124194
|
};
|
|
123356
124195
|
/** Label names, from either `["Bug"]` or `[{ name: "Bug" }]`. */
|
|
123357
124196
|
const labelNames = (value) => {
|
|
123358
124197
|
if (!Array.isArray(value)) return [];
|
|
123359
|
-
return value.map((entry) => text(entry) ?? text(asRecord$
|
|
124198
|
+
return value.map((entry) => text(entry) ?? text(asRecord$4(entry)?.["name"])).filter((name) => name !== void 0);
|
|
123360
124199
|
};
|
|
123361
124200
|
/**
|
|
123362
124201
|
* The p4code status an issue is in.
|
|
@@ -123401,7 +124240,7 @@ const statusToLinearState = (status) => {
|
|
|
123401
124240
|
* Medium, and quietly rewrite the local row with it.
|
|
123402
124241
|
*/
|
|
123403
124242
|
const priorityFromLinear = (value) => {
|
|
123404
|
-
const numeric = typeof value === "number" ? value : asRecord$
|
|
124243
|
+
const numeric = typeof value === "number" ? value : asRecord$4(value)?.["value"];
|
|
123405
124244
|
return typeof numeric === "number" ? PRIORITY_FROM_LINEAR[numeric] ?? "none" : "none";
|
|
123406
124245
|
};
|
|
123407
124246
|
const priorityToLinear = (priority) => PRIORITY_TO_LINEAR[priority];
|
|
@@ -126920,7 +127759,7 @@ function toAssetFilePath(repositoryPath, root) {
|
|
|
126920
127759
|
const REGISTRY_SEARCH_URL = "https://www.skills.sh/api/search";
|
|
126921
127760
|
const GITHUB_API_URL = "https://api.github.com";
|
|
126922
127761
|
const GITHUB_RAW_URL = "https://raw.githubusercontent.com";
|
|
126923
|
-
const REQUEST_TIMEOUT_MS
|
|
127762
|
+
const REQUEST_TIMEOUT_MS = 15e3;
|
|
126924
127763
|
/** More than a person reads, few enough that the panel is a list and not a feed. */
|
|
126925
127764
|
const MAX_SEARCH_RESULTS = 25;
|
|
126926
127765
|
/**
|
|
@@ -126960,7 +127799,7 @@ const emptyFetch = (id, unavailable) => ({
|
|
|
126960
127799
|
const make$29 = Effect.gen(function* () {
|
|
126961
127800
|
const http = yield* HttpClient.HttpClient;
|
|
126962
127801
|
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
|
|
127802
|
+
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
127803
|
});
|
|
126965
127804
|
const search = (query) => Effect.gen(function* () {
|
|
126966
127805
|
const response = yield* request(`${REGISTRY_SEARCH_URL}?q=${encodeURIComponent(query)}`).pipe(Effect.orElseSucceed(() => null));
|
|
@@ -130108,6 +130947,7 @@ const RPC_REQUIRED_SCOPE = /* @__PURE__ */ new Map([
|
|
|
130108
130947
|
[WS_METHODS.storageCleanupSweep, AuthOrchestrationOperateScope],
|
|
130109
130948
|
[WS_METHODS.worktreeCleanupPreview, AuthOrchestrationReadScope],
|
|
130110
130949
|
[WS_METHODS.worktreeCleanupSweep, AuthOrchestrationOperateScope],
|
|
130950
|
+
[WS_METHODS.hubAnalyticsQuery, AuthOrchestrationReadScope],
|
|
130111
130951
|
[WS_METHODS.hubGetSyncStatus, AuthOrchestrationReadScope],
|
|
130112
130952
|
[WS_METHODS.hubConnect, AuthOrchestrationOperateScope],
|
|
130113
130953
|
[WS_METHODS.hubDisconnect, AuthOrchestrationOperateScope],
|
|
@@ -131135,6 +131975,13 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker, computerUseBrok
|
|
|
131135
131975
|
[WS_METHODS.storageCleanupSweep]: (_input) => observeRpcEffect$1(WS_METHODS.storageCleanupSweep, storageCleanup.sweep, { "rpc.aggregate": "storageCleanup" }),
|
|
131136
131976
|
[WS_METHODS.worktreeCleanupPreview]: (_input) => observeRpcEffect$1(WS_METHODS.worktreeCleanupPreview, worktreeCleanup.preview, { "rpc.aggregate": "worktreeCleanup" }),
|
|
131137
131977
|
[WS_METHODS.worktreeCleanupSweep]: (_input) => observeRpcEffect$1(WS_METHODS.worktreeCleanupSweep, worktreeCleanup.sweep, { "rpc.aggregate": "worktreeCleanup" }),
|
|
131978
|
+
[WS_METHODS.hubAnalyticsQuery]: (query) => observeRpcEffect$1(WS_METHODS.hubAnalyticsQuery, Effect.gen(function* () {
|
|
131979
|
+
const state = yield* hubLink.current;
|
|
131980
|
+
if (Option.isNone(state.settings)) return yield* new HubLinkError({ detail: "No hub is configured." });
|
|
131981
|
+
const transport = yield* makeHubActivityTransport(state.settings.value);
|
|
131982
|
+
const membership = yield* transport.identity();
|
|
131983
|
+
return yield* transport.queryFacts(query, membership);
|
|
131984
|
+
}).pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause instanceof HubLinkError ? cause.detail : "The hub could not be reached." }))), { "rpc.aggregate": "hub" }),
|
|
131138
131985
|
[WS_METHODS.hubGetSyncStatus]: (_input) => observeRpcEffect$1(WS_METHODS.hubGetSyncStatus, assetSync.status, { "rpc.aggregate": "hub" }),
|
|
131139
131986
|
[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
131987
|
[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 +133179,23 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (options)
|
|
|
132332
133179
|
...event,
|
|
132333
133180
|
requestId: RuntimeRequestId.make(String(pending.requestId))
|
|
132334
133181
|
};
|
|
133182
|
+
/**
|
|
133183
|
+
* Stamp a compacted event with how long its pass actually took.
|
|
133184
|
+
*
|
|
133185
|
+
* The service owns both ends of a compaction, so it is the only place that
|
|
133186
|
+
* can measure one; every reader downstream would be guessing from
|
|
133187
|
+
* surrounding timestamps instead.
|
|
133188
|
+
*/
|
|
133189
|
+
const withCompactionMeasurement = (event, pending, nowMs) => event.type === "thread.state.changed" && event.payload.state === "compacted" ? {
|
|
133190
|
+
...event,
|
|
133191
|
+
payload: {
|
|
133192
|
+
...event.payload,
|
|
133193
|
+
detail: {
|
|
133194
|
+
...typeof event.payload.detail === "object" && event.payload.detail !== null ? event.payload.detail : {},
|
|
133195
|
+
durationMs: Math.max(0, nowMs - pending.startedAtMs)
|
|
133196
|
+
}
|
|
133197
|
+
}
|
|
133198
|
+
} : event;
|
|
132335
133199
|
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
133200
|
const processFallbackCompactionEvent = (pending, event) => Effect.gen(function* () {
|
|
132337
133201
|
if (pendingCompactions.get(event.threadId) !== pending) {
|
|
@@ -132341,7 +133205,7 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (options)
|
|
|
132341
133205
|
const matchesTurn = event.turnId !== void 0 && event.turnId === pending.expectedTurnId;
|
|
132342
133206
|
if (matchesTurn && isCompactedEvent(event)) {
|
|
132343
133207
|
pending.compactedEventObserved = true;
|
|
132344
|
-
yield* publishRuntimeEvent(withCompactionRequestId(event, pending));
|
|
133208
|
+
yield* publishRuntimeEvent(withCompactionMeasurement(withCompactionRequestId(event, pending), pending, yield* Clock.currentTimeMillis));
|
|
132345
133209
|
return;
|
|
132346
133210
|
}
|
|
132347
133211
|
yield* publishRuntimeEvent(event);
|
|
@@ -132354,7 +133218,10 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (options)
|
|
|
132354
133218
|
type: "thread.state.changed",
|
|
132355
133219
|
payload: {
|
|
132356
133220
|
state: "compacted",
|
|
132357
|
-
detail: {
|
|
133221
|
+
detail: {
|
|
133222
|
+
source: "provider-native-command",
|
|
133223
|
+
durationMs: Math.max(0, (yield* Clock.currentTimeMillis) - pending.startedAtMs)
|
|
133224
|
+
}
|
|
132358
133225
|
},
|
|
132359
133226
|
...pending.requestId !== void 0 ? { requestId: RuntimeRequestId.make(String(pending.requestId)) } : {}
|
|
132360
133227
|
};
|
|
@@ -132386,7 +133253,7 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (options)
|
|
|
132386
133253
|
if (pendingCompaction.native) {
|
|
132387
133254
|
const compacted = isCompactedEvent(canonicalEvent);
|
|
132388
133255
|
const terminal = compacted ? "completed" : canonicalEvent.type === "turn.completed" && canonicalEvent.payload.state === "completed" ? null : compactionTerminal(canonicalEvent);
|
|
132389
|
-
yield* publishRuntimeEvent(compacted ? withCompactionRequestId(canonicalEvent, pendingCompaction) : canonicalEvent);
|
|
133256
|
+
yield* publishRuntimeEvent(compacted ? withCompactionMeasurement(withCompactionRequestId(canonicalEvent, pendingCompaction), pendingCompaction, yield* Clock.currentTimeMillis) : canonicalEvent);
|
|
132390
133257
|
if (terminal !== null) yield* settleCompaction(canonicalEvent.threadId, pendingCompaction, terminal);
|
|
132391
133258
|
return;
|
|
132392
133259
|
}
|
|
@@ -132685,6 +133552,7 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (options)
|
|
|
132685
133552
|
const completion = yield* Deferred.make();
|
|
132686
133553
|
const pending = {
|
|
132687
133554
|
completion,
|
|
133555
|
+
startedAtMs: yield* Clock.currentTimeMillis,
|
|
132688
133556
|
native: compaction.type === "native",
|
|
132689
133557
|
providerInstanceId: routed.instanceId,
|
|
132690
133558
|
requestId,
|
|
@@ -133660,22 +134528,27 @@ PreviewToolkit.toLayer(handlers$5);
|
|
|
133660
134528
|
//#endregion
|
|
133661
134529
|
//#region src/mcp/TicketResolver.ts
|
|
133662
134530
|
var TicketResolver = class extends Context.Service()("@p4code/cli/mcp/TicketResolver") {};
|
|
134531
|
+
/**
|
|
134532
|
+
* The client's reasons map one-to-one except `failed`, which for a caller
|
|
134533
|
+
* here means "try again", not "fix your configuration".
|
|
134534
|
+
*/
|
|
134535
|
+
const toResolveError = (cause) => new TicketResolveError({
|
|
134536
|
+
reason: cause.reason === "failed" ? "unavailable" : cause.reason,
|
|
134537
|
+
detail: cause.detail
|
|
134538
|
+
});
|
|
133663
134539
|
const stringField = (record, key) => {
|
|
133664
134540
|
const value = record[key];
|
|
133665
134541
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
133666
134542
|
};
|
|
133667
134543
|
const make$12 = Effect.gen(function* () {
|
|
133668
134544
|
const linear = yield* LinearClient;
|
|
133669
|
-
|
|
134545
|
+
const resolve = Effect.fn("TicketResolver.resolve")(function* (reference) {
|
|
133670
134546
|
const identifier = parseTicketReference(reference);
|
|
133671
134547
|
if (identifier === null) return yield* new TicketResolveError({
|
|
133672
134548
|
reason: "not_found",
|
|
133673
134549
|
detail: `"${reference}" does not look like a ticket reference.`
|
|
133674
134550
|
});
|
|
133675
|
-
const issue = readOne(yield* linear.call(LINEAR_GET_ISSUE_TOOL, { id: identifier }).pipe(Effect.mapError(
|
|
133676
|
-
reason: cause.reason === "failed" ? "unavailable" : cause.reason,
|
|
133677
|
-
detail: cause.detail
|
|
133678
|
-
}))));
|
|
134551
|
+
const issue = readOne(yield* linear.call(LINEAR_GET_ISSUE_TOOL, { id: identifier }).pipe(Effect.mapError(toResolveError)));
|
|
133679
134552
|
const url = issue === void 0 ? void 0 : stringField(issue, "url");
|
|
133680
134553
|
const title = issue === void 0 ? void 0 : stringField(issue, "title");
|
|
133681
134554
|
if (issue === void 0 || url === void 0 || title === void 0) return yield* new TicketResolveError({
|
|
@@ -133693,7 +134566,43 @@ const make$12 = Effect.gen(function* () {
|
|
|
133693
134566
|
title,
|
|
133694
134567
|
...description === void 0 ? {} : { description }
|
|
133695
134568
|
};
|
|
133696
|
-
})
|
|
134569
|
+
});
|
|
134570
|
+
return {
|
|
134571
|
+
resolve,
|
|
134572
|
+
comment: Effect.fn("TicketResolver.comment")(function* (input) {
|
|
134573
|
+
const ticket = yield* resolve(input.reference);
|
|
134574
|
+
const identifier = ticket.externalRef.identifier;
|
|
134575
|
+
const comment = readOne(yield* linear.call(LINEAR_SAVE_COMMENT_TOOL, {
|
|
134576
|
+
issueId: identifier,
|
|
134577
|
+
body: input.body
|
|
134578
|
+
}).pipe(Effect.mapError(toResolveError)));
|
|
134579
|
+
const commentUrl = comment === void 0 ? void 0 : stringField(comment, "url");
|
|
134580
|
+
const attachedUrls = [];
|
|
134581
|
+
const failedAttachments = [];
|
|
134582
|
+
for (const attachment of input.attachments ?? []) {
|
|
134583
|
+
const failure = yield* linear.call(LINEAR_CREATE_ATTACHMENT_TOOL, {
|
|
134584
|
+
issueId: identifier,
|
|
134585
|
+
url: attachment.url,
|
|
134586
|
+
...attachment.title === void 0 ? {} : { title: attachment.title }
|
|
134587
|
+
}).pipe(Effect.as(void 0), Effect.catch((cause) => Effect.logWarning("ticket attachment failed", {
|
|
134588
|
+
ticket: identifier,
|
|
134589
|
+
url: attachment.url,
|
|
134590
|
+
detail: cause.detail
|
|
134591
|
+
}).pipe(Effect.as(cause.detail))));
|
|
134592
|
+
if (failure === void 0) attachedUrls.push(attachment.url);
|
|
134593
|
+
else failedAttachments.push({
|
|
134594
|
+
url: attachment.url,
|
|
134595
|
+
detail: failure
|
|
134596
|
+
});
|
|
134597
|
+
}
|
|
134598
|
+
return {
|
|
134599
|
+
externalRef: ticket.externalRef,
|
|
134600
|
+
...commentUrl === void 0 ? {} : { commentUrl },
|
|
134601
|
+
attachedUrls,
|
|
134602
|
+
failedAttachments
|
|
134603
|
+
};
|
|
134604
|
+
})
|
|
134605
|
+
};
|
|
133697
134606
|
});
|
|
133698
134607
|
const layer$5 = Layer.effect(TicketResolver, make$12);
|
|
133699
134608
|
//#endregion
|
|
@@ -133814,7 +134723,29 @@ const TicketResolveTool = Tool.make("ticket_resolve", {
|
|
|
133814
134723
|
failure: Schema$1.Union([TaskToolUnavailableError, TicketResolveError]),
|
|
133815
134724
|
dependencies: [McpInvocationContext, TicketResolver]
|
|
133816
134725
|
}).annotate(Tool.Title, "Resolve a ticket").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
|
|
133817
|
-
|
|
134726
|
+
/**
|
|
134727
|
+
* Writing one comment back to the ticket, through p4code's own transport.
|
|
134728
|
+
*
|
|
134729
|
+
* Separate from `ticket_resolve` because it writes, and on the task toolkit
|
|
134730
|
+
* for the same reason that one is: it is the tracker side of the work a board
|
|
134731
|
+
* row describes, under the one capability a session is granted.
|
|
134732
|
+
*
|
|
134733
|
+
* It exists because a provider's own tracker registration is not a transport
|
|
134734
|
+
* p4code controls - it can be absent, or hold a credential the tracker has
|
|
134735
|
+
* stopped accepting - while closeout is the moment that must not fail
|
|
134736
|
+
* silently. `LinearClient` picks the stored API key when there is one and the
|
|
134737
|
+
* registered MCP server otherwise, so this reaches the ticket whenever any
|
|
134738
|
+
* route to it exists. Both halves of a Fusion pair may call it; which half
|
|
134739
|
+
* should is the prompt's business, not the toolkit's.
|
|
134740
|
+
*/
|
|
134741
|
+
const TicketCommentTool = Tool.make("ticket_comment", {
|
|
134742
|
+
description: "Comment on a tracker ticket by id or URL (e.g. MOBILE-12241, or a linear.app link), optionally filing links as attachments alongside it. Use it for an authorized closeout note or a requested ticket comment - never for progress chatter. Reaches the tracker through p4code's own credential, so it works when the session has no tracker server of its own. The comment is written as given: Markdown, and the caller's words. Attachments take publicly reachable URLs; a local file path cannot be filed. A link the tracker refuses comes back in failedAttachments with the reason - retry that link alone rather than the whole call, which would file the comment twice.",
|
|
134743
|
+
parameters: TicketCommentInput,
|
|
134744
|
+
success: TicketCommentResult,
|
|
134745
|
+
failure: Schema$1.Union([TaskToolUnavailableError, TicketResolveError]),
|
|
134746
|
+
dependencies: [McpInvocationContext, TicketResolver]
|
|
134747
|
+
}).annotate(Tool.Title, "Comment on a ticket").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
|
|
134748
|
+
const TaskToolkit = Toolkit.make(TaskListTool, TaskGetTool, TaskCurrentTool, TaskCreateTool, TaskUpdateTool, TaskProposeTool, TicketResolveTool, TicketCommentTool);
|
|
133818
134749
|
//#endregion
|
|
133819
134750
|
//#region src/mcp/toolkits/tasks/handlers.ts
|
|
133820
134751
|
const nowIso$1 = Effect.map(DateTime.now, DateTime.formatIso);
|
|
@@ -133917,6 +134848,10 @@ const TaskToolkitHandlersLive = TaskToolkit.toLayer({
|
|
|
133917
134848
|
yield* requireTaskCapability();
|
|
133918
134849
|
return yield* (yield* TicketResolver).resolve(input.reference);
|
|
133919
134850
|
}),
|
|
134851
|
+
ticket_comment: (input) => Effect.gen(function* () {
|
|
134852
|
+
yield* requireTaskCapability();
|
|
134853
|
+
return yield* (yield* TicketResolver).comment(input);
|
|
134854
|
+
}),
|
|
133920
134855
|
task_propose: () => Effect.gen(function* () {
|
|
133921
134856
|
yield* requireTaskCapability();
|
|
133922
134857
|
return { awaitingConfirmation: true };
|
|
@@ -136107,7 +137042,7 @@ const watchesAsFusionSupervisor = Effect.fn("mcp.watch.watchesAsFusionSupervisor
|
|
|
136107
137042
|
return (yield* (yield* ProjectionSnapshotQuery).getActiveThreadPairs().pipe(Effect.orElseSucceed(() => []))).some((pair) => pair.watcherThreadId === readerThreadId && pair.implementerThreadId === watchedThreadId);
|
|
136108
137043
|
});
|
|
136109
137044
|
/** Bounded so one report cannot dominate a thread's activity storage. */
|
|
136110
|
-
const clip = (value, limit) => {
|
|
137045
|
+
const clip$1 = (value, limit) => {
|
|
136111
137046
|
const trimmed = value?.trim();
|
|
136112
137047
|
if (!trimmed) return null;
|
|
136113
137048
|
return trimmed.length <= limit ? trimmed : `${trimmed.slice(0, limit)}…`;
|
|
@@ -136143,8 +137078,8 @@ const WatchToolkitHandlersLive = WatchToolkit.toLayer({
|
|
|
136143
137078
|
serverReadActivityId: serverEvidenceReadActivityId(input.threadId, input.throughSequence),
|
|
136144
137079
|
closedAt: createdAt
|
|
136145
137080
|
});
|
|
136146
|
-
const summary = clip(input.summary, THREAD_EVIDENCE_REPORT_MAX_CHARS);
|
|
136147
|
-
const reason = clip(input.reason, 500);
|
|
137081
|
+
const summary = clip$1(input.summary, THREAD_EVIDENCE_REPORT_MAX_CHARS);
|
|
137082
|
+
const reason = clip$1(input.reason, 500);
|
|
136148
137083
|
const outcome = summary === null && input.outcome === "summarized" ? "failed" : input.outcome;
|
|
136149
137084
|
return {
|
|
136150
137085
|
recorded: yield* engine.dispatch({
|
|
@@ -136166,7 +137101,7 @@ const WatchToolkitHandlersLive = WatchToolkit.toLayer({
|
|
|
136166
137101
|
throughSequence: input.throughSequence,
|
|
136167
137102
|
outcome,
|
|
136168
137103
|
summary,
|
|
136169
|
-
model: clip(input.model, MODEL_ID_MAX_CHARS),
|
|
137104
|
+
model: clip$1(input.model, MODEL_ID_MAX_CHARS),
|
|
136170
137105
|
reason: reason ?? (outcome === "summarized" ? null : "the supervisor gave no reason"),
|
|
136171
137106
|
context,
|
|
136172
137107
|
timing
|
|
@@ -136354,118 +137289,6 @@ const ComputerUseMcpLive = ComputerUseToolkitRegistrationLive.pipe(Layer.provide
|
|
|
136354
137289
|
}).pipe(Layer.provide(McpAuthMiddlewareLive))));
|
|
136355
137290
|
const layer$4 = Layer.mergeAll(Layer.fresh(StandardMcpLive), Layer.fresh(ComputerUseMcpLive));
|
|
136356
137291
|
//#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
137292
|
//#region src/hub/HubActivityProjection.ts
|
|
136470
137293
|
const titles = {
|
|
136471
137294
|
running: "Agent working",
|
|
@@ -136596,6 +137419,407 @@ const makeHubActivityOutbound = Effect.fn("HubActivityOutbound.make")(function*
|
|
|
136596
137419
|
})
|
|
136597
137420
|
};
|
|
136598
137421
|
});
|
|
137422
|
+
const encodeFact = Schema$1.encodeEffect(Schema$1.fromJsonString(HubAnalyticsFactInput));
|
|
137423
|
+
const decodeStoredFact = Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(HubAnalyticsFactInput));
|
|
137424
|
+
/** Stage one derived fact. Returns false when this id was already staged. */
|
|
137425
|
+
const stageAnalyticsFact = Effect.fn("AnalyticsFactOutbox.stage")(function* (fact) {
|
|
137426
|
+
const sql = yield* SqlClient.SqlClient;
|
|
137427
|
+
const payload = yield* encodeFact(fact);
|
|
137428
|
+
const now = yield* Clock.currentTimeMillis;
|
|
137429
|
+
return (yield* sql`
|
|
137430
|
+
INSERT INTO analytics_fact_outbox (event_id, kind, version, occurred_at, payload, pending, created_at)
|
|
137431
|
+
VALUES (${fact.eventId}, ${fact.kind}, ${fact.version}, ${fact.occurredAt}, ${payload}, 1, ${now})
|
|
137432
|
+
ON CONFLICT(event_id) DO NOTHING
|
|
137433
|
+
RETURNING event_id`).length === 1;
|
|
137434
|
+
});
|
|
137435
|
+
/** The oldest undelivered facts, in the order they were captured. */
|
|
137436
|
+
const pendingAnalyticsFacts = Effect.fn("AnalyticsFactOutbox.pending")(function* (limit = 16) {
|
|
137437
|
+
return yield* (yield* SqlClient.SqlClient)`
|
|
137438
|
+
SELECT event_id, payload FROM analytics_fact_outbox WHERE pending = 1
|
|
137439
|
+
ORDER BY created_at, event_id LIMIT ${limit}`;
|
|
137440
|
+
});
|
|
137441
|
+
/**
|
|
137442
|
+
* Mark one fact delivered.
|
|
137443
|
+
*
|
|
137444
|
+
* The hub answers `accepted: false` for a fact it already holds, which is the
|
|
137445
|
+
* same outcome as a fresh accept from here: the row is delivered either way,
|
|
137446
|
+
* and a retry that raced an earlier success must not stay pending forever.
|
|
137447
|
+
*/
|
|
137448
|
+
const acknowledgeAnalyticsFact = Effect.fn("AnalyticsFactOutbox.acknowledge")(function* (eventId) {
|
|
137449
|
+
return (yield* (yield* SqlClient.SqlClient)`UPDATE analytics_fact_outbox SET pending = 0
|
|
137450
|
+
WHERE event_id = ${eventId} AND pending = 1 RETURNING event_id`).length === 1;
|
|
137451
|
+
});
|
|
137452
|
+
/** Where a source's scan left off, so a restart resumes instead of replaying. */
|
|
137453
|
+
const readAnalyticsCursor = Effect.fn("AnalyticsFactOutbox.readCursor")(function* (source) {
|
|
137454
|
+
return (yield* (yield* SqlClient.SqlClient)`SELECT after_sequence FROM analytics_fact_cursors WHERE source = ${source}`)[0]?.after_sequence ?? 0;
|
|
137455
|
+
});
|
|
137456
|
+
const writeAnalyticsCursor = Effect.fn("AnalyticsFactOutbox.writeCursor")(function* (source, afterSequence) {
|
|
137457
|
+
yield* (yield* SqlClient.SqlClient)`INSERT INTO analytics_fact_cursors (source, after_sequence)
|
|
137458
|
+
VALUES (${source}, ${afterSequence})
|
|
137459
|
+
ON CONFLICT(source) DO UPDATE SET after_sequence = excluded.after_sequence`;
|
|
137460
|
+
});
|
|
137461
|
+
//#endregion
|
|
137462
|
+
//#region src/analytics/analyticsFacts.ts
|
|
137463
|
+
const MAX_QUESTION_CHARS = 2e3;
|
|
137464
|
+
const MAX_HEADER_CHARS = 120;
|
|
137465
|
+
const MAX_OPTION_LABEL_CHARS = 200;
|
|
137466
|
+
const MAX_OPTION_DESCRIPTION_CHARS = 600;
|
|
137467
|
+
const MAX_PRESENTED_OPTIONS = 12;
|
|
137468
|
+
const MAX_CUSTOM_ANSWER_CHARS = 2e3;
|
|
137469
|
+
const MAX_REASON_CHARS = 200;
|
|
137470
|
+
const MAX_MODEL_CHARS = 120;
|
|
137471
|
+
const clip = (value, max) => value.length <= max ? value : value.slice(0, max);
|
|
137472
|
+
const asRecord$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
137473
|
+
const asFiniteInt$1 = (value) => typeof value === "number" && Number.isFinite(value) ? Math.round(value) : null;
|
|
137474
|
+
/** `question:<requestId>:<questionId>`, so a replayed resolution derives the same id. */
|
|
137475
|
+
const questionFactId = (requestId, questionId) => `question:${requestId}:${questionId}`;
|
|
137476
|
+
/**
|
|
137477
|
+
* One fact per question in a resolved request.
|
|
137478
|
+
*
|
|
137479
|
+
* The presented options travel with the answer because the answer alone says
|
|
137480
|
+
* nothing: "Patch" only means something beside what else was offered, and a
|
|
137481
|
+
* custom answer only means something beside what it refused.
|
|
137482
|
+
*/
|
|
137483
|
+
function questionFactsFrom(input) {
|
|
137484
|
+
const questions = Array.isArray(input.questions) ? input.questions : [];
|
|
137485
|
+
const answers = asRecord$3(input.answers) ?? {};
|
|
137486
|
+
const facts = [];
|
|
137487
|
+
for (const raw of questions) {
|
|
137488
|
+
const question = asRecord$3(raw);
|
|
137489
|
+
if (question === null) continue;
|
|
137490
|
+
const questionId = typeof question.id === "string" ? question.id : null;
|
|
137491
|
+
const prompt = typeof question.question === "string" ? question.question : null;
|
|
137492
|
+
if (questionId === null || prompt === null || prompt.length === 0) continue;
|
|
137493
|
+
const options = (Array.isArray(question.options) ? question.options : []).slice(0, MAX_PRESENTED_OPTIONS).flatMap((entry) => {
|
|
137494
|
+
const option = asRecord$3(entry);
|
|
137495
|
+
const label = typeof option?.label === "string" ? option.label : null;
|
|
137496
|
+
return label === null || label.length === 0 ? [] : [{
|
|
137497
|
+
label: clip(label, MAX_OPTION_LABEL_CHARS),
|
|
137498
|
+
description: clip(typeof option?.description === "string" ? option.description : "", MAX_OPTION_DESCRIPTION_CHARS)
|
|
137499
|
+
}];
|
|
137500
|
+
});
|
|
137501
|
+
const given = answers[questionId];
|
|
137502
|
+
const selected = (Array.isArray(given) ? given : given === void 0 ? [] : [given]).flatMap((value) => typeof value === "string" && value.length > 0 ? [value] : []);
|
|
137503
|
+
const indexes = selected.map((label) => options.findIndex((option) => option.label === label));
|
|
137504
|
+
const custom = selected.find((_, position) => indexes[position] === -1);
|
|
137505
|
+
const answer = selected.length === 0 ? { kind: "dismissed" } : custom !== void 0 ? {
|
|
137506
|
+
kind: "custom",
|
|
137507
|
+
text: clip(custom, MAX_CUSTOM_ANSWER_CHARS)
|
|
137508
|
+
} : {
|
|
137509
|
+
kind: "option",
|
|
137510
|
+
optionIndexes: indexes,
|
|
137511
|
+
labels: selected.map((label) => clip(label, MAX_OPTION_LABEL_CHARS))
|
|
137512
|
+
};
|
|
137513
|
+
facts.push({
|
|
137514
|
+
eventId: questionFactId(input.requestId, questionId),
|
|
137515
|
+
occurredAt: input.occurredAt,
|
|
137516
|
+
kind: "question.answered",
|
|
137517
|
+
version: 1,
|
|
137518
|
+
payload: {
|
|
137519
|
+
questionId,
|
|
137520
|
+
threadId: input.threadId,
|
|
137521
|
+
header: clip(typeof question.header === "string" ? question.header : "", MAX_HEADER_CHARS),
|
|
137522
|
+
question: clip(prompt, MAX_QUESTION_CHARS),
|
|
137523
|
+
multiSelect: question.multiSelect === true,
|
|
137524
|
+
options,
|
|
137525
|
+
answer
|
|
137526
|
+
}
|
|
137527
|
+
});
|
|
137528
|
+
}
|
|
137529
|
+
return facts;
|
|
137530
|
+
}
|
|
137531
|
+
/** `fusion:<threadId>:<throughSequence>`: one handoff covers one range, once. */
|
|
137532
|
+
const fusionFactId = (threadId, throughSequence) => `fusion:${threadId}:${throughSequence}`;
|
|
137533
|
+
/**
|
|
137534
|
+
* One review, read from the two rows that recorded it.
|
|
137535
|
+
*
|
|
137536
|
+
* The outcome, the model and the duration come from the report, because that
|
|
137537
|
+
* is the row the server wrote them on; the coverage and the summary come from
|
|
137538
|
+
* the handoff, because that is the row that carried them. Neither is guessed
|
|
137539
|
+
* from the other, and a handoff with no report yields nothing at all.
|
|
137540
|
+
*/
|
|
137541
|
+
function fusionHandoffFactFrom(input) {
|
|
137542
|
+
const payload = asRecord$3(input.payload);
|
|
137543
|
+
const report = asRecord$3(input.reportPayload);
|
|
137544
|
+
if (payload === null || report === null) return null;
|
|
137545
|
+
const outcome = report.outcome;
|
|
137546
|
+
if (outcome !== "summarized" && outcome !== "fallback" && outcome !== "failed" && outcome !== "unreported") return null;
|
|
137547
|
+
const coverage = asRecord$3(payload.coverage) ?? {};
|
|
137548
|
+
const context = asRecord$3(report.context);
|
|
137549
|
+
const timing = asRecord$3(report.timing);
|
|
137550
|
+
const summary = typeof payload.summary === "string" ? payload.summary : null;
|
|
137551
|
+
const reason = typeof report.reason === "string" ? clip(report.reason, MAX_REASON_CHARS) : null;
|
|
137552
|
+
return {
|
|
137553
|
+
eventId: fusionFactId(input.threadId, input.throughSequence),
|
|
137554
|
+
occurredAt: input.occurredAt,
|
|
137555
|
+
kind: "fusion.handoff",
|
|
137556
|
+
version: 1,
|
|
137557
|
+
payload: {
|
|
137558
|
+
fusionVersion: input.fusionVersion,
|
|
137559
|
+
threadId: input.threadId,
|
|
137560
|
+
status: outcome,
|
|
137561
|
+
model: typeof report.model === "string" ? clip(report.model, MAX_MODEL_CHARS) : null,
|
|
137562
|
+
reason,
|
|
137563
|
+
transcriptEvents: asFiniteInt$1(coverage.transcriptEvents) ?? 0,
|
|
137564
|
+
transcriptIncluded: asFiniteInt$1(coverage.transcriptIncluded) ?? 0,
|
|
137565
|
+
transcriptDropped: asFiniteInt$1(coverage.transcriptDropped) ?? 0,
|
|
137566
|
+
transcriptClippedFields: asFiniteInt$1(coverage.transcriptClippedFields) ?? 0,
|
|
137567
|
+
ledgerRows: asFiniteInt$1(coverage.ledgerRows) ?? 0,
|
|
137568
|
+
ledgerTotalRows: asFiniteInt$1(coverage.ledgerTotalRows) ?? 0,
|
|
137569
|
+
summaryChars: summary === null ? null : summary.length,
|
|
137570
|
+
contextDeltaTokens: asFiniteInt$1(context?.deltaTokens),
|
|
137571
|
+
durationMs: asFiniteInt$1(timing?.durationMs)
|
|
137572
|
+
}
|
|
137573
|
+
};
|
|
137574
|
+
}
|
|
137575
|
+
/** `compression:<threadId>:<turnId>`: one pass per turn, once. */
|
|
137576
|
+
const compressionFactId = (threadId, turnId) => `compression:${threadId}:${turnId}`;
|
|
137577
|
+
/**
|
|
137578
|
+
* A compression pass, only when it was actually measured.
|
|
137579
|
+
*
|
|
137580
|
+
* Both token readings and the duration have to come from recorded state: a
|
|
137581
|
+
* pass whose before or after was never written is not reported as zero, and
|
|
137582
|
+
* the delta is derived from the two readings rather than supplied beside them.
|
|
137583
|
+
*/
|
|
137584
|
+
function compressionFactFrom(input) {
|
|
137585
|
+
const before = asFiniteInt$1(input.beforeTokens);
|
|
137586
|
+
const after = asFiniteInt$1(input.afterTokens);
|
|
137587
|
+
const durationMs = asFiniteInt$1(input.durationMs);
|
|
137588
|
+
if (before === null || after === null || durationMs === null || durationMs < 0) return null;
|
|
137589
|
+
if (before < 0 || after < 0) return null;
|
|
137590
|
+
return {
|
|
137591
|
+
eventId: compressionFactId(input.threadId, input.turnId),
|
|
137592
|
+
occurredAt: input.occurredAt,
|
|
137593
|
+
kind: "compression.applied",
|
|
137594
|
+
version: 1,
|
|
137595
|
+
payload: {
|
|
137596
|
+
mode: input.mode,
|
|
137597
|
+
threadId: input.threadId,
|
|
137598
|
+
beforeTokens: before,
|
|
137599
|
+
afterTokens: after,
|
|
137600
|
+
deltaTokens: before - after,
|
|
137601
|
+
durationMs
|
|
137602
|
+
}
|
|
137603
|
+
};
|
|
137604
|
+
}
|
|
137605
|
+
//#endregion
|
|
137606
|
+
//#region src/analytics/AnalyticsFactCapture.ts
|
|
137607
|
+
const ANALYTICS_QUESTION_SOURCE = "events.user-input";
|
|
137608
|
+
const ANALYTICS_FUSION_SOURCE = "events.fusion-evidence";
|
|
137609
|
+
const ANALYTICS_COMPRESSION_SOURCE = "events.context-compaction";
|
|
137610
|
+
const decodeJson$1 = Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(Schema$1.Unknown));
|
|
137611
|
+
const asRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
137612
|
+
const asFiniteInt = (value) => typeof value === "number" && Number.isFinite(value) ? Math.round(value) : null;
|
|
137613
|
+
const asUsedTokens = (payload) => asFiniteInt(asRecord$2(payload)?.usedTokens);
|
|
137614
|
+
/** One appended activity, read out of its event envelope. */
|
|
137615
|
+
const readActivityEvent = Effect.fn("AnalyticsFactCapture.activity")(function* (row) {
|
|
137616
|
+
const payload = asRecord$2(yield* decodeJson$1(row.payload_json));
|
|
137617
|
+
const activity = asRecord$2(payload?.activity);
|
|
137618
|
+
const threadId = typeof payload?.threadId === "string" ? payload.threadId : null;
|
|
137619
|
+
if (activity === null || threadId === null) return null;
|
|
137620
|
+
return {
|
|
137621
|
+
sequence: row.sequence,
|
|
137622
|
+
threadId,
|
|
137623
|
+
turnId: typeof activity.turnId === "string" ? activity.turnId : null,
|
|
137624
|
+
createdAt: typeof activity.createdAt === "string" ? activity.createdAt : "",
|
|
137625
|
+
payload: asRecord$2(activity.payload) ?? {}
|
|
137626
|
+
};
|
|
137627
|
+
});
|
|
137628
|
+
/** Candidate rows of one activity kind, after a source's own cursor. */
|
|
137629
|
+
const readCandidates = Effect.fn("AnalyticsFactCapture.candidates")(function* (kind, after, limit) {
|
|
137630
|
+
return yield* (yield* SqlClient.SqlClient)`
|
|
137631
|
+
SELECT sequence, payload_json FROM orchestration_events
|
|
137632
|
+
WHERE sequence > ${after} AND event_type = 'thread.activity-appended'
|
|
137633
|
+
AND json_extract(payload_json, '$.activity.kind') = ${kind}
|
|
137634
|
+
ORDER BY sequence ASC LIMIT ${limit}`;
|
|
137635
|
+
});
|
|
137636
|
+
/**
|
|
137637
|
+
* The request a resolution answers, matched on the exact request id.
|
|
137638
|
+
*
|
|
137639
|
+
* A newer question in the same thread must never lend its options to an older
|
|
137640
|
+
* answer, which is what a "most recent request" lookup would do whenever two
|
|
137641
|
+
* requests were open at once.
|
|
137642
|
+
*/
|
|
137643
|
+
const readQuestionRequest = Effect.fn("AnalyticsFactCapture.questionRequest")(function* (threadId, requestId, throughSequence) {
|
|
137644
|
+
const [row] = yield* (yield* SqlClient.SqlClient)`
|
|
137645
|
+
SELECT sequence, payload_json FROM orchestration_events
|
|
137646
|
+
WHERE sequence <= ${throughSequence} AND event_type = 'thread.activity-appended'
|
|
137647
|
+
AND json_extract(payload_json, '$.activity.kind') = 'user-input.requested'
|
|
137648
|
+
AND json_extract(payload_json, '$.threadId') = ${threadId}
|
|
137649
|
+
AND json_extract(payload_json, '$.activity.payload.requestId') = ${requestId}
|
|
137650
|
+
ORDER BY sequence DESC LIMIT 1`;
|
|
137651
|
+
if (row === void 0) return null;
|
|
137652
|
+
return (yield* readActivityEvent(row))?.payload ?? null;
|
|
137653
|
+
});
|
|
137654
|
+
/** The handoff a report closes, found by the range the report names. */
|
|
137655
|
+
const readHandoff = Effect.fn("AnalyticsFactCapture.handoff")(function* (threadId, throughSequence, beforeSequence) {
|
|
137656
|
+
const [row] = yield* (yield* SqlClient.SqlClient)`
|
|
137657
|
+
SELECT sequence, payload_json FROM orchestration_events
|
|
137658
|
+
WHERE sequence <= ${beforeSequence} AND event_type = 'thread.activity-appended'
|
|
137659
|
+
AND json_extract(payload_json, '$.activity.kind') = 'fusion.evidence.handoff'
|
|
137660
|
+
AND json_extract(payload_json, '$.activity.payload.watchedThreadId') = ${threadId}
|
|
137661
|
+
AND json_extract(payload_json, '$.activity.payload.throughSequence') = ${throughSequence}
|
|
137662
|
+
ORDER BY sequence DESC LIMIT 1`;
|
|
137663
|
+
if (row === void 0) return null;
|
|
137664
|
+
return (yield* readActivityEvent(row))?.payload ?? null;
|
|
137665
|
+
});
|
|
137666
|
+
/**
|
|
137667
|
+
* The nearest context reading on one side of a compaction, in the same turn.
|
|
137668
|
+
*
|
|
137669
|
+
* Correlating on the turn is the point: a later turn's context update must
|
|
137670
|
+
* never be able to close an old pass, which is exactly what "nearest reading
|
|
137671
|
+
* in this thread" would let it do once the pass itself was never completed.
|
|
137672
|
+
*/
|
|
137673
|
+
const readContextReading = Effect.fn("AnalyticsFactCapture.contextReading")(function* (threadId, turnId, sequence, side) {
|
|
137674
|
+
const sql = yield* SqlClient.SqlClient;
|
|
137675
|
+
const row = (side === "before" ? yield* sql`
|
|
137676
|
+
SELECT sequence, payload_json FROM orchestration_events
|
|
137677
|
+
WHERE sequence < ${sequence} AND event_type = 'thread.activity-appended'
|
|
137678
|
+
AND json_extract(payload_json, '$.activity.kind') = 'context-window.updated'
|
|
137679
|
+
AND json_extract(payload_json, '$.threadId') = ${threadId}
|
|
137680
|
+
AND json_extract(payload_json, '$.activity.turnId') = ${turnId}
|
|
137681
|
+
ORDER BY sequence DESC LIMIT 1` : yield* sql`
|
|
137682
|
+
SELECT sequence, payload_json FROM orchestration_events
|
|
137683
|
+
WHERE sequence > ${sequence} AND event_type = 'thread.activity-appended'
|
|
137684
|
+
AND json_extract(payload_json, '$.activity.kind') = 'context-window.updated'
|
|
137685
|
+
AND json_extract(payload_json, '$.threadId') = ${threadId}
|
|
137686
|
+
AND json_extract(payload_json, '$.activity.turnId') = ${turnId}
|
|
137687
|
+
ORDER BY sequence ASC LIMIT 1`)[0];
|
|
137688
|
+
return row === void 0 ? null : yield* readActivityEvent(row);
|
|
137689
|
+
});
|
|
137690
|
+
const stageAll = Effect.fn("AnalyticsFactCapture.stageAll")(function* (facts) {
|
|
137691
|
+
let staged = 0;
|
|
137692
|
+
for (const fact of facts) if (yield* stageAnalyticsFact(fact)) staged += 1;
|
|
137693
|
+
return staged;
|
|
137694
|
+
});
|
|
137695
|
+
/** Questions: one fact per question in each resolved request. */
|
|
137696
|
+
const captureQuestionFacts = Effect.fn("AnalyticsFactCapture.questions")(function* (limit = 128) {
|
|
137697
|
+
const after = yield* readAnalyticsCursor(ANALYTICS_QUESTION_SOURCE);
|
|
137698
|
+
const rows = yield* readCandidates("user-input.resolved", after, limit);
|
|
137699
|
+
let cursor = after;
|
|
137700
|
+
let staged = 0;
|
|
137701
|
+
for (const row of rows) {
|
|
137702
|
+
const event = yield* readActivityEvent(row);
|
|
137703
|
+
const requestId = typeof event?.payload.requestId === "string" ? event.payload.requestId : null;
|
|
137704
|
+
if (event !== null && requestId !== null) {
|
|
137705
|
+
const request = yield* readQuestionRequest(event.threadId, requestId, event.sequence);
|
|
137706
|
+
if (request !== null) staged += yield* stageAll(questionFactsFrom({
|
|
137707
|
+
requestId,
|
|
137708
|
+
threadId: event.threadId,
|
|
137709
|
+
occurredAt: event.createdAt,
|
|
137710
|
+
questions: request.questions,
|
|
137711
|
+
answers: event.payload.answers
|
|
137712
|
+
}));
|
|
137713
|
+
}
|
|
137714
|
+
cursor = row.sequence;
|
|
137715
|
+
}
|
|
137716
|
+
if (cursor !== after) yield* writeAnalyticsCursor(ANALYTICS_QUESTION_SOURCE, cursor);
|
|
137717
|
+
return {
|
|
137718
|
+
staged,
|
|
137719
|
+
cursor
|
|
137720
|
+
};
|
|
137721
|
+
});
|
|
137722
|
+
/**
|
|
137723
|
+
* Fusion: staged from the evidence report, never before it.
|
|
137724
|
+
*
|
|
137725
|
+
* The report is what carries the model, the context delta and the measured
|
|
137726
|
+
* duration, so a fact staged at handoff time would be missing exactly the
|
|
137727
|
+
* numbers it exists to carry - and could never gain them, because the fact is
|
|
137728
|
+
* append-only.
|
|
137729
|
+
*/
|
|
137730
|
+
const captureFusionFacts = Effect.fn("AnalyticsFactCapture.fusion")(function* (input) {
|
|
137731
|
+
const after = yield* readAnalyticsCursor(ANALYTICS_FUSION_SOURCE);
|
|
137732
|
+
const rows = yield* readCandidates("fusion.evidence.summary", after, input.limit ?? 128);
|
|
137733
|
+
let cursor = after;
|
|
137734
|
+
let staged = 0;
|
|
137735
|
+
for (const row of rows) {
|
|
137736
|
+
const event = yield* readActivityEvent(row);
|
|
137737
|
+
const watchedThreadId = typeof event?.payload.watchedThreadId === "string" ? event.payload.watchedThreadId : null;
|
|
137738
|
+
const throughSequence = asFiniteInt(event?.payload.throughSequence);
|
|
137739
|
+
if (event !== null && watchedThreadId !== null && throughSequence !== null) {
|
|
137740
|
+
const handoff = yield* readHandoff(watchedThreadId, throughSequence, event.sequence);
|
|
137741
|
+
if (handoff !== null) {
|
|
137742
|
+
const fact = fusionHandoffFactFrom({
|
|
137743
|
+
threadId: watchedThreadId,
|
|
137744
|
+
throughSequence,
|
|
137745
|
+
occurredAt: event.createdAt,
|
|
137746
|
+
fusionVersion: input.fusionVersion,
|
|
137747
|
+
payload: handoff,
|
|
137748
|
+
reportPayload: event.payload
|
|
137749
|
+
});
|
|
137750
|
+
if (fact !== null) staged += yield* stageAll([fact]);
|
|
137751
|
+
}
|
|
137752
|
+
}
|
|
137753
|
+
cursor = row.sequence;
|
|
137754
|
+
}
|
|
137755
|
+
if (cursor !== after) yield* writeAnalyticsCursor(ANALYTICS_FUSION_SOURCE, cursor);
|
|
137756
|
+
return {
|
|
137757
|
+
staged,
|
|
137758
|
+
cursor
|
|
137759
|
+
};
|
|
137760
|
+
});
|
|
137761
|
+
/**
|
|
137762
|
+
* Compression: only a pass the service measured and the provider reported.
|
|
137763
|
+
*
|
|
137764
|
+
* The duration comes from the compaction event the provider service stamped,
|
|
137765
|
+
* the tokens from the recorded context readings either side. A pass whose
|
|
137766
|
+
* later reading has not landed yet holds back only this cursor, so a question
|
|
137767
|
+
* or a handoff behind it is still captured on the same tick.
|
|
137768
|
+
*/
|
|
137769
|
+
const captureCompressionFacts = Effect.fn("AnalyticsFactCapture.compression")(function* (limit = 128) {
|
|
137770
|
+
const after = yield* readAnalyticsCursor(ANALYTICS_COMPRESSION_SOURCE);
|
|
137771
|
+
const rows = yield* readCandidates("context-compaction", after, limit);
|
|
137772
|
+
let cursor = after;
|
|
137773
|
+
let staged = 0;
|
|
137774
|
+
for (const row of rows) {
|
|
137775
|
+
const event = yield* readActivityEvent(row);
|
|
137776
|
+
if (event === null || event.turnId === null) {
|
|
137777
|
+
cursor = row.sequence;
|
|
137778
|
+
continue;
|
|
137779
|
+
}
|
|
137780
|
+
const durationMs = asFiniteInt(asRecord$2(event.payload.detail)?.durationMs);
|
|
137781
|
+
const before = yield* readContextReading(event.threadId, event.turnId, event.sequence, "before");
|
|
137782
|
+
const afterReading = yield* readContextReading(event.threadId, event.turnId, event.sequence, "after");
|
|
137783
|
+
if (afterReading === null) break;
|
|
137784
|
+
const mode = asRecord$2(before?.payload)?.compressMode ?? afterReading.payload.compressMode;
|
|
137785
|
+
const fact = typeof mode === "string" ? compressionFactFrom({
|
|
137786
|
+
threadId: event.threadId,
|
|
137787
|
+
turnId: event.turnId,
|
|
137788
|
+
occurredAt: event.createdAt,
|
|
137789
|
+
mode,
|
|
137790
|
+
beforeTokens: asUsedTokens(before?.payload),
|
|
137791
|
+
afterTokens: asUsedTokens(afterReading.payload),
|
|
137792
|
+
durationMs
|
|
137793
|
+
}) : null;
|
|
137794
|
+
if (fact !== null) staged += yield* stageAll([fact]);
|
|
137795
|
+
cursor = row.sequence;
|
|
137796
|
+
}
|
|
137797
|
+
if (cursor !== after) yield* writeAnalyticsCursor(ANALYTICS_COMPRESSION_SOURCE, cursor);
|
|
137798
|
+
return {
|
|
137799
|
+
staged,
|
|
137800
|
+
cursor
|
|
137801
|
+
};
|
|
137802
|
+
});
|
|
137803
|
+
/** One tick over every source. A source that stalls never blocks another. */
|
|
137804
|
+
const captureAnalyticsFacts = Effect.fn("AnalyticsFactCapture.run")(function* (input) {
|
|
137805
|
+
const questions = yield* captureQuestionFacts(input.limit);
|
|
137806
|
+
const fusion = yield* captureFusionFacts(input);
|
|
137807
|
+
const compression = yield* captureCompressionFacts(input.limit);
|
|
137808
|
+
return { staged: questions.staged + fusion.staged + compression.staged };
|
|
137809
|
+
});
|
|
137810
|
+
//#endregion
|
|
137811
|
+
//#region src/analytics/AnalyticsDelivery.ts
|
|
137812
|
+
const runAnalyticsTick = Effect.fn("AnalyticsDelivery.tick")(function* (transport, identity, fusionVersion) {
|
|
137813
|
+
yield* captureAnalyticsFacts({ fusionVersion });
|
|
137814
|
+
let delivered = 0;
|
|
137815
|
+
for (const row of yield* pendingAnalyticsFacts()) {
|
|
137816
|
+
const fact = yield* decodeStoredFact(row.payload);
|
|
137817
|
+
yield* transport.postFact(fact, identity);
|
|
137818
|
+
yield* acknowledgeAnalyticsFact(row.event_id);
|
|
137819
|
+
delivered += 1;
|
|
137820
|
+
}
|
|
137821
|
+
return { delivered };
|
|
137822
|
+
});
|
|
136599
137823
|
//#endregion
|
|
136600
137824
|
//#region src/hub/HubTunnelReconnect.ts
|
|
136601
137825
|
const HUB_TUNNEL_RETRY_INITIAL_MS = 1e3;
|
|
@@ -136634,6 +137858,7 @@ const runHubActivityTick = Effect.fn("HubActivityRuntime.tick")(function* (outbo
|
|
|
136634
137858
|
yield* transport.post(activity, identity);
|
|
136635
137859
|
yield* outbound.acknowledge(pending.thread_id, pending.revision);
|
|
136636
137860
|
}
|
|
137861
|
+
yield* runAnalyticsTick(transport, identity, version$1).pipe(Effect.catchCause((cause) => Effect.logDebug("analytics tick deferred", { cause })));
|
|
136637
137862
|
});
|
|
136638
137863
|
/** A credential generation owns every request, staging page and delay. Old destinations remain isolated. */
|
|
136639
137864
|
const runHubActivityEmission = Effect.fn("HubActivityRuntime.run")(function* (settings, ready) {
|
|
@@ -141591,22 +142816,95 @@ function describeHandoffEvent(event, implementerThreadId, clip) {
|
|
|
141591
142816
|
}
|
|
141592
142817
|
}
|
|
141593
142818
|
/**
|
|
142819
|
+
* An activity line's evidence fields, its kind dropped and its title read the
|
|
142820
|
+
* way the ledger reads it: a provider spells the opening fragment's title
|
|
142821
|
+
* "<title> started", which is the same call rather than a different one.
|
|
142822
|
+
*/
|
|
142823
|
+
function activityFields(content) {
|
|
142824
|
+
const [, ...fields] = content.split(" | ");
|
|
142825
|
+
const [title, ...rest] = fields;
|
|
142826
|
+
return title === void 0 ? fields : [title.replace(/ started$/u, ""), ...rest];
|
|
142827
|
+
}
|
|
142828
|
+
/** What a line is a fragment of, or null when it stands alone. */
|
|
142829
|
+
function fragmentIdentity(event) {
|
|
142830
|
+
if (event.type === "thread.message-sent") {
|
|
142831
|
+
const messageId = asTrimmed(event.payload.messageId);
|
|
142832
|
+
return messageId === null ? null : `message:${messageId}`;
|
|
142833
|
+
}
|
|
142834
|
+
if (event.type === "thread.activity-appended") {
|
|
142835
|
+
const data = asRecord(asRecord(event.payload.activity.payload)?.data);
|
|
142836
|
+
const toolCallId = asTrimmed(data?.toolCallId);
|
|
142837
|
+
return toolCallId === null ? null : `tool:${toolCallId}`;
|
|
142838
|
+
}
|
|
142839
|
+
return null;
|
|
142840
|
+
}
|
|
142841
|
+
/**
|
|
142842
|
+
* Fragments whose evidence a later line of the same message or call already
|
|
142843
|
+
* carries, so the transcript spends its budget on evidence rather than on the
|
|
142844
|
+
* same text streamed twenty times.
|
|
142845
|
+
*
|
|
142846
|
+
* A streaming message is folded only when a later fragment literally contains
|
|
142847
|
+
* it, and a tool fragment only when every field it carries reappears later, so
|
|
142848
|
+
* nothing unique is ever folded away. What a folded line did carry alone - the
|
|
142849
|
+
* call's identity, its status and the sequence it started at - is exactly what
|
|
142850
|
+
* the ledger records beside this transcript.
|
|
142851
|
+
*/
|
|
142852
|
+
function supersededLines(lines) {
|
|
142853
|
+
const groups = /* @__PURE__ */ new Map();
|
|
142854
|
+
for (const line of lines) {
|
|
142855
|
+
if (line.identity === null) continue;
|
|
142856
|
+
const group = groups.get(line.identity);
|
|
142857
|
+
if (group === void 0) groups.set(line.identity, [line]);
|
|
142858
|
+
else group.push(line);
|
|
142859
|
+
}
|
|
142860
|
+
const superseded = /* @__PURE__ */ new Set();
|
|
142861
|
+
for (const group of groups.values()) {
|
|
142862
|
+
if (group.length < 2) continue;
|
|
142863
|
+
const last = group[group.length - 1];
|
|
142864
|
+
for (let index = 0; index < group.length - 1; index += 1) {
|
|
142865
|
+
const line = group[index];
|
|
142866
|
+
if (line.shape === "message") {
|
|
142867
|
+
if (last.shape === "message" && last.content.includes(line.content)) superseded.add(line);
|
|
142868
|
+
continue;
|
|
142869
|
+
}
|
|
142870
|
+
const laterFields = new Set(group.slice(index + 1).flatMap((later) => later.fields));
|
|
142871
|
+
if (line.fields.every((field) => laterFields.has(field))) superseded.add(line);
|
|
142872
|
+
}
|
|
142873
|
+
}
|
|
142874
|
+
return superseded;
|
|
142875
|
+
}
|
|
142876
|
+
/**
|
|
141594
142877
|
* Flatten the range, newest kept when the bound bites.
|
|
141595
142878
|
*
|
|
141596
|
-
*
|
|
141597
|
-
*
|
|
141598
|
-
*
|
|
142879
|
+
* Duplicate fragments are folded before the bound is applied, because dropping
|
|
142880
|
+
* a unique event to make room for the twelfth copy of a streaming message is a
|
|
142881
|
+
* loss this handoff can simply avoid. Dropping the oldest is still the lesser
|
|
142882
|
+
* evil once the bound does bite, and unlike in the triage window the loss is
|
|
142883
|
+
* reported rather than absorbed: a range that did not fit is a range this
|
|
142884
|
+
* handoff must not call complete. Clipped fields are counted on the lines
|
|
142885
|
+
* actually handed over, so a field clipped inside a folded or dropped line is
|
|
142886
|
+
* not charged against coverage twice.
|
|
141599
142887
|
*/
|
|
141600
142888
|
function buildHandoffEvidence(events, implementerThreadId, maxChars) {
|
|
141601
|
-
const
|
|
141602
|
-
const lines = [];
|
|
142889
|
+
const rendered = [];
|
|
141603
142890
|
for (const event of events) {
|
|
142891
|
+
const clip = makeClipper();
|
|
141604
142892
|
const text = describeHandoffEvent(event, implementerThreadId, clip);
|
|
141605
|
-
if (text
|
|
142893
|
+
if (text === null) continue;
|
|
142894
|
+
const content = text.replace(/^#\d+ /u, "");
|
|
142895
|
+
const shape = event.type === "thread.message-sent" ? "message" : "activity";
|
|
142896
|
+
rendered.push({
|
|
141606
142897
|
sequence: event.sequence,
|
|
141607
|
-
text
|
|
142898
|
+
text,
|
|
142899
|
+
clippedFields: clip.clipped,
|
|
142900
|
+
identity: fragmentIdentity(event),
|
|
142901
|
+
shape,
|
|
142902
|
+
content,
|
|
142903
|
+
fields: shape === "activity" ? activityFields(content) : []
|
|
141608
142904
|
});
|
|
141609
142905
|
}
|
|
142906
|
+
const superseded = supersededLines(rendered);
|
|
142907
|
+
const lines = rendered.filter((line) => !superseded.has(line));
|
|
141610
142908
|
const kept = [];
|
|
141611
142909
|
let used = 0;
|
|
141612
142910
|
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
@@ -141618,10 +142916,11 @@ function buildHandoffEvidence(events, implementerThreadId, maxChars) {
|
|
|
141618
142916
|
}
|
|
141619
142917
|
return {
|
|
141620
142918
|
transcript: kept.map((line) => line.text).join("\n"),
|
|
141621
|
-
eventCount:
|
|
142919
|
+
eventCount: rendered.length,
|
|
141622
142920
|
includedCount: kept.length,
|
|
141623
142921
|
droppedCount: lines.length - kept.length,
|
|
141624
|
-
|
|
142922
|
+
coalescedCount: superseded.size,
|
|
142923
|
+
clippedFields: kept.reduce((total, line) => total + line.clippedFields, 0),
|
|
141625
142924
|
firstSequence: kept[0]?.sequence ?? null,
|
|
141626
142925
|
lastSequence: kept[kept.length - 1]?.sequence ?? null,
|
|
141627
142926
|
includedSequences: new Set(kept.map((line) => line.sequence))
|
|
@@ -141677,6 +142976,29 @@ const HANDOFF_SUMMARY_INSTRUCTIONS = [
|
|
|
141677
142976
|
"cite at least one sequence, and never a sequence absent from the transcript",
|
|
141678
142977
|
"name the user's requirements and corrections, and quote the exact text of any failure or error"
|
|
141679
142978
|
];
|
|
142979
|
+
/** Enough sequences to show the shape of a citation without dictating the summary. */
|
|
142980
|
+
const CITATION_EXAMPLE_COUNT = 2;
|
|
142981
|
+
/** The lowest and highest sequence handed over, as the citations they must be copied as. */
|
|
142982
|
+
function citationExamples(includedSequences) {
|
|
142983
|
+
const sorted = [...includedSequences].sort((left, right) => left - right);
|
|
142984
|
+
return (sorted.length <= CITATION_EXAMPLE_COUNT ? sorted : [sorted[0], sorted[sorted.length - 1]]).map((sequence) => `#${sequence}`);
|
|
142985
|
+
}
|
|
142986
|
+
/**
|
|
142987
|
+
* The citation rules carrying real sequences, because `#<sequence>` read as a
|
|
142988
|
+
* placeholder is what a summarizer answers without any number at all.
|
|
142989
|
+
*/
|
|
142990
|
+
function handoffSummaryInstructions(input) {
|
|
142991
|
+
const examples = citationExamples(input.includedSequences);
|
|
142992
|
+
return examples.length === 0 ? HANDOFF_SUMMARY_INSTRUCTIONS : [...HANDOFF_SUMMARY_INSTRUCTIONS, `copy citations from the line starts of this transcript, for example ${examples.join(" and ")}`];
|
|
142993
|
+
}
|
|
142994
|
+
/**
|
|
142995
|
+
* The single repair attempt: the same rules, plus what the discarded answer
|
|
142996
|
+
* got wrong. Nothing here relaxes the rule - a second miss falls back.
|
|
142997
|
+
*/
|
|
142998
|
+
function handoffSummaryRepairInstructions(input) {
|
|
142999
|
+
const invented = citedSequences(input.summary).filter((sequence) => !input.includedSequences.has(sequence)).slice(0, CITATION_EXAMPLE_COUNT).map((sequence) => `#${sequence}`);
|
|
143000
|
+
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`];
|
|
143001
|
+
}
|
|
141680
143002
|
/** How a summarized handoff opens, so a caller can tell one from a fallback. */
|
|
141681
143003
|
const SERVER_HANDOFF_SUMMARY_PREFIX = "Server evidence handoff for builder thread";
|
|
141682
143004
|
/**
|
|
@@ -141706,6 +143028,7 @@ const EMPTY_EVIDENCE = {
|
|
|
141706
143028
|
eventCount: 0,
|
|
141707
143029
|
includedCount: 0,
|
|
141708
143030
|
droppedCount: 0,
|
|
143031
|
+
coalescedCount: 0,
|
|
141709
143032
|
clippedFields: 0,
|
|
141710
143033
|
firstSequence: null,
|
|
141711
143034
|
lastSequence: null,
|
|
@@ -141765,19 +143088,20 @@ function resolveHandoffModel(settings) {
|
|
|
141765
143088
|
};
|
|
141766
143089
|
}
|
|
141767
143090
|
/**
|
|
141768
|
-
*
|
|
143091
|
+
* Why a summary may not stand in for reading the range, or null when it may.
|
|
141769
143092
|
*
|
|
141770
|
-
* Empty
|
|
141771
|
-
*
|
|
141772
|
-
*
|
|
141773
|
-
*
|
|
143093
|
+
* Empty and uncited are refused because a claim with no sequence behind it
|
|
143094
|
+
* cannot be checked against the ledger, and a citation outside the window is
|
|
143095
|
+
* refused because it is evidence of a model describing something other than
|
|
143096
|
+
* this turn. The two are named apart because they fail differently: one is a
|
|
143097
|
+
* summarizer that ignored the rule, the other one that read something else.
|
|
141774
143098
|
*/
|
|
141775
|
-
function
|
|
143099
|
+
function summaryRejection(input) {
|
|
141776
143100
|
const trimmed = input.summary.trim();
|
|
141777
|
-
if (trimmed.length === 0) return
|
|
143101
|
+
if (trimmed.length === 0) return "summary-uncited";
|
|
141778
143102
|
const cited = citedSequences(trimmed);
|
|
141779
|
-
if (cited.length === 0) return
|
|
141780
|
-
return cited.every((sequence) => input.includedSequences.has(sequence));
|
|
143103
|
+
if (cited.length === 0) return "summary-uncited";
|
|
143104
|
+
return cited.every((sequence) => input.includedSequences.has(sequence)) ? null : "summary-citation-invalid";
|
|
141781
143105
|
}
|
|
141782
143106
|
const buildFusionEvidenceHandoff = Effect.fn("fusionEvidenceHandoff.build")(function* (input) {
|
|
141783
143107
|
const engine = yield* OrchestrationEngineService;
|
|
@@ -141833,20 +143157,51 @@ const buildFusionEvidenceHandoff = Effect.fn("fusionEvidenceHandoff.build")(func
|
|
|
141833
143157
|
if (driver === void 0 || !HARDENED_SUMMARY_DRIVERS.has(driver)) return yield* fallback("provider-profile-unhardened");
|
|
141834
143158
|
if (textGeneration.summarizeFusionEvidence === void 0) return yield* fallback("provider-cannot-summarize");
|
|
141835
143159
|
const summarize = textGeneration.summarizeFusionEvidence;
|
|
141836
|
-
const
|
|
143160
|
+
const runSummary = (instructions) => Effect.suspend(() => summarize({
|
|
141837
143161
|
cwd: serverConfig.cwd,
|
|
141838
143162
|
transcript: evidence.transcript,
|
|
141839
143163
|
maxTranscriptChars: HANDOFF_TRANSCRIPT_MAX_CHARS,
|
|
141840
|
-
instructions
|
|
143164
|
+
instructions,
|
|
141841
143165
|
modelSelection: model
|
|
141842
|
-
})).pipe(Effect.map((result) => Option.some(result)), Effect.
|
|
141843
|
-
|
|
141844
|
-
|
|
141845
|
-
|
|
141846
|
-
|
|
141847
|
-
|
|
141848
|
-
|
|
141849
|
-
|
|
143166
|
+
})).pipe(Effect.map((result) => Option.some(result)), Effect.catchCause(() => Effect.succeed(Option.none())));
|
|
143167
|
+
/** The hardened identity checks, which a repair may never talk its way past. */
|
|
143168
|
+
const identityRejection = (result) => result.agentName !== P4_SUMMARIZER_AGENT.name && result.agentName !== "summarizer" ? "summarizer-agent-mismatch" : result.requestedModel !== model.model ? "summarizer-model-mismatch" : null;
|
|
143169
|
+
const noSummary = (reason) => ({
|
|
143170
|
+
produced: Option.none(),
|
|
143171
|
+
reason
|
|
143172
|
+
});
|
|
143173
|
+
const attempt = yield* Effect.gen(function* () {
|
|
143174
|
+
const first = yield* runSummary(handoffSummaryInstructions({ includedSequences: evidence.includedSequences }));
|
|
143175
|
+
if (Option.isNone(first)) return noSummary("summary-unavailable");
|
|
143176
|
+
const identity = identityRejection(first.value);
|
|
143177
|
+
if (identity !== null) return noSummary(identity);
|
|
143178
|
+
const rejection = summaryRejection({
|
|
143179
|
+
summary: first.value.summary,
|
|
143180
|
+
includedSequences: evidence.includedSequences
|
|
143181
|
+
});
|
|
143182
|
+
if (rejection === null) return {
|
|
143183
|
+
produced: first,
|
|
143184
|
+
reason: null
|
|
143185
|
+
};
|
|
143186
|
+
const repaired = yield* runSummary(handoffSummaryRepairInstructions({
|
|
143187
|
+
rejection,
|
|
143188
|
+
summary: first.value.summary,
|
|
143189
|
+
includedSequences: evidence.includedSequences
|
|
143190
|
+
}));
|
|
143191
|
+
if (Option.isNone(repaired)) return noSummary(rejection);
|
|
143192
|
+
const repairedIdentity = identityRejection(repaired.value);
|
|
143193
|
+
if (repairedIdentity !== null) return noSummary(repairedIdentity);
|
|
143194
|
+
const repairedRejection = summaryRejection({
|
|
143195
|
+
summary: repaired.value.summary,
|
|
143196
|
+
includedSequences: evidence.includedSequences
|
|
143197
|
+
});
|
|
143198
|
+
return repairedRejection === null ? {
|
|
143199
|
+
produced: repaired,
|
|
143200
|
+
reason: null
|
|
143201
|
+
} : noSummary(repairedRejection);
|
|
143202
|
+
}).pipe(Effect.timeoutOption(HANDOFF_TIMEOUT), Effect.map(Option.getOrElse(() => noSummary("summary-unavailable"))));
|
|
143203
|
+
const produced = attempt.produced;
|
|
143204
|
+
if (Option.isNone(produced)) return yield* fallback(attempt.reason ?? "summary-unavailable");
|
|
141850
143205
|
const preparedAt = DateTime.formatIso(yield* DateTime.now);
|
|
141851
143206
|
return {
|
|
141852
143207
|
status: "summarized",
|
|
@@ -142133,6 +143488,25 @@ const make$3 = Effect.gen(function* () {
|
|
|
142133
143488
|
*/
|
|
142134
143489
|
const triageSkipStreak = /* @__PURE__ */ new Map();
|
|
142135
143490
|
/**
|
|
143491
|
+
* Consecutive status turns coalesced per pair. In-memory like the counters
|
|
143492
|
+
* above, and safe there: the classification is recomputed from the same
|
|
143493
|
+
* events on replay, so a restart costs at most one extra wake rather than a
|
|
143494
|
+
* wrong one.
|
|
143495
|
+
*/
|
|
143496
|
+
const statusCoalesceStreak = /* @__PURE__ */ new Map();
|
|
143497
|
+
/**
|
|
143498
|
+
* The monitor tasks each pair currently has open.
|
|
143499
|
+
*
|
|
143500
|
+
* A progress row carries no task type, so the only thing that makes it a
|
|
143501
|
+
* wait rather than unclassified work is the `task.started` that named it -
|
|
143502
|
+
* and that row belongs to an earlier turn, outside the window this turn is
|
|
143503
|
+
* described over. Held per pair so an open watch stays recognisable across
|
|
143504
|
+
* the turns it reports through, and dropped the moment the task ends.
|
|
143505
|
+
* In-memory like the counters above, and safe the same way: a restart
|
|
143506
|
+
* forgets the watch and costs one extra wake rather than a wrong one.
|
|
143507
|
+
*/
|
|
143508
|
+
const openMonitorTaskIds = /* @__PURE__ */ new Map();
|
|
143509
|
+
/**
|
|
142136
143510
|
* Gate detection and human-override resolution act on the CURRENT persisted
|
|
142137
143511
|
* gate state, so replaying them against historical events is wrong twice
|
|
142138
143512
|
* over: an old plan event would open a fresh gate, and an old user message
|
|
@@ -142551,15 +143925,28 @@ const make$3 = Effect.gen(function* () {
|
|
|
142551
143925
|
/**
|
|
142552
143926
|
* What the builder did between two review boundaries, read from the event
|
|
142553
143927
|
* window rather than remembered: whether supervisor advice started the turn,
|
|
142554
|
-
* whether a human spoke into it,
|
|
142555
|
-
*
|
|
142556
|
-
*
|
|
142557
|
-
* see rather than fund.
|
|
143928
|
+
* whether a human spoke into it, whether any tool ran, and whether anything
|
|
143929
|
+
* happened that a supervisor has to see. A text-only builder question counts
|
|
143930
|
+
* as idle on purpose: one or two such rounds pass unhindered, and a longer
|
|
143931
|
+
* tool-free exchange is a debate the user should see rather than fund.
|
|
143932
|
+
*
|
|
143933
|
+
* `waitMarked`, `changedState`, `failedTool` and `gateActivity` are read from
|
|
143934
|
+
* activity kinds and typed task classifications alone; nothing here inspects
|
|
143935
|
+
* prose. Only a live, named wait marks a turn as waiting: a wake scheduled
|
|
143936
|
+
* for a real time, or a Monitor-tool task and its own progress. A command, a
|
|
143937
|
+
* background shell and an unnamed task all count as work, because any of
|
|
143938
|
+
* them can change the repository, and every ending - a cancelled wake, a
|
|
143939
|
+
* completed task, a failure, a decline - is a result rather than a wait.
|
|
142558
143940
|
*/
|
|
142559
143941
|
const describeBuilderTurn = (input) => {
|
|
142560
143942
|
let startedByAdvice = false;
|
|
142561
143943
|
let humanMessage = false;
|
|
142562
143944
|
let ranTools = false;
|
|
143945
|
+
let changedState = false;
|
|
143946
|
+
let failedTool = false;
|
|
143947
|
+
let gateActivity = false;
|
|
143948
|
+
let waitMarked = false;
|
|
143949
|
+
const monitorTaskIds = input.monitorTaskIds;
|
|
142563
143950
|
let summary;
|
|
142564
143951
|
const assistantMessages = /* @__PURE__ */ new Map();
|
|
142565
143952
|
for (const event of input.events) {
|
|
@@ -142573,12 +143960,39 @@ const make$3 = Effect.gen(function* () {
|
|
|
142573
143960
|
if (event.payload.text.startsWith("[fusion-advice]")) startedByAdvice = true;
|
|
142574
143961
|
else if (!isFusionProtocolText(event.payload.text)) humanMessage = true;
|
|
142575
143962
|
}
|
|
142576
|
-
if (event.type === "thread.activity-appended" && event.payload.threadId === input.implementerThreadId
|
|
143963
|
+
if (event.type === "thread.activity-appended" && event.payload.threadId === input.implementerThreadId) {
|
|
143964
|
+
const activity = event.payload.activity;
|
|
143965
|
+
const activityPayload = activity.payload ?? {};
|
|
143966
|
+
const unsuccessful = activityPayload.status === "failed" || activityPayload.status === "declined";
|
|
143967
|
+
if (activity.kind.startsWith("tool.")) {
|
|
143968
|
+
ranTools = true;
|
|
143969
|
+
if (activity.kind === "tool.denied" || unsuccessful) failedTool = true;
|
|
143970
|
+
else changedState = true;
|
|
143971
|
+
} else if (activity.kind === "wake.scheduled") if (activityPayload.wakeAt === null || activityPayload.wakeAt === void 0) changedState = true;
|
|
143972
|
+
else waitMarked = true;
|
|
143973
|
+
else if (activity.kind.startsWith("task.")) {
|
|
143974
|
+
if ((unsuccessful || activity.kind === "task.completed") && activityPayload.taskId !== void 0) monitorTaskIds.delete(activityPayload.taskId);
|
|
143975
|
+
if (unsuccessful) failedTool = true;
|
|
143976
|
+
else if (activity.kind === "task.completed") changedState = true;
|
|
143977
|
+
else if (activity.kind === "task.started") {
|
|
143978
|
+
const taskType = activityPayload.taskType;
|
|
143979
|
+
if (taskType !== void 0 && MONITOR_TOOL_TASK_TYPES.has(taskType)) {
|
|
143980
|
+
waitMarked = true;
|
|
143981
|
+
if (activityPayload.taskId !== void 0) monitorTaskIds.add(activityPayload.taskId);
|
|
143982
|
+
} else if (taskType === void 0 || !INERT_TASK_TYPES.has(taskType)) changedState = true;
|
|
143983
|
+
} else if (activity.kind === "task.progress" && activityPayload.taskId !== void 0 && monitorTaskIds.has(activityPayload.taskId)) waitMarked = true;
|
|
143984
|
+
}
|
|
143985
|
+
}
|
|
143986
|
+
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
143987
|
}
|
|
142578
143988
|
return {
|
|
142579
143989
|
startedByAdvice,
|
|
142580
143990
|
humanMessage,
|
|
142581
143991
|
ranTools,
|
|
143992
|
+
changedState,
|
|
143993
|
+
failedTool,
|
|
143994
|
+
gateActivity,
|
|
143995
|
+
waitMarked,
|
|
142582
143996
|
summary
|
|
142583
143997
|
};
|
|
142584
143998
|
};
|
|
@@ -143158,7 +144572,7 @@ const make$3 = Effect.gen(function* () {
|
|
|
143158
144572
|
});
|
|
143159
144573
|
return;
|
|
143160
144574
|
}
|
|
143161
|
-
const idleCycles = turn.startedByAdvice && !turn.humanMessage && !turn.ranTools ? (idleAdviceCycles.get(currentPair.id) ?? 0) + 1 : 0;
|
|
144575
|
+
const idleCycles = turn.startedByAdvice && !turn.humanMessage && !turn.ranTools && !turn.waitMarked && !turn.changedState && !turn.failedTool ? (idleAdviceCycles.get(currentPair.id) ?? 0) + 1 : 0;
|
|
143162
144576
|
idleAdviceCycles.set(currentPair.id, idleCycles);
|
|
143163
144577
|
if (idleCycles >= 3) {
|
|
143164
144578
|
idleAdviceCycles.delete(currentPair.id);
|
|
@@ -143170,6 +144584,18 @@ const make$3 = Effect.gen(function* () {
|
|
|
143170
144584
|
});
|
|
143171
144585
|
return;
|
|
143172
144586
|
}
|
|
144587
|
+
const statusOnly = turn.startedByAdvice && !turn.humanMessage && turn.waitMarked && !turn.changedState && !turn.failedTool && !turn.gateActivity;
|
|
144588
|
+
const streak = statusCoalesceStreak.get(currentPair.id);
|
|
144589
|
+
const alreadyCounted = streak !== void 0 && completion.sequence <= streak.throughSequence;
|
|
144590
|
+
const coalesced = statusOnly ? alreadyCounted ? streak.count : (streak?.count ?? 0) + 1 : 0;
|
|
144591
|
+
if (statusOnly && coalesced < 5) {
|
|
144592
|
+
statusCoalesceStreak.set(currentPair.id, {
|
|
144593
|
+
count: coalesced,
|
|
144594
|
+
throughSequence: Math.max(streak?.throughSequence ?? 0, completion.sequence)
|
|
144595
|
+
});
|
|
144596
|
+
return;
|
|
144597
|
+
}
|
|
144598
|
+
statusCoalesceStreak.delete(currentPair.id);
|
|
143173
144599
|
const triageSettings = yield* readTriageSettings;
|
|
143174
144600
|
if (triageSettings !== null) {
|
|
143175
144601
|
if (currentPair.triagedFromSequence !== null && completion.sequence <= currentPair.triagedFromSequence) return;
|
|
@@ -143256,13 +144682,18 @@ const make$3 = Effect.gen(function* () {
|
|
|
143256
144682
|
if (pair.reviewExperiment) completions = completions.filter((completion) => completion.payload.state === "completed").slice(-1);
|
|
143257
144683
|
let boundary = activationCompletion?.sequence ?? pair.lastReviewedImplementerSequence;
|
|
143258
144684
|
for (const completion of completions.filter((candidate) => candidate.payload.state === "completed")) {
|
|
144685
|
+
const monitorTaskIds = openMonitorTaskIds.get(pair.id) ?? /* @__PURE__ */ new Set();
|
|
143259
144686
|
const turn = describeBuilderTurn({
|
|
143260
144687
|
events,
|
|
143261
144688
|
implementerThreadId: pair.implementerThreadId,
|
|
144689
|
+
pairId: pair.id,
|
|
143262
144690
|
afterSequence: boundary,
|
|
143263
144691
|
throughSequence: completion.sequence,
|
|
143264
|
-
turnId: completion.payload.turnId
|
|
144692
|
+
turnId: completion.payload.turnId,
|
|
144693
|
+
monitorTaskIds
|
|
143265
144694
|
});
|
|
144695
|
+
if (monitorTaskIds.size === 0) openMonitorTaskIds.delete(pair.id);
|
|
144696
|
+
else openMonitorTaskIds.set(pair.id, monitorTaskIds);
|
|
143266
144697
|
boundary = completion.sequence;
|
|
143267
144698
|
yield* processReview(pair, completion, turn);
|
|
143268
144699
|
}
|
|
@@ -144891,11 +146322,11 @@ const WorkspaceEntriesLayerLive = layer$17.pipe(Layer.provide(layer$70));
|
|
|
144891
146322
|
const WorkspaceFileSystemLayerLive = layer$16.pipe(Layer.provide(layer$70), Layer.provide(WorkspaceEntriesLayerLive));
|
|
144892
146323
|
const WorkspaceLayerLive = Layer.mergeAll(layer$70, WorkspaceEntriesLayerLive, WorkspaceFileSystemLayerLive);
|
|
144893
146324
|
const ProjectFaviconResolverLayerLive = layer$65.pipe(Layer.provide(layer$70), Layer.provide(layer$66));
|
|
144894
|
-
const AuthLayerLive = layer$
|
|
146325
|
+
const AuthLayerLive = layer$115.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$119));
|
|
144895
146326
|
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$
|
|
144897
|
-
const UsageLayerLive = layer$25.pipe(Layer.provide(layer$93.pipe(Layer.provide(layer$
|
|
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$
|
|
146327
|
+
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));
|
|
146328
|
+
const UsageLayerLive = layer$25.pipe(Layer.provide(layer$93.pipe(Layer.provide(layer$119))));
|
|
146329
|
+
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
146330
|
/**
|
|
144900
146331
|
* Hub asset sync.
|
|
144901
146332
|
*
|
|
@@ -145547,7 +146978,7 @@ const serviceCommand = Command.make("service").pipe(Command.withDescription("Man
|
|
|
145547
146978
|
]));
|
|
145548
146979
|
//#endregion
|
|
145549
146980
|
//#region src/bin.ts
|
|
145550
|
-
const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$
|
|
146981
|
+
const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$123, FetchHttpClient.layer);
|
|
145551
146982
|
/**
|
|
145552
146983
|
* Prints an update reminder before dispatching, at most once a day.
|
|
145553
146984
|
*
|