mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
Merge branch 'main' into feature/charts
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Support adaptive thinking levels for Claude Opus and Sonnet 5 and later.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Apply the Agent Manager base branch picker selection to the active diff immediately. Changing the base branch now refreshes the diff against the new base instead of keeping the previous comparison until the scope or session changed.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Add a scope selector and base branch picker to the Agent Manager diff review. The side panel and full-screen review now let you switch between Branch, Staged, Unstaged, and Session scopes for the selected worktree, and the Branch scope's base branch can be overridden from a picker next to it. Branch stays the default, so existing review behavior is unchanged.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Add an experimental multi-project foundation for Agent Manager behind the Multi-Project Agent Manager toggle in Kilo Settings > Experimental. A persistent project registry catalogs additional Git repositories across restarts while the current workspace repository stays the pinned default project, and all repository-bound services (state, worktrees, setup scripts, stale tracking, polling) are now owned by immutable per-project contexts that can be activated, expanded, and switched through new project protocol messages. Every expanded project runs its own git stats and PR pollers, so all visible project accordions show live data at the same time; switching projects keeps previously visited projects expanded and restores the exact worktree or session that was open there. Sessions created anywhere (sidebar, another window, the CLI) appear in the owning project's list immediately, and open tabs stay private to the project they were opened in. Single-project behavior is unchanged when the experiment is disabled.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Make the Agent Manager diff review follow the sidebar selection instead of a single session. Switching session tabs inside a worktree no longer refetches the Branch, Staged, and Unstaged scopes, the Session scope now swaps to the active session's changes on tab switch, and the Local tab gains the Session scope so sessions running in the workspace can be reviewed on their own. The Session scope shows a notice when snapshots are disabled instead of a blank list, worktrees without an open session now still show their branch diff, and the Apply dialog lists the worktree's changes again.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Let users open Agent Manager terminals in the VS Code terminal or an embedded side panel. The terminal button's dropdown picks the destination; the side panel shares the right-hand inspector with the diff view and keeps running in the background when hidden.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Make Cmd/Ctrl+/ toggle the Agent Manager terminal even when the webview keybinding forwarding drops the key while the prompt input is focused, and stop it from triggering the Agent Manager terminal while the Kilo sidebar is focused.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Match the Agent Manager terminal shortcut fallback to the platform modifier (Cmd on macOS, Ctrl elsewhere) and consume the extension echo once per keypress so unrelated invocations are no longer swallowed.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep conversations and workspace files unchanged when a checkpoint cannot be fully restored.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep the Agent Manager terminal cursor visible on the bottom row.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Run Agent Manager project scripts in the terminal selected by the existing toolbar dropdown. Agent Manager panel uses the named side terminal, while VS Code terminal retains the integrated task flow.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"@kilocode/kilo-telemetry": patch
|
||||
---
|
||||
|
||||
Reduce CLI startup time by deferring Kilo-specific module loading until commands actually run, caching the telemetry profile lookup across invocations, and uploading telemetry in the background so process exit is not delayed by a network round trip
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Fix settings snapping back to their previous value after being cleared to "Not set" when multiple config files exist (e.g. both `kilo.json` and `kilo.jsonc`)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Include basic-auth credentials in the Local and Network Console URLs printed by `kilo console`, so users on headless hosts (no `DISPLAY`/`WAYLAND_DISPLAY`, SSH sessions, CI runners) can open the URL in a browser on another machine and reach the Console.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Allow users to enable web search for models from all providers through Kilo configuration, VS Code settings, and Kilo Console settings.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Keep Nix builds on the Bun version required by the repository.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Update the visible agent mode when cycling modes in Kilo sidebars and pending session tabs.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Fix settings changes sometimes failing to save and apply in VS Code.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Keep RC installations up to date when a newer stable CLI release is published.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-memory": patch
|
||||
---
|
||||
|
||||
Accept memory digest responses that include additional model-generated fields.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Report Bash commands terminated by a signal with the conventional 128 + signum exit code (e.g. 139 for SIGSEGV) instead of hanging until the command timeout.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Persist MCP server enable and disable changes from VS Code settings across window reloads.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Preserve parenthesized tilde expressions as literal text in rendered chat messages.
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Non-interactive `kilo run` no longer reports success for runs that did not complete. A plain
|
||||
headless run (neither `--auto` nor `--dangerously-skip-permissions`) in which the CLI
|
||||
auto-rejected at least one permission ask now exits 1 with a stderr diagnostic naming the cause,
|
||||
and a run whose session errors mid-stream now prints that diagnostic to stderr under
|
||||
`--format json` as well (previously the JSON branch swallowed it). Runs that complete their turn
|
||||
with no auto-rejected permission still exit 0. Under `--format json` the auto-reject path adds a
|
||||
new `error` event to the stream; existing event shapes are unchanged. The same exit-1 rule applies
|
||||
to a plain non-interactive `--attach` run that auto-rejects an ask (that run was equally crippled);
|
||||
interactive mode is untouched.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Fix session transcripts losing their final messages when the CLI exits — pending uploads are now flushed on shutdown and as soon as a session closes.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Show prompt input toggle tooltips instantly on hover instead of after a delay.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": minor
|
||||
---
|
||||
|
||||
Publish a signed GitHub-hosted JetBrains plugin build with the CLI bundled for offline installation.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Allow sending prompts while a session is busy and show queued prompts with a remove action.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Navigate long conversations from a compact prompt rail that loads earlier history as you scroll.
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Changes from opencode v1.17.5 to v1.17.9 upstream:
|
||||
|
||||
- Core Bugfixes: Improved MCP server compatibility by declaring Kilo's supported client capabilities.
|
||||
- Core Bugfixes: Plugin client requests now reuse the active server instead of assuming the default local port.
|
||||
- Core Bugfixes: ACP shell tool calls now show the command and working directory from the start.
|
||||
- Core Bugfixes: Plugin-provided shell environment variables now apply to PTY sessions.
|
||||
- Core Bugfixes: OpenAI-compatible providers now accept MCP tool schemas that previously failed validation. (@jquense)
|
||||
- Core Bugfixes: Cloudflare AI Gateway now receives the configured API key correctly. (@keefetang)
|
||||
- Core Bugfixes: MCP tools without declared schema properties now work with providers that expect object properties.
|
||||
- Core Bugfixes: Long-running MCP tools now keep their timeout alive when they report progress. (@Nomadcxx)
|
||||
- Core Bugfixes: The MCP OAuth callback server now shuts down once authorization finishes or is cancelled.
|
||||
- Core Bugfixes: MCP tool failures now surface the server's error text instead of a generic failure.
|
||||
- Core Bugfixes: MCP OAuth error pages now escape provider error text correctly.
|
||||
- Core Bugfixes: Honor configured agent step limits by forcing a final text response instead of failing mid-run.
|
||||
- Core Bugfixes: Queue steering prompts before dismissing pending questions so the previous turn cannot resume first.
|
||||
- Core Bugfixes: Prevent local server credentials from leaking into spawned PTY processes.
|
||||
- Core Bugfixes: Fix Devstral model detection when provider IDs use different casing. (@Robin1987China)
|
||||
- Core Bugfixes: Pass configured custom headers to Copilot model requests.
|
||||
- Core Improvements: MCP servers can now receive the current workspace as a client root.
|
||||
- Core Improvements: Session timelines load much faster and avoid flicker or scroll jumps.
|
||||
- Core Improvements: Add `high` and `max` thinking variants for GLM-5.2 across supported providers. (@imranshaiedi-byte)
|
||||
- Core Improvements: Stop wrapping follow-up user messages in a steering reminder so prompt caching stays effective.
|
||||
- TUI Bugfixes: MCP debug now uses the SDK's latest protocol version.
|
||||
- TUI Bugfixes: Only show the background subagent shortcut when the server supports it.
|
||||
- UI Bugfixes: Render completed Mermaid blocks from diagram source instead of fenced Markdown.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Fix the `@` "Past chats" picker in Agent Manager showing only the current session's directory. It now lists previous sessions across the whole worktree family — the local workspace and every Agent Manager worktree — each labeled with its worktree name, matching the Agent Manager session search. Any listed session can be attached as context, including chats from other worktrees of the same repository.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Add Persian (Farsi) as a UI language, including right-to-left layout. Contributed by Babak Safabahar.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Fix bash permission rules being bypassed on PowerShell for commands containing a bare `--` such as `git checkout -- <file>`. Commands the shell parser cannot parse now get checked against their raw command text instead of executing without a permission check.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Emit each agent event once from `kilo run --format json`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Open an embedded terminal automatically when switching to a worktree without one.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Prevent the VS Code backend from eagerly starting native file watchers for every Agent Manager worktree.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep message and response copy buttons working after switching focus away from VS Code.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Show the provider's actual error message when an OpenAI or Azure Responses API stream fails (for example an upstream rate limit) instead of a generic retry notice.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Include the underlying reason in search execution failures instead of showing a bare "ripgrep execution failed" message.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep Windows snapshot diffs parseable and preserve valid files when a stored patch is malformed.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Make Agent Manager panel terminals behave like session tabs: right-click Close and Close Others, arrow-key tab navigation, overflow scrolling with edge fades, and stable tab widths while closing. The new-terminal button now sits directly next to the last terminal tab instead of the far edge of the panel.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": minor
|
||||
---
|
||||
|
||||
Support executing shell commands embedded in skill files. Commands written as `` !`command` `` in a SKILL.md run and their output is inlined into the skill. Only trusted skills can run commands and `KILO_DISABLE_SKILL_SHELL` disables the behavior; when the model loads a skill, the commands are shown in a single up-front approval before running.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Run Agent Manager worktree setup scripts in the terminal selected by the toolbar dropdown. Agent Manager panel shows live setup output in a named `Setup` side tab that reveals itself while provisioning and hides again on success unless you engaged with the panel, while VS Code terminal retains the integrated task flow.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Bound the wait for a provider's first response byte by the request timeout. A provider that accepts a request and returns headers but never sends body data now fails and retries instead of leaving the turn hanging after a tool call completes. The same `timeout` value now covers both the connection phase and the wait for the first byte as a single deadline; streaming responses that have already produced data are unaffected.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Stabilize cross-platform CLI subprocess tests under constrained CI runners
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Open Kilo chats, settings, and files as tabs in the selected editor pane without creating, locking, or resizing editor panes.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Remove unused translation keys from the Agent Manager, sidebar webview, shared kilo-i18n, and autocomplete dictionaries across all locales, and add a conservative lint test for unreferenced, unprotected dictionary keys.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-telemetry": patch
|
||||
---
|
||||
|
||||
Include the host operating system name, version, and architecture in telemetry events.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Show the `Ctrl+T` variant cycling shortcut in the TUI prompt hint row whenever the active model exposes reasoning variants, as the first hint before the agent and command palette hints
|
||||
@@ -7,15 +7,20 @@
|
||||
*
|
||||
* Pre-filter drops (triage never sees these):
|
||||
* - PRs labeled auto-docs (this bot's own rolling PRs)
|
||||
* - chore/test/ci/build/docs/style/refactor/revert conventional titles
|
||||
* - chore/test/ci/build/docs/style/refactor conventional titles
|
||||
* - PRs touching only docs/non-product paths
|
||||
*
|
||||
* Revert PRs (conventional `revert(...):` and GitHub-native `Revert "..."` titles)
|
||||
* are intercepted as signals, never added to the digest; affected digest entries
|
||||
* gain `reverted_by` so triage/edit can skip them.
|
||||
*
|
||||
* Bot-authored PRs are kept: release/dependency bots ship user-facing
|
||||
* changes too, and the label + docs-only guards above prevent loops.
|
||||
*/
|
||||
|
||||
import fs from "node:fs"
|
||||
import { api, appendOutput, appendSummary, listPrFiles, searchIssues } from "./lib.mjs"
|
||||
import { revertTitleKind, parseRevertTargets, applyRevertAnnotations, unannotatedRevertSignals } from "./reverts.mjs"
|
||||
|
||||
const SOURCE_REPOS = ["Kilo-Org/cloud", "Kilo-Org/kilocode"]
|
||||
const OUT_DIR = "docs-sync-out"
|
||||
@@ -23,6 +28,7 @@ const BODY_LIMIT = 2000
|
||||
const SLIM_BODY_LIMIT = 300
|
||||
const PATCH_LIMIT = 8000
|
||||
const FILE_LIMIT = 30
|
||||
// Revert PRs (conventional AND GitHub-native) are intercepted below BEFORE this filter; the "revert" alternative here is unreachable and kept only to minimize diff.
|
||||
const DROP_TITLE = /^(chore|test|ci|build|docs|style|refactor|revert)(\(.+\))?!?:/i
|
||||
const DOCS_ONLY_PATH = /^(packages\/kilo-docs\/|\.github\/docs-sync\/|docs-sync-out\/|docs\/|[^/]+\.md$)/
|
||||
|
||||
@@ -44,7 +50,8 @@ const since = argSince()
|
||||
console.log(`collecting PRs merged since ${since.toISOString()}`)
|
||||
|
||||
const digest = []
|
||||
const dropped = { label: 0, title: 0, docs_only: 0, fetch_error: 0 }
|
||||
const dropped = { label: 0, title: 0, docs_only: 0, fetch_error: 0, revert: 0 }
|
||||
const reverts = []
|
||||
|
||||
for (const fullRepo of SOURCE_REPOS) {
|
||||
const prs = await mergedPrs(fullRepo, since)
|
||||
@@ -56,6 +63,22 @@ for (const fullRepo of SOURCE_REPOS) {
|
||||
dropped.label++
|
||||
continue
|
||||
}
|
||||
if (revertTitleKind(item.title ?? "")) {
|
||||
try {
|
||||
const pr = await api(`/repos/${fullRepo}/pulls/${item.number}`)
|
||||
const targets = parseRevertTargets(pr.body ?? "", fullRepo)
|
||||
reverts.push({ url: pr.html_url, merged_at: pr.merged_at, targets })
|
||||
if (targets.length === 0) {
|
||||
console.warn(`::warning::revert PR ${fullRepo}#${item.number} has no parseable targets`)
|
||||
}
|
||||
dropped.revert++
|
||||
} catch (err) {
|
||||
// One dead revert PR must not abort the run; its targets just go unannotated.
|
||||
console.warn(`::warning::skipping revert ${fullRepo}#${item.number}: ${err.message}`)
|
||||
dropped.fetch_error++
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (DROP_TITLE.test(item.title ?? "")) {
|
||||
dropped.title++
|
||||
continue
|
||||
@@ -108,6 +131,9 @@ for (const fullRepo of SOURCE_REPOS) {
|
||||
}
|
||||
}
|
||||
|
||||
const applied = applyRevertAnnotations(digest, reverts)
|
||||
const unannotated = unannotatedRevertSignals(reverts, applied)
|
||||
|
||||
digest.sort((a, b) => new Date(a.merged_at) - new Date(b.merged_at))
|
||||
|
||||
fs.mkdirSync(OUT_DIR, { recursive: true })
|
||||
@@ -124,14 +150,31 @@ console.log(`kept ${digest.length} PRs, dropped:`, dropped)
|
||||
appendOutput("count", digest.length)
|
||||
appendOutput("digest", `${OUT_DIR}/digest.json`)
|
||||
|
||||
appendSummary(
|
||||
[
|
||||
"### docs-sync collect",
|
||||
"",
|
||||
`- window: since \`${since.toISOString()}\``,
|
||||
`- kept: **${digest.length}** PRs`,
|
||||
`- dropped: ${dropped.label} auto-docs, ${dropped.title} title filter, ${dropped.docs_only} docs-only, ${dropped.fetch_error} fetch errors`,
|
||||
"",
|
||||
...digest.map((d) => `- [${d.repo}#${d.number}](${d.url}) ${d.title}`),
|
||||
].join("\n"),
|
||||
)
|
||||
const summaryLines = [
|
||||
"### docs-sync collect",
|
||||
"",
|
||||
`- window: since \`${since.toISOString()}\``,
|
||||
`- kept: **${digest.length}** PRs`,
|
||||
`- dropped: ${dropped.label} auto-docs, ${dropped.title} title filter, ${dropped.docs_only} docs-only, ${dropped.fetch_error} fetch errors, ${dropped.revert} reverts intercepted`,
|
||||
"",
|
||||
...digest.map((d) => `- [${d.repo}#${d.number}](${d.url}) ${d.title}`),
|
||||
]
|
||||
if (applied.length > 0) {
|
||||
summaryLines.push("", "**revert annotations:**", ...applied.map(([target, reverter]) => `- ${target} — reverted by ${reverter}`))
|
||||
}
|
||||
if (unannotated.missed.length > 0 || unannotated.unparsed.length > 0) {
|
||||
summaryLines.push("", "**revert targets with no in-window annotation:**")
|
||||
for (const m of unannotated.missed) {
|
||||
summaryLines.push(`- ${m.url} — unannotated targets: ${m.targets.join(", ")}`)
|
||||
}
|
||||
for (const url of unannotated.unparsed) {
|
||||
summaryLines.push(`- ${url} (no parseable targets)`)
|
||||
}
|
||||
}
|
||||
if (unannotated.chains.length > 0) {
|
||||
summaryLines.push("", "**revert chains (not annotated):**")
|
||||
for (const c of unannotated.chains) {
|
||||
summaryLines.push(`- ${c.url}${c.targets.length > 0 ? ` (targets: ${c.targets.join(", ")})` : ""}`)
|
||||
}
|
||||
}
|
||||
appendSummary(summaryLines.join("\n"))
|
||||
|
||||
@@ -4,13 +4,16 @@ Before writing anything:
|
||||
|
||||
1. Read `packages/kilo-docs/AGENTS.md` and `packages/kilo-docs/STYLE_GUIDE.md` and follow them exactly: Markdoc custom tags, the `/docs` prefix in image paths, navigation files under `lib/nav/`, redirect rules, and the generated-screenshot policy.
|
||||
2. Read the attached batch files: the full-details file (PR title, body, file list, `patch_excerpt` diffs) and the triage file (docs-worthiness verdicts, target sections, priorities).
|
||||
3. Verify facts against the current source tree — for Kilo-Org/kilocode PRs. This checkout reflects current kilocode main: before documenting a command, flag, setting, default, or behavior from a kilocode PR, confirm it exists in the current source. Existence alone is not enough: defaults, whether an option is required or optional, and on-by-default behavior must also match the current tree — a symbol that still exists as opt-in does not justify documenting default-on behavior. When the merged diff and the current tree disagree, the current tree wins — the change may have been reverted or superseded; skip it and record why. For Kilo-Org/cloud PRs the source is not in this checkout: rely on the PR diff and body, and on the `reverted_by` field below. Reading any file in the checkout for verification is expected; the hard rule against touching anything outside `packages/kilo-docs/` applies to writing only.
|
||||
|
||||
For each PR in the batch, in priority order:
|
||||
For each PR in the batch, in priority order, first decide whether it needs documentation at all — skipping is a first-class outcome. For each one that does:
|
||||
|
||||
- Find the most relevant existing docs page(s) and make minimal, precise updates in the style of the surrounding content.
|
||||
- Create a new page only when no existing page fits; then add it to the matching nav file in `packages/kilo-docs/lib/nav/`.
|
||||
- Document only behavior that is actually present in the merged diff. If the PR body or diff shows the feature is behind a flag or otherwise not user-visible yet, skip it and record why.
|
||||
- Document only behavior that is actually present in the merged diff and still present in the product now (step 3 above: when they disagree, the current state wins). If the PR body or diff shows the feature is behind a flag or otherwise not user-visible yet, skip it and record why.
|
||||
- If a PR turns out not to need documentation, skip it and record why. Trust evidence over the triage verdict.
|
||||
- A batch PR with a `reverted_by` field was reverted by that PR; skip it unless there is clear evidence the change is present now — for kilocode PRs verify it in the current source tree (re-land); for cloud PRs require explicit re-land evidence in the entry itself.
|
||||
- Skipping is a normal outcome: a batch where every PR is skipped is a valid result. Never write docs just to have something to show.
|
||||
|
||||
Hard rules:
|
||||
|
||||
|
||||
@@ -82,8 +82,13 @@ Batch specifics for this run: the PRs to handle are in the attached ${batchFile}
|
||||
break
|
||||
}
|
||||
|
||||
// Headless `kilo run` auto-rejects every permission ask; without --auto the
|
||||
// agent cannot run shell commands. SECURITY: --auto grants unrestricted bash
|
||||
// to an agent steered by external PR content. Hardening deferred: a scoped
|
||||
// permission.bash map via KILO_CONFIG_CONTENT should replace --auto once the
|
||||
// required shell patterns are stable (see PR #12605 review thread).
|
||||
const result = runKilo({
|
||||
args: ["run", prompt, "-m", model, "--variant", "high", "--dir", process.cwd(), "-f", batchFile, "-f", triageFile],
|
||||
args: ["run", "--auto", prompt, "-m", model, "--variant", "high", "--dir", process.cwd(), "-f", batchFile, "-f", triageFile],
|
||||
timeoutMs: Math.min(BATCH_TIMEOUT_MS, left),
|
||||
streamStdout: true,
|
||||
label: `edit batch ${index} attempt ${attempt}`,
|
||||
|
||||
@@ -167,13 +167,53 @@ export function sleepSync(ms) {
|
||||
const STDERR_TAIL_LINES = 20
|
||||
const STDERR_TAIL_CHARS = 4_000
|
||||
|
||||
// CSI sequences (colour, cursor moves, erases). kilo renders its TUI to stderr,
|
||||
// so an unstripped tail lands in the rolling PR's pending table as
|
||||
// "^[[0m→ ^[[0mRead packages/..." and the cause is unreadable. Stripped before
|
||||
// the line/char slice so escapes do not eat the budget. The persisted
|
||||
// docs-sync-out/kilo-stderr-*.log stays raw — that is the debugging record.
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const ANSI_CSI = /\u001b\[[0-9;?]*[ -/]*[@-~]/g
|
||||
|
||||
function tailText(text, { lines = STDERR_TAIL_LINES, chars = STDERR_TAIL_CHARS } = {}) {
|
||||
const s = String(text ?? "").trim()
|
||||
const s = String(text ?? "")
|
||||
.replace(ANSI_CSI, "")
|
||||
.trim()
|
||||
if (!s) return ""
|
||||
const lastLines = s.split("\n").slice(-lines).join("\n")
|
||||
return lastLines.length > chars ? lastLines.slice(-chars) : lastLines
|
||||
}
|
||||
|
||||
/**
|
||||
* Artifact files are raw: GitHub masks secret values in log streams only, and the runner
|
||||
* env holds long-lived secrets (KILO_API_KEY), so exact values of secret-looking env vars
|
||||
* are redacted before stdout/stderr is persisted or printed.
|
||||
* Matching is exact-substring and case-sensitive on values — JSON-escaped, base64'd, or
|
||||
* line-wrapped renderings and values shorter than 8 chars survive (same limitation as
|
||||
* GitHub's own log masking); this is defense-in-depth, not a guarantee the logs are clean.
|
||||
*/
|
||||
export function redactEnvSecrets(text) {
|
||||
let out = String(text ?? "")
|
||||
// Also match CREDENTIAL/PASSWORD/ORG_ID/_PAT (e.g. KILO_ORG_ID, GH_PAT) beyond KEY|TOKEN|SECRET.
|
||||
const nameRe = /KEY|TOKEN|SECRET|CREDENTIAL|PASSWORD|ORG_ID|_PAT$/i
|
||||
const candidates = []
|
||||
for (const [name, value] of Object.entries(process.env)) {
|
||||
if (!nameRe.test(name)) continue
|
||||
if (typeof value !== "string" || value.length < 8) continue
|
||||
candidates.push(value)
|
||||
}
|
||||
// Longer values first so a shorter secret that is a prefix of a longer one cannot leave a remainder.
|
||||
candidates.sort((a, b) => b.length - a.length)
|
||||
for (const value of candidates) {
|
||||
if (!out.includes(value)) continue
|
||||
out = out.split(value).join("***")
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/** Max bytes of child stderr persisted to docs-sync-out/ (full buffer, not the console tail). */
|
||||
const STDERR_LOG_MAX_CHARS = 8 * 1024 * 1024
|
||||
|
||||
/**
|
||||
* Run `kilo` via spawnSync so stderr is always recoverable — including when
|
||||
* the child exits 0 after writing a diagnostic (execFileSync cannot return
|
||||
@@ -181,6 +221,10 @@ function tailText(text, { lines = STDERR_TAIL_LINES, chars = STDERR_TAIL_CHARS }
|
||||
*
|
||||
* streamStdout:true → inherit fd 1 (edit live log); false → capture stdout
|
||||
* (triage parses it). stderr is always buffered.
|
||||
*
|
||||
* Always writes the full captured stderr to
|
||||
* docs-sync-out/kilo-stderr-<sanitized-label>.log (unconditional — success and
|
||||
* failure). The console return value still uses the short tailText.
|
||||
*/
|
||||
export function runKilo({ args, timeoutMs, streamStdout = false, label = "kilo" }) {
|
||||
const result = spawnSync("kilo", args, {
|
||||
@@ -193,16 +237,30 @@ export function runKilo({ args, timeoutMs, streamStdout = false, label = "kilo"
|
||||
const timedOut = Boolean(result.error && result.error.code === "ETIMEDOUT")
|
||||
const exitCode =
|
||||
typeof result.status === "number" ? result.status : timedOut ? null : result.status === null ? null : result.status
|
||||
const stderrTail = tailText(result.stderr)
|
||||
const stdout = streamStdout ? "" : String(result.stdout ?? "")
|
||||
const stderrRaw = String(result.stderr ?? "")
|
||||
const stderrSafe = redactEnvSecrets(stderrRaw)
|
||||
const stderrTail = tailText(stderrSafe)
|
||||
const stdoutSafe = streamStdout ? "" : redactEnvSecrets(String(result.stdout ?? ""))
|
||||
// ok is "process finished without OS-level failure". Callers still treat a
|
||||
// missing summary / unparseable output as failure even when ok is true —
|
||||
// exit 0 is not success for the docs-sync bot.
|
||||
const ok = !result.error && result.status === 0
|
||||
|
||||
// Persist full stderr on every call (not gated on ok/exitCode/summary). Cap is
|
||||
// generous (megabytes) so long batch dumps keep auto-rejecting lines; console
|
||||
// still uses the short tail above.
|
||||
try {
|
||||
fs.mkdirSync("docs-sync-out", { recursive: true })
|
||||
const safe = label.replace(/[^A-Za-z0-9._-]/g, "-")
|
||||
const body = stderrSafe.length > STDERR_LOG_MAX_CHARS ? stderrSafe.slice(-STDERR_LOG_MAX_CHARS) : stderrSafe
|
||||
fs.writeFileSync(`docs-sync-out/kilo-stderr-${safe}.log`, body)
|
||||
} catch (err) {
|
||||
console.warn(`${label}: failed to write kilo-stderr log: ${err.message}`)
|
||||
}
|
||||
|
||||
if (result.error && !timedOut) {
|
||||
console.warn(`${label}: spawn error: ${result.error.message}`)
|
||||
}
|
||||
|
||||
return { ok, stdout, stderrTail, exitCode, timedOut }
|
||||
return { ok, stdout: stdoutSafe, stderrTail, exitCode, timedOut }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// kilocode_change - new file
|
||||
|
||||
/**
|
||||
* Line-wise stdin→stdout filter that redacts secret-looking env values.
|
||||
* Used in the docs-sync workflow so kilo stdout piped to edit-log.txt is safe.
|
||||
* Env values contain no newlines, so line-wise processing never splits a value.
|
||||
*/
|
||||
|
||||
import { redactEnvSecrets } from "./lib.mjs"
|
||||
|
||||
let carry = ""
|
||||
|
||||
process.stdin.setEncoding("utf8")
|
||||
process.stdin.on("data", (chunk) => {
|
||||
carry += chunk
|
||||
let idx
|
||||
while ((idx = carry.indexOf("\n")) !== -1) {
|
||||
const line = carry.slice(0, idx + 1)
|
||||
carry = carry.slice(idx + 1)
|
||||
process.stdout.write(redactEnvSecrets(line))
|
||||
}
|
||||
})
|
||||
process.stdin.on("end", () => {
|
||||
if (carry.length > 0) process.stdout.write(redactEnvSecrets(carry))
|
||||
})
|
||||
@@ -0,0 +1,153 @@
|
||||
// kilocode_change - new file
|
||||
|
||||
/**
|
||||
* Pure helpers for intercepting revert PRs during docs-sync collect.
|
||||
* No I/O, no imports — offline-testable title/body/annotation logic only.
|
||||
*/
|
||||
|
||||
/** Detect revert PR titles. Returns "conventional" | "github-native" | null. */
|
||||
export function revertTitleKind(title) {
|
||||
const t = String(title ?? "")
|
||||
if (/^revert(\(.+\))?!?:/i.test(t)) return "conventional"
|
||||
if (/^revert\s+["']/i.test(t)) return "github-native"
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse revert targets from a PR body. `defaultRepo` ("Kilo-Org/kilocode") resolves bare `#N`.
|
||||
* Returns [{ repo, number, url }] with url = `https://github.com/${repo}/pull/${number}`.
|
||||
* Handles the conventional trailer form: a line starting with `Reverts` (case-insensitive),
|
||||
* e.g. `Reverts #12249 and #12481.`, `Reverts Kilo-Org/cloud#42.`, comma-separated lists,
|
||||
* several such lines in one body, and bulleted/quoted single-line trailers
|
||||
* (`- Reverts #12249`, `> Reverts #12249`).
|
||||
* NOT handled (documented limitation): `This reverts commit <sha>.` (no PR number),
|
||||
* mid-sentence forms (`This reverts #5.`), narrative mentions (`Revert the fix in #99999`
|
||||
* is prose, not a trailer — must NOT produce a target), `Revertsomething #5` (prose glued
|
||||
* to the word — `\b` word boundary makes it inert), and multi-line lists
|
||||
* (`Reverts:\n- #1\n- #2`).
|
||||
*/
|
||||
export function parseRevertTargets(body, defaultRepo) {
|
||||
const text = String(body ?? "")
|
||||
const repoDefault = String(defaultRepo ?? "")
|
||||
const seen = new Set()
|
||||
const out = []
|
||||
const lineRe = /^[ \t>*-]*reverts\b[ \t:]*([^\n]*)/gim
|
||||
let lineMatch
|
||||
while ((lineMatch = lineRe.exec(text)) !== null) {
|
||||
const capture = lineMatch[1] ?? ""
|
||||
const refRe = /(?:([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+))?#(\d+)/g
|
||||
let refMatch
|
||||
while ((refMatch = refRe.exec(capture)) !== null) {
|
||||
const repo = refMatch[1] || repoDefault
|
||||
const number = Number(refMatch[2])
|
||||
if (!repo || !Number.isInteger(number)) continue
|
||||
const url = `https://github.com/${repo}/pull/${number}`
|
||||
if (seen.has(url)) continue
|
||||
seen.add(url)
|
||||
out.push({ repo, number, url })
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* revertSignals: [{ url, merged_at, targets: [{ repo, number, url }] }]
|
||||
* Returns Map<targetUrl, { url, merged_at }> — the annotation for each reverted PR.
|
||||
* Map keys are lowercased target urls; values keep signal.url as authored.
|
||||
* Revert-of-revert: a signal whose own url is itself a target of another signal is
|
||||
* dropped entirely (a re-land: net effect zero — one set lookup, no chain walking).
|
||||
*/
|
||||
export function computeRevertAnnotations(revertSignals) {
|
||||
const signals = Array.isArray(revertSignals) ? revertSignals : []
|
||||
const revertedUrls = new Set()
|
||||
for (const signal of signals) {
|
||||
for (const t of signal?.targets ?? []) {
|
||||
if (t?.url) revertedUrls.add(t.url.toLowerCase())
|
||||
}
|
||||
}
|
||||
const annotations = new Map()
|
||||
for (const signal of signals) {
|
||||
if (!signal?.url || revertedUrls.has(signal.url.toLowerCase())) continue
|
||||
for (const t of signal.targets ?? []) {
|
||||
if (!t?.url) continue
|
||||
annotations.set(t.url.toLowerCase(), { url: signal.url, merged_at: signal.merged_at })
|
||||
}
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies computeRevertAnnotations to digest entries in place: an entry whose url
|
||||
* was reverted gains `reverted_by: { url, merged_at }` (the reverter).
|
||||
* Lookup is case-insensitive; applied pairs still report entry.url (canonical).
|
||||
* Returns applied [targetUrl, reverterUrl] pairs (digest entries only) for reporting.
|
||||
*/
|
||||
export function applyRevertAnnotations(digest, revertSignals) {
|
||||
const annotations = computeRevertAnnotations(revertSignals)
|
||||
const applied = []
|
||||
for (const entry of digest ?? []) {
|
||||
if (!entry?.url) continue
|
||||
const ann = annotations.get(entry.url.toLowerCase())
|
||||
if (!ann) continue
|
||||
entry.reverted_by = { url: ann.url, merged_at: ann.merged_at }
|
||||
applied.push([entry.url, ann.url])
|
||||
}
|
||||
return applied
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports intercepted reverts whose targets received no digest annotation, for the
|
||||
* step summary. `signals` is the same shape as computeRevertAnnotations takes;
|
||||
* `appliedPairs` is applyRevertAnnotations' return ([targetUrl, reverterUrl]).
|
||||
* Returns { missed, unparsed, chains }:
|
||||
* - missed: [{ url, targets: [targetUrl, ...] }] — one entry per signal with at least
|
||||
* one unannotated plain target, listing exactly the targets that missed (a partially-covered
|
||||
* revert still surfaces its uncovered targets). Plain = not itself an intercepted revert.
|
||||
* - unparsed: [url, ...] — urls of signals with zero targets (they already warn at
|
||||
* intercept time).
|
||||
* - chains: [{ url, targets: [targetUrl, ...] }] — signals involved in a revert chain,
|
||||
* reported for human visibility only; the pipeline never resolves a chain's net effect.
|
||||
* A signal lands here when EITHER its own url is a target of another signal (cancelled
|
||||
* by computeRevertAnnotations — lists ALL its targets), OR at least one of its targets
|
||||
* is itself an intercepted revert (re-land chain link — lists ONLY those chain-link
|
||||
* targets; plain targets still flow through missed).
|
||||
* Cancelled signals skip missed/unparsed. Chain-linked (non-cancelled) signals may also
|
||||
* appear in missed for their unannotated plain targets. Chain-link targets never appear
|
||||
* in missed. All url comparisons are case-insensitive.
|
||||
*/
|
||||
export function unannotatedRevertSignals(signals, appliedPairs) {
|
||||
const list = Array.isArray(signals) ? signals : []
|
||||
const annotated = new Set((appliedPairs ?? []).map(([target]) => String(target ?? "").toLowerCase()))
|
||||
const signalUrls = new Set(list.map((s) => String(s?.url ?? "").toLowerCase()))
|
||||
const cancelledUrls = new Set()
|
||||
for (const s of list) {
|
||||
for (const t of s?.targets ?? []) {
|
||||
if (t?.url) cancelledUrls.add(t.url.toLowerCase())
|
||||
}
|
||||
}
|
||||
const missed = []
|
||||
const unparsed = []
|
||||
const chains = []
|
||||
for (const s of list) {
|
||||
const url = s?.url
|
||||
if (!url) continue
|
||||
const targets = (s.targets ?? []).map((t) => t?.url).filter(Boolean)
|
||||
const cancelled = cancelledUrls.has(url.toLowerCase())
|
||||
const chainTargets = targets.filter((u) => signalUrls.has(u.toLowerCase()))
|
||||
if (cancelled) {
|
||||
chains.push({ url, targets })
|
||||
continue
|
||||
}
|
||||
if (chainTargets.length > 0) {
|
||||
chains.push({ url, targets: chainTargets })
|
||||
}
|
||||
if (targets.length === 0) {
|
||||
unparsed.push(url)
|
||||
continue
|
||||
}
|
||||
const plainTargets = targets.filter((u) => !signalUrls.has(u.toLowerCase()))
|
||||
const missing = plainTargets.filter((u) => !annotated.has(u.toLowerCase()))
|
||||
if (missing.length > 0) missed.push({ url, targets: missing })
|
||||
}
|
||||
return { missed, unparsed, chains }
|
||||
}
|
||||
+923
-17
File diff suppressed because it is too large
Load Diff
@@ -4,11 +4,12 @@ The attached `digest.json` file contains PRs recently merged to Kilo-Org/cloud a
|
||||
|
||||
A PR is docs-worthy ONLY if a user of Kilo Code would need to learn something new or change how they use the product after this PR ships. Examples: new commands, flags, settings, UI workflows, providers, pricing/limits changes, breaking behavior changes, or fixes that change documented behavior.
|
||||
|
||||
A PR is NOT docs-worthy when it is: an internal refactor, infrastructure or CI work, a feature-flag scaffold that is not yet user-visible, test or dependency work, a bug fix that merely restores already-documented behavior, or a change only visible to contributors or self-hosters.
|
||||
A PR is NOT docs-worthy when it is: an internal refactor, infrastructure or CI work, a feature-flag scaffold that is not yet user-visible, test or dependency work, a bug fix that merely restores already-documented behavior, a change only visible to contributors or self-hosters, an internal technical fix or plumbing change with no user-visible behavior or workflow change (for example stream-liveness/watchdog plumbing, internal retry or timeout handling with no user-facing setting), or UI or rendering polish that does not change what a user must do or learn.
|
||||
|
||||
Rules:
|
||||
|
||||
- Include every input PR exactly once, identified by its `number` and `url`. Never invent PRs.
|
||||
- A digest entry with a `reverted_by` field had its changes reverted by that later PR in the same window. Treat such PRs as not docs-worthy unless the entry's body or file list shows clear evidence the change was re-landed after the revert. When `reverted_by` drives your verdict, the `reason` must cite it, e.g. "reverted by https://github.com/Kilo-Org/kilocode/pull/12497".
|
||||
- When unsure, set `docs_worthy` to false and explain the doubt in `reason`.
|
||||
- `target_sections` is only filled for docs-worthy PRs. Use rough docs areas, e.g. `getting-started`, `code-with-ai/platforms/cli`, `code-with-ai/platforms/vscode`, `code-with-ai/agents`, `ai-providers`, `teams`, `enterprise`, `automate`.
|
||||
- `reason` is one short sentence, written for the human who reviews the final docs PR.
|
||||
|
||||
@@ -71,8 +71,13 @@ function triageChunk(chunk, index, budgetDeadline) {
|
||||
break
|
||||
}
|
||||
|
||||
// Headless `kilo run` auto-rejects every permission ask; without --auto the
|
||||
// agent cannot run shell commands. SECURITY: --auto grants unrestricted bash
|
||||
// to an agent steered by external PR content. Hardening deferred: a scoped
|
||||
// permission.bash map via KILO_CONFIG_CONTENT should replace --auto once the
|
||||
// required shell patterns are stable (see PR #12605 review thread).
|
||||
const result = runKilo({
|
||||
args: ["run", prompt, "-m", model, "--dir", process.cwd(), "-f", chunkFile],
|
||||
args: ["run", "--auto", prompt, "-m", model, "--dir", process.cwd(), "-f", chunkFile],
|
||||
timeoutMs: Math.min(CHUNK_TIMEOUT_MS, left),
|
||||
streamStdout: false,
|
||||
label: `triage chunk ${index} attempt ${attempt}`,
|
||||
|
||||
@@ -66,8 +66,13 @@ jobs:
|
||||
sync:
|
||||
if: github.repository == 'Kilo-Org/kilocode' && github.event_name != 'pull_request'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
# Budget: 3 setup/collect + 35 triage + 50 edit + 2 verify + 10 fix + 2 upsert = 102 min, 18-minute reserve.
|
||||
timeout-minutes: 120
|
||||
# Budget: 4 setup/collect + 90 triage + 120 edit + 2 verify + 10 fix + 2 upsert = 228 min, 12-minute reserve.
|
||||
# These are ceilings, not costs: a caught-up run triages ~2 chunks and edits
|
||||
# ~1 batch and finishes in ~25 min. The old 35/50 pair was the binding
|
||||
# constraint on backlog drain — run 30306629290 deferred 54 PRs untriaged and
|
||||
# 31 unedited purely on budget, with no attempt made. See the throughput note
|
||||
# in the PR description for the arithmetic.
|
||||
timeout-minutes: 240
|
||||
env:
|
||||
# Both are required: without KILO_ORG_ID the gateway bills the key
|
||||
# owner's personal balance (402 "Add credits") instead of the org.
|
||||
@@ -116,6 +121,9 @@ jobs:
|
||||
if: steps.collect.outputs.count != '0'
|
||||
env:
|
||||
SINCE_OVERRIDE: ${{ steps.wm.outputs.since_override }}
|
||||
# Default 35 fit only 8 of 11 chunks on a 254-PR window. Headroom for
|
||||
# --auto making chunks slower now that the agent really runs commands.
|
||||
TRIAGE_BUDGET_MINUTES: "90"
|
||||
run: node .github/docs-sync/triage.mjs
|
||||
|
||||
- name: Filter docs-worthy PRs
|
||||
@@ -153,6 +161,11 @@ jobs:
|
||||
- name: Update docs (Kilo CLI, batched)
|
||||
if: (steps.worthy.outputs.count || '0') != '0' && inputs.dry_run != true
|
||||
continue-on-error: true
|
||||
env:
|
||||
# Default 50 fit only 4 of 11 batches. A healthy --auto batch is ~8 min,
|
||||
# and edit.mjs will not start a batch without EDIT_BATCH_TIMEOUT_MINUTES
|
||||
# (15) left, so 120 covers 14 batches = 70 PRs against ~5 worthy/day.
|
||||
EDIT_BUDGET_MINUTES: "120"
|
||||
run: node .github/docs-sync/edit.mjs
|
||||
|
||||
- name: Verify docs build and tests
|
||||
@@ -174,9 +187,19 @@ jobs:
|
||||
NEXT_PUBLIC_POSTHOG_KEY: ${{ secrets.POSTHOG_API_KEY }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
kilo run "The docs build or tests failed. Read the attached docs-sync-out/verify.log and fix the packages/kilo-docs changes so they pass. Do not revert doc edits; fix them. Do not modify anything outside packages/kilo-docs." \
|
||||
# Headless kilo run auto-rejects every permission ask; the runner has no
|
||||
# user config granting bash, so without --auto the agent cannot run ordinary
|
||||
# shell commands against the repository.
|
||||
kilo run --auto "The docs build or tests failed. Read the attached docs-sync-out/verify.log and fix the packages/kilo-docs changes so they pass. Do not revert doc edits; fix them. Do not modify anything outside packages/kilo-docs." \
|
||||
-m "$EDIT_MODEL" --dir "$GITHUB_WORKSPACE" -f docs-sync-out/verify.log \
|
||||
| tee -a docs-sync-out/edit-log.txt
|
||||
| node .github/docs-sync/redact-stream.mjs \
|
||||
| tee -a docs-sync-out/edit-log.txt \
|
||||
|| echo "::warning::kilo fix pass exited nonzero; re-verifying anyway"
|
||||
# The rebuild below decides this step's outcome, not the agent's exit code.
|
||||
# Without the guard above, `set -o pipefail` + the default `bash -e` would
|
||||
# abort here once the CLI half of this PR ships: a mid-stream session error
|
||||
# (or an auto-rejected ask) exits 1, verify2.log is never written, and
|
||||
# `Re-verify status` reports VERIFIED=false even when the docs build fine.
|
||||
{ bun run --filter @kilocode/kilo-docs build && bun run --filter @kilocode/kilo-docs test; } 2>&1 | tee docs-sync-out/verify2.log
|
||||
|
||||
- name: Re-verify status
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
# Agent Manager multi-project configuration architecture
|
||||
|
||||
**Status:** Blocking architecture for multi-project release
|
||||
|
||||
**Date:** 2026-07-22
|
||||
|
||||
This document is the canonical configuration specification for Agent Manager multi-project support. The main UI/runtime plan references it and must not duplicate or contradict it.
|
||||
|
||||
The executable sequence from the current branch is [`agent-manager-multi-project-implementation-handoff.md`](./agent-manager-multi-project-implementation-handoff.md).
|
||||
|
||||
## Decision
|
||||
|
||||
Keep the useful current split between user and project settings, but make every Settings read and write target explicit, immutable, revisioned, and independent from Agent Manager activation.
|
||||
|
||||
Multi-project must not ship broadly while Settings can load a draft for project A and resolve its save target from the mutable active project B.
|
||||
|
||||
## Current behavior
|
||||
|
||||
Kilo has four configuration stores:
|
||||
|
||||
| Store | Example | Owner |
|
||||
|---|---|---|
|
||||
| VS Code preferences | VS Code `settings.json` | This VS Code user/installation |
|
||||
| Kilo user config | `~/.config/kilo/kilo.json` | User defaults across projects and Kilo clients |
|
||||
| Kilo project config | `<repo>/.kilo/kilo.jsonc` | Repository behavior and overrides |
|
||||
| Runtime/session state | In-memory, directory-qualified | One project, worktree, or session |
|
||||
|
||||
The shared Settings save currently calls `splitConfigByScope()`:
|
||||
|
||||
- `commit_message` is written to project config;
|
||||
- `indexing.enabled` is written to project config;
|
||||
- all other generic Settings fields are written to user config.
|
||||
|
||||
The Indexing tab also has an explicit Global/Project selector and can write the entire `indexing` object to either layer. This is too broad because project config can receive provider/model/vector-store credentials and infrastructure settings.
|
||||
|
||||
Several controls are VS Code preferences and bypass Kilo config entirely, including autocomplete UI, browser automation, notifications, max auto-approve cost, commit-message output language, and indexing button visibility.
|
||||
|
||||
## Confirmed blocking failure
|
||||
|
||||
The current protocol does not bind a draft to the config target it was loaded from:
|
||||
|
||||
1. `KiloProvider.fetchAndSendConfig()` resolves a mutable current directory and sends unqualified `configLoaded` state.
|
||||
2. The webview owns one global/project/effective draft.
|
||||
3. Agent Manager changes the active project from A to B.
|
||||
4. The webview sends an unqualified `updateConfig`.
|
||||
5. `KiloProvider.handleUpdateConfig()` resolves the current directory again at save time and may write A's draft to B.
|
||||
|
||||
Reads are directory-scoped, but writes are not bound to the read target. This is the release blocker.
|
||||
|
||||
The backend also lacks an expected target/revision precondition, so external editors or another window can overwrite a config between read and save.
|
||||
|
||||
## Ownership policy
|
||||
|
||||
### VS Code preferences
|
||||
|
||||
These remain in VS Code settings and do not participate in project config:
|
||||
|
||||
- extension language and presentation preferences;
|
||||
- autocomplete enablement, keybindings, provider, and model;
|
||||
- browser automation enablement/system Chrome/headless mode;
|
||||
- notification enablement and sound;
|
||||
- maximum automatic approval cost;
|
||||
- commit-message output language;
|
||||
- indexing button visibility while indexing is disabled;
|
||||
- multi-project feature enablement.
|
||||
|
||||
### Kilo user config
|
||||
|
||||
These are personal defaults or security policy and should be edited in User scope:
|
||||
|
||||
- default, small, and subagent models and variants;
|
||||
- provider enablement, custom providers, credentials, and endpoints;
|
||||
- user/global agents and default agent;
|
||||
- permission defaults and user tool defaults;
|
||||
- sandbox policy, network access, writable paths, and allowed hosts;
|
||||
- compaction, checkpoint/snapshot, and tool-output defaults;
|
||||
- username and display behavior;
|
||||
- sharing, remote control, telemetry, and experimental features;
|
||||
- user/global formatter, LSP, MCP, skills, instructions, commands, and workflows;
|
||||
- indexing provider, model, credentials, vector storage, and global tuning defaults.
|
||||
|
||||
A trusted project may override many of these at runtime, but editing User scope never writes those overrides.
|
||||
|
||||
### Kilo project config
|
||||
|
||||
These describe repository behavior and are valid project settings:
|
||||
|
||||
- commit-message prompt;
|
||||
- repository indexing file extensions, include/ignore rules, and deliberate project tuning overrides;
|
||||
- repository instructions;
|
||||
- repository skill paths;
|
||||
- repository commands/workflows;
|
||||
- trusted project agents;
|
||||
- trusted project MCP servers;
|
||||
- repository formatter/LSP overrides;
|
||||
- repository watcher ignores;
|
||||
- repository-specific tool restrictions and permission requests.
|
||||
|
||||
Project configuration may override user model/agent/tool defaults, but provider credentials and security-policy weakening must not be silently authored into a repository.
|
||||
|
||||
### Machine-local project consent
|
||||
|
||||
Indexing enablement is privacy consent, not repository configuration. Store it outside the repository, keyed by canonical `ProjectId` in machine-local extension state:
|
||||
|
||||
- newly observed projects default to indexing disabled;
|
||||
- users explicitly enable indexing for one project on this machine;
|
||||
- repository config cannot enable indexing;
|
||||
- repository config may describe what to index, but consent gates whether indexing starts;
|
||||
- canonical project identity prevents a symlink or alternate path from bypassing consent.
|
||||
|
||||
Effective indexing requires both valid user-global indexing configuration and machine-local consent for that project.
|
||||
|
||||
### Current tabs
|
||||
|
||||
| Settings tab | Correct editable target |
|
||||
|---|---|
|
||||
| Models | User by default; explicit Project scope may override models/agents |
|
||||
| Providers | User only for credentials/endpoints; project-provided entries are source-labelled |
|
||||
| Agent Behaviour | User or explicit trusted Project scope |
|
||||
| Auto Approve | User Kilo config; max cost remains VS Code preference |
|
||||
| Browser | VS Code preferences |
|
||||
| Checkpoints | User default or explicit Project override |
|
||||
| Display | User config |
|
||||
| Autocomplete | VS Code preferences |
|
||||
| Notifications | VS Code preferences |
|
||||
| Context | User defaults; repository watcher/instruction rules in explicit Project scope |
|
||||
| Commit Message | Project scope for prompt; language remains VS Code preference |
|
||||
| Indexing | User for provider/model/credentials/storage; machine-local project consent for enablement; Project for repository rules |
|
||||
| Experimental | User config; multi-project also mirrors to VS Code preference |
|
||||
| Sandboxing | User config only |
|
||||
| Language | VS Code preference |
|
||||
| MCP/Commands/Skills | User defaults or explicit trusted Project scope |
|
||||
|
||||
No field silently chooses a file during save. The UI must display its scope.
|
||||
|
||||
## Settings UX
|
||||
|
||||
Use explicit scope and project controls:
|
||||
|
||||
```text
|
||||
Scope: User | Project
|
||||
|
||||
Project: backend
|
||||
Target: /projects/backend/.kilo/kilo.jsonc
|
||||
```
|
||||
|
||||
- User scope always targets user config.
|
||||
- Project scope requires an explicit trusted project selector.
|
||||
- The Settings selector is separate from Agent Manager's active project.
|
||||
- Opening Settings may initialize the project selector once from the current project, but later Agent Manager switches never change it.
|
||||
- A dirty project draft cannot move to another project. Selector changes require Save, Discard, or Stay.
|
||||
- Inherited values show source badges such as User, Project: backend, and Managed.
|
||||
- Project scope offers Override and Reset to inherited.
|
||||
- Project-sourced providers cannot be silently deleted from project config through User scope.
|
||||
|
||||
Runtime config still follows the exact session directory. Settings Project scope targets the registered project root, not the active worktree. Worktree-config editing is a separate future feature requiring an explicit `WorktreeRef`.
|
||||
|
||||
## Immutable binding contract
|
||||
|
||||
A Settings read returns an opaque binding:
|
||||
|
||||
```ts
|
||||
interface SettingsBinding {
|
||||
id: string
|
||||
connectionGeneration: number
|
||||
scope: "global" | "project"
|
||||
project?: {
|
||||
projectId: string
|
||||
root: string
|
||||
generation: number
|
||||
}
|
||||
directory: string
|
||||
target: {
|
||||
scope: "global" | "project"
|
||||
path: string
|
||||
revision: string
|
||||
exists: boolean
|
||||
writable: boolean
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The write contains only the opaque binding and patch:
|
||||
|
||||
```ts
|
||||
interface WriteSettingsConfig {
|
||||
type: "settingsConfig.write"
|
||||
requestId: string
|
||||
bindingId: string
|
||||
set: Record<string, unknown>
|
||||
unset: string[][]
|
||||
}
|
||||
```
|
||||
|
||||
The extension stores the authoritative binding. On write it must:
|
||||
|
||||
1. reject unknown/expired bindings;
|
||||
2. verify project existence, generation, and trust;
|
||||
3. capture the binding before the first await;
|
||||
4. use the binding's stored directory and scope;
|
||||
5. never call `getWorkspaceDirectory()`, `contexts.active()`, or use a worktree/session fallback;
|
||||
6. clear a draft only from the matching `{ requestId, bindingId }` response.
|
||||
|
||||
Bindings expire after save, reconnect, trust revocation, project removal, or context generation change.
|
||||
|
||||
## Backend revision contract
|
||||
|
||||
`GET /config/overlay` must return the exact global/project target path, parsed raw target config, effective config/source metadata, and a revision.
|
||||
|
||||
The revision is a SHA-256 fingerprint of canonical target path, existence marker, and exact file bytes. This catches content changes, JSONC comment-only edits, and target changes.
|
||||
|
||||
`PATCH /config/overlay` accepts one scope and requires:
|
||||
|
||||
```ts
|
||||
{
|
||||
scope: "global" | "project"
|
||||
set: Record<string, unknown>
|
||||
unset: string[][]
|
||||
expected: {
|
||||
path: string
|
||||
revision: string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The backend must re-resolve the authoritative target, verify path/revision under a target lock, patch the raw target layer, validate it, atomically replace the file, and return a fresh snapshot. It never accepts an arbitrary client path.
|
||||
|
||||
Expected failures include expired binding, unknown/untrusted project, changed target, revision conflict, invalid config, non-writable target, and I/O failure. Every failure preserves the draft.
|
||||
|
||||
## Required implementation
|
||||
|
||||
1. Add revisioned target descriptors and compare-and-swap writes to the Kilo config overlay API.
|
||||
2. Split activation-bound runtime config state from binding-keyed Settings editor state.
|
||||
3. Replace unqualified `configLoaded`/`updateConfig` with settings read/write messages carrying request and binding IDs.
|
||||
4. Replace hidden `splitConfigByScope` saves with explicit scope on every editable control.
|
||||
5. Restrict Indexing Project scope to repository rules; keep provider/model/credentials/storage in User scope.
|
||||
6. Move indexing enablement from project config to machine-local consent keyed by canonical `ProjectId`, default off.
|
||||
7. Audit direct config mutators outside the save bar, especially provider disconnect, imports/resets, custom providers, work styles, permission rules, and indexing actions.
|
||||
8. Make Open Project Config take a `ProjectRef`, resolve the immutable registered root, and verify trust.
|
||||
9. Partition config caches/events by scope, directory, target, revision, and activation generation.
|
||||
|
||||
## Blocking tests
|
||||
|
||||
- Load Settings for A, switch Agent Manager to B, save: only A's bound target changes.
|
||||
- Same test while selecting A/B worktrees and sessions.
|
||||
- User-scope save always changes only user config.
|
||||
- Project-scope save requires the explicit trusted project and changes only its registered root config.
|
||||
- Dirty drafts survive Agent Manager switches and cannot migrate between Settings projects.
|
||||
- Out-of-order reads and writes update only the matching request/binding.
|
||||
- External file change causes a revision conflict without losing the draft.
|
||||
- A changed config target path causes a target conflict.
|
||||
- Project removal, generation change, or trust revocation expires its binding.
|
||||
- Indexing provider/model/credentials/storage never enter project config through the form.
|
||||
- A repository file containing `indexing.enabled: true` cannot grant indexing consent.
|
||||
- New projects default to indexing disabled until explicitly enabled on this machine.
|
||||
- Consent follows canonical project identity across symlink/path aliases and never leaks to another project.
|
||||
- `commit_message.prompt` and repository indexing rules still support explicit project writes.
|
||||
- Runtime worktree config uses the worktree directory while Project Settings remains bound to the registered project root.
|
||||
|
||||
## Release gate
|
||||
|
||||
Keep multi-project disabled by default until the immutable binding/revision contract and the blocking tests above are implemented. The useful existing project-local behavior should be preserved, not removed; its write target must become explicit and immutable.
|
||||
@@ -0,0 +1,459 @@
|
||||
# Agent Manager multi-project implementation handoff
|
||||
|
||||
**Status:** Implementation-ready handoff from the current branch
|
||||
|
||||
**Date:** 2026-07-22
|
||||
|
||||
This document tells the next implementation model exactly how to continue from branch `abalone-bactrosaurus` at commit `8f48da2278` plus the uncommitted plan files. It is the execution checklist. Detailed decisions live in:
|
||||
|
||||
- [`agent-manager-multi-project-configuration.md`](./agent-manager-multi-project-configuration.md) for configuration ownership, bindings, revisions, indexing consent, and settings tests;
|
||||
- [`agent-manager-multi-project-uniform-ui.md`](./agent-manager-multi-project-uniform-ui.md) for runtime, routing, lifecycle, uniform sidebar UI, and acceptance criteria.
|
||||
|
||||
If this handoff conflicts with either architecture document, stop and resolve the contradiction before coding.
|
||||
|
||||
## Goal
|
||||
|
||||
Finish the current prototype into a safe, complete multi-project Agent Manager behind a default-off VS Code experimental flag.
|
||||
|
||||
The feature is done only when:
|
||||
|
||||
- multiple expanded projects show the same full Agent Manager sidebar behavior;
|
||||
- every action uses explicit project/worktree/session ownership;
|
||||
- Settings cannot write the wrong project's config;
|
||||
- indexing requires machine-local consent per canonical project;
|
||||
- project identity is canonical, Git-root-aware, common-dir-safe, and remote-authority-aware;
|
||||
- single-project behavior remains unchanged when the flag is off.
|
||||
|
||||
## Current branch baseline
|
||||
|
||||
### Implemented and worth keeping
|
||||
|
||||
- persistent additional-project registry with serialized/re-read mutations;
|
||||
- separate `ProjectContext` objects and per-project Agent Manager state files;
|
||||
- project-stamped state, stats, PR, and session payloads;
|
||||
- canonical worktree presence comparison for symlink/`/tmp` aliases;
|
||||
- per-project session listing from project root and worktree directories;
|
||||
- partial production `ProjectRouteService` integration and raw-session ambiguity rejection;
|
||||
- project-qualified sidebar DOM IDs and cross-project previous/next navigation;
|
||||
- atomic selection validation and same-project activation fast path;
|
||||
- flag-off return to pinned project;
|
||||
- Experimental Settings toggle and translations;
|
||||
- focused unit tests for registry, contexts, paths, pollers, routing, sessions, selection, and navigation.
|
||||
|
||||
### Not implemented or incomplete
|
||||
|
||||
- immutable/revisioned Settings bindings;
|
||||
- machine-local indexing consent;
|
||||
- canonical pinned Git toplevel, `commonGitDir`, URI scheme/authority identity;
|
||||
- strict project envelopes for all Agent Manager operations;
|
||||
- complete route-service use for every session/terminal/diff/share/permission operation;
|
||||
- one full sidebar body shared by single- and multi-project modes;
|
||||
- uniform per-context poller and stale-state ownership;
|
||||
- all repository mutations through `ProjectContext.run()`;
|
||||
- complete real two-project E2E verification.
|
||||
|
||||
### Known diff cleanup
|
||||
|
||||
- remove unrelated `packages/opencode/package.json` ordering/dependency drift;
|
||||
- restore unrelated `bun.lock` ordering/version drift unless required by a retained change;
|
||||
- remove `experimental.multi_project` from the CLI config schema and generated SDK once the VS Code flag is the sole source of truth;
|
||||
- rewrite the changeset after final behavior is complete.
|
||||
|
||||
Do not reset or discard unrelated user changes. Inspect every cleanup diff before applying it.
|
||||
|
||||
## Non-negotiable invariants
|
||||
|
||||
1. Agent Manager activation is not a Settings write target.
|
||||
2. No identified operation falls back to the active project or workspace root in multi-project mode.
|
||||
3. A project config write targets the explicitly selected registered project root, never an active worktree.
|
||||
4. Runtime config remains directory-correct: project Local uses root, worktree sessions use exact worktree directory.
|
||||
5. Repository config cannot grant indexing consent.
|
||||
6. New projects default to indexing disabled on this machine.
|
||||
7. Provider/API credentials and indexing infrastructure never enter project config through Settings.
|
||||
8. Raw session/worktree IDs are never sufficient UI/runtime identity when multiple projects are exposed.
|
||||
9. The full existing sidebar behavior is reused; do not maintain a simplified multi-project copy.
|
||||
10. Do not raise `AgentManagerProvider.ts` or `AgentManagerApp.tsx` line caps.
|
||||
|
||||
## Work order
|
||||
|
||||
Complete slices in order. Do not start the next slice while the current slice's tests or checks fail.
|
||||
|
||||
## Slice 0A: consolidate the feature flag
|
||||
|
||||
### Required result
|
||||
|
||||
Use one source of truth: VS Code application setting `kilo-code.new.experimental.multiProject`.
|
||||
|
||||
### Required changes
|
||||
|
||||
1. Remove `experimental.multi_project` from:
|
||||
- `packages/core/src/v1/config/config.ts`;
|
||||
- `packages/sdk/js/src/v2/gen/types.gen.ts` by regenerating the SDK after schema removal;
|
||||
- `packages/kilo-vscode/webview-ui/src/types/messages/config.ts`.
|
||||
2. Do not hand-edit generated SDK files. Run `./script/generate.ts` from repository root after endpoint/schema changes.
|
||||
3. Change `ExperimentalTab.tsx` so the toggle reads the VS Code setting delivered in `configLoaded.settings`, not `config().experimental`.
|
||||
4. Add `multiProject` to the extension settings payload sent by `KiloProvider.fetchAndSendConfig()`.
|
||||
5. Toggling it sends only `updateSetting("experimental.multiProject", checked)`.
|
||||
6. Keep `VscodeHost.multiProject()` and its change listener reading the same VS Code setting.
|
||||
|
||||
### Tests
|
||||
|
||||
- toggle initial state reflects VS Code setting when CLI config disagrees;
|
||||
- toggling updates the VS Code setting and Agent Manager reacts;
|
||||
- no CLI/project config file gains `experimental.multi_project`.
|
||||
|
||||
### Stop gate
|
||||
|
||||
Run from `packages/kilo-vscode/`:
|
||||
|
||||
```sh
|
||||
bun run format
|
||||
bun run format:check
|
||||
bun run typecheck
|
||||
bun run lint
|
||||
bun run test:unit
|
||||
```
|
||||
|
||||
Do not continue if any command fails.
|
||||
|
||||
## Slice 0B: machine-local indexing consent
|
||||
|
||||
### Required result
|
||||
|
||||
Indexing enablement is explicit machine-local consent keyed by canonical `ProjectId`, default false. Project config controls indexing rules but cannot enable indexing.
|
||||
|
||||
### Required changes
|
||||
|
||||
1. Add a versioned machine-local consent store in the extension, not repository config and not synced across machines.
|
||||
2. Use canonical project identity as the key. The pinned project and registered projects use the same identity resolver.
|
||||
3. Remove hidden routing of `indexing.enabled` to project config from `webview-ui/src/utils/config-scope.ts`.
|
||||
4. Remove project/global inheritance logic for `indexing.enabled` from `indexing-tab-state.ts`.
|
||||
5. The Indexing UI requires an explicit project selector for enablement and reads/writes consent through dedicated messages.
|
||||
6. Keep these in User config only:
|
||||
- provider;
|
||||
- model and dimension;
|
||||
- credentials/API keys/base URLs;
|
||||
- vector-store type and connection/storage settings;
|
||||
- machine/infrastructure tuning defaults.
|
||||
7. Keep these available in explicit Project scope:
|
||||
- file extensions;
|
||||
- repository include/ignore rules;
|
||||
- deliberate repository chunking/tuning overrides.
|
||||
8. Indexing startup/status must require both valid User indexing configuration and consent for the routed project.
|
||||
9. A repository containing `indexing.enabled: true` must not enable indexing.
|
||||
|
||||
### Suggested files
|
||||
|
||||
- new VS Code-free consent store under `src/indexing/` or `src/agent-manager/`;
|
||||
- `src/KiloProvider.ts` only as a thin protocol adapter;
|
||||
- `webview-ui/src/components/settings/IndexingTab.tsx`;
|
||||
- `webview-ui/src/components/settings/indexing-tab-state.ts`;
|
||||
- `webview-ui/src/context/config.tsx` only if necessary;
|
||||
- focused tests beside existing indexing tests.
|
||||
|
||||
### Tests
|
||||
|
||||
- unknown project defaults off;
|
||||
- A enabled does not enable B;
|
||||
- symlink/path alias resolves to the same consent;
|
||||
- repository config cannot grant consent;
|
||||
- removing/re-adding behavior follows the documented store policy;
|
||||
- no credentials/storage settings are emitted in a Project-scope patch.
|
||||
|
||||
## Slice 0C: revisioned config overlay backend
|
||||
|
||||
### Required result
|
||||
|
||||
Config reads return authoritative target descriptors and revisions. Writes use compare-and-swap and cannot overwrite external changes or a changed target.
|
||||
|
||||
### Required changes
|
||||
|
||||
1. Extend Kilo-owned config overlay code:
|
||||
- `packages/opencode/src/kilocode/config/overlay.ts`;
|
||||
- a new Kilo-owned writer under `packages/opencode/src/kilocode/config/`;
|
||||
- `packages/opencode/src/kilocode/server/httpapi/groups/config-console.ts`;
|
||||
- `packages/opencode/src/kilocode/server/httpapi/handlers/config-console.ts`.
|
||||
2. Read response includes for global and project targets:
|
||||
- canonical path;
|
||||
- scope;
|
||||
- exists/writable;
|
||||
- SHA-256 revision of canonical path, existence marker, and exact bytes;
|
||||
- raw parsed target layer;
|
||||
- effective config and source metadata.
|
||||
3. Write request accepts exactly one scope, `set`, `unset`, and expected path/revision.
|
||||
4. Server re-resolves the authoritative target. Never trust client path as a destination.
|
||||
5. Lock by canonical target path, re-read inside lock, compare revision, patch raw JSONC, validate, and atomically replace.
|
||||
6. Return a fresh authoritative snapshot.
|
||||
7. Return typed 409 conflicts for target/revision changes; preserve drafts client-side.
|
||||
8. Keep Kilo logic in `packages/opencode/src/kilocode/`. Shared upstream files get minimal marked delegation only when unavoidable.
|
||||
9. Regenerate the SDK after endpoint changes.
|
||||
|
||||
### Tests
|
||||
|
||||
Add/extend:
|
||||
|
||||
- `packages/opencode/test/kilocode/server/config-overlay.test.ts`;
|
||||
- `packages/opencode/test/kilocode/project-config-update.test.ts`.
|
||||
|
||||
Cover:
|
||||
|
||||
- exact raw target layer;
|
||||
- comment-only external edit conflict;
|
||||
- missing-file target revision;
|
||||
- newly created higher-priority target conflict;
|
||||
- concurrent writers: one success, one 409;
|
||||
- symlink escape rejection;
|
||||
- global and project writes remain separate;
|
||||
- atomic write failure never exposes partial content.
|
||||
|
||||
### Stop gate
|
||||
|
||||
From `packages/opencode/`:
|
||||
|
||||
```sh
|
||||
bun run typecheck
|
||||
bun test test/kilocode/server/config-overlay.test.ts
|
||||
bun test test/kilocode/project-config-update.test.ts
|
||||
```
|
||||
|
||||
From repository root:
|
||||
|
||||
```sh
|
||||
./script/generate.ts
|
||||
bun run script/check-opencode-annotations.ts
|
||||
bun run script/check-opencode-promise-facades.ts
|
||||
```
|
||||
|
||||
## Slice 0D: immutable Settings bindings
|
||||
|
||||
### Required result
|
||||
|
||||
Settings has explicit `User | Project` scope. Project scope requires an explicit trusted project selector. Drafts and saves never follow Agent Manager activation.
|
||||
|
||||
### Required changes
|
||||
|
||||
1. Split runtime-effective config from Settings editor config.
|
||||
2. Add a binding controller under `packages/kilo-vscode/src/kilo-provider/`; keep `KiloProvider.ts` as a thin adapter.
|
||||
3. Replace unqualified config protocol with:
|
||||
- read request carrying request ID, scope, and optional project ID;
|
||||
- snapshot carrying opaque binding ID and source/target metadata;
|
||||
- write request carrying request ID, binding ID, set, and unset.
|
||||
4. The extension stores authoritative bindings. The webview never supplies a writable path.
|
||||
5. Project binding resolution:
|
||||
- resolve registered `ProjectContext.root`;
|
||||
- require project existence, matching generation, and trust;
|
||||
- never use active worktree/session or `getWorkspaceDirectory()` after binding creation.
|
||||
6. Bindings expire on successful save, reconnect, project removal, generation change, or trust revocation.
|
||||
7. Separate drafts by scope/project binding.
|
||||
8. Dirty selector changes prompt Save, Discard, or Stay.
|
||||
9. Out-of-order reads/writes update only matching request/binding.
|
||||
10. External config events refresh clean drafts and mark dirty drafts stale without overwriting them.
|
||||
11. Remove `splitConfigByScope()` after all controls declare scope explicitly.
|
||||
12. Audit direct config mutators outside the save bar, including provider disconnect, imports/resets, custom providers, work styles, Permission Dock, MCP actions, and indexing.
|
||||
13. Open Project Config takes explicit `ProjectRef`, checks trust, and resolves the registered root.
|
||||
|
||||
### UI scope policy
|
||||
|
||||
Use the table in `agent-manager-multi-project-configuration.md`. Do not invent another policy.
|
||||
|
||||
### Blocking tests
|
||||
|
||||
- load A, activate B, save A: only A target changes;
|
||||
- same while selecting worktrees/sessions;
|
||||
- User save changes only user config;
|
||||
- Project save changes only selected trusted project's root config;
|
||||
- dirty draft cannot migrate to another project;
|
||||
- project removal/trust revocation expires binding;
|
||||
- external edit returns conflict and preserves draft;
|
||||
- no save-time active-directory lookup occurs.
|
||||
|
||||
## Slice 1: canonical project identity
|
||||
|
||||
### Required result
|
||||
|
||||
Every project is a canonical Git repository root with URI authority and common Git directory identity.
|
||||
|
||||
### Required changes
|
||||
|
||||
1. Replace path-only project descriptors with:
|
||||
|
||||
```ts
|
||||
{
|
||||
id,
|
||||
uri,
|
||||
scheme,
|
||||
authority,
|
||||
root,
|
||||
commonGitDir,
|
||||
label,
|
||||
order,
|
||||
trusted,
|
||||
addedAt
|
||||
}
|
||||
```
|
||||
|
||||
2. Pinned and added projects use the same resolver:
|
||||
- validate URI host scope;
|
||||
- `git rev-parse --show-toplevel`;
|
||||
- `git rev-parse --path-format=absolute --git-common-dir`;
|
||||
- realpath/normalize both;
|
||||
- derive ID from scheme, authority, and canonical root.
|
||||
3. Opening VS Code in a repository subdirectory still uses the Git toplevel and root state file.
|
||||
4. Reject another exposed project sharing canonical `commonGitDir`.
|
||||
5. Key Git mutation locks by common Git directory.
|
||||
6. Preserve remote URI scheme/authority in picker, storage, and open-folder operations.
|
||||
7. Migrate or safely read the current registry version without dropping valid entries.
|
||||
|
||||
### Tests
|
||||
|
||||
- workspace subdirectory resolves to repository root;
|
||||
- symlink aliases dedupe;
|
||||
- linked worktree/common-dir duplicate rejected;
|
||||
- other remote authority hidden but preserved;
|
||||
- missing/non-Git entry remains removable and never falls back to pinned root.
|
||||
|
||||
## Slice 2: strict routing and lifecycle
|
||||
|
||||
### Required result
|
||||
|
||||
Every repository-bound operation is explicitly project-qualified once multiple projects are exposed.
|
||||
|
||||
### Required changes
|
||||
|
||||
1. Introduce one project envelope at the Agent Manager boundary with project ID and generation.
|
||||
2. Compatibility adapter injects pinned identity only in single-project mode.
|
||||
3. Multi-project mode rejects missing/mismatched identity with typed errors.
|
||||
4. Wire `ProjectRouteService` into every existing-session operation:
|
||||
- transcript/messages;
|
||||
- prompt/abort;
|
||||
- share/unshare;
|
||||
- fork/continuation;
|
||||
- permission/question responses;
|
||||
- terminal;
|
||||
- file/context operations;
|
||||
- diff/apply/revert.
|
||||
5. Route global SSE events once by exact directory/session ownership.
|
||||
6. Preserve non-Agent-Manager KiloProvider behavior.
|
||||
7. Execute repository mutations through `ctx.run()` and generation-check commits.
|
||||
8. Removal waits for mutation queue, stops pollers/watchers, flushes state, detaches routes, then removes registry entry.
|
||||
9. Flag-off activates pinned Local and suspends secondaries without aborting sessions.
|
||||
|
||||
### Tests
|
||||
|
||||
- same raw session ID across projects is ambiguous without qualifier;
|
||||
- B Local/share/unshare/permission never routes through A;
|
||||
- unknown IDs produce no terminal/file/diff operation;
|
||||
- project switch during mutation cannot change operation ownership;
|
||||
- late completion after removal cannot mutate replacement context.
|
||||
|
||||
## Slice 3: uniform context-owned state and polling
|
||||
|
||||
### Required result
|
||||
|
||||
Every initialized expanded project owns the same live-state services. Active selection changes cadence/detail only, not ownership.
|
||||
|
||||
### Required changes
|
||||
|
||||
1. Move stats and PR pollers into `ProjectContext`; remove active singleton/background split.
|
||||
2. Each context owns stale/presence state, cached stats, local stats, PR state, run/setup state, and generation.
|
||||
3. Background presence updates project stale state rather than emitting empty arrays.
|
||||
4. All project outputs include project ID and generation; webview discards stale generations.
|
||||
5. Session-created/deleted/updated events refresh the owning project store without active-project filtering.
|
||||
6. Panel visibility and expansion uniformly control polling.
|
||||
|
||||
### Tests
|
||||
|
||||
- two expanded projects both receive stats/presence/PR updates;
|
||||
- collapse stops only that project's pollers;
|
||||
- stale late poll result is dropped;
|
||||
- background session deletion updates only its project.
|
||||
|
||||
## Slice 4: one full sidebar body
|
||||
|
||||
### Required result
|
||||
|
||||
Single- and multi-project modes render the same full `ProjectSidebarBody` implementation.
|
||||
|
||||
### Required changes
|
||||
|
||||
1. Extract the current full `renderBody()` from `AgentManagerApp.tsx` intact.
|
||||
2. Parameterize it with one project store and project-qualified actions.
|
||||
3. Preserve:
|
||||
- busy state;
|
||||
- drag/drop and grouping;
|
||||
- section actions;
|
||||
- search and keyboard hints;
|
||||
- run/setup state;
|
||||
- New Worktree dialog;
|
||||
- worktree menus and PR actions;
|
||||
- complete managed/unassigned session behavior.
|
||||
4. Delete the simplified duplicate body path.
|
||||
5. Render project rows keyed by stable project ID and keep bodies mounted across active changes.
|
||||
6. New Worktree receives explicit target project, defaulting to Settings/detail selection policy as specified.
|
||||
|
||||
### Tests
|
||||
|
||||
- single-project behavior unchanged with flag off;
|
||||
- two expanded projects render two full bodies;
|
||||
- active selection changes no body mount identity;
|
||||
- drag/drop, sections, worktree actions, sessions, and keyboard navigation work across projects.
|
||||
|
||||
## Slice 5: cleanup and release validation
|
||||
|
||||
1. Remove unrelated manifest/lockfile drift.
|
||||
2. Update changeset to final user-visible behavior.
|
||||
3. Run all extension and affected CLI checks.
|
||||
4. Build/package the extension.
|
||||
5. Use the VS Code self-test fixture with two repositories and multiple worktrees/sessions.
|
||||
6. Keep feature default off until every blocking test passes.
|
||||
|
||||
### Required extension checks
|
||||
|
||||
From `packages/kilo-vscode/`:
|
||||
|
||||
```sh
|
||||
bun run format
|
||||
bun run format:check
|
||||
bun run typecheck
|
||||
bun run lint
|
||||
bun run test:unit
|
||||
bun run knip
|
||||
bun run check-kilocode-change
|
||||
bun run compile
|
||||
```
|
||||
|
||||
From repository root:
|
||||
|
||||
```sh
|
||||
bun run script/check-opencode-annotations.ts
|
||||
bun run script/check-opencode-promise-facades.ts
|
||||
bun run script/check-md-table-padding.ts
|
||||
```
|
||||
|
||||
### Manual scenarios
|
||||
|
||||
1. Flag off: current single-project Agent Manager is unchanged.
|
||||
2. Enable flag in Experimental Settings; restart; value persists without CLI/project config changes.
|
||||
3. Add/trust/expand two repositories; restart; pinned remains first.
|
||||
4. Both projects show Local, worktrees, sessions, stats, PR state, and full actions.
|
||||
5. Navigate previous/next across projects.
|
||||
6. Create/delete/rename worktrees in both projects.
|
||||
7. Send prompts and answer permission/question requests in both projects.
|
||||
8. Share/unshare B while A is active; exact B directory is used.
|
||||
9. Load Project Settings A, activate B, save A; B is unchanged.
|
||||
10. Modify A config externally while draft is dirty; conflict preserves draft.
|
||||
11. Indexing defaults off for a new project; enabling A does not enable B; repo config cannot enable either.
|
||||
12. Disable flag while B is active; pinned Local becomes active and B remains registered/suspended.
|
||||
13. Repeat key routing and identity checks in a remote VS Code window.
|
||||
|
||||
## Rules for the implementation model
|
||||
|
||||
- Work on one slice at a time.
|
||||
- Read the referenced architecture section before editing.
|
||||
- Add focused tests before or with behavior changes.
|
||||
- Use `apply_patch` for manual edits.
|
||||
- Do not hand-edit generated SDK files.
|
||||
- Do not raise file-size caps.
|
||||
- Do not add fallback compatibility code unless the plan explicitly requires the single-project adapter.
|
||||
- Do not claim a slice complete until its stop gate passes.
|
||||
- If a required API or ownership decision is missing, stop and update this plan instead of guessing.
|
||||
@@ -0,0 +1,351 @@
|
||||
# Agent Manager multi-project runtime
|
||||
|
||||
> UI architecture update: the active-body plus background-summary model in this document is superseded by `agent-manager-multi-project-uniform-ui.md`. The runtime findings remain useful, but the final UI renders one permanent real interactive body for every expanded project and requires strict project-qualified routing before background actions are enabled.
|
||||
|
||||
**Status:** Runtime foundation implemented (this worktree); accordion UI pending
|
||||
|
||||
## Implementation status (2026-07-20)
|
||||
|
||||
Landed in this worktree, all covered by unit tests (`tests/unit/agent-project-*.test.ts`):
|
||||
|
||||
- `src/agent-manager/project-paths.ts` — canonical root resolution (`realpath` + `normalizePath`), case-aware `samePath`, deterministic `projectIdFor(root)` (sha1, stable across restarts), `resolveGitRoot`.
|
||||
- `src/agent-manager/project-registry.ts` — versioned global catalog of **additional** projects (storage-injected, corrupt-safe, dedupe by id, trust/label/order). The pinned workspace project is never persisted.
|
||||
- `src/agent-manager/project-context.ts` — immutable `ProjectContext` (owns WorktreeStateManager/WorktreeManager/SetupScriptService/stale set per canonical root, lazy creation, peek accessors) and `ProjectContexts` coordinator (pinned derivation, active/expanded lifecycle, trust+flag gating, `syncPinned()` for workspace changes, webview `ProjectSnapshot`s).
|
||||
- `src/agent-manager/project-messages.ts` — vscode-free handlers: `requestProjects`, `addProject` (folder picker → git root resolve → dedupe), `removeProject`, `selectProject`, `setProjectExpanded`, `trustProject`. All fail closed when the flag is off.
|
||||
- `src/agent-manager/project-wiring.ts` — factory assembling registry/contexts/deps/host listeners (extracted to keep `AgentManagerProvider.ts` under its 2000-line cap).
|
||||
- `AgentManagerProvider` — fields `state/worktrees/setupScript/staleWorktreeIds` replaced by context-backed getters; every existing handler now operates on the active project with zero per-handler changes. `onMessage` consumes project messages first and drops messages whose `projectId` mismatches the active project (stale-pane protection). `activateProject` re-runs `initializeState` for the new context; pollers/importer/run/terminal follow the active context through the existing getters. `agentManager.state` now carries `projectId`.
|
||||
- Protocol — `agentManager.projects` out-message (`multiProject` flag + `ProjectSnapshot[]`); new in-messages listed above; `projectId?` on `agentManager.state` and `createWorktree`. Webview type mirrors updated (`webview-ui/src/types/messages/{extension,webview}-messages.ts`).
|
||||
- Host — `pickFolder`, `multiProject`, `readProjects`/`writeProjects` (globalState key `agentManager.projects`), `onDidChangeWorkspaceFolders`, `onDidChangeMultiProject`.
|
||||
- Setting — `kilo-code.new.agentManager.multiProject` (boolean, default false, application scope).
|
||||
- Changeset — `.changeset/agent-manager-multi-project.md`.
|
||||
|
||||
Behavior with flag off: only the pinned workspace project exists; snapshots contain exactly one project; all registry data is preserved but hidden; no UI change.
|
||||
|
||||
## Remaining for the UI session (accordion)
|
||||
|
||||
- Project accordion rendering from `agentManager.projects`: header-only rows for collapsed/uninitialized projects (registry metadata only), full existing Local/worktree body for the active project, expanded non-active bodies from per-context state (extension must push per-context state with that context's `projectId` — currently `pushState` only pushes the active context).
|
||||
- Add-project entry point, trust CTA (sends `agentManager.trustProject` then expand/select), remove/rename in project menu.
|
||||
- `selectProject` before any repo action in a non-active project; send `projectId` on `createWorktree` (extension drops mismatches).
|
||||
- New Worktree dialog project selector (defaults to active project).
|
||||
- Extension follow-ups: hidden-cadence polling for expanded projects; session-owner index so Local sessions of project B open/share correctly even while A is active (currently Local sessions resolve through the active root — acceptable only while the UI activates before opening).
|
||||
- Implemented: per-context `pushState` for expanded non-active projects; per-project git stats/PR pollers (`ProjectPollers` in `project-pollers.ts`) so every expanded accordion receives live stats/PR data tagged with its projectId; webview `project-live.ts` store routing per-project payloads into per-project summary stores; `activate()` keeps the previously active project expanded.
|
||||
- Known boundary: `KiloProvider.projectID` and session refresh remain single-project; cross-project SSE filtering by session→directory owner is a follow-up (see "Verified findings" below).
|
||||
|
||||
## Objective (original)
|
||||
|
||||
Build the project-aware Agent Manager runtime before changing the UI. The eventual UI is a pinned current-project view plus lazily initialized additional project accordions, with one shared tab/detail area. The first backend slice must be mergeable with the flag disabled and must route today's single project through the same project-aware contracts that secondary projects will use.
|
||||
|
||||
The implementation must preserve these invariants:
|
||||
|
||||
- The first project is always the canonical Git root of the first VS Code workspace folder in the current window. It is derived at runtime, is always first, and cannot be removed or replaced by persisted state.
|
||||
- Global extension storage contains only the catalog of additional projects and project-list UI metadata. Each repository remains authoritative for its own `.kilo/agent-manager.json`.
|
||||
- A collapsed, never-opened additional project causes no `.kilo/agent-manager.json` read, Git mutation, setup/run initialization, terminal creation, or poller subscription.
|
||||
- Every session/worktree/draft operation has a project identity internally. Once secondary-project activation is enabled and more than one project is exposed, missing or mismatched identity is rejected rather than routed to the current workspace.
|
||||
- The feature flag gates catalog exposure, secondary activation, and multi-project wire/UI behavior. It does not select a separate legacy implementation.
|
||||
|
||||
## Verified findings
|
||||
|
||||
- `AgentManagerProvider` currently owns the panel and all repository-bound resources in one 1,941-line singleton. Its root is always `Host.workspacePath()` and its state/managers are lazily cached against that root (`packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts:56-212`, `1654-1695`). The architecture test caps this file at 2,000 lines and explicitly forbids raising the cap (`packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts:793-854`).
|
||||
- Opening the panel immediately initializes the current repository, performs `ensureGitExclude`, loads `.kilo/agent-manager.json`, discovers/restores worktrees, registers sessions, starts state-dependent behavior, and recovers prompts (`AgentManagerProvider.ts:267-360`). This whole sequence cannot run for a collapsed catalog entry.
|
||||
- `WorktreeStateManager` is correctly repository-scoped by constructor root and writes only `<root>/.kilo/agent-manager.json` (`WorktreeStateManager.ts:100-124`, `632-656`, `797-847`). Its save queue and `flush()` are suitable for a context shutdown barrier, but it has no cancellation/dispose primitive (`755-795`).
|
||||
- The embedded Agent Manager `KiloProvider` is one provider attached to the one panel/webview (`agent-manager/vscode-host.ts:67-169`). It already routes many SDK calls through a session-to-directory map, but local sessions are allowed to fall back to `workspaceFolders[0]` (`KiloProvider.ts:4343-4378`; `kilo-provider-utils.ts:319-345`). That fallback becomes a cross-project write risk.
|
||||
- `KiloProvider` has singleton project state (`projectID`, `currentSession`, project-scoped caches, one active project directory) and current session refresh treats all extra directories as worktrees of one canonical project (`KiloProvider.ts:344-359`, `1984-2029`; `kilo-provider-utils.ts:230-295`). It is not multi-project-correct without refactoring.
|
||||
- The shared backend and generated SDK support an explicit `directory` on session share and unshare (`packages/sdk/js/src/v2/gen/sdk.gen.ts:4733-4795`). Sharing must therefore use the same project/session route as prompts and transcript reads.
|
||||
- Backend session IDs are high-entropy process-global IDs (`packages/opencode/src/id/id.ts:1-17`, `23-33`, `42-49`). Raw IDs can remain the SDK/server identifier, but the UI/runtime identity still needs a project-qualified reference, and route registration must reject an observed cross-project collision.
|
||||
- `session.status` currently discards the SSE directory before invoking branch naming (`AgentManagerProvider.ts:204-220`). The orchestration bridge subscribes globally and rejects requests outside its root (`orchestration-bridge.ts:175-203`). Creating one unchanged bridge per project would let the wrong bridge reject a valid request before the correct project handles it.
|
||||
- Multiple `SessionTerminalManager` instances would each register the same global VS Code commands and terminal listeners (`SessionTerminalManager.ts:35-81`). It must remain panel/coordinator-owned, with project-qualified keys and explicit CWDs. Also, `TerminalRouter` currently falls back from an unknown worktree ID to the repository root (`terminal-routing.ts:120-130`), which must be removed in strict routing.
|
||||
- `VscodeHost.workspacePath()` uses `workspaceFolders[0]` (`vscode-host.ts:172-174`; `review-utils.ts:12-15`). It loses URI scheme/authority, and `openFolder` recreates paths with `Uri.file`. A catalog containing only path strings would be unsafe across local, SSH, container, and other remote extension hosts.
|
||||
- Existing Kilo experimental config is CLI/project config, not an application feature-flag service (`features.ts`; `webview-ui/src/types/messages/config.ts`). A multi-project bootstrap flag cannot be project-scoped because choosing which project config to read is the behavior being gated.
|
||||
|
||||
## Architecture decisions
|
||||
|
||||
### 1. Project identity and registry semantics
|
||||
|
||||
Add a VS Code-free `ProjectRegistry` backed by an injected storage and resolver interface. `vscode-host.ts` implements those interfaces using `ExtensionContext.globalState`, workspace/open-dialog URIs, filesystem canonicalization, and Git.
|
||||
|
||||
Use these core types in `src/agent-manager/project.ts`:
|
||||
|
||||
```ts
|
||||
type ProjectId = string
|
||||
|
||||
interface ProjectLocation {
|
||||
uri: string
|
||||
scheme: string
|
||||
authority: string
|
||||
root: string
|
||||
commonGitDir: string
|
||||
}
|
||||
|
||||
interface ProjectDescriptor extends ProjectLocation {
|
||||
id: ProjectId
|
||||
name: string
|
||||
pinned: boolean
|
||||
collapsed: boolean
|
||||
order: number
|
||||
status: "unverified" | "ready" | "missing" | "notGit" | "wrongAuthority"
|
||||
}
|
||||
```
|
||||
|
||||
`root` is the canonical filesystem Git toplevel. Resolve it only when deriving the pinned project, adding a project, or expanding an unverified project:
|
||||
|
||||
1. Start from the selected workspace/folder URI.
|
||||
2. Reject a URI whose scheme/authority is not the current extension-host scope.
|
||||
3. Run `git -C <fsPath> rev-parse --show-toplevel` through the existing hidden-window process wrapper.
|
||||
4. Resolve the result with `fs.promises.realpath`; normalize separators, trailing separators, and Windows case for identity comparison.
|
||||
5. Resolve and canonicalize `git rev-parse --path-format=absolute --git-common-dir` as `commonGitDir`.
|
||||
6. Derive `ProjectId` deterministically as a versioned SHA-256/base64url hash of `scheme + NUL + authority + NUL + normalized canonical root`. Do not expose a path-concatenated ID on the wire.
|
||||
|
||||
Different linked worktree roots have different project IDs, but the first iteration must reject adding a project whose canonical `commonGitDir` matches any project already exposed in the window. Two Agent Manager contexts mutating the same Git common directory would bypass the current root-keyed worktree lock and could manage the same branches twice. This is a product-valid restriction to distinct Git projects, not a path workaround. Also change `WorktreeManager`'s write-lock key to canonical `commonGitDir` so future relaxation does not reintroduce `index.lock` races.
|
||||
|
||||
Persist one unsynced global-state value under `kilo.agentManager.projects.v1`:
|
||||
|
||||
```ts
|
||||
interface StoredProjectCatalogV1 {
|
||||
version: 1
|
||||
projects: Array<{
|
||||
id: ProjectId
|
||||
uri: string
|
||||
scheme: string
|
||||
authority: string
|
||||
root: string
|
||||
commonGitDir: string
|
||||
name?: string
|
||||
collapsed: boolean
|
||||
order: number
|
||||
addedAt: string
|
||||
}>
|
||||
}
|
||||
```
|
||||
|
||||
Do not call `setKeysForSync`; machine/remote paths must not sync to other machines. Serialize mutations in-process. Each mutation re-reads the latest value, validates it, applies add/remove/reorder/metadata changes, and performs one `globalState.update`.
|
||||
|
||||
Registry behavior is exact:
|
||||
|
||||
- `snapshot(flag)` derives the pinned current project fresh. It never reads a persisted active project.
|
||||
- The pinned descriptor is first, has `pinned: true`, and defaults to expanded. It is not written into the catalog.
|
||||
- Persisted entries matching the pinned project ID are suppressed, not deleted. They reappear as additional projects when another window has a different pinned project.
|
||||
- Additional entries are filtered to the current URI scheme/authority before exposure. Entries for other authorities remain untouched in storage.
|
||||
- Reading the catalog does not stat, realpath, run Git, or open per-repository state. Persisted additional entries start as `unverified`.
|
||||
- Adding validates/canonicalizes the chosen folder, rejects the pinned project, duplicate IDs, duplicate common Git directories, and authority mismatches, then persists it. A newly added project may be marked expanded and explicitly activated by the caller; listing alone never initializes it.
|
||||
- A missing or no-longer-Git root is marked unavailable only when expansion validates it. Keep the entry and its metadata. Do not prune it automatically and do not create a `ProjectContext`.
|
||||
- Removing is allowed only for non-pinned projects. It removes only the catalog entry after the context is idle/disposed; it never deletes `.kilo`, worktrees, sessions, branches, or shares.
|
||||
- On every extension/panel restart, the active selection is the pinned project's Local context. Persisted additional projects and collapse/order/name metadata survive, but a stale secondary active session does not override the current VS Code window.
|
||||
|
||||
For a multi-root VS Code workspace, preserve current behavior by deriving the pinned project from `workspaceFolders[0]`, then canonicalize that folder to its Git toplevel. Do not silently select another workspace folder if the first is not a Git repository.
|
||||
|
||||
### 2. Coordinator and immutable lazy ProjectContext
|
||||
|
||||
Turn `AgentManagerProvider` into the panel-level coordinator and extract repository behavior into `project-context.ts`. Do not instantiate one current `AgentManagerProvider` per project.
|
||||
|
||||
The coordinator owns:
|
||||
|
||||
- the one panel and embedded `KiloProvider`/`SessionProvider`;
|
||||
- `ProjectRegistry`, active `ProjectId`, exposed descriptors, and `Map<ProjectId, LazyProjectContext>`;
|
||||
- a shared `Semaphore(3)` for Git/network polling across all projects;
|
||||
- the one project/session router;
|
||||
- one project-aware `AgentManagerVisiblePresence`;
|
||||
- one `SessionTerminalManager` and one project-aware `TerminalRouter`;
|
||||
- one global orchestration ingress and the global SSE/tool/status subscriptions;
|
||||
- project and activation generations used to discard stale async results.
|
||||
|
||||
Each `ProjectContext` is constructed once with an immutable descriptor/root/common Git directory and owns only repository-bound resources:
|
||||
|
||||
- `WorktreeStateManager`, `WorktreeManager`, `SetupScriptService`, and repository `GitOps`;
|
||||
- local diff functions, `WorktreeDiffController`, `WorktreeImporter`, and `RunController`;
|
||||
- `GitStatsPoller`, `PRStatusBridge`/poller, and `BranchNamingController`;
|
||||
- per-project cached state/stats/stale IDs, managed/open session refs, and initialization promise;
|
||||
- project-specific message handlers currently embedded in `AgentManagerProvider`.
|
||||
|
||||
It does not own a webview, `KiloProvider`, VS Code terminal command registrations, global connection subscriptions, or a global orchestration subscription.
|
||||
|
||||
Lifecycle:
|
||||
|
||||
1. `cold`: descriptor only. No state manager, manager, poller, watcher, or per-repo read exists.
|
||||
2. `initializing`: one shared promise validates the location, constructs resources, performs the existing `ensureGitExclude -> state.load -> recoverWorktrees -> register routes` sequence, and captures a context generation.
|
||||
3. `ready/expanded`: posts project-stamped state and permits project actions. Pollers run only when both the panel is visible and this project is expanded. Multiple expanded contexts may be ready concurrently.
|
||||
4. `suspended/collapsed`: if active, the coordinator first activates pinned Local (or another explicit target). Stop stats/PR/diff polling and watchers, stop accepting new project mutations, wait for the current mutation barrier, flush state, and detach project-specific event side effects such as automatic branch naming. Keep validated session routes/tracking so a running backend session can still surface status and permission/question events without a repository read. Do not abort agents merely because an accordion collapsed.
|
||||
5. `disposed`: increment generation first, stop/dispose resources, await terminal-router/run/mutation cleanup and state flush, detach routes, then remove the exact context object from the map. Late completions may neither post nor mutate a replacement context.
|
||||
|
||||
Expansion of a suspended context resumes its existing in-memory state and pollers; expansion of a cold context initializes. Concurrent expansions share the same promise. Collapse during initialization records the desired collapsed state, allows non-cancellable Git/state work to finish, suppresses stale posts, then immediately suspends.
|
||||
|
||||
The coordinator routes global events by canonical event directory before invoking a context. `session.status` must retain its directory. A single orchestration ingress determines the one owning context and delegates there; it rejects an unknown directory once, globally. It must never create one rejecting bridge per project. A collapsed project does not auto-initialize in response to an unsolicited orchestration event; return a typed `project_inactive` result.
|
||||
|
||||
Panel close keeps existing semantics for sessions actually opened/created by that panel, but aggregates their project-qualified routes before aborting. Project collapse and flag-off transitions do not abort them. Removing a project is refused while one of its tracked sessions or Agent Manager run/mutation is busy, rather than orphaning an interaction prompt.
|
||||
|
||||
### 3. One embedded KiloProvider, made project-aware
|
||||
|
||||
Use one embedded `KiloProvider`. Multiple providers are not safe or useful here:
|
||||
|
||||
- there is one webview and one message handler;
|
||||
- each provider would own conflicting current-session/cached UI state and duplicate SSE handling;
|
||||
- multiple terminal/visibility registrations would be ambiguous;
|
||||
- it would not solve session identity at the shared webview boundary.
|
||||
|
||||
Refactor its Agent Manager adapter around an injected project/session route service while leaving sidebar and standalone providers unchanged. The route service supplies:
|
||||
|
||||
```ts
|
||||
interface ProjectSessionRef {
|
||||
projectId: ProjectId
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
interface SessionRoute extends ProjectSessionRef {
|
||||
directory: string
|
||||
generation: number
|
||||
}
|
||||
```
|
||||
|
||||
Internally key UI maps by `projectId + NUL + sessionId`; never use that concatenation on the wire. Keep the raw `sessionId` for SDK calls and backend presence. Maintain a reverse raw-ID index only to process backend events. If the same raw ID is observed in two projects, mark it ambiguous and reject/drop operations and events that cannot also be disambiguated by event directory.
|
||||
|
||||
Required routing changes:
|
||||
|
||||
- Register an explicit route for every Agent Manager session, including Local sessions at a project root. Do not omit a map entry merely because it equals the first workspace root.
|
||||
- New/draft operations resolve from the outer project envelope and an explicit Local/worktree context. Unknown worktree IDs are errors; remove `TerminalRouter`'s fallback to root.
|
||||
- Existing session operations resolve only from `ProjectSessionRef`. In strict multi-project mode, an unknown session never falls back to `workspaceFolders[0]` or the currently active project.
|
||||
- Child/subagent sessions inherit the parent's project and directory. A directory-bearing event must also pass the owning-context check before registration.
|
||||
- Session list refresh runs per project root plus that project's worktree directories. It emits sessions stamped with that project; it does not infer one canonical `projectID` from the first root and merge all projects into it.
|
||||
- Replace singleton Agent Manager use of `KiloProvider.projectID` with the route table/directory ownership check. Keep existing project filtering for non-Agent-Manager provider instances.
|
||||
- On active-project/session change, call one `activateProject(projectId, root)` path. It updates `projectDirectory`, invalidates/refetches project-scoped config, providers, agents, commands, skills, MCP, indexing, sandbox, and Git status for that root. Guard every async refresh with activation generation so project A cannot populate caches after switching to B.
|
||||
- Every session create, continuation, prompt, permission/question response, terminal operation, diff operation, file/context operation, and share/unshare SDK call receives the resolved explicit directory.
|
||||
|
||||
This picks up each secondary project's config naturally through the CLI's directory-scoped instance/config resolution. Do not copy provider/config/settings data into global storage.
|
||||
|
||||
### 4. Project-qualified protocol and share semantics
|
||||
|
||||
Define an internal/wire envelope in `agent-manager/types.ts` and mirror it in `webview-ui/src/types/messages/agent-manager.ts`:
|
||||
|
||||
```ts
|
||||
interface ProjectStamp {
|
||||
projectId: ProjectId
|
||||
generation: number
|
||||
}
|
||||
|
||||
interface ProjectEnvelope<T> {
|
||||
type: "agentManager.projectMessage"
|
||||
project: ProjectStamp
|
||||
payload: T
|
||||
}
|
||||
```
|
||||
|
||||
Use project-qualified references for sessions, worktrees, and drafts:
|
||||
|
||||
```ts
|
||||
interface SessionRef { projectId: ProjectId; sessionId: string }
|
||||
interface WorktreeRef { projectId: ProjectId; worktreeId: string }
|
||||
interface DraftRef { projectId: ProjectId; draftId: string }
|
||||
```
|
||||
|
||||
Raw IDs remain in each repository's existing state file. The repository/context supplies the project identity when state is loaded. Do not migrate session IDs into global state and do not permit moving an existing session record between projects. A future cross-project continuation must explicitly fork/create in the target project; relabeling a raw session is invalid.
|
||||
|
||||
Protocol rules:
|
||||
|
||||
- The coordinator is the only component that unwraps inbound envelopes and wraps context output.
|
||||
- Global catalog/route errors are global messages. Repository state, stats, session metadata, diff, run, PR, terminal, and chat messages are project envelopes.
|
||||
- Preserve `requestId` inside the payload. Return a typed route error (`project_required`, `project_unknown`, `project_inactive`, `project_mismatch`, `session_unknown`, or `session_ambiguous`) without forwarding the original message.
|
||||
- Validate the envelope project against the context, persisted session/worktree ownership, session route, and event/request directory. A valid raw ID in the wrong envelope is still rejected.
|
||||
- Outbound generation lets the webview/coordinator discard messages from a collapsed/disposed/replaced context.
|
||||
- While the flag is off, or while only the pinned project is exposed, a compatibility adapter wraps today's unqualified webview messages with the pinned project and unwraps pinned output to the existing shape. The core handlers still receive envelopes.
|
||||
- Once the flag is on and a second project is exposed, unqualified project/session/worktree messages fail closed. The multi-project UI must send/consume envelopes and use composite refs as tab/store keys.
|
||||
|
||||
Sharing is an operation on `SessionRef`, not on a bare session ID:
|
||||
|
||||
- Manual share calls `client.session.share({ sessionID, directory })`; unshare uses the exact same resolved route.
|
||||
- Automatic sharing remains a project config behavior because session creation is sent with the target project's directory.
|
||||
- Store no share URL or share state in the project catalog. Session list/get/update data remains authoritative and is enriched with `projectId` on output.
|
||||
- Removing/collapsing a project never unshares its sessions. A share URL is externally usable, but reopening or mutating the local session still requires its local project route.
|
||||
- A mismatched or missing project cannot share/unshare, even if the raw session ID happens to resolve elsewhere.
|
||||
|
||||
### 5. Feature flag, restart, and migration behavior
|
||||
|
||||
Contribute an application-scoped VS Code setting, default false, for example `kilo-code.new.agentManager.multiProject`, with `scope: "application"`. Read it in the host/coordinator; do not put it in CLI `experimental` config.
|
||||
|
||||
- Flag off: derive and initialize only pinned current project, use the compatibility wire adapter, and preserve the exact current view. Read but do not expose, validate, initialize, rewrite, or delete additional catalog entries.
|
||||
- Flag on: publish lightweight descriptors. Only explicit expansion initializes an additional project. Selecting Local/worktree also expands and initializes its owner before activation.
|
||||
- Runtime on -> off: increment activation generation, activate pinned Local, suspend all secondary contexts, switch to compatibility protocol, and preserve catalog/per-repo state. Do not abort backend sessions.
|
||||
- Runtime off -> on: expose stored descriptors as cold/unverified. Do not eagerly restore a persisted secondary active project.
|
||||
|
||||
No `.kilo/agent-manager.json` schema migration is needed. Existing state is loaded unchanged by the pinned context, and additional contexts load their own existing files only when expanded. An absent catalog means no additional projects. Invalid catalog entries are ignored in the snapshot and logged, but a successful write must preserve valid entries from other authorities. Never scan the filesystem to discover projects as migration.
|
||||
|
||||
On restored legacy panel/webview state, unqualified tabs are accepted only in compatibility single-project mode. When multi-project mode exposes a second project, send a protocol reset and rebuild tabs/session lists from project envelopes instead of guessing which project owns a restored raw session ID.
|
||||
|
||||
Remote behavior:
|
||||
|
||||
- Scope catalog exposure and identity by full URI scheme + authority, not `vscode.env.remoteName` alone.
|
||||
- Run Git and filesystem operations in the current extension host against `fsPath`; preserve the original URI for `openFolder` and picker operations.
|
||||
- Reject selection from another authority. Keep such stored entries for their matching window/host.
|
||||
- Unsupported virtual filesystems, missing roots, permission failures, and non-Git directories become unavailable descriptors after explicit validation. They do not fall back to the pinned root.
|
||||
|
||||
## Ordered implementation plan
|
||||
|
||||
### Slice A: mergeable routing foundation before multi-project UI
|
||||
|
||||
This is the narrow first PR. It produces no secondary-project UI, but the current pinned project runs through the durable identity/routing contracts and the registry is fully tested.
|
||||
|
||||
1. Add `src/agent-manager/project.ts` with IDs, locations, descriptors, stamps, envelopes, and ref types. Add `src/agent-manager/project-registry.ts` with injected storage/resolver, v1 schema validation, pinned merge, authority filtering, dedupe, and serialized mutations.
|
||||
2. Extend `Host` in `src/agent-manager/host.ts`; implement global-state storage, canonical Git/URI resolution, pinned project resolution, and the application flag in `src/agent-manager/vscode-host.ts`. Preserve URIs in `openFolder`. Add the setting contribution in `packages/kilo-vscode/package.json`.
|
||||
3. Add `src/agent-manager/project-session-router.ts`. It must register Local and worktree routes, resolve refs/directories, inherit child routes, validate directory ownership, detect raw-ID ambiguity, and implement compatibility versus strict mode.
|
||||
4. Add envelope/route errors to `src/agent-manager/types.ts` and the mirrored webview type file. Add a coordinator adapter in `AgentManagerProvider` that derives the pinned project at panel attach, wraps all legacy inbound messages, stamps context output, and switches strictness based on exposed-project count. In this slice only the pinned context is activated, so rendered behavior remains unchanged.
|
||||
5. Extend the `SessionProvider` adapter (`host.ts`, `vscode-host.ts`) with project-aware `setSessionRoute`, `trackSession`, `getSessionInfo`, `loadMessages`, `share`, and `unshare` methods. Retain temporary legacy methods only as single-project adapters, not as a second implementation.
|
||||
6. Modify `KiloProvider`, `kilo-provider/options.ts`, and the extracted helpers in `kilo-provider-utils.ts` so Agent Manager Local sessions also have explicit routes, unknown strict routes fail closed, child routes inherit project identity, and project activation invalidates project-scoped cache work by generation. Keep non-Agent-Manager provider behavior unchanged.
|
||||
7. Update `extension.ts` auto-approve/session-directory lookup to ask the router/coordinator for the active or unique `SessionRef`; do not merge raw ID maps and choose the first match.
|
||||
8. Remove unknown-worktree-to-root fallback in `terminal-routing.ts`, and inject the pinned project root into `createTerminalHost` instead of reading `workspaceFolders[0]` internally.
|
||||
|
||||
Slice A is complete when the flag-disabled Agent Manager is visually and behaviorally unchanged, every current project session has an explicit project route, and unit tests prove a second registered route cannot be accidentally handled through pinned-root fallback. Do not expose Add Project until Slice B is complete.
|
||||
|
||||
### Slice B: lazy multi-context runtime, still before UI rendering
|
||||
|
||||
1. Add `src/agent-manager/project-context.ts` and move root-bound state/managers/handlers out of `AgentManagerProvider`. Keep context code VS Code-free. Reduce the provider cap to the new rounded-up line count instead of raising it.
|
||||
2. Make `AgentManagerProvider` the coordinator described above. Add cold/initializing/ready/suspended/disposed state, generation guards, expansion/activation APIs, shared semaphore, and per-project message dispatch.
|
||||
3. Refactor status/tool/orchestration subscriptions so the coordinator routes once by project/directory. Do not retain per-context global subscriptions that can reject each other's requests.
|
||||
4. Make terminal management panel-global and key mappings by composite session/worktree refs. A context supplies only an already-validated CWD. Ensure context collapse cannot switch to another project's terminal by raw ID.
|
||||
5. Refactor session refresh to accept one project at a time and emit project-stamped results. Register routes for discovered Local sessions before they can be opened. Preserve failed-directory session IDs only within the affected project.
|
||||
6. Change `WorktreeManager`'s mutation lock key to canonical common Git directory and reject adding another exposed context with the same common directory.
|
||||
7. Add backend catalog commands (`requestProjects`, `addProject`, `removeProject`, `expandProject`, `collapseProject`, `activateProject`) behind the flag. These are testable protocol endpoints but need not yet have visible controls.
|
||||
|
||||
### Slice C: first usable flagged UI
|
||||
|
||||
After A and B are green, implement project accordions and the project selector in the New Worktree modal. Keep the current markup/layout when only the pinned project exists. Use composite refs in webview stores/tabs, one shared tab bar/detail pane, and clearly show the project name on cross-project session tabs and share actions. This UI slice is intentionally outside the backend-first scope.
|
||||
|
||||
## Exact tests
|
||||
|
||||
Add or update these focused tests under `packages/kilo-vscode/tests/unit/`:
|
||||
|
||||
- `agent-manager-project-registry.test.ts`: pinned project is always first/fresh; persisted active cannot replace it; additional roots survive restart; pinned duplicate is suppressed; add canonicalizes subdirectories/symlinks; duplicate root/common Git dir and wrong authority are rejected; flag-off snapshot exposes only pinned without mutating catalog; catalog listing performs no resolver/filesystem calls; missing entries remain stored.
|
||||
- `agent-manager-project-router.test.ts`: every Local/worktree session has a route; matching refs resolve; wrong project, unknown session, unknown worktree, missing project, and raw-ID collision fail closed; child inherits parent project/directory; compatibility inference works only for one exposed project; share and unshare use the same explicit directory.
|
||||
- `agent-manager-project-context.test.ts`: collapsed cold projects construct/read nothing; concurrent expand initializes once; two expanded contexts use separate state roots and share the concurrency gate; collapse stops pollers and flushes; collapse during init suppresses posts; disposed-generation results cannot update a replacement context; remove is blocked while busy.
|
||||
- Extend `kilo-provider-session-refresh.test.ts`: lists per project instead of merging project roots; outputs project stamps; failed worktree listing preserves only that project's sessions; activation generation drops stale config/session results.
|
||||
- Extend `kilo-provider-worktree-context.test.ts`: strict unknown session/worktree has no workspace fallback; Local routes explicitly resolve to their own project root; a secondary Local session cannot resolve to pinned root.
|
||||
- Extend `AgentManagerProvider.spec.ts`: single-project legacy messages are wrapped pinned; two-project unqualified messages return `project_required`; mismatched session/project returns `project_mismatch`; status/tool events dispatch by directory; restart selects pinned Local.
|
||||
- Add terminal routing coverage: unknown project/worktree produces an error and no PTY/VS Code terminal; identical raw session IDs in different projects cannot select the wrong terminal.
|
||||
- Extend `agent-manager-arch.test.ts`: new domain modules do not import `vscode`; lower `AgentManagerProvider.ts` maxLines after extraction.
|
||||
|
||||
Run from `packages/kilo-vscode/` after each slice:
|
||||
|
||||
```sh
|
||||
bun test tests/unit/agent-manager-project-registry.test.ts tests/unit/agent-manager-project-router.test.ts tests/unit/kilo-provider-session-refresh.test.ts tests/unit/kilo-provider-worktree-context.test.ts
|
||||
bun run typecheck
|
||||
bun run lint
|
||||
bun run test:unit
|
||||
bun run knip
|
||||
bun run check-kilocode-change
|
||||
```
|
||||
|
||||
For Slice B, add the context/provider/terminal tests to the focused first command. No CLI/SDK regeneration is required because this architecture uses existing explicit-directory SDK parameters and adds only extension/webview protocol types.
|
||||
|
||||
## Manual validation after the UI slice
|
||||
|
||||
1. With the flag off and a non-empty stored catalog, restart VS Code. Agent Manager must look and behave exactly as today and use only the current window's Git root.
|
||||
2. Turn the flag on, add a second repository, restart, and verify its header persists while the pinned current project remains first and active. Confirm the collapsed second project creates no output/state access/poll traffic until expanded.
|
||||
3. Expand both projects, open Local and worktree sessions from each, switch rapidly, send prompts, answer permission/question requests, open terminals/diffs, and share then unshare. Verify every operation affects the displayed project's repository and config.
|
||||
4. Disable the flag while a secondary project is active. Verify pinned Local becomes active, secondary state remains on disk/catalog, and re-enabling restores the descriptor without eager initialization.
|
||||
5. Rename/remove a cataloged directory and restart. Verify the project remains as unavailable, cannot route actions to pinned root, and can be removed without touching its repository state.
|
||||
6. Repeat in a remote window. Verify local/other-authority catalog entries are hidden but preserved and folder opening retains the remote URI authority.
|
||||
|
||||
## Risks and rollback
|
||||
|
||||
- The largest correctness risk is a hidden workspace-root fallback in `KiloProvider` or a helper. Treat every Agent Manager method that accepts `sessionID`, `worktreeId`, `draftID`, `requestID`, or directory as part of the routing audit. Tests should fail if strict mode calls `workspaceFolders[0]` for an identified operation.
|
||||
- Project-scoped KiloProvider caches can show stale settings after a rapid project switch. Activation generation checks are required before posting and before replacing caches; merely cancelling transcript load is insufficient.
|
||||
- Global event consumers can race. There must be exactly one ownership decision for orchestration/tool events, and directory must be retained for status/permission/question handling.
|
||||
- Multiple contexts increase polling/process pressure. Share the concurrency semaphore, gate all pollers by panel visibility and expansion, and suspend on collapse/flag-off.
|
||||
- Global-state updates from separate VS Code windows do not provide a transactional CAS. The first iteration should re-read before each serialized mutation and tolerate another window appearing after reload; do not build a filesystem lock or custom database for this feature.
|
||||
- Rollback is safe: turn off the application flag. The pinned path and existing `.kilo/agent-manager.json` format remain intact, while the unused global catalog is preserved for a later re-enable.
|
||||
@@ -0,0 +1,210 @@
|
||||
# Agent Manager Multi-Project — Shipping Gaps
|
||||
|
||||
Status as of 2026-07-27. The multi-project sidebar renders and works end to end in the
|
||||
self-test harness (both projects list worktrees/sessions/sections, section creation
|
||||
persists, worktree delete works, project switching restores selection, live session
|
||||
upsert verified). Full unit suite green (3444 pass / 0 fail), typecheck, lint, knip,
|
||||
arch caps all pass. Everything is uncommitted in the worktree.
|
||||
|
||||
This document lists what is still missing, ranked by whether it should block shipping.
|
||||
|
||||
Two audiences matter for the blocking decision:
|
||||
|
||||
- **All users**: the branch changes shared surfaces (legacy sidebar refactor, config
|
||||
write path, indexing consent, pollers). Regressions here ship to everyone, flag off
|
||||
or not.
|
||||
- **Experimental users**: multi-project mode is gated behind
|
||||
`kilo-code.new.experimental.multiProject`, default off. Rough edges here are
|
||||
acceptable if they cannot corrupt data.
|
||||
|
||||
---
|
||||
|
||||
## 1. Blocks shipping (all-user surfaces)
|
||||
|
||||
### 1.0 Empty-state skeleton regression (fixed, must stay fixed)
|
||||
|
||||
`initializeState` and `onRequestState` only called `refreshSessions()` when the
|
||||
managed state contained at least one session. With zero managed sessions the
|
||||
backend listing never ran, `sessionsLoaded` never reached the webview, and both
|
||||
the WORKTREES and SESSIONS sections stayed on skeletons forever (the worktree
|
||||
gate requires `worktreesLoaded() && sessionsLoaded()`). Any user whose state file
|
||||
lost its sessions — exactly what the earlier session-persist bugs caused — hit a
|
||||
permanently empty-looking sidebar. Reproduced in the harness on a worktree-root
|
||||
workspace and fixed by making both refreshes unconditional. Root-caused via
|
||||
stage-by-stage init logging on 2026-07-27; do not reintroduce a content-based
|
||||
guard here.
|
||||
|
||||
### 1.1 Config write path can fail or wipe drafts for existing callers — P0-6 / P1-7
|
||||
|
||||
The config binding rework requires every `updateConfig` sender to carry a binding id,
|
||||
and `configUpdateFailed` currently wipes the draft for the failed scope. Any existing
|
||||
sender without a binding (permission dock, model picker, auto-approve, onboarding)
|
||||
now fails or loses the user's unsaved edits.
|
||||
|
||||
- Backend half is done: `expected` is optional in the overlay schema, writer, and
|
||||
handler, so a client without a binding writes unconditionally again instead of
|
||||
getting a 400. The webview half (draft retention on `configUpdateFailed`, and the
|
||||
audit of every `updateConfig` sender) is still open.
|
||||
- Work: the code change is small; the real work is auditing every webview
|
||||
`updateConfig` sender and classifying it.
|
||||
- Also: this change is orthogonal to multi-project. Split it into its own commit
|
||||
(`feat(vscode): config write revision bindings`) so it can be reverted alone.
|
||||
|
||||
### 1.2 Indexing status read silently revokes consent — P0-3 / P0-4
|
||||
|
||||
Partially addressed: untrusted projects are now filtered out of the consent list
|
||||
(P0-4), and config scope switching plus project-scoped `indexing.enabled` writes
|
||||
were restored after the rework had hardcoded the tab to global scope (the earlier
|
||||
P1-8 gap). The remaining blocker is the read path below.
|
||||
|
||||
|
||||
`fetchAndSendIndexingStatus` issues `PUT /indexing/consent` (a write) on a plain
|
||||
status refresh, defaulting to `enabled: false` for any project not in local
|
||||
`globalState`. On a fresh machine/profile, the first status read turns indexing off
|
||||
for users who had it on. A refresh can also target the wrong project
|
||||
(`requestIndexingStatus` resolves from the current session's directory). Separately,
|
||||
the Indexing tab lists untrusted projects and would let a user enable indexing for a
|
||||
repo the trust system has not approved.
|
||||
|
||||
- Fix: read status with a GET; PUT only from the explicit setter; seed consent from
|
||||
the effective config on first read instead of defaulting false; require an explicit
|
||||
project on refresh; filter the project list to trusted projects.
|
||||
- Dependency: needs a read-only status endpoint. If none exists, this escapes into
|
||||
`packages/opencode` (shared upstream code, needs `kilocode_change` markers) or the
|
||||
cloud repo.
|
||||
- Also orthogonal to multi-project; split into its own commit
|
||||
(`feat(vscode): per-project indexing consent`).
|
||||
|
||||
### 1.3 Land the current work as separate, revertable commits
|
||||
|
||||
Everything currently sits uncommitted in one worktree. The review's recommendation
|
||||
stands: `git reset --soft origin/main` and stage by path into three commits —
|
||||
multi-project, config bindings, indexing consent. The multi-project commit message
|
||||
must not promise per-project config/indexing behavior that lands in the other two.
|
||||
|
||||
---
|
||||
|
||||
## 2. Should fix soon after (experimental surface, data-integrity relevant)
|
||||
|
||||
### 2.1 Same repo can register twice — P0-5
|
||||
|
||||
`projectIdFor` hashes the canonical path verbatim while `samePath` folds case, so two
|
||||
casings of one repo produce two project ids, two contexts, and two state managers
|
||||
racing to write the same `.kilo/agent-manager.json` (last write wins, worktrees and
|
||||
sessions vanish).
|
||||
|
||||
- Fix: fold case inside `projectIdFor` on darwin/win32; reject `addProject` when
|
||||
`samePath` matches any registered root.
|
||||
- Migration: existing registry keys were built from the old hash, so re-key them on
|
||||
load or dual-lookup on read. Note `canonicalizePath` already realpaths existing
|
||||
paths; the fallback branch must fold case too.
|
||||
- Why not blocking: requires an unusual casing mismatch at registration time, and the
|
||||
feature is flag-gated.
|
||||
|
||||
### 2.2 Route service is shared across panels but versioned per context — P0-7
|
||||
|
||||
Two VS Code windows each have their own `ProjectContexts` with independent generation
|
||||
counters feeding one shared `ProjectRouteService`. Panel B registering a project at
|
||||
generation 0 while panel A is at 2 unregisters panel A's routes; closing one panel
|
||||
drops routes the other still needs.
|
||||
|
||||
- Fix: panel-qualified keys (`panelId + projectId + sessionId`), generations issued
|
||||
by the service, ambiguity computed across all panels.
|
||||
- Why not blocking: needs two windows running Agent Manager against the same repos.
|
||||
The fallback already refuses ambiguous raw ids instead of resolving them wrong.
|
||||
- Note: no two-panel test harness exists today; this fix should create one.
|
||||
|
||||
### 2.3 `gh pr` noise in repos without remotes — Bug 5
|
||||
|
||||
`gh pr view` fails every 15s per worktree forever, logs before the dedupe, and
|
||||
`pollOnce` rejections are unhandled.
|
||||
|
||||
- Fix: log after the `lastHash` dedupe; `void this.pollOnce().catch(log)` in
|
||||
`schedule`; per-root remote probe with a single error emission; skip PR pollers for
|
||||
remote-less projects in `ProjectPollers.sync`.
|
||||
|
||||
---
|
||||
|
||||
## 3. UX papercuts in experimental mode (fix opportunistically)
|
||||
|
||||
- **Legacy tabs orphaned on upgrade** (P1-1): `createLocalTabs` migrates persisted
|
||||
`localSessionIDs` into a `single` bucket that `tabKey()` never reads once the
|
||||
catalog arrives. Migrate `single` to the pinned project id on first state apply.
|
||||
- **`restoreProjectTarget` skips tab bookkeeping** (P1-3): a restored session has no
|
||||
tab. Call `selectLocal()` first, add the tab, then select.
|
||||
- **`ensurePendingTab` runs before restore** (P1-4): switch adds a "New Session"
|
||||
draft that restore may contradict. Move it after restore.
|
||||
- **Per-keystroke state saves**: `setActiveTarget` writes
|
||||
`.kilo/agent-manager.json` on every selection change. Debounce or persist on
|
||||
deactivation/dispose only.
|
||||
- **Untranslated Indexing tab strings** (P1-9), **unused-ish composite id schemes**
|
||||
(P1-12), **stats messages tagged at emit time** (P1-14), **no presence sync for
|
||||
background projects** (P1-15), **registry read cache** (P1-16/17), **realpath
|
||||
syscall churn** (P1-18), **`resolveProjectRoot` process spawning** (P1-19).
|
||||
|
||||
---
|
||||
|
||||
## 4. Structural debt (schedule, don't block)
|
||||
|
||||
### 4.1 Two sidebar implementations — Arch 1.1
|
||||
|
||||
Tracked as #12685 (section parity) and #12686 (sidebar drag-and-drop).
|
||||
|
||||
`AgentManagerApp` keeps the legacy `renderBody` (now `SidebarBody.tsx`) and
|
||||
`ProjectSidebarBody.tsx` as a reduced reimplementation. The reimplementation already
|
||||
caused one full outage of multi-project mode (missing `DragDropProvider` crashed the
|
||||
webview render). Missing versus legacy: worktree ordering, drag-and-drop reorder and
|
||||
move-to-section, grouping, busy/navHint/shortcut badges, section auto-rename, stats
|
||||
skeletons.
|
||||
|
||||
- Direction: single-project becomes the degenerate case of multi-project (one
|
||||
implicit pinned project, header row hidden), `SidebarBody.tsx` is deleted.
|
||||
- Do this last: it churns the same message-stamping code as 2.2, needs per-project
|
||||
DnD state, and must keep legacy pixel-identical for the default-off population.
|
||||
Use the visual-regression skill to cover both modes before merging it.
|
||||
|
||||
### 4.2 Ambient project scope via AsyncLocalStorage — Arch 1.3
|
||||
|
||||
`ProjectScope` plus the `this.state`/`this.context` getters make the target project
|
||||
invisible at call sites; any continuation escaping ALS silently falls back to the
|
||||
active project. `provider-lifecycle.ts` shows the intended end state (explicit deps).
|
||||
Keep threading `ctx` explicitly into the remaining handler groups; add a dev-mode log
|
||||
in the `context` getter when a project-stamped message resolves without a scope.
|
||||
|
||||
### 4.3 Per-project webview store — Arch 1.2
|
||||
|
||||
`worktrees()`, `managedSessions()`, `selection()` etc. are single-valued with
|
||||
`memKey()`/`tabKey()` and two "current project" accessors that disagree during the
|
||||
switch window. Long-term: one `createProjectStore(projectId)` per project. For now
|
||||
the gating added to the remember effect contains the known race.
|
||||
|
||||
---
|
||||
|
||||
## 5. Verification gaps to close before the PR
|
||||
|
||||
- **SSE session upsert** is unit-tested (`upsertSession`, `byDirectory`, fresh-skip
|
||||
re-post) but not E2E-verified: the harness backend's basic-auth credentials could
|
||||
not be re-extracted after a window reload, so external session creation was not
|
||||
exercised live. Verify manually: `kilo` a new session in a registered repo from a
|
||||
terminal and watch it appear in that project's sidebar.
|
||||
- **Tab isolation across projects** (per-project buckets) is unit-tested but not
|
||||
E2E-verified with real sessions open in two projects.
|
||||
- **Legacy mode parity**: legacy sidebar and tab bar were smoke-tested (render,
|
||||
select, section create + inline rename), but not the full matrix (delete, DnD
|
||||
reorder, move-to-section, review tab, terminals). The extraction moved ~700 lines
|
||||
of JSX; a visual-regression pass over `SidebarBody`/`TabBar` stories is the
|
||||
cheapest safety net.
|
||||
- **Harness instability**: the isolated VS Code window crashed repeatedly during this
|
||||
work. If it keeps failing on the next pass, say so in the PR rather than claiming
|
||||
coverage that does not exist.
|
||||
|
||||
---
|
||||
|
||||
## Proposed landing sequence
|
||||
|
||||
1. Quick independent fixes: 2.3 (gh noise), P0-4 (trust filter), 1.1's audit + 1.2's
|
||||
fallback (config), 1.2's indexing read/write split if the GET endpoint exists.
|
||||
2. 2.1 (case-fold + registry migration).
|
||||
3. 2.2 (route service, with a two-panel test harness).
|
||||
4. 1.1's config/indexing commits split out and merged separately.
|
||||
5. Arch 1.1 (sidebar collapse) with visual-regression coverage; then the P1 batch.
|
||||
@@ -0,0 +1,740 @@
|
||||
# Agent Manager multi-project uniform UI architecture
|
||||
|
||||
**Status:** Ready, corrected architecture; implementation pending
|
||||
|
||||
**Date:** 2026-07-22
|
||||
|
||||
This plan supersedes the active-body plus background-summary UI described in `agent-manager-multi-project-runtime.md`. The existing project registry, immutable project-root concept, per-project state payloads, and project-tagged stats work are useful foundations. The current read-only `ProjectSummary` and active-project dynamic getter routing are not the target architecture.
|
||||
|
||||
Configuration reads, writes, scopes, drafts, trust, revisions, and release-blocking tests are specified canonically in [`agent-manager-multi-project-configuration.md`](./agent-manager-multi-project-configuration.md). If this plan and that document differ on configuration behavior, the configuration document wins.
|
||||
|
||||
The step-by-step implementation sequence from the current branch is [`agent-manager-multi-project-implementation-handoff.md`](./agent-manager-multi-project-implementation-handoff.md). Implementers should follow that handoff slice by slice rather than treating this architecture document as an unordered task list.
|
||||
|
||||
## Product requirement
|
||||
|
||||
Every expanded project permanently renders the real existing Agent Manager sidebar UI:
|
||||
|
||||
- the real Local card;
|
||||
- the real worktree cards, including their current menus and actions;
|
||||
- the real session list, which may initially use a simplified layout but must contain real live session data;
|
||||
- live git stats, PR state, run/setup state, stale state, and session state owned by that project.
|
||||
|
||||
Selecting or interacting with a project may change the shared detail pane and top toolbar. It must not remove, replace, downgrade, or remount any expanded project body. There is no active-body versus background-summary rendering mode.
|
||||
|
||||
The shared detail pane remains singular. The sidebar displays all expanded projects concurrently; the detail pane displays one selected project/local/worktree/session target at a time.
|
||||
|
||||
## Architecture verdict on the current worktree
|
||||
|
||||
The current implementation is a valid partial runtime foundation, but it is not safe to extend directly into fully interactive background cards.
|
||||
|
||||
Keep and evolve:
|
||||
|
||||
- `ProjectRegistry` and the pinned-workspace project concept;
|
||||
- immutable root ownership in `ProjectContext`;
|
||||
- `initContextState` extraction;
|
||||
- project-stamped state/stats/PR messages;
|
||||
- per-project webview stores;
|
||||
- reusable `WorktreeItem`, `SectionHeader`, `WorktreeSectionActions`, and `UnassignedSessionsSection` components;
|
||||
- the application-scoped feature flag.
|
||||
|
||||
Replace or refactor before exposing interactive multi-project UI:
|
||||
|
||||
- delete `ProjectSummary`; do not add more behavior to it;
|
||||
- remove the rule that only `project.active` renders `renderBody()`;
|
||||
- stop routing normal actions through `contexts.active()` and optional `projectId`;
|
||||
- replace raw session/worktree/local UI keys with project-qualified references;
|
||||
- replace the active singleton poller plus background poller split with uniform context-owned polling;
|
||||
- make context initialization, mutation, suspension, and disposal explicit and generation-guarded;
|
||||
- make the one embedded `KiloProvider` project/session-route aware.
|
||||
- separate activation-bound runtime config from the Settings editor's immutable read/write binding.
|
||||
|
||||
## Core model
|
||||
|
||||
### Project identity
|
||||
|
||||
A project is one canonical Git repository root in the current extension host.
|
||||
|
||||
```ts
|
||||
type ProjectId = string
|
||||
|
||||
interface ProjectRef {
|
||||
projectId: ProjectId
|
||||
}
|
||||
|
||||
interface WorktreeRef extends ProjectRef {
|
||||
worktreeId: string
|
||||
}
|
||||
|
||||
interface SessionRef extends ProjectRef {
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
type SidebarTarget =
|
||||
| { projectId: ProjectId; kind: "local" }
|
||||
| { projectId: ProjectId; kind: "worktree"; worktreeId: string }
|
||||
| { projectId: ProjectId; kind: "session"; sessionId: string }
|
||||
```
|
||||
|
||||
Raw backend and repository-local IDs remain unchanged. Composite references are required at every panel/runtime boundary so identical local sentinels, worktree IDs, section IDs, or session IDs cannot cross projects.
|
||||
|
||||
Project identity must include the extension-host URI scheme and authority in addition to the canonical root. Adding a project must also resolve canonical `git-common-dir` and reject another exposed project sharing that common Git directory. Linked worktrees of one repository cannot be registered as independent projects in the first release.
|
||||
|
||||
### Active project means detail selection only
|
||||
|
||||
`activeProjectId` is not a rendering mode and not an implicit mutation target.
|
||||
|
||||
It means:
|
||||
|
||||
- which project owns the current shared detail/chat/diff/terminal pane;
|
||||
- which project the global top toolbar and global New Session/Run buttons target;
|
||||
- which project/worktree directory supplies runtime-effective Kilo configuration in the one embedded `KiloProvider`.
|
||||
|
||||
It never selects a Settings write target. Settings bindings are explicit and remain unchanged when detail selection changes.
|
||||
|
||||
Project expansion controls rendering. Every `project.expanded` project renders one stable `ProjectSidebarBody` keyed by `projectId`, regardless of active status.
|
||||
|
||||
### Atomic selection
|
||||
|
||||
Do not send `selectProject` followed by an unqualified worktree/session action. VS Code message handlers are asynchronous and are not a transaction queue.
|
||||
|
||||
Use one atomic selection intent:
|
||||
|
||||
```ts
|
||||
interface ActivateSelectionMessage {
|
||||
type: "agentManager.activateSelection"
|
||||
target: SidebarTarget
|
||||
}
|
||||
```
|
||||
|
||||
The coordinator validates the project and target, ensures the context is ready, activates the project-specific Kilo route, then emits one project-qualified activation result. The sidebar body stays mounted; only shared detail state changes.
|
||||
|
||||
Mutations that do not need the detail pane, such as rename, delete, section edits, run, setup, open window, PR actions, or worktree creation, execute directly against their explicit `ProjectRef` or `WorktreeRef`. They do not activate another project as a side effect.
|
||||
|
||||
## Configuration architecture
|
||||
|
||||
See [`agent-manager-multi-project-configuration.md`](./agent-manager-multi-project-configuration.md) for the complete contract.
|
||||
|
||||
### Verified blocking failure
|
||||
|
||||
The current config protocol is unsafe for multi-project use:
|
||||
|
||||
- `KiloProvider.fetchAndSendConfig()` resolves a mutable current directory and emits one unqualified `configLoaded` value (`packages/kilo-vscode/src/KiloProvider.ts:2513`).
|
||||
- `ConfigProvider` owns one global/project/effective draft and sends an unqualified `updateConfig` (`packages/kilo-vscode/webview-ui/src/context/config.tsx:54`, `:243`).
|
||||
- `KiloProvider.handleUpdateConfig()` resolves `getWorkspaceDirectory()` again at save time (`packages/kilo-vscode/src/KiloProvider.ts:2982`). A draft loaded for project A can therefore be written to project B after detail activation changes.
|
||||
- The backend derives the project target from request directory and has no target or revision precondition (`packages/opencode/src/config/config.ts:944`, `packages/opencode/src/kilocode/config/config.ts:67`). Concurrent editors can overwrite one another, and a newly created higher-priority config file can redirect a pending write.
|
||||
- Hidden scope splitting currently writes `commit_message` and `indexing.enabled` to the project while most fields go global (`packages/kilo-vscode/webview-ui/src/utils/config-scope.ts:3`). The UI does not make this target choice explicit.
|
||||
- Project config writes also exist outside the save bar. In particular, provider disconnect can call `saveProject()` using the mutable provider workspace directory (`packages/kilo-vscode/src/provider-actions.ts:252`). Disabling only `handleUpdateConfig()` is insufficient.
|
||||
|
||||
This is a release blocker, not a follow-up cleanup.
|
||||
|
||||
### Product decision and staged order
|
||||
|
||||
Preserve useful project-local editing such as `commit_message.prompt` and repository indexing rules. Indexing enablement is machine-local project consent, default off, and cannot be granted by repository config. Replace hidden active-directory targeting with explicit `User | Project` scope, a required Settings project selector for Project scope, an opaque immutable binding, and optimistic concurrency by target revision.
|
||||
|
||||
Do not maintain one write model for flag-off mode and another for multi-project mode. Single-project mode uses the same explicit protocol, with a narrow adapter that injects the pinned `ProjectRef` for reads and open-file actions.
|
||||
|
||||
### Separate runtime config from Settings editor config
|
||||
|
||||
The current `ConfigProvider` conflates two different state machines. Split them:
|
||||
|
||||
- **Runtime config** is read-only UI state for the selected detail/session route. It is keyed by `{ projectId, directory, activationGeneration }`, follows atomic detail activation, and drives chat, providers, agents, features, display, MCP, indexing, and sandbox behavior.
|
||||
- **Settings editor config** is a scoped layer editor keyed by an immutable binding. It never follows detail activation and its draft is never applied optimistically to runtime config.
|
||||
|
||||
Use distinct messages. Names may follow repository conventions, but the fields and invariants are mandatory:
|
||||
|
||||
```ts
|
||||
interface RuntimeConfigLoaded {
|
||||
type: "runtimeConfigLoaded"
|
||||
route: {
|
||||
projectId: ProjectId
|
||||
directory: string
|
||||
activationGeneration: number
|
||||
}
|
||||
config: Config
|
||||
features: FeatureFlags
|
||||
}
|
||||
|
||||
type ConfigScope = "global" | "project"
|
||||
|
||||
interface ConfigTarget {
|
||||
scope: ConfigScope
|
||||
path: string
|
||||
revision: string
|
||||
exists: boolean
|
||||
writable: boolean
|
||||
}
|
||||
|
||||
interface SettingsBinding {
|
||||
id: string
|
||||
connectionGeneration: number
|
||||
scope: ConfigScope
|
||||
project?: {
|
||||
projectId: ProjectId
|
||||
root: string
|
||||
generation: number
|
||||
}
|
||||
directory: string
|
||||
target: ConfigTarget
|
||||
}
|
||||
|
||||
interface ReadSettingsConfig {
|
||||
type: "settingsConfig.read"
|
||||
requestId: string
|
||||
scope: ConfigScope
|
||||
projectId?: ProjectId
|
||||
}
|
||||
|
||||
interface SettingsConfigSnapshot {
|
||||
type: "settingsConfig.snapshot"
|
||||
requestId: string
|
||||
binding: SettingsBinding
|
||||
preview?: {
|
||||
projectId: ProjectId
|
||||
root: string
|
||||
generation: number
|
||||
directory: string
|
||||
}
|
||||
targetConfig: Config
|
||||
effective: Config
|
||||
global: Config
|
||||
project: Config
|
||||
fields: Record<string, ResolvedConfigField>
|
||||
collections: Record<string, ResolvedConfigField[]>
|
||||
}
|
||||
|
||||
interface WriteSettingsConfig {
|
||||
type: "settingsConfig.write"
|
||||
requestId: string
|
||||
bindingId: string
|
||||
set: Partial<Config>
|
||||
unset: string[][]
|
||||
}
|
||||
```
|
||||
|
||||
For a global read, `projectId` is optional preview context used only to explain project shadowing. It never changes the global target. A project read requires `projectId` and always resolves the immutable `ProjectContext.root`, not the active worktree or session.
|
||||
|
||||
For User scope, preview identity is presentation state, not part of the editable target identity. The controller keeps the global target binding and draft separate from the latest project preview. A clean preview change may replace both from one fresh snapshot. A dirty preview change updates only `preview`, effective/source metadata, and project raw data. It retains the original global target revision; if the fresh read reports a different global path or revision, mark the draft stale instead of silently rebasing it.
|
||||
|
||||
`targetConfig` is the parsed raw content of the one file the API will patch. It is not the aggregate `global` or `project` layer. The form edits this raw target value so it never copies values inherited from another file into the target. The aggregate values remain in the snapshot solely for source, inheritance, and effective-preview UI. Existing JSONC comments remain server-side raw text and are preserved by patching; the webview does not round-trip serialized file contents.
|
||||
|
||||
Parse `targetConfig` from raw JSONC without expanding `{env:}` or `{file:}` variables, so the Settings protocol never turns placeholders into secret values. If the exact target is syntactically or structurally invalid, return diagnostics and `writable: false` for form editing rather than treating it as `{}` and overwriting it. The recovery action is **Open file**. Source metadata must cover every path rendered by Settings, not only the current hand-maintained overlay field list.
|
||||
|
||||
The extension owns an in-memory binding registry. Binding IDs are opaque, unpredictable, and scoped to one provider/webview lifecycle. The webview returns only `bindingId`, not a client-authoritative path or project envelope. On write the extension must:
|
||||
|
||||
1. look up `bindingId` and reject unknown or expired bindings;
|
||||
2. verify that the project still exists, has the same context generation, and remains trusted;
|
||||
3. capture the stored binding before the first `await`;
|
||||
4. send the stored directory and expected target to the backend without calling `getWorkspaceDirectory()`, `contexts.active()`, or any fallback;
|
||||
5. route success or failure by `{ requestId, binding.id }` only.
|
||||
|
||||
A binding expires after a successful save, backend reconnect, trust revocation, project removal, or context generation change. Success returns a new snapshot and binding. Cached snapshots are keyed by binding/scope/project identity; there is no singleton `cachedConfigMessage` for Settings.
|
||||
|
||||
### Backend read/write and optimistic concurrency contract
|
||||
|
||||
Evolve the Kilo-owned `/config/overlay` API rather than adding project identity to generic `Config.update`. The backend does not know Agent Manager `projectId`; it enforces the routed directory and filesystem target while the extension enforces project identity.
|
||||
|
||||
`GET /config/overlay` continues to take explicit `directory` and `scope`, but returns:
|
||||
|
||||
```ts
|
||||
interface ConfigOverlaySnapshot {
|
||||
context: { directory: string; worktree?: string }
|
||||
scope: ConfigScope
|
||||
targetConfig: Config
|
||||
effective: Config
|
||||
global: Config
|
||||
project: Config
|
||||
sources: ConfigSource[]
|
||||
targets: {
|
||||
global: ConfigTarget
|
||||
project: ConfigTarget
|
||||
active: ConfigTarget
|
||||
}
|
||||
fields: Record<string, ResolvedConfigField>
|
||||
collections: Record<string, ResolvedConfigField[]>
|
||||
}
|
||||
```
|
||||
|
||||
The revision is a SHA-256 fingerprint of scope, canonical target path, existence marker, and exact file bytes. It is not an mtime. Exact bytes are required so comment-only JSONC edits and delete/recreate cycles with different content cause conflicts. The missing-file revision includes the intended canonical path. An ABA delete/recreate with identical bytes may retain the same revision; that state is content-equivalent and does not lose a user edit.
|
||||
|
||||
`PATCH /config/overlay?directory=...` accepts exactly one scope per request:
|
||||
|
||||
```ts
|
||||
interface ConfigOverlayWrite {
|
||||
scope: ConfigScope
|
||||
set?: Record<string, unknown>
|
||||
unset?: string[][]
|
||||
expected: {
|
||||
path: string
|
||||
revision: string
|
||||
}
|
||||
}
|
||||
|
||||
interface ConfigOverlayWriteResult {
|
||||
outcome: "applied" | "applied_but_overridden"
|
||||
changed: boolean
|
||||
overriddenPaths: string[][]
|
||||
snapshot: ConfigOverlaySnapshot
|
||||
}
|
||||
```
|
||||
|
||||
The server performs the following transaction:
|
||||
|
||||
1. Resolve the authoritative target from the routed instance directory and requested scope. Never accept `expected.path` as an arbitrary destination.
|
||||
2. Canonicalize and compare the resolved and expected paths. Reject a changed target, including a newly created higher-priority config file.
|
||||
3. Acquire a cross-process lock keyed by canonical target path. Project writes must use the same locking discipline already used for global config.
|
||||
4. Inside the lock, re-resolve the target, read exact bytes, recompute the revision, and reject a mismatch.
|
||||
5. Apply `set`/`unset` to the raw target layer, preserve JSONC comments where supported, validate the result, and atomically replace the target using a temporary file in the same directory. Do not expose a partially written config.
|
||||
6. Invalidate the affected config instances, reload the overlay, and return the authoritative post-write snapshot. Do not fabricate optimistic config if refresh fails.
|
||||
7. Emit a config event containing `scope`, routed `directory`, canonical `target`, and new `revision`. Global changes invalidate all runtime config caches; project changes invalidate only matching directory/project caches.
|
||||
|
||||
`applied_but_overridden` is success. For each `set` leaf, the refreshed overlay identifies whether a higher layer still wins, such as project config shadowing a user value or managed/runtime config shadowing a project value. The UI keeps the saved value and explains why effective behavior did not change.
|
||||
|
||||
Do not send global and project patches in one webview message or pretend that two files save atomically. User and Project scope maintain independent drafts and save independently. VS Code extension preferences such as autocomplete settings are a third, visibly separate store with their own request acknowledgements.
|
||||
|
||||
Domain actions outside Settings must not perform client-side read-modify-write against a mutable effective config. Provider/custom-provider/work-style actions are User-global operations and should call either the same revision-aware User endpoint or a narrow backend mutation that merges named fields under the target lock. Permission Dock "always" rules remain a narrow global rule mutation tied to the exact pending request/session route; that session directory validates the request but does not select a project config target. All such mutations emit the revisioned config event, so a dirty Settings editor becomes stale rather than being overwritten.
|
||||
|
||||
Expected typed failures are:
|
||||
|
||||
- `unknown_project` or `stale_project_generation` from the extension;
|
||||
- `untrusted_project` or `workspace_untrusted` from the extension;
|
||||
- `binding_mismatch` or `binding_expired` from the extension;
|
||||
- HTTP 409 `config_target_changed` with the current target descriptor;
|
||||
- HTTP 409 `config_revision_conflict` with the current target descriptor and fresh snapshot when available;
|
||||
- HTTP 403 `config_target_not_writable` or project target escaping its trusted root;
|
||||
- HTTP 422 `config_invalid` with schema/parse details;
|
||||
- an I/O failure with no success acknowledgement.
|
||||
|
||||
Every failure preserves the draft and names the scope/project/path. A config event is never treated as confirmation of the user's save; only a matching write response may clear that draft.
|
||||
|
||||
### Draft and project-switch behavior
|
||||
|
||||
- Detail activation from project A to B does not change the Settings selector, binding, values, draft, save state, or target.
|
||||
- A User Settings draft is global and survives all detail-project switches.
|
||||
- Project scope uses an explicit trusted project selector that never follows detail activation automatically.
|
||||
- On first open, the selector may initialize once from the pinned/selected project for convenience, but that choice is immediately captured as Settings state.
|
||||
- Choosing another Settings project with a dirty draft prompts `Save`, `Discard`, or `Stay`. Drafts are never carried to a different project.
|
||||
- Clean selector changes issue a new request and ignore out-of-order responses by `requestId`.
|
||||
- A save captures one binding. Switching visible settings while it is in flight does not redirect it; the result updates only the original binding store and identifies that target in its notification.
|
||||
- External config events refresh a clean binding. For a dirty or saving binding they mark it stale and show a changed-on-disk banner without replacing local edits.
|
||||
- A 409 preserves the draft and disables blind retry. The first version offers `Reload`, `Open file`, and `Discard`; it does not auto-merge or force overwrite. A future three-way merge may use the original snapshot, fresh snapshot, and draft.
|
||||
|
||||
### Scope UX
|
||||
|
||||
The first release presents explicit `User | Project` scope, a required selector in Project scope, source badges, and separate binding-keyed drafts. The selected scope applies to the entire operation. Remove hidden `splitConfigByScope`; no setting silently chooses a different file. Inherited user values remain visible in Project scope with `Override`/`Reset to inherited` affordances backed by `set`/`unset`.
|
||||
|
||||
### Project root, worktree, and trust semantics
|
||||
|
||||
- A Project Settings binding targets the registered checkout root held by `ProjectContext.root`. It never targets the last selected Local/worktree/session card.
|
||||
- Runtime config remains directory-correct: Local uses the project root, and a worktree session uses its exact routed worktree directory. Branch-specific config in a managed worktree can therefore differ at runtime.
|
||||
- Editing a project's root config does not claim to update already-created worktree checkouts. Editing a worktree config from Settings is out of scope for the first Project Settings release. A future worktree editor must use an explicit `WorktreeRef` and its own immutable binding.
|
||||
- The backend resolver may choose an existing root or `.kilo`/`.kilocode` config according to current precedence, but the expected target must remain within the trusted project checkout after canonical/symlink resolution. A symlink escape is read-only and must not be patched through the API.
|
||||
- For an existing target, canonicalize the file with `realpath`. For a missing target, canonicalize its nearest existing ancestor and append the remaining path components before checking containment. Global writes must similarly remain under the canonical `Global.Path.config` root. Do not rely on a lexical prefix check.
|
||||
- Additional Agent Manager projects must be registry-trusted before project overlay evaluation, opening/creating project config, or project writes. Global User Settings remain editable without trusting a project.
|
||||
- The pinned project is trusted for config evaluation only when the VS Code workspace is trusted. Do not treat `pinned` as an unconditional trust grant.
|
||||
- Trust is stored outside the repository in the extension registry/global state. A project config file cannot grant its own trust. Revoking trust invalidates all bindings and project runtime caches.
|
||||
|
||||
### Concrete implementation sequence for config safety
|
||||
|
||||
1. In `packages/opencode/src/kilocode/config/overlay.ts`, replace path-only targets with revisioned target descriptors and add generic changed-path source resolution.
|
||||
2. Add a Kilo-owned compare-and-swap writer under `packages/opencode/src/kilocode/config/` that resolves, confines, locks, fingerprints, patches, validates, and atomically replaces one target. Keep shared `packages/opencode/src/config/config.ts` changes to minimal marked delegation/invalidation hooks.
|
||||
3. Update `packages/opencode/src/kilocode/server/httpapi/groups/config-console.ts` and `handlers/config-console.ts` with the write precondition, result, and typed error contracts. Regenerate `packages/sdk/js/`.
|
||||
4. Extract a config binding controller under `packages/kilo-vscode/src/kilo-provider/` and have `KiloProvider.ts` delegate reads/writes to it. The controller receives explicit project-route resolution; it never reads mutable active workspace state during a bound operation.
|
||||
5. Replace `configLoaded`, `configUpdated`, and unqualified `updateConfig` in `webview-ui/src/types/messages/` with runtime and Settings message families. Keep a temporary single-project adapter only at the protocol edge.
|
||||
6. Split `webview-ui/src/context/config.tsx` into activation-bound runtime state and binding-keyed Settings editor state. Settings components consume the editor context; non-settings consumers continue to use runtime-effective config.
|
||||
7. Remove `webview-ui/src/utils/config-scope.ts` after migrating every control to explicit scope. Preserve explicit project editing for commit messages and repository indexing rules; move indexing enablement to machine-local project consent.
|
||||
8. Audit all extension config mutators and route user-config imports, resets, custom providers, work-style writes, project writes, and save-bar writes through a revision-aware endpoint or backend atomic field-level mutation. A project-sourced item must never fall back to an active directory write.
|
||||
9. Make open-file actions take `ProjectRef`, resolve `ProjectContext.root`, verify trust, and show the exact path. For a missing target, open an unsaved document at that explicit path or require an explicit create action; never create it merely by visiting Settings.
|
||||
10. Update config events and cache invalidation so scope/directory/revision are retained end to end.
|
||||
|
||||
Focused tests belong in:
|
||||
|
||||
- `packages/opencode/test/kilocode/server/config-overlay.test.ts` for raw target snapshots, target changes, 409 revisions, source shadowing, locks, symlink confinement, and response outcomes;
|
||||
- `packages/opencode/test/kilocode/project-config-update.test.ts` for target selection and atomic writes from repository roots/nested directories;
|
||||
- a new Kilo-owned backend unit test beside those files only if the compare-and-swap writer needs direct fault-injection coverage;
|
||||
- `packages/kilo-vscode/tests/unit/config-utils.test.ts`, replacing singleton `ConfigState` assumptions with binding/request-aware draft tests;
|
||||
- `packages/kilo-vscode/tests/unit/config-scope.test.ts`, deleted when `splitConfigByScope` is removed and replaced by tests proving one explicit scope per save;
|
||||
- Agent Manager route/context tests for immutable project-root resolution, trust revocation, removal, and generation changes;
|
||||
- KiloProvider protocol tests for A-to-B activation during reads and writes, out-of-order responses, cache partitioning, and auditing non-save-bar mutators.
|
||||
|
||||
## One `.kilo/agent-manager.json` per project
|
||||
|
||||
Yes, every project has its own repository-local state file:
|
||||
|
||||
```text
|
||||
project-a/.kilo/agent-manager.json
|
||||
project-b/.kilo/agent-manager.json
|
||||
project-c/.kilo/agent-manager.json
|
||||
```
|
||||
|
||||
This is already structurally supported because `WorktreeStateManager` derives its file from its immutable constructor root. The final architecture makes this ownership explicit and safe.
|
||||
|
||||
### Ownership rules
|
||||
|
||||
Each `ProjectContext` owns exactly one:
|
||||
|
||||
- canonical repository root and canonical Git common directory;
|
||||
- `WorktreeStateManager(root)` writing only `<root>/.kilo/agent-manager.json`;
|
||||
- `WorktreeManager(root)`;
|
||||
- `SetupScriptService(root)` and project run service;
|
||||
- diff/import/branch-naming services;
|
||||
- git stats and PR pollers;
|
||||
- worktree/session/section/order/run/stale caches;
|
||||
- mutation queue and initialization promise.
|
||||
|
||||
The global project registry stores descriptors only. It never stores worktrees, sections, sessions, tab order, or repository behavior. The pinned project is derived from the current workspace and remains outside the registry.
|
||||
|
||||
### Initialization
|
||||
|
||||
The first explicit expansion of a trusted project calls one single-flight `ensureReady()` promise:
|
||||
|
||||
1. validate root and Git common directory;
|
||||
2. construct project-owned services;
|
||||
3. update that repository's local Git exclude;
|
||||
4. load only that repository's `.kilo/agent-manager.json`;
|
||||
5. discover and reconcile only that repository's worktrees;
|
||||
6. register Local and worktree session routes for that project;
|
||||
7. start context-owned polling if the project is expanded and the panel is visible;
|
||||
8. post one generation-stamped project snapshot.
|
||||
|
||||
Repeated expansion must reuse the same initialization promise. Collapse/removal during initialization records the desired lifecycle transition and prevents late completion from posting or mutating a replacement context.
|
||||
|
||||
### Writes and disposal
|
||||
|
||||
All mutations for one project execute through that context's serialized mutation queue. An operation captures the context and generation before its first await and never re-resolves through `contexts.active()` afterward.
|
||||
|
||||
Collapse:
|
||||
|
||||
- leaves the project registered;
|
||||
- stops pollers/watchers after current operations reach a safe boundary;
|
||||
- flushes its own state file;
|
||||
- retains session routes required for already-running backend sessions;
|
||||
- never aborts a session merely because the UI collapsed.
|
||||
|
||||
Removal:
|
||||
|
||||
- never deletes `.kilo/agent-manager.json`, worktrees, branches, or sessions;
|
||||
- refuses removal while a project mutation/run requires ownership, unless an explicit coordinated shutdown is implemented;
|
||||
- awaits context disposal and state flush before deleting the registry descriptor;
|
||||
- invalidates context generation before awaits so late callbacks are ignored.
|
||||
|
||||
Panel disposal awaits all initialized contexts before shared services are disposed.
|
||||
|
||||
Two VS Code windows writing the same repository state file remain the same pre-existing concurrency boundary as today's single-project Agent Manager. This plan prevents one panel from creating two contexts for the same Git common directory; cross-window file coordination is not expanded in this feature.
|
||||
|
||||
## ProjectContext lifecycle
|
||||
|
||||
`ProjectContext` becomes a lifecycle owner rather than only a lazy service container.
|
||||
|
||||
```ts
|
||||
type ProjectLifecycle =
|
||||
| "cold"
|
||||
| "initializing"
|
||||
| "ready"
|
||||
| "suspended"
|
||||
| "disposing"
|
||||
| "disposed"
|
||||
|
||||
interface ProjectContext {
|
||||
readonly id: ProjectId
|
||||
readonly root: string
|
||||
readonly commonGitDir: string
|
||||
readonly generation: number
|
||||
ensureReady(): Promise<ReadyProjectContext>
|
||||
run<T>(operation: (ctx: ReadyProjectContext) => Promise<T>): Promise<T>
|
||||
suspend(): Promise<void>
|
||||
dispose(): Promise<void>
|
||||
}
|
||||
```
|
||||
|
||||
Every async init, poll, mutation, diff, run, import, setup, PR update, branch naming, and session operation checks context identity and generation before committing state or posting to the webview.
|
||||
|
||||
## Strict project-qualified protocol
|
||||
|
||||
When multi-project mode exposes more than one project, every repository operation requires project identity.
|
||||
|
||||
Required project qualification includes:
|
||||
|
||||
- worktree create/delete/rename/import/order/section/open/PR actions;
|
||||
- Local and worktree session create/open/close/fork/promote/share/unshare actions;
|
||||
- prompt, abort, permission, question, command, sandbox, and context actions;
|
||||
- terminal create/input/resize/close/show actions;
|
||||
- diff/watch/apply/revert actions;
|
||||
- run/setup/configuration actions;
|
||||
- all state/session/diff/run/terminal/setup/PR output messages.
|
||||
|
||||
Missing, unknown, mismatched, inactive-when-required, or ambiguous identities return a typed route error. They never fall back to the current project or `workspaceFolders[0]`.
|
||||
|
||||
Single-project mode retains a narrow compatibility adapter that injects the pinned project identity. Core handlers always operate on explicit references.
|
||||
|
||||
## One KiloProvider with a project/session route service
|
||||
|
||||
Keep one embedded `KiloProvider`. Multiple providers would duplicate event handling, terminal registrations, caches, and current-session state for one webview.
|
||||
|
||||
Add an Agent Manager route service:
|
||||
|
||||
```ts
|
||||
interface SessionRoute extends SessionRef {
|
||||
directory: string
|
||||
generation: number
|
||||
}
|
||||
```
|
||||
|
||||
It must:
|
||||
|
||||
- register explicit routes for every Local session at its project root;
|
||||
- register every worktree session at its worktree path;
|
||||
- let child sessions inherit the parent route;
|
||||
- validate directory ownership;
|
||||
- detect ambiguous raw session IDs;
|
||||
- supply the exact directory to every SDK operation, including share/unshare;
|
||||
- remove current-root and workspace-root fallback for identified Agent Manager sessions;
|
||||
- refresh sessions independently for every initialized project and emit real project-stamped `SessionInfo` records.
|
||||
|
||||
The shared detail pane consumes the selected `SessionRef`. Every sidebar body consumes its project's real session store. Background session UI may initially be visually simpler, but it must not display fabricated titles or placeholder session objects.
|
||||
|
||||
Project activation in `KiloProvider` must invalidate and refresh project-scoped config, providers, agents, commands, skills, MCP state, indexing, sandbox state, and Git status under an activation generation guard.
|
||||
|
||||
## Global services with composite keys
|
||||
|
||||
Some services remain panel-global to avoid duplicate VS Code registrations, but all associations use composite references.
|
||||
|
||||
- terminal mappings: `ProjectId + SessionId` or `ProjectId + WorktreeId`;
|
||||
- run state: keyed by `WorktreeRef`, including project Local;
|
||||
- visible/presence state: keyed by `SessionRef`;
|
||||
- tabs and sidebar selection: keyed by `SidebarTarget`;
|
||||
- diff/apply/revert state: keyed by `ProjectRef` and target;
|
||||
- pending setup/create/delete state: keyed by project-qualified resource refs.
|
||||
|
||||
Remove unknown-worktree-to-current-root terminal fallback. A context supplies a validated CWD to the global terminal manager.
|
||||
|
||||
Global backend events are routed once by event directory and registered session route. Status, tool, orchestration, permission, and question events retain their directory and enter exactly one owning project context. A cold/collapsed context is not initialized by an unsolicited event.
|
||||
|
||||
## Uniform project sidebar UI
|
||||
|
||||
### One body implementation
|
||||
|
||||
Extract the existing real `renderBody()` from `AgentManagerApp.tsx` into `ProjectSidebarBody`. Do not maintain active and background implementations.
|
||||
|
||||
`ProjectSidebarBody` receives a stable project-scoped store and project-qualified action callbacks. It reuses:
|
||||
|
||||
- the existing Local card markup;
|
||||
- `WorktreeSectionActions`;
|
||||
- `WorktreeItem`;
|
||||
- `SectionHeader` and current drag/drop behavior;
|
||||
- `UnassignedSessionsSection`;
|
||||
- existing rename/delete/open/PR/run/setup/session affordances.
|
||||
|
||||
Render it for every expanded project:
|
||||
|
||||
```tsx
|
||||
<For each={projects()}>
|
||||
{(project) => (
|
||||
<ProjectAccordion project={project}>
|
||||
<Show when={project.expanded}>
|
||||
<ProjectSidebarBody projectId={project.id} />
|
||||
</Show>
|
||||
</ProjectAccordion>
|
||||
)}
|
||||
</For>
|
||||
```
|
||||
|
||||
The body is keyed by `projectId`, not active status. Changing detail selection cannot remount it.
|
||||
|
||||
### Per-project webview stores
|
||||
|
||||
Replace active-only shared sidebar signals with `Map<ProjectId, ProjectSidebarStore>` or an equivalent Solid store registry.
|
||||
|
||||
Each store owns:
|
||||
|
||||
- state/worktrees/sections/order/stale state;
|
||||
- real `SessionInfo` and managed-session state;
|
||||
- git stats and local stats;
|
||||
- PR and run/setup state;
|
||||
- pending delete/rename/create state;
|
||||
- sidebar search/order/collapse state where repository-owned;
|
||||
- generation for dropping stale output.
|
||||
|
||||
Only shared detail/chat/terminal state remains global. The top toolbar reads the selected detail target's project store.
|
||||
|
||||
`project-live.ts` may be evolved into this complete store registry. It must stop mirroring active payloads into a separate sidebar signal set.
|
||||
|
||||
### Scrolling and layout
|
||||
|
||||
The project list becomes the sidebar scroll container. Individual project bodies must not use a global `50vh` cap that makes two projects fight for viewport height. Section and project collapse state controls density; normal browser scrolling exposes all expanded worktrees.
|
||||
|
||||
### UI behavior
|
||||
|
||||
- Clicking a Local/worktree/session card emits one atomic project-qualified selection.
|
||||
- Rename/delete/run/open/section/PR actions execute against the clicked card's project without changing sidebar structure.
|
||||
- The active project may receive an emphasis style, but no different markup.
|
||||
- Active project chevrons are not disabled; expansion and detail selection are separate concepts.
|
||||
- Global New Session/Run buttons target the last selected detail project.
|
||||
- New Worktree accepts an explicit target project, defaulting to the selected detail project.
|
||||
|
||||
## Uniform polling and live state
|
||||
|
||||
Every ready expanded context owns the same poller set. There is no active singleton poller plus background poller split.
|
||||
|
||||
Pollers emit `{ projectId, generation }` and update only their owning context/store. Stop/suspend increments generation before clearing timers so in-flight Git/PR results are dropped.
|
||||
|
||||
Context state is pushed after every project mutation, not only initial expansion or active-project changes. This includes worktree/session/section/order/run/setup changes in background projects.
|
||||
|
||||
Panel visibility changes poll cadence uniformly across all expanded contexts.
|
||||
|
||||
## Registry correctness
|
||||
|
||||
Registry mutations use one in-process queue. Each mutation re-reads persisted storage, validates it, applies one mutation, writes once, then updates memory. Trust/remove persistence failures are user-visible and do not leave UI state ahead of storage.
|
||||
|
||||
Registry descriptors include canonical root, canonical Git common directory, URI scheme/authority, order, label, trust, and added time. They do not contain Agent Manager repository state.
|
||||
|
||||
## Implementation order
|
||||
|
||||
### Slice 0: configuration safety blocker
|
||||
|
||||
1. Introduce revisioned `/config/overlay` reads and compare-and-swap writes, first for User scope and with the same generic implementation covered for Project scope.
|
||||
2. Separate runtime-effective config messages/state from immutable Settings bindings and drafts.
|
||||
3. Add explicit `User | Project` Settings scope and an immutable trusted project selector/binding for Project writes.
|
||||
4. Remove hidden `splitConfigByScope` persistence and audit every direct/indirect config mutation, including provider disconnect, import/reset, custom providers, work-style presets, Permission Dock, and indexing.
|
||||
5. Make config events scope/directory/target/revision aware and generation-guard runtime activation refreshes.
|
||||
6. Add target-switch, stale-binding, revision-conflict, trust, and draft-preservation tests.
|
||||
|
||||
Do not enable multi-project broadly before this slice passes.
|
||||
|
||||
### Slice 1: routing and identity blockers
|
||||
|
||||
1. Add project-qualified refs and strict protocol types.
|
||||
2. Add the project/session route service and explicit Local routes.
|
||||
3. Remove Agent Manager current/workspace-root fallback for identified operations.
|
||||
4. Add atomic project-qualified selection.
|
||||
5. Convert panel-global maps to composite keys.
|
||||
6. Add strict routing and raw-ID collision tests.
|
||||
|
||||
Do not expose interactive background controls before this slice passes.
|
||||
|
||||
### Slice 2: context lifecycle and service ownership
|
||||
|
||||
1. Add single-flight initialization, lifecycle state, generation, and mutation queue to `ProjectContext`.
|
||||
2. Move root-bound diff/import/run/setup/branch-naming/poller ownership behind context APIs.
|
||||
3. Replace dynamic active getters inside async mutations with captured contexts.
|
||||
4. Add suspend/remove/dispose coordination.
|
||||
5. Reject duplicate Git common directories and serialize registry writes.
|
||||
6. Route global backend events by directory.
|
||||
|
||||
### Slice 3: complete per-project stores
|
||||
|
||||
1. Refresh and store real sessions per project.
|
||||
2. Emit all relevant output with project and generation.
|
||||
3. Evolve `project-live.ts` into a complete project sidebar store registry.
|
||||
4. Make polling uniform and generation-safe.
|
||||
5. Add background mutation/state propagation tests.
|
||||
|
||||
### Slice 4: uniform real UI
|
||||
|
||||
1. Extract the current `renderBody()` intact into `ProjectSidebarBody`.
|
||||
2. Parameterize it by one project store and project-qualified callbacks.
|
||||
3. Render one body per expanded project.
|
||||
4. Delete `ProjectSummary` and its CSS.
|
||||
5. Separate project expansion from detail activation.
|
||||
6. Add project-aware New Worktree targeting.
|
||||
7. Add component/visual tests proving no body remount or structural change on selection.
|
||||
|
||||
### Slice 5: validation and release gating
|
||||
|
||||
1. Run focused lifecycle/router/context tests after each slice.
|
||||
2. From `packages/opencode/`, run the focused config overlay/project-update tests and CLI typecheck. Run the opencode annotation and Promise-facade guards for shared-file hooks.
|
||||
3. Regenerate the JS SDK after endpoint schema changes and verify generated consumers compile; never hand-edit generated sources.
|
||||
4. From `packages/kilo-vscode/`, run typecheck, lint, focused/unit tests, knip, compile/package, and architecture guards.
|
||||
5. Manually exercise two repositories with multiple worktrees and sessions in both, including a dirty User draft during project activation, an external config edit conflict, project trust revocation, project removal, and project-config open targeting.
|
||||
6. Keep the feature flag default off until all acceptance criteria pass.
|
||||
|
||||
## Blocking tests
|
||||
|
||||
The feature is not complete until tests prove:
|
||||
|
||||
1. Two concurrent expands initialize one context once.
|
||||
2. Collapse/remove during initialization produces no late state or poller posts.
|
||||
3. A project switch during create/import/setup/session/run/diff cannot change operation ownership.
|
||||
4. Project B Local sessions never resolve through project A.
|
||||
5. Missing/mismatched project references fail closed.
|
||||
6. Poller/PR late results after stop or generation change are dropped.
|
||||
7. Same raw worktree/session/local IDs in two projects do not collide in runtime or UI stores.
|
||||
8. Two expanded projects render two permanent full `ProjectSidebarBody` instances.
|
||||
9. Selecting a card changes only shared detail state and active emphasis, not body structure or mount identity.
|
||||
10. Every worktree/session/terminal/diff/run/setup/section/PR action targets the clicked project's context.
|
||||
11. Registry writes preserve concurrent add/remove/trust changes.
|
||||
12. Roots sharing one Git common directory cannot both register.
|
||||
13. Panel shutdown awaits all contexts and stops background activity.
|
||||
14. Flag-off behavior remains the current single-project UI and state file.
|
||||
15. A Settings draft loaded for project A cannot write project B after any activation, expansion, removal, trust, or worktree switch.
|
||||
16. User Settings writes target the revisioned user file; explicit Project Settings writes target only the selected trusted project's revisioned root config. No key is silently rerouted.
|
||||
17. A clean external config change refreshes Settings; a dirty or saving binding is marked stale without losing its draft.
|
||||
18. Concurrent editors using the same revision produce one success and one 409 conflict, with no lost update or partial file.
|
||||
19. A comment-only JSONC edit, file deletion/recreation with changed bytes, or newly created higher-priority config changes the revision/target and rejects the stale write.
|
||||
20. A save response clears only the matching `{ requestId, binding.id }`; an SSE config event or out-of-order read cannot confirm a save.
|
||||
21. Global and project writes are independently acknowledged and never presented as one atomic transaction.
|
||||
22. Project config open/read/write actions reject unknown, untrusted, removed, generation-stale, symlink-escaping, or mismatched project targets.
|
||||
23. Runtime config for a selected managed worktree uses that worktree directory, while Project Settings/open-file targets the immutable registered project root.
|
||||
24. Project-sourced providers and other config collections cannot trigger an implicit active-project write from Settings actions.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- No `ProjectSummary` or alternate fake-card body exists.
|
||||
- Every expanded project displays real live Local, worktree, and session UI concurrently.
|
||||
- Every visible worktree/session action is usable and routes by explicit project identity.
|
||||
- Switching detail selection causes no sidebar body remount, replacement, collapse, or data reset.
|
||||
- Each project reads and writes only its own `.kilo/agent-manager.json`.
|
||||
- Expanded projects remain live independently; collapsed projects suspend safely.
|
||||
- No identified operation falls back to active/current/workspace root.
|
||||
- Single-project behavior and persisted state remain backward-compatible when the flag is off.
|
||||
- Shared Settings exposes explicit User and Project scope; Project writes require a trusted project selector and immutable binding.
|
||||
- Runtime project activation and Settings editor binding are separate state machines.
|
||||
- Every Settings config write has an immutable scope/target binding and revision precondition; narrow domain mutations merge named fields under the same target lock. No config mutation uses a save-time active-directory lookup.
|
||||
- Dirty drafts survive external updates and conflicts and can never migrate between projects.
|
||||
- Project Settings targets the explicit registered root, never the active Agent Manager project or active worktree.
|
||||
|
||||
## Planned UI
|
||||
|
||||
```text
|
||||
┌─ Sidebar ─────────────────────────────────┬─ Shared detail pane ──────────┐
|
||||
│ ← selected project search calendar gear │ │
|
||||
│ │ Chat / diff / terminal │
|
||||
│ PROJECTS + │ for the selected │
|
||||
│ ▾ abalone-bactrosaurus │ project/worktree/session │
|
||||
│ ┌─────────────────────────────────┐ │ │
|
||||
│ │ Local main +3295 -548 ↓54 │ │ │
|
||||
│ └─────────────────────────────────┘ │ │
|
||||
│ WORKTREES actions │ │
|
||||
│ ┌─────────────────────────────────┐ │ │
|
||||
│ │ feature-x #1234 +120 -8 ↓2 │ │ │
|
||||
│ │ real menus, rename, run, open │ │ │
|
||||
│ └─────────────────────────────────┘ │ │
|
||||
│ ┌─────────────────────────────────┐ │ │
|
||||
│ │ bugfix-y +45 -12 │ │ │
|
||||
│ └─────────────────────────────────┘ │ │
|
||||
│ SESSIONS │ │
|
||||
│ • Fix login redirect running │ │
|
||||
│ • Refactor auth │ │
|
||||
│ │ │
|
||||
│ ▾ kilo-pi-provider │ │
|
||||
│ ┌─────────────────────────────────┐ │ │
|
||||
│ │ Local master +16 -6 ↓1 │ │ │
|
||||
│ └─────────────────────────────────┘ │ │
|
||||
│ WORKTREES actions │ │
|
||||
│ ┌─────────────────────────────────┐ │ │
|
||||
│ │ provider-z #88 +88 -3 │ │ │
|
||||
│ │ real menus, rename, run, open │ │ │
|
||||
│ └─────────────────────────────────┘ │ │
|
||||
│ SESSIONS │ │
|
||||
│ • Add provider tests │ │
|
||||
│ │ │
|
||||
│ ▸ docs-repo collapsed │ │
|
||||
│ │ │
|
||||
│ [New Session] [Run] │ │
|
||||
└───────────────────────────────────────────┴──────────────────────────────┘
|
||||
|
||||
Selecting any card:
|
||||
|
||||
- keeps every project body mounted and pixel-stable;
|
||||
- changes only the selected styling, top toolbar target, and shared detail pane;
|
||||
- never swaps a real body for a summary or vice versa.
|
||||
```
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode-ai/core",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -40,20 +40,20 @@
|
||||
"@ai-sdk/alibaba": "1.0.17",
|
||||
"@ai-sdk/amazon-bedrock": "4.0.112",
|
||||
"@ai-sdk/anthropic": "3.0.82",
|
||||
"@ai-sdk/azure": "3.0.49",
|
||||
"@ai-sdk/azure": "3.0.93",
|
||||
"@ai-sdk/cerebras": "2.0.54",
|
||||
"@ai-sdk/cohere": "3.0.27",
|
||||
"@ai-sdk/deepinfra": "2.0.41",
|
||||
"@ai-sdk/gateway": "3.0.104",
|
||||
"@ai-sdk/gateway": "3.0.157",
|
||||
"@ai-sdk/google": "3.0.73",
|
||||
"@ai-sdk/google-vertex": "4.0.128",
|
||||
"@ai-sdk/groq": "3.0.31",
|
||||
"@ai-sdk/mistral": "3.0.27",
|
||||
"@ai-sdk/openai": "3.0.53",
|
||||
"@ai-sdk/openai": "3.0.88",
|
||||
"@ai-sdk/openai-compatible": "2.0.48",
|
||||
"@ai-sdk/perplexity": "3.0.26",
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@ai-sdk/provider-utils": "4.0.23",
|
||||
"@ai-sdk/provider": "3.0.14",
|
||||
"@ai-sdk/provider-utils": "4.0.40",
|
||||
"@ai-sdk/togetherai": "2.0.41",
|
||||
"@ai-sdk/vercel": "2.0.39",
|
||||
"@ai-sdk/xai": "3.0.102",
|
||||
@@ -127,7 +127,7 @@
|
||||
},
|
||||
"packages/effect-drizzle-sqlite": {
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -141,7 +141,7 @@
|
||||
},
|
||||
"packages/effect-sqlite-node": {
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
},
|
||||
@@ -153,7 +153,7 @@
|
||||
},
|
||||
"packages/http-recorder": {
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"@effect/platform-node": "4.0.0-beta.74",
|
||||
"@effect/platform-node-shared": "4.0.0-beta.74",
|
||||
@@ -174,7 +174,7 @@
|
||||
},
|
||||
"packages/kilo-console": {
|
||||
"name": "@kilocode/kilo-console",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"@kilocode/kilo-indexing": "workspace:*",
|
||||
"@kilocode/kilo-web-ui": "workspace:*",
|
||||
@@ -197,7 +197,7 @@
|
||||
},
|
||||
"packages/kilo-docs": {
|
||||
"name": "@kilocode/kilo-docs",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"@docsearch/css": "^4",
|
||||
"@docsearch/js": "^4",
|
||||
@@ -227,12 +227,12 @@
|
||||
},
|
||||
"packages/kilo-gateway": {
|
||||
"name": "@kilocode/kilo-gateway",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"@ai-sdk/alibaba": "1.0.17",
|
||||
"@ai-sdk/anthropic": "3.0.71",
|
||||
"@ai-sdk/anthropic": "3.0.82",
|
||||
"@ai-sdk/mistral": "3.0.27",
|
||||
"@ai-sdk/openai": "3.0.53",
|
||||
"@ai-sdk/openai": "3.0.88",
|
||||
"@ai-sdk/openai-compatible": "2.0.48",
|
||||
"@clack/prompts": "1.0.0-alpha.1",
|
||||
"@kilocode/plugin": "workspace:*",
|
||||
@@ -263,7 +263,7 @@
|
||||
},
|
||||
"packages/kilo-i18n": {
|
||||
"name": "@kilocode/kilo-i18n",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "catalog:",
|
||||
"@types/bun": "catalog:",
|
||||
@@ -273,7 +273,7 @@
|
||||
},
|
||||
"packages/kilo-indexing": {
|
||||
"name": "@kilocode/kilo-indexing",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-bedrock-runtime": "3.1005.0",
|
||||
"@aws-sdk/credential-provider-ini": "3.972.31",
|
||||
@@ -305,11 +305,11 @@
|
||||
},
|
||||
"packages/kilo-jetbrains": {
|
||||
"name": "@kilocode/kilo-jetbrains",
|
||||
"version": "7.4.15",
|
||||
"version": "7.4.16",
|
||||
},
|
||||
"packages/kilo-memory": {
|
||||
"name": "@kilocode/kilo-memory",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
"zod": "catalog:",
|
||||
@@ -323,7 +323,7 @@
|
||||
},
|
||||
"packages/kilo-sandbox": {
|
||||
"name": "@kilocode/sandbox",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sandbox-runtime": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -338,7 +338,7 @@
|
||||
},
|
||||
"packages/kilo-telemetry": {
|
||||
"name": "@kilocode/kilo-telemetry",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"@kilocode/kilo-gateway": "workspace:*",
|
||||
"posthog-node": "4.4.0",
|
||||
@@ -352,7 +352,7 @@
|
||||
},
|
||||
"packages/kilo-ui": {
|
||||
"name": "@kilocode/kilo-ui",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"@kobalte/core": "0.13.11",
|
||||
@@ -390,7 +390,7 @@
|
||||
},
|
||||
"packages/kilo-vscode": {
|
||||
"name": "kilo-code",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"@kilocode/kilo-gateway": "workspace:*",
|
||||
@@ -459,7 +459,7 @@
|
||||
},
|
||||
"packages/kilo-web-ui": {
|
||||
"name": "@kilocode/kilo-web-ui",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"@kilocode/kilo-ui": "workspace:*",
|
||||
"@kobalte/core": "catalog:",
|
||||
@@ -476,7 +476,7 @@
|
||||
},
|
||||
"packages/llm": {
|
||||
"name": "@opencode-ai/llm",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"@smithy/eventstream-codec": "4.2.14",
|
||||
"@smithy/util-utf8": "4.2.2",
|
||||
@@ -494,7 +494,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "@kilocode/cli",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"bin": {
|
||||
"kilo": "./bin/kilo",
|
||||
"kilocode": "./bin/kilo",
|
||||
@@ -506,20 +506,20 @@
|
||||
"@ai-sdk/alibaba": "1.0.17",
|
||||
"@ai-sdk/amazon-bedrock": "4.0.112",
|
||||
"@ai-sdk/anthropic": "3.0.82",
|
||||
"@ai-sdk/azure": "3.0.49",
|
||||
"@ai-sdk/azure": "3.0.93",
|
||||
"@ai-sdk/cerebras": "2.0.54",
|
||||
"@ai-sdk/cohere": "3.0.27",
|
||||
"@ai-sdk/deepinfra": "2.0.41",
|
||||
"@ai-sdk/gateway": "3.0.104",
|
||||
"@ai-sdk/gateway": "3.0.157",
|
||||
"@ai-sdk/google": "3.0.73",
|
||||
"@ai-sdk/google-vertex": "4.0.128",
|
||||
"@ai-sdk/groq": "3.0.31",
|
||||
"@ai-sdk/mistral": "3.0.27",
|
||||
"@ai-sdk/openai": "3.0.53",
|
||||
"@ai-sdk/openai": "3.0.88",
|
||||
"@ai-sdk/openai-compatible": "2.0.48",
|
||||
"@ai-sdk/perplexity": "3.0.26",
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@ai-sdk/provider-utils": "4.0.23",
|
||||
"@ai-sdk/provider": "3.0.14",
|
||||
"@ai-sdk/provider-utils": "4.0.40",
|
||||
"@ai-sdk/togetherai": "2.0.41",
|
||||
"@ai-sdk/vercel": "2.0.39",
|
||||
"@ai-sdk/xai": "3.0.102",
|
||||
@@ -661,7 +661,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@kilocode/plugin",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
@@ -689,7 +689,7 @@
|
||||
},
|
||||
"packages/plugin-atomic-chat": {
|
||||
"name": "@kilocode/plugin-atomic-chat",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"@kilocode/plugin": "workspace:*",
|
||||
},
|
||||
@@ -703,7 +703,7 @@
|
||||
},
|
||||
"packages/script": {
|
||||
"name": "@opencode-ai/script",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"semver": "^7.6.3",
|
||||
},
|
||||
@@ -714,7 +714,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@kilocode/sdk",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
@@ -729,7 +729,7 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@opencode-ai/server",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"drizzle-orm": "catalog:",
|
||||
@@ -743,7 +743,7 @@
|
||||
},
|
||||
"packages/storybook": {
|
||||
"name": "@opencode-ai/storybook",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"devDependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@solidjs/meta": "catalog:",
|
||||
@@ -766,7 +766,7 @@
|
||||
},
|
||||
"packages/tui": {
|
||||
"name": "@opencode-ai/tui",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"@kilocode/plugin": "workspace:*",
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
@@ -793,7 +793,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"@kobalte/core": "catalog:",
|
||||
@@ -846,22 +846,22 @@
|
||||
},
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"web-tree-sitter",
|
||||
"esbuild",
|
||||
"tree-sitter-bash",
|
||||
"protobufjs",
|
||||
"web-tree-sitter",
|
||||
"tree-sitter-bash",
|
||||
],
|
||||
"patchedDependencies": {
|
||||
"virtua@0.49.1": "patches/virtua@0.49.1.patch",
|
||||
"mammoth@1.12.0": "patches/mammoth@1.12.0.patch",
|
||||
"@ff-labs/fff-bun@0.9.4": "patches/@ff-labs%2Ffff-bun@0.9.4.patch",
|
||||
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
||||
"@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
|
||||
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
|
||||
"pacote@21.5.1": "patches/pacote@21.5.1.patch",
|
||||
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
|
||||
"@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch",
|
||||
"@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch",
|
||||
"@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
|
||||
"@ff-labs/fff-bun@0.9.4": "patches/@ff-labs%2Ffff-bun@0.9.4.patch",
|
||||
"pacote@21.5.1": "patches/pacote@21.5.1.patch",
|
||||
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
||||
"@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch",
|
||||
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
|
||||
"virtua@0.49.1": "patches/virtua@0.49.1.patch",
|
||||
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
|
||||
"mammoth@1.12.0": "patches/mammoth@1.12.0.patch",
|
||||
},
|
||||
"overrides": {
|
||||
"@effect/platform-node-shared": "4.0.0-beta.74",
|
||||
@@ -917,7 +917,7 @@
|
||||
"@types/node": "24.12.4",
|
||||
"@types/semver": "7.7.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260316.1",
|
||||
"ai": "6.0.168",
|
||||
"ai": "6.0.235",
|
||||
"cross-spawn": "7.0.6",
|
||||
"diff": "8.0.4",
|
||||
"dompurify": "3.4.2",
|
||||
@@ -970,7 +970,7 @@
|
||||
|
||||
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.82", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-WKKou2wbhGGYV8PSALAPyV2YY4nfCqCPkyBzYtJtDA9yCcIFwsbtkTNgg7bqtLCVzeEsY7wwxRoCWy+EMfrw/A=="],
|
||||
|
||||
"@ai-sdk/azure": ["@ai-sdk/azure@3.0.49", "", { "dependencies": { "@ai-sdk/openai": "3.0.48", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wskgAL+OmrHG7by/iWIxEBQCEdc1mDudha/UZav46i0auzdFfsDB/k2rXZaC4/3nWSgMZkxr0W3ncyouEGX/eg=="],
|
||||
"@ai-sdk/azure": ["@ai-sdk/azure@3.0.93", "", { "dependencies": { "@ai-sdk/deepseek": "2.0.50", "@ai-sdk/openai": "3.0.88", "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.40" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9nKNmRtopjE3+L+286/4sLBDfDvmdsL7fiQXG0TE38NywxkDDLUhdr0KSllkRQ+m1/8bO7x4rIVhHSjQQiqmnQ=="],
|
||||
|
||||
"@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.54", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-m+oLYlZUA0j5aeE8BrjDd2DSCx6HujqPB2C0YjQF7iGQ4sTCCmhHfH2o0uY7vlSPIJpA2ak6YyF7R14a5dq8fw=="],
|
||||
|
||||
@@ -980,13 +980,13 @@
|
||||
|
||||
"@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@2.0.41", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-y6RoOP7DGWmDSiSxrUSt5p18sbz+Ixe5lMVPmdE7x+Tr5rlrzvftyHhjWHfqlAtoYERZTGFbP6tPW1OfQcrb4A=="],
|
||||
|
||||
"@ai-sdk/deepseek": ["@ai-sdk/deepseek@2.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.29" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-IkBQPEfRUbPEcWzigT76KttqoDqM4FR8H8cp5pgAw5RdDN++nUoJa0uZan3LuOp4IC8BKGZABJCqB97CMIYLdQ=="],
|
||||
"@ai-sdk/deepseek": ["@ai-sdk/deepseek@2.0.50", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.40" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-pnl22aGBIdpK0sXSJN4zDykIXTdyO9m3gFhno3SBPpdwT4Tq041Uy0i/EDWSxIiFa9JEokFGiCt4NNzcE5ntRA=="],
|
||||
|
||||
"@ai-sdk/elevenlabs": ["@ai-sdk/elevenlabs@2.0.35", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.29" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-KjgM2IDMwm1qTKaW2n2qCr2ok9/wlhUC9ggNvyYYeCupEEGR8Gki1oFRA16PsBcyEr98k+kaKw4cF6D0XaVWWA=="],
|
||||
|
||||
"@ai-sdk/fireworks": ["@ai-sdk/fireworks@2.0.55", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.50", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.29" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cdlFOq4NuwZdgdwocae5ItT7uKh3mf4junJWGVKtOcs7ATY42VZLzIU2MFBgm2imGVZXwB6kcl3jd9sDBnJ8cw=="],
|
||||
|
||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.104", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZKX5n74io8VIRlhIMSLWVlvT3sXC8Z7cZ9GHuWBWZDVi96+62AIsWuLGvMfcBA1STYuSoDrp6rIziZmvrTq0TA=="],
|
||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.157", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.40", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-jO0LUsIQC+FeJRQI1KFiNcfTGp6a0lXrymekaXfBIvhVEOyt0NUyc9wiVi/dYGHQmWFXUo8QgmghgGAGLOPTAA=="],
|
||||
|
||||
"@ai-sdk/google": ["@ai-sdk/google@3.0.73", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-o2MuIeyvZrFIeIbnbA8Thrr63irdyUBh0uWBZ2lY6yFeXuE/tcwyXF74bDKS4KvTu84uFpQfpbS/LXHGKKXz+g=="],
|
||||
|
||||
@@ -996,15 +996,15 @@
|
||||
|
||||
"@ai-sdk/mistral": ["@ai-sdk/mistral@3.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZXe7nZQgliDdjz5ufH5RKpHWxbN72AzmzzKGbF/z+0K9GN5tUCnftrQRvTRFHA5jAzTapcm2BEevmGLVbMkW+A=="],
|
||||
|
||||
"@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="],
|
||||
"@ai-sdk/openai": ["@ai-sdk/openai@3.0.88", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.40" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-6M4+bxK/UMijDYK2ia1hCKYma1iajFYxHDlhbinFUdhH2WbFsjoZRMpSjEMlTOBptqmh2J1JclKnprGulF8WwQ=="],
|
||||
|
||||
"@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.48", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z9MC6M4Oh/yUY/F/eszOtO8wc2nMz99XmZQKd2gWTtyIfe716xTfrKe3aYZKg20NZDtyjqPPKPSR+wqz7q1T7Q=="],
|
||||
|
||||
"@ai-sdk/perplexity": ["@ai-sdk/perplexity@3.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-dXzrVsLR5f6tr+U04jq4AXoRroGFBTvODnLgss0SWbzNjGGQg3XqtQ9j7rCLo6o8qbYGuAHvqUrIpUCuiscuFg=="],
|
||||
|
||||
"@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
"@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="],
|
||||
|
||||
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.23", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg=="],
|
||||
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.40", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OL5IrpUm9Y8Dwy+w/vvFwPotS6m52O9W0op2oXgXdCROMJIBalBI0oro6OIBYkPxvm5Xg02GSkoQN25RlR0bnw=="],
|
||||
|
||||
"@ai-sdk/togetherai": ["@ai-sdk/togetherai@2.0.41", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-k3p9e3k0/gpDDyTtvafsK4HYR4D/aUQW/kzCwWo1+CzdBU84i4L14gWISC/mv6tgSicMXHcEUd521fPufQwNlg=="],
|
||||
|
||||
@@ -2670,7 +2670,7 @@
|
||||
|
||||
"aggregate-error": ["aggregate-error@4.0.1", "", { "dependencies": { "clean-stack": "^4.0.0", "indent-string": "^5.0.0" } }, "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w=="],
|
||||
|
||||
"ai": ["ai@6.0.168", "", { "dependencies": { "@ai-sdk/gateway": "3.0.104", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2HqCJuO+1V2aV7vfYs5LFEUfxbkGX+5oa54q/gCCTL7KLTdbxcCu5D7TdLA5kwsrs3Szgjah9q6D9tpjHM3hUQ=="],
|
||||
"ai": ["ai@6.0.235", "", { "dependencies": { "@ai-sdk/gateway": "3.0.157", "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.40", "@opentelemetry/api": "^1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Uyeuvowo20XChpFC0lUaQq8xf0Kg7OElA56fEjUYoeWMJIzqshDTz4S57KXobcJ4m+iDQxMeJoUj06CIxSLx7A=="],
|
||||
|
||||
"ai-gateway-provider": ["ai-gateway-provider@3.1.2", "", { "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^4.0.62", "@ai-sdk/anthropic": "^3.0.46", "@ai-sdk/azure": "^3.0.31", "@ai-sdk/cerebras": "^2.0.34", "@ai-sdk/cohere": "^3.0.21", "@ai-sdk/deepgram": "^2.0.20", "@ai-sdk/deepseek": "^2.0.20", "@ai-sdk/elevenlabs": "^2.0.20", "@ai-sdk/fireworks": "^2.0.34", "@ai-sdk/google": "^3.0.30", "@ai-sdk/google-vertex": "^4.0.61", "@ai-sdk/groq": "^3.0.24", "@ai-sdk/mistral": "^3.0.20", "@ai-sdk/openai": "^3.0.30", "@ai-sdk/perplexity": "^3.0.19", "@ai-sdk/xai": "^3.0.57", "@openrouter/ai-sdk-provider": "^2.2.3" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^2.0.0", "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.0", "ai": "^6.0.0" } }, "sha512-krGNnJSoO/gJ7Hbe5nQDlsBpDUGIBGtMQTRUaW7s1MylsfvLduba0TLWzQaGtOmNRkP0pGhtGlwsnS6FNQMlyw=="],
|
||||
|
||||
@@ -4756,6 +4756,10 @@
|
||||
|
||||
"@ai-sdk/alibaba/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
||||
|
||||
"@ai-sdk/alibaba/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"@ai-sdk/alibaba/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.23", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg=="],
|
||||
|
||||
"@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.81", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-B1JDd9Ugq9R5AgIaW3674lhGCMMYJcPUxnrZh8fzbGojgg4QvHFRv6eZahGQAUsmGHbcf74G9bdSBDLWQGY2GA=="],
|
||||
|
||||
"@ai-sdk/amazon-bedrock/@ai-sdk/openai": ["@ai-sdk/openai@3.0.67", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-oAiGC9eWG7IgtdsdS74bOCnAAHarAfTJhWN9x5INwnWPekL802AvF+0I5DvLzIF1MIRmNw4N8mPSL/GUVbX9Mw=="],
|
||||
@@ -4768,14 +4772,12 @@
|
||||
|
||||
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||
|
||||
"@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@3.0.48", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ALmj/53EXpcRqMbGpPJPP4UOSWw0q4VGpnDo7YctvsynjkrKDmoneDG/1a7VQnSPYHnJp6tTRMf5ZdxZ5whulg=="],
|
||||
|
||||
"@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||
|
||||
"@ai-sdk/cerebras/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||
|
||||
"@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||
|
||||
"@ai-sdk/cohere/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"@ai-sdk/cohere/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||
|
||||
"@ai-sdk/deepgram/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||
@@ -4784,12 +4786,10 @@
|
||||
|
||||
"@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.37", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-+POSFVcgiu47BK64dhsI6OpcDC0/VAE2ZSaXdXGNNhpC/ava++uSRJYks0k2bpfY0wwCTgpAWZsXn/dG2Yppiw=="],
|
||||
|
||||
"@ai-sdk/deepinfra/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"@ai-sdk/deepinfra/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||
|
||||
"@ai-sdk/deepseek/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||
|
||||
"@ai-sdk/deepseek/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.29", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-uhukHaCBvqkwBHkT8C2PrnqKTCoLn3pdHXqtcR9I8ErH+flbzgW4o7VHSNIup9LRu+WBvZIZDQLsx6rwl2tiOA=="],
|
||||
|
||||
"@ai-sdk/elevenlabs/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||
|
||||
"@ai-sdk/elevenlabs/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.29", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-uhukHaCBvqkwBHkT8C2PrnqKTCoLn3pdHXqtcR9I8ErH+flbzgW4o7VHSNIup9LRu+WBvZIZDQLsx6rwl2tiOA=="],
|
||||
@@ -4812,24 +4812,34 @@
|
||||
|
||||
"@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||
|
||||
"@ai-sdk/groq/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"@ai-sdk/groq/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||
|
||||
"@ai-sdk/mistral/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"@ai-sdk/mistral/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||
|
||||
"@ai-sdk/openai-compatible/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||
|
||||
"@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||
|
||||
"@ai-sdk/perplexity/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"@ai-sdk/perplexity/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||
|
||||
"@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@ai-sdk/togetherai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.37", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-+POSFVcgiu47BK64dhsI6OpcDC0/VAE2ZSaXdXGNNhpC/ava++uSRJYks0k2bpfY0wwCTgpAWZsXn/dG2Yppiw=="],
|
||||
|
||||
"@ai-sdk/togetherai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"@ai-sdk/togetherai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||
|
||||
"@ai-sdk/vercel/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.37", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-+POSFVcgiu47BK64dhsI6OpcDC0/VAE2ZSaXdXGNNhpC/ava++uSRJYks0k2bpfY0wwCTgpAWZsXn/dG2Yppiw=="],
|
||||
|
||||
"@ai-sdk/vercel/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||
|
||||
"@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.56", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cQrN6OUn/jvsY3OdsU6Wn+ss7vp1iwIcakZKSlSRMnYqShBfyT7Qht+eqmgxs7w9ttrw6FAG6o11AiBs+iEsTA=="],
|
||||
@@ -5076,8 +5086,6 @@
|
||||
|
||||
"@kilocode/kilo-docs/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"@kilocode/kilo-gateway/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.71", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bUWOzrzR0gJKJO/PLGMR4uH2dqEgqGhrsCV+sSpk4KtOEnUQlfjZI/F7BFlqSvVpFbjdgYRRLysAeEZpJ6S1lg=="],
|
||||
|
||||
"@kilocode/kilo-indexing/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
|
||||
|
||||
"@kilocode/kilo-indexing/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
@@ -5092,6 +5100,8 @@
|
||||
|
||||
"@modelcontextprotocol/sdk/jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="],
|
||||
|
||||
"@morphllm/morphsdk/ai": ["ai@6.0.168", "", { "dependencies": { "@ai-sdk/gateway": "3.0.104", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2HqCJuO+1V2aV7vfYs5LFEUfxbkGX+5oa54q/gCCTL7KLTdbxcCu5D7TdLA5kwsrs3Szgjah9q6D9tpjHM3hUQ=="],
|
||||
|
||||
"@morphllm/morphsdk/openai": ["openai@4.104.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" }, "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA=="],
|
||||
|
||||
"@npmcli/arborist/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
@@ -5346,10 +5356,16 @@
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.71", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bUWOzrzR0gJKJO/PLGMR4uH2dqEgqGhrsCV+sSpk4KtOEnUQlfjZI/F7BFlqSvVpFbjdgYRRLysAeEZpJ6S1lg=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/azure": ["@ai-sdk/azure@3.0.49", "", { "dependencies": { "@ai-sdk/openai": "3.0.48", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wskgAL+OmrHG7by/iWIxEBQCEdc1mDudha/UZav46i0auzdFfsDB/k2rXZaC4/3nWSgMZkxr0W3ncyouEGX/eg=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/deepseek": ["@ai-sdk/deepseek@2.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.29" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-IkBQPEfRUbPEcWzigT76KttqoDqM4FR8H8cp5pgAw5RdDN++nUoJa0uZan3LuOp4IC8BKGZABJCqB97CMIYLdQ=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/google": ["@ai-sdk/google@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CbR82EgGPNrj/6q0HtclwuCqe0/pDShyv3nWDP/A9DroujzWXnLMlUJVrgPOsg4b40zQCwwVs2XSKCxvt/4QaA=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.112", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.71", "@ai-sdk/google": "3.0.64", "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cSfHCkM+9ZrFtQWIN1WlV93JPD+isGSdFxKj7u1L9m2aLVZajlXdcE41GL9hMt7ld7bZYE4NnZ+4VLxBAHE+Eg=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/xai": ["@ai-sdk/xai@3.0.92", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yqRMdEVfSkZCXs5mqeKfYOuQElo93igwz/4zWbYkhZWAW7tDh+5uqRef6MtQVYF/bTC1EsdbeO9DtsOtAZMOLA=="],
|
||||
|
||||
"archiver-utils/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||
@@ -5700,12 +5716,12 @@
|
||||
|
||||
"@actions/artifact/@actions/core/@actions/exec": ["@actions/exec@2.0.0", "", { "dependencies": { "@actions/io": "^2.0.0" } }, "sha512-k8ngrX2voJ/RIN6r9xB82NVqKpnMRtxDoiO+g3olkIUpQNqjArXrCQceduQZCQj3P3xm32pChRLqRrtXTlqhIw=="],
|
||||
|
||||
"@ai-sdk/alibaba/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@ai-sdk/azure/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@ai-sdk/cerebras/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@ai-sdk/cohere/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
@@ -5714,8 +5730,6 @@
|
||||
|
||||
"@ai-sdk/deepinfra/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@ai-sdk/deepseek/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@ai-sdk/elevenlabs/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@ai-sdk/fireworks/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
@@ -5854,6 +5868,12 @@
|
||||
|
||||
"@manypkg/find-root/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
|
||||
"@morphllm/morphsdk/ai/@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.104", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZKX5n74io8VIRlhIMSLWVlvT3sXC8Z7cZ9GHuWBWZDVi96+62AIsWuLGvMfcBA1STYuSoDrp6rIziZmvrTq0TA=="],
|
||||
|
||||
"@morphllm/morphsdk/ai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"@morphllm/morphsdk/ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.23", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg=="],
|
||||
|
||||
"@morphllm/morphsdk/openai/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@npmcli/package-json/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
@@ -6042,8 +6062,38 @@
|
||||
|
||||
"@vscode/vsce/hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.23", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.23", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@3.0.48", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ALmj/53EXpcRqMbGpPJPP4UOSWw0q4VGpnDo7YctvsynjkrKDmoneDG/1a7VQnSPYHnJp6tTRMf5ZdxZ5whulg=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/azure/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/deepseek/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/deepseek/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.29", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-uhukHaCBvqkwBHkT8C2PrnqKTCoLn3pdHXqtcR9I8ErH+flbzgW4o7VHSNIup9LRu+WBvZIZDQLsx6rwl2tiOA=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.23", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.23", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.23", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/xai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||
@@ -6296,6 +6346,8 @@
|
||||
|
||||
"@manypkg/find-root/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||
|
||||
"@morphllm/morphsdk/ai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@octokit/graphql/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||
|
||||
"@octokit/rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
||||
@@ -6382,6 +6434,20 @@
|
||||
|
||||
"@vscode/test-cli/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/azure/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/deepseek/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/xai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"archiver-utils/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-Y64ujq2R4SiyOavsXmaYsQkVNfdjhkzmPolOEV1RVyc=",
|
||||
"aarch64-linux": "sha256-7S209ir2j9o9NXYmMUnEfo6m64cUIkexd8L95UeCMFE=",
|
||||
"aarch64-darwin": "sha256-bE1zw7EK+4rgUgw80t8hdc2yojAF2f8ihjorh6B7jJE=",
|
||||
"x86_64-darwin": "sha256-xukM9cEvZAKCXewun9xReGBKz09i63LACLN/eH54020="
|
||||
"x86_64-linux": "sha256-sid/FCql4CmiAb1MNGMH33fwNs6esv9UNC819Yb03bQ=",
|
||||
"aarch64-linux": "sha256-1H1873nLqmcKrumAjnjPPts5+EyJLBm1Z5VKsOYANeg=",
|
||||
"aarch64-darwin": "sha256-i7D5RSPIVxXNY1gUnsYdFhVU2FxYEcjqYcQx6zZrJjg=",
|
||||
"x86_64-darwin": "sha256-5lSTqolFVcaKqN6ndKGJLcCUP8JQ8i60np+3eAulnZg="
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -54,7 +54,7 @@
|
||||
"drizzle-kit": "1.0.0-rc.2",
|
||||
"drizzle-orm": "1.0.0-rc.2",
|
||||
"effect": "4.0.0-beta.74",
|
||||
"ai": "6.0.168",
|
||||
"ai": "6.0.235",
|
||||
"cross-spawn": "7.0.6",
|
||||
"hono": "4.12.12",
|
||||
"hono-openapi": "1.1.2",
|
||||
@@ -171,6 +171,6 @@
|
||||
"pacote@21.5.1": "patches/pacote@21.5.1.patch",
|
||||
"mammoth@1.12.0": "patches/mammoth@1.12.0.patch"
|
||||
},
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"peerDependencies": {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"name": "@opencode-ai/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
@@ -85,20 +85,20 @@
|
||||
"@ai-sdk/alibaba": "1.0.17",
|
||||
"@ai-sdk/amazon-bedrock": "4.0.112",
|
||||
"@ai-sdk/anthropic": "3.0.82",
|
||||
"@ai-sdk/azure": "3.0.49",
|
||||
"@ai-sdk/azure": "3.0.93",
|
||||
"@ai-sdk/cerebras": "2.0.54",
|
||||
"@ai-sdk/cohere": "3.0.27",
|
||||
"@ai-sdk/deepinfra": "2.0.41",
|
||||
"@ai-sdk/gateway": "3.0.104",
|
||||
"@ai-sdk/gateway": "3.0.157",
|
||||
"@ai-sdk/google": "3.0.73",
|
||||
"@ai-sdk/google-vertex": "4.0.128",
|
||||
"@ai-sdk/groq": "3.0.31",
|
||||
"@ai-sdk/mistral": "3.0.27",
|
||||
"@ai-sdk/openai": "3.0.53",
|
||||
"@ai-sdk/openai": "3.0.88",
|
||||
"@ai-sdk/openai-compatible": "2.0.48",
|
||||
"@ai-sdk/perplexity": "3.0.26",
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@ai-sdk/provider-utils": "4.0.23",
|
||||
"@ai-sdk/provider": "3.0.14",
|
||||
"@ai-sdk/provider-utils": "4.0.40",
|
||||
"@ai-sdk/togetherai": "2.0.41",
|
||||
"@ai-sdk/vercel": "2.0.39",
|
||||
"@ai-sdk/xai": "3.0.102",
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as NodePath from "@effect/platform-node/NodePath"
|
||||
import { prepareCommand as prepareSandbox } from "@kilocode/sandbox" // kilocode_change
|
||||
import { tap as tapStdio, tapped } from "./kilocode/stdio-tap" // kilocode_change - Bun drops buffered stdio on close
|
||||
import * as SpawnValidation from "./kilocode/spawn-validation" // kilocode_change
|
||||
import { settle } from "./kilocode/exit-code" // kilocode_change - settle signal termination as 128 + signum
|
||||
import * as Deferred from "effect/Deferred"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Exit from "effect/Exit"
|
||||
@@ -441,16 +442,7 @@ export const make = Effect.gen(function* () {
|
||||
getInputFd: fd.getInputFd,
|
||||
getOutputFd: fd.getOutputFd,
|
||||
isRunning: Effect.map(Deferred.isDone(signal), (done) => !done),
|
||||
exitCode: Effect.flatMap(Deferred.await(signal), ([code, signal]) => {
|
||||
if (Predicate.isNotNull(code)) return Effect.succeed(ExitCode(code))
|
||||
return Effect.fail(
|
||||
toPlatformError(
|
||||
"exitCode",
|
||||
new Error(`Process interrupted due to receipt of signal: '${signal}'`),
|
||||
command,
|
||||
),
|
||||
)
|
||||
}),
|
||||
exitCode: Effect.flatMap(Deferred.await(signal), settle), // kilocode_change - signal termination settles as 128 + signum
|
||||
kill: (opts?: ChildProcess.KillOptions) => {
|
||||
const sig = opts?.killSignal ?? "SIGTERM"
|
||||
const send = (s: NodeJS.Signals) =>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { constants } from "node:os"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Predicate from "effect/Predicate"
|
||||
import { ExitCode } from "effect/unstable/process/ChildProcessSpawner"
|
||||
|
||||
// A process terminated by a signal produces a null exit code (see the "exit"
|
||||
// event on node:child_process). Report the conventional 128 + signum code
|
||||
// (e.g. 139 for SIGSEGV) instead of failing, which left consumers like the
|
||||
// bash tool waiting on a numeric code that never arrived.
|
||||
export const settle = ([code, signal]: readonly [code: number | null, signal: NodeJS.Signals | null]) => {
|
||||
if (Predicate.isNotNull(code)) return Effect.succeed(ExitCode(code))
|
||||
if (Predicate.isNotNull(signal) && signal in constants.signals) {
|
||||
return Effect.succeed(ExitCode(128 + constants.signals[signal]))
|
||||
}
|
||||
return Effect.succeed(ExitCode(1))
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
import { spawn } from "child_process"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
import type { Proc } from "../../pty/pty"
|
||||
import { Log } from "../../util/log"
|
||||
|
||||
const log = Log.create({ service: "pty.termination" })
|
||||
const GRACE_MS = 200
|
||||
const SPAWN_TIMEOUT_MS = 5_000
|
||||
|
||||
export type Process = Pick<Proc, "pid" | "onExit" | "kill">
|
||||
|
||||
export type Runtime = {
|
||||
readonly platform: NodeJS.Platform
|
||||
readonly taskkill: (
|
||||
file: string,
|
||||
args: string[],
|
||||
opts: { stdio: "ignore"; windowsHide: true; timeout: number },
|
||||
) => Promise<boolean>
|
||||
readonly tree: () => Promise<Array<{ pid: number; parent: number }>>
|
||||
readonly alive: (pid: number) => boolean
|
||||
readonly signal: (pid: number, signal: "SIGTERM" | "SIGKILL") => void
|
||||
readonly sleep: (ms: number) => Promise<void>
|
||||
}
|
||||
|
||||
const runtime: Runtime = {
|
||||
platform: process.platform,
|
||||
taskkill,
|
||||
tree,
|
||||
alive: (pid) => {
|
||||
try {
|
||||
process.kill(pid, 0)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
},
|
||||
signal: (pid, signal) => process.kill(pid, signal),
|
||||
sleep,
|
||||
}
|
||||
|
||||
function direct(proc: Process, signal?: "SIGTERM" | "SIGKILL") {
|
||||
try {
|
||||
proc.kill(signal)
|
||||
} catch (err) {
|
||||
log.warn("failed to kill PTY directly", { err, pid: proc.pid, signal })
|
||||
}
|
||||
}
|
||||
|
||||
function descendants(root: number, rows: Array<{ pid: number; parent: number }>) {
|
||||
const children = new Map<number, number[]>()
|
||||
for (const row of rows) {
|
||||
const list = children.get(row.parent) ?? []
|
||||
list.push(row.pid)
|
||||
children.set(row.parent, list)
|
||||
}
|
||||
const seen = new Set<number>()
|
||||
const collect = (pid: number): number[] => {
|
||||
const result: number[] = []
|
||||
for (const child of children.get(pid) ?? []) {
|
||||
if (seen.has(child)) continue
|
||||
seen.add(child)
|
||||
result.push(...collect(child), child)
|
||||
}
|
||||
return result
|
||||
}
|
||||
return collect(root)
|
||||
}
|
||||
|
||||
async function family(root: number, input: Runtime) {
|
||||
const rows = await input.tree().catch((err) => {
|
||||
log.debug("failed to inspect PTY process tree", { err, pid: root })
|
||||
return []
|
||||
})
|
||||
return [...descendants(root, rows), root]
|
||||
}
|
||||
|
||||
function signal(proc: Process, pids: number[], value: "SIGTERM" | "SIGKILL", input: Runtime) {
|
||||
for (const pid of pids) {
|
||||
let sent = false
|
||||
for (const target of [-pid, pid]) {
|
||||
try {
|
||||
input.signal(target, value)
|
||||
sent = true
|
||||
} catch (err) {
|
||||
log.debug("failed to signal PTY process", { err, pid: target, signal: value })
|
||||
}
|
||||
}
|
||||
if (pid === proc.pid && !sent) direct(proc, value)
|
||||
}
|
||||
}
|
||||
|
||||
async function tree(file: string = "ps", args: string[] = ["-axo", "pid=,ppid="]) {
|
||||
return await new Promise<Array<{ pid: number; parent: number }>>((resolve) => {
|
||||
try {
|
||||
const child = spawn(file, args, {
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
windowsHide: true,
|
||||
timeout: SPAWN_TIMEOUT_MS,
|
||||
killSignal: "SIGKILL",
|
||||
})
|
||||
const chunks: Buffer[] = []
|
||||
child.stdout?.on("data", (chunk: Buffer) => chunks.push(chunk))
|
||||
child.once("error", () => resolve([]))
|
||||
child.once("close", (code) => {
|
||||
if (code !== 0) return resolve([])
|
||||
const rows = Buffer.concat(chunks)
|
||||
.toString("utf8")
|
||||
.trim()
|
||||
.split("\n")
|
||||
.filter(Boolean)
|
||||
.map((line) => line.trim().split(/\s+/).map(Number))
|
||||
.filter(([pid, parent]) => Number.isSafeInteger(pid) && Number.isSafeInteger(parent))
|
||||
.map(([pid, parent]) => ({ pid: pid!, parent: parent! }))
|
||||
resolve(rows)
|
||||
})
|
||||
} catch {
|
||||
resolve([])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function taskkill(
|
||||
file: string,
|
||||
args: string[],
|
||||
opts: { stdio: "ignore"; windowsHide: true; timeout: number },
|
||||
) {
|
||||
return await new Promise<boolean>((resolve) => {
|
||||
try {
|
||||
const child = spawn(file, args, opts)
|
||||
child.once("exit", (code) => resolve(code === 0))
|
||||
child.once("error", (err) => {
|
||||
log.warn("taskkill failed", { err })
|
||||
resolve(false)
|
||||
})
|
||||
} catch (err) {
|
||||
log.warn("failed to start taskkill", { err })
|
||||
resolve(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function terminate(proc: Process, input: Runtime = runtime): Promise<void> {
|
||||
const state = { exited: false }
|
||||
const listener = proc.onExit(() => {
|
||||
state.exited = true
|
||||
})
|
||||
try {
|
||||
if (!proc.pid) {
|
||||
direct(proc)
|
||||
if (!state.exited) await input.sleep(GRACE_MS)
|
||||
return
|
||||
}
|
||||
|
||||
if (input.platform === "win32") {
|
||||
const killed = await input.taskkill("taskkill", ["/pid", String(proc.pid), "/f", "/t"], {
|
||||
stdio: "ignore",
|
||||
windowsHide: true,
|
||||
timeout: SPAWN_TIMEOUT_MS,
|
||||
})
|
||||
if (!killed && !state.exited) direct(proc)
|
||||
if (!state.exited) await input.sleep(GRACE_MS)
|
||||
return
|
||||
}
|
||||
|
||||
const initial = await family(proc.pid, input)
|
||||
signal(proc, initial, "SIGTERM", input)
|
||||
await input.sleep(GRACE_MS)
|
||||
const remaining = new Set(initial.filter(input.alive))
|
||||
if (input.alive(proc.pid)) for (const pid of await family(proc.pid, input)) remaining.add(pid)
|
||||
if (remaining.size > 0) {
|
||||
signal(proc, [...remaining], "SIGKILL", input)
|
||||
await input.sleep(GRACE_MS)
|
||||
}
|
||||
} finally {
|
||||
listener.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
export * as KiloPtyTermination from "./termination"
|
||||
@@ -210,13 +210,19 @@ export const layer = Layer.effect(
|
||||
if (snapshot) return snapshot
|
||||
if (Flag.KILO_DISABLE_MODELS_FETCH) return {}
|
||||
// Flock is cross-process: concurrent opencode CLIs can race on this cache file.
|
||||
const text = yield* Effect.scoped(
|
||||
return yield* Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
yield* Flock.effect(lockKey)
|
||||
return yield* fetchAndWrite()
|
||||
// kilocode_change start - re-read under the lock: a concurrent refresh
|
||||
// may already have recovered the corrupted cache while we waited, and
|
||||
// fetching again here would duplicate the network call.
|
||||
const rechecked = yield* loadFromDisk
|
||||
if (rechecked) return rechecked
|
||||
// kilocode_change end
|
||||
const text = yield* fetchAndWrite()
|
||||
return JSON.parse(text) as Record<string, Provider>
|
||||
}),
|
||||
)
|
||||
return JSON.parse(text) as Record<string, Provider>
|
||||
}).pipe(Effect.withSpan("ModelsDev.populate"), Effect.orDie)
|
||||
|
||||
const [cachedGet, invalidate] = yield* Effect.cachedInvalidateWithTTL(populate, Duration.infinity)
|
||||
|
||||
+37
-24
@@ -11,6 +11,7 @@ import { SessionSchema } from "./session/schema" // kilocode_change
|
||||
import { Shell } from "./shell"
|
||||
import { lazy } from "./util/lazy"
|
||||
import { KiloPtySelfCommand } from "./kilocode/pty-self-command" // kilocode_change
|
||||
import { KiloPtyTermination } from "./kilocode/pty/termination" // kilocode_change
|
||||
|
||||
const BUFFER_LIMIT = 1024 * 1024 * 2
|
||||
// Exited sessions stay observable (status, exit code, retained output) until removed explicitly.
|
||||
@@ -35,6 +36,7 @@ type Active = {
|
||||
cursor: number
|
||||
subscribers: Map<object, Subscriber>
|
||||
listeners: Disp[]
|
||||
stopping: boolean // kilocode_change
|
||||
}
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
@@ -83,6 +85,8 @@ export type AttachInput = {
|
||||
readonly onData: (chunk: string) => void
|
||||
// Fired once when the session stops producing output: process exit (exitCode set), removal, or service teardown.
|
||||
readonly onEnd: (event: { exitCode?: number }) => void
|
||||
// Canonical routes can replay retained output after exit; legacy callers retain the former error.
|
||||
readonly allowExited?: boolean // kilocode_change
|
||||
}
|
||||
|
||||
export type Attachment = {
|
||||
@@ -147,23 +151,25 @@ export const layer = Layer.effect(
|
||||
session.subscribers.clear()
|
||||
}
|
||||
|
||||
function teardown(session: Active) {
|
||||
// kilocode_change start - terminate the complete PTY tree before reporting removal.
|
||||
async function teardown(session: Active) {
|
||||
session.stopping = true
|
||||
if (session.info.status === "running") await KiloPtyTermination.terminate(session.process)
|
||||
for (const listener of session.listeners) listener.dispose()
|
||||
session.listeners.length = 0
|
||||
if (session.info.status === "running") {
|
||||
try {
|
||||
session.process.kill()
|
||||
} catch {}
|
||||
}
|
||||
notifyEnd(session, {})
|
||||
notifyEnd(session, session.info.status === "exited" ? { exitCode: session.info.exitCode } : {})
|
||||
}
|
||||
// kilocode_change end
|
||||
|
||||
yield* Effect.addFinalizer(() =>
|
||||
Effect.sync(() => {
|
||||
for (const session of sessions.values()) teardown(session)
|
||||
sessions.clear()
|
||||
exitOrder.length = 0
|
||||
}),
|
||||
yield* Effect.addFinalizer(
|
||||
() =>
|
||||
// kilocode_change start - wait for process-tree termination during async service teardown.
|
||||
Effect.promise(async () => {
|
||||
await Promise.all(Array.from(sessions.values()).map(teardown))
|
||||
sessions.clear()
|
||||
exitOrder.length = 0
|
||||
}),
|
||||
// kilocode_change end
|
||||
)
|
||||
|
||||
const requireSession = Effect.fn("Pty.requireSession")(function* (id: PtyID) {
|
||||
@@ -173,14 +179,18 @@ export const layer = Layer.effect(
|
||||
})
|
||||
|
||||
const removeSession = Effect.fnUntraced(function* (id: PtyID) {
|
||||
const session = sessions.get(id)
|
||||
if (!session) return
|
||||
sessions.delete(id)
|
||||
const index = exitOrder.indexOf(id)
|
||||
if (index !== -1) exitOrder.splice(index, 1)
|
||||
yield* Effect.logInfo("removing session", { id })
|
||||
teardown(session)
|
||||
yield* events.publish(Event.Deleted, { id: session.info.id })
|
||||
// kilocode_change start - removal and its deleted event are one uninterruptible lifecycle transition.
|
||||
yield* Effect.gen(function* () {
|
||||
const session = sessions.get(id)
|
||||
if (!session) return
|
||||
yield* Effect.logInfo("removing session", { id })
|
||||
yield* Effect.promise(() => teardown(session))
|
||||
sessions.delete(id)
|
||||
const index = exitOrder.indexOf(id)
|
||||
if (index !== -1) exitOrder.splice(index, 1)
|
||||
yield* events.publish(Event.Deleted, { id: session.info.id })
|
||||
}).pipe(Effect.uninterruptible)
|
||||
// kilocode_change end
|
||||
})
|
||||
|
||||
const remove = Effect.fn("Pty.remove")(function* (id: PtyID) {
|
||||
@@ -204,9 +214,10 @@ export const layer = Layer.effect(
|
||||
args: input.args ? [...input.args] : undefined,
|
||||
cwd: input.cwd,
|
||||
})
|
||||
const implicit = !resolved.command
|
||||
const command = resolved.command || Shell.preferred(Config.latest(yield* config.entries(), "shell"))
|
||||
const base = resolved.args ?? []
|
||||
const args = Shell.login(command) ? [...base, "-l"] : [...base]
|
||||
const args = implicit && Shell.login(command) ? [...base, "-l"] : [...base]
|
||||
const cwd = resolved.cwd || location.directory
|
||||
// kilocode_change end
|
||||
const env = {
|
||||
@@ -246,6 +257,7 @@ export const layer = Layer.effect(
|
||||
cursor: 0,
|
||||
subscribers: new Map(),
|
||||
listeners: [],
|
||||
stopping: false, // kilocode_change
|
||||
}
|
||||
sessions.set(id, session)
|
||||
session.listeners.push(
|
||||
@@ -269,7 +281,7 @@ export const layer = Layer.effect(
|
||||
session.bufferCursor += excess
|
||||
}),
|
||||
proc.onExit(({ exitCode }) => {
|
||||
if (session.info.status === "exited") return
|
||||
if (session.info.status === "exited" || session.stopping) return // kilocode_change
|
||||
session.info.status = "exited"
|
||||
session.info.exitCode = exitCode
|
||||
notifyEnd(session, { exitCode })
|
||||
@@ -309,7 +321,7 @@ export const layer = Layer.effect(
|
||||
|
||||
const attach = Effect.fn("Pty.attach")(function* (id: PtyID, input: AttachInput) {
|
||||
const session = yield* requireSession(id)
|
||||
if (session.info.status !== "running") return yield* new ExitedError({ ptyID: id })
|
||||
if (session.info.status !== "running" && !input.allowExited) return yield* new ExitedError({ ptyID: id }) // kilocode_change
|
||||
yield* Effect.logInfo("client attached to session", { id, directory: location.directory })
|
||||
const token = {}
|
||||
const subscriber: Subscriber = {
|
||||
@@ -318,6 +330,7 @@ export const layer = Layer.effect(
|
||||
active: false,
|
||||
detached: false,
|
||||
pending: [],
|
||||
end: session.info.status === "exited" ? { exitCode: session.info.exitCode } : undefined, // kilocode_change
|
||||
}
|
||||
session.subscribers.set(token, subscriber)
|
||||
const start = session.bufferCursor
|
||||
|
||||
@@ -165,11 +165,13 @@ export const layer = Layer.effect(
|
||||
)
|
||||
const abortable = input.signal ? program.pipe(Effect.raceFirst(waitForAbort(input.signal))) : program
|
||||
return abortable.pipe(
|
||||
Effect.mapError((cause) =>
|
||||
cause instanceof Error || cause instanceof InvalidPatternError
|
||||
? cause
|
||||
: failure("ripgrep execution failed", cause),
|
||||
),
|
||||
// kilocode_change start - surface the underlying reason instead of a bare wrapper message
|
||||
Effect.mapError((cause) => {
|
||||
if (cause instanceof Error || cause instanceof InvalidPatternError) return cause
|
||||
const detail = cause instanceof globalThis.Error && cause.message.trim() ? `: ${cause.message.trim()}` : ""
|
||||
return failure(`ripgrep execution failed${detail}`, cause)
|
||||
}),
|
||||
// kilocode_change end
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -229,6 +229,9 @@ export const Info = Schema.Struct({
|
||||
layout: Schema.optional(ConfigLayoutV1.Layout).annotate({ description: "@deprecated Always uses stretch layout." }),
|
||||
permission: Schema.optional(ConfigPermissionV1.Info),
|
||||
tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)),
|
||||
web_search: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "Make web search available to models from all providers (default: false)",
|
||||
}), // kilocode_change
|
||||
attachment: Schema.optional(ConfigAttachmentV1.Info).annotate({
|
||||
description: "Attachment processing configuration, including image size limits and resizing behavior",
|
||||
}),
|
||||
|
||||
@@ -45,6 +45,8 @@ export type Reply = typeof Reply.Type
|
||||
export const ReplyBody = Schema.Struct({
|
||||
reply: Reply,
|
||||
message: Schema.String.pipe(Schema.optional),
|
||||
// kilocode_change - set by clients when a human answered the prompt; the server refuses machine approvals of skill-shell batches
|
||||
interactive: Schema.Boolean.pipe(Schema.optional),
|
||||
}).annotate({ identifier: "PermissionReplyBody" })
|
||||
export type ReplyBody = typeof ReplyBody.Type
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Effect } from "effect"
|
||||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { settle } from "@opencode-ai/core/kilocode/exit-code"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const fx = testEffect(CrossSpawnSpawner.defaultLayer)
|
||||
|
||||
const code = (exit: readonly [number | null, NodeJS.Signals | null]) => Effect.runPromise(settle(exit))
|
||||
|
||||
describe("exit-code settle", () => {
|
||||
test("maps exit results to numeric codes", async () => {
|
||||
expect(await code([0, null])).toBe(ChildProcessSpawner.ExitCode(0))
|
||||
expect(await code([42, null])).toBe(ChildProcessSpawner.ExitCode(42))
|
||||
expect(await code([null, "SIGSEGV"])).toBe(ChildProcessSpawner.ExitCode(128 + 11))
|
||||
expect(await code([null, "SIGTERM"])).toBe(ChildProcessSpawner.ExitCode(128 + 15))
|
||||
expect(await code([null, null])).toBe(ChildProcessSpawner.ExitCode(1))
|
||||
expect(await code([null, "SIGWHAT" as NodeJS.Signals])).toBe(ChildProcessSpawner.ExitCode(1))
|
||||
})
|
||||
|
||||
fx.effect(
|
||||
"reports signal termination as 128 + signum",
|
||||
Effect.gen(function* () {
|
||||
if (process.platform === "win32") return
|
||||
const handle = yield* ChildProcess.make(process.execPath, ["-e", "process.kill(process.pid, 'SIGKILL')"])
|
||||
expect(yield* handle.exitCode).toBe(ChildProcessSpawner.ExitCode(128 + 9))
|
||||
}),
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,108 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { KiloPtyTermination } from "../../src/kilocode/pty/termination"
|
||||
|
||||
function fake(pid = 123) {
|
||||
const calls: Array<string | undefined> = []
|
||||
const proc: KiloPtyTermination.Process = {
|
||||
pid,
|
||||
onExit: () => ({ dispose() {} }),
|
||||
kill: (signal) => calls.push(signal),
|
||||
}
|
||||
return { proc, calls }
|
||||
}
|
||||
|
||||
function runtime(
|
||||
platform: NodeJS.Platform,
|
||||
input: {
|
||||
taskkill?: boolean
|
||||
signal?: "throw"
|
||||
tree?: Array<{ pid: number; parent: number }>
|
||||
} = {},
|
||||
) {
|
||||
const tasks: Array<{
|
||||
file: string
|
||||
args: string[]
|
||||
opts: { stdio: "ignore"; windowsHide: true; timeout: number }
|
||||
}> = []
|
||||
const signals: Array<{ pid: number; signal: "SIGTERM" | "SIGKILL" }> = []
|
||||
const sleeps: number[] = []
|
||||
const value: KiloPtyTermination.Runtime = {
|
||||
platform,
|
||||
taskkill: async (file, args, opts) => {
|
||||
tasks.push({ file, args, opts })
|
||||
return input.taskkill ?? true
|
||||
},
|
||||
tree: async () => input.tree ?? [],
|
||||
alive: () => true,
|
||||
signal: (pid, signal) => {
|
||||
signals.push({ pid, signal })
|
||||
if (input.signal === "throw") throw new Error("process group unavailable")
|
||||
},
|
||||
sleep: async (ms) => {
|
||||
sleeps.push(ms)
|
||||
},
|
||||
}
|
||||
return { value, tasks, signals, sleeps }
|
||||
}
|
||||
|
||||
describe("pty process-tree termination", () => {
|
||||
test("uses hidden taskkill for Windows process trees", async () => {
|
||||
const item = fake(42)
|
||||
const input = runtime("win32")
|
||||
|
||||
await KiloPtyTermination.terminate(item.proc, input.value)
|
||||
|
||||
expect(input.tasks).toEqual([
|
||||
{
|
||||
file: "taskkill",
|
||||
args: ["/pid", "42", "/f", "/t"],
|
||||
opts: { stdio: "ignore", windowsHide: true, timeout: 5_000 },
|
||||
},
|
||||
])
|
||||
expect(input.signals).toEqual([])
|
||||
expect(item.calls).toEqual([])
|
||||
expect(input.sleeps).toEqual([200])
|
||||
})
|
||||
|
||||
test("signals POSIX process groups before escalating", async () => {
|
||||
const item = fake(42)
|
||||
const input = runtime("linux")
|
||||
|
||||
await KiloPtyTermination.terminate(item.proc, input.value)
|
||||
|
||||
expect(input.signals).toEqual([
|
||||
{ pid: -42, signal: "SIGTERM" },
|
||||
{ pid: 42, signal: "SIGTERM" },
|
||||
{ pid: -42, signal: "SIGKILL" },
|
||||
{ pid: 42, signal: "SIGKILL" },
|
||||
])
|
||||
expect(item.calls).toEqual([])
|
||||
expect(input.sleeps).toEqual([200, 200])
|
||||
})
|
||||
|
||||
test("falls back to direct PTY signals when a process group is unavailable", async () => {
|
||||
const item = fake(42)
|
||||
const input = runtime("darwin", { signal: "throw" })
|
||||
|
||||
await KiloPtyTermination.terminate(item.proc, input.value)
|
||||
|
||||
expect(item.calls).toEqual(["SIGTERM", "SIGKILL"])
|
||||
})
|
||||
|
||||
test("signals descendants that run in separate process groups", async () => {
|
||||
const item = fake(42)
|
||||
const input = runtime("linux", {
|
||||
tree: [
|
||||
{ pid: 43, parent: 42 },
|
||||
{ pid: 44, parent: 43 },
|
||||
],
|
||||
})
|
||||
|
||||
await KiloPtyTermination.terminate(item.proc, input.value)
|
||||
|
||||
expect(input.signals).toContainEqual({ pid: -44, signal: "SIGTERM" })
|
||||
expect(input.signals).toContainEqual({ pid: 44, signal: "SIGKILL" })
|
||||
expect(input.signals).toContainEqual({ pid: -43, signal: "SIGTERM" })
|
||||
expect(input.signals).toContainEqual({ pid: 43, signal: "SIGKILL" })
|
||||
})
|
||||
})
|
||||
@@ -127,6 +127,53 @@ describe("pty", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
// kilocode_change start - explicit commands must not acquire implicit login-shell arguments.
|
||||
ptyTest("preserves explicit command arguments", () =>
|
||||
Effect.gen(function* () {
|
||||
const args = ["-c", 'printf "<%s>" "$0"; sleep 5']
|
||||
const info = yield* createPty("sh", args)
|
||||
expect(info.args).toEqual(args)
|
||||
|
||||
const attached = yield* attachCollecting(info.id)
|
||||
expect(yield* waitForOutput(attached.output, "<sh>")).toContain("<sh>")
|
||||
}),
|
||||
)
|
||||
|
||||
// (script terminals forward raw output to xterm without transcoding).
|
||||
ptyTest("round-trips non-ASCII output byte-identically", () =>
|
||||
Effect.gen(function* () {
|
||||
const pty = yield* Pty.Service
|
||||
const marker = "café-über-北京-🚀"
|
||||
const info = yield* createPty("sh", ["-c", `printf '${marker}\\n'`])
|
||||
const attached = yield* attachCollecting(info.id)
|
||||
expect(yield* waitForOutput(attached.output, marker)).toContain(marker)
|
||||
}),
|
||||
)
|
||||
ptyTest("terminates background descendants outside the shell process group", () =>
|
||||
Effect.gen(function* () {
|
||||
const pty = yield* Pty.Service
|
||||
const info = yield* createPty("sh", ["-c", 'sleep 30 & printf "<CHILD:%s>" "$!"; wait'])
|
||||
const attached = yield* attachCollecting(info.id)
|
||||
const output = yield* waitForOutput(attached.output, ">")
|
||||
const match = output.match(/<CHILD:(\d+)>/)
|
||||
expect(match?.[1]).toBeDefined()
|
||||
const pid = Number(match?.[1])
|
||||
|
||||
yield* pty.remove(info.id)
|
||||
yield* Effect.sleep("100 millis")
|
||||
const alive = yield* Effect.sync(() => {
|
||||
try {
|
||||
process.kill(pid, 0)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
})
|
||||
expect(alive).toBe(false)
|
||||
}),
|
||||
)
|
||||
// kilocode_change end
|
||||
|
||||
ptyTest("replays buffered output and streams live output to attachments", () =>
|
||||
Effect.gen(function* () {
|
||||
const pty = yield* Pty.Service
|
||||
@@ -201,6 +248,31 @@ describe("pty", () => {
|
||||
expect(Cause.squash(result.cause)).toMatchObject({ _tag: "Pty.ExitedError", ptyID: info.id })
|
||||
}),
|
||||
)
|
||||
|
||||
// kilocode_change start - canonical attachments replay retained exited output, then end without accepting input.
|
||||
ptyTest("replays exited output and ends when enabled", () =>
|
||||
Effect.gen(function* () {
|
||||
const pty = yield* Pty.Service
|
||||
const events = yield* subscribePtyEvents()
|
||||
const info = yield* createPty("sh", ["-c", 'printf "replayed"; exit 7'])
|
||||
expect(yield* waitForEvents(events, info.id, 2)).toEqual(["created", "exited"])
|
||||
|
||||
const ended = yield* Deferred.make<{ exitCode?: number }>()
|
||||
const attachment = yield* pty.attach(info.id, {
|
||||
allowExited: true,
|
||||
onData: () => {},
|
||||
onEnd: (event) => Deferred.doneUnsafe(ended, Effect.succeed(event)),
|
||||
})
|
||||
expect(attachment.replay).toContain("replayed")
|
||||
|
||||
attachment.write("ignored")
|
||||
yield* pty.remove(info.id)
|
||||
attachment.activate()
|
||||
expect(yield* Deferred.await(ended).pipe(Effect.timeout("5 seconds"))).toEqual({ exitCode: 7 })
|
||||
attachment.detach()
|
||||
}),
|
||||
)
|
||||
// kilocode_change end
|
||||
})
|
||||
|
||||
const configuredShell = process.platform === "win32" ? undefined : Bun.which("bash")
|
||||
|
||||
@@ -61,4 +61,18 @@ describe("Ripgrep", () => {
|
||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
||||
),
|
||||
)
|
||||
|
||||
// kilocode_change start - surfaced error keeps the underlying reason
|
||||
it.live("includes the underlying reason in execution failures", () =>
|
||||
Effect.gen(function* () {
|
||||
const ripgrep = yield* Ripgrep.Service
|
||||
const controller = new AbortController()
|
||||
controller.abort()
|
||||
const error = yield* ripgrep
|
||||
.find({ cwd: process.cwd(), pattern: "*", limit: 1, signal: controller.signal })
|
||||
.pipe(Effect.flip)
|
||||
expect(error.message).toMatch(/^ripgrep execution failed: .+/)
|
||||
}),
|
||||
)
|
||||
// kilocode_change end
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "kilo"
|
||||
name = "Kilo"
|
||||
description = "The open source coding agent."
|
||||
version = "7.4.16"
|
||||
version = "7.4.17"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/Kilo-Org/kilocode"
|
||||
@@ -11,26 +11,26 @@ name = "Kilo"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.16/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.17/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.16/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.17/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.16/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.17/opencode-linux-arm64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.16/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.17/opencode-linux-x64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.16/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.17/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"description": "Record and replay Effect HTTP client traffic with deterministic cassettes",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-console",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -13,7 +13,11 @@ export function SourcesRoute() {
|
||||
<ConfigToolbar
|
||||
title="Load Order"
|
||||
description="Load order and editability without exposing secret values."
|
||||
meta={<Tag>{data().overlay.targets.active ?? "Read only"}</Tag>}
|
||||
meta={
|
||||
<Tag>
|
||||
{data().overlay.targets.active.writable ? data().overlay.targets.active.scope : "Read only"}
|
||||
</Tag>
|
||||
}
|
||||
/>
|
||||
|
||||
<div class="table" role="table" aria-label="Config sources">
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { createMemo, createSignal, For, Show } from "solid-js"
|
||||
import { ConfigRow, SectionTitle, StatusTag } from "@kilocode/kilo-web-ui/console"
|
||||
import { Button } from "@kilocode/kilo-web-ui/button"
|
||||
import { Card } from "@kilocode/kilo-web-ui/card"
|
||||
import { SearchField } from "../../components/SearchField"
|
||||
import { useConfig } from "../../context/config"
|
||||
import { toolCapabilities, toolName } from "../../shared/utils"
|
||||
import { ConfigCountTag as CountTag, ConfigPage } from "./ConfigPage"
|
||||
import { ConfigCountTag as CountTag, ConfigPage, ConfigTag as Tag, SourceBadge } from "./ConfigPage"
|
||||
|
||||
export function ToolsRoute() {
|
||||
const ctx = useConfig()
|
||||
const [search, setSearch] = createSignal("")
|
||||
const snap = () => ctx.data()
|
||||
const websearch = createMemo(() => snap()?.overlay.fields.web_search)
|
||||
const searchEnabled = createMemo(() => websearch()?.value === true)
|
||||
const rows = createMemo(() => {
|
||||
const data = snap()
|
||||
if (!data) return []
|
||||
@@ -52,6 +56,48 @@ export function ToolsRoute() {
|
||||
}
|
||||
description="Built-in tools available to agents, including file access, terminal execution, search, fetch, and orchestration tools."
|
||||
>
|
||||
<Card class="ui-card" padding={0}>
|
||||
<header class="ui-card-header">
|
||||
<div>
|
||||
<h2>Web search</h2>
|
||||
<p>Control web search availability for models from providers that do not enable it by default.</p>
|
||||
</div>
|
||||
<Show when={ctx.query()?.scope === "project" && websearch()?.overridden}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={Boolean(ctx.saving())}
|
||||
onClick={() => ctx.unset([["web_search"]])}
|
||||
>
|
||||
Revert
|
||||
</Button>
|
||||
</Show>
|
||||
</header>
|
||||
<div class="ui-form">
|
||||
<button
|
||||
class="ui-toggle"
|
||||
classList={{ selected: searchEnabled() }}
|
||||
type="button"
|
||||
aria-pressed={searchEnabled()}
|
||||
disabled={Boolean(ctx.saving()) || websearch()?.editable === false}
|
||||
onClick={() => ctx.save({ web_search: !searchEnabled() })}
|
||||
>
|
||||
<span>
|
||||
<strong>Enable for all providers</strong>
|
||||
<small>Search requests use Exa or Parallel.</small>
|
||||
<Show when={websearch()?.reason}>{(reason) => <small>{reason()}</small>}</Show>
|
||||
</span>
|
||||
<span class="tags">
|
||||
<SourceBadge
|
||||
source={websearch()?.source}
|
||||
inherited={websearch()?.inherited}
|
||||
overridden={websearch()?.overridden}
|
||||
/>
|
||||
<Tag tone={searchEnabled() ? "success" : "neutral"}>{searchEnabled() ? "On" : "Off"}</Tag>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<SearchField
|
||||
label="Filter tools"
|
||||
value={search()}
|
||||
|
||||
@@ -46,6 +46,7 @@ export const AiProvidersNav: NavSection[] = [
|
||||
{ href: "/ai-providers/groq", children: "Groq" },
|
||||
{ href: "/ai-providers/cerebras", children: "Cerebras" },
|
||||
{ href: "/ai-providers/fireworks", children: "Fireworks AI" },
|
||||
{ href: "/ai-providers/mixlayer", children: "Mixlayer" },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-docs",
|
||||
"version": "7.4.16",
|
||||
"version": "7.4.17",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --webpack --port 3002",
|
||||
|
||||
@@ -53,7 +53,7 @@ Then set your default model:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"model": "cerebras/llama-4-scout-17b-16e-instruct",
|
||||
"model": "cerebras/gpt-oss-120b",
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: "Using Mixlayer with Kilo Code | Fast Open-Model Inference"
|
||||
description: "Run open models like GLM and Qwen on Mixlayer's OpenAI-compatible API in Kilo Code. Setup guide for VS Code and the CLI."
|
||||
---
|
||||
|
||||
# Using Mixlayer With Kilo Code
|
||||
|
||||
Mixlayer is an inference platform for open models such as GLM and Qwen, with a serving stack built from scratch by core contributors to Candle. It exposes an OpenAI-compatible API and is available as a built-in provider in Kilo Code.
|
||||
|
||||
**Website:** [https://mixlayer.com/](https://mixlayer.com/)
|
||||
|
||||
## Getting an API Key
|
||||
|
||||
1. **Sign Up/Sign In:** Go to [Mixlayer](https://mixlayer.com/) and create an account or sign in.
|
||||
2. **Navigate to API Keys:** Open the [Mixlayer console](https://console.mixlayer.com/) and go to the API Keys page.
|
||||
3. **Create a Key:** Click **New Key**, give it a descriptive name (e.g., "Kilo Code"), and copy it. You will not be able to view it again.
|
||||
|
||||
## Configuration in Kilo Code
|
||||
|
||||
Mixlayer is available as a **built-in provider** in Kilo Code, so you can connect it directly — no custom provider setup needed.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="VSCode" %}
|
||||
|
||||
1. Open **Settings** (gear icon) and go to the **Providers** tab.
|
||||
2. Click **Connect provider**, search for **Mixlayer**, and select it.
|
||||
3. Enter your Mixlayer API key.
|
||||
4. Pick a model — Kilo Code fetches the available models automatically.
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="CLI" %}
|
||||
|
||||
**Method 1 — `/connect` (recommended)**
|
||||
|
||||
Run `kilo`, then use the `/connect` command, select **Mixlayer**, and paste your API key when prompted:
|
||||
|
||||
```bash
|
||||
kilo
|
||||
# then, inside Kilo, run:
|
||||
/connect
|
||||
```
|
||||
|
||||
**Method 2 — config file**
|
||||
|
||||
Set your API key and add Mixlayer in your `kilo.json` config file (`~/.config/kilo/kilo.json` or `./kilo.json`):
|
||||
|
||||
```bash
|
||||
export MIXLAYER_API_KEY="your-api-key"
|
||||
```
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"provider": {
|
||||
"mixlayer": {
|
||||
"env": ["MIXLAYER_API_KEY"],
|
||||
},
|
||||
},
|
||||
"model": "mixlayer/z-ai/glm-5.2",
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## Models
|
||||
|
||||
Mixlayer serves open models including:
|
||||
|
||||
- `z-ai/glm-5.2` — 256K context
|
||||
- `qwen/qwen3.5-397b-a17b` and the Qwen 3.5 / 3.6 line (vision-capable)
|
||||
- `moonshotai/kimi-k2.7-code`
|
||||
|
||||
Tool calling and reasoning are supported across the model line. See the [Mixlayer docs](https://docs.mixlayer.com) for the full, current model list and supported parameters.
|
||||
|
||||
## Tips and Notes
|
||||
|
||||
- **Model list:** Kilo Code auto-detects available models from Mixlayer's `/v1/models` endpoint, so the picker stays current with your account.
|
||||
- **Pricing:** See the [Mixlayer console](https://console.mixlayer.com/) for current per-model pricing.
|
||||
- **Reasoning:** Qwen models support a thinking mode; reasoning tokens count against the output budget, so give responses enough room when reasoning is enabled.
|
||||
@@ -159,7 +159,9 @@ Each request can include 1-20 tasks. Each task must include at least one of `pro
|
||||
|
||||
The companion `agent_manager_models` tool searches models and their supported reasoning variants on demand. Results are grouped by model name (with the offering providers listed for reference) and limited to 20 per call, so the full catalog is never added to the conversation context.
|
||||
|
||||
The tool uses the `agent_manager` permission. Approval prompts are scoped to the requested capability, so approving `worktree` does not automatically approve `local`, an overview, or a targeted prompt. Prompting an existing managed session requires an explicit `prompt` approval the first time, even if Agent Manager session creation was previously approved broadly.
|
||||
The same tool also manages existing sessions. It can return a compact overview of sections, worktrees, and local sessions, send a prompt to one managed session, or stop a managed session. Stopping aborts the session's active work and removes it from the panel, just like closing the session tab.
|
||||
|
||||
The tool uses the `agent_manager` permission. Approval prompts are scoped to the requested capability, so approving `worktree` does not automatically approve `local`, an overview, or a targeted prompt. Prompting an existing managed session requires an explicit `prompt` approval the first time, even if Agent Manager session creation was previously approved broadly. Stopping a session likewise requires an explicit `stop` approval.
|
||||
|
||||
## Sections
|
||||
|
||||
@@ -241,6 +243,8 @@ Create a script file in `.kilo/` using the appropriate filename for your platfor
|
||||
|
||||
Kilo runs the script automatically whenever a new worktree is created. It uses `sh` for POSIX scripts, PowerShell for `.ps1`, and `cmd.exe` for `.cmd` / `.bat`, so executable permissions are not required.
|
||||
|
||||
Where the script runs follows the terminal destination dropdown in the Agent Manager toolbar. **Agent Manager panel** shows live output in a named `Setup` tab in the side terminal panel. After success, the panel returns to its previous state unless you interacted with it; the retained tab remains available for review. Failures keep the panel open. **VS Code terminal** runs setup as a task in the integrated terminal. The script keeps the existing five-minute timeout; when it expires, the setup process tree is terminated and the failed tab retains its partial output.
|
||||
|
||||
Two extra variables are injected into the setup script's environment:
|
||||
|
||||
| Variable | Value |
|
||||
@@ -263,7 +267,7 @@ if [ -f "$REPO_PATH/apps/web/.env.local" ] && [ ! -f "$WORKTREE_PATH/apps/web/.e
|
||||
fi
|
||||
```
|
||||
|
||||
If the setup script fails, Agent Manager shows the failure and keeps the worktree available so you can inspect it, fix the script, or run setup steps manually.
|
||||
If the setup script fails, Agent Manager shows the failure (a failed `Setup` tab in the side terminal panel, or the task output in the integrated terminal) and keeps the worktree available so you can inspect it, fix the script, or run setup steps manually.
|
||||
|
||||
### Environment File Copying
|
||||
|
||||
@@ -339,10 +343,12 @@ Two extra variables are injected into the script's environment:
|
||||
|
||||
### Using the run button
|
||||
|
||||
- **Run:** Click the play button in the toolbar or press `Cmd+E` (macOS) / `Ctrl+E` (Windows/Linux). Output appears in a dedicated VS Code task panel.
|
||||
- **Run:** Click the play button in the toolbar or press `Cmd+E` (macOS) / `Ctrl+E` (Windows/Linux). Output appears in a named `Run` tab in the Agent Manager terminal panel and remains available after the script exits.
|
||||
- **Stop:** Click the stop button (same position) or press `Cmd+E` again while running.
|
||||
- **Configure:** Click the dropdown arrow next to the run button and select "Configure run script" to open the script in your editor.
|
||||
|
||||
The terminal destination dropdown in the Agent Manager toolbar also controls where the script runs. **Agent Manager panel** uses the named side terminal, while **VS Code terminal** runs it as a task in the integrated terminal. The integrated terminal option is kept for comparison and will be removed in a future release.
|
||||
|
||||
## Session State and Persistence
|
||||
|
||||
Agent Manager state is persisted in `.kilo/agent-manager.json`. It stores worktrees, sections, session tabs, ordering, collapsed state, diff preferences, and cached PR metadata. Git branches and worktree directories remain on disk separately.
|
||||
|
||||
@@ -25,9 +25,16 @@ Type `@` in the chat input to get autocomplete suggestions. You can mention:
|
||||
| **File** | Attach a file's contents to your message | `@src/utils.ts` |
|
||||
| **Terminal** | Include your active VS Code terminal output | `@terminal` |
|
||||
| **Git Changes** | Attach uncommitted working-tree diffs and new files | `@git-changes` |
|
||||
| **Past chats** | Attach a previous session's transcript as context | `@` → **Past chats** → pick a session |
|
||||
|
||||
Selecting a suggestion inserts the mention and highlights it in the input. File contents, terminal output, and git changes are attached as context when you send the message.
|
||||
|
||||
### Referencing Past Chats
|
||||
|
||||
Choosing **Past chats** from the `@` menu opens a searchable picker of your previous sessions in the current workspace, ordered by recency. Selecting a session inserts a highlighted mention token; when you send the message, that session's current transcript is attached as context so the agent can build on the earlier conversation. Clicking the mention token opens the referenced session.
|
||||
|
||||
Very long transcripts are truncated, keeping the beginning and end, so a single mention cannot fill the context window.
|
||||
|
||||
### Drag and Drop
|
||||
|
||||
You can also add file mentions by dragging and dropping:
|
||||
@@ -70,6 +77,7 @@ This means the agent can explore your entire project as needed, rather than bein
|
||||
| **Mention files when helpful** | If you know the exact file, mention its path to save the agent a search step |
|
||||
| **Keep editor tabs relevant** | Open tabs are passed as context, so keep relevant files open |
|
||||
| **Trust the agent's tools** | The agent can search, read, and explore your codebase — let it do the discovery work |
|
||||
| **Reference a past chat** | Type `@` and choose **Past chats** to open a searchable picker of previous sessions in the current workspace. Selecting a session attaches its current transcript as context when you send the message. |
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="CLI" %}
|
||||
|
||||
@@ -26,6 +26,7 @@ While the specifics change constantly, some principles stay consistent:
|
||||
|
||||
- Use the **model selector** in the chat prompt area to pick a model for the current session. You can also type `/models` to open the model picker.
|
||||
- When the selected model supports variants, type `/variant` to open the reasoning effort selector.
|
||||
- Press `Shift+Tab` in the prompt input to cycle to the next reasoning effort variant, wrapping after the last one. This works in the sidebar chat, the Agent Manager prompt, and the New Worktree dialog, and the variant selector tooltip shows the shortcut on hover. To keep `Shift+Tab` for keyboard focus navigation instead, disable the `kilo-code.new.chat.shiftTabCyclesVariant` setting (also available under **Settings → Display**).
|
||||
- Set per-agent defaults and a global default in the **Settings** panel (Models tab), or directly in the `kilo.jsonc` config file.
|
||||
- **Model precedence:** Session override → Last picked per agent → Per-agent config → Global config → [Auto Free](/docs/code-with-ai/agents/auto-model#tiers) (note: Auto Free may route to providers that log prompts — see the Auto Model page for details).
|
||||
- The model selector remembers the last model you picked for each agent — switching agents restores your previous choice. A manual pick always beats config settings; use the **reset button** (visible when your active model differs from config) to go back to the config default.
|
||||
|
||||
@@ -100,6 +100,10 @@ Clicking **Revert to here** does two things:
|
||||
|
||||
The button is only active when the agent is idle. While the agent is running, the button is disabled to prevent reverting mid-operation.
|
||||
|
||||
{% callout type="note" %}
|
||||
Workspace restoration requires snapshots. If snapshots are disabled or the reverted range has no stored checkpoint, only the conversation is rewound — the agent's file changes remain on disk, and the revert banner warns you (see below).
|
||||
{% /callout %}
|
||||
|
||||
### The Revert Banner
|
||||
|
||||
After reverting, a **Revert Banner** appears at the bottom of the chat. The banner shows:
|
||||
@@ -113,6 +117,11 @@ The banner provides two actions:
|
||||
- **Redo** — Steps forward one message at a time, re-applying changes from the next reverted message
|
||||
- **Redo All** — Restores the workspace to the latest state and un-hides all messages (only shown when more than one message is reverted)
|
||||
|
||||
If a revert could not restore your workspace files, the banner warns you that only the conversation was rewound:
|
||||
|
||||
- **Snapshots disabled** — the banner explains that file changes were not restored because snapshots are disabled, and offers an **Enable snapshots** button that opens **Settings → Checkpoints**.
|
||||
- **No checkpoint available** — the banner explains that no file checkpoint was available, so workspace changes remain on disk (for example, when reverting a range that predates checkpoints).
|
||||
|
||||
### Making a Revert Permanent
|
||||
|
||||
While in a reverted state, you have two choices:
|
||||
|
||||
@@ -36,6 +36,16 @@ Before using Cloud Agents:
|
||||
|
||||
Your work is always pushed to GitHub, ensuring nothing is lost.
|
||||
|
||||
## Starting Tasks from the CLI
|
||||
|
||||
Use the `kilo cloud` command to run Cloud Agent tasks without opening the browser:
|
||||
|
||||
```bash
|
||||
kilo cloud start --prompt "Fix the flaky login test" --repo Kilo-Org/kilocode
|
||||
```
|
||||
|
||||
`kilo cloud` can start tasks, send follow-up prompts, and check task status and results. Repository, branch, model, mode, and organization are inferred from your local checkout and CLI defaults unless you pass the matching flags. Add `--stream` to `kilo cloud start` to print task events as JSONL until the task completes. See the [CLI reference](/docs/code-with-ai/platforms/cli-reference#kilo-cloud) for all commands and options.
|
||||
|
||||
## How Cloud Agents Work
|
||||
|
||||
- Each user receives an **isolated Linux container** with common dev tools preinstalled (Node.js, git, gh CLI, glab CLI, etc.).
|
||||
@@ -96,7 +106,7 @@ You can customize each Cloud Agent session by also defining env vars and startup
|
||||
|
||||
## Skills
|
||||
|
||||
Cloud Agents support project-level [skills](/docs/code-with-ai/platforms/cli#skills) stored in your repository. When your repo is cloned, any skills in `.kilocode/skills/` are automatically available.
|
||||
Cloud Agents support project-level [skills](/docs/code-with-ai/platforms/cli#skills) stored in your repository. When your repo is cloned, any skills in `.kilocode/skills/` are automatically available. Skill folders are uploaded as `.zip` archives, with up to 40 companion files per skill.
|
||||
|
||||
{% callout type="note" %}
|
||||
Global skills (`~/.kilocode/skills/`) are not available in Cloud Agents since there is no persistent user home directory.
|
||||
|
||||
@@ -8,3 +8,11 @@ description: "Using Kilo Code in JetBrains IDEs"
|
||||
## Installation
|
||||
|
||||
{% partial file="install-jetbrains.md" /%}
|
||||
|
||||
## Settings
|
||||
|
||||
Open **Settings → Tools → Kilo Code** to configure the plugin. The JetBrains plugin reads and writes the same shared `kilo.jsonc` config files as the CLI and the VS Code extension, so changes apply across clients. See [Settings](/docs/getting-started/settings) for config file locations and precedence.
|
||||
|
||||
- **Auto-Approve** — set per-tool permission levels (Allow / Ask / Deny) and manage granular command and path exceptions without editing config by hand. Permission prompts offer one-time approvals alongside saved allow/reject rules. See [Auto-Approving Actions](/docs/getting-started/settings/auto-approving-actions) for the shared permission model.
|
||||
- **Context** — toggle auto-compaction, set the auto-compaction limit (the percentage of the model window that triggers compaction), enable pruning of old tool outputs, and manage file watcher ignore patterns. See [Context Condensing](/docs/customize/context/context-condensing) and [.kilocodeignore](/docs/customize/context/kilocodeignore) for what these settings control.
|
||||
- **Agent Behavior → Skills** — inspect loaded skills, add extra skill sources (local paths or remote URLs), edit or remove custom skills, and open skill files in the editor. See [Skills](/docs/customize/skills) for the skill format and discovery rules.
|
||||
|
||||
@@ -20,6 +20,10 @@ The mobile app lets you:
|
||||
- Spawn Cloud Agents and code directly from the app.
|
||||
- Monitor and view all non-remote sessions in one place.
|
||||
- Create, onboard, and manage KiloClaw instances.
|
||||
- Send follow-up messages while a session is still running — they are queued and processed in order.
|
||||
- Run slash commands (like `/compact`) on connected remote CLI sessions, and start a new session in the same workspace with `/new`. Older CLI versions that do not support remote commands prompt you to upgrade.
|
||||
- Review GitHub pull requests end to end — diffs, checks, comments, and merging.
|
||||
- Start a new session on a connected `kilo remote` CLI instance with the **Run on** picker.
|
||||
|
||||
## Kilo Pass and Billing
|
||||
|
||||
@@ -39,6 +43,43 @@ For Kilo Pass pricing, billing, and account management details, use the [Kilo Pa
|
||||
{% image src="/docs/img/mobile-apps/session-filters.webp" alt="Kilo Code mobile session filter panel for Cloud Extension CLI Slack and other platforms" caption="Filter sessions by platform and project, including Cloud, Extension, CLI, Slack, and other sessions." /%}
|
||||
{% /imageGallery %}
|
||||
|
||||
## Choosing where a session runs
|
||||
|
||||
The new-session screen includes a **Run on** picker that chooses where your session runs:
|
||||
|
||||
- **Cloud Agent** — the managed cloud environment (the default).
|
||||
- **A connected CLI instance** — a `kilo remote` CLI running on your own machine. The picker lists the instances currently connected to your account.
|
||||
|
||||
Remote sessions use the CLI's own defaults, so the composer skips model, mode, and repository selection; you type your first prompt in the chat after the session starts. Sessions started in an organization context always run on the Cloud Agent, so the picker does not appear there.
|
||||
|
||||
## Queueing follow-up messages
|
||||
|
||||
The composer stays editable while the agent is working, so you don't have to wait for a session to finish before sending your next message. Type your follow-up and press **Send** to add it to the session's queue; queued messages are processed in order. While a session is streaming, **Stop** appears only when the composer is empty — with text entered, Send takes its place.
|
||||
|
||||
A queued message shows a subtle **Queued** badge on its bubble. The badge clears when the message starts processing or when the queue drains or is cancelled. Queueing works for Cloud Agent sessions and for remote sessions on a connected `kilo remote` CLI instance.
|
||||
|
||||
## Reviewing GitHub pull requests
|
||||
|
||||
Open a pull request from a PR link to review it without leaving the app:
|
||||
|
||||
- **Overview** — PR state and CI checks at a glance.
|
||||
- **Files** — syntax-highlighted diffs with line-level comments and a file navigator.
|
||||
- **Discussion** — review threads with replies, resolve/unresolve, and reactions.
|
||||
|
||||
Comments you leave are collected into a pending review on your device and submitted to GitHub as a single review. When the PR is ready, you can merge it (merge, squash, or rebase), enable or disable auto-merge, or update the branch — all from the app.
|
||||
|
||||
PR review uses your connected GitHub account; the app asks you to connect GitHub if you have not already.
|
||||
|
||||
## Session cost and model details
|
||||
|
||||
The app shows what each session cost and which models did the work:
|
||||
|
||||
- **Session list** — a finished session with a recorded cost shows it in the row's meta line (for example, `$0.12 · 5m ago`). Sessions that are still running or have no cost show no cost.
|
||||
- **Cost breakdown** — open a session's Context usage sheet to see a Token usage section (input, output, reasoning, cache read, and cache write tokens, plus the cache hit rate) and a collapsible Models section with each model's name, provider, step count, and cost. A Subagents row covers any remaining spend, so the per-model costs always add up to the session total.
|
||||
- **Per-message model label** — assistant messages show a dimmed model label on the first assistant reply and whenever the model changes during the session. Turns routed by [Auto Model](/docs/code-with-ai/agents/auto-model) show the concrete model that handled the turn.
|
||||
|
||||
Cost is recorded when a session closes; sessions that closed before this feature shipped do not show a cost.
|
||||
|
||||
## Android App
|
||||
|
||||
The Android app is available now on Google Play.
|
||||
|
||||
@@ -97,6 +97,7 @@ BYOK lets you use your own provider API keys with the Kilo AI Gateway. When a BY
|
||||
| Kimi Code | `kimi-coding` |
|
||||
| Martian | `martian` |
|
||||
| Neuralwatt | `neuralwatt` |
|
||||
| NVIDIA | `nvidia-byok` |
|
||||
| Ollama Cloud | `ollama-cloud` |
|
||||
| OpenCode Go | `opencode-go` |
|
||||
| OrcaRouter | `orcarouter` |
|
||||
|
||||
@@ -49,6 +49,7 @@ These providers offer coding-focused subscriptions or dedicated endpoints. Bring
|
||||
- Martian
|
||||
- Mistral Codestral
|
||||
- Neuralwatt
|
||||
- NVIDIA
|
||||
- Ollama Cloud
|
||||
- OpenCode Go
|
||||
- OrcaRouter
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user