@kangentic/protocol 0.1.1 → 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/LICENSE +659 -4
- package/dist/capabilities/board-tools.d.ts +22 -0
- package/dist/capabilities/verbs.d.ts +1 -1
- package/dist/crypto/slot.d.ts +1 -0
- package/dist/events/event.d.ts +76 -16
- package/dist/events/payloads.d.ts +220 -0
- package/dist/index.cjs +548 -23
- package/dist/index.d.ts +6 -1
- package/dist/index.mjs +525 -23
- package/dist/wire/json-value.d.ts +9 -0
- package/dist/wire/payloads.d.ts +139 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -7,11 +7,16 @@ export { createKKHandshake, type KKHandshakeOptions } from './crypto/noise/kk';
|
|
|
7
7
|
export { createPairingInitiatorHandshake, createPairingResponderHandshake, type PairingInitiatorOptions, type PairingResponderOptions, } from './crypto/pairing-handshake';
|
|
8
8
|
export { SecretstreamState, deriveSecretstreamPair, FrameTag, type SecretstreamDirectionPair, } from './crypto/secretstream';
|
|
9
9
|
export { deriveShortAuthenticationString, type ShortAuthenticationString } from './crypto/sas';
|
|
10
|
+
export { deriveSessionSlotId } from './crypto/slot';
|
|
10
11
|
export type { JsonValue, BridgeMessage, HeartbeatMessage, CapabilityRequestMessage, CapabilityResponseMessage, EventMessage } from './wire/messages';
|
|
11
12
|
export { encodeMessage, decodeMessage, MAX_FRAME_LENGTH } from './wire/framing';
|
|
13
|
+
export { isJsonValue, isRecord } from './wire/json-value';
|
|
12
14
|
export { SessionFrameKind, wrapSessionFrame, unwrapSessionFrame } from './wire/session-frame';
|
|
15
|
+
export { parseCapabilityRequestPayload, parseReadStreamResponsePayload, parseReadBoardResponsePayload, parseReadDiffResponsePayload, type CapabilityRequestPayloadMap, type CapabilityResponsePayloadMap, type ReadStreamRequestPayload, type ReadStreamResponsePayload, type ReadBoardRequestPayload, type ReadBoardProjectSummary, type ReadBoardProjectListResponsePayload, type ReadBoardSnapshotResponsePayload, type ReadBoardResponsePayload, type ReadDiffScope, type ReadDiffRequestPayload, type ReadDiffResponsePayload, type SendUserMessageRequestPayload, type SendUserMessageResponsePayload, type MoveTaskRequestPayload, type MoveTaskResponsePayload, type AnswerPermissionPromptRequestPayload, type AnswerPermissionPromptResponsePayload, type InteractiveTerminalRequestPayload, type InteractiveTerminalResponsePayload, type BoardToolRequestPayload, type BoardToolResponsePayload, } from './wire/payloads';
|
|
13
16
|
export { PAIRING_URI_SCHEME, encodePairingQrPayload, decodePairingQrPayload, type PairingQrPayload, } from './pairing/qr';
|
|
14
17
|
export { signRosterEntry, verifyRosterEntry, encodeRosterEntryForSigning, isRosterEntryExpired, findRosterDevice, rosterDeviceCapabilitySet, capabilitySetToRosterCapabilities, type RosterDeviceEntry, type DeviceRoster, } from './roster/roster';
|
|
15
18
|
export { CAPABILITY_VERBS, isCapabilityVerb, capabilitySetFromArray, capabilitySetToArray, type CapabilityVerb, type CapabilitySet, } from './capabilities/verbs';
|
|
16
|
-
export
|
|
19
|
+
export { BOARD_TOOL_READ_NAMES, BOARD_TOOL_WRITE_NAMES, isBoardToolReadName, isBoardToolWriteName, type BoardToolReadName, type BoardToolWriteName, type BoardToolName, } from './capabilities/board-tools';
|
|
20
|
+
export { isBridgeEvent, parseActivityEventPayload, type BridgeEvent, type TranscriptEvent, type ActivityEvent, type ActivityEventPayload, type TerminalEvent, type BoardEvent, type BoardEventPayload, type DiffEvent, } from './events/event';
|
|
21
|
+
export { parseTranscriptEntriesWire, isActivityStateWire, isActivityReasonWire, parseSessionUsageWire, parseSessionEventWire, parseBoardColumnWire, parseBoardTaskWire, parseBacklogItemWire, parseDiffFileListWire, parseDiffFileContentWire, type TranscriptBlockWire, type TranscriptEntryWire, type TranscriptTurnUsageWire, type TranscriptSystemSubtypeWire, type ActivityStateWire, type ActivityReasonWire, type SessionUsageWire, type SessionEventWire, type BoardColumnWire, type BoardTaskWire, type BacklogItemWire, type DiffFileStatusWire, type DiffFileWire, type DiffFileListWire, type DiffFileContentWire, } from './events/payloads';
|
|
17
22
|
export type { Transport, TransportState, Unsubscribe } from './transport/transport';
|
package/dist/index.mjs
CHANGED
|
@@ -4170,6 +4170,14 @@ function deriveShortAuthenticationString(handshakeHash) {
|
|
|
4170
4170
|
return { digits, emoji };
|
|
4171
4171
|
}
|
|
4172
4172
|
|
|
4173
|
+
// src/crypto/slot.ts
|
|
4174
|
+
var SESSION_SLOT_LABEL = "kangentic-session-slot-v1";
|
|
4175
|
+
var SESSION_SLOT_LENGTH = 16;
|
|
4176
|
+
function deriveSessionSlotId(desktopStaticPublicKey, phoneStaticPublicKey) {
|
|
4177
|
+
const material = concatBytes4(desktopStaticPublicKey, phoneStaticPublicKey);
|
|
4178
|
+
return bytesToHex(deriveLabeledKey(material, SESSION_SLOT_LABEL, SESSION_SLOT_LENGTH));
|
|
4179
|
+
}
|
|
4180
|
+
|
|
4173
4181
|
// src/capabilities/verbs.ts
|
|
4174
4182
|
var CAPABILITY_VERBS = [
|
|
4175
4183
|
"read-stream",
|
|
@@ -4177,7 +4185,10 @@ var CAPABILITY_VERBS = [
|
|
|
4177
4185
|
"read-diff",
|
|
4178
4186
|
"send-user-message",
|
|
4179
4187
|
"move-task",
|
|
4180
|
-
"answer-permission-prompt"
|
|
4188
|
+
"answer-permission-prompt",
|
|
4189
|
+
"interactive-terminal",
|
|
4190
|
+
"board-tool-read",
|
|
4191
|
+
"board-tool-write"
|
|
4181
4192
|
];
|
|
4182
4193
|
function isCapabilityVerb(value) {
|
|
4183
4194
|
return CAPABILITY_VERBS.includes(value);
|
|
@@ -4193,6 +4204,19 @@ function capabilitySetToArray(capabilities) {
|
|
|
4193
4204
|
return Array.from(capabilities);
|
|
4194
4205
|
}
|
|
4195
4206
|
|
|
4207
|
+
// src/wire/json-value.ts
|
|
4208
|
+
function isJsonValue(value) {
|
|
4209
|
+
if (value === null) return true;
|
|
4210
|
+
const valueType = typeof value;
|
|
4211
|
+
if (valueType === "boolean" || valueType === "number" || valueType === "string") return true;
|
|
4212
|
+
if (Array.isArray(value)) return value.every(isJsonValue);
|
|
4213
|
+
if (valueType === "object") return Object.values(value).every(isJsonValue);
|
|
4214
|
+
return false;
|
|
4215
|
+
}
|
|
4216
|
+
function isRecord(value) {
|
|
4217
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4218
|
+
}
|
|
4219
|
+
|
|
4196
4220
|
// src/wire/framing.ts
|
|
4197
4221
|
var MAX_FRAME_LENGTH = 1024 * 1024;
|
|
4198
4222
|
function encodeMessage(message) {
|
|
@@ -4209,16 +4233,29 @@ function decodeMessage(bytes) {
|
|
|
4209
4233
|
const parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
4210
4234
|
return validateBridgeMessage(parsed);
|
|
4211
4235
|
}
|
|
4212
|
-
function
|
|
4213
|
-
if (
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
}
|
|
4220
|
-
|
|
4221
|
-
|
|
4236
|
+
function validateEvent(event) {
|
|
4237
|
+
if (typeof event.kind !== "string") throw new Error('event message missing a valid "kind"');
|
|
4238
|
+
if (!isJsonValue(event.payload)) throw new Error("event payload is not JSON-serializable");
|
|
4239
|
+
switch (event.kind) {
|
|
4240
|
+
case "transcript":
|
|
4241
|
+
case "activity":
|
|
4242
|
+
case "terminal": {
|
|
4243
|
+
if (typeof event.sessionId !== "string") throw new Error(`"${event.kind}" event is missing "sessionId"`);
|
|
4244
|
+
if (typeof event.taskId !== "string") throw new Error(`"${event.kind}" event is missing "taskId"`);
|
|
4245
|
+
return event;
|
|
4246
|
+
}
|
|
4247
|
+
case "board": {
|
|
4248
|
+
if (typeof event.projectId !== "string") throw new Error('"board" event is missing "projectId"');
|
|
4249
|
+
if (event.taskId !== void 0 && typeof event.taskId !== "string") throw new Error('"board" event has a non-string "taskId"');
|
|
4250
|
+
return event;
|
|
4251
|
+
}
|
|
4252
|
+
case "diff": {
|
|
4253
|
+
if (typeof event.taskId !== "string") throw new Error('"diff" event is missing "taskId"');
|
|
4254
|
+
return event;
|
|
4255
|
+
}
|
|
4256
|
+
default:
|
|
4257
|
+
throw new Error(`event message has an unknown event kind: ${event.kind}`);
|
|
4258
|
+
}
|
|
4222
4259
|
}
|
|
4223
4260
|
function validateBridgeMessage(value) {
|
|
4224
4261
|
if (!isRecord(value) || typeof value.type !== "string") {
|
|
@@ -4247,18 +4284,8 @@ function validateBridgeMessage(value) {
|
|
|
4247
4284
|
};
|
|
4248
4285
|
}
|
|
4249
4286
|
case "event": {
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
throw new Error('event message missing a valid "event"');
|
|
4253
|
-
}
|
|
4254
|
-
if (typeof event.taskId !== "string") throw new Error('event is missing "taskId"');
|
|
4255
|
-
if (!isJsonValue(event.payload)) throw new Error("event payload is not JSON-serializable");
|
|
4256
|
-
if (event.kind === "transcript" || event.kind === "activity") {
|
|
4257
|
-
if (typeof event.sessionId !== "string") throw new Error(`"${event.kind}" event is missing "sessionId"`);
|
|
4258
|
-
} else if (event.kind !== "board") {
|
|
4259
|
-
throw new Error(`event message has an unknown event kind: ${event.kind}`);
|
|
4260
|
-
}
|
|
4261
|
-
return { type: "event", event };
|
|
4287
|
+
if (!isRecord(value.event)) throw new Error('event message missing a valid "event"');
|
|
4288
|
+
return { type: "event", event: validateEvent(value.event) };
|
|
4262
4289
|
}
|
|
4263
4290
|
default:
|
|
4264
4291
|
throw new Error(`Unknown bridge message type: ${value.type}`);
|
|
@@ -4285,6 +4312,360 @@ function unwrapSessionFrame(frame) {
|
|
|
4285
4312
|
return { kind, payload: frame.subarray(1) };
|
|
4286
4313
|
}
|
|
4287
4314
|
|
|
4315
|
+
// src/events/payloads.ts
|
|
4316
|
+
var DIFF_FILE_STATUSES = ["A", "M", "D", "R", "C", "U"];
|
|
4317
|
+
var TRANSCRIPT_SYSTEM_SUBTYPES = ["compaction", "command", "command_output", "session_boundary"];
|
|
4318
|
+
function requireString(record, field, context) {
|
|
4319
|
+
const value = record[field];
|
|
4320
|
+
if (typeof value !== "string") throw new Error(`${context} is missing a string "${field}"`);
|
|
4321
|
+
return value;
|
|
4322
|
+
}
|
|
4323
|
+
function requireNumber(record, field, context) {
|
|
4324
|
+
const value = record[field];
|
|
4325
|
+
if (typeof value !== "number") throw new Error(`${context} is missing a number "${field}"`);
|
|
4326
|
+
return value;
|
|
4327
|
+
}
|
|
4328
|
+
function requireBoolean(record, field, context) {
|
|
4329
|
+
const value = record[field];
|
|
4330
|
+
if (typeof value !== "boolean") throw new Error(`${context} is missing a boolean "${field}"`);
|
|
4331
|
+
return value;
|
|
4332
|
+
}
|
|
4333
|
+
function isTranscriptBlockWire(value) {
|
|
4334
|
+
if (!isRecord(value)) return false;
|
|
4335
|
+
if (value.type === "text" || value.type === "thinking") return typeof value.text === "string";
|
|
4336
|
+
if (value.type === "tool_use") return typeof value.id === "string" && typeof value.name === "string" && "input" in value;
|
|
4337
|
+
return false;
|
|
4338
|
+
}
|
|
4339
|
+
function isTranscriptEntryWire(value) {
|
|
4340
|
+
if (!isRecord(value)) return false;
|
|
4341
|
+
if (typeof value.uuid !== "string" || typeof value.ts !== "number") return false;
|
|
4342
|
+
switch (value.kind) {
|
|
4343
|
+
case "user":
|
|
4344
|
+
return typeof value.text === "string";
|
|
4345
|
+
case "assistant":
|
|
4346
|
+
return Array.isArray(value.blocks) && value.blocks.every(isTranscriptBlockWire);
|
|
4347
|
+
case "tool_result":
|
|
4348
|
+
return typeof value.toolUseId === "string" && typeof value.content === "string";
|
|
4349
|
+
case "system":
|
|
4350
|
+
return typeof value.subtype === "string" && TRANSCRIPT_SYSTEM_SUBTYPES.includes(value.subtype) && typeof value.text === "string";
|
|
4351
|
+
default:
|
|
4352
|
+
return false;
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4355
|
+
function parseTranscriptEntriesWire(payload) {
|
|
4356
|
+
if (!Array.isArray(payload)) throw new Error("transcript payload must be an array of entries");
|
|
4357
|
+
return payload.map((entry, index) => {
|
|
4358
|
+
if (!isTranscriptEntryWire(entry)) throw new Error(`transcript payload entry ${index} is malformed`);
|
|
4359
|
+
return entry;
|
|
4360
|
+
});
|
|
4361
|
+
}
|
|
4362
|
+
function isActivityStateWire(value) {
|
|
4363
|
+
return value === "thinking" || value === "idle" || value === "permission";
|
|
4364
|
+
}
|
|
4365
|
+
function isActivityReasonWire(value) {
|
|
4366
|
+
if (!isRecord(value)) return false;
|
|
4367
|
+
switch (value.kind) {
|
|
4368
|
+
case "idle":
|
|
4369
|
+
case "permission":
|
|
4370
|
+
case "turn-active":
|
|
4371
|
+
return true;
|
|
4372
|
+
case "tool":
|
|
4373
|
+
return typeof value.pendingCount === "number" && (value.currentTool === null || typeof value.currentTool === "string");
|
|
4374
|
+
case "subagent":
|
|
4375
|
+
return typeof value.depth === "number";
|
|
4376
|
+
case "background-shell":
|
|
4377
|
+
return typeof value.count === "number" && Array.isArray(value.ids) && value.ids.every((id) => typeof id === "string");
|
|
4378
|
+
default:
|
|
4379
|
+
return false;
|
|
4380
|
+
}
|
|
4381
|
+
}
|
|
4382
|
+
function parseSessionUsageWire(payload) {
|
|
4383
|
+
if (!isRecord(payload)) throw new Error("usage payload must be an object");
|
|
4384
|
+
const contextWindow = payload.contextWindow;
|
|
4385
|
+
if (!isRecord(contextWindow)) throw new Error('usage payload is missing "contextWindow"');
|
|
4386
|
+
const cost = payload.cost;
|
|
4387
|
+
if (!isRecord(cost)) throw new Error('usage payload is missing "cost"');
|
|
4388
|
+
const model = payload.model;
|
|
4389
|
+
if (!isRecord(model)) throw new Error('usage payload is missing "model"');
|
|
4390
|
+
return {
|
|
4391
|
+
contextWindow: {
|
|
4392
|
+
usedPercentage: requireNumber(contextWindow, "usedPercentage", "usage contextWindow"),
|
|
4393
|
+
usedTokens: requireNumber(contextWindow, "usedTokens", "usage contextWindow"),
|
|
4394
|
+
cacheTokens: requireNumber(contextWindow, "cacheTokens", "usage contextWindow"),
|
|
4395
|
+
totalInputTokens: requireNumber(contextWindow, "totalInputTokens", "usage contextWindow"),
|
|
4396
|
+
totalOutputTokens: requireNumber(contextWindow, "totalOutputTokens", "usage contextWindow"),
|
|
4397
|
+
contextWindowSize: requireNumber(contextWindow, "contextWindowSize", "usage contextWindow")
|
|
4398
|
+
},
|
|
4399
|
+
cost: {
|
|
4400
|
+
totalCostUsd: requireNumber(cost, "totalCostUsd", "usage cost"),
|
|
4401
|
+
totalDurationMs: requireNumber(cost, "totalDurationMs", "usage cost")
|
|
4402
|
+
},
|
|
4403
|
+
...typeof payload.toolCallCount === "number" ? { toolCallCount: payload.toolCallCount } : {},
|
|
4404
|
+
model: {
|
|
4405
|
+
id: requireString(model, "id", "usage model"),
|
|
4406
|
+
displayName: requireString(model, "displayName", "usage model"),
|
|
4407
|
+
...typeof model.effort === "string" ? { effort: model.effort } : {}
|
|
4408
|
+
}
|
|
4409
|
+
};
|
|
4410
|
+
}
|
|
4411
|
+
function parseSessionEventWire(payload) {
|
|
4412
|
+
if (!isRecord(payload)) throw new Error("session event payload must be an object");
|
|
4413
|
+
return {
|
|
4414
|
+
ts: requireNumber(payload, "ts", "session event"),
|
|
4415
|
+
type: requireString(payload, "type", "session event"),
|
|
4416
|
+
...typeof payload.tool === "string" ? { tool: payload.tool } : {},
|
|
4417
|
+
...typeof payload.toolId === "string" ? { toolId: payload.toolId } : {},
|
|
4418
|
+
...typeof payload.detail === "string" ? { detail: payload.detail } : {}
|
|
4419
|
+
};
|
|
4420
|
+
}
|
|
4421
|
+
function parseStringArray(value, context) {
|
|
4422
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
|
|
4423
|
+
throw new Error(`${context} is not a string array`);
|
|
4424
|
+
}
|
|
4425
|
+
return value;
|
|
4426
|
+
}
|
|
4427
|
+
function nullableString(record, field) {
|
|
4428
|
+
const value = record[field];
|
|
4429
|
+
return typeof value === "string" ? value : null;
|
|
4430
|
+
}
|
|
4431
|
+
function nullableNumber(record, field) {
|
|
4432
|
+
const value = record[field];
|
|
4433
|
+
return typeof value === "number" ? value : null;
|
|
4434
|
+
}
|
|
4435
|
+
function parseBoardColumnWire(value) {
|
|
4436
|
+
if (!isRecord(value)) throw new Error("board column must be an object");
|
|
4437
|
+
return {
|
|
4438
|
+
id: requireString(value, "id", "board column"),
|
|
4439
|
+
name: requireString(value, "name", "board column"),
|
|
4440
|
+
description: nullableString(value, "description"),
|
|
4441
|
+
role: nullableString(value, "role"),
|
|
4442
|
+
position: requireNumber(value, "position", "board column"),
|
|
4443
|
+
color: requireString(value, "color", "board column"),
|
|
4444
|
+
icon: nullableString(value, "icon"),
|
|
4445
|
+
is_archived: requireBoolean(value, "is_archived", "board column"),
|
|
4446
|
+
is_ghost: requireBoolean(value, "is_ghost", "board column")
|
|
4447
|
+
};
|
|
4448
|
+
}
|
|
4449
|
+
function parseBoardTaskWire(value) {
|
|
4450
|
+
if (!isRecord(value)) throw new Error("board task must be an object");
|
|
4451
|
+
return {
|
|
4452
|
+
id: requireString(value, "id", "board task"),
|
|
4453
|
+
display_id: requireNumber(value, "display_id", "board task"),
|
|
4454
|
+
title: requireString(value, "title", "board task"),
|
|
4455
|
+
description: typeof value.description === "string" ? value.description : "",
|
|
4456
|
+
swimlane_id: requireString(value, "swimlane_id", "board task"),
|
|
4457
|
+
position: requireNumber(value, "position", "board task"),
|
|
4458
|
+
agent: nullableString(value, "agent"),
|
|
4459
|
+
session_id: nullableString(value, "session_id"),
|
|
4460
|
+
worktree_path: nullableString(value, "worktree_path"),
|
|
4461
|
+
branch_name: nullableString(value, "branch_name"),
|
|
4462
|
+
pr_number: nullableNumber(value, "pr_number"),
|
|
4463
|
+
pr_url: nullableString(value, "pr_url"),
|
|
4464
|
+
pr_state: nullableString(value, "pr_state"),
|
|
4465
|
+
base_branch: nullableString(value, "base_branch"),
|
|
4466
|
+
labels: Array.isArray(value.labels) ? parseStringArray(value.labels, "board task labels") : [],
|
|
4467
|
+
priority: typeof value.priority === "number" ? value.priority : 0,
|
|
4468
|
+
attachment_count: typeof value.attachment_count === "number" ? value.attachment_count : 0,
|
|
4469
|
+
archived_at: nullableString(value, "archived_at"),
|
|
4470
|
+
created_at: requireString(value, "created_at", "board task"),
|
|
4471
|
+
updated_at: requireString(value, "updated_at", "board task")
|
|
4472
|
+
};
|
|
4473
|
+
}
|
|
4474
|
+
function parseBacklogItemWire(value) {
|
|
4475
|
+
if (!isRecord(value)) throw new Error("backlog item must be an object");
|
|
4476
|
+
return {
|
|
4477
|
+
id: requireString(value, "id", "backlog item"),
|
|
4478
|
+
title: requireString(value, "title", "backlog item"),
|
|
4479
|
+
description: typeof value.description === "string" ? value.description : "",
|
|
4480
|
+
priority: typeof value.priority === "number" ? value.priority : 0,
|
|
4481
|
+
labels: Array.isArray(value.labels) ? parseStringArray(value.labels, "backlog item labels") : [],
|
|
4482
|
+
position: requireNumber(value, "position", "backlog item"),
|
|
4483
|
+
item_type: nullableString(value, "item_type"),
|
|
4484
|
+
external_url: nullableString(value, "external_url"),
|
|
4485
|
+
attachment_count: typeof value.attachment_count === "number" ? value.attachment_count : 0,
|
|
4486
|
+
created_at: requireString(value, "created_at", "backlog item"),
|
|
4487
|
+
updated_at: requireString(value, "updated_at", "backlog item")
|
|
4488
|
+
};
|
|
4489
|
+
}
|
|
4490
|
+
function parseDiffFileListWire(payload) {
|
|
4491
|
+
if (!isRecord(payload)) throw new Error("diff file list must be an object");
|
|
4492
|
+
if (!Array.isArray(payload.files)) throw new Error('diff file list is missing "files"');
|
|
4493
|
+
const files = payload.files.map((file, index) => {
|
|
4494
|
+
if (!isRecord(file)) throw new Error(`diff file entry ${index} must be an object`);
|
|
4495
|
+
const status = requireString(file, "status", `diff file entry ${index}`);
|
|
4496
|
+
if (!DIFF_FILE_STATUSES.includes(status)) throw new Error(`diff file entry ${index} has an invalid "status"`);
|
|
4497
|
+
return {
|
|
4498
|
+
path: requireString(file, "path", `diff file entry ${index}`),
|
|
4499
|
+
status,
|
|
4500
|
+
insertions: requireNumber(file, "insertions", `diff file entry ${index}`),
|
|
4501
|
+
deletions: requireNumber(file, "deletions", `diff file entry ${index}`),
|
|
4502
|
+
...typeof file.oldPath === "string" ? { oldPath: file.oldPath } : {},
|
|
4503
|
+
binary: requireBoolean(file, "binary", `diff file entry ${index}`)
|
|
4504
|
+
};
|
|
4505
|
+
});
|
|
4506
|
+
return {
|
|
4507
|
+
files,
|
|
4508
|
+
totalInsertions: requireNumber(payload, "totalInsertions", "diff file list"),
|
|
4509
|
+
totalDeletions: requireNumber(payload, "totalDeletions", "diff file list")
|
|
4510
|
+
};
|
|
4511
|
+
}
|
|
4512
|
+
function parseDiffFileContentWire(payload) {
|
|
4513
|
+
if (!isRecord(payload)) throw new Error("diff file content must be an object");
|
|
4514
|
+
return {
|
|
4515
|
+
original: requireString(payload, "original", "diff file content"),
|
|
4516
|
+
modified: requireString(payload, "modified", "diff file content"),
|
|
4517
|
+
language: requireString(payload, "language", "diff file content")
|
|
4518
|
+
};
|
|
4519
|
+
}
|
|
4520
|
+
|
|
4521
|
+
// src/wire/payloads.ts
|
|
4522
|
+
function parseReadStreamResponsePayload(payload) {
|
|
4523
|
+
if (!isRecord(payload)) throw new Error("read-stream response must be an object");
|
|
4524
|
+
if (typeof payload.scrollback !== "string") throw new Error('read-stream response is missing "scrollback"');
|
|
4525
|
+
if (!isRecord(payload.activity)) throw new Error('read-stream response is missing "activity"');
|
|
4526
|
+
const state = payload.activity.state;
|
|
4527
|
+
const reason = payload.activity.reason;
|
|
4528
|
+
if (state !== null && !isActivityStateWire(state)) throw new Error('read-stream response has an invalid activity "state"');
|
|
4529
|
+
if (reason !== null && !isActivityReasonWire(reason)) throw new Error('read-stream response has an invalid activity "reason"');
|
|
4530
|
+
if (payload.awaitedPromptId !== null && typeof payload.awaitedPromptId !== "string") {
|
|
4531
|
+
throw new Error('read-stream response has an invalid "awaitedPromptId"');
|
|
4532
|
+
}
|
|
4533
|
+
return {
|
|
4534
|
+
scrollback: payload.scrollback,
|
|
4535
|
+
activity: { state: state ?? null, reason: reason ?? null },
|
|
4536
|
+
usage: payload.usage === null || payload.usage === void 0 ? null : parseSessionUsageWire(payload.usage),
|
|
4537
|
+
awaitedPromptId: payload.awaitedPromptId ?? null
|
|
4538
|
+
};
|
|
4539
|
+
}
|
|
4540
|
+
function parseReadStreamRequestPayload(payload) {
|
|
4541
|
+
if (!isRecord(payload)) throw new Error("read-stream payload must be an object");
|
|
4542
|
+
if (typeof payload.sessionId !== "string") throw new Error('read-stream payload missing "sessionId"');
|
|
4543
|
+
if (payload.action !== "subscribe" && payload.action !== "unsubscribe") {
|
|
4544
|
+
throw new Error('read-stream payload has an invalid "action"');
|
|
4545
|
+
}
|
|
4546
|
+
return { sessionId: payload.sessionId, action: payload.action };
|
|
4547
|
+
}
|
|
4548
|
+
function parseReadBoardResponsePayload(payload) {
|
|
4549
|
+
if (!isRecord(payload)) throw new Error("read-board response must be an object");
|
|
4550
|
+
if (Array.isArray(payload.projects)) {
|
|
4551
|
+
const projects = payload.projects.map((project, index) => {
|
|
4552
|
+
if (!isRecord(project) || typeof project.id !== "string" || typeof project.name !== "string") {
|
|
4553
|
+
throw new Error(`read-board project ${index} is malformed`);
|
|
4554
|
+
}
|
|
4555
|
+
return { id: project.id, name: project.name };
|
|
4556
|
+
});
|
|
4557
|
+
return { projects };
|
|
4558
|
+
}
|
|
4559
|
+
if (typeof payload.projectId !== "string") throw new Error('read-board response is missing "projectId"');
|
|
4560
|
+
if (!Array.isArray(payload.columns)) throw new Error('read-board response is missing "columns"');
|
|
4561
|
+
if (!Array.isArray(payload.tasks)) throw new Error('read-board response is missing "tasks"');
|
|
4562
|
+
if (!Array.isArray(payload.backlog)) throw new Error('read-board response is missing "backlog"');
|
|
4563
|
+
return {
|
|
4564
|
+
projectId: payload.projectId,
|
|
4565
|
+
columns: payload.columns.map((column) => parseBoardColumnWire(column)),
|
|
4566
|
+
tasks: payload.tasks.map((task) => parseBoardTaskWire(task)),
|
|
4567
|
+
backlog: payload.backlog.map((item) => parseBacklogItemWire(item))
|
|
4568
|
+
};
|
|
4569
|
+
}
|
|
4570
|
+
function parseReadBoardRequestPayload(payload) {
|
|
4571
|
+
if (!isRecord(payload)) throw new Error("read-board payload must be an object");
|
|
4572
|
+
if (payload.projectId !== void 0 && typeof payload.projectId !== "string") {
|
|
4573
|
+
throw new Error('read-board payload has a non-string "projectId"');
|
|
4574
|
+
}
|
|
4575
|
+
if (payload.action !== void 0 && payload.action !== "subscribe" && payload.action !== "unsubscribe") {
|
|
4576
|
+
throw new Error('read-board payload has an invalid "action"');
|
|
4577
|
+
}
|
|
4578
|
+
return { projectId: payload.projectId, action: payload.action };
|
|
4579
|
+
}
|
|
4580
|
+
function parseReadDiffResponsePayload(payload) {
|
|
4581
|
+
if (!isRecord(payload)) throw new Error("read-diff response must be an object");
|
|
4582
|
+
return "files" in payload ? parseDiffFileListWire(payload) : parseDiffFileContentWire(payload);
|
|
4583
|
+
}
|
|
4584
|
+
function parseReadDiffRequestPayload(payload) {
|
|
4585
|
+
if (!isRecord(payload)) throw new Error("read-diff payload must be an object");
|
|
4586
|
+
if (typeof payload.taskId !== "string") throw new Error('read-diff payload missing "taskId"');
|
|
4587
|
+
if (typeof payload.projectId !== "string") throw new Error('read-diff payload missing "projectId"');
|
|
4588
|
+
if (payload.filePath !== void 0 && typeof payload.filePath !== "string") {
|
|
4589
|
+
throw new Error('read-diff payload has a non-string "filePath"');
|
|
4590
|
+
}
|
|
4591
|
+
if (payload.scope !== void 0 && payload.scope !== "working" && payload.scope !== "staged" && payload.scope !== "branch") {
|
|
4592
|
+
throw new Error('read-diff payload has an invalid "scope"');
|
|
4593
|
+
}
|
|
4594
|
+
if (payload.action !== void 0 && payload.action !== "subscribe" && payload.action !== "unsubscribe") {
|
|
4595
|
+
throw new Error('read-diff payload has an invalid "action"');
|
|
4596
|
+
}
|
|
4597
|
+
return {
|
|
4598
|
+
taskId: payload.taskId,
|
|
4599
|
+
projectId: payload.projectId,
|
|
4600
|
+
filePath: payload.filePath,
|
|
4601
|
+
scope: payload.scope,
|
|
4602
|
+
action: payload.action
|
|
4603
|
+
};
|
|
4604
|
+
}
|
|
4605
|
+
function parseSendUserMessageRequestPayload(payload) {
|
|
4606
|
+
if (!isRecord(payload)) throw new Error("send-user-message payload must be an object");
|
|
4607
|
+
if (typeof payload.sessionId !== "string") throw new Error('send-user-message payload missing "sessionId"');
|
|
4608
|
+
if (typeof payload.text !== "string") throw new Error('send-user-message payload missing "text"');
|
|
4609
|
+
return { sessionId: payload.sessionId, text: payload.text };
|
|
4610
|
+
}
|
|
4611
|
+
function parseMoveTaskRequestPayload(payload) {
|
|
4612
|
+
if (!isRecord(payload)) throw new Error("move-task payload must be an object");
|
|
4613
|
+
if (typeof payload.taskId !== "string") throw new Error('move-task payload missing "taskId"');
|
|
4614
|
+
if (typeof payload.targetSwimlaneId !== "string") throw new Error('move-task payload missing "targetSwimlaneId"');
|
|
4615
|
+
if (typeof payload.targetPosition !== "number") throw new Error('move-task payload missing "targetPosition"');
|
|
4616
|
+
if (typeof payload.projectId !== "string") throw new Error('move-task payload missing "projectId"');
|
|
4617
|
+
return {
|
|
4618
|
+
taskId: payload.taskId,
|
|
4619
|
+
targetSwimlaneId: payload.targetSwimlaneId,
|
|
4620
|
+
targetPosition: payload.targetPosition,
|
|
4621
|
+
projectId: payload.projectId
|
|
4622
|
+
};
|
|
4623
|
+
}
|
|
4624
|
+
function parseAnswerPermissionPromptRequestPayload(payload) {
|
|
4625
|
+
if (!isRecord(payload)) throw new Error("answer-permission-prompt payload must be an object");
|
|
4626
|
+
if (typeof payload.sessionId !== "string") throw new Error('answer-permission-prompt payload missing "sessionId"');
|
|
4627
|
+
if (typeof payload.promptId !== "string") throw new Error('answer-permission-prompt payload missing "promptId"');
|
|
4628
|
+
if (typeof payload.keystrokes !== "string") throw new Error('answer-permission-prompt payload missing "keystrokes"');
|
|
4629
|
+
return { sessionId: payload.sessionId, promptId: payload.promptId, keystrokes: payload.keystrokes };
|
|
4630
|
+
}
|
|
4631
|
+
function parseInteractiveTerminalRequestPayload(payload) {
|
|
4632
|
+
if (!isRecord(payload)) throw new Error("interactive-terminal payload must be an object");
|
|
4633
|
+
if (typeof payload.sessionId !== "string") throw new Error('interactive-terminal payload missing "sessionId"');
|
|
4634
|
+
if (typeof payload.data !== "string") throw new Error('interactive-terminal payload missing "data"');
|
|
4635
|
+
return { sessionId: payload.sessionId, data: payload.data };
|
|
4636
|
+
}
|
|
4637
|
+
function parseBoardToolRequestPayload(payload) {
|
|
4638
|
+
if (!isRecord(payload)) throw new Error("board-tool payload must be an object");
|
|
4639
|
+
if (typeof payload.tool !== "string") throw new Error('board-tool payload missing "tool"');
|
|
4640
|
+
if (payload.params === void 0 || !isJsonValue(payload.params)) throw new Error('board-tool payload missing a JSON "params"');
|
|
4641
|
+
return { tool: payload.tool, params: payload.params };
|
|
4642
|
+
}
|
|
4643
|
+
function parseCapabilityRequestPayload(verb, payload) {
|
|
4644
|
+
switch (verb) {
|
|
4645
|
+
case "read-stream":
|
|
4646
|
+
return parseReadStreamRequestPayload(payload);
|
|
4647
|
+
case "read-board":
|
|
4648
|
+
return parseReadBoardRequestPayload(payload);
|
|
4649
|
+
case "read-diff":
|
|
4650
|
+
return parseReadDiffRequestPayload(payload);
|
|
4651
|
+
case "send-user-message":
|
|
4652
|
+
return parseSendUserMessageRequestPayload(payload);
|
|
4653
|
+
case "move-task":
|
|
4654
|
+
return parseMoveTaskRequestPayload(payload);
|
|
4655
|
+
case "answer-permission-prompt":
|
|
4656
|
+
return parseAnswerPermissionPromptRequestPayload(payload);
|
|
4657
|
+
case "interactive-terminal":
|
|
4658
|
+
return parseInteractiveTerminalRequestPayload(payload);
|
|
4659
|
+
case "board-tool-read":
|
|
4660
|
+
case "board-tool-write":
|
|
4661
|
+
return parseBoardToolRequestPayload(payload);
|
|
4662
|
+
default: {
|
|
4663
|
+
const exhaustiveCheck = verb;
|
|
4664
|
+
throw new Error(`Unknown capability verb: ${String(exhaustiveCheck)}`);
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
|
|
4288
4669
|
// src/wire/base64url.ts
|
|
4289
4670
|
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
4290
4671
|
var DECODE_MAP = {};
|
|
@@ -4446,7 +4827,107 @@ function rosterDeviceCapabilitySet(entry) {
|
|
|
4446
4827
|
function capabilitySetToRosterCapabilities(capabilities) {
|
|
4447
4828
|
return capabilitySetToArray(capabilities);
|
|
4448
4829
|
}
|
|
4830
|
+
|
|
4831
|
+
// src/capabilities/board-tools.ts
|
|
4832
|
+
var BOARD_TOOL_READ_NAMES = [
|
|
4833
|
+
"search_tasks",
|
|
4834
|
+
"find_task",
|
|
4835
|
+
"get_current_task",
|
|
4836
|
+
"get_task_stats",
|
|
4837
|
+
"get_usage_stats",
|
|
4838
|
+
"board_summary",
|
|
4839
|
+
"list_sessions",
|
|
4840
|
+
"get_session_history",
|
|
4841
|
+
"get_column_detail",
|
|
4842
|
+
"get_handoff_context",
|
|
4843
|
+
"get_transcript",
|
|
4844
|
+
"get_session_files",
|
|
4845
|
+
"get_session_events"
|
|
4846
|
+
];
|
|
4847
|
+
var BOARD_TOOL_WRITE_NAMES = [
|
|
4848
|
+
"create_task",
|
|
4849
|
+
"update_task",
|
|
4850
|
+
"delete_task",
|
|
4851
|
+
"link_pr",
|
|
4852
|
+
"remove_attachment",
|
|
4853
|
+
"update_column",
|
|
4854
|
+
"create_backlog_task",
|
|
4855
|
+
"promote_backlog",
|
|
4856
|
+
"update_backlog_item",
|
|
4857
|
+
"delete_backlog_item"
|
|
4858
|
+
];
|
|
4859
|
+
function isBoardToolReadName(value) {
|
|
4860
|
+
return BOARD_TOOL_READ_NAMES.includes(value);
|
|
4861
|
+
}
|
|
4862
|
+
function isBoardToolWriteName(value) {
|
|
4863
|
+
return BOARD_TOOL_WRITE_NAMES.includes(value);
|
|
4864
|
+
}
|
|
4865
|
+
|
|
4866
|
+
// src/events/event.ts
|
|
4867
|
+
var BOARD_CHANGES = ["task-created", "task-updated", "task-deleted", "swimlane-updated", "backlog-changed"];
|
|
4868
|
+
function parseActivityEventPayload(payload) {
|
|
4869
|
+
if (!isRecord(payload)) throw new Error("activity payload must be an object");
|
|
4870
|
+
switch (payload.type) {
|
|
4871
|
+
case "activity": {
|
|
4872
|
+
if (!isActivityStateWire(payload.state)) throw new Error('activity payload has an invalid "state"');
|
|
4873
|
+
if (!isActivityReasonWire(payload.reason)) throw new Error('activity payload has an invalid "reason"');
|
|
4874
|
+
return { type: "activity", state: payload.state, reason: payload.reason };
|
|
4875
|
+
}
|
|
4876
|
+
case "usage":
|
|
4877
|
+
return { type: "usage", usage: parseSessionUsageWire(payload.usage) };
|
|
4878
|
+
case "event":
|
|
4879
|
+
return { type: "event", event: parseSessionEventWire(payload.event) };
|
|
4880
|
+
case "permission": {
|
|
4881
|
+
if (typeof payload.promptId !== "string") throw new Error('permission payload is missing "promptId"');
|
|
4882
|
+
if (typeof payload.pending !== "boolean") throw new Error('permission payload is missing "pending"');
|
|
4883
|
+
return { type: "permission", promptId: payload.promptId, pending: payload.pending };
|
|
4884
|
+
}
|
|
4885
|
+
default:
|
|
4886
|
+
throw new Error('activity payload has an unknown "type"');
|
|
4887
|
+
}
|
|
4888
|
+
}
|
|
4889
|
+
function isBridgeEvent(value) {
|
|
4890
|
+
if (!isRecord(value)) return false;
|
|
4891
|
+
switch (value.kind) {
|
|
4892
|
+
case "transcript": {
|
|
4893
|
+
if (typeof value.sessionId !== "string" || typeof value.taskId !== "string") return false;
|
|
4894
|
+
try {
|
|
4895
|
+
parseTranscriptEntriesWire(value.payload);
|
|
4896
|
+
return true;
|
|
4897
|
+
} catch {
|
|
4898
|
+
return false;
|
|
4899
|
+
}
|
|
4900
|
+
}
|
|
4901
|
+
case "activity": {
|
|
4902
|
+
if (typeof value.sessionId !== "string" || typeof value.taskId !== "string") return false;
|
|
4903
|
+
try {
|
|
4904
|
+
parseActivityEventPayload(value.payload);
|
|
4905
|
+
return true;
|
|
4906
|
+
} catch {
|
|
4907
|
+
return false;
|
|
4908
|
+
}
|
|
4909
|
+
}
|
|
4910
|
+
case "terminal": {
|
|
4911
|
+
if (typeof value.sessionId !== "string" || typeof value.taskId !== "string") return false;
|
|
4912
|
+
return isRecord(value.payload) && typeof value.payload.data === "string";
|
|
4913
|
+
}
|
|
4914
|
+
case "board": {
|
|
4915
|
+
if (typeof value.projectId !== "string") return false;
|
|
4916
|
+
if (value.taskId !== void 0 && typeof value.taskId !== "string") return false;
|
|
4917
|
+
if (!isRecord(value.payload)) return false;
|
|
4918
|
+
const change = value.payload.change;
|
|
4919
|
+
if (typeof change !== "string" || !BOARD_CHANGES.includes(change)) return false;
|
|
4920
|
+
return Array.isArray(value.payload.ids) && value.payload.ids.every((id) => typeof id === "string");
|
|
4921
|
+
}
|
|
4922
|
+
case "diff":
|
|
4923
|
+
return typeof value.taskId === "string" && value.payload === null;
|
|
4924
|
+
default:
|
|
4925
|
+
return false;
|
|
4926
|
+
}
|
|
4927
|
+
}
|
|
4449
4928
|
export {
|
|
4929
|
+
BOARD_TOOL_READ_NAMES,
|
|
4930
|
+
BOARD_TOOL_WRITE_NAMES,
|
|
4450
4931
|
CAPABILITY_VERBS,
|
|
4451
4932
|
CipherState,
|
|
4452
4933
|
ED25519_KEY_LENGTH,
|
|
@@ -4470,6 +4951,7 @@ export {
|
|
|
4470
4951
|
decodeMessage,
|
|
4471
4952
|
decodePairingQrPayload,
|
|
4472
4953
|
deriveSecretstreamPair,
|
|
4954
|
+
deriveSessionSlotId,
|
|
4473
4955
|
deriveShortAuthenticationString,
|
|
4474
4956
|
encodeMessage,
|
|
4475
4957
|
encodePairingQrPayload,
|
|
@@ -4479,8 +4961,28 @@ export {
|
|
|
4479
4961
|
generateEd25519KeyPair,
|
|
4480
4962
|
generateX25519KeyPair,
|
|
4481
4963
|
hexToBytes,
|
|
4964
|
+
isActivityReasonWire,
|
|
4965
|
+
isActivityStateWire,
|
|
4966
|
+
isBoardToolReadName,
|
|
4967
|
+
isBoardToolWriteName,
|
|
4968
|
+
isBridgeEvent,
|
|
4482
4969
|
isCapabilityVerb,
|
|
4970
|
+
isJsonValue,
|
|
4971
|
+
isRecord,
|
|
4483
4972
|
isRosterEntryExpired,
|
|
4973
|
+
parseActivityEventPayload,
|
|
4974
|
+
parseBacklogItemWire,
|
|
4975
|
+
parseBoardColumnWire,
|
|
4976
|
+
parseBoardTaskWire,
|
|
4977
|
+
parseCapabilityRequestPayload,
|
|
4978
|
+
parseDiffFileContentWire,
|
|
4979
|
+
parseDiffFileListWire,
|
|
4980
|
+
parseReadBoardResponsePayload,
|
|
4981
|
+
parseReadDiffResponsePayload,
|
|
4982
|
+
parseReadStreamResponsePayload,
|
|
4983
|
+
parseSessionEventWire,
|
|
4984
|
+
parseSessionUsageWire,
|
|
4985
|
+
parseTranscriptEntriesWire,
|
|
4484
4986
|
randomBytes4 as randomBytes,
|
|
4485
4987
|
rosterDeviceCapabilitySet,
|
|
4486
4988
|
signRosterEntry,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single `isJsonValue` runtime check, shared by framing.ts (validating a
|
|
3
|
+
* whole decoded BridgeMessage) and payloads.ts (validating one verb's
|
|
4
|
+
* request payload before a handler trusts it). Split out so neither module
|
|
5
|
+
* has to re-derive the other's copy.
|
|
6
|
+
*/
|
|
7
|
+
import type { JsonValue } from './messages';
|
|
8
|
+
export declare function isJsonValue(value: unknown): value is JsonValue;
|
|
9
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|