Skip to content

Add Runtime v2 implementation skeleton - #5

Merged
likun666661 merged 1 commit into
runtime-v2-architecture-docfrom
runtime-v2-implementation-pr
Jun 14, 2026
Merged

Add Runtime v2 implementation skeleton#5
likun666661 merged 1 commit into
runtime-v2-architecture-docfrom
runtime-v2-implementation-pr

Conversation

@likun666661

Copy link
Copy Markdown
Member

Summary

  • add the canonical RuntimeEvent contract and tests under @maka/core
  • add Runtime v2 runtime seams: adapters/model-history, InvocationContext, RuntimeRunner, AgentFlow, and AiSdkFlow
  • wire core/runtime package exports and add implementation notes without replacing SessionManager/AiSdkBackend/AgentRun hot paths

Verification

  • npm run build
  • npm run typecheck
  • npm --workspace @maka/core run test (613/613)
  • npm --workspace @maka/runtime run test (384/384)
  • git diff --check

Notes

This PR is intentionally stacked on runtime-v2-architecture-doc to keep the architecture document PR separate from the implementation skeleton.

@likun666661
likun666661 merged commit dcc6346 into runtime-v2-architecture-doc Jun 14, 2026
jackwener pushed a commit that referenced this pull request Jun 21, 2026
jackwener added a commit that referenced this pull request Jun 21, 2026
…doc-merge)

1. **iframe sandbox/CSP language tightened** (@kenji #1):
   - Remove the "separate webContents partition + permissive CSP for
     iframe" promise — DOM `<iframe>` doesn't get an independent
     partition without `<webview>` / BrowserView. Don't promise what
     the DOM can't deliver.
   - Add `allow-forms` and `allow-modals` to the not-allowed list
   - Switch from `src="file://..."` to `srcdoc={await readText()}` so
     content delivery goes through the path-guard read helper
   - Note that global CSP `default-src 'self'` stays locked — sandbox
     attribute carries all isolation

2. **Symlink-aware path guard** (@kenji #2):
   - Both `realpath(artifactRoot)` AND `realpath(target)` required,
     then containment check
   - Mirror PR56 open-path-guard which already has symlink-escape +
     `..` + URL-scheme test coverage

3. **readBinary MIME allow-list** (@kenji #3):
   - Allowlist: image/png/jpeg/gif/webp/svg+xml, application/pdf
   - MIME sniffed at read time, NOT trusted from record.kind or
     record.mimeType
   - Unknown → `reason: 'unsupported_mime'` + UI shows metadata +
     "在 Finder 中打开" / "另存为" actions (no raw bytes to renderer)

4. **Deleted tombstone blocks reads** (@kenji #4):
   - `status: 'deleted'` records immediately return `reason: 'deleted'`
     from readText / readBinary — even if the underlying file still
     exists on disk
   - Surface table updated: deleted row preview shows "此 artifact 已
     删除,预览已停止"
   - Permanent path purge is a separate Settings · 数据 flow

5. **PR108c runtime hook scope** (@kenji #5):
   - First version does NOT do LLM extractor
   - Only deterministic outputs: Write→file record, Edit→diff record,
     Bash with explicit redirect → file record
   - stdout/stderr NOT auto-promoted (noise)
   - LLM extractor (parse ```html``` from assistant message) deferred
     to PR109+

Also: type rename `storagePath` → `relativePath` to enforce contract
in the type system; full reason union widened on both read helpers.

doc total: 1416 lines.
jackwener added a commit that referenced this pull request Jun 21, 2026
…inese-first labels, stub views for non-implemented modules

Implements the IA refactor WAWQAQ locked in msg `b86b47d1`: the
sidebar's second part is now a top-level 5-module nav (会话 / 搜索 /
计划 / 技能 / 每日回顾) rather than session filter tabs. Pinned /
Archived / Chats become internal Sessions filters. Settings stays at
the bottom; a placeholder 版本更新 button sits above it.

Per xuan `47e204f2` priorities applied directly:
  - #2 IA: top-level modules; filters moved into Sessions.
  - #4 visual hierarchy: nav rows reuse `.maka-nav-row` (transparent
    bg, accent-tinted selected state, no card stacking).
  - #5 Chinese-first naming throughout the surface; English stays
    as accessible keywords in the command palette.
  - #7 Search nav item placeholder for Phase 4 modal; clicking now
    switches the section to `'search'` and renders an empty-state
    stub. Phase 4 will swap the section switch for an
    `onOpenSearchModal()` callback (already plumbed through props).
  - #8 disabled / hover / focus states pinned for the Update
    placeholder (aria-disabled + data-disabled + CSS dim).

== Type-level changes ==

`packages/ui/src/components.tsx`:

  export type NavSelection =
    | { section: 'sessions'; filter: SessionFilter }
    | { section: 'search' }
    | { section: 'automations' }
    | { section: 'skills' }
    | { section: 'daily-review' };

  // New module label map (Chinese-first):
  const MODULE_NAV_LABEL: Record<NavSelection['section'], string> = {
    sessions: '会话', search: '搜索', automations: '计划',
    skills: '技能', 'daily-review': '每日回顾',
  };

== SessionListPanel JSX shape ==

  <aside>
    <header>新建对话 button</header>
    <nav.maka-sidebar-modules>          5 module nav rows  (NEW)
    <div.maka-session-filter hidden={!sessions}>      Pinned/Chats/Archived
    <div.maka-session-search hidden={!sessions}>      session-name search
    <section.maka-session-list>
      ├ skills view (existing, reused for `selection.section==='skills'`)
      ├ sessions view (existing, when 'sessions')
      └ STUB_VIEWS[section] (search / automations / daily-review)
    <footer.maka-session-panel-footer>
      ├ 版本更新 button (NEW; UI placeholder, aria-disabled by default)
      └ 设置 button (existing; renamed from "Settings")
  </aside>

Skills no longer appears in the footer — it lives in the top-level
module nav alongside the other 4. The chat surface (`ChatPanel`)
shows a neutral "从 会话 选择对话" message when the active section
is `search` / `automations` / `daily-review`.

== Persistence ==

`apps/desktop/src/renderer/main.tsx readNavSelection`: recognizes
`search` / `automations` / `daily-review` as valid section variants
when restoring from localStorage. Corrupted entries fall back to
`{ section: 'sessions', filter: 'chats' }`.

== CSS ==

`apps/desktop/src/renderer/styles.css`:

  .maka-session-panel grid-template-rows:
    auto auto auto auto minmax(0, 1fr) auto;

  /* Row 2 is the new module nav. Phase 1's `.maka-session-list` grid
   * fix (auto + minmax(0, 1fr)) and `.maka-list-stack` overflow:auto
   * are preserved verbatim. */

  .maka-sidebar-modules { display: grid; gap: 2px; padding: 4px 8px; }

  /* Disabled nav-row dimming (used by 版本更新 placeholder). */
  .maka-nav-row[aria-disabled="true"],
  .maka-nav-row[data-disabled="true"] { cursor: not-allowed; opacity: 0.55; }
  .maka-nav-row[aria-disabled="true"]:hover,
  .maka-nav-row[data-disabled="true"]:hover { background: transparent; }

== Tests ==

  core 392 / storage 50 / runtime 103 / desktop 645 = 1190 pass.
  Typecheck + build green across all workspaces.
  Phase 1's CSS contract test still passes (grid + min-height + overflow
  pins survive the IA refactor).

== Out of scope (Phase 3 / 4) ==

  - Session row slimming (~85px → ~32px). Phase 3.
  - Search modal implementation. Phase 4 (resurrects `useThreadSearch`
    from `yuejing/pr-search-2.6`; swaps current Search stub for modal
    trigger).
  - Real auto-update wiring (Electron updater). Future PR-AUTOUPDATE-0.
  - Automations / Daily Review feature implementation. Future
    PR-TIME-* / PR-DAILY-*.

Anchors:
  WAWQAQ msg `b86b47d1` + `f5f6f834` (IA + Skills inclusion + Update button).
  xuan msg `47e204f2` (8-point priority list); `dc790a54` (Phase 1 scope refinement);
  `adcf0c95` (per-phase gate workflow).
  kenji msg `fcd9c54f` (consolidated packet design); `0f7bb872` (scroll P0 architecture).

Branch base: `655a09f` (Phase 1 + fixup, on top of main `809875a`).
jackwener added a commit that referenced this pull request Jun 21, 2026
…l + 版本更新 passive (4 nits + capability state CSS)

WAWQAQ msg `1893c08e` 没动静 → starting PR-UX-POLISH-1 in parallel
with kenji's PR-DESKTOP-SMOKE-0 instead of waiting on the Sidebar
resize verdict. Commit lands on top of `yuejing/pr-sidebar-ia-0`
so it merges as a stack once Sidebar is greenlit.

Per ratified scope (yuejing UX audit msg `9c779b56` + xuan +
kenji boundary 1 closed-state enum):

1. Filter input shortcut hint corrected:
   `筛选会话…  F 聚焦` → `筛选会话…  ⌘F 聚焦`
   The actual binding is ⌘F (Meta+F) via the existing
   `useEffect` in `SessionListPanel`; the lone `F` placeholder
   was misdocumenting the chord.

2. Composer streaming hint disambiguated from reasoning stream:
   `Maka 正在思考…` → `Maka 正在回答…` (zh)
   `Maka is thinking…` → `Maka is responding…` (en)
   ReasoningPanel still uses `正在思考…` / `Thinking…` for the
   model's extended-thinking stream. Distinct signals now have
   distinct copy — composer = output-streaming;
   ReasoningPanel = reasoning-streaming.

3. Sidebar group label visual gap:
   Without `display: flex; gap` on `.maka-list-group-label`, the
   `<span>label</span><span>count</span>` rendered as `会话65`
   (no separator). Added `display: flex; align-items: baseline;
   gap: 6px` so the rendered form is `会话 65` (or with future
   bullet separator: `会话 · 65`).

4. 版本更新 footer entry switched from aria-disabled fake-button
   to passive `data-state="coming_soon"` informational tag with
   small `即将推出` badge. Removes the "looks tappable but does
   nothing" affordance. When `onOpenUpdate` IS wired (future
   PR-AUTOUPDATE-0), the button form returns automatically.

   This is the first integration of the capability presentation
   enum (kenji boundary 1, msg `88a9f29d`): UI consumes the state
   via props, never derives `operational` from token/config
   presence. CSS adds `.maka-nav-row[data-state="coming_soon"]`
   styling + `.maka-nav-row-state-badge` pill for the "即将推出"
   chip. The closed enum
   (`available / coming_soon / needs_config / blocked_by_permission /
   disabled_by_policy / unsupported_platform`) is documented in
   `notes/pr-ux-polish-1-gate.md`.

Eyebrow icon variety (audit P2 #5) skipped — re-examined source
and setup heroes already use varied icons (SettingsIcon, KeyRound,
Cpu, AlertCircle via SetupHero); only NeedsConnectionHero and
ReadyEmptyHero still use Sparkles which is correct for those
states.

Stale pill tone (audit P1 #4) skipped — already amber-toned
(`--warning` token at 0.18 alpha + warning-text); the only weak
signal is the 0.7 opacity row dim, which is the intentional
secondary signal.

Gates:
- typecheck clean, UI + renderer build clean
- desktop tests: 643 pass / 0 fail (no test additions yet —
  capability enum contract test arrives in commit 2 alongside
  the search-service rename + daily-review consolidation +
  permission-center cleanup + modal backdrop token unification)
- core tests: 392 pass

Subsequent commits in this PR (commit 2+):
- Permission Center "即将可用" hide / change to passive copy
- Settings · 搜索服务 → 联网搜索 rename
- Daily Review Settings sub-page consolidation
- Modal backdrop token unification
- `? 快捷键` sidebar chip (small a11y discoverability add)
- Extend visible-copy-hygiene-contract with capability enum
  presentation gate (no `coming_soon` should render as <button>
  without explicit `data-state="coming_soon"`)
jackwener pushed a commit that referenced this pull request Jun 21, 2026
jackwener added a commit that referenced this pull request Jun 21, 2026
…doc-merge)

1. **iframe sandbox/CSP language tightened** (@kenji #1):
   - Remove the "separate webContents partition + permissive CSP for
     iframe" promise — DOM `<iframe>` doesn't get an independent
     partition without `<webview>` / BrowserView. Don't promise what
     the DOM can't deliver.
   - Add `allow-forms` and `allow-modals` to the not-allowed list
   - Switch from `src="file://..."` to `srcdoc={await readText()}` so
     content delivery goes through the path-guard read helper
   - Note that global CSP `default-src 'self'` stays locked — sandbox
     attribute carries all isolation

2. **Symlink-aware path guard** (@kenji #2):
   - Both `realpath(artifactRoot)` AND `realpath(target)` required,
     then containment check
   - Mirror PR56 open-path-guard which already has symlink-escape +
     `..` + URL-scheme test coverage

3. **readBinary MIME allow-list** (@kenji #3):
   - Allowlist: image/png/jpeg/gif/webp/svg+xml, application/pdf
   - MIME sniffed at read time, NOT trusted from record.kind or
     record.mimeType
   - Unknown → `reason: 'unsupported_mime'` + UI shows metadata +
     "在 Finder 中打开" / "另存为" actions (no raw bytes to renderer)

4. **Deleted tombstone blocks reads** (@kenji #4):
   - `status: 'deleted'` records immediately return `reason: 'deleted'`
     from readText / readBinary — even if the underlying file still
     exists on disk
   - Surface table updated: deleted row preview shows "此 artifact 已
     删除,预览已停止"
   - Permanent path purge is a separate Settings · 数据 flow

5. **PR108c runtime hook scope** (@kenji #5):
   - First version does NOT do LLM extractor
   - Only deterministic outputs: Write→file record, Edit→diff record,
     Bash with explicit redirect → file record
   - stdout/stderr NOT auto-promoted (noise)
   - LLM extractor (parse ```html``` from assistant message) deferred
     to PR109+

Also: type rename `storagePath` → `relativePath` to enforce contract
in the type system; full reason union widened on both read helpers.

doc total: 1416 lines.
jackwener pushed a commit that referenced this pull request Jun 21, 2026
jackwener added a commit that referenced this pull request Jun 24, 2026
…ts (#211)

Kenji aesthetic-audit reminder 9 (msg `bacde601` 2026-06-24,
finding #5): three streaming-state indicator dots have the same
semantic ("live work happening") but their pulse amplitudes drifted:

- `.maka-list-row-streaming-dot` keyframe at styles.css:2231 — `scale(1.1)`
- `.maka-reasoning-panel-dot` keyframe at styles.css:11645 — `scale(1.15)`
- `.maka-tool-output-stream-pulse` keyframe at styles.css:12634 — `scale(1.18)`

The amplitudes pile up when more than one is visible at the same
time (e.g. user in a turn that's streaming text AND a tool call is
in flight) and the screen reads as agitated.

Fix: settle all three to `scale(1.1)` — the calmest of the three
because these dots run continuously while a turn is streaming and
the larger pulses are visually loud at that frequency. List-row dot
was already at 1.1 so this is a unification at the existing low end,
not a raise.

Single file, two keyframe value tweaks. Existing PR-202 styles.css
edits are in different regions (onboarding rules + cubic-bezier
sweep on transition values, not @Keyframes scale values), so no
overlap.
jackwener added a commit that referenced this pull request Jun 25, 2026
Per @kenji audit `26a221be` against #237 main `4e0247f6`:

**#1 — bottom 「想先看看效果?」 hero buttons left-aligned.**
PR #236 right-aligned the top 「打开每日回顾」 button via
`.settingsFeatureStatusHeroActions` but the bottom 「生成每日回顾 /
生成深度分析」 row still used inline `style={{ display: 'flex', gap:
8, marginTop: 8 }}`, so it stayed glued to the paragraph margin.
Route the bottom row through the same class. Add `gap: 8px` to the
class so multi-button clusters keep proper spacing.

**#2 — 执行时间 disabled when the master switch is off.**
The disable condition was `formDisabled || savingKey === 'executeTime'
|| !(effectiveConfig?.enabled ?? false)`. That last clause blocked the
common UX of "pick a time first, then turn it on". Drop it — the time
value is harmless to edit while disabled (it doesn't fire until the
switch flips), and the disabled state was misread as "broken UI".

**#3 — 分析模型 default-row label still verbose.**
WAWQAQ's directive: "所有模型选择只用模型名". PR #236 went from
`使用对话默认模型(Codex OAuth · email · gpt-5.5)` to
`对话默认(gpt-5.5)` — better, but the model id parenthetical still
mixes "selectable model" with "follow chat default". Per @kenji's
"don't conflate the two semantics", the default row becomes plain
`跟随对话默认`; the explicit options carry the model id.

**#4 — duplicate model ids across connections looked unselectable.**
When two enabled connections both expose `gpt-5.5`, the flat
`model`-only labels read as two identical entries. Detect the collision
at build time and append `· ${connection.name}` ONLY to the colliding
entries; unique model ids stay terse per WAWQAQ's directive.

Deferred (with @kenji concurrence):
- **#5** (`data-control-width` semantic API replacing CSS-selector width
  patches) — pure refactor, no end-result change. WAWQAQ explicitly
  said 「只看最后的结果」.
- **#6** (collapse Daily Review hero card pile into a single grouped
  settings surface) — bigger visual restructure judgment call; needs
  WAWQAQ sign-off before swinging at the whole page layout.
jackwener added a commit that referenced this pull request Jun 25, 2026
…#244)

Bundled response to @kenji audit findings #5 + #6 (msg `26a221be`)
plus the page-restructure WAWQAQ explicitly authorized
(thread msg `afbe542d`: 「一次性多改,别他妈分这么多轮」).

**Finding #6 — Daily Review settings page restructure.**
Removed the two `.settingsFeatureStatusHero` cards (intro + 「想先看看
效果?」). They were promo chrome on a workbench settings surface and
duplicated information the section banner already carries. The
「打开每日回顾」 / 「生成每日回顾」 / 「生成深度分析」 actions are now
folded into a single right-aligned `.settingsPageFooterActions`
toolbar rendered below the rows. Net: one banner, one grouped rows
surface, one action footer — no card pile.

The `.settingsFeatureStatusHero*` CSS rules stay because the voice
settings page still uses them; this PR just drops Daily Review's
dependency on them.

**Finding #5 — semantic width attr instead of class-selector patches.**
`.settingsRow > .settingsTimeInput` and
`.settingsRow > .settingsBaseSelectTrigger` were globally-scoped
selectors that implicitly assumed the row contained one specific
control type. Move the contract to a `data-control-width="compact|
select"` attribute on the row itself; mark the 执行时间 and 分析模型
rows accordingly. The old global rules are gone — only the data-attr
rules survive.

Verified the 4 other `SettingsSelect` call sites (proxy host, gateway
host, bot domain, usage filter) are inside `.settingsField`, not
`.settingsRow`, so they aren't affected by the dropped rule.
jackwener added a commit that referenced this pull request Jun 25, 2026
@kenji audit thread `#my-ai:4821a792` msg `e4cfbfb0` vs main `71d88454`.
WAWQAQ msg `782a1663` + `继续`: bundle these, don't split.

**#1 — Memory page top control rows had no outer card surface.**
The three control rows (本地 MEMORY.md / 模型上下文可读取 / 项目指令
文件) were direct children of `.settingsStructuredPage`, which is
transparent. `.settingsFormRow` only carries a hairline bottom border,
so users saw three floating rows without the grouped-card visual every
other settings surface uses. Wrap them in `.settingsRows` so they
inherit the 12px outer border + radius + overflow:hidden treatment.

**#2 — Plan Reminder delivery picker showed text-only IM channels.**
Settings → 机器人对话 switched to real brand logos (Iconify simple-icons)
in #BOT-SETTINGS-UI-0, but the chat-side Plan Reminder delivery flow's
`平台` select was still rendering plain Chinese labels. Same channels
should read the same everywhere.
- Move `BOT_BRAND` to `packages/ui/src/bot-brand.ts` so both surfaces
  share a single brand-metadata source instead of one duplicating the
  other.
- Extend `PlanReminderSelect` to accept a third tuple slot (optional
  `ReactNode` icon); `PlanReminderPanel` now passes an `<IconifyIcon
  icon={brand.iconifyId}>` per provider with the existing offline
  glyph fallback.
- Update `SettingsModal.tsx` to import `BOT_BRAND` from `@maka/ui`
  instead of declaring a local copy.

**#5 — Two inline `style={{ marginTop }}` spacing patches.**
- `Alert variant="error" style={{ marginTop: 12 }}` → `className=
  "settingsSurfaceAlert"`.
- `<div className="settingsActionRow" style={{ marginTop: 8 }}>` →
  `className="settingsActionRow settingsNoticeAction"`.
The new classes pin the spacing decision in CSS where the design
tokens live, instead of inline JSX.

Deferred per kenji's own scope note + WAWQAQ 「只看最后的结果」:
- **#3** (`RadioCard` / `ChoiceCard` primitive for theme/palette picker)
  — pure design-system refactor, no visible delta on Settings → 外观.
- **#4** (`SettingsSelect` uplift to rich-option contract) — bigger
  cross-cutting refactor; current fix covers the immediate model-name
  and disambiguation issues kenji raised in round 1.
- **#6** (layout-property transitions in sidebar/tabs/accordion) —
  kenji explicitly flagged these as visual-smoke-gated; will land
  alongside the smoke config in a separate PR.
jackwener added a commit that referenced this pull request Jun 25, 2026
… dvh (#250)

@kenji audit msg `232aec0f` (`#my-ai:c28a6293`). Round 3 against
`af66ebb7` post-#249.

**#2 — Plan Reminder select collapsed state was text-only.**
PR #247 added a brand icon to each `<SelectItem>`, but `<SelectValue
/>` defaulted to rendering just the label string — open dropdown
showed a logo, collapsed trigger showed plain text. Build a
`value → { label, icon }` lookup inside `PlanReminderSelect` and pass
a function-child to `SelectValue` so the picked state renders the same
icon + label row the dropdown items render.

**#6 — `100vh` residue in renderer CSS.**
Four sites still on `100vh`:
- `maka-tokens.css:760` `.maka-shell` `height`
- `maka-tokens.css:1941` `.maka-modal` `max-height: calc(100vh - 80px)`
- `styles.css:5028` `.maka-help-modal` `max-height: calc(100vh - 96px)`
- `styles.css:13121` `.maka-onboarding-stack` `min-height: calc(100vh - 84px)`

Replace each with `100dvh` so Electron browser frames with dynamic
chrome (or a future mobile-style viewport) don't get half a viewport
of height calculation drift. Other renderer surfaces already use
`100dvh`; this brings the legacy four into line.

Deferred per kenji's own scope:
- **#1** feishu/dingtalk official-brand-kit sourcing — separate PR.
- **#3 / #4** SettingsSelect / Segmented / ChoiceCard primitive
  unification — design-system refactor; no end-result delta on its
  own.
- **#5** layout-property transitions in sidebar/tabs/accordion —
  kenji explicitly gated on visual smoke harness.
anaconda110 added a commit to anaconda110/maka-agent that referenced this pull request Aug 3, 2026
… (P1 apache#5)

The previous MainApplication.kt diverged from the RN 0.79.7 official template
(@react-native-community/template@0.79.7) in ways that risk compile/runtime
failure under the bridgeless ReactHost path:

- reactNativeHost was a plain ReactNativeHost anonymous object. The
  getDefaultReactHost(context, reactNativeHost) overload (DefaultReactHost.kt:287)
  requires a DefaultReactNativeHost (it does `require(reactNativeHost is
  DefaultReactNativeHost)`), so this would throw at runtime. Switched to
  DefaultReactNativeHost, overriding isNewArchEnabled/isHermesEnabled from
  BuildConfig so the host reports the correct engine/arch flags.
- getReactHost() was declared as `override fun getReactHost(): ReactHost`,
  but ReactApplication#reactHost is a property (`val reactHost: ReactHost?`).
  Changed to `override val reactHost: ReactHost` (Kotlin property form), matching
  the template and the interface.
- MainApplication did not implement ReactApplication. Added the interface so
  the RN gradle plugin / tooling can locate the host via the standard contract.
- SoLoader.init used the legacy 2-arg SoLoader.init(this, false). Switched to the
  template's SoLoader.init(this, OpenSourceMergedSoMapping), which is the
  0.79.7 public API for so-merging JNI_OnLoad registration.
- DefaultNewArchitectureEntryPoint.load() is now imported statically and called
  as load(), matching the template; the IS_NEW_ARCHITECTURE_ENABLED gate is
  preserved (P0 apache#4).

Aligns with .hive/android-code-review.md P1 apache#5.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant