@sjhmars/happy-bridge 0.1.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/README.md +90 -0
- package/cordis.patch.yml +6 -0
- package/lib/client.js +305 -0
- package/lib/index.js +4548 -0
- package/lib/invariant.js +15 -0
- package/lib/types/archive-sync.d.ts +38 -0
- package/lib/types/attachments.d.ts +19 -0
- package/lib/types/bridge.d.ts +227 -0
- package/lib/types/bytes.d.ts +16 -0
- package/lib/types/catalogs.d.ts +119 -0
- package/lib/types/client/HappyBridgeCard.d.ts +41 -0
- package/lib/types/client/index.d.ts +16 -0
- package/lib/types/client/locales.d.ts +34 -0
- package/lib/types/credentials.d.ts +59 -0
- package/lib/types/encryption.d.ts +104 -0
- package/lib/types/grant.d.ts +87 -0
- package/lib/types/happy-version.d.ts +11 -0
- package/lib/types/history.d.ts +184 -0
- package/lib/types/http.d.ts +108 -0
- package/lib/types/inbound.d.ts +88 -0
- package/lib/types/inbox.d.ts +39 -0
- package/lib/types/index.d.ts +24 -0
- package/lib/types/invariant.d.ts +11 -0
- package/lib/types/machine.d.ts +40 -0
- package/lib/types/pairing.d.ts +23 -0
- package/lib/types/paths.d.ts +53 -0
- package/lib/types/remote.d.ts +34 -0
- package/lib/types/rpc.d.ts +22 -0
- package/lib/types/session-socket.d.ts +145 -0
- package/lib/types/types.d.ts +104 -0
- package/package.json +111 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/** Happy content encryption: legacy NaCl secretbox or AES-256-GCM dataKey. */
|
|
2
|
+
import { decodeBase64, encodeBase64 } from './bytes.ts';
|
|
3
|
+
import type { Credentials, EncryptionVariant } from './types.ts';
|
|
4
|
+
/** Content key used for one session or machine. */
|
|
5
|
+
export interface CryptoContext {
|
|
6
|
+
/** AES-GCM or secretbox key. */
|
|
7
|
+
key: Uint8Array;
|
|
8
|
+
/** Algorithm selected from the account credentials. */
|
|
9
|
+
variant: EncryptionVariant;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Encrypt a JSON value the way Happy CLI `encrypt()` does.
|
|
13
|
+
* @param ctx - session or machine crypto.
|
|
14
|
+
* @param data - JSON-serializable plaintext.
|
|
15
|
+
* @returns nonce+ciphertext bytes.
|
|
16
|
+
*/
|
|
17
|
+
export declare function encryptJson(ctx: CryptoContext, data: unknown): Uint8Array;
|
|
18
|
+
/**
|
|
19
|
+
* Decrypt a Happy ciphertext into JSON.
|
|
20
|
+
* @param ctx - matching crypto.
|
|
21
|
+
* @param data - nonce+ciphertext bytes.
|
|
22
|
+
* @returns plaintext or `null` when the box does not open.
|
|
23
|
+
*/
|
|
24
|
+
export declare function decryptJson(ctx: CryptoContext, data: Uint8Array): unknown;
|
|
25
|
+
/**
|
|
26
|
+
* Encrypt JSON and return the on-wire base64 string.
|
|
27
|
+
* @param ctx - session or machine crypto.
|
|
28
|
+
* @param data - JSON-serializable plaintext.
|
|
29
|
+
* @returns base64 ciphertext.
|
|
30
|
+
*/
|
|
31
|
+
export declare function encryptB64(ctx: CryptoContext, data: unknown): string;
|
|
32
|
+
/**
|
|
33
|
+
* Decode base64 then decrypt JSON.
|
|
34
|
+
* @param ctx - matching crypto.
|
|
35
|
+
* @param value - base64 ciphertext.
|
|
36
|
+
* @returns plaintext or `null`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function decryptB64(ctx: CryptoContext, value: string): unknown;
|
|
39
|
+
/**
|
|
40
|
+
* Open the pairing `response` blob (ephemeral-box bundle).
|
|
41
|
+
* @param encryptedBundle - ephPublicKey + nonce + ciphertext.
|
|
42
|
+
* @param recipientSecretKey - our box secret key.
|
|
43
|
+
* @returns 32-byte shared secret, or versioned dataKey payload, or `null`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function decryptWithEphemeralKey(encryptedBundle: Uint8Array, recipientSecretKey: Uint8Array): Uint8Array | null;
|
|
46
|
+
/**
|
|
47
|
+
* Wrap a data-encryption key for the account public key.
|
|
48
|
+
* @param dataKey - 32-byte DEK.
|
|
49
|
+
* @param recipientPublicKey - account box public key.
|
|
50
|
+
* @returns versioned bundle Happy stores as `dataEncryptionKey`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function wrapDataEncryptionKey(dataKey: Uint8Array, recipientPublicKey: Uint8Array): Uint8Array;
|
|
53
|
+
/**
|
|
54
|
+
* Content crypto for a newly created Happy session.
|
|
55
|
+
* @param credentials - account credentials.
|
|
56
|
+
* @returns session key plus optional wrapped DEK for POST /v1/sessions.
|
|
57
|
+
*/
|
|
58
|
+
export declare function sessionCrypto(credentials: Credentials): {
|
|
59
|
+
ctx: CryptoContext;
|
|
60
|
+
dataEncryptionKey: Uint8Array | undefined;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Content crypto for the machine entity (uses `machineKey` on dataKey accounts).
|
|
64
|
+
* @param credentials - account credentials.
|
|
65
|
+
* @returns machine key plus optional wrapped DEK.
|
|
66
|
+
*/
|
|
67
|
+
export declare function machineCrypto(credentials: Credentials): {
|
|
68
|
+
ctx: CryptoContext;
|
|
69
|
+
dataEncryptionKey: Uint8Array | undefined;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Encrypt a binary blob with NaCl crypto_secretbox (XSalsa20-Poly1305).
|
|
73
|
+
* Wire format: nonce (24 bytes) then ciphertext plus 16-byte auth tag.
|
|
74
|
+
* Matches Happy App/CLI `encryptBlob`.
|
|
75
|
+
* @param data - plaintext bytes.
|
|
76
|
+
* @param key - 32-byte blob key from {@link deriveBlobKey}.
|
|
77
|
+
* @returns nonce + ciphertext.
|
|
78
|
+
*/
|
|
79
|
+
export declare function encryptBlob(data: Uint8Array, key: Uint8Array): Uint8Array;
|
|
80
|
+
/**
|
|
81
|
+
* Decrypt a binary blob encrypted with NaCl crypto_secretbox.
|
|
82
|
+
* @param bundle - nonce + ciphertext from {@link encryptBlob}.
|
|
83
|
+
* @param key - matching 32-byte blob key.
|
|
84
|
+
* @returns plaintext bytes, or `null` when the box does not open.
|
|
85
|
+
*/
|
|
86
|
+
export declare function decryptBlob(bundle: Uint8Array, key: Uint8Array): Uint8Array | null;
|
|
87
|
+
/**
|
|
88
|
+
* Session blob key for Happy file attachments.
|
|
89
|
+
* Legacy accounts: `deriveKey(secret, 'Happy Blobs', ['master'])`.
|
|
90
|
+
* DataKey accounts: `deriveKey(dataKey, 'Happy Blobs', ['session'])`.
|
|
91
|
+
* @param ctx - the same session crypto used for JSON envelopes.
|
|
92
|
+
* @returns 32-byte secretbox key.
|
|
93
|
+
*/
|
|
94
|
+
export declare function deriveBlobKey(ctx: CryptoContext): Promise<Uint8Array>;
|
|
95
|
+
/**
|
|
96
|
+
* HMAC-SHA512 hierarchical key tree used by Happy CLI `deriveKey`.
|
|
97
|
+
* @param master - root secret.
|
|
98
|
+
* @param usage - domain string such as `Happy Blobs`.
|
|
99
|
+
* @param path - child indexes such as `['session']` or `['master']`.
|
|
100
|
+
* @returns 32-byte derived key.
|
|
101
|
+
*/
|
|
102
|
+
export declare function deriveKey(master: Uint8Array, usage: string, path: readonly string[]): Promise<Uint8Array>;
|
|
103
|
+
export { encodeBase64, decodeBase64 };
|
|
104
|
+
//# sourceMappingURL=encryption.d.ts.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** Remote-grant checks: what the phone is allowed to do. */
|
|
2
|
+
import type { Config, RemoteGrant } from './types.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Whether `actual` is at least as deep as `needed`.
|
|
5
|
+
* @param actual - currently selected grant.
|
|
6
|
+
* @param needed - minimum required grant.
|
|
7
|
+
* @returns true when the phone may perform the action.
|
|
8
|
+
*/
|
|
9
|
+
export declare function grantAtLeast(actual: RemoteGrant, needed: RemoteGrant): boolean;
|
|
10
|
+
/** Claude-only permissionMode strings that must not be treated as dsh presets. */
|
|
11
|
+
export declare const CLAUDE_PERMISSION_MODES: ReadonlySet<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Decide how inbound `meta.permissionMode` maps onto a dsh preset.
|
|
14
|
+
* @param mode - Happy message meta.permissionMode.
|
|
15
|
+
* @param dshPresets - currently advertised preset names.
|
|
16
|
+
* @returns `apply` with the preset, `ignore` for Claude-only values, or `unknown`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function classifyPermissionMode(mode: string, dshPresets: readonly string[]): {
|
|
19
|
+
kind: 'apply';
|
|
20
|
+
preset: string;
|
|
21
|
+
} | {
|
|
22
|
+
kind: 'ignore';
|
|
23
|
+
} | {
|
|
24
|
+
kind: 'unknown';
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Split a Happy `meta.model` / spawn `modelMode` code into provider and model.
|
|
28
|
+
* @param code - `provider/model`, Rig `provider:model`, or a bare model id.
|
|
29
|
+
* @returns provider (empty when bare) and model.
|
|
30
|
+
*/
|
|
31
|
+
export declare function splitModelCode(code: string): {
|
|
32
|
+
provider: string;
|
|
33
|
+
model: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Model code from a Happy inbound message (`meta.model` plus optional provider).
|
|
37
|
+
* @param meta - Happy message meta object.
|
|
38
|
+
* @returns `provider/model` when both are known, otherwise the raw model string.
|
|
39
|
+
*/
|
|
40
|
+
export declare function messageModelCode(meta: Record<string, unknown>): string | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Effort from a Happy inbound message. Current App wire uses `effort`;
|
|
43
|
+
* spawn and older clients send `effortLevel`.
|
|
44
|
+
* @param meta - Happy message meta object.
|
|
45
|
+
* @returns effort id, `null` when the field is explicitly cleared, or undefined when omitted.
|
|
46
|
+
*/
|
|
47
|
+
export declare function messageEffort(meta: Record<string, unknown>): string | null | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Whether a settings write can land on the live bridge without disposing sockets.
|
|
50
|
+
* Grant / pairOnStart changes take effect in place; URL or credential-dir
|
|
51
|
+
* changes restart the relay.
|
|
52
|
+
* @param previous - config the live bridge is using.
|
|
53
|
+
* @param next - config just resolved from settings.
|
|
54
|
+
*/
|
|
55
|
+
export declare function sameHappyRuntime(previous: Config, next: Config): boolean;
|
|
56
|
+
/** Provider / model / effort triple the phone and Host picker share. */
|
|
57
|
+
export interface ModelOverrideFields {
|
|
58
|
+
provider: string;
|
|
59
|
+
model: string;
|
|
60
|
+
reasoningEffort?: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Whether two overrides name the same Host selection.
|
|
64
|
+
* @param previous - last remembered override, if any.
|
|
65
|
+
* @param next - candidate override.
|
|
66
|
+
*/
|
|
67
|
+
export declare function sameModelOverride(previous: ModelOverrideFields | undefined, next: ModelOverrideFields): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Model and effort Happy stores on session metadata.
|
|
70
|
+
* @param meta - decrypted Happy session metadata object.
|
|
71
|
+
* @returns the current model and/or effort, or undefined when neither is set.
|
|
72
|
+
*/
|
|
73
|
+
export declare function catalogModelPick(meta: Record<string, unknown>): CatalogModelPick | undefined;
|
|
74
|
+
/** Model and/or effort Happy wrote into session metadata. */
|
|
75
|
+
export interface CatalogModelPick {
|
|
76
|
+
model?: string;
|
|
77
|
+
effort?: string | null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Whether an inbound Happy catalog is the same pick we last published.
|
|
81
|
+
* Slash and colon provider/model codes compare as one pair.
|
|
82
|
+
* @param previous - last pick we wrote, if any.
|
|
83
|
+
* @param next - pick decoded from inbound metadata.
|
|
84
|
+
* @returns true when the inbound pick is our own echo.
|
|
85
|
+
*/
|
|
86
|
+
export declare function sameCatalogPick(previous: CatalogModelPick | undefined, next: CatalogModelPick): boolean;
|
|
87
|
+
//# sourceMappingURL=grant.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version strings the Happy App compares against its minimum CLI.
|
|
3
|
+
*
|
|
4
|
+
* This is a reported compatibility tag matching npm `happy`, not this
|
|
5
|
+
* plugin's package version and not an install of the official CLI.
|
|
6
|
+
* Bump when Happy publishes a newer CLI that the App starts nagging for.
|
|
7
|
+
*/
|
|
8
|
+
export declare const HAPPY_CLI_VERSION = "1.2.0";
|
|
9
|
+
/** Official `X-Happy-Client` / socket `happyClient` value. */
|
|
10
|
+
export declare const HAPPY_CLIENT = "cli-coding-session/1.2.0";
|
|
11
|
+
//# sourceMappingURL=happy-version.d.ts.map
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/** Fold a session label and replayable chat items from the harness log. */
|
|
2
|
+
import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment/types';
|
|
3
|
+
/** Minimal log event the history fold reads. */
|
|
4
|
+
export interface HistoryEvent {
|
|
5
|
+
type: string;
|
|
6
|
+
time?: number;
|
|
7
|
+
data: unknown;
|
|
8
|
+
}
|
|
9
|
+
/** One Happy envelope to emit when filling an empty mirrored session. */
|
|
10
|
+
export type HistoryItem = {
|
|
11
|
+
kind: 'turn-start';
|
|
12
|
+
time: number;
|
|
13
|
+
} | {
|
|
14
|
+
kind: 'turn-end';
|
|
15
|
+
time: number;
|
|
16
|
+
status: 'completed' | 'failed' | 'cancelled';
|
|
17
|
+
} | {
|
|
18
|
+
kind: 'user';
|
|
19
|
+
time: number;
|
|
20
|
+
text: string;
|
|
21
|
+
images: ImageAttachmentRef[];
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'assistant';
|
|
24
|
+
time: number;
|
|
25
|
+
text: string;
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'tool-start';
|
|
28
|
+
time: number;
|
|
29
|
+
call: string;
|
|
30
|
+
name: string;
|
|
31
|
+
title: string;
|
|
32
|
+
description: string;
|
|
33
|
+
args: Record<string, unknown>;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'tool-end';
|
|
36
|
+
time: number;
|
|
37
|
+
call: string;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Happy session-list title: logged title, else first human prompt, else the
|
|
41
|
+
* same "新会话" the web sidebar uses. Never the folder name — that belongs
|
|
42
|
+
* on Happy's project-group header via `metadata.path`.
|
|
43
|
+
* @param events - session log.
|
|
44
|
+
* @returns non-empty label.
|
|
45
|
+
*/
|
|
46
|
+
export declare function sessionLabel(events: readonly HistoryEvent[]): string;
|
|
47
|
+
/**
|
|
48
|
+
* Same blank rule as the web sidebar: no `turn/start` yet means a
|
|
49
|
+
* provisional New Session placeholder, not a conversation.
|
|
50
|
+
* @param events - session log.
|
|
51
|
+
* @returns true when the sidebar would hide this row unless it is selected.
|
|
52
|
+
*/
|
|
53
|
+
export declare function isBlankSession(events: readonly HistoryEvent[]): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Visible user/assistant/tool turns to copy onto an empty Happy session.
|
|
56
|
+
* Plugin-injected user rows stay off the phone. Assistant chunks are skipped
|
|
57
|
+
* in favor of the committed assistant/message. Reasoning blocks become a
|
|
58
|
+
* collapsible Think card; tool-call blocks become Happy-known tool cards.
|
|
59
|
+
* @param events - session log in seq order.
|
|
60
|
+
* @returns replay items in log order.
|
|
61
|
+
*/
|
|
62
|
+
export declare function historyItems(events: readonly HistoryEvent[]): HistoryItem[];
|
|
63
|
+
/**
|
|
64
|
+
* Visible human prompt from a `user/message` log event.
|
|
65
|
+
* Plugin injects and tool-result rows return empty.
|
|
66
|
+
* @param event - one session log event.
|
|
67
|
+
* @returns trimmed concatenated text blocks, or `''`.
|
|
68
|
+
*/
|
|
69
|
+
export declare function visibleUserText(event: HistoryEvent): string;
|
|
70
|
+
/**
|
|
71
|
+
* Image blocks from a human `user/message`. Plugin injects stay off the phone.
|
|
72
|
+
* @param event - one session log event.
|
|
73
|
+
* @returns attachment refs in content order.
|
|
74
|
+
*/
|
|
75
|
+
export declare function visibleUserImages(event: HistoryEvent): ImageAttachmentRef[];
|
|
76
|
+
/** One committed assistant content block, already folded for Happy. */
|
|
77
|
+
export type AssistantPart = {
|
|
78
|
+
kind: 'thinking';
|
|
79
|
+
text: string;
|
|
80
|
+
} | {
|
|
81
|
+
kind: 'text';
|
|
82
|
+
text: string;
|
|
83
|
+
} | {
|
|
84
|
+
kind: 'tool';
|
|
85
|
+
call: string;
|
|
86
|
+
name: string;
|
|
87
|
+
args: Record<string, unknown>;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Walk committed assistant content in log order: reasoning, visible text, tool calls.
|
|
91
|
+
* @param content - `assistant/message` content array.
|
|
92
|
+
* @returns Happy-ready parts, skipping empty text.
|
|
93
|
+
*/
|
|
94
|
+
export declare function assistantParts(content: unknown): AssistantPart[];
|
|
95
|
+
/**
|
|
96
|
+
* Happy App hides `thinking: true` text and tools named `think` /
|
|
97
|
+
* `CodexReasoning` / `GeminiReasoning`. Names starting `mcp__` become a
|
|
98
|
+
* one-line MCP row with no body. `Note` is unknown to that table, so it
|
|
99
|
+
* stays a tappable card; full text rides in `args.text`.
|
|
100
|
+
*/
|
|
101
|
+
export declare const THINK_TOOL_NAME = "Note";
|
|
102
|
+
/** One Happy tool-call-start payload. */
|
|
103
|
+
export interface HappyToolCard {
|
|
104
|
+
name: string;
|
|
105
|
+
title: string;
|
|
106
|
+
description: string;
|
|
107
|
+
args: Record<string, unknown>;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Collapsed Think row label. Happy compact rows show `description`.
|
|
111
|
+
* @param text - accumulated or committed reasoning.
|
|
112
|
+
* @returns `Think` or `Think ·` plus the first line.
|
|
113
|
+
*/
|
|
114
|
+
export declare function thinkLabel(text: string): string;
|
|
115
|
+
/**
|
|
116
|
+
* Think card for a finished reasoning block. Full text rides in `args`
|
|
117
|
+
* so a tap opens the detail page; the row itself stays one line.
|
|
118
|
+
* @param text - committed reasoning.
|
|
119
|
+
*/
|
|
120
|
+
export declare function thinkCard(text: string): HappyToolCard;
|
|
121
|
+
/**
|
|
122
|
+
* Map a dsh tool onto a Happy card. Compact rows only paint `description`,
|
|
123
|
+
* so that field is `Grep · pattern` (tool name plus the web summary).
|
|
124
|
+
* `name` stays PascalCase so Happy can still pick icons.
|
|
125
|
+
* @param name - registered dsh tool name.
|
|
126
|
+
* @param args - parsed tool arguments.
|
|
127
|
+
*/
|
|
128
|
+
export declare function happyTool(name: string, args: Record<string, unknown>): HappyToolCard;
|
|
129
|
+
/**
|
|
130
|
+
* Web-style one-line label used in tests: `Grep · pattern`.
|
|
131
|
+
* @param name - registered tool name.
|
|
132
|
+
* @param args - parsed tool arguments.
|
|
133
|
+
*/
|
|
134
|
+
export declare function toolTitle(name: string, args: Record<string, unknown>): string;
|
|
135
|
+
/**
|
|
136
|
+
* Preset the session actually runs: last `agent-preset/selected`, else the
|
|
137
|
+
* creation-header value. Phone wake must mount this same composition, not the
|
|
138
|
+
* header alone — a blank session may have switched before its first turn.
|
|
139
|
+
* @param events - session log, oldest first.
|
|
140
|
+
* @param headerAgentPreset - `header.agentPreset` from inspect.
|
|
141
|
+
*/
|
|
142
|
+
export declare function resolveSessionPreset(events: readonly HistoryEvent[], headerAgentPreset?: string): string | undefined;
|
|
143
|
+
/** Provider, model, and optional effort used to start a phone-woken turn. */
|
|
144
|
+
export interface WakeModelSelection {
|
|
145
|
+
provider: string;
|
|
146
|
+
model: string;
|
|
147
|
+
reasoningEffort?: string;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Same precedence Host `selectionFor` uses: first usable provider/model
|
|
151
|
+
* wins; a later candidate may only fill a missing thinking level when it is
|
|
152
|
+
* that same model. Empty provider/model pairs are skipped.
|
|
153
|
+
* @param primary - process pick, else `session.requestHeader()?.config`.
|
|
154
|
+
* @param fallbacks - remaining sources, usually the log then `agentDefaultModel`.
|
|
155
|
+
*/
|
|
156
|
+
export declare function wakeModelSelection(primary: WakeModelSelection | undefined, ...fallbacks: Array<WakeModelSelection | undefined>): WakeModelSelection | undefined;
|
|
157
|
+
/**
|
|
158
|
+
* Effort the first phone-spawn / phone-wake request should send.
|
|
159
|
+
* Current pick wins; otherwise a preferred (web) value or the model's
|
|
160
|
+
* advertised default, but only when the model lists that id.
|
|
161
|
+
* @param currentEffort - already chosen effort, if any.
|
|
162
|
+
* @param preferred - web picker effort to reuse when the model accepts it.
|
|
163
|
+
* @param modelDefault - `resolveModelInfo().reasoning.defaultEffort`.
|
|
164
|
+
* @param supported - advertised effort ids; empty/absent means any string is accepted.
|
|
165
|
+
*/
|
|
166
|
+
export declare function pinWakeEffort(currentEffort: string | undefined, preferred: string | undefined, modelDefault: string | undefined, supported: readonly string[] | undefined): string | undefined;
|
|
167
|
+
/**
|
|
168
|
+
* Sidebar sessions that should stay linked on the phone: every workspace
|
|
169
|
+
* membership except the registry-global archive set.
|
|
170
|
+
* @param workspaces - `workspaceRegistry.list()` projections.
|
|
171
|
+
* @param archived - `workspaceRegistry.archivedSessionIds`.
|
|
172
|
+
*/
|
|
173
|
+
export declare function unarchivedSessionIds(workspaces: readonly {
|
|
174
|
+
sessionIds: readonly string[];
|
|
175
|
+
}[], archived: readonly string[]): string[];
|
|
176
|
+
/**
|
|
177
|
+
* Sidebar ids still eligible for a Happy socket: not archived on the web,
|
|
178
|
+
* and not dismissed from the phone.
|
|
179
|
+
* @param wanted - {@link unarchivedSessionIds} result.
|
|
180
|
+
* @param dismissed - phone stop-session / archive ids.
|
|
181
|
+
* @returns ids that should have a live Happy socket.
|
|
182
|
+
*/
|
|
183
|
+
export declare function mirrorTargets(wanted: readonly string[], dismissed: ReadonlySet<string>): string[];
|
|
184
|
+
//# sourceMappingURL=history.d.ts.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/** Happy HTTP helpers: auth, sessions, machines, attachments. */
|
|
2
|
+
import type { CryptoContext } from './encryption.ts';
|
|
3
|
+
/**
|
|
4
|
+
* JSON POST/GET against the Happy API with the CLI client header.
|
|
5
|
+
* @param serverUrl - API origin.
|
|
6
|
+
* @param path - path beginning with `/`.
|
|
7
|
+
* @param init - method, token, JSON body.
|
|
8
|
+
* @returns parsed JSON, or throws with status text.
|
|
9
|
+
*/
|
|
10
|
+
export declare function happyFetch(serverUrl: string, path: string, init: {
|
|
11
|
+
method?: string;
|
|
12
|
+
token?: string;
|
|
13
|
+
body?: unknown;
|
|
14
|
+
}): Promise<unknown>;
|
|
15
|
+
/**
|
|
16
|
+
* Create or load a Happy session by tag.
|
|
17
|
+
* @param serverUrl - API origin.
|
|
18
|
+
* @param token - bearer token.
|
|
19
|
+
* @param tag - stable tag such as `dsh:<sessionId>`.
|
|
20
|
+
* @param crypto - content encryption for this session.
|
|
21
|
+
* @param metadata - plaintext metadata.
|
|
22
|
+
* @param agentState - plaintext agent state.
|
|
23
|
+
* @param dataEncryptionKey - wrapped DEK bytes when using dataKey.
|
|
24
|
+
* @returns Happy session id and versions.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createOrLoadSession(input: {
|
|
27
|
+
serverUrl: string;
|
|
28
|
+
token: string;
|
|
29
|
+
tag: string;
|
|
30
|
+
crypto: CryptoContext;
|
|
31
|
+
metadata: unknown;
|
|
32
|
+
agentState: unknown;
|
|
33
|
+
dataEncryptionKey?: Uint8Array;
|
|
34
|
+
}): Promise<{
|
|
35
|
+
id: string;
|
|
36
|
+
seq: number;
|
|
37
|
+
metadataVersion: number;
|
|
38
|
+
agentStateVersion: number;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Register or update the machine entity so the App can spawn onto this Host.
|
|
42
|
+
* @param input - machine id, encrypted metadata, optional daemon state.
|
|
43
|
+
* @returns versions from the server.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createOrLoadMachine(input: {
|
|
46
|
+
serverUrl: string;
|
|
47
|
+
token: string;
|
|
48
|
+
machineId: string;
|
|
49
|
+
crypto: CryptoContext;
|
|
50
|
+
metadata: unknown;
|
|
51
|
+
daemonState: unknown;
|
|
52
|
+
dataEncryptionKey?: Uint8Array;
|
|
53
|
+
}): Promise<{
|
|
54
|
+
metadataVersion: number;
|
|
55
|
+
daemonStateVersion: number;
|
|
56
|
+
}>;
|
|
57
|
+
/**
|
|
58
|
+
* Upload an already-encrypted attachment the way Happy CLI `uploadLocalImageAttachmentEnvelope` does:
|
|
59
|
+
* POST `/v1/sessions/:id/attachments/request-upload` `{ filename, size }` → `{ ref, uploadUrl, method }`
|
|
60
|
+
* then PUT octet-stream or POST multipart to `uploadUrl`.
|
|
61
|
+
* Presigned URLs must not receive extra headers; server-local URLs need Bearer.
|
|
62
|
+
* @param serverUrl - API origin.
|
|
63
|
+
* @param token - bearer token.
|
|
64
|
+
* @param sessionId - Happy session id that owns the blob.
|
|
65
|
+
* @param filename - display name sent to request-upload.
|
|
66
|
+
* @param encrypted - nonce+ciphertext from `encryptBlob`.
|
|
67
|
+
* @returns Happy `ref` to put on a user `file` event.
|
|
68
|
+
*/
|
|
69
|
+
export declare function uploadEncryptedAttachment(serverUrl: string, token: string, sessionId: string, filename: string, encrypted: Uint8Array): Promise<string>;
|
|
70
|
+
/**
|
|
71
|
+
* Download an encrypted attachment blob the way Happy CLI does:
|
|
72
|
+
* POST `/v1/sessions/:id/attachments/request-download` → `{ downloadUrl }` → GET bytes.
|
|
73
|
+
* S3 presigned URLs must not receive extra headers; server-local URLs need Bearer.
|
|
74
|
+
* @param serverUrl - API origin.
|
|
75
|
+
* @param token - bearer token.
|
|
76
|
+
* @param sessionId - Happy session id that owns the blob.
|
|
77
|
+
* @param ref - file event `ref`.
|
|
78
|
+
* @returns encrypted nonce+ciphertext bytes.
|
|
79
|
+
*/
|
|
80
|
+
export declare function downloadEncryptedAttachment(serverUrl: string, token: string, sessionId: string, ref: string): Promise<Uint8Array>;
|
|
81
|
+
/**
|
|
82
|
+
* Mark a Happy session inactive without deleting it. Accidental archive of a
|
|
83
|
+
* real conversation can still receive a later phone send.
|
|
84
|
+
* @param serverUrl - API origin.
|
|
85
|
+
* @param token - bearer token.
|
|
86
|
+
* @param sessionId - Happy session id.
|
|
87
|
+
*/
|
|
88
|
+
export declare function archiveHappySession(serverUrl: string, token: string, sessionId: string): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Remove a Happy cloud session so the App can drop it from the list.
|
|
91
|
+
* Already-gone ids are ignored.
|
|
92
|
+
* @param serverUrl - API origin.
|
|
93
|
+
* @param token - bearer token.
|
|
94
|
+
* @param sessionId - Happy session id.
|
|
95
|
+
*/
|
|
96
|
+
export declare function deleteHappySession(serverUrl: string, token: string, sessionId: string): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Happy cloud sessions this token can see. Used to drop blank ghosts the
|
|
99
|
+
* App can only archive, not delete.
|
|
100
|
+
* @param serverUrl - API origin.
|
|
101
|
+
* @param token - bearer token.
|
|
102
|
+
* @returns id and optional tag; empty when the list endpoint is unavailable.
|
|
103
|
+
*/
|
|
104
|
+
export declare function listHappySessions(serverUrl: string, token: string): Promise<{
|
|
105
|
+
id: string;
|
|
106
|
+
tag: string;
|
|
107
|
+
}[]>;
|
|
108
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/** Classify inbound Happy user text as a registered slash command or ordinary chat. */
|
|
2
|
+
import type { PermissionRpc } from './types.ts';
|
|
3
|
+
/** Normalized inbound payload from a decrypted Happy message. */
|
|
4
|
+
export type HappyInbound = {
|
|
5
|
+
kind: 'text';
|
|
6
|
+
text: string;
|
|
7
|
+
meta: Record<string, unknown>;
|
|
8
|
+
} | {
|
|
9
|
+
kind: 'file';
|
|
10
|
+
ref: string;
|
|
11
|
+
name: string;
|
|
12
|
+
mimeType?: string;
|
|
13
|
+
meta: Record<string, unknown>;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Read a user text or file event from a decrypted Happy payload.
|
|
17
|
+
* The App wraps file events as `{ role: 'session', content: { type: 'session', data: { ev } } }`.
|
|
18
|
+
* happy-wire envelopes put `ev` directly on `content`. Chat text from the phone
|
|
19
|
+
* is still `{ role: 'user', content: { type: 'text' } }`.
|
|
20
|
+
* @param plain - decrypted socket payload.
|
|
21
|
+
* @returns inbound chat or file, or `undefined` when the payload is not user input.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseHappyInbound(plain: unknown): HappyInbound | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Parse a candidate slash line the same way `dsh-commands` `parseCommand` does.
|
|
26
|
+
* @param line - complete user text.
|
|
27
|
+
* @returns name + rawInput, or `undefined` when the line is not a command.
|
|
28
|
+
*/
|
|
29
|
+
export declare function parseSlashLine(line: string): {
|
|
30
|
+
name: string;
|
|
31
|
+
rawInput: string;
|
|
32
|
+
} | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Decide whether inbound phone text should run as a command or as followup.
|
|
35
|
+
* Unknown `/name` stays chat so user-invocable skills still inject.
|
|
36
|
+
* @param line - complete user text.
|
|
37
|
+
* @param commandNames - registered command names without the leading slash.
|
|
38
|
+
* @returns `command` when the whole line is a registered command, otherwise `chat`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function classifyInboundText(line: string, commandNames: ReadonlySet<string>): 'command' | 'chat';
|
|
41
|
+
/**
|
|
42
|
+
* Translate Happy AskUserQuestion `answers` (`{ [question text]: "a, b" }`)
|
|
43
|
+
* back into harness `{ id, selected }` rows.
|
|
44
|
+
* @param answers - permission RPC `updatedInput.answers`.
|
|
45
|
+
* @param questions - original harness questions in order.
|
|
46
|
+
* @returns selected labels keyed by question id.
|
|
47
|
+
*/
|
|
48
|
+
export declare function answersFromHappy(answers: Record<string, string> | undefined, questions: readonly {
|
|
49
|
+
id: string;
|
|
50
|
+
question: string;
|
|
51
|
+
}[]): {
|
|
52
|
+
id: string;
|
|
53
|
+
selected: string[];
|
|
54
|
+
}[];
|
|
55
|
+
/**
|
|
56
|
+
* Fill every still-open question with the typed chat line as `custom`.
|
|
57
|
+
* @param questions - harness questions in order.
|
|
58
|
+
* @param text - the App composer line.
|
|
59
|
+
* @returns one answer row per question.
|
|
60
|
+
*/
|
|
61
|
+
export declare function customAnswersFromText(questions: readonly {
|
|
62
|
+
id: string;
|
|
63
|
+
}[], text: string): {
|
|
64
|
+
id: string;
|
|
65
|
+
selected: [];
|
|
66
|
+
custom: string;
|
|
67
|
+
}[];
|
|
68
|
+
/**
|
|
69
|
+
* The option label that declines a plan-review question.
|
|
70
|
+
* @param question - first question of a plan-review `ask`.
|
|
71
|
+
* @returns the non-approve option label, or Keep planning.
|
|
72
|
+
*/
|
|
73
|
+
export declare function planReviewDeclineLabel(question: {
|
|
74
|
+
intent?: {
|
|
75
|
+
kind: string;
|
|
76
|
+
approve: string;
|
|
77
|
+
};
|
|
78
|
+
options?: readonly {
|
|
79
|
+
label: string;
|
|
80
|
+
}[];
|
|
81
|
+
}): string;
|
|
82
|
+
/**
|
|
83
|
+
* Decode a Happy `permission` RPC body.
|
|
84
|
+
* @param params - decrypted RPC params.
|
|
85
|
+
* @returns id, approved flag, optional Always-allow decision, and answers.
|
|
86
|
+
*/
|
|
87
|
+
export declare function parsePermissionRpc(params: unknown): PermissionRpc;
|
|
88
|
+
//# sourceMappingURL=inbound.d.ts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Save phone non-image files into the session workspace so Harness `read` can open them. */
|
|
2
|
+
import type { PendingFile } from './types.ts';
|
|
3
|
+
/** Directory under the session cwd that holds phone files for `read`. */
|
|
4
|
+
export declare const HAPPY_INBOX_DIR = "happy-inbox";
|
|
5
|
+
/**
|
|
6
|
+
* Strip path separators and reserved characters so the file stays inside `happy-inbox/`.
|
|
7
|
+
* @param name - Happy's original filename.
|
|
8
|
+
* @returns a single path segment, or `file` when nothing usable remains.
|
|
9
|
+
*/
|
|
10
|
+
export declare function sanitizeInboxName(name: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Pick a basename that is not already taken in this inbox (on disk or in this batch).
|
|
13
|
+
* @param taken - basenames already used.
|
|
14
|
+
* @param name - Happy's original filename.
|
|
15
|
+
* @returns a unique basename under `happy-inbox/`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function uniqueInboxName(taken: ReadonlySet<string>, name: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Workspace-relative path the `read` tool resolves against session cwd. Always `/`.
|
|
20
|
+
* @param filename - a sanitized inbox basename.
|
|
21
|
+
* @returns `happy-inbox/<filename>`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function inboxReadPath(filename: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Append a `read`-tool instruction after the user's typed text.
|
|
26
|
+
* Harness web does not admit non-image composer uploads; ordinary files are workspace paths for `read`.
|
|
27
|
+
* @param text - what the phone typed, possibly empty.
|
|
28
|
+
* @param relativePaths - `happy-inbox/...` paths already written.
|
|
29
|
+
* @returns the followup text, or `text` when there are no files.
|
|
30
|
+
*/
|
|
31
|
+
export declare function inboxReadPrompt(text: string, relativePaths: readonly string[]): string;
|
|
32
|
+
/**
|
|
33
|
+
* Write extras into `<cwd>/happy-inbox/` and return relative paths for `read`.
|
|
34
|
+
* @param cwd - session workspace root.
|
|
35
|
+
* @param files - decrypted non-image attachments.
|
|
36
|
+
* @returns posix-relative paths the model should pass to `read`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function saveInboxFiles(cwd: string, files: readonly PendingFile[]): Promise<string[]>;
|
|
39
|
+
//# sourceMappingURL=inbox.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host half of Happy remote control: pair a running dsh web/desktop client
|
|
3
|
+
* with Happy App so the phone drives the same harness sessions.
|
|
4
|
+
* @module @sjhmars/happy-bridge
|
|
5
|
+
*/
|
|
6
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
8
|
+
import type { Config as HappyBridgeConfig } from './types.ts';
|
|
9
|
+
export type { Config as HappyBridgeConfig, PairingStatus, RemoteGrant } from './types.ts';
|
|
10
|
+
/** Cordis plugin name. */
|
|
11
|
+
export declare const name = "happy-bridge";
|
|
12
|
+
/** Settings namespace keyed by the Plugins tab card. */
|
|
13
|
+
export declare const HAPPY_BRIDGE_NS: import("@deepseek-ai/dsh-settings").SettingsNamespace;
|
|
14
|
+
/** Wait for the agent registry before mirroring sessions. */
|
|
15
|
+
export declare const inject: string[];
|
|
16
|
+
/** Validated plugin config. Illegal values fail at load. */
|
|
17
|
+
export declare const Config: Schema<HappyBridgeConfig>;
|
|
18
|
+
/**
|
|
19
|
+
* Mount the Host half: settings namespace, Typert Remote, Happy relay.
|
|
20
|
+
* @param ctx - Host context.
|
|
21
|
+
* @param config - composition entry config.
|
|
22
|
+
*/
|
|
23
|
+
export declare function apply(ctx: Context, config: HappyBridgeConfig): void;
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Package invariant companion for `@sjhmars/happy-bridge`. */
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
export declare const name = "happy-bridge-invariant";
|
|
4
|
+
export declare const inject: string[];
|
|
5
|
+
/**
|
|
6
|
+
* Register this package's invariant companion.
|
|
7
|
+
* @param ctx - Host context carrying the invariant registry.
|
|
8
|
+
* @returns the registration disposer after setup succeeds.
|
|
9
|
+
*/
|
|
10
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
11
|
+
//# sourceMappingURL=invariant.d.ts.map
|