Compare commits

..
Author SHA1 Message Date
Vasanthdev2004 a4578dacc0 test(tools): stop the tty session before removing its root
TestExecCommandTTYSkipsInteractiveBlock runs ssh to a bogus host under
tty with a 250ms yield. When ssh is still resolving the host after the
yield, the session keeps running in the test's directory and nothing
stops it, so on Windows t.TempDir's cleanup fails with a sharing
violation. It failed Smoke (windows-latest) on a README-only PR.

Stop the session in a cleanup, and build the tool on execTestRoot,
which retries the removal while Windows still holds the directory right
after the stop, as the other exec tests that leave a session do.
2026-10-27 12:31:03 +05:30
Vasanth T 164150633a feat(skills): install a skill's bundled files and point the skill tool at them (#1167)
* feat(skills): install a skill's bundled files and point the skill tool at them

A skill was only its SKILL.md: install copied that one file, and the skill
tool returned only the markdown body, so a skill whose instructions run its
own scripts was broken by installing it, and a hand-placed skill directory
gave the model no way to find the files beside it.

Install now copies the whole skill directory, leaving out hidden entries
(.git, .env) and symlinks, which it neither follows nor copies but reports.
A script stays executable; everything else is installed 0644. A tree over
1,000 files or 32 MiB is refused. skills.lock records a treeHash beside the
SKILL.md hash; skill info reports drift in bundled files, and an entry from
before this is still checked on SKILL.md alone. A reinstall that changed
only a bundled file is reported as an update.

Both skill tools end their output with the skill's directory and a list of
up to 50 bundled files when there are any; a SKILL.md-only skill is
returned unchanged.

Closes #584

* fix(skills): compare a reinstall over an old lock entry with SKILL.md alone

A lock entry written before tree hashes has no treeHash, so the reinstall
compared the new tree hash with an empty string and always counted as an
update. Re-adding an unchanged skill that is only a SKILL.md printed
"Updated" and "the files bundled with it changed" when there were none.

That entry comes from an install that copied SKILL.md alone, so compare
with that: it is an update when SKILL.md changed or the reinstall now
brings bundled files. InstallResult gains previousTreeHash, and the CLI
uses it to say the bundled files are now installed rather than changed
on the first reinstall over such an entry.

* fix(skills): match the tree hash when recovering an interrupted install

lockReconciler matched both trees of an interrupted workspace on the
SKILL.md hash alone. A reinstall that changes only a bundled file keeps
SKILL.md, so a kill between the tree swap and the lockfile publish left
two trees carrying the recorded hash, and recovery kept the new one and
retired the backup the lockfile still described.

A tree now matches an entry with a tree hash only when both hashes
match. An entry from before tree hashes is still matched on SKILL.md
alone. A tree hash that cannot be computed is returned as an error, the
way an unreadable SKILL.md already is, so recovery decides nothing about
a tree it could not read.

Also bring the package comment up to date: install copies the skill
directory, not SKILL.md alone.
2026-10-25 07:24:59 -07:00
Vasanth T 5d080ef46b test(tui): pin the cwd in tests that assert text is absent from the view (#1170)
* test(tui): pin the cwd in tests that assert text is absent from the view

Nine tests built their model with an empty Options.Cwd, so newModel took
the package directory and the title bar showed the developer's real
branch and checkout path. Each then asserted that some text was absent
from the whole view, and failed wherever that branch or path happened to
contain it: TestSuggestionOverlayRenders on a branch with /model in its
name, TestSuggestionOverlayCapsRowsWithoutMoreText on a branch or path
containing "more" or "compact", the provider wizard tests on "offline",
"chatgpt" or "gpt-4.1", and so on.

They now pin Options.Cwd to isolatedCwd, a path that does not exist, so
no branch is read and the path is the same on every machine. A guard
test checks that isolatedCwd stays outside any git checkout.

* test(tui): make isolatedCwd fully qualified and absent by construction

The fixed /workspace/zero path could exist on some machine, a dev
container for one, and newModel would then read whatever is there. On
Windows it was also only drive-relative: \workspace\zero is not a fully
qualified path.

isolatedCwd is now a child of the home TestMain isolates that nothing
creates, so it is fully qualified, absent and outside any checkout on
every platform, and the title bar shows ~/not-a-checkout everywhere. A
t.TempDir path would have put the user name and the test name into the
view. The guard test checks all three properties.

* test(tui): clear the branch in view-test models instead of trusting the cwd

isolatedCwd is a child of the isolated home, which lives under the
temporary directory. With TMPDIR inside a checkout, gitBranch walks up
from that absent path to the checkout and puts its branch in the title
bar, so the guard failed on an ordinary branch and a colliding branch
broke the nine view tests again.

newViewTestModel now builds those models: it sets the isolated cwd and
clears the branch after construction, since no path can promise there
is no checkout above it. The guard keeps only what a path can promise
(fully qualified and absent), and a new test puts the home inside a
checkout on feat/model-picker and checks that neither that branch nor
the real checkout path reaches the view.

* test(tui): assert no checkout path reaches the view in the enclosing-checkout test

The test checked the branch strings and that the isolated cwd shows,
but a view could show both the isolated cwd and a real checkout path
without failing it. It now also asserts that neither the enclosing
checkout nor the directory the tests run from appears in the view.
2026-10-22 06:52:51 -07:00
Vasanth T 1d233c0a9b test(modelregistry): clear ZERO_DISABLE_MODELS_FETCH in the models.dev tests (#1169)
The variable turns off both the models.dev fetch and the cached overlay,
and four tests in modelsdev_test.go inherited it from the shell. With it
set, TestRefreshModelsDevCacheRejectsBadBodyWithoutClobbering and
TestDefaultModelEntriesAppliesFreshCache fail, and
TestCachedModelsDevProvidersIgnoresStaleCache and
TestCachedModelsDevProvidersRequiresOptIn pass without reaching the
check they are named for: the nil they expect comes from the variable.

Clear it in each of them, as the two sibling refresh tests already do.
2026-10-21 07:33:36 -07:00
Vasanth T de1443b2ed fix(deps): refresh the lockfile to hono 4.13.13 (#1171)
GHSA-5r4p-p66f-jhc7 (moderate): hono's serveStatic decodes the request
path a second time, so middleware on static paths can be bypassed.
Versions 4.12.4 to 4.13.10 are affected and main pins 4.13.9 through
tuistory, which release packaging installs as a local-control helper.

npm update takes hono to 4.13.13, inside every range that asks for it.
package.json is unchanged and nothing else in the lock moves.
2026-10-21 07:12:22 -07:00
Felix-Ayush 84c1b15540 fix(oauth): reuse the crash-safe secret store and sync encrypted publications (#1165)
* fix(oauth): use the crash-safe secret store and sync token publications

The encrypted-file OAuth backend carried its own AES-GCM secret implementation that had drifted from internal/securefile: a crash during first login left <secret>.lock behind and every later Save timed out instead of reclaiming it, and the secret and token blobs were published without fsync.\n\nUse securefile.Crypter with an opt-in publication directory so the secret still publishes through the sandbox-denied .secret.publish directory, add file and directory barriers to the encrypted token blob, and extend securefile with the publication directory plus destination-directory sync.\n\nMove the concurrent-creation and wrong-sized-secret coverage to securefile, add a stale-lock regression through the OAuth store, and test publication-dir use, sync ordering, sync-failure preservation, and retry after a failed secret sync. The stale-lock test fails on the previous code.

* test(oauth): cover post-rename directory-sync failures

The existing failure tests inject a file sync error before the rename. Add coverage for a destination-directory barrier failure after the token or secret has been atomically replaced: the write must report the error and the newly published bytes must remain readable. Also cover the corresponding securefile secret path, including its expected size.

* fix(securefile): never clobber a newer lock when restoring a reclaimed one

reclaimStaleLock could rename a lock that had just been replaced, then restore the quarantined copy with os.Rename, replacing a third holder's new lock. Both holders could then publish different secrets for the same file. Restore with O_CREATE|O_EXCL and discard the quarantined copy when the destination is already held.

Extend the directory-sync failure test to prove a later caller actually loads the published secret, not just its size. Add restore tests for the free and already-held cases.
2026-10-21 18:41:38 +08:00
Vasanth T 6393a90ed6 fix: bump Go to 1.26.10 for GO-2026-6809 (#1168)
govulncheck now reports GO-2026-6809 (crypto/x509 distrusts compromised
intermediate certificates) against Go 1.26.9, reachable through
daemon.WriteFrame, so Security & Code Health fails on main and on every
open PR. It is fixed in 1.26.10.

CI and release builds read the toolchain from go.mod. The stated
source-build minimum in README.md, README_ZH.md and docs/INSTALL.md
moves with it.
2026-10-21 16:37:49 +08:00
Vasanth T cd3e0b0a9f test(cli): isolate the exec image tests from the models.dev refresh (#1161)
runExec starts a background models.dev download unless
ZERO_DISABLE_MODELS_FETCH is set. TestRunExecImageAttachmentHistory moved
the user directories into a TempDir by hand but left the fetch on, so a
5.7 MB download landed in the TempDir while its cleanup ran, and the
macOS runner failed with "unlinkat ... directory not empty". The two
older image tests had no isolation and fetched into the real user cache.

Use isolateCLIUserState in all three, which also points the cache path
into the fixture and turns the fetch off.
2026-10-18 10:08:09 +08:00
Felix-Ayush aa9ba8412e fix(tools): grep stops where a file's line breaks stop (#1160)
* fix(tools): grep stops where a file's line breaks stop

scanGrepFile treated EOF after a trailing line break - and an empty file -
as the START of another line, and handed the matcher an empty line one
number past the real content. Every pattern that matches an empty string
then reported a match on a line no reader sees:

  a.txt holds "x\n"; grep pattern "^$"
    -> a.txt:2:           (grep -c '' GNU-style reports no such line)
  same file, pattern ".*", count mode
    -> "2 matches found"  (one real line, one phantom)
  an empty file, pattern "^$"
    -> a.txt:1:           (the file has no lines at all)

read_file and the file tracker's countLines already state the rule the
scan violated: "the trailing newline does not open a line", and an empty
file has no lines. The scan now stops there too, so anchored and
empty-matching patterns can only match the lines the content carries.

Real matches, numbering, count-per-hit accounting for non-empty patterns,
file mode, glob and symlink handling are unchanged.

* test(tools): make the grep line-number parity test catch the phantom

The parity test matched only "needle", a pattern that cannot match the
empty line the old EOF branch invented, so its absence-of-line-4
assertion passed on the code being fixed. The pattern now also matches an
empty line, and the expected output is asserted exactly: on the pre-fix
scanner the same run reported

    grep = "a.txt:2: needle two\na.txt:4: ",
        want exactly the line read_file numbers 2

so the phantom is visible right beside the real line, and the test is
about the numbering claim it states. Also gofmt-aligns the case table
that made the CI formatting check fail.
2026-10-18 10:07:34 +08:00
Felix-Ayush a97db8832f fix(redaction): redact credentials whose trigger a greedy span swallowed (#1158)
Two credentials glued end to end share one recovering property: the second
one's trigger prefix is where redaction can re-anchor. For fixed-length
bodies that anchor is exact (AKIA{16} stops at the next key), which is the
case the anchored end-chaining always covered. For unbounded bodies it
isn't: github's [A-Za-z0-9]{36,} body eats the literal "ghp" of the token
glued behind it, so the first match ends PAST the second trigger, the scan
that re-anchors at the span end finds nothing there, and the second
credential leaves the output as +_bbbb... / .eyJzdWIi... plaintext:

  RedactString("ghp_aaa..36a..ghp_bbb..36b..")
    -> "[REDACTED]_bbbb......"   (second token's body printed in full)
  RedactString(jwtA+jwtB)
    -> "[REDACTED].eyJzdWIiOiI5ODc2...SA" (second token's payload+sig)

The same holds across shapes (a github key directly feeding "sk-ant-..."
through its body) and for three tokens in one run.

redactAdjacent now follows the run a match starts: at each boundary it
re-binds the match at the EARLIEST trigger occurrence inside the span that
stills validates as its shape (the head before the trigger must match the
anchored pattern exactly, and the run chained from the trigger must reach
past the span, so a mid-body lookalike and a mid-span cut that recovers
nothing leave the span alone), and continues under whichever shape matched
the swallowed trigger. Candidate examination is capped at
maxTriggerOccurrences per span so body spam of trigger lookalikes costs
what the span costs, and the first validating cut wins.

End-chaining for fixed-length bodies, single-credential shapes, custom
replacement templates, and the split path's announced no-op-for-contiguous
text guarantee are all unchanged; the regression tests pin the glued pairs
that previously leaked plus the AWS chaining and lone-token cases.
2026-10-18 10:07:04 +08:00
Vasanth T ef4da1323f fix(workspaceindex): import gitsafe under the Twigpine module path (#1153)
#1149 was opened before #1105 moved the module to github.com/Twigpine/zero
and merged right after it, so its two imports of internal/gitsafe still
used the old path. Nothing provides github.com/Gitlawb/zero any more, so
main stopped building at 2ffe1831.
2026-10-17 16:59:52 +08:00
zzucs 1e848492e5 fix(sessions): preserve image attachment summaries on resume (#1152)
* fix(sessions): preserve image attachment summaries on resume

Persist TUI-compatible attachment counts for ACP and exec user events.
Restore readable markers in live history, session replay and resumed
prompts without storing or resending historical image data.

Cover image-only and mixed prompts, restart recovery, malformed metadata
and the aborted-turn boundary with regression tests.

Fixes #1135

* fix(workspaceindex): follow the current module path

Use github.com/Twigpine/zero/internal/gitsafe in the ignore scanner and its
tests. The upstream module rename preceded the scanner's gitsafe change,
leaving these two imports on the old path and preventing the rebased tree
from compiling.

* fix(sessions): count only images accepted by the model

Take ACP and exec attachment counts after the vision capability gate,
matching the TUI's persisted attachment semantics. Update the ignored-image
regressions to require zero saved images and no historical image marker.

Refs #1135
2026-10-17 16:59:21 +08:00
Vasanth T 2ffe1831b0 fix(workspaceindex): run Scan's git calls through gitsafe (#1149)
* fix(workspaceindex): run Scan's git calls through gitsafe

Scan asks git for ignored paths on every turn with its own gitCommand, which
turned off core.fsmonitor and dropped the repository-location variables but
still let a clean filter or diff driver from the repository's own config run
during status. It now goes through gitsafe, like the TUI and the scratch-file
warning: hooks are off as well, and a repository whose config defines a filter
or diff command takes the non-git path. The status call also passes
--ignore-submodules=dirty.

Scan makes two git calls, so gitsafe gains Check, which reads the repository
config once and builds several commands from it. Command is now Check plus one
command, and each command gets its own copy of the environment.

* test(workspaceindex): pin Scan's fallback when gitsafe refuses the repository

A repository whose own config defines a filter or diff command is refused by
gitsafe, and Scan then takes the non-git path. Scan reaches gitsafe.Check
through a package variable now, so a test can stand in for such a repository
without configuring one that names a command, and
TestScanFallsBackWhenGitsafeRefusesTheRepository checks both sides: with git
consulted, .gitignore decides and the fixed list does not apply; refused, the
fixed list applies and .gitignore is not read.
2026-10-17 07:14:03 +08:00
PierrunoYTandClaude Opus 5.5 13e2596b2e chore: move repository references to Twigpine/zero (#1105)
Update the Go module path, imports, install scripts, docs, workflows,
packaging and action defaults from Gitlawb/zero to Twigpine/zero. The npm
scope (already @twigpine/zero on main, with its @gitlawb/zero migration
notes), LICENSE, action author, the OpenGateway provider and historical
CHANGELOG links are unchanged.

The X-AIMLAPI-Integration-Repo header keeps Gitlawb/zero: it is what
AIMLAPI sees for attribution, so changing it needs a maintainer's yes.
docs/INSTALL.md keeps `go install github.com/Gitlawb/zero/...`, because
every tagged release so far declares that module path. The Homebrew
formula comment names the Twigpine/homebrew-tap tap.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-17 07:13:21 +08:00
HazyLabandcl-ment 8c4e759798 feat(sandbox): support self-exec pattern to eliminate separate helper binary (#946)
* feat(sandbox): support self-exec pattern to eliminate separate helper binary

Linux sandboxing previously required distributing a separate 'zero-linux-sandbox'
binary and looking it up on PATH.

This wires an internal '__sandbox-helper' entrypoint directly in cmd/zero/main.go
and configures findLinuxSandboxHelperCommand to fallback to os.Executable(),
enabling single-binary zero distributions with fully functional sandboxing.

* fix(sandbox): prefer PATH helper before self-exec fallback

Colocated zero-linux-sandbox stays first. A standalone helper on PATH
is used next. Self-exec of the main zero binary is only the last
fallback, so an installed helper is not ignored.

* feat(sandbox): clean formatting in main.go and add self-exec unit tests

* feat(sandbox): insert __sandbox-helper verb in bwrap plan for self-exec binary

* fix(sandbox): drive Linux selection from the self-exec helper resolver

Availability and command-plan construction now share the same helper
resolution and keep the __sandbox-helper prefix on the backend, so a
main-only install with bwrap reaches native sandboxing.

* fix(sandbox): self-exec only the zero main binary, not test binaries

* fix(sandbox): carry standalone vs self-exec identity into the inner bwrap plan

A versioned physical helper (zero-linux-sandbox-v1) must not receive
__sandbox-helper. The inner plan uses an explicit SelfExec flag, threaded
from argv, instead of guessing from filepath.Base.

* docs(install): describe self-exec as the last helper fallback

Self-execution runs only when neither a colocated nor a PATH
zero-linux-sandbox is available.

* docs(install): state colocated, PATH, then self-exec helper order

---------

Co-authored-by: cl-ment <[email protected]>
2026-10-16 14:09:28 +08:00
691 changed files with 4579 additions and 2026 deletions

No files matched your search

+10 -9
View File
@@ -12,7 +12,7 @@
<p align="center">
<a href="LICENSE"><img alt="license" src="https://img.shields.io/badge/license-MIT-blue"></a>
<img alt="Go 1.26.9+" src="https://img.shields.io/badge/Go-1.26.9+-00ADD8?logo=go&logoColor=white">
<img alt="Go 1.26.10+" src="https://img.shields.io/badge/Go-1.26.10+-00ADD8?logo=go&logoColor=white">
<img alt="25+ providers" src="https://img.shields.io/badge/providers-25+-34E2EA">
<a href="https://discord.gg/CaQDS6wdFn"><img alt="Discord" src="https://img.shields.io/badge/Discord-join-5865F2?logo=discord&logoColor=white"></a>
<br>
@@ -158,7 +158,7 @@ irm https://raw.githubusercontent.com/Twigpine/zero/main/scripts/install.ps1 | i
### From source
Source builds require Go 1.26.9+.
Source builds require Go 1.26.10+.
```bash
git clone https://github.com/Twigpine/zero.git
@@ -173,17 +173,18 @@ If you are testing before the first public release, build from source:
go build -o zero ./cmd/zero
```
On Linux, build the sandbox helper too if you want native sandboxing:
On Linux, native sandboxing needs Bubblewrap. The main `zero` binary re-executes
itself as the helper when `zero-linux-sandbox` is absent. A separate helper next
to `zero` or on `PATH` still takes precedence:
```bash
go build -o zero-linux-sandbox ./cmd/zero-linux-sandbox
go build -o zero-seccomp ./cmd/zero-seccomp # optional compatibility wrapper
go build -o zero-linux-sandbox ./cmd/zero-linux-sandbox # optional
go build -o zero-seccomp ./cmd/zero-seccomp # optional compatibility wrapper
```
Put `zero` and `zero-linux-sandbox` in the same directory on `PATH`
(`~/.local/bin` is a good default). macOS does not need an extra helper binary.
Windows source builds can use the main `zero.exe` as their sandbox helper; release
archives still ship standalone Windows helper executables.
Put `zero` on `PATH` (`~/.local/bin` is a good default). macOS does not need an
extra helper binary. Windows source builds can use the main `zero.exe` as their
sandbox helper; release archives still ship standalone Windows helper executables.
More install details: [docs/INSTALL.md](docs/INSTALL.md).
+6 -6
View File
@@ -12,7 +12,7 @@
<p align="center">
<a href="LICENSE"><img alt="license" src="https://img.shields.io/badge/license-MIT-blue"></a>
<img alt="Go 1.26.9+" src="https://img.shields.io/badge/Go-1.26.9+-00ADD8?logo=go&logoColor=white">
<img alt="Go 1.26.10+" src="https://img.shields.io/badge/Go-1.26.10+-00ADD8?logo=go&logoColor=white">
<img alt="25+ providers" src="https://img.shields.io/badge/providers-25+-34E2EA">
<a href="https://discord.gg/CaQDS6wdFn"><img alt="Discord" src="https://img.shields.io/badge/Discord-join-5865F2?logo=discord&logoColor=white"></a>
<br>
@@ -136,7 +136,7 @@ irm https://raw.githubusercontent.com/Twigpine/zero/main/scripts/install.ps1 | i
### 从源码构建
源码构建需要 Go 1.26.9+。
源码构建需要 Go 1.26.10+。
```bash
git clone https://github.com/Twigpine/zero.git
@@ -150,14 +150,14 @@ go run ./cmd/zero
go build -o zero ./cmd/zero
```
在 Linux 上,如果你需要原生沙箱,还需要构建沙箱辅助程序:
在 Linux 上,原生沙箱需要 Bubblewrap。主程序 `zero` 在找不到 `zero-linux-sandbox` 时会以自身作为辅助进程重新执行。同目录或 PATH 上的独立 helper 仍优先:
```bash
go build -o zero-linux-sandbox ./cmd/zero-linux-sandbox
go build -o zero-seccomp ./cmd/zero-seccomp # 可选的兼容性包装器
go build -o zero-linux-sandbox ./cmd/zero-linux-sandbox # 可选
go build -o zero-seccomp ./cmd/zero-seccomp # 可选的兼容性包装器
```
将 `zero` 和 `zero-linux-sandbox` 放在 `PATH` 上的同一目录中(`~/.local/bin` 是一个好的默认选择)。macOS 不需要额外的辅助二进制文件。Windows 源码构建可以使用主 `zero.exe` 作为沙箱辅助程序;发布包仍然附带独立的 Windows 辅助可执行文件。
将 `zero` 放在 `PATH` 上(`~/.local/bin` 是一个好的默认选择)。macOS 不需要额外的辅助二进制文件。Windows 源码构建可以使用主 `zero.exe` 作为沙箱辅助程序;发布包仍然附带独立的 Windows 辅助可执行文件。
更多安装细节:[docs/INSTALL.md](docs/INSTALL.md)。
+5 -5
View File
@@ -2,8 +2,8 @@
## Questions, setup help, configuration
Ask in [GitHub Discussions](https://github.com/Gitlawb/zero/discussions). Use
[Q&A](https://github.com/Gitlawb/zero/discussions/categories/q-a) so answers can
Ask in [GitHub Discussions](https://github.com/Twigpine/zero/discussions). Use
[Q&A](https://github.com/Twigpine/zero/discussions/categories/q-a) so answers can
be marked and found by the next person.
The fastest way to a useful answer is to include:
@@ -18,19 +18,19 @@ The fastest way to a useful answer is to include:
## Bugs
If Zero does something wrong (crashes, wrong output, broken behavior), file a
[bug report](https://github.com/Gitlawb/zero/issues/new/choose). If you are not
[bug report](https://github.com/Twigpine/zero/issues/new/choose). If you are not
sure whether it is a bug or a setup problem, start in Discussions and we will
promote it to an issue if it is one.
## Feature ideas
Start a thread in
[Ideas](https://github.com/Gitlawb/zero/discussions/categories/ideas). Zero is
[Ideas](https://github.com/Twigpine/zero/discussions/categories/ideas). Zero is
in controlled development (see CONTRIBUTING.md), so discussing an idea first is
also the path to getting a PR accepted.
## Security issues
Never file a public issue or discussion. Report privately via
[GitHub Security Advisories](https://github.com/Gitlawb/zero/security/advisories/new)
[GitHub Security Advisories](https://github.com/Twigpine/zero/security/advisories/new)
(see SECURITY.md).
+1 -1
View File
@@ -5,7 +5,7 @@ package main
import (
"os"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/sandbox"
)
func main() {
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"strconv"
"strings"
"github.com/Gitlawb/zero/internal/perfbench"
"github.com/Twigpine/zero/internal/perfbench"
)
const (
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/perfbench"
"github.com/Twigpine/zero/internal/perfbench"
)
func TestParseArgsUsesCliAndEnvOverrides(t *testing.T) {
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"path/filepath"
"strings"
"github.com/Gitlawb/zero/internal/perfbench"
"github.com/Twigpine/zero/internal/perfbench"
)
// taskOptions configures the `zero-perf-bench tasks` subcommand: the reproducible
+2 -2
View File
@@ -9,8 +9,8 @@ import (
"path/filepath"
"strings"
"github.com/Gitlawb/zero/internal/execprofile"
"github.com/Gitlawb/zero/internal/perfbench"
"github.com/Twigpine/zero/internal/execprofile"
"github.com/Twigpine/zero/internal/perfbench"
)
// turnOptions configures the `zero-perf-bench turn` subcommand: the per-turn
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"os"
"strings"
"github.com/Gitlawb/zero/internal/review"
"github.com/Twigpine/zero/internal/review"
)
func main() {
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"path/filepath"
"strings"
"github.com/Gitlawb/zero/internal/release"
"github.com/Twigpine/zero/internal/release"
)
func main() {
+1 -1
View File
@@ -12,7 +12,7 @@ import (
"os/exec"
"syscall"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/sandbox"
)
func main() {
+1 -1
View File
@@ -3,7 +3,7 @@ package main
import (
"os"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/sandbox"
)
func main() {
+1 -1
View File
@@ -3,7 +3,7 @@ package main
import (
"os"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/sandbox"
)
func main() {
+5 -1
View File
@@ -3,9 +3,13 @@ package main
import (
"os"
"github.com/Gitlawb/zero/internal/cli"
"github.com/Twigpine/zero/internal/cli"
"github.com/Twigpine/zero/internal/sandbox"
)
func main() {
if len(os.Args) > 1 && os.Args[1] == sandbox.LinuxSandboxHelperSubcommand {
os.Exit(sandbox.RunLinuxSandboxHelper(os.Args[1:], os.Stderr))
}
os.Exit(cli.Run(os.Args[1:], os.Stdout, os.Stderr))
}
+11
View File
@@ -110,6 +110,8 @@ Discovery roots (earlier wins on name collisions):
A missing directory is fine — Zero just omits it. Management commands (`zero skill add` / `zero skill remove`) always write to the primary Zero dir only; `list` / `info` search primary + `~/.agents/skills`. `skill info` reports lock source/hash when present and whether the on-disk `SKILL.md` has drifted from the recorded hash.
For a skill installed since Zero started installing bundled files, `skill info` also reports drift in any file installed with it.
```text
~/.local/share/zero/skills/ # primary (install/remove target)
run-benchmarks/
@@ -140,6 +142,15 @@ Only `name` and `description` are recognized in the frontmatter today. The `name
The `skill` tool lets the agent load any discovered skill by name (primary, agents, or plugin).
A skill can ship files beside its `SKILL.md`, in the [agentskills.io](https://agentskills.io/specification) layout: `scripts/`, `references/`, `assets/` or anything else the instructions refer to. `zero skill add` installs the whole skill directory, leaving out hidden entries such as `.git` and not following or copying symlinks (it lists any it skipped). A skill larger than 1,000 files or 32 MiB is refused. When the `skill` tool loads a skill that has such files, its output ends with the skill's directory and a list of the files, so instructions like "run `scripts/fill.py`" can be followed. Running a bundled script goes through the normal tool permissions; installing or loading a skill never runs anything.
```text
pdf/
SKILL.md
scripts/fill.py
references/form-fields.md
```
## 4. Hooks
Hooks fire shell commands on lifecycle events. Configure them in JSON:
+27 -14
View File
@@ -185,13 +185,19 @@ describe a version that is no longer on disk. Prefer `mise upgrade`.
go install github.com/Gitlawb/zero/cmd/zero@latest
```
This builds from source, so it needs Go 1.26.9+ and it does not go through the
Every release so far declares the module path `github.com/Gitlawb/zero`, so
`go install` has to use that path; `github.com/Twigpine/zero/cmd/zero@latest`
fails with "module declares its path as" until a release is tagged with the new
module path.
This builds from source, so it needs Go 1.26.10+ and it does not go through the
release archives. Two consequences worth knowing before you pick it:
- On Linux you also need the sandbox helper, which is a separate binary and is
not installed by this command. See
- On Linux, Bubblewrap is still required for native sandboxing. Zero resolves
the helper in this order: an executable `zero-linux-sandbox` next to `zero`,
then `PATH`, then self-exec of the main binary. Standalone helpers remain
optional and preferred. See
[Sandbox Helpers For Source Builds](#sandbox-helpers-for-source-builds).
Without it, native sandboxing is unavailable.
- `zero upgrade` treats the result as a standalone install and will replace the
binary with a release build rather than rebuilding from source. If you chose
`go install` deliberately, rerun it instead.
@@ -212,25 +218,32 @@ Build a local binary:
go build -o zero ./cmd/zero
```
Source builds require Go 1.26.9+.
Source builds require Go 1.26.10+.
### Sandbox Helpers For Source Builds
Release archives include the platform sandbox helpers. If you build directly
from source, build the helpers you need:
Release archives include the platform sandbox helpers. A source build of the
main `zero` binary is enough for Linux native sandboxing. Zero resolves the
helper in this order:
Linux:
1. an executable `zero-linux-sandbox` next to `zero`
2. then `PATH`
3. then self-exec of the main binary
Bubblewrap must still be installed. Standalone helpers remain optional and
preferred.
A separate helper remains supported if it sits next to `zero` or on `PATH`:
```bash
go build -o zero ./cmd/zero
go build -o zero-linux-sandbox ./cmd/zero-linux-sandbox
go build -o zero-seccomp ./cmd/zero-seccomp
go build -o zero-linux-sandbox ./cmd/zero-linux-sandbox # optional
go build -o zero-seccomp ./cmd/zero-seccomp # optional compatibility wrapper
```
Put `zero` and `zero-linux-sandbox` in the same directory on `PATH`, for example
`~/.local/bin`. `zero-seccomp` is kept as a compatibility wrapper; the sandbox
helper applies the Unix-socket filter itself when that sandbox option is enabled.
Linux native sandboxing also requires Bubblewrap to be installed.
Put `zero` on `PATH`, for example `~/.local/bin`. `zero-seccomp` is kept as a
compatibility wrapper; the sandbox helper applies the Unix-socket filter itself
when that sandbox option is enabled.
macOS uses the system sandbox and does not need an extra helper binary.
+2 -2
View File
@@ -1,6 +1,6 @@
module github.com/Gitlawb/zero
module github.com/Twigpine/zero
go 1.26.9
go 1.26.10
require (
charm.land/bubbles/v2 v2.1.1
+27 -20
View File
@@ -13,18 +13,18 @@ import (
"strings"
"sync"
"github.com/Gitlawb/zero/internal/agent"
"github.com/Gitlawb/zero/internal/config"
"github.com/Gitlawb/zero/internal/modelregistry"
"github.com/Gitlawb/zero/internal/providercatalog"
"github.com/Gitlawb/zero/internal/providermodelcatalog"
"github.com/Gitlawb/zero/internal/providermodeldiscovery"
"github.com/Gitlawb/zero/internal/redaction"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Gitlawb/zero/internal/sessions"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/usage"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/agent"
"github.com/Twigpine/zero/internal/config"
"github.com/Twigpine/zero/internal/modelregistry"
"github.com/Twigpine/zero/internal/providercatalog"
"github.com/Twigpine/zero/internal/providermodelcatalog"
"github.com/Twigpine/zero/internal/providermodeldiscovery"
"github.com/Twigpine/zero/internal/redaction"
"github.com/Twigpine/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/sessions"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/usage"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// Deps are the ZERO capabilities the ACP Agent drives. The CLI fills these with
@@ -528,8 +528,6 @@ func (a *Agent) runTurn(ctx context.Context, sess *acpSession, userText string,
queue := func(input sessions.AppendEventInput) {
pendingEvents = append(pendingEvents, input)
}
queue(messageEvent("user", userText))
visionCache := make(map[string]bool)
var visionCacheMu sync.Mutex
supportsVision := func(modelID string) bool {
@@ -553,6 +551,9 @@ func (a *Agent) runTurn(ctx context.Context, sess *acpSession, userText string,
effectivePrompt = fmt.Sprintf("[Note: %s]\n\n%s", msg, userText)
images = nil
}
// Match the TUI: only images actually sent to the model enter history.
attachments := sessions.MessageAttachments{Images: len(images)}
queue(messageEvent("user", userText, attachments))
opts := agent.Options{
Cwd: sess.cwd,
@@ -593,7 +594,7 @@ func (a *Agent) runTurn(ctx context.Context, sess *acpSession, userText string,
agentPrompt := buildPrompt(sess.snapshotHistory(), effectivePrompt)
result, runErr := a.deps.RunAgent(ctx, agentPrompt, provider, opts)
if result.FinalAnswer != "" {
queue(messageEvent("assistant", result.FinalAnswer))
queue(messageEvent("assistant", result.FinalAnswer, sessions.MessageAttachments{}))
}
reason, stopErr := stopReasonFor(result, runErr)
if stopErr != nil {
@@ -614,7 +615,7 @@ func (a *Agent) runTurn(ctx context.Context, sess *acpSession, userText string,
// one file write, which is the contiguity restored here — not a disk
// transaction, and not a promise about crashes mid-fsync. Reported by @jatmn.
persistenceErr := a.persistTurn(sess.id, pendingEvents)
sess.appendHistory(turnRecord{user: userText, assistant: result.FinalAnswer})
sess.appendHistory(turnRecord{user: attachments.WithContent(userText), assistant: result.FinalAnswer})
a.warnPersistence(
note,
"save session history",
@@ -930,10 +931,14 @@ func (a *Agent) persistTurn(sessionID string, inputs []sessions.AppendEventInput
return err
}
func messageEvent(role, content string) sessions.AppendEventInput {
func messageEvent(role, content string, attachments sessions.MessageAttachments) sessions.AppendEventInput {
payload := map[string]any{"role": role, "content": content}
if role == "user" && (attachments.Images > 0 || attachments.Documents > 0) {
payload["attachments"] = attachments
}
return sessions.AppendEventInput{
Type: sessions.EventMessage,
Payload: map[string]any{"role": role, "content": content},
Payload: payload,
}
}
@@ -1094,14 +1099,16 @@ func (a *Agent) loadHistory(sessionID string, requireHistoryLog bool) ([]turnRec
continue
}
var msg struct {
Role string `json:"role"`
Content string `json:"content"`
Role string `json:"role"`
Content string `json:"content"`
Attachments sessions.MessageAttachments `json:"attachments"`
}
if json.Unmarshal(raw, &msg) != nil {
continue
}
switch msg.Role {
case "user":
msg.Content = msg.Attachments.WithContent(msg.Content)
messages = append(messages, persistedMessage{eventID: persistedMessageIdentity(sessionID, e), role: msg.Role, content: msg.Content})
if havePending {
records = append(records, turnRecord{user: pendingUser})
+12 -8
View File
@@ -19,13 +19,13 @@ import (
"testing"
"time"
"github.com/Gitlawb/zero/internal/agent"
"github.com/Gitlawb/zero/internal/config"
"github.com/Gitlawb/zero/internal/providermodeldiscovery"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Gitlawb/zero/internal/sessions"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/agent"
"github.com/Twigpine/zero/internal/config"
"github.com/Twigpine/zero/internal/providermodeldiscovery"
"github.com/Twigpine/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/sessions"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// fakeProvider streams a canned assistant message and ends the turn — enough to
@@ -960,7 +960,11 @@ func TestACPHardTurnFailureDoesNotCommitHistory(t *testing.T) {
t.Fatal(err)
}
if err := h.client.Call(ctx, MethodSessionPrompt, PromptParams{
SessionID: created.SessionID, Prompt: []ContentBlock{TextBlock("failed question")},
SessionID: created.SessionID,
Prompt: []ContentBlock{
TextBlock("failed question"),
ImageBlock(base64.StdEncoding.EncodeToString([]byte("failed image")), "image/png"),
},
}, &PromptResult{}); err == nil {
t.Fatal("hard RunAgent failure was reported as a successful turn")
}
+1 -1
View File
@@ -15,7 +15,7 @@ import (
"fmt"
"testing"
"github.com/Gitlawb/zero/internal/agent"
"github.com/Twigpine/zero/internal/agent"
)
// ---- an offered option is an acceptable answer ----
+181
View File
@@ -0,0 +1,181 @@
package acp
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"strings"
"testing"
"time"
"github.com/Twigpine/zero/internal/agent"
"github.com/Twigpine/zero/internal/config"
"github.com/Twigpine/zero/internal/providermodeldiscovery"
"github.com/Twigpine/zero/internal/sessions"
"github.com/Twigpine/zero/internal/zeroruntime"
)
func TestACPImageAttachmentHistory(t *testing.T) {
for _, tc := range []struct {
name string
text string
images int
withoutVision bool
want string
}{
{name: "image only", images: 1, want: "[1 image]"},
{name: "text and images", text: "compare these", images: 2, want: "compare these [2 images]"},
{name: "ignored by model", images: 1, withoutVision: true, want: ""},
} {
t.Run(tc.name, func(t *testing.T) {
deps := testDeps(t)
deps.DiscoverModels = func(context.Context, config.ProviderProfile) ([]providermodeldiscovery.Model, error) {
modalities := []string{"text", "image"}
if tc.withoutVision {
modalities = []string{"text"}
}
return []providermodeldiscovery.Model{{ID: "fake-model", InputModalities: modalities}}, nil
}
type observedTurn struct {
prompt string
images int
}
turns := make(chan observedTurn, 8)
deps.RunAgent = func(_ context.Context, prompt string, _ zeroruntime.Provider, opts agent.Options) (agent.Result, error) {
turns <- observedTurn{prompt: prompt, images: len(opts.Images)}
return agent.Result{FinalAnswer: "received"}, nil
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
workspace := t.TempDir()
h := newHarness(t, deps)
defer h.stop()
var created NewSessionResult
if err := h.client.Call(ctx, MethodSessionNew, NewSessionParams{Cwd: workspace}, &created); err != nil {
t.Fatal(err)
}
const imageData = "image-history-bytes-not-for-storage"
encoded := base64.StdEncoding.EncodeToString([]byte(imageData))
var blocks []ContentBlock
if tc.text != "" {
blocks = append(blocks, TextBlock(tc.text))
}
for range tc.images {
blocks = append(blocks, ImageBlock(encoded, "image/png"))
}
if err := h.client.Call(ctx, MethodSessionPrompt, PromptParams{SessionID: created.SessionID, Prompt: blocks}, &PromptResult{}); err != nil {
t.Fatal(err)
}
live := <-turns
wantLiveImages := tc.images
if tc.withoutVision {
wantLiveImages = 0
}
if live.images != wantLiveImages {
t.Fatalf("live images = %d, want %d", live.images, wantLiveImages)
}
events, err := deps.Store.ReadEvents(created.SessionID)
if err != nil || len(events) < 2 {
t.Fatalf("read completed turn: events=%+v err=%v", events, err)
}
var user struct {
Role string `json:"role"`
Content string `json:"content"`
Attachments struct {
Images int `json:"images"`
} `json:"attachments"`
}
if err := json.Unmarshal(events[0].Payload, &user); err != nil {
t.Fatal(err)
}
if user.Role != "user" || user.Content != tc.text || user.Attachments.Images != wantLiveImages {
t.Errorf("stored user event = %s, want original text and attachments.images=%d", events[0].Payload, wantLiveImages)
}
for _, event := range events {
if bytes.Contains(event.Payload, []byte(encoded)) || bytes.Contains(event.Payload, []byte(imageData)) {
t.Errorf("image data leaked into session event: %s", event.Payload)
}
}
followUp := func(t *testing.T, client *clientHarness) {
t.Helper()
if err := client.client.Call(ctx, MethodSessionPrompt, PromptParams{
SessionID: created.SessionID, Prompt: []ContentBlock{TextBlock("what did I send?")},
}, &PromptResult{}); err != nil {
t.Fatal(err)
}
got := <-turns
if want := "User: " + tc.want + "\n"; !strings.Contains(got.prompt, want) {
t.Errorf("seeded prompt missing user history %q: %q", want, got.prompt)
}
if got.images != 0 {
t.Errorf("historical images reattached: %d", got.images)
}
}
followUp(t, h)
h.stop()
// Fresh agents and stores reproduce a process restart, with no shared
// in-memory history to hide a missing attachment in the event log.
for _, method := range []string{MethodSessionLoad, MethodSessionResume} {
t.Run(method, func(t *testing.T) {
restoredDeps := deps
restoredDeps.Store = sessions.NewStore(sessions.StoreOptions{RootDir: deps.Store.RootDir})
loader := newHarness(t, restoredDeps)
defer loader.stop()
if err := loader.client.Call(ctx, method, LoadSessionParams{SessionID: created.SessionID, Cwd: workspace}, &LoadSessionResult{}); err != nil {
t.Fatal(err)
}
if method == MethodSessionLoad {
select {
case update := <-loader.notifications:
if update.SessionUpdate != UpdateUserMessageChunk || update.Content.Text != tc.want {
t.Errorf("replayed user = %+v, want %q", update, tc.want)
}
case <-ctx.Done():
t.Fatal("user turn was not replayed")
}
} else {
select {
case update := <-loader.notifications:
t.Errorf("resume replayed transcript: %+v", update)
default:
}
}
followUp(t, loader)
})
}
})
}
}
func TestACPAttachmentHistoryReadsExistingSessionMessages(t *testing.T) {
deps := testDeps(t)
meta, err := deps.Store.Create(sessions.CreateInput{SessionID: "attachment-compatibility", Cwd: t.TempDir()})
if err != nil {
t.Fatal(err)
}
for i, payload := range []string{
`{"role":"user","content":"old text"}`,
`{"role":"user","content":"","attachments":{"images":1,"documents":2}}`,
`{"role":"user","content":"still text","attachments":{"images":-1,"documents":1.5}}`,
`{"role":"assistant","content":"answer","attachments":{"images":1}}`,
} {
if _, err := deps.Store.AppendEvent(meta.SessionID, sessions.AppendEventInput{Type: sessions.EventMessage, Payload: json.RawMessage(payload)}); err != nil {
t.Fatalf("append message %d: %v", i, err)
}
}
a := &Agent{deps: deps}
_, messages, _, err := a.loadHistory(meta.SessionID, false)
if err != nil {
t.Fatal(err)
}
want := []string{"old text", "[1 image] [2 documents]", "still text", "answer"}
if len(messages) != len(want) {
t.Fatalf("messages = %+v", messages)
}
for i, text := range want {
if messages[i].content != text {
t.Errorf("message %d = %q, want %q", i, messages[i].content, text)
}
}
}
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"testing"
"time"
"github.com/Gitlawb/zero/internal/agent"
"github.com/Gitlawb/zero/internal/sessions"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/agent"
"github.com/Twigpine/zero/internal/sessions"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// captureAgentOptions makes every turn record the agent.Options it was built
+1 -1
View File
@@ -3,7 +3,7 @@ package acp
import (
"encoding/json"
"github.com/Gitlawb/zero/internal/agent"
"github.com/Twigpine/zero/internal/agent"
)
// permission.go maps ZERO's permission prompt model onto ACP's
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/agent"
"github.com/Twigpine/zero/internal/agent"
)
func escalationRequest(escalates bool) agent.PermissionRequest {
+1 -1
View File
@@ -3,7 +3,7 @@ package acp
import (
"testing"
"github.com/Gitlawb/zero/internal/agent"
"github.com/Twigpine/zero/internal/agent"
)
func TestBuildPermissionOptions(t *testing.T) {
+3 -3
View File
@@ -8,8 +8,8 @@ import (
"unicode"
"unicode/utf8"
"github.com/Gitlawb/zero/internal/agent"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Twigpine/zero/internal/agent"
"github.com/Twigpine/zero/internal/tools"
)
// translate.go maps ZERO's agent events onto ACP session/update payloads. The
@@ -83,7 +83,7 @@ func browserToolDetails(name string) (*BrowserToolDetails, bool) {
}
}
const zeroBrowserMetaKey = "github.com/Gitlawb/zero/browser"
const zeroBrowserMetaKey = "github.com/Twigpine/zero/browser"
// attachBrowserToolDetails stores ZERO's browser descriptor in ACP's reserved
// extension channel. Keeping this in one helper prevents start, result, and
+2 -2
View File
@@ -8,8 +8,8 @@ import (
"unicode"
"unicode/utf8"
"github.com/Gitlawb/zero/internal/agent"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Twigpine/zero/internal/agent"
"github.com/Twigpine/zero/internal/tools"
)
func browserDescriptor(t *testing.T, update ToolCallUpdate) BrowserToolDetails {
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"testing"
"time"
"github.com/Gitlawb/zero/internal/agent"
"github.com/Gitlawb/zero/internal/sessions"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/agent"
"github.com/Twigpine/zero/internal/sessions"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// `zero exec` persists every provider usage report as a session usage event, so
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
func enabledSelfCorrector(t *testing.T) *SelfCorrector {
@@ -5,8 +5,8 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Twigpine/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/tools"
)
type normalizationRecordingShellTool struct {
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// providerCallingAskUserThenAnswer returns a mock provider whose first turn calls
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"testing"
"time"
"github.com/Gitlawb/zero/internal/testutil"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/testutil"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
func TestAsyncDiagnosticsNilCollectorNoOps(t *testing.T) {
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"runtime"
"strings"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Twigpine/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/tools"
"mvdan.cc/sh/v3/syntax"
)
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"runtime"
"testing"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Twigpine/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/tools"
)
func TestProposedCommandPrefixUsesSafeSimpleCommands(t *testing.T) {
+2 -2
View File
@@ -8,8 +8,8 @@ import (
"strings"
"unicode/utf8"
"github.com/Gitlawb/zero/internal/trace"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/trace"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// Session compaction.
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// compactableHistory returns a history whose middle is large enough to trip
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
func TestCompactMessagesReturnsMetadataForManualCompaction(t *testing.T) {
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"strings"
"unicode/utf8"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// Compaction preservation.
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"testing"
"unicode/utf8"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// stateConversation is a long enough conversation that Compact elides a middle
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"regexp"
"strings"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
const (
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"testing"
"unicode/utf8"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
func TestClipBytesHandlesTinyLimitsOnRuneBoundaries(t *testing.T) {
@@ -5,7 +5,7 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// recentEdits extracts each mutated file's path and a one-line note from the
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"sync/atomic"
"testing"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
var markerPattern = regexp.MustCompile(`msg-\d+`)
+3 -3
View File
@@ -6,9 +6,9 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/trace"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/trace"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// --- Pure Compact() tests -------------------------------------------------
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// someRequestContains reports whether any message sent to the provider across all
+1 -1
View File
@@ -3,7 +3,7 @@ package agent
import (
"encoding/json"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// Context budget.
+1 -1
View File
@@ -3,7 +3,7 @@ package agent
import (
"testing"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
func TestMeasureContextSplitsByCategory(t *testing.T) {
+1 -1
View File
@@ -3,7 +3,7 @@ package agent
import (
"strings"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
type contextPlannerConfig struct {
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"reflect"
"testing"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
func TestContextPlannerPreservesProviderRequest(t *testing.T) {
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"fmt"
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Twigpine/zero/internal/tools"
)
// richMCPTool mimics a real MCP tool's schema (Exa/Firecrawl-style search): an
+2 -2
View File
@@ -8,8 +8,8 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
func TestOptionsDeferThresholdFieldExists(t *testing.T) {
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"context"
"testing"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Twigpine/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/tools"
)
func TestExecuteToolCallCategorizesFilteredDenial(t *testing.T) {
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"context"
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Twigpine/zero/internal/tools"
)
// argsPermTool implements tools.ArgsPermissioner; plainTool does not.
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// TestOptionsModelSwitcherFieldExists asserts the escalation hook is an
+2 -2
View File
@@ -2,8 +2,8 @@
package agent
import (
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
func equalStringSlices(left []string, right []string) bool {
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"strings"
"time"
"github.com/Gitlawb/zero/internal/lsp"
"github.com/Twigpine/zero/internal/lsp"
)
// fileDiagnosticsTimeout bounds one inline post-edit diagnostics check so a
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
type customApplyPatchTool struct {
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"strconv"
"strings"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// Guardrail thresholds for the agent loop. These keep a runaway model from
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// emptyTurn is a stream that produces no visible text and no tool calls.
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/hooks"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Twigpine/zero/internal/hooks"
"github.com/Twigpine/zero/internal/tools"
)
func TestAppendHookFeedbackFormatsOutput(t *testing.T) {
+9 -9
View File
@@ -11,15 +11,15 @@ import (
"strings"
"sync"
"github.com/Gitlawb/zero/internal/execution"
"github.com/Gitlawb/zero/internal/hooks"
"github.com/Gitlawb/zero/internal/modelregistry"
"github.com/Gitlawb/zero/internal/redaction"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Gitlawb/zero/internal/streamjson"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/trace"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/execution"
"github.com/Twigpine/zero/internal/hooks"
"github.com/Twigpine/zero/internal/modelregistry"
"github.com/Twigpine/zero/internal/redaction"
"github.com/Twigpine/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/streamjson"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/trace"
"github.com/Twigpine/zero/internal/zeroruntime"
)
const maxTurnsAnswer = "Agent reached maximum number of turns without a final answer."
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"context"
"testing"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// imageEchoProvider records the messages of the first request it receives, then
+7 -7
View File
@@ -13,13 +13,13 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/execution"
"github.com/Gitlawb/zero/internal/hooks"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Gitlawb/zero/internal/specmode"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/trace"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/execution"
"github.com/Twigpine/zero/internal/hooks"
"github.com/Twigpine/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/specmode"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/trace"
"github.com/Twigpine/zero/internal/zeroruntime"
)
type mockProvider struct {
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"context"
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Twigpine/zero/internal/tools"
)
type safetyTool struct {
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"testing"
"time"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/trace"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/trace"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// midStreamAbortProvider connects successfully but emits a transport-abort
@@ -8,10 +8,10 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/hooks"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/trace"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/hooks"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/trace"
"github.com/Twigpine/zero/internal/zeroruntime"
)
type propagationOutputTool struct {
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"context"
"sync"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Twigpine/zero/internal/tools"
)
// Parallel read-ahead for tool batches. When a turn requests several
+2 -2
View File
@@ -7,8 +7,8 @@ import (
"testing"
"time"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// probeTool records execution overlap and ordering so tests can assert what
@@ -3,7 +3,7 @@ package agent
import (
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Twigpine/zero/internal/tools"
)
func TestPartitionToolsActiveAppendsLoadedToolAfterEagerBlock(t *testing.T) {
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"reflect"
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// countingSchemaTool records how many times its Parameters() schema is read, so a
@@ -4,8 +4,8 @@ import (
"context"
"testing"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Twigpine/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/tools"
)
// promptShellTool is a minimal shell-style tool whose Safety requires a prompt,
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/sandbox"
)
func TestPermissionScope(t *testing.T) {
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
func planUpdateTurn(status string) zeroruntime.CollectedStream {
@@ -4,8 +4,8 @@ import (
"path/filepath"
"testing"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Twigpine/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/tools"
)
// escalatingEngine allows unsandboxed escalation: no denied reads, so
+1 -1
View File
@@ -1,7 +1,7 @@
package agent
import (
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/sandbox"
)
// profileController observes per-turn run signals and decides at most one
+4 -4
View File
@@ -5,10 +5,10 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/trace"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/trace"
"github.com/Twigpine/zero/internal/zeroruntime"
)
func TestProfileControllerNilPolicyIsNoOp(t *testing.T) {
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// These ceilings are a deliberate ratchet on Zero's fixed per-turn overhead — the
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"sort"
"strings"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// promptSubstrings are the cacheable components of the prompt a run sends to a
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// TestComputePrefixFingerprintStableAcrossCalls asserts the headline property
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"fmt"
"strings"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// Stale tool-output pruning reclaims context at ZERO token/latency cost before
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// bigToolResult builds a tool-result message with approximately tokens worth of
+3 -3
View File
@@ -7,9 +7,9 @@ import (
"strings"
"time"
"github.com/Gitlawb/zero/internal/errhint"
"github.com/Gitlawb/zero/internal/trace"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/errhint"
"github.com/Twigpine/zero/internal/trace"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// Mid-stream reconnect: a long autonomous task (a big refactor, a swarm member,
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"testing"
"time"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// flakyProvider fails the first failBefore connect attempts with failErr, then
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
func TestRequestPermissionsTurnGrantAllowsLaterToolAndCleansUp(t *testing.T) {
+4 -4
View File
@@ -7,10 +7,10 @@ import (
"path/filepath"
"strings"
"github.com/Gitlawb/zero/internal/lsp"
"github.com/Gitlawb/zero/internal/redaction"
"github.com/Gitlawb/zero/internal/trace"
"github.com/Gitlawb/zero/internal/verify"
"github.com/Twigpine/zero/internal/lsp"
"github.com/Twigpine/zero/internal/redaction"
"github.com/Twigpine/zero/internal/trace"
"github.com/Twigpine/zero/internal/verify"
)
// defaultSelfCorrectMaxAttempts bounds how many corrective rounds a single run
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/lsp"
"github.com/Gitlawb/zero/internal/verify"
"github.com/Twigpine/zero/internal/lsp"
"github.com/Twigpine/zero/internal/verify"
)
type fakeVerifier struct {
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"sync/atomic"
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// stallProvider connects successfully (HTTP 200) but the stream emits a stall/idle
+4 -4
View File
@@ -9,10 +9,10 @@ import (
"strings"
"unicode/utf8"
"github.com/Gitlawb/zero/internal/config"
"github.com/Gitlawb/zero/internal/repomap"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/workspaceseed"
"github.com/Twigpine/zero/internal/config"
"github.com/Twigpine/zero/internal/repomap"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/workspaceseed"
)
// coreSystemPrompt is the de-branded coding-craft instruction set: identity,
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/sandbox"
)
// TestMain points userConfigDirForPrompt at an empty, package-wide temp
+3 -3
View File
@@ -10,9 +10,9 @@ import (
"strings"
"unicode/utf8"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/trace"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/trace"
"github.com/Twigpine/zero/internal/zeroruntime"
)
type taskStatus string
+3 -3
View File
@@ -5,9 +5,9 @@ import (
"reflect"
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/trace"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/trace"
"github.com/Twigpine/zero/internal/zeroruntime"
)
func TestTaskStateReplayIsDeterministic(t *testing.T) {
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
func TestNonVisionImageNoticeQualifiesBudgetRecovery(t *testing.T) {
+1 -1
View File
@@ -3,7 +3,7 @@ package agent
import (
"context"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// sessionProvider adapts a TurnSession back to the Provider interface so the
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"strings"
"testing"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/zeroruntime"
)
// fakeTurnSession wraps an inner provider for streaming while counting the
+7 -7
View File
@@ -3,13 +3,13 @@ package agent
import (
"context"
"github.com/Gitlawb/zero/internal/execution"
"github.com/Gitlawb/zero/internal/hooks"
"github.com/Gitlawb/zero/internal/sandbox"
"github.com/Gitlawb/zero/internal/streamjson"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/trace"
"github.com/Gitlawb/zero/internal/zeroruntime"
"github.com/Twigpine/zero/internal/execution"
"github.com/Twigpine/zero/internal/hooks"
"github.com/Twigpine/zero/internal/sandbox"
"github.com/Twigpine/zero/internal/streamjson"
"github.com/Twigpine/zero/internal/tools"
"github.com/Twigpine/zero/internal/trace"
"github.com/Twigpine/zero/internal/zeroruntime"
)
type Message = zeroruntime.Message
@@ -6,7 +6,7 @@ import (
"io"
"os"
"github.com/Gitlawb/zero-fixture/internal/release"
"github.com/Twigpine/zero-fixture/internal/release"
)
func smokeTarget(alreadyBuilt bool) string {
+1 -1
View File
@@ -1,3 +1,3 @@
module github.com/Gitlawb/zero-fixture
module github.com/Twigpine/zero-fixture
go 1.22
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"strings"
"time"
"github.com/Gitlawb/zero/internal/repoinfo"
"github.com/Twigpine/zero/internal/repoinfo"
)
// BuildPrompt returns the bootstrap prompt for `zero init` / `/init`. It runs
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"testing"
"time"
"github.com/Gitlawb/zero/internal/repoinfo"
"github.com/Twigpine/zero/internal/repoinfo"
)
func intPtr(n int) *int { return &n }
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"syscall"
"time"
"github.com/Gitlawb/zero/internal/execution"
"github.com/Twigpine/zero/internal/execution"
)
// terminationGracePeriod is how long a process has to exit after SIGTERM before
Loaded 100 of 691 files, more files were not shown because too many files have changed in this diff. Show more