mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
f6a891b1f55bbc7e865aa2c8660d0b3fa700a5d2
2825
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f6a891b1f5 |
[codex] fix linux AppImage launcher startup (#4494)
* fix linux AppImage packaged entry * fix linux desktop launcher env * fix linux appimage start env * fix linux packaged entry keepalive * fix linux appimage desktop metadata args * fix linux appimage launcher env wrapper * fix: preserve appimage lifecycle ownership * fix: export direct apprun appimage marker |
||
|
|
d7463072a8 |
fix(web): Plan 模式生成/重写 HTML 后自动打开生成的文件 (#5602)
* fix(web): re-focus HTML rewritten in place when a turn ends
Plan mode's core loop is plan -> generate -> edit the plan -> regenerate.
On the second generation the deliverable (index.html) already exists, so
the turn-end auto-open — a pure pre/post file-NAME diff — saw no new file
and left the viewer stuck on the markdown plan. Protocols that emit no
Write tool events (codex, gemini, opencode, ACP agents) have no per-write
fallback, so nothing opened at all.
Turn-end selection now also considers pre-existing project files whose
mtime lands inside the turn window (same attribution as AssistantMessage's
inferProducedFilesFromTurn), ranked by the existing HTML > markdown
preview priority. Turns without a start stamp keep the produced-only
behavior.
Red spec: e2e/ui/real-daemon-run.test.ts "Plan mode regeneration re-opens
the existing generated HTML file" (red on main, green here), plus a
first-generation companion spec and unit coverage for the new selector.
Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
* worker: Plan模式下,不会自动打开生成的HTML文件
Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
* fix(web): scope the turn-window auto-open to agent-touched files and bound it by turn end
Review follow-ups on the Plan-mode auto-open selector: the mtime
attribution window [turnStartedAt - 1s, +inf) could attribute a file the
USER was editing to the turn - in Plan mode plan.md autosaves from the
split editor during a run, so a text-only turn could yank focus back to
it - and the missing upper bound let post-turn edits fall in-window on
the reload/reattach recovery call sites.
selectAutoOpenTurnArtifact now accepts:
- agentTouchedFileNames: when the protocol emits Write/Edit tool events
(claude stream-json), mtime-window candidates are restricted to files
the agent actually wrote; protocols with no write events (codex,
gemini, opencode, ACP) pass an empty set and keep the pure time
window - the case the window exists for.
- turnEndedAt: bounds the window at endedAt + 60s, matching
AssistantMessage's inferProducedFilesFromTurn on both sides; callers
without an end stamp keep the open-ended window.
All three call sites resolve the agent's raw tool paths to project file
names through the existing findTouchedProjectFile matching (new
resolveAgentTouchedFileNames helper) and pass their terminal timestamp.
Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
* chore: untrack the looper task-attachment directory
The runner's fallback lifecycle commit (
|
||
|
|
3e5725e543 |
fix(web): unwrap false-positive question-form open tag in prose (#5493)
* fix(web): unwrap false-positive question-form open tag in prose When the model mentions `<question-form>` inside backtick-quoted prose before emitting the real form, splitOnQuestionForms matched the first open tag against the real close tag. The body between them failed JSON parse, the real form was swallowed as plain text, and findFirstQuestionForm returned null. Reopening the banner then fell through to the manual/historical path, which locked all inputs (interactive=false). Fix: when a body fails to parse, check for another open tag inside it. If found, unwind to that position so the real form gets a clean parse on the next iteration. * fix(web): avoid prose duplication when unwinding false-positive question-form tag Slicing from cursor re-emitted prose that was already pushed before the false-positive tag was parsed. Slice from openStart so only the text between the false-positive open tag and the real one is emitted. Also assert segment reconstruction equals the raw input. * fix(web): unwind false-positive question-form tags in both parser branches Two changes: 1. closeIdx === -1 branch (tag-name mismatch): prose mentioning <ask-question> when the real form uses <question-form> now unwinds to an inner open tag instead of swallowing the entire remainder. 2. Malformed-body branch: rename inner→nestedOpen, drop always-true resumeAt guard, and replace line-number comment with durable prose. Test: tag-name mismatch fixture + reconstruction assertion. |
||
|
|
4988021896 | fix: use baked previews for slide presets (#5630) | ||
|
|
4b660237c0 |
feat(prompts): land the slim system-prompt line as the default charter (#5603)
* test(daemon): freeze scenario x section gating matrix for composeSystemPrompt
composeSystemPrompt splices ~30 conditional sections whose gates form an
implicit matrix (session mode x design system x skill surface x memory x
execution profile). Before any prompt-slimming refactor, this golden
snapshot freezes which sections each representative scenario receives,
so a re-gating regression surfaces as a snapshot diff instead of a
silent production behavior change.
Detection is line-anchored heading markers: discovery quotes headings
like "## Active plugin" mid-sentence, so bare substring matching would
false-positive on sections that were never spliced in. A companion
assertion fails when a marker goes stale (no scenario produces it), and
position invariants pin injection-resistance first / role-marker guard
last across every scenario.
* perf(prompts): stop shipping rare-path media/deck payloads on every run
Langfuse telemetry (24h, 2k-trace sample) puts the composed charter at
p50 96KB (~24K tokens) with a 21% stable-cache hit rate — most turns
resend it whole. Two always-on blocks serve paths most runs never take:
- MEDIA_DISPATCH_HINT (2.8KB into every non-media project) embedded a
40-line worked bash polling loop. The loop moves to 'od media help'
(the CLI already prints the exact next wait command on handoff), and
the hint keeps only the dispatch rules agents historically fumbled:
no provider REST calls, no API-key asks, POSIX not PowerShell,
python3 not jq, fal-ai/* passthrough. 2,771 -> 1,547 chars.
- The freeform maybe-deck branch injected the full 20.7KB deck
framework into every kind=other / no-metadata run 'just in case'.
New detectDeckIntentSignal() scans the same request text the agent
will see (transcript included, so a deck mention anywhere in the
conversation stays sticky) for generous EN/CJK deck vocabulary and
skips the framework when nothing matches: 78.5K -> 57.7K chars on
non-deck freeform runs. False positives fall back to today's
behavior; deck-kind projects stay unconditional. An absent signal
(contracts/BYOK composer, tests) preserves legacy always-inject.
Also relieves the POSIX argv budget: DS-active runs sat within 4KB of
the 100KB hard cap that fails argv-bound adapters (aider/deepseek).
Baseline check: tests/media/policy-routes.test.ts (7) and
tests/connection-test.test.ts (1, local Kimi CLI version) fail
identically on the unmodified tree — no new failures from this change.
* test(daemon): refresh golden matrix snapshot for upstream memory prompt hardening
Rebase onto main picked up #4913, which reworded the memory
rule-proposal block (+230 chars in memory-bearing scenarios). Section
gating is unchanged — size-only refresh.
* feat(prompts): rewritten slim core charter behind OD_PROMPT_CORE=slim
The always-on doctrine layer (DISCOVERY_AND_PHILOSOPHY ~28K chars +
designer charter ~14K + three tail overrides that re-assert rules the
layered stack states in softer or contradictory forms) is rewritten as
ONE ~15.5K-char document: core-slim.ts. Same composed inputs drop from
65.7K to 36.0K chars (-45%); freeform non-deck runs from 57.7K to 28.9K.
Design rules of the rewrite:
- Every rule stated exactly once, under an explicit precedence ladder
(user request > active skill > design system > memory > charter).
- Contradictions resolved instead of stacked: one verification budget
(static pass + P0 checklist + craft scan + at most ONE render; the
'two passes is normal' re-score loop is gone), one turn-1 form
decision path (four skip conditions listed in one place, including
the memory task-brief replacement), Tweaks reconciled (iteration
surface on request, never demo controls in product files).
- Protocol markers are frozen API and survive verbatim: question-form
shapes/ids, pick_direction/brand_spec/reference_match branch values,
task-type routing options, data-od-id, EDITMODE markers, pinned
React/Babel/Motion tags, artifact handoff shapes.
- Emphasis quota: no IMPORTANT/CRITICAL inflation; imperatives carry
the weight.
Rollout: ComposeInput.promptCoreVariant ('classic' default | 'slim'),
daemon flips via OD_PROMPT_CORE=slim — zero behavior change until set,
A/B-able per daemon. Ask mode keeps the clarifying-questions tail (it
composes no core charter). Guards in tests/prompts/core-slim.test.ts:
a 16KB byte budget that fails any silent regrowth, frozen-marker
assertions, handoff profile switch, and classic-parity checks; matrix
snapshot gains slim scenarios.
Not yet covered (follow-ups): the packages/contracts BYOK composer
still ships only the classic stack; instruction-following A/B against
classic is the sign-off gate before changing the default.
* feat(prompts): shrink the slim core to an 8KB budget by evicting what a system prompt shouldn't carry
The 15.5K v1 slim charter still carried content that has no claim to an
always-on seat. v2 evicts it by category:
- Already owned elsewhere: the od-default task-type router form ships in
plugins/_official/scenarios/od-default/SKILL.md and reaches the prompt
as '## Active skill'; the charter now just defers to skill-owned
turn-1 forms. An ownership test pins the form to the skill file.
- Conditional-path content: per-platform delivery contracts (breakpoint
matrix, per-target files, native chrome) move to
PLATFORM_CONTRACTS_BLOCK, injected only when the project declares a
platform or multiple targets. Deck rules were already duplicated by
the deck-gated directive - dropped from the charter entirely.
- Model-competence content: PDF extraction how-tos, attached-image
explanations, JSON syntax reminders - deleted.
- Recital: the default discovery form example keeps only the brand
question verbatim (its pick_direction/brand_spec/reference_match
values are the one protocol-matched part); the other six questions
become a one-line recipe. Option labels were always model-localized
per language, so exact English menus were never stable anyway.
- React pins keep versions (the actual bug fix: 18.3.1 pairing +
framer-motion React build) but drop the SRI hashes and script-tag
boilerplate - reviewers should weigh the integrity-attribute loss.
Charter now renders at 8,187 bytes (budget test tightened 16KB -> 8KB).
Composed prompt for the common design path: 65.7K classic -> 29.6K slim
(-55%); freeform non-deck: 57.7K -> 21.5K (-63%).
* perf(prompts): evict the remaining always-on payloads from the slim stack
Follow-up to the 8KB charter: the composed slim prompt still carried
~13K of non-charter payload. Four evictions, all slim-only (classic
stays byte-stable for the A/B):
- Project metadata becomes a fact sheet ('facts' style). The classic
block had grown seven doctrine rules plus a responsive contract and
cross-platform rule - prose owned by the slim charter's Craft section
and PLATFORM_CONTRACTS_BLOCK. facts keeps key-value fields, media
workflow data, and two compact delivery lines (screen files, product
depth). kind=other: 3.6K -> ~1.1K.
- Memory two-loop scaffolding compressed ~5.5K -> ~2.9K: identical
headings and od-card shapes (web parses the card types; the daemon
checks the verify-scorecard), repeated rationale prose cut.
- zh-CN locale override drops the worked quick-brief sample copy under
slim - it mirrored the classic form verbatim, which slim recipes
instead; the form contract already mandates localization. 2K -> 0.85K.
- MEDIA_DISPATCH_HINT gates on detectMediaIntentSignal (same
transcript-sticky pattern as the deck signal): most conversations
never mention media, and the hint returns for the rest of the session
the moment one does.
- Direction library becomes a pull layer: slim composes a ~1.3K
id+label index and the agent fetches the chosen direction's full
palette/posture spec via the new 'od tools directions --id <id>'
CLI (also --label / --json / bare list). One tool call on the
direction-pick path, which runs at most once per project, replaces
6.7K of always-on palette data. Classic keeps the inline library.
Composed slim sizes: freeform non-deck 21.5K -> 12.6K; design full
stack 29.6K -> 22.2K (fixture DS; media hint drops another 1.4K on
non-media conversations). Baseline classic: 57.7K / 65.7K.
* refactor(prompts): restructure the slim prompt as static-first layers for cache and hierarchy
Two structural defects in the slim composition, both flagged against
prompt best-practice guidance:
1. Heading hierarchy was inverted: the document opened with an H2
('## Security: prompt injection resistance') and the H1 charter
appeared six sections later. The security section now lives INSIDE
the charter as an H2 right after Precedence (single source moved to
core-slim.ts; the classic stack imports it for its unchanged
standalone-first position), so the composed document reads
identity-first with a coherent H1 > H2 tree.
2. Ordering was cache-hostile: conditional blocks (mode override,
locale, direction index, platform contracts) preceded the static
charter — the exact 'conditional prefix sections' anti-pattern from
the prompt-caching guidance, where every flag combination becomes a
distinct prefix and nothing is shared. Slim now composes in
variability tiers:
static charter (identical across all runs — shared cacheable
prefix) -> conversation-stable (mode, locale) -> project-stable
(direction index, frames, platform, memory, instructions, DS,
skill, metadata, MCP) -> turn-variable (maybe-deck and media-hint
signal blocks, deferred to after MCP so a mid-conversation signal
flip only invalidates the suffix) -> recency-pinned role guard.
The charter's Precedence section now states that a session-mode
directive appearing after it takes precedence, replacing the
positional 'read first' authority the old ordering relied on.
Classic composition is byte-untouched (A/B baseline); ask mode keeps
the classic head. Charter budget 8KB -> 9.25KB (absorbed the ~1KB
security section; composed totals unchanged: freeform 12.7K,
full-stack 22.4K). New layered-ordering test pins the tier sequence
with line-anchored markers; the matrix position invariant now asserts
the per-variant head.
* fix(prompts): resolve five structure/expression defects from the charter review
Findings from the senior-review pass, each with a concrete failure mode:
1. Turn-ordinal section names misfire mid-session. 'Turn 1/2/3+' made
the model treat 'now is not turn 1' as license to skip the fresh-
brief flow when a NEW artifact is requested at turn 8 — and,
conversely, an event-based fix alone would have re-asked questions
the session already answered. Sections are now event-named ('On a
fresh brief' / 'When the brand answer arrives' / 'Once direction is
locked') AND a session-inheritance rule makes the mid-session case
explicit: a fresh brief inherits the conversation's locked
direction/brand/audience, asks only genuinely new unknowns, and
builds directly when there are none — matching current production
behavior with zero extra rounds or cache cost.
2. The handoff rule — the single highest-cost-if-violated output
protocol (never emit <artifact> in filesystem runs) — was the only
rule block without a heading. Promoted to '## Handoff'.
3. The form example read backwards: it showed the LAST question
(brand) and described the earlier six in reverse ('preceded
in-form by'), the only place in the prompt requiring the model to
assemble structure in reverse. Now forward-order: output question
shown in full, brand question shown in full (its values are the
protocol-matched part), the middle questions listed as
insert-between recipes.
4. Dangling reference: Craft said 'single family only for
tech/utility' — a direction name defined only in the Direction
index, which is absent whenever a design system is active.
Reworded direction-independent.
5. direction-cards was dead vocabulary: listed as a legal control but
every mention forbade it. Its one legitimate path is now named —
only when the user explicitly asks to see direction options.
Charter budget 9.25KB -> 10KB: fix #3 cost ~0.7KB (restoring the full
output-question JSON), accepted deliberately — it also restores
few-shot density for weaker fleet models, and the review's own
conclusion was that the last byte-squeezing round had negative ROI.
* fix(prompts): identity-first head for slim ask mode
The last remaining hierarchy inversion: slim ask still opened with the
H2 security block before the H1 ask override. Ask now matches design
mode's structure — the ask override (the turn's whole charter) opens
the document and the security section reads as its first subsection.
Both blocks are static, so the swap is cache-neutral. Classic remains
byte-untouched.
* fix(prompts): repair five slim-vs-classic regressions from the second audit
Second senior review against the shipping classic stack surfaced two real
bugs and four wrongly-compressed classic behaviors in the slim variant:
- text_artifact/BYOK runs got the direction INDEX plus an instruction to
run the `od tools directions` CLI they do not have; classic inlined the
palettes. Slim now profile-switches: filesystem keeps the 1.3K pull
index, text_artifact gets the full inline library back (+1.35K tokens
on that path only; still ~68% below classic, and now under deepseek's
30KB Windows argv cap, which classic never was).
- API_MODE_OVERRIDE's literal scope ('every LATER instruction … is
overridden') inverted when slim moved the charter ahead of it: the
charter's TodoWrite/render-check instructions escaped the override.
Plain-stream slim runs now compose the override first again. Pure
reorder, zero bytes.
- PLATFORM_CONTRACTS_BLOCK gated only on project metadata, missing
freeform briefs that name a platform in text ('做个 iOS app 原型').
New detectPlatformIntentSignal (same generous EN/CJK shape as the
deck/media signals) ORs into the gate; server wires it.
- media-contract's 'explicit layer overrides' paragraph cross-referenced
the classic-only layer names; reworded layer-agnostic (classic +19B).
- Restored four classic rules the compression pass misfiled as
model-competence filler: never hot-link user-attached images by URL
(product constraint), skill/DS per-domain precedence clause, expressive
form-control guidance, and the modern-CSS encouragement line. Charter
budget 10,240 -> 10,752 with rationale in the test comment.
Classic bytes untouched except the shared media-contract rewording.
Golden matrix: gating unchanged; sizes only (classic +19, slim +537).
Pre-existing failures unrelated to this change and failing on the
unmodified baseline: tests/media/policy-routes.test.ts (7), Kimi
connection test (local CLI version mismatch).
* fix(prompts): defer signal-triggered platform contracts to the turn-variable suffix
Two follow-ups from the third structure audit:
- PLATFORM_CONTRACTS_BLOCK sat in the project-stable zone while its gate
now includes the turn-variable conversation-text signal. A mid-session
'make it an iOS app' would insert the block early and break the cached
prefix for every section after it (DS, skill, memory, metadata, MCP).
The gate is now split by trigger stability: metadata triggers keep the
early slot (fixed at project creation), signal-only triggers defer to
slimTurnVariableParts like the deck/media signal blocks. The
slimTurnVariableParts declaration hoists above the design section so
the gate can reach it; no byte or ordering change for any existing
gating combination.
- Slim ask mode on a plain stream composed CHAT -> injection -> API
override, inverting classic's authority order (API before CHAT, so
'overrides every rule below' covers the chat charter). The ask head
now leads with the API override on plain streams; the later push is
gated on !isSlimCore. Zero-byte reorder; the two overrides do not
conflict in content, this restores parity with the shipping order.
* fix(prompts): restore form-tailoring mandate and first-message form scope
The slim-vs-classic blind A/B eval (2026-07-04, 17 pairs, double-order
opus judging) surfaced two form regressions with converging judge
rationales:
- Form quality lost 12:1:4 to classic. Mechanism: classic's discovery
layer carries extensive per-brief form-tailoring guidance; slim
compressed it to the single word 'tailor'. Models shipped the default
recipe verbatim — app-platform checkboxes on landing-page briefs, the
fundraising ask/traction buried in one textarea placeholder. Restored
as an explicit mandate with concrete per-brief examples, a
plausible-targets-only rule for the platform options, and a
translate-the-labels note on the tone recipe.
- DS + detailed-brief projects skipped the turn-1 form 3/6 vs classic
1/6: the mid-session inheritance clause generalized to first
messages. Added the explicit scope limit: inheritance never applies
to a project's first message.
Charter budget 10,752 -> 11,264 with the eval citation in the test
comment. Golden matrix: sizes only.
* fix(prompts): template tokens in the seed must be replaced before handoff
The 40-task x 5-turn A/B eval (2026-07-05) found slim's remaining T2
quality gap is driven by catastrophic failures, not taste: 3 of 39
pairs shipped the live-artifact skill seed with unrendered {{mustache}}
tokens (classic did this once). Mechanism: slim compressed classic's
seed-handling guidance to 'copy the seed, paste layouts', losing the
explicit fill-with-real-content step.
- Read-before-write bullet now ends with: replace EVERY template token
with real content; a {{placeholder}} or empty section reaching the
user is a failed delivery. (Also covers the eval's 4 thin-content
failures.)
- Self-check static pass adds 'leftover template tokens' to its scan.
Charter 11,155B, within the existing 11,264 budget — no bump needed.
* fix(prompts): first build ships complete, no stub sections
Clean-scope T2 analysis of the 40-task eval (excluding the 10 tasks
confounded by the live-artifact skill protocol) still shows a first
build completeness gap: main 7.34 vs slim 6.10, driven by thin-content
deliveries (empty canvases, placeholder-looking blocks) on 4 of 29
pairs. The charter's 'show something visible early' line, uncushioned,
reads as license to hand off the early skeleton.
- Qualify it: '…but the turn ships the COMPLETE artifact: no stub
sections.'
- Self-check static pass adds 'empty or stub sections' to its scan.
11,215B, within the 11,264 budget.
* refactor(prompts): split the overloaded turn-1 form section by concern
The slim charter's '## On a fresh brief' H2 bundled six distinct
concerns under one heading — trigger, default action, mid-session
inheritance, skip conditions, form-shape tailoring, and the form JSON
contract — so a maintainer could not scan the outline to find any one
rule, and the cross-cutting form contract (valid on ANY turn) was buried
under a heading scoped to fresh briefs.
Behavior-equivalent restructure (every directive's wording preserved;
only headings added and the form contract's 'any turn' note moved from a
parenthetical to the section lead):
- ## Turn 1 — the discovery form (trigger + default action)
- ### When to skip or inherit the form (inheritance + skip conditions)
- ## Writing a `<question-form>` (cross-cutting: shape + tailoring + example + ordering)
- ### Form contract (JSON validity + type enum + localization)
The charter outline now reads as a per-concern document. Frozen protocol
markers (form JSON, brand values, type enum, allowCustom) unchanged.
Charter 11,242 -> 11,322B; budget raised to 12,288 for readability
headroom at the maintainer's direction (budget explicitly deprioritized
for this change). Golden snapshot regenerated; 113 prompt tests green.
* refactor(prompts): readability second pass + multi-turn DS/constraint adherence
Two changes, from a critical re-read plus production feedback.
A) Readability / logic second pass (behavior-equivalent, wording preserved):
- Heading style: the two remaining imperative-tail H2s became event
labels with the directive moved to the body lead ('## When the brand
answer arrives', '## Once direction is locked'), so the outline reads in
one consistent grammar. Dropping '…hand off' also fixes a heading that
promised handoff while the rules live in the next section.
- The self-check bullet's inline (1)(2)(3)(4) run-on became a four-item
sub-list — the least-scannable line is now the most scannable.
- The 70-word 'Read before you write' sentence split into shorter units;
'show early / ship complete' separated into its own bullet so the two
ideas stop reading as contradictory.
- Precedence: skill and DS collapsed into one rank split by domain (was
numbered 2 vs 3, contradicting the 'each highest in its own domain'
clause) — removes the only real logical seam in the ladder.
B) Multi-turn edit adherence (new '## On an edit or tweak' section):
Production feedback reports the design system and explicit user
constraints frequently drift during multi-turn edits. The charter was
turn-1/first-build heavy and had no standing edit-mode invariant. Added,
in the lifecycle between build and handoff:
- an edit touches ONLY what the user named; untouched sections/values
stay as they were; re-read the file and edit in place.
- the design system binds on EVERY turn, not just first build — no
drift, no raw hex, no palette re-pick because this turn was about
something else.
- every hard constraint stated this session persists across later turns
until the user relaxes it; a new request adds to constraints, never
silently resets them.
Charter 11,322 -> 12,029B, within the 12,288 budget (no raise). New
frozen assertion pins the adherence invariants against future
compression. 114 prompt tests + guard + typecheck green; golden snapshot
regenerated.
* refactor(prompts): two-tier charter — lifecycle spine, grouped pillars, precedence precision
From a senior word-by-word re-read. The charter was 13 flat H2s spanning
three conceptual tiers (foundations / lifecycle / reference) with no
visible spine, and the precedence wording could invert authority.
Structure (behavior-equivalent, rule wording preserved):
- 13 flat H2 -> 5 H2: two foundations (Precedence, Security) + three
pillars, each grouping its H3s —
## Discovery — ask before you build (Turn 1, skip/inherit, writing, contract)
## Delivery — brand → build → iterate (brand answer, build, edit, handoff)
## Craft & contracts (craft, technical, conduct)
The delivery lifecycle is now one connected, ordered spine — 'Editing
an existing artifact' reads as a named phase of it, which should help
the multi-turn drift the edit-adherence rules target.
Precision fixes:
- Precedence: 'On conflict, higher wins: 1…2…3…4' was ambiguous (which
number is higher?) — reworded to an explicit list, 'the one higher on
this list wins — the user's request is the highest authority, this
charter the lowest', and the trailing pronoun tightened to 'overrides
it wherever the two conflict'.
- 'no tool calls or file reads first' -> 'before it'; 'Resolve the
source' -> 'Resolve the brand source'; 'restyle the whole thing' ->
'restyle beyond the request'.
- Trimmed one of three localization restatements (kept the authoritative
one in Form contract, now naming labels/options/placeholders) and the
'however complete the brief reads' duplicate.
Handoff/Craft/Technical/Conduct headings demote to H3 under their
pillars (local constants, not shared with classic). Charter 12,029 ->
12,294B; budget 12,288 -> 13,312. Ordering/adherence test anchors and
golden snapshot updated. 114 tests + guard + typecheck green.
* fix(prompts): make edit-adherence forceful — do exactly what was asked, verify it landed
Production feedback and maintainer review: the edit section addressed
only scope (don't over-change) and persistence (DS/constraints), but not
the core instruction-following failure — 'the user asked for A and the
result isn't A'. The whole section was defensive; the positive imperative
that the requested change must actually happen, completely, was missing.
Strengthened '### Editing an existing artifact':
- Leads with the imperative: the delivered file must actually be A — do
exactly what was asked, in full, in every place it applies, with
complete-vs-partial examples ('dark green' = every element, not one).
- Forbids reinterpreting, 'improving on', partially applying, or
substituting own taste for the user's literal words — ties to
Precedence #1 (user's request is highest authority).
- New 'Verify before you hand off' bullet: open the file, confirm the
change is actually present and complete and no in-force constraint
broke; never report a change you did not make (the claim-vs-reality
failure).
- Kept the scope / DS-binding / constraint-persistence bullets.
Charter 12,294 -> 13,122B, within the 13,312 budget. Frozen assertion
updated to pin the positive imperative and the verify bullet.
* fix(prompts): a later explicit request overrides a conflicting earlier constraint
Edge case raised in review: turn 2 'fix the color to blue, don't change
it', turn 4 'change everything to yellow'. Correct behavior is turn 4
wins (the user is changing their own mind; yellow becomes the new
standing constraint), which Precedence #1 + 'persist until the user
relaxes it' already imply — but the old bullet also said 'a new request
ADDS to those constraints', which a literal model could read as
additive-only and stall on the turn-2 'don't touch it'.
Rewrote the constraint-persistence bullet to make the override explicit:
constraints carry forward, only the user can lift or change one, and a
later explicit request overrides a conflicting earlier constraint — with
the blue->yellow turns as the in-prompt example. The anti-silent-drop
guard (model must never drop a standing constraint on its own) is kept.
Charter 13,122 -> 13,412B; budget 13,312 -> 14,336.
* fix(prompts): make the plan step agent-agnostic — TodoWrite is Claude-only
Open Design drives many local code agents; the real session mix is ~65%
non-Claude (codex, opencode, gemini, ACP family, …). The slim charter's
plan step hardcoded 'first tool call is TodoWrite' + in_progress/completed
— a Claude Code tool those agents don't have. For ~2/3 of traffic that
instruction is dead: the agent either skips planning (losing the exact
lever that helps multi-turn adherence) or hallucinates a tool it lacks.
Everything else in the slim charter was already agent-agnostic (render
checks and direction pulls go through the shell OD CLI wrapper; protocol
markup is assistant text), so TodoWrite was the one hardcoding.
- Charter plan step → generic: 'lay out a short, updatable plan; if your
runtime has a structured plan/todo/task tool, use it, else a numbered
list; never call a tool you don't have.'
- Composer injects a concrete note ('Your plan tool is TodoWrite; the
host renders it as a live Todos card; mark in_progress/completed') only
for Claude-family runs (streamFormat claude-stream-json: claude,
codebuddy, amp) — the only agents with TodoWrite and the host's live
Todos card. Agent-specifics live in the composer, portable doctrine in
the charter.
- API_MODE_OVERRIDE reframed from 'TodoWrite/Read/… are unavailable' to
'any tool call — TodoWrite/…/or whatever your runtime exposes — will
not execute'. Keeps the specific names (issue #313 anti-pseudo-tool,
test-pinned) while covering non-Claude runtimes.
Two frozen tests pin: charter has no TodoWrite + generic wording;
Claude-family gets the note, json-event-stream doesn't. 116 tests +
guard + typecheck green. Charter 13412 to 13543B (budget 14336).
* feat(prompts,live-artifacts): render one-level repeats, tighten slim charter craft guards
- live-artifacts/render: implement the documented `data-od-repeat="item in data.path"`
one-level repeat directive (specified in the schema but never rendered), with
loop-scoped `{{item.*}}` bindings, single-pass expansion so data values are never
re-interpolated, and explicit rejection of nested repeats / non-array sources.
- live-artifact SKILL.md: state the html_template_v1 binding contract in the workflow
(flat data.* scalars + data-od-repeat lists; no hand-loops, no bare loop vars) so
templates stay within the renderer's capability.
- core-slim charter: "replace every template token" now yields to a skill's
template-plus-data protocol; add an objective Layout-integrity craft rule (no
overlap, no clipping, oversized display type must fit its column, charts use fills
not outlines) and name those failure modes in the self-check.
* feat(prompts): add agent-agnostic production-value craft rule to slim charter
Visual-first pages regressed on slim: hero images were hand-drawn CSS boxes
instead of generated art (P1), and controls read flat/greyscale (P5). The slim
charter had dropped all of main's imagery + visual-system guidance.
Add one "Production value — feel shipped, not greyscale" craft rule: generate a
real image via the Open Design media tool (referenced through the injected
`"$OD_NODE_BIN" "$OD_BIN"` wrapper, so it is runtime-agnostic — not tied to any
one code agent) when a hero/product shot would carry the page; ship a real
product palette, colored interaction states, and elevated primary controls.
Bump SLIM_CORE_BYTE_BUDGET 14_336 -> 15_360 to seat this load-bearing guidance.
* feat(prompts): generalize the production-value imagery rule beyond hero shots
40-task eval + user review (C3 tea gift-box, P1) showed slim under-generates real
imagery on visual-first work, defaulting to hand-drawn schematic graphics. Reframe
the rule from a narrow "hero/product shot" trigger to a general default: whenever a
real picture would lift the artifact (product, place, food, person, hero, texture),
generate it via the media tool; drop to a diagram/UI mock only when that genuinely
serves the content better. Still agent-agnostic (media tool via the injected
"$OD_NODE_BIN" "$OD_BIN" wrapper).
* fix(prompts): let the production-value rule use the run's own image generation
Hard-pointing imagery at `od media generate` only works when a daemon spawned the
agent (it injects OD_PROJECT_ID); a standalone agent (eval harness, external
runtime) can't reach it, so slim silently fell back to hand-drawn placeholders
while codex-run peers used their native imagegen. Broaden the rule: generate through
whatever image generation the run offers — the Open Design media tool when wired up,
otherwise the runtime's own native image generation. Verified: C3 slim now emits a
real generated product photo instead of a schematic box.
* test(daemon): drop connected-external-mcp from the golden matrix
Upstream moved the connected-external-MCP directive out of
composeSystemPrompt into the per-turn slice (live OAuth token state must
stay out of the cached stable prefix), so it is no longer a compose
input or a gateable section.
* chore(prompts): default to the slim charter on the validation branch
VALIDATION-ONLY commit for feat/system-prompt: packaged beta builds must
exercise the rewritten slim charter, and the packaged sidecar env
allowlist does not forward OD_PROMPT_CORE, so the default flips to slim
here. OD_PROMPT_CORE=classic restores the classic stack. Do not carry
this default into main.
* perf(prompts): batch slim tool work, with quality guards restored (#5555)
* perf(prompts): batch slim tool work
* fix(prompts): restore seed-copy rule and unconditional own-browser ban
The tool-economy pass (
|
||
|
|
e20ae0bdbe | test(e2e): cover uploaded root-relative image preview (#5621) | ||
|
|
61ee3914d3 |
fix(web): paint the update-ready prompt above the raised home composer (#5608)
The updater prompt lives inside .entry-main__topbar, an isolated sticky stacking context at z-index 10, so its own z-index 80 could not escape. When the composer's agent picker (or another composer popover) is open, .home-hero__input-card:has(...) raises the card to z-index 1700, painting the composer and its popover over the update prompt on scrolled home windows. Mirror the settings-menu precedent and lift the topbar to z-index 1900 while .updater-popup is open. Red spec: e2e/ui/updater-popup-stacking.test.ts reproduces the overlap with elementFromPoint sampling (red on main, green here). Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) |
||
|
|
8f7c4bb80a |
fix(web): hold the next-step card while a question form awaits answers (#5616)
A clarification turn terminates its run while the emitted <question-form> is still waiting for the user in the Questions tab, so the terminal-turn next-step card (Continue working / Generate artifact / More) popped up during the question phase. Gate showNextStepActions on the message's question forms: hold the card until the immediate next user message submits the form answers (skip-all submits through the same path), and ignore suppressed direction forms since they render as a locked pill the user cannot answer. Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) |
||
|
|
799c6395c0 |
fix(daemon): retry transient AMR runtime closes (#5564)
* fix(daemon): retry transient AMR runtime closes * fix(daemon): classify runtime close before retry * fix(daemon): retry generic stream failures --------- Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
a250ac662e |
fix(web): stop silent-updates settings toggle from crashing the page (#5609)
* fix(web): stop silent-updates settings toggle from crashing the page Settings → About read event.currentTarget.checked inside a functional setCfg updater. React clears currentTarget after the handler returns, so when SettingsDialog already has pending lanes (about-updater status, autosave), the deferred updater throws TypeError and lands on the reload error page. Capture the checked value before setState. * test(web): fix TS2352 in silent-updates race test null cast The bad-pattern assertion cast `event.currentTarget` (typed `HTMLInputElement | null`) directly to `HTMLInputElement`, which `tsc --noEmit` rejects with TS2352 because the types don't overlap. Route the cast through `unknown` (matching the fixed-capture assertion below it) so Preflight typecheck passes. Generated-By: looper 0.10.3 (runner=fixer, agent=claude-code) --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
ad584ff901 |
fix(web): keep deck page counters in sync during keyboard paging (#5594)
* fix(web): keep deck page counters in sync during keyboard paging Host-driven deck navigation (keyboard shortcuts and prev/next buttons) used to mutate slide classes/transforms directly in the srcdoc bridge, bypassing the artifact's own navigation script. Decks that render their own page counter from an internal index never saw the move, so the bottom page number stayed frozen on the first slide while the canvas advanced. The bridge now prefers dispatching the navigation key to the artifact's own keyboard handler and only falls back to direct DOM mutation when no handler moves the deck. The mirror gap: iframe-focused keyboard navigation on transform-track decks mutates only the shared track element, which the bridge's MutationObserver (slide attributes only) never saw, leaving the HOST counter stale. The observer now watches the track's style too. Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) * worker: 使用键盘快捷键翻页的时候,下面的页码不会变动 Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) * fix(web): wait out deferred deck key handlers before the bridge's direct-DOM fallback Review follow-up for #5594: the artifact-key probe treated only a synchronous index change as proof the artifact handled the key. Decks that defer their keydown state update (requestAnimationFrame or setTimeout) fell through to direct DOM mutation immediately AND received the key twice — the window dispatch and the document re-dispatch both reach a deferred window listener — so one host "next" advanced two slides and desynced the artifact's internal index. The probe is now asynchronous: dispatch to one target, then wait one bounded mutation window (MutationObserver on class/style/hidden plus a 48ms cap) for the active index or track transform to change before trying the next dispatch target or the direct-DOM fallback. Synchronous handlers still resolve on a zero-latency fast path, and a responsive dispatch target is cached so later keypresses skip re-probing. Decks with no keyboard handling must not pay the probe wait: the key path is gated on the artifact actually shipping keyboard navigation, seeded from a source scan of the artifact html before any od bridge is injected plus registration-time tracking through the existing addEventListener patch — the same two-pronged pattern the od:slide message-listener flag already uses. This preserves the pinned invariant that the bridge-owned fallback stays synchronous for handler-less decks. Regression fixtures: a deferred window-listening deck (red on the synchronous probe with a visible double advance to slide 3) and seed-scan assertions proving od-injected bridge scripts do not contaminate the keyboard-navigation flag. Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) * fix(web): catch external-script deck keyboard runtimes with a head-start registry hook Review follow-up for #5594: detectArtifactKeyboardNavigation() only sees inline HTML bytes, so decks that load their keyboard runtime via <script src> (design-templates/html-ppt wires ../assets/runtime.js, which registers an ArrowRight/ArrowLeft document keydown handler) were classified handler-less. Their listeners also register before the deck bridge executes at the end of <body>, so the bridge's own addEventListener patches installed too late — the flag stayed false, host-driven od:slide fell back to direct DOM mutation, and artifact-owned counters / internal indices went stale again. A tiny registry hook is now injected at the start of <head> — before any artifact script — wrapping window/document addEventListener and flagging keydown listeners that pass the shared navigation-key screen into window.__odArtifactKeydownNavigation. The deck bridge reads that flag lazily alongside its inline-source seed and its own late patches, so registrations are observed no matter when they happen relative to the bridge. The bridge marks its own top-of-file keydown listeners via __odDeckBridgeOwnListenerInstall so the framework branch's key-token source does not classify every framework deck as artifact-navigable. The navigation-key screen is now a single shared snippet between the hook and the bridge. Regression: a fixture that registers its keydown handler before the bridge evaluates with no keydown token in the html bytes (the script-src shape) — red on the previous commit (counter stays 1 while the canvas moves), green with the hook. Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) |
||
|
|
d7151b2b60 |
feat(analytics): link downloads and first-party browser opens (#5442)
* feat(analytics): attribute desktop downloads * fix(analytics): initialize attribution routes after HTTP deps * feat(analytics): bridge first-party browser opens * feat(analytics): bridge first-party anchor opens * fix(analytics): make attribution claims idempotent * fix(analytics): record pending attribution results * chore(analytics): bind landing attribution KV * fix(ci): remove landing preview wrangler config override * fix(landing): resolve download attribution function * fix(analytics): harden attribution token consumption * fix(analytics): preserve same-installation claim retries * fix(analytics): merge recovered attribution retries * fix(analytics): preserve attributed browser links * fix(analytics): clear attribution on privacy reset * fix(analytics): bridge stopped AMR console links * fix(ci): deploy attribution schema before Pages * fix(ci): run attribution migrations with Wrangler 4 --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: PerishCode <perishcode@gmail.com> |
||
|
|
da7c4ae051 |
fix(web): rank Community "Newest" by shipped publishedAt, not local install timestamps (#5597)
* fix(web): rank Community "Newest" by shipped publishedAt, not local install timestamps
The Community gallery Newest toggle sorted by the installed-record
updatedAt/installedAt. Those are stamped when a machine seeds its own
database, so a fresh install (or an upgrade that re-stamps the catalog)
writes the whole bundled catalog milliseconds apart in folder-walk
order — Newest showed reverse walk order (roughly reverse-alphabetical),
unrelated to publication recency.
Root-cause fix: bundled templates now ship a real publication date.
- packages/contracts: add optional manifest field publishedAt (ISO 8601).
- apps/web sortByNewest: freshness = publishedAt when parseable, else
updatedAt (right signal for user-installed plugins); tie-breaks and
stability unchanged.
- plugins/_official: backfill publishedAt into all 460 bundled manifests
from each folder's first-appearance commit date in upstream git
history.
- e2e: repository-resource guard requiring every bundled manifest to
declare a parseable publishedAt, so the signal cannot silently erode.
- docs: field reference entries in plugins-spec (en + zh-CN).
Issue: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/ca0778af-97f5-4fc0-bc63-739280904979
Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
* worker: 模板用最新筛选,筛选出来的不是最新的
Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
* fix(web): keep shipped appeal order inside same-publishedAt batches
Review follow-up (#5597, nettee): after the freshness tie on an equal
publishedAt, the comparator still consulted local updatedAt/installedAt,
which a fresh install stamps in folder-walk order — so every
same-publication batch (largest: 322 manifests on one catalog date)
re-sorted per machine instead of keeping the shipped visual-appeal
ranking. Same-publishedAt records now preserve their incoming position;
local timestamps break ties only when at least one side has no usable
publishedAt. Adds the requested regression spec pinning
same-publishedAt / different-updatedAt records.
Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
* fix(web): gate publishedAt freshness to bundled records; untrack oversized looper attachment
Review follow-up (#5597, nettee round 2): publishedAt was overriding
updatedAt for every visible plugin, so a user-installed github/local/
marketplace plugin carrying an old manifest publishedAt would be buried
behind bundled templates despite a newer local install. publishedAtMs
now returns null for non-bundled records — "Newest" means install
recency for user installs, catalog publication for bundled ones. Adds
the requested regression (non-bundled, old publishedAt, newer
updatedAt) and clarifies the field docs (en + zh-CN).
CI repair: commit
|
||
|
|
9d055a0fa6 |
feat(amr): add artifact-based upgrade guidance for Free users (#5591)
* feat(amr): add artifact-based upgrade guidance * fix(amr): fail open when billing plan is unavailable --------- Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com> |
||
|
|
db4613797e |
fix(web): replace retired Home/nav-rail logo with the current brand glyph (#5599)
The web app still shipped the pre-rebrand mark (444x444 dark tile with a white cursor ring) in /logo.svg, /logo.png, and /brand-icon.svg, so the Home hero, the entry nav rail, and the cloud onboarding page all showed the old logo. The landing page moved to the ink superellipse glyph in PR #3444; copy that glyph in as the web app's brand assets. Because the new glyph is a solid ink mark (not self-contrasting like the old dark tile), render it through a shared .od-brand-glyph primitive that masks /brand-icon.svg with --text-strong — the same pattern the cloud onboarding logo already used — so it reads as ink in light mode and cream in dark mode. Hero/nav-rail img tags become masked spans, and the hero's panel disc behind the mark is dropped since the glyph is self-shaped. home-logo-assets.test.ts now pins the asset contents (current glyph path present, retired markers absent) instead of only the img src strings. Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) |
||
|
|
bd67d8cc7b | fix(web): align fullscreen settings controls (#5550) | ||
|
|
e2502d0e5d |
feat(web,daemon): keep annotation position anchors when the screenshot is missing (#4105)
Re-applied onto current main (the original branch predated the server.ts -> runtimes/chat-prompt-inputs.ts extraction and 400+ upstream commits; a mechanical rebase was not meaningful). - normalizeCommentAttachments keeps screenshot-less visual marks that carry a usable anchor (positive-size pagePosition or selector), and drops anchor-less ones so the agent is never hard-scoped onto fields that identify no region - renderCommentAttachmentHint / web context render the structured location instead of a screenshot line when none exists, with a screenshot-less intent copy on both sides - ChatComposer builds the visual attachment whenever the mark has a location, with or without the screenshot upload (#4080 capture failures no longer reduce the send to bare prose) Fixes #4084 |
||
|
|
125c7141ad |
fix(web): anchor browser-assist guide and workspace snapshot toasts to their panes (#5590)
The Download Page guide toast emitted when Continue extraction cannot read any snapshot (and the other brand continue-extraction flow toasts) rendered through the viewport-fixed fallback, floating bottom-center across both panes and covering the composer send area. Scope them to the chat pane so they anchor between the transcript and the composer, matching the browser-assist confirm guide fixed in #5403. The workspace-owned Download Page snapshot progress/result toast (and tab launcher errors) had the same defect: a bare fixed .od-toast centers on the whole window. Wrap them in a .workspace-toast-anchor pinned to the workspace pane's bottom-center, with .workspace as the containing block. Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) |
||
|
|
906d6ff1f9 |
fix(web): keep the sticky home topbar above the popover-elevated composer card (#5596)
Opening any popover inside the home composer card (agent/model switcher, session-mode toggle, plugin picker, template menu) lifted the whole card to z-index 1700, which out-stacked the sticky entry topbar (z-index 10) and the fixed modal family (automation 80, plus-menu 90, plugins-import 100, use-everywhere 920). With the home view scrolled, the card body painted over the topbar chips (GitHub star, Teams, Discord, settings). The lift only needs to beat the static home content below the hero, whose stacking contexts top out at 8, so 9 restores the intended layering: content < elevated card < sticky topbar < fixed modals. Popover z-indexes inside the card are local to its stacking context and keep working unchanged. Source: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/928fae75-d660-426f-be8e-f98ed4eb88e1 Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) |
||
|
|
ebada4cac3 |
fix(daemon): pin mermaid dark-theme discipline into the deck framework prompt (#5595)
Dark-themed decks and pages that embed a Mermaid diagram had zero guidance on diagram theming, so the model shipped Mermaid's default (light) theme on dark slides: near-black edge labels, black strokes, and a transparent svg background sitting directly on the dark slide background. Reproduced on a dark deck slide (--bg #101014): edge labels measured ~3.3:1 contrast (below WCAG AA) and connector lines were nearly invisible (~1.9:1) — matching the 0.14.1 acceptance report "暗黑模式下 mermaid 图表的文字与背景对比度不足". Add a compact "Mermaid diagram theme discipline" section to DECK_FRAMEWORK_DIRECTIVE, sibling to the data-chart discipline (#5542): prefer deck-token-styled HTML/CSS/SVG diagrams; when embedding Mermaid, derive the theme from the slide background at initialize time (dark background → theme: 'dark', or theme: 'base' + themeVariables with a dark primaryColor — darkMode alone keeps the cream node fill default); never rely on the transparent svg backdrop; plus a drift-mode ban list and a pre-handoff/pre-emit self-check item. Mirror the identical section into the contracts copy so API/BYOK mode is covered too, keeping each side's self-check heading intact. With the disciplined init, the same dark slide renders the diagram at ≥7.9:1 (edge labels) and 10.5:1 (node text) contrast. Anchor the change with prompt-composition tests on both sides asserting the directive carries the mermaid-theme markers (red on main, green here). Source: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/9cd2904d-e52c-4813-9e4f-e7a3512aaa12 Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) |
||
|
|
53eff935e1 |
feat(amr): enable Parallel Search MCP (#5552)
Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
db0871f1b0 |
docs(agent-adapters): describe the data-oriented RuntimeAgentDef registry (not a class interface) (#5579)
* docs(agent-adapters): describe the data-oriented RuntimeAgentDef registry (not a class interface)
The doc described adapters as a class-shaped `interface AgentAdapter { detect/
capabilities/run/cancel/resume }` and said the implementation "lives in
apps/daemon/src/agents.ts". The shipped code is data-oriented: agents.ts is a
thin re-export barrel and the real contract is the `RuntimeAgentDef` object
literal (apps/daemon/src/runtimes/types.ts), one per CLI under runtimes/defs/*,
registered in the BASE_AGENT_DEFS array with a unique-id guard
(runtimes/registry.ts) and consumed by a generic detect/launch/invoke/stream
engine. Adding a CLI is one def file + one registry line, zero engine edits.
Rewrites the Adapter contract section and the source-layout section to match,
and fixes the agents.ts pointer.
* docs(agent-adapters): fix §11 layout-tree paths + restore two accurate blocks
Addresses the two review findings on #5579, each verified against
apps/daemon/src on this head (not taken on the reviewer's word):
1. §11 source-layout tree pointed several files at the wrong directory:
- claude-stream.ts / qoder-stream.ts / json-event-stream.ts / plain-stream.ts
live under runtimes/, not flat at src/
- the ACP and pi-rpc transports are agent-protocol/{acp,pi-rpc}/ directories
dispatched via agent-protocol/index.ts (attachAcpSession / attachPiRpcSession),
not flat *.ts files
- only copilot-stream.ts sits flat at src/
Also corrected the stale eventParser= annotations to the real streamFormat=
dispatch values (claude-stream-json, acp-json-rpc, pi-rpc, plain, ...).
2. Restore two still-accurate blocks the earlier rewrite dropped without mention:
- §5.13 Plain stream artifact handoff — streamFormat:'plain' is still used by
5 defs (aider/qwen/deepseek/antigravity/grok-build) and runtimes/plain-stream.ts
still scans stdout for <artifact> blocks and writes project files.
- the §5.7 OpenCode --dangerously-skip-permissions bullet —
runtimes/defs/opencode.ts still defines SKIP_PERMISSIONS_FLAG.
Docs-only; no code changes.
* docs(agent-adapters): stop presenting `gemini` as a shipped def (review round 2)
PerishCode's re-review found the doc named `gemini` as a concrete, existing
runtime, but there is no `runtimes/defs/gemini.ts` and no `geminiAgentDef` —
verified against the 25 real defs in registry.ts (amr, claude, codex, devin,
opencode, byok-opencode, hermes, trae-cli, grok-build, kimi, cursor-agent,
qwen, qoder, copilot, amp, pi, kiro, kilo, vibe, deepseek, aider, antigravity,
reasonix, codebuddy, mimo). Fixed the three real-artifact lists that claimed it
exists, and one adjacent accuracy gap:
- §1 defs list, registry snippet, and the §11 layout tree now use `cursor-agent`
/ `cursorAgentDef` / `cursor-agent.ts` (a def that actually ships).
- The "concrete def (shape)" example is recast as an explicitly made-up
`acme` CLI ("not a shipped def") so it can't be mistaken for a real file, and
its `streamFormat` now uses a real dispatch value (`claude-stream-json`).
- Corrected the `RuntimeAgentDef` field comments: `streamFormat` e.g. values
(`stream-json`/`text` -> `claude-stream-json`/`plain`) and, adjacent, the
`eventParser` e.g. values (`claude-stream`/`qoder-stream` -> the real
`codex`/`cursor-agent`/`opencode`).
The Gemini-CLI-as-P2-target prose (§5.6, the §5 capability table, the thesis)
is left as-is — that's design intent, not a claim the def exists today.
Docs-only; no code changes.
|
||
|
|
8d20f5566b |
feat(guard): catch @ts-nocheck files with unresolved relative imports (#5164)
* feat(guard): catch @ts-nocheck files with unresolved relative imports `// @ts-nocheck` disables all type checking for a file, including TS2307 "Cannot find module". That silently hides broken relative imports: a barrel move or file rename can leave a @ts-nocheck module importing a path that no longer resolves, and `pnpm typecheck` stays green while the module fails to load at runtime. tsc also never verifies the specifier of a dynamic import() even in a fully-checked file. Add a guard check (wired into `pnpm guard` and its unit-test list) that re-establishes the one guarantee @ts-nocheck removes: every relative import / export-from / import-type / dynamic import() specifier in a @ts-nocheck TypeScript source must resolve to a real module on disk. NodeNext .js -> .ts mapping, extensionless directory-index barrels, and asset extensions are all handled; string literals in comments/expressions are ignored (AST-based). Clean on the current tree: 33 @ts-nocheck files, 0 violations. * fix(guard): resolve .mjs/.cjs imports extension-specifically Review follow-up (#5164): resolvesRelativeSpecifier mapped .js/.mjs/.cjs to one broad candidate list, so `import './x.mjs'` passed whenever `x.ts` existed and `./x.cjs` whenever `x.ts`/`x.mts` existed — but NodeNext emits .mjs only from .mts and .cjs only from .cts (a same-basename .ts emits .js), so those imports fail at runtime. That was a false negative in the guard's core promise. Split candidates by requested JS extension: .js -> [.ts,.tsx,.js,.jsx], .jsx -> [.tsx,.jsx], .mjs -> [.mts,.mjs], .cjs -> [.cts,.cjs]. Add fixture assertions for the accepted matches and the rejected .mjs/.cjs mismatches. --------- Co-authored-by: LA <la@LAs-MacBook-Pro.lan> |
||
|
|
d954bee805 | Extract telemetry worker from monorepo (#5565) | ||
|
|
83d67d5a62 |
fix(packaged): widen win32 daemon status-wait budget + back off polling (#5515)
* fix(packaged): widen win32 daemon status-wait budget + back off polling Windows first launches are the single biggest packaged startup-crash bucket (packaged_runtime_failed, failure_kind=unknown): ~178 events / 61 devices/day, all status-wait timeouts with no exit code, signal, or daemon log. PostHog shows ~90% of those devices opened the app on a later launch — the daemon was slow, not dead — because Defender scanning freshly-written packaged binaries inflates the cold start (native better-sqlite3 load + first SQLite open/migrate + status-pipe bind) past the 35s wait budget. - resolveDaemonStatusTimeoutMs is now platform-aware: win32 gets 90s of AV-scan headroom, other OSes keep 35s. The OD_LEGACY_DATA_DIR migration override still wins on every platform. The web sidecar wait uses the same widened baseline (it has no legacy path). - waitForStatus backs off geometrically (150ms -> 1.5s cap) between connect polls instead of a flat 150ms, so a not-yet-bound pipe is not hammered with connects that add contention to an already-slow start. - classifyStartupFailure tags the timeout message as `status-timeout` instead of `unknown`, so the bucket is measurable — the signal for whether the budget raise actually drained the AV-scan-slow launches. * fix(packaged): keep timeoutMs a hard bound — cap the backoff sleep to remaining budget Review follow-up (@nettee): with the new geometric backoff, waitForStatus could overshoot the configured status-wait budget by a full backoff interval — a slow/dead sidecar could sleep the whole 1.5s cap after the deadline had passed before the while-condition re-checked it (~2.3s over, vs ~0.95s pre-change). Since this PR intentionally widens the win32 budget, the overshoot matters. Cap each poll sleep to the time remaining before the deadline (and break out immediately when none remains), so timeoutMs stays a hard-ish upper bound; the only residual overshoot is the single in-flight requestJsonIpc connect timeout. |
||
|
|
f23c96edff |
fix(web): classify deploy failures instead of the generic "Error" code (#5554)
* fix(web): classify deploy failures instead of the generic "Error" code `artifact_deploy_result` reported `err.name` on the deploy catch path, which collapses to the generic "Error" for a plain thrown Error — ~half of failed deploys (~40/day) land in that opaque bucket with no way to tell an auth failure from a network drop from a provider 5xx, so the deploy failure rate can't be root-caused from telemetry. Add `deployErrorCode(err)` (mirrors export-error-code.ts / #5220): structured `.code` wins, then map the common transport / provider-API signatures (NETWORK, TIMEOUT, RATE_LIMITED, FORBIDDEN, NOT_FOUND, HTTP_<status>, desktop sidecar skew) to fixed codes, else fall back to err.name. Only fixed codes are emitted — never free-form message text — so no deploy token, account id, zone, or URL can leak into analytics. * fix(web): preserve deploy HTTP status/code so classification isn't lost upstream Review follow-up (#5554): deployErrorCode can only emit HTTP_<status> or a structured code if that survives into the deploy catch, but deployProjectFile threw a bare `new Error(payload.error.message || ... )`, dropping the status whenever the provider/daemon returned a human message. So the daemon-backed deploy failures the classifier was meant to bucket still collapsed to "Error". deployProjectFile now attaches a `.code` to the thrown error: a structured provider/daemon `error.code` when present, else `HTTP_<status>`. deployErrorCode reads `.code` first, so those failures now bucket by status instead of "Error". Transport failures (fetch throws before a response) still classify via message. * fix(web): ignore the daemon's generic deploy envelope code so status bucketing runs Follow-up to @nettee's review: the daemon deploy route (apps/daemon/src/routes/deploy.ts) collapses every non-404 provider failure's code to `BAD_REQUEST` (and 404 to `FILE_NOT_FOUND`) while keeping the REAL provider HTTP status on the response and the real message in the body. Since the prior change preferred `payload.error.code`, a live 403/429/5xx was rethrown as `{ code: 'BAD_REQUEST' }` and deployErrorCode returned that generic code, never reaching HTTP_403 / RATE_LIMITED / etc. Ignore the generic envelope codes (shared `GENERIC_DEPLOY_ENVELOPE_CODES`) at both the throw site (fall back to the real `HTTP_${resp.status}`) and inside deployErrorCode as a safety net, so status/message-based bucketing runs. Added registry tests that exercise the actual daemon envelope (BAD_REQUEST+403 → HTTP_403, FILE_NOT_FOUND+404 → HTTP_404) and a deployErrorCode generic-code test. |
||
|
|
c6a1c9571b |
feat(landing-page): add Stuart Gardoll customer story (#5382)
* feat(landing-page): add Stuart Gardoll customer story Second /stories customer story (/stories/stuart-gardoll), following the ikigai-one pattern: a bespoke full-bleed v4 page (painterly hero + snapshot card, alternating bands, two autoplay proof videos, dark final CTA) authored as an HTML partial + per-locale i18n module, with canonical + [locale] routes so the hreflang alternates resolve. Adds a second card to the /stories index. English-first: the 18 locale routes render the English body until translations land (same fallback the blog uses). * chore(landing-page): use ikigai's OG share card on the stories hub Swaps the ikigai card image on /stories from the busy 'Unfair Advantage' promo composite to its 1.91:1 OG share card, so it sits consistently next to the new Stuart Gardoll card (both person-forward designed cards, left-anchored). * feat(landing-page): translate Stuart Gardoll story to all landing locales Adds the per-locale story body partials (stuart-gardoll-main.<locale>.html) and fills STORY_META / STORY_CARD for every landing locale, mirroring ikigai-one. The enabled locales (de/es/fr/it/ja/ko/pt-br/ru/tr/zh) now render fully translated /stories/stuart-gardoll pages; the remaining partials are staged for when those locales turn on. Brand names, links, videos, and HTML structure are preserved verbatim across every language. * feat(landing-page): apply Stuart Gardoll's requested story edits + relocalize Per the customer's sign-off email: rebrand Connectio → Connect I/O everywhere, new bio (AI engineer & consultant, Connect I/O founder, ex-FlutterFlow Growth Engineer, builds in public on Let's Build), name links to LinkedIn, Read It built in a Flutter and GCP stack, an About Stuart CTA band, and refreshed meta/OG description. His verbatim quotes (headline + pull-quotes) are kept in English across every locale at his request. Re-translated the 10 active landing locales from the revised source; dropped the 7 retired-locale partials so the new page ships only the active set. * chore(landing-page): correct Connectio → Connect I/O on Stuart share + hub card images The OG share card and the /stories hub card art had the old "Connectio" spelling baked in; regenerated with the correct "Connect I/O" per the customer's sign-off (his ask covered social-share text). --------- Co-authored-by: Scarlett Huang <scarletthuang@Scarletts-MacBook-Air.local> |
||
|
|
bdc66c978e |
fix(daemon): pin data-chart discipline into the deck framework prompt (#5542)
Chat-generated decks that fall through to DECK_FRAMEWORK_DIRECTIVE (no-skill-seed deck projects + the freeform slide-deck path) had zero guidance for data charts, so the model hand-wrote div/CSS bar charts with two stable failure modes that match the reported symptoms: bar lengths eyeballed as magic `height: NN%` numbers that don't track the data, and value labels clipped away inside fixed-height bars. Add a compact "Data chart discipline" section to the directive: a paste-ready skeleton that carries each value as an inline `--v`, declares one shared `--max` on the chart container, and computes bar length via `calc(var(--v) / var(--max) * 100%)`; a rule that every data point renders a visible category + value label outside the bar (never inside a clipping fixed-height track, same root cause as the brand-deck `funnel-label-clipped` guard); a drift-mode ban list; and two pre-handoff / pre-emit self-check items. Mirror the identical section into the contracts copy so API/BYOK mode is covered too, keeping each side's self-check heading intact. Anchor the change with prompt-composition tests on both sides asserting the directive now carries the chart-discipline markers (red on main, green here). Source: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/34ed7e1b-5676-48d6-8195-a4be71124ad4 Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) |
||
|
|
7332f43f88 | docs: clarify macOS od command collision (#5219) | ||
|
|
ae27ad2541 |
Add Atlas Cloud BYOK provider preset (#5406)
* Add Atlas Cloud BYOK provider preset * Add UTM to Atlas Cloud provider link --------- Co-authored-by: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com> |
||
|
|
6426847fba |
fix(web): separate run success from result delivery (#5525)
* fix(web): distinguish run success from result delivery Persist delivery outcomes separately so successful design runs cannot report completion before a usable artifact is available. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): recover missing design delivery after reload * chore(ci): rerun pull request checks * fix(web): avoid reattaching local delivery finalization * fix(web): retry delivery failures * chore(ci): rerun pull request checks * chore(ci): retrigger managed checks --------- Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
35e8ded24d | fix(web): restore silent update checkbox geometry (#5547) | ||
|
|
d55c3ff216 |
fix(web): align skills filter counts (#5450)
* fix(web): align skills filter counts Generated-By: looper 0.10.1 (runner=worker, agent=codex) * fix(web): type skills search locale helper Generated-By: looper 0.10.1 (runner=fixer, agent=codex) --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
e6bef56e19 |
fix(web): keep the split resize handle hitbox off the chat scrollbar gutter (#5526)
The chat/workspace split handle extends its pointer hitbox with a ::before overhang of left: -2px / right: -10px. The 2px chat-side overhang sits exactly on the outer edge of the chat-log's 8px scrollbar gutter, so clicks aimed at the (~4px visible) scrollbar thumb start a panel resize instead of scrolling, and hovering the gutter flips the cursor to col-resize with the divider highlight. In RTL the physical right: -10px lands on the chat side and covers the entire gutter. Switch the overhang to logical properties with a hard invariant: the hitbox never crosses the handle's inline-start edge (where the scrollbar lives in both directions) and extends 8px on the workspace side only (inset-inline-start: 0; inset-inline-end: -8px). Effective grab width stays 16px; resize JS is untouched. Red spec: e2e/ui/split-resize-scrollbar-hitbox.test.ts probes elementFromPoint on the gutter edge (LTR + RTL), asserts hover does not enter the resize posture, and keeps a drag-the-handle regression guard. Source: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/5c505d35-42e9-40e7-ab07-ba6e3bbfb2ee Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) |
||
|
|
3e09ebf282 |
feat(release): publish localized changelog metadata (#5520)
* feat(release): publish changelog metadata * fix(release): verify release notes from stored bytes Generated-By: looper 0.10.3 (runner=fixer, agent=codex) --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
7e302031d3 |
fix(web): use current logo on home surfaces (#5396)
Fixes #5365 Verified with focused RED/GREEN regression, web typecheck, pnpm guard, root typecheck, visual DOM readback, and independent Codex diff review. |
||
|
|
4b4c7f4028 |
Improve empty agent output guidance (#4241)
* Improve empty agent output guidance * Preserve updated empty-output guidance |
||
|
|
5127ad188b |
fix: add Kiro to agentLabels.ts (AGENT_LABELS + AGENT_ALIASES) (#5085)
* fix: add Kiro to agentLabels.ts (AGENT_LABELS + AGENT_ALIASES)
- Add kiro to AGENT_LABELS so exactAgentDisplayName('kiro') returns 'Kiro'
- Add 'kiro cli' and 'kiro-cli' aliases to AGENT_ALIASES
- Kiro icon (kiro.svg) already exists, this was purely a missing label mapping
Closes #5081
* test: pin Kiro label and alias resolution in agentLabels utility
PerishCode's review on PR #5085: mapping change is merge-safe but the
new behavior should be pinned in the existing utility test matrix.
Added 4 tests covering:
- agentDisplayName resolves 'kiro cli' → 'Kiro' (alias)
- agentDisplayName resolves 'kiro-cli' → 'Kiro' (alias)
- exactAgentDisplayName returns 'Kiro' for exact id 'kiro' (canonical label)
- exactAgentDisplayName returns null for 'kiro cli'/'kiro-cli' (aliases are
not themselves agents — substring aliases stay routed via agentDisplayName
and do not pollute the exact-id table)
Locks the fix from db025410 and prevents regression.
* test: update kiro alias assertions to match exactAgentDisplayName behavior
|
||
|
|
2a6aaeb3d2 |
refactor(daemon): group browser bridges into browser/ (#5192)
Co-authored-by: LA <la@LAs-MacBook-Pro.lan> |
||
|
|
80f94faa04 |
fix(daemon): classify structured Vela ACP auth-required details as AMR_AUTH_REQUIRED (#5158)
classifyAmrAccountFailureDetails only mapped the insufficient_balance / recharge half of the accountAction union. When vela reports an auth failure through ACP structured details while the protocol message is generic, the run fell into the opaque AGENT_EXECUTION_FAILED bucket instead of the AMR_AUTH_REQUIRED card with its relogin affordance. Add the symmetric auth_required / relogin branch, mirroring the structured balance branch that #5053 introduced. Co-authored-by: tomsen-ai <230283659+tomsen-ai@users.noreply.github.com> |
||
|
|
aa2d631473 |
fix(daemon): classify antigravity silent-exit connection tests instead of "exit 0" (#5431)
The Antigravity CLI (`agy`) is silent on stdout/stderr in print mode for both missing-auth and quota-exhausted failures: it exits 0 with no assistant text and only records the real upstream error in its `--log-file`. The connection-test path never handed `agy` a `--log-file` and never inspected one, so every such failure collapsed into `kind: 'unknown'` with `detail: 'exit 0'`, which Settings renders as the opaque "Test failed: exit 0" the user reported — with no hint that they simply need to sign in. Mirror the chat-run empty-output guard already in server.ts: give the smoke test a temp `--log-file` under its throwaway cwd, and when `agy` produces no visible text, fold the log tail into the classifier input and route to the specific result — `agent_auth_required` (with the actionable "open a terminal and run `agy` to sign in" guidance) or `rate_limited` (quota) — falling back to `agent_auth_required` on a clean silent exit, since that empirically means missing OAuth. A non-clean crash still reports as `agent_spawn_failed` with its exit code. Both the onboarding and Settings test surfaces already render these kinds, so the fix reaches the UI (and the `od` CLI, which shares the endpoint) with no contract or i18n change. Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) |
||
|
|
91d737d10e | docs: add Reasonix to localized platform tables (#5477) | ||
|
|
e522062121 | fix(web): isolate BYOK messages endpoint models (#5494) | ||
|
|
f841cdf3fe |
Add Osaka meetup recap and AI PPT tutorial (#5439)
Co-authored-by: koki yanlai xu <koki@kokideMacBook-Air.local> |
||
|
|
6c457a6436 |
fix(daemon): preserve data dir for media wrapper env (#5459)
* fix(daemon): preserve data dir for media wrapper env Generated-By: looper 0.10.2 (runner=worker, agent=codex) * fix(daemon): derive AMR home from daemon data dir Generated-By: looper 0.10.2 (runner=fixer, agent=codex) |
||
|
|
8325aa3d21 |
test: expand project context daemon coverage (#5516)
* test: cover project context daemon flows * test: remove private daemon import from e2e Generated-By: looper 0.10.1 (runner=fixer, agent=codex) |
||
|
|
c6241ecad2 | fix(daemon): honor BYOK media defaults in dispatch hint (#5440) | ||
|
|
d4762a9522 |
fix(desktop): break renderer crash-reload loops with a circuit breaker (#5322)
* fix(desktop): break renderer crash-reload loops with a circuit breaker A renderer that crashes deterministically — a GPU/V8 CHECK failure, a corrupt profile that aborts on every load — is reloaded forever by the desktop poll loop: load → crash → reload → crash. One 0.14.0 device did exactly this, emitting 26,011 desktop_renderer_crash events in a single day (96% of all renderer crashes on the release) while its window stayed blank the whole time. Add RendererCrashLoopBreaker (pure, unit-tested): after 5 crashes inside a 60s rolling window it opens. The poll loop then parks on a static, dependency-free error screen that explains recovery is automatic, rather than cycling the crash; markRendererFailed no-ops while open. Per-crash telemetry is suppressed once open — the tripping crash is still reported, flagged loop_tripped — so one wedged device can't emit tens of thousands of identical events. The breaker re-arms after a 5-minute quiet cooldown so a transient fault still self-heals without a reinstall. * feat(desktop): make crash-loop recovery active + observable Review feedback: the breaker only "waited" — its cooldown re-arm reloaded the same URL from the same cache, and the whole flow was near-invisible. On re-arm it now actively recovers: clears the HTTP + V8 code caches (non-destructive — no cookies or storage) before the retry reload, targeting the most common recoverable cause (a corrupt cached bundle or shader) instead of hoping conditions changed on their own. And it's now observable: a console.warn when the breaker opens (parking), a console.info per recovery attempt, and a bounded recovery-attempt analytics signal (reason:"recovery-attempt", recovery_attempt: N, at most one per cooldown). Triage can tell a chronic looper (index keeps climbing) from a device that recovered (events simply stop) without re-introducing the per-crash spam the breaker was built to kill. * revert(desktop): drop active cache-clear recovery, keep it passive + observable Reviewer worry: clearing caches on every cooldown could amplify churn on a wedged device without helping the dominant crash cause (a GPU/V8 CHECK, exit 0x80000003), i.e. pour oil on the fire. Back the breaker off to "stop the loop + passive retry": on re-arm it reloads without mutating any device state and lets a transient fault clear on its own. The observability added alongside stays: a warn on breaker-open, an info per recovery attempt, and the bounded recovery-attempt analytics signal (recovery_attempt: N) so chronic loopers are still visible. * feat(desktop): add report + save-logs actions to the renderer crash screen The recovery screen was informational only. On a renderer crash the daemon is still alive, so give the wedged user an actionable exit — reusing IPC the preload already exposes, no new surface: - "Report a problem" → window.__od__.openExternal() opens a prefilled GitHub new-issue whose title + body carry the app version, OS, and the renderer exit code (e.g. 0x80000003) a triager needs, and ask the user to attach the logs. - "Save logs…" → window.openDesignDesktop.exportDiagnostics() runs the existing diagnostics-bundle export (save dialog → zip). Neither an issue URL nor mailto can carry a file attachment, so the flow is save-then-attach. Each button hides itself if its bridge is missing (preload failed to load) to avoid a dead control. * feat(desktop): add "Email us" (support@open-design.ai) to the crash screen Alongside "Report a problem" / "Save logs…", the recovery screen now offers a prefilled mailto to our support address for users who'd rather email than open a GitHub account. It reuses the existing openExternal bridge; the shell:open-external handler now also allows a mailto addressed strictly to the support address (isSupportMailtoUrl, unit-tested) — no other scheme or address opens, so widening past http can't be abused into arbitrary-scheme launches. The link hides itself if the bridge is absent, like the other actions. * style(desktop): match crash screen to the app's monochrome brand palette The crash screen used a stray blue accent that didn't match the app. Repaint it with the neutral design tokens from apps/web (tokens.css): warm off-white #faf9f7 + near-black #1a1916, a monochrome near-black primary button, and a plain underlined (not blue) email link. Dark mode inverts the pair to a light button on the dark bg (a near-black button would vanish), mirroring the black/white onboarding look. * fix(desktop): address review — tighten support mailto + reveal crash screen Two Looper findings on the crash-screen actions: - Security: isSupportMailtoUrl validated only protocol + pathname, so `mailto:support@open-design.ai?bcc=attacker@example.com` (or ?to=/?cc=) still passed and shell.openExternal would launch the mail client with extra recipients/headers through the renderer-exposed bridge. Now reject any query key other than the crash screen's own subject/body. - Startup lifecycle: parking on the crash screen left `revealed` false and the splash open when the loop tripped before revealWhenReady(), so the runtime's show() kept focusing the splash — a user re-focusing during a startup crash loop was sent back to the boot splash. Route the park through revealMainWindow() so the recovery screen becomes the revealed, focused window and the splash is torn down (no-op after normal reveal). * feat(diagnostics): make the support bundle root-cause native renderer crashes Audit of "Save logs…" for the crash-recovery screen: on a renderer crash the bundle collected text logs but NOT the one artifact that can explain an opaque native abort like 0x80000003 (a V8/Chromium CHECK) — a minidump. crashReporter was never started (no dumps existed anywhere), the crash-report collector was macOS-only, and there was no GPU info (the usual suspect for such crashes). Close the gap end to end: - apps/desktop: start crashReporter (uploadToServer:false — on-device only) and relocate Electron's crashDumps into <logs/desktop>/crashes, inside the log tree the export already bundles; snapshot GPU + feature-status + runtime info to <logs/desktop>/gpu-info.json at startup. - packages/diagnostics: add a `binary` LogSource kind (copied verbatim, no text decode/redaction so a minidump isn't corrupted) and findCrashDumps() to collect .dmp files; buildDiagnosticsZip gains a `crashDumps` input. - apps/daemon: bundle logs/desktop/gpu-info.json and the relocated crash minidumps (cross-platform, capped, within 14 days). The renderer crash context (reason/exit code/url) is already captured — the main-process console.error lands in logs/desktop/latest.log — so a bundle now carries stack (minidump) + GPU state + context: enough to root-cause. * fix(desktop): mkdir crashDumps dir before setPath so first-crash dumps land in the log tree Review (PerishCode): app.setPath('crashDumps', dir) throws when the dir does not exist; on a fresh runtime logs/desktop/crashes won't exist yet, so setPath threw, we fell back to Electron's default crashDumps location, and the daemon export (which only scans <logs/desktop>/crashes) missed the minidump for exactly the first crash loop this is meant to root-cause. mkdirSync the dir (recursive) before setPath. * fix(desktop): reject CR/LF in the support mailto subject/body Review (PerishCode): the key allowlist rejected to/cc/bcc keys, but a decoded CRLF in an allowed value (subject=ok%0D%0ABcc:attacker@…) could still inject a mail header once the mailto reaches the mail client. Reject any subject/body value containing CR or LF. |
||
|
|
fc5b276f7d |
feat(ci): surface merge-queue needs-validation ejection as a PR comment (#5519)
A PR ejected from the merge queue by the needs-validation gate leaves no trace on the PR itself: the gate fails on the queue's transient ref, the PR's own checks stay green, and mergeQueueEntry silently goes null. The only way to learn why the PR fell out of the queue is to dig through merge_group workflow runs, which nobody watches (observed on #5329). Producer: the merge_group gate in ci.yml now emits a handoff/comment artifact targeting the labeled PR right before it blocks, and uploads it on the failure path. Consumer: comment.atom.yml accepts merge_group workflow_run events; since a merge_group run's head_sha is the queue's synthetic merge commit, artifacts from those runs are bound to their producing run by run_id, and the base-freshness skip is waived (PRs ahead in the queue move the base while the run completes). The live-PR head check still guards staleness. Topology coverage pins the new boundary: comment.atom consumes pull_request + merge_group; autofix/report stay pull_request-only. |
||
|
|
88b411efd7 |
fix(daemon): reap the failed attempt's process group on same-run retry (#5463)
* fix(daemon): reap the failed attempt's process group on same-run retry
A same-run retry tore down the failing attempt with run.child.kill('SIGTERM') —
a signal to the direct child PID only. Killing a parent does not kill its
children on POSIX, so any descendants the CLI spawned (MCP servers, tool
subprocesses, internal runners) were orphaned (re-parented to PID 1) and kept
running. One process group leaked per retry, accumulating over the daemon's
lifetime. The cancel path already disbands the whole group via
process.kill(-pgid); the daemon even spawns detached and captures
run.processGroupId, but the retry teardown never used it.
Add signalProcessGroup(pgid, signal) — a group signal that works even after the
direct child object has exited (grandchildren outlive it), kept SEPARATE from
signalChildProcess so the shared cancel/escalation path keeps its childHasExited
guard against the cross-generation kill fixed in #5202. reapProcessGroup(pgid)
does SIGTERM then a SIGKILL escalation, both bound to the CAPTURED pgid.
tearDownAttemptForRetry now snapshots the attempt's child + pgid, reaps its
group by the captured pgid (falling back to a direct-child signal on win32 / no
pgid), and clears run.processGroupId so the next attempt captures cleanly. The
escalation targets the captured pgid, never the shared field the next attempt
overwrites — so it cannot hit the healthy new attempt's group (#5202).
Fixes #5462.
Regression: tests/retry-orphan-process-group.test.ts drives a full run over the
HTTP API with a fake CLI that spawns a long-lived grandchild then stalls into a
retryable timeout; it asserts the grandchild is dead after the run finishes. Red
on origin/main (grandchild survives, PPID=1), green with this fix. Verified live
on a real daemon: a 20x retry stress leaked 20/20 grandchildren before the fix
and 0/20 after, with every run still completing 'succeeded'.
* fix(daemon): reap the process group on cancel/shutdown when the child already exited
Secondary case from #5462: when the parent CLI has already exited but a
grandchild still holds the inherited stdio pipe (run still running), cancel
returned 200 and finished the run canceled, but the grandchild survived — the
same childHasExited short-circuit in signalChildProcess that the retry teardown
hit. killChild (used only by the terminating cancel / shutdownActive paths, never
by a re-spawning retry) now falls back to signalProcessGroup by pgid when the
direct child has already exited, so cancel/shutdown reap surviving descendants
too. No cross-generation risk: both callers terminate and never re-spawn into
the pgid (cf. #5202).
Covered by the existing cancel/shutdown/lifecycle suites staying green (no
regression); the retry-orphan regression exercises the shared signalProcessGroup
helper end-to-end. A dedicated cancel-after-parent-exit HTTP regression can be
added if preferred — the forensics harness confirms this case manually.
---------
Co-authored-by: tomsen-ai <230283659+tomsen-ai@users.noreply.github.com>
|