@kangentic/protocol 0.7.0 → 0.8.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/capabilities/board-tools.d.ts +1 -1
- package/dist/events/event.d.ts +18 -0
- package/dist/events/payloads.d.ts +9 -1
- package/dist/index.cjs +27 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +25 -2
- package/dist/pairing/qr.d.ts +1 -0
- package/dist/pairing/relay-address.d.ts +18 -0
- package/dist/wire/payloads.d.ts +14 -0
- package/package.json +1 -1
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* this protocol surface honest: a tool renamed or removed desktop-side
|
|
14
14
|
* fails that test instead of silently breaking phones.
|
|
15
15
|
*/
|
|
16
|
-
export declare const BOARD_TOOL_READ_NAMES: readonly ["search_tasks", "find_task", "get_current_task", "get_task_stats", "get_usage_stats", "board_summary", "list_sessions", "get_session_history", "get_column_detail", "get_handoff_context", "get_transcript", "get_session_files", "get_session_events"];
|
|
16
|
+
export declare const BOARD_TOOL_READ_NAMES: readonly ["search_tasks", "find_task", "get_current_task", "get_task_stats", "get_usage_stats", "board_summary", "list_sessions", "get_session_history", "get_column_detail", "get_handoff_context", "get_transcript", "get_session_files", "get_session_events", "get_activity_intervals"];
|
|
17
17
|
export declare const BOARD_TOOL_WRITE_NAMES: readonly ["create_task", "update_task", "delete_task", "link_pr", "remove_attachment", "update_column", "create_backlog_task", "promote_backlog", "update_backlog_item", "delete_backlog_item"];
|
|
18
18
|
export type BoardToolReadName = (typeof BOARD_TOOL_READ_NAMES)[number];
|
|
19
19
|
export type BoardToolWriteName = (typeof BOARD_TOOL_WRITE_NAMES)[number];
|
package/dist/events/event.d.ts
CHANGED
|
@@ -69,6 +69,24 @@ export type ActivityEventPayload = {
|
|
|
69
69
|
| {
|
|
70
70
|
type: 'session-ended';
|
|
71
71
|
intentional: boolean;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The agent's most recent assistant message, already collapsed to a short
|
|
75
|
+
* plain-text preview, pushed whenever it changes.
|
|
76
|
+
*
|
|
77
|
+
* A phone's session list renders exactly one line per session. Deriving it
|
|
78
|
+
* client-side cost a transcript-window request per session (measured 2.3 to
|
|
79
|
+
* 34.6 KB each, to keep a string under 200 characters) plus 0.7 to 3.8
|
|
80
|
+
* seconds of desktop work per request. The desktop already resolves the
|
|
81
|
+
* transcript to compute its delta pushes, so it can carry the line for
|
|
82
|
+
* free on a feed the phone is receiving anyway.
|
|
83
|
+
*
|
|
84
|
+
* Absent from pre-0.8.0 desktops; a phone that sees none should fall back
|
|
85
|
+
* to whatever it can derive locally rather than showing nothing.
|
|
86
|
+
*/
|
|
87
|
+
| {
|
|
88
|
+
type: 'message-preview';
|
|
89
|
+
text: string;
|
|
72
90
|
};
|
|
73
91
|
export interface ActivityEvent {
|
|
74
92
|
kind: 'activity';
|
|
@@ -115,11 +115,19 @@ export interface TerminalDimensionsWire {
|
|
|
115
115
|
}
|
|
116
116
|
/** Mirrors the desktop's ActivityState. 'permission' means the agent paused for user approval (incl. AskUserQuestion / ExitPlanMode pauses). */
|
|
117
117
|
export type ActivityStateWire = 'thinking' | 'idle' | 'permission';
|
|
118
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* Mirrors the desktop's ActivityReason discriminated union. `since` (epoch ms
|
|
120
|
+
* the session first needed the user) is optional, additive-field style like
|
|
121
|
+
* `toolCallCount`/`effort` above: an older phone parsing a payload with the
|
|
122
|
+
* field missing still validates, and a desktop that has not yet learned a
|
|
123
|
+
* `since` for a phantom-state edge case can omit it.
|
|
124
|
+
*/
|
|
119
125
|
export type ActivityReasonWire = {
|
|
120
126
|
kind: 'idle';
|
|
127
|
+
since?: number;
|
|
121
128
|
} | {
|
|
122
129
|
kind: 'permission';
|
|
130
|
+
since?: number;
|
|
123
131
|
} | {
|
|
124
132
|
kind: 'tool';
|
|
125
133
|
pendingCount: number;
|
package/dist/index.cjs
CHANGED
|
@@ -32,6 +32,7 @@ __export(index_exports, {
|
|
|
32
32
|
KK_PATTERN: () => KK_PATTERN,
|
|
33
33
|
MAX_DECODED_LENGTH: () => MAX_DECODED_LENGTH,
|
|
34
34
|
MAX_FRAME_LENGTH: () => MAX_FRAME_LENGTH,
|
|
35
|
+
MAX_RELAY_ADDRESS_LENGTH: () => MAX_RELAY_ADDRESS_LENGTH,
|
|
35
36
|
PAIRING_URI_SCHEME: () => PAIRING_URI_SCHEME,
|
|
36
37
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
37
38
|
PUSH_CATEGORIES: () => PUSH_CATEGORIES,
|
|
@@ -70,6 +71,7 @@ __export(index_exports, {
|
|
|
70
71
|
isPushCategory: () => isPushCategory,
|
|
71
72
|
isRecord: () => isRecord,
|
|
72
73
|
isRosterEntryExpired: () => isRosterEntryExpired,
|
|
74
|
+
isSecureRelayAddress: () => isSecureRelayAddress,
|
|
73
75
|
openPushEnvelope: () => openPushEnvelope,
|
|
74
76
|
parseActivityEventPayload: () => parseActivityEventPayload,
|
|
75
77
|
parseBacklogItemWire: () => parseBacklogItemWire,
|
|
@@ -5317,6 +5319,7 @@ function isActivityReasonWire(value) {
|
|
|
5317
5319
|
switch (value.kind) {
|
|
5318
5320
|
case "idle":
|
|
5319
5321
|
case "permission":
|
|
5322
|
+
return value.since === void 0 || typeof value.since === "number";
|
|
5320
5323
|
case "turn-active":
|
|
5321
5324
|
return true;
|
|
5322
5325
|
case "tool":
|
|
@@ -5545,6 +5548,10 @@ function parseReadStreamRequestPayload(payload) {
|
|
|
5545
5548
|
}
|
|
5546
5549
|
request.limit = payload.limit;
|
|
5547
5550
|
}
|
|
5551
|
+
if (payload.terminal !== void 0) {
|
|
5552
|
+
if (typeof payload.terminal !== "boolean") throw new Error('read-stream payload has an invalid "terminal"');
|
|
5553
|
+
request.terminal = payload.terminal;
|
|
5554
|
+
}
|
|
5548
5555
|
return request;
|
|
5549
5556
|
}
|
|
5550
5557
|
var ACCENT_COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
|
|
@@ -5741,11 +5748,22 @@ function parseCapabilityRequestPayload(verb, payload) {
|
|
|
5741
5748
|
}
|
|
5742
5749
|
}
|
|
5743
5750
|
|
|
5751
|
+
// src/pairing/relay-address.ts
|
|
5752
|
+
var MAX_RELAY_ADDRESS_LENGTH = 512;
|
|
5753
|
+
var LOOPBACK_WS_PREFIXES = ["ws://localhost", "ws://127.0.0.1", "ws://[::1]"];
|
|
5754
|
+
function isSecureRelayAddress(relayAddress) {
|
|
5755
|
+
if (relayAddress.startsWith("wss://")) return true;
|
|
5756
|
+
return LOOPBACK_WS_PREFIXES.some((prefix) => {
|
|
5757
|
+
if (!relayAddress.startsWith(prefix)) return false;
|
|
5758
|
+
const boundaryChar = relayAddress.charAt(prefix.length);
|
|
5759
|
+
return boundaryChar === "" || boundaryChar === ":" || boundaryChar === "/" || boundaryChar === "?" || boundaryChar === "#";
|
|
5760
|
+
});
|
|
5761
|
+
}
|
|
5762
|
+
|
|
5744
5763
|
// src/pairing/qr.ts
|
|
5745
5764
|
var PAIRING_URI_SCHEME = "kangentic-pair";
|
|
5746
5765
|
var PAYLOAD_VERSION = 1;
|
|
5747
5766
|
var PAIRING_TOKEN_LENGTH = 32;
|
|
5748
|
-
var MAX_RELAY_ADDRESS_LENGTH = 512;
|
|
5749
5767
|
var MAX_PAYLOAD_LENGTH = 4096;
|
|
5750
5768
|
function writeUint32(value) {
|
|
5751
5769
|
const bytes = new Uint8Array(4);
|
|
@@ -5876,7 +5894,8 @@ var BOARD_TOOL_READ_NAMES = [
|
|
|
5876
5894
|
"get_handoff_context",
|
|
5877
5895
|
"get_transcript",
|
|
5878
5896
|
"get_session_files",
|
|
5879
|
-
"get_session_events"
|
|
5897
|
+
"get_session_events",
|
|
5898
|
+
"get_activity_intervals"
|
|
5880
5899
|
];
|
|
5881
5900
|
var BOARD_TOOL_WRITE_NAMES = [
|
|
5882
5901
|
"create_task",
|
|
@@ -5926,6 +5945,10 @@ function parseActivityEventPayload(payload) {
|
|
|
5926
5945
|
if (typeof payload.intentional !== "boolean") throw new Error('session-ended payload is missing "intentional"');
|
|
5927
5946
|
return { type: "session-ended", intentional: payload.intentional };
|
|
5928
5947
|
}
|
|
5948
|
+
case "message-preview": {
|
|
5949
|
+
if (typeof payload.text !== "string") throw new Error('message-preview payload is missing "text"');
|
|
5950
|
+
return { type: "message-preview", text: payload.text };
|
|
5951
|
+
}
|
|
5929
5952
|
default:
|
|
5930
5953
|
throw new Error('activity payload has an unknown "type"');
|
|
5931
5954
|
}
|
|
@@ -5992,6 +6015,7 @@ function isBridgeEvent(value) {
|
|
|
5992
6015
|
KK_PATTERN,
|
|
5993
6016
|
MAX_DECODED_LENGTH,
|
|
5994
6017
|
MAX_FRAME_LENGTH,
|
|
6018
|
+
MAX_RELAY_ADDRESS_LENGTH,
|
|
5995
6019
|
PAIRING_URI_SCHEME,
|
|
5996
6020
|
PROTOCOL_VERSION,
|
|
5997
6021
|
PUSH_CATEGORIES,
|
|
@@ -6030,6 +6054,7 @@ function isBridgeEvent(value) {
|
|
|
6030
6054
|
isPushCategory,
|
|
6031
6055
|
isRecord,
|
|
6032
6056
|
isRosterEntryExpired,
|
|
6057
|
+
isSecureRelayAddress,
|
|
6033
6058
|
openPushEnvelope,
|
|
6034
6059
|
parseActivityEventPayload,
|
|
6035
6060
|
parseBacklogItemWire,
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export { SessionFrameKind, wrapSessionFrame, unwrapSessionFrame } from './wire/s
|
|
|
15
15
|
export { parseCapabilityRequestPayload, parseReadStreamResponsePayload, parseReadBoardResponsePayload, parseReadDiffResponsePayload, parseTranscriptWindowResponsePayload, parseRegisterPushRequestPayload, type CapabilityRequestPayloadMap, type CapabilityResponsePayloadMap, type ReadStreamRequestPayload, type ReadStreamResponsePayload, type ReadStreamSessionStatusWire, type TranscriptWindowResponsePayload, 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, type RegisterPushRequestPayload, type RegisterPushResponsePayload, } from './wire/payloads';
|
|
16
16
|
export { PUSH_CATEGORIES, isPushCategory, parsePushEnvelopePlaintext, sealPushEnvelope, openPushEnvelope, PUSH_ENVELOPE_MAX_AGE_MS, PUSH_ENVELOPE_MAX_FUTURE_SKEW_MS, type PushCategory, type PushEnvelopePlaintext, } from './crypto/push-envelope';
|
|
17
17
|
export { PAIRING_URI_SCHEME, encodePairingQrPayload, decodePairingQrPayload, type PairingQrPayload, } from './pairing/qr';
|
|
18
|
+
export { MAX_RELAY_ADDRESS_LENGTH, isSecureRelayAddress } from './pairing/relay-address';
|
|
18
19
|
export { signRosterEntry, verifyRosterEntry, encodeRosterEntryForSigning, isRosterEntryExpired, findRosterDevice, rosterDeviceCapabilitySet, capabilitySetToRosterCapabilities, type RosterDeviceEntry, type DeviceRoster, } from './roster/roster';
|
|
19
20
|
export { CAPABILITY_VERBS, isCapabilityVerb, capabilitySetFromArray, capabilitySetToArray, type CapabilityVerb, type CapabilitySet, } from './capabilities/verbs';
|
|
20
21
|
export { BOARD_TOOL_READ_NAMES, BOARD_TOOL_WRITE_NAMES, isBoardToolReadName, isBoardToolWriteName, type BoardToolReadName, type BoardToolWriteName, type BoardToolName, } from './capabilities/board-tools';
|
package/dist/index.mjs
CHANGED
|
@@ -5206,6 +5206,7 @@ function isActivityReasonWire(value) {
|
|
|
5206
5206
|
switch (value.kind) {
|
|
5207
5207
|
case "idle":
|
|
5208
5208
|
case "permission":
|
|
5209
|
+
return value.since === void 0 || typeof value.since === "number";
|
|
5209
5210
|
case "turn-active":
|
|
5210
5211
|
return true;
|
|
5211
5212
|
case "tool":
|
|
@@ -5434,6 +5435,10 @@ function parseReadStreamRequestPayload(payload) {
|
|
|
5434
5435
|
}
|
|
5435
5436
|
request.limit = payload.limit;
|
|
5436
5437
|
}
|
|
5438
|
+
if (payload.terminal !== void 0) {
|
|
5439
|
+
if (typeof payload.terminal !== "boolean") throw new Error('read-stream payload has an invalid "terminal"');
|
|
5440
|
+
request.terminal = payload.terminal;
|
|
5441
|
+
}
|
|
5437
5442
|
return request;
|
|
5438
5443
|
}
|
|
5439
5444
|
var ACCENT_COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
|
|
@@ -5630,11 +5635,22 @@ function parseCapabilityRequestPayload(verb, payload) {
|
|
|
5630
5635
|
}
|
|
5631
5636
|
}
|
|
5632
5637
|
|
|
5638
|
+
// src/pairing/relay-address.ts
|
|
5639
|
+
var MAX_RELAY_ADDRESS_LENGTH = 512;
|
|
5640
|
+
var LOOPBACK_WS_PREFIXES = ["ws://localhost", "ws://127.0.0.1", "ws://[::1]"];
|
|
5641
|
+
function isSecureRelayAddress(relayAddress) {
|
|
5642
|
+
if (relayAddress.startsWith("wss://")) return true;
|
|
5643
|
+
return LOOPBACK_WS_PREFIXES.some((prefix) => {
|
|
5644
|
+
if (!relayAddress.startsWith(prefix)) return false;
|
|
5645
|
+
const boundaryChar = relayAddress.charAt(prefix.length);
|
|
5646
|
+
return boundaryChar === "" || boundaryChar === ":" || boundaryChar === "/" || boundaryChar === "?" || boundaryChar === "#";
|
|
5647
|
+
});
|
|
5648
|
+
}
|
|
5649
|
+
|
|
5633
5650
|
// src/pairing/qr.ts
|
|
5634
5651
|
var PAIRING_URI_SCHEME = "kangentic-pair";
|
|
5635
5652
|
var PAYLOAD_VERSION = 1;
|
|
5636
5653
|
var PAIRING_TOKEN_LENGTH = 32;
|
|
5637
|
-
var MAX_RELAY_ADDRESS_LENGTH = 512;
|
|
5638
5654
|
var MAX_PAYLOAD_LENGTH = 4096;
|
|
5639
5655
|
function writeUint32(value) {
|
|
5640
5656
|
const bytes = new Uint8Array(4);
|
|
@@ -5765,7 +5781,8 @@ var BOARD_TOOL_READ_NAMES = [
|
|
|
5765
5781
|
"get_handoff_context",
|
|
5766
5782
|
"get_transcript",
|
|
5767
5783
|
"get_session_files",
|
|
5768
|
-
"get_session_events"
|
|
5784
|
+
"get_session_events",
|
|
5785
|
+
"get_activity_intervals"
|
|
5769
5786
|
];
|
|
5770
5787
|
var BOARD_TOOL_WRITE_NAMES = [
|
|
5771
5788
|
"create_task",
|
|
@@ -5815,6 +5832,10 @@ function parseActivityEventPayload(payload) {
|
|
|
5815
5832
|
if (typeof payload.intentional !== "boolean") throw new Error('session-ended payload is missing "intentional"');
|
|
5816
5833
|
return { type: "session-ended", intentional: payload.intentional };
|
|
5817
5834
|
}
|
|
5835
|
+
case "message-preview": {
|
|
5836
|
+
if (typeof payload.text !== "string") throw new Error('message-preview payload is missing "text"');
|
|
5837
|
+
return { type: "message-preview", text: payload.text };
|
|
5838
|
+
}
|
|
5818
5839
|
default:
|
|
5819
5840
|
throw new Error('activity payload has an unknown "type"');
|
|
5820
5841
|
}
|
|
@@ -5880,6 +5901,7 @@ export {
|
|
|
5880
5901
|
KK_PATTERN,
|
|
5881
5902
|
MAX_DECODED_LENGTH,
|
|
5882
5903
|
MAX_FRAME_LENGTH,
|
|
5904
|
+
MAX_RELAY_ADDRESS_LENGTH,
|
|
5883
5905
|
PAIRING_URI_SCHEME,
|
|
5884
5906
|
PROTOCOL_VERSION,
|
|
5885
5907
|
PUSH_CATEGORIES,
|
|
@@ -5918,6 +5940,7 @@ export {
|
|
|
5918
5940
|
isPushCategory,
|
|
5919
5941
|
isRecord,
|
|
5920
5942
|
isRosterEntryExpired,
|
|
5943
|
+
isSecureRelayAddress,
|
|
5921
5944
|
openPushEnvelope,
|
|
5922
5945
|
parseActivityEventPayload,
|
|
5923
5946
|
parseBacklogItemWire,
|
package/dist/pairing/qr.d.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relay address rules shared, byte-for-byte, with the mobile app's
|
|
3
|
+
* src/pairing/qr.ts. This module imports nothing on purpose: it is
|
|
4
|
+
* deep-imported by the desktop's src/shared/relay.ts, which the RENDERER
|
|
5
|
+
* bundles, and pulling in the rest of this package (crypto/primitives ->
|
|
6
|
+
* @noble/*) would drag Noise crypto into that bundle for no reason. Keep it
|
|
7
|
+
* dependency-free.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MAX_RELAY_ADDRESS_LENGTH = 512;
|
|
10
|
+
/**
|
|
11
|
+
* True if the phone's pairing QR scanner will accept this relay address.
|
|
12
|
+
* wss:// is always secure; ws:// is accepted only for loopback, since the
|
|
13
|
+
* pairing token doubles as the Noise PSK and is dialed verbatim as
|
|
14
|
+
* `?slot=`. Prefix-based (not hostname-based) with an explicit boundary
|
|
15
|
+
* check, so `ws://localhost.evil.com` - a hostname that merely starts with
|
|
16
|
+
* the string "localhost" - is rejected rather than treated as loopback.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isSecureRelayAddress(relayAddress: string): boolean;
|
package/dist/wire/payloads.d.ts
CHANGED
|
@@ -32,6 +32,20 @@ export interface ReadStreamRequestPayload {
|
|
|
32
32
|
beforeIndex?: number;
|
|
33
33
|
/** transcript-window only: maximum entries wanted (the desktop may cap this and may return fewer). */
|
|
34
34
|
limit?: number;
|
|
35
|
+
/**
|
|
36
|
+
* subscribe only: whether this subscription wants live PTY bytes.
|
|
37
|
+
*
|
|
38
|
+
* A phone watching its session list needs activity, permission and
|
|
39
|
+
* transcript pushes, but NOT the terminal - it discards those bytes on
|
|
40
|
+
* arrival. Measured on a live board, that discard cost roughly 13MB an hour
|
|
41
|
+
* of relay traffic and mobile data for a feed showing no terminal at all.
|
|
42
|
+
*
|
|
43
|
+
* Omitted means true, so an older phone keeps the previous behaviour and an
|
|
44
|
+
* older desktop that ignores the field simply keeps sending (wasteful, not
|
|
45
|
+
* broken). Set false for a list-only subscription and re-subscribe with it
|
|
46
|
+
* true when a terminal actually opens.
|
|
47
|
+
*/
|
|
48
|
+
terminal?: boolean;
|
|
35
49
|
}
|
|
36
50
|
/**
|
|
37
51
|
* Mirrors the desktop's SessionStatus. 'suspended' is a registered-but-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kangentic/protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Kangentic mobile bridge wire protocol: framing, Noise-based pairing and session handshakes, signed device roster, and capability verbs shared between desktop and mobile.",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.mjs",
|