@gajae-code/coding-agent 0.12.1 → 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/types/config/settings-schema.d.ts +2 -2
- package/dist/types/gjc-runtime/ralplan-review-conflicts.d.ts +114 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -3
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +6 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +16 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +1 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +9 -0
- package/dist/types/modes/theme/theme.d.ts +3 -1
- package/dist/types/modes/types.d.ts +32 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -2
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +22 -3
- package/dist/types/task/index.d.ts +4 -0
- package/dist/types/task/output-manager.d.ts +1 -0
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/tools/index.d.ts +2 -0
- package/package.json +8 -8
- package/scripts/dogfood-ralplan-review-conflicts.ts +272 -0
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/scripts/run-acp-conformance.ts +5 -0
- package/src/async/job-manager.ts +22 -20
- package/src/config/settings-schema.ts +2 -2
- package/src/debug/index.ts +42 -17
- package/src/defaults/gjc/skills/ralplan/SKILL.md +9 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -2
- package/src/gjc-runtime/launch-worktree.ts +103 -1
- package/src/gjc-runtime/ledger-event-renderer.ts +1 -0
- package/src/gjc-runtime/ralplan-review-conflicts.ts +472 -0
- package/src/gjc-runtime/ralplan-runtime.ts +80 -10
- package/src/gjc-runtime/ultragoal-evidence.ts +6 -74
- package/src/gjc-runtime/ultragoal-runtime.ts +236 -3
- package/src/gjc-runtime/workflow-manifest.generated.json +29 -0
- package/src/gjc-runtime/workflow-manifest.ts +17 -2
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/modes/DESIGN.md +53 -0
- package/src/modes/components/irc-sidebar.ts +30 -1
- package/src/modes/components/tool-status-header.ts +0 -1
- package/src/modes/controllers/command-controller.ts +38 -34
- package/src/modes/controllers/event-controller.ts +27 -16
- package/src/modes/controllers/extension-ui-controller.ts +186 -34
- package/src/modes/controllers/selector-controller.ts +115 -22
- package/src/modes/interactive-mode.ts +189 -62
- package/src/modes/jobs-observer.ts +1 -1
- package/src/modes/theme/theme.ts +11 -9
- package/src/modes/types.ts +88 -0
- package/src/sdk/broker/lifecycle.ts +18 -13
- package/src/sdk/bus/chat-daemon-control.ts +4 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +3 -2
- package/src/sdk/protocol/operation-inventory.generated.json +11 -0
- package/src/sdk/session.ts +1 -0
- package/src/session/agent-session.ts +22 -1
- package/src/session/internal/managed-session-scope.ts +158 -107
- package/src/session/internal/managed-session-storage.ts +445 -57
- package/src/session/session-storage.ts +29 -44
- package/src/task/index.ts +257 -31
- package/src/task/output-manager.ts +24 -14
- package/src/task/receipt.ts +11 -1
- package/src/tools/index.ts +2 -0
|
@@ -57,7 +57,7 @@ export const EMBEDDED_DOCS: Readonly<Record<string, string>> = {
|
|
|
57
57
|
"onboarding-packet.md": "# Gajae-Code Onboarding Packet\n\nThis packet is a docs-only, public-safe context seed for the `gajae-code` repository as inspected on 2026-06-01. It is intentionally a no-new-skill artifact: not a new workflow skill, command, agent, configuration surface, issue template, or runtime behavior.\n\n## Purpose in one paragraph\n\nGajae-Code is the `gjc` coding-agent CLI and supporting monorepo. The product centers on a small public workflow loop: clarify with `deep-interview`, plan with `ralplan`, execute and verify through `ultragoal`, and use `team` only when parallel tmux workers are useful. The main product package is `packages/coding-agent/`; supporting packages provide LLM/provider access, agent runtime, TUI rendering, native helpers, stats, utilities, benchmarks, and SDK machine interfaces.\n\n## Fixed public surface\n\nKeep this invariant front-and-center when onboarding to the repo:\n\n- Default workflow skills: `deep-interview`, `ralplan`, `team`, `ultragoal`.\n- Public role agents: `executor`, `architect`, `planner`, `critic`.\n- Bundled default workflow skill sources live under `packages/coding-agent/src/defaults/gjc/skills/`.\n- Bundled role-agent prompt sources live under `packages/coding-agent/src/prompts/agents/`.\n- Runtime state, specs, plans, goals, team state, and local overrides belong under `.gjc/` for the product and `.omx/` only for this agent-run orchestration.\n\nDo not add a fifth default skill, fifth public role agent, new command, new config surface, or feature-intake behavior unless that product decision has already been made and the default-surface gates are updated.\n\n## Primary entrypoints\n\n| Area | Repo-relative path | Why it matters |\n| ---------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| CLI bootstrap | `packages/coding-agent/src/cli.ts` | Registers top-level CLI commands and routes default launch behavior. |\n| Session launch | `packages/coding-agent/src/main.ts` | Converts CLI/runtime settings into agent-session creation and mode dispatch. |\n| Agent assembly | `packages/coding-agent/src/sdk/session.ts` | Loads settings, default skills, rules, tools, auth/model state, system prompt, and agent runtime. |\n| Built-in tools | `packages/coding-agent/src/tools/index.ts` | Registers file, shell, edit, search, browser, task/subagent, and related public coding-harness tools. Memory backends are private integrations, not public tools. |\n| Default skills | `packages/coding-agent/src/defaults/gjc-defaults.ts` | Embeds and installs the four default workflow skills plus deep-interview fragments. |\n| Role agents | `packages/coding-agent/src/task/agents.ts` | Embeds bundled task-agent prompts; tests enforce public role-agent expectations. |\n| Product overview | `README.md` | Explains installation, product story, fixed workflow surface, and development entry commands. |\n| Architecture map | `docs/codebase-overview.md` | Public package map and runtime-flow reference. |\n\n## Package map\n\n- `packages/coding-agent/` — main `gjc` CLI, workflows, session runtime, tool registry, discovery, settings, prompts, and tests.\n- `packages/ai/` — provider/model boundary, streaming, auth, model registry, retries, and provider integrations.\n- `packages/agent/` — stateful agent loop and append-only context runtime.\n- `packages/tui/` — terminal UI framework and rendering primitives.\n- `packages/natives/` plus `crates/*` — native helpers, Rust/N-API bindings, shell/PTY, text search, AST, filesystem, and media utilities.\n- `packages/utils/` — shared TypeScript utilities, logging, formatting, process helpers, JSON/frontmatter, and sanitization.\n- `packages/stats/` — local observability dashboard and session/model usage aggregation.\n- `packages/typescript-edit-benchmark/` — TypeScript edit benchmark tooling.\n- External machine clients use the SDK WebSocket interface documented in `docs/sdk.md`; `--mode rpc`, `--mode rpc-ui`, and `--mode bridge` were removed.\n\n## Build, test, and validation commands\n\nPrefer targeted checks first, then broader checks when code changes justify them. For this docs-only packet, lightweight validation is enough.\n\n| Command | Scope | When to use |\n| --------------------------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------- |\n| `bun install` | Workspace dependencies | Initial local setup. |\n| `bun run install:defaults` | Local default install | Installs source-bundled default workflow definitions for local development. |\n| `bun packages/coding-agent/src/cli.ts --help` | CLI smoke/discovery | Fast source checkout CLI sanity check. |\n| `bun run check:ts` | Type/lint/default UI checks | Broad TypeScript validation; heavier than docs-only changes. |\n| `bun run test` | Full TS + Rust tests | Broad regression check; use for runtime/product changes. |\n| `bun run ci:test:smoke` | CLI version/help/stats worker smoke | Useful before release/install changes. |\n| `bun scripts/check-visible-definitions.ts` | Default surface gate | Required after workflow-definition changes. |\n| `bun scripts/verify-g002-gates.ts` | Rebrand/default-surface gate | Required after workflow-definition or public-surface changes. |\n| `bun scripts/rebrand-inventory.ts --strict` | Rebrand inventory gate | Required after workflow-definition or public-surface changes. |\n| `bun test packages/coding-agent/test/default-gjc-definitions.test.ts` | Four-skills/four-agents contract | Required after default workflow/agent surface changes. |\n\nRepository rule: do not run `tsc` or `npx tsc`; use the Bun scripts above.\n\n## Danger zones\n\n- **Default surface expansion:** `packages/coding-agent/src/defaults/gjc/skills/`, `packages/coding-agent/src/defaults/gjc-defaults.ts`, `packages/coding-agent/src/prompts/agents/`, and model-assignment tests are contract-heavy. Changes here can accidentally alter the fixed four-skills/four-agents shape.\n- **CLI commands:** `packages/coding-agent/src/cli.ts` and `packages/coding-agent/src/commands/` define visible behavior. Adding commands or aliases is a product-surface change.\n- **Runtime/session assembly:** `packages/coding-agent/src/main.ts`, `packages/coding-agent/src/sdk/session.ts`, discovery, settings, tools, and system-prompt paths can affect every session.\n- **TUI/logging:** Avoid `console.log`, `console.warn`, or `console.error` inside `packages/coding-agent/`; use the centralized logger to avoid corrupting TUI rendering.\n- **Secrets/auth/config:** Keep `docs/secrets.md`, auth broker/gateway code, settings, and environment-variable docs public-safe. Do not expose tokens or private infrastructure.\n- **Native/Rust build:** `packages/natives/` and `crates/*` can require platform-specific toolchains and CI artifact behavior.\n- **Generated model data:** Do not edit `packages/ai/src/models.json` directly; update generators/descriptors/resolvers and regenerate with `bun --cwd=packages/ai run generate-models`.\n\n## Unknowns worth preserving\n\n- Which onboarding packet shape will be most useful for future `gjc` context ingestion is still an experiment, not a product contract.\n- Public issue #158 / `gajae-deep-onboarding` context is summarized only from the user-provided prompt in this run; this packet does not add issue intake or feature workflow behavior.\n- Full CI may depend on runner/system dependencies and native artifacts; docs-only changes usually do not need the full matrix locally.\n- Some packages contain internal or hidden utility prompts/agents beyond the four public role agents. Public-facing docs should keep the four-role contract clear.\n\n## First safe tasks for a new contributor or agent\n\n1. Read `README.md`, `docs/codebase-overview.md`, and this packet.\n2. Run `bun packages/coding-agent/src/cli.ts --help` for a fast CLI surface check after dependencies are installed.\n3. For docs-only edits, run formatting/check commands that do not mutate runtime behavior.\n4. For default-surface edits, run the four required gates listed in the command table before claiming completion.\n5. For package code edits, start with the nearest package test, then escalate to `bun run check:ts` or `bun run test` as risk increases.\n6. Before changing `packages/coding-agent/src/defaults/gjc/skills/`, `packages/coding-agent/src/prompts/agents/`, `packages/coding-agent/src/commands/`, or config/settings paths, write down whether the change alters public surface area.\n\n## Context seed checklist\n\nA future agent can use this packet as context if it preserves these constraints:\n\n- Keep changes public-safe and repo-relative.\n- Prefer docs and tests over new runtime abstractions for onboarding experiments.\n- Treat the fixed four-skills/four-agents shape as a product constraint.\n- Verify claims with repo files before summarizing them.\n- Report validation evidence and caveats instead of implying hidden automation.\n",
|
|
58
58
|
"onboarding-receipt.md": "# Onboarding Packet Receipt\n\n- Date: 2026-06-01\n- Scope: docs-only no-new-skill onboarding packet experiment for this repository.\n- Output files:\n - `docs/onboarding-packet.md`\n - `docs/onboarding-receipt.md`\n- Public-safe boundary: no secrets, tokens, hidden prompts, private infrastructure, internal ops, or private paths beyond repo-relative paths.\n- Product boundary: no new skill, command, agent slot, issue, config, or runtime behavior.\n\n## Evidence inspected\n\n- `README.md`\n- `docs/codebase-overview.md`\n- `package.json`\n- `packages/coding-agent/package.json`\n- `packages/coding-agent/src/cli.ts`\n- `packages/coding-agent/src/main.ts`\n- `packages/coding-agent/src/sdk/session.ts`\n- `packages/coding-agent/src/defaults/gjc-defaults.ts`\n- `packages/coding-agent/src/task/agents.ts`\n- `packages/coding-agent/test/default-gjc-definitions.test.ts`\n- `.github/workflows/ci.yml`\n- `.github/workflows/dev-ci.yml`\n\n## Result\n\nThe packet records repo purpose, package layout, main entrypoints, build/test commands, danger zones, unknowns, and first safe tasks without changing the product surface. It is suitable as a public context seed for future onboarding experiments, not as a feature intake mechanism.\n\n## Caveats\n\n- The attempted `omx question --input '<json>' --json` interview round failed before user input because the runtime reported no attached tmux client; no human answer was inferred from that failed call.\n- Public issue context is limited to the user-provided prompt summary for this run.\n- Full CI was not required for the docs-only artifact unless later code/runtime files change.\n",
|
|
59
59
|
"ooo-bridge-extension-contract.md": "# Ouroboros `ooo` bridge extension contract\n\nGJC exposes the `ooo` bridge through the existing extension input-event surface. It is not a default workflow skill, hook, slash command, or built-in agent.\n\n## Interception surface\n\nExtensions register an `input` handler:\n\n```ts\nimport { createOuroborosOooBridge } from \"@gajae-code/coding-agent/extensibility/extensions\";\n\nexport default function activate(gjc) {\n gjc.on(\"input\", createOuroborosOooBridge());\n}\n```\n\nThe handler matches only the bare exact prefix:\n\n- `ooo`\n- `ooo ...`\n\nIt does not match embedded or longer-token text such as `please ooo status`, `oooo`, or `/ooo`.\n\nThe extension runner already treats `InputEventResult.handled === true` as terminal: the input is not sent through normal model flow. An empty result (`{}`) means continue/pass-through, preserving existing chained input handlers and normal prompt handling.\n\n## Dispatch and result semantics\n\n`createOuroborosOooBridge()` is a small specialization of `createExactPrefixCommandBridge()`:\n\n- command: `ouroboros`\n- arguments: `dispatch`, then the full submitted input text\n- recursion guard variable: the Ouroboros bridge recursion-depth environment variable\n\n- continue/pass-through exit code: `78`\n\nExit-code mapping:\n\n| Dispatch result | GJC input result |\n| --- | --- |\n| `0` | `{ handled: true }`; do not send input to the model. |\n| `78` | `{}`; continue/pass-through so GJC processes the input normally. |\n| any other non-zero | Surface an extension error notification using stderr, then stdout, then a generic exit-code message, and return `{ handled: true }`; the failed `ooo` command is terminal and is not sent to the model. |\n\n## Recursion guard\n\nBefore dispatch, the helper increments the Ouroboros bridge recursion-depth environment variable and restores its previous value after dispatch finishes. A current numeric depth of `0` or `1` is dispatchable, which preserves concurrent independent interactive inputs while marking child dispatcher processes with depth `1`. A current numeric depth greater than `1`, or any non-empty non-numeric value, returns `{}` without dispatching.\n\nThis means the bridge allows exactly one inherited bridge-marked dispatcher level and blocks recursive re-entry from deeper bridge-marked children. The guard also passes through `event.source === \"extension\"` to avoid extension-originated messages re-entering the bridge.\n\n## Installation and discovery\n\nThe canonical install location is the agent extensions directory discovered by the native GJC provider:\n\n- user-level: `$HOME/${GJC_CONFIG_DIR:-.gjc}/agent/extensions`\n- project-level: `<cwd>/.gjc/extensions`\n\nFor native discovery, install one of:\n\n- `extensions/<name>.ts` or `extensions/<name>.js`\n- `extensions/<name>/index.ts` or `extensions/<name>/index.js`\n- `extensions/<name>/package.json` declaring extension entries\n\nThe loader scans one level under each `extensions` directory. Complex packages should use a package manifest instead of relying on recursive discovery.\n\n`GJC_CONFIG_DIR` selects the **home-relative** config directory name: the config root is `<home>/<GJC_CONFIG_DIR>`, defaulting to `~/.gjc`. It does not select a project directory — the project-level path is the constant `.gjc` (`discovery/helpers.ts`, `getProjectAgentDir()`), so `GJC_CONFIG_DIR` never moves it. `GJC_CODING_AGENT_DIR` overrides the agent directory **path** rather than naming one under `$HOME`; it is resolved with `path.resolve`, so an absolute value is used as-is and a relative value is resolved against the current working directory.\n\nDiscovery is the exception to that second override. The native provider builds its user-level root from `GJC_CONFIG_DIR` alone (`<home>/<config-dir>/agent`) and never consults `getAgentDir()`, so an operator who sets `GJC_CODING_AGENT_DIR` moves the agent directory for the rest of the product but **not** for extension, skill, rule, or hook discovery.\n\nHooks are not the input bridge surface: `packages/coding-agent/src/capability/hook.ts` defines pre/post tool hooks only.\n",
|
|
60
|
-
"perf-profiling-corpus.md": "# Perf profiling corpus\n\nThe profiling corpus is the **successor** to the static [`cpu-hotspot-map.json`](./cpu-hotspot-map.json) ranking (see [`hotspot-map-successor.md`](./hotspot-map-successor.md)). The static map ranked hotspots by complexity × trigger frequency but never measured real CPU self-time. The corpus replaces that guess with measured, separated evidence and is the source of future perf prioritization.\n\nImplementation:\n\n- Schema + evidence taxonomy + validation: `packages/coding-agent/bench/perf-corpus-schema.ts`\n- Runner: `packages/coding-agent/bench/perf-corpus.bench.ts`\n- Threshold/evidence ledger: `packages/coding-agent/bench/perf-threshold.ledger.ts`\n- Tests: `packages/coding-agent/test/perf-corpus.test.ts`\n- Deterministic memory surface workloads: `packages/coding-agent/bench/memory-baseline-workloads.ts`\n\n## Evidence taxonomy\n\nEach metric and optimization claim is classified by **evidence class**. These classes must never be conflated:\n\n| Class | Meaning | Sufficient for CPU self-time? |\n|---|---|---|\n| `wall-clock-proxy` | elapsed time around a phase/operation | No |\n| `process-cpu-usage` | `process.cpuUsage()` user/system deltas | No |\n| `profiler-self-time` | profiler/sampled attribution of self-time to a symbol | **Yes (required)** |\n| `rss-memory` | RSS/heap baseline/growth/return | No (memory only) |\n| `byte-parity` | golden rendered/persisted/provider/materialized comparisons | n/a (safety) |\n| `ledger-approved-threshold` | human-approved threshold change | n/a (process) |\n\nOptimization **status vocabulary** for a hotspot:\n\n- `CPU-self-time confirmed` — requires `profiler-self-time` evidence (an `artifactPath` or non-empty `samples`).\n- `fallback-toggle-confirmed` — comparable before/after or feature/fallback-toggle evidence proves an end-to-end win without byte changes.\n- `covered-current` — the corpus exercises the path but has no comparable before/after evidence.\n- `not-visible` — the path was not exercised or showed no measurable impact.\n- `needs-trace-coverage` — the corpus lacks fixture coverage for the path.\n\nA v1–v3 win is **never** called \"confirmed\" from current-only coverage. `validatePerfCorpusReport()` enforces this: a `CPU-self-time confirmed` classification is rejected unless the report carries profiler self-time evidence.\n\n## Schema (gjc.perf-corpus/2)\n\n`PerfCorpusReport` keeps the evidence classes as **separate named fields** per fixture:\n\n- `wallClockPhase: Record<string, { elapsedMs, p50Ms?, p95Ms?, advisoryOnly }>`\n- `processCpuUsage: Record<string, { userMicros, systemMicros, elapsedMs, cpuFraction? }>`\n- `profilerSelfTime: { profiler, artifactPath?, samples? }`\n- `rssMemory: { baselineBytes, peakBytes?, growthBytes, returnBytes, ... }`\n- `byteParity: { renderedGolden?, persistedJsonlGolden?, providerPayloadGolden?, materializedSessionGolden? }`\n- `memoryBaseline?: { surface, profile, iterations, operations, operationsPerSecond, samples, postTeardown, rssSlopeBytesPerSecond, heapSlopeBytesPerSecond, processTreeBaselineRssBytes, processTreePostTeardownRssBytes, processTreeSampler }`\n- `runner: { command, argv, environment, platform, arch, bunVersion?, ci?, profile, durationTargetMs?, memoryIsolation, iterationsTarget, gcExposed, memoryChildGcExposed, memoryChildExecArgv }` pins the actual parent argv, normalized workload controls, isolation, parent GC availability, and the fixed isolated-child runtime flags separately.\n- `gitSha` is the full checked-out `HEAD` when Git is available, with `GITHUB_SHA` used only as a fallback; `gitDirty` explicitly marks tracked or untracked worktree changes so local evidence cannot silently masquerade as a clean commit. The runner captures SHA and the complete porcelain worktree fingerprint before and after the workloads and rejects any in-flight source-state change.\n- Every detailed sample separates `rssBytes`, `heapUsedBytes`, `heapTotalBytes`, `externalBytes`, `arrayBuffersBytes`, and `activeResourceCount`.\n\n`hotspotClassifications: HotspotClassification[]` carry `{ hotspotId, status, evidenceClass, artifactRefs, notes }`. The current v1–v3 reclassification lives in `V1_V3_RECLASSIFICATION`; no entry is `CPU-self-time confirmed` because no profiler artifacts have been captured yet.\n\n## Privacy rules\n\n- Never commit raw private session transcripts.\n- Default fixtures are `synthetic` (deterministic PRNG, no real data).\n- `sanitized-real` / `dogfood-redacted` fixtures are allowed only with documented redaction in `privacy.redactionNotes`; `privacy.rawPrivateTranscriptCommitted` must be `false`.\n\n## Commands\n\n```bash\n# Emit a corpus report (stable JSON)\nbun packages/coding-agent/bench/perf-corpus.bench.ts\n\n# Run the corpus schema/classification/ledger tests\nbun test packages/coding-agent/test/perf-corpus.test.ts\n```\n\n```bash\n# Emit the detailed short memory profile with explicit GC return samples\nbun --smol --expose-gc packages/coding-agent/bench/perf-corpus.bench.ts\n\n# Opt into the longer bounded soak profile\nGJC_MEMORY_PROFILE=soak bun --smol --expose-gc packages/coding-agent/bench/perf-corpus.bench.ts\n\n# Override the per-surface duration (250–60000 ms) and minimum iterations\nGJC_MEMORY_PROFILE=soak GJC_MEMORY_DURATION_MS=10000 GJC_MEMORY_ITERATIONS=100000 bun --smol --expose-gc packages/coding-agent/bench/perf-corpus.bench.ts\n```\n\n## Profiler-artifact expectations\n\nThe base runner attaches no profiler (`profilerSelfTime.profiler: \"none\"`), so it can never promote a hotspot to `CPU-self-time confirmed`. To confirm CPU self-time:\n\n1. Capture a profiler artifact (e.g. a `.cpuprofile`) while running the relevant fixture.\n2. Record it in the fixture's `profilerSelfTime` as `{ profiler, artifactPath, samples }`.\n3. Set the hotspot classification to `CPU-self-time confirmed` with `evidenceClass: \"profiler-self-time\"` and the artifact in `artifactRefs`.\n4. `validatePerfCorpusReport()` will then accept the claim.\n\n## Threshold-promotion process\n\nWall-clock and RSS thresholds are noisy. Promotion is gradual:\n\n1. **Advisory** — reported in the corpus JSON / console; never fails CI. All thresholds start here (`APPLIED_PERF_THRESHOLDS`, `advisoryOrEnforced: \"advisory\"`, `varianceCharacterized: false`).\n2. **Opt-in numeric** — exercised under `PI_TUI_PERF_GATES=1` (see `packages/tui/test/perf-gates.test.ts`).\n3. **Enforced** — a hard CI gate, allowed only with `varianceCharacterized: true`, passed before/after `benchmarkEvidence`, and human approval. `validatePerfThresholdLedger()` rejects enforced thresholds lacking this evidence.\n\nHeld thresholds (`HELD_PERF_THRESHOLDS`) name candidates that need variance characterization before enforcement.\n\n## Memory baseline protocol\n\nDetailed memory fixtures cover seven explicit surfaces: CLI startup/configuration, AgentSession-style message/context lifecycle, blob/external buffers, worker generations, Telegram reconnect/queue settlement, TUI render/dispose churn, and shared/native transfer boundaries. The fixtures are synthetic lifecycle proxies: they establish a reproducible allocation and teardown envelope but do not by themselves prove a production leak. A production optimization claim still requires a workload adapter that exercises the implicated owner and a same-host before/after artifact.\nThe command-line runner executes each memory surface in a fresh Bun subprocess and records `runner.memoryIsolation: \"process-per-surface\"` so allocator high-water state from one fixture cannot contaminate the next surface's baseline. Programmatic `runPerfCorpusBenchmark()` defaults to in-process fixtures and records `\"in-process\"` for focused contract tests; pass `{ isolatedMemory: true }` for acceptance-equivalent evidence. Process-tree RSS snapshots exclude the `ps` sampler process and degrade both endpoints to `\"unavailable\"` when either snapshot fails. The process-tree baseline is captured after GC, followed by another GC that clears sampler allocations before the local baseline and workload begin. Soak workloads use single-iteration batches so approximately 50 ms sampling cannot be hidden behind a large synchronous chunk. Post-teardown return fields remain `null` when GC is unavailable.\n\nUse the `short` profile for deterministic contract and shape checks; its bounded iteration window intentionally reports `null` slopes when less than 250 ms is observed. Use `soak` for repeated sampling and slope characterization. For decision evidence:\nThe soak default runs each surface for at least one second and samples at approximately 50 ms intervals. `GJC_MEMORY_DURATION_MS` accepts 250–60000 ms and `GJC_MEMORY_ITERATIONS` accepts 1–10000000; record overrides with the artifact.\n\n1. Pin the source SHA, Bun version, platform/architecture, profile, fixture inputs, and command.\n2. Run at least five short repetitions and three independent soak repetitions on an otherwise idle runner.\n3. Exclude warm-up from slope decisions and report the raw samples, median, p95, variance/confidence interval, peak, and post-teardown values. The runner discards the first quarter of the observed window, capped at 250 ms, before calculating a slope and requires at least 250 ms of steady-state samples.\n4. Interpret heap, external/array-buffer, RSS, and process-tree evidence separately. A high post-GC RSS with a returned heap may be allocator high-water residency, not a reachability leak.\n5. Do not enforce a numeric threshold until variance is characterized and recorded in the threshold ledger. A claimed optimization needs either a statistically supported improvement on the same workload or removal of a reproducible unbounded slope.\n6. Treat active handles and post-teardown residue as lifecycle signals, not byte-parity proof. Behavior, transcript/blob integrity, throughput, and latency remain independent gates.\n\nThe default fixtures contain no user or provider data. Raw private transcripts remain prohibited.\n\n## Memory retention & fail-closed materialization\n\nResident-memory retention (hotspots M01–M05) was bounded in Optimization Suite v3 (#548): `EphemeralBlobStore` externalizes large resident text to a session-scoped disk cache with an 8 MiB LRU buffer budget, `getEntries()`/`buildSessionContext()` are served from revision-keyed WeakRef caches and return caller-owned clones, and `captureState`/`restoreState` bump revision domains. Materialization is split by byte sensitivity:\n\n- **Resident byte-sensitive TEXT** (`resolveTextBlobSync`) is **fail-closed**: a missing resident blob throws `ResidentBlobMissingError` rather than degrading, so a missing blob can never silently leak a `blob:sha256:` reference into provider payloads, UI, or exports.\n- **Persisted images** (`resolveImageData`/`resolveImageDataUrl` and sync variants) are the **legacy persisted-image compatibility boundary**: a missing blob warns and returns the reference as-is so legacy-session resume degrades gracefully. New byte-sensitive resident data must NOT use this warn-and-return path.\n\nThis contract is locked by `packages/coding-agent/test/resident-materialization.test.ts`. Retained growth and post-GC return are measured by `packages/coding-agent/bench/session-memory.bench.ts` (emits the corpus `rssMemory` shape).\n\n**Measured deferral:** further memory rewrites beyond these byte-parity-preserving bounds are deferred to corpus prioritization. Per [`native-ffi-optimization-policy.md`](./native-ffi-optimization-policy.md) and the byte-parity principle, speculative memory rewrites wait for profiler/RSS corpus evidence rather than being undertaken on a static-ranking guess.\n",
|
|
60
|
+
"perf-profiling-corpus.md": "# Perf profiling corpus\n\nThe profiling corpus is the **successor** to the static [`cpu-hotspot-map.json`](./cpu-hotspot-map.json) ranking (see [`hotspot-map-successor.md`](./hotspot-map-successor.md)). The static map ranked hotspots by complexity × trigger frequency but never measured real CPU self-time. The corpus replaces that guess with measured, separated evidence and is the source of future perf prioritization.\n\nImplementation:\n\n- Schema + evidence taxonomy + validation: `packages/coding-agent/bench/perf-corpus-schema.ts`\n- Runner: `packages/coding-agent/bench/perf-corpus.bench.ts`\n- Threshold/evidence ledger: `packages/coding-agent/bench/perf-threshold.ledger.ts`\n- Tests: `packages/coding-agent/test/perf-corpus.test.ts`\n- Deterministic memory surface workloads: `packages/coding-agent/bench/memory-baseline-workloads.ts`\n\n## Evidence taxonomy\n\nEach metric and optimization claim is classified by **evidence class**. These classes must never be conflated:\n\n| Class | Meaning | Sufficient for CPU self-time? |\n|---|---|---|\n| `wall-clock-proxy` | elapsed time around a phase/operation | No |\n| `process-cpu-usage` | `process.cpuUsage()` user/system deltas | No |\n| `profiler-self-time` | profiler/sampled attribution of self-time to a symbol | **Yes (required)** |\n| `rss-memory` | RSS/heap baseline/growth/return | No (memory only) |\n| `byte-parity` | golden rendered/persisted/provider/materialized comparisons | n/a (safety) |\n| `ledger-approved-threshold` | human-approved threshold change | n/a (process) |\n\nOptimization **status vocabulary** for a hotspot:\n\n- `CPU-self-time confirmed` — requires `profiler-self-time` evidence (an `artifactPath` or non-empty `samples`).\n- `fallback-toggle-confirmed` — comparable before/after or feature/fallback-toggle evidence proves an end-to-end win without byte changes.\n- `covered-current` — the corpus exercises the path but has no comparable before/after evidence.\n- `not-visible` — the path was not exercised or showed no measurable impact.\n- `needs-trace-coverage` — the corpus lacks fixture coverage for the path.\n\nA v1–v3 win is **never** called \"confirmed\" from current-only coverage. `validatePerfCorpusReport()` enforces this: a `CPU-self-time confirmed` classification is rejected unless the report carries profiler self-time evidence.\n\n## Schema (gjc.perf-corpus/2)\n\n`PerfCorpusReport` keeps the evidence classes as **separate named fields** per fixture:\n\n- `wallClockPhase: Record<string, { elapsedMs, p50Ms?, p95Ms?, advisoryOnly }>`\n- `processCpuUsage: Record<string, { userMicros, systemMicros, elapsedMs, cpuFraction? }>`\n- `profilerSelfTime: { profiler, artifactPath?, samples? }`\n- `rssMemory: { baselineBytes, peakBytes?, growthBytes, returnBytes, ... }`\n- `byteParity: { renderedGolden?, persistedJsonlGolden?, providerPayloadGolden?, materializedSessionGolden? }`\n- `memoryBaseline?: { surface, profile, iterations, operations, operationsPerSecond, samples, postTeardown, rssSlopeBytesPerSecond, heapSlopeBytesPerSecond, processTreeBaselineRssBytes, processTreePostTeardownRssBytes, processTreeSampler }`\n- `runner: { command, argv, environment, platform, arch, bunVersion?, ci?, profile, durationTargetMs?, memoryIsolation, iterationsTarget, gcExposed, memoryChildGcExposed, memoryChildExecArgv }` pins the actual parent argv, normalized workload controls, isolation, parent GC availability, and the fixed isolated-child runtime flags separately.\n- `gitSha` is the full checked-out `HEAD` when Git is available, with `GITHUB_SHA` used only as a fallback; `gitDirty` explicitly marks tracked or untracked worktree changes so local evidence cannot silently masquerade as a clean commit. The runner captures SHA and the complete porcelain worktree fingerprint before and after the workloads and rejects any in-flight source-state change.\n- Every detailed sample separates `rssBytes`, `heapUsedBytes`, `heapTotalBytes`, `externalBytes`, `arrayBuffersBytes`, and `activeResourceCount`.\n\n`hotspotClassifications: HotspotClassification[]` carry `{ hotspotId, status, evidenceClass, artifactRefs, notes }`. The current v1–v3 reclassification lives in `V1_V3_RECLASSIFICATION`; no entry is `CPU-self-time confirmed` because no profiler artifacts have been captured yet.\n\n## Privacy rules\n\n- Never commit raw private session transcripts.\n- Default fixtures are `synthetic` (deterministic PRNG, no real data).\n- `sanitized-real` / `dogfood-redacted` fixtures are allowed only with documented redaction in `privacy.redactionNotes`; `privacy.rawPrivateTranscriptCommitted` must be `false`.\n\n## Commands\n\n```bash\n# Emit a corpus report (stable JSON)\nbun packages/coding-agent/bench/perf-corpus.bench.ts\n\n# Run the corpus schema/classification/ledger tests\nbun test packages/coding-agent/test/perf-corpus.test.ts\n```\n\n```bash\n# Emit the detailed short memory profile with explicit GC return samples\nbun --smol --expose-gc packages/coding-agent/bench/perf-corpus.bench.ts\n\n# Opt into the longer bounded soak profile\nGJC_MEMORY_PROFILE=soak bun --smol --expose-gc packages/coding-agent/bench/perf-corpus.bench.ts\n\n# Override the per-surface duration (250–60000 ms) and minimum iterations\nGJC_MEMORY_PROFILE=soak GJC_MEMORY_DURATION_MS=10000 GJC_MEMORY_ITERATIONS=100000 bun --smol --expose-gc packages/coding-agent/bench/perf-corpus.bench.ts\n```\n\n## Profiler-artifact expectations\n\nThe base runner attaches no profiler (`profilerSelfTime.profiler: \"none\"`), so it can never promote a hotspot to `CPU-self-time confirmed`. To confirm CPU self-time:\n\n1. Capture a profiler artifact (e.g. a `.cpuprofile`) while running the relevant fixture.\n2. Record it in the fixture's `profilerSelfTime` as `{ profiler, artifactPath, samples }`.\n3. Set the hotspot classification to `CPU-self-time confirmed` with `evidenceClass: \"profiler-self-time\"` and the artifact in `artifactRefs`.\n4. `validatePerfCorpusReport()` will then accept the claim.\n\n## Threshold-promotion process\n\nWall-clock and RSS thresholds are noisy. Promotion is gradual:\n\n1. **Advisory** — reported in the corpus JSON / console; never fails CI. All thresholds start here (`APPLIED_PERF_THRESHOLDS`, `advisoryOrEnforced: \"advisory\"`, `varianceCharacterized: false`).\n2. **Opt-in numeric** — exercised under `PI_TUI_PERF_GATES=1` (see `packages/tui/test/perf-gates.test.ts`).\n3. **Enforced** — a hard CI gate, allowed only with `varianceCharacterized: true`, passed before/after `benchmarkEvidence`, and human approval. `validatePerfThresholdLedger()` rejects enforced thresholds lacking this evidence.\n\nHeld thresholds (`HELD_PERF_THRESHOLDS`) name candidates that need variance characterization before enforcement.\n\n## Memory baseline protocol\n\nDetailed memory fixtures cover seven explicit surfaces: CLI startup/configuration, AgentSession-style message/context lifecycle, blob/external buffers, worker generations, Telegram reconnect/queue settlement, TUI render/dispose churn, and shared/native transfer boundaries. The fixtures are synthetic lifecycle proxies: they establish a reproducible allocation and teardown envelope but do not by themselves prove a production leak. A production optimization claim still requires a workload adapter that exercises the implicated owner and a same-host before/after artifact.\nThe command-line runner executes each memory surface in a fresh Bun subprocess and records `runner.memoryIsolation: \"process-per-surface\"` so allocator high-water state from one fixture cannot contaminate the next surface's baseline. Programmatic `runPerfCorpusBenchmark()` defaults to in-process fixtures and records `\"in-process\"` for focused contract tests; pass `{ isolatedMemory: true }` for acceptance-equivalent evidence. Process-tree RSS snapshots exclude the `ps` sampler process and degrade both endpoints to `\"unavailable\"` when either snapshot fails. The process-tree baseline is captured after GC, followed by another GC that clears sampler allocations before the local baseline and workload begin. Soak workloads use single-iteration batches so approximately 50 ms sampling cannot be hidden behind a large synchronous chunk. Post-teardown return fields remain `null` when GC is unavailable.\n\nUse the `short` profile for deterministic contract and shape checks; its bounded iteration window intentionally reports `null` slopes when less than 250 ms is observed. Use `soak` for repeated sampling and slope characterization. For decision evidence:\nThe soak default runs each surface for at least one second and samples at approximately 50 ms intervals. `GJC_MEMORY_DURATION_MS` accepts 250–60000 ms and `GJC_MEMORY_ITERATIONS` accepts 1–10000000; record overrides with the artifact.\n\n1. Pin the source SHA, Bun version, platform/architecture, profile, fixture inputs, and command.\n2. Run at least five short repetitions and three independent soak repetitions on an otherwise idle runner.\n3. Exclude warm-up from slope decisions and report the raw samples, median, p95, variance/confidence interval, peak, and post-teardown values. The runner discards the first quarter of the observed window, capped at 250 ms, before calculating a slope and requires at least 250 ms of steady-state samples.\n4. Interpret heap, external/array-buffer, RSS, and process-tree evidence separately. A high post-GC RSS with a returned heap may be allocator high-water residency, not a reachability leak.\n5. Do not enforce a numeric threshold until variance is characterized and recorded in the threshold ledger. A claimed optimization needs either a statistically supported improvement on the same workload or removal of a reproducible unbounded slope.\n6. Treat active handles and post-teardown residue as lifecycle signals, not byte-parity proof. Behavior, transcript/blob integrity, throughput, and latency remain independent gates.\n\nThe default fixtures contain no user or provider data. Raw private transcripts remain prohibited.\n\n## Memory retention & fail-closed materialization\n\nResident-memory retention (hotspots M01–M05) was bounded in Optimization Suite v3 (#548): `EphemeralBlobStore` externalizes large resident text to a session-scoped disk cache with an 8 MiB LRU buffer budget, `getEntries()`/`buildSessionContext()` are served from revision-keyed WeakRef caches and return caller-owned clones, and `captureState`/`restoreState` bump revision domains. Materialization is split by byte sensitivity:\n\n- **Resident byte-sensitive TEXT** (`resolveTextBlobSync`) is **fail-closed**: a missing resident blob throws `ResidentBlobMissingError` rather than degrading, so a missing blob can never silently leak a `blob:sha256:` reference into provider payloads, UI, or exports.\n- **Persisted images** (`resolveImageData`/`resolveImageDataUrl` and sync variants) are the **legacy persisted-image compatibility boundary**: a missing blob warns and returns the reference as-is so legacy-session resume degrades gracefully. New byte-sensitive resident data must NOT use this warn-and-return path.\n\nThis contract is locked by `packages/coding-agent/test/resident-materialization.test.ts`. Retained growth and post-GC return are measured by `packages/coding-agent/bench/session-memory.bench.ts` (emits the corpus `rssMemory` shape).\n\n**Measured deferral:** further memory rewrites beyond these byte-parity-preserving bounds are deferred to corpus prioritization. Per [`native-ffi-optimization-policy.md`](./native-ffi-optimization-policy.md) and the byte-parity principle, speculative memory rewrites wait for profiler/RSS corpus evidence rather than being undertaken on a static-ranking guess.\n\n## Authenticated sealed-corpus result\n\n- Evidence status: `SUFFICIENT_EVIDENCE`\n- Action decision: `ACTION`\n- Action family: `sustained-heap-growth`\n- Measurement head: `ae37704ea58c5181043ef2a325c3aa1878884c25`\n- Admission: short 5/5, soak 24/24\n- `agent-session` endpoint median: 2232879.966 B/s, BCa lower 2198738.248, Theil-Sen median 917654.71\n- `tui` endpoint median: 170829.216 B/s, BCa lower 154600.451, Theil-Sen median 4391.02\n- p95: `OMITTED_IMPOSSIBLE` (24 blocks insufficient for 95% empirical coverage per exact-order-statistic method)\n- All five preregistered limitations preserved\n- JS heap separated from process RSS/external/native; no production leak or causal site claimed\n- Raw corpus retained outside git, read-only, access-restricted, hash-bound by external receipt\n- Published files:\n - `artifacts/perf-corpus-memory-evidence-report.json`\n - `artifacts/perf-corpus-memory-evidence-manifest.json`\n - `artifacts/perf-corpus-memory-evidence-notebook.ipynb`\n",
|
|
61
61
|
"porting-from-pi-mono.md": "# Porting From pi-mono: A Practical Merge Guide\n\nThis guide is a repeatable checklist for porting changes from pi-mono into this repo.\nUse it for any merge: single file, feature branch, or full release sync.\n\n## Last Sync Point (historical upstream marker)\n\n**Commit:** `b21b42d032919de2f2e6920a76fa9a37c3920c0a`\n**Date:** 2026-03-22\n\nUpdate this section after each sync; do not reuse the previous range. This commit is an upstream pi-mono marker and may not exist in this repo's local object database.\n\nWhen starting a new sync, generate patches from this commit forward in a pi-mono checkout or remote that contains the commit:\n\n```bash\ngit format-patch b21b42d032919de2f2e6920a76fa9a37c3920c0a..HEAD --stdout > changes.patch\n```\n\n## 0) Define the scope\n\n- Identify the upstream reference (commit, tag, or PR).\n- List the packages or folders you plan to touch.\n- Decide which features are in-scope and which are intentionally skipped.\n\n## 1) Bring code over safely\n\n- Prefer a clean, focused diff rather than a wholesale copy.\n- Avoid copying built artifacts or generated files.\n- If upstream added new files, add them explicitly and review contents.\n\n## 2) Match import extension conventions\n\nMost runtime TypeScript sources omit `.js` in internal imports, but several current entrypoints and tool modules keep `.js` for ESM/runtime compatibility. Follow the surrounding file and package export style; do not blanket-strip or blanket-add extensions.\n\n- In `packages/coding-agent` runtime sources, prefer extensionless internal imports when the surrounding module does, but preserve existing `.js` imports in files that already require them.\n- In `packages/tui/test` and `packages/natives/bench`, keep `.js` where surrounding files already use it.\n- Keep real file extensions when required by tooling or import assertions (e.g., `.json`, `.css`, `.md` text embeds).\n- Example: `import { x } from \"./foo.js\";` → `import { x } from \"./foo\";` only when that package/file convention is extensionless.\n\n## 3) Replace import scopes\n\nUpstream uses different package scopes. Replace them consistently.\n\n- Replace old scopes with the local scope used here.\n- Examples (adjust to match the actual packages you are porting):\n - `@mariozechner/gajae-code` → `@gajae-code/coding-agent`\n - `@mariozechner/pi-agent-core` → `@gajae-code/agent-core`\n - `@mariozechner/pi-tui` → `@gajae-code/tui`\n - `@mariozechner/pi-ai` → `@gajae-code/ai`\n\n## 4) Use Bun APIs where they improve on Node\n\nWe run on Bun, but the current source intentionally mixes Bun APIs with small Node standard-library APIs. Replace Node APIs only when Bun provides a clearer, safer, or simpler implementation; do not mechanically rewrite every Node import.\n\n**Prefer replacing when porting new code:**\n\n- Process spawning: prefer Bun Shell `$` for simple commands; use `Bun.spawn`/`Bun.spawnSync` for streaming or process control. Keep existing `child_process` only where its exact semantics are needed.\n- HTTP clients: `node-fetch`, `axios` → native `fetch`\n- SQLite: `better-sqlite3` → `bun:sqlite`\n- Env loading: `dotenv` → Bun loads `.env` automatically\n- Runtime text/assets: prefer Bun imports such as `with { type: \"text\" }` or `Bun.file()` over copy steps or bundled fallback file reads.\n\n**DO NOT replace (these work fine in Bun):**\n\n- `os.homedir()` — do NOT replace with `Bun.env.HOME` or literal `\"~\"`\n- `os.tmpdir()` — do NOT replace with `Bun.env.TMPDIR || \"/tmp\"` or hardcoded paths\n- `fs.mkdtempSync()` — do NOT replace with manual path construction\n- `path.join()`, `path.resolve()`, etc. — these are fine\n\n**Import style:** Use the `node:` prefix for Node standard-library imports. Namespace imports are common, but named imports are acceptable where the surrounding code already uses them.\n\n**Additional Bun conventions:**\n\n- Prefer Bun Shell `$` for short, non-streaming commands; use `Bun.spawn` only when you need streaming I/O or process control.\n- Use `Bun.file()`/`Bun.write()` for simple files and `node:fs/promises` for directory-oriented operations. Existing synchronous `node:fs` calls are acceptable when the calling flow is intentionally synchronous.\n- Avoid `Bun.file().exists()` checks; use `isEnoent` handling in try/catch.\n- Prefer `Bun.sleep(ms)` over `setTimeout` wrappers.\n\n**Wrong:**\n\n```typescript\n// BROKEN: env vars may be undefined, \"~\" is not expanded\nconst home = Bun.env.HOME || \"~\";\nconst tmp = Bun.env.TMPDIR || \"/tmp\";\n```\n\n**Correct:**\n\n```typescript\nimport * as os from \"node:os\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst configDir = path.join(os.homedir(), \".config\", \"myapp\");\nconst tempDir = fs.mkdtempSync(path.join(os.tmpdir(), \"myapp-\"));\n```\n\n## 5) Prefer Bun embeds (no copying)\n\nDo not add new runtime asset copy steps. Keep assets in repo and prefer Bun embeds/imports; preserve existing explicit generation workflows such as `packages/coding-agent/src/export/html/template.generated.ts`.\n\n- If upstream copies assets into a dist folder, replace with Bun-friendly embeds.\n- Prompts are static `.md` files; use Bun text imports (`with { type: \"text\" }`) and Handlebars instead of inline prompt strings.\n- Use `import.meta.dir` + `Bun.file` to load adjacent non-text resources.\n- Keep assets in-repo and let the bundler include them.\n- Eliminate copy scripts unless the user explicitly requests them or the package already has an intentional generation step.\n- If upstream reads a bundled fallback file at runtime, replace filesystem reads with a Bun text embed import unless the current package already uses a generated asset pipeline.\n - Example (provider instructions fallback):\n - `const FALLBACK_PROMPT_PATH = join(import.meta.dir, \"openai-code-instructions.md\");` -> removed\n - `import FALLBACK_INSTRUCTIONS from \"./openai-code-instructions.md\" with { type: \"text\" };`\n - Use `return FALLBACK_INSTRUCTIONS;` instead of `readFileSync(FALLBACK_PROMPT_PATH, \"utf8\")`\n\n## 6) Port `package.json` carefully\n\nTreat `package.json` as a contract. Merge intentionally.\n\n- Keep existing `name`, `version`, `type`, `exports`, and `bin` unless the port requires changes.\n- Replace npm/node scripts with Bun equivalents (e.g., `bun check`, `bun test`).\n- Ensure dependencies use the correct scope.\n- Do not downgrade dependencies to fix type errors; upgrade instead.\n- Validate workspace package links and `peerDependencies`.\n\n## 7) Align code style and tooling\n\n- Keep existing formatting conventions.\n- Do not introduce `any` unless required.\n- Avoid dynamic imports unless they are required for optional dependencies, startup cost, or runtime-only modules; prefer top-level imports otherwise.\n- Never build prompts in code; prompts are static `.md` files rendered with Handlebars.\n- In `packages/coding-agent`, use `logger` from `@gajae-code/utils` for internal/runtime logging; CLI command files may use `console.*` for intentional user-facing output.\n- Use `Promise.withResolvers()` instead of `new Promise((resolve, reject) => ...)`.\n- Prefer ES `#` private fields for new encapsulated state. Constructor parameter properties already exist in current code and are acceptable; do not churn unrelated access modifiers while porting.\n- Prefer existing helpers and utilities over new ad-hoc code.\n Preserve Bun-first infrastructure changes already made in this repo:\n - Runtime is Bun (no Node entry points for the main CLI).\n - Package manager is Bun (no npm lockfiles).\n - Heavy Node APIs should not be introduced casually; current source still uses selected Node APIs (`node:crypto`, `node:readline`, synchronous `node:fs`, and `child_process`) where they fit provider, CLI, or process-control semantics.\n - Lightweight Node APIs (`os.homedir`, `os.tmpdir`, `fs.mkdtempSync`, `path.*`) are kept.\n - CLI shebangs use `bun` (not `node`, not `tsx`).\n - TypeScript packages generally use source files directly; `@gajae-code/natives` exports generated native bindings from `packages/natives/native`.\n - CI workflows run Bun for install/check/test.\n\n## 8) Remove old compatibility layers\n\nUnless requested, remove upstream compatibility shims.\n\n- Delete old APIs that were replaced.\n- Update all call sites to the new API directly.\n- Do not keep `*_v2` or parallel versions.\n\n## 9) Update docs and references\n\n- Replace pi-mono repo links where appropriate.\n- Update examples to use Bun and correct package scopes.\n- Ensure README instructions still match the current repo behavior.\n\n## 10) Validate the port\n\nRun the standard checks after changes:\n\n- `bun check`\n\nIf the repo already has failing checks unrelated to your changes, call that out.\nTests use Bun's runner (not Vitest), but only run `bun test` when explicitly requested.\n\n## 11) Protect improved features (regression trap list)\n\nIf you already improved behavior locally, treat those as **non‑negotiable**. Before porting, write down\nthe improvements and add explicit checks so they don’t get lost in the merge.\n\n- **Freeze the expected behavior**: add a short “before/after” note for each improvement (inputs, outputs,\n defaults, edge cases). This prevents silent rollback.\n- **Map old → new APIs**: if upstream renamed concepts (hooks → extensions, custom tools → tools, etc.),\n ensure every old entry point still wires through. One missed flag or export equals lost functionality.\n- **Verify exports**: check `package.json` `exports`, public types, and barrel files. Upstream ports often\n forget to re-export local additions.\n- **Cover non‑happy paths**: if you fixed error handling, timeouts, or fallback logic, add a test or at\n least a manual checklist that exercises those paths.\n- **Check defaults and config merge order**: improvements often live in defaults. Confirm new defaults\n didn’t revert (e.g., new config precedence, disabled features, tool lists).\n- **Audit env/shell behavior**: if you fixed execution or sandboxing, verify the new path still uses your\n sanitized env and does not reintroduce alias/function overrides.\n- **Re-run targeted samples**: keep a minimal set of \"known good\" examples and run them after the port\n (CLI flags, extension registration, tool execution).\n\n## 12) Detect and handle reworked code\n\nBefore porting a file, check if upstream significantly refactored it:\n\n```bash\n# Compare the file you're about to port against what you have locally\ngit diff HEAD upstream/main -- path/to/file.ts\n```\n\nIf the diff shows the file was **reworked** (not just patched):\n\n- New abstractions, renamed concepts, merged modules, changed data flow\n\nThen you must **read the new implementation thoroughly** before porting. Blind merging of reworked code loses functionality because:\n\nNote: interactive mode was recently split into controllers/utils/types. When backporting related changes, port updates into the individual files we created and ensure `interactive-mode.ts` wiring stays in sync.\n\n1. **Defaults change silently** - A new variable `defaultFoo = [a, b]` may replace an old `getAllFoo()` that returned `[a, b, c, d, e]`.\n\n2. **API options get dropped** - When systems merge (e.g., `hooks` + `customTools` → `extensions`), old options may not wire through to the new implementation.\n\n3. **Code paths go stale** - A renamed concept (e.g., `hookMessage` → `custom`) needs updates in every switch statement, type guard, and handler—not just the definition.\n\n4. **Context/capabilities shrink** - Old APIs may have exposed `{ logger, typebox, pi }` that new APIs forgot to include.\n\n### Semantic porting process\n\nWhen upstream reworked a module:\n\n1. **Read the old implementation** - Understand what it did, what options it accepted, what it exposed.\n\n2. **Read the new implementation** - Understand the new abstractions and how they map to old behavior.\n\n3. **Verify feature parity** - For each capability in the old code, confirm the new code preserves it or explicitly removes it.\n\n4. **Grep for stragglers** - Search for old names/concepts that may have been missed in switch statements, handlers, UI components.\n\n5. **Test the boundaries** - CLI flags, SDK options, event handlers, default values—these are where regressions hide.\n\n### Quick checks\n\n```bash\n# Find all uses of an old concept that may need updating\nrg \"oldConceptName\" --type ts\n\n# Compare default values between versions\ngit show upstream/main:path/to/file.ts | rg \"default|DEFAULT\"\n\n# Check if all enum/union values have handlers\nrg \"case \\\"\" path/to/file.ts\n```\n\n## 13) Quick audit checklist\n\nUse this as a final pass before you finish:\n\n- [ ] Import extensions follow the local package convention (no blanket `.js` stripping)\n- [ ] No newly introduced Node-only APIs unless they match an existing justified pattern\n- [ ] All package scopes updated\n- [ ] `package.json` scripts use Bun\n- [ ] Prompts are `.md` text imports (no inline prompt strings)\n- [ ] No internal/runtime `console.*` in coding-agent; CLI user-facing output is intentional\n- [ ] Assets load via Bun embed/import patterns, or through an existing intentional generation pipeline\n- [ ] Tests or checks run (or explicitly noted as blocked)\n- [ ] No functionality regressions (see sections 11-12)\n\n## 14) Commit message format\n\nWhen committing a backport, follow the repo format `<type>(scope): <past-tense description>` and keep the commit\nrange in the title.\n\n```\nfix(coding-agent): backported pi-mono changes (<from>..<to>)\n\npackages/<package>:\n- <type>: <description>\n- <type>: <description> (#<issue> by @<contributor>)\n\npackages/<other-package>:\n- <type>: <description>\n```\n\n**Example:**\n\n```\nfix(coding-agent): backported pi-mono changes (9f3eef65f..52532c7c0)\n\npackages/ai:\n- fix: handle \"sensitive\" stop reason from Anthropic API\n- fix: normalize tool call IDs with special characters for Responses API\n- fix: add overflow detection for Bedrock, MiniMax, Kimi providers\n- fix: 429 status is rate limiting, not context overflow\n\npackages/tui:\n- fix: refactored autocomplete state tracking\n- fix: file autocomplete should not trigger on empty text\n- fix: configurable autocomplete max visible items\n- fix: improved table column width calculation with word-aware wrapping\n\npackages/coding-agent:\n- fix: preserve external config.yml edits on save (#1046 by @nicobailonMD)\n- fix: resolve macOS NFD and curly quote variants in file paths\n```\n\n**Rules:**\n\n- Group changes by package\n- Use conventional commit types (`fix`, `feat`, `refactor`, `perf`, `docs`)\n- Include upstream issue/PR numbers and contributor attribution for external contributions\n- The commit range in the title helps track sync points\n\n## 15) Intentional Divergences\n\nOur fork has architectural decisions that differ from upstream. **Do not port these upstream patterns:**\n\n### UI Architecture\n\n| Upstream | Our Fork | Reason |\n| ------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |\n| `FooterDataProvider` class | `StatusLineComponent` | Simpler, integrated status line |\n| `ctx.ui.setHeader()` / `ctx.ui.setFooter()` | No-op stubs in current extension contexts | Not currently wired to replace the TUI status/header UI |\n| `ctx.ui.setEditorComponent()` | No-op stubs in current extension contexts | Custom editor replacement is not currently wired |\n| `InteractiveModeOptions` options object | Positional constructor args (options type still exported) | Keep constructor signature; update the type when upstream adds fields |\n\n### Component Naming\n\n| Upstream | Our Fork |\n| ---------------------------- | ----------------------- |\n| `extension-input.ts` | `hook-input.ts` |\n| `extension-selector.ts` | `hook-selector.ts` |\n| `ExtensionInputComponent` | `HookInputComponent` |\n| `ExtensionSelectorComponent` | `HookSelectorComponent` |\n\n### API Naming\n\n| Upstream | Our Fork | Notes |\n| ---------------------------------------- | ---------------------------------------- | ----------------------------------------- |\n| `sessionManager.appendSessionInfo(name)` | `sessionManager.setSessionName(name)` | We use `sessionName` throughout |\n| `sessionManager.getSessionName()` | `sessionManager.getSessionName()` | Same (we unified to match upstream's RPC) |\n| `agent.sessionName` / `setSessionName()` | `agent.sessionName` / `setSessionName()` | Same |\n\n### File Consolidation\n\n| Upstream | Our Fork | Reason |\n| -------------------------------------------------- | --------------------------------------------------------- | --------------------------------------------- |\n| `clipboard.ts` + `clipboard-image.ts` (tool files) | `src/utils/clipboard.ts` backed by `@gajae-code/natives` | Native implementation with a small TS wrapper |\n\n### Test Framework\n\n| Upstream | Our Fork |\n| ------------------------- | ----------------------------- |\n| `vitest` with `vi.mock()` | `bun:test` with `vi` from bun |\n| `node:test` assertions | `expect()` matchers |\n\n### Tool Architecture\n\n| Upstream | Our Fork | Notes |\n| ----------------------------------- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |\n| `createTool(cwd: string, options?)` | `createTools(session: ToolSession)` via `BUILTIN_TOOLS` registry | Tool factories accept `ToolSession` and can return `null` |\n| Per-tool `*Operations` interfaces | Only current per-tool override interfaces remain (for example `FindOperations`) | Used for SSH/remote overrides where present |\n| Node.js `fs/promises` everywhere | Bun file APIs for simple file writes/reads, `node:fs/promises` for dirs, selected sync `node:fs` where needed | Prefer Bun APIs when they simplify |\n\n### Auth Storage\n\n| Upstream | Our Fork | Notes |\n| ------------------------------- | ------------------------------------------- | -------------------------------------------- |\n| `proper-lockfile` + `auth.json` | `agent.db` (bun:sqlite) | Credentials stored exclusively in `agent.db` |\n| Single credential per provider | Multi-credential with round-robin selection | Session affinity and backoff logic preserved |\n\n### Extensions\n\n| Upstream | Our Fork |\n| ----------------------------- | ------------------------------------------------- |\n| `jiti` for TypeScript loading | Native Bun `import()` |\n| `pkg.pi` manifest field | `pkg.gjc` preferred; fallback to `pkg.pi` remains |\n\n### Skip These Upstream Features\n\nWhen porting, **skip** these files/features entirely:\n\n- `footer-data-provider.ts` — we use StatusLineComponent\n- `clipboard-image.ts` — image clipboard support is exposed through `src/utils/clipboard.ts` backed by `@gajae-code/natives`\n- GitHub workflow files — we have our own CI\n- `models.generated.ts` — auto-generated, regenerate locally (as models.json instead)\n\n### Features We Added (Preserve These)\n\nThese exist in our fork but not upstream. **Never overwrite:**\n\n- `StatusLineComponent` in interactive mode\n- Multi-credential auth with session affinity\n- Capability-based discovery system (`defineCapability`, `registerProvider`, `loadCapability`, `skillCapability`, etc.)\n- MCP/Exa/SSH integrations\n- LSP writethrough for format-on-save\n- Bash interception (`checkBashInterception`)\n- Fuzzy path suggestions in read tool\n",
|
|
62
62
|
"porting-to-natives.md": "# Porting to pi-natives (N-API) — Field Notes\n\nThis is a practical guide for moving hot paths into `crates/pi-natives` and wiring them through the generated native package entrypoint. It exists to avoid the same failures happening twice.\n\n## When to port\n\nPort when any of these are true:\n\n- The hot path runs in render loops, tight UI updates, or large batches.\n- JS allocations dominate (string churn, regex backtracking, large arrays).\n- You already have a JS baseline and can benchmark both versions side by side.\n- The work is CPU-bound or blocking I/O that can run on the libuv thread pool.\n- The work is async I/O that can run on Tokio's runtime (for example shell execution).\n\nRust is reserved for native bindings, native OS/process/filesystem integration, and measured hot paths. New crates or Rust source trees must have an explicit native/performance rationale in `scripts/check-rust-scope.ts`; keep product policy, orchestration, and glue code in TypeScript unless the benchmark or native boundary justifies moving it.\n\nAvoid ports that depend on JS-only state or dynamic imports. N-API exports should be data-in/data-out. Long-running work should go through `task::blocking` (CPU-bound/blocking I/O) or `task::future` (async I/O) with cancellation where the caller needs `timeoutMs` or `AbortSignal`.\n\n> **Optimization ports need evidence first.** A native port proposed to optimize a *leftover algorithmic hot path* must clear the gates in [`native-ffi-optimization-policy.md`](./native-ffi-optimization-policy.md) (corpus evidence, `profilerSelfTime` attribution, measured FFI overhead, representative p50/p95 win, byte parity, documented rollback cost). New OS/process/native-primitive bindings follow this guide as usual.\n\n## Current package shape\n\n`@gajae-code/natives` no longer has a `packages/natives/src/<module>` TypeScript wrapper layer. The package root points at generated native artifacts:\n\n- runtime entry: `packages/natives/native/index.js`\n- types entry: `packages/natives/native/index.d.ts`\n- loader helpers: `packages/natives/native/loader-state.js`\n- embedded manifest: `packages/natives/native/embedded-addon.js`\n\nConsumers import directly from `@gajae-code/natives`. The generated declarations are produced during `bun --cwd=packages/natives run build`.\n\n## Anatomy of a native export\n\n**Rust side:**\n\n- Implementation lives in `crates/pi-natives/src/<module>.rs`.\n- If you add a new module, register it in `crates/pi-natives/src/lib.rs`.\n- Export with `#[napi]`; snake_case exports are converted to camelCase automatically. Use explicit JS names only for true aliases/non-default names. Use `#[napi(object)]` for object-shaped structs.\n- For CPU-bound or blocking work, use `task::blocking(tag, cancel_token, work)`.\n- For async work that needs Tokio, use `task::future(env, tag, work)`.\n- Pass a `CancelToken` when the API exposes `timeoutMs` or `AbortSignal`, and call `heartbeat()` inside long loops.\n\n**Package/build side:**\n\n- `packages/natives/scripts/build-native.ts` runs napi-rs, installs the `.node` artifact, copies generated `index.js`/`index.d.ts`, and appends enum runtime exports.\n- `packages/natives/native/index.js` is the loader that chooses a candidate `.node` file and returns the loaded addon.\n- `packages/natives/package.json` exposes only the package root (`@gajae-code/natives`).\n\n**Consumer side:**\n\n- Update direct imports/callsites in `packages/coding-agent` or `packages/tui` when the new export replaces a JS implementation.\n- Keep higher-level policy in consumers unless it belongs in the native primitive itself.\n\n## Porting checklist\n\n1. **Add the Rust implementation**\n\n- Put the core logic in a plain Rust function.\n- If it is a new module, add it to `crates/pi-natives/src/lib.rs`.\n- Expose it with `#[napi]` so the default snake_case -> camelCase mapping stays consistent.\n- Keep signatures owned and simple: `String`, `Vec<String>`, `Uint8Array`, `Either<JsString, Uint8Array>`, or `#[napi(object)]` structs.\n- For CPU-bound or blocking work, use `task::blocking`; for async work, use `task::future`.\n- If exposing cancellation, include `timeout_ms: Option<u32>` and `signal: Option<Unknown<'env>>` in options, create `CancelToken::new(...)`, and heartbeat in long loops.\n\n2. **Build generated bindings**\n\n- Run `bun --cwd=packages/natives run build`.\n- Confirm the generated `packages/natives/native/index.d.ts` includes the new export with the intended JS name/signature.\n- Confirm `packages/natives/native/index.js` still has generated enum exports appended when enum changes are involved.\n\n3. **Update consumers**\n\n- Import the new export directly from `@gajae-code/natives`.\n- Replace only callsites where the native implementation is faster/equivalent and preserves behavior.\n- Remove obsolete JS implementation code in the same change when the native path becomes canonical.\n\n4. **Add benchmarks**\n\n- Put benchmarks next to the owning package (`packages/tui/bench`, `packages/natives/bench`, or `packages/coding-agent/bench`).\n- Include a JS baseline and native version in the same run.\n- Use `Bun.nanoseconds()` and a fixed iteration count.\n- Keep benchmark inputs realistic for the hot path.\n\n5. **Run focused verification**\n\n- Build the native package.\n- Run the benchmark.\n- Run the narrow tests or scenario covering the changed export/callsites.\n\n## Pain points and how to avoid them\n\n### 1) Stale platform/variant artifacts\n\nThe loader probes platform-tagged artifacts in deterministic order. For x64, selected variant candidates are tried before the unsuffixed default fallback:\n\n- `modern`: `pi_natives.<tag>-modern.node`, then `...-baseline.node`, then `pi_natives.<tag>.node`.\n- `baseline`: `pi_natives.<tag>-baseline.node`, then `pi_natives.<tag>.node`.\n\nNon-x64 uses `pi_natives.<tag>.node`.\n\nCompiled binaries also probe `<getNativesDir()>/<version>/...` and a legacy user-data directory before package/executable locations. If any earlier candidate is stale, a new export may appear missing.\n\n**Fix:** remove stale candidate/cache files and rebuild.\n\n```bash\nrm packages/natives/native/pi_natives.<platform>-<arch>.node\nrm packages/natives/native/pi_natives.<platform>-<arch>-modern.node\nrm packages/natives/native/pi_natives.<platform>-<arch>-baseline.node\nbun --cwd=packages/natives run build\n```\n\nFor compiled binaries, delete the versioned addon cache shown in the loader error (normally under `~/.gjc/natives/<version>` unless `$XDG_DATA_HOME/gjc` is used).\n\n### 2) Generated types do not match loaded binary\n\nThis can happen when `native/index.d.ts` was regenerated but the `.node` file being loaded is stale or from a different platform/variant.\n\nVerify the loaded export set from the actual candidate path:\n\n```bash\nbun -e 'const tag = `${process.platform}-${process.arch}`; const mod = require(`./packages/natives/native/pi_natives.${tag}.node`); console.log(Object.keys(mod).sort())'\n```\n\nFix the build/candidate mismatch. Do not paper over it with optional consumer checks if the export is required.\n\n### 3) Rust signature mismatch\n\nKeep N-API signatures simple and owned. Avoid borrowed references like `&str` in public exports. If you need structured data, use `#[napi(object)]` structs. If you need callbacks, use napi-rs `ThreadsafeFunction` and keep callback error/value behavior explicit.\n\n### 4) Enum runtime exports\n\nnapi-rs declarations alone are not enough for JS callers that use enum objects at runtime. `scripts/gen-enums.ts` appends enum objects to `native/index.js`. If you add or change a native enum, verify both `native/index.d.ts` and the generated enum export block in `native/index.js`.\n\n### 5) Benchmarking mistakes\n\n- Do not compare different inputs or allocations.\n- Keep JS and native using identical input arrays.\n- Run both in the same benchmark file to avoid skew.\n- Include enough iterations to smooth startup noise, but keep inputs realistic.\n\n## Benchmark template\n\n```ts\nconst ITERATIONS = 2000;\n\nfunction bench(name: string, fn: () => void): number {\n const start = Bun.nanoseconds();\n for (let i = 0; i < ITERATIONS; i++) fn();\n const elapsed = (Bun.nanoseconds() - start) / 1e6;\n console.log(\n `${name}: ${elapsed.toFixed(2)}ms total (${(elapsed / ITERATIONS).toFixed(6)}ms/op)`,\n );\n return elapsed;\n}\n\nbench(\"feature/js\", () => {\n jsImpl(sample);\n});\n\nbench(\"feature/native\", () => {\n nativeImpl(sample);\n});\n```\n\n## Verification checklist\n\n- Generated `native/index.d.ts` includes the new export and intended TS signature.\n- The loaded `.node` file's `Object.keys(require(candidate))` includes the new export.\n- Runtime enum objects are present when the change adds/changes enums.\n- Bench numbers are recorded in the PR/notes.\n- Call sites are updated only if native is faster/equal and behavior-compatible.\n- Obsolete JS code is removed when the native implementation becomes canonical.\n\n## Rule of thumb\n\n- If native is slower, do not switch callsites. Keep or remove the export based on whether it has a near-term owner.\n- If native is faster and behavior-compatible, switch callsites and keep a benchmark to catch regressions.\n",
|
|
63
63
|
"prompt-architect-reports/README.md": "# Prompt architect reports\n\nGenerated from the four architect subagents spawned to review prompt optimization/enhancement opportunities, then augmented by inspecting failed subagent JSONL contexts.\n\n## Artifacts\n\n- `agent-prompts.raw.json` — usable structured report from `2-AgentPrompts`.\n- `recovery-summary.md` — summary of context recovery for failed/errored agents.\n- `recovered-context/0-ToolPrompts.recovered.md` — recovered tool-prompt review context plus all 34 structured `report_finding` findings.\n- `recovered-context/0-ToolPrompts.findings.json` — recovered tool-prompt findings as JSON.\n- `recovered-context/1-SystemPrompts.recovered.md` — recovered system-prompt context: reads/searches/errors; no findings/yield emitted.\n- `recovered-context/1-SystemPrompts.findings.json` — empty; no `report_finding` calls emitted.\n- `recovered-context/3-SkillMiscPrompts.recovered.md` — recovered skill/misc context: reads/searches/errors; no findings/yield emitted.\n- `recovered-context/3-SkillMiscPrompts.findings.json` — empty; no `report_finding` calls emitted.\n- `tool-prompts.raw.md`, `system-prompts.raw.md`, `skill-misc-prompts.raw.json` — initial raw-stub artifacts kept for audit history; superseded by `recovery-summary.md` and `recovered-context/`.\n- `system-prompts.rerun.json` — successful re-run of the SystemPrompts lane (grade C, 12 findings: 1 P1, 6 P2, 5 P3).\n- `skill-misc-prompts.rerun.json` — successful re-run of the SkillMiscPrompts lane (grade C, 9 findings: 1 P1, 4 P2, 4 P3).\n\n## Usable verdicts\n\n### AgentPrompts\n\nUsable report. Verdict: **B-** with **16 findings**: **2 P1**, **5 P2**, **9 P3**.\n\nTop fixes:\n\n1. Add a persistence-context gate to `architect.md` and `critic.md` so `gjc ralplan --write` is used only inside an active ralplan lane; otherwise return the full review in `yield.result.data`.\n2. Wire `report_finding` into the architect output contract and define the severity mapping `CRITICAL -> P0`, `HIGH -> P1`, `MEDIUM -> P2`, `LOW -> P3`.\n3. Extract the ultragoal red-team executor QA block from the always-loaded executor prompt into an ultragoal-only injected fragment or assignment contract.\n\n### ToolPrompts\n\nNo final `yield` or grade, but context recovery found **34 structured findings** emitted through `report_finding` before stalls/429: **4 P1**, **18 P2**, **12 P3**.\n\nHighest-impact recovered findings:\n\n1. `replace.md` recommends `cat`/`sed` shell alternatives that directly contradict `bash.md`, `read.md`, and `search.md` bans.\n2. `monitor.md` documents invalid `job({op:\"list\"})`; actual schema expects `job({list: true})`.\n3. `apply-patch.md` has a truncated “Within a hunk each line starts with:” sentence.\n4. `ast-edit.md` omits the preview-to-`resolve({action:\"apply\"})` persistence flow.\n\n### SystemPrompts (re-run)\n\nGrade **C**, **12 findings** (1 P1, 6 P2, 5 P3). Top fixes: remove the `<soul>` block contradicting the base prompt's authority/safety contracts; guard `{{toolRefs.search_tool_bm25}}` discovery text on the actual activator tool; make plan-mode subagent output instructions yield-aware. See `system-prompts.rerun.json`.\n\n### SkillMiscPrompts (re-run)\n\nGrade **C**, **9 findings** (1 P1, 4 P2, 4 P3). Top fixes: fix unrendered `{{ARGUMENTS}}` in deep-interview SKILL; remove dead `plan` skill / `--research-setup` / `gjc sparkshell` / `team_cleanup` references; complete the ultragoal `executorQa` replay contract. See `skill-misc-prompts.rerun.json`.\n\n## Status\n\nAll four lanes now have usable reports: AgentPrompts and ToolPrompts findings were applied in this branch's prompt fixes; SystemPrompts and SkillMiscPrompts re-run findings are recorded above and pending application.\n",
|
package/src/modes/DESIGN.md
CHANGED
|
@@ -291,6 +291,59 @@ textual state without SGR. Korean, Japanese, Chinese, and mixed CJK/Latin prose
|
|
|
291
291
|
must wrap at semantic phrase boundaries, never through an action/status label or
|
|
292
292
|
short identifier; a semantic CJK break is a visual-QA failure.
|
|
293
293
|
|
|
294
|
+
### Direct-root anatomy, IRC lane, and pin boundary
|
|
295
|
+
|
|
296
|
+
The production root has one ordered, direct-child anatomy. Do not wrap, reorder,
|
|
297
|
+
or independently pin these regions:
|
|
298
|
+
|
|
299
|
+
1. `ircSplitView` is the viewport anchor and owns the transcript (and the IRC
|
|
300
|
+
sidebar when effective);
|
|
301
|
+
2. `pendingMessagesContainer`, `statusContainer`, `todoContainer`, and
|
|
302
|
+
`btwContainer` follow the anchor **before** the pin boundary;
|
|
303
|
+
3. `statusLine` is the pin boundary; and
|
|
304
|
+
4. `hookWidgetContainerAbove`, `editorContainer`, `petFloorContainer`, and
|
|
305
|
+
`hookWidgetContainerBelow` are the later direct composer children in that
|
|
306
|
+
order.
|
|
307
|
+
|
|
308
|
+
`statusContainer` is transient operation/status content in the pre-boundary
|
|
309
|
+
application flow; it is not the persistent `statusLine` telemetry rail. The
|
|
310
|
+
status line begins the fixed suffix. The focused editor, its cursor, and every
|
|
311
|
+
later direct composer child remain below that boundary during manual history,
|
|
312
|
+
streaming, and reflow. Pending messages, todo, and BTW therefore remain
|
|
313
|
+
transcript-adjacent rather than becoming accidental fixed chrome.
|
|
314
|
+
|
|
315
|
+
IRC has one shared work-lane geometry. At **64 cells or narrower** the IRC lane
|
|
316
|
+
is ineffective and transcript/todo use the full width. At **65 cells** it is
|
|
317
|
+
exactly **32 / 3 / 30** cells (left work lane / separator / IRC lane). At wider
|
|
318
|
+
sizes the same split calculation applies; todo uses the left work lane whenever
|
|
319
|
+
IRC is effective, including empty, streaming, and long IRC histories. Todo
|
|
320
|
+
does not create a second sidebar, a separately rounded width, or a different
|
|
321
|
+
collapse rule.
|
|
322
|
+
|
|
323
|
+
Todo is absent when empty. Populated todos support long text, multiple phases,
|
|
324
|
+
and collapsed/expanded views without crossing the separator or overflowing
|
|
325
|
+
their work lane. The active phase is retained in collapsed view; expanded view
|
|
326
|
+
keeps phase/task order. IRC empty, streaming, and long states preserve the
|
|
327
|
+
same root order and pin boundary.
|
|
328
|
+
|
|
329
|
+
Row reservation is content-priority based: reserve the focused composer and
|
|
330
|
+
status line first, then normal hooks; when height is constrained, drop the
|
|
331
|
+
manual-output notice first, then the decorative pet, then low-priority hooks.
|
|
332
|
+
A zero-row transcript capacity is valid. Neither reservation nor degradation
|
|
333
|
+
may hide focus/cursor geometry, move an anchor into pinned chrome, or turn a
|
|
334
|
+
resize into a follow/manual state change. Manual and follow paths, streaming
|
|
335
|
+
updates, width growth/shrink, and height growth/shrink preserve the anchor
|
|
336
|
+
semantics; resize must recompute the shared IRC/todo lane before rendering.
|
|
337
|
+
|
|
338
|
+
All lane measurement, clipping, and wrapping is ANSI-aware terminal-cell
|
|
339
|
+
measurement. Terminal graphics may be shown only where the effective layout
|
|
340
|
+
permits them and must degrade to the textual graphics fallback without changing
|
|
341
|
+
row ownership. ANSI/color output preserves visible cursor/focus state; ASCII
|
|
342
|
+
and no-color output retains textual selection/status affordances. CJK and mixed
|
|
343
|
+
CJK/Latin todo, IRC, status, and BTW text break at semantic phrase boundaries,
|
|
344
|
+
not inside a phase/action/status label, short identifier, or grapheme cluster.
|
|
345
|
+
Those defects, width overflow, overlap, hidden focus/cursor, and lost anchors
|
|
346
|
+
are blocking visual-QA failures.
|
|
294
347
|
### Sticky viewport deterministic visual QA
|
|
295
348
|
|
|
296
349
|
`test/fixtures/tui/sticky-viewport-showcase.ts` is a fixed-clock, no-network,
|
|
@@ -82,6 +82,35 @@ export function computeIrcSplitWidths(width: number): {
|
|
|
82
82
|
rightWidth: sidebarWidth,
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolves the single shared work-lane geometry. The left lane is used by both
|
|
87
|
+
* transcript and todo content whenever the IRC lane is effective.
|
|
88
|
+
*/
|
|
89
|
+
export function computeIrcWorkLaneWidths(
|
|
90
|
+
width: number,
|
|
91
|
+
sidebarVisible: boolean,
|
|
92
|
+
): { leftWidth: number; separatorWidth: number; rightWidth: number } {
|
|
93
|
+
const fullWidth = Math.max(0, Math.floor(width));
|
|
94
|
+
return sidebarVisible
|
|
95
|
+
? computeIrcSplitWidths(fullWidth)
|
|
96
|
+
: { leftWidth: fullWidth, separatorWidth: 0, rightWidth: 0 };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export class IrcLeftLaneComponent implements Component {
|
|
100
|
+
constructor(
|
|
101
|
+
private readonly content: Component,
|
|
102
|
+
private readonly isSidebarVisible: (width: number) => boolean,
|
|
103
|
+
) {}
|
|
104
|
+
|
|
105
|
+
render(width: number): string[] {
|
|
106
|
+
const layout = computeIrcWorkLaneWidths(width, this.isSidebarVisible(width));
|
|
107
|
+
return this.content.render(layout.leftWidth);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
invalidate(): void {
|
|
111
|
+
this.content.invalidate?.();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
85
114
|
|
|
86
115
|
type SemanticLine = Readonly<{
|
|
87
116
|
kind: "header" | "body" | "elision" | "blank";
|
|
@@ -288,7 +317,7 @@ export class IrcSplitViewComponent implements ViewportAnchorProvider {
|
|
|
288
317
|
renderWithViewportAnchors(width: number): ViewportAnchorRender {
|
|
289
318
|
if (!this.#visible) return renderComponentWithViewportAnchors(this.leftPane, width);
|
|
290
319
|
const componentTheme = typeof this.componentTheme === "function" ? this.componentTheme() : this.componentTheme;
|
|
291
|
-
const { leftWidth, separatorWidth, rightWidth } =
|
|
320
|
+
const { leftWidth, separatorWidth, rightWidth } = computeIrcWorkLaneWidths(width, this.#visible);
|
|
292
321
|
if (rightWidth === 0) return renderComponentWithViewportAnchors(this.leftPane, width);
|
|
293
322
|
const separator = separatorWidth > 0 ? componentTheme.fg("dim", ` ${componentTheme.boxSharp.vertical} `) : "";
|
|
294
323
|
const leftRender = withTerminalGraphicsFallback(
|
|
@@ -206,7 +206,6 @@ export class StatusLineComponent implements Component {
|
|
|
206
206
|
separator: settings.get("statusLine.separator"),
|
|
207
207
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
208
208
|
showSkillHud: settings.get("statusLine.showSkillHud"),
|
|
209
|
-
showActionHints: settings.get("statusLine.showActionHints"),
|
|
210
209
|
segmentOptions: settings.getGroup("statusLine").segmentOptions,
|
|
211
210
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
212
211
|
maxRows: settings.get("statusLine.maxRows"),
|
|
@@ -34,7 +34,12 @@ import { BorderedLoader } from "../../modes/components/bordered-loader";
|
|
|
34
34
|
import { DynamicBorder } from "../../modes/components/dynamic-border";
|
|
35
35
|
import { EvalExecutionComponent } from "../../modes/components/eval-execution";
|
|
36
36
|
import { getMarkdownTheme, getSymbolTheme, theme } from "../../modes/theme/theme";
|
|
37
|
-
import
|
|
37
|
+
import {
|
|
38
|
+
clearInteractiveActivityLoaders,
|
|
39
|
+
type InteractiveModeContext,
|
|
40
|
+
stopInteractiveActivityIndicator,
|
|
41
|
+
suspendInteractiveActivityIndicator,
|
|
42
|
+
} from "../../modes/types";
|
|
38
43
|
import { computeContextBreakdown, renderContextUsage } from "../../modes/utils/context-usage";
|
|
39
44
|
import { buildHotkeysMarkdown, formatHotkeyMarkdownCode } from "../../modes/utils/hotkeys-markdown";
|
|
40
45
|
import { buildToolsMarkdown } from "../../modes/utils/tools-markdown";
|
|
@@ -965,12 +970,10 @@ export class CommandController {
|
|
|
965
970
|
|
|
966
971
|
async #runNewSessionFlow(options?: NewSessionOptions, label: string = "New session started"): Promise<boolean> {
|
|
967
972
|
if (!(await this.ctx.session.newSession(options))) return false;
|
|
973
|
+
if (this.ctx.isStopped?.()) return false;
|
|
974
|
+
clearInteractiveActivityLoaders(this.ctx);
|
|
968
975
|
|
|
969
|
-
|
|
970
|
-
this.ctx.loadingAnimation.stop();
|
|
971
|
-
this.ctx.loadingAnimation = undefined;
|
|
972
|
-
}
|
|
973
|
-
this.ctx.statusContainer.clear();
|
|
976
|
+
stopInteractiveActivityIndicator(this.ctx);
|
|
974
977
|
this.ctx.resetIrcSidebarSession();
|
|
975
978
|
this.ctx.resetObserverRegistry();
|
|
976
979
|
setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
|
|
@@ -991,6 +994,7 @@ export class CommandController {
|
|
|
991
994
|
|
|
992
995
|
this.ctx.chatContainer.addChild(new Text(`${theme.fg("accent", `${theme.status.success} ${label}`)}`, 1, 0));
|
|
993
996
|
await this.ctx.reloadTodos();
|
|
997
|
+
if (this.ctx.isStopped?.()) return false;
|
|
994
998
|
this.ctx.ui.requestRender();
|
|
995
999
|
return true;
|
|
996
1000
|
}
|
|
@@ -1000,19 +1004,18 @@ export class CommandController {
|
|
|
1000
1004
|
}
|
|
1001
1005
|
|
|
1002
1006
|
async handleContextClearCommand(): Promise<void> {
|
|
1003
|
-
if (this.ctx.
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
}
|
|
1007
|
-
this.ctx.statusContainer.clear();
|
|
1008
|
-
|
|
1007
|
+
if (this.ctx.isStopped?.()) return;
|
|
1008
|
+
clearInteractiveActivityLoaders(this.ctx);
|
|
1009
|
+
stopInteractiveActivityIndicator(this.ctx);
|
|
1009
1010
|
if (this.ctx.session.isCompacting) {
|
|
1010
1011
|
this.ctx.session.abortCompaction();
|
|
1011
1012
|
while (this.ctx.session.isCompacting) {
|
|
1012
1013
|
await Bun.sleep(10);
|
|
1013
1014
|
}
|
|
1014
1015
|
}
|
|
1016
|
+
if (this.ctx.isStopped?.()) return;
|
|
1015
1017
|
if (!(await this.ctx.session.clearContext())) return;
|
|
1018
|
+
if (this.ctx.isStopped?.()) return;
|
|
1016
1019
|
setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
|
|
1017
1020
|
|
|
1018
1021
|
this.ctx.statusLine.invalidate();
|
|
@@ -1033,6 +1036,7 @@ export class CommandController {
|
|
|
1033
1036
|
new Text(`${theme.fg("accent", `${theme.status.success} Context cleared`)}`, 1, 0),
|
|
1034
1037
|
);
|
|
1035
1038
|
await this.ctx.reloadTodos();
|
|
1039
|
+
if (this.ctx.isStopped?.()) return;
|
|
1036
1040
|
this.ctx.ui.requestRender();
|
|
1037
1041
|
}
|
|
1038
1042
|
|
|
@@ -1049,17 +1053,15 @@ export class CommandController {
|
|
|
1049
1053
|
this.ctx.showWarning("Wait for the current response to finish or abort it before forking.");
|
|
1050
1054
|
return;
|
|
1051
1055
|
}
|
|
1052
|
-
if (this.ctx.loadingAnimation) {
|
|
1053
|
-
this.ctx.loadingAnimation.stop();
|
|
1054
|
-
this.ctx.loadingAnimation = undefined;
|
|
1055
|
-
}
|
|
1056
|
-
this.ctx.statusContainer.clear();
|
|
1057
1056
|
|
|
1058
1057
|
const success = await this.ctx.session.fork();
|
|
1058
|
+
if (this.ctx.isStopped?.()) return;
|
|
1059
1059
|
if (!success) {
|
|
1060
1060
|
this.ctx.showError("Fork failed (session not persisted or cancelled)");
|
|
1061
1061
|
return;
|
|
1062
1062
|
}
|
|
1063
|
+
clearInteractiveActivityLoaders(this.ctx);
|
|
1064
|
+
stopInteractiveActivityIndicator(this.ctx);
|
|
1063
1065
|
this.ctx.resetIrcSidebarSession();
|
|
1064
1066
|
|
|
1065
1067
|
this.ctx.statusLine.invalidate();
|
|
@@ -1256,11 +1258,7 @@ export class CommandController {
|
|
|
1256
1258
|
customInstructionsOrOptions?: string | CompactOptions,
|
|
1257
1259
|
isAuto = false,
|
|
1258
1260
|
): Promise<CompactionOutcome> {
|
|
1259
|
-
|
|
1260
|
-
this.ctx.loadingAnimation.stop();
|
|
1261
|
-
this.ctx.loadingAnimation = undefined;
|
|
1262
|
-
}
|
|
1263
|
-
this.ctx.statusContainer.clear();
|
|
1261
|
+
const releaseActivityIndicator = suspendInteractiveActivityIndicator(this.ctx);
|
|
1264
1262
|
|
|
1265
1263
|
const originalOnEscape = this.ctx.editor.onEscape;
|
|
1266
1264
|
this.ctx.editor.onEscape = () => {
|
|
@@ -1287,24 +1285,28 @@ export class CommandController {
|
|
|
1287
1285
|
? customInstructionsOrOptions
|
|
1288
1286
|
: undefined;
|
|
1289
1287
|
await this.ctx.session.compact(instructions, options);
|
|
1288
|
+
if (this.ctx.isStopped?.()) return outcome;
|
|
1290
1289
|
|
|
1291
1290
|
this.ctx.rebuildChatFromMessages("reconcile-same-transcript");
|
|
1292
1291
|
|
|
1293
1292
|
this.ctx.statusLine.invalidate();
|
|
1294
1293
|
this.ctx.updateEditorTopBorder();
|
|
1295
1294
|
} catch (error) {
|
|
1296
|
-
|
|
1297
|
-
|
|
1295
|
+
outcome = error instanceof CompactionCancelledError ? "cancelled" : "failed";
|
|
1296
|
+
if (this.ctx.isStopped?.()) return outcome;
|
|
1297
|
+
if (outcome === "cancelled") {
|
|
1298
1298
|
this.ctx.showError("Compaction cancelled");
|
|
1299
1299
|
} else {
|
|
1300
|
-
outcome = "failed";
|
|
1301
1300
|
const message = error instanceof Error ? error.message : String(error);
|
|
1302
1301
|
this.ctx.showError(`Compaction failed: ${message}`);
|
|
1303
1302
|
}
|
|
1304
1303
|
} finally {
|
|
1305
1304
|
compactingLoader.stop();
|
|
1306
|
-
this.ctx.
|
|
1307
|
-
|
|
1305
|
+
if (!this.ctx.isStopped?.()) {
|
|
1306
|
+
this.ctx.statusContainer.clear();
|
|
1307
|
+
this.ctx.editor.onEscape = originalOnEscape;
|
|
1308
|
+
}
|
|
1309
|
+
releaseActivityIndicator();
|
|
1308
1310
|
}
|
|
1309
1311
|
await this.ctx.flushCompactionQueue({ willRetry: false });
|
|
1310
1312
|
return outcome;
|
|
@@ -1323,11 +1325,7 @@ export class CommandController {
|
|
|
1323
1325
|
return;
|
|
1324
1326
|
}
|
|
1325
1327
|
|
|
1326
|
-
|
|
1327
|
-
this.ctx.loadingAnimation.stop();
|
|
1328
|
-
this.ctx.loadingAnimation = undefined;
|
|
1329
|
-
}
|
|
1330
|
-
this.ctx.statusContainer.clear();
|
|
1328
|
+
const releaseActivityIndicator = suspendInteractiveActivityIndicator(this.ctx);
|
|
1331
1329
|
|
|
1332
1330
|
const originalOnEscape = this.ctx.editor.onEscape;
|
|
1333
1331
|
this.ctx.editor.onEscape = () => {
|
|
@@ -1347,6 +1345,7 @@ export class CommandController {
|
|
|
1347
1345
|
try {
|
|
1348
1346
|
// Handoff generation runs as a oneshot request; the new session is shown after it completes.
|
|
1349
1347
|
const result = await this.ctx.session.handoff(customInstructions);
|
|
1348
|
+
if (this.ctx.isStopped?.()) return;
|
|
1350
1349
|
|
|
1351
1350
|
if (!result) {
|
|
1352
1351
|
this.ctx.showError("Handoff cancelled");
|
|
@@ -1361,6 +1360,7 @@ export class CommandController {
|
|
|
1361
1360
|
this.ctx.updateEditorTopBorder();
|
|
1362
1361
|
this.ctx.updateEditorBorderColor();
|
|
1363
1362
|
await this.ctx.reloadTodos();
|
|
1363
|
+
if (this.ctx.isStopped?.()) return;
|
|
1364
1364
|
|
|
1365
1365
|
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
1366
1366
|
this.ctx.chatContainer.addChild(
|
|
@@ -1370,6 +1370,7 @@ export class CommandController {
|
|
|
1370
1370
|
this.ctx.showStatus(`Handoff document saved to: ${result.savedPath}`);
|
|
1371
1371
|
}
|
|
1372
1372
|
} catch (error) {
|
|
1373
|
+
if (this.ctx.isStopped?.()) return;
|
|
1373
1374
|
const message = error instanceof Error ? error.message : String(error);
|
|
1374
1375
|
if (message === "Handoff cancelled" || (error instanceof Error && error.name === "AbortError")) {
|
|
1375
1376
|
this.ctx.showError("Handoff cancelled");
|
|
@@ -1389,8 +1390,11 @@ export class CommandController {
|
|
|
1389
1390
|
}
|
|
1390
1391
|
} finally {
|
|
1391
1392
|
handoffLoader.stop();
|
|
1392
|
-
this.ctx.
|
|
1393
|
-
|
|
1393
|
+
if (!this.ctx.isStopped?.()) {
|
|
1394
|
+
this.ctx.statusContainer.clear();
|
|
1395
|
+
this.ctx.editor.onEscape = originalOnEscape;
|
|
1396
|
+
}
|
|
1397
|
+
releaseActivityIndicator();
|
|
1394
1398
|
}
|
|
1395
1399
|
}
|
|
1396
1400
|
|
|
@@ -15,7 +15,12 @@ import { TodoReminderComponent } from "../../modes/components/todo-reminder";
|
|
|
15
15
|
import { ToolExecutionComponent, type ToolExecutionHandle } from "../../modes/components/tool-execution";
|
|
16
16
|
import { TtsrNotificationComponent } from "../../modes/components/ttsr-notification";
|
|
17
17
|
import { getSymbolTheme, theme } from "../../modes/theme/theme";
|
|
18
|
-
import
|
|
18
|
+
import {
|
|
19
|
+
type InteractiveModeContext,
|
|
20
|
+
stopInteractiveActivityIndicator,
|
|
21
|
+
syncInteractiveActivityIndicator,
|
|
22
|
+
type TodoPhase,
|
|
23
|
+
} from "../../modes/types";
|
|
19
24
|
import type { PlanApprovalDetails } from "../../plan-mode/approved-plan";
|
|
20
25
|
import { completionNotifyDisabledByEnv } from "../../sdk/bus/config";
|
|
21
26
|
import { summaryFromMessage } from "../../sdk/bus/helpers";
|
|
@@ -166,6 +171,14 @@ export class EventController {
|
|
|
166
171
|
this.ctx.retryEscapeHandler = undefined;
|
|
167
172
|
}
|
|
168
173
|
this.ctx.retryEscapePrimed = false;
|
|
174
|
+
if (this.ctx.autoCompactionEscapeHandler) {
|
|
175
|
+
this.ctx.editor.onEscape = this.ctx.autoCompactionEscapeHandler;
|
|
176
|
+
this.ctx.autoCompactionEscapeHandler = undefined;
|
|
177
|
+
}
|
|
178
|
+
if (this.ctx.autoCompactionLoader) {
|
|
179
|
+
this.ctx.autoCompactionLoader.stop();
|
|
180
|
+
this.ctx.autoCompactionLoader = undefined;
|
|
181
|
+
}
|
|
169
182
|
if (this.ctx.retryLoader) {
|
|
170
183
|
this.ctx.retryLoader.stop();
|
|
171
184
|
this.ctx.retryLoader = undefined;
|
|
@@ -266,7 +279,9 @@ export class EventController {
|
|
|
266
279
|
}
|
|
267
280
|
|
|
268
281
|
async #dispatchEvent(event: AgentSessionEvent): Promise<void> {
|
|
282
|
+
if (this.ctx.isStopped?.()) return;
|
|
269
283
|
if (!this.ctx.isInitialized) await this.ctx.init();
|
|
284
|
+
if (this.ctx.isStopped?.()) return;
|
|
270
285
|
this.#visibleTranscriptChanged = false;
|
|
271
286
|
this.#handlingEvent = true;
|
|
272
287
|
try {
|
|
@@ -274,6 +289,7 @@ export class EventController {
|
|
|
274
289
|
this.ctx.updateEditorTopBorder();
|
|
275
290
|
const run = this.#handlers[event.type] as (e: AgentSessionEvent) => Promise<void>;
|
|
276
291
|
await run(event);
|
|
292
|
+
if (this.ctx.isStopped?.()) return;
|
|
277
293
|
if (this.#visibleTranscriptChanged) this.ctx.recordVisibleTranscriptMutation?.();
|
|
278
294
|
if (this.ctx.isTranscriptViewerOpen?.()) this.ctx.refreshTranscriptViewer?.();
|
|
279
295
|
} finally {
|
|
@@ -878,23 +894,22 @@ export class EventController {
|
|
|
878
894
|
const planDetails = details.sourceResultDetails as PlanApprovalDetails | undefined;
|
|
879
895
|
if (planDetails) {
|
|
880
896
|
await this.ctx.planModeController.handleApproval(planDetails);
|
|
897
|
+
if (this.ctx.isStopped?.()) return;
|
|
881
898
|
}
|
|
882
899
|
}
|
|
883
900
|
}
|
|
884
901
|
}
|
|
885
902
|
|
|
886
903
|
async #handleAgentEnd(_event: Extract<AgentSessionEvent, { type: "agent_end" }>): Promise<void> {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
this.ctx.loadingAnimation = undefined;
|
|
890
|
-
this.ctx.statusContainer.clear();
|
|
891
|
-
}
|
|
904
|
+
this.ctx.setWorkingMessage(undefined);
|
|
905
|
+
stopInteractiveActivityIndicator(this.ctx);
|
|
892
906
|
if (this.ctx.streamingComponent) {
|
|
893
907
|
this.ctx.chatContainer.removeChild(this.ctx.streamingComponent);
|
|
894
908
|
this.ctx.streamingComponent = undefined;
|
|
895
909
|
this.ctx.streamingMessage = undefined;
|
|
896
910
|
}
|
|
897
911
|
await this.ctx.planModeController.flushPendingModelSwitch();
|
|
912
|
+
if (this.ctx.isStopped?.()) return;
|
|
898
913
|
for (const toolCallId of Array.from(this.ctx.pendingTools.keys())) {
|
|
899
914
|
if (!this.#backgroundToolCallIds.has(toolCallId)) {
|
|
900
915
|
this.ctx.pendingTools.delete(toolCallId);
|
|
@@ -922,11 +937,7 @@ export class EventController {
|
|
|
922
937
|
this.ctx.editor.onEscape = () => {
|
|
923
938
|
this.ctx.session.abortCompaction();
|
|
924
939
|
};
|
|
925
|
-
|
|
926
|
-
this.ctx.loadingAnimation.stop();
|
|
927
|
-
this.ctx.loadingAnimation = undefined;
|
|
928
|
-
}
|
|
929
|
-
this.ctx.statusContainer.clear();
|
|
940
|
+
stopInteractiveActivityIndicator(this.ctx, { restoreBackground: false });
|
|
930
941
|
const reasonText =
|
|
931
942
|
event.reason === "overflow" ? "Context overflow detected, " : event.reason === "idle" ? "Idle " : "";
|
|
932
943
|
const actionLabel = event.action === "handoff" ? "Auto-handoff" : "Auto context-full maintenance";
|
|
@@ -982,6 +993,7 @@ export class EventController {
|
|
|
982
993
|
this.ctx.statusLine.invalidate();
|
|
983
994
|
this.ctx.updateEditorTopBorder();
|
|
984
995
|
await this.ctx.reloadTodos();
|
|
996
|
+
if (this.ctx.isStopped?.()) return;
|
|
985
997
|
this.ctx.showStatus("Auto-handoff completed");
|
|
986
998
|
} else if (event.skipped) {
|
|
987
999
|
// Benign skip: no model selected, no candidate models available, or nothing
|
|
@@ -995,6 +1007,8 @@ export class EventController {
|
|
|
995
1007
|
this.ctx.showWarning("Auto context-full maintenance failed; continuing without maintenance");
|
|
996
1008
|
}
|
|
997
1009
|
await this.ctx.flushCompactionQueue({ willRetry: event.willRetry });
|
|
1010
|
+
if (this.ctx.isStopped?.()) return;
|
|
1011
|
+
syncInteractiveActivityIndicator(this.ctx);
|
|
998
1012
|
this.ctx.ui.requestRender();
|
|
999
1013
|
}
|
|
1000
1014
|
|
|
@@ -1024,11 +1038,7 @@ export class EventController {
|
|
|
1024
1038
|
this.ctx.session.abortRetry();
|
|
1025
1039
|
}
|
|
1026
1040
|
};
|
|
1027
|
-
|
|
1028
|
-
this.ctx.loadingAnimation.stop();
|
|
1029
|
-
this.ctx.loadingAnimation = undefined;
|
|
1030
|
-
}
|
|
1031
|
-
this.ctx.statusContainer.clear();
|
|
1041
|
+
stopInteractiveActivityIndicator(this.ctx, { restoreBackground: false });
|
|
1032
1042
|
// Stop any prior retry loader/timer before installing a new one.
|
|
1033
1043
|
this.ctx.retryLoader?.stop();
|
|
1034
1044
|
this.#clearRetryCountdown();
|
|
@@ -1071,6 +1081,7 @@ export class EventController {
|
|
|
1071
1081
|
if (!event.success) {
|
|
1072
1082
|
this.ctx.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
|
|
1073
1083
|
}
|
|
1084
|
+
syncInteractiveActivityIndicator(this.ctx);
|
|
1074
1085
|
this.ctx.ui.requestRender();
|
|
1075
1086
|
}
|
|
1076
1087
|
|