@hydraharness/harness-client-ui-input-trigger 0.0.0-stage → 0.1.1-rc.6

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.
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Frozen cross-package contract for the input trigger pipeline. Types only —
3
+ * no runtime code. Sources (ui-commands / ui-skill / ui-reference) and the
4
+ * conversation input layer import from here; changes require main-thread
5
+ * arbitration.
6
+ *
7
+ * Providers receive a {@link ClientSessionContext} projection per call —
8
+ * never a Cordis context or the mutable Session. RPC and service access go
9
+ * through the provider plugin's own root context captured at registration.
10
+ */
11
+ import type { ClientContext, SessionId } from '@hydraharness/harness-client-runtime/client';
12
+ /**
13
+ * The provider-facing projection of one client session. It carries stable
14
+ * identity alone; a source that calls Agent-bound RPCs must consult its own
15
+ * service's capability state because an addressed persisted subagent may
16
+ * have a client scope without a live Host Agent.
17
+ */
18
+ export interface ClientSessionContext {
19
+ readonly sessionId: SessionId;
20
+ }
21
+ /** Trigger character a source binds to. */
22
+ export type TriggerChar = '/' | '@';
23
+ /** Where the trigger token sits in the draft: leading (trimmed draft starts with it) or inline. */
24
+ export type TriggerPosition = 'leading' | 'inline';
25
+ /** Which of the three pick paths produced a pick. */
26
+ export type PickVia = 'menu' | 'space' | 'enter';
27
+ /** One menu candidate. Pure display data — zero behavior declaration. */
28
+ export interface InputTriggerCandidate {
29
+ readonly name: string;
30
+ readonly description?: string;
31
+ readonly icon?: string;
32
+ readonly hint?: string;
33
+ /** Optional visual heading shared by adjacent candidates; sectioned groups omit their source-title row. */
34
+ readonly section?: string;
35
+ /** Opaque source-owned pick payload. */
36
+ readonly value?: string;
37
+ }
38
+ /** Pick-moment snapshot of the trigger token span. CAS: stale draftRev ⇒ the whole action no-ops. */
39
+ export interface TokenSpan {
40
+ readonly start: number;
41
+ readonly end: number;
42
+ readonly draftRev: number;
43
+ }
44
+ /** Base64-encoded composer image accompanying one claimed submit transaction. */
45
+ export interface SubmitImageAttachment {
46
+ /** Declared media type; the host verifies it against the decoded bytes. */
47
+ readonly mediaType: 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif';
48
+ /** Canonical base64 encoding of the image bytes. */
49
+ readonly data: string;
50
+ /** Optional display name; never interpreted as a path. */
51
+ readonly name?: string;
52
+ }
53
+ /**
54
+ * Command-mode entry credential. Pure data + a closure method — no class, no
55
+ * cross-package runtime value (client bundle purity).
56
+ */
57
+ export interface CommandClaim {
58
+ /** Integrity-watched draft prefix, e.g. `'/goal '` — breaking startsWith releases the claim. */
59
+ readonly token: string;
60
+ /** Ghost-text hint rendered while the claim's args are blank. */
61
+ readonly hint?: string;
62
+ /**
63
+ * Whether composer image attachments may accompany this command's submit.
64
+ * Absent = the composer refuses to submit while images are attached, keeping
65
+ * the draft and the images in place behind a visible notice.
66
+ */
67
+ readonly images?: boolean;
68
+ /**
69
+ * Enter transaction, supplied by the source as a closure.
70
+ * @param images - serialized composer images accompanying the submission;
71
+ * the composer passes them only when {@link CommandClaim.images} is true.
72
+ */
73
+ submit(args: string, actx: ClientContext, images: readonly SubmitImageAttachment[]): Promise<SubmitOutcome>;
74
+ }
75
+ /**
76
+ * Inline reference insertion. The draft holds the complete display text while
77
+ * the occurrence retains its range; the owner supplies both user-facing projections at insert time
78
+ * (the model representation is serialized on submit via the source codec).
79
+ */
80
+ export interface ReferenceInsert {
81
+ readonly source: string;
82
+ readonly ref: string;
83
+ /** Inline display label (fallback-cached on the occurrence). */
84
+ readonly label: string;
85
+ /** Optional domain glyph shown beside the label. */
86
+ readonly appearance?: 'session' | 'file' | 'folder';
87
+ /** Clipboard / persistence projection, e.g. `/name` (never the model form). */
88
+ readonly clipboardText: string;
89
+ }
90
+ /** Settled result of a command submit transaction. */
91
+ export interface SubmitOutcome {
92
+ readonly kind: 'success' | 'error';
93
+ readonly text?: string;
94
+ }
95
+ /**
96
+ * Unified pick return. `undefined` = miss → default sink; `'handled'` = the
97
+ * source dealt with it internally (e.g. opened its popup shell). The `text`
98
+ * arm is the plain-text reference path (decision recorded in
99
+ * .agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md):
100
+ * the token span is
101
+ * replaced with literal text — no occurrence identity, no placeholder; any
102
+ * chip visual is derived downstream by scanning the draft against the
103
+ * source lexicons.
104
+ */
105
+ export type PickOutcome = {
106
+ readonly claim: CommandClaim;
107
+ } | {
108
+ readonly insert: ReferenceInsert;
109
+ } | {
110
+ readonly text: string;
111
+ readonly continue?: boolean;
112
+ } | 'handled' | undefined;
113
+ /**
114
+ * Non-text composer submission state visible to enter adjudication. The
115
+ * composer owns the actual attachment payloads; adjudication only needs their
116
+ * presence to accept or refuse a whole submission.
117
+ */
118
+ export interface SubmitEnvelope {
119
+ /** Number of image attachments accompanying the draft. */
120
+ readonly images: number;
121
+ }
122
+ /** Candidate request passed to a source. The signal is superseded on query change / menu close. */
123
+ export interface CandidateRequest {
124
+ readonly query: string;
125
+ /** Whether the active @file token is an open quoted path. */
126
+ readonly quoted?: boolean;
127
+ readonly position: TriggerPosition;
128
+ readonly signal: AbortSignal;
129
+ }
130
+ /** Everything a source receives on pick: candidate + session projection + the span snapshot for CAS. */
131
+ export interface InputTriggerPick {
132
+ readonly candidate: InputTriggerCandidate;
133
+ readonly session: ClientSessionContext;
134
+ readonly position: TriggerPosition;
135
+ readonly via: PickVia;
136
+ readonly span: TokenSpan;
137
+ }
138
+ /**
139
+ * Reference codec owned by a source that produces {@link ReferenceInsert}
140
+ * outcomes: the clipboard projection for copy/cut/persistence, and the model
141
+ * serialization invoked per occurrence by the submit attempt (async, abort
142
+ * rides the attempt signal; failure blocks the send — never a silent
143
+ * downgrade to the clipboard text).
144
+ */
145
+ export interface ReferenceCodec {
146
+ /** Clipboard / persistence projection of one reference (e.g. `/name`). */
147
+ clipboardText(ref: string): string;
148
+ /** Model serialization of one reference (e.g. `<skill>name</skill>`). */
149
+ serialize(ref: string, signal: AbortSignal): Promise<string>;
150
+ }
151
+ /**
152
+ * One trigger source. Every callback receives the session's
153
+ * ClientSessionContext projection; sources keep no copy across calls.
154
+ *
155
+ * Space/enter adjudication rides the optional match hooks: implementing one
156
+ * IS the participation claim — the pipeline polls each implementing source
157
+ * with the leading token; the first non-undefined answer wins (registration
158
+ * order); no claimant → default sink. The hooks split because their timing
159
+ * budgets differ: space fires mid-keystroke and must answer synchronously
160
+ * from hot state, while enter may await the source's own warmup.
161
+ */
162
+ export interface InputTriggerSource {
163
+ readonly trigger: TriggerChar;
164
+ /** Menu group label; unique per trigger — duplicate registration throws. */
165
+ readonly name: string;
166
+ /** Menu group display order (lower = higher in the list; default 0). */
167
+ readonly order?: number;
168
+ /** Whether the menu renders the source-title row; defaults to true. */
169
+ readonly showGroupTitle?: boolean;
170
+ candidates(session: ClientSessionContext, req: CandidateRequest): Promise<readonly InputTriggerCandidate[]>;
171
+ /** Every pick lands here; claim/insert outcomes are executed by the pipeline via the scoped input events. */
172
+ onPick(pick: InputTriggerPick): PickOutcome;
173
+ /** Synchronous space-time adjudication over hot state only. `token` is the just-completed leading token (e.g. '/goal'). */
174
+ matchSpace?(session: ClientSessionContext, token: string): PickOutcome;
175
+ /**
176
+ * Enter-time adjudication; may strong-wait the source's own warmup and
177
+ * reject on warmup failure. `line` is the full trimmed draft: the source
178
+ * parses it and applies its own kind policy — args-tolerant kinds claim
179
+ * with trailing text present, bare-token-only kinds answer undefined
180
+ * unless the line is exactly the token. `envelope` describes the rest of
181
+ * the composer submission; a source that would consume the line but cannot
182
+ * consume the whole envelope throws to surface the refusal and leave the
183
+ * submission intact.
184
+ */
185
+ matchEnter?(session: ClientSessionContext, line: string, signal: AbortSignal, envelope: SubmitEnvelope): Promise<PickOutcome>;
186
+ /**
187
+ * Scope-birth prewarm hook (fire-and-forget): the per-session controller
188
+ * calls it once when the session scope comes alive so sources can fetch
189
+ * their backing data before the first interaction.
190
+ */
191
+ warm?(session: ClientSessionContext): void;
192
+ /**
193
+ * Synchronous hot-snapshot name roll for plain-text reference decoration.
194
+ * Implementing IS the participation claim: the render side
195
+ * scans the draft for `<trigger><name>` tokens and decorates exact matches.
196
+ * `undefined` = backing data not warm yet — no decoration, never a fetch
197
+ * (the render path must stay synchronous and side-effect free).
198
+ */
199
+ lexicon?(session: ClientSessionContext): readonly string[] | undefined;
200
+ /**
201
+ * Subscribe to changes of this source's {@link InputTriggerSource.lexicon} answer
202
+ * for one session (backing data settled, invalidated, or refreshed). The
203
+ * controller re-polls lexicon on each notification; a source whose roll
204
+ * never changes after warm omits the hook.
205
+ * @param session - stable session projection.
206
+ * @param listener - invalidation callback.
207
+ * @returns unsubscribe.
208
+ */
209
+ subscribeLexicon?(session: ClientSessionContext, listener: () => void): () => void;
210
+ /** Reference codec; required for sources producing insert outcomes. */
211
+ readonly codec?: ReferenceCodec;
212
+ }
213
+ /** Trigger availability tier, derived from the input phase by the wiring layer. */
214
+ export interface TriggerGuard {
215
+ /** plain: '/' and '@' live; claimed: '/' suppressed, '@' live; frozen: none. */
216
+ readonly tier: 'plain' | 'claimed' | 'frozen';
217
+ }
218
+ /** Keys the menu intercepts while open (all behind the IME composition guard). */
219
+ export type ArbitrateKey = 'up' | 'down' | 'enter' | 'escape';
220
+ /** consumed = key handled; pick-highlighted = enter picked the highlight; pass = let the input see it. */
221
+ export type ArbitrateOutcome = 'consumed' | 'pick-highlighted' | 'pass';
222
+ /** Request payload of the scoped begin-command input event. */
223
+ export interface BeginCommandRequest {
224
+ readonly claim: CommandClaim;
225
+ readonly span: TokenSpan;
226
+ }
227
+ /** Request payload of the scoped insert-reference input event. */
228
+ export interface InsertReferenceRequest {
229
+ readonly reference: ReferenceInsert;
230
+ readonly span: TokenSpan;
231
+ }
232
+ /** Request payload of the scoped consume-token input event. */
233
+ export interface ConsumeTokenRequest {
234
+ readonly guard: {
235
+ readonly kind: 'span';
236
+ readonly span: TokenSpan;
237
+ } | {
238
+ readonly kind: 'bare-token';
239
+ readonly token: string;
240
+ };
241
+ }
242
+ /** Request payload of the scoped insert-text input event (the plain-text reference path). */
243
+ export interface InsertTextRequest {
244
+ /** Literal replacement for the trigger token span (e.g. `/name `). */
245
+ readonly text: string;
246
+ readonly span: TokenSpan;
247
+ /** Keep completion open after the splice (directory descent): the input re-tracks at the caret. */
248
+ readonly continue?: boolean;
249
+ }
250
+ declare module '@hydraharness/cordis' {
251
+ interface Events {
252
+ /**
253
+ * Applies one command claim to the scoped Input. Dispatched with the
254
+ * session's scope carrier; the owning session's input listener returns
255
+ * `true` only after the phase and span CAS checks pass and the machine
256
+ * actually mutated — producers treat anything else as "not applied".
257
+ * @param request - Claim and menu-time span CAS.
258
+ * @mode bail
259
+ */
260
+ 'slash/input-begin-command'(request: BeginCommandRequest): true | undefined;
261
+ /**
262
+ * Inserts one reference into the scoped Input (same carrier routing and
263
+ * applied-truth contract as begin-command).
264
+ * @param request - Reference and menu-time span CAS.
265
+ * @mode bail
266
+ */
267
+ 'slash/input-insert-reference'(request: InsertReferenceRequest): true | undefined;
268
+ /**
269
+ * Consumes one command token after business success (popup settle /
270
+ * menu-pick execute). Same carrier routing and applied-truth contract.
271
+ * @param request - Exact span or bare-token guard.
272
+ * @mode bail
273
+ */
274
+ 'slash/input-consume-token'(request: ConsumeTokenRequest): true | undefined;
275
+ /**
276
+ * Replaces the trigger token span with literal text — the plain-text
277
+ * reference path. Same carrier routing and applied-truth
278
+ * contract; the draft gains ordinary characters, no occurrence entry.
279
+ * @param request - Replacement text and menu-time span CAS.
280
+ * @mode bail
281
+ */
282
+ 'slash/input-insert-text'(request: InsertTextRequest): true | undefined;
283
+ }
284
+ }
285
+ //# sourceMappingURL=types.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,77 @@
1
1
  {
2
2
  "name": "@hydraharness/harness-client-ui-input-trigger",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "description": "Input trigger pipeline: '/' and '@' detection, candidate menu, pick routing to registered sources",
4
+ "version": "0.1.1-rc.6",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/MaiHongPhong1902/Hydra-Harness.git",
11
+ "directory": "packages/client/ui-input-trigger"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./invariant": {
22
+ "types": "./lib/types/invariant.d.ts",
23
+ "default": "./lib/invariant.js"
24
+ },
25
+ "./client": {
26
+ "types": "./lib/types/client/index.d.ts",
27
+ "default": "./lib/client.js"
28
+ },
29
+ "./src/*": "./src/*",
30
+ "./package.json": "./package.json"
31
+ },
32
+ "hydra": {
33
+ "plugin": {
34
+ "application": "Show command and reference suggestions when typing / or @ in the composer."
35
+ },
36
+ "client": {
37
+ "inject": [
38
+ "@hydraharness/harness-client-runtime",
39
+ "@hydraharness/harness-client-locale"
40
+ ],
41
+ "platform": "web"
42
+ }
43
+ },
44
+ "license": "MIT",
45
+ "dependencies": {
46
+ "clsx": "^2.0.0"
47
+ },
48
+ "peerDependencies": {
49
+ "@hydraharness/harness-client-runtime": "^0.1.1-rc.6",
50
+ "@hydraharness/harness-client-locale": "^0.1.1-rc.6",
51
+ "@hydraharness/harness-invariants": "^0.1.1-rc.6",
52
+ "@hydraharness/cordis": "^4.0.2",
53
+ "@hydraharness/harness-file-reference": "^0.1.1-rc.6"
54
+ },
55
+ "devDependencies": {
56
+ "@types/react": "~18.3.1",
57
+ "react": "^18.2.0",
58
+ "@hydraharness/harness-client-locale": "^0.1.1-rc.6",
59
+ "@hydraharness/harness-client-ui-primitives": "^0.1.1-rc.6",
60
+ "@hydraharness/harness-client-runtime": "^0.1.1-rc.6",
61
+ "@hydraharness/harness-client-test-runtime": "^0.1.1-rc.6",
62
+ "@hydraharness/harness-client-ui-slots": "^0.1.1-rc.6",
63
+ "@hydraharness/harness-invariants": "^0.1.1-rc.6",
64
+ "@hydraharness/cordis": "^4.0.2",
65
+ "@hydraharness/harness-file-reference": "^0.1.1-rc.6"
66
+ },
67
+ "files": [
68
+ "lib/index.js",
69
+ "lib/invariant.js",
70
+ "lib/client.js",
71
+ "lib/types/**/*.d.ts"
72
+ ],
73
+ "scripts": {
74
+ "bundle": "tsdown",
75
+ "watch": "tsdown --watch"
76
+ }
6
77
  }