@kangentic/protocol 0.1.0 → 0.2.0

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/index.cjs CHANGED
@@ -20,6 +20,8 @@ var __toCommonJS = (mod2) => __copyProps(__defProp({}, "__esModule", { value: tr
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ BOARD_TOOL_READ_NAMES: () => BOARD_TOOL_READ_NAMES,
24
+ BOARD_TOOL_WRITE_NAMES: () => BOARD_TOOL_WRITE_NAMES,
23
25
  CAPABILITY_VERBS: () => CAPABILITY_VERBS,
24
26
  CipherState: () => CipherState,
25
27
  ED25519_KEY_LENGTH: () => ED25519_KEY_LENGTH,
@@ -43,6 +45,7 @@ __export(index_exports, {
43
45
  decodeMessage: () => decodeMessage,
44
46
  decodePairingQrPayload: () => decodePairingQrPayload,
45
47
  deriveSecretstreamPair: () => deriveSecretstreamPair,
48
+ deriveSessionSlotId: () => deriveSessionSlotId,
46
49
  deriveShortAuthenticationString: () => deriveShortAuthenticationString,
47
50
  encodeMessage: () => encodeMessage,
48
51
  encodePairingQrPayload: () => encodePairingQrPayload,
@@ -52,8 +55,28 @@ __export(index_exports, {
52
55
  generateEd25519KeyPair: () => generateEd25519KeyPair,
53
56
  generateX25519KeyPair: () => generateX25519KeyPair,
54
57
  hexToBytes: () => hexToBytes,
58
+ isActivityReasonWire: () => isActivityReasonWire,
59
+ isActivityStateWire: () => isActivityStateWire,
60
+ isBoardToolReadName: () => isBoardToolReadName,
61
+ isBoardToolWriteName: () => isBoardToolWriteName,
62
+ isBridgeEvent: () => isBridgeEvent,
55
63
  isCapabilityVerb: () => isCapabilityVerb,
64
+ isJsonValue: () => isJsonValue,
65
+ isRecord: () => isRecord,
56
66
  isRosterEntryExpired: () => isRosterEntryExpired,
67
+ parseActivityEventPayload: () => parseActivityEventPayload,
68
+ parseBacklogItemWire: () => parseBacklogItemWire,
69
+ parseBoardColumnWire: () => parseBoardColumnWire,
70
+ parseBoardTaskWire: () => parseBoardTaskWire,
71
+ parseCapabilityRequestPayload: () => parseCapabilityRequestPayload,
72
+ parseDiffFileContentWire: () => parseDiffFileContentWire,
73
+ parseDiffFileListWire: () => parseDiffFileListWire,
74
+ parseReadBoardResponsePayload: () => parseReadBoardResponsePayload,
75
+ parseReadDiffResponsePayload: () => parseReadDiffResponsePayload,
76
+ parseReadStreamResponsePayload: () => parseReadStreamResponsePayload,
77
+ parseSessionEventWire: () => parseSessionEventWire,
78
+ parseSessionUsageWire: () => parseSessionUsageWire,
79
+ parseTranscriptEntriesWire: () => parseTranscriptEntriesWire,
57
80
  randomBytes: () => randomBytes4,
58
81
  rosterDeviceCapabilitySet: () => rosterDeviceCapabilitySet,
59
82
  signRosterEntry: () => signRosterEntry,
@@ -4236,6 +4259,14 @@ function deriveShortAuthenticationString(handshakeHash) {
4236
4259
  return { digits, emoji };
4237
4260
  }
4238
4261
 
4262
+ // src/crypto/slot.ts
4263
+ var SESSION_SLOT_LABEL = "kangentic-session-slot-v1";
4264
+ var SESSION_SLOT_LENGTH = 16;
4265
+ function deriveSessionSlotId(desktopStaticPublicKey, phoneStaticPublicKey) {
4266
+ const material = concatBytes4(desktopStaticPublicKey, phoneStaticPublicKey);
4267
+ return bytesToHex(deriveLabeledKey(material, SESSION_SLOT_LABEL, SESSION_SLOT_LENGTH));
4268
+ }
4269
+
4239
4270
  // src/capabilities/verbs.ts
4240
4271
  var CAPABILITY_VERBS = [
4241
4272
  "read-stream",
@@ -4243,7 +4274,10 @@ var CAPABILITY_VERBS = [
4243
4274
  "read-diff",
4244
4275
  "send-user-message",
4245
4276
  "move-task",
4246
- "answer-permission-prompt"
4277
+ "answer-permission-prompt",
4278
+ "interactive-terminal",
4279
+ "board-tool-read",
4280
+ "board-tool-write"
4247
4281
  ];
4248
4282
  function isCapabilityVerb(value) {
4249
4283
  return CAPABILITY_VERBS.includes(value);
@@ -4259,6 +4293,19 @@ function capabilitySetToArray(capabilities) {
4259
4293
  return Array.from(capabilities);
4260
4294
  }
4261
4295
 
4296
+ // src/wire/json-value.ts
4297
+ function isJsonValue(value) {
4298
+ if (value === null) return true;
4299
+ const valueType = typeof value;
4300
+ if (valueType === "boolean" || valueType === "number" || valueType === "string") return true;
4301
+ if (Array.isArray(value)) return value.every(isJsonValue);
4302
+ if (valueType === "object") return Object.values(value).every(isJsonValue);
4303
+ return false;
4304
+ }
4305
+ function isRecord(value) {
4306
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4307
+ }
4308
+
4262
4309
  // src/wire/framing.ts
4263
4310
  var MAX_FRAME_LENGTH = 1024 * 1024;
4264
4311
  function encodeMessage(message) {
@@ -4275,16 +4322,29 @@ function decodeMessage(bytes) {
4275
4322
  const parsed = JSON.parse(new TextDecoder().decode(bytes));
4276
4323
  return validateBridgeMessage(parsed);
4277
4324
  }
4278
- function isJsonValue(value) {
4279
- if (value === null) return true;
4280
- const t = typeof value;
4281
- if (t === "boolean" || t === "number" || t === "string") return true;
4282
- if (Array.isArray(value)) return value.every(isJsonValue);
4283
- if (t === "object") return Object.values(value).every(isJsonValue);
4284
- return false;
4285
- }
4286
- function isRecord(value) {
4287
- return typeof value === "object" && value !== null;
4325
+ function validateEvent(event) {
4326
+ if (typeof event.kind !== "string") throw new Error('event message missing a valid "kind"');
4327
+ if (!isJsonValue(event.payload)) throw new Error("event payload is not JSON-serializable");
4328
+ switch (event.kind) {
4329
+ case "transcript":
4330
+ case "activity":
4331
+ case "terminal": {
4332
+ if (typeof event.sessionId !== "string") throw new Error(`"${event.kind}" event is missing "sessionId"`);
4333
+ if (typeof event.taskId !== "string") throw new Error(`"${event.kind}" event is missing "taskId"`);
4334
+ return event;
4335
+ }
4336
+ case "board": {
4337
+ if (typeof event.projectId !== "string") throw new Error('"board" event is missing "projectId"');
4338
+ if (event.taskId !== void 0 && typeof event.taskId !== "string") throw new Error('"board" event has a non-string "taskId"');
4339
+ return event;
4340
+ }
4341
+ case "diff": {
4342
+ if (typeof event.taskId !== "string") throw new Error('"diff" event is missing "taskId"');
4343
+ return event;
4344
+ }
4345
+ default:
4346
+ throw new Error(`event message has an unknown event kind: ${event.kind}`);
4347
+ }
4288
4348
  }
4289
4349
  function validateBridgeMessage(value) {
4290
4350
  if (!isRecord(value) || typeof value.type !== "string") {
@@ -4313,18 +4373,8 @@ function validateBridgeMessage(value) {
4313
4373
  };
4314
4374
  }
4315
4375
  case "event": {
4316
- const event = value.event;
4317
- if (!isRecord(event) || typeof event.kind !== "string") {
4318
- throw new Error('event message missing a valid "event"');
4319
- }
4320
- if (typeof event.taskId !== "string") throw new Error('event is missing "taskId"');
4321
- if (!isJsonValue(event.payload)) throw new Error("event payload is not JSON-serializable");
4322
- if (event.kind === "transcript" || event.kind === "activity") {
4323
- if (typeof event.sessionId !== "string") throw new Error(`"${event.kind}" event is missing "sessionId"`);
4324
- } else if (event.kind !== "board") {
4325
- throw new Error(`event message has an unknown event kind: ${event.kind}`);
4326
- }
4327
- return { type: "event", event };
4376
+ if (!isRecord(value.event)) throw new Error('event message missing a valid "event"');
4377
+ return { type: "event", event: validateEvent(value.event) };
4328
4378
  }
4329
4379
  default:
4330
4380
  throw new Error(`Unknown bridge message type: ${value.type}`);
@@ -4351,6 +4401,360 @@ function unwrapSessionFrame(frame) {
4351
4401
  return { kind, payload: frame.subarray(1) };
4352
4402
  }
4353
4403
 
4404
+ // src/events/payloads.ts
4405
+ var DIFF_FILE_STATUSES = ["A", "M", "D", "R", "C", "U"];
4406
+ var TRANSCRIPT_SYSTEM_SUBTYPES = ["compaction", "command", "command_output", "session_boundary"];
4407
+ function requireString(record, field, context) {
4408
+ const value = record[field];
4409
+ if (typeof value !== "string") throw new Error(`${context} is missing a string "${field}"`);
4410
+ return value;
4411
+ }
4412
+ function requireNumber(record, field, context) {
4413
+ const value = record[field];
4414
+ if (typeof value !== "number") throw new Error(`${context} is missing a number "${field}"`);
4415
+ return value;
4416
+ }
4417
+ function requireBoolean(record, field, context) {
4418
+ const value = record[field];
4419
+ if (typeof value !== "boolean") throw new Error(`${context} is missing a boolean "${field}"`);
4420
+ return value;
4421
+ }
4422
+ function isTranscriptBlockWire(value) {
4423
+ if (!isRecord(value)) return false;
4424
+ if (value.type === "text" || value.type === "thinking") return typeof value.text === "string";
4425
+ if (value.type === "tool_use") return typeof value.id === "string" && typeof value.name === "string" && "input" in value;
4426
+ return false;
4427
+ }
4428
+ function isTranscriptEntryWire(value) {
4429
+ if (!isRecord(value)) return false;
4430
+ if (typeof value.uuid !== "string" || typeof value.ts !== "number") return false;
4431
+ switch (value.kind) {
4432
+ case "user":
4433
+ return typeof value.text === "string";
4434
+ case "assistant":
4435
+ return Array.isArray(value.blocks) && value.blocks.every(isTranscriptBlockWire);
4436
+ case "tool_result":
4437
+ return typeof value.toolUseId === "string" && typeof value.content === "string";
4438
+ case "system":
4439
+ return typeof value.subtype === "string" && TRANSCRIPT_SYSTEM_SUBTYPES.includes(value.subtype) && typeof value.text === "string";
4440
+ default:
4441
+ return false;
4442
+ }
4443
+ }
4444
+ function parseTranscriptEntriesWire(payload) {
4445
+ if (!Array.isArray(payload)) throw new Error("transcript payload must be an array of entries");
4446
+ return payload.map((entry, index) => {
4447
+ if (!isTranscriptEntryWire(entry)) throw new Error(`transcript payload entry ${index} is malformed`);
4448
+ return entry;
4449
+ });
4450
+ }
4451
+ function isActivityStateWire(value) {
4452
+ return value === "thinking" || value === "idle" || value === "permission";
4453
+ }
4454
+ function isActivityReasonWire(value) {
4455
+ if (!isRecord(value)) return false;
4456
+ switch (value.kind) {
4457
+ case "idle":
4458
+ case "permission":
4459
+ case "turn-active":
4460
+ return true;
4461
+ case "tool":
4462
+ return typeof value.pendingCount === "number" && (value.currentTool === null || typeof value.currentTool === "string");
4463
+ case "subagent":
4464
+ return typeof value.depth === "number";
4465
+ case "background-shell":
4466
+ return typeof value.count === "number" && Array.isArray(value.ids) && value.ids.every((id) => typeof id === "string");
4467
+ default:
4468
+ return false;
4469
+ }
4470
+ }
4471
+ function parseSessionUsageWire(payload) {
4472
+ if (!isRecord(payload)) throw new Error("usage payload must be an object");
4473
+ const contextWindow = payload.contextWindow;
4474
+ if (!isRecord(contextWindow)) throw new Error('usage payload is missing "contextWindow"');
4475
+ const cost = payload.cost;
4476
+ if (!isRecord(cost)) throw new Error('usage payload is missing "cost"');
4477
+ const model = payload.model;
4478
+ if (!isRecord(model)) throw new Error('usage payload is missing "model"');
4479
+ return {
4480
+ contextWindow: {
4481
+ usedPercentage: requireNumber(contextWindow, "usedPercentage", "usage contextWindow"),
4482
+ usedTokens: requireNumber(contextWindow, "usedTokens", "usage contextWindow"),
4483
+ cacheTokens: requireNumber(contextWindow, "cacheTokens", "usage contextWindow"),
4484
+ totalInputTokens: requireNumber(contextWindow, "totalInputTokens", "usage contextWindow"),
4485
+ totalOutputTokens: requireNumber(contextWindow, "totalOutputTokens", "usage contextWindow"),
4486
+ contextWindowSize: requireNumber(contextWindow, "contextWindowSize", "usage contextWindow")
4487
+ },
4488
+ cost: {
4489
+ totalCostUsd: requireNumber(cost, "totalCostUsd", "usage cost"),
4490
+ totalDurationMs: requireNumber(cost, "totalDurationMs", "usage cost")
4491
+ },
4492
+ ...typeof payload.toolCallCount === "number" ? { toolCallCount: payload.toolCallCount } : {},
4493
+ model: {
4494
+ id: requireString(model, "id", "usage model"),
4495
+ displayName: requireString(model, "displayName", "usage model"),
4496
+ ...typeof model.effort === "string" ? { effort: model.effort } : {}
4497
+ }
4498
+ };
4499
+ }
4500
+ function parseSessionEventWire(payload) {
4501
+ if (!isRecord(payload)) throw new Error("session event payload must be an object");
4502
+ return {
4503
+ ts: requireNumber(payload, "ts", "session event"),
4504
+ type: requireString(payload, "type", "session event"),
4505
+ ...typeof payload.tool === "string" ? { tool: payload.tool } : {},
4506
+ ...typeof payload.toolId === "string" ? { toolId: payload.toolId } : {},
4507
+ ...typeof payload.detail === "string" ? { detail: payload.detail } : {}
4508
+ };
4509
+ }
4510
+ function parseStringArray(value, context) {
4511
+ if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
4512
+ throw new Error(`${context} is not a string array`);
4513
+ }
4514
+ return value;
4515
+ }
4516
+ function nullableString(record, field) {
4517
+ const value = record[field];
4518
+ return typeof value === "string" ? value : null;
4519
+ }
4520
+ function nullableNumber(record, field) {
4521
+ const value = record[field];
4522
+ return typeof value === "number" ? value : null;
4523
+ }
4524
+ function parseBoardColumnWire(value) {
4525
+ if (!isRecord(value)) throw new Error("board column must be an object");
4526
+ return {
4527
+ id: requireString(value, "id", "board column"),
4528
+ name: requireString(value, "name", "board column"),
4529
+ description: nullableString(value, "description"),
4530
+ role: nullableString(value, "role"),
4531
+ position: requireNumber(value, "position", "board column"),
4532
+ color: requireString(value, "color", "board column"),
4533
+ icon: nullableString(value, "icon"),
4534
+ is_archived: requireBoolean(value, "is_archived", "board column"),
4535
+ is_ghost: requireBoolean(value, "is_ghost", "board column")
4536
+ };
4537
+ }
4538
+ function parseBoardTaskWire(value) {
4539
+ if (!isRecord(value)) throw new Error("board task must be an object");
4540
+ return {
4541
+ id: requireString(value, "id", "board task"),
4542
+ display_id: requireNumber(value, "display_id", "board task"),
4543
+ title: requireString(value, "title", "board task"),
4544
+ description: typeof value.description === "string" ? value.description : "",
4545
+ swimlane_id: requireString(value, "swimlane_id", "board task"),
4546
+ position: requireNumber(value, "position", "board task"),
4547
+ agent: nullableString(value, "agent"),
4548
+ session_id: nullableString(value, "session_id"),
4549
+ worktree_path: nullableString(value, "worktree_path"),
4550
+ branch_name: nullableString(value, "branch_name"),
4551
+ pr_number: nullableNumber(value, "pr_number"),
4552
+ pr_url: nullableString(value, "pr_url"),
4553
+ pr_state: nullableString(value, "pr_state"),
4554
+ base_branch: nullableString(value, "base_branch"),
4555
+ labels: Array.isArray(value.labels) ? parseStringArray(value.labels, "board task labels") : [],
4556
+ priority: typeof value.priority === "number" ? value.priority : 0,
4557
+ attachment_count: typeof value.attachment_count === "number" ? value.attachment_count : 0,
4558
+ archived_at: nullableString(value, "archived_at"),
4559
+ created_at: requireString(value, "created_at", "board task"),
4560
+ updated_at: requireString(value, "updated_at", "board task")
4561
+ };
4562
+ }
4563
+ function parseBacklogItemWire(value) {
4564
+ if (!isRecord(value)) throw new Error("backlog item must be an object");
4565
+ return {
4566
+ id: requireString(value, "id", "backlog item"),
4567
+ title: requireString(value, "title", "backlog item"),
4568
+ description: typeof value.description === "string" ? value.description : "",
4569
+ priority: typeof value.priority === "number" ? value.priority : 0,
4570
+ labels: Array.isArray(value.labels) ? parseStringArray(value.labels, "backlog item labels") : [],
4571
+ position: requireNumber(value, "position", "backlog item"),
4572
+ item_type: nullableString(value, "item_type"),
4573
+ external_url: nullableString(value, "external_url"),
4574
+ attachment_count: typeof value.attachment_count === "number" ? value.attachment_count : 0,
4575
+ created_at: requireString(value, "created_at", "backlog item"),
4576
+ updated_at: requireString(value, "updated_at", "backlog item")
4577
+ };
4578
+ }
4579
+ function parseDiffFileListWire(payload) {
4580
+ if (!isRecord(payload)) throw new Error("diff file list must be an object");
4581
+ if (!Array.isArray(payload.files)) throw new Error('diff file list is missing "files"');
4582
+ const files = payload.files.map((file, index) => {
4583
+ if (!isRecord(file)) throw new Error(`diff file entry ${index} must be an object`);
4584
+ const status = requireString(file, "status", `diff file entry ${index}`);
4585
+ if (!DIFF_FILE_STATUSES.includes(status)) throw new Error(`diff file entry ${index} has an invalid "status"`);
4586
+ return {
4587
+ path: requireString(file, "path", `diff file entry ${index}`),
4588
+ status,
4589
+ insertions: requireNumber(file, "insertions", `diff file entry ${index}`),
4590
+ deletions: requireNumber(file, "deletions", `diff file entry ${index}`),
4591
+ ...typeof file.oldPath === "string" ? { oldPath: file.oldPath } : {},
4592
+ binary: requireBoolean(file, "binary", `diff file entry ${index}`)
4593
+ };
4594
+ });
4595
+ return {
4596
+ files,
4597
+ totalInsertions: requireNumber(payload, "totalInsertions", "diff file list"),
4598
+ totalDeletions: requireNumber(payload, "totalDeletions", "diff file list")
4599
+ };
4600
+ }
4601
+ function parseDiffFileContentWire(payload) {
4602
+ if (!isRecord(payload)) throw new Error("diff file content must be an object");
4603
+ return {
4604
+ original: requireString(payload, "original", "diff file content"),
4605
+ modified: requireString(payload, "modified", "diff file content"),
4606
+ language: requireString(payload, "language", "diff file content")
4607
+ };
4608
+ }
4609
+
4610
+ // src/wire/payloads.ts
4611
+ function parseReadStreamResponsePayload(payload) {
4612
+ if (!isRecord(payload)) throw new Error("read-stream response must be an object");
4613
+ if (typeof payload.scrollback !== "string") throw new Error('read-stream response is missing "scrollback"');
4614
+ if (!isRecord(payload.activity)) throw new Error('read-stream response is missing "activity"');
4615
+ const state = payload.activity.state;
4616
+ const reason = payload.activity.reason;
4617
+ if (state !== null && !isActivityStateWire(state)) throw new Error('read-stream response has an invalid activity "state"');
4618
+ if (reason !== null && !isActivityReasonWire(reason)) throw new Error('read-stream response has an invalid activity "reason"');
4619
+ if (payload.awaitedPromptId !== null && typeof payload.awaitedPromptId !== "string") {
4620
+ throw new Error('read-stream response has an invalid "awaitedPromptId"');
4621
+ }
4622
+ return {
4623
+ scrollback: payload.scrollback,
4624
+ activity: { state: state ?? null, reason: reason ?? null },
4625
+ usage: payload.usage === null || payload.usage === void 0 ? null : parseSessionUsageWire(payload.usage),
4626
+ awaitedPromptId: payload.awaitedPromptId ?? null
4627
+ };
4628
+ }
4629
+ function parseReadStreamRequestPayload(payload) {
4630
+ if (!isRecord(payload)) throw new Error("read-stream payload must be an object");
4631
+ if (typeof payload.sessionId !== "string") throw new Error('read-stream payload missing "sessionId"');
4632
+ if (payload.action !== "subscribe" && payload.action !== "unsubscribe") {
4633
+ throw new Error('read-stream payload has an invalid "action"');
4634
+ }
4635
+ return { sessionId: payload.sessionId, action: payload.action };
4636
+ }
4637
+ function parseReadBoardResponsePayload(payload) {
4638
+ if (!isRecord(payload)) throw new Error("read-board response must be an object");
4639
+ if (Array.isArray(payload.projects)) {
4640
+ const projects = payload.projects.map((project, index) => {
4641
+ if (!isRecord(project) || typeof project.id !== "string" || typeof project.name !== "string") {
4642
+ throw new Error(`read-board project ${index} is malformed`);
4643
+ }
4644
+ return { id: project.id, name: project.name };
4645
+ });
4646
+ return { projects };
4647
+ }
4648
+ if (typeof payload.projectId !== "string") throw new Error('read-board response is missing "projectId"');
4649
+ if (!Array.isArray(payload.columns)) throw new Error('read-board response is missing "columns"');
4650
+ if (!Array.isArray(payload.tasks)) throw new Error('read-board response is missing "tasks"');
4651
+ if (!Array.isArray(payload.backlog)) throw new Error('read-board response is missing "backlog"');
4652
+ return {
4653
+ projectId: payload.projectId,
4654
+ columns: payload.columns.map((column) => parseBoardColumnWire(column)),
4655
+ tasks: payload.tasks.map((task) => parseBoardTaskWire(task)),
4656
+ backlog: payload.backlog.map((item) => parseBacklogItemWire(item))
4657
+ };
4658
+ }
4659
+ function parseReadBoardRequestPayload(payload) {
4660
+ if (!isRecord(payload)) throw new Error("read-board payload must be an object");
4661
+ if (payload.projectId !== void 0 && typeof payload.projectId !== "string") {
4662
+ throw new Error('read-board payload has a non-string "projectId"');
4663
+ }
4664
+ if (payload.action !== void 0 && payload.action !== "subscribe" && payload.action !== "unsubscribe") {
4665
+ throw new Error('read-board payload has an invalid "action"');
4666
+ }
4667
+ return { projectId: payload.projectId, action: payload.action };
4668
+ }
4669
+ function parseReadDiffResponsePayload(payload) {
4670
+ if (!isRecord(payload)) throw new Error("read-diff response must be an object");
4671
+ return "files" in payload ? parseDiffFileListWire(payload) : parseDiffFileContentWire(payload);
4672
+ }
4673
+ function parseReadDiffRequestPayload(payload) {
4674
+ if (!isRecord(payload)) throw new Error("read-diff payload must be an object");
4675
+ if (typeof payload.taskId !== "string") throw new Error('read-diff payload missing "taskId"');
4676
+ if (typeof payload.projectId !== "string") throw new Error('read-diff payload missing "projectId"');
4677
+ if (payload.filePath !== void 0 && typeof payload.filePath !== "string") {
4678
+ throw new Error('read-diff payload has a non-string "filePath"');
4679
+ }
4680
+ if (payload.scope !== void 0 && payload.scope !== "working" && payload.scope !== "staged" && payload.scope !== "branch") {
4681
+ throw new Error('read-diff payload has an invalid "scope"');
4682
+ }
4683
+ if (payload.action !== void 0 && payload.action !== "subscribe" && payload.action !== "unsubscribe") {
4684
+ throw new Error('read-diff payload has an invalid "action"');
4685
+ }
4686
+ return {
4687
+ taskId: payload.taskId,
4688
+ projectId: payload.projectId,
4689
+ filePath: payload.filePath,
4690
+ scope: payload.scope,
4691
+ action: payload.action
4692
+ };
4693
+ }
4694
+ function parseSendUserMessageRequestPayload(payload) {
4695
+ if (!isRecord(payload)) throw new Error("send-user-message payload must be an object");
4696
+ if (typeof payload.sessionId !== "string") throw new Error('send-user-message payload missing "sessionId"');
4697
+ if (typeof payload.text !== "string") throw new Error('send-user-message payload missing "text"');
4698
+ return { sessionId: payload.sessionId, text: payload.text };
4699
+ }
4700
+ function parseMoveTaskRequestPayload(payload) {
4701
+ if (!isRecord(payload)) throw new Error("move-task payload must be an object");
4702
+ if (typeof payload.taskId !== "string") throw new Error('move-task payload missing "taskId"');
4703
+ if (typeof payload.targetSwimlaneId !== "string") throw new Error('move-task payload missing "targetSwimlaneId"');
4704
+ if (typeof payload.targetPosition !== "number") throw new Error('move-task payload missing "targetPosition"');
4705
+ if (typeof payload.projectId !== "string") throw new Error('move-task payload missing "projectId"');
4706
+ return {
4707
+ taskId: payload.taskId,
4708
+ targetSwimlaneId: payload.targetSwimlaneId,
4709
+ targetPosition: payload.targetPosition,
4710
+ projectId: payload.projectId
4711
+ };
4712
+ }
4713
+ function parseAnswerPermissionPromptRequestPayload(payload) {
4714
+ if (!isRecord(payload)) throw new Error("answer-permission-prompt payload must be an object");
4715
+ if (typeof payload.sessionId !== "string") throw new Error('answer-permission-prompt payload missing "sessionId"');
4716
+ if (typeof payload.promptId !== "string") throw new Error('answer-permission-prompt payload missing "promptId"');
4717
+ if (typeof payload.keystrokes !== "string") throw new Error('answer-permission-prompt payload missing "keystrokes"');
4718
+ return { sessionId: payload.sessionId, promptId: payload.promptId, keystrokes: payload.keystrokes };
4719
+ }
4720
+ function parseInteractiveTerminalRequestPayload(payload) {
4721
+ if (!isRecord(payload)) throw new Error("interactive-terminal payload must be an object");
4722
+ if (typeof payload.sessionId !== "string") throw new Error('interactive-terminal payload missing "sessionId"');
4723
+ if (typeof payload.data !== "string") throw new Error('interactive-terminal payload missing "data"');
4724
+ return { sessionId: payload.sessionId, data: payload.data };
4725
+ }
4726
+ function parseBoardToolRequestPayload(payload) {
4727
+ if (!isRecord(payload)) throw new Error("board-tool payload must be an object");
4728
+ if (typeof payload.tool !== "string") throw new Error('board-tool payload missing "tool"');
4729
+ if (payload.params === void 0 || !isJsonValue(payload.params)) throw new Error('board-tool payload missing a JSON "params"');
4730
+ return { tool: payload.tool, params: payload.params };
4731
+ }
4732
+ function parseCapabilityRequestPayload(verb, payload) {
4733
+ switch (verb) {
4734
+ case "read-stream":
4735
+ return parseReadStreamRequestPayload(payload);
4736
+ case "read-board":
4737
+ return parseReadBoardRequestPayload(payload);
4738
+ case "read-diff":
4739
+ return parseReadDiffRequestPayload(payload);
4740
+ case "send-user-message":
4741
+ return parseSendUserMessageRequestPayload(payload);
4742
+ case "move-task":
4743
+ return parseMoveTaskRequestPayload(payload);
4744
+ case "answer-permission-prompt":
4745
+ return parseAnswerPermissionPromptRequestPayload(payload);
4746
+ case "interactive-terminal":
4747
+ return parseInteractiveTerminalRequestPayload(payload);
4748
+ case "board-tool-read":
4749
+ case "board-tool-write":
4750
+ return parseBoardToolRequestPayload(payload);
4751
+ default: {
4752
+ const exhaustiveCheck = verb;
4753
+ throw new Error(`Unknown capability verb: ${String(exhaustiveCheck)}`);
4754
+ }
4755
+ }
4756
+ }
4757
+
4354
4758
  // src/wire/base64url.ts
4355
4759
  var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
4356
4760
  var DECODE_MAP = {};
@@ -4512,8 +4916,108 @@ function rosterDeviceCapabilitySet(entry) {
4512
4916
  function capabilitySetToRosterCapabilities(capabilities) {
4513
4917
  return capabilitySetToArray(capabilities);
4514
4918
  }
4919
+
4920
+ // src/capabilities/board-tools.ts
4921
+ var BOARD_TOOL_READ_NAMES = [
4922
+ "search_tasks",
4923
+ "find_task",
4924
+ "get_current_task",
4925
+ "get_task_stats",
4926
+ "get_usage_stats",
4927
+ "board_summary",
4928
+ "list_sessions",
4929
+ "get_session_history",
4930
+ "get_column_detail",
4931
+ "get_handoff_context",
4932
+ "get_transcript",
4933
+ "get_session_files",
4934
+ "get_session_events"
4935
+ ];
4936
+ var BOARD_TOOL_WRITE_NAMES = [
4937
+ "create_task",
4938
+ "update_task",
4939
+ "delete_task",
4940
+ "link_pr",
4941
+ "remove_attachment",
4942
+ "update_column",
4943
+ "create_backlog_task",
4944
+ "promote_backlog",
4945
+ "update_backlog_item",
4946
+ "delete_backlog_item"
4947
+ ];
4948
+ function isBoardToolReadName(value) {
4949
+ return BOARD_TOOL_READ_NAMES.includes(value);
4950
+ }
4951
+ function isBoardToolWriteName(value) {
4952
+ return BOARD_TOOL_WRITE_NAMES.includes(value);
4953
+ }
4954
+
4955
+ // src/events/event.ts
4956
+ var BOARD_CHANGES = ["task-created", "task-updated", "task-deleted", "swimlane-updated", "backlog-changed"];
4957
+ function parseActivityEventPayload(payload) {
4958
+ if (!isRecord(payload)) throw new Error("activity payload must be an object");
4959
+ switch (payload.type) {
4960
+ case "activity": {
4961
+ if (!isActivityStateWire(payload.state)) throw new Error('activity payload has an invalid "state"');
4962
+ if (!isActivityReasonWire(payload.reason)) throw new Error('activity payload has an invalid "reason"');
4963
+ return { type: "activity", state: payload.state, reason: payload.reason };
4964
+ }
4965
+ case "usage":
4966
+ return { type: "usage", usage: parseSessionUsageWire(payload.usage) };
4967
+ case "event":
4968
+ return { type: "event", event: parseSessionEventWire(payload.event) };
4969
+ case "permission": {
4970
+ if (typeof payload.promptId !== "string") throw new Error('permission payload is missing "promptId"');
4971
+ if (typeof payload.pending !== "boolean") throw new Error('permission payload is missing "pending"');
4972
+ return { type: "permission", promptId: payload.promptId, pending: payload.pending };
4973
+ }
4974
+ default:
4975
+ throw new Error('activity payload has an unknown "type"');
4976
+ }
4977
+ }
4978
+ function isBridgeEvent(value) {
4979
+ if (!isRecord(value)) return false;
4980
+ switch (value.kind) {
4981
+ case "transcript": {
4982
+ if (typeof value.sessionId !== "string" || typeof value.taskId !== "string") return false;
4983
+ try {
4984
+ parseTranscriptEntriesWire(value.payload);
4985
+ return true;
4986
+ } catch {
4987
+ return false;
4988
+ }
4989
+ }
4990
+ case "activity": {
4991
+ if (typeof value.sessionId !== "string" || typeof value.taskId !== "string") return false;
4992
+ try {
4993
+ parseActivityEventPayload(value.payload);
4994
+ return true;
4995
+ } catch {
4996
+ return false;
4997
+ }
4998
+ }
4999
+ case "terminal": {
5000
+ if (typeof value.sessionId !== "string" || typeof value.taskId !== "string") return false;
5001
+ return isRecord(value.payload) && typeof value.payload.data === "string";
5002
+ }
5003
+ case "board": {
5004
+ if (typeof value.projectId !== "string") return false;
5005
+ if (value.taskId !== void 0 && typeof value.taskId !== "string") return false;
5006
+ if (!isRecord(value.payload)) return false;
5007
+ const change = value.payload.change;
5008
+ if (typeof change !== "string" || !BOARD_CHANGES.includes(change)) return false;
5009
+ return Array.isArray(value.payload.ids) && value.payload.ids.every((id) => typeof id === "string");
5010
+ }
5011
+ case "diff":
5012
+ return typeof value.taskId === "string" && value.payload === null;
5013
+ default:
5014
+ return false;
5015
+ }
5016
+ }
4515
5017
  // Annotate the CommonJS export names for ESM import in node:
4516
5018
  0 && (module.exports = {
5019
+ BOARD_TOOL_READ_NAMES,
5020
+ BOARD_TOOL_WRITE_NAMES,
4517
5021
  CAPABILITY_VERBS,
4518
5022
  CipherState,
4519
5023
  ED25519_KEY_LENGTH,
@@ -4537,6 +5041,7 @@ function capabilitySetToRosterCapabilities(capabilities) {
4537
5041
  decodeMessage,
4538
5042
  decodePairingQrPayload,
4539
5043
  deriveSecretstreamPair,
5044
+ deriveSessionSlotId,
4540
5045
  deriveShortAuthenticationString,
4541
5046
  encodeMessage,
4542
5047
  encodePairingQrPayload,
@@ -4546,8 +5051,28 @@ function capabilitySetToRosterCapabilities(capabilities) {
4546
5051
  generateEd25519KeyPair,
4547
5052
  generateX25519KeyPair,
4548
5053
  hexToBytes,
5054
+ isActivityReasonWire,
5055
+ isActivityStateWire,
5056
+ isBoardToolReadName,
5057
+ isBoardToolWriteName,
5058
+ isBridgeEvent,
4549
5059
  isCapabilityVerb,
5060
+ isJsonValue,
5061
+ isRecord,
4550
5062
  isRosterEntryExpired,
5063
+ parseActivityEventPayload,
5064
+ parseBacklogItemWire,
5065
+ parseBoardColumnWire,
5066
+ parseBoardTaskWire,
5067
+ parseCapabilityRequestPayload,
5068
+ parseDiffFileContentWire,
5069
+ parseDiffFileListWire,
5070
+ parseReadBoardResponsePayload,
5071
+ parseReadDiffResponsePayload,
5072
+ parseReadStreamResponsePayload,
5073
+ parseSessionEventWire,
5074
+ parseSessionUsageWire,
5075
+ parseTranscriptEntriesWire,
4551
5076
  randomBytes,
4552
5077
  rosterDeviceCapabilitySet,
4553
5078
  signRosterEntry,