@ottimis/jack-chat-core
v0.5.5
Published
Transport-agnostic message reducer and types for Jack chat (desktop + mobile).
Keywords
Readme
@ottimis/jack-chat-core
Transport-agnostic reducer and type definitions for the Jack chat UI. Shared between the Electron desktop app (jack/) and the React Native / Expo mobile app (jack-mobile/).
What this package contains
- Types —
ChatMessage,ChatState,TaskItem, the provider-neutral message contract (NormalizedMessage,NormalizedBlock,NormalizedToolRef,ToolShape, …), plus the preload-level contracts (FileChangeData,PermissionRequestData,ContextUsageInfo,SlashCommandDef). - Pure helpers —
isJackTaskTool,isTaskTool(deprecated),pickStr. Slash-command parsing is no longer in this package — seeReduceContext.parseSlashEnvelope/ReduceContext.isCliMarkerOnlycallbacks for the provider injection point. - Reducer —
createInitialState()+reduce(state, event, ctx?)that consumes provider-neutralNormalizedMessages plus out-of-band events (permission, file change, error) and produces the list of message bubbles the UI renders.ctxcarries the host clock and optional provider-specific text parsers. - History loader —
loadHistory(rawMessages, sessionId, ctx?)that replays a transcript expressed asNormalizedMessage[]into a startingChatMessage[]. Slash envelope detection and CLI-marker filtering happen only when the matching ctx callbacks are injected.
AgentEvent actions
| kind | Origin | Purpose |
|-----------------------|---------|-------------------------------------------------------------------------|
| sdk | backend | Provider-neutral NormalizedMessage (assistant, user, partial_event, turn_result, session_state, …) |
| permission-request | backend | Inline pending-permission card (only when inlineFileDiff present) |
| file-change | backend | Replace a tool card with a file-diff card after PostToolUse hook |
| agent-error | backend | Append an error result card, clear running flag |
| slash-feedback | host | Append a slash feedback chip (host-side slash executor output) |
| context-usage | host | Append a context-usage chip |
| user-prompt | client | User typed and submitted a prompt |
| turn-started | server | A new user turn just started; passive observers render the bubble + flip running |
| reset | client | Reset to an empty state, optionally switching session |
| load-history | client | Replace state entirely with a replayed transcript for a session |
| slash-invocation | client | User invoked a slash command (built-in or unknown), append chip |
| permission-resolved | client | User decided an inline permission: deny removes card, allow marks streaming |
| interrupt | client | User stopped the turn — reset runtime flags, keep messages |
0.5.0
Breaking: parseSlashEnvelope, isCliMarkerOnly, expandCommandBody, and SLASH_ENVELOPE_START are no longer exported. The reducer's slash-command envelope detection is now optional and provider-driven via two new ReduceContext callbacks:
parseSlashEnvelope?: (text: string) => ParsedSlashEnvelope | nullisCliMarkerOnly?: (text: string) => boolean
Hosts targeting Claude must inject those callbacks at runtime; hosts targeting providers without a slash convention (Codex, Gemini, …) omit them and the reducer renders user messages without slash chip detection or marker filtering.
Renamed (with deprecated alias): ClaudeCommandDef → SlashCommandDef. The old name remains as a @deprecated alias and will be removed in 0.6.0.
Reasoning: chat-core is provider-neutral; Claude-specific text parsing belongs in the provider package, not in the rendering layer. This change unblocks the multi-provider integration on the host side.
Migration:
- Hosts that consume Claude: re-implement
parseSlashEnvelope/isCliMarkerOnlyin your provider layer (the previous logic is ~30 lines, see Jack'sproviders/claude/slashCommands.ts) and pass them viaReduceContext. - Type imports: replace
ClaudeCommandDefwithSlashCommandDef. expandCommandBody: re-implement in the host slash-command executor; the body-expansion logic is host-specific anyway.
0.4.1
Additive: ChatMessage gains optional toolShape, toolRefKind, toolMcpServerSlug fields. The reducer populates them from NormalizedBlock.tool_use.toolRef when the final assistant message arrives (and during history replay). Renderers can switch from toolName-keyed card selection to shape-keyed dispatch; back-compat preserved since the fields are optional.
- Native tools carry their canonical
shapefrom the provider's tool catalog ('fs.read' | 'fs.write' | …). - MCP-routed tools always map to
toolShape: 'mcp'and carrytoolMcpServerSlugso the renderer can show the MCP badge + server slug without re-parsingtoolName. - Streaming-only states (between
content_block_startand the final assistant message) leave the fieldsundefined— only the wire name is on the wire there. Renderers should fall back totoolNamematching for that window. 'unknown'shapes (native tools without a catalog entry) are preserved verbatim, not coerced toundefined, so the renderer can pick a generic JSON view rather than fall back to provider-name pattern matching.
No breaking changes — existing 0.4.0 consumers keep compiling.
Breaking changes in 0.4.0
AgentEvent.kind = 'sdk'now carries a provider-neutralNormalizedMessageinstead of an Anthropic-shapedSdkMessage. Hosts must translate provider-native streams toNormalizedMessagebefore dispatching; Jack ≥ 1.3 ships a Claude translator atsrc/main/providers/claude/normalize.ts.loadHistoryand theload-historyaction now consumeNormalizedMessage[](wasSdkMessage[]).- The Anthropic-shaped block types (
AnthropicContentBlock,AnthropicTextBlock,AnthropicThinkingBlock,AnthropicToolUseBlock,AnthropicToolResultBlock,AnthropicStreamEvent,StreamEventDelta,SdkMessage,HistorySdkMessage) are no longer exported. - Streaming token-level events still expect a Claude
stream_eventshape insideNormalizedMessage.partial_event.raw. A normalized partial-block event will land in a follow-up release once a second provider drives the design. - Use the new
isJackTaskTool(ref: NormalizedToolRef)helper to detect Jack's task-family MCP tools. The legacyisTaskTool(name)helper remains exported but is deprecated. - The Claude
system/status:compactingsignal is not currently mapped to a normalized kind; the'Compacting'status label is unreachable until a follow-up contract update introduces a dedicated state.
What this package does NOT contain
- React components (desktop uses DOM, mobile uses RN primitives — components are host-specific).
- Transport (desktop uses Electron IPC, mobile will use SSE).
- Slash-command execution (host-specific side effects).
- Provider-native translators. The host (Jack main process) owns those — chat-core only consumes the normalized payload.
Consumers
jack/(desktop, Electron): link via"@ottimis/jack-chat-core": "file:../jack-chat-core".jack-mobile/(Expo RN): same link; requiresmetro.config.jswatchFoldersaddition.
