c7057d0fd2b9f97f2da5ebf700bb36bab18c1285
472
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c7057d0fd2 |
refactor(dashboard): delete redundant company-status line + arbitrary animation cap
Two subtractions surfaced by the fresh Jony+Jobs pass on the Dashboard:
- The "Company status line" at the top of the dashboard showed three
counters: `{activeAgents} agents active · {activeTasks} tasks in
progress · {completed} completed`. The 4-card MetricsGrid directly
below it already rendered the same agent count and active-tasks
count as its primary tiles. Seeing the same number in vertical
succession is the cheapest way to tell users "we weren't sure
what to show." Deleted the whole status-line block + the companyName
prop + the now-unused caller wiring in OperationsSections.
- The Active Work list rendered a `animate-ping` halo on the first
two running runs (`i < 2`) and skipped it for the rest. Arbitrary —
either the ping is a meaningful "I'm active" signal for every
running run, or it is decoration. Dropped the ping entirely; the
solid green dot on the robot icon is already enough to say
"running," and losing the double-layered animation lets the
dashboard feel calm instead of twitchy.
Dead i18n keys removed alongside (zh + en):
dash.agentActive / dash.agentsActive / dash.taskInProgress /
dash.tasksInProgress / dash.completedCount.
Typecheck clean.
|
||
|
|
50685b952c |
refactor(agent-form): add tab navigation inside Edit modal; pair short fields in 2 columns; delete dead hidden input
The Edit modal was still a vertical scroll of 5 stacked sections (12+
form fields + an embedded Heartbeat card + action bar) even after C5
named the sections. A modal is a transient affordance — making the
user scroll inside it was the wrong design. Same IA move we made for
the Settings page one round ago: add tabs.
Tabs inside the modal:
Identity | Runtime | Budget & scope | Behavior
Each tab swaps the pane. Tab label carries the section context, so
every section body lost its SectionHeader wrapper — the nested
"card-in-a-card" look around HeartbeatSection is gone; Behavior tab
just renders the heartbeat controls directly.
Field-level polish bundled in the same pass because it hit the same
render paths:
- Identity tab: Name + Role paired in a 2-column grid; Department +
Reports To paired. Title spans full width because its values are
often longer. The modal no longer grows tall just to fit two short
inputs side by side.
- Runtime tab: Status stands alone at the top (high-frequency
control — pause/resume). Connector + Model paired; Thinking Effort
+ Workspace paired. Each paired row keeps its inline hint (connector
issue / effort-not-supported) tied to the field it belongs to,
instead of appearing as loose paragraphs between fields.
- Budget tab: Budget input + BudgetBar + burn line cluster into one
grouped column so typing a new cap updates the visual context in
place.
- Behavior tab: HeartbeatSection rendered at the tab root — no
SectionHeader wrapper around it. The card's "Continuous Execution"
title + Wake-now button now read as the primary content of the
tab, not as something buried beneath a header.
Also removed:
- `<input type="hidden" data-agent-id={agent.id} />` in RuntimeSection
— dead code, zero semantic purpose, leftover from an earlier
experiment. Caught during the audit.
- `SectionHeader.tsx` — no more callers after sections stopped
wrapping themselves in it. File deleted.
Added i18n keys (zh + en):
agent.form.tabsAria.
Tab-label copy reuses the existing `agent.form.sectionIdentity`,
`sectionRuntime`, `sectionConstraints`, `sectionBehavior` keys —
same strings that the removed SectionHeader rendered, now carried
by the tab labels.
Covers EJ1 / EJ2 / EJ3 / EJ4 / EJ5 / ES1 / ES3 / ES4 from the
second-round Edit-modal audit.
|
||
|
|
1933fd6192 |
refactor(settings): relocate profile-path + secret-backend diagnostics into Data tab
Profile path and secret backend are **read-only diagnostic facts** — they tell you which directory the profile lives in and which keystore ciphertexts are stored against. They are not preferences; they do not belong in the General tab alongside the theme / language / auto-update controls. Moved both into a new "Profile" group inside the Data tab, rendered via a small `ReadOnlyRow` helper so both pieces of information share consistent bordered-card styling and get a clear label/value split. General tab now contains only the three actual preferences (theme, language, updater) + the inline updater hint. The previous General rendering dropped profile path as raw mono text under the updater checkbox — visually tangled with the preference control. With diagnostics in Data and preferences in General, each tab reads with a single purpose. No new i18n keys required — reused `exec.profile` / `exec.profilePath` / `exec.secretBackend`, which were already translated in zh + en. Follow-up polish; no behavioral change. Typecheck + lint pass. |
||
|
|
03bcc8a8f0 |
feat(settings): add top-row tab navigation; fold Providers + mode + secrets into one IA
The settings page was a single vertical scroll containing seven unrelated concerns. Users had to scroll past theme preferences to reach Providers — which is the most-touched surface — and mode + secrets + shortcuts lived below that. There was no signal about where anything was. Introduced `SettingsShell` with a horizontal tab row along the top: General | Execution mode | Providers | Shortcuts | Data | Secrets Each tab swaps the content pane. Active tab is visually anchored with a 2px primary underline + foreground text; inactive tabs stay muted with a hover promotion. All tabs are real `role="tab"` + `aria-selected` so screen readers announce the current pane; the nav has `aria-label="Settings categories"`. Tab state is local — settings are ephemeral navigation, no reason to persist across company switches or app restarts. Default is "general" because that's where first-time users land. Refactor consequences: - `OperationsSections.tsx` now renders `<SettingsShell />` + the OAuth prompt modal portal; the old double-Panel wrapper with "Profile and release" + "Providers" titles is gone. The "Profile and release" label always fought with what was actually inside the pane — Jobs audit item S-S2 addressed. - Providers moved from a separate sibling Panel into the main IA. Previously Providers came AFTER a 7-section scroll; now it is a peer of General / Mode / etc., reachable in one click. - Deleted the now-unused `SettingsPane.tsx` orchestrator. Its single-column rendering survived only as a composition step that SettingsShell replaces. Added i18n keys (zh + en): settings.tabs.general / mode / providers / shortcuts / data / secrets; settings.tabsAria. Covers J-S2 / J-S7 / S-S5 / S-S7 from the settings audit. |
||
|
|
16e45b6a8d |
refactor(settings): split 73-line god file into 5 sections + auto-save preferences + guard Restore
The previous SettingsPane.tsx hid 8 unrelated concerns behind 73 "lines"
of JSX where each line ran 400-2000 chars. Reading the file was
infeasible; editing it meant scrolling horizontally through a single
statement spanning every button, modal, and table row.
Split by concern into `settings-sections/`:
SettingsPane.tsx 62 LOC orchestrator
GeneralSection.tsx 94 LOC theme / language / auto-update
ExecutionModeSection.tsx 105 LOC AI CEO ↔ AI Co-Founder toggle
ShortcutsSection.tsx 40 LOC keyboard reference (read-only)
DataSection.tsx 123 LOC update check / backup / restore /
secret backend
SecretsSection.tsx 102 LOC per-company secret bindings
Every file now reads top-to-bottom with normal indentation.
Behavioral changes bundled (they belong with the restructure, not a
separate pass):
- Preferences save model unified: theme already auto-saved on change;
language and auto-update checkbox now do the same. The global
"Save settings" button is gone — there is no longer a state where
a toggled language sits unpersisted waiting for a second click.
- Execution mode card gained a subheader line that names the current
company ("Applies to current company: X"). Multi-company users can
no longer confuse which company they just flipped.
- Auto-updater checkbox gained an inline hint: "Check for application
updates at launch" — the previous naked checkbox never explained
what happens when it is off.
- Restore profile moved into DataSection's backup/restore group,
given `tone="danger"`, and gated behind a `window.confirm` with
copy warning about running work being lost. Backup stays neutral.
The previous layout put Restore in the same 2-col grid as Save +
Check Updates + Backup, making a profile-replacing operation look
as routine as saving a theme preference.
- Secret backend display promoted from a naked text line to a
bordered card so it matches the surrounding section rhythm.
Added i18n keys (zh + en):
settings.updaterHint / settings.modeScope / settings.backupRestore /
settings.restoreConfirm / settings.restoreHint.
Covers J-S1 / J-S6 / S-S1 / S-S2 / S-S3 / S-S4 from the settings audit.
Remaining work (tab sidebar, Providers IA reordering, copy sweep)
lands in follow-up commits.
|
||
|
|
fd6471988a |
refactor(app): remove Console feature entirely
Per user request: the "显示控制台 / Show console" button in the top-right header and the bottom drawer it opened are no longer needed — the Runs section already renders the same log tail in-page via RunDetail, so the drawer was a duplicate surface. Deleted the entire feature instead of leaving a hidden toggle that attracts bugs. Removed files: - src/renderer/views/ConsoleView.tsx (126 LOC) Removed renderer integration points: - App.tsx: header "Show/Hide console" button, ConsoleView render, setConsoleOpen(true) call after startRun, TerminalWindow icon import - views/sections/ExecutionSections.tsx: onOpenConsole callback, setConsoleOpen subscription - components/runs/RunCenter.tsx: onOpenConsole prop (removed from forwarding chain) - components/runs/RunDetail.tsx: onOpenConsole prop + the Lightning "Open console" ActionButton. The cancel-run button remains as the sole Panel action now that the console entry point is gone. - hooks/useUIController.ts: consoleOpen / setConsoleOpen subscription - hooks/useDesktopEvents.ts: "toggle-console" shortcut dispatcher - state/slices/navigation.ts: consoleOpen field, setConsoleOpen, toggleConsole actions, and the two auto-open-on-navigate triggers that used to flip the drawer when jumping to a run Removed main-process integration: - main/desktop-shell-menu.ts: "Toggle Console" menu item + CmdOrCtrl+` accelerator - main/desktop-shell-window.ts: onShortcut union type narrowed - shared/types/events.ts: ShortcutEvent action union narrowed Removed i18n keys (zh + en): - app.hideConsole / app.showConsole - console.title / console.noRuns / console.close - common.openLog / common.noRunSelected / common.waitingForLog - runs.openConsole Log capture itself (logBufferRef in useCompanyController + run-log events) is unchanged — the Runs section still displays it. Nothing was moved to a "disabled but present" state; the feature is out of the tree entirely. Typecheck and lint pass. |
||
|
|
dfebced29f |
refactor(agent-form): hide Skills section from Edit modal
Per user request: remove the Skills toggle list from the agent Edit
modal. Skills stay accessible through their own dedicated Skills
section in the command palette (paletteOnlyNavDefs in useUIController),
so nothing is actually gated away — the Edit modal just stops
double-rendering the control.
Also removed the unused SkillsSection.tsx file since nothing else
references it (added only in C5 / commit
|
||
|
|
3608578ba3 |
refactor(communication): localize ChannelHeader strings; bump tiny chips from 9px to 10px
Stragglers from the Communication audit that did not fit into C10's
FilterBar scope:
- ChannelHeader carried two hardcoded English strings: the sidebar
reveal button's aria-label ("Show sidebar") and the "Open task"
link that appears on task-discussion channels. Both now go through
i18n — added `comm.showSidebar` ("显示侧边栏" / "Show sidebar") and
reused the existing `comm.openTask` key. Users running in Chinese
now get a fully Chinese header.
- MessageBubble had two 9px uppercase chips (department badge and
priority badge). 9px with tracking is unreadable at standard display
density; bumped both to 10px and loosened the tracking slightly so
the letters don't collide. Same typography decision we made for
the OrgChart connector label in C3.
Covers the remaining Communication audit items. J-C1 (resizable
sidebar) and J-C6 (resizable ThreadPanel) are feature additions and
stay out of scope per the audit's "bug fix only" contract.
|
||
|
|
ab731e64d2 |
fix(onboarding): actually probe shell PATH for node; stop returning Electron's bundled version
The `checkNodeInstalled` IPC handler was hardcoded to
`{ installed: true, version: process.version }`. That meant:
1. The NodeCheckStep in onboarding ALWAYS passed, regardless of what
was actually on the user's machine.
2. The "Node.js vX.Y installed" success banner showed the **Electron
runtime's** bundled Node version, not the one the agent's bash
tool will invoke. A user with no shell Node saw "Node.js v22.x
installed" and moved on, then every agent bash call that executed
`node -e 'fetch(...)'` (the pattern the API prompt-builder strongly
recommends in `api-docs/header.en.md`) crashed at runtime.
Replaced the stub with an `execFile("node", ["--version"])` probe that
runs in the same PATH the bash tool will later inherit. Added a 3s
timeout so a hung PATH entry doesn't stall onboarding. On failure the
handler returns `{ installed: false, version: null }` — the existing
NodeCheckStep already renders the platform-specific install
instructions (brew / winget / nvm) in that case.
No UI change; the fake-pass was the bug. Covers the onboarding audit
item about misleading health checks.
|
||
|
|
e89fe043f7 |
refactor(providers): split displayHint from status color + normalize type scale
Two providers-panel audit items landed together because they both
come down to "the eye should have to track one piece of status info
in one place":
- ProviderRow displayHint (e.g. "sk-ant-...abcd", "claude.pro@gmail.com",
"AWS profile: prod") was color-coded by credential status. That made
the hint itself read as green/amber/red — e.g. an active connection
would render its hint in success green, which looked like the hint
was announcing its own success. The dot on the left already carries
the status signal; the hint should just identify which credential we
are looking at.
Split the second line so the hint is always muted-foreground. When
status is anything other than `active` or `not_configured`, we
append a colored status label after a separator ("· Expired") so
the hint stays readable while the anomaly still surfaces.
- Type scale cleanup: the group header and count used `text-[10px]
tracking-[0.12em]` which was the smallest text on the page. Bumped
to `text-[11px] tracking-[0.08em]` to match the section-header
rhythm established in the agent Edit form (commit
|
||
|
|
bbfb02fcdd |
refactor(console): boost log contrast + dedupe empty state
The Console panel (the bottom drawer opened with `显示控制台` / `Show console`) had two issues the audit flagged as "not readable by a normal user": 1. Log output was rendered in `text-muted-foreground` — roughly 45% contrast on dark backgrounds. Terminal output needs high contrast because users scan for error keywords at a glance. Boosted to `text-foreground/90`, which tracks the theme's foreground color so logs read clearly in both dark and light themes. Run meta values (Started / Finished / Cost) also promoted from muted to foreground; the field labels stay muted so the value is the emphasized element. 2. Empty state "No run selected" rendered TWICE — once in the left meta pane, once in the log pane. Restructured: when no run is selected, hide both panes and show a single centered message in the full-width drawer area. Faster to parse, less visual noise. Covers J-Co2 / S-Co1. Other Console items (live log filtering, clear button, cost emphasis near budget cap) are deferred — they are feature additions, not bug fixes, and belong in a separate commit with UX sign-off. |
||
|
|
1f3484ac19 |
refactor(communication): polish FilterBar + thread time + composer shortcut
Communication page audit items from the second-round pass landed as
one commit because they sit on overlapping render paths:
- FilterBar had two native <select> elements (channel type / priority)
using raw option tags. After C9 normalized the Select primitive with
an explicit CaretDown, these two were the last stragglers. Migrated
to Select compact. Chevron affordance now matches the rest of the app.
- FilterBar search input carried a `focusRingColor: "var(--primary)"`
inline style that is NOT a valid CSS property; it had zero rendering
effect. Replaced with Tailwind `focus:border-[color:var(--primary)]
focus:ring-1 focus:ring-[color:var(--primary)]` which actually draws
the focus ring the author intended.
- FilterBar primary button + unread-only pill used inline
`style={{ backgroundColor: "var(--primary)" }}` to work around the
(formerly missing) Tailwind color token. `bg-primary` is now defined
via the theme tokens — switched both to Tailwind utilities so we
don't mix CSS-in-JS with class-based styling in a single component.
- ChannelItem unread badge: 9px tabular-nums. 9px was unreadable on
1x displays; bumped to 10px and added `tabular-nums` so multi-digit
counts (10, 99, 99+) don't shift alignment.
- ThreadPanel event timeline time was formatted with
`toLocaleTimeString([], …)` which ignores the app's zh/en locale
setting. Switched to the shared `formatMessageTime` helper so the
thread respects locale and also handles yesterday/earlier dates.
- MessageComposer shortcut hint was a hardcoded English string
"Press {cmd}+Enter to send". Replaced with the existing
`comm.sendShortcut` locale key ("按 {0}+Enter 发送" / "Press {0}+
Enter to send") + cmdOrCtrlLabel() interpolation.
Covers J-C2 / J-C3 / J-C4 / J-C5 / J-C7 / J-C8 from the Communication
audit. The remaining J-C1 (hardcoded 260px sidebar) and J-C6 (380px
thread panel) are intentional — resizable panels would be a separate
UX feature, not a bug fix.
|
||
|
|
e87170f105 |
refactor(ui): render explicit CaretDown on Select; normalize affordance across platforms
Native <select> chrome varies: macOS renders a dual-arrow indicator, Windows and Linux render a single caret, and on dark themes the native caret can drop to near-invisible contrast. The agent Edit modal (image 5 in the audit) had five side-by-side Selects where the indicator read as a subtle decoration, making the field look closer to a filled-in text input than a pick-from-list. Switched Select to: - `appearance-none` to strip native chrome - Phosphor CaretDown rendered absolutely on the right, bold weight, muted-foreground so it reads against both card and background - Padding-right increased to reserve space so long option labels do not overlap the caret - Opacity drop when disabled so the affordance tracks the field state Scope: only the shared Select primitive; every caller inherits the fix automatically. No API change. Covers J10. |
||
|
|
36bed1b0f1 |
feat(team): add TeamHeaderSummary strip + delete unreachable orgchart section
Two S-level audit items landed together because they are the same
"first-second orientation" concern on the team page:
S1 — Empty OrgChart was the first thing the board saw; the canvas
carried no summary data. Now a compact summary strip sits above
the chart showing Members / Running / Attention / Pending hires /
This-month budget (spent/cap/%). All five numbers computed from
the existing snapshot — no extra IPC.
S9 — Activity and budget context previously required visiting three
separate pages. The summary strip collapses the minimum-useful
subset into a single glance; the full drill-down paths (Work,
Approvals, Costs) remain one click away.
S10 — The `orgchart` SectionId was a dead rendering branch:
- Not in the sidebar (useUIController sidebarGroups)
- Not in the command palette (paletteOnlyNavDefs)
- Not reachable from any navigateTo() call in renderer code
The only callers were an automation post-hire notification and a
unit test — both pointed at "orgchart" as a navigation target.
Routed both to "agents" instead (which renders the OrgChart at
the top anyway, plus the new summary strip and the roster), so
clicking the "Agent Hired" notification now lands on the team
page with the new hire highlighted.
Removed:
- `orgchart` from SectionId enum (src/shared/types/enums.ts)
- `section === "orgchart"` render branch (OrganizationSections.tsx)
- `nav.orgchart` i18n key (zh + en)
Added:
- src/renderer/components/organization/TeamHeaderSummary.tsx
- team.summary.* i18n keys (zh + en)
Covers S1 / S9 / S10.
|
||
|
|
ad00a91754 |
chore(copy): humanize team-page labels; drop dead org.fit key
Jobs audit item S11: "智能体名册" (Intelligent-agent roster) reads as
machine-generated in a UI whose left navigation already uses the human
word "团队" (Team). The collision between the technical term and the
domain term made the team page feel less real than it is — it is a page
about running an AI company whose members happen to be agents, so the
label should match the nav.
- `org.agentRoster` "智能体名册" → "团队成员"
- `org.agentRoster` (en) "Agent roster" → "Team members"
- `org.noAgentsYet` "暂无已招聘的智能体" → "还没有团队成员"
- `org.noAgentsYet` (en) "No agents hired yet" → "No team members yet"
- `org.noAgentsDetail` (en) "Hired agents..." → "Hired members..."
- `org.hireRequestNote` Reworded to use "新成员" + tighter flow
- `agent.form.pendingReadOnly` "编辑智能体资料" → "编辑成员资料"
Technical contexts where "智能体" is the subject matter (skills injection
hint, workload charts, run tables, etc.) are left untouched — the word
is the product's central noun and replacing it everywhere would harm
clarity for users reading technical copy.
Also dropped the dead `org.fit` / "Fit" key — replaced by `org.fitChart`
in commit
|
||
|
|
dc33131553 |
refactor(selection): split selectedAgentId into focus vs edit-modal states; improve Edit modal title
Before this commit one piece of state (`selectedAgentId`) did three
jobs: roster highlight, navigation destination, and Edit-modal open
flag. The three jobs had incompatible lifecycles — highlight should
persist across modal close, navigation should land on a screen
without opening a modal, and the modal itself only wants to open on
explicit intent. The conflation was the root cause of the "Edit: CEO
auto-opens on first Team view" bug (commit
|
||
|
|
9981ee342b |
refactor(agent-form): split EditFormBody into 5 semantic sections + add Cancel/dirty-state + always-visible Thinking Effort + binary OrgChart hierarchy
Previously the agent Edit modal was a 351-LOC wall of 17 stacked form
fields with no visual grouping, mixed header styles between the
skills heading and the field labels, a Save button that was always
enabled, no Cancel affordance, and a Thinking Effort field that
silently disappeared when the selected connector did not support it.
This commit restructures the edit surface into five named sections
— Identity, Runtime, Budget & scope, Behavior, Skills — each with a
consistent SectionHeader (11px uppercase muted, border-top divider).
Every section lives in its own ≤150-LOC file under
agent-form-fields/edit-sections/ so nothing stays god-sized:
EditFormBody.tsx 199 LOC (orchestrator: isDirty, handlers)
IdentitySection.tsx 74 LOC
RuntimeSection.tsx 114 LOC
ConstraintsSection.tsx 58 LOC
SkillsSection.tsx 36 LOC
EditActionBar.tsx 87 LOC
SectionHeader.tsx 18 LOC
Behavioral changes:
- Save is disabled until the form is dirty (trimmed-string + numeric
budget comparison against the persisted agent record). The board
sees whether changes are captured without clicking Save to find out.
- Cancel button added, wired through AgentForm → AgentEditor →
OrganizationSections, which clears selectedAgentId (same as the
modal's close X — a proper bottom action avoids users reaching for
the corner).
- Thinking Effort is always shown. When the connector provider does
not accept the hint the Select is disabled with an inline note,
so the option is discoverable instead of silently hidden.
- Budget shows a live BudgetBar + "Spent $X of $Y" line when the
budget or spend is non-zero. The raw number input on its own left
burn state invisible.
- Capabilities placeholder replaced with a concrete example
("writes React components, reviews PRs, ...") — the previous
"What this agent does" was a circular prompt.
- OrgChart role bar went from three tiers (CEO 4px primary /
manager 3px warning / IC 2px muted 30%) to binary (root 4px
primary / everyone else 2px border). Orange-warning for managers
read as an alert, not a rank; managerial position is already
encoded in the tree's topology.
Also added i18n keys (zh + en):
agent.form.sectionIdentity / sectionRuntime / sectionConstraints /
sectionBehavior / effortNotSupported / budgetBurn; updated
capabilitiesPlaceholder copy.
Covers J2 / J7 / J8 / S5 / S6 / S7 / S8.
|
||
|
|
62d0964af3 |
feat(org): add OrgChart status legend + CEO delete/terminate guards
Two audit items addressed together because they share the same org-page
render surface and both enforce "read the screen correctly before acting":
1) Status dot colors in OrgChart were undocumented — users saw five
distinct colors (running/active/idle/paused/error/terminated/
pending_approval) with no key. Added a compact top-left legend that
only lists the statuses actually present in the current org, so a
healthy all-running company shows two rows, not seven. Label copy
reuses existing agent.form.status* keys (zh/en).
2) The agent Edit modal offered Delete and Status=terminated on every
agent, including the root (no reportsTo) — the CEO. Deleting or
terminating a root orphans every direct report. Added `isRootAgent`
check in EditFormBody:
- Delete button: disabled, with `rootDeleteBlocked` inline note
telling the user to reassign reports first.
- Status select: `terminated` option hidden; `rootTerminateBlocked`
note explains why.
Scope note: this is UI-level guardrail only. The deleteAgent IPC /
API route is unchanged — same-company power users with DB access can
still do surgical fixes; the board-facing UX just refuses to be a
footgun. Covers J5 / S3 / S4.
|
||
|
|
50a7196b3e |
refactor(org): drop legacy adapterLabels / claude_compat fallback; unify connector label resolution
OrgChart and AgentRoster both carried a `claude_compat`-prefix fallback chained against `adapterLabels[connectorId] ?? connectorId` to paper over the pre-pi-mono connector id space (claude_local / codex_local / gemini_local / claude_compat_2..10). The migration to pi-mono's catalog retired those ids months ago — the fallback is dead code that still runs `String.prototype.startsWith` on every render and forks the label resolution into two subtly different shapes in two files. Replaced with `formatConnectorLabel(connector, fallbackId)` in lib/formatters.ts, used by both OrgChart and AgentRoster. Also in this commit (typography polish bundled because touching the same render paths): - OrgChart connector label: 9px uppercase tracking-0.08em → 11px tracking-tight. The 9px chip was unreadable at default zoom. - OrgChart zoom controls: `+` / `−` / "Fit" mixed text + character ops replaced with Plus / Minus / CornersOut icons (imported from the existing icon barrel) + a focus-visible ring for keyboard users. No rendering changes for the non-cosmetic path (status dots, card layout, edges). The removed `adapterLabels` map was exported from orgTreeLayout.ts and had no other importers. Covers J1 / J3 / J4 / J6 from the team-page audit. |
||
|
|
41132e2da4 |
fix(team): don't auto-open CEO edit modal after onboarding
useCompanyController.handleOnboardingDone() pre-selected the newly-created
lead agent via setSelectedAgentId(). OrganizationSections.tsx uses the same
`selectedAgentId` state as the Edit modal open flag (`open={!!selectedAgent}`),
so the first visit to the Team section after onboarding auto-opened
`Edit: CEO` with no user action.
Removed the pre-select. Roster highlight on navigation still works via
the explicit `setSelectedAgentId(entityId)` path in the navigation slice;
the Edit modal now only opens when the user clicks a roster row, matching
user expectation.
Scope note: this does not yet split `selectedAgentId` into separate
`focusedAgentId` + `editingAgentId` states; clicking a roster row still
opens the editor immediately. That UX split belongs to the Jony/Jobs
Team-page audit pass, not this single-line fix.
|
||
|
|
02ba887610 |
fix(runtime): stop gating pi tool calls; Co-Founder approvals live at API layer
The Co-Founder beforeToolCall hook (introduced in
|
||
|
|
4cfb926971 |
refactor(schema): split 852-LOC migration ledger into era files
schema.ts had grown to 852 LOC — migration history is append-only, so
the file gets bigger every release. Organize by era without fragmenting
the history.
| File | LOC | Contents |
|-----------------------------------|-----|---------------------------------------|
| schema.ts | 195 | helpers, runMigrations, |
| | | ensureOptionalColumns, seed, init |
| migrations/types.ts | 47 | Migration/Deps types, column/table |
| | | helpers |
| migrations/v1-v15.ts | 50 | createMigrationsV1V15 — v1..v15 |
| migrations/v16-v25.ts | 157 | createMigrationsV16V25 — v16..v25 |
| migrations/v26-v34.ts | 158 | createMigrationsV26V34 — v26..v34 |
| migrations/v35-v37.ts | 326 | createMigrationsV35V37 — three heavy |
| | | table-rebuilds (runs/tasks/skills) |
Each era exports a factory `createMigrationsVxxVyy(deps)` that takes
`{ db, readAll }` — migrations' `fn` bodies captured those names via
closure in the original; factory pattern preserves the closure shape
byte-for-byte while making the dependency flow explicit.
Master `schema.ts` imports all four and concatenates them into the
final ascending list. Order preserved including the quirky v31 -> v33
-> v32 -> v34 sequence that predated my changes.
All 37 migrations' `version`, `name`, and `sql`/`fn` bodies are
byte-for-byte identical. No behavior change; 578 tests green.
`database-base.ts` (449 LOC) kept as-is. It's a thin linear pass-
through facade — every method is 1–3 lines. Splitting a facade of
this kind by forcing mixins or abstract classes costs more than it
saves. Not a god component in the Linus sense (one responsibility:
delegate); flagged here so future readers know it's deliberate.
|
||
|
|
00b0e0aaed |
refactor(ui+skills): split import.ts + primitives.tsx god files
Two more > 400 LOC modules split along responsibility seams. Zero
behavior change — public APIs re-export from the original file paths,
so every consumer import stays working.
| Original | Split into | LOC each |
|---------------------------------------|-------------------------------|----------|
| src/main/skills/import.ts (458) | import (orchestrator) / | 132 |
| | discovery (findSkillMdFiles, | 153 |
| | buildLocalInventory) | |
| | materialize | 24 |
| | import-local | 147 |
| | import-url | 85 |
| src/renderer/components/ui/ | primitives (barrel re-export) | 11 |
| primitives.tsx (419) | Card / Eyebrow / Badge / | 9–59 |
| | Spinner / Panel / Field / | each |
| | Input / Select / TextArea / | |
| | ActionButton / SectionButton | |
`createSkillImporter` still returns `{ importFromSource, importFromGitHub,
importFromLocal, importFromUrl, cleanupSkillFiles, parseGitHubUrl,
findSkillMdFiles, buildLocalInventory }` — every existing consumer
resolves unchanged.
`primitives.tsx` is now a pure barrel re-exporting the 11 primitives.
Consumers can migrate to direct imports (`../ui/Card`) opportunistically
but the barrel path keeps working.
|
||
|
|
dbdec86cdd |
refactor(database): split 5 god modules into domain-scoped files
Five files under `src/main/database/` exceeded 400 LOC by combining multiple responsibilities. Split along natural fault lines. Zero behavior change — each original `build*Methods` factory still returns the same method surface, now via spread of smaller builders. | Original | Split into | LOC each | |-----------------------------------|-------------------------------------|----------------| | tasks-crud.ts (640) | tasks-crud / task-sessions-crud / | 250 / 252 / | | | task-queries | 177 | | agents-hierarchy.ts (487) | agents-hierarchy / agent-permissions| 350 / 151 | | company-settings.ts (476) | company-settings / company-metrics | 122 / 370 | | documents.ts (426) | documents / config-revisions | 318 / 126 | | runs.ts (413) | runs / run-logs / run-events / | 245 / 96 / | | | cost-entries | 82 / 52 | AppDatabase call sites unchanged: - db.tasks.saveTask, db.tasks.ensureAgentSession (task-sessions-crud), db.tasks.listAgentTasks (task-queries) - db.agents.getChainOfCommand, db.agents.hasAgentPermission - db.company.getCompanySettings, db.company.getStandupReport (company-metrics) - db.documentsMod.saveDocument, db.documentsMod.recordConfigRevision - db.runs.createRun, db.runs.appendRunLog (run-logs), db.runs.appendRunEvent (run-events), db.runs.recordCostEntry (cost-entries) `database-base.ts` (449) kept as-is — thin facade where every pass-through is one line; splitting adds mechanical files without behavior benefit. Will revisit if it grows further. Aggregator files (`tasks.ts`, `agents.ts`, `company.ts`) each import and spread the new sub-builders. No renamed exports. |
||
|
|
f012f8e880 |
refactor(skills): drop company_skills.markdown column, read from disk via IPC
`company_skills.markdown` stored the full SKILL.md body. The on-disk
file under `profile/skills/{companyId}/{slug}/SKILL.md` is what pi-
coding-agent actually reads via `loadSkills` at runtime. The DB column
was a duplicate — import wrote to both, renderer read from DB. Any
discrepancy between disk and DB was invisible until the agent behaved
differently from what the user saw in SkillDetail.
Disk becomes the single source of truth. DB keeps name / description /
metadata / source info / file inventory — the cheap lookup row. UI reads
SKILL.md content live via a new IPC channel.
## Migration v37 (rebuild company_skills without markdown)
Table-rebuild pattern matching v35/v36: `fn:` form, PRAGMA walk for
drift columns, row-count assertion before DROP, `defer_foreign_keys`
for the `agent_skill_assignments(skill_id)` reference. Indexes
recreated. Idempotent via `schema_migrations` ledger.
Existing DB rows' markdown content is discarded — but the on-disk
SKILL.md files are the authoritative copy, so no user-visible data
loss. Any agent using those skills keeps working; the UI just starts
reading from disk instead of the DB cache.
## Code changes
- `src/main/database/skills.ts`: drop `markdown` from read mapping,
upsert input type, INSERT column/bind list, ON CONFLICT DO UPDATE.
- `src/shared/types/records-core.ts`: `SkillRecord.markdown` removed.
- `src/main/skills/import.ts` + `import-github.ts`: drop `markdown:`
from 4 `saveCompanySkill` call sites. Disk-write via
`materializeSkillFiles` unchanged (it's what the file agent reads).
- `src/main/skills/index.ts`: expose `skillFilesDir` for the new IPC
handler to resolve `${profile}/skills/${companyId}/${slug}/SKILL.md`.
- `src/shared/contracts/channels.ts` + `schemas-execution.ts` +
`desktop-api/skills.ts`: new `getSkillMarkdown` channel
`{ companyId, slug } → DesktopResult<string>`.
- `src/preload/index.ts`: expose `getSkillMarkdown(companyId, slug)`.
- `src/main/ipc-skill-handlers.ts`: handler validates schema, ensures
company, resolves path, `readFile`, returns content. Fails closed
with `SKILL_NOT_FOUND` on ENOENT, `SKILL_READ_FAILED` on other
read errors.
- `src/renderer/components/skills/SkillDetail.tsx`: `useEffect` fetches
markdown via IPC when the skill detail loads; shows "Loading…" until
resolved, surfaces read errors. Preserves `stripFrontmatter`.
- Tests: drop `markdown:` from 2 fixture sites
(`skill-types.test.ts`, `skill-aware-assignment.spec.ts`).
## Verification
- typecheck / lint / 578 tests green
- `grep 'skill\.markdown' src/` → 0 DB-record reads
- `grep '\.markdown\b' src/main/database/skills.ts` → 0
- Remaining `.markdown` references are all `ghSkill.markdown` (GitHub
fetcher's intermediate struct feeding `materializeSkillFiles` to
disk) and parameter names in `stripFrontmatter(markdown: string)` /
fs-writer helpers — none read from a DB row.
|
||
|
|
7e1d4199ee |
refactor(schema): make runs.task_id nullable, delete heartbeat workaround
The NOT NULL constraint forced heartbeat runs to synthesize a fake task row (`[Heartbeat] X — Check for work`) so the FK could resolve. Three layers of code — acquireHeartbeatTask, findReusableHeartbeatTask, cleanupDuplicateHeartbeatTasks, plus the is_heartbeat flag column + UI filter — existed to paper over one bad constraint. Remove the constraint and the whole workaround vanishes. SQLite table rebuild with data preservation. Live production DB, no backup — migration is atomic, idempotent, and verified before DROP. ## Migration v35 (rebuild runs with nullable task_id) Uses `fn:` form so it can: - Snapshot `COUNT(*) FROM runs` before copy - Walk `PRAGMA table_info(runs)` to detect drift columns (v34 declared a column set; any additive ALTER since v14 shows up here) - Assert `COUNT(*) FROM runs_new == beforeCount` before `DROP TABLE runs` - `PRAGMA defer_foreign_keys = ON` so run_events(run_id) FK defers to commit time, by which runs has the same id set under the new name - Recreate all 6 indexes on the new table If the assertion fails, the migration throws inside the existing `transaction()` wrapper. SQLite rolls back the whole migration, old `runs` table is untouched, `schema_migrations` never records v35. Next app start retries cleanly. ## Migration v36 (drop tasks.is_heartbeat + synthetic rows) Runs now carry `task_id = NULL` for heartbeat dispatches. The `is_heartbeat` flag is dead data going forward. 1. `UPDATE runs SET task_id = NULL WHERE task_id IN (SELECT id FROM tasks WHERE is_heartbeat = 1)` 2. `DELETE FROM tasks WHERE is_heartbeat = 1` (synthetic rows, not real work) 3. Rebuild `tasks` without the `is_heartbeat` column (same defer-FK + row-count-assert pattern as v35) ## Code cleanup - `RunRecord.taskId: string | null`; `createRun` / `cancelRunForDeletion` / `finishRun` guard every `UPDATE tasks` path with a null-check. - `TaskRecord.isHeartbeat` removed; every SELECT / filter / UI reference dropped. - Delete `acquireHeartbeatTask` / `findReusableHeartbeatTask` / `cleanupDuplicateHeartbeatTasks` — dead after the constraint lifts. - Delete `src/main/orchestration/heartbeat-task.ts` (pure workaround constant file). - `heartbeat.ts` creates runs with `taskId: null` when the agent has no assigned work. Turn context already supports nullable task (heartbeatFallbackSection); prompt surface unchanged. - `validateTaskCreation` drops the `isHeartbeat` parameter — user- authored + automation-authored tasks are never heartbeat. - Renderer: `run.taskId ? taskMap.get(...) : undefined` in Dashboard / RunCenter / RunDetail / RunQueue. - Delete `tests/unit/heartbeat-coalescing.test.ts` (12 tests that exercised the deleted DB methods). - Drop 1 isHeartbeat-bypass test from `task-quality-gates.test.ts`. ## Grep verification - `isHeartbeatTaskTitle src/` → 0 - `acquireHeartbeatTask src/` → migration comment only - `is_heartbeat src/` → schema.ts migration history only - `isHeartbeat src/` → 0 Tests: 578/578 (was 591 — delta is 13 tests that tested deleted code). |
||
|
|
92795f7bfb |
refactor(session): scope pi sessions to agents instead of tasks
Sessions were keyed by (task, agent). Every task switch started a fresh
transcript — the agent lost everything it had learned on the previous
task. That breaks the product premise: a CEO should remember its past
decisions, an engineer should carry context between bugs.
Move pi's AgentMessage transcript to be agent-scoped. One session per
agent (per company). Tasks enter the ongoing conversation as user turns;
rotation/compaction boundaries follow the agent's long-running session.
Additive schema only — no row rewrites on the live production DB.
- Migration v34: `ALTER TABLE agents ADD COLUMN session_id TEXT` +
`idx_agents_session_id`. `ensureOptionalColumns` mirror for fresh DBs.
- `AgentRecord.sessionId?: string | null` in the read model; included
in every full-agent SELECT (entity-crud, snapshot, agents-hierarchy
chain-of-command / direct-reports / heartbeat-eligible).
- `db.ensureAgentSession({ agentId, companyId, adapterType })` —
idempotent: returns existing `agents.session_id` if set, else inserts
a new `task_sessions` row with an empty `task_id` sentinel (agent-
scoped, task-id is now metadata) and stores the id on the agent.
- `db.setAgentSessionId(agentId, sessionId)` — used by rotation.
- `db.updateTasksActiveSessionForAgent(agentId, sessionId)` — mirrors
the agent's current session onto every task the agent owns, so
existing per-task readers (`tasks.active_session_id`) still work.
- `turn-context.resolveSession`: pulls the agent's session via
`ensureAgentSession` instead of `getActiveTaskSession(task.id)`,
then points the current task at it so UI continues to resolve.
- `session-management-service.rotateSession`: new session carries
`taskId:""`, then `setAgentSessionId` + `updateTasksActiveSessionForAgent`
so every task the agent owns follows the rotation boundary.
- `session-entries.listActive`: `ORDER BY created_at, sequence` — the
created_at timestamp is authoritative; sequence stays as same-ms
tiebreaker. Forward-safety for future merge scenarios.
Existing agents' first post-upgrade run creates a fresh agent-scoped
session. Their pre-migration per-task transcripts remain in the DB as
historical records but are not loaded for new runs. This is a one-time
transcript reset, accepted over a risky lossy merge on the live DB.
Documented in a code comment on `ensureAgentSession`.
|
||
|
|
5a3647e98e |
refactor(prompt): collapse en/zh mirror to English + runtime locale directive
Prompt builder kept two full translations side by side — system-en.ts +
system-zh.ts, operating-en.ts + operating-zh.ts, and 38 *.zh.md files
across api-docs, sops, ceo-modes, department-guidelines, and
wake-reasons. Any change required hand-syncing across two locales; drift
was guaranteed. The Chinese user wasn't getting Chinese thinking — the
model reads whatever prompt we give it and responds in whatever language
we tell it to.
English is now the single source of truth. At the tail of the assembled
system prompt, when `ctx.locale === "zh"`, append a directive:
Respond to the user in Chinese.
Claude, GPT, and Gemini all comply with the directive reliably. English
users (the default) get no footer.
- Deleted 38 `*.zh.md` files (api-docs 11, sops 15, ceo-modes 5,
department-guidelines 7, wake-reasons 1 via zh.ts).
- Deleted `messages/system-zh.ts`, `messages/operating-zh.ts`,
`messages/locale.ts`.
- Simplified `messages/index.ts` to a direct re-export of the English
bundle; getters no longer take a locale argument.
- Stripped zh imports + locale branching from `api-section.ts`,
`sop-section.ts`, `operating-instructions.ts`,
`operating-instructions-ceo.ts`, `api-endpoints.ts`, `wake-reasons/`.
- `buildSOPSection()`, `getSOP()`, `renderWakeReason()` drop the locale
arg; callers updated.
- Footer injection in `system-prompt.ts` (lines 246-249), placed after
the importantRulesSection so the model reads it last (highest
instruction salience).
`ctx.locale` stays on `PromptContext` — load-bearing for the footer.
Anthropic prompt cache hits improve (one cache key instead of two for
the same agent across locales).
|
||
|
|
40040b3dd2 |
refactor(errors): surface 57 deferred catch-and-swallow sites via logger
The earlier sweep cleaned the hot path (worker/runtime/automation/api-
server/ipc core — 59 sites). This pass finishes the deferred long tail:
main/* services, database/* CRUD modules, api-server/* routes, ipc-*
handlers, thread-integration, automation helpers, browser, skills.
Default strategy: catch (error) { logger.warn("[module] context:", ...) }
— preserve current behavior, make the failure visible instead of
silently hidden. Widened 3 logger interface signatures to include warn
where callers only passed info/error previously (thread-helpers,
host-environment-service, ipc-domain-common).
Legitimate boundaries left intact: JSON.parse on untrusted input, DNS
resolver no-records paths, explicit safe* helpers (safeJson, parseJson,
parseTriggerDetail), URL-parse predicates, and `<unserialisable>`
fallbacks inside the prompt-builder summarisers. Each kept catch now
either has a reason in context or is a documented safe* helper.
Tests adjusted only where test-constructed no-op loggers needed a warn
stub (message-dispatch, company-mode-switch, ipc-agent-graph-handlers).
With the earlier pass, AgentCompany no longer has silently-swallowing
catch blocks in main-process or renderer business code.
|
||
|
|
ecd1bc2e33 |
refactor(worker): single active-run map replaces parallel claim structures
Worker scheduling invariants ("one agent at a time", "one exclusive
workspace lock at a time") were enforced across three parallel
structures: `activeByAgent: Map<string, string>`,
`activeByWorkspace: Map<string, string>`, and `queue: RunJob[]`. Every
claim, release, and tick had to update them in lockstep, with defensive
comments explaining the ordering to future readers.
Replace with one `active: Map<runId, ActiveRun>` where ActiveRun
carries agentId + workspaceKey. Busy checks iterate the map (≤ dozens
of entries); dispatch is a single set + splice. Release is a single
delete. No parallel structures to keep in sync.
- Add `isAgentBusy(agentId)` and `isWorkspaceBusy(lockKey)` as local
helpers — both O(n) over the active map, which is fine at this scale.
- `tickQueue` is claim-before-dispatch: writes `active.set` synchronously
before `startRun` so no async window exists between claim and
dispatch.
- Drop the `lockKey` parameter threading through `startRun` — the map
carries it.
|
||
|
|
883fd8e118 |
fix(skills): honor agent_skill_assignments in runtime skill projection
After Wave D1 switched to pi-coding-agent's loadSkills, the runtime ignored the per-agent assignment table entirely — every agent saw the full company catalog. The UI (SkillAgentToggles) still let users curate which skills each agent should see, so user decisions silently did nothing. Pi's progressive-disclosure model accepts a skillPaths list at load time. Use that to pass only assigned skills, preserving the user's curation while keeping the description-only prompt footprint. - listRuntimeSkillEntries reads db.listAgentSkillAssignments(agentId), filters company skills to the assigned set, passes only those paths to loadSkills. - Empty assignment set short-circuits (no SKILL.md scan, no prompt section). - connectorId parameter stays for signature compatibility; no current connector-specific policy applies. UI contract unchanged. Runtime now obeys it. |
||
|
|
14dc9a4897 |
refactor(cost): single writer for cost_entries, finishRun reads SUM
Cost was written through three independent paths: worker-runner shipped `finalUsage.costUsd` in run-finished -> finishRun wrote `runs.cost_usd`, `tasks.cost_usd += costUsd`, `agents.spent_monthly_usd += costUsd`, and inserted a per-run aggregate row in cost_entries. CostWriter additionally inserted per-message rows in cost_entries from main. Any CostWriter failure created silent drift between `SUM(cost_entries)` (read by budget enforcement) and `runs.cost_usd` (read by the UI). Make CostWriter the single writer, finishRun the single reader. - Add `db.recordCostEntry(...)` as the first-class insert path for per-message cost rows. CostWriter calls it; tests call it directly. - Remove the aggregate INSERT from finishRun. finishRun now reads `SELECT COALESCE(SUM(amount_usd), 0) FROM cost_entries WHERE run_id=? AND unattributed=0` and writes that as the authoritative run total to runs.cost_usd, tasks.cost_usd, and agents.spent_monthly_usd. - Drop `costUsd` from `finishRun` input, the worker `run-finished` ChildMessage shape, the automation `RunFinishedEvent` type, and every caller + test fixture. Cost is owned by the per-message path. - worker-runner stops extracting `usage.cost.total` — token counts stay for session accumulation, cost comes from pi-ai via CostWriter. - run-finished-helpers now gates budget incident checks on `event.status === "succeeded"` instead of a cost field that no longer exists on the event. - automation-run-completion fires `budget_threshold` on every succeeded run (post-run spent_monthly_usd already reflects the SUM path). - Update budget tests to write a `recordCostEntry` before `finishRun` to simulate the per-message cost path; drop `costUsd` from cross- subsystem, edge-case-budget, database, and related fixtures. One writer, one reader, one source of truth. No drift. |
||
|
|
77f81a7c5a |
fix(runtime): revive AI Co-Founder tool-call gating via pi beforeToolCall
An earlier cleanup deleted HumanApprovalHook wholesale on the rationale
that the body always returned {}. That was the stub implementation, not
the feature: 版本更新.md specifies AI Co-Founder mode as "human-reviewed
execution that pauses until human review arrives". With no beforeToolCall
attached, Co-Founder and CEO modes behaved identically — a silent
regression of the product's core differentiator.
Bring the hook back with real gating logic and split the Agent
construction path by execution mode so CEO runs pay zero IPC overhead.
- HumanApprovalHook.before(): create a `dangerous_command` approval row
for non-whitelisted tool calls, emit approval-updated, return a promise
that resolves when a human decides.
- AUTO_APPROVED_TOOLS whitelist: read/ls/glob/grep/find/tree. Anything
else (bash, write, edit, …) waits for human approval.
- approvalHook.resolveApproval(): invoked from wire-api-server's
onApprovalResolved *before* automation and thread follow-ups, so the
pi Agent unblocks the instant the human decides.
- approvalHook.cancelAll(): release every pending wait on shutdown so the
worker can drain cleanly; wired into service-graph-pi-runtime.release.
- worker-runner: attach beforeToolCall only when job.executionMode ===
"ai_cofounder". CEO mode constructs Agent with no hook, no RPC hops.
- WorkerRpcClient.invoke: accept per-call timeoutMs override so approval
RPCs can wait 30min instead of the 30s default.
- worker-types schema: restore beforeToolCall in the method enum.
- RuntimeBridge.handleRpc: restore beforeToolCall branch.
No changes to approval DB schema, approval lifecycle, or existing
automation/thread handling. CEO-mode runs retain zero-IPC-per-tool
behavior from the prior cleanup.
|
||
|
|
b50716d4e8 |
refactor(messaging): project inter-agent messages into pi session transcript
Inter-agent messages previously entered the recipient's context as a separate prompt block (recentMessages), rebuilt every turn. This double- charged tokens and kept messages outside pi's native AgentMessage stream. Now at prepareRun: pull the recipient's unread inbox, project each AgentMessageRecord into a pi user-role AgentMessage, persist to SqliteSessionStore, and mark delivered. The recipient reads messages as natural conversation turns in the transcript. - db: listAgentInboxUnread + markAgentInboxDelivered (transactional) - agent-runtime/inbox-projection.ts: AgentMessageRecord -> pi AgentMessage - runtime-bridge.prepareRun: inject + deliver before loadMessages - prompt-builder: delete recentMessages context block + buildMessagesSection - message-normalize: drop unused normalizeMessagesForPrompt - delete prompt-builder/attachment-resolver.ts (orphan after section removal) Send path (api-server/routes-messages, ipc, UI) unchanged — inbox table remains the UI source. Transactional delivery marking prevents duplicate injection across runs. Failed append -> no mark -> retry on next run. |
||
|
|
5547c29491 | update | ||
|
|
806200b3d1 |
fix(runtime): correct pi AgentEvent projection + add Console diagnostics
karpathy-guidelines pass — never guess at types, read the source.
Root cause of the silent "等待日志输出" Console:
1. runtime-bridge's projectAgentEventToLogChunk switched on event types
that never exist in pi. Real types per pi-mono-reference/packages/
agent/src/types.ts `AgentEvent`:
agent_start / agent_end / turn_start / turn_end
message_start / message_update / message_end
tool_execution_start / tool_execution_update / tool_execution_end
(NOT the `assistant_message_delta` / `tool_call_start` / `error` I
invented.) Every case therefore fell through to `default → null`,
so zero log chunks were emitted even when the agent was streaming
tokens.
Rewrote the switch to match the real union. Streaming assistant
text and thinking now route through `message_update` ->
`assistantMessageEvent.text_delta` / `thinking_delta` (see
pi-mono-reference/packages/ai/src/types.ts `AssistantMessageEvent`
for the inner protocol). Tool activity routes through
`tool_execution_start` / `tool_execution_end`.
2. worker-runner had no Console-visible breadcrumbs between
`run-status: running` and eventual turn output, so when the agent
stalled (credential fetch, model resolution, first API call) the
user saw a bare "RUNNING" with no clue where time was being spent.
Added 5 targeted `run-log` probes:
[runner] starting run ...
[runner] prepareRun ok, resolving model
[runner] model=... provider=... — constructing Agent
[pi-event] <type> (inside agent.subscribe, one per event)
[runner] calling agent.prompt (N chars)
[runner] agent.prompt resolved
These are structured runtime signals, not test/mockup data. They
surface the exact phase a stuck run is in.
typecheck 0 / lint 0-0 / test:unit 508/508 / build:verify 0-0.
|
||
|
|
7fc707ab8c |
fix(onboarding): stabilize connector-model state so native <select> stays open
The CEO-runtime model dropdown (and the team-model pool) collapsed the
instant a user clicked to open it whenever the app received a background
snapshot refresh. Root cause:
useEffect(executionReadyConnectors, ...)
→ loadModels() loads catalog for every ready connector
→ setConnectorModelsById(Object.fromEntries(entries))
— always a brand-new object reference, even if the catalog
returned from the IPC is byte-identical to the previous load
→ connectorModels useMemo picks up a new reference
→ modelOptions useMemo picks up a new reference
→ <Select> re-renders its options children
→ Chromium's native <select> treats DOM mutation of <option>
children as "popup stale" and closes the dropdown.
Because snapshot refreshes fire on every domain-changed event
(credentials-changed, run-status, heartbeat-status, etc.), the user
effectively could not hold the dropdown open long enough to pick a
model.
Fix: structurally compare the freshly-loaded catalog against the
previous state and bail out of setState when the shape is identical
(same connector ids, same per-connector model lists in order, same
id/label/enabled flags). Same guard on the error-path reset so an
already-empty map does not trigger a re-render either.
typecheck 0 / lint 0-0 / test:unit 508/508 / build 0-0.
|
||
|
|
683dded501 |
fix(runtime): autonomous beforeToolCall + project agent-event to Console log
Two coupled user-visible defects were fixed.
1. Every tool call (shell / edit / write / read / ...) triggered a
synchronous human-approval prompt under BOTH execution modes. The
worker blocked inside pi's beforeToolCall waiting on a DB approval
row, so runs never progressed — "等待日志输出" forever.
The execution-mode contract is now enforced:
- AI CEO → fully autonomous, no framework gate.
- AI Co-Founder → still fully autonomous at the pi layer. Human
review is a model-driven decision (LLM calls an explicit
`request_approval` tool when it judges review necessary), not
a framework-level interrupt.
HumanApprovalHook collapses to a no-op `before` / `after` /
`cancelAll`. All machinery that queued `dangerous_command`
approvals from the worker-side hook is removed. No more starving
the agent on `node -e` self-call checks.
2. The Console panel only subscribed to `run-log` stdout/stderr
chunks, so pi's agent-event stream (assistant_message_delta,
tool_call_start/end, errors, turn_end) produced zero visible
output. With bug (1) fixed, runs now execute — but the user
would still see a blank Console.
runtime-bridge.handleAgentEvent now projects each meaningful
agent-event into a run-log chunk (assistant_message_delta → raw
delta, tool_call_start → `[tool:bash] {args}`, tool_call_end →
`[tool:done|error:name] {result}`, error → stderr). ConsoleView
picks these up via the existing run-log DesktopEvent pipe with
no renderer change.
tests/unit/approval-hook.test.ts rewritten to assert the new
autonomous contract (before always returns {}, no approval rows
written, cancelAll is a no-op). Tests 508/508 pass.
typecheck 0 / lint 0-0 / build:verify 0-0.
|
||
|
|
9b45b12508 |
fix(onboarding): drop "Default" sentinel from model picker; auto-select real model
The CEO-runtime model dropdown on the Company onboarding step rendered
a {value:"", label:"默认 / Default"} sentinel as the first option, a
holdover from the adapter era when "" meant "let the connector pick".
Under pi-mono every connector carries a defaultModel and every run
must bind a concrete model id, so the sentinel is now misleading —
picking it left selectedModel empty and the wizard would advance
without a valid model.
- CompanyStep: drop the sentinel from the options array; the dropdown
now shows only real models (or a single "Loading..." entry while
the per-connector fetch is in flight).
- useConnectorModelSelection: when the connector's current model is
not enabled, auto-select the connector's defaultModel if enabled,
otherwise the first enabled model. An empty enabled list still
falls back to "" so the CompanyStep hint UI behaves correctly.
Result: Fire Pass now shows "Kimi K2.5 Turbo (Fire Pass)" as the
sole option (pre-selected). Every other connector picks its catalog
default up-front.
typecheck 0 / lint 0-0 / build 0-0 / test:unit 508/508.
|
||
|
|
e7e43bd0a6 |
fix(fireworks): route Fireworks models through resolveModel everywhere
pi-ai's getModel/getModels does not know about the Fireworks providers (fireworks, fireworks-firepass); both live under our local model-resolver because Fireworks speaks the Anthropic-compat endpoint with Bearer auth at https://api.fireworks.ai/inference, not pi-ai's built-in catalog. Three call sites were still hitting pi-ai directly and failing silently for Fireworks: 1. connector-model-policy.ts — listProviderModels() caught the pi-ai throw and returned []. The "Fire Pass (CEO runtime)" card in the Team Models onboarding step therefore showed "暂无模型 / 已启用 0/0", blocking users on Fire Pass from continuing onboarding. 2. worker/worker-runner.ts — getModel() threw mid-run when the selected connector was Fireworks, so the worker could never actually execute an agent turn against Fire Pass or the curated Fireworks model set. Now uses resolveModel() + the Fireworks-specific Bearer header from resolveExtraStreamHeaders() so streamSimple hits the correct endpoint with the correct auth. 3. agent-runtime/cost-writer.ts — cost observation silently wrote amount_usd=null for Fireworks runs because getModel() threw inside the try/catch. Now resolves via resolveModel() so the Fireworks curated contextWindow/maxTokens are visible to calculateCost(). Verification: pnpm typecheck 0 / pnpm lint 0-0 / pnpm build:verify 0-0 / pnpm test:unit 508/508. |
||
|
|
84a3520ea8 |
fix(ipc): register credentials + oauth handlers in main runtime
The `registerCredentialIpcHandlers` and `registerOAuthIpcHandlers` entry points existed in `ipc-credentials-handlers.ts` and `ipc-oauth-handlers.ts` but were never called — `registerHandlers()` in `main-runtime-support.ts` only wired `registerCoreHandlers` and `registerDomainHandlers`. The result: every renderer call to `credentials:list` / `credentials:save` / `credentials:remove` / `credentials:test` / `oauth:start` / `oauth:cancel` / `oauth:prompt-response` / `oauth:manual-code-response` crashed with "No handler registered". Wire both handler families against the PiRuntimeServices container (credentialStore, credentialResolver, oauthLoginService) already exposed via `serviceGraph.getPiRuntime()`. The refreshConnectorHealth callback reuses the existing connectorHealthService so status badges flip between `ready` and `auth_required` immediately after a save/login. typecheck 0 / lint 0-0 / build 0-0 / test:unit 508/508 preserved. |
||
|
|
e94939a562 |
test(connector-id): migrate fixtures and assertions to pi-mono ids
Replaces legacy adapter ids (codex_local / claude_local / gemini_local / claude_compatible) across ~42 test files with pi-mono equivalents: - codex_local → openai-codex - claude_local → claude-pro - gemini_local → gemini-cli - claude_compatible → anthropic Hand-rewrites where fixture semantics changed: - connector-matching: TASK_TYPE_PREFERENCE ordering rebased (claude-pro first for code/content/management; google first for research) - connector-model-policy: getModels(provider) from pi-ai instead of the deleted adapter.module.models array - session-management + edge-case-session: adapter exemption removed; rotation now uniform under pi-mono - ipc-core-admin-handlers: CSV compat model replaced with pi-ai anthropic - connectors.spec: lookup by openai-codex id (catalog now starts with claude-pro) - onboarding-model-pool: listProviderModels(provider) helper; label text changed to "ChatGPT Plus/Pro (Codex)" - database integration: added vault insecure-fallback env + electron mock - command-paths: uses new @main/connector-helpers normalizeCommandValue - pi-mono-migrations: added `as string` casts for 3 negative-existence assertions verifying legacy ids were removed Drops: - pnpm-workspace.yaml packages: [] (was "packages/*"; directory empty) - obsolete CONNECTOR_STRENGTHS import; .module.models probing Verification: - pnpm typecheck → 0 errors - pnpm lint → 0 errors / 0 warnings - pnpm build:verify → 0 errors / 0 warnings - pnpm test:unit → 508/508 pass - pnpm test:integration → 96/96 pass - All 6 adapter hard gates pass (NOT FOUND / 0 matches / empty) - pi-ai / pi-agent-core / pi-coding-agent all resolved @0.67.6 |
||
|
|
4de9cd0339 |
refactor(worker): use pi-mono Agent directly
Rewrites the utility-process worker to drive pi-agent-core's Agent class against pi-ai models and pi-coding-agent tools. The entry file keeps the queue and cancellation state, delegates runs to WorkerRunner, and routes RPC responses through WorkerRpcClient so beforeToolCall/afterToolCall approvals and prepareRun credential resolution travel back to main's RuntimeBridge. Worker message schema gains `agent-event` + `rpc-request` envelopes, and run-finished no longer depends on the deleted sessionCodec. |
||
|
|
892476bd36 |
refactor(runtime): wire pi-runtime services and extend RuntimeBridge
Bootstraps CredentialStore/Resolver, OAuthLoginService, and RuntimeBridge inside service-graph via the existing pi-runtime factory, and exposes the bridge to worker-service for agent-event + rpc-request routing. Adds a `prepareRun` RPC that resolves credentials, loads prior messages, and registers the active run context in a single call. Session rotation drops the adapter nativeContextManagement branch; run payloads now carry the taskSessionId and executionMode the worker needs to drive pi-agent-core. |
||
|
|
06a3ba603e |
refactor(connectors): remove adapter module references from services
Drops every `definition.module.*` call and legacy id hardcode (claude_local, codex_local, gemini_local, claude_compat_*) from the connector services. Health checks now evaluate stored credentials against the declarative auth descriptor, model policy reads provider models via pi-ai, matching keys off provider ids, and hire-hook logs activity without invoking an adapter hook. Introduces a local normalizeCommandValue helper to replace the deleted @agentcompany/adapter-utils/server-utils import. |
||
|
|
60178ceafa |
chore(adapter): delete adapter packages and drop build wiring
Removes the four workspace adapter packages (claude-local, codex-local, gemini-local, adapter-utils) now that all runtime provider dispatch lives in @mariozechner/pi-ai + pi-agent-core + pi-coding-agent. Refreshes the lockfile, drops bundledMainDependencies from the Vite config, and adds pi-coding-agent as a direct dependency for the worker's coding tools. |
||
|
|
4a120f4fc6 |
feat(settings): drive providers panel from pi-mono catalog
ProvidersPanel reads all 26 CONNECTOR_CATALOG entries and groups them by auth kind (OAuth / API key / Azure / AWS / Vertex). ProviderRow dispatches to the matching dedicated form component per auth kind. AWS, Azure, Vertex forms rewritten with Tailwind classes and a shared field style module so the Settings pane renders with the same aesthetic as the rest of the app. |
||
|
|
7fdd3c16bf |
feat(renderer): rewrite providers UI on pi-mono catalog shape
Onboarding ProvidersStep now reads the 26-entry CONNECTOR_CATALOG and renders featured connectors (claude-pro, openai-codex, anthropic, openai, amazon-bedrock) up front with a collapsible advanced list for the remaining 21. Each auth kind gets a dedicated card component: OAuth button flow, inline API-key form, and a cloud deep-link to Settings. Delete legacy Claude/Codex/Gemini hardcoded cards and the compatible-API slot rotation. Swap ModelManagement Settings panel for the new ProvidersPanel driven by credentialsList IPC. |
||
|
|
6a25379995 |
feat(shared): expose pi-mono connector catalog to renderer
Add @shared/connector-catalog.ts mirroring the 26-entry pi-mono catalog from src/main/connector-definitions so renderer can drive provider UI without importing main-process modules. Widen connectorInputSchema to accept the new ConnectorId union (5 OAuth + 18 API key + 3 cloud). |
||
|
|
ea78c09615 |
refactor(connector): adapt buildDefaultConnectorRows to pi-mono shape
Pi-mono's ConnectorDefinition no longer carries defaultCommand,
configurationDoc, capabilityMatrix, or listModels. Synthesise sane
defaults for the legacy ConnectorRecord fields:
- command defaults to empty (pi-mono has no CLI entrypoint)
- capabilityMatrix collapses to a uniform pi-mono baseline
- configurationDoc is generated from the auth descriptor using the
existing buildOAuthDoc / buildApiKeyDoc / buildCloudDoc helpers
These columns stay in the schema so the UI surfaces that still read
them continue to function; the worker and resolver no longer branch on
capability flags since pi-mono handles dispatch uniformly.
|