mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-11 05:01:36 -10:00
Brings gnanam1990's 195-commit zeromaxing/orchestrate branch (head6916f853) onto mainb32f7ce4, with every original commit unchanged. All conflict resolution lives in this merge commit. Resolutions that combine both sides: - module path: Gitlawb/zero imports rewritten to Twigpine/zero - config: project maxTurns stays tighten-only and records maxTurnsSet only when it applies; the partial (no-provider) config keeps Profiles - config writer: SetKeepFinishedAgents takes main's config file lock - exec: execTurnBudget applies before the zeromaxing refusal/delta notice - exec_command: tty-gated interactive guard (main) plus the sandbox-selected shell preflight (PR); the Windows description guidance is the <environment> pointer, kept paired with hostExecCommandShellGuidance for the token ratchet - specialist: LifecycleHooks (main) alongside the PR's executor fields; main and the PR both fixed resume --model, so it is emitted once - cli: registerSpecialistTools keeps the PR's signature and wires main's lifecycle hooks bridge into the executor - tui: main's file-view refresh, run-details clicks, notify fields and locked model selection, combined with the PR's plan messages, chip, background-plan /new guard and spinner-tick ordering - resume model tests: both add/add files kept (resume_parent_model_test.go) Co-Authored-By: Claude Opus 5.5 <[email protected]> Rebased integration onto b32f7ce4; original #829 commits and integration resolutions retained.
89 lines
3.0 KiB
Go
89 lines
3.0 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/Twigpine/zero/internal/agent"
|
|
)
|
|
|
|
// THE CHILD MUST PUBLISH WHAT MAKES ITS TURN PRICEABLE.
|
|
//
|
|
// This writer is the only channel a parent has. Its session record already keeps
|
|
// cachedInputTokens / cacheWriteTokens / reasoningTokens so a turn can be costed
|
|
// exactly; the stream carried prompt/completion/total alone, so every sub-agent
|
|
// turn rolled up to its parent was priced as if nothing had been cached.
|
|
//
|
|
// A measured plan task had 49,280 of 49,894 prompt tokens served from cache —
|
|
// 98.8%. Plan tasks are the ideal cache case: one large stable prompt, re-sent
|
|
// every turn.
|
|
func TestTheChildPublishesCacheAndReasoningTokensOnTheUsageStream(t *testing.T) {
|
|
var stdout, stderr bytes.Buffer
|
|
writer := execEventWriter{
|
|
stdout: &stdout,
|
|
stderr: &stderr,
|
|
format: execOutputStreamJSON,
|
|
runID: "run_usage",
|
|
streamedText: &strings.Builder{},
|
|
}
|
|
writer.usage(agent.Usage{
|
|
PromptTokens: 30000,
|
|
CompletionTokens: 500,
|
|
CachedInputTokens: 29000,
|
|
CacheWriteTokens: 1000,
|
|
ReasoningTokens: 120,
|
|
})
|
|
if writer.err != nil {
|
|
t.Fatalf("usage: %v", writer.err)
|
|
}
|
|
|
|
var payload map[string]any
|
|
if err := json.Unmarshal([]byte(strings.TrimSpace(stdout.String())), &payload); err != nil {
|
|
t.Fatalf("decode stream event %q: %v", stdout.String(), err)
|
|
}
|
|
for key, want := range map[string]float64{
|
|
"cachedInputTokens": 29000, "cacheWriteTokens": 1000, "reasoningTokens": 120,
|
|
} {
|
|
got, ok := payload[key]
|
|
if !ok {
|
|
t.Errorf("the stream omits %q, so a parent prices this turn as uncached: %v", key, payload)
|
|
continue
|
|
}
|
|
if number, ok := got.(float64); !ok || number != want {
|
|
t.Errorf("%s: want %v, got %v", key, want, got)
|
|
}
|
|
}
|
|
// The three original fields must be exactly as before.
|
|
for key, want := range map[string]float64{
|
|
"promptTokens": 30000, "completionTokens": 500, "totalTokens": 30500,
|
|
} {
|
|
if got, ok := payload[key].(float64); !ok || got != want {
|
|
t.Errorf("%s changed: want %v, got %v", key, want, payload[key])
|
|
}
|
|
}
|
|
}
|
|
|
|
// NON-ZERO ONLY, exactly like usage.EventUsagePayload. A provider reporting no
|
|
// cache must produce the same three-field event it always did, or every existing
|
|
// reader of this stream sees a shape it has never seen.
|
|
func TestAProviderWithNoCacheStillEmitsTheOriginalUsageShape(t *testing.T) {
|
|
var stdout, stderr bytes.Buffer
|
|
writer := execEventWriter{
|
|
stdout: &stdout, stderr: &stderr, format: execOutputStreamJSON,
|
|
runID: "run_usage", streamedText: &strings.Builder{},
|
|
}
|
|
writer.usage(agent.Usage{PromptTokens: 100, CompletionTokens: 10})
|
|
|
|
var payload map[string]any
|
|
if err := json.Unmarshal([]byte(strings.TrimSpace(stdout.String())), &payload); err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
for _, key := range []string{"cachedInputTokens", "cacheWriteTokens", "reasoningTokens"} {
|
|
if _, present := payload[key]; present {
|
|
t.Errorf("%q was emitted as a zero, widening the event for every provider that reports no cache", key)
|
|
}
|
|
}
|
|
}
|