sigit-code 1.6.1__tar.gz → 1.6.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.agents/AGENTS.md +52 -8
- {sigit_code-1.6.1/.claude → sigit_code-1.6.3/.agents}/skills/agent-client-protocol/SKILL.md +1 -0
- {sigit_code-1.6.1/.agents → sigit_code-1.6.3/.claude}/skills/agent-client-protocol/SKILL.md +1 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/AGENTS.md +52 -8
- {sigit_code-1.6.1 → sigit_code-1.6.3}/CHANGELOG.md +88 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/CLAUDE.md +52 -8
- {sigit_code-1.6.1 → sigit_code-1.6.3}/Cargo.lock +2 -1
- {sigit_code-1.6.1 → sigit_code-1.6.3}/Cargo.toml +6 -1
- {sigit_code-1.6.1 → sigit_code-1.6.3}/PKG-INFO +1 -1
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/backend.rs +64 -12
- sigit_code-1.6.3/src/client_fs.rs +350 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/main.rs +1197 -211
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/tools.rs +379 -180
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/workspace.rs +1 -1
- sigit_code-1.6.3/tests/acp_client_fs.rs +549 -0
- sigit_code-1.6.3/tests/acp_concurrent_sessions.rs +343 -0
- sigit_code-1.6.3/tests/acp_logout.rs +179 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/tests/acp_permissions.rs +138 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/tests/acp_session_load.rs +170 -0
- sigit_code-1.6.3/tests/version_flag.rs +32 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.agents/skills/ai-assisted-coding/SKILL.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.agents/skills/branding/SKILL.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.agents/skills/run-sigit/SKILL.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.agents/skills/run-sigit/driver.mjs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.agents/skills/run-sigit/tui-smoke.sh +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.agents/skills/sigit-code-release/SKILL.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.agents/skills/tool-calling/SKILL.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.cargo/config.toml +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.claude/skills/ai-assisted-coding/SKILL.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.claude/skills/branding/SKILL.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.claude/skills/run-sigit/SKILL.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.claude/skills/run-sigit/driver.mjs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.claude/skills/run-sigit/tui-smoke.sh +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.claude/skills/sigit-code-release/SKILL.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.claude/skills/tool-calling/SKILL.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.github/workflows/ci.yml +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.github/workflows/release-aur.yml +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.github/workflows/release-crates.yml +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.github/workflows/release-github.yml +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.github/workflows/release-homebrew.yml +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.github/workflows/release-npm.yml +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.github/workflows/release-nuget.yml +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.github/workflows/release-pypi.yml +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.github/workflows/release-scoop.yml +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.github/workflows/release-winget.yml +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.gitignore +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.nvmrc +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/.sigit/factory/config.toml +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/LICENSE +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/README.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/docs/headless.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/docs/hooks.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/docs/mcp.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/examples/settings-with-hooks.toml +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/examples/skills/README.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/examples/skills/commit-message/SKILL.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/npm/README.md.tmpl +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/npm/package-compat.json.tmpl +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/npm/package-main.json.tmpl +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/npm/package.json.tmpl +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/npm/scripts/render-main-package.cjs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/npm/scripts/render-platform-package.cjs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/npm/sigit/.gitignore +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/npm/sigit/README.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/npm/sigit/package.json +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/npm/sigit/src/index.ts +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/npm/sigit/tsconfig.json +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/nuget/.gitignore +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/nuget/sigit/Program.cs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/nuget/sigit/README.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/nuget/sigit/SiGit.Code.csproj +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/packaging/aur/PKGBUILD.in +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/packaging/nfpm.yaml +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/packaging/windows/check-static-crt.ps1 +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/packaging/winget/getSigit.siGitCode.installer.yaml.in +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/packaging/winget/getSigit.siGitCode.locale.en-US.yaml.in +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/packaging/winget/getSigit.siGitCode.yaml.in +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/pypi/README.md +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/pypi/pyproject.toml +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/pyproject.toml +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/rust-toolchain.toml +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/account.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/browser_auth.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/chat.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/commands.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/credentials.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/frontmatter.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/headless.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/hooks.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/inline_tool_calls.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/instructions.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/mcp.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/models.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/permissions.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/provider.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/session_store.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/settings.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/setup.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/skills.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/src/subagents.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/tests/acp_endpoint_errors.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/tests/acp_images.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/tests/acp_multi_root.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/tests/acp_session_list.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/tests/acp_stop_reasons.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/tests/acp_tool_stdin.rs +0 -0
- {sigit_code-1.6.1 → sigit_code-1.6.3}/tests/headless_mode.rs +0 -0
|
@@ -113,9 +113,20 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
113
113
|
- **`src/main.rs`** — entry point, mode dispatch, the full ACP `Agent` impl (session lifecycle:
|
|
114
114
|
new/load/fork/prompt/cancel, config options, slash-command advertisement), and the `SYSTEM_PROMPT`.
|
|
115
115
|
ACP session state owns its roots, conversation, and selected model even though the process has
|
|
116
|
-
one live backend; activating a thread parks and restores all
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
one working directory and one live backend slot; activating a thread parks and restores all
|
|
117
|
+
three. A thread on an HTTP backend keeps a backend of its own (`InferenceBackend::fresh`,
|
|
118
|
+
`SessionState::remote`), so its turn can wait on the endpoint while another thread is
|
|
119
|
+
installed. Unknown session ids are rejected instead of silently borrowing the active thread's
|
|
120
|
+
cwd. Prompt cancellation takes no lock, which lets a client cancel a turn whatever it holds.
|
|
121
|
+
`session/load` and `session/resume` share `restore_session`; the only difference is that
|
|
122
|
+
resume must not replay the history as `session/update`. `session/close` is the one place a
|
|
123
|
+
`SessionState` is dropped. It runs in two halves: `begin_close` signals the session's turn
|
|
124
|
+
from the dispatch loop (the turn holds the session's lock, so the signal cannot wait for it) and
|
|
125
|
+
marks the id in `closing_sessions`, which makes `handle_prompt` cancel a prompt that was
|
|
126
|
+
still queued; `handle_close_session` then runs under the session and workspace locks and
|
|
127
|
+
removes the state, the permission grants and the background commands
|
|
128
|
+
(`tools::kill_session_tasks`). It leaves `session_store` alone, so a closed thread still lists
|
|
129
|
+
and reopens. The
|
|
119
130
|
`SYSTEM_PROMPT` bakes in smbCloud-specific context the agent should use when the repo is clearly
|
|
120
131
|
smbCloud, and stay general otherwise.
|
|
121
132
|
- **`src/backend.rs`** — the `InferenceBackend` trait and neutral types (`ToolSpec`, `ToolCall`,
|
|
@@ -268,10 +279,21 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
268
279
|
announced, because it sets the announced status: a call that will ask starts `pending`, the
|
|
269
280
|
permission request carries that call's own id, and an `in_progress` update follows approval.
|
|
270
281
|
That update also puts the card's title back, since the permission request overwrites it
|
|
271
|
-
with the full arguments.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
282
|
+
with the full arguments. The Manual/Auto/Plan choice reaches an ACP client twice: as the
|
|
283
|
+
`sigit-permission-mode` config option, and as session `modes` with `session/set_mode`, which
|
|
284
|
+
the spec is retiring but some clients still render instead. Both are built from
|
|
285
|
+
`PERMISSION_MODES` in `main.rs` and read the same state, so whatever changes the mode has to
|
|
286
|
+
refresh both (`ConfigOptionUpdate` and `send_current_mode`). Note: ACP turn-affecting handlers run in `cx.spawn`ed tasks
|
|
287
|
+
so the dispatch loop can route the client's permission answer mid-turn — don't move them back
|
|
288
|
+
inline, and don't await client requests from inline handlers (deadlock). Two locks order them.
|
|
289
|
+
A per-session lock (`SiGitAgent::session_lock`) is held for a whole request, so requests on one
|
|
290
|
+
thread keep their order. `SiGitAgent::workspace_lock` guards what a session installs
|
|
291
|
+
process-wide (cwd, workspace roots, session MCP servers, the live backend slot, the engine's
|
|
292
|
+
conversation); lifecycle and config handlers hold it throughout, and `handle_prompt` holds it
|
|
293
|
+
through a `WorkspaceHold` that it releases while waiting on an HTTP endpoint or a permission
|
|
294
|
+
answer and retakes (reinstalling its session via `resume_workspace`) before any tool runs. An
|
|
295
|
+
on-device turn never releases it. Take the session lock first, never the other way round. Tool
|
|
296
|
+
execution, subagents included, still runs one session at a time.
|
|
275
297
|
- **`src/instructions.rs`** — project instruction files, the always-on counterpart to skills.
|
|
276
298
|
Reads `AGENTS.md` (the cross-tool [agents.md](https://agents.md) standard) and `CLAUDE.md`,
|
|
277
299
|
walking from the session cwd up to the repo root (nearest ancestor with `.git`, never above it),
|
|
@@ -299,6 +321,23 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
299
321
|
every root. MCP is deliberately not on that list — `mcp::init` runs once at startup, before
|
|
300
322
|
any session exists, so a second root's `.sigit/mcp.toml` has nobody to tell. (The servers a
|
|
301
323
|
client names in `mcpServers` are a different thing and are per session; see `src/mcp.rs`.)
|
|
324
|
+
- **`src/client_fs.rs`** — file reads and writes through the ACP client. A client that
|
|
325
|
+
advertises `fs.readTextFile` / `fs.writeTextFile` in `initialize` serves `fs/read_text_file`
|
|
326
|
+
and `fs/write_text_file` from its buffers, so `read_file` sees unsaved changes and
|
|
327
|
+
`edit_file` lands in the open buffer instead of underneath it. `tools.rs` has no access to the
|
|
328
|
+
connection, so this is a seam like the one `mcp::call_tool` gives MCP: `handle_initialize`
|
|
329
|
+
registers a `ClientFileSystem` (`AcpClientFs` in `main.rs`, holding the connection), and
|
|
330
|
+
`execute_tool_impl` asks `route_for` before it dispatches `read_file`, `create_file`,
|
|
331
|
+
`edit_file` or `multi_edit`. Each of those tools is split into a parse step and a pure
|
|
332
|
+
render/apply step (`ReadFileCall`, `CreateFileCall`, `EditCall`) that the disk path and the
|
|
333
|
+
client path share, so the model gets the same result text either way. The two capabilities are
|
|
334
|
+
independent: a client that only reads still gets its edits written to disk. The disk is the
|
|
335
|
+
fallback throughout: nothing is registered in the TUI or headless modes, a path outside the
|
|
336
|
+
session's roots is not routed, and a request the client fails or leaves unanswered for 30
|
|
337
|
+
seconds is retried on disk with a warning in the log. The client is only ever asked from
|
|
338
|
+
inside a spawned prompt turn, never from `handle_initialize` itself (deadlock, same as
|
|
339
|
+
permission requests). Existence checks and `create_file`'s parent directories still use the
|
|
340
|
+
disk. `SIGIT_CLIENT_FS=off` turns the routing off. Covered by `tests/acp_client_fs.rs`.
|
|
302
341
|
- **`src/chat.rs`** — the Unix-only ratatui TUI. Loading-spinner phase then chat; uses
|
|
303
342
|
`tokio::select!` to multiplex terminal events with streaming tokens.
|
|
304
343
|
- **`src/headless.rs`** — non-interactive `sigit run` execution for scripts, CI, and Factory
|
|
@@ -320,6 +359,10 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
320
359
|
those read env vars once at init.
|
|
321
360
|
- **`src/account.rs`** — siGit Code Cloud auth (`/login`, `/logout`, `/whoami`); authenticates
|
|
322
361
|
against the account API and stores a session token. Performs no console I/O.
|
|
362
|
+
ACP `logout` (the editor's sign out button, shown only because `handle_initialize` advertises
|
|
363
|
+
`agentCapabilities.auth.logout`) and `/logout` both go through `SiGitAgent::sign_out` in
|
|
364
|
+
`main.rs`. The request names no session, so `leave_cloud` moves the parked threads off their
|
|
365
|
+
cloud tier along with the live one and each of them gets a fresh model picker.
|
|
323
366
|
- **`src/browser_auth.rs`** — browser sign-in, the path the editor's "Sign in to siGit Code"
|
|
324
367
|
button takes. sigit is a public OAuth client (client id `sigit-code-cli`, no secret, PKCE
|
|
325
368
|
S256) against the authorization server at `$SIGIT_API_URL/oauth`. Two ways the code gets
|
|
@@ -363,7 +406,8 @@ verbosity with `RUST_LOG`.
|
|
|
363
406
|
`OPENAI_BASE_URL` / `OPENAI_API_KEY` (provider override), `SIGIT_API_URL` (account API base,
|
|
364
407
|
default `https://sigit.si`), `SIGIT_CLOUD_URL`, `SIGIT_CONFIG_DIR` (default `~/.config/sigit`),
|
|
365
408
|
`SIGIT_MODEL`, `SIGIT_MAX_TOOL_ROUNDS` (1 to 500, default 24; the tool-round cap of a
|
|
366
|
-
headless run or an ACP prompt turn), `SIGIT_MCP` (`off` disables MCP), `
|
|
409
|
+
headless run or an ACP prompt turn), `SIGIT_MCP` (`off` disables MCP), `SIGIT_CLIENT_FS` (`off` keeps the file tools on disk even when the
|
|
410
|
+
ACP client offers `fs/read_text_file` / `fs/write_text_file`), `SIGIT_MCP_SMBCLOUD` (`off` drops the baked-in
|
|
367
411
|
smbCloud CLI server), `SIGIT_MCP_OFFICIAL` (`off` drops the baked-in
|
|
368
412
|
server), `SIGIT_PERMISSIONS` (`allow`/`ask`/`deny` — overrides the default permission mode for
|
|
369
413
|
mutating tools; the escape hatch for clients without permission-request support),
|
|
@@ -205,6 +205,7 @@ the connection and the agent anymore. Don't reintroduce the mpsc forwarder patte
|
|
|
205
205
|
| `ListSessionsRequest` | `handle_list_sessions` | the editor's "Import Threads" picker; gated by `SessionListCapabilities` |
|
|
206
206
|
| `PromptRequest` | `handle_prompt` | the turn: parse blocks → slash commands or tool-calling loop |
|
|
207
207
|
| `SetSessionConfigOptionRequest` | `handle_set_session_config_option` | the Zed model picker — switches/downloads models |
|
|
208
|
+
| `SetSessionModeRequest` | `handle_set_session_mode` | legacy mode selector; same Manual/Auto/Plan state as the Permissions config option |
|
|
208
209
|
| `CancelNotification` | `handle_cancel` | notification, no response |
|
|
209
210
|
|
|
210
211
|
Everything else is left to the SDK default (method not found).
|
|
@@ -205,6 +205,7 @@ the connection and the agent anymore. Don't reintroduce the mpsc forwarder patte
|
|
|
205
205
|
| `ListSessionsRequest` | `handle_list_sessions` | the editor's "Import Threads" picker; gated by `SessionListCapabilities` |
|
|
206
206
|
| `PromptRequest` | `handle_prompt` | the turn: parse blocks → slash commands or tool-calling loop |
|
|
207
207
|
| `SetSessionConfigOptionRequest` | `handle_set_session_config_option` | the Zed model picker — switches/downloads models |
|
|
208
|
+
| `SetSessionModeRequest` | `handle_set_session_mode` | legacy mode selector; same Manual/Auto/Plan state as the Permissions config option |
|
|
208
209
|
| `CancelNotification` | `handle_cancel` | notification, no response |
|
|
209
210
|
|
|
210
211
|
Everything else is left to the SDK default (method not found).
|
|
@@ -113,9 +113,20 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
113
113
|
- **`src/main.rs`** — entry point, mode dispatch, the full ACP `Agent` impl (session lifecycle:
|
|
114
114
|
new/load/fork/prompt/cancel, config options, slash-command advertisement), and the `SYSTEM_PROMPT`.
|
|
115
115
|
ACP session state owns its roots, conversation, and selected model even though the process has
|
|
116
|
-
one live backend; activating a thread parks and restores all
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
one working directory and one live backend slot; activating a thread parks and restores all
|
|
117
|
+
three. A thread on an HTTP backend keeps a backend of its own (`InferenceBackend::fresh`,
|
|
118
|
+
`SessionState::remote`), so its turn can wait on the endpoint while another thread is
|
|
119
|
+
installed. Unknown session ids are rejected instead of silently borrowing the active thread's
|
|
120
|
+
cwd. Prompt cancellation takes no lock, which lets a client cancel a turn whatever it holds.
|
|
121
|
+
`session/load` and `session/resume` share `restore_session`; the only difference is that
|
|
122
|
+
resume must not replay the history as `session/update`. `session/close` is the one place a
|
|
123
|
+
`SessionState` is dropped. It runs in two halves: `begin_close` signals the session's turn
|
|
124
|
+
from the dispatch loop (the turn holds the session's lock, so the signal cannot wait for it) and
|
|
125
|
+
marks the id in `closing_sessions`, which makes `handle_prompt` cancel a prompt that was
|
|
126
|
+
still queued; `handle_close_session` then runs under the session and workspace locks and
|
|
127
|
+
removes the state, the permission grants and the background commands
|
|
128
|
+
(`tools::kill_session_tasks`). It leaves `session_store` alone, so a closed thread still lists
|
|
129
|
+
and reopens. The
|
|
119
130
|
`SYSTEM_PROMPT` bakes in smbCloud-specific context the agent should use when the repo is clearly
|
|
120
131
|
smbCloud, and stay general otherwise.
|
|
121
132
|
- **`src/backend.rs`** — the `InferenceBackend` trait and neutral types (`ToolSpec`, `ToolCall`,
|
|
@@ -268,10 +279,21 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
268
279
|
announced, because it sets the announced status: a call that will ask starts `pending`, the
|
|
269
280
|
permission request carries that call's own id, and an `in_progress` update follows approval.
|
|
270
281
|
That update also puts the card's title back, since the permission request overwrites it
|
|
271
|
-
with the full arguments.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
282
|
+
with the full arguments. The Manual/Auto/Plan choice reaches an ACP client twice: as the
|
|
283
|
+
`sigit-permission-mode` config option, and as session `modes` with `session/set_mode`, which
|
|
284
|
+
the spec is retiring but some clients still render instead. Both are built from
|
|
285
|
+
`PERMISSION_MODES` in `main.rs` and read the same state, so whatever changes the mode has to
|
|
286
|
+
refresh both (`ConfigOptionUpdate` and `send_current_mode`). Note: ACP turn-affecting handlers run in `cx.spawn`ed tasks
|
|
287
|
+
so the dispatch loop can route the client's permission answer mid-turn — don't move them back
|
|
288
|
+
inline, and don't await client requests from inline handlers (deadlock). Two locks order them.
|
|
289
|
+
A per-session lock (`SiGitAgent::session_lock`) is held for a whole request, so requests on one
|
|
290
|
+
thread keep their order. `SiGitAgent::workspace_lock` guards what a session installs
|
|
291
|
+
process-wide (cwd, workspace roots, session MCP servers, the live backend slot, the engine's
|
|
292
|
+
conversation); lifecycle and config handlers hold it throughout, and `handle_prompt` holds it
|
|
293
|
+
through a `WorkspaceHold` that it releases while waiting on an HTTP endpoint or a permission
|
|
294
|
+
answer and retakes (reinstalling its session via `resume_workspace`) before any tool runs. An
|
|
295
|
+
on-device turn never releases it. Take the session lock first, never the other way round. Tool
|
|
296
|
+
execution, subagents included, still runs one session at a time.
|
|
275
297
|
- **`src/instructions.rs`** — project instruction files, the always-on counterpart to skills.
|
|
276
298
|
Reads `AGENTS.md` (the cross-tool [agents.md](https://agents.md) standard) and `CLAUDE.md`,
|
|
277
299
|
walking from the session cwd up to the repo root (nearest ancestor with `.git`, never above it),
|
|
@@ -299,6 +321,23 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
299
321
|
every root. MCP is deliberately not on that list — `mcp::init` runs once at startup, before
|
|
300
322
|
any session exists, so a second root's `.sigit/mcp.toml` has nobody to tell. (The servers a
|
|
301
323
|
client names in `mcpServers` are a different thing and are per session; see `src/mcp.rs`.)
|
|
324
|
+
- **`src/client_fs.rs`** — file reads and writes through the ACP client. A client that
|
|
325
|
+
advertises `fs.readTextFile` / `fs.writeTextFile` in `initialize` serves `fs/read_text_file`
|
|
326
|
+
and `fs/write_text_file` from its buffers, so `read_file` sees unsaved changes and
|
|
327
|
+
`edit_file` lands in the open buffer instead of underneath it. `tools.rs` has no access to the
|
|
328
|
+
connection, so this is a seam like the one `mcp::call_tool` gives MCP: `handle_initialize`
|
|
329
|
+
registers a `ClientFileSystem` (`AcpClientFs` in `main.rs`, holding the connection), and
|
|
330
|
+
`execute_tool_impl` asks `route_for` before it dispatches `read_file`, `create_file`,
|
|
331
|
+
`edit_file` or `multi_edit`. Each of those tools is split into a parse step and a pure
|
|
332
|
+
render/apply step (`ReadFileCall`, `CreateFileCall`, `EditCall`) that the disk path and the
|
|
333
|
+
client path share, so the model gets the same result text either way. The two capabilities are
|
|
334
|
+
independent: a client that only reads still gets its edits written to disk. The disk is the
|
|
335
|
+
fallback throughout: nothing is registered in the TUI or headless modes, a path outside the
|
|
336
|
+
session's roots is not routed, and a request the client fails or leaves unanswered for 30
|
|
337
|
+
seconds is retried on disk with a warning in the log. The client is only ever asked from
|
|
338
|
+
inside a spawned prompt turn, never from `handle_initialize` itself (deadlock, same as
|
|
339
|
+
permission requests). Existence checks and `create_file`'s parent directories still use the
|
|
340
|
+
disk. `SIGIT_CLIENT_FS=off` turns the routing off. Covered by `tests/acp_client_fs.rs`.
|
|
302
341
|
- **`src/chat.rs`** — the Unix-only ratatui TUI. Loading-spinner phase then chat; uses
|
|
303
342
|
`tokio::select!` to multiplex terminal events with streaming tokens.
|
|
304
343
|
- **`src/headless.rs`** — non-interactive `sigit run` execution for scripts, CI, and Factory
|
|
@@ -320,6 +359,10 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
320
359
|
those read env vars once at init.
|
|
321
360
|
- **`src/account.rs`** — siGit Code Cloud auth (`/login`, `/logout`, `/whoami`); authenticates
|
|
322
361
|
against the account API and stores a session token. Performs no console I/O.
|
|
362
|
+
ACP `logout` (the editor's sign out button, shown only because `handle_initialize` advertises
|
|
363
|
+
`agentCapabilities.auth.logout`) and `/logout` both go through `SiGitAgent::sign_out` in
|
|
364
|
+
`main.rs`. The request names no session, so `leave_cloud` moves the parked threads off their
|
|
365
|
+
cloud tier along with the live one and each of them gets a fresh model picker.
|
|
323
366
|
- **`src/browser_auth.rs`** — browser sign-in, the path the editor's "Sign in to siGit Code"
|
|
324
367
|
button takes. sigit is a public OAuth client (client id `sigit-code-cli`, no secret, PKCE
|
|
325
368
|
S256) against the authorization server at `$SIGIT_API_URL/oauth`. Two ways the code gets
|
|
@@ -363,7 +406,8 @@ verbosity with `RUST_LOG`.
|
|
|
363
406
|
`OPENAI_BASE_URL` / `OPENAI_API_KEY` (provider override), `SIGIT_API_URL` (account API base,
|
|
364
407
|
default `https://sigit.si`), `SIGIT_CLOUD_URL`, `SIGIT_CONFIG_DIR` (default `~/.config/sigit`),
|
|
365
408
|
`SIGIT_MODEL`, `SIGIT_MAX_TOOL_ROUNDS` (1 to 500, default 24; the tool-round cap of a
|
|
366
|
-
headless run or an ACP prompt turn), `SIGIT_MCP` (`off` disables MCP), `
|
|
409
|
+
headless run or an ACP prompt turn), `SIGIT_MCP` (`off` disables MCP), `SIGIT_CLIENT_FS` (`off` keeps the file tools on disk even when the
|
|
410
|
+
ACP client offers `fs/read_text_file` / `fs/write_text_file`), `SIGIT_MCP_SMBCLOUD` (`off` drops the baked-in
|
|
367
411
|
smbCloud CLI server), `SIGIT_MCP_OFFICIAL` (`off` drops the baked-in
|
|
368
412
|
server), `SIGIT_PERMISSIONS` (`allow`/`ask`/`deny` — overrides the default permission mode for
|
|
369
413
|
mutating tools; the escape hatch for clients without permission-request support),
|
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.6.3
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **A client can resume a session and close one.** siGit Code now advertises
|
|
8
|
+
`sessionCapabilities.resume` and `sessionCapabilities.close`.
|
|
9
|
+
`session/resume` restores a saved thread the way `session/load` does, but
|
|
10
|
+
sends none of it back: it is for a client that still has the thread on
|
|
11
|
+
screen, after an agent restart for example. `session/close` cancels the
|
|
12
|
+
session's running turn and drops what the process held for it, which is its
|
|
13
|
+
state, its permission grants and plan mode, the MCP servers the client named
|
|
14
|
+
for it, and the background commands it started. The saved history is left
|
|
15
|
+
alone, so a closed thread still lists and reopens (#149).
|
|
16
|
+
|
|
17
|
+
- **The permission modes are also offered as ACP session modes.** Manual, Auto
|
|
18
|
+
and Plan were only reachable as a config option, which is what the protocol
|
|
19
|
+
now recommends and what Zed reads. A client that draws the older mode
|
|
20
|
+
selector saw nothing. `session/new`, `session/load` and `session/fork` now
|
|
21
|
+
return the same three in `modes`, `session/set_mode` switches between them,
|
|
22
|
+
and a change made any other way (the config option, `/plan`, `/clear`,
|
|
23
|
+
`/reload`) is announced with `current_mode_update`. A client that reads
|
|
24
|
+
config options ignores `modes`, so nothing changes there (#148).
|
|
25
|
+
|
|
26
|
+
- **An editor can sign out of siGit Code.** The `initialize` reply carried an
|
|
27
|
+
empty `auth` object, so a client drew no sign out button, and typing
|
|
28
|
+
`/logout` was the only way out of an account. The reply now advertises
|
|
29
|
+
`agentCapabilities.auth.logout`, and a `logout` request ends the account
|
|
30
|
+
session through the method `/logout` calls. The request names no session,
|
|
31
|
+
so it moves every open thread that used the account, not only the live one:
|
|
32
|
+
a thread parked on a cloud tier goes on-device with the live thread instead
|
|
33
|
+
of failing to restore its tier on its next prompt, and every thread that
|
|
34
|
+
moved gets a fresh model picker. Local inference is switched back on if it
|
|
35
|
+
was off, so a thread opened after signing out asks for no cloud tier
|
|
36
|
+
(#150).
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **Two threads can run a turn at the same time.** An editor runs one siGit
|
|
41
|
+
Code process for every thread it has open, and a single lock ordered every
|
|
42
|
+
turn-affecting request in it, so a second prompt waited for the first
|
|
43
|
+
thread's whole turn and two prompts never overlapped. The lock held what a
|
|
44
|
+
session installs process-wide — the working directory, the workspace roots,
|
|
45
|
+
the MCP servers the client named for it, the live backend — none of which a
|
|
46
|
+
turn touches while it waits on an HTTP endpoint, which is most of its time.
|
|
47
|
+
A session on an HTTP backend now keeps a backend of its own, so its
|
|
48
|
+
conversation is no longer swapped in and out of a shared one, and a turn
|
|
49
|
+
releases the process-wide lock while it waits on the endpoint or on a
|
|
50
|
+
permission answer and takes it back, reinstalling its session, before any
|
|
51
|
+
tool runs. On-device turns are unchanged, since the engine keeps one
|
|
52
|
+
conversation, and tool execution still runs one session at a time (#176).
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- **A prompt whose inference failed is kept through a model switch.** A turn
|
|
57
|
+
that ended in an endpoint error stayed in the client's thread, but the
|
|
58
|
+
carryover into a new model dropped its trailing user message, and the error
|
|
59
|
+
path never saved the session. Only a cancelled prompt should be taken back
|
|
60
|
+
out, so cancellation now removes it from the live history, and the error
|
|
61
|
+
path persists the session (#124).
|
|
62
|
+
|
|
63
|
+
## 1.6.2
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
|
|
67
|
+
- **`sigit --version` prints the release.** `sigit --version` and `sigit -V`
|
|
68
|
+
print `sigit <version>` on stdout and exit 0. Before, the flag was not
|
|
69
|
+
recognized and siGit Code started a session, which left a script that asked
|
|
70
|
+
for the version waiting.
|
|
71
|
+
|
|
72
|
+
- **File tools read and write through the editor when it offers to.** An ACP
|
|
73
|
+
client that advertises `fs.readTextFile` or `fs.writeTextFile` serves those
|
|
74
|
+
requests from its open buffers. siGit Code always used the disk, so
|
|
75
|
+
`read_file` missed unsaved changes and `edit_file` wrote underneath a buffer
|
|
76
|
+
the user might have modified. `read_file`, `create_file`, `edit_file` and
|
|
77
|
+
`multi_edit` now go through the client for paths inside the session's roots.
|
|
78
|
+
The two capabilities are independent, and the disk is still the fallback:
|
|
79
|
+
for a path outside the roots, and for a request the client fails or leaves
|
|
80
|
+
unanswered for 30 seconds. `SIGIT_CLIENT_FS=off` keeps the file tools on
|
|
81
|
+
disk (#147).
|
|
82
|
+
|
|
83
|
+
### Fixed
|
|
84
|
+
|
|
85
|
+
- **A file attached from the editor is found when its path needs escaping.**
|
|
86
|
+
A `resource_link` was read by stripping `file://` and using the rest as the
|
|
87
|
+
path, so a space arrived as `%20`, a non-ASCII name failed, and a Windows
|
|
88
|
+
URI kept its leading slash. A `#` in a file name was also taken for the
|
|
89
|
+
line-range fragment. The URI is now parsed properly (#170).
|
|
90
|
+
|
|
3
91
|
## 1.6.1
|
|
4
92
|
|
|
5
93
|
### Added
|
|
@@ -113,9 +113,20 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
113
113
|
- **`src/main.rs`** — entry point, mode dispatch, the full ACP `Agent` impl (session lifecycle:
|
|
114
114
|
new/load/fork/prompt/cancel, config options, slash-command advertisement), and the `SYSTEM_PROMPT`.
|
|
115
115
|
ACP session state owns its roots, conversation, and selected model even though the process has
|
|
116
|
-
one live backend; activating a thread parks and restores all
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
one working directory and one live backend slot; activating a thread parks and restores all
|
|
117
|
+
three. A thread on an HTTP backend keeps a backend of its own (`InferenceBackend::fresh`,
|
|
118
|
+
`SessionState::remote`), so its turn can wait on the endpoint while another thread is
|
|
119
|
+
installed. Unknown session ids are rejected instead of silently borrowing the active thread's
|
|
120
|
+
cwd. Prompt cancellation takes no lock, which lets a client cancel a turn whatever it holds.
|
|
121
|
+
`session/load` and `session/resume` share `restore_session`; the only difference is that
|
|
122
|
+
resume must not replay the history as `session/update`. `session/close` is the one place a
|
|
123
|
+
`SessionState` is dropped. It runs in two halves: `begin_close` signals the session's turn
|
|
124
|
+
from the dispatch loop (the turn holds the session's lock, so the signal cannot wait for it) and
|
|
125
|
+
marks the id in `closing_sessions`, which makes `handle_prompt` cancel a prompt that was
|
|
126
|
+
still queued; `handle_close_session` then runs under the session and workspace locks and
|
|
127
|
+
removes the state, the permission grants and the background commands
|
|
128
|
+
(`tools::kill_session_tasks`). It leaves `session_store` alone, so a closed thread still lists
|
|
129
|
+
and reopens. The
|
|
119
130
|
`SYSTEM_PROMPT` bakes in smbCloud-specific context the agent should use when the repo is clearly
|
|
120
131
|
smbCloud, and stay general otherwise.
|
|
121
132
|
- **`src/backend.rs`** — the `InferenceBackend` trait and neutral types (`ToolSpec`, `ToolCall`,
|
|
@@ -268,10 +279,21 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
268
279
|
announced, because it sets the announced status: a call that will ask starts `pending`, the
|
|
269
280
|
permission request carries that call's own id, and an `in_progress` update follows approval.
|
|
270
281
|
That update also puts the card's title back, since the permission request overwrites it
|
|
271
|
-
with the full arguments.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
282
|
+
with the full arguments. The Manual/Auto/Plan choice reaches an ACP client twice: as the
|
|
283
|
+
`sigit-permission-mode` config option, and as session `modes` with `session/set_mode`, which
|
|
284
|
+
the spec is retiring but some clients still render instead. Both are built from
|
|
285
|
+
`PERMISSION_MODES` in `main.rs` and read the same state, so whatever changes the mode has to
|
|
286
|
+
refresh both (`ConfigOptionUpdate` and `send_current_mode`). Note: ACP turn-affecting handlers run in `cx.spawn`ed tasks
|
|
287
|
+
so the dispatch loop can route the client's permission answer mid-turn — don't move them back
|
|
288
|
+
inline, and don't await client requests from inline handlers (deadlock). Two locks order them.
|
|
289
|
+
A per-session lock (`SiGitAgent::session_lock`) is held for a whole request, so requests on one
|
|
290
|
+
thread keep their order. `SiGitAgent::workspace_lock` guards what a session installs
|
|
291
|
+
process-wide (cwd, workspace roots, session MCP servers, the live backend slot, the engine's
|
|
292
|
+
conversation); lifecycle and config handlers hold it throughout, and `handle_prompt` holds it
|
|
293
|
+
through a `WorkspaceHold` that it releases while waiting on an HTTP endpoint or a permission
|
|
294
|
+
answer and retakes (reinstalling its session via `resume_workspace`) before any tool runs. An
|
|
295
|
+
on-device turn never releases it. Take the session lock first, never the other way round. Tool
|
|
296
|
+
execution, subagents included, still runs one session at a time.
|
|
275
297
|
- **`src/instructions.rs`** — project instruction files, the always-on counterpart to skills.
|
|
276
298
|
Reads `AGENTS.md` (the cross-tool [agents.md](https://agents.md) standard) and `CLAUDE.md`,
|
|
277
299
|
walking from the session cwd up to the repo root (nearest ancestor with `.git`, never above it),
|
|
@@ -299,6 +321,23 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
299
321
|
every root. MCP is deliberately not on that list — `mcp::init` runs once at startup, before
|
|
300
322
|
any session exists, so a second root's `.sigit/mcp.toml` has nobody to tell. (The servers a
|
|
301
323
|
client names in `mcpServers` are a different thing and are per session; see `src/mcp.rs`.)
|
|
324
|
+
- **`src/client_fs.rs`** — file reads and writes through the ACP client. A client that
|
|
325
|
+
advertises `fs.readTextFile` / `fs.writeTextFile` in `initialize` serves `fs/read_text_file`
|
|
326
|
+
and `fs/write_text_file` from its buffers, so `read_file` sees unsaved changes and
|
|
327
|
+
`edit_file` lands in the open buffer instead of underneath it. `tools.rs` has no access to the
|
|
328
|
+
connection, so this is a seam like the one `mcp::call_tool` gives MCP: `handle_initialize`
|
|
329
|
+
registers a `ClientFileSystem` (`AcpClientFs` in `main.rs`, holding the connection), and
|
|
330
|
+
`execute_tool_impl` asks `route_for` before it dispatches `read_file`, `create_file`,
|
|
331
|
+
`edit_file` or `multi_edit`. Each of those tools is split into a parse step and a pure
|
|
332
|
+
render/apply step (`ReadFileCall`, `CreateFileCall`, `EditCall`) that the disk path and the
|
|
333
|
+
client path share, so the model gets the same result text either way. The two capabilities are
|
|
334
|
+
independent: a client that only reads still gets its edits written to disk. The disk is the
|
|
335
|
+
fallback throughout: nothing is registered in the TUI or headless modes, a path outside the
|
|
336
|
+
session's roots is not routed, and a request the client fails or leaves unanswered for 30
|
|
337
|
+
seconds is retried on disk with a warning in the log. The client is only ever asked from
|
|
338
|
+
inside a spawned prompt turn, never from `handle_initialize` itself (deadlock, same as
|
|
339
|
+
permission requests). Existence checks and `create_file`'s parent directories still use the
|
|
340
|
+
disk. `SIGIT_CLIENT_FS=off` turns the routing off. Covered by `tests/acp_client_fs.rs`.
|
|
302
341
|
- **`src/chat.rs`** — the Unix-only ratatui TUI. Loading-spinner phase then chat; uses
|
|
303
342
|
`tokio::select!` to multiplex terminal events with streaming tokens.
|
|
304
343
|
- **`src/headless.rs`** — non-interactive `sigit run` execution for scripts, CI, and Factory
|
|
@@ -320,6 +359,10 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
320
359
|
those read env vars once at init.
|
|
321
360
|
- **`src/account.rs`** — siGit Code Cloud auth (`/login`, `/logout`, `/whoami`); authenticates
|
|
322
361
|
against the account API and stores a session token. Performs no console I/O.
|
|
362
|
+
ACP `logout` (the editor's sign out button, shown only because `handle_initialize` advertises
|
|
363
|
+
`agentCapabilities.auth.logout`) and `/logout` both go through `SiGitAgent::sign_out` in
|
|
364
|
+
`main.rs`. The request names no session, so `leave_cloud` moves the parked threads off their
|
|
365
|
+
cloud tier along with the live one and each of them gets a fresh model picker.
|
|
323
366
|
- **`src/browser_auth.rs`** — browser sign-in, the path the editor's "Sign in to siGit Code"
|
|
324
367
|
button takes. sigit is a public OAuth client (client id `sigit-code-cli`, no secret, PKCE
|
|
325
368
|
S256) against the authorization server at `$SIGIT_API_URL/oauth`. Two ways the code gets
|
|
@@ -363,7 +406,8 @@ verbosity with `RUST_LOG`.
|
|
|
363
406
|
`OPENAI_BASE_URL` / `OPENAI_API_KEY` (provider override), `SIGIT_API_URL` (account API base,
|
|
364
407
|
default `https://sigit.si`), `SIGIT_CLOUD_URL`, `SIGIT_CONFIG_DIR` (default `~/.config/sigit`),
|
|
365
408
|
`SIGIT_MODEL`, `SIGIT_MAX_TOOL_ROUNDS` (1 to 500, default 24; the tool-round cap of a
|
|
366
|
-
headless run or an ACP prompt turn), `SIGIT_MCP` (`off` disables MCP), `
|
|
409
|
+
headless run or an ACP prompt turn), `SIGIT_MCP` (`off` disables MCP), `SIGIT_CLIENT_FS` (`off` keeps the file tools on disk even when the
|
|
410
|
+
ACP client offers `fs/read_text_file` / `fs/write_text_file`), `SIGIT_MCP_SMBCLOUD` (`off` drops the baked-in
|
|
367
411
|
smbCloud CLI server), `SIGIT_MCP_OFFICIAL` (`off` drops the baked-in
|
|
368
412
|
server), `SIGIT_PERMISSIONS` (`allow`/`ask`/`deny` — overrides the default permission mode for
|
|
369
413
|
mutating tools; the escape hatch for clients without permission-request support),
|
|
@@ -5778,7 +5778,7 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
|
|
5778
5778
|
|
|
5779
5779
|
[[package]]
|
|
5780
5780
|
name = "sigit"
|
|
5781
|
-
version = "1.6.
|
|
5781
|
+
version = "1.6.3"
|
|
5782
5782
|
dependencies = [
|
|
5783
5783
|
"agent-client-protocol",
|
|
5784
5784
|
"anyhow",
|
|
@@ -5801,6 +5801,7 @@ dependencies = [
|
|
|
5801
5801
|
"tokio-util",
|
|
5802
5802
|
"toml 0.8.23",
|
|
5803
5803
|
"tracing-subscriber",
|
|
5804
|
+
"url",
|
|
5804
5805
|
"uuid 1.26.0",
|
|
5805
5806
|
]
|
|
5806
5807
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "sigit"
|
|
3
|
-
version = "1.6.
|
|
3
|
+
version = "1.6.3"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
description = "siGit Code — ACP-compatible AI coding agent. Sí, git."
|
|
6
6
|
documentation = "https://github.com/getsigit/sigit"
|
|
@@ -60,6 +60,11 @@ base64 = "0.22"
|
|
|
60
60
|
rand = "0.9"
|
|
61
61
|
sha2 = "0.10"
|
|
62
62
|
|
|
63
|
+
# ACP `resource_link` blocks carry file URIs. Parsing them with `url` decodes
|
|
64
|
+
# percent escapes and maps Windows drive paths, which hand-stripping `file://`
|
|
65
|
+
# did not. Already in the graph through reqwest.
|
|
66
|
+
url = "2"
|
|
67
|
+
|
|
63
68
|
# Release binaries ship to five registries, and the NuGet package bundles all
|
|
64
69
|
# six targets in a single archive, so binary size is a distribution constraint
|
|
65
70
|
# rather than a nicety. Stripping symbols takes the macOS arm64 binary from
|
|
@@ -445,6 +445,15 @@ pub trait InferenceBackend: Send + Sync {
|
|
|
445
445
|
/// local model while requests actually go to the cloud.
|
|
446
446
|
fn is_remote(&self) -> bool;
|
|
447
447
|
|
|
448
|
+
/// A backend for the same endpoint and model with an empty conversation,
|
|
449
|
+
/// or `None` when the conversation cannot be separated from the backend
|
|
450
|
+
/// (on-device, the engine holds it). An editor keeps several threads open
|
|
451
|
+
/// in one process; this is what lets each of them own its history instead
|
|
452
|
+
/// of taking turns on a shared one.
|
|
453
|
+
fn fresh(&self) -> Option<Arc<dyn InferenceBackend>> {
|
|
454
|
+
None
|
|
455
|
+
}
|
|
456
|
+
|
|
448
457
|
/// A serializable snapshot of the conversation history, one JSON object per
|
|
449
458
|
/// message (`{"role": ..., "content": ...}` at minimum). The snapshot is
|
|
450
459
|
/// what the session store persists; it includes any seeded system message
|
|
@@ -1554,6 +1563,19 @@ impl InferenceBackend for OpenAiBackend {
|
|
|
1554
1563
|
true
|
|
1555
1564
|
}
|
|
1556
1565
|
|
|
1566
|
+
fn fresh(&self) -> Option<Arc<dyn InferenceBackend>> {
|
|
1567
|
+
Some(Arc::new(Self {
|
|
1568
|
+
base_url: self.base_url.clone(),
|
|
1569
|
+
api_key: self.api_key.clone(),
|
|
1570
|
+
model: self.model.clone(),
|
|
1571
|
+
// The client is a handle on one connection pool; sharing it keeps
|
|
1572
|
+
// connections to the endpoint warm across sessions.
|
|
1573
|
+
http: self.http.clone(),
|
|
1574
|
+
history: Mutex::new(Vec::new()),
|
|
1575
|
+
accepts_images: self.accepts_images,
|
|
1576
|
+
}))
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1557
1579
|
async fn history_snapshot(&self) -> Vec<serde_json::Value> {
|
|
1558
1580
|
self.history.lock().await.clone()
|
|
1559
1581
|
}
|
|
@@ -1944,18 +1966,25 @@ pub fn carryover_history(snapshot: Vec<serde_json::Value>) -> Vec<serde_json::Va
|
|
|
1944
1966
|
}
|
|
1945
1967
|
}
|
|
1946
1968
|
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1969
|
+
carried
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
/// Take a cancelled prompt's user message back out of the live history.
|
|
1973
|
+
///
|
|
1974
|
+
/// The client drops a cancelled prompt from its thread, so the model must not
|
|
1975
|
+
/// keep answering it. Only the first inference round can cancel with the user
|
|
1976
|
+
/// message as the last entry; later rounds end on tool or assistant output.
|
|
1977
|
+
/// A prompt that fails with an error is not taken back: the client keeps it,
|
|
1978
|
+
/// so the history keeps it too and a model switch carries it over.
|
|
1979
|
+
pub async fn forget_trailing_user_message(backend: &dyn InferenceBackend) {
|
|
1980
|
+
let mut history = backend.history_snapshot().await;
|
|
1981
|
+
if history
|
|
1952
1982
|
.last()
|
|
1953
1983
|
.is_some_and(|message| message["role"] == "user")
|
|
1954
1984
|
{
|
|
1955
|
-
|
|
1985
|
+
history.pop();
|
|
1986
|
+
backend.restore_history(history).await;
|
|
1956
1987
|
}
|
|
1957
|
-
|
|
1958
|
-
carried
|
|
1959
1988
|
}
|
|
1960
1989
|
|
|
1961
1990
|
/// Replay `carried` (from [`carryover_history`]) into `backend`, on top of the
|
|
@@ -2161,16 +2190,39 @@ mod tests {
|
|
|
2161
2190
|
}
|
|
2162
2191
|
|
|
2163
2192
|
#[test]
|
|
2164
|
-
fn
|
|
2193
|
+
fn carryover_keeps_a_trailing_user_message_from_a_failed_turn() {
|
|
2194
|
+
// The client keeps a prompt whose inference errored, so a switch must
|
|
2195
|
+
// not drop it (issue #124).
|
|
2165
2196
|
let carried = carryover_history(vec![
|
|
2166
2197
|
serde_json::json!({ "role": "system", "content": "prompt" }),
|
|
2167
2198
|
serde_json::json!({ "role": "user", "content": "completed question" }),
|
|
2168
2199
|
serde_json::json!({ "role": "assistant", "content": "completed answer" }),
|
|
2169
|
-
serde_json::json!({ "role": "user", "content": "
|
|
2200
|
+
serde_json::json!({ "role": "user", "content": "failed question" }),
|
|
2170
2201
|
]);
|
|
2171
2202
|
|
|
2172
|
-
assert_eq!(carried.len(),
|
|
2173
|
-
assert_eq!(carried.last().unwrap()["content"], "
|
|
2203
|
+
assert_eq!(carried.len(), 3, "{carried:#?}");
|
|
2204
|
+
assert_eq!(carried.last().unwrap()["content"], "failed question");
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
#[tokio::test]
|
|
2208
|
+
async fn forget_trailing_user_message_drops_only_a_cancelled_prompt() {
|
|
2209
|
+
let backend = OpenAiBackend::new("http://localhost", "", "m", None);
|
|
2210
|
+
backend
|
|
2211
|
+
.restore_history(vec![
|
|
2212
|
+
serde_json::json!({ "role": "user", "content": "completed question" }),
|
|
2213
|
+
serde_json::json!({ "role": "assistant", "content": "completed answer" }),
|
|
2214
|
+
serde_json::json!({ "role": "user", "content": "cancelled question" }),
|
|
2215
|
+
])
|
|
2216
|
+
.await;
|
|
2217
|
+
|
|
2218
|
+
forget_trailing_user_message(&backend).await;
|
|
2219
|
+
|
|
2220
|
+
let history = backend.history_snapshot().await;
|
|
2221
|
+
assert_eq!(history.len(), 2, "{history:#?}");
|
|
2222
|
+
assert_eq!(history[1]["content"], "completed answer");
|
|
2223
|
+
|
|
2224
|
+
forget_trailing_user_message(&backend).await;
|
|
2225
|
+
assert_eq!(backend.history_snapshot().await.len(), 2);
|
|
2174
2226
|
}
|
|
2175
2227
|
|
|
2176
2228
|
#[test]
|