Commit Graph
1296 Commits
Author SHA1 Message Date
weishu ef956af3e6 merge: A-M2bc iOS chat pipeline port + fixture harness
# Conflicts:
#	ios/README.md
2026-08-17 16:34:23 +08:00
weishu 7c34567678 feat(ios): chat pipeline port + fixture harness (A-M2bc)
Port web/src/chat/** file-for-file into HapiProtocol/Chat/:

- ChatTypes: block/tool/permission/usage models; AgentEvent as a
  record-backed struct (verbatim wire projection) with a typed Kind view
  and exact-key-set constructors for every synthesized event shape
- Normalize/NormalizeUser/NormalizeAgent: the full decode tree (codex /
  output incl. agy / event families, skip rules, stringify-never-drop)
- Tracer: sidechain grouping via parentToolUseId with prompt-match and
  parentUuid-chain fallbacks (orphan reparenting included)
- ReducerEvents (pipe-format limit parsing, dedupe, api-error folding),
  ReducerTools (BlockBox shared-mutation model, JS-spread-faithful
  permission merging via presentKeys), ReducerCliOutput,
  ReducerTimeline (tool pairing, stream coalescing, agent-run cards,
  title-changed synthesis, sentinel suppression), Reducer
  (pending-permission synthesis gates, latestUsage, goal filtering)
- ToolGroups + codex-exploration family (codexCommandPresentation's
  grouping predicates); ToolPresentation (minimal title/subtitle,
  full knownTools catalog deferred to HapiUI)
- JSInterop: JS semantics pinned once (nullish coalescing over wire
  values, truthiness, safeStringify, canonical JSON serializer with
  integer-preserving number formatting, JS-anchored regex helpers)
- FixtureProjection: the normative projection (projection.ts) plus
  runChatFixturePipeline
- ChatFixtureTests: one swift-testing case per shared/fixtures/chat/*.json
  (48), canonical-JSON byte comparison, per-fixture line-level diff on
  mismatch, VERSION gate

Dictionary-order note: wherever the TS relies on JS object insertion
order (agentState requests iteration), the port imposes ascending key
order — equivalent to the reference replay because fixture inputs are
canonically key-sorted on disk and JSON.parse preserves that order.
2026-08-17 16:32:24 +08:00
weishu 80948e93c9 merge: B-M2a Android chat pipeline port (48 fixtures green) 2026-08-17 16:21:25 +08:00
weishu 03651984e0 feat(android): chat pipeline port — 48 chat fixtures green (B-M2a)
Port web/src/chat/ (normalize → reduce → group) to :core:protocol
app.hapi.protocol.chat, file-for-file:

- ChatTypes (blocks/tool-call/permission/usage; AgentEvent sealed over a
  verbatim raw JsonObject so wire events project byte-exact)
- Normalize/NormalizeUser/NormalizeAgent (codex/output/event families,
  agy mapping, skip rules, stringify-never-drop fallback)
- Tracer (parentToolUseId grouping, prompt fallback, orphans)
- ReducerEvents/ReducerTools/ReducerCliOutput/ReducerTimeline/Reducer
  (usage-limit pipe parsing, dedupe + title echo, api-error folding,
  tool pairing, stream coalescing, agent-run cards, turn-duration,
  title-changed synthesis, sentinel suppression, pending-permission
  synthesis with oldest-visible gate, latestUsage, goal filtering)
- ToolGroups (families, id stability, buildVisibleChatBlocks)
- ToolPresentation (event labels; java.time in place of toLocaleString)
- FixtureProjection + CanonicalJson + ChatFixtureTest: every
  shared/fixtures/chat/*.json replayed and compared under canonical
  JSON equality, with a loud fixtureVersion gate

TS undefined maps to Kotlin null, TS null to JsonNull; permission
objects carry a presence set so JS spread-merge semantics survive.

:core:protocol:test 223/223 green (48 fixtures), :app:assembleDebug ok.
2026-08-17 16:20:27 +08:00
weishu d585232876 merge: A-M1d iOS pairing flow + app session wiring 2026-08-17 15:59:05 +08:00
weishu 3363d1c75b feat(ios): pairing flow, deep link, app session wiring (A-M1d)
- HapiProtocol/Pairing/BindLink: parses the companion deeplink
  (hapicompanion://bind?hub=&code=) and the web direct-access QR
  (?hub=&token=) with URLSearchParams form-decoding semantics, in
  lockstep with the Android port (tests mirror BindLinkTest.kt).
- HapiClient/Auth/HubPairingService: normalize -> GET /health
  (reachability + protocolVersion) -> POST /api/auth -> persist
  Keychain + registry + active hub; unpair with fallback. Covered by
  PairingLogicTests through the HTTPPerforming seam.
- App layer: AppModel (@Observable @MainActor pairing state machine:
  restore, pair, switch, sign out, deep-link routing, scenePhase,
  terminal-auth-failure banner) + HubSession (per-active-hub APIClient/
  AuthManager/global SSEClient with suspend-resume and a connection
  state for the UI; store routing is TODO(M2)).
- Pairing UI: welcome flow, VisionKit QR scanner (with Simulator/
  permission fallbacks), manual entry (paste-friendly), shared confirm
  sheet with per-PairingFailure error states.
- HapiApp routes hapicompanion:// through AppModel (paired hubs switch
  with a notice, never log the token); RootView switches unpaired/
  paired and hosts the deep-link confirm sheet; HomePlaceholderView
  shows hub, connection dot, hub switcher (M2a replaces it with the
  session list).
- Info.plist: NSCameraUsageDescription; README: pairing guide + manual
  test pass.
2026-08-17 15:58:17 +08:00
weishu dfcf9849b9 merge: B-M2c Android message window store, pagination fixtures green 2026-08-17 15:51:34 +08:00
weishu 99df45e99a feat(android): message window store port, pagination fixtures green (B-M2c)
:core:protocol window/ — pure state machine ported function-for-function from
web/src/lib/message-window-store.ts + messages.ts: merge-by-(id|localId) with
optimistic echo reconciliation, position ordering (invokedAt ?? createdAt, seq,
ASCII id tie-break), trim-preserving-queued with the codex agent-run budget,
epoch reset handling, latest-replace with request-baseline identity
preservation, consumed/cancelled/queued-reconcile transitions, tail/history
modes, hydrate/persist shapes. MessageRetention ports the null-decision tree
of normalizeDecryptedMessage (dedup with the B-M2a pipeline port flagged).

:core:data store/ — per-session MessageWindowStore (StateFlow + Mutex,
single-flight tail sync with trailing drain, fetchOlder with epoch-reset
resync, SSE ingest hooks, optimistic sends, queued-state reconciliation,
seedFrom for resume id changes) + WindowSnapshots (atomic JSON files, LRU 10;
JsonSnapshotStore dedup TODO) + MessageWindowStores registry. Minimal
MessagesApi interface (sealed MessagesQuery) extracted over the two message
endpoints, implemented by HapiApi.

Gate: PaginationFixtureTest replays all 11 shared/fixtures/pagination scripts
against the real store — requests, older-load outcomes, reconcile candidates
and the final window projection all exact — 11/11 green; plus targeted
concurrency/snapshot/seed unit tests. :app:assembleDebug green.
2026-08-17 15:50:47 +08:00
weishu 96d81a3f12 merge: B-M2b Android session/machine stores + session list
# Conflicts:
#	android/app/build.gradle.kts
2026-08-17 15:48:09 +08:00
weishu f702cbf844 feat(android): session/machine stores + session list (B-M2b)
:core:protocol — summary-side patch path ported from the web reference:
- wire/SummaryPatching.kt: patchSessionSummary port with the summary path's
  >= versioned gates (replicated web divergence vs the detail path's strict >,
  documented), derived-field recompute (pendingRequestsCount/Kinds/Requests
  capped 5, todoProgress), render-irrelevance filter (activeAt-only keep-alive
  suppression), toSessionSummary/toSessionSummaryMetadata projection, and the
  deprecated canApplyVersionedSummaryPatch legacy gate.
- wire/SessionSorting.kt: exact sortSessionSummaries comparator
  (globalPinned > pinned > active > pendingRequestsCount among active >
  updatedAt desc; Long-safe, stable).
- SessionMetadata grows the per-flavor agent session id fields the
  agentSessionId projection needs.

:core:data store/ — StateFlow stores with per-hub JSON snapshots:
- JsonSnapshotStore: debounced (500 ms) atomic tmp+fsync+rename snapshots,
  synchronous cold-start load, corrupt files degrade to null.
- SessionStore: sorted summary list + per-id detail cache (strict-> detail
  patching via SessionPatching), full-session upsert preserving hub-computed
  scheduled fields, REST fallback for unparseable payloads, coalesced refresh
  (16 ms batch like the web), optimistic pin/archive.
- MachineStore: full/patch/null machine-updated decision tree per sse.md.
- LastSeenStore: per-session last-seen watermarks + once-per-scope baseline
  seeding + unread derivation (sessionLastSeen.ts/sessionAttention.ts port).
- StoreSyncTargets: SyncEventRouter fan-in — global-scope message-stream
  events refresh the list, gap handshakes full-resync.

:app feature/sessions/ — standalone screen + plain-constructor ViewModel:
- SessionListScreen: pinned section, status dot with thinking pulse, flavor/
  machine/worktree meta line, pending badge, todo chip, unread dot, machine
  filter chips, PullToRefreshBox, offline banner, empty states, long-press
  pin/archive sheet; taps emit onOpenSession only (no navigation).
- SessionListViewModel: store combine -> UiState, owns the global SSE
  subscription while started (subscribe after collector registration),
  entry refresh, error SharedFlow for snackbars.

Tests: SummaryPatching/SessionSorting JVM tests (cases mined from
useSSE.test.ts), store tests (stale/equal/newer patches, full replace,
keep-alive identity, snapshot round-trips, optimistic rollback), ViewModel
combine + handshake tests with fake stores. All of :core:protocol:test,
:core:data:test, :app:testDebugUnitTest, :app:assembleDebug green.
2026-08-17 15:47:05 +08:00
weishu e85ad3fb75 merge: B-M1d Android pairing flow + DI graphs + navigation 2026-08-17 15:40:50 +08:00
weishu 3cf2a669c3 feat(android): pairing flow, deep link, DI graphs, navigation skeleton (B-M1d) 2026-08-17 15:39:29 +08:00
weishu d6571c1bf7 merge: A-M2e iOS HapiUI rendering foundation
# Conflicts:
#	ios/Packages/HapiKit/Package.swift
#	ios/README.md
2026-08-17 15:34:56 +08:00
weishu ddfe659c2b feat(ios): HapiUI rendering foundation — markdown, code, diff, theme (A-M2e) 2026-08-17 15:33:20 +08:00
weishu 9d2ef4e01c merge: A-M1c iOS SSE client + reconnect state machine
# Conflicts:
#	ios/README.md
2026-08-17 15:29:20 +08:00
weishu 15f48c9c65 feat(ios): SSE client + reconnect state machine (A-M1c)
HapiClient/SSE per docs/api/client-contract/sse.md and the web reference
(web/src/hooks/useSSE.ts):

- SSELineParser: incremental frame parser (LF/CRLF, multi-line data,
  comments, retry ignored); per-block ids only — sticky-cursor semantics
  live in the caller so id-less heartbeats can never blank the cursor.
- ReconnectPolicy + SSETimings: 0s first retry, 1s x2 capped at 30s,
  300s slow ceiling after 8 attempts, 0-500ms injectable jitter; connect
  timeout 10s, staleness 90s, foreground-resume 45s, watchdog tick 10s.
- SSETransport protocol + URLSessionSSETransport (URLSession.bytes with
  dedicated config: 300s idle timeout, 7d resource, no waitsForConnectivity).
- actor SSEClient: idle/connecting/connected/backoff/suspended machine;
  handshake-gated connected state surfacing resume ok|gap; per-subscription
  cursor sent as ?lastEventId, advanced only after consumer yield
  (at-least-once); one free token-refresh retry per connect cycle on 401;
  suspend defers retries, resume applies the 45s staleness check;
  acceptEncodingIdentity escape hatch (gzip streaming verification TODO).
- NetworkPathObserving + NWPathObserver: path change while connected is
  treated as a transport error (immediate reconnect).
- swift-testing suite on a fake transport + manual clock: parser edge
  cases, exact backoff schedule + seeded jitter bounds, handshake gating,
  ok/gap verdicts, cursor replay + isolation, heartbeat vs watchdog,
  90s staleness, connect timeout, suspend/resume, event ordering,
  unknown-type passthrough, 401 bypass cap, path-change reconnect.
2026-08-17 15:28:14 +08:00
weishu 7d436aad29 merge: A-M1b iOS HapiClient API transport + auth 2026-08-17 15:20:47 +08:00
weishu 1f0ec6184d feat(ios): HapiClient API transport + auth (A-M1b)
APIClient (final class, Sendable) with typed endpoints for the M2/M3 REST
surface behind an HTTPPerforming seam; actor AuthManager with single-flight
JWT refresh (POST /api/auth), proactive refresh 10 min before exp, terminal
authFailed state, and 401 -> refresh -> retry-once wiring per
docs/api/client-contract/auth.md; Keychain credential store (per-hub records
under run.hapi.companion) with CredentialStoring seam + in-memory double;
HubRegistry (ordered hubs + active hub in injectable UserDefaults, origin
normalization); payload-only JWT decoding; APIError {status, code, body}
parsing per errors.md; minimal multipart builder for M4c dictation; 256 MB
URLCache session for generated images.

Request/response wire models (SendMessageRequest, PermissionApproveRequest,
SpawnRequest/SpawnResponse, MessagesQuery, envelopes, RPC-wrapped shapes)
join HapiProtocol/Models mirroring shared/src/apiTypes.ts.

swift-testing coverage: JWT decode (padding/garbage/hostile exp), auth
single-flight (8 concurrent callers -> one exchange), 401 retry-once and
terminal paths, APIError body parsing, endpoint request construction
byte-checks (cursor queries, deliveryMode body, answers formats, explicit
null model reset) via a recording performer, hub URL normalization,
multipart bytes.
2026-08-17 15:20:03 +08:00
weishu f275bae4cb merge: B-M1c Android SSE engine + reconnect state machine
# Conflicts:
#	android/README.md
#	android/core/data/build.gradle.kts
#	android/gradle/libs.versions.toml
2026-08-17 15:16:38 +08:00
weishu 3570017743 merge: B-M2d1 Android markdown/code/diff rendering foundation
# Conflicts:
#	android/gradle/libs.versions.toml
2026-08-17 15:15:10 +08:00
weishu 483155ece3 feat(android): SSE engine + reconnect state machine (B-M1c)
:core:data app.hapi.data.sse — the /api/events transport per
docs/api/client-contract/sse.md (reference web/src/hooks/useSSE.ts):

- SseConnection: SseTransport seam (Connected/Event/Failure flow) with the
  okhttp-sse implementation on a dedicated client (readTimeout=0, no cache,
  own dispatcher), buildEventsUrl, Last-Event-ID header on resume, and an
  acceptEncodingIdentity fallback flag for the gzip escape hatch.
- ReconnectPolicy: normative constants + pure backoff schedule (immediate
  first retry, 1s..30s exponential, 300s ceiling after 8 attempts, 0..500ms
  injected jitter).
- SseEngine: per-key (global / session:<id>) connection loops — handshake
  gate on connection-changed{status:connected} with ok/gap resume verdict,
  per-key cursors advanced only after the downstream hand-off
  (at-least-once), 10s connect deadline, 90s staleness watchdog ticking 10s,
  one silent 401 re-auth per cycle costing no backoff attempt, background
  retry deferral + 45s foreground stale check; all timing via injected
  delay/clock for virtual-time tests.
- SyncEventRouter: 13-type union fan-out to the SyncTargets seam (M2 wires
  stores), handshake gap -> requestFullResync, Unknown ignored.

Tests (32): virtual-time engine suite (fake transport + turbine), policy
schedule with seeded jitter, router mapping, and MockWebServer integration
through the real okhttp transport — including proof that gzip SSE frames
surface incrementally (flush-per-event body withheld behind a throttle).
Gzip also verified against a live local hub (Content-Encoding: gzip,
handshake decoded instantly, heartbeat +30.0s mid-stream).
2026-08-17 15:14:55 +08:00
weishu d39197b97d feat(android): markdown/code/diff rendering foundation (B-M2d1)
:core:protocol (pure JVM, all web-transform ports fixture-style tested):
- markdown/MarkdownTransforms.kt: repairTables (remark-repair-tables port),
  disableIndentedCode (enabledBlockTypes minus IndentedCodeBlock == micromark
  disable codeIndented) + canonical parser factory (GFM tables, strikethrough
  requireTwoTildes, autolink), stripCjkAutolinkArtifacts, detectFilePathLinks/
  matchWholeFilePath/rewriteExplicitLinkTarget (remark-file-path-links port)
- markdown/HrefPolicy.kt: Allowed|ConfirmFirst|Blocked scheme policy with
  double-percent-decode + control-char-strip bypass hardening (web parity);
  scheme-less/SPA/protocol-relative fail closed on native
- git/UnifiedDiffParser.kt: full unified diff -> DiffFile/DiffHunk/DiffLine
  (rename/copy/new/deleted/binary/mode/similarity, quoted paths, bare codex
  diffs, tolerant of truncated hunks + wrong LLM counts)
- 56 new JVM tests (repair matrix, parser config, CJK, path detection incl.
  negatives, href matrix, 11 diff scenarios cross-checked vs web semantics)

:app Compose UI:
- ui/markdown/Markdown.kt: transforms -> commonmark AST walk; paragraphs/
  headings/lists (ordered/bullet/task via stripped text markers)/blockquote/
  tables (intrinsic-width grid in horizontal scroll)/thematic break/html
  monospace fallback; unknown fences (mermaid/math) degrade to CodeBlock;
  LinkAnnotation links via LocalMarkdownLinkHandler (onFilePath/onUrl+policy)
- ui/markdown/CodeBlock.kt: header chip + copy, horizontal scroll, highlights
  1.0.0 syntax coloring off-main via produceState with 200-entry LRU keyed by
  (hash, len, lang, dark), 400-line cap then plain
- ui/components/DiffView.kt: hunk headers, +/- tinted rows, dual line-number
  gutters, compact/expand
- ui/theme: HapiExtendedColors (code/diff/table/quote tokens mirroring web
  index.css light/dark/OLED) + pure-black OLED scheme, provided by HapiTheme
- MarkdownPreviewParameterProvider + kitchen-sink @Previews (compile-checked)

deps (Maven Central verified): org.commonmark:commonmark 0.24.0 + gfm-tables/
gfm-strikethrough/autolink exts, dev.snipme:highlights 1.0.0 (jvm variant)

Verified: :core:protocol:test 141 green, :app:assembleDebug green
2026-08-17 15:14:01 +08:00
weishu 8f7468e932 merge: B-M1b Android auth + HapiApi transport 2026-08-17 13:08:23 +08:00
weishu 6f1dc23d77 feat(android): auth + HapiApi transport (B-M1b)
:core:data auth + API transport for track B M1:

- auth/: JwtPeek (unverified {uid,ns,exp} peek for proactive refresh),
  CredentialStore interface + EncryptedPrefsCredentialStore
  (security-crypto) + in-memory impl, HubUrls origin normalization,
  HubRegistry (ordered multi-hub roster + active hub behind a storage
  seam), AuthInterceptor + single-flight TokenAuthenticator (401 ->
  re-exchange -> retry once, Mutex single-flight, terminal AuthEvents,
  ensureFreshToken() for SSE pre-connect)
- api/: HapiApi (plain OkHttp + kotlinx.serialization, one suspend fun
  per v1 endpoint incl. generated-image bytes + multipart transcription
  helper), ApiError with (status, code) per errors.md
- HubSession: per-hub factory wiring clients (timeouts, auth decoration,
  256 MB image cache), DI-free
- :core:protocol wire/: request DTOs (ApiRequests.kt) + response DTOs
  (auth, health, resume/reopen, cancel/steer/queued-state, spawn,
  slash-commands/skills, machine list-directory/paths-exists, uploads,
  transcription)
- tests: 45 unit tests (MockWebServer) — refresh/retry-once, concurrent
  single-flight, terminal 401 paths, ApiError code mapping, request
  shapes (cursors+epoch, deliveryMode, nested answers, explicit nulls),
  JwtPeek/HubUrls/HubRegistry
2026-08-17 13:07:44 +08:00
weishu 1e80327d76 feat(ios): HapiProtocol wire models, catalogs, session patching (A-M1a) 2026-08-17 12:31:34 +08:00
weishu fada270424 docs(api): sse.md — activeTurnStartedAt is not patch-applied (matches reference impl + fixtures) 2026-08-17 12:12:32 +08:00
weishu a3013d56a0 merge: B-M1a Android protocol wire models + catalogs + patching 2026-08-17 12:11:28 +08:00
weishu f110845be6 merge: K6 fixtures batch 2 (events, permissions, sidechains, flavors, catalogs)
# Conflicts:
#	shared/fixtures/README.md
#	web/scripts/fixtures/generate.ts
2026-08-17 12:11:22 +08:00
weishu 36a190e852 merge: K7 SSE patch + pagination scenario fixtures 2026-08-17 12:07:50 +08:00
weishu e1bfdd6265 feat(fixtures): SSE patch + pagination scenario fixtures (K7)
Two new golden-fixture suites, expectations machine-generated from the web
implementation (same source-of-truth principle as the chat suite):

- shared/fixtures/sse/ (12 cases): session-updated versioned-patch
  application via applySessionDetailPatch — strict version gates for
  metadata/agentState/todos/teamState, out-of-order arrival, teamState:null
  clear, max-monotonic updatedAt, flat last-write-wins fields, sub-minute
  activeAt keep-alive drop, scratchlistUpdatedAt trigger, and the pinned
  actual behavior that activeTurnStartedAt is NOT applied by the patch path.
  Inputs are stored schema-normalized and validated against SessionSchema /
  strict SessionPatchSchema at generation time; per-patch applied/unchanged
  verdicts are part of the contract.

- shared/fixtures/pagination/ (11 cases): op scripts driving the real
  message-window store with a scripted ApiClient — latest page + SSE ingest,
  before-cursor older pages, epoch-mismatch reset (window discard + recorded
  internal latest request), reset:true replace preserving optimistic rows,
  localId echo reconciliation, messages-consumed invokedAt stamping (no
  cursor advance), message-cancelled removal, cancel-too-late invoked-row
  ingest, hidden-row cursor advance, 400-row trim preserving queued rows,
  and queued-state gap recovery. Documents pin the exact requests the store
  issued, older-load outcomes, reconcile candidates, and a minimal window
  projection (ids/order, queued/optimistic flags, hasMore, epoch, viewMode,
  compound cursors).

Generator: web/scripts/fixtures/{sse,pagination}/ extend the K5 framework
(canonical serialization reused; suites pruned of stale files). Web
self-conformance tests replay every fixture against the real implementation
in bun run test:web. README documents schemas, projections, replay
contracts, and native consumption for both suites.

No time injection needed: the store's only Date.now() reads gate
notification throttling, which never reaches persisted or projected state.
2026-08-17 12:06:57 +08:00
weishu b09dd67268 feat(android): protocol wire models, catalogs, session patching (B-M1a)
:core:protocol, package app.hapi.protocol — pure-JVM foundations for the
Android client, mirrored from shared/src/schemas.ts + sessionSummary.ts and
docs/api/client-contract/{sse,pagination,messages}.md:

- wire/: shared HapiJson config; DecryptedMessage with tri-state invokedAt
  (custom serializer — explicit null = queued vs absent = legacy-invoked);
  AttachmentMetadata; AgentState + (completed) requests; Session verified
  field-by-field against SessionSchema; typed-subset SessionMetadata;
  SessionSummary/PendingRequest; SessionPatch with VersionedValue wrappers
  and a strict SessionPatches.parse (unknown key / empty / mistyped -> null,
  replicating SessionPatchSchema.strict()); Machine family; the 13-type
  SyncEvent union behind total SyncEvents.parse (unknown/malformed ->
  Unknown, never throws; machine-updated data kept tri-state for the
  removed-vs-refetch distinction); MessagesPage + REST envelopes.
- catalog/: AgentFlavor (known + Other(raw)) with capabilities/labels from
  flavors.ts; PermissionMode/tone table, per-flavor mode lists, codex
  collaboration + copilot agent modes incl. legacy 'fleet' normalization.
- patch/SessionPatching.kt: exact port of web/src/lib/sessionPatch.ts —
  strict > version gates, max-monotonic updatedAt, activeAt <60s
  render-irrelevance, and deliberately NOT applying activeTurnStartedAt /
  scratchlistUpdatedAt (the TS reference never assigns them).

Tests (85 total, all green): golden-fixture decoding over shared/fixtures/
chat/* (fails loudly on zero files or unsupported VERSION), version-gate /
keep-alive cases ported from useSSE.test.ts, full-session + list + messages
page decode, SyncEvent union coverage, catalog pins (TODO(K6): switch to
fixtures/catalogs/modes.json once generated). :app:assembleDebug verified.
2026-08-17 12:06:32 +08:00
weishu c1ecb4798e feat(fixtures): batch 2 — events, permissions, sidechains, flavors, catalogs (K6)
34 new golden chat fixtures generated from the web pipeline, plus a
machine-generated modes catalog:

- Claude output: away_summary/microcompact_boundary/compact_boundary system
  subtypes, <task-notification> summary extraction, multi-block assistant
  message with an array-of-parts (text+image) tool_result
- AgentEvent union completed: switch/message/error/title-changed(+dedupe)/
  limit-warning(pipe text)/api-error folding/turn-duration/compact-summary/
  abort-restore/unknown-type passthrough; thread-goal updated+cleared (silent)
- Permission lifecycle via agentState.completedRequests: approved_for_session
  (mode+allowTools), denied with reason, canceled, AskUserQuestion flat
  answers, request_user_input nested answers (all with explicit createdAt)
- Sidechain: Task tool_use with parentToolUseId-grouped subagent children
- codex family: error, context_compacted, generated-image, review JSON,
  thread-goal messages, CodexBash call/result pair, exploration tool group
- cli-output: cli-origin command block + stdout merge
- agy flavor (output envelope): agy_message, agy_tool_action run_command
  mapping; cursor ACP plan snapshot (copilot skipped: identical codex shapes)
- generator now also emits shared/fixtures/catalogs/modes.json from
  shared/src/modes.ts (per-flavor permission modes with labels/tones);
  self-conformance test covers it; README documents the catalogs contract
2026-08-17 12:04:22 +08:00
weishu c547f652f2 merge: K8 fixtures drift gate + monorepo conventions 2026-08-17 11:48:48 +08:00
weishu 25129c5686 ci: fixtures drift gate + monorepo conventions for native apps (K8) 2026-08-17 11:47:31 +08:00
weishu 81146e68c5 docs(api): drop temporary dead-link ignores now that all contract pages exist 2026-08-17 11:28:14 +08:00
weishu 3885f65ad5 merge: B-M0 Android scaffold 2026-08-17 11:26:09 +08:00
weishu 2ffaf7fb82 merge: A-M0 iOS scaffold 2026-08-17 11:25:53 +08:00
weishu 659fe258ff merge: K4+K5 golden fixtures 2026-08-17 11:25:46 +08:00
weishu 1a40db3b2d merge: K2 client contract docs (sse/pagination/messages) 2026-08-17 11:25:46 +08:00
weishu 68f4f92e48 merge: K1 client contract docs (auth/rest/errors) 2026-08-17 11:25:46 +08:00
weishu d87ce4206e merge: K3 pairing label 2026-08-17 11:25:46 +08:00
weishu 9479f667b5 feat(fixtures): golden chat fixtures generated from web pipeline (K4+K5) 2026-08-17 11:23:34 +08:00
weishu 83bd25aa3b feat(android): scaffold Compose app + core modules + CI (B-M0) 2026-08-17 11:22:35 +08:00
weishu e0e7be39c3 feat(ios): scaffold SwiftUI app + HapiKit package + CI (A-M0) 2026-08-17 11:18:50 +08:00
weishu 1b9ca34fb5 docs(api): add native client contract (sse, pagination, messages) 2026-08-17 11:18:14 +08:00
weishu 16ebba12e8 docs(api): add native client contract (auth, rest, errors) 2026-08-17 11:15:00 +08:00
weishu 4f671453c9 chore(hub): platform-neutral companion pairing label 2026-08-17 11:08:04 +08:00
weishu 12847ddd0f Release version 0.28.0 2026-08-17 10:03:11 +08:00
AnanovoandGitHub d644d4f30f feat(cli): follow conversation language in status prompt (#1584) 2026-08-16 23:06:06 +08:00
AnanovoandGitHub 76e00f0091 feat(hub): add background-only ServerChan fallback (#1608)
* feat(hub): add background-only ServerChan fallback

* fix(hub): validate ServerChan background setting type

* fix(web): preserve pending visibility transitions

* fix(web): guard visibility reports across subscriptions
2026-08-16 23:04:03 +08:00