sigit-code 1.6.0__tar.gz → 1.6.1__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.
Files changed (102) hide show
  1. {sigit_code-1.6.0 → sigit_code-1.6.1}/.agents/AGENTS.md +28 -2
  2. sigit_code-1.6.1/.sigit/factory/config.toml +62 -0
  3. {sigit_code-1.6.0 → sigit_code-1.6.1}/AGENTS.md +28 -2
  4. {sigit_code-1.6.0 → sigit_code-1.6.1}/CHANGELOG.md +49 -0
  5. {sigit_code-1.6.0 → sigit_code-1.6.1}/CLAUDE.md +28 -2
  6. {sigit_code-1.6.0 → sigit_code-1.6.1}/Cargo.lock +1 -1
  7. {sigit_code-1.6.0 → sigit_code-1.6.1}/Cargo.toml +1 -1
  8. {sigit_code-1.6.0 → sigit_code-1.6.1}/PKG-INFO +1 -1
  9. {sigit_code-1.6.0 → sigit_code-1.6.1}/docs/headless.md +5 -0
  10. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/backend.rs +399 -19
  11. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/headless.rs +155 -5
  12. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/main.rs +368 -48
  13. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/provider.rs +56 -0
  14. sigit_code-1.6.1/tests/acp_images.rs +318 -0
  15. {sigit_code-1.6.0 → sigit_code-1.6.1}/tests/acp_permissions.rs +93 -0
  16. sigit_code-1.6.1/tests/acp_stop_reasons.rs +439 -0
  17. {sigit_code-1.6.0 → sigit_code-1.6.1}/tests/headless_mode.rs +109 -0
  18. {sigit_code-1.6.0 → sigit_code-1.6.1}/.agents/skills/agent-client-protocol/SKILL.md +0 -0
  19. {sigit_code-1.6.0 → sigit_code-1.6.1}/.agents/skills/ai-assisted-coding/SKILL.md +0 -0
  20. {sigit_code-1.6.0 → sigit_code-1.6.1}/.agents/skills/branding/SKILL.md +0 -0
  21. {sigit_code-1.6.0 → sigit_code-1.6.1}/.agents/skills/run-sigit/SKILL.md +0 -0
  22. {sigit_code-1.6.0 → sigit_code-1.6.1}/.agents/skills/run-sigit/driver.mjs +0 -0
  23. {sigit_code-1.6.0 → sigit_code-1.6.1}/.agents/skills/run-sigit/tui-smoke.sh +0 -0
  24. {sigit_code-1.6.0 → sigit_code-1.6.1}/.agents/skills/sigit-code-release/SKILL.md +0 -0
  25. {sigit_code-1.6.0 → sigit_code-1.6.1}/.agents/skills/tool-calling/SKILL.md +0 -0
  26. {sigit_code-1.6.0 → sigit_code-1.6.1}/.cargo/config.toml +0 -0
  27. {sigit_code-1.6.0 → sigit_code-1.6.1}/.claude/skills/agent-client-protocol/SKILL.md +0 -0
  28. {sigit_code-1.6.0 → sigit_code-1.6.1}/.claude/skills/ai-assisted-coding/SKILL.md +0 -0
  29. {sigit_code-1.6.0 → sigit_code-1.6.1}/.claude/skills/branding/SKILL.md +0 -0
  30. {sigit_code-1.6.0 → sigit_code-1.6.1}/.claude/skills/run-sigit/SKILL.md +0 -0
  31. {sigit_code-1.6.0 → sigit_code-1.6.1}/.claude/skills/run-sigit/driver.mjs +0 -0
  32. {sigit_code-1.6.0 → sigit_code-1.6.1}/.claude/skills/run-sigit/tui-smoke.sh +0 -0
  33. {sigit_code-1.6.0 → sigit_code-1.6.1}/.claude/skills/sigit-code-release/SKILL.md +0 -0
  34. {sigit_code-1.6.0 → sigit_code-1.6.1}/.claude/skills/tool-calling/SKILL.md +0 -0
  35. {sigit_code-1.6.0 → sigit_code-1.6.1}/.github/workflows/ci.yml +0 -0
  36. {sigit_code-1.6.0 → sigit_code-1.6.1}/.github/workflows/release-aur.yml +0 -0
  37. {sigit_code-1.6.0 → sigit_code-1.6.1}/.github/workflows/release-crates.yml +0 -0
  38. {sigit_code-1.6.0 → sigit_code-1.6.1}/.github/workflows/release-github.yml +0 -0
  39. {sigit_code-1.6.0 → sigit_code-1.6.1}/.github/workflows/release-homebrew.yml +0 -0
  40. {sigit_code-1.6.0 → sigit_code-1.6.1}/.github/workflows/release-npm.yml +0 -0
  41. {sigit_code-1.6.0 → sigit_code-1.6.1}/.github/workflows/release-nuget.yml +0 -0
  42. {sigit_code-1.6.0 → sigit_code-1.6.1}/.github/workflows/release-pypi.yml +0 -0
  43. {sigit_code-1.6.0 → sigit_code-1.6.1}/.github/workflows/release-scoop.yml +0 -0
  44. {sigit_code-1.6.0 → sigit_code-1.6.1}/.github/workflows/release-winget.yml +0 -0
  45. {sigit_code-1.6.0 → sigit_code-1.6.1}/.gitignore +0 -0
  46. {sigit_code-1.6.0 → sigit_code-1.6.1}/.nvmrc +0 -0
  47. {sigit_code-1.6.0 → sigit_code-1.6.1}/LICENSE +0 -0
  48. {sigit_code-1.6.0 → sigit_code-1.6.1}/README.md +0 -0
  49. {sigit_code-1.6.0 → sigit_code-1.6.1}/docs/hooks.md +0 -0
  50. {sigit_code-1.6.0 → sigit_code-1.6.1}/docs/mcp.md +0 -0
  51. {sigit_code-1.6.0 → sigit_code-1.6.1}/examples/settings-with-hooks.toml +0 -0
  52. {sigit_code-1.6.0 → sigit_code-1.6.1}/examples/skills/README.md +0 -0
  53. {sigit_code-1.6.0 → sigit_code-1.6.1}/examples/skills/commit-message/SKILL.md +0 -0
  54. {sigit_code-1.6.0 → sigit_code-1.6.1}/npm/README.md.tmpl +0 -0
  55. {sigit_code-1.6.0 → sigit_code-1.6.1}/npm/package-compat.json.tmpl +0 -0
  56. {sigit_code-1.6.0 → sigit_code-1.6.1}/npm/package-main.json.tmpl +0 -0
  57. {sigit_code-1.6.0 → sigit_code-1.6.1}/npm/package.json.tmpl +0 -0
  58. {sigit_code-1.6.0 → sigit_code-1.6.1}/npm/scripts/render-main-package.cjs +0 -0
  59. {sigit_code-1.6.0 → sigit_code-1.6.1}/npm/scripts/render-platform-package.cjs +0 -0
  60. {sigit_code-1.6.0 → sigit_code-1.6.1}/npm/sigit/.gitignore +0 -0
  61. {sigit_code-1.6.0 → sigit_code-1.6.1}/npm/sigit/README.md +0 -0
  62. {sigit_code-1.6.0 → sigit_code-1.6.1}/npm/sigit/package.json +0 -0
  63. {sigit_code-1.6.0 → sigit_code-1.6.1}/npm/sigit/src/index.ts +0 -0
  64. {sigit_code-1.6.0 → sigit_code-1.6.1}/npm/sigit/tsconfig.json +0 -0
  65. {sigit_code-1.6.0 → sigit_code-1.6.1}/nuget/.gitignore +0 -0
  66. {sigit_code-1.6.0 → sigit_code-1.6.1}/nuget/sigit/Program.cs +0 -0
  67. {sigit_code-1.6.0 → sigit_code-1.6.1}/nuget/sigit/README.md +0 -0
  68. {sigit_code-1.6.0 → sigit_code-1.6.1}/nuget/sigit/SiGit.Code.csproj +0 -0
  69. {sigit_code-1.6.0 → sigit_code-1.6.1}/packaging/aur/PKGBUILD.in +0 -0
  70. {sigit_code-1.6.0 → sigit_code-1.6.1}/packaging/nfpm.yaml +0 -0
  71. {sigit_code-1.6.0 → sigit_code-1.6.1}/packaging/windows/check-static-crt.ps1 +0 -0
  72. {sigit_code-1.6.0 → sigit_code-1.6.1}/packaging/winget/getSigit.siGitCode.installer.yaml.in +0 -0
  73. {sigit_code-1.6.0 → sigit_code-1.6.1}/packaging/winget/getSigit.siGitCode.locale.en-US.yaml.in +0 -0
  74. {sigit_code-1.6.0 → sigit_code-1.6.1}/packaging/winget/getSigit.siGitCode.yaml.in +0 -0
  75. {sigit_code-1.6.0 → sigit_code-1.6.1}/pypi/README.md +0 -0
  76. {sigit_code-1.6.0 → sigit_code-1.6.1}/pypi/pyproject.toml +0 -0
  77. {sigit_code-1.6.0 → sigit_code-1.6.1}/pyproject.toml +0 -0
  78. {sigit_code-1.6.0 → sigit_code-1.6.1}/rust-toolchain.toml +0 -0
  79. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/account.rs +0 -0
  80. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/browser_auth.rs +0 -0
  81. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/chat.rs +0 -0
  82. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/commands.rs +0 -0
  83. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/credentials.rs +0 -0
  84. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/frontmatter.rs +0 -0
  85. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/hooks.rs +0 -0
  86. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/inline_tool_calls.rs +0 -0
  87. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/instructions.rs +0 -0
  88. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/mcp.rs +0 -0
  89. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/models.rs +0 -0
  90. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/permissions.rs +0 -0
  91. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/session_store.rs +0 -0
  92. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/settings.rs +0 -0
  93. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/setup.rs +0 -0
  94. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/skills.rs +0 -0
  95. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/subagents.rs +0 -0
  96. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/tools.rs +0 -0
  97. {sigit_code-1.6.0 → sigit_code-1.6.1}/src/workspace.rs +0 -0
  98. {sigit_code-1.6.0 → sigit_code-1.6.1}/tests/acp_endpoint_errors.rs +0 -0
  99. {sigit_code-1.6.0 → sigit_code-1.6.1}/tests/acp_multi_root.rs +0 -0
  100. {sigit_code-1.6.0 → sigit_code-1.6.1}/tests/acp_session_list.rs +0 -0
  101. {sigit_code-1.6.0 → sigit_code-1.6.1}/tests/acp_session_load.rs +0 -0
  102. {sigit_code-1.6.0 → sigit_code-1.6.1}/tests/acp_tool_stdin.rs +0 -0
@@ -127,10 +127,31 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
127
127
  endpoint can also fail *after* the response is open, reporting it as a `data:` frame holding
128
128
  the same envelope; that frame has no `choices`, so `consume_stream` has to check for it
129
129
  explicitly or it parses as an empty chunk and the turn ends looking like an empty answer.
130
+ `TurnResult::finish` carries the endpoint's `finish_reason` up to the loop, and
131
+ `stop_reason_for` in `main.rs` turns it into the ACP stop reason: the tool-round cap is
132
+ `max_turn_requests`, `length` is `max_tokens`, `content_filter` is `refusal`. ACP defines a
133
+ refusal as a turn the next prompt will not include, so `handle_prompt` restores the history
134
+ it snapshotted before the turn instead of only relabelling the response.
135
+ The round that reaches the cap (24, or `SIGIT_MAX_TOOL_ROUNDS`) is followed by one forced
136
+ text reply with no tools. The model cannot see that the tools are gone, so
137
+ `round_cap_note` is appended to that round's last tool result, and the turn always closes
138
+ with `round_cap_stop_message`. Without both, the thread stops on the model announcing a
139
+ step that never runs (issue #120).
130
140
  Some models write tool calls into content as text; `src/inline_tool_calls.rs` recovers the
131
141
  well-formed ones. A block that doesn't parse (or never closes) is dropped from both the reply
132
142
  and history, and `OpenAiBackend::complete` retries once with a note telling the model the call
133
143
  didn't run. Leaving the raw block in history makes the model invent `<function_results>` later.
144
+ Image attachments: ACP fixes `promptCapabilities.image` for the whole connection, while the
145
+ model can change on any turn, so the capability is always advertised and the decision is made
146
+ per prompt. `InferenceBackend::accepts_images` answers for the active model (on-device: no;
147
+ remote: `provider::model_accepts_images`, which reads the hard-coded `IMAGE_TIERS` for
148
+ `onde-*` ids and says yes for a user's own endpoint). `images_for_turn` in `main.rs` either
149
+ passes the images to `send_message_with_images` or drops them with a note to the user and a
150
+ bracketed note to the model. A message with images is stored in history as OpenAI content
151
+ parts and stays that way; `OpenAiBackend` strips the images per request (`without_images`)
152
+ when its model cannot read them, which is what makes a mid-thread switch to a text-only tier
153
+ safe. Read history text through `backend::message_text`, never `content.as_str()`.
154
+ `IMAGE_TIERS` mirrors onde-cloud's `IMAGE_CANDIDATES`; update both together.
134
155
  - **`src/provider.rs`** — decides *which* backend serves inference. Resolution order, first match
135
156
  wins: (1) override via `OPENAI_BASE_URL`+`OPENAI_API_KEY` or active profile in
136
157
  `~/.config/sigit/providers.toml`; (2) siGit Code Cloud when logged in; (3) on-device.
@@ -243,7 +264,11 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
243
264
  overrides and the default mode from `[permissions]` in `settings.toml` (`allow`/`ask`/`deny`,
244
265
  default `ask`; `SIGIT_PERMISSIONS` env overrides the default). On `ask`, the ACP path sends
245
266
  `session/request_permission` (allow once / allow for session / deny) and the TUI pauses the
246
- inference task on a y/a/n prompt. Note: ACP turn-affecting handlers run in `cx.spawn`ed tasks
267
+ inference task on a y/a/n prompt. On the ACP path the decision is taken *before* the call is
268
+ announced, because it sets the announced status: a call that will ask starts `pending`, the
269
+ permission request carries that call's own id, and an `in_progress` update follows approval.
270
+ That update also puts the card's title back, since the permission request overwrites it
271
+ with the full arguments. Note: ACP turn-affecting handlers run in `cx.spawn`ed tasks
247
272
  serialized by `SiGitAgent::turn_lock` so the dispatch loop can route the client's permission
248
273
  answer mid-turn — don't move them back inline, and don't await client requests from inline
249
274
  handlers (deadlock).
@@ -337,7 +362,8 @@ verbosity with `RUST_LOG`.
337
362
 
338
363
  `OPENAI_BASE_URL` / `OPENAI_API_KEY` (provider override), `SIGIT_API_URL` (account API base,
339
364
  default `https://sigit.si`), `SIGIT_CLOUD_URL`, `SIGIT_CONFIG_DIR` (default `~/.config/sigit`),
340
- `SIGIT_MODEL`, `SIGIT_MCP` (`off` disables MCP), `SIGIT_MCP_SMBCLOUD` (`off` drops the baked-in
365
+ `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), `SIGIT_MCP_SMBCLOUD` (`off` drops the baked-in
341
367
  smbCloud CLI server), `SIGIT_MCP_OFFICIAL` (`off` drops the baked-in
342
368
  server), `SIGIT_PERMISSIONS` (`allow`/`ask`/`deny` — overrides the default permission mode for
343
369
  mutating tools; the escape hatch for clients without permission-request support),
@@ -0,0 +1,62 @@
1
+ [repo]
2
+ owner = "getsigit"
3
+ name = "sigit"
4
+ slug = "getsigit-sigit"
5
+
6
+ [agent]
7
+ login = "sigit-code-review"
8
+ display_name = "siGit Code Review"
9
+
10
+ [harvest]
11
+ state = "closed"
12
+ limit = 50
13
+ since = "2026-09-01"
14
+ cadence = "weekly"
15
+ day_of_week = "Mon"
16
+ runs_dir = "~/.agents/skills/sigit-code-review-outer-loop/runs"
17
+ report_format = "markdown"
18
+ compare_previous = true
19
+
20
+ [review.reviewer_skill]
21
+ name = "sigit-code-review-outer-loop"
22
+ version = "1.0.0"
23
+
24
+ [review.author_skill]
25
+ name = "sigit-code-review-feedbacks-response"
26
+ version = "1.0.0"
27
+
28
+ [review.repo_guidance]
29
+ branch_rules = "{fix,feature,release}/*"
30
+ ci_gate = "CI"
31
+ watch_paths = ["src/**"]
32
+ ignore_paths = ["**/*.md", "**/*.txt", "docs/**", "vendor/**", ".github/**", "*.lock"]
33
+
34
+ [[guardrails.known_issues]]
35
+ pattern = "all_zero_summary"
36
+ source = "getsigit-sigit"
37
+ description = "Zero findings on a large control-flow diff may be recall loss from an over-corrected prompt bar, not a clean review. Seen on stack #163 (900 additions, 0 findings)."
38
+ action = "flag_in_report"
39
+
40
+ [[guardrails.known_issues]]
41
+ pattern = "confident_false_positives"
42
+ source = "getsigit-sigit"
43
+ description = "Confident declarative findings whose premise is contradicted by the cited code. Seen on stack #160 (5 of 5 findings rejected, rejected_rate 1.0). The hedge ban replaced speculative questions with confident wrong claims."
44
+ action = "flag_in_report"
45
+
46
+ [[guardrails.known_issues]]
47
+ pattern = "approval_on_disagree"
48
+ source = "getsigit-sigit"
49
+ description = "Agent approves within 30-45s of an author disagreement without processing the correction. Compounds with confident false positives: wrong claim, author corrects, agent approves without re-reading."
50
+ action = "flag_in_report"
51
+
52
+ [[guardrails.known_issues]]
53
+ pattern = "repeat_findings_high"
54
+ source = "getsigit-sigit-si"
55
+ description = "Repeat findings above 30% indicate the agent is re-raising issues the author already addressed."
56
+ action = "flag_in_report"
57
+
58
+ [[guardrails.known_issues]]
59
+ pattern = "force_pushes_inflate_rounds"
60
+ source = "smbcloudXYZ-smbcloud"
61
+ description = "Force pushes cause one-for-one review round inflation; the agent re-reads unchanged diffs."
62
+ action = "note_in_report"
@@ -127,10 +127,31 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
127
127
  endpoint can also fail *after* the response is open, reporting it as a `data:` frame holding
128
128
  the same envelope; that frame has no `choices`, so `consume_stream` has to check for it
129
129
  explicitly or it parses as an empty chunk and the turn ends looking like an empty answer.
130
+ `TurnResult::finish` carries the endpoint's `finish_reason` up to the loop, and
131
+ `stop_reason_for` in `main.rs` turns it into the ACP stop reason: the tool-round cap is
132
+ `max_turn_requests`, `length` is `max_tokens`, `content_filter` is `refusal`. ACP defines a
133
+ refusal as a turn the next prompt will not include, so `handle_prompt` restores the history
134
+ it snapshotted before the turn instead of only relabelling the response.
135
+ The round that reaches the cap (24, or `SIGIT_MAX_TOOL_ROUNDS`) is followed by one forced
136
+ text reply with no tools. The model cannot see that the tools are gone, so
137
+ `round_cap_note` is appended to that round's last tool result, and the turn always closes
138
+ with `round_cap_stop_message`. Without both, the thread stops on the model announcing a
139
+ step that never runs (issue #120).
130
140
  Some models write tool calls into content as text; `src/inline_tool_calls.rs` recovers the
131
141
  well-formed ones. A block that doesn't parse (or never closes) is dropped from both the reply
132
142
  and history, and `OpenAiBackend::complete` retries once with a note telling the model the call
133
143
  didn't run. Leaving the raw block in history makes the model invent `<function_results>` later.
144
+ Image attachments: ACP fixes `promptCapabilities.image` for the whole connection, while the
145
+ model can change on any turn, so the capability is always advertised and the decision is made
146
+ per prompt. `InferenceBackend::accepts_images` answers for the active model (on-device: no;
147
+ remote: `provider::model_accepts_images`, which reads the hard-coded `IMAGE_TIERS` for
148
+ `onde-*` ids and says yes for a user's own endpoint). `images_for_turn` in `main.rs` either
149
+ passes the images to `send_message_with_images` or drops them with a note to the user and a
150
+ bracketed note to the model. A message with images is stored in history as OpenAI content
151
+ parts and stays that way; `OpenAiBackend` strips the images per request (`without_images`)
152
+ when its model cannot read them, which is what makes a mid-thread switch to a text-only tier
153
+ safe. Read history text through `backend::message_text`, never `content.as_str()`.
154
+ `IMAGE_TIERS` mirrors onde-cloud's `IMAGE_CANDIDATES`; update both together.
134
155
  - **`src/provider.rs`** — decides *which* backend serves inference. Resolution order, first match
135
156
  wins: (1) override via `OPENAI_BASE_URL`+`OPENAI_API_KEY` or active profile in
136
157
  `~/.config/sigit/providers.toml`; (2) siGit Code Cloud when logged in; (3) on-device.
@@ -243,7 +264,11 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
243
264
  overrides and the default mode from `[permissions]` in `settings.toml` (`allow`/`ask`/`deny`,
244
265
  default `ask`; `SIGIT_PERMISSIONS` env overrides the default). On `ask`, the ACP path sends
245
266
  `session/request_permission` (allow once / allow for session / deny) and the TUI pauses the
246
- inference task on a y/a/n prompt. Note: ACP turn-affecting handlers run in `cx.spawn`ed tasks
267
+ inference task on a y/a/n prompt. On the ACP path the decision is taken *before* the call is
268
+ announced, because it sets the announced status: a call that will ask starts `pending`, the
269
+ permission request carries that call's own id, and an `in_progress` update follows approval.
270
+ That update also puts the card's title back, since the permission request overwrites it
271
+ with the full arguments. Note: ACP turn-affecting handlers run in `cx.spawn`ed tasks
247
272
  serialized by `SiGitAgent::turn_lock` so the dispatch loop can route the client's permission
248
273
  answer mid-turn — don't move them back inline, and don't await client requests from inline
249
274
  handlers (deadlock).
@@ -337,7 +362,8 @@ verbosity with `RUST_LOG`.
337
362
 
338
363
  `OPENAI_BASE_URL` / `OPENAI_API_KEY` (provider override), `SIGIT_API_URL` (account API base,
339
364
  default `https://sigit.si`), `SIGIT_CLOUD_URL`, `SIGIT_CONFIG_DIR` (default `~/.config/sigit`),
340
- `SIGIT_MODEL`, `SIGIT_MCP` (`off` disables MCP), `SIGIT_MCP_SMBCLOUD` (`off` drops the baked-in
365
+ `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), `SIGIT_MCP_SMBCLOUD` (`off` drops the baked-in
341
367
  smbCloud CLI server), `SIGIT_MCP_OFFICIAL` (`off` drops the baked-in
342
368
  server), `SIGIT_PERMISSIONS` (`allow`/`ask`/`deny` — overrides the default permission mode for
343
369
  mutating tools; the escape hatch for clients without permission-request support),
@@ -1,5 +1,54 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.6.1
4
+
5
+ ### Added
6
+
7
+ - **Images can be attached to a prompt in the editor.** siGit Code now
8
+ advertises `promptCapabilities.image`, so an ACP client offers image
9
+ attachments. The image goes to the model when the active model reads images.
10
+ The model picker marks those models with "reads images". The on-device
11
+ models and four cloud tiers (`nova`, `orbit`, `apex`, `flux`) are text-only:
12
+ with one of them active the image is left out, a note in the thread says so,
13
+ and the model is told an image was withheld. Switching a thread that already
14
+ holds images to a text-only model flags that once as well. Cloud tiers need
15
+ siGit Code Cloud to accept image parts, which is rolling out separately
16
+ (#134).
17
+
18
+ - **The tool-round cap of a headless run can be set.** `--max-tool-rounds <n>`
19
+ or `SIGIT_MAX_TOOL_ROUNDS` (1 to 500) replaces the built-in 24 for
20
+ `sigit run` and `sigit -p`. The flag wins over the variable.
21
+
22
+ ### Fixed
23
+
24
+ - **A prompt turn now reports why it stopped.** ACP defines five stop reasons
25
+ and siGit Code only ever sent `end_turn` and `cancelled`. A turn that runs
26
+ into the tool-round cap now ends with `max_turn_requests`, a reply cut off
27
+ at the token limit with `max_tokens`, and a reply the endpoint withheld
28
+ (`content_filter`) with `refusal`. A refused turn is also taken out of the
29
+ conversation, which is what the protocol tells the client to expect (#140).
30
+
31
+ - **A tool call that needs approval is one card, not two.** The permission
32
+ request carried an id of its own, so an ACP client showed an approval card
33
+ beside a call that already claimed to be running. The call is now announced
34
+ as `pending`, the permission request names that same call, and it moves to
35
+ `in_progress` once approved. Calls allowed by policy still start out
36
+ `in_progress` (#138).
37
+
38
+ - **A turn that runs out of tool rounds says so.** In an editor, a long task
39
+ could stop on a line like "Now commit:" with nothing after it. The turn had
40
+ used all 24 tool rounds, the model spent its last reply announcing the next
41
+ step, and the call behind it was dropped. siGit Code now tells the model
42
+ that its rounds are used up before that last reply, and ends the turn with a
43
+ message saying why it stopped and that "continue" picks it back up.
44
+ `SIGIT_MAX_TOOL_ROUNDS` now sets the cap for editor sessions too (#120).
45
+
46
+ - **A headless run no longer ends silently with no final message.** When the
47
+ last round came back empty, which is what happens when a run is cut off at
48
+ the tool-round cap mid-task, stdout was empty and the exit status was 0, so
49
+ a caller could not tell it from a run with nothing to report. siGit Code now
50
+ asks the model once for its final message before finishing.
51
+
3
52
  ## 1.6.0
4
53
 
5
54
  ### Added
@@ -127,10 +127,31 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
127
127
  endpoint can also fail *after* the response is open, reporting it as a `data:` frame holding
128
128
  the same envelope; that frame has no `choices`, so `consume_stream` has to check for it
129
129
  explicitly or it parses as an empty chunk and the turn ends looking like an empty answer.
130
+ `TurnResult::finish` carries the endpoint's `finish_reason` up to the loop, and
131
+ `stop_reason_for` in `main.rs` turns it into the ACP stop reason: the tool-round cap is
132
+ `max_turn_requests`, `length` is `max_tokens`, `content_filter` is `refusal`. ACP defines a
133
+ refusal as a turn the next prompt will not include, so `handle_prompt` restores the history
134
+ it snapshotted before the turn instead of only relabelling the response.
135
+ The round that reaches the cap (24, or `SIGIT_MAX_TOOL_ROUNDS`) is followed by one forced
136
+ text reply with no tools. The model cannot see that the tools are gone, so
137
+ `round_cap_note` is appended to that round's last tool result, and the turn always closes
138
+ with `round_cap_stop_message`. Without both, the thread stops on the model announcing a
139
+ step that never runs (issue #120).
130
140
  Some models write tool calls into content as text; `src/inline_tool_calls.rs` recovers the
131
141
  well-formed ones. A block that doesn't parse (or never closes) is dropped from both the reply
132
142
  and history, and `OpenAiBackend::complete` retries once with a note telling the model the call
133
143
  didn't run. Leaving the raw block in history makes the model invent `<function_results>` later.
144
+ Image attachments: ACP fixes `promptCapabilities.image` for the whole connection, while the
145
+ model can change on any turn, so the capability is always advertised and the decision is made
146
+ per prompt. `InferenceBackend::accepts_images` answers for the active model (on-device: no;
147
+ remote: `provider::model_accepts_images`, which reads the hard-coded `IMAGE_TIERS` for
148
+ `onde-*` ids and says yes for a user's own endpoint). `images_for_turn` in `main.rs` either
149
+ passes the images to `send_message_with_images` or drops them with a note to the user and a
150
+ bracketed note to the model. A message with images is stored in history as OpenAI content
151
+ parts and stays that way; `OpenAiBackend` strips the images per request (`without_images`)
152
+ when its model cannot read them, which is what makes a mid-thread switch to a text-only tier
153
+ safe. Read history text through `backend::message_text`, never `content.as_str()`.
154
+ `IMAGE_TIERS` mirrors onde-cloud's `IMAGE_CANDIDATES`; update both together.
134
155
  - **`src/provider.rs`** — decides *which* backend serves inference. Resolution order, first match
135
156
  wins: (1) override via `OPENAI_BASE_URL`+`OPENAI_API_KEY` or active profile in
136
157
  `~/.config/sigit/providers.toml`; (2) siGit Code Cloud when logged in; (3) on-device.
@@ -243,7 +264,11 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
243
264
  overrides and the default mode from `[permissions]` in `settings.toml` (`allow`/`ask`/`deny`,
244
265
  default `ask`; `SIGIT_PERMISSIONS` env overrides the default). On `ask`, the ACP path sends
245
266
  `session/request_permission` (allow once / allow for session / deny) and the TUI pauses the
246
- inference task on a y/a/n prompt. Note: ACP turn-affecting handlers run in `cx.spawn`ed tasks
267
+ inference task on a y/a/n prompt. On the ACP path the decision is taken *before* the call is
268
+ announced, because it sets the announced status: a call that will ask starts `pending`, the
269
+ permission request carries that call's own id, and an `in_progress` update follows approval.
270
+ That update also puts the card's title back, since the permission request overwrites it
271
+ with the full arguments. Note: ACP turn-affecting handlers run in `cx.spawn`ed tasks
247
272
  serialized by `SiGitAgent::turn_lock` so the dispatch loop can route the client's permission
248
273
  answer mid-turn — don't move them back inline, and don't await client requests from inline
249
274
  handlers (deadlock).
@@ -337,7 +362,8 @@ verbosity with `RUST_LOG`.
337
362
 
338
363
  `OPENAI_BASE_URL` / `OPENAI_API_KEY` (provider override), `SIGIT_API_URL` (account API base,
339
364
  default `https://sigit.si`), `SIGIT_CLOUD_URL`, `SIGIT_CONFIG_DIR` (default `~/.config/sigit`),
340
- `SIGIT_MODEL`, `SIGIT_MCP` (`off` disables MCP), `SIGIT_MCP_SMBCLOUD` (`off` drops the baked-in
365
+ `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), `SIGIT_MCP_SMBCLOUD` (`off` drops the baked-in
341
367
  smbCloud CLI server), `SIGIT_MCP_OFFICIAL` (`off` drops the baked-in
342
368
  server), `SIGIT_PERMISSIONS` (`allow`/`ask`/`deny` — overrides the default permission mode for
343
369
  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.0"
5781
+ version = "1.6.1"
5782
5782
  dependencies = [
5783
5783
  "agent-client-protocol",
5784
5784
  "anyhow",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sigit"
3
- version = "1.6.0"
3
+ version = "1.6.1"
4
4
  edition = "2024"
5
5
  description = "siGit Code — ACP-compatible AI coding agent. Sí, git."
6
6
  documentation = "https://github.com/getsigit/sigit"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sigit-code
3
- Version: 1.6.0
3
+ Version: 1.6.1
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -27,6 +27,11 @@ for interactive permission are denied in headless mode unless granted with
27
27
  `--allow-tool <name>`. A repeatable `--deny-tool <name>` takes precedence over grants and
28
28
  settings.
29
29
 
30
+ A run makes at most 24 rounds of tool calls. `--max-tool-rounds <n>`, or the
31
+ `SIGIT_MAX_TOOL_ROUNDS` environment variable, sets a different cap from 1 to 500; the flag wins.
32
+ When the cap is reached, or the model otherwise ends without saying anything, siGit Code asks it
33
+ once for a final message, so stdout is not left empty by a run that was cut off mid-task.
34
+
30
35
  ## JSONL output
31
36
 
32
37
  Pass `--output jsonl` for a machine-readable stdout stream: