@ottimis/jack-provider-sdk 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.
@@ -0,0 +1,700 @@
1
+ /**
2
+ * JackProvider — plugin contract for an AI provider integration.
3
+ *
4
+ * A provider package (in-tree `providers/claude/`, future external
5
+ * `jack-codex`, `jack-gemini`, …) registers a single `JackProvider` object
6
+ * that wires up everything the host needs to drive that AI:
7
+ *
8
+ * - one or more {@link BackendDescriptor}s (the wire-protocol implementations)
9
+ * - a {@link CapabilityMatrix} so the UI knows what features to show
10
+ * - a {@link ToolDescriptor} catalog so the renderer can map provider-native
11
+ * tool names to canonical Jack shapes
12
+ * - a {@link JackProvider.detect} probe so the gate UI can warn when the
13
+ * host lacks a usable installation
14
+ *
15
+ * This file is the boundary between Jack core and a provider package — keep
16
+ * it free of provider-specific imports.
17
+ */
18
+
19
+ import type { AgentBackend, AgentQueryOptions } from './backend'
20
+ import type {
21
+ ClientToolHandler,
22
+ NormalizedMessage,
23
+ NormalizedToolRef,
24
+ ProviderUserContentPolicy,
25
+ ToolShape
26
+ } from '@ottimis/jack-chat-core'
27
+
28
+ export type ProviderId = string
29
+
30
+ /**
31
+ * Slash-command definition surfaced by a provider. Different providers
32
+ * source these differently — Claude scans `.claude/commands/` and
33
+ * declares a fixed list of CLI builtins; Codex / Gemini have their own
34
+ * conventions or none at all. The rendered shape is the same.
35
+ */
36
+ export type SlashCommandDef = {
37
+ name: string
38
+ scope: 'user' | 'project' | 'builtin'
39
+ description?: string
40
+ argumentHint?: string
41
+ body: string
42
+ filePath: string
43
+ }
44
+
45
+ /**
46
+ * Parsed envelope a provider's CLI may wrap slash commands in when it logs
47
+ * them into the session transcript. Claude uses
48
+ * `<command-name>foo</command-name><command-args>bar</command-args>` plus
49
+ * an optional `<local-command-stdout>...</local-command-stdout>`.
50
+ */
51
+ export type ParsedSlashEnvelope = {
52
+ commandName: string
53
+ commandArgs?: string
54
+ commandStdout?: string
55
+ }
56
+
57
+ /**
58
+ * Provider-declared slash-command support. Every field is optional so a
59
+ * partial implementation degrades gracefully — e.g. a provider with
60
+ * builtin commands but no envelope detection just declares
61
+ * `builtins` and the host skips the envelope hook.
62
+ */
63
+ export type SlashCommandSupport = {
64
+ /** Static catalog of builtins the runtime intercepts. */
65
+ builtins: SlashCommandDef[]
66
+ /**
67
+ * Scan host filesystem for user/project file-based commands. Returns
68
+ * the catalog of file-based defs the user has authored locally. Empty
69
+ * array when the provider doesn't support file-based commands.
70
+ */
71
+ scanCommands?(projectPath?: string): Promise<SlashCommandDef[]>
72
+ /**
73
+ * Detect the provider's slash envelope inside a user message text.
74
+ * Return null when the text doesn't match — caller renders it as a
75
+ * normal user bubble. Plumbed through `ReduceContext.parseSlashEnvelope`
76
+ * to chat-core's reducer.
77
+ */
78
+ parseEnvelope?(text: string): ParsedSlashEnvelope | null
79
+ /**
80
+ * True when the message body is only CLI markers (e.g. Claude's
81
+ * `<local-command-stdout>...</local-command-stdout>` blobs that show
82
+ * up between turns). Used by `loadHistory` to drop noise from the
83
+ * transcript. Plumbed through `ReduceContext.isCliMarkerOnly`.
84
+ */
85
+ isCliMarkerOnly?(text: string): boolean
86
+ /**
87
+ * Substitute the provider's argument placeholders in a file-based
88
+ * command body. Claude uses `$N` (positional) and `$ARGUMENTS` (full
89
+ * raw args). Other providers with file-based commands declare their
90
+ * own substitution rule.
91
+ */
92
+ expandBody?(def: SlashCommandDef, rawArgs: string): string
93
+ /**
94
+ * Subscribe to wire-sourced slash command updates. Used by providers
95
+ * that surface their command catalog dynamically over the wire instead
96
+ * of (or in addition to) on-disk files — Gemini ACP emits a
97
+ * `session/update { sessionUpdate: 'available_commands_update' }`
98
+ * notification per session with the runtime command list.
99
+ *
100
+ * Contract:
101
+ * - The provider invokes the callback whenever the wire publishes a
102
+ * new catalog. The callback receives the FULL set (not a delta) so
103
+ * the host's command store can replace verbatim.
104
+ * - Returns an unsubscribe function. The host calls it on session
105
+ * close.
106
+ * - Optional. Providers without wire-driven commands (Claude file-
107
+ * based, Codex no-commands) leave it undefined and the host falls
108
+ * back to {@link builtins} + {@link scanCommands}.
109
+ *
110
+ * Wire-sourced commands COEXIST with `builtins` and `scanCommands` —
111
+ * the host merges the three sets (wire takes precedence on name
112
+ * collisions, since it reflects the agent's actual runtime state).
113
+ */
114
+ subscribeToWireCommands?(
115
+ sessionId: string,
116
+ callback: (commands: SlashCommandDef[]) => void
117
+ ): () => void
118
+ }
119
+
120
+ /**
121
+ * Options for {@link JackProvider.readSessionTranscript}. Provider-neutral
122
+ * superset of what each backend supports — providers ignore fields they
123
+ * can't honor (e.g. a remote-only provider with no on-disk replay).
124
+ */
125
+ export type ReadSessionTranscriptOptions = {
126
+ /** Provider-side conversation id (the value persisted in `sessions.provider_session_id`). REQUIRED. */
127
+ providerSessionId: string
128
+ /** cwd hint Claude needs to find the right `~/.claude/projects/<encoded>` dir. */
129
+ cwd?: string
130
+ /** Cap on the number of messages returned (caller decides head vs tail). */
131
+ limit?: number
132
+ /** Skip ahead N messages before reading (caller-driven pagination). */
133
+ offset?: number
134
+ /**
135
+ * When true, include host-injected `system` rows (init, etc.). Default
136
+ * false matches the existing behavior of the Claude SDK loader and the
137
+ * current consumers.
138
+ */
139
+ includeSystemMessages?: boolean
140
+ }
141
+
142
+ /**
143
+ * Provider-declared model identifiers used by host one-shot tasks. These
144
+ * are the bits of "we need to ask the model something cheap and quick"
145
+ * (session-name suggestion, agent-def suggestion, shared-template hint)
146
+ * that previously hardcoded `claude-haiku-4-5` everywhere. Each provider
147
+ * picks its own cheapest acceptable model.
148
+ */
149
+ export type ProviderModelDefaults = {
150
+ /**
151
+ * Cheapest model the host should use for one-shot suggester tasks.
152
+ * MUST be available on every account that has the provider installed —
153
+ * suggesters degrade if the user can't access it.
154
+ */
155
+ oneShot: string
156
+ }
157
+
158
+ /**
159
+ * One entry of the inline model dropdown rendered under the chat composer.
160
+ * `value` is what gets passed to the provider's `/model` slash handler;
161
+ * `label` is the short human display (e.g. `Sonnet`, `Pro`, `Flash`).
162
+ */
163
+ export type ProviderModelOption = {
164
+ value: string
165
+ label: string
166
+ }
167
+
168
+ /**
169
+ * Declarative rules a provider hands the host so the host knows *how* to
170
+ * handle the provider's content — chat sanitization, future system-prompt
171
+ * injection strategy, future tool-name detection hints.
172
+ *
173
+ * Today only `userContent` is wired; new namespaces are added as additional
174
+ * optional fields without breaking external provider packages. The host
175
+ * forwards the relevant slice to its consumer:
176
+ * - `userContent` → `provider.readSessionTranscript` (on-disk replay) +
177
+ * chat-core `ReduceContext.userContentPolicy` (live wire + history).
178
+ *
179
+ * Empty / undefined fields are no-ops.
180
+ */
181
+ export type ProviderPolicies = {
182
+ userContent?: ProviderUserContentPolicy
183
+ }
184
+
185
+ /**
186
+ * Capabilities the UI gates on. Honest declaration > aspirational —
187
+ * a provider that lies here will produce dead UI affordances.
188
+ *
189
+ * When you add a feature to Jack that depends on a provider primitive,
190
+ * add a flag here so the corresponding renderer can opt out for providers
191
+ * that don't support it.
192
+ */
193
+ export type CapabilityMatrix = {
194
+ /** Token-by-token assistant streaming (Claude `stream_event`). */
195
+ partialMessages: boolean
196
+ /** Hook events the provider can emit. */
197
+ hooks: {
198
+ PreToolUse: boolean
199
+ PostToolUse: boolean
200
+ }
201
+ /** Native plan-mode primitive (Claude `ExitPlanMode`). */
202
+ planMode: boolean
203
+ /** Native question primitive (Claude `AskUserQuestion`). */
204
+ askUserQuestion: boolean
205
+ /** Subagent spawn: 'native' = provider has it, 'polyfill' = simulated, 'none' = absent. */
206
+ subagents: 'native' | 'polyfill' | 'none'
207
+ /** MCP (Model Context Protocol) server support. */
208
+ mcp: boolean
209
+ /** Claude-style structuredPatch in PostToolUse for fs.edit/fs.write. */
210
+ structuredPatch: boolean
211
+ /** Resume an existing session by id (preserves chat history across spawns). */
212
+ resumeSession: boolean
213
+ /** Switch model live without respawn (Claude control request `set_model`). */
214
+ liveModelSwitch: boolean
215
+ /** Switch permission mode live without respawn. */
216
+ livePermissionModeSwitch: boolean
217
+ /**
218
+ * Permission flow granularity.
219
+ *
220
+ * - `'callback'` — the provider exposes a per-call canUseTool callback:
221
+ * each tool invocation can be blocked, modified, or auto-allowed
222
+ * before it fires (Claude). The renderer subscribes to the channel
223
+ * and the user sees every request.
224
+ * - `'sandbox-only'` — no per-call callback. The sandbox / approval
225
+ * policy is set at spawn time and the sandbox blocks violations as
226
+ * runtime errors; the model reads the error and self-corrects
227
+ * (Codex). The PermissionCard has no channel to subscribe to: the
228
+ * renderer hides it and only shows the post-fact audit log.
229
+ */
230
+ permissionGranularity: 'callback' | 'sandbox-only'
231
+ }
232
+
233
+ /**
234
+ * Re-exports of canonical wire-shape types from chat-core so consumers of
235
+ * this SDK get them at the same import path. Keeps `JackProvider` type
236
+ * definitions self-contained in this package.
237
+ */
238
+ export type { ToolShape }
239
+ export type {
240
+ ClientToolHandler,
241
+ ClientToolHandlerContext,
242
+ ClientFsHandler,
243
+ ClientTerminalHandler,
244
+ ClientToolsHandler,
245
+ TerminalSpec,
246
+ TerminalHandle,
247
+ TerminalOutput,
248
+ RegisteredTool,
249
+ ToolCallResult
250
+ } from '@ottimis/jack-chat-core'
251
+
252
+ export type ToolDescriptor = {
253
+ /** Name as the provider emits it on the wire (e.g. 'Edit', 'apply_patch'). */
254
+ providerToolName: string
255
+ /** Canonical shape — the renderer keys off this. */
256
+ shape: ToolShape
257
+ /**
258
+ * Hint for renderer / analytics card classification:
259
+ *
260
+ * - `'bespoke'`: the renderer has a dedicated React card (Read,
261
+ * Write, Edit, Bash, apply_patch, …).
262
+ * - `'schema'`: the renderer uses SmartGenericRegistry to build a
263
+ * data-driven card (CronCreate, Skill, EnterPlanMode, …).
264
+ * - omitted = `'generic'` fallback (JSON renderer).
265
+ *
266
+ * MCP tools (`mcp__<slug>__<name>`) are classified separately via
267
+ * `parseToolName` → kind=mcp and don't need this flag.
268
+ */
269
+ cardStyle?: 'bespoke' | 'schema'
270
+ }
271
+
272
+ /**
273
+ * Probe result for {@link JackProvider.detect}. Used by the bootstrap
274
+ * gate UI (`ProviderGate`) to decide whether to render the rest of the
275
+ * app and, when missing or unauthenticated, what affordance to show
276
+ * (install command, sign-in flow, docs link).
277
+ *
278
+ * On the `installed: true` branch, `authenticated` is an OPTIONAL
279
+ * three-state signal:
280
+ * - `true` → credentials present and probably valid
281
+ * - `false` → binary present, credentials missing or expired
282
+ * - omitted → provider doesn't model auth (e.g. SDK that's self-contained,
283
+ * or auth is implicit via the same install)
284
+ */
285
+ export type ProviderDetectResult =
286
+ | {
287
+ installed: true
288
+ /** Tri-state: true = creds present, false = creds missing/expired, undefined = N/A. */
289
+ authenticated?: boolean
290
+ /** Human-readable reason when `authenticated: false` (e.g. "OAuth token expired"). */
291
+ authReason?: string
292
+ /** Single-line command that authenticates (e.g. `claude login` / `codex login` / `gemini auth login`). */
293
+ signInCommand?: string
294
+ /** External docs URL for the auth flow when distinct from install docs. */
295
+ authDocsUrl?: string
296
+ details?: Record<string, unknown>
297
+ }
298
+ | {
299
+ installed: false
300
+ reason: string
301
+ probedPaths?: string[]
302
+ /** Single-line shell command that installs the missing runtime. */
303
+ installCommand?: string
304
+ /** External docs URL pointing at the canonical install guide. */
305
+ docsUrl?: string
306
+ }
307
+
308
+ /**
309
+ * One backend = one wire-protocol implementation. A provider can ship
310
+ * several (Claude: `sdk` bundles cli.js inside the asar, `cli` calls the
311
+ * user's locally installed binary). Selection happens via
312
+ * `JACK_AGENT_BACKEND` env var; default is `JackProvider.defaultBackendId`.
313
+ *
314
+ * `factory` is lazy so unused backends pay zero cost at boot.
315
+ */
316
+ export type BackendDescriptor = {
317
+ id: string
318
+ label: string
319
+ factory: () => AgentBackend
320
+ /**
321
+ * True when the backend ships its own runtime and doesn't depend on a
322
+ * host install (e.g. Claude's `sdk` backend embeds `cli.js` inside the
323
+ * asar). The bootstrap gate skips the install-missing screen when the
324
+ * active backend is self-contained, regardless of `detect()` result.
325
+ * Defaults to `false` for backends that drive a host-installed binary.
326
+ */
327
+ selfContained?: boolean
328
+ /**
329
+ * Backend-level capability overrides. When present, these take precedence
330
+ * over the provider-level {@link CapabilityMatrix} for sessions running
331
+ * on this backend. Provider-level remains the default for backends that
332
+ * don't override.
333
+ *
334
+ * Use case: Gemini ships `cli` (stream-json) and `acp` (JSON-RPC)
335
+ * transports with **different** feature sets — ACP exposes structured
336
+ * plan, live model switch, callback-style permission gating; stream-json
337
+ * has none. The provider declares the LCD at provider-level and ACP
338
+ * overrides the deltas. Pattern A providers (Claude SDK/CLI are wire-
339
+ * identical) typically don't need this and leave it undefined.
340
+ */
341
+ capabilities?: Partial<CapabilityMatrix>
342
+ }
343
+
344
+ /**
345
+ * Context the host hands to {@link JackProvider.prepareSpawnOptions} so the
346
+ * provider can decide what to wire (e.g. asar-unpacked CLI path in packaged
347
+ * builds vs. a no-op in dev). Kept narrow on purpose — providers that need
348
+ * more state should read it themselves.
349
+ */
350
+ export type PrepareSpawnContext = {
351
+ /** True in packaged builds (Electron `app.isPackaged`). */
352
+ isPackaged: boolean
353
+ }
354
+
355
+ /**
356
+ * MCP server registration in canonical Anthropic spec shape. Used by
357
+ * {@link KnowledgeContext.mcpServers} as the cross-provider exchange format
358
+ * for `kind=mcp` knowledge sources. Each provider's
359
+ * {@link JackProvider.applyKnowledgeContext} translates this into whatever
360
+ * its native runtime expects (Claude SDK `mcpServers` map; Codex
361
+ * `mcp_servers.toml`; …).
362
+ */
363
+ export type KnowledgeMcpResolution =
364
+ | { type: 'stdio'; command: string; args?: string[]; env?: Record<string, string> }
365
+ | { type: 'http'; url: string; headers?: Record<string, string> }
366
+ | { type: 'sse'; url: string; headers?: Record<string, string> }
367
+
368
+ /**
369
+ * Provider-neutral container for everything the host has computed about the
370
+ * agent's working context: the system prompt addendum (markdown), the extra
371
+ * working directories, and any MCP server registrations resolved from
372
+ * `kind=mcp` knowledge sources.
373
+ *
374
+ * The host merges multiple KnowledgeContexts (workspace context +
375
+ * AgentDefinition knowledge + per-instance overrides) into one before
376
+ * handing it to {@link JackProvider.applyKnowledgeContext}, which folds it
377
+ * into the provider's native {@link AgentQueryOptions} shape.
378
+ */
379
+ export type KnowledgeContext = {
380
+ /**
381
+ * Markdown block to append to the agent's system prompt. Already formatted
382
+ * — providers can either embed it verbatim (Claude `systemPrompt.append`)
383
+ * or split it across system / first-user message (other providers).
384
+ */
385
+ systemPromptAppend: string
386
+ /** Absolute paths the agent should treat as part of its working set. */
387
+ directories: string[]
388
+ /** Resolved MCP server registrations keyed by slug. */
389
+ mcpServers: Record<string, KnowledgeMcpResolution>
390
+ }
391
+
392
+ /**
393
+ * Visual identity declared by each provider. The host surfaces it in the
394
+ * chat composer + sidebar so the user sees which provider is driving a
395
+ * session at a glance.
396
+ *
397
+ * Lightweight by design — providers shouldn't need to ship images or
398
+ * elaborate themes. Renderer treats `accentColor` as a CSS color (any
399
+ * format CSS accepts) and `iconKey` as an enum of curated lucide icon
400
+ * names the renderer maps to React components. Providers that don't
401
+ * declare branding fall back to neutral defaults.
402
+ */
403
+ export type ProviderBranding = {
404
+ /**
405
+ * Primary accent color. Used as a subtle border on the chat composer +
406
+ * a small dot/icon next to the provider name in sidebar entries.
407
+ * Format: any valid CSS color (`#ff6b6b`, `oklch(...)`).
408
+ *
409
+ * Choose a color with enough contrast on both light + dark themes
410
+ * (~50% lightness works). The renderer applies it at low opacity for
411
+ * borders so vivid hex codes are fine.
412
+ */
413
+ accentColor: string
414
+ /**
415
+ * Curated icon key — one of the names the renderer maps to a lucide
416
+ * component. Keeping this an enum (instead of free-form SVG/asset)
417
+ * means providers don't ship rendering assets and the host stays in
418
+ * control of what shapes can land in the UI.
419
+ */
420
+ iconKey?: string
421
+ }
422
+
423
+ export type JackProvider = {
424
+ id: ProviderId
425
+ label: string
426
+ /**
427
+ * Visual identity (accent color + icon) the renderer can use to mark
428
+ * which provider drives a session. Optional; renderer falls back to
429
+ * neutral host theme when absent.
430
+ */
431
+ branding?: ProviderBranding
432
+ /**
433
+ * Probe the host to see if the provider is usable (binary installed,
434
+ * credentials present, …). Surface result in the bootstrap gate.
435
+ */
436
+ detect(): Promise<ProviderDetectResult>
437
+ backends: BackendDescriptor[]
438
+ /** Must match one of `backends[].id`. Used when no env override is set. */
439
+ defaultBackendId: string
440
+ capabilities: CapabilityMatrix
441
+ /**
442
+ * Declarative rules that tell the host how to interpret this provider's
443
+ * data. Distinct from {@link CapabilityMatrix} (what the provider CAN do
444
+ * — drives UI gating) — `policies` say *how* the host should handle
445
+ * content the provider emits or persists. Provider authors declare them
446
+ * statically; the host consumes them at well-defined entry points
447
+ * (`readSessionTranscript`, chat-core reducer via ReduceContext, …).
448
+ *
449
+ * Optional everywhere: a provider that doesn't need any rule simply
450
+ * omits the field. New rule namespaces grow {@link ProviderPolicies}
451
+ * without breaking existing providers.
452
+ */
453
+ policies?: ProviderPolicies
454
+ /**
455
+ * Default model identifiers for host one-shot tasks. Read by suggester
456
+ * call sites (session naming, agent-def hint, shared-template hint) so
457
+ * they don't hardcode a Claude-specific model.
458
+ */
459
+ modelDefaults: ProviderModelDefaults
460
+ /**
461
+ * Options surfaced in the inline Model dropdown under the chat composer.
462
+ * Empty / omitted = no Model dropdown rendered (regardless of
463
+ * `liveModelSwitch`). Selection fires the provider's `/model <value>`
464
+ * slash handler. Hardcoded list is fine for providers with a fixed
465
+ * family (Claude: opus/sonnet/haiku); providers whose available models
466
+ * are dynamic per-session (Gemini's `availableModels[]`) leave this
467
+ * empty until a per-session push lands.
468
+ */
469
+ modelOptions?: readonly ProviderModelOption[]
470
+ /**
471
+ * Reasoning-effort tiers surfaced in the inline Effort dropdown.
472
+ * Empty / omitted = no Effort dropdown rendered. Selection fires the
473
+ * provider's `/effort <value>` slash handler. Only Claude exposes
474
+ * effort tiers as a live switch; other providers (Codex via
475
+ * spawn-time, Gemini not at all) leave this empty.
476
+ */
477
+ effortLevels?: readonly string[]
478
+ /**
479
+ * Tools the provider surfaces in `tool_use` messages, with their
480
+ * canonical shape. Tools not listed fall back to the generic renderer.
481
+ * The `mcp__` prefixed tools are dynamic and resolved at runtime, not
482
+ * declared here.
483
+ */
484
+ toolCatalog: ToolDescriptor[]
485
+ /**
486
+ * Optional hook called by the host right before each `backend.query()`
487
+ * so the provider can wire packaging-specific spawn details (e.g.
488
+ * Claude's SDK backend pointing at an asar-unpacked `cli.js` and the
489
+ * macOS Electron Helper). Mutate `options` in place. Called once per
490
+ * spawn, after the host has already populated `cwd`, `mcpServers`,
491
+ * knowledge context, and any sandbox `spawner`. No-op for providers
492
+ * that don't need it (CLI-only, dev-only, …).
493
+ */
494
+ prepareSpawnOptions?(options: AgentQueryOptions, ctx: PrepareSpawnContext): void
495
+ /**
496
+ * Parse a wire tool name into a {@link NormalizedToolRef}. Each provider
497
+ * owns its own naming convention (Claude: `mcp__<slug>__<tool>` for MCP,
498
+ * literal name for native; Codex: `apply_patch` style; …). The host
499
+ * calls this whenever it needs to reason about a tool name without
500
+ * committing to a specific provider's format — e.g. detecting Jack's
501
+ * own MCP tools, MCP card classification, audit logs.
502
+ */
503
+ parseToolName(rawName: string): NormalizedToolRef
504
+ /**
505
+ * Optional slash-command support. Providers that surface a `/command`
506
+ * UX (Claude's `.claude/commands/`, the `<command-name>` envelope in
507
+ * transcripts, etc.) declare it here; providers without any slash
508
+ * convention (Codex, Gemini, …) leave it undefined and the renderer
509
+ * hides the slash autocomplete + skips envelope detection.
510
+ */
511
+ slashCommands?: SlashCommandSupport
512
+ /**
513
+ * Fold a provider-neutral {@link KnowledgeContext} into the provider's
514
+ * native {@link AgentQueryOptions} shape. Mutates `options` in place.
515
+ *
516
+ * Claude maps the three fields onto SDK options:
517
+ * - `systemPromptAppend` → `systemPrompt.append`
518
+ * - `directories` → `additionalDirectories`
519
+ * - `mcpServers` → `mcpServers`
520
+ *
521
+ * Other providers may package the same data differently (e.g. Codex
522
+ * inlines MCP into a config TOML and treats `directories` as sandbox
523
+ * mount points). Called once per spawn, after the host has merged
524
+ * workspace context + AgentDefinition knowledge + per-instance
525
+ * overrides into a single {@link KnowledgeContext}.
526
+ */
527
+ applyKnowledgeContext(context: KnowledgeContext, options: AgentQueryOptions): void
528
+ /**
529
+ * Read a session's persisted transcript and return it as
530
+ * {@link NormalizedMessage}[]. Replaces direct `getSessionMessages`
531
+ * calls sprinkled across the host (indexer, mobile routes, IPC, name
532
+ * suggester) — those used to import from the Claude SDK and would
533
+ * crash for any other provider. Now they go through this hook.
534
+ *
535
+ * Implementations MUST:
536
+ * - return rows in chronological order (oldest first)
537
+ * - populate `messageId` on every message that has one in the source
538
+ * (Claude JSONL `uuid` → top-level `messageId`)
539
+ * - preserve `raw` verbatim (lossless)
540
+ *
541
+ * Returns an empty array when the session has no transcript yet (e.g.
542
+ * fresh row, never sent a turn).
543
+ */
544
+ readSessionTranscript(opts: ReadSessionTranscriptOptions): Promise<NormalizedMessage[]>
545
+ /**
546
+ * Attach an in-process MCP server to the spawn options. Used by the
547
+ * host to expose Jack-specific tools to the agent (e.g. partner
548
+ * transcript reader for reviewer/tester slots in pair mode) without
549
+ * going through an external MCP process.
550
+ *
551
+ * Provider-neutral spec: the host hands name/version + tool list, the
552
+ * provider wraps them into whatever shape its SDK accepts. Optional —
553
+ * providers without an in-process MCP API simply omit this method, and
554
+ * the host quietly degrades (the agent doesn't get the Jack tools).
555
+ *
556
+ * Claude wraps via `createSdkMcpServer` + `tool` from the agent SDK.
557
+ * Codex SDK has no in-process MCP — global `codex mcp add` only — so
558
+ * `codexProvider` leaves this undefined and pair-mode reviewers
559
+ * running on Codex don't get `get_partner_transcript`. Documented
560
+ * limitation.
561
+ */
562
+ attachInProcessMcpServer?(
563
+ options: AgentQueryOptions,
564
+ spec: InProcessMcpServerSpec
565
+ ): void
566
+ /**
567
+ * Pattern B (ACP-speaking providers like jack-gemini): the host injects
568
+ * a {@link ClientToolHandler} the provider invokes for fs/terminal/tools
569
+ * execution requested by the agent over JSON-RPC. The provider stores
570
+ * the reference and routes ACP `fs/*`, `terminal/*`, `tools/*` requests
571
+ * through it instead of calling `node:fs` / `node-pty` directly.
572
+ *
573
+ * Pattern A providers (Claude, Codex) leave this undefined; the host
574
+ * detects the pattern by absence and skips wiring.
575
+ *
576
+ * `ctx` carries the host's correlation ids the provider may want to
577
+ * bridge to wire-driven side channels (e.g. mapping
578
+ * `available_commands_update` notifications back to the renderer's
579
+ * slash command store via the host's session id). Optional for
580
+ * providers that don't need it.
581
+ */
582
+ attachClientToolHandler?(
583
+ handler: ClientToolHandler,
584
+ ctx?: { jackSessionId?: string }
585
+ ): void
586
+ /**
587
+ * Persisted permission rules manager. The host's
588
+ * `permissions:{list,add,remove}` IPC dispatches through this — providers
589
+ * that don't persist permission rules (sandbox-only models like Codex)
590
+ * leave it undefined and the host returns empty snapshots.
591
+ */
592
+ persistedPermissions?: PersistedPermissionsApi
593
+ }
594
+
595
+ /**
596
+ * Provider-neutral spec for an in-process MCP server the host wants to
597
+ * expose to the agent. Each tool carries a zod schema for argument
598
+ * validation + an async handler that produces MCP `content` blocks.
599
+ *
600
+ * Mirrors the surface of Claude's `tool()` factory but stays SDK-free
601
+ * here so non-Claude providers can implement
602
+ * {@link JackProvider.attachInProcessMcpServer} without dragging in
603
+ * `@anthropic-ai/claude-agent-sdk`.
604
+ */
605
+ export type InProcessMcpServerSpec = {
606
+ name: string
607
+ version: string
608
+ tools: InProcessMcpToolSpec[]
609
+ }
610
+
611
+ /**
612
+ * Behaviour token the provider persists alongside each rule. Mirror of
613
+ * Claude's `permissions.{allow,deny,ask}` arrays — providers with a
614
+ * different vocabulary translate to/from this enum at their boundary.
615
+ */
616
+ export type PermissionBehavior = 'allow' | 'deny' | 'ask'
617
+
618
+ /**
619
+ * Layer the rule lives in. The four-way split mirrors Claude's
620
+ * user/userLocal/project/projectLocal settings cascade. Providers that
621
+ * persist fewer layers populate only the relevant blocks; consumers see
622
+ * empty arrays for the rest.
623
+ */
624
+ export type PermissionSource = 'user' | 'userLocal' | 'project' | 'projectLocal'
625
+
626
+ /**
627
+ * One persisted rule as the provider stores it. `tool` + `pattern` are a
628
+ * convenience parse — `raw` is the source of truth for round-trip writes
629
+ * (remove/add use the raw string verbatim).
630
+ */
631
+ export type PermissionRule = {
632
+ /** Tool name (e.g. `Bash`, `Edit`, `mcp__figma__authenticate`). */
633
+ tool: string
634
+ /** Content inside the parens (the glob / pattern), or null if the rule has no parens. */
635
+ pattern: string | null
636
+ /** Original string as stored in the settings file — source of truth for round-trip writes. */
637
+ raw: string
638
+ }
639
+
640
+ export type PermissionsSourceBlock = {
641
+ source: PermissionSource
642
+ /** Absolute path of the settings file (null if no project context was provided). */
643
+ path: string | null
644
+ /** True if the file currently exists on disk. */
645
+ exists: boolean
646
+ allow: PermissionRule[]
647
+ deny: PermissionRule[]
648
+ ask: PermissionRule[]
649
+ }
650
+
651
+ export type PermissionsSnapshot = {
652
+ user: PermissionsSourceBlock
653
+ userLocal: PermissionsSourceBlock
654
+ project: PermissionsSourceBlock
655
+ projectLocal: PermissionsSourceBlock
656
+ }
657
+
658
+ /**
659
+ * Persisted permission rules manager — provider-declared, optional. The
660
+ * host's `permissions:list/add/remove` IPC dispatches through the active
661
+ * provider's implementation. Providers without a persisted permissions
662
+ * model leave this undefined and the host returns empty snapshots.
663
+ *
664
+ * The neutral shape (four sources × three behaviours × `tool(pattern)`
665
+ * rules) was generalised from Claude's `.claude/settings*.json` cascade
666
+ * but stays generic enough for other providers' approval-policy stores.
667
+ * A provider with a different vocabulary (e.g. Codex `approval_policy`
668
+ * keyed by command prefix) translates inside this method.
669
+ */
670
+ export type PersistedPermissionsApi = {
671
+ list(projectPath?: string): PermissionsSnapshot
672
+ remove(
673
+ source: PermissionSource,
674
+ behavior: PermissionBehavior,
675
+ rawRule: string,
676
+ projectPath?: string
677
+ ): boolean
678
+ add(
679
+ source: PermissionSource,
680
+ behavior: PermissionBehavior,
681
+ rawRule: string,
682
+ projectPath?: string
683
+ ): boolean
684
+ }
685
+
686
+ export type InProcessMcpToolSpec = {
687
+ name: string
688
+ description: string
689
+ /**
690
+ * Zod schema for the tool arguments. Providers that don't speak zod
691
+ * natively can call `.shape` to inspect fields. We keep zod here
692
+ * instead of JSON Schema because the host already uses it everywhere
693
+ * (one source of truth for tool shapes).
694
+ */
695
+ schema: Record<string, unknown>
696
+ handler: (args: Record<string, unknown>) => Promise<{
697
+ content: Array<{ type: 'text'; text: string }>
698
+ isError?: boolean
699
+ }>
700
+ }