From 2b5b52af855373c6edd57bdf1bda5ed0cdb7d1df Mon Sep 17 00:00:00 2001 From: Jim Wordelman Date: Wed, 22 Apr 2026 14:05:02 -0700 Subject: [PATCH 01/85] fix: rebuild bare resume command to include schema defaults (#799) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pool-agent sessions resumed through the control-dispatcher path dropped --dangerously-skip-permissions, --settings, and other schema-default launch flags. The previous shouldPreserveStoredRuntimeCommand treated a stored command that exactly equalled the bare provider binary ("claude") as complete and preserved it, skipping BuildProviderLaunchCommand. The resulting claude --resume ran without the unrestricted permission mode flag and wedged on interactive tool-call prompts. Rebuild when the stored command is only the bare binary while still preserving longer stored commands that already carry flags (so existing paths that persist fully-resolved command strings continue to win). The API path (internal/api/session_runtime.go) and the CLI worker-boundary path (cmd/gc/worker_handle.go) carry parallel copies of the helper — update both and add regression tests covering each. Co-Authored-By: Claude Opus 4.7 (1M context) --- cmd/gc/worker_handle.go | 11 ++++- cmd/gc/worker_handle_test.go | 56 +++++++++++++++++++++++ internal/api/handler_session_chat_test.go | 47 +++++++++++++++++++ internal/api/session_runtime.go | 11 ++++- 4 files changed, 123 insertions(+), 2 deletions(-) diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index 33072d9c2c..2366790437 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -366,7 +366,16 @@ func shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand string) b if resolvedCommand == "" { return true } - return storedCommand == resolvedCommand || strings.HasPrefix(storedCommand, resolvedCommand+" ") + // A bare stored command (just the provider binary) lacks schema + // defaults like --dangerously-skip-permissions and the --settings + // path. Rebuild from the current config instead of preserving it. + // See #799: pool-agent sessions resumed through the control- + // dispatcher path wedged on interactive permission prompts because + // the bare stored command was preserved without re-injecting flags. + if storedCommand == resolvedCommand { + return false + } + return strings.HasPrefix(storedCommand, resolvedCommand+" ") } func resolveWorkerRuntimeWithConfig(cfg *config.City, info session.Info, sessionKind string) *config.ResolvedProvider { diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index d821c06a54..72a4f03e04 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -142,6 +142,62 @@ func TestResolvedWorkerRuntimeWithConfigUsesProviderLaunchCommand(t *testing.T) } } +// TestResolvedWorkerRuntimeResumesPoolSessionPreservesLaunchFlags is a +// regression test for gastownhall/gascity#799: a pool-agent session +// resumed through the control-dispatcher path must reconstruct the full +// launch command (--dangerously-skip-permissions, --settings, schema +// defaults) even when the persisted session command is the bare +// provider name. The pre-fix path dropped those flags and caused pool +// workers resumed via `claude --resume ` to wedge on interactive +// permission prompts. +func TestResolvedWorkerRuntimeResumesPoolSessionPreservesLaunchFlags(t *testing.T) { + cityDir := t.TempDir() + gcDir := filepath.Join(cityDir, ".gc") + if err := os.MkdirAll(gcDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(gcDir, "settings.json"), []byte(`{"hooks":{}}`), 0o644); err != nil { + t.Fatal(err) + } + + claude := config.BuiltinProviders()["claude"] + maxActive := 3 + cfg := &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Agents: []config.Agent{{ + Name: "perspective_planner", + Provider: "claude", + MaxActiveSessions: &maxActive, + }}, + Providers: map[string]config.ProviderSpec{ + "claude": claude, + }, + } + + // Simulate a pool-instance session bead whose persisted command is + // the bare provider name — the shape produced before the April 2026 + // worker-boundary refactor when the API created the bead with + // sessionCreateAgentCommand(resolved) before the reconciler synced + // the full tp.Command. + runtimeCfg := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + Template: "perspective_planner", + Command: "claude", + WorkDir: cityDir, + }, "") + if runtimeCfg == nil { + t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") + } + if !strings.Contains(runtimeCfg.Command, "--dangerously-skip-permissions") { + t.Fatalf("resumed pool Command = %q, want --dangerously-skip-permissions", runtimeCfg.Command) + } + if !strings.Contains(runtimeCfg.Command, "--effort max") { + t.Fatalf("resumed pool Command = %q, want --effort max default", runtimeCfg.Command) + } + if !strings.Contains(runtimeCfg.Command, "--settings") { + t.Fatalf("resumed pool Command = %q, want --settings arg", runtimeCfg.Command) + } +} + func TestWorkerHandleForSessionWithConfigUsesResolvedProviderOnResume(t *testing.T) { cityDir := t.TempDir() writePhase0InterfaceCity(t, cityDir, `[workspace] diff --git a/internal/api/handler_session_chat_test.go b/internal/api/handler_session_chat_test.go index bf1f1973a4..de87e00b70 100644 --- a/internal/api/handler_session_chat_test.go +++ b/internal/api/handler_session_chat_test.go @@ -1,6 +1,7 @@ package api import ( + "strings" "testing" "github.com/gastownhall/gascity/internal/config" @@ -111,3 +112,49 @@ func TestBuildSessionResumePreservesStoredResolvedCommand(t *testing.T) { t.Fatalf("resume command = %q, want %q", got, want) } } + +// TestBuildSessionResumeRebuildsBareStoredCommandForPoolClaudeAgent is a +// regression test for gastownhall/gascity#799: when a pool-agent session +// resumed through the control-dispatcher path has only the bare +// provider binary ("claude") as its stored command, the API must +// re-inject schema defaults (--dangerously-skip-permissions) and the +// provider-owned --settings path from the current resolved config. +// Before the fix, the bare stored command was preserved as-is and pool +// workers wedged on interactive permission prompts on resume. +func TestBuildSessionResumeRebuildsBareStoredCommandForPoolClaudeAgent(t *testing.T) { + fs := newSessionFakeState(t) + claude := config.BuiltinProviders()["claude"] + maxActive := 3 + fs.cfg = &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Agents: []config.Agent{ + { + Name: "perspective_planner", + Provider: "claude", + MaxActiveSessions: &maxActive, + }, + }, + Providers: map[string]config.ProviderSpec{ + "claude": claude, + }, + } + + srv := New(fs) + info := session.Info{ + ID: "gc-1", + Template: "perspective_planner", + Command: "claude", + Provider: "claude", + WorkDir: fs.cityPath, + SessionKey: "abc-123", + ResumeFlag: "--resume", + } + + cmd, _ := srv.buildSessionResume(info) + if !strings.Contains(cmd, "--dangerously-skip-permissions") { + t.Fatalf("resume command missing default args:\n got: %s", cmd) + } + if !strings.Contains(cmd, "--resume abc-123") { + t.Fatalf("resume command missing resume flag:\n got: %s", cmd) + } +} diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index 3be2da5b9c..7fd64520fd 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -150,7 +150,16 @@ func shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand string) b if resolvedCommand == "" { return true } - return storedCommand == resolvedCommand || strings.HasPrefix(storedCommand, resolvedCommand+" ") + // A bare stored command (just the provider binary) lacks schema + // defaults like --dangerously-skip-permissions and the --settings + // path. Rebuild from the current config instead of preserving it. + // See #799: pool-agent sessions resumed through the control- + // dispatcher path wedged on interactive permission prompts because + // the bare stored command was preserved without re-injecting flags. + if storedCommand == resolvedCommand { + return false + } + return strings.HasPrefix(storedCommand, resolvedCommand+" ") } func (s *Server) resolveWorkerSessionRuntime(info session.Info, _ string) (*worker.ResolvedRuntime, error) { From 53532e246e23c8957f309881d088fc1184a8fca5 Mon Sep 17 00:00:00 2001 From: m1ddl3w4r3 Date: Wed, 22 Apr 2026 19:53:48 -0500 Subject: [PATCH 02/85] fix(profiles): add readiness tuning to cursor provider Pool-managed and ephemeral cursor-agent sessions were timing out during create because the cursor profile had no ReadyPromptPrefix or ReadyDelayMs, so the reconciler could not detect when the TUI was ready to accept piped-in prompts. Named sessions worked because their prompt is delivered as an exec arg, bypassing readiness detection. cursor-agent 2026.04+ renders its composer input with a U+2192 (right arrow) prefix after roughly 5-10s of startup (workspace-trust gate + statsig + model load). Match the pattern used by the claude/codex/ gemini/copilot profiles: ReadyPromptPrefix: "\u2192 " ReadyDelayMs: 10000 Verified on macOS arm64 with cursor-agent 2026.04.17: ephemeral cursor workers now claim beads, execute mol-do-work, write the requested files, and run gc runtime drain-ack cleanly instead of cycling stopped -> creating -> stopped against a deadline_exceeded outcome. Made-with: Cursor --- internal/worker/builtin/profiles.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/internal/worker/builtin/profiles.go b/internal/worker/builtin/profiles.go index b3dbfd5821..1a71703f37 100644 --- a/internal/worker/builtin/profiles.go +++ b/internal/worker/builtin/profiles.go @@ -260,13 +260,15 @@ var builtinProviderSpecs = map[string]BuiltinProviderSpec{ }, }, "cursor": { - DisplayName: "Cursor Agent", - Command: "cursor-agent", - Args: []string{"-f"}, - PromptMode: "arg", - ProcessNames: []string{"cursor-agent"}, - SupportsHooks: true, - InstructionsFile: "AGENTS.md", + DisplayName: "Cursor Agent", + Command: "cursor-agent", + Args: []string{"-f"}, + PromptMode: "arg", + ReadyPromptPrefix: "\u2192 ", + ReadyDelayMs: 10000, + ProcessNames: []string{"cursor-agent"}, + SupportsHooks: true, + InstructionsFile: "AGENTS.md", }, "copilot": { DisplayName: "GitHub Copilot", From e78cf8d182f473cd3700f63ca7f58ef7a8acbbaa Mon Sep 17 00:00:00 2001 From: Casey Boyle Date: Thu, 23 Apr 2026 10:00:56 -0500 Subject: [PATCH 03/85] fix(sling): refuse dispatch against bead IDs not in the store MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A bead-ID-shaped argument that doesn't resolve in the local store would previously flow through `gc sling` unchecked — the existing `beadExistsInStore` lookup fires only when the arg *doesn't* look like a bead ID (the inline-text auto-create path). When the arg DOES look like a bead ID, sling assumed it was a reference and dispatched. That assumption is fine for humans who copy-paste IDs, but breaks down in two cases: 1. LLM-driven dispatch that emits well-formed-looking IDs it invented (hallucinated IDs, unsubstituted template placeholders). Workers receive assignments to beads that never existed. 2. Plain human typos. `sc-qq3q0` for `sc-qq3a0` gets the same silent success — dispatch proceeds to a dead reference and workers stall. ## Historical context `looksLikeBeadID` was added in 777c9edf (Phase 3a: name-based session addressing) purely as a routing heuristic: decide whether a string is a bead ID or a session-template name. It was never validation — `resolveSessionID`, its original caller, errors out when the downstream lookup fails. efa9f337 (inline sling) reused `looksLikeBeadID` to disambiguate inline text from bead IDs. The resulting condition `!looksLikeBeadID && !beadExistsInStore` only fires the store lookup for text-shaped args; bead-shaped args went unchecked. This PR closes that gap. ## What changes Add an explicit guard after the inline-create block: if the argument matches `looksLikeBeadID` shape AND does not resolve in the store AND `--force` is not set, sling errors with a clear message pointing at `--force` as the escape hatch. - Formula mode (`--formula` / `-f`) is exempt, since the argument is a formula name, not a bead ID. - `--force` bypasses the check, consistent with the flag's existing role as the escape hatch for cross-rig routing and suspended-agent sling. Semantically: "I know the bead exists in a view the local store hasn't synced yet — dispatch anyway." - Error message names the store root so operators can tell *which* store view disagrees with the caller's assumption. Updated `--force` help text to reflect the new behavior. ## Tests - `TestCmdSlingRefusesMissingBead` — bead-shaped arg with no matching bead → exit non-zero with a "not found" message that mentions `--force`. - `TestCmdSlingForceBypassesMissingBeadCheck` — same setup with `--force` → the guard message does not appear in stderr. - `TestCmdSlingAcceptsExistingBead` — seeded real bead + matching prefix → guard does not fire. Co-Authored-By: Claude Opus 4.7 (1M context) --- cmd/gc/cmd_sling.go | 18 +++++- cmd/gc/cmd_sling_test.go | 127 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 1 deletion(-) diff --git a/cmd/gc/cmd_sling.go b/cmd/gc/cmd_sling.go index c8ee1bac3e..222f4123e4 100644 --- a/cmd/gc/cmd_sling.go +++ b/cmd/gc/cmd_sling.go @@ -103,7 +103,7 @@ Examples: } cmd.Flags().BoolVarP(&formula, "formula", "f", false, "treat argument as formula name") cmd.Flags().BoolVar(&nudge, "nudge", false, "nudge target after routing") - cmd.Flags().BoolVar(&force, "force", false, "suppress warnings and allow cross-rig routing") + cmd.Flags().BoolVar(&force, "force", false, "suppress warnings, allow cross-rig routing, and dispatch even if the bead does not resolve in the local store") cmd.Flags().StringVarP(&title, "title", "t", "", "wisp root bead title (with --formula or --on)") cmd.Flags().StringArrayVar(&vars, "var", nil, "variable substitution for formula (key=value, repeatable)") cmd.Flags().StringVar(&merge, "merge", "", "merge strategy: direct, mr, or local") @@ -271,6 +271,22 @@ func cmdSling(args []string, isFormula, doNudge, force bool, title string, vars beadOrFormula = created.ID } + // Refuse to dispatch against a bead-shaped argument that doesn't + // resolve in the store. `looksLikeBeadID` was originally a routing + // heuristic (bead ID vs. session-template name) introduced for + // name-based session addressing, and the inline-text block above + // reused it for text-vs-ID disambiguation — neither ever validated + // existence. Without a check here a typo'd or fabricated bead ID + // silently flows through and strands workers on a dead reference. + // `resolveSessionID` already errors when its template-shaped + // argument doesn't resolve; this brings sling to parity. + // --force bypasses for timing-race cases where the bead exists in a + // remote store view not yet synced locally. + if !isFormula && looksLikeBeadID(beadOrFormula) && !beadExistsInStore(store, beadOrFormula) && !force { + fmt.Fprintf(stderr, "gc sling: bead %q not found in store %s\n use --force to dispatch anyway (e.g. bead exists in a remote view not yet synced locally)\n", beadOrFormula, storeRef) //nolint:errcheck // best-effort stderr + return 1 + } + opts := slingOpts{ Target: a, BeadOrFormula: beadOrFormula, diff --git a/cmd/gc/cmd_sling_test.go b/cmd/gc/cmd_sling_test.go index dc03a0d450..b1ca3c5f40 100644 --- a/cmd/gc/cmd_sling_test.go +++ b/cmd/gc/cmd_sling_test.go @@ -953,6 +953,133 @@ dir = "frontend" } } +// setupCmdSlingBeadExistsFixture writes a minimal city.toml with a single +// rig + worker agent and positions the test CWD inside the city. Used by +// the bead-existence tests below. Returns the city directory. +func setupCmdSlingBeadExistsFixture(t *testing.T) string { + t.Helper() + configureIsolatedRuntimeEnv(t) + t.Setenv("GC_BEADS", "file") + + cityDir := t.TempDir() + rigDir := filepath.Join(cityDir, "frontend") + if err := os.MkdirAll(rigDir, 0o755); err != nil { + t.Fatalf("MkdirAll(rig): %v", err) + } + if err := ensureScopedFileStoreLayout(cityDir); err != nil { + t.Fatalf("ensureScopedFileStoreLayout: %v", err) + } + if err := ensurePersistedScopeLocalFileStore(cityDir); err != nil { + t.Fatalf("ensurePersistedScopeLocalFileStore(city): %v", err) + } + if err := ensurePersistedScopeLocalFileStore(rigDir); err != nil { + t.Fatalf("ensurePersistedScopeLocalFileStore(rig): %v", err) + } + cityToml := `[workspace] +name = "demo" + +[[rigs]] +name = "frontend" +path = "frontend" +prefix = "FE" + +[[agent]] +name = "worker" +dir = "frontend" +` + if err := os.WriteFile(filepath.Join(cityDir, "city.toml"), []byte(cityToml), 0o644); err != nil { + t.Fatalf("WriteFile(city.toml): %v", err) + } + t.Chdir(cityDir) + return cityDir +} + +func TestCmdSlingRefusesMissingBead(t *testing.T) { + // A bead-ID-shaped argument that doesn't resolve in the store must + // cause sling to error out — otherwise a fabricated / typo'd ID + // would flow through and strand workers on a dead reference. + setupCmdSlingBeadExistsFixture(t) + + var stdout, stderr bytes.Buffer + code := cmdSling( + []string{"frontend/worker", "FE-ghost1"}, + false, false, false, // isFormula, doNudge, force=false + "", nil, "", + true, false, "", + false, false, false, + "", "", + &stdout, &stderr, + ) + if code == 0 { + t.Fatalf("cmdSling returned 0, want non-zero; stderr: %s", stderr.String()) + } + got := stderr.String() + if !strings.Contains(got, "FE-ghost1") { + t.Errorf("stderr missing bead ID; got: %s", got) + } + if !strings.Contains(got, "not found") { + t.Errorf("stderr missing 'not found' phrasing; got: %s", got) + } + if !strings.Contains(got, "--force") { + t.Errorf("stderr should mention --force as the escape hatch; got: %s", got) + } +} + +func TestCmdSlingForceBypassesMissingBeadCheck(t *testing.T) { + // --force must bypass the bead-existence check. The call may still + // fail further downstream (we don't assert a success exit here), but + // stderr must not contain the "not found" guard message. + setupCmdSlingBeadExistsFixture(t) + + var stdout, stderr bytes.Buffer + _ = cmdSling( + []string{"frontend/worker", "FE-ghost1"}, + false, false, true, // force=true + "", nil, "", + true, false, "", + false, false, false, + "", "", + &stdout, &stderr, + ) + got := stderr.String() + if strings.Contains(got, "not found in store") { + t.Errorf("--force did not bypass bead-existence check; stderr: %s", got) + } +} + +func TestCmdSlingAcceptsExistingBead(t *testing.T) { + // When a bead-ID-shaped argument IS present in the store, the new + // existence check must not fire. This test only asserts the check + // does not trip — it doesn't assert sling completes successfully, + // since downstream routing has its own gates (cross-rig, etc.) + // that are out of scope for this change. + cityDir := setupCmdSlingBeadExistsFixture(t) + rigDir := filepath.Join(cityDir, "frontend") + + rigStore, err := openStoreAtForCity(rigDir, cityDir) + if err != nil { + t.Fatalf("openStoreAtForCity(rig): %v", err) + } + seeded, err := rigStore.Create(beads.Bead{Title: "real work", Type: "task"}) + if err != nil { + t.Fatalf("seeding bead: %v", err) + } + + var stdout, stderr bytes.Buffer + _ = cmdSling( + []string{"frontend/worker", seeded.ID}, + false, false, false, // force=false; existence check should pass naturally + "", nil, "", + true, false, "", + false, false, false, + "", "", + &stdout, &stderr, + ) + if strings.Contains(stderr.String(), "not found in store") { + t.Errorf("existence check incorrectly tripped on a real bead; stderr: %s", stderr.String()) + } +} + func TestSlingStoreEnvUsesRigBdRuntimeForMixedProviderRig(t *testing.T) { cityDir := t.TempDir() wantPort := strconv.Itoa(writeReachableManagedDoltState(t, cityDir)) From 85a3192e5c5b61ce0df0aaaef71008aa7584679d Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Thu, 23 Apr 2026 20:49:38 +0000 Subject: [PATCH 04/85] Fix sling missing bead validation --- cmd/gc/cmd_sling.go | 183 ++++---- cmd/gc/cmd_sling_test.go | 628 +++++++++++++++++++++++++- docs/reference/cli.md | 2 +- docs/schema/openapi.json | 14 +- docs/schema/openapi.txt | 14 +- internal/api/genclient/client_gen.go | 3 + internal/api/handler_sling.go | 80 +++- internal/api/handler_sling_test.go | 322 +++++++++++++ internal/api/huma_handlers_sling.go | 22 +- internal/api/huma_types_sling.go | 1 + internal/api/openapi.json | 14 +- internal/api/openapi_problem_types.go | 45 ++ internal/api/supervisor.go | 1 + internal/sling/sling.go | 166 ++++++- internal/sling/sling_core.go | 155 +++++-- internal/sling/sling_test.go | 428 +++++++++++++++++- 16 files changed, 1933 insertions(+), 145 deletions(-) create mode 100644 internal/api/openapi_problem_types.go diff --git a/cmd/gc/cmd_sling.go b/cmd/gc/cmd_sling.go index 222f4123e4..e737ea0cb7 100644 --- a/cmd/gc/cmd_sling.go +++ b/cmd/gc/cmd_sling.go @@ -103,7 +103,7 @@ Examples: } cmd.Flags().BoolVarP(&formula, "formula", "f", false, "treat argument as formula name") cmd.Flags().BoolVar(&nudge, "nudge", false, "nudge target after routing") - cmd.Flags().BoolVar(&force, "force", false, "suppress warnings, allow cross-rig routing, and dispatch even if the bead does not resolve in the local store") + cmd.Flags().BoolVar(&force, "force", false, "suppress warnings, allow cross-rig routing, allow graph workflow replacement, and for direct bead routes dispatch even if the bead does not resolve in the local store") cmd.Flags().StringVarP(&title, "title", "t", "", "wisp root bead title (with --formula or --on)") cmd.Flags().StringArrayVar(&vars, "var", nil, "variable substitution for formula (key=value, repeatable)") cmd.Flags().StringVar(&merge, "merge", "", "merge strategy: direct, mr, or local") @@ -195,6 +195,7 @@ func cmdSling(args []string, isFormula, doNudge, force bool, title string, vars } emitLoadCityConfigWarnings(stderr, prov) applyFeatureFlags(cfg) + cityName := loadedCityName(cfg, cityPath) var target, beadOrFormula string switch { @@ -211,7 +212,7 @@ func cmdSling(args []string, isFormula, doNudge, force bool, title string, vars fmt.Fprintf(stderr, "gc sling: --formula requires explicit target\n") //nolint:errcheck // best-effort stderr return 1 } - if !looksLikeBeadID(beadOrFormula) { + if !canInferSlingDefaultTargetFromBead(cfg, beadOrFormula) { fmt.Fprintf(stderr, "gc sling: inline text requires explicit target\n usage: gc sling %q\n", beadOrFormula) //nolint:errcheck // best-effort stderr return 1 } @@ -245,12 +246,10 @@ func cmdSling(args []string, isFormula, doNudge, force bool, title string, vars } sp := newSessionProvider() - cityName := loadedCityName(cfg, cityPath) - storeDir := resolveSlingStoreRoot(cfg, cityPath, beadOrFormula, a) - store, err := openStoreAtForCity(storeDir, cityPath) + storeDir, store, err := openSlingStoreForSource(cfg, cityPath, beadOrFormula, a) if err != nil { - fmt.Fprintf(stderr, "gc sling: opening store %s: %v\n", storeDir, err) //nolint:errcheck // best-effort stderr + fmt.Fprintf(stderr, "gc sling: %v\n", err) //nolint:errcheck // best-effort stderr return 1 } storeRef := workflowStoreRefForDir(storeDir, cityPath, cityName, cfg) @@ -258,33 +257,20 @@ func cmdSling(args []string, isFormula, doNudge, force bool, title string, vars // Inline text mode: if the argument doesn't look like a bead ID // (and we're not in formula mode), create a task bead from the text. - // Skip during dry-run to avoid side effects. - // Also check if the bead exists in the store — hierarchical IDs like - // "Prefix-abc.1" have dots that looksLikeBeadID doesn't recognize. - if !isFormula && !dryRun && !looksLikeBeadID(beadOrFormula) && !beadExistsInStore(store, beadOrFormula) { - created, err := store.Create(beads.Bead{Title: beadOrFormula, Description: stdinDescription, Type: "task"}) - if err != nil { - fmt.Fprintf(stderr, "gc sling: creating bead: %v\n", err) //nolint:errcheck // best-effort stderr - return 1 + // During dry-run, mark the text as preview-only instead of creating it. + inlineText := false + if !isFormula { + createInlineBead, previewInlineText := resolveInlineBeadAction(cfg, beadOrFormula, dryRun) + inlineText = previewInlineText + if createInlineBead { + created, err := store.Create(beads.Bead{Title: beadOrFormula, Description: stdinDescription, Type: "task"}) + if err != nil { + fmt.Fprintf(stderr, "gc sling: creating bead: %v\n", err) //nolint:errcheck // best-effort stderr + return 1 + } + fmt.Fprintf(stdout, "Created %s — %q\n", created.ID, beadOrFormula) //nolint:errcheck // best-effort stdout + beadOrFormula = created.ID } - fmt.Fprintf(stdout, "Created %s — %q\n", created.ID, beadOrFormula) //nolint:errcheck // best-effort stdout - beadOrFormula = created.ID - } - - // Refuse to dispatch against a bead-shaped argument that doesn't - // resolve in the store. `looksLikeBeadID` was originally a routing - // heuristic (bead ID vs. session-template name) introduced for - // name-based session addressing, and the inline-text block above - // reused it for text-vs-ID disambiguation — neither ever validated - // existence. Without a check here a typo'd or fabricated bead ID - // silently flows through and strands workers on a dead reference. - // `resolveSessionID` already errors when its template-shaped - // argument doesn't resolve; this brings sling to parity. - // --force bypasses for timing-race cases where the bead exists in a - // remote store view not yet synced locally. - if !isFormula && looksLikeBeadID(beadOrFormula) && !beadExistsInStore(store, beadOrFormula) && !force { - fmt.Fprintf(stderr, "gc sling: bead %q not found in store %s\n use --force to dispatch anyway (e.g. bead exists in a remote view not yet synced locally)\n", beadOrFormula, storeRef) //nolint:errcheck // best-effort stderr - return 1 } opts := slingOpts{ @@ -301,6 +287,7 @@ func cmdSling(args []string, isFormula, doNudge, force bool, title string, vars Nudge: doNudge, Force: force, DryRun: dryRun, + InlineText: inlineText, ScopeKind: scopeKind, ScopeRef: scopeRef, } @@ -405,7 +392,10 @@ func resolveSlingStoreRoot(cfg *config.City, cityPath, beadOrFormula string, a c // resolveStoreScopeRoot would silently alias them to the city // scope. Skip them so sling falls back to the agent's rig_dir or // the city store instead of operating on the wrong store. - if bp := beadPrefix(beadOrFormula); bp != "" { + if bp := beadPrefix(beadOrFormula); bp != "" && !looksLikeInlineText(cfg, beadOrFormula) { + if sling.IsHQPrefix(cfg, bp) { + return storeDir + } if rig, found := findRigByPrefix(cfg, bp); found && strings.TrimSpace(rig.Path) != "" { return resolveStoreScopeRoot(cityPath, rig.Path) } @@ -416,6 +406,19 @@ func resolveSlingStoreRoot(cfg *config.City, cityPath, beadOrFormula string, a c return storeDir } +func openSlingStoreForSource(cfg *config.City, cityPath, beadOrFormula string, a config.Agent) (string, beads.Store, error) { + storeDir := resolveSlingStoreRoot(cfg, cityPath, beadOrFormula, a) + store, err := openStoreAtForCity(storeDir, cityPath) + if err != nil { + return "", nil, fmt.Errorf("opening store %s: %w", storeDir, err) + } + return storeDir, store, nil +} + +func canInferSlingDefaultTargetFromBead(cfg *config.City, beadOrFormula string) bool { + return looksLikeBeadID(beadOrFormula) || looksLikeConfiguredBeadID(cfg, beadOrFormula) +} + // populateSlingDepsCallbacks fills in the interface fields on SlingDeps. func populateSlingDepsCallbacks(deps *slingDeps) { deps.Resolver = cliAgentResolver{} @@ -656,6 +659,11 @@ func doSling(opts slingOpts, deps slingDeps, querier BeadQuerier, stdout, stderr printSourceWorkflowConflict(stderr, conflictErr, deps.StoreRef) return 3 } + var missingBeadErr *sling.MissingBeadError + if errors.As(err, &missingBeadErr) { + printMissingBeadError(stderr, missingBeadErr, missingBeadForceApplies(opts)) + return 1 + } fmt.Fprintln(stderr, err) //nolint:errcheck return 1 } @@ -690,8 +698,14 @@ func doSlingBatch(opts slingOpts, deps slingDeps, querier BeadChildQuerier, stdo result, err = sling.DoSlingBatch(opts, deps, querier) } else { result, err = sl.ExpandConvoy(context.Background(), opts.BeadOrFormula, opts.Target, sling.RouteOpts{ - Merge: opts.Merge, NoConvoy: opts.NoConvoy, Owned: opts.Owned, - Nudge: opts.Nudge, Force: opts.Force, SkipPoke: opts.SkipPoke, DryRun: opts.DryRun, + Merge: opts.Merge, + NoConvoy: opts.NoConvoy, + Owned: opts.Owned, + Nudge: opts.Nudge, + Force: opts.Force, + SkipPoke: opts.SkipPoke, + DryRun: opts.DryRun, + InlineText: opts.InlineText, }, querier) } // Print warnings before error check so they're visible on failure. @@ -712,6 +726,11 @@ func doSlingBatch(opts slingOpts, deps slingDeps, querier BeadChildQuerier, stdo if printed := printSourceWorkflowConflicts(stderr, err, deps.StoreRef); printed > 0 { return 3 } + var missingBeadErr *sling.MissingBeadError + if errors.As(err, &missingBeadErr) { + printMissingBeadError(stderr, missingBeadErr, missingBeadForceApplies(opts)) + return 1 + } // In batch mode, per-child FailReasons have already been rendered // by printBatchSlingResult above. The error returned from // DoSlingBatch is an errors.Join of a "N/M children failed" @@ -749,6 +768,19 @@ func doSlingBatch(opts slingOpts, deps slingDeps, querier BeadChildQuerier, stdo return 0 } +func printMissingBeadError(stderr io.Writer, err *sling.MissingBeadError, allowForce bool) { + fmt.Fprintln(stderr, err) //nolint:errcheck + if allowForce { + fmt.Fprintln(stderr, " verify the bead ID, or use --force if it exists in a remote view not yet synced locally") //nolint:errcheck + return + } + fmt.Fprintln(stderr, " verify the bead ID; --force does not bypass missing source validation for formula-backed routes") //nolint:errcheck +} + +func missingBeadForceApplies(opts sling.SlingOpts) bool { + return !opts.IsFormula && opts.OnFormula == "" && (opts.NoFormula || opts.Target.EffectiveDefaultSlingFormula() == "") +} + func sourceWorkflowCleanupCommand(sourceBeadID, storeRef string) string { args := []string{"gc workflow delete-source", sourceBeadID} if storeRef = strings.TrimSpace(storeRef); storeRef != "" { @@ -1689,63 +1721,54 @@ func isCustomSlingQuery(a config.Agent) bool { // one digit to distinguish base36 hashes from English words like // "hello-world". Strings with spaces or multiple dashes (like // "code-review") are treated as inline text for ad-hoc bead creation. -// beadExistsInStore returns true if the given ID resolves to a bead in the store. -// Used as a fallback when looksLikeBeadID returns false for valid hierarchical -// IDs (e.g., "ProjectWrenUnity-0fze.1"). -func beadExistsInStore(store beads.Store, id string) bool { - _, err := store.Get(id) - return err == nil -} - func looksLikeBeadID(s string) bool { - if strings.ContainsAny(s, " \t\n") { - return false - } - i := strings.Index(s, "-") - if i <= 0 || i == len(s)-1 { + _, baseSuffix, ok := sling.BeadIDParts(s) + if !ok || len(baseSuffix) > 8 { return false } - // Must have exactly one dash. - if strings.Count(s, "-") != 1 { - return false + return looksLikeBeadIDSuffix(baseSuffix) +} + +func looksLikeBeadIDSuffix(baseSuffix string) bool { + if len(baseSuffix) <= 4 { + return true } - prefix := s[:i] - for idx, c := range prefix { - if idx == 0 { - if ('A' > c || c > 'Z') && ('a' > c || c > 'z') { - return false - } - continue - } - if ('0' > c || c > '9') && ('a' > c || c > 'z') && ('A' > c || c > 'Z') { - return false + for _, c := range baseSuffix { + if '0' <= c && c <= '9' { + return true } } - suffix := s[i+1:] - // Strip hierarchical child suffix (e.g., ".1" from "0fze.1") for validation. - // Dots delimit parent-child hierarchy in bead IDs. - baseSuffix := suffix - if dot := strings.IndexByte(suffix, '.'); dot > 0 { - baseSuffix = suffix[:dot] + return false +} + +func shouldCreateInlineBead(cfg *config.City, beadOrFormula string) bool { + return looksLikeInlineText(cfg, beadOrFormula) +} + +func resolveInlineBeadAction(cfg *config.City, beadOrFormula string, dryRun bool) (createInlineBead, previewInlineText bool) { + if dryRun && looksLikeInlineText(cfg, beadOrFormula) { + return false, true } - for _, c := range baseSuffix { - if ('0' > c || c > '9') && ('a' > c || c > 'z') && ('A' > c || c > 'Z') { - return false - } + return shouldCreateInlineBead(cfg, beadOrFormula), false +} + +func looksLikeInlineText(cfg *config.City, beadOrFormula string) bool { + return !looksLikeBeadID(beadOrFormula) && !looksLikeConfiguredBeadID(cfg, beadOrFormula) +} + +func looksLikeConfiguredBeadID(cfg *config.City, s string) bool { + prefix, baseSuffix, ok := sling.BeadIDParts(s) + if !ok || len(baseSuffix) > 8 { + return false } - // Bead ID suffixes from bd are base36 hashes (3-8 chars) or - // sequential integers. Short suffixes (1-4 chars) are accepted - // unconditionally — no English words are that short after a dash. - // Longer suffixes (5-8 chars) must contain at least one digit to - // distinguish base36 hashes from English words like "world". - if len(baseSuffix) > 8 { + if cfg == nil { return false } - if len(baseSuffix) <= 4 { + if strings.EqualFold(prefix, config.EffectiveHQPrefix(cfg)) { return true } - for _, c := range baseSuffix { - if '0' <= c && c <= '9' { + for i := range cfg.Rigs { + if strings.EqualFold(prefix, cfg.Rigs[i].EffectivePrefix()) { return true } } diff --git a/cmd/gc/cmd_sling_test.go b/cmd/gc/cmd_sling_test.go index b1ca3c5f40..be2827dcd9 100644 --- a/cmd/gc/cmd_sling_test.go +++ b/cmd/gc/cmd_sling_test.go @@ -3,6 +3,7 @@ package main import ( "bytes" "context" + "encoding/json" "errors" "fmt" "io" @@ -43,6 +44,29 @@ func (s *selectiveErrStore) Create(b beads.Bead) (beads.Bead, error) { return s.Store.Create(b) } +type getErrStore struct { + beads.Store + err error +} + +func (s *getErrStore) Get(_ string) (beads.Bead, error) { + return beads.Bead{}, s.err +} + +func seededStore(ids ...string) beads.Store { + seed := make([]beads.Bead, 0, len(ids)) + for _, id := range ids { + seed = append(seed, beads.Bead{ + ID: id, + Title: id, + Type: "task", + Status: "open", + Metadata: map[string]string{}, + }) + } + return beads.NewMemStoreFrom(0, seed, nil) +} + // recordingStore wraps a store and overrides Get for bead injection. type recordingStore struct { beads.Store @@ -90,7 +114,10 @@ func (s *slingTestStore) Get(id string) (beads.Bead, error) { } b, ok := s.synthetic[id] if !ok { - return beads.Bead{}, err + if _, _, looksLikeBead := sling.BeadIDParts(id); !looksLikeBead { + return beads.Bead{}, err + } + return s.ensureSynthetic(id), nil } return b, nil } @@ -1025,6 +1052,379 @@ func TestCmdSlingRefusesMissingBead(t *testing.T) { } } +func TestPrintMissingBeadErrorFormulaBackedDoesNotSuggestForce(t *testing.T) { + var stderr bytes.Buffer + printMissingBeadError(&stderr, &sling.MissingBeadError{BeadID: "FE-ghost1", StoreRef: "rig:frontend"}, false) + + got := stderr.String() + if strings.Contains(got, "use --force") { + t.Fatalf("stderr = %q, should not suggest force for formula-backed missing source", got) + } + if !strings.Contains(got, "does not bypass missing source validation") { + t.Fatalf("stderr = %q, want formula-backed force diagnostic", got) + } +} + +func TestCmdSlingDryRunRefusesMissingBead(t *testing.T) { + setupCmdSlingBeadExistsFixture(t) + + var stdout, stderr bytes.Buffer + code := cmdSling( + []string{"frontend/worker", "FE-ghost1"}, + false, false, false, + "", nil, "", + true, false, "", + false, false, true, + "", "", + &stdout, &stderr, + ) + if code == 0 { + t.Fatalf("cmdSling dry-run returned 0, want non-zero; stdout=%s stderr=%s", stdout.String(), stderr.String()) + } + got := stderr.String() + if !strings.Contains(got, "FE-ghost1") { + t.Errorf("stderr missing bead ID; got: %s", got) + } + if !strings.Contains(got, "not found") { + t.Errorf("stderr missing missing-bead phrasing; got: %s", got) + } +} + +func TestCmdSlingDryRunPreviewsInlineText(t *testing.T) { + cityDir := setupCmdSlingBeadExistsFixture(t) + + var stdout, stderr bytes.Buffer + code := cmdSling( + []string{"frontend/worker", "write docs"}, + false, false, false, + "", nil, "", + true, false, "", + false, false, true, + "", "", + &stdout, &stderr, + ) + if code != 0 { + t.Fatalf("cmdSling dry-run returned %d, want 0; stdout=%s stderr=%s", code, stdout.String(), stderr.String()) + } + if strings.Contains(stderr.String(), "not found") { + t.Fatalf("stderr = %s, want no missing-bead diagnostic", stderr.String()) + } + out := stdout.String() + if !strings.Contains(out, "write docs") { + t.Fatalf("stdout = %s, want inline text in dry-run preview", out) + } + if strings.Contains(out, "Created ") { + t.Fatalf("stdout = %s, want no bead creation during dry-run", out) + } + if !strings.Contains(out, "No side effects executed (--dry-run).") { + t.Fatalf("stdout = %s, want dry-run footer", out) + } + + rigStore, err := openStoreAtForCity(filepath.Join(cityDir, "frontend"), cityDir) + if err != nil { + t.Fatalf("openStoreAtForCity(rig): %v", err) + } + beadList, err := rigStore.List(beads.ListQuery{AllowScan: true}) + if err != nil { + t.Fatalf("rigStore.List: %v", err) + } + if len(beadList) != 0 { + t.Fatalf("rig store bead count = %d, want 0: %#v", len(beadList), beadList) + } +} + +func TestResolveInlineBeadActionDryRunInlineTextDoesNotProbeStore(t *testing.T) { + create, inlineText := resolveInlineBeadAction(&config.City{}, "write docs", true) + if create { + t.Fatal("create = true, want false during dry-run") + } + if !inlineText { + t.Fatal("inlineText = false, want true") + } +} + +func TestResolveInlineBeadActionWhitespaceInlineTextDoesNotProbeStore(t *testing.T) { + create, inlineText := resolveInlineBeadAction(&config.City{}, "write docs", false) + if !create { + t.Fatal("create = false, want true for whitespace inline text") + } + if inlineText { + t.Fatal("inlineText = true, want false outside dry-run") + } +} + +func TestResolveInlineBeadActionSingleTokenInlineTextDoesNotProbeStore(t *testing.T) { + create, inlineText := resolveInlineBeadAction(&config.City{}, "docs", false) + if !create { + t.Fatal("create = false, want true for single-token inline text") + } + if inlineText { + t.Fatal("inlineText = true, want false outside dry-run") + } +} + +func TestResolveInlineBeadActionBeadIDDoesNotProbeStore(t *testing.T) { + create, inlineText := resolveInlineBeadAction(&config.City{}, "FE-123", false) + if create { + t.Fatal("create = true, want false for bead ID") + } + if inlineText { + t.Fatal("inlineText = true, want false") + } +} + +func TestResolveInlineBeadActionConfiguredAlphaSuffixIsBeadID(t *testing.T) { + cfg := &config.City{ + Workspace: config.Workspace{Name: "test", Prefix: "HQ"}, + Rigs: []config.Rig{{Name: "frontend", Path: "/tmp/frontend", Prefix: "FE"}}, + } + + create, inlineText := resolveInlineBeadAction(cfg, "FE-hello", false) + if create { + t.Fatal("create = true, want false for configured bead ID with all-alpha suffix") + } + if inlineText { + t.Fatal("inlineText = true, want false outside dry-run") + } + + create, inlineText = resolveInlineBeadAction(cfg, "FE-a1pha", false) + if create { + t.Fatal("create = true, want false for configured bead ID with digit") + } + if inlineText { + t.Fatal("inlineText = true, want false for configured bead ID") + } +} + +func TestCmdSlingConfiguredPrefixAllAlphaExistingBeadUsesPrefixStore(t *testing.T) { + configureIsolatedRuntimeEnv(t) + t.Setenv("GC_BEADS", "file") + + cityDir := t.TempDir() + frontendDir := filepath.Join(cityDir, "frontend") + ordersDir := filepath.Join(cityDir, "orders") + for _, dir := range []string{frontendDir, ordersDir} { + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatalf("MkdirAll(%s): %v", dir, err) + } + } + if err := ensureScopedFileStoreLayout(cityDir); err != nil { + t.Fatalf("ensureScopedFileStoreLayout: %v", err) + } + for _, dir := range []string{cityDir, frontendDir, ordersDir} { + if err := ensurePersistedScopeLocalFileStore(dir); err != nil { + t.Fatalf("ensurePersistedScopeLocalFileStore(%s): %v", dir, err) + } + } + writeTestFileStoreBeads(t, frontendDir, []beads.Bead{{ + ID: "FE-abcde", + Title: "existing frontend work", + Type: "task", + Status: "open", + Metadata: map[string]string{}, + }}) + cityToml := `[workspace] +name = "demo" + +[[rigs]] +name = "frontend" +path = "frontend" +prefix = "FE" + +[[rigs]] +name = "orders" +path = "orders" +prefix = "OD" + +[[agent]] +name = "worker" +dir = "orders" +` + if err := os.WriteFile(filepath.Join(cityDir, "city.toml"), []byte(cityToml), 0o644); err != nil { + t.Fatalf("WriteFile(city.toml): %v", err) + } + t.Chdir(cityDir) + + var stdout, stderr bytes.Buffer + code := cmdSling( + []string{"orders/worker", "FE-abcde"}, + false, false, true, + "", nil, "", + true, false, "", + true, false, false, + "", "", + &stdout, &stderr, + ) + if code != 0 { + t.Fatalf("cmdSling returned %d, want 0; stdout=%s stderr=%s", code, stdout.String(), stderr.String()) + } + if strings.Contains(stdout.String(), "Created ") { + t.Fatalf("stdout = %q, want existing bead route without inline creation", stdout.String()) + } + + frontendStore, err := openStoreAtForCity(frontendDir, cityDir) + if err != nil { + t.Fatalf("openStoreAtForCity(frontend): %v", err) + } + routed, err := frontendStore.Get("FE-abcde") + if err != nil { + t.Fatalf("frontendStore.Get(FE-abcde): %v", err) + } + if routed.Metadata["gc.routed_to"] != "orders/worker" { + t.Fatalf("frontend bead gc.routed_to = %q, want orders/worker", routed.Metadata["gc.routed_to"]) + } + + ordersStore, err := openStoreAtForCity(ordersDir, cityDir) + if err != nil { + t.Fatalf("openStoreAtForCity(orders): %v", err) + } + ordersBeads, err := ordersStore.List(beads.ListQuery{AllowScan: true}) + if err != nil { + t.Fatalf("ordersStore.List: %v", err) + } + if len(ordersBeads) != 0 { + t.Fatalf("orders store bead count = %d, want 0: %#v", len(ordersBeads), ordersBeads) + } +} + +func TestCmdSlingConfiguredPrefixAllAlphaExistingBeadUsesSelectedPrefixStore(t *testing.T) { + cityDir, frontendDir := setupCmdSlingConfiguredPrefixAllAlphaFrontendFixture(t, false, true) + + var stdout, stderr bytes.Buffer + code := cmdSling( + []string{"frontend/worker", "FE-abcde"}, + false, false, false, + "", nil, "", + true, false, "", + true, false, false, + "", "", + &stdout, &stderr, + ) + if code != 0 { + t.Fatalf("cmdSling returned %d, want 0; stdout=%s stderr=%s", code, stdout.String(), stderr.String()) + } + if strings.Contains(stdout.String(), "Created ") { + t.Fatalf("stdout = %q, want existing bead route without inline creation", stdout.String()) + } + + frontendStore, err := openStoreAtForCity(frontendDir, cityDir) + if err != nil { + t.Fatalf("openStoreAtForCity(frontend): %v", err) + } + routed, err := frontendStore.Get("FE-abcde") + if err != nil { + t.Fatalf("frontendStore.Get(FE-abcde): %v", err) + } + if routed.Metadata["gc.routed_to"] != "frontend/worker" { + t.Fatalf("frontend bead gc.routed_to = %q, want frontend/worker", routed.Metadata["gc.routed_to"]) + } + all, err := frontendStore.List(beads.ListQuery{AllowScan: true}) + if err != nil { + t.Fatalf("frontendStore.List: %v", err) + } + if len(all) != 1 { + t.Fatalf("frontend store bead count = %d, want 1: %#v", len(all), all) + } +} + +func TestCmdSlingOneArgConfiguredPrefixAllAlphaExistingBeadUsesDefaultTarget(t *testing.T) { + cityDir, frontendDir := setupCmdSlingConfiguredPrefixAllAlphaFrontendFixture(t, true, true) + + var stdout, stderr bytes.Buffer + code := cmdSling( + []string{"FE-abcde"}, + false, false, false, + "", nil, "", + true, false, "", + true, false, false, + "", "", + &stdout, &stderr, + ) + if code != 0 { + t.Fatalf("cmdSling returned %d, want 0; stdout=%s stderr=%s", code, stdout.String(), stderr.String()) + } + if strings.Contains(stdout.String(), "Created ") { + t.Fatalf("stdout = %q, want existing bead route without inline creation", stdout.String()) + } + + frontendStore, err := openStoreAtForCity(frontendDir, cityDir) + if err != nil { + t.Fatalf("openStoreAtForCity(frontend): %v", err) + } + routed, err := frontendStore.Get("FE-abcde") + if err != nil { + t.Fatalf("frontendStore.Get(FE-abcde): %v", err) + } + if routed.Metadata["gc.routed_to"] != "frontend/worker" { + t.Fatalf("frontend bead gc.routed_to = %q, want frontend/worker", routed.Metadata["gc.routed_to"]) + } +} + +func setupCmdSlingConfiguredPrefixAllAlphaFrontendFixture(t *testing.T, defaultTarget, seedExisting bool) (cityDir, frontendDir string) { + t.Helper() + configureIsolatedRuntimeEnv(t) + t.Setenv("GC_BEADS", "file") + + cityDir = t.TempDir() + frontendDir = filepath.Join(cityDir, "frontend") + if err := os.MkdirAll(frontendDir, 0o755); err != nil { + t.Fatalf("MkdirAll(frontend): %v", err) + } + if err := ensureScopedFileStoreLayout(cityDir); err != nil { + t.Fatalf("ensureScopedFileStoreLayout: %v", err) + } + for _, dir := range []string{cityDir, frontendDir} { + if err := ensurePersistedScopeLocalFileStore(dir); err != nil { + t.Fatalf("ensurePersistedScopeLocalFileStore(%s): %v", dir, err) + } + } + if seedExisting { + writeTestFileStoreBeads(t, frontendDir, []beads.Bead{{ + ID: "FE-abcde", + Title: "existing frontend work", + Type: "task", + Status: "open", + Metadata: map[string]string{}, + }}) + } + defaultTargetLine := "" + if defaultTarget { + defaultTargetLine = "default_sling_target = \"frontend/worker\"\n" + } + cityToml := `[workspace] +name = "demo" + +[[rigs]] +name = "frontend" +path = "frontend" +prefix = "FE" +` + defaultTargetLine + ` +[[agent]] +name = "worker" +dir = "frontend" +` + if err := os.WriteFile(filepath.Join(cityDir, "city.toml"), []byte(cityToml), 0o644); err != nil { + t.Fatalf("WriteFile(city.toml): %v", err) + } + t.Chdir(cityDir) + return cityDir, frontendDir +} + +func writeTestFileStoreBeads(t *testing.T, scopeRoot string, stored []beads.Bead) { + t.Helper() + data := struct { + Seq int `json:"seq"` + Beads []beads.Bead `json:"beads"` + }{Seq: len(stored), Beads: stored} + raw, err := json.Marshal(data) + if err != nil { + t.Fatalf("Marshal file store beads: %v", err) + } + if err := os.WriteFile(filepath.Join(scopeRoot, ".gc", "beads.json"), append(raw, '\n'), 0o644); err != nil { + t.Fatalf("WriteFile(%s): %v", filepath.Join(scopeRoot, ".gc", "beads.json"), err) + } +} + func TestCmdSlingForceBypassesMissingBeadCheck(t *testing.T) { // --force must bypass the bead-existence check. The call may still // fail further downstream (we don't assert a success exit here), but @@ -1047,6 +1447,59 @@ func TestCmdSlingForceBypassesMissingBeadCheck(t *testing.T) { } } +func TestCmdSlingForceMissingBeadPrintsAutoConvoyWarning(t *testing.T) { + configureIsolatedRuntimeEnv(t) + t.Setenv("GC_BEADS", "file") + + cityDir := t.TempDir() + rigDir := filepath.Join(cityDir, "frontend") + if err := os.MkdirAll(rigDir, 0o755); err != nil { + t.Fatalf("MkdirAll(rig): %v", err) + } + if err := ensureScopedFileStoreLayout(cityDir); err != nil { + t.Fatalf("ensureScopedFileStoreLayout: %v", err) + } + for _, dir := range []string{cityDir, rigDir} { + if err := ensurePersistedScopeLocalFileStore(dir); err != nil { + t.Fatalf("ensurePersistedScopeLocalFileStore(%s): %v", dir, err) + } + } + cityToml := `[workspace] +name = "demo" + +[[rigs]] +name = "frontend" +path = "frontend" +prefix = "FE" + +[[agent]] +name = "worker" +dir = "frontend" +sling_query = "true" +` + if err := os.WriteFile(filepath.Join(cityDir, "city.toml"), []byte(cityToml), 0o644); err != nil { + t.Fatalf("WriteFile(city.toml): %v", err) + } + t.Chdir(cityDir) + + var stdout, stderr bytes.Buffer + code := cmdSling( + []string{"frontend/worker", "FE-ghost1"}, + false, false, true, + "", nil, "", + false, false, "", + true, false, false, + "", "", + &stdout, &stderr, + ) + if code != 0 { + t.Fatalf("cmdSling returned %d, want 0; stdout=%s stderr=%s", code, stdout.String(), stderr.String()) + } + if !strings.Contains(stderr.String(), "forced dispatch skipped missing-bead validation") { + t.Fatalf("stderr = %q, want forced missing-bead auto-convoy warning", stderr.String()) + } +} + func TestCmdSlingAcceptsExistingBead(t *testing.T) { // When a bead-ID-shaped argument IS present in the store, the new // existence check must not fire. This test only asserts the check @@ -1080,6 +1533,98 @@ func TestCmdSlingAcceptsExistingBead(t *testing.T) { } } +func TestCmdSlingRefusesMissingConfiguredFallbackBeadID(t *testing.T) { + configureIsolatedRuntimeEnv(t) + t.Setenv("GC_BEADS", "file") + + cityDir := t.TempDir() + rigDir := filepath.Join(cityDir, "orders") + if err := os.MkdirAll(rigDir, 0o755); err != nil { + t.Fatalf("MkdirAll(rig): %v", err) + } + if err := ensureScopedFileStoreLayout(cityDir); err != nil { + t.Fatalf("ensureScopedFileStoreLayout: %v", err) + } + if err := ensurePersistedScopeLocalFileStore(cityDir); err != nil { + t.Fatalf("ensurePersistedScopeLocalFileStore(city): %v", err) + } + if err := ensurePersistedScopeLocalFileStore(rigDir); err != nil { + t.Fatalf("ensurePersistedScopeLocalFileStore(rig): %v", err) + } + cityToml := `[workspace] +name = "demo" + +[[rigs]] +name = "orders" +path = "orders" +prefix = "od" + +[[agent]] +name = "worker" +dir = "orders" +` + if err := os.WriteFile(filepath.Join(cityDir, "city.toml"), []byte(cityToml), 0o644); err != nil { + t.Fatalf("WriteFile(city.toml): %v", err) + } + t.Chdir(cityDir) + + var stdout, stderr bytes.Buffer + code := cmdSling( + []string{"orders/worker", "od-zzzz1"}, + false, false, false, + "", nil, "", + true, false, "", + false, false, false, + "", "", + &stdout, &stderr, + ) + if code == 0 { + t.Fatalf("cmdSling returned 0, want non-zero; stdout=%s stderr=%s", stdout.String(), stderr.String()) + } + if strings.Contains(stdout.String(), "Created ") { + t.Fatalf("stdout = %q, want missing bead error instead of inline creation", stdout.String()) + } + if !strings.Contains(stderr.String(), "not found") { + t.Fatalf("stderr = %q, want missing bead diagnostic", stderr.String()) + } +} + +func TestCmdSlingRefusesMissingConfiguredPrefixAllAlphaBeadID(t *testing.T) { + cityDir, _ := setupCmdSlingConfiguredPrefixAllAlphaFrontendFixture(t, false, false) + + var stdout, stderr bytes.Buffer + code := cmdSling( + []string{"frontend/worker", "FE-abcde"}, + false, false, false, + "", nil, "", + true, false, "", + true, false, false, + "", "", + &stdout, &stderr, + ) + if code == 0 { + t.Fatalf("cmdSling returned 0, want non-zero; stdout=%s stderr=%s", stdout.String(), stderr.String()) + } + if strings.Contains(stdout.String(), "Created ") { + t.Fatalf("stdout = %q, want missing bead error instead of inline creation", stdout.String()) + } + if !strings.Contains(stderr.String(), "not found") { + t.Fatalf("stderr = %q, want missing bead diagnostic", stderr.String()) + } + + frontendStore, err := openStoreAtForCity(filepath.Join(cityDir, "frontend"), cityDir) + if err != nil { + t.Fatalf("openStoreAtForCity(frontend): %v", err) + } + all, err := frontendStore.List(beads.ListQuery{AllowScan: true}) + if err != nil { + t.Fatalf("frontendStore.List: %v", err) + } + if len(all) != 0 { + t.Fatalf("frontend store bead count = %d, want 0: %#v", len(all), all) + } +} + func TestSlingStoreEnvUsesRigBdRuntimeForMixedProviderRig(t *testing.T) { cityDir := t.TempDir() wantPort := strconv.Itoa(writeReachableManagedDoltState(t, cityDir)) @@ -1600,11 +2145,14 @@ func TestDoSlingBatchGetFails(t *testing.T) { opts := testOpts(a, "BL-42") code := doSlingBatch(opts, deps, q, stdout, stderr) - if code != 0 { - t.Fatalf("doSlingBatch returned %d, want 0 (falls through to doSling); stderr: %s", code, stderr.String()) + if code == 0 { + t.Fatalf("doSlingBatch returned 0, want lookup failure; stdout: %s", stdout.String()) } - if !strings.Contains(stdout.String(), "Slung BL-42") { - t.Errorf("stdout = %q, want direct sling output", stdout.String()) + if !strings.Contains(stderr.String(), "bd not available") { + t.Errorf("stderr = %q, want lookup failure", stderr.String()) + } + if len(runner.calls) != 0 { + t.Fatalf("runner calls = %#v, want none", runner.calls) } } @@ -2307,6 +2855,37 @@ func TestResolveSlingStoreRootPrefersBeadPrefixRig(t *testing.T) { } } +func TestResolveSlingStoreRootUsesPrefixRigForConfiguredAllAlphaBeadID(t *testing.T) { + cityPath := filepath.Join(t.TempDir(), "city") + cfg := &config.City{ + Rigs: []config.Rig{ + {Name: "frontend", Path: filepath.Join("rigs", "frontend"), Prefix: "FE"}, + {Name: "orders", Path: filepath.Join("rigs", "orders"), Prefix: "od"}, + }, + } + + got := resolveSlingStoreRoot(cfg, cityPath, "FE-hello", config.Agent{Dir: "orders"}) + want := filepath.Join(cityPath, "rigs", "frontend") + if got != want { + t.Fatalf("resolveSlingStoreRoot() = %q, want %q", got, want) + } +} + +func TestResolveSlingStoreRootUsesCityRootForHQPrefix(t *testing.T) { + cityPath := filepath.Join(t.TempDir(), "city") + cfg := &config.City{ + Workspace: config.Workspace{Name: "bright-lights", Prefix: "hq"}, + Rigs: []config.Rig{ + {Name: "alpha", Path: filepath.Join("rigs", "alpha"), Prefix: "al"}, + }, + } + + got := resolveSlingStoreRoot(cfg, cityPath, "hq-123", config.Agent{Dir: "alpha"}) + if got != cityPath { + t.Fatalf("resolveSlingStoreRoot() = %q, want city root %q", got, cityPath) + } +} + func TestSlingFormulaRepoDirUsesCanonicalRigRoot(t *testing.T) { cityPath := filepath.Join(t.TempDir(), "city") deps := slingDeps{ @@ -3207,6 +3786,7 @@ func TestDryRunSingleBead(t *testing.T) { q := &fakeQuerier{bead: beads.Bead{ID: "BL-42", Title: "Implement login page", Type: "task", Status: "open"}} deps, stdout, stderr := testDeps(cfg, sp, runner.run) + deps.Store = seededStore("BL-42") opts := testOpts(a, "BL-42") opts.DryRun = true code := doSling(opts, deps, q, stdout, stderr) @@ -3258,6 +3838,7 @@ func TestDryRunSingleBeadExpandsSlingQuerySummary(t *testing.T) { q := &fakeQuerier{bead: beads.Bead{ID: "FR-42", Title: "Implement login page", Type: "task", Status: "open"}} deps, stdout, stderr := testDeps(cfg, sp, runner.run) + deps.Store = seededStore("FR-42") opts := testOpts(a, "FR-42") opts.DryRun = true code := doSling(opts, deps, q, stdout, stderr) @@ -3314,6 +3895,7 @@ func TestDryRunOnFormula(t *testing.T) { q.childrenOf["BL-42"] = []beads.Bead{} // no molecule children deps, stdout, stderr := testDeps(cfg, sp, runner.run) + deps.Store = seededStore("BL-42") opts := testOpts(a, "BL-42") opts.OnFormula = "code-review" opts.DryRun = true @@ -3351,6 +3933,7 @@ func TestDryRunMultiSessionConfig(t *testing.T) { } deps, stdout, stderr := testDeps(cfg, sp, runner.run) + deps.Store = seededStore("BL-42") opts := testOpts(a, "BL-42") opts.DryRun = true code := doSling(opts, deps, nil, stdout, stderr) @@ -3487,6 +4070,7 @@ func TestDryRunNudgeRunning(t *testing.T) { a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} deps, stdout, stderr := testDeps(cfg, sp, runner.run) + deps.Store = seededStore("BL-1") opts := testOpts(a, "BL-1") opts.Nudge = true opts.DryRun = true @@ -3520,6 +4104,7 @@ func TestDryRunNudgeNotRunning(t *testing.T) { a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} deps, stdout, stderr := testDeps(cfg, sp, runner.run) + deps.Store = seededStore("BL-1") opts := testOpts(a, "BL-1") opts.Nudge = true opts.DryRun = true @@ -3541,6 +4126,7 @@ func TestDryRunNoMutations(t *testing.T) { a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} deps, stdout, stderr := testDeps(cfg, sp, runner.run) + deps.Store = seededStore("BL-42") opts := testOpts(a, "BL-42") opts.DryRun = true code := doSling(opts, deps, nil, stdout, stderr) @@ -3560,6 +4146,7 @@ func TestDryRunSuspendedWarning(t *testing.T) { a := config.Agent{Name: "mayor", Suspended: true, MaxActiveSessions: intPtr(1)} deps, stdout, stderr := testDeps(cfg, sp, runner.run) + deps.Store = seededStore("BL-1") opts := testOpts(a, "BL-1") opts.DryRun = true code := doSling(opts, deps, nil, stdout, stderr) @@ -3590,6 +4177,7 @@ func TestDryRunOnExistingMolecule(t *testing.T) { } deps, stdout, stderr := testDeps(cfg, sp, runner.run) + deps.Store = seededStore("BL-42") opts := testOpts(a, "BL-42") opts.OnFormula = "code-review" opts.DryRun = true @@ -3613,6 +4201,7 @@ func TestDryRunNilQuerier(t *testing.T) { a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} deps, stdout, stderr := testDeps(cfg, sp, runner.run) + deps.Store = seededStore("BL-42") opts := testOpts(a, "BL-42") opts.DryRun = true code := doSling(opts, deps, nil, stdout, stderr) @@ -3888,6 +4477,7 @@ func TestDryRunIdempotentBead(t *testing.T) { q := &fakeQuerier{bead: beads.Bead{ID: "BL-42", Title: "Login page", Assignee: "mayor", Status: "open"}} deps, stdout, stderr := testDeps(cfg, sp, runner.run) + deps.Store = seededStore("BL-42") opts := testOpts(a, "BL-42") opts.DryRun = true code := doSling(opts, deps, q, stdout, stderr) @@ -4125,6 +4715,7 @@ func TestDryRunCrossRigSection(t *testing.T) { q := &fakeQuerier{bead: beads.Bead{ID: "FE-123", Type: "task", Status: "open"}} deps, stdout, stderr := testDeps(cfg, sp, runner.run) + deps.Store = seededStore("FE-123") opts := testOpts(a, "FE-123") opts.DryRun = true code := doSling(opts, deps, q, stdout, stderr) @@ -4526,6 +5117,7 @@ func TestDefaultFormulaDryRun(t *testing.T) { a := config.Agent{Name: "polecat", Dir: "hw", DefaultSlingFormula: strPtr("mol-polecat-work")} deps, stdout, stderr := testDeps(cfg, sp, runner.run) + deps.Store = seededStore("HW-42") opts := testOpts(a, "HW-42") opts.DryRun = true code := doSling(opts, deps, nil, stdout, stderr) @@ -4938,7 +5530,7 @@ func TestLooksLikeBeadID(t *testing.T) { } } -func TestBeadExistsInStoreFallback(t *testing.T) { +func TestProbeBeadInStoreFallback(t *testing.T) { base := beads.NewMemStore() store := &recordingStore{ Store: base, @@ -4946,16 +5538,36 @@ func TestBeadExistsInStoreFallback(t *testing.T) { } // beadExistsInStore should find it. - if !beadExistsInStore(store, "ProjectWrenUnity-0fze.1") { + exists, err := sling.ProbeBeadInStore(store, "ProjectWrenUnity-0fze.1") + if err != nil { + t.Fatalf("beadExistsInStore(existing): %v", err) + } + if !exists { t.Error("beadExistsInStore should find existing bead") } // Non-existent bead should return false. - if beadExistsInStore(store, "nonexistent-xyz") { + exists, err = sling.ProbeBeadInStore(store, "nonexistent-xyz") + if err != nil { + t.Fatalf("beadExistsInStore(missing): %v", err) + } + if exists { t.Error("beadExistsInStore should return false for missing bead") } } +func TestProbeBeadInStoreSurfacesLookupError(t *testing.T) { + store := &recordingStore{Store: &getErrStore{Store: beads.NewMemStore(), err: fmt.Errorf("lookup failed")}} + + _, err := sling.ProbeBeadInStore(store, "gc-1") + if err == nil { + t.Fatal("ProbeBeadInStore error = nil, want lookup failure") + } + if !strings.Contains(err.Error(), "lookup failed") { + t.Fatalf("ProbeBeadInStore error = %q, want lookup failure", err) + } +} + func TestOneArgSlingInlineTextRequiresTarget(t *testing.T) { // Inline text with 1 arg should error asking for explicit target. cmd := newSlingCmd(&bytes.Buffer{}, &bytes.Buffer{}) diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 1f49fec05a..82f818f639 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -2315,7 +2315,7 @@ gc sling [target] [flags] | Flag | Type | Default | Description | |------|------|---------|-------------| | `-n`, `--dry-run` | bool | | show what would be done without executing | -| `--force` | bool | | suppress warnings and allow cross-rig routing | +| `--force` | bool | | suppress warnings, allow cross-rig routing, allow graph workflow replacement, and for direct bead routes dispatch even if the bead does not resolve in the local store | | `-f`, `--formula` | bool | | treat argument as formula name | | `--merge` | string | | merge strategy: direct, mr, or local | | `--no-convoy` | bool | | skip auto-convoy creation | diff --git a/docs/schema/openapi.json b/docs/schema/openapi.json index ee1f1e44e7..e939ed7f91 100644 --- a/docs/schema/openapi.json +++ b/docs/schema/openapi.json @@ -1796,10 +1796,16 @@ "default": "about:blank", "description": "A URI reference to human-readable documentation for the error.", "examples": [ - "https://example.com/errors/example" + "https://example.com/errors/example", + "urn:gascity:error:sling-missing-bead", + "urn:gascity:error:sling-cross-rig" ], "format": "uri", - "type": "string" + "type": "string", + "x-gascity-problem-types": [ + "urn:gascity:error:sling-missing-bead", + "urn:gascity:error:sling-cross-rig" + ] } }, "type": "object" @@ -5825,6 +5831,10 @@ "description": "Bead ID to sling.", "type": "string" }, + "force": { + "description": "Bypass cross-rig guards; for direct bead routes, also bypass missing-bead validation. Formula-backed graph routes may replace existing live workflow roots but still require the source bead to exist.", + "type": "boolean" + }, "formula": { "description": "Formula name for workflow launch.", "type": "string" diff --git a/docs/schema/openapi.txt b/docs/schema/openapi.txt index ee1f1e44e7..e939ed7f91 100644 --- a/docs/schema/openapi.txt +++ b/docs/schema/openapi.txt @@ -1796,10 +1796,16 @@ "default": "about:blank", "description": "A URI reference to human-readable documentation for the error.", "examples": [ - "https://example.com/errors/example" + "https://example.com/errors/example", + "urn:gascity:error:sling-missing-bead", + "urn:gascity:error:sling-cross-rig" ], "format": "uri", - "type": "string" + "type": "string", + "x-gascity-problem-types": [ + "urn:gascity:error:sling-missing-bead", + "urn:gascity:error:sling-cross-rig" + ] } }, "type": "object" @@ -5825,6 +5831,10 @@ "description": "Bead ID to sling.", "type": "string" }, + "force": { + "description": "Bypass cross-rig guards; for direct bead routes, also bypass missing-bead validation. Formula-backed graph routes may replace existing live workflow roots but still require the source bead to exist.", + "type": "boolean" + }, "formula": { "description": "Formula name for workflow launch.", "type": "string" diff --git a/internal/api/genclient/client_gen.go b/internal/api/genclient/client_gen.go index aca06d0ab9..2a210adb9a 100644 --- a/internal/api/genclient/client_gen.go +++ b/internal/api/genclient/client_gen.go @@ -2273,6 +2273,9 @@ type SlingInputBody struct { // Bead Bead ID to sling. Bead *string `json:"bead,omitempty"` + // Force Bypass cross-rig guards; for direct bead routes, also bypass missing-bead validation. Formula-backed graph routes may replace existing live workflow roots but still require the source bead to exist. + Force *bool `json:"force,omitempty"` + // Formula Formula name for workflow launch. Formula *string `json:"formula,omitempty"` diff --git a/internal/api/handler_sling.go b/internal/api/handler_sling.go index cfa21b17a8..5c1e3acc3b 100644 --- a/internal/api/handler_sling.go +++ b/internal/api/handler_sling.go @@ -65,16 +65,26 @@ func (s *Server) execSling(ctx context.Context, body slingBody, _ string) (*slin formulaName := strings.TrimSpace(body.Formula) attachedBeadID := strings.TrimSpace(body.AttachedBeadID) + storeBeadID := slingStoreBeadID(body) // Build deps and construct Sling instance. - store := s.findSlingStore(body.Rig, agentCfg) + store := s.findSlingStore(body.Rig, agentCfg, storeBeadID) + storeRef := s.slingStoreRef(body.Rig, agentCfg, storeBeadID) + if store == nil && allowsForceStoreFallback(body, agentCfg) { + store = s.findSlingStore(body.Rig, agentCfg, "") + storeRef = s.slingStoreRef(body.Rig, agentCfg, "") + } + if store == nil { + message := fmt.Sprintf("bead prefix store %s is not registered; cannot verify bead %q", storeRef, storeBeadID) + return nil, http.StatusBadRequest, "missing_bead", message, nil + } deps := sling.SlingDeps{ CityName: s.state.CityName(), CityPath: s.state.CityPath(), Cfg: s.state.Config(), SP: s.state.SessionProvider(), Store: store, - StoreRef: s.slingStoreRef(body.Rig, agentCfg), + StoreRef: storeRef, SourceWorkflowStores: func() ([]sling.SourceWorkflowStore, error) { return s.sourceWorkflowStores(), nil }, @@ -145,6 +155,19 @@ func (s *Server) execSling(ctx context.Context, body slingBody, _ string) (*slin if errors.As(err, &conflictErr) { return nil, http.StatusConflict, "conflict", err.Error(), conflictErr } + var lookupErr *sling.BeadLookupError + if errors.As(err, &lookupErr) { + fmt.Fprintf(apiSlingStderr(), "gc api sling: %v\n", lookupErr) //nolint:errcheck + return nil, http.StatusInternalServerError, "internal", "sling bead lookup failed", nil + } + var missingBeadErr *sling.MissingBeadError + if errors.As(err, &missingBeadErr) { + return nil, http.StatusBadRequest, "missing_bead", err.Error(), nil + } + var crossRigErr *sling.CrossRigError + if errors.As(err, &crossRigErr) { + return nil, http.StatusBadRequest, "cross_rig", err.Error(), nil + } return nil, http.StatusBadRequest, "invalid", err.Error(), nil } @@ -170,6 +193,24 @@ func (s *Server) execSling(ctx context.Context, body slingBody, _ string) (*slin return resp, http.StatusCreated, "", "", nil } +func allowsForceStoreFallback(body slingBody, agentCfg config.Agent) bool { + if !body.Force || strings.TrimSpace(body.Bead) == "" { + return false + } + if strings.TrimSpace(body.Formula) != "" || strings.TrimSpace(body.AttachedBeadID) != "" { + return false + } + return agentCfg.EffectiveDefaultSlingFormula() == "" +} + +func slingStoreBeadID(body slingBody) string { + // Formula attachment validates the attached bead, not the formula name. + if attachedBeadID := strings.TrimSpace(body.AttachedBeadID); attachedBeadID != "" { + return attachedBeadID + } + return strings.TrimSpace(body.Bead) +} + // sourceWorkflowCleanupHint renders the CLI command that clears the blocking // source workflow. Surfaced in the conflict response body so users can fix // the state without grepping docs. @@ -183,7 +224,14 @@ func sourceWorkflowCleanupHint(sourceBeadID, storeRef string) string { } // findSlingStore returns the bead store for sling operations. -func (s *Server) findSlingStore(rig string, agentCfg config.Agent) beads.Store { +func (s *Server) findSlingStore(rig string, agentCfg config.Agent, beadID string) beads.Store { + // Match the CLI's bead-prefix-first resolution so existence checks consult + // the bead's home store before any cross-rig guard runs. + if resolvedRig, cityScope := s.slingStoreScopeForBead(beadID); cityScope { + return s.state.CityBeadStore() + } else if resolvedRig != "" { + return s.state.BeadStore(resolvedRig) + } if rig != "" { if store := s.state.BeadStore(rig); store != nil { return store @@ -198,7 +246,12 @@ func (s *Server) findSlingStore(rig string, agentCfg config.Agent) beads.Store { } // slingStoreRef returns a store ref string for the sling context. -func (s *Server) slingStoreRef(rig string, agentCfg config.Agent) string { +func (s *Server) slingStoreRef(rig string, agentCfg config.Agent, beadID string) string { + if resolvedRig, cityScope := s.slingStoreScopeForBead(beadID); cityScope { + return "city:" + s.state.CityName() + } else if resolvedRig != "" { + return "rig:" + resolvedRig + } if rig != "" { return "rig:" + rig } @@ -208,6 +261,25 @@ func (s *Server) slingStoreRef(rig string, agentCfg config.Agent) string { return "city:" + s.state.CityName() } +func (s *Server) slingStoreScopeForBead(beadID string) (rigName string, cityScope bool) { + beadID = strings.TrimSpace(beadID) + if beadID == "" { + return "", false + } + prefix := sling.BeadPrefix(beadID) + if prefix == "" { + return "", false + } + if sling.IsHQPrefix(s.state.Config(), prefix) { + return "", true + } + rig, ok := sling.FindRigByPrefix(s.state.Config(), prefix) + if !ok { + return "", false + } + return rig.Name, false +} + func (s *Server) sourceWorkflowStores() []sling.SourceWorkflowStore { stores := make([]sling.SourceWorkflowStore, 0, len(s.state.BeadStores())+1) if cityStore := s.state.CityBeadStore(); cityStore != nil { diff --git a/internal/api/handler_sling_test.go b/internal/api/handler_sling_test.go index 7dbf7c1ca1..5b4ab296bc 100644 --- a/internal/api/handler_sling_test.go +++ b/internal/api/handler_sling_test.go @@ -3,6 +3,7 @@ package api import ( "bytes" "encoding/json" + "errors" "io" "net/http" "net/http/httptest" @@ -10,6 +11,7 @@ import ( "path/filepath" "strings" "testing" + "time" "github.com/gastownhall/gascity/internal/agentutil" "github.com/gastownhall/gascity/internal/beads" @@ -18,6 +20,15 @@ import ( "github.com/gastownhall/gascity/internal/molecule" ) +type getErrStore struct { + beads.Store + err error +} + +func (s *getErrStore) Get(_ string) (beads.Bead, error) { + return beads.Bead{}, s.err +} + // newSlingTestServer creates a test handler wrapping a Server that has a // fake runner injected (captures commands without executing real shell // processes). @@ -83,6 +94,317 @@ func TestSlingWithBead(t *testing.T) { } } +func TestSlingWithMissingBeadReturnsBadRequest(t *testing.T) { + h, state := newSlingTestServer(t) + + body := `{"target":"myrig/worker","bead":"gc-zzzzz"}` + rec := httptest.NewRecorder() + h.ServeHTTP(rec, newPostRequest(cityURL(state, "/sling"), strings.NewReader(body))) + + if rec.Code != http.StatusBadRequest { + t.Fatalf("status = %d, want 400; body = %s", rec.Code, rec.Body.String()) + } + var problem struct { + Type string `json:"type"` + Detail string `json:"detail"` + } + if err := json.NewDecoder(rec.Body).Decode(&problem); err != nil { + t.Fatalf("decode: %v", err) + } + if problem.Type != "urn:gascity:error:sling-missing-bead" { + t.Fatalf("type = %q, want missing-bead discriminator", problem.Type) + } + if !strings.Contains(problem.Detail, "not found") { + t.Fatalf("detail = %s, want missing bead diagnostic", problem.Detail) + } + if strings.Contains(problem.Detail, "--force") { + t.Fatalf("detail = %s, want transport-neutral missing bead error", problem.Detail) + } +} + +func TestSlingAttachFormulaMissingBeadReturnsBadRequest(t *testing.T) { + h, state := newSlingTestServer(t) + + body := `{"target":"myrig/worker","formula":"code-review","attached_bead_id":"gc-zzzzz"}` + rec := httptest.NewRecorder() + h.ServeHTTP(rec, newPostRequest(cityURL(state, "/sling"), strings.NewReader(body))) + + if rec.Code != http.StatusBadRequest { + t.Fatalf("status = %d, want 400; body = %s", rec.Code, rec.Body.String()) + } + var problem struct { + Type string `json:"type"` + Detail string `json:"detail"` + } + if err := json.NewDecoder(rec.Body).Decode(&problem); err != nil { + t.Fatalf("decode: %v", err) + } + if problem.Type != "urn:gascity:error:sling-missing-bead" { + t.Fatalf("type = %q, want missing-bead discriminator", problem.Type) + } + if !strings.Contains(problem.Detail, "gc-zzzzz") || !strings.Contains(problem.Detail, "not found") { + t.Fatalf("detail = %s, want attached missing bead diagnostic", problem.Detail) + } +} + +func TestSlingWithLookupFailureReturnsInternalServerError(t *testing.T) { + h, state := newSlingTestServer(t) + state.stores["myrig"] = &getErrStore{ + Store: state.stores["myrig"], + err: errors.New("backend unavailable"), + } + var stderr bytes.Buffer + oldStderr := apiSlingStderr + apiSlingStderr = func() io.Writer { return &stderr } + t.Cleanup(func() { apiSlingStderr = oldStderr }) + + body := `{"target":"myrig/worker","bead":"gc-zzzzz"}` + rec := httptest.NewRecorder() + h.ServeHTTP(rec, newPostRequest(cityURL(state, "/sling"), strings.NewReader(body))) + + if rec.Code != http.StatusInternalServerError { + t.Fatalf("status = %d, want 500; body = %s", rec.Code, rec.Body.String()) + } + if !strings.Contains(rec.Body.String(), "sling bead lookup failed") { + t.Fatalf("body = %s, want sanitized lookup failure", rec.Body.String()) + } + if strings.Contains(rec.Body.String(), "backend unavailable") { + t.Fatalf("body = %s, should not expose backend failure detail", rec.Body.String()) + } + if !strings.Contains(stderr.String(), "backend unavailable") { + t.Fatalf("stderr = %s, want backend failure detail logged", stderr.String()) + } +} + +func TestSlingWithForceBypassesMissingBeadGuard(t *testing.T) { + h, state := newSlingTestServer(t) + + body := `{"target":"myrig/worker","bead":"gc-zzzzz","force":true}` + rec := httptest.NewRecorder() + h.ServeHTTP(rec, newPostRequest(cityURL(state, "/sling"), strings.NewReader(body))) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want 200; body = %s", rec.Code, rec.Body.String()) + } +} + +func TestSlingCrossRigExistingBeadReturnsCrossRigError(t *testing.T) { + h, state := newSlingTestServer(t) + state.stores["frontend"] = beads.NewMemStoreFrom(0, []beads.Bead{ + {ID: "FE-123", Title: "frontend task", Type: "task", Status: "open"}, + }, nil) + state.cfg.Rigs = append(state.cfg.Rigs, config.Rig{Name: "frontend", Path: "/tmp/frontend", Prefix: "FE"}) + + body := `{"rig":"myrig","target":"myrig/worker","bead":"FE-123"}` + rec := httptest.NewRecorder() + h.ServeHTTP(rec, newPostRequest(cityURL(state, "/sling"), strings.NewReader(body))) + + if rec.Code != http.StatusBadRequest { + t.Fatalf("status = %d, want 400; body = %s", rec.Code, rec.Body.String()) + } + var problem struct { + Type string `json:"type"` + Detail string `json:"detail"` + } + if err := json.NewDecoder(rec.Body).Decode(&problem); err != nil { + t.Fatalf("decode: %v", err) + } + if problem.Type != "urn:gascity:error:sling-cross-rig" { + t.Fatalf("type = %q, want cross-rig discriminator", problem.Type) + } + if !strings.Contains(problem.Detail, "cross-rig") { + t.Fatalf("detail = %s, want cross-rig diagnostic", problem.Detail) + } + if strings.Contains(problem.Detail, "not found") { + t.Fatalf("detail = %s, want cross-rig error instead of missing bead", problem.Detail) + } +} + +func TestSlingCrossRigExistingCityBeadReturnsCrossRigError(t *testing.T) { + h, state := newSlingTestServer(t) + state.cfg.Workspace.Prefix = "HQ" + state.cityBeadStore = beads.NewMemStoreFrom(0, []beads.Bead{ + {ID: "HQ-123", Title: "city task", Type: "task", Status: "open"}, + }, nil) + + body := `{"rig":"myrig","target":"myrig/worker","bead":"HQ-123"}` + rec := httptest.NewRecorder() + h.ServeHTTP(rec, newPostRequest(cityURL(state, "/sling"), strings.NewReader(body))) + + if rec.Code != http.StatusBadRequest { + t.Fatalf("status = %d, want 400; body = %s", rec.Code, rec.Body.String()) + } + var problem struct { + Type string `json:"type"` + Detail string `json:"detail"` + } + if err := json.NewDecoder(rec.Body).Decode(&problem); err != nil { + t.Fatalf("decode: %v", err) + } + if problem.Type != "urn:gascity:error:sling-cross-rig" { + t.Fatalf("type = %q, want cross-rig discriminator", problem.Type) + } + if strings.Contains(problem.Detail, "not found") { + t.Fatalf("detail = %s, want city bead to be found before cross-rig guard", problem.Detail) + } +} + +func TestSlingStoreRefReportsPrefixStoreWhenPrefixStoreMissing(t *testing.T) { + state := newFakeMutatorState(t) + state.cfg.Rigs = append(state.cfg.Rigs, config.Rig{Name: "frontend", Path: "/tmp/frontend", Prefix: "FE"}) + srv := New(state) + agentCfg := config.Agent{Name: "worker", Dir: "myrig"} + + store := srv.findSlingStore("myrig", agentCfg, "FE-123") + if store != nil { + t.Fatalf("findSlingStore returned %T, want nil missing prefix store", store) + } + if got := srv.slingStoreRef("myrig", agentCfg, "FE-123"); got != "rig:frontend" { + t.Fatalf("slingStoreRef = %q, want rig:frontend", got) + } +} + +func TestSlingPrefixStoreMissingReturnsMissingBead(t *testing.T) { + h, state := newSlingTestServer(t) + state.cfg.Rigs = append(state.cfg.Rigs, config.Rig{Name: "frontend", Path: "/tmp/frontend", Prefix: "FE"}) + + body := `{"target":"myrig/worker","bead":"FE-123"}` + rec := httptest.NewRecorder() + h.ServeHTTP(rec, newPostRequest(cityURL(state, "/sling"), strings.NewReader(body))) + + if rec.Code != http.StatusBadRequest { + t.Fatalf("status = %d, want 400; body = %s", rec.Code, rec.Body.String()) + } + var problem struct { + Type string `json:"type"` + Detail string `json:"detail"` + } + if err := json.NewDecoder(rec.Body).Decode(&problem); err != nil { + t.Fatalf("decode: %v", err) + } + if problem.Type != "urn:gascity:error:sling-missing-bead" { + t.Fatalf("type = %q, want missing-bead discriminator", problem.Type) + } + if !strings.Contains(problem.Detail, "rig:frontend") { + t.Fatalf("detail = %s, want prefix store ref", problem.Detail) + } +} + +func TestSlingForcePrefixStoreMissingFallsBackToTargetStore(t *testing.T) { + h, state := newSlingTestServer(t) + state.cfg.Rigs = append(state.cfg.Rigs, config.Rig{Name: "frontend", Path: "/tmp/frontend", Prefix: "FE"}) + + body := `{"target":"myrig/worker","bead":"FE-123","force":true}` + rec := httptest.NewRecorder() + h.ServeHTTP(rec, newPostRequest(cityURL(state, "/sling"), strings.NewReader(body))) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want 200; body = %s", rec.Code, rec.Body.String()) + } +} + +func TestSlingAttachFormulaForcePrefixStoreMissingDoesNotFallbackToTargetStore(t *testing.T) { + h, state := newSlingTestServer(t) + state.cfg.Rigs = append(state.cfg.Rigs, config.Rig{Name: "frontend", Path: "/tmp/frontend", Prefix: "FE"}) + state.stores["myrig"] = beads.NewMemStoreFrom(0, []beads.Bead{ + {ID: "FE-123", Title: "wrong-store task", Type: "task", Status: "open"}, + }, nil) + + body := `{"target":"myrig/worker","formula":"code-review","attached_bead_id":"FE-123","force":true}` + rec := httptest.NewRecorder() + h.ServeHTTP(rec, newPostRequest(cityURL(state, "/sling"), strings.NewReader(body))) + + if rec.Code != http.StatusBadRequest { + t.Fatalf("status = %d, want 400; body = %s", rec.Code, rec.Body.String()) + } + var problem struct { + Type string `json:"type"` + Detail string `json:"detail"` + } + if err := json.NewDecoder(rec.Body).Decode(&problem); err != nil { + t.Fatalf("decode: %v", err) + } + if problem.Type != "urn:gascity:error:sling-missing-bead" { + t.Fatalf("type = %q, want missing-bead discriminator; body = %s", problem.Type, rec.Body.String()) + } + if !strings.Contains(problem.Detail, "rig:frontend") { + t.Fatalf("detail = %s, want prefix store ref", problem.Detail) + } +} + +func TestSlingDefaultFormulaForcePrefixStoreMissingDoesNotFallbackToTargetStore(t *testing.T) { + h, state := newSlingTestServer(t) + state.cfg.Rigs = append(state.cfg.Rigs, config.Rig{Name: "frontend", Path: "/tmp/frontend", Prefix: "FE"}) + defaultFormula := "code-review" + state.cfg.Agents[0].DefaultSlingFormula = &defaultFormula + state.stores["myrig"] = beads.NewMemStoreFrom(0, []beads.Bead{ + {ID: "FE-123", Title: "wrong-store task", Type: "task", Status: "open"}, + }, nil) + + body := `{"target":"myrig/worker","bead":"FE-123","title":"Review this","force":true}` + rec := httptest.NewRecorder() + h.ServeHTTP(rec, newPostRequest(cityURL(state, "/sling"), strings.NewReader(body))) + + if rec.Code != http.StatusBadRequest { + t.Fatalf("status = %d, want 400; body = %s", rec.Code, rec.Body.String()) + } + var problem struct { + Type string `json:"type"` + Detail string `json:"detail"` + } + if err := json.NewDecoder(rec.Body).Decode(&problem); err != nil { + t.Fatalf("decode: %v", err) + } + if problem.Type != "urn:gascity:error:sling-missing-bead" { + t.Fatalf("type = %q, want missing-bead discriminator; body = %s", problem.Type, rec.Body.String()) + } + if !strings.Contains(problem.Detail, "rig:frontend") { + t.Fatalf("detail = %s, want prefix store ref", problem.Detail) + } +} + +func TestSlingProblemTypesDocumentedInOpenAPI(t *testing.T) { + spec := readCommittedOpenAPISpec(t) + components, err := json.Marshal(spec["components"]) + if err != nil { + t.Fatalf("marshal components: %v", err) + } + for _, want := range []string{slingMissingBeadProblemType, slingCrossRigProblemType} { + if !bytes.Contains(components, []byte(want)) { + t.Fatalf("OpenAPI components missing problem type %q", want) + } + } +} + +func TestDocumentProblemTypesIsIdempotent(t *testing.T) { + state := newFakeMutatorState(t) + sm := NewSupervisorMux(&stateCityResolver{state: state}, false, "test", time.Now()) + oapi := sm.humaAPI.OpenAPI() + + documentProblemTypes(oapi) + documentProblemTypes(oapi) + + errorModel := oapi.Components.Schemas.Map()["ErrorModel"] + if errorModel == nil || errorModel.Properties == nil { + t.Fatal("ErrorModel schema missing") + } + typeSchema := errorModel.Properties["type"] + if typeSchema == nil { + t.Fatal("ErrorModel.type schema missing") + } + counts := map[string]int{} + for _, example := range typeSchema.Examples { + if s, ok := example.(string); ok { + counts[s]++ + } + } + for _, problemType := range documentedProblemTypes { + if counts[problemType] != 1 { + t.Fatalf("example count for %q = %d, want 1", problemType, counts[problemType]) + } + } +} + func TestSlingLogsMalformedCustomSlingQueryWarning(t *testing.T) { srv, state := newSlingTestServer(t) for i := range state.cfg.Agents { diff --git a/internal/api/huma_handlers_sling.go b/internal/api/huma_handlers_sling.go index f996c5ff76..34a48017ee 100644 --- a/internal/api/huma_handlers_sling.go +++ b/internal/api/huma_handlers_sling.go @@ -28,6 +28,7 @@ func (s *Server) humaHandleSling(ctx context.Context, input *SlingInput) (*Sling Vars: input.Body.Vars, ScopeKind: input.Body.ScopeKind, ScopeRef: input.Body.ScopeRef, + Force: input.Body.Force, } if body.Target == "" { @@ -100,7 +101,7 @@ func (s *Server) humaHandleSling(ctx context.Context, input *SlingInput) (*Sling // only a string detail, so we build the Problem Details error // manually with structured extensions. if conflict != nil && status == http.StatusConflict { - storeRef := s.slingStoreRef(body.Rig, agentCfg) + storeRef := s.slingStoreRef(body.Rig, agentCfg, slingStoreBeadID(body)) hint := sourceWorkflowCleanupHint(conflict.SourceBeadID, storeRef) return nil, &huma.ErrorModel{ Status: http.StatusConflict, @@ -113,6 +114,25 @@ func (s *Server) humaHandleSling(ctx context.Context, input *SlingInput) (*Sling }, } } + if status >= http.StatusInternalServerError { + return nil, huma.Error500InternalServerError(message) + } + if code == "missing_bead" { + return nil, &huma.ErrorModel{ + Type: slingMissingBeadProblemType, + Status: http.StatusBadRequest, + Title: http.StatusText(http.StatusBadRequest), + Detail: message, + } + } + if code == "cross_rig" { + return nil, &huma.ErrorModel{ + Type: slingCrossRigProblemType, + Status: http.StatusBadRequest, + Title: http.StatusText(http.StatusBadRequest), + Detail: message, + } + } return nil, huma.Error400BadRequest(message) } diff --git a/internal/api/huma_types_sling.go b/internal/api/huma_types_sling.go index 05ea6d6097..19159e1b0e 100644 --- a/internal/api/huma_types_sling.go +++ b/internal/api/huma_types_sling.go @@ -23,5 +23,6 @@ type SlingInput struct { Vars map[string]string `json:"vars,omitempty" doc:"Formula variables."` ScopeKind string `json:"scope_kind,omitempty" doc:"Scope kind (city or rig)."` ScopeRef string `json:"scope_ref,omitempty" doc:"Scope reference."` + Force bool `json:"force,omitempty" doc:"Bypass cross-rig guards; for direct bead routes, also bypass missing-bead validation. Formula-backed graph routes may replace existing live workflow roots but still require the source bead to exist."` } } diff --git a/internal/api/openapi.json b/internal/api/openapi.json index ee1f1e44e7..e939ed7f91 100644 --- a/internal/api/openapi.json +++ b/internal/api/openapi.json @@ -1796,10 +1796,16 @@ "default": "about:blank", "description": "A URI reference to human-readable documentation for the error.", "examples": [ - "https://example.com/errors/example" + "https://example.com/errors/example", + "urn:gascity:error:sling-missing-bead", + "urn:gascity:error:sling-cross-rig" ], "format": "uri", - "type": "string" + "type": "string", + "x-gascity-problem-types": [ + "urn:gascity:error:sling-missing-bead", + "urn:gascity:error:sling-cross-rig" + ] } }, "type": "object" @@ -5825,6 +5831,10 @@ "description": "Bead ID to sling.", "type": "string" }, + "force": { + "description": "Bypass cross-rig guards; for direct bead routes, also bypass missing-bead validation. Formula-backed graph routes may replace existing live workflow roots but still require the source bead to exist.", + "type": "boolean" + }, "formula": { "description": "Formula name for workflow launch.", "type": "string" diff --git a/internal/api/openapi_problem_types.go b/internal/api/openapi_problem_types.go new file mode 100644 index 0000000000..d301a85e51 --- /dev/null +++ b/internal/api/openapi_problem_types.go @@ -0,0 +1,45 @@ +package api + +import "github.com/danielgtaylor/huma/v2" + +const ( + slingMissingBeadProblemType = "urn:gascity:error:sling-missing-bead" + slingCrossRigProblemType = "urn:gascity:error:sling-cross-rig" +) + +var documentedProblemTypes = []string{ + slingMissingBeadProblemType, + slingCrossRigProblemType, +} + +func documentProblemTypes(oapi *huma.OpenAPI) { + if oapi == nil || oapi.Components == nil || oapi.Components.Schemas == nil { + return + } + errorModel := oapi.Components.Schemas.Map()["ErrorModel"] + if errorModel == nil || errorModel.Properties == nil { + return + } + typeSchema := errorModel.Properties["type"] + if typeSchema == nil { + return + } + for _, problemType := range documentedProblemTypes { + if !hasProblemTypeExample(typeSchema.Examples, problemType) { + typeSchema.Examples = append(typeSchema.Examples, problemType) + } + } + if typeSchema.Extensions == nil { + typeSchema.Extensions = map[string]any{} + } + typeSchema.Extensions["x-gascity-problem-types"] = append([]string(nil), documentedProblemTypes...) +} + +func hasProblemTypeExample(examples []any, problemType string) bool { + for _, example := range examples { + if s, ok := example.(string); ok && s == problemType { + return true + } + } + return false +} diff --git a/internal/api/supervisor.go b/internal/api/supervisor.go index f0e3f40d12..70f67beddb 100644 --- a/internal/api/supervisor.go +++ b/internal/api/supervisor.go @@ -90,6 +90,7 @@ func NewSupervisorMux(resolver CityResolver, readOnly bool, version string, star } sm.registerSupervisorRoutes() sm.registerCityRoutes() + documentProblemTypes(sm.humaAPI.OpenAPI()) // /svc/* workspace-service pass-through. This is the single remaining // non-Huma registration on the supervisor — untyped by design (the // proxy passes bodies through to external service processes, which diff --git a/internal/sling/sling.go b/internal/sling/sling.go index 3bd6a60733..186ee552ba 100644 --- a/internal/sling/sling.go +++ b/internal/sling/sling.go @@ -6,6 +6,7 @@ package sling import ( "context" + "errors" "fmt" "io" "os" @@ -50,8 +51,11 @@ type SlingOpts struct { Nudge bool Force bool DryRun bool - ScopeKind string - ScopeRef string + // InlineText is set only by the CLI path for ad-hoc task text. API + // callers always provide explicit bead or formula references. + InlineText bool + ScopeKind string + ScopeRef string } // AgentResolver resolves an agent name to a config.Agent. @@ -103,6 +107,9 @@ type SlingDeps struct { Runner SlingRunner Store beads.Store StoreRef string + // ValidationQuerier overrides Store for existence checks when a caller has + // already resolved the bead through a narrower view. + ValidationQuerier BeadQuerier // SourceWorkflowStores lists every bead store that may contain workflow // roots for source-workflow singleton checks and recovery. SourceWorkflowStores func() ([]SourceWorkflowStore, error) @@ -185,7 +192,10 @@ type RouteOpts struct { Nudge bool Force bool DryRun bool - SkipPoke bool + // InlineText is set only by the CLI path for ad-hoc task text. API + // callers always provide explicit bead or formula references. + InlineText bool + SkipPoke bool } // FormulaOpts holds options for formula-based operations. @@ -213,6 +223,7 @@ func (s *Sling) RouteBead(_ context.Context, beadID string, target config.Agent, Force: opts.Force, SkipPoke: opts.SkipPoke, DryRun: opts.DryRun, + InlineText: opts.InlineText, }, s.deps, s.deps.Store) } @@ -264,6 +275,7 @@ func (s *Sling) ExpandConvoy(_ context.Context, convoyID string, target config.A Force: opts.Force, SkipPoke: opts.SkipPoke, DryRun: opts.DryRun, + InlineText: opts.InlineText, }, s.deps, querier) } @@ -293,6 +305,9 @@ func SlingTracef(format string, args ...any) { // FindRigByPrefix finds a rig whose effective prefix matches (case-insensitive). func FindRigByPrefix(cfg *config.City, prefix string) (config.Rig, bool) { + if cfg == nil { + return config.Rig{}, false + } lp := strings.ToLower(prefix) for _, r := range cfg.Rigs { if strings.ToLower(r.EffectivePrefix()) == lp { @@ -302,6 +317,12 @@ func FindRigByPrefix(cfg *config.City, prefix string) (config.Rig, bool) { return config.Rig{}, false } +// IsHQPrefix reports whether prefix matches the city's HQ bead prefix. +func IsHQPrefix(cfg *config.City, prefix string) bool { + prefix = strings.TrimSpace(prefix) + return cfg != nil && prefix != "" && strings.EqualFold(prefix, config.EffectiveHQPrefix(cfg)) +} + // RigDirForBead resolves the rig directory for a bead ID by extracting // the bead prefix and looking up the rig path. func RigDirForBead(cfg *config.City, beadID string) string { @@ -398,44 +419,82 @@ func RigPrefixForAgent(a config.Agent, cfg *config.City) string { // CheckCrossRig returns a warning message if a rig-scoped agent receives // a bead from a different rig. Returns "" if routing is safe. func CheckCrossRig(beadID string, a config.Agent, cfg *config.City) string { - if cfg == nil || a.Dir == "" { + err := CrossRigRouteError(beadID, a, cfg) + if err == nil { return "" } + return err.Error() +} + +// CrossRigError reports that a rig-scoped agent was asked to route a bead from +// a different rig. +type CrossRigError struct { + BeadID string + BeadPrefix string + Target string + RigPrefix string +} + +// Error returns the cross-rig routing diagnostic. +func (e *CrossRigError) Error() string { + return fmt.Sprintf("cross-rig routing — bead %s (prefix %q) → agent %s (rig prefix %q)", e.BeadID, e.BeadPrefix, e.Target, e.RigPrefix) +} + +// CrossRigRouteError returns a typed cross-rig error when routing is unsafe. +func CrossRigRouteError(beadID string, a config.Agent, cfg *config.City) *CrossRigError { + if cfg == nil || a.Dir == "" { + return nil + } bp := BeadPrefix(beadID) if bp == "" { - return "" + return nil } rp := RigPrefixForAgent(a, cfg) if rp == "" { - return "" + return nil } if strings.EqualFold(bp, rp) { - return "" + return nil + } + return &CrossRigError{ + BeadID: beadID, + BeadPrefix: bp, + Target: a.QualifiedName(), + RigPrefix: rp, } - return fmt.Sprintf("cross-rig routing — bead %s (prefix %q) → agent %s (rig prefix %q)", beadID, bp, a.QualifiedName(), rp) } -// BeadExistsInStore checks if a bead exists in the given store. -func BeadExistsInStore(store beads.Store, id string) bool { - if store == nil { - return false +// ProbeBeadInStore checks if a bead exists in the given store and surfaces +// non-not-found lookup errors. +func ProbeBeadInStore(store beads.Store, id string) (bool, error) { + return probeBeadInQuerier(store, id) +} + +func probeBeadInQuerier(querier BeadQuerier, id string) (bool, error) { + if querier == nil { + return false, fmt.Errorf("store unavailable") + } + _, err := querier.Get(id) + if err == nil { + return true, nil + } + if errors.Is(err, beads.ErrNotFound) { + return false, nil } - _, err := store.Get(id) - return err == nil + return false, err } -// LooksLikeBeadID reports whether a string looks like a bead ID. +// LooksLikeBeadID reports whether a string loosely resembles a bead ID. +// +// Deprecated: use BeadIDParts for the stricter routing heuristic. func LooksLikeBeadID(s string) bool { s = strings.TrimSpace(s) if s == "" { return false } - // Bead IDs are typically prefix-NNN or just NNN. - // They don't contain spaces, slashes, or common text punctuation. if strings.ContainsAny(s, " \t\n/\\") { return false } - // If it contains a digit, it's likely a bead ID. for _, c := range s { if c >= '0' && c <= '9' { return true @@ -444,6 +503,77 @@ func LooksLikeBeadID(s string) bool { return false } +// BeadIDParts trims surrounding whitespace and parses a bead-like string into +// prefix and base suffix, ignoring any hierarchical ".child" suffix. It +// validates the structured bead-ID shape used by the CLI's stricter routing +// heuristic. +func BeadIDParts(s string) (prefix, baseSuffix string, ok bool) { + s = strings.TrimSpace(s) + if s == "" || strings.ContainsAny(s, " \t\n") { + return "", "", false + } + i := strings.Index(s, "-") + if i <= 0 || i == len(s)-1 || strings.Count(s, "-") != 1 { + return "", "", false + } + prefix = s[:i] + for idx, c := range prefix { + if idx == 0 { + if ('A' > c || c > 'Z') && ('a' > c || c > 'z') { + return "", "", false + } + continue + } + if ('0' > c || c > '9') && ('a' > c || c > 'z') && ('A' > c || c > 'Z') { + return "", "", false + } + } + suffix := s[i+1:] + baseSuffix = suffix + if dot := strings.IndexByte(suffix, '.'); dot > 0 { + baseSuffix = suffix[:dot] + } + if baseSuffix == "" { + return "", "", false + } + for _, c := range baseSuffix { + if ('0' > c || c > '9') && ('a' > c || c > 'z') && ('A' > c || c > 'Z') { + return "", "", false + } + } + return prefix, baseSuffix, true +} + +// MissingBeadError reports that a requested bead reference did not resolve in +// the target store. +type MissingBeadError struct { + BeadID string + StoreRef string +} + +// Error returns the missing-bead diagnostic. +func (e *MissingBeadError) Error() string { + return fmt.Sprintf("bead %q not found in store %s", e.BeadID, e.StoreRef) +} + +// BeadLookupError reports an operational failure while checking whether a bead +// exists in the target store. +type BeadLookupError struct { + BeadID string + StoreRef string + Err error +} + +// Error returns the lookup-failure diagnostic. +func (e *BeadLookupError) Error() string { + return fmt.Sprintf("getting bead %q from store %s: %v", e.BeadID, e.StoreRef, e.Err) +} + +// Unwrap returns the underlying lookup failure. +func (e *BeadLookupError) Unwrap() error { + return e.Err +} + func normalizeSlingQuery(query string) string { return strings.Join(strings.Fields(query), " ") } diff --git a/internal/sling/sling_core.go b/internal/sling/sling_core.go index a4a1799f8d..c7c69c787b 100644 --- a/internal/sling/sling_core.go +++ b/internal/sling/sling_core.go @@ -75,15 +75,19 @@ func preflight(opts SlingOpts, deps SlingDeps, querier BeadQuerier) (SlingResult result.PoolEmpty = true } - // Cross-rig guard. - if !opts.IsFormula && !opts.Force && !opts.DryRun { - if msg := CheckCrossRig(opts.BeadOrFormula, a, deps.Cfg); msg != "" { - return result, fmt.Errorf("%s", msg) + if shouldValidateExistingBead(opts) { + if err := validateExistingBead(opts.BeadOrFormula, deps); err != nil { + return result, err + } + } + if shouldGuardCrossRig(opts) { + if err := CrossRigRouteError(opts.BeadOrFormula, a, deps.Cfg); err != nil { + return result, err } } // Pre-flight idempotency check. - if !opts.IsFormula && !opts.Force { + if shouldCheckBeadState(opts) { check := CheckBeadState(querier, opts.BeadOrFormula, a, deps) if check.Idempotent { result.Idempotent = true @@ -115,6 +119,51 @@ func preflight(opts SlingOpts, deps SlingDeps, querier BeadQuerier) (SlingResult return result, nil } +func shouldValidateExistingBead(opts SlingOpts) bool { + if opts.IsFormula || (opts.DryRun && opts.InlineText) { + return false + } + return !opts.Force || usesFormulaBackedRoute(opts) +} + +func usesFormulaBackedRoute(opts SlingOpts) bool { + return opts.OnFormula != "" || (!opts.NoFormula && opts.Target.EffectiveDefaultSlingFormula() != "") +} + +func shouldGuardCrossRig(opts SlingOpts) bool { + return !opts.IsFormula && !opts.Force && !opts.DryRun +} + +func shouldCheckBeadState(opts SlingOpts) bool { + return !opts.IsFormula && !opts.Force && (!opts.DryRun || !opts.InlineText) +} + +func validateExistingBead(beadID string, deps SlingDeps) error { + querier := deps.ValidationQuerier + if querier == nil { + querier = deps.Store + } + return validateExistingBeadInQuerier(beadID, deps.StoreRef, querier) +} + +func validateExistingBeadInQuerier(beadID, storeRef string, querier BeadQuerier) error { + storeRef = strings.TrimSpace(storeRef) + if storeRef == "" { + storeRef = "local" + } + if querier == nil { + return &BeadLookupError{BeadID: beadID, StoreRef: storeRef, Err: errors.New("store not configured")} + } + exists, err := probeBeadInQuerier(querier, beadID) + if err != nil { + return &BeadLookupError{BeadID: beadID, StoreRef: storeRef, Err: err} + } + if exists { + return nil + } + return &MissingBeadError{BeadID: beadID, StoreRef: storeRef} +} + // slingFormula handles the --formula dispatch path. func slingFormula(opts SlingOpts, deps SlingDeps) (SlingResult, error) { a := opts.Target @@ -294,25 +343,43 @@ func finalize(opts SlingOpts, deps SlingDeps, beadID, method string, result Slin // Auto-convoy. if !opts.NoConvoy && !opts.IsFormula && deps.Store != nil { - var convoyLabels []string - if opts.Owned { - convoyLabels = []string{"owned"} - } - convoy, err := deps.Store.Create(beads.Bead{ - Title: fmt.Sprintf("sling-%s", beadID), - Type: "convoy", - Labels: convoyLabels, - }) + createAutoConvoy := true + exists, err := ProbeBeadInStore(deps.Store, beadID) if err != nil { result.MetadataErrors = append(result.MetadataErrors, - fmt.Sprintf("creating auto-convoy: %v", err)) - } else { - parentID := convoy.ID - if err := deps.Store.Update(beadID, beads.UpdateOpts{ParentID: &parentID}); err != nil { + fmt.Sprintf("checking bead before auto-convoy: %v", err)) + createAutoConvoy = false + } else if !exists { + if opts.Force { result.MetadataErrors = append(result.MetadataErrors, - fmt.Sprintf("linking bead to convoy: %v", err)) + fmt.Sprintf("forced dispatch skipped missing-bead validation for %s; no local auto-convoy created", beadID)) } else { - result.ConvoyID = convoy.ID + result.MetadataErrors = append(result.MetadataErrors, + fmt.Sprintf("skipping auto-convoy: bead %s is not present in the local store", beadID)) + } + createAutoConvoy = false + } + if createAutoConvoy { + var convoyLabels []string + if opts.Owned { + convoyLabels = []string{"owned"} + } + convoy, err := deps.Store.Create(beads.Bead{ + Title: fmt.Sprintf("sling-%s", beadID), + Type: "convoy", + Labels: convoyLabels, + }) + if err != nil { + result.MetadataErrors = append(result.MetadataErrors, + fmt.Sprintf("creating auto-convoy: %v", err)) + } else { + parentID := convoy.ID + if err := deps.Store.Update(beadID, beads.UpdateOpts{ParentID: &parentID}); err != nil { + result.MetadataErrors = append(result.MetadataErrors, + fmt.Sprintf("linking bead to convoy: %v", err)) + } else { + result.ConvoyID = convoy.ID + } } } } @@ -725,11 +792,34 @@ func DoSlingBatch(opts SlingOpts, deps SlingDeps, querier BeadChildQuerier) (Sli return DoSling(opts, deps, querier) } + containerQuerier := BeadQuerier(querier) b, err := querier.Get(opts.BeadOrFormula) if err != nil { - singleOpts := opts - singleOpts.IsFormula = false - return DoSling(singleOpts, deps, querier) + if !errors.Is(err, beads.ErrNotFound) { + return SlingResult{Target: a.QualifiedName()}, &BeadLookupError{ + BeadID: opts.BeadOrFormula, + StoreRef: deps.StoreRef, + Err: err, + } + } + if selected, ok := selectedStoreContainer(opts, deps); ok { + b = selected + // The caller's querier could not see the container, so deps.Store + // becomes authoritative for both validation and child expansion. + querier = deps.Store + containerQuerier = deps.Store + } else { + singleOpts := opts + singleOpts.IsFormula = false + return DoSling(singleOpts, deps, querier) + } + } + if b.Type == "epic" || beads.IsContainerType(b.Type) { + if shouldValidateExistingBead(opts) { + if err := validateExistingBeadInQuerier(opts.BeadOrFormula, deps.StoreRef, containerQuerier); err != nil { + return SlingResult{Target: a.QualifiedName()}, err + } + } } if b.Type == "epic" { return SlingResult{}, fmt.Errorf("bead %s is an epic; first-class support is for convoys only", b.ID) @@ -738,7 +828,9 @@ func DoSlingBatch(opts SlingOpts, deps SlingDeps, querier BeadChildQuerier) (Sli if !beads.IsContainerType(b.Type) { singleOpts := opts singleOpts.IsFormula = false - return DoSling(singleOpts, deps, querier) + singleDeps := deps + singleDeps.ValidationQuerier = containerQuerier + return DoSling(singleOpts, singleDeps, querier) } children, err := querier.List(beads.ListQuery{ @@ -765,8 +857,8 @@ func DoSlingBatch(opts SlingOpts, deps SlingDeps, querier BeadChildQuerier) (Sli // Cross-rig guard on container. if !opts.Force && !opts.DryRun { - if msg := CheckCrossRig(b.ID, a, deps.Cfg); msg != "" { - return SlingResult{}, fmt.Errorf("%s", msg) + if err := CrossRigRouteError(b.ID, a, deps.Cfg); err != nil { + return SlingResult{}, err } } @@ -939,3 +1031,14 @@ func DoSlingBatch(opts SlingOpts, deps SlingDeps, querier BeadChildQuerier) (Sli } return batchResult, nil } + +func selectedStoreContainer(opts SlingOpts, deps SlingDeps) (beads.Bead, bool) { + if deps.Store == nil { + return beads.Bead{}, false + } + b, err := deps.Store.Get(opts.BeadOrFormula) + if err != nil { + return beads.Bead{}, false + } + return b, b.Type == "epic" || beads.IsContainerType(b.Type) +} diff --git a/internal/sling/sling_test.go b/internal/sling/sling_test.go index 937d532ac7..9214647645 100644 --- a/internal/sling/sling_test.go +++ b/internal/sling/sling_test.go @@ -11,8 +11,10 @@ import ( "testing" "github.com/gastownhall/gascity/internal/beads" + beadsexec "github.com/gastownhall/gascity/internal/beads/exec" "github.com/gastownhall/gascity/internal/config" "github.com/gastownhall/gascity/internal/formulatest" + "github.com/gastownhall/gascity/internal/fsys" "github.com/gastownhall/gascity/internal/runtime" "github.com/gastownhall/gascity/internal/sourceworkflow" ) @@ -32,6 +34,15 @@ type fakeRunner struct { rules []fakeRunnerRule } +type getErrStore struct { + beads.Store + err error +} + +func (s *getErrStore) Get(_ string) (beads.Bead, error) { + return beads.Bead{}, s.err +} + type closeAllFailMemStore struct { *beads.MemStore failCloseAllCalls int @@ -77,7 +88,22 @@ func (r *fakeRunner) run(dir, command string, env map[string]string) (string, er return "", nil } -func intPtr(v int) *int { return &v } +func intPtr(v int) *int { return &v } +func stringPtr(v string) *string { return &v } + +func seededStore(ids ...string) *beads.MemStore { + seed := make([]beads.Bead, 0, len(ids)) + for _, id := range ids { + seed = append(seed, beads.Bead{ + ID: id, + Title: id, + Type: "task", + Status: "open", + Metadata: map[string]string{}, + }) + } + return beads.NewMemStoreFrom(0, seed, nil) +} // testResolver implements AgentResolver for tests using exact match. type testResolver struct{} @@ -324,6 +350,7 @@ func TestDoSlingBeadToFixedAgent(t *testing.T) { a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} deps := testDeps(cfg, sp, runner.run) + deps.Store = seededStore("BL-42") result, err := DoSling(testOpts(a, "BL-42"), deps, nil) if err != nil { t.Fatalf("DoSling error: %v", err) @@ -346,6 +373,7 @@ func TestDoSlingSuspendedAgentWarns(t *testing.T) { a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1), Suspended: true} deps := testDeps(cfg, sp, runner.run) + deps.Store = seededStore("BL-42") result, err := DoSling(testOpts(a, "BL-42"), deps, nil) if err != nil { t.Fatalf("DoSling error: %v", err) @@ -363,6 +391,7 @@ func TestDoSlingRunnerError(t *testing.T) { a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} deps := testDeps(cfg, sp, runner.run) + deps.Store = seededStore("BL-42") _, err := DoSling(testOpts(a, "BL-42"), deps, nil) if err == nil { @@ -406,6 +435,7 @@ func TestDoSlingCrossRigBlocks(t *testing.T) { a := config.Agent{Name: "worker", Dir: "other", MaxActiveSessions: intPtr(1)} deps := testDeps(cfg, sp, runner.run) + deps.Store = seededStore("BL-42") _, err := DoSling(testOpts(a, "BL-42"), deps, nil) if err == nil { @@ -579,6 +609,7 @@ func TestDoSlingCustomSlingQueryExpandsTemplateContext(t *testing.T) { deps := testDeps(cfg, runtime.NewFake(), runner.run) deps.CityPath = cityPath deps.CityName = "" + deps.Store = seededStore("FR-99") opts := testOpts(a, "FR-99") result, err := DoSling(opts, deps, nil) if err != nil { @@ -616,6 +647,7 @@ func TestNewSlingValid(t *testing.T) { func TestSlingRouteBead(t *testing.T) { runner := newFakeRunner() deps := testDeps(&config.City{Workspace: config.Workspace{Name: "test"}}, runtime.NewFake(), runner.run) + deps.Store = seededStore("BL-42") s, err := New(deps) if err != nil { t.Fatal(err) @@ -640,6 +672,337 @@ func TestSlingRouteBead(t *testing.T) { } } +func TestSlingRouteBeadRejectsMissingBead(t *testing.T) { + runner := newFakeRunner() + deps := testDeps(&config.City{Workspace: config.Workspace{Name: "test"}}, runtime.NewFake(), runner.run) + deps.Store = beads.NewMemStore() + s, err := New(deps) + if err != nil { + t.Fatal(err) + } + + a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} + _, err = s.RouteBead(context.Background(), "BL-404", a, RouteOpts{}) + if err == nil { + t.Fatal("RouteBead error = nil, want missing bead error") + } + if !strings.Contains(err.Error(), "BL-404") || !strings.Contains(err.Error(), "not found") { + t.Fatalf("RouteBead error = %q, want missing bead diagnostic", err) + } + if len(runner.calls) != 0 { + t.Fatalf("runner calls = %#v, want none", runner.calls) + } +} + +func TestProbeBeadInStoreTreatsBackendNotFoundAsMissing(t *testing.T) { + fileStore, err := beads.OpenFileStore(fsys.OSFS{}, filepath.Join(t.TempDir(), "beads.json")) + if err != nil { + t.Fatalf("OpenFileStore: %v", err) + } + bdStore := beads.NewBdStore(t.TempDir(), func(_, _ string, _ ...string) ([]byte, error) { + return []byte(`[]`), nil + }) + execScript := filepath.Join(t.TempDir(), "beads-provider") + if err := os.WriteFile(execScript, []byte("#!/bin/sh\ncase \"$1\" in\n get) echo 'not found' >&2; exit 1 ;;\n *) exit 2 ;;\nesac\n"), 0o755); err != nil { + t.Fatalf("write exec provider: %v", err) + } + + tests := []struct { + name string + store beads.Store + }{ + {name: "mem", store: beads.NewMemStore()}, + {name: "file", store: fileStore}, + {name: "caching", store: beads.NewCachingStoreForTest(beads.NewMemStore(), nil)}, + {name: "bd", store: bdStore}, + {name: "exec", store: beadsexec.NewStore(execScript)}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + exists, err := ProbeBeadInStore(tt.store, "NOPE-1") + if err != nil { + t.Fatalf("ProbeBeadInStore error = %v, want nil", err) + } + if exists { + t.Fatal("ProbeBeadInStore exists = true, want false") + } + }) + } +} + +func TestSlingRouteBeadDryRunRejectsMissingBead(t *testing.T) { + runner := newFakeRunner() + deps := testDeps(&config.City{Workspace: config.Workspace{Name: "test"}}, runtime.NewFake(), runner.run) + deps.Store = beads.NewMemStore() + s, err := New(deps) + if err != nil { + t.Fatal(err) + } + + a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} + _, err = s.RouteBead(context.Background(), "BL-404", a, RouteOpts{DryRun: true}) + if err == nil { + t.Fatal("RouteBead dry-run error = nil, want missing bead error") + } + if !strings.Contains(err.Error(), "BL-404") || !strings.Contains(err.Error(), "not found") { + t.Fatalf("RouteBead dry-run error = %q, want missing bead diagnostic", err) + } + if len(runner.calls) != 0 { + t.Fatalf("runner calls = %#v, want none", runner.calls) + } +} + +func TestDoSlingDryRunInlineTextSkipsMissingBeadValidation(t *testing.T) { + runner := newFakeRunner() + deps := testDeps(&config.City{Workspace: config.Workspace{Name: "test"}}, runtime.NewFake(), runner.run) + deps.Store = beads.NewMemStore() + a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} + + result, err := DoSling(SlingOpts{ + Target: a, + BeadOrFormula: "write docs", + DryRun: true, + InlineText: true, + }, deps, nil) + if err != nil { + t.Fatalf("DoSling dry-run inline text: %v", err) + } + if !result.DryRun { + t.Fatalf("DryRun = false, want true") + } + if result.BeadID != "write docs" { + t.Fatalf("BeadID = %q, want inline text", result.BeadID) + } + if len(runner.calls) != 0 { + t.Fatalf("runner calls = %#v, want none", runner.calls) + } +} + +func TestDoSlingBatchValidatesContainerInQuerierStore(t *testing.T) { + runner := newFakeRunner() + deps := testDeps(&config.City{Workspace: config.Workspace{Name: "test"}}, runtime.NewFake(), runner.run) + deps.Store = beads.NewMemStore() + querier := beads.NewMemStoreFrom(0, []beads.Bead{ + {ID: "BL-1", Title: "convoy", Type: "convoy", Status: "open", Metadata: map[string]string{}}, + {ID: "BL-2", Title: "child", Type: "task", Status: "open", ParentID: "BL-1", Metadata: map[string]string{}}, + }, nil) + + a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} + result, err := DoSlingBatch(SlingOpts{Target: a, BeadOrFormula: "BL-1"}, deps, querier) + if err != nil { + t.Fatalf("DoSlingBatch: %v", err) + } + if result.Method != "batch" { + t.Fatalf("Method = %q, want batch", result.Method) + } + if result.Routed != 1 { + t.Fatalf("Routed = %d, want 1", result.Routed) + } + if len(runner.calls) != 1 { + t.Fatalf("runner calls = %#v, want one", runner.calls) + } +} + +func TestDoSlingBatchFallsBackToSelectedStoreForContainerExpansion(t *testing.T) { + runner := newFakeRunner() + deps := testDeps(&config.City{Workspace: config.Workspace{Name: "test"}}, runtime.NewFake(), runner.run) + convoy, err := deps.Store.Create(beads.Bead{Title: "convoy", Type: "convoy"}) + if err != nil { + t.Fatalf("create convoy: %v", err) + } + if _, err := deps.Store.Create(beads.Bead{Title: "child", Type: "task", Status: "open", ParentID: convoy.ID}); err != nil { + t.Fatalf("create child: %v", err) + } + wrongQuerier := beads.NewMemStore() + + a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} + result, err := DoSlingBatch(SlingOpts{Target: a, BeadOrFormula: convoy.ID}, deps, wrongQuerier) + if err != nil { + t.Fatalf("DoSlingBatch: %v", err) + } + if result.Method != "batch" { + t.Fatalf("Method = %q, want batch", result.Method) + } + if result.Routed != 1 { + t.Fatalf("Routed = %d, want 1", result.Routed) + } +} + +func TestDoSlingBatchUsesCallerQuerierChildrenWhenContainerExistsThere(t *testing.T) { + runner := newFakeRunner() + deps := testDeps(&config.City{Workspace: config.Workspace{Name: "test"}}, runtime.NewFake(), runner.run) + deps.Store = beads.NewMemStoreFrom(0, []beads.Bead{ + {ID: "BL-1", Title: "convoy", Type: "convoy", Status: "open", Metadata: map[string]string{}}, + {ID: "BL-store-only", Title: "store child", Type: "task", Status: "open", ParentID: "BL-1", Metadata: map[string]string{}}, + }, nil) + querier := beads.NewMemStoreFrom(0, []beads.Bead{ + {ID: "BL-1", Title: "convoy", Type: "convoy", Status: "open", Metadata: map[string]string{}}, + {ID: "BL-query-only", Title: "query child", Type: "task", Status: "open", ParentID: "BL-1", Metadata: map[string]string{}}, + }, nil) + + a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} + result, err := DoSlingBatch(SlingOpts{Target: a, BeadOrFormula: "BL-1"}, deps, querier) + if err != nil { + t.Fatalf("DoSlingBatch: %v", err) + } + if result.Routed != 1 { + t.Fatalf("Routed = %d, want 1", result.Routed) + } + if len(result.Children) != 1 || result.Children[0].BeadID != "BL-query-only" { + t.Fatalf("children = %#v, want caller querier child", result.Children) + } +} + +func TestDoSlingBatchRoutesNonContainerFoundInQuerierStore(t *testing.T) { + runner := newFakeRunner() + deps := testDeps(&config.City{Workspace: config.Workspace{Name: "test"}}, runtime.NewFake(), runner.run) + deps.Store = beads.NewMemStore() + querier := beads.NewMemStoreFrom(0, []beads.Bead{ + {ID: "BL-1", Title: "task", Type: "task", Status: "open", Metadata: map[string]string{}}, + }, nil) + + a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} + result, err := DoSlingBatch(SlingOpts{Target: a, BeadOrFormula: "BL-1"}, deps, querier) + if err != nil { + t.Fatalf("DoSlingBatch: %v", err) + } + if result.Method != "bead" { + t.Fatalf("Method = %q, want bead", result.Method) + } + if len(runner.calls) != 1 { + t.Fatalf("runner calls = %#v, want one", runner.calls) + } + if result.ConvoyID != "" { + t.Fatalf("ConvoyID = %q, want no local auto-convoy", result.ConvoyID) + } + if len(result.MetadataErrors) != 1 || !strings.Contains(result.MetadataErrors[0], "skipping auto-convoy") { + t.Fatalf("MetadataErrors = %#v, want auto-convoy skip warning", result.MetadataErrors) + } +} + +func TestDoSlingBatchDoesNotFallbackOnQuerierLookupError(t *testing.T) { + runner := newFakeRunner() + deps := testDeps(&config.City{Workspace: config.Workspace{Name: "test"}}, runtime.NewFake(), runner.run) + deps.StoreRef = "rig:selected" + convoy, err := deps.Store.Create(beads.Bead{Title: "convoy", Type: "convoy"}) + if err != nil { + t.Fatalf("create convoy: %v", err) + } + if _, err := deps.Store.Create(beads.Bead{Title: "child", Type: "task", Status: "open", ParentID: convoy.ID}); err != nil { + t.Fatalf("create child: %v", err) + } + querier := &getErrStore{Store: beads.NewMemStore(), err: fmt.Errorf("backend unavailable")} + + a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} + _, err = DoSlingBatch(SlingOpts{Target: a, BeadOrFormula: convoy.ID}, deps, querier) + if err == nil { + t.Fatal("DoSlingBatch error = nil, want lookup error") + } + var lookup *BeadLookupError + if !errors.As(err, &lookup) { + t.Fatalf("DoSlingBatch error = %T %[1]v, want BeadLookupError", err) + } + if len(runner.calls) != 0 { + t.Fatalf("runner calls = %#v, want none", runner.calls) + } +} + +func TestSlingRouteBeadForceAllowsMissingBead(t *testing.T) { + runner := newFakeRunner() + deps := testDeps(&config.City{Workspace: config.Workspace{Name: "test"}}, runtime.NewFake(), runner.run) + deps.Store = beads.NewMemStore() + s, err := New(deps) + if err != nil { + t.Fatal(err) + } + + a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} + result, err := s.RouteBead(context.Background(), "BL-404", a, RouteOpts{Force: true}) + if err != nil { + t.Fatalf("RouteBead force: %v", err) + } + if len(runner.calls) != 1 { + t.Fatalf("runner calls = %#v, want one call", runner.calls) + } + if len(result.MetadataErrors) != 1 || !strings.Contains(result.MetadataErrors[0], "forced dispatch skipped missing-bead validation") { + t.Fatalf("MetadataErrors = %#v, want forced missing-bead warning", result.MetadataErrors) + } + all, err := deps.Store.List(beads.ListQuery{AllowScan: true}) + if err != nil { + t.Fatalf("list beads: %v", err) + } + if len(all) != 0 { + t.Fatalf("stored beads = %#v, want no orphan auto-convoy", all) + } +} + +func TestSlingRouteDefaultFormulaForceStillRejectsMissingBead(t *testing.T) { + runner := newFakeRunner() + deps := testDeps(&config.City{Workspace: config.Workspace{Name: "test"}}, runtime.NewFake(), runner.run) + deps.Store = beads.NewMemStore() + s, err := New(deps) + if err != nil { + t.Fatal(err) + } + + a := config.Agent{Name: "mayor", DefaultSlingFormula: stringPtr("code-review"), MaxActiveSessions: intPtr(1)} + _, err = s.RouteBead(context.Background(), "BL-404", a, RouteOpts{Force: true}) + if err == nil { + t.Fatal("RouteBead force with default formula error = nil, want missing bead error") + } + var missing *MissingBeadError + if !errors.As(err, &missing) { + t.Fatalf("RouteBead force with default formula error = %T %[1]v, want MissingBeadError", err) + } + all, listErr := deps.Store.List(beads.ListQuery{AllowScan: true}) + if listErr != nil { + t.Fatalf("list beads: %v", listErr) + } + if len(all) != 0 { + t.Fatalf("stored beads = %#v, want no orphan formula state", all) + } + if len(runner.calls) != 0 { + t.Fatalf("runner calls = %#v, want none", runner.calls) + } +} + +func TestValidateExistingBeadInQuerierNilIsLookupError(t *testing.T) { + err := validateExistingBeadInQuerier("BL-42", "rig:missing", nil) + var lookup *BeadLookupError + if !errors.As(err, &lookup) { + t.Fatalf("error = %T %[1]v, want BeadLookupError", err) + } + var missing *MissingBeadError + if errors.As(err, &missing) { + t.Fatalf("error = %T %[1]v, should not report missing bead for nil store", err) + } +} + +func TestSlingRouteBeadSurfacesStoreLookupError(t *testing.T) { + runner := newFakeRunner() + deps := testDeps(&config.City{Workspace: config.Workspace{Name: "test"}}, runtime.NewFake(), runner.run) + deps.Store = &getErrStore{Store: beads.NewMemStore(), err: fmt.Errorf("backend unavailable")} + s, err := New(deps) + if err != nil { + t.Fatal(err) + } + + a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} + _, err = s.RouteBead(context.Background(), "BL-42", a, RouteOpts{}) + if err == nil { + t.Fatal("RouteBead error = nil, want store lookup failure") + } + if !strings.Contains(err.Error(), "backend unavailable") { + t.Fatalf("RouteBead error = %q, want backend failure", err) + } + if strings.Contains(err.Error(), "not found") { + t.Fatalf("RouteBead error = %q, want store failure, not not-found", err) + } + if len(runner.calls) != 0 { + t.Fatalf("runner calls = %#v, want none", runner.calls) + } +} + func TestSlingLaunchFormula(t *testing.T) { runner := newFakeRunner() cfg := &config.City{Workspace: config.Workspace{Name: "test"}} @@ -681,6 +1044,7 @@ func TestSlingRouteBeadWithTypedRouter(t *testing.T) { cfg := &config.City{Workspace: config.Workspace{Name: "test"}} deps := testDeps(cfg, runtime.NewFake(), newFakeRunner().run) deps.Router = router + deps.Store = seededStore("BL-42") s, err := New(deps) if err != nil { @@ -733,6 +1097,61 @@ func TestSlingAttachFormula(t *testing.T) { } } +func TestSlingAttachFormulaRejectsMissingBead(t *testing.T) { + runner := newFakeRunner() + cfg := &config.City{Workspace: config.Workspace{Name: "test"}} + deps := testDeps(cfg, runtime.NewFake(), runner.run) + deps.Store = beads.NewMemStore() + + s, err := New(deps) + if err != nil { + t.Fatal(err) + } + a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} + _, err = s.AttachFormula(context.Background(), "code-review", "BL-404", a, FormulaOpts{}) + if err == nil { + t.Fatal("AttachFormula error = nil, want missing bead error") + } + var missing *MissingBeadError + if !errors.As(err, &missing) { + t.Fatalf("AttachFormula error = %T %[1]v, want MissingBeadError", err) + } + if len(runner.calls) != 0 { + t.Fatalf("runner calls = %#v, want none", runner.calls) + } +} + +func TestSlingAttachFormulaForceStillRejectsMissingBead(t *testing.T) { + runner := newFakeRunner() + cfg := &config.City{Workspace: config.Workspace{Name: "test"}} + deps := testDeps(cfg, runtime.NewFake(), runner.run) + deps.Store = beads.NewMemStore() + + s, err := New(deps) + if err != nil { + t.Fatal(err) + } + a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} + _, err = s.AttachFormula(context.Background(), "code-review", "BL-404", a, FormulaOpts{Force: true}) + if err == nil { + t.Fatal("AttachFormula force error = nil, want missing bead error") + } + var missing *MissingBeadError + if !errors.As(err, &missing) { + t.Fatalf("AttachFormula force error = %T %[1]v, want MissingBeadError", err) + } + all, listErr := deps.Store.List(beads.ListQuery{AllowScan: true}) + if listErr != nil { + t.Fatalf("list beads: %v", listErr) + } + if len(all) != 0 { + t.Fatalf("stored beads = %#v, want no orphan formula state", all) + } + if len(runner.calls) != 0 { + t.Fatalf("runner calls = %#v, want none", runner.calls) + } +} + func TestSlingAttachGraphFormulaRejectsExistingLiveRoot(t *testing.T) { formulaDir := t.TempDir() if err := os.WriteFile(filepath.Join(formulaDir, "graph-work.formula.toml"), []byte(` @@ -1901,6 +2320,7 @@ func TestDoSlingPoolEmptyWarns(t *testing.T) { cfg := &config.City{Workspace: config.Workspace{Name: "test"}} a := config.Agent{Name: "pool", MaxActiveSessions: intPtr(0)} deps := testDeps(cfg, runtime.NewFake(), runner.run) + deps.Store = seededStore("BL-1") result, err := DoSling(testOpts(a, "BL-1"), deps, nil) if err != nil { t.Fatalf("DoSling: %v", err) @@ -1937,6 +2357,7 @@ func TestFinalizeNoConvoyWhenSuppressed(t *testing.T) { cfg := &config.City{Workspace: config.Workspace{Name: "test"}} a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} deps := testDeps(cfg, runtime.NewFake(), runner.run) + deps.Store = seededStore("BL-1") result, err := DoSling(SlingOpts{ Target: a, BeadOrFormula: "BL-1", NoConvoy: true, @@ -2026,6 +2447,7 @@ func TestDoSlingDryRun(t *testing.T) { cfg := &config.City{Workspace: config.Workspace{Name: "test"}} a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} deps := testDeps(cfg, runtime.NewFake(), runner.run) + deps.Store = seededStore("BL-1") result, err := DoSling(SlingOpts{ Target: a, BeadOrFormula: "BL-1", DryRun: true, @@ -2046,6 +2468,7 @@ func TestDoSlingNudgeSignal(t *testing.T) { cfg := &config.City{Workspace: config.Workspace{Name: "test"}} a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1)} deps := testDeps(cfg, runtime.NewFake(), runner.run) + deps.Store = seededStore("BL-1") result, err := DoSling(SlingOpts{ Target: a, BeadOrFormula: "BL-1", Nudge: true, @@ -2067,6 +2490,7 @@ func TestDoSlingSuspendedAgentWarnsEvenOnFailure(t *testing.T) { a := config.Agent{Name: "mayor", MaxActiveSessions: intPtr(1), Suspended: true} deps := testDeps(cfg, runtime.NewFake(), runner.run) + deps.Store = seededStore("BL-1") result, err := DoSling(testOpts(a, "BL-1"), deps, nil) if err == nil { @@ -2089,6 +2513,7 @@ func TestDoSlingNonexistentTargetFails(_ *testing.T) { } nonexistent := config.Agent{Name: "nonexistent", MaxActiveSessions: intPtr(1)} deps := testDeps(cfg, runtime.NewFake(), runner.run) + deps.Store = seededStore("BL-1") // Cross-rig and routing should still work even if agent doesn't exist in config. // The runner will fail, but the domain doesn't validate agent existence. result, err := DoSling(testOpts(nonexistent, "BL-1"), deps, nil) @@ -2107,6 +2532,7 @@ func TestDoSlingPoolEmptyWarnsOnFailure(t *testing.T) { cfg := &config.City{Workspace: config.Workspace{Name: "test"}} a := config.Agent{Name: "empty-pool", MaxActiveSessions: intPtr(0)} deps := testDeps(cfg, runtime.NewFake(), runner.run) + deps.Store = seededStore("BL-1") result, err := DoSling(testOpts(a, "BL-1"), deps, nil) if err == nil { From e1be2b246916ef36e81a5d4ca2b125a555e6aedb Mon Sep 17 00:00:00 2001 From: Chris Sells Date: Tue, 21 Apr 2026 22:17:03 -0700 Subject: [PATCH 05/85] api: async city lifecycle endpoints --- .githooks/pre-commit | 36 +- AGENTS.md | 8 + CONTRIBUTING.md | 14 +- cmd/gc/city_registry.go | 74 + cmd/gc/cityinit_impl.go | 305 ++ cmd/gc/cmd_supervisor.go | 79 +- cmd/gc/cmd_supervisor_city.go | 46 + cmd/gc/controller.go | 2 +- cmd/gen-client/main.go | 2 +- cmd/genspec/main.go | 5 +- contrib/session-scripts/gc-session-k8s | 6 +- docs/reference/api.md | 157 ++ docs/reference/cli.md | 168 ++ docs/reference/exec-beads-provider.md | 7 +- docs/schema/openapi.json | 2834 +++++++++++++++++-- docs/schema/openapi.txt | 2834 +++++++++++++++++-- engdocs/architecture/beads.md | 8 +- engdocs/architecture/life-of-a-bead.md | 6 +- internal/api/city_scope.go | 71 +- internal/api/client.go | 16 +- internal/api/event_payloads.go | 91 + internal/api/genclient/client_gen.go | 3228 ++++++++++++++++------ internal/api/genclient_roundtrip_test.go | 2 +- internal/api/huma_handlers_supervisor.go | 225 +- internal/api/huma_optional_param.go | 58 + internal/api/huma_spec_framework.go | 82 + internal/api/openapi.json | 2834 +++++++++++++++++-- internal/api/openapi_sync_test.go | 4 +- internal/api/server.go | 2 +- internal/api/sse.go | 127 +- internal/api/supervisor.go | 74 +- internal/api/supervisor_city_routes.go | 3 + internal/api/supervisor_test.go | 2 +- internal/api/test_helpers_test.go | 6 +- internal/cityinit/cityinit.go | 209 ++ internal/events/events.go | 68 +- specs/architecture.md | 125 +- specs/huma-usage.md | 341 +++ test/integration/huma_binary_test.go | 416 ++- 39 files changed, 12761 insertions(+), 1814 deletions(-) create mode 100644 cmd/gc/cityinit_impl.go create mode 100644 internal/api/huma_optional_param.go create mode 100644 internal/api/huma_spec_framework.go create mode 100644 internal/cityinit/cityinit.go create mode 100644 specs/huma-usage.md diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 17b4480cf9..f438212aba 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -2,9 +2,10 @@ set -euo pipefail staged_go_files=$(git diff --cached --name-only --diff-filter=ACM -- '*.go' || true) -staged_web_src=$(git diff --cached --name-only --diff-filter=ACM -- 'cmd/gc/dashboard/web/src/' 'cmd/gc/dashboard/web/index.html' 'cmd/gc/dashboard/web/public/' 'cmd/gc/dashboard/web/package.json' 'cmd/gc/dashboard/web/vite.config.ts' 'cmd/gc/dashboard/web/tsconfig.json' || true) +staged_web_src=$(git diff --cached --name-only --diff-filter=ACM -- 'cmd/gc/dashboard/web/src/' 'cmd/gc/dashboard/web/index.html' 'cmd/gc/dashboard/web/public/' 'cmd/gc/dashboard/web/package.json' 'cmd/gc/dashboard/web/openapi-ts.config.ts' 'cmd/gc/dashboard/web/vite.config.ts' 'cmd/gc/dashboard/web/tsconfig.json' || true) +staged_docs=$(git diff --cached --name-only --diff-filter=ACM -- '*.md' 'docs/**' 'engdocs/**' 'plans/**' 'specs/**' 'AGENTS.md' 'CONTRIBUTING.md' 'README.md' 'TESTING.md' || true) -if [ -z "$staged_go_files" ] && [ -z "$staged_web_src" ]; then +if [ -z "$staged_go_files" ] && [ -z "$staged_web_src" ] && [ -z "$staged_docs" ]; then exit 0 fi @@ -22,11 +23,22 @@ if [ -n "$staged_go_files" ]; then go run ./cmd/genspec git add internal/api/openapi.json docs/schema/openapi.json docs/schema/openapi.txt + go generate ./internal/api/genclient + git add internal/api/genclient/client_gen.go + # Regenerate the config/schema docs derived from Go structs and # stage every generated artifact so commits do not leave the # worktree dirty. go run ./cmd/genschema git add docs/schema/city-schema.json docs/schema/city-schema.txt docs/reference/config.md docs/reference/cli.md + + make lint + make vet + make test +fi + +if [ -n "$staged_docs" ]; then + make check-docs fi # Dashboard SPA rebuild: whenever the spec changes OR the SPA source @@ -36,20 +48,12 @@ fi if command -v npm >/dev/null 2>&1; then spec_changed=$(git diff --cached --name-only --diff-filter=ACM -- 'internal/api/openapi.json' || true) if [ -n "$spec_changed" ] || [ -n "$staged_web_src" ]; then - ( - cd cmd/gc/dashboard/web - if [ ! -d node_modules ]; then - npm install --silent - fi - npm run gen --silent - # Typecheck BEFORE build: vite's build transpiles TS to JS and - # silently ignores type errors. Without this step, SPA type - # regressions against the regenerated spec (e.g. a query param - # tightening from string to boolean) ship unnoticed. `tsc - # --noEmit` fails fast and stops the commit. - npm run typecheck --silent - npm run build --silent - ) + # Typecheck BEFORE build: vite's build transpiles TS to JS and + # silently ignores type errors. The Makefile target also runs the + # Vitest suite, builds dist/, and smoke-runs the compiled SPA via + # Vite preview so a bundle that builds but won't serve is caught + # before CI. + make dashboard-check dashboard-smoke git add cmd/gc/dashboard/web/dist fi else diff --git a/AGENTS.md b/AGENTS.md index 6e03ebdaf5..49c2b34ff5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -218,6 +218,14 @@ Before considering any task complete: - `go test ./...` passes - `go vet ./...` clean +- `.githooks/pre-commit` is active locally (`git config core.hooksPath` + prints `.githooks`) and has run for the staged change +- `make dashboard-check` passes for any change touching `internal/api/`, + `internal/api/openapi.json`, `docs/schema/openapi.*`, + `cmd/gc/dashboard/`, or generated dashboard types +- The dashboard starts locally and serves the app for dashboard/API-schema + changes; use `npm run preview -- --host 127.0.0.1 --port ` from + `cmd/gc/dashboard/web` after `make dashboard-check` - Every exported function has a doc comment - No premature abstractions - Tests cover happy path AND edge cases diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 46c8403868..09e0778073 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,7 +22,10 @@ contributors. Before making changes, read: auto-formats staged Go files and, when any Go file is staged, regenerates `internal/api/openapi.json` and `docs/schema/openapi.json` from the live supervisor. The hook stages both spec copies so the -committed spec never drifts from what the server actually serves. +committed spec never drifts from what the server actually serves. It also +runs the fast CI-equivalent gates for local changes: `make lint`, +`make vet`, and `make test` for Go changes, and `make check-docs` for +Markdown/docs/spec changes. **Dashboard SPA.** The dashboard at `cmd/gc/dashboard/web/` is a TypeScript SPA that talks directly to the supervisor's OpenAPI-typed @@ -32,9 +35,14 @@ endpoints. When `internal/api/openapi.json` or files under spec) and rebuilds `cmd/gc/dashboard/web/dist/` (the compiled bundle that the Go static server embeds via `go:embed`). The hook needs Node / npm on your PATH; if npm is missing, the hook warns and -skips the rebuild (CI enforces it). Run `make dashboard-dev` to +skips the rebuild (CI enforces it). The hook runs dashboard typecheck, +Vitest, and production build for dashboard/API-schema changes. Run +`make dashboard-dev` to iterate with Vite HMR, `make dashboard-build` to produce a fresh -bundle, `make dashboard-check` for typecheck + build + test. +bundle, `make dashboard-check` for typecheck + build + test. For +dashboard or API-schema changes, also smoke the built app with +`npm run preview -- --host 127.0.0.1 --port ` from +`cmd/gc/dashboard/web/` and load the served page before pushing. ## Development Workflow diff --git a/cmd/gc/city_registry.go b/cmd/gc/city_registry.go index f84d494f5c..c46a7e508b 100644 --- a/cmd/gc/city_registry.go +++ b/cmd/gc/city_registry.go @@ -1,12 +1,15 @@ package main import ( + "io" "path/filepath" "sync" "sync/atomic" "time" "github.com/gastownhall/gascity/internal/api" + "github.com/gastownhall/gascity/internal/events" + "github.com/gastownhall/gascity/internal/supervisor" ) // cityView is a read-only projection of managedCity, built at snapshot time. @@ -172,6 +175,77 @@ func (r *cityRegistry) Snapshot() *citySnapshot { return r.snap.Load() } +// TransientCityEventProviders implements api.TransientCityEventSource +// so the supervisor-scope event multiplexer can surface events from +// every registered city's .gc/events.jsonl — including those that +// aren't yet in the Running set. Covers four cases uniformly: +// +// - Newly scaffolded: written to cities.toml by Scaffold, but the +// reconciler hasn't picked it up yet. Not in cityRegistry snap +// yet; discovered directly from the on-disk supervisor registry. +// - Pending: reconciler picked up, cityView exists in snap.all +// with Started=false. +// - In progress: reconciler is running prepareCityForSupervisor. +// - Failed: reconciler gave up; entry lives in initFailures. +// +// Reading cities.toml directly (not just snap.all) closes the race +// between Scaffold returning 202 and the reconciler tick picking up +// the city — a client that subscribes to /v0/events/stream +// immediately after POST /v0/city sees the new city's event file in +// the multiplexer without waiting for the reconciler. +// +// Best-effort: cities whose event file is missing or unreadable are +// simply skipped. +func (r *cityRegistry) TransientCityEventProviders() map[string]events.Provider { + snap := r.snap.Load() + // Collect non-Running cities known to the runtime registry. + paths := make(map[string]string, len(snap.all)) + for _, v := range snap.all { + if v == nil || v.Started { + continue + } + name := v.Name + if name == "" { + name = filepath.Base(v.Path) + } + paths[name] = v.Path + } + // Also read cities.toml directly so cities Scaffold just + // registered — but the reconciler hasn't processed yet — are + // visible. Running cities already covered by the main + // multiplexer loop (via ListCities); skip them here. + running := make(map[string]struct{}, len(snap.byName)) + for name, v := range snap.byName { + if v != nil && v.Started { + running[name] = struct{}{} + } + } + reg := supervisor.NewRegistry(supervisor.RegistryPath()) + if entries, err := reg.List(); err == nil { + for _, e := range entries { + name := e.EffectiveName() + if _, already := running[name]; already { + continue + } + if _, already := paths[name]; already { + continue + } + paths[name] = e.Path + } + } + + out := make(map[string]events.Provider, len(paths)) + for name, path := range paths { + evPath := filepath.Join(path, ".gc", "events.jsonl") + fr, err := events.NewFileRecorder(evPath, io.Discard) + if err != nil { + continue + } + out[name] = fr + } + return out +} + // CityState returns the api.State for a named city, or nil if not found/not running. // Lock-free read from the atomic snapshot. func (r *cityRegistry) CityState(name string) api.State { diff --git a/cmd/gc/cityinit_impl.go b/cmd/gc/cityinit_impl.go new file mode 100644 index 0000000000..64262bc173 --- /dev/null +++ b/cmd/gc/cityinit_impl.go @@ -0,0 +1,305 @@ +package main + +// cityinit.Initializer implementation. Bridges the domain interface +// declared in internal/cityinit to the concrete scaffold + finalize +// helpers in this package. Supplied to api.NewSupervisorMux at +// construction so POST /v0/city calls Init in-process — no +// subprocess, no 30-second deadline, no stderr-scraping. +// +// The long-term plan is to move doInit/finalizeInit and their +// helpers into internal/cityinit so the domain logic physically +// lives in the object model (per specs/architecture.md §1). This +// bridge is the minimum viable unblocker: the HTTP API no longer +// shells out, both surfaces drive the same in-process code path via +// the same typed contract, and the refactor of where the body lives +// is a follow-up. + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/gastownhall/gascity/internal/api" + "github.com/gastownhall/gascity/internal/cityinit" + "github.com/gastownhall/gascity/internal/config" + "github.com/gastownhall/gascity/internal/events" + "github.com/gastownhall/gascity/internal/fsys" + "github.com/gastownhall/gascity/internal/supervisor" +) + +// localInitializer implements cityinit.Initializer by dispatching to +// this package's existing doInit + finalizeInit functions. +type localInitializer struct{} + +// NewInitializer returns the Initializer the supervisor uses to +// service POST /v0/city. Exported so `gc supervisor run` can wire it +// into api.NewSupervisorMux. +func NewInitializer() cityinit.Initializer { + return localInitializer{} +} + +// Scaffold runs the fast portion of city creation so the HTTP API +// handler can return 202 Accepted without blocking on the slow +// finalize work. Writes the on-disk shape (via doInit), then +// registers the city with the supervisor so the reconciler picks +// it up on its next tick. The reconciler owns finalize from there; +// readiness is signaled via city.ready / city.init_failed events on +// the supervisor event bus (see internal/api/event_payloads.go). +func (localInitializer) Scaffold(_ context.Context, req cityinit.InitRequest) (*cityinit.InitResult, error) { + if err := validateInitRequest(&req); err != nil { + return nil, err + } + dir := req.Dir + if err := os.MkdirAll(dir, 0o755); err != nil { + return nil, fmt.Errorf("creating directory %q: %w", dir, err) + } + + wiz := wizardConfig{ + configName: req.ConfigName, + provider: req.Provider, + startCommand: req.StartCommand, + bootstrapProfile: req.BootstrapProfile, + } + if wiz.configName == "" { + wiz.configName = "tutorial" + } + + if cityHasScaffoldFS(fsys.OSFS{}, dir) { + return nil, cityinit.ErrAlreadyInitialized + } + if code := doInit(fsys.OSFS{}, dir, wiz, req.NameOverride, io.Discard, io.Discard); code != 0 { + if code == initExitAlreadyInitialized { + return nil, cityinit.ErrAlreadyInitialized + } + return nil, fmt.Errorf("scaffold failed (exit %d)", code) + } + + cityName := resolveCityName(req.NameOverride, "", dir) + + // Create .gc/events.jsonl immediately and emit city.created before + // the supervisor reconciler picks up the city. Two reasons: + // + // 1. The supervisor event multiplexer (see + // internal/api/supervisor.go:buildMultiplexer) includes + // transient-city event providers via + // TransientCityEventSource. With the file in place, a + // subscriber to /v0/events/stream that connects right after + // POST returns 202 sees a non-empty multiplexer and can + // replay events via after_cursor=0. + // + // 2. The supervisor event stream's no-providers precheck rejects + // subscriptions with 503 when the multiplexer is empty. By + // populating at least one event log before registration, + // POST /v0/city → subscribe works even when no other cities + // exist yet (the fresh-supervisor scenario). + // + // Best-effort: a failure to open the recorder does not fail the + // Scaffold call — the reconciler creates its own recorder later + // and city.ready/city.init_failed will land there. The city is + // still usable, just without the leading city.created marker. + if fr, frErr := events.NewFileRecorder(filepath.Join(dir, ".gc", "events.jsonl"), io.Discard); frErr == nil { + if payload, mErr := json.Marshal(api.CityCreatedPayload{Name: cityName, Path: dir}); mErr == nil { + fr.Record(events.Event{ + Type: events.CityCreated, + Actor: "gc", + Subject: cityName, + Payload: payload, + }) + } + fr.Close() //nolint:errcheck // best-effort + } + + // Register the city with the supervisor without blocking on the + // reconciler's tick. The standard registerCityWithSupervisor + // waits for prepareCityForSupervisor to complete, which is the + // very blocking behavior the async POST /v0/city contract + // exists to avoid. registerCityForAPI fires a reload signal at + // the supervisor and returns immediately; the reconciler picks + // up the city on its own schedule. + if err := registerCityForAPI(dir, req.NameOverride); err != nil { + return nil, fmt.Errorf("register with supervisor: %w", err) + } + + return &cityinit.InitResult{ + CityName: cityName, + CityPath: dir, + ProviderUsed: req.Provider, + }, nil +} + +// Init scaffolds + finalizes a new city. Errors are mapped to the +// typed sentinels in package cityinit so callers (HTTP API, future +// in-process consumers) can pattern-match via errors.Is. +func (localInitializer) Init(_ context.Context, req cityinit.InitRequest) (*cityinit.InitResult, error) { + if err := validateInitRequest(&req); err != nil { + return nil, err + } + dir := req.Dir + if err := os.MkdirAll(dir, 0o755); err != nil { + return nil, fmt.Errorf("creating directory %q: %w", dir, err) + } + + wiz := wizardConfig{ + configName: req.ConfigName, + provider: req.Provider, + startCommand: req.StartCommand, + bootstrapProfile: req.BootstrapProfile, + } + if wiz.configName == "" { + wiz.configName = "tutorial" + } + + // Check for an already-initialized directory BEFORE calling + // doInit so we can return ErrAlreadyInitialized without also + // writing "gc init: already initialized" to stderr (the CLI + // path wants that; the API does not). + if cityHasScaffoldFS(fsys.OSFS{}, dir) { + return nil, cityinit.ErrAlreadyInitialized + } + + // doInit writes directly to io.Writer arguments (CLI progress + // narration). The API path discards those; error return is + // carried as an exit code, which we translate into typed + // sentinels below. + if code := doInit(fsys.OSFS{}, dir, wiz, req.NameOverride, io.Discard, io.Discard); code != 0 { + if code == initExitAlreadyInitialized { + return nil, cityinit.ErrAlreadyInitialized + } + return nil, fmt.Errorf("scaffold failed (exit %d)", code) + } + + // finalizeInit likewise writes to io.Writer and returns 0/1. + // Discard its narration; the HTTP response conveys structured + // errors via the sentinel types. + if code := finalizeInit(dir, io.Discard, io.Discard, initFinalizeOptions{ + skipProviderReadiness: req.SkipProviderReadiness, + showProgress: false, + commandName: "gc init", + }); code != 0 { + // finalizeInit's current contract is "blocked, check + // stderr". Without a structured return type we can't map + // to specific sentinels; future work splits this out. + return nil, fmt.Errorf("finalize failed (exit %d)", code) + } + + cityName := resolveCityName(req.NameOverride, "", dir) + return &cityinit.InitResult{ + CityName: cityName, + CityPath: dir, + ProviderUsed: req.Provider, + }, nil +} + +// Unregister removes the city's registry entry and signals the +// supervisor to reconcile. Fire-and-forget: returns as soon as the +// registry file is updated and the reload signal is sent. The +// supervisor reconciler discovers the missing entry on its next +// tick, stops the city's controller, and emits city.unregistered +// (or city.unregister_failed on stop failure). See cmd_supervisor.go +// for the reconciler side. +// +// Looks the city up by name. The registry is keyed by path on disk, +// so we scan entries to find the one whose effective name matches. +// Name collisions would violate the registry's uniqueness invariant +// and are rejected at Register time; we take the first match. +// +// Emits city.unregister_requested to the city's event log before +// unregistering so /v0/events/stream subscribers see the start of +// the teardown. Once the registry entry is gone, the transient +// event-provider lookup (cityRegistry.TransientCityEventProviders) +// will still surface this city to new subscribers via its snap.all +// entry until the reconciler fully drops it. +func (localInitializer) Unregister(_ context.Context, req cityinit.UnregisterRequest) (*cityinit.UnregisterResult, error) { + name := strings.TrimSpace(req.CityName) + if name == "" { + return nil, fmt.Errorf("%w: city_name is required", cityinit.ErrNotRegistered) + } + + reg := supervisor.NewRegistry(supervisor.RegistryPath()) + entries, err := reg.List() + if err != nil { + return nil, fmt.Errorf("reading supervisor registry: %w", err) + } + var match supervisor.CityEntry + var found bool + for _, e := range entries { + if e.EffectiveName() == name { + match = e + found = true + break + } + } + if !found { + return nil, fmt.Errorf("%w: %q", cityinit.ErrNotRegistered, name) + } + + // Emit city.unregister_requested so subscribers that connected + // before the unregister call see the start of the teardown. The + // city's event file exists for any city that got far enough into + // Scaffold; best-effort if it's missing. + if fr, frErr := events.NewFileRecorder(filepath.Join(match.Path, ".gc", "events.jsonl"), io.Discard); frErr == nil { + if payload, mErr := json.Marshal(api.CityUnregisterRequestedPayload{Name: match.EffectiveName(), Path: match.Path}); mErr == nil { + fr.Record(events.Event{ + Type: events.CityUnregisterRequested, + Actor: "gc", + Subject: match.EffectiveName(), + Payload: payload, + }) + } + fr.Close() //nolint:errcheck // best-effort + } + + if err := reg.Unregister(match.Path); err != nil { + // Should not happen — we just read this entry — but wrap to + // satisfy the ErrNotRegistered contract if it does. + if errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("%w: %q: %w", cityinit.ErrNotRegistered, name, err) + } + return nil, fmt.Errorf("removing %q from supervisor registry: %w", name, err) + } + + // Wake the reconciler; same fire-and-forget signal the Scaffold + // path uses. If the supervisor isn't reachable the periodic + // ticker picks up the change on its next interval. + reloadSupervisorNoWait() + + return &cityinit.UnregisterResult{ + CityName: match.EffectiveName(), + CityPath: match.Path, + }, nil +} + +// validateInitRequest performs the membership / mutual-exclusion +// checks that the HTTP layer previously did inline. Keeping them in +// the bridge means the CLI also benefits from the same validation +// when its call site moves (follow-up). +func validateInitRequest(req *cityinit.InitRequest) error { + if req.Dir == "" { + return fmt.Errorf("%w: dir is required", cityinit.ErrInvalidProvider) + } + if !filepath.IsAbs(req.Dir) { + return fmt.Errorf("dir must be absolute: %q", req.Dir) + } + if req.Provider == "" && req.StartCommand == "" { + return fmt.Errorf("%w: provider or start_command required", cityinit.ErrInvalidProvider) + } + if req.Provider != "" { + if _, ok := config.BuiltinProviders()[req.Provider]; !ok { + return fmt.Errorf("%w: unknown provider %q", cityinit.ErrInvalidProvider, req.Provider) + } + } + if req.BootstrapProfile != "" { + // normalizeBootstrapProfile accepts every spelling the CLI + // and HTTP API currently support; reuse it here so the two + // projections can't disagree. + if _, err := normalizeBootstrapProfile(req.BootstrapProfile); err != nil { + return fmt.Errorf("%w: %w", cityinit.ErrInvalidBootstrapProfile, err) + } + } + return nil +} diff --git a/cmd/gc/cmd_supervisor.go b/cmd/gc/cmd_supervisor.go index b756ff31c1..84706297c8 100644 --- a/cmd/gc/cmd_supervisor.go +++ b/cmd/gc/cmd_supervisor.go @@ -3,6 +3,7 @@ package main import ( "bufio" "context" + "encoding/json" "errors" "fmt" "io" @@ -698,7 +699,7 @@ func runSupervisor(stdout, stderr io.Writer) int { if readOnly { fmt.Fprintf(stderr, "gc supervisor: binding to %s — mutation endpoints disabled (non-localhost)\n", bind) //nolint:errcheck } - apiMux := api.NewSupervisorMux(registry, readOnly, version, startedAt) + apiMux := api.NewSupervisorMux(registry, NewInitializer(), readOnly, version, startedAt) pprofSrv, pprofErr := api.StartPprof("") if pprofErr != nil { @@ -902,13 +903,10 @@ func reconcileCities( }) for i, mc := range toStop { - name := filepath.Base(toStopPaths[i]) + path := toStopPaths[i] + name := filepath.Base(path) fmt.Fprintf(stdout, "Unregistered city '%s', stopping...\n", name) //nolint:errcheck - // Reconcile path: stop error is already logged inside stopManagedCity - // and propagating it would require bubbling through the whole - // reconcile loop. The supervisor-shutdown aggregator is the path - // that cares about these errors. - _ = stopManagedCity(mc, toStopPaths[i], stderr) + stopErr := stopManagedCity(mc, path, stderr) // Clear backoff so re-registering starts immediately. cr.BatchUpdate(func( _ map[string]*managedCity, @@ -916,10 +914,36 @@ func reconcileCities( initFailures map[string]*initFailRecord, panicHistory map[string]*panicRecord, ) { - delete(panicHistory, toStopPaths[i]) - delete(initFailures, toStopPaths[i]) + delete(panicHistory, path) + delete(initFailures, path) }) - fmt.Fprintf(stdout, "City '%s' stopped.\n", name) //nolint:errcheck + // Emit the terminal unregister event to the city's event log + // so /v0/events/stream subscribers observe completion without + // polling. The event lands on disk BEFORE the running-city + // provider is dropped from the multiplexer, so connected + // subscribers see the event via the running-provider path. + // Best-effort: a failure to open the recorder just means + // subscribers learn via GET /v0/cities instead. + evType := events.CityUnregistered + var payload []byte + if stopErr == nil { + fmt.Fprintf(stdout, "City '%s' stopped.\n", name) //nolint:errcheck + p, _ := json.Marshal(api.CityUnregisteredPayload{Name: name, Path: path}) + payload = p + } else { + evType = events.CityUnregisterFailed + p, _ := json.Marshal(api.CityUnregisterFailedPayload{Name: name, Path: path, Error: stopErr.Error()}) + payload = p + } + if fr, frErr := events.NewFileRecorder(filepath.Join(path, ".gc", "events.jsonl"), stderr); frErr == nil { + fr.Record(events.Event{ + Type: evType, + Actor: "gc", + Subject: name, + Payload: payload, + }) + fr.Close() //nolint:errcheck // best-effort + } } // Clear panicHistory and initFailures for any path no longer in the @@ -1132,6 +1156,28 @@ func reconcileCities( ) { delete(initStatus, path) }) + // Emit city.init_failed to the city's event file so + // clients watching /v0/events/stream observe async + // failure signal without polling. Best-effort: if the + // file recorder can't open (e.g. .gc/ missing or + // permissions), fall through to recordInitFailure which + // surfaces the error via /v0/cities. + evPath := filepath.Join(path, ".gc", "events.jsonl") + if fr, frErr := events.NewFileRecorder(evPath, stderr); frErr == nil { + if payload, mErr := json.Marshal(api.CityInitFailedPayload{ + Name: cityName, + Path: path, + Error: err.Error(), + }); mErr == nil { + fr.Record(events.Event{ + Type: events.CityInitFailed, + Actor: "gc", + Subject: cityName, + Payload: payload, + }) + } + fr.Close() //nolint:errcheck // best-effort + } recordInitFailure(cityName, fmt.Sprintf("init: %v", err)) continue } @@ -1503,6 +1549,19 @@ func reconcileCities( }(cityName, path, fr, lis, sockPath, sockInfo, lock) rec.Record(events.Event{Type: events.ControllerStarted, Actor: "gc"}) + // Signal city.ready on the supervisor event bus so clients + // that POST /v0/city and subscribe to /v0/events/stream + // observe completion without polling. Handler returned 202 + // synchronously; this event is the async completion signal. + readyPayload, readyErr := json.Marshal(api.CityReadyPayload{Name: cityName, Path: path}) + if readyErr == nil { + rec.Record(events.Event{ + Type: events.CityReady, + Actor: "gc", + Subject: cityName, + Payload: readyPayload, + }) + } telemetry.RecordControllerLifecycle(context.Background(), "started") fmt.Fprintf(stdout, "Launching city '%s' (%s)\n", cityName, path) //nolint:errcheck } diff --git a/cmd/gc/cmd_supervisor_city.go b/cmd/gc/cmd_supervisor_city.go index 61ad8d476a..a2c9ac5733 100644 --- a/cmd/gc/cmd_supervisor_city.go +++ b/cmd/gc/cmd_supervisor_city.go @@ -242,6 +242,52 @@ func registerCityWithSupervisorNamed(cityPath, nameOverride string, stdout, stde return 0 } +// registerCityForAPI is the fire-and-forget variant of +// registerCityWithSupervisor for the async POST /v0/city path. +// Writes the city to the supervisor registry and signals the +// reconciler to wake up, but does NOT wait for the reconciler to +// finish processing the city — if we waited, POST would block on +// the full prepareCityForSupervisor (pack materialization, beads +// lifecycle, etc.) which is the exact behavior the async contract +// was designed to avoid. The reconciler picks up the city on its +// next tick; subscribers to /v0/events/stream see city.created +// (written by Scaffold), then city.ready or city.init_failed when +// the reconciler completes. +func registerCityForAPI(cityPath, nameOverride string) error { + cityPath = normalizePathForCompare(cityPath) + name, err := registeredCityName(cityPath, nameOverride) + if err != nil { + return err + } + reg := supervisor.NewRegistry(supervisor.RegistryPath()) + if err := reg.Register(cityPath, name); err != nil { + return err + } + // Best-effort wake: if the supervisor isn't reachable, fall + // through; the periodic ticker will pick up the registry entry + // on its next interval. + reloadSupervisorNoWait() + return nil +} + +// reloadSupervisorNoWait sends a "reload" command to the supervisor +// socket without waiting for the reply. Used by registerCityForAPI +// so the async POST /v0/city handler doesn't block on the +// reconciler tick. +func reloadSupervisorNoWait() { + sockPath, _ := runningSupervisorSocket() + if sockPath == "" { + return + } + conn, err := net.DialTimeout("unix", sockPath, 2*time.Second) + if err != nil { + return + } + defer conn.Close() //nolint:errcheck // best-effort + _ = conn.SetWriteDeadline(time.Now().Add(1 * time.Second)) + _, _ = conn.Write([]byte("reload\n")) +} + func retrySupervisorCityStartAfterControllerLock(cityPath string, stdout, stderr io.Writer, startErr error) (bool, error) { if startErr == nil || !strings.Contains(startErr.Error(), "city failed to start: controller lock: controller already running") { return false, startErr diff --git a/cmd/gc/controller.go b/cmd/gc/controller.go index 862a011c19..c550df4808 100644 --- a/cmd/gc/controller.go +++ b/cmd/gc/controller.go @@ -1151,7 +1151,7 @@ func runController( if readOnly { fmt.Fprintf(stderr, "api: binding to %s — mutation endpoints disabled (non-localhost)\n", bind) //nolint:errcheck } - apiMux := api.NewSupervisorMux(&singleCityStateResolver{state: cs}, readOnly, "controller", time.Now()) + apiMux := api.NewSupervisorMux(&singleCityStateResolver{state: cs}, NewInitializer(), readOnly, "controller", time.Now()) addr := net.JoinHostPort(bind, strconv.Itoa(cfg.API.Port)) apiLis, apiErr := net.Listen("tcp", addr) if apiErr != nil { diff --git a/cmd/gen-client/main.go b/cmd/gen-client/main.go index 12dc63c52a..c683d43a1b 100644 --- a/cmd/gen-client/main.go +++ b/cmd/gen-client/main.go @@ -42,7 +42,7 @@ func main() { func run() error { // Step 1: fetch the 3.0-downgraded spec from the supervisor. - sm := api.NewSupervisorMux(emptyResolver{}, false, "", time.Time{}) + sm := api.NewSupervisorMux(emptyResolver{}, nil, false, "", time.Time{}) req := httptest.NewRequest(http.MethodGet, "/openapi-3.0.json", nil) rec := httptest.NewRecorder() sm.ServeHTTP(rec, req) diff --git a/cmd/genspec/main.go b/cmd/genspec/main.go index 717d023610..44a02782e7 100644 --- a/cmd/genspec/main.go +++ b/cmd/genspec/main.go @@ -43,7 +43,10 @@ func main() { flag.BoolVar(&stdoutFlag, "stdout", false, "Write the spec to stdout instead of disk.") flag.Parse() - sm := api.NewSupervisorMux(emptyResolver{}, false, "", time.Time{}) + // Spec generation does not exercise city creation; nil Initializer + // leaves POST /v0/city returning 501 in the live spec, which is + // not observable at spec generation time. + sm := api.NewSupervisorMux(emptyResolver{}, nil, false, "", time.Time{}) req := httptest.NewRequest(http.MethodGet, "/openapi.json", nil) rec := httptest.NewRecorder() sm.ServeHTTP(rec, req) diff --git a/contrib/session-scripts/gc-session-k8s b/contrib/session-scripts/gc-session-k8s index db8f0acf08..01afb52a1a 100755 --- a/contrib/session-scripts/gc-session-k8s +++ b/contrib/session-scripts/gc-session-k8s @@ -304,10 +304,8 @@ case "$op" in # differs from the host work_dir, keep a compatibility mount at the original # city path for any remaining absolute-path references in staged files. main_vol_mounts='[{name: "ws", mountPath: "/workspace"}, {name: "claude-config", mountPath: "/tmp/claude-secret", readOnly: true}]' - extra_jq_args=() if [ -n "$gc_city" ] && [ "$gc_city" != "$work_dir" ]; then main_vol_mounts='[{name: "ws", mountPath: "/workspace"}, {name: "city", mountPath: $city}, {name: "claude-config", mountPath: "/tmp/claude-secret", readOnly: true}]' - extra_jq_args+=(--arg city "$gc_city") fi if [ "$needs_staging" = "true" ]; then @@ -334,7 +332,7 @@ case "$op" in --arg work_dir "$pod_work_dir" \ --arg sa "$SERVICE_ACCOUNT" \ --argjson env "$env_array" \ - ${extra_jq_args[@]+"${extra_jq_args[@]}"} \ + --arg city "$gc_city" \ '{ apiVersion: "v1", kind: "Pod", @@ -402,7 +400,7 @@ case "$op" in --arg work_dir "$pod_work_dir" \ --arg sa "$SERVICE_ACCOUNT" \ --argjson env "$env_array" \ - ${extra_jq_args[@]+"${extra_jq_args[@]}"} \ + --arg city "$gc_city" \ '{ apiVersion: "v1", kind: "Pod", diff --git a/docs/reference/api.md b/docs/reference/api.md index 7f43ea222a..c74cc5f0cf 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -42,6 +42,43 @@ The spec is the full reference. A brief summary of the surfaces: - **Config & packs.** Per-city config and pack metadata under `/v0/city/{cityName}/config` and `/v0/city/{cityName}/packs`. +## Request and response headers + +Every operation's header contract appears in the OpenAPI spec — if a +request header is required or a response header is promised, the +spec describes it. The two cross-cutting headers every API client +should know about: + +- **`X-GC-Request`** (request header, required on all mutations). + Anti-CSRF token required on every POST, PUT, PATCH, and DELETE. + Any non-empty value is accepted; the header's presence is what + the server checks. Requests without it are rejected with + `403 csrf: X-GC-Request header required on mutation endpoints`. + Leveraging the same-origin policy, a cross-origin attacker + cannot set this header on a forged request. The generated Go + and TypeScript clients set this header automatically; only raw + HTTP clients need to remember it. +- **`X-GC-Request-Id`** (response header, every response). + Opaque per-response identifier the server assigns for log + correlation. Every response — success or error — carries this + header; the spec declares it via a `$ref` to + `components.headers.X-GC-Request-Id`. Include its value in bug + reports so the server's logs can be traced. + +SSE stream operations emit additional runtime-status headers before +the first event frame: + +- **`stream-agent-output` / `stream-agent-output-qualified`**: + `GC-Agent-Status` — set to `stopped` when the agent is not + running and the stream is replaying transcript from the session + log instead of live output. +- **`stream-session`**: `GC-Session-State` (e.g. `active`, + `closed`) and `GC-Session-Status` (`stopped` when the session's + underlying process is not running). + +Each header's schema is documented in the operation's +`responses.200.headers` in the spec. + ## Errors Every error response is an RFC 9457 Problem Details body @@ -70,6 +107,126 @@ closes immediately. For example, `GET /v0/events/stream` returns `503 application/problem+json` with `detail: "no_providers: ..."` when no running city has an event provider registered. +## Creating a city (asynchronous) + +`POST /v0/city` is an **asynchronous** operation. The response is +`202 Accepted` returned as soon as the city has been scaffolded on +disk and registered with the supervisor. The slow finalize work +(pack materialization, bead store startup, formula resolution, +agent validation) runs on the supervisor reconciler's next tick. +Clients observe completion via the supervisor event stream — there +is nothing to poll. + +### Response + +```json +{ + "ok": true, + "name": "my-city", + "path": "/abs/path/to/my-city" +} +``` + +The `name` field is the city's resolved runtime identity +(`workspace.name` from `city.toml`, or the directory basename). +Use it to filter the event stream for completion. + +### Completion events + +On the same `/v0/events/stream` the client will see (in order): + +- `city.created` (`CityCreatedPayload`) — emitted by the scaffold + step before `POST` returns. `subject` and payload `name` equal + the response's `name`. +- `city.ready` (`CityReadyPayload`) — the reconciler finished + `prepareCityForSupervisor` successfully. Matching event: + `subject == name` and `type == "city.ready"`. +- `city.init_failed` (`CityInitFailedPayload`) — the reconciler + gave up. The payload's `error` field describes why. + +Exactly one of `city.ready` or `city.init_failed` lands per +successful `POST`. Clients wait for either; no polling of +`GET /v0/cities` or `GET /v0/city/{cityName}/readiness` is +required. + +### Subscribe before or after POST + +Either order works. The recommended flow is: + +1. `POST /v0/city` and wait for `202`. +2. `GET /v0/events/stream?after_cursor=0` — request replay from + the start so `city.created` (and possibly `city.ready`) are + delivered even if they fired before subscribe. +3. Read frames until `subject == response.name` and + `type ∈ {"city.ready", "city.init_failed"}`. + +**Empty supervisor is fine.** The event stream works even when +no cities existed before the `POST`. `POST` writes the city to +the supervisor registry (`cities.toml`) and creates +`.gc/events.jsonl` synchronously before returning 202, so the +event multiplexer finds the new city on the very next +`buildMultiplexer` call. Subscribers do **not** need to retry on +`503 no_providers`; if that error surfaces after a successful +202, it's a bug. + +### Errors + +- `409 conflict: city already initialized at ` — the target + directory already has a scaffolded city. +- `422` — invalid provider, invalid bootstrap profile, or other + body-validation failure. +- `503` — a hard dependency is missing on the host, or a provider + the city needs is not ready. +- `500` — unexpected scaffold failure; consult the server logs + via the `X-GC-Request-Id` correlation header. + +## Unregistering a city (asynchronous) + +`POST /v0/city/{cityName}/unregister` removes a city from the +supervisor's registry and signals the supervisor to stop the city's +controller. Like `POST /v0/city`, it is asynchronous: the response +is `202 Accepted` returned as soon as the registry entry is gone +and the supervisor is notified. The supervisor reconciler stops the +controller on its next tick and emits the completion event. + +The city directory on disk is **not** touched. This operation only +detaches the city from the supervisor; reattaching it later is a +simple `gc register`. + +### Response + +```json +{ + "ok": true, + "name": "my-city", + "path": "/abs/path/to/my-city" +} +``` + +### Completion events + +On `/v0/events/stream` the client will see (in order): + +- `city.unregister_requested` + (`CityUnregisterRequestedPayload`) — emitted by the handler + before the registry write so subscribers see the teardown start. +- `city.unregistered` (`CityUnregisteredPayload`) — emitted by the + reconciler once the city's controller has stopped. Matching + event: `subject == name` and `type == "city.unregistered"`. +- `city.unregister_failed` (`CityUnregisterFailedPayload`) — emitted + by the reconciler if the controller did not stop cleanly. The + payload's `error` field describes the failure. + +Exactly one of `city.unregistered` or `city.unregister_failed` +lands per successful unregister. Clients wait for either. + +### Errors + +- `404 not_found: city not registered with supervisor: ` — no + entry in the registry for that name. +- `501` — supervisor has no Initializer wired (test-only configs). +- `500` — unexpected registry write failure. + ## Event Contract The event APIs, the SSE streams, and `gc events` are the same contract diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 82f818f639..32f37161f2 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -24,6 +24,7 @@ gc [flags] | [gc beads](#gc-beads) | Manage the beads provider | | [gc build-image](#gc-build-image) | Build a prebaked agent container image | | [gc cities](#gc-cities) | List registered cities | +| [gc completion](#gc-completion) | Generate the autocompletion script for the specified shell | | [gc config](#gc-config) | Inspect and validate city configuration | | [gc converge](#gc-converge) | Manage convergence loops (bounded iterative refinement) | | [gc convoy](#gc-convoy) | Manage convoys — graphs of related work | @@ -52,6 +53,7 @@ gc [flags] | [gc runtime](#gc-runtime) | Process-intrinsic runtime operations | | [gc service](#gc-service) | Inspect workspace services | | [gc session](#gc-session) | Manage interactive chat sessions | +| [gc shell](#gc-shell) | Manage the Gas City shell integration hook | | [gc skill](#gc-skill) | List visible skills | | [gc sling](#gc-sling) | Route work to a session config or agent | | [gc start](#gc-start) | Start the city under the machine-wide supervisor | @@ -304,6 +306,127 @@ List registered cities gc cities list ``` +## gc completion + +Generate the autocompletion script for gc for the specified shell. +See each sub-command's help for details on how to use the generated script. + +``` +gc completion +``` + +| Subcommand | Description | +|------------|-------------| +| [gc completion bash](#gc-completion-bash) | Generate the autocompletion script for bash | +| [gc completion fish](#gc-completion-fish) | Generate the autocompletion script for fish | +| [gc completion powershell](#gc-completion-powershell) | Generate the autocompletion script for powershell | +| [gc completion zsh](#gc-completion-zsh) | Generate the autocompletion script for zsh | + +## gc completion bash + +Generate the autocompletion script for the bash shell. + +This script depends on the 'bash-completion' package. +If it is not installed already, you can install it via your OS's package manager. + +To load completions in your current shell session: + + source <(gc completion bash) + +To load completions for every new session, execute once: + +#### Linux: + + gc completion bash > /etc/bash_completion.d/gc + +#### macOS: + + gc completion bash > $(brew --prefix)/etc/bash_completion.d/gc + +You will need to start a new shell for this setup to take effect. + +``` +gc completion bash +``` + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--no-descriptions` | bool | | disable completion descriptions | + +## gc completion fish + +Generate the autocompletion script for the fish shell. + +To load completions in your current shell session: + + gc completion fish | source + +To load completions for every new session, execute once: + + gc completion fish > ~/.config/fish/completions/gc.fish + +You will need to start a new shell for this setup to take effect. + +``` +gc completion fish [flags] +``` + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--no-descriptions` | bool | | disable completion descriptions | + +## gc completion powershell + +Generate the autocompletion script for powershell. + +To load completions in your current shell session: + + gc completion powershell | Out-String | Invoke-Expression + +To load completions for every new session, add the output of the above command +to your powershell profile. + +``` +gc completion powershell [flags] +``` + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--no-descriptions` | bool | | disable completion descriptions | + +## gc completion zsh + +Generate the autocompletion script for the zsh shell. + +If shell completion is not already enabled in your environment you will need +to enable it. You can execute the following once: + + echo "autoload -U compinit; compinit" >> ~/.zshrc + +To load completions in your current shell session: + + source <(gc completion zsh) + +To load completions for every new session, execute once: + +#### Linux: + + gc completion zsh > "${fpath[1]}/_gc" + +#### macOS: + + gc completion zsh > $(brew --prefix)/share/zsh/site-functions/_gc + +You will need to start a new shell for this setup to take effect. + +``` +gc completion zsh [flags] +``` + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--no-descriptions` | bool | | disable completion descriptions | + ## gc config Inspect, validate, and debug the resolved city configuration. @@ -2249,6 +2372,51 @@ gc session wake gc-42 gc session wake mayor ``` +## gc shell + +The shell integration adds a completion hook to your shell RC file that +provides tab-completion for gc commands and flags. + +Subcommands: install, remove, status. + +``` +gc shell +``` + +| Subcommand | Description | +|------------|-------------| +| [gc shell install](#gc-shell-install) | Install or update shell integration | +| [gc shell remove](#gc-shell-remove) | Remove shell integration | +| [gc shell status](#gc-shell-status) | Show shell integration status | + +## gc shell install + +Install or update the gc shell completion hook. + +If no shell is specified, the shell is detected from $SHELL. +The completion script is written to ~/.gc/completions/ and a source line +is added to your shell RC file. + +``` +gc shell install [bash|zsh|fish] +``` + +## gc shell remove + +Remove the gc shell completion hook from your shell RC file and delete the completion script. + +``` +gc shell remove +``` + +## gc shell status + +Show shell integration status + +``` +gc shell status +``` + ## gc skill List skills visible to the current city. diff --git a/docs/reference/exec-beads-provider.md b/docs/reference/exec-beads-provider.md index ef1afd6348..c64629db24 100644 --- a/docs/reference/exec-beads-provider.md +++ b/docs/reference/exec-beads-provider.md @@ -296,9 +296,10 @@ Stdout: the root bead ID as plain text (e.g., `WP-42\n`). ### Status Mapping -Gas City uses 3 statuses: `open`, `in_progress`, `closed`. The exec -script must normalize its backend's statuses to these three. For example, -`bd` maps `blocked`, `review`, and `testing` to `open`. +Gas City preserves backend status values at the API boundary. Providers +should emit the native status string they support, such as bd's `open`, +`in_progress`, `blocked`, `review`, `testing`, and `closed`. An empty +status is treated as `open`. ## Implementation Plan diff --git a/docs/schema/openapi.json b/docs/schema/openapi.json index e939ed7f91..0095c093c3 100644 --- a/docs/schema/openapi.json +++ b/docs/schema/openapi.json @@ -1,5 +1,14 @@ { "components": { + "headers": { + "X-GC-Request-Id": { + "description": "Opaque per-response identifier assigned by the server for log correlation. Every response carries this header.", + "schema": { + "description": "Opaque per-response identifier assigned by the server for log correlation. Every response carries this header.", + "type": "string" + } + } + }, "schemas": { "AdapterCapabilities": { "additionalProperties": false, @@ -1023,17 +1032,38 @@ "CityCreateResponse": { "additionalProperties": false, "properties": { + "name": { + "description": "Resolved city name as persisted in city.toml. Use this to filter the event stream for completion.", + "type": "string" + }, "ok": { - "description": "True on success.", + "description": "True when scaffolding + registration succeeded. Does not imply the city is ready yet; watch /v0/events/stream for city.ready.", "type": "boolean" }, "path": { - "description": "Resolved absolute path of the created city.", + "description": "Resolved absolute path of the created city directory.", "type": "string" } }, "required": [ "ok", + "name", + "path" + ], + "type": "object" + }, + "CityCreatedPayload": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "name", "path" ], "type": "object" @@ -1117,6 +1147,35 @@ ], "type": "object" }, + "CityInitFailedPayload": { + "additionalProperties": false, + "properties": { + "error": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "phases_completed": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + } + }, + "required": [ + "name", + "path", + "error" + ], + "type": "object" + }, "CityPatchInputBody": { "additionalProperties": false, "properties": { @@ -1127,6 +1186,97 @@ }, "type": "object" }, + "CityReadyPayload": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "name", + "path" + ], + "type": "object" + }, + "CityUnregisterFailedPayload": { + "additionalProperties": false, + "properties": { + "error": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "name", + "path", + "error" + ], + "type": "object" + }, + "CityUnregisterRequestedPayload": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "name", + "path" + ], + "type": "object" + }, + "CityUnregisterResponse": { + "additionalProperties": false, + "properties": { + "name": { + "description": "Resolved registry name. Filter the event stream by this to observe completion.", + "type": "string" + }, + "ok": { + "description": "True when the registry entry was removed and the supervisor was signaled. Does not imply the city's controller has stopped yet; watch /v0/events/stream for city.unregistered.", + "type": "boolean" + }, + "path": { + "description": "Resolved absolute city directory. The directory itself is not modified; unregister only affects the supervisor's registry.", + "type": "string" + } + }, + "required": [ + "ok", + "name", + "path" + ], + "type": "object" + }, + "CityUnregisteredPayload": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "name", + "path" + ], + "type": "object" + }, "ConfigAgentResponse": { "additionalProperties": false, "properties": { @@ -1865,6 +2015,24 @@ { "$ref": "#/components/schemas/BoundEventPayload" }, + { + "$ref": "#/components/schemas/CityCreatedPayload" + }, + { + "$ref": "#/components/schemas/CityInitFailedPayload" + }, + { + "$ref": "#/components/schemas/CityReadyPayload" + }, + { + "$ref": "#/components/schemas/CityUnregisterFailedPayload" + }, + { + "$ref": "#/components/schemas/CityUnregisterRequestedPayload" + }, + { + "$ref": "#/components/schemas/CityUnregisteredPayload" + }, { "$ref": "#/components/schemas/GroupCreatedEventPayload" }, @@ -6862,7 +7030,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -6872,7 +7045,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get health" @@ -6890,7 +7068,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -6900,7 +7083,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 cities" @@ -6909,6 +7097,19 @@ "/v0/city": { "post": { "operationId": "post-v0-city", + "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + } + ], "requestBody": { "content": { "application/json": { @@ -6920,7 +7121,7 @@ "required": true }, "responses": { - "200": { + "202": { "content": { "application/json": { "schema": { @@ -6928,7 +7129,12 @@ } } }, - "description": "OK" + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -6938,7 +7144,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city" @@ -6970,7 +7181,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -6980,7 +7196,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name" @@ -6988,6 +7209,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7020,7 +7252,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7030,7 +7267,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name" @@ -7040,6 +7282,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-agent-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7072,7 +7325,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7082,7 +7340,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name agent by base" @@ -7131,6 +7394,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -7142,7 +7408,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name agent by base" @@ -7150,6 +7421,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-agent-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7192,7 +7474,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7202,7 +7489,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name agent by base" @@ -7264,7 +7556,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7274,7 +7571,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name agent by base output" @@ -7375,7 +7677,19 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "GC-Agent-Status": { + "description": "Agent runtime status at the time streaming began. Emitted as \"stopped\" when the agent is not running (the stream then serves replayed transcript from the session log).", + "schema": { + "description": "Agent runtime status at the time streaming began. Emitted as \"stopped\" when the agent is not running (the stream then serves replayed transcript from the session log).", + "type": "string" + } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7385,7 +7699,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Stream agent output in real time" @@ -7395,6 +7714,17 @@ "post": { "operationId": "post-v0-city-by-city-name-agent-by-base-by-action", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7441,17 +7771,27 @@ } } }, - "description": "OK" - }, - "default": { - "content": { + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } + }, + "default": { + "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorModel" } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name agent by base by action" @@ -7461,6 +7801,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-agent-by-dir-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7503,7 +7854,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7513,7 +7869,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name agent by dir by base" @@ -7572,6 +7933,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -7583,7 +7947,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name agent by dir by base" @@ -7591,6 +7960,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-agent-by-dir-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7643,7 +8023,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7653,7 +8038,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name agent by dir by base" @@ -7725,7 +8115,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7735,7 +8130,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name agent by dir by base output" @@ -7846,7 +8246,19 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "GC-Agent-Status": { + "description": "Agent runtime status at the time streaming began. Emitted as \"stopped\" when the agent is not running (the stream then serves replayed transcript from the session log).", + "schema": { + "description": "Agent runtime status at the time streaming began. Emitted as \"stopped\" when the agent is not running (the stream then serves replayed transcript from the session log).", + "type": "string" + } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7856,7 +8268,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Stream agent output in real time (qualified name)" @@ -7866,6 +8283,17 @@ "post": { "operationId": "post-v0-city-by-city-name-agent-by-dir-by-base-by-action", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7922,7 +8350,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7932,7 +8365,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name agent by dir by base by action" @@ -8037,6 +8475,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8048,7 +8489,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name agents" @@ -8056,6 +8502,17 @@ "post": { "operationId": "create-agent", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8088,7 +8545,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8098,7 +8560,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create an agent" @@ -8108,6 +8575,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-bead-by-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8140,7 +8618,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8150,7 +8633,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name bead by ID" @@ -8199,6 +8687,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8210,7 +8701,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name bead by ID" @@ -8218,6 +8714,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-bead-by-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8260,7 +8767,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8270,7 +8782,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name bead by ID" @@ -8280,6 +8797,17 @@ "post": { "operationId": "post-v0-city-by-city-name-bead-by-id-assign", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8334,6 +8862,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8345,7 +8876,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name bead by ID assign" @@ -8355,6 +8891,17 @@ "post": { "operationId": "post-v0-city-by-city-name-bead-by-id-close", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8387,7 +8934,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8397,7 +8949,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name bead by ID close" @@ -8448,6 +9005,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8459,7 +9019,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name bead by ID deps" @@ -8469,6 +9034,17 @@ "post": { "operationId": "post-v0-city-by-city-name-bead-by-id-reopen", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8501,7 +9077,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8511,7 +9092,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name bead by ID reopen" @@ -8521,6 +9107,17 @@ "post": { "operationId": "post-v0-city-by-city-name-bead-by-id-update", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8563,7 +9160,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8573,7 +9175,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name bead by ID update" @@ -8706,6 +9313,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8717,7 +9327,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name beads" @@ -8725,6 +9340,17 @@ "post": { "operationId": "create-bead", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8775,6 +9401,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8786,7 +9415,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create a bead" @@ -8837,6 +9471,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8848,7 +9485,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name beads graph by root ID" @@ -8909,6 +9551,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8920,7 +9565,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name beads ready" @@ -8961,6 +9611,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8972,7 +9625,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name config" @@ -9013,6 +9671,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9024,7 +9685,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name config explain" @@ -9056,7 +9722,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9066,7 +9737,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name config validate" @@ -9076,6 +9752,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-convoy-by-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9108,7 +9795,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9118,7 +9810,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name convoy by ID" @@ -9167,6 +9864,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9178,7 +9878,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name convoy by ID" @@ -9188,6 +9893,17 @@ "post": { "operationId": "post-v0-city-by-city-name-convoy-by-id-add", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9230,7 +9946,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9240,7 +9961,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name convoy by ID add" @@ -9291,6 +10017,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9302,7 +10031,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name convoy by ID check" @@ -9313,9 +10047,20 @@ "operationId": "post-v0-city-by-city-name-convoy-by-id-close", "parameters": [ { - "description": "City name.", - "in": "path", - "name": "cityName", + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, + { + "description": "City name.", + "in": "path", + "name": "cityName", "required": true, "schema": { "description": "City name.", @@ -9344,7 +10089,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9354,7 +10104,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name convoy by ID close" @@ -9364,6 +10119,17 @@ "post": { "operationId": "post-v0-city-by-city-name-convoy-by-id-remove", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9406,7 +10172,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9416,7 +10187,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name convoy by ID remove" @@ -9499,6 +10275,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9510,7 +10289,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name convoys" @@ -9518,6 +10302,17 @@ "post": { "operationId": "create-convoy", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9559,6 +10354,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9570,7 +10368,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create a convoy" @@ -9683,6 +10486,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9694,7 +10500,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name events" @@ -9702,6 +10513,17 @@ "post": { "operationId": "emit-event", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9734,7 +10556,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9744,7 +10571,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Emit an event" @@ -9854,7 +10686,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9864,7 +10701,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Stream city events in real time" @@ -9874,6 +10716,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-extmsg-adapters", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9906,7 +10759,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9916,7 +10774,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name extmsg adapters" @@ -9955,6 +10818,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9966,7 +10832,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name extmsg adapters" @@ -9974,6 +10845,17 @@ "post": { "operationId": "register-extmsg-adapter", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10006,7 +10888,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10016,7 +10903,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Register an external messaging adapter" @@ -10026,6 +10918,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-bind", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10058,7 +10961,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10068,7 +10976,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg bind" @@ -10119,6 +11032,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -10130,7 +11046,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name extmsg bindings" @@ -10212,7 +11133,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10222,7 +11148,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name extmsg groups" @@ -10230,6 +11161,17 @@ "post": { "operationId": "ensure-extmsg-group", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10262,7 +11204,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10272,7 +11219,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Ensure an external messaging group exists" @@ -10282,6 +11234,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-inbound", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10314,7 +11277,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10324,7 +11292,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg inbound" @@ -10334,6 +11307,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-outbound", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10366,7 +11350,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10376,7 +11365,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg outbound" @@ -10386,6 +11380,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-extmsg-participants", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10418,7 +11423,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10428,7 +11438,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name extmsg participants" @@ -10436,6 +11451,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-participants", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10468,7 +11494,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10478,7 +11509,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg participants" @@ -10579,6 +11615,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -10590,7 +11629,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name extmsg transcript" @@ -10600,6 +11644,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-transcript-ack", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10632,7 +11687,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10642,7 +11702,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg transcript ack" @@ -10652,6 +11717,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-unbind", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10684,7 +11760,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10694,7 +11775,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg unbind" @@ -10767,7 +11853,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10777,7 +11868,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name formula by name" @@ -10829,7 +11925,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10839,7 +11940,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name formulas" @@ -10903,7 +12009,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10913,7 +12024,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name formulas feed" @@ -10986,7 +12102,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10996,7 +12117,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name formulas by name" @@ -11006,6 +12132,17 @@ "post": { "operationId": "post-v0-city-by-city-name-formulas-by-name-preview", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11048,7 +12185,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11058,7 +12200,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name formulas by name preview" @@ -11134,7 +12281,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11144,7 +12296,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name formulas by name runs" @@ -11176,7 +12333,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11186,7 +12348,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name health" @@ -11299,6 +12466,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -11310,7 +12480,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name mail" @@ -11318,6 +12493,17 @@ "post": { "operationId": "send-mail", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11368,6 +12554,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -11379,7 +12568,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Send a mail message" @@ -11431,7 +12625,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11441,7 +12640,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name mail count" @@ -11502,6 +12706,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -11513,7 +12720,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name mail thread by ID" @@ -11523,6 +12735,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-mail-by-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11565,7 +12788,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11575,7 +12803,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name mail by ID" @@ -11634,6 +12867,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -11645,7 +12881,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name mail by ID" @@ -11655,6 +12896,17 @@ "post": { "operationId": "post-v0-city-by-city-name-mail-by-id-archive", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11697,7 +12949,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11707,7 +12964,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name mail by ID archive" @@ -11717,6 +12979,17 @@ "post": { "operationId": "post-v0-city-by-city-name-mail-by-id-mark-unread", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11759,7 +13032,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11769,7 +13047,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name mail by ID mark unread" @@ -11779,6 +13062,17 @@ "post": { "operationId": "post-v0-city-by-city-name-mail-by-id-read", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11821,7 +13115,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11831,7 +13130,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name mail by ID read" @@ -11841,6 +13145,17 @@ "post": { "operationId": "reply-mail", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11902,6 +13217,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -11913,7 +13231,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Reply to a mail message" @@ -11965,7 +13288,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11975,7 +13303,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name order history by bead ID" @@ -12017,7 +13350,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12027,7 +13365,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name order by name" @@ -12037,6 +13380,17 @@ "post": { "operationId": "post-v0-city-by-city-name-order-by-name-disable", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12069,7 +13423,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12079,7 +13438,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name order by name disable" @@ -12089,6 +13453,17 @@ "post": { "operationId": "post-v0-city-by-city-name-order-by-name-enable", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12121,7 +13496,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12131,7 +13511,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name order by name enable" @@ -12163,7 +13548,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12173,7 +13563,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name orders" @@ -12205,7 +13600,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12215,7 +13615,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name orders check" @@ -12279,7 +13684,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12289,7 +13699,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name orders feed" @@ -12355,7 +13770,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12365,7 +13785,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name orders history" @@ -12397,7 +13822,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12407,7 +13837,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name packs" @@ -12417,6 +13852,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-patches-agent-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12449,7 +13895,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12459,7 +13910,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name patches agent by base" @@ -12508,6 +13964,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -12519,7 +13978,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches agent by base" @@ -12529,6 +13993,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-patches-agent-by-dir-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12571,7 +14046,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12581,7 +14061,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name patches agent by dir by base" @@ -12640,6 +14125,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -12651,7 +14139,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches agent by dir by base" @@ -12692,6 +14185,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -12703,7 +14199,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches agents" @@ -12712,7 +14213,18 @@ "operationId": "put-v0-city-by-city-name-patches-agents", "parameters": [ { - "description": "City name.", + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, + { + "description": "City name.", "in": "path", "name": "cityName", "required": true, @@ -12743,7 +14255,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12753,7 +14270,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Put v0 city by city name patches agents" @@ -12763,6 +14285,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-patches-provider-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12795,7 +14328,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12805,7 +14343,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name patches provider by name" @@ -12854,6 +14397,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -12865,7 +14411,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches provider by name" @@ -12906,6 +14457,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -12917,7 +14471,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches providers" @@ -12925,6 +14484,17 @@ "put": { "operationId": "put-v0-city-by-city-name-patches-providers", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12957,7 +14527,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12967,7 +14542,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Put v0 city by city name patches providers" @@ -12977,6 +14557,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-patches-rig-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13009,7 +14600,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13019,7 +14615,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name patches rig by name" @@ -13068,6 +14669,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13079,7 +14683,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches rig by name" @@ -13120,6 +14729,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13131,7 +14743,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches rigs" @@ -13139,6 +14756,17 @@ "put": { "operationId": "put-v0-city-by-city-name-patches-rigs", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13171,7 +14799,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13181,7 +14814,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Put v0 city by city name patches rigs" @@ -13233,7 +14871,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13243,7 +14886,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name provider readiness" @@ -13253,6 +14901,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-provider-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13285,7 +14944,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13295,7 +14959,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name provider by name" @@ -13344,6 +15013,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13355,7 +15027,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name provider by name" @@ -13363,6 +15040,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-provider-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13405,7 +15093,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13415,7 +15108,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name provider by name" @@ -13456,6 +15154,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13467,7 +15168,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name providers" @@ -13475,6 +15181,17 @@ "post": { "operationId": "create-provider", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13507,7 +15224,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13517,7 +15239,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create a provider" @@ -13558,6 +15285,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13569,7 +15299,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name providers public" @@ -13621,7 +15356,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13631,7 +15371,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name readiness" @@ -13641,6 +15386,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-rig-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13673,7 +15429,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13683,7 +15444,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name rig by name" @@ -13742,6 +15508,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13753,7 +15522,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name rig by name" @@ -13761,6 +15535,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-rig-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13803,7 +15588,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13813,7 +15603,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name rig by name" @@ -13823,6 +15618,17 @@ "post": { "operationId": "post-v0-city-by-city-name-rig-by-name-by-action", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13865,7 +15671,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13875,7 +15686,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name rig by name by action" @@ -13946,6 +15762,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13957,7 +15776,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name rigs" @@ -13965,6 +15789,17 @@ "post": { "operationId": "create-rig", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13997,7 +15832,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14007,7 +15847,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create a rig" @@ -14058,6 +15903,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14069,7 +15917,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name service by name" @@ -14079,6 +15932,17 @@ "post": { "operationId": "post-v0-city-by-city-name-service-by-name-restart", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14111,7 +15975,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14121,7 +15990,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name service by name restart" @@ -14162,6 +16036,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14173,7 +16050,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name services" @@ -14234,6 +16116,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14245,7 +16130,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name session by ID" @@ -14253,6 +16143,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-session-by-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14304,6 +16205,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14315,9 +16219,14 @@ } } }, - "description": "Error" - } - }, + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } + } + }, "summary": "Patch v0 city by city name session by ID" } }, @@ -14366,6 +16275,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14377,7 +16289,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name session by ID agents" @@ -14438,6 +16355,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14449,7 +16369,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name session by ID agents by agent ID" @@ -14459,6 +16384,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-close", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14501,7 +16437,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14511,7 +16452,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID close" @@ -14521,6 +16467,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-kill", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14553,7 +16510,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14563,7 +16525,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID kill" @@ -14573,6 +16540,17 @@ "post": { "operationId": "send-session-message", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14615,7 +16593,12 @@ } } }, - "description": "Accepted" + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14625,7 +16608,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Send a message to a session" @@ -14676,6 +16664,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14687,7 +16678,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name session by ID pending" @@ -14697,6 +16693,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-rename", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14748,6 +16755,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14759,7 +16769,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID rename" @@ -14769,6 +16784,17 @@ "post": { "operationId": "respond-session", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14811,7 +16837,12 @@ } } }, - "description": "Accepted" + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14821,7 +16852,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Respond to a pending interaction" @@ -14831,6 +16867,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-stop", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14863,7 +16910,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14873,7 +16925,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID stop" @@ -15061,7 +17118,26 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "GC-Session-State": { + "description": "Session state at the time streaming began (e.g. active, closed).", + "schema": { + "description": "Session state at the time streaming began (e.g. active, closed).", + "type": "string" + } + }, + "GC-Session-Status": { + "description": "Runtime status at the time streaming began. Emitted as \"stopped\" when the session's underlying process is not running.", + "schema": { + "description": "Runtime status at the time streaming began. Emitted as \"stopped\" when the session's underlying process is not running.", + "type": "string" + } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15071,7 +17147,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Stream session output in real time" @@ -15081,6 +17162,17 @@ "post": { "operationId": "submit-session", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15123,7 +17215,12 @@ } } }, - "description": "Accepted" + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15133,7 +17230,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Submit a message to a session" @@ -15143,6 +17245,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-suspend", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15175,7 +17288,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15185,7 +17303,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID suspend" @@ -15266,6 +17389,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -15277,7 +17403,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name session by ID transcript" @@ -15287,6 +17418,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-wake", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15319,7 +17461,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15329,7 +17476,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID wake" @@ -15422,6 +17574,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -15433,7 +17588,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name sessions" @@ -15441,6 +17601,17 @@ "post": { "operationId": "create-session", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15473,7 +17644,12 @@ } } }, - "description": "Accepted" + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15483,7 +17659,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create a session" @@ -15493,6 +17674,17 @@ "post": { "operationId": "post-v0-city-by-city-name-sling", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15525,7 +17717,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15535,7 +17732,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name sling" @@ -15596,6 +17798,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -15607,16 +17812,93 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name status" } }, + "/v0/city/{cityName}/unregister": { + "post": { + "operationId": "post-v0-city-by-city-name-unregister", + "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, + { + "description": "Supervisor-registered city name.", + "in": "path", + "name": "cityName", + "required": true, + "schema": { + "description": "Supervisor-registered city name.", + "type": "string" + } + } + ], + "responses": { + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CityUnregisterResponse" + } + } + }, + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } + } + }, + "summary": "Post v0 city by city name unregister" + } + }, "/v0/city/{cityName}/workflow/{workflow_id}": { "delete": { "operationId": "delete-v0-city-by-city-name-workflow-by-workflow-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15679,7 +17961,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15689,7 +17976,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name workflow by workflow ID" @@ -15758,6 +18050,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -15769,7 +18064,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name workflow by workflow ID" @@ -15831,7 +18131,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15841,7 +18146,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 events" @@ -15938,7 +18248,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15948,7 +18263,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Stream tagged events from all running cities." @@ -15988,7 +18308,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15998,7 +18323,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 provider readiness" @@ -16038,7 +18368,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -16048,7 +18383,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 readiness" diff --git a/docs/schema/openapi.txt b/docs/schema/openapi.txt index e939ed7f91..0095c093c3 100644 --- a/docs/schema/openapi.txt +++ b/docs/schema/openapi.txt @@ -1,5 +1,14 @@ { "components": { + "headers": { + "X-GC-Request-Id": { + "description": "Opaque per-response identifier assigned by the server for log correlation. Every response carries this header.", + "schema": { + "description": "Opaque per-response identifier assigned by the server for log correlation. Every response carries this header.", + "type": "string" + } + } + }, "schemas": { "AdapterCapabilities": { "additionalProperties": false, @@ -1023,17 +1032,38 @@ "CityCreateResponse": { "additionalProperties": false, "properties": { + "name": { + "description": "Resolved city name as persisted in city.toml. Use this to filter the event stream for completion.", + "type": "string" + }, "ok": { - "description": "True on success.", + "description": "True when scaffolding + registration succeeded. Does not imply the city is ready yet; watch /v0/events/stream for city.ready.", "type": "boolean" }, "path": { - "description": "Resolved absolute path of the created city.", + "description": "Resolved absolute path of the created city directory.", "type": "string" } }, "required": [ "ok", + "name", + "path" + ], + "type": "object" + }, + "CityCreatedPayload": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "name", "path" ], "type": "object" @@ -1117,6 +1147,35 @@ ], "type": "object" }, + "CityInitFailedPayload": { + "additionalProperties": false, + "properties": { + "error": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "phases_completed": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + } + }, + "required": [ + "name", + "path", + "error" + ], + "type": "object" + }, "CityPatchInputBody": { "additionalProperties": false, "properties": { @@ -1127,6 +1186,97 @@ }, "type": "object" }, + "CityReadyPayload": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "name", + "path" + ], + "type": "object" + }, + "CityUnregisterFailedPayload": { + "additionalProperties": false, + "properties": { + "error": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "name", + "path", + "error" + ], + "type": "object" + }, + "CityUnregisterRequestedPayload": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "name", + "path" + ], + "type": "object" + }, + "CityUnregisterResponse": { + "additionalProperties": false, + "properties": { + "name": { + "description": "Resolved registry name. Filter the event stream by this to observe completion.", + "type": "string" + }, + "ok": { + "description": "True when the registry entry was removed and the supervisor was signaled. Does not imply the city's controller has stopped yet; watch /v0/events/stream for city.unregistered.", + "type": "boolean" + }, + "path": { + "description": "Resolved absolute city directory. The directory itself is not modified; unregister only affects the supervisor's registry.", + "type": "string" + } + }, + "required": [ + "ok", + "name", + "path" + ], + "type": "object" + }, + "CityUnregisteredPayload": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "name", + "path" + ], + "type": "object" + }, "ConfigAgentResponse": { "additionalProperties": false, "properties": { @@ -1865,6 +2015,24 @@ { "$ref": "#/components/schemas/BoundEventPayload" }, + { + "$ref": "#/components/schemas/CityCreatedPayload" + }, + { + "$ref": "#/components/schemas/CityInitFailedPayload" + }, + { + "$ref": "#/components/schemas/CityReadyPayload" + }, + { + "$ref": "#/components/schemas/CityUnregisterFailedPayload" + }, + { + "$ref": "#/components/schemas/CityUnregisterRequestedPayload" + }, + { + "$ref": "#/components/schemas/CityUnregisteredPayload" + }, { "$ref": "#/components/schemas/GroupCreatedEventPayload" }, @@ -6862,7 +7030,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -6872,7 +7045,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get health" @@ -6890,7 +7068,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -6900,7 +7083,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 cities" @@ -6909,6 +7097,19 @@ "/v0/city": { "post": { "operationId": "post-v0-city", + "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + } + ], "requestBody": { "content": { "application/json": { @@ -6920,7 +7121,7 @@ "required": true }, "responses": { - "200": { + "202": { "content": { "application/json": { "schema": { @@ -6928,7 +7129,12 @@ } } }, - "description": "OK" + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -6938,7 +7144,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city" @@ -6970,7 +7181,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -6980,7 +7196,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name" @@ -6988,6 +7209,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7020,7 +7252,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7030,7 +7267,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name" @@ -7040,6 +7282,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-agent-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7072,7 +7325,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7082,7 +7340,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name agent by base" @@ -7131,6 +7394,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -7142,7 +7408,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name agent by base" @@ -7150,6 +7421,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-agent-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7192,7 +7474,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7202,7 +7489,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name agent by base" @@ -7264,7 +7556,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7274,7 +7571,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name agent by base output" @@ -7375,7 +7677,19 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "GC-Agent-Status": { + "description": "Agent runtime status at the time streaming began. Emitted as \"stopped\" when the agent is not running (the stream then serves replayed transcript from the session log).", + "schema": { + "description": "Agent runtime status at the time streaming began. Emitted as \"stopped\" when the agent is not running (the stream then serves replayed transcript from the session log).", + "type": "string" + } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7385,7 +7699,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Stream agent output in real time" @@ -7395,6 +7714,17 @@ "post": { "operationId": "post-v0-city-by-city-name-agent-by-base-by-action", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7441,17 +7771,27 @@ } } }, - "description": "OK" - }, - "default": { - "content": { + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } + }, + "default": { + "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorModel" } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name agent by base by action" @@ -7461,6 +7801,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-agent-by-dir-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7503,7 +7854,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7513,7 +7869,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name agent by dir by base" @@ -7572,6 +7933,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -7583,7 +7947,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name agent by dir by base" @@ -7591,6 +7960,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-agent-by-dir-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7643,7 +8023,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7653,7 +8038,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name agent by dir by base" @@ -7725,7 +8115,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7735,7 +8130,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name agent by dir by base output" @@ -7846,7 +8246,19 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "GC-Agent-Status": { + "description": "Agent runtime status at the time streaming began. Emitted as \"stopped\" when the agent is not running (the stream then serves replayed transcript from the session log).", + "schema": { + "description": "Agent runtime status at the time streaming began. Emitted as \"stopped\" when the agent is not running (the stream then serves replayed transcript from the session log).", + "type": "string" + } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7856,7 +8268,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Stream agent output in real time (qualified name)" @@ -7866,6 +8283,17 @@ "post": { "operationId": "post-v0-city-by-city-name-agent-by-dir-by-base-by-action", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7922,7 +8350,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7932,7 +8365,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name agent by dir by base by action" @@ -8037,6 +8475,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8048,7 +8489,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name agents" @@ -8056,6 +8502,17 @@ "post": { "operationId": "create-agent", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8088,7 +8545,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8098,7 +8560,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create an agent" @@ -8108,6 +8575,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-bead-by-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8140,7 +8618,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8150,7 +8633,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name bead by ID" @@ -8199,6 +8687,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8210,7 +8701,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name bead by ID" @@ -8218,6 +8714,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-bead-by-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8260,7 +8767,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8270,7 +8782,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name bead by ID" @@ -8280,6 +8797,17 @@ "post": { "operationId": "post-v0-city-by-city-name-bead-by-id-assign", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8334,6 +8862,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8345,7 +8876,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name bead by ID assign" @@ -8355,6 +8891,17 @@ "post": { "operationId": "post-v0-city-by-city-name-bead-by-id-close", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8387,7 +8934,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8397,7 +8949,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name bead by ID close" @@ -8448,6 +9005,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8459,7 +9019,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name bead by ID deps" @@ -8469,6 +9034,17 @@ "post": { "operationId": "post-v0-city-by-city-name-bead-by-id-reopen", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8501,7 +9077,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8511,7 +9092,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name bead by ID reopen" @@ -8521,6 +9107,17 @@ "post": { "operationId": "post-v0-city-by-city-name-bead-by-id-update", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8563,7 +9160,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8573,7 +9175,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name bead by ID update" @@ -8706,6 +9313,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8717,7 +9327,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name beads" @@ -8725,6 +9340,17 @@ "post": { "operationId": "create-bead", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8775,6 +9401,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8786,7 +9415,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create a bead" @@ -8837,6 +9471,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8848,7 +9485,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name beads graph by root ID" @@ -8909,6 +9551,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8920,7 +9565,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name beads ready" @@ -8961,6 +9611,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8972,7 +9625,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name config" @@ -9013,6 +9671,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9024,7 +9685,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name config explain" @@ -9056,7 +9722,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9066,7 +9737,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name config validate" @@ -9076,6 +9752,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-convoy-by-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9108,7 +9795,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9118,7 +9810,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name convoy by ID" @@ -9167,6 +9864,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9178,7 +9878,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name convoy by ID" @@ -9188,6 +9893,17 @@ "post": { "operationId": "post-v0-city-by-city-name-convoy-by-id-add", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9230,7 +9946,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9240,7 +9961,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name convoy by ID add" @@ -9291,6 +10017,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9302,7 +10031,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name convoy by ID check" @@ -9313,9 +10047,20 @@ "operationId": "post-v0-city-by-city-name-convoy-by-id-close", "parameters": [ { - "description": "City name.", - "in": "path", - "name": "cityName", + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, + { + "description": "City name.", + "in": "path", + "name": "cityName", "required": true, "schema": { "description": "City name.", @@ -9344,7 +10089,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9354,7 +10104,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name convoy by ID close" @@ -9364,6 +10119,17 @@ "post": { "operationId": "post-v0-city-by-city-name-convoy-by-id-remove", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9406,7 +10172,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9416,7 +10187,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name convoy by ID remove" @@ -9499,6 +10275,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9510,7 +10289,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name convoys" @@ -9518,6 +10302,17 @@ "post": { "operationId": "create-convoy", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9559,6 +10354,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9570,7 +10368,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create a convoy" @@ -9683,6 +10486,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9694,7 +10500,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name events" @@ -9702,6 +10513,17 @@ "post": { "operationId": "emit-event", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9734,7 +10556,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9744,7 +10571,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Emit an event" @@ -9854,7 +10686,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9864,7 +10701,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Stream city events in real time" @@ -9874,6 +10716,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-extmsg-adapters", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9906,7 +10759,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9916,7 +10774,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name extmsg adapters" @@ -9955,6 +10818,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9966,7 +10832,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name extmsg adapters" @@ -9974,6 +10845,17 @@ "post": { "operationId": "register-extmsg-adapter", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10006,7 +10888,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10016,7 +10903,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Register an external messaging adapter" @@ -10026,6 +10918,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-bind", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10058,7 +10961,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10068,7 +10976,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg bind" @@ -10119,6 +11032,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -10130,7 +11046,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name extmsg bindings" @@ -10212,7 +11133,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10222,7 +11148,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name extmsg groups" @@ -10230,6 +11161,17 @@ "post": { "operationId": "ensure-extmsg-group", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10262,7 +11204,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10272,7 +11219,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Ensure an external messaging group exists" @@ -10282,6 +11234,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-inbound", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10314,7 +11277,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10324,7 +11292,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg inbound" @@ -10334,6 +11307,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-outbound", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10366,7 +11350,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10376,7 +11365,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg outbound" @@ -10386,6 +11380,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-extmsg-participants", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10418,7 +11423,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10428,7 +11438,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name extmsg participants" @@ -10436,6 +11451,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-participants", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10468,7 +11494,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10478,7 +11509,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg participants" @@ -10579,6 +11615,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -10590,7 +11629,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name extmsg transcript" @@ -10600,6 +11644,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-transcript-ack", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10632,7 +11687,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10642,7 +11702,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg transcript ack" @@ -10652,6 +11717,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-unbind", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10684,7 +11760,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10694,7 +11775,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg unbind" @@ -10767,7 +11853,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10777,7 +11868,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name formula by name" @@ -10829,7 +11925,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10839,7 +11940,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name formulas" @@ -10903,7 +12009,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10913,7 +12024,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name formulas feed" @@ -10986,7 +12102,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10996,7 +12117,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name formulas by name" @@ -11006,6 +12132,17 @@ "post": { "operationId": "post-v0-city-by-city-name-formulas-by-name-preview", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11048,7 +12185,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11058,7 +12200,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name formulas by name preview" @@ -11134,7 +12281,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11144,7 +12296,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name formulas by name runs" @@ -11176,7 +12333,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11186,7 +12348,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name health" @@ -11299,6 +12466,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -11310,7 +12480,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name mail" @@ -11318,6 +12493,17 @@ "post": { "operationId": "send-mail", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11368,6 +12554,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -11379,7 +12568,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Send a mail message" @@ -11431,7 +12625,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11441,7 +12640,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name mail count" @@ -11502,6 +12706,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -11513,7 +12720,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name mail thread by ID" @@ -11523,6 +12735,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-mail-by-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11565,7 +12788,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11575,7 +12803,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name mail by ID" @@ -11634,6 +12867,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -11645,7 +12881,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name mail by ID" @@ -11655,6 +12896,17 @@ "post": { "operationId": "post-v0-city-by-city-name-mail-by-id-archive", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11697,7 +12949,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11707,7 +12964,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name mail by ID archive" @@ -11717,6 +12979,17 @@ "post": { "operationId": "post-v0-city-by-city-name-mail-by-id-mark-unread", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11759,7 +13032,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11769,7 +13047,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name mail by ID mark unread" @@ -11779,6 +13062,17 @@ "post": { "operationId": "post-v0-city-by-city-name-mail-by-id-read", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11821,7 +13115,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11831,7 +13130,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name mail by ID read" @@ -11841,6 +13145,17 @@ "post": { "operationId": "reply-mail", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11902,6 +13217,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -11913,7 +13231,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Reply to a mail message" @@ -11965,7 +13288,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11975,7 +13303,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name order history by bead ID" @@ -12017,7 +13350,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12027,7 +13365,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name order by name" @@ -12037,6 +13380,17 @@ "post": { "operationId": "post-v0-city-by-city-name-order-by-name-disable", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12069,7 +13423,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12079,7 +13438,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name order by name disable" @@ -12089,6 +13453,17 @@ "post": { "operationId": "post-v0-city-by-city-name-order-by-name-enable", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12121,7 +13496,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12131,7 +13511,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name order by name enable" @@ -12163,7 +13548,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12173,7 +13563,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name orders" @@ -12205,7 +13600,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12215,7 +13615,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name orders check" @@ -12279,7 +13684,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12289,7 +13699,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name orders feed" @@ -12355,7 +13770,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12365,7 +13785,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name orders history" @@ -12397,7 +13822,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12407,7 +13837,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name packs" @@ -12417,6 +13852,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-patches-agent-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12449,7 +13895,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12459,7 +13910,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name patches agent by base" @@ -12508,6 +13964,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -12519,7 +13978,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches agent by base" @@ -12529,6 +13993,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-patches-agent-by-dir-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12571,7 +14046,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12581,7 +14061,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name patches agent by dir by base" @@ -12640,6 +14125,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -12651,7 +14139,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches agent by dir by base" @@ -12692,6 +14185,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -12703,7 +14199,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches agents" @@ -12712,7 +14213,18 @@ "operationId": "put-v0-city-by-city-name-patches-agents", "parameters": [ { - "description": "City name.", + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, + { + "description": "City name.", "in": "path", "name": "cityName", "required": true, @@ -12743,7 +14255,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12753,7 +14270,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Put v0 city by city name patches agents" @@ -12763,6 +14285,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-patches-provider-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12795,7 +14328,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12805,7 +14343,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name patches provider by name" @@ -12854,6 +14397,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -12865,7 +14411,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches provider by name" @@ -12906,6 +14457,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -12917,7 +14471,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches providers" @@ -12925,6 +14484,17 @@ "put": { "operationId": "put-v0-city-by-city-name-patches-providers", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12957,7 +14527,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12967,7 +14542,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Put v0 city by city name patches providers" @@ -12977,6 +14557,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-patches-rig-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13009,7 +14600,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13019,7 +14615,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name patches rig by name" @@ -13068,6 +14669,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13079,7 +14683,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches rig by name" @@ -13120,6 +14729,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13131,7 +14743,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches rigs" @@ -13139,6 +14756,17 @@ "put": { "operationId": "put-v0-city-by-city-name-patches-rigs", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13171,7 +14799,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13181,7 +14814,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Put v0 city by city name patches rigs" @@ -13233,7 +14871,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13243,7 +14886,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name provider readiness" @@ -13253,6 +14901,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-provider-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13285,7 +14944,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13295,7 +14959,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name provider by name" @@ -13344,6 +15013,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13355,7 +15027,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name provider by name" @@ -13363,6 +15040,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-provider-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13405,7 +15093,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13415,7 +15108,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name provider by name" @@ -13456,6 +15154,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13467,7 +15168,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name providers" @@ -13475,6 +15181,17 @@ "post": { "operationId": "create-provider", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13507,7 +15224,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13517,7 +15239,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create a provider" @@ -13558,6 +15285,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13569,7 +15299,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name providers public" @@ -13621,7 +15356,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13631,7 +15371,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name readiness" @@ -13641,6 +15386,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-rig-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13673,7 +15429,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13683,7 +15444,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name rig by name" @@ -13742,6 +15508,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13753,7 +15522,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name rig by name" @@ -13761,6 +15535,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-rig-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13803,7 +15588,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13813,7 +15603,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name rig by name" @@ -13823,6 +15618,17 @@ "post": { "operationId": "post-v0-city-by-city-name-rig-by-name-by-action", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13865,7 +15671,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13875,7 +15686,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name rig by name by action" @@ -13946,6 +15762,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13957,7 +15776,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name rigs" @@ -13965,6 +15789,17 @@ "post": { "operationId": "create-rig", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13997,7 +15832,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14007,7 +15847,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create a rig" @@ -14058,6 +15903,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14069,7 +15917,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name service by name" @@ -14079,6 +15932,17 @@ "post": { "operationId": "post-v0-city-by-city-name-service-by-name-restart", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14111,7 +15975,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14121,7 +15990,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name service by name restart" @@ -14162,6 +16036,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14173,7 +16050,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name services" @@ -14234,6 +16116,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14245,7 +16130,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name session by ID" @@ -14253,6 +16143,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-session-by-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14304,6 +16205,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14315,9 +16219,14 @@ } } }, - "description": "Error" - } - }, + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } + } + }, "summary": "Patch v0 city by city name session by ID" } }, @@ -14366,6 +16275,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14377,7 +16289,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name session by ID agents" @@ -14438,6 +16355,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14449,7 +16369,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name session by ID agents by agent ID" @@ -14459,6 +16384,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-close", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14501,7 +16437,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14511,7 +16452,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID close" @@ -14521,6 +16467,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-kill", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14553,7 +16510,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14563,7 +16525,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID kill" @@ -14573,6 +16540,17 @@ "post": { "operationId": "send-session-message", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14615,7 +16593,12 @@ } } }, - "description": "Accepted" + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14625,7 +16608,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Send a message to a session" @@ -14676,6 +16664,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14687,7 +16678,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name session by ID pending" @@ -14697,6 +16693,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-rename", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14748,6 +16755,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14759,7 +16769,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID rename" @@ -14769,6 +16784,17 @@ "post": { "operationId": "respond-session", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14811,7 +16837,12 @@ } } }, - "description": "Accepted" + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14821,7 +16852,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Respond to a pending interaction" @@ -14831,6 +16867,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-stop", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14863,7 +16910,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14873,7 +16925,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID stop" @@ -15061,7 +17118,26 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "GC-Session-State": { + "description": "Session state at the time streaming began (e.g. active, closed).", + "schema": { + "description": "Session state at the time streaming began (e.g. active, closed).", + "type": "string" + } + }, + "GC-Session-Status": { + "description": "Runtime status at the time streaming began. Emitted as \"stopped\" when the session's underlying process is not running.", + "schema": { + "description": "Runtime status at the time streaming began. Emitted as \"stopped\" when the session's underlying process is not running.", + "type": "string" + } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15071,7 +17147,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Stream session output in real time" @@ -15081,6 +17162,17 @@ "post": { "operationId": "submit-session", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15123,7 +17215,12 @@ } } }, - "description": "Accepted" + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15133,7 +17230,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Submit a message to a session" @@ -15143,6 +17245,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-suspend", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15175,7 +17288,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15185,7 +17303,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID suspend" @@ -15266,6 +17389,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -15277,7 +17403,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name session by ID transcript" @@ -15287,6 +17418,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-wake", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15319,7 +17461,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15329,7 +17476,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID wake" @@ -15422,6 +17574,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -15433,7 +17588,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name sessions" @@ -15441,6 +17601,17 @@ "post": { "operationId": "create-session", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15473,7 +17644,12 @@ } } }, - "description": "Accepted" + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15483,7 +17659,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create a session" @@ -15493,6 +17674,17 @@ "post": { "operationId": "post-v0-city-by-city-name-sling", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15525,7 +17717,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15535,7 +17732,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name sling" @@ -15596,6 +17798,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -15607,16 +17812,93 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name status" } }, + "/v0/city/{cityName}/unregister": { + "post": { + "operationId": "post-v0-city-by-city-name-unregister", + "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, + { + "description": "Supervisor-registered city name.", + "in": "path", + "name": "cityName", + "required": true, + "schema": { + "description": "Supervisor-registered city name.", + "type": "string" + } + } + ], + "responses": { + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CityUnregisterResponse" + } + } + }, + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } + } + }, + "summary": "Post v0 city by city name unregister" + } + }, "/v0/city/{cityName}/workflow/{workflow_id}": { "delete": { "operationId": "delete-v0-city-by-city-name-workflow-by-workflow-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15679,7 +17961,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15689,7 +17976,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name workflow by workflow ID" @@ -15758,6 +18050,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -15769,7 +18064,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name workflow by workflow ID" @@ -15831,7 +18131,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15841,7 +18146,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 events" @@ -15938,7 +18248,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15948,7 +18263,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Stream tagged events from all running cities." @@ -15988,7 +18308,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15998,7 +18323,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 provider readiness" @@ -16038,7 +18368,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -16048,7 +18383,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 readiness" diff --git a/engdocs/architecture/beads.md b/engdocs/architecture/beads.md index 17c701c058..b3b30a04eb 100644 --- a/engdocs/architecture/beads.md +++ b/engdocs/architecture/beads.md @@ -180,10 +180,10 @@ enforced by the conformance suite in `internal/beads/beadstest/conformance.go`. `internal/beads/` (and `test/integration/`) directly invokes the bd binary. -14. **BdStore maps bd's extended statuses to Gas City's three.** bd uses - open, in_progress, blocked, review, testing, closed. Gas City maps - closed to closed, in_progress to in_progress, and everything else - to open. +14. **BdStore preserves bd's status vocabulary.** bd uses open, + in_progress, blocked, review, testing, closed. Gas City preserves + non-empty status values at the API boundary and only normalizes an + empty backend status to open. 15. **FileStore uses atomic writes.** Persistence writes go to a temp file first, then `os.Rename` to the target path -- never partial diff --git a/engdocs/architecture/life-of-a-bead.md b/engdocs/architecture/life-of-a-bead.md index 91bf8da6c3..37b46739f3 100644 --- a/engdocs/architecture/life-of-a-bead.md +++ b/engdocs/architecture/life-of-a-bead.md @@ -347,9 +347,9 @@ mechanism. ``` **Status mapping.** The bd CLI uses six statuses (open, in_progress, -blocked, review, testing, closed). `mapBdStatus()` in -`internal/beads/bdstore.go` collapses these to Gas City's three: closed -maps to closed, in_progress maps to in_progress, everything else to open. +blocked, review, testing, closed). Gas City preserves those non-empty +status values at the API boundary so clients can round-trip bd semantics. +An empty status from a backend is normalized to the store default, open. ## Code Map diff --git a/internal/api/city_scope.go b/internal/api/city_scope.go index 65dacb7f4c..23d5eb4d9e 100644 --- a/internal/api/city_scope.go +++ b/internal/api/city_scope.go @@ -78,6 +78,49 @@ func bindCity[I any, O any]( } } +// csrfHeaderName is the anti-CSRF header required on every mutation +// request. Any non-empty value satisfies the check; the header's +// presence is what matters, because cross-origin XHR from an attacker +// origin cannot set custom request headers without triggering a CORS +// preflight the API does not grant. See OWASP's "Use of Custom Request +// Headers" defense. +const csrfHeaderName = "X-GC-Request" + +// csrfHeaderDescription is the shared description used for the header +// in generated OpenAPI specs so the spec and runtime enforcement agree. +const csrfHeaderDescription = "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks." + +// addMutationCSRFParam is an operationHandler (see huma.Post et al.) +// that appends the X-GC-Request required header parameter to op. +// Mutation-verb registration helpers pass this handler so the spec +// describes the middleware's enforcement rather than advertising a +// false "no special headers needed" contract. +// +// The header is declared once per mutation operation (OpenAPI 3.1 has +// no mechanism for global per-verb parameters; see +// speakeasy.com/openapi/responses/headers). Idempotent so handlers +// whose input struct happens to declare the header explicitly are not +// double-registered. +func addMutationCSRFParam(op *huma.Operation) { + for _, p := range op.Parameters { + if p != nil && p.In == "header" && p.Name == csrfHeaderName { + return + } + } + minLen := 1 + op.Parameters = append(op.Parameters, &huma.Param{ + Name: csrfHeaderName, + In: "header", + Required: true, + Description: csrfHeaderDescription, + Schema: &huma.Schema{ + Type: "string", + MinLength: &minLen, + Description: csrfHeaderDescription, + }, + }) +} + // cityGet registers a per-city GET op at /v0/city/{cityName}+tail. // The tail starts with "/" (e.g. "/agents") or is "" for the // city-detail base path. @@ -87,41 +130,51 @@ func cityGet[I any, O any](sm *SupervisorMux, tail string, huma.Get(sm.humaAPI, cityScopePrefix+tail, bindCity(sm, fn)) } -// cityPost is the POST sibling of cityGet. +// cityPost is the POST sibling of cityGet. Every city-scoped POST +// mutation flows through this helper, so declaring the X-GC-Request +// header param here covers every current and future mutation without +// per-input-struct boilerplate. func cityPost[I any, O any](sm *SupervisorMux, tail string, fn func(*Server, context.Context, *I) (*O, error), ) { - huma.Post(sm.humaAPI, cityScopePrefix+tail, bindCity(sm, fn)) + huma.Post(sm.humaAPI, cityScopePrefix+tail, bindCity(sm, fn), addMutationCSRFParam) } -// cityPut is the PUT sibling of cityGet. +// cityPut is the PUT sibling of cityGet. See cityPost for the CSRF +// header rationale. func cityPut[I any, O any](sm *SupervisorMux, tail string, fn func(*Server, context.Context, *I) (*O, error), ) { - huma.Put(sm.humaAPI, cityScopePrefix+tail, bindCity(sm, fn)) + huma.Put(sm.humaAPI, cityScopePrefix+tail, bindCity(sm, fn), addMutationCSRFParam) } -// cityPatch is the PATCH sibling of cityGet. +// cityPatch is the PATCH sibling of cityGet. See cityPost for the CSRF +// header rationale. func cityPatch[I any, O any](sm *SupervisorMux, tail string, fn func(*Server, context.Context, *I) (*O, error), ) { - huma.Patch(sm.humaAPI, cityScopePrefix+tail, bindCity(sm, fn)) + huma.Patch(sm.humaAPI, cityScopePrefix+tail, bindCity(sm, fn), addMutationCSRFParam) } -// cityDelete is the DELETE sibling of cityGet. +// cityDelete is the DELETE sibling of cityGet. See cityPost for the +// CSRF header rationale. func cityDelete[I any, O any](sm *SupervisorMux, tail string, fn func(*Server, context.Context, *I) (*O, error), ) { - huma.Delete(sm.humaAPI, cityScopePrefix+tail, bindCity(sm, fn)) + huma.Delete(sm.humaAPI, cityScopePrefix+tail, bindCity(sm, fn), addMutationCSRFParam) } // cityRegister is the per-city analog of huma.Register. Use it when // the op needs explicit OperationID, DefaultStatus, Summary, etc. -// op.Path is the tail after /v0/city/{cityName}. +// op.Path is the tail after /v0/city/{cityName}. CSRF-header declaration +// is applied automatically for mutation verbs. func cityRegister[I any, O any](sm *SupervisorMux, op huma.Operation, fn func(*Server, context.Context, *I) (*O, error), ) { op.Path = cityScopePrefix + op.Path + if isMutationMethod(op.Method) { + addMutationCSRFParam(&op) + } huma.Register(sm.humaAPI, op, bindCity(sm, fn)) } diff --git a/internal/api/client.go b/internal/api/client.go index e38f771faf..1ac31b07ac 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -244,7 +244,7 @@ func (c *Client) RestartService(name string) error { if err := c.requireCityScope(); err != nil { return err } - resp, err := c.cw.PostV0CityByCityNameServiceByNameRestartWithResponse(context.Background(), c.cityName, name) + resp, err := c.cw.PostV0CityByCityNameServiceByNameRestartWithResponse(context.Background(), c.cityName, name, nil) return checkMutation(resp, err) } @@ -258,7 +258,7 @@ func (c *Client) patchCity(suspend bool) error { if err := c.requireCityScope(); err != nil { return err } - resp, err := c.cw.PatchV0CityByCityNameWithResponse(context.Background(), c.cityName, genclient.PatchV0CityByCityNameJSONRequestBody{Suspended: &suspend}) + resp, err := c.cw.PatchV0CityByCityNameWithResponse(context.Background(), c.cityName, nil, genclient.PatchV0CityByCityNameJSONRequestBody{Suspended: &suspend}) return checkMutation(resp, err) } @@ -285,12 +285,12 @@ func (c *Client) postAgentAction(name, action string) error { if dir, base, ok := strings.Cut(name, "/"); ok { resp, err := c.cw.PostV0CityByCityNameAgentByDirByBaseByActionWithResponse( context.Background(), c.cityName, dir, base, - genclient.PostV0CityByCityNameAgentByDirByBaseByActionParamsAction(action)) + genclient.PostV0CityByCityNameAgentByDirByBaseByActionParamsAction(action), nil) return checkMutation(resp, err) } resp, err := c.cw.PostV0CityByCityNameAgentByBaseByActionWithResponse( context.Background(), c.cityName, name, - genclient.PostV0CityByCityNameAgentByBaseByActionParamsAction(action)) + genclient.PostV0CityByCityNameAgentByBaseByActionParamsAction(action), nil) return checkMutation(resp, err) } @@ -308,7 +308,7 @@ func (c *Client) postRigAction(name, action string) error { if err := c.requireCityScope(); err != nil { return err } - resp, err := c.cw.PostV0CityByCityNameRigByNameByActionWithResponse(context.Background(), c.cityName, name, action) + resp, err := c.cw.PostV0CityByCityNameRigByNameByActionWithResponse(context.Background(), c.cityName, name, action, nil) return checkMutation(resp, err) } @@ -317,7 +317,7 @@ func (c *Client) KillSession(id string) error { if err := c.requireCityScope(); err != nil { return err } - resp, err := c.cw.PostV0CityByCityNameSessionByIdKillWithResponse(context.Background(), c.cityName, id) + resp, err := c.cw.PostV0CityByCityNameSessionByIdKillWithResponse(context.Background(), c.cityName, id, nil) return checkMutation(resp, err) } @@ -329,7 +329,7 @@ func (c *Client) SendSessionMessage(id, message string) error { } ctx, cancel := context.WithTimeout(context.Background(), sessionMessageTimeout) defer cancel() - resp, err := c.cw.SendSessionMessageWithResponse(ctx, c.cityName, id, genclient.SendSessionMessageJSONRequestBody{ + resp, err := c.cw.SendSessionMessageWithResponse(ctx, c.cityName, id, nil, genclient.SendSessionMessageJSONRequestBody{ Message: message, }) return checkMutation(resp, err) @@ -346,7 +346,7 @@ func (c *Client) SubmitSession(id, message string, intent session.SubmitIntent) i := genclient.SubmitIntent(intent) body.Intent = &i } - resp, err := c.cw.SubmitSessionWithResponse(context.Background(), c.cityName, id, body) + resp, err := c.cw.SubmitSessionWithResponse(context.Background(), c.cityName, id, nil, body) if err != nil { return SessionSubmitResponse{}, &connError{err: fmt.Errorf("request failed: %w", err)} } diff --git a/internal/api/event_payloads.go b/internal/api/event_payloads.go index 8c5485c636..df2aeda5af 100644 --- a/internal/api/event_payloads.go +++ b/internal/api/event_payloads.go @@ -28,6 +28,91 @@ type MailEventPayload struct { // IsEventPayload marks MailEventPayload as an events.Payload variant. func (MailEventPayload) IsEventPayload() {} +// CityCreatedPayload is emitted on city.created when the supervisor's +// POST /v0/city handler has scaffolded and registered a new city. +// Consumers subscribed to /v0/events/stream use this event to learn +// about newly-created cities before they are fully initialized. The +// matching city.ready / city.init_failed event follows once the +// supervisor reconciler finishes preparing the city (or gives up). +type CityCreatedPayload struct { + Name string `json:"name"` + Path string `json:"path"` +} + +// IsEventPayload marks CityCreatedPayload as an events.Payload variant. +func (CityCreatedPayload) IsEventPayload() {} + +// CityReadyPayload is emitted on city.ready when the supervisor +// reconciler has finished preparing a city (bead store started, +// formulas resolved, agents validated). The city is now in the +// running inventory and ready to accept work. +type CityReadyPayload struct { + Name string `json:"name"` + Path string `json:"path"` +} + +// IsEventPayload marks CityReadyPayload as an events.Payload variant. +func (CityReadyPayload) IsEventPayload() {} + +// CityInitFailedPayload is emitted on city.init_failed when the +// supervisor reconciler fails to bring up a city. The payload carries +// a human-readable error string sourced from the reconciler step that +// failed (validate rigs, startBeadsLifecycle, etc.) plus the phases +// the reconciler completed before the failure. +type CityInitFailedPayload struct { + Name string `json:"name"` + Path string `json:"path"` + Error string `json:"error"` + PhasesCompleted []string `json:"phases_completed,omitempty"` +} + +// IsEventPayload marks CityInitFailedPayload as an events.Payload variant. +func (CityInitFailedPayload) IsEventPayload() {} + +// CityUnregisterRequestedPayload is emitted on +// city.unregister_requested when a client POSTs +// /v0/city/{cityName}/unregister. Subscribers see this event before +// the supervisor reconciler stops the city's controller, then see +// city.unregistered (success) or city.unregister_failed (stop +// failure) once the reconciler completes. +type CityUnregisterRequestedPayload struct { + Name string `json:"name"` + Path string `json:"path"` +} + +// IsEventPayload marks CityUnregisterRequestedPayload as an +// events.Payload variant. +func (CityUnregisterRequestedPayload) IsEventPayload() {} + +// CityUnregisteredPayload is emitted on city.unregistered when the +// supervisor reconciler has removed a city from its running set +// after the city was removed from the registry. The controller is +// stopped; the city directory is untouched on disk. +type CityUnregisteredPayload struct { + Name string `json:"name"` + Path string `json:"path"` +} + +// IsEventPayload marks CityUnregisteredPayload as an events.Payload +// variant. +func (CityUnregisteredPayload) IsEventPayload() {} + +// CityUnregisterFailedPayload is emitted on city.unregister_failed +// when the supervisor reconciler cannot stop a city's controller +// after its registry entry was removed. The Error field carries a +// human-readable description of what failed (e.g. "controller did +// not stop within timeout"). Operators can inspect the city's +// controller process and retry. +type CityUnregisterFailedPayload struct { + Name string `json:"name"` + Path string `json:"path"` + Error string `json:"error"` +} + +// IsEventPayload marks CityUnregisterFailedPayload as an +// events.Payload variant. +func (CityUnregisterFailedPayload) IsEventPayload() {} + // BeadEventPayload is the shape of every bead.* event payload // (BeadCreated, BeadUpdated, BeadClosed). The payload carries a full // snapshot of the bead as of the event; it is emitted by the beads @@ -97,9 +182,15 @@ func init() { events.RegisterPayload(events.ControllerStopped, events.NoPayload{}) events.RegisterPayload(events.CitySuspended, events.NoPayload{}) events.RegisterPayload(events.CityResumed, events.NoPayload{}) + events.RegisterPayload(events.CityCreated, CityCreatedPayload{}) + events.RegisterPayload(events.CityReady, CityReadyPayload{}) + events.RegisterPayload(events.CityInitFailed, CityInitFailedPayload{}) events.RegisterPayload(events.OrderFired, events.NoPayload{}) events.RegisterPayload(events.OrderCompleted, events.NoPayload{}) events.RegisterPayload(events.OrderFailed, events.NoPayload{}) events.RegisterPayload(events.ProviderSwapped, events.NoPayload{}) events.RegisterPayload(events.WorkerOperation, WorkerOperationEventPayload{}) + events.RegisterPayload(events.CityUnregisterRequested, CityUnregisterRequestedPayload{}) + events.RegisterPayload(events.CityUnregistered, CityUnregisteredPayload{}) + events.RegisterPayload(events.CityUnregisterFailed, CityUnregisterFailedPayload{}) } diff --git a/internal/api/genclient/client_gen.go b/internal/api/genclient/client_gen.go index 2a210adb9a..4144e52ec4 100644 --- a/internal/api/genclient/client_gen.go +++ b/internal/api/genclient/client_gen.go @@ -506,10 +506,19 @@ type CityCreateRequestBootstrapProfile string // CityCreateResponse defines model for CityCreateResponse. type CityCreateResponse struct { - // Ok True on success. + // Name Resolved city name as persisted in city.toml. Use this to filter the event stream for completion. + Name string `json:"name"` + + // Ok True when scaffolding + registration succeeded. Does not imply the city is ready yet; watch /v0/events/stream for city.ready. Ok bool `json:"ok"` - // Path Resolved absolute path of the created city. + // Path Resolved absolute path of the created city directory. + Path string `json:"path"` +} + +// CityCreatedPayload defines model for CityCreatedPayload. +type CityCreatedPayload struct { + Name string `json:"name"` Path string `json:"path"` } @@ -536,12 +545,57 @@ type CityInfo struct { Status *string `json:"status,omitempty"` } +// CityInitFailedPayload defines model for CityInitFailedPayload. +type CityInitFailedPayload struct { + Error string `json:"error"` + Name string `json:"name"` + Path string `json:"path"` + PhasesCompleted *[]string `json:"phases_completed,omitempty"` +} + // CityPatchInputBody defines model for CityPatchInputBody. type CityPatchInputBody struct { // Suspended Whether the city is suspended. Suspended *bool `json:"suspended,omitempty"` } +// CityReadyPayload defines model for CityReadyPayload. +type CityReadyPayload struct { + Name string `json:"name"` + Path string `json:"path"` +} + +// CityUnregisterFailedPayload defines model for CityUnregisterFailedPayload. +type CityUnregisterFailedPayload struct { + Error string `json:"error"` + Name string `json:"name"` + Path string `json:"path"` +} + +// CityUnregisterRequestedPayload defines model for CityUnregisterRequestedPayload. +type CityUnregisterRequestedPayload struct { + Name string `json:"name"` + Path string `json:"path"` +} + +// CityUnregisterResponse defines model for CityUnregisterResponse. +type CityUnregisterResponse struct { + // Name Resolved registry name. Filter the event stream by this to observe completion. + Name string `json:"name"` + + // Ok True when the registry entry was removed and the supervisor was signaled. Does not imply the city's controller has stopped yet; watch /v0/events/stream for city.unregistered. + Ok bool `json:"ok"` + + // Path Resolved absolute city directory. The directory itself is not modified; unregister only affects the supervisor's registry. + Path string `json:"path"` +} + +// CityUnregisteredPayload defines model for CityUnregisteredPayload. +type CityUnregisteredPayload struct { + Name string `json:"name"` + Path string `json:"path"` +} + // ConfigAgentResponse defines model for ConfigAgentResponse. type ConfigAgentResponse struct { Dir *string `json:"dir,omitempty"` @@ -2623,6 +2677,30 @@ type WorkspaceResponse struct { Suspended bool `json:"suspended"` } +// PostV0CityParams defines parameters for PostV0City. +type PostV0CityParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PatchV0CityByCityNameParams defines parameters for PatchV0CityByCityName. +type PatchV0CityByCityNameParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// DeleteV0CityByCityNameAgentByBaseParams defines parameters for DeleteV0CityByCityNameAgentByBase. +type DeleteV0CityByCityNameAgentByBaseParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PatchV0CityByCityNameAgentByBaseParams defines parameters for PatchV0CityByCityNameAgentByBase. +type PatchV0CityByCityNameAgentByBaseParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameAgentByBaseOutputParams defines parameters for GetV0CityByCityNameAgentByBaseOutput. type GetV0CityByCityNameAgentByBaseOutputParams struct { // Tail Number of recent compaction segments to return. This API parameter keeps compaction-segment semantics even though gc session logs --tail counts displayed transcript entries. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. @@ -2632,9 +2710,27 @@ type GetV0CityByCityNameAgentByBaseOutputParams struct { Before *string `form:"before,omitempty" json:"before,omitempty"` } +// PostV0CityByCityNameAgentByBaseByActionParams defines parameters for PostV0CityByCityNameAgentByBaseByAction. +type PostV0CityByCityNameAgentByBaseByActionParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // PostV0CityByCityNameAgentByBaseByActionParamsAction defines parameters for PostV0CityByCityNameAgentByBaseByAction. type PostV0CityByCityNameAgentByBaseByActionParamsAction string +// DeleteV0CityByCityNameAgentByDirByBaseParams defines parameters for DeleteV0CityByCityNameAgentByDirByBase. +type DeleteV0CityByCityNameAgentByDirByBaseParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PatchV0CityByCityNameAgentByDirByBaseParams defines parameters for PatchV0CityByCityNameAgentByDirByBase. +type PatchV0CityByCityNameAgentByDirByBaseParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameAgentByDirByBaseOutputParams defines parameters for GetV0CityByCityNameAgentByDirByBaseOutput. type GetV0CityByCityNameAgentByDirByBaseOutputParams struct { // Tail Number of recent compaction segments to return. This API parameter keeps compaction-segment semantics even though gc session logs --tail counts displayed transcript entries. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. @@ -2644,6 +2740,12 @@ type GetV0CityByCityNameAgentByDirByBaseOutputParams struct { Before *string `form:"before,omitempty" json:"before,omitempty"` } +// PostV0CityByCityNameAgentByDirByBaseByActionParams defines parameters for PostV0CityByCityNameAgentByDirByBaseByAction. +type PostV0CityByCityNameAgentByDirByBaseByActionParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // PostV0CityByCityNameAgentByDirByBaseByActionParamsAction defines parameters for PostV0CityByCityNameAgentByDirByBaseByAction. type PostV0CityByCityNameAgentByDirByBaseByActionParamsAction string @@ -2671,6 +2773,48 @@ type GetV0CityByCityNameAgentsParams struct { // GetV0CityByCityNameAgentsParamsRunning defines parameters for GetV0CityByCityNameAgents. type GetV0CityByCityNameAgentsParamsRunning string +// CreateAgentParams defines parameters for CreateAgent. +type CreateAgentParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// DeleteV0CityByCityNameBeadByIdParams defines parameters for DeleteV0CityByCityNameBeadById. +type DeleteV0CityByCityNameBeadByIdParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PatchV0CityByCityNameBeadByIdParams defines parameters for PatchV0CityByCityNameBeadById. +type PatchV0CityByCityNameBeadByIdParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameBeadByIdAssignParams defines parameters for PostV0CityByCityNameBeadByIdAssign. +type PostV0CityByCityNameBeadByIdAssignParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameBeadByIdCloseParams defines parameters for PostV0CityByCityNameBeadByIdClose. +type PostV0CityByCityNameBeadByIdCloseParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameBeadByIdReopenParams defines parameters for PostV0CityByCityNameBeadByIdReopen. +type PostV0CityByCityNameBeadByIdReopenParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameBeadByIdUpdateParams defines parameters for PostV0CityByCityNameBeadByIdUpdate. +type PostV0CityByCityNameBeadByIdUpdateParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameBeadsParams defines parameters for GetV0CityByCityNameBeads. type GetV0CityByCityNameBeadsParams struct { // Index Event sequence number; when provided, blocks until a newer event arrives. @@ -2703,6 +2847,9 @@ type GetV0CityByCityNameBeadsParams struct { // CreateBeadParams defines parameters for CreateBead. type CreateBeadParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` + // IdempotencyKey Idempotency key for safe retries. IdempotencyKey *string `json:"Idempotency-Key,omitempty"` } @@ -2716,6 +2863,30 @@ type GetV0CityByCityNameBeadsReadyParams struct { Wait *string `form:"wait,omitempty" json:"wait,omitempty"` } +// DeleteV0CityByCityNameConvoyByIdParams defines parameters for DeleteV0CityByCityNameConvoyById. +type DeleteV0CityByCityNameConvoyByIdParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameConvoyByIdAddParams defines parameters for PostV0CityByCityNameConvoyByIdAdd. +type PostV0CityByCityNameConvoyByIdAddParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameConvoyByIdCloseParams defines parameters for PostV0CityByCityNameConvoyByIdClose. +type PostV0CityByCityNameConvoyByIdCloseParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameConvoyByIdRemoveParams defines parameters for PostV0CityByCityNameConvoyByIdRemove. +type PostV0CityByCityNameConvoyByIdRemoveParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameConvoysParams defines parameters for GetV0CityByCityNameConvoys. type GetV0CityByCityNameConvoysParams struct { // Index Event sequence number; when provided, blocks until a newer event arrives. @@ -2731,6 +2902,12 @@ type GetV0CityByCityNameConvoysParams struct { Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` } +// CreateConvoyParams defines parameters for CreateConvoy. +type CreateConvoyParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameEventsParams defines parameters for GetV0CityByCityNameEvents. type GetV0CityByCityNameEventsParams struct { // Index Event sequence number; when provided, blocks until a newer event arrives. @@ -2755,6 +2932,12 @@ type GetV0CityByCityNameEventsParams struct { Since *string `form:"since,omitempty" json:"since,omitempty"` } +// EmitEventParams defines parameters for EmitEvent. +type EmitEventParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // StreamEventsParams defines parameters for StreamEvents. type StreamEventsParams struct { // AfterSeq Reconnect position: only deliver events after this sequence number. @@ -2764,6 +2947,24 @@ type StreamEventsParams struct { LastEventID *string `json:"Last-Event-ID,omitempty"` } +// DeleteV0CityByCityNameExtmsgAdaptersParams defines parameters for DeleteV0CityByCityNameExtmsgAdapters. +type DeleteV0CityByCityNameExtmsgAdaptersParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// RegisterExtmsgAdapterParams defines parameters for RegisterExtmsgAdapter. +type RegisterExtmsgAdapterParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameExtmsgBindParams defines parameters for PostV0CityByCityNameExtmsgBind. +type PostV0CityByCityNameExtmsgBindParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameExtmsgBindingsParams defines parameters for GetV0CityByCityNameExtmsgBindings. type GetV0CityByCityNameExtmsgBindingsParams struct { // SessionId Session ID to list bindings for. @@ -2788,6 +2989,36 @@ type GetV0CityByCityNameExtmsgGroupsParams struct { Kind *string `form:"kind,omitempty" json:"kind,omitempty"` } +// EnsureExtmsgGroupParams defines parameters for EnsureExtmsgGroup. +type EnsureExtmsgGroupParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameExtmsgInboundParams defines parameters for PostV0CityByCityNameExtmsgInbound. +type PostV0CityByCityNameExtmsgInboundParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameExtmsgOutboundParams defines parameters for PostV0CityByCityNameExtmsgOutbound. +type PostV0CityByCityNameExtmsgOutboundParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// DeleteV0CityByCityNameExtmsgParticipantsParams defines parameters for DeleteV0CityByCityNameExtmsgParticipants. +type DeleteV0CityByCityNameExtmsgParticipantsParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameExtmsgParticipantsParams defines parameters for PostV0CityByCityNameExtmsgParticipants. +type PostV0CityByCityNameExtmsgParticipantsParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameExtmsgTranscriptParams defines parameters for GetV0CityByCityNameExtmsgTranscript. type GetV0CityByCityNameExtmsgTranscriptParams struct { // ScopeId Scope ID. @@ -2809,6 +3040,18 @@ type GetV0CityByCityNameExtmsgTranscriptParams struct { Kind *string `form:"kind,omitempty" json:"kind,omitempty"` } +// PostV0CityByCityNameExtmsgTranscriptAckParams defines parameters for PostV0CityByCityNameExtmsgTranscriptAck. +type PostV0CityByCityNameExtmsgTranscriptAckParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameExtmsgUnbindParams defines parameters for PostV0CityByCityNameExtmsgUnbind. +type PostV0CityByCityNameExtmsgUnbindParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameFormulaByNameParams defines parameters for GetV0CityByCityNameFormulaByName. type GetV0CityByCityNameFormulaByNameParams struct { // ScopeKind Scope kind (city or rig). @@ -2854,6 +3097,12 @@ type GetV0CityByCityNameFormulasByNameParams struct { Target string `form:"target" json:"target"` } +// PostV0CityByCityNameFormulasByNamePreviewParams defines parameters for PostV0CityByCityNameFormulasByNamePreview. +type PostV0CityByCityNameFormulasByNamePreviewParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameFormulasByNameRunsParams defines parameters for GetV0CityByCityNameFormulasByNameRuns. type GetV0CityByCityNameFormulasByNameRunsParams struct { // ScopeKind Scope kind (city or rig). @@ -2892,6 +3141,9 @@ type GetV0CityByCityNameMailParams struct { // SendMailParams defines parameters for SendMail. type SendMailParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` + // IdempotencyKey Idempotency key for safe retries. IdempotencyKey *string `json:"Idempotency-Key,omitempty"` } @@ -2915,6 +3167,9 @@ type GetV0CityByCityNameMailThreadByIdParams struct { type DeleteV0CityByCityNameMailByIdParams struct { // Rig Rig hint. Rig *string `form:"rig,omitempty" json:"rig,omitempty"` + + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` } // GetV0CityByCityNameMailByIdParams defines parameters for GetV0CityByCityNameMailById. @@ -2927,24 +3182,36 @@ type GetV0CityByCityNameMailByIdParams struct { type PostV0CityByCityNameMailByIdArchiveParams struct { // Rig Rig hint. Rig *string `form:"rig,omitempty" json:"rig,omitempty"` + + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` } // PostV0CityByCityNameMailByIdMarkUnreadParams defines parameters for PostV0CityByCityNameMailByIdMarkUnread. type PostV0CityByCityNameMailByIdMarkUnreadParams struct { // Rig Rig hint. Rig *string `form:"rig,omitempty" json:"rig,omitempty"` + + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` } // PostV0CityByCityNameMailByIdReadParams defines parameters for PostV0CityByCityNameMailByIdRead. type PostV0CityByCityNameMailByIdReadParams struct { // Rig Rig hint. Rig *string `form:"rig,omitempty" json:"rig,omitempty"` + + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` } // ReplyMailParams defines parameters for ReplyMail. type ReplyMailParams struct { // Rig Rig hint. Rig *string `form:"rig,omitempty" json:"rig,omitempty"` + + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` } // GetV0CityByCityNameOrderHistoryByBeadIdParams defines parameters for GetV0CityByCityNameOrderHistoryByBeadId. @@ -2953,6 +3220,18 @@ type GetV0CityByCityNameOrderHistoryByBeadIdParams struct { StoreRef *string `form:"store_ref,omitempty" json:"store_ref,omitempty"` } +// PostV0CityByCityNameOrderByNameDisableParams defines parameters for PostV0CityByCityNameOrderByNameDisable. +type PostV0CityByCityNameOrderByNameDisableParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameOrderByNameEnableParams defines parameters for PostV0CityByCityNameOrderByNameEnable. +type PostV0CityByCityNameOrderByNameEnableParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameOrdersFeedParams defines parameters for GetV0CityByCityNameOrdersFeed. type GetV0CityByCityNameOrdersFeedParams struct { // ScopeKind Scope kind (city or rig). @@ -2977,6 +3256,48 @@ type GetV0CityByCityNameOrdersHistoryParams struct { Before *string `form:"before,omitempty" json:"before,omitempty"` } +// DeleteV0CityByCityNamePatchesAgentByBaseParams defines parameters for DeleteV0CityByCityNamePatchesAgentByBase. +type DeleteV0CityByCityNamePatchesAgentByBaseParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// DeleteV0CityByCityNamePatchesAgentByDirByBaseParams defines parameters for DeleteV0CityByCityNamePatchesAgentByDirByBase. +type DeleteV0CityByCityNamePatchesAgentByDirByBaseParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PutV0CityByCityNamePatchesAgentsParams defines parameters for PutV0CityByCityNamePatchesAgents. +type PutV0CityByCityNamePatchesAgentsParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// DeleteV0CityByCityNamePatchesProviderByNameParams defines parameters for DeleteV0CityByCityNamePatchesProviderByName. +type DeleteV0CityByCityNamePatchesProviderByNameParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PutV0CityByCityNamePatchesProvidersParams defines parameters for PutV0CityByCityNamePatchesProviders. +type PutV0CityByCityNamePatchesProvidersParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// DeleteV0CityByCityNamePatchesRigByNameParams defines parameters for DeleteV0CityByCityNamePatchesRigByName. +type DeleteV0CityByCityNamePatchesRigByNameParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PutV0CityByCityNamePatchesRigsParams defines parameters for PutV0CityByCityNamePatchesRigs. +type PutV0CityByCityNamePatchesRigsParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameProviderReadinessParams defines parameters for GetV0CityByCityNameProviderReadiness. type GetV0CityByCityNameProviderReadinessParams struct { // Providers Comma-separated provider names to check (default: claude,codex,gemini). @@ -2986,6 +3307,24 @@ type GetV0CityByCityNameProviderReadinessParams struct { Fresh *bool `form:"fresh,omitempty" json:"fresh,omitempty"` } +// DeleteV0CityByCityNameProviderByNameParams defines parameters for DeleteV0CityByCityNameProviderByName. +type DeleteV0CityByCityNameProviderByNameParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PatchV0CityByCityNameProviderByNameParams defines parameters for PatchV0CityByCityNameProviderByName. +type PatchV0CityByCityNameProviderByNameParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// CreateProviderParams defines parameters for CreateProvider. +type CreateProviderParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameReadinessParams defines parameters for GetV0CityByCityNameReadiness. type GetV0CityByCityNameReadinessParams struct { // Items Comma-separated readiness items to check (default: claude,codex,gemini,github_cli). @@ -2995,12 +3334,30 @@ type GetV0CityByCityNameReadinessParams struct { Fresh *bool `form:"fresh,omitempty" json:"fresh,omitempty"` } +// DeleteV0CityByCityNameRigByNameParams defines parameters for DeleteV0CityByCityNameRigByName. +type DeleteV0CityByCityNameRigByNameParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameRigByNameParams defines parameters for GetV0CityByCityNameRigByName. type GetV0CityByCityNameRigByNameParams struct { // Git Include git status. Git *bool `form:"git,omitempty" json:"git,omitempty"` } +// PatchV0CityByCityNameRigByNameParams defines parameters for PatchV0CityByCityNameRigByName. +type PatchV0CityByCityNameRigByNameParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameRigByNameByActionParams defines parameters for PostV0CityByCityNameRigByNameByAction. +type PostV0CityByCityNameRigByNameByActionParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameRigsParams defines parameters for GetV0CityByCityNameRigs. type GetV0CityByCityNameRigsParams struct { // Index Event sequence number; when provided, blocks until a newer event arrives. @@ -3013,16 +3370,67 @@ type GetV0CityByCityNameRigsParams struct { Git *bool `form:"git,omitempty" json:"git,omitempty"` } +// CreateRigParams defines parameters for CreateRig. +type CreateRigParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameServiceByNameRestartParams defines parameters for PostV0CityByCityNameServiceByNameRestart. +type PostV0CityByCityNameServiceByNameRestartParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameSessionByIdParams defines parameters for GetV0CityByCityNameSessionById. type GetV0CityByCityNameSessionByIdParams struct { // Peek Include last output preview. Peek *bool `form:"peek,omitempty" json:"peek,omitempty"` } +// PatchV0CityByCityNameSessionByIdParams defines parameters for PatchV0CityByCityNameSessionById. +type PatchV0CityByCityNameSessionByIdParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // PostV0CityByCityNameSessionByIdCloseParams defines parameters for PostV0CityByCityNameSessionByIdClose. type PostV0CityByCityNameSessionByIdCloseParams struct { // Delete Permanently delete bead after closing. Delete *bool `form:"delete,omitempty" json:"delete,omitempty"` + + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameSessionByIdKillParams defines parameters for PostV0CityByCityNameSessionByIdKill. +type PostV0CityByCityNameSessionByIdKillParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// SendSessionMessageParams defines parameters for SendSessionMessage. +type SendSessionMessageParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameSessionByIdRenameParams defines parameters for PostV0CityByCityNameSessionByIdRename. +type PostV0CityByCityNameSessionByIdRenameParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// RespondSessionParams defines parameters for RespondSession. +type RespondSessionParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameSessionByIdStopParams defines parameters for PostV0CityByCityNameSessionByIdStop. +type PostV0CityByCityNameSessionByIdStopParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` } // StreamSessionParams defines parameters for StreamSession. @@ -3031,6 +3439,18 @@ type StreamSessionParams struct { Format *string `form:"format,omitempty" json:"format,omitempty"` } +// SubmitSessionParams defines parameters for SubmitSession. +type SubmitSessionParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameSessionByIdSuspendParams defines parameters for PostV0CityByCityNameSessionByIdSuspend. +type PostV0CityByCityNameSessionByIdSuspendParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameSessionByIdTranscriptParams defines parameters for GetV0CityByCityNameSessionByIdTranscript. type GetV0CityByCityNameSessionByIdTranscriptParams struct { // Tail Number of recent compaction segments to return. This API parameter keeps compaction-segment semantics even though gc session logs --tail counts displayed transcript entries. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. @@ -3043,6 +3463,12 @@ type GetV0CityByCityNameSessionByIdTranscriptParams struct { Before *string `form:"before,omitempty" json:"before,omitempty"` } +// PostV0CityByCityNameSessionByIdWakeParams defines parameters for PostV0CityByCityNameSessionByIdWake. +type PostV0CityByCityNameSessionByIdWakeParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameSessionsParams defines parameters for GetV0CityByCityNameSessions. type GetV0CityByCityNameSessionsParams struct { // Cursor Pagination cursor from a previous response's next_cursor field. @@ -3061,6 +3487,18 @@ type GetV0CityByCityNameSessionsParams struct { Peek *bool `form:"peek,omitempty" json:"peek,omitempty"` } +// CreateSessionParams defines parameters for CreateSession. +type CreateSessionParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameSlingParams defines parameters for PostV0CityByCityNameSling. +type PostV0CityByCityNameSlingParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // GetV0CityByCityNameStatusParams defines parameters for GetV0CityByCityNameStatus. type GetV0CityByCityNameStatusParams struct { // Index Event sequence number; when provided, blocks until a newer event arrives. @@ -3070,6 +3508,12 @@ type GetV0CityByCityNameStatusParams struct { Wait *string `form:"wait,omitempty" json:"wait,omitempty"` } +// PostV0CityByCityNameUnregisterParams defines parameters for PostV0CityByCityNameUnregister. +type PostV0CityByCityNameUnregisterParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + // DeleteV0CityByCityNameWorkflowByWorkflowIdParams defines parameters for DeleteV0CityByCityNameWorkflowByWorkflowId. type DeleteV0CityByCityNameWorkflowByWorkflowIdParams struct { // ScopeKind Scope kind (city or rig). @@ -3080,6 +3524,9 @@ type DeleteV0CityByCityNameWorkflowByWorkflowIdParams struct { // Delete Permanently delete beads from store. Delete *bool `form:"delete,omitempty" json:"delete,omitempty"` + + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` } // GetV0CityByCityNameWorkflowByWorkflowIdParams defines parameters for GetV0CityByCityNameWorkflowByWorkflowId. @@ -3331,22 +3778,22 @@ func (t *EventPayload) MergeBoundEventPayload(v BoundEventPayload) error { return err } -// AsGroupCreatedEventPayload returns the union data inside the EventPayload as a GroupCreatedEventPayload -func (t EventPayload) AsGroupCreatedEventPayload() (GroupCreatedEventPayload, error) { - var body GroupCreatedEventPayload +// AsCityCreatedPayload returns the union data inside the EventPayload as a CityCreatedPayload +func (t EventPayload) AsCityCreatedPayload() (CityCreatedPayload, error) { + var body CityCreatedPayload err := json.Unmarshal(t.union, &body) return body, err } -// FromGroupCreatedEventPayload overwrites any union data inside the EventPayload as the provided GroupCreatedEventPayload -func (t *EventPayload) FromGroupCreatedEventPayload(v GroupCreatedEventPayload) error { +// FromCityCreatedPayload overwrites any union data inside the EventPayload as the provided CityCreatedPayload +func (t *EventPayload) FromCityCreatedPayload(v CityCreatedPayload) error { b, err := json.Marshal(v) t.union = b return err } -// MergeGroupCreatedEventPayload performs a merge with any union data inside the EventPayload, using the provided GroupCreatedEventPayload -func (t *EventPayload) MergeGroupCreatedEventPayload(v GroupCreatedEventPayload) error { +// MergeCityCreatedPayload performs a merge with any union data inside the EventPayload, using the provided CityCreatedPayload +func (t *EventPayload) MergeCityCreatedPayload(v CityCreatedPayload) error { b, err := json.Marshal(v) if err != nil { return err @@ -3357,22 +3804,22 @@ func (t *EventPayload) MergeGroupCreatedEventPayload(v GroupCreatedEventPayload) return err } -// AsInboundEventPayload returns the union data inside the EventPayload as a InboundEventPayload -func (t EventPayload) AsInboundEventPayload() (InboundEventPayload, error) { - var body InboundEventPayload +// AsCityInitFailedPayload returns the union data inside the EventPayload as a CityInitFailedPayload +func (t EventPayload) AsCityInitFailedPayload() (CityInitFailedPayload, error) { + var body CityInitFailedPayload err := json.Unmarshal(t.union, &body) return body, err } -// FromInboundEventPayload overwrites any union data inside the EventPayload as the provided InboundEventPayload -func (t *EventPayload) FromInboundEventPayload(v InboundEventPayload) error { +// FromCityInitFailedPayload overwrites any union data inside the EventPayload as the provided CityInitFailedPayload +func (t *EventPayload) FromCityInitFailedPayload(v CityInitFailedPayload) error { b, err := json.Marshal(v) t.union = b return err } -// MergeInboundEventPayload performs a merge with any union data inside the EventPayload, using the provided InboundEventPayload -func (t *EventPayload) MergeInboundEventPayload(v InboundEventPayload) error { +// MergeCityInitFailedPayload performs a merge with any union data inside the EventPayload, using the provided CityInitFailedPayload +func (t *EventPayload) MergeCityInitFailedPayload(v CityInitFailedPayload) error { b, err := json.Marshal(v) if err != nil { return err @@ -3383,22 +3830,22 @@ func (t *EventPayload) MergeInboundEventPayload(v InboundEventPayload) error { return err } -// AsMailEventPayload returns the union data inside the EventPayload as a MailEventPayload -func (t EventPayload) AsMailEventPayload() (MailEventPayload, error) { - var body MailEventPayload +// AsCityReadyPayload returns the union data inside the EventPayload as a CityReadyPayload +func (t EventPayload) AsCityReadyPayload() (CityReadyPayload, error) { + var body CityReadyPayload err := json.Unmarshal(t.union, &body) return body, err } -// FromMailEventPayload overwrites any union data inside the EventPayload as the provided MailEventPayload -func (t *EventPayload) FromMailEventPayload(v MailEventPayload) error { +// FromCityReadyPayload overwrites any union data inside the EventPayload as the provided CityReadyPayload +func (t *EventPayload) FromCityReadyPayload(v CityReadyPayload) error { b, err := json.Marshal(v) t.union = b return err } -// MergeMailEventPayload performs a merge with any union data inside the EventPayload, using the provided MailEventPayload -func (t *EventPayload) MergeMailEventPayload(v MailEventPayload) error { +// MergeCityReadyPayload performs a merge with any union data inside the EventPayload, using the provided CityReadyPayload +func (t *EventPayload) MergeCityReadyPayload(v CityReadyPayload) error { b, err := json.Marshal(v) if err != nil { return err @@ -3409,22 +3856,22 @@ func (t *EventPayload) MergeMailEventPayload(v MailEventPayload) error { return err } -// AsNoPayload returns the union data inside the EventPayload as a NoPayload -func (t EventPayload) AsNoPayload() (NoPayload, error) { - var body NoPayload +// AsCityUnregisterFailedPayload returns the union data inside the EventPayload as a CityUnregisterFailedPayload +func (t EventPayload) AsCityUnregisterFailedPayload() (CityUnregisterFailedPayload, error) { + var body CityUnregisterFailedPayload err := json.Unmarshal(t.union, &body) return body, err } -// FromNoPayload overwrites any union data inside the EventPayload as the provided NoPayload -func (t *EventPayload) FromNoPayload(v NoPayload) error { +// FromCityUnregisterFailedPayload overwrites any union data inside the EventPayload as the provided CityUnregisterFailedPayload +func (t *EventPayload) FromCityUnregisterFailedPayload(v CityUnregisterFailedPayload) error { b, err := json.Marshal(v) t.union = b return err } -// MergeNoPayload performs a merge with any union data inside the EventPayload, using the provided NoPayload -func (t *EventPayload) MergeNoPayload(v NoPayload) error { +// MergeCityUnregisterFailedPayload performs a merge with any union data inside the EventPayload, using the provided CityUnregisterFailedPayload +func (t *EventPayload) MergeCityUnregisterFailedPayload(v CityUnregisterFailedPayload) error { b, err := json.Marshal(v) if err != nil { return err @@ -3435,22 +3882,22 @@ func (t *EventPayload) MergeNoPayload(v NoPayload) error { return err } -// AsOutboundEventPayload returns the union data inside the EventPayload as a OutboundEventPayload -func (t EventPayload) AsOutboundEventPayload() (OutboundEventPayload, error) { - var body OutboundEventPayload +// AsCityUnregisterRequestedPayload returns the union data inside the EventPayload as a CityUnregisterRequestedPayload +func (t EventPayload) AsCityUnregisterRequestedPayload() (CityUnregisterRequestedPayload, error) { + var body CityUnregisterRequestedPayload err := json.Unmarshal(t.union, &body) return body, err } -// FromOutboundEventPayload overwrites any union data inside the EventPayload as the provided OutboundEventPayload -func (t *EventPayload) FromOutboundEventPayload(v OutboundEventPayload) error { +// FromCityUnregisterRequestedPayload overwrites any union data inside the EventPayload as the provided CityUnregisterRequestedPayload +func (t *EventPayload) FromCityUnregisterRequestedPayload(v CityUnregisterRequestedPayload) error { b, err := json.Marshal(v) t.union = b return err } -// MergeOutboundEventPayload performs a merge with any union data inside the EventPayload, using the provided OutboundEventPayload -func (t *EventPayload) MergeOutboundEventPayload(v OutboundEventPayload) error { +// MergeCityUnregisterRequestedPayload performs a merge with any union data inside the EventPayload, using the provided CityUnregisterRequestedPayload +func (t *EventPayload) MergeCityUnregisterRequestedPayload(v CityUnregisterRequestedPayload) error { b, err := json.Marshal(v) if err != nil { return err @@ -3461,22 +3908,22 @@ func (t *EventPayload) MergeOutboundEventPayload(v OutboundEventPayload) error { return err } -// AsUnboundEventPayload returns the union data inside the EventPayload as a UnboundEventPayload -func (t EventPayload) AsUnboundEventPayload() (UnboundEventPayload, error) { - var body UnboundEventPayload +// AsCityUnregisteredPayload returns the union data inside the EventPayload as a CityUnregisteredPayload +func (t EventPayload) AsCityUnregisteredPayload() (CityUnregisteredPayload, error) { + var body CityUnregisteredPayload err := json.Unmarshal(t.union, &body) return body, err } -// FromUnboundEventPayload overwrites any union data inside the EventPayload as the provided UnboundEventPayload -func (t *EventPayload) FromUnboundEventPayload(v UnboundEventPayload) error { +// FromCityUnregisteredPayload overwrites any union data inside the EventPayload as the provided CityUnregisteredPayload +func (t *EventPayload) FromCityUnregisteredPayload(v CityUnregisteredPayload) error { b, err := json.Marshal(v) t.union = b return err } -// MergeUnboundEventPayload performs a merge with any union data inside the EventPayload, using the provided UnboundEventPayload -func (t *EventPayload) MergeUnboundEventPayload(v UnboundEventPayload) error { +// MergeCityUnregisteredPayload performs a merge with any union data inside the EventPayload, using the provided CityUnregisteredPayload +func (t *EventPayload) MergeCityUnregisteredPayload(v CityUnregisteredPayload) error { b, err := json.Marshal(v) if err != nil { return err @@ -3487,22 +3934,22 @@ func (t *EventPayload) MergeUnboundEventPayload(v UnboundEventPayload) error { return err } -// AsWorkerOperationEventPayload returns the union data inside the EventPayload as a WorkerOperationEventPayload -func (t EventPayload) AsWorkerOperationEventPayload() (WorkerOperationEventPayload, error) { - var body WorkerOperationEventPayload +// AsGroupCreatedEventPayload returns the union data inside the EventPayload as a GroupCreatedEventPayload +func (t EventPayload) AsGroupCreatedEventPayload() (GroupCreatedEventPayload, error) { + var body GroupCreatedEventPayload err := json.Unmarshal(t.union, &body) return body, err } -// FromWorkerOperationEventPayload overwrites any union data inside the EventPayload as the provided WorkerOperationEventPayload -func (t *EventPayload) FromWorkerOperationEventPayload(v WorkerOperationEventPayload) error { +// FromGroupCreatedEventPayload overwrites any union data inside the EventPayload as the provided GroupCreatedEventPayload +func (t *EventPayload) FromGroupCreatedEventPayload(v GroupCreatedEventPayload) error { b, err := json.Marshal(v) t.union = b return err } -// MergeWorkerOperationEventPayload performs a merge with any union data inside the EventPayload, using the provided WorkerOperationEventPayload -func (t *EventPayload) MergeWorkerOperationEventPayload(v WorkerOperationEventPayload) error { +// MergeGroupCreatedEventPayload performs a merge with any union data inside the EventPayload, using the provided GroupCreatedEventPayload +func (t *EventPayload) MergeGroupCreatedEventPayload(v GroupCreatedEventPayload) error { b, err := json.Marshal(v) if err != nil { return err @@ -3513,33 +3960,189 @@ func (t *EventPayload) MergeWorkerOperationEventPayload(v WorkerOperationEventPa return err } -func (t EventPayload) MarshalJSON() ([]byte, error) { - b, err := t.union.MarshalJSON() - return b, err +// AsInboundEventPayload returns the union data inside the EventPayload as a InboundEventPayload +func (t EventPayload) AsInboundEventPayload() (InboundEventPayload, error) { + var body InboundEventPayload + err := json.Unmarshal(t.union, &body) + return body, err } -func (t *EventPayload) UnmarshalJSON(b []byte) error { - err := t.union.UnmarshalJSON(b) +// FromInboundEventPayload overwrites any union data inside the EventPayload as the provided InboundEventPayload +func (t *EventPayload) FromInboundEventPayload(v InboundEventPayload) error { + b, err := json.Marshal(v) + t.union = b return err } -// RequestEditorFn is the function signature for the RequestEditor callback function -type RequestEditorFn func(ctx context.Context, req *http.Request) error +// MergeInboundEventPayload performs a merge with any union data inside the EventPayload, using the provided InboundEventPayload +func (t *EventPayload) MergeInboundEventPayload(v InboundEventPayload) error { + b, err := json.Marshal(v) + if err != nil { + return err + } -// Doer performs HTTP requests. -// -// The standard http.Client implements this interface. -type HttpRequestDoer interface { - Do(req *http.Request) (*http.Response, error) + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// Client which conforms to the OpenAPI3 specification for this service. -type Client struct { - // The endpoint of the server conforming to this interface, with scheme, - // https://api.deepmap.com for example. This can contain a path relative - // to the server, such as https://api.deepmap.com/dev-test, and all the - // paths in the swagger spec will be appended to the server. - Server string +// AsMailEventPayload returns the union data inside the EventPayload as a MailEventPayload +func (t EventPayload) AsMailEventPayload() (MailEventPayload, error) { + var body MailEventPayload + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromMailEventPayload overwrites any union data inside the EventPayload as the provided MailEventPayload +func (t *EventPayload) FromMailEventPayload(v MailEventPayload) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeMailEventPayload performs a merge with any union data inside the EventPayload, using the provided MailEventPayload +func (t *EventPayload) MergeMailEventPayload(v MailEventPayload) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsNoPayload returns the union data inside the EventPayload as a NoPayload +func (t EventPayload) AsNoPayload() (NoPayload, error) { + var body NoPayload + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromNoPayload overwrites any union data inside the EventPayload as the provided NoPayload +func (t *EventPayload) FromNoPayload(v NoPayload) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeNoPayload performs a merge with any union data inside the EventPayload, using the provided NoPayload +func (t *EventPayload) MergeNoPayload(v NoPayload) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsOutboundEventPayload returns the union data inside the EventPayload as a OutboundEventPayload +func (t EventPayload) AsOutboundEventPayload() (OutboundEventPayload, error) { + var body OutboundEventPayload + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromOutboundEventPayload overwrites any union data inside the EventPayload as the provided OutboundEventPayload +func (t *EventPayload) FromOutboundEventPayload(v OutboundEventPayload) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeOutboundEventPayload performs a merge with any union data inside the EventPayload, using the provided OutboundEventPayload +func (t *EventPayload) MergeOutboundEventPayload(v OutboundEventPayload) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsUnboundEventPayload returns the union data inside the EventPayload as a UnboundEventPayload +func (t EventPayload) AsUnboundEventPayload() (UnboundEventPayload, error) { + var body UnboundEventPayload + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromUnboundEventPayload overwrites any union data inside the EventPayload as the provided UnboundEventPayload +func (t *EventPayload) FromUnboundEventPayload(v UnboundEventPayload) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeUnboundEventPayload performs a merge with any union data inside the EventPayload, using the provided UnboundEventPayload +func (t *EventPayload) MergeUnboundEventPayload(v UnboundEventPayload) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsWorkerOperationEventPayload returns the union data inside the EventPayload as a WorkerOperationEventPayload +func (t EventPayload) AsWorkerOperationEventPayload() (WorkerOperationEventPayload, error) { + var body WorkerOperationEventPayload + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromWorkerOperationEventPayload overwrites any union data inside the EventPayload as the provided WorkerOperationEventPayload +func (t *EventPayload) FromWorkerOperationEventPayload(v WorkerOperationEventPayload) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeWorkerOperationEventPayload performs a merge with any union data inside the EventPayload, using the provided WorkerOperationEventPayload +func (t *EventPayload) MergeWorkerOperationEventPayload(v WorkerOperationEventPayload) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t EventPayload) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *EventPayload) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string // Doer for performing requests, typically a *http.Client with any // customized settings, such as certificate chains. @@ -3603,28 +4206,28 @@ type ClientInterface interface { GetV0Cities(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityWithBody request with any body - PostV0CityWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityWithBody(ctx context.Context, params *PostV0CityParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostV0City(ctx context.Context, body PostV0CityJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0City(ctx context.Context, params *PostV0CityParams, body PostV0CityJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityName request GetV0CityByCityName(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*http.Response, error) // PatchV0CityByCityNameWithBody request with any body - PatchV0CityByCityNameWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PatchV0CityByCityNameWithBody(ctx context.Context, cityName string, params *PatchV0CityByCityNameParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PatchV0CityByCityName(ctx context.Context, cityName string, body PatchV0CityByCityNameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PatchV0CityByCityName(ctx context.Context, cityName string, params *PatchV0CityByCityNameParams, body PatchV0CityByCityNameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteV0CityByCityNameAgentByBase request - DeleteV0CityByCityNameAgentByBase(ctx context.Context, cityName string, base string, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteV0CityByCityNameAgentByBase(ctx context.Context, cityName string, base string, params *DeleteV0CityByCityNameAgentByBaseParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameAgentByBase request GetV0CityByCityNameAgentByBase(ctx context.Context, cityName string, base string, reqEditors ...RequestEditorFn) (*http.Response, error) // PatchV0CityByCityNameAgentByBaseWithBody request with any body - PatchV0CityByCityNameAgentByBaseWithBody(ctx context.Context, cityName string, base string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PatchV0CityByCityNameAgentByBaseWithBody(ctx context.Context, cityName string, base string, params *PatchV0CityByCityNameAgentByBaseParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PatchV0CityByCityNameAgentByBase(ctx context.Context, cityName string, base string, body PatchV0CityByCityNameAgentByBaseJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PatchV0CityByCityNameAgentByBase(ctx context.Context, cityName string, base string, params *PatchV0CityByCityNameAgentByBaseParams, body PatchV0CityByCityNameAgentByBaseJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameAgentByBaseOutput request GetV0CityByCityNameAgentByBaseOutput(ctx context.Context, cityName string, base string, params *GetV0CityByCityNameAgentByBaseOutputParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3633,18 +4236,18 @@ type ClientInterface interface { StreamAgentOutput(ctx context.Context, cityName string, base string, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameAgentByBaseByAction request - PostV0CityByCityNameAgentByBaseByAction(ctx context.Context, cityName string, base string, action PostV0CityByCityNameAgentByBaseByActionParamsAction, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameAgentByBaseByAction(ctx context.Context, cityName string, base string, action PostV0CityByCityNameAgentByBaseByActionParamsAction, params *PostV0CityByCityNameAgentByBaseByActionParams, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteV0CityByCityNameAgentByDirByBase request - DeleteV0CityByCityNameAgentByDirByBase(ctx context.Context, cityName string, dir string, base string, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteV0CityByCityNameAgentByDirByBase(ctx context.Context, cityName string, dir string, base string, params *DeleteV0CityByCityNameAgentByDirByBaseParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameAgentByDirByBase request GetV0CityByCityNameAgentByDirByBase(ctx context.Context, cityName string, dir string, base string, reqEditors ...RequestEditorFn) (*http.Response, error) // PatchV0CityByCityNameAgentByDirByBaseWithBody request with any body - PatchV0CityByCityNameAgentByDirByBaseWithBody(ctx context.Context, cityName string, dir string, base string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PatchV0CityByCityNameAgentByDirByBaseWithBody(ctx context.Context, cityName string, dir string, base string, params *PatchV0CityByCityNameAgentByDirByBaseParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PatchV0CityByCityNameAgentByDirByBase(ctx context.Context, cityName string, dir string, base string, body PatchV0CityByCityNameAgentByDirByBaseJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PatchV0CityByCityNameAgentByDirByBase(ctx context.Context, cityName string, dir string, base string, params *PatchV0CityByCityNameAgentByDirByBaseParams, body PatchV0CityByCityNameAgentByDirByBaseJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameAgentByDirByBaseOutput request GetV0CityByCityNameAgentByDirByBaseOutput(ctx context.Context, cityName string, dir string, base string, params *GetV0CityByCityNameAgentByDirByBaseOutputParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3653,45 +4256,45 @@ type ClientInterface interface { StreamAgentOutputQualified(ctx context.Context, cityName string, dir string, base string, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameAgentByDirByBaseByAction request - PostV0CityByCityNameAgentByDirByBaseByAction(ctx context.Context, cityName string, dir string, base string, action PostV0CityByCityNameAgentByDirByBaseByActionParamsAction, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameAgentByDirByBaseByAction(ctx context.Context, cityName string, dir string, base string, action PostV0CityByCityNameAgentByDirByBaseByActionParamsAction, params *PostV0CityByCityNameAgentByDirByBaseByActionParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameAgents request GetV0CityByCityNameAgents(ctx context.Context, cityName string, params *GetV0CityByCityNameAgentsParams, reqEditors ...RequestEditorFn) (*http.Response, error) // CreateAgentWithBody request with any body - CreateAgentWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateAgentWithBody(ctx context.Context, cityName string, params *CreateAgentParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - CreateAgent(ctx context.Context, cityName string, body CreateAgentJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateAgent(ctx context.Context, cityName string, params *CreateAgentParams, body CreateAgentJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteV0CityByCityNameBeadById request - DeleteV0CityByCityNameBeadById(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteV0CityByCityNameBeadById(ctx context.Context, cityName string, id string, params *DeleteV0CityByCityNameBeadByIdParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameBeadById request GetV0CityByCityNameBeadById(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) // PatchV0CityByCityNameBeadByIdWithBody request with any body - PatchV0CityByCityNameBeadByIdWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PatchV0CityByCityNameBeadByIdWithBody(ctx context.Context, cityName string, id string, params *PatchV0CityByCityNameBeadByIdParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PatchV0CityByCityNameBeadById(ctx context.Context, cityName string, id string, body PatchV0CityByCityNameBeadByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PatchV0CityByCityNameBeadById(ctx context.Context, cityName string, id string, params *PatchV0CityByCityNameBeadByIdParams, body PatchV0CityByCityNameBeadByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameBeadByIdAssignWithBody request with any body - PostV0CityByCityNameBeadByIdAssignWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameBeadByIdAssignWithBody(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdAssignParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostV0CityByCityNameBeadByIdAssign(ctx context.Context, cityName string, id string, body PostV0CityByCityNameBeadByIdAssignJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameBeadByIdAssign(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdAssignParams, body PostV0CityByCityNameBeadByIdAssignJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameBeadByIdClose request - PostV0CityByCityNameBeadByIdClose(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameBeadByIdClose(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdCloseParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameBeadByIdDeps request GetV0CityByCityNameBeadByIdDeps(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameBeadByIdReopen request - PostV0CityByCityNameBeadByIdReopen(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameBeadByIdReopen(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdReopenParams, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameBeadByIdUpdateWithBody request with any body - PostV0CityByCityNameBeadByIdUpdateWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameBeadByIdUpdateWithBody(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdUpdateParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostV0CityByCityNameBeadByIdUpdate(ctx context.Context, cityName string, id string, body PostV0CityByCityNameBeadByIdUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameBeadByIdUpdate(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdUpdateParams, body PostV0CityByCityNameBeadByIdUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameBeads request GetV0CityByCityNameBeads(ctx context.Context, cityName string, params *GetV0CityByCityNameBeadsParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3717,63 +4320,63 @@ type ClientInterface interface { GetV0CityByCityNameConfigValidate(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteV0CityByCityNameConvoyById request - DeleteV0CityByCityNameConvoyById(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteV0CityByCityNameConvoyById(ctx context.Context, cityName string, id string, params *DeleteV0CityByCityNameConvoyByIdParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameConvoyById request GetV0CityByCityNameConvoyById(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameConvoyByIdAddWithBody request with any body - PostV0CityByCityNameConvoyByIdAddWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameConvoyByIdAddWithBody(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdAddParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostV0CityByCityNameConvoyByIdAdd(ctx context.Context, cityName string, id string, body PostV0CityByCityNameConvoyByIdAddJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameConvoyByIdAdd(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdAddParams, body PostV0CityByCityNameConvoyByIdAddJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameConvoyByIdCheck request GetV0CityByCityNameConvoyByIdCheck(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameConvoyByIdClose request - PostV0CityByCityNameConvoyByIdClose(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameConvoyByIdClose(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdCloseParams, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameConvoyByIdRemoveWithBody request with any body - PostV0CityByCityNameConvoyByIdRemoveWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameConvoyByIdRemoveWithBody(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdRemoveParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostV0CityByCityNameConvoyByIdRemove(ctx context.Context, cityName string, id string, body PostV0CityByCityNameConvoyByIdRemoveJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameConvoyByIdRemove(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdRemoveParams, body PostV0CityByCityNameConvoyByIdRemoveJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameConvoys request GetV0CityByCityNameConvoys(ctx context.Context, cityName string, params *GetV0CityByCityNameConvoysParams, reqEditors ...RequestEditorFn) (*http.Response, error) // CreateConvoyWithBody request with any body - CreateConvoyWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateConvoyWithBody(ctx context.Context, cityName string, params *CreateConvoyParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - CreateConvoy(ctx context.Context, cityName string, body CreateConvoyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateConvoy(ctx context.Context, cityName string, params *CreateConvoyParams, body CreateConvoyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameEvents request GetV0CityByCityNameEvents(ctx context.Context, cityName string, params *GetV0CityByCityNameEventsParams, reqEditors ...RequestEditorFn) (*http.Response, error) // EmitEventWithBody request with any body - EmitEventWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + EmitEventWithBody(ctx context.Context, cityName string, params *EmitEventParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - EmitEvent(ctx context.Context, cityName string, body EmitEventJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + EmitEvent(ctx context.Context, cityName string, params *EmitEventParams, body EmitEventJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // StreamEvents request StreamEvents(ctx context.Context, cityName string, params *StreamEventsParams, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteV0CityByCityNameExtmsgAdaptersWithBody request with any body - DeleteV0CityByCityNameExtmsgAdaptersWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteV0CityByCityNameExtmsgAdaptersWithBody(ctx context.Context, cityName string, params *DeleteV0CityByCityNameExtmsgAdaptersParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - DeleteV0CityByCityNameExtmsgAdapters(ctx context.Context, cityName string, body DeleteV0CityByCityNameExtmsgAdaptersJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteV0CityByCityNameExtmsgAdapters(ctx context.Context, cityName string, params *DeleteV0CityByCityNameExtmsgAdaptersParams, body DeleteV0CityByCityNameExtmsgAdaptersJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameExtmsgAdapters request GetV0CityByCityNameExtmsgAdapters(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*http.Response, error) // RegisterExtmsgAdapterWithBody request with any body - RegisterExtmsgAdapterWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + RegisterExtmsgAdapterWithBody(ctx context.Context, cityName string, params *RegisterExtmsgAdapterParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - RegisterExtmsgAdapter(ctx context.Context, cityName string, body RegisterExtmsgAdapterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + RegisterExtmsgAdapter(ctx context.Context, cityName string, params *RegisterExtmsgAdapterParams, body RegisterExtmsgAdapterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameExtmsgBindWithBody request with any body - PostV0CityByCityNameExtmsgBindWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameExtmsgBindWithBody(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgBindParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostV0CityByCityNameExtmsgBind(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgBindJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameExtmsgBind(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgBindParams, body PostV0CityByCityNameExtmsgBindJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameExtmsgBindings request GetV0CityByCityNameExtmsgBindings(ctx context.Context, cityName string, params *GetV0CityByCityNameExtmsgBindingsParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3782,42 +4385,42 @@ type ClientInterface interface { GetV0CityByCityNameExtmsgGroups(ctx context.Context, cityName string, params *GetV0CityByCityNameExtmsgGroupsParams, reqEditors ...RequestEditorFn) (*http.Response, error) // EnsureExtmsgGroupWithBody request with any body - EnsureExtmsgGroupWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + EnsureExtmsgGroupWithBody(ctx context.Context, cityName string, params *EnsureExtmsgGroupParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - EnsureExtmsgGroup(ctx context.Context, cityName string, body EnsureExtmsgGroupJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + EnsureExtmsgGroup(ctx context.Context, cityName string, params *EnsureExtmsgGroupParams, body EnsureExtmsgGroupJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameExtmsgInboundWithBody request with any body - PostV0CityByCityNameExtmsgInboundWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameExtmsgInboundWithBody(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgInboundParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostV0CityByCityNameExtmsgInbound(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgInboundJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameExtmsgInbound(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgInboundParams, body PostV0CityByCityNameExtmsgInboundJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameExtmsgOutboundWithBody request with any body - PostV0CityByCityNameExtmsgOutboundWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameExtmsgOutboundWithBody(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgOutboundParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostV0CityByCityNameExtmsgOutbound(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgOutboundJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameExtmsgOutbound(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgOutboundParams, body PostV0CityByCityNameExtmsgOutboundJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteV0CityByCityNameExtmsgParticipantsWithBody request with any body - DeleteV0CityByCityNameExtmsgParticipantsWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteV0CityByCityNameExtmsgParticipantsWithBody(ctx context.Context, cityName string, params *DeleteV0CityByCityNameExtmsgParticipantsParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - DeleteV0CityByCityNameExtmsgParticipants(ctx context.Context, cityName string, body DeleteV0CityByCityNameExtmsgParticipantsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteV0CityByCityNameExtmsgParticipants(ctx context.Context, cityName string, params *DeleteV0CityByCityNameExtmsgParticipantsParams, body DeleteV0CityByCityNameExtmsgParticipantsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameExtmsgParticipantsWithBody request with any body - PostV0CityByCityNameExtmsgParticipantsWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameExtmsgParticipantsWithBody(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgParticipantsParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostV0CityByCityNameExtmsgParticipants(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgParticipantsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameExtmsgParticipants(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgParticipantsParams, body PostV0CityByCityNameExtmsgParticipantsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameExtmsgTranscript request GetV0CityByCityNameExtmsgTranscript(ctx context.Context, cityName string, params *GetV0CityByCityNameExtmsgTranscriptParams, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameExtmsgTranscriptAckWithBody request with any body - PostV0CityByCityNameExtmsgTranscriptAckWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameExtmsgTranscriptAckWithBody(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgTranscriptAckParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostV0CityByCityNameExtmsgTranscriptAck(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgTranscriptAckJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameExtmsgTranscriptAck(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgTranscriptAckParams, body PostV0CityByCityNameExtmsgTranscriptAckJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameExtmsgUnbindWithBody request with any body - PostV0CityByCityNameExtmsgUnbindWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameExtmsgUnbindWithBody(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgUnbindParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostV0CityByCityNameExtmsgUnbind(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgUnbindJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameExtmsgUnbind(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgUnbindParams, body PostV0CityByCityNameExtmsgUnbindJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameFormulaByName request GetV0CityByCityNameFormulaByName(ctx context.Context, cityName string, name string, params *GetV0CityByCityNameFormulaByNameParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3832,9 +4435,9 @@ type ClientInterface interface { GetV0CityByCityNameFormulasByName(ctx context.Context, cityName string, name string, params *GetV0CityByCityNameFormulasByNameParams, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameFormulasByNamePreviewWithBody request with any body - PostV0CityByCityNameFormulasByNamePreviewWithBody(ctx context.Context, cityName string, name string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameFormulasByNamePreviewWithBody(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameFormulasByNamePreviewParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostV0CityByCityNameFormulasByNamePreview(ctx context.Context, cityName string, name string, body PostV0CityByCityNameFormulasByNamePreviewJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameFormulasByNamePreview(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameFormulasByNamePreviewParams, body PostV0CityByCityNameFormulasByNamePreviewJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameFormulasByNameRuns request GetV0CityByCityNameFormulasByNameRuns(ctx context.Context, cityName string, name string, params *GetV0CityByCityNameFormulasByNameRunsParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3883,10 +4486,10 @@ type ClientInterface interface { GetV0CityByCityNameOrderByName(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameOrderByNameDisable request - PostV0CityByCityNameOrderByNameDisable(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameOrderByNameDisable(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameOrderByNameDisableParams, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameOrderByNameEnable request - PostV0CityByCityNameOrderByNameEnable(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameOrderByNameEnable(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameOrderByNameEnableParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameOrders request GetV0CityByCityNameOrders(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3904,13 +4507,13 @@ type ClientInterface interface { GetV0CityByCityNamePacks(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteV0CityByCityNamePatchesAgentByBase request - DeleteV0CityByCityNamePatchesAgentByBase(ctx context.Context, cityName string, base string, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteV0CityByCityNamePatchesAgentByBase(ctx context.Context, cityName string, base string, params *DeleteV0CityByCityNamePatchesAgentByBaseParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNamePatchesAgentByBase request GetV0CityByCityNamePatchesAgentByBase(ctx context.Context, cityName string, base string, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteV0CityByCityNamePatchesAgentByDirByBase request - DeleteV0CityByCityNamePatchesAgentByDirByBase(ctx context.Context, cityName string, dir string, base string, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteV0CityByCityNamePatchesAgentByDirByBase(ctx context.Context, cityName string, dir string, base string, params *DeleteV0CityByCityNamePatchesAgentByDirByBaseParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNamePatchesAgentByDirByBase request GetV0CityByCityNamePatchesAgentByDirByBase(ctx context.Context, cityName string, dir string, base string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3919,12 +4522,12 @@ type ClientInterface interface { GetV0CityByCityNamePatchesAgents(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*http.Response, error) // PutV0CityByCityNamePatchesAgentsWithBody request with any body - PutV0CityByCityNamePatchesAgentsWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PutV0CityByCityNamePatchesAgentsWithBody(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesAgentsParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PutV0CityByCityNamePatchesAgents(ctx context.Context, cityName string, body PutV0CityByCityNamePatchesAgentsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PutV0CityByCityNamePatchesAgents(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesAgentsParams, body PutV0CityByCityNamePatchesAgentsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteV0CityByCityNamePatchesProviderByName request - DeleteV0CityByCityNamePatchesProviderByName(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteV0CityByCityNamePatchesProviderByName(ctx context.Context, cityName string, name string, params *DeleteV0CityByCityNamePatchesProviderByNameParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNamePatchesProviderByName request GetV0CityByCityNamePatchesProviderByName(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3933,12 +4536,12 @@ type ClientInterface interface { GetV0CityByCityNamePatchesProviders(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*http.Response, error) // PutV0CityByCityNamePatchesProvidersWithBody request with any body - PutV0CityByCityNamePatchesProvidersWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PutV0CityByCityNamePatchesProvidersWithBody(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesProvidersParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PutV0CityByCityNamePatchesProviders(ctx context.Context, cityName string, body PutV0CityByCityNamePatchesProvidersJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PutV0CityByCityNamePatchesProviders(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesProvidersParams, body PutV0CityByCityNamePatchesProvidersJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteV0CityByCityNamePatchesRigByName request - DeleteV0CityByCityNamePatchesRigByName(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteV0CityByCityNamePatchesRigByName(ctx context.Context, cityName string, name string, params *DeleteV0CityByCityNamePatchesRigByNameParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNamePatchesRigByName request GetV0CityByCityNamePatchesRigByName(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3947,31 +4550,31 @@ type ClientInterface interface { GetV0CityByCityNamePatchesRigs(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*http.Response, error) // PutV0CityByCityNamePatchesRigsWithBody request with any body - PutV0CityByCityNamePatchesRigsWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PutV0CityByCityNamePatchesRigsWithBody(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesRigsParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PutV0CityByCityNamePatchesRigs(ctx context.Context, cityName string, body PutV0CityByCityNamePatchesRigsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PutV0CityByCityNamePatchesRigs(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesRigsParams, body PutV0CityByCityNamePatchesRigsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameProviderReadiness request GetV0CityByCityNameProviderReadiness(ctx context.Context, cityName string, params *GetV0CityByCityNameProviderReadinessParams, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteV0CityByCityNameProviderByName request - DeleteV0CityByCityNameProviderByName(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteV0CityByCityNameProviderByName(ctx context.Context, cityName string, name string, params *DeleteV0CityByCityNameProviderByNameParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameProviderByName request GetV0CityByCityNameProviderByName(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) // PatchV0CityByCityNameProviderByNameWithBody request with any body - PatchV0CityByCityNameProviderByNameWithBody(ctx context.Context, cityName string, name string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PatchV0CityByCityNameProviderByNameWithBody(ctx context.Context, cityName string, name string, params *PatchV0CityByCityNameProviderByNameParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PatchV0CityByCityNameProviderByName(ctx context.Context, cityName string, name string, body PatchV0CityByCityNameProviderByNameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PatchV0CityByCityNameProviderByName(ctx context.Context, cityName string, name string, params *PatchV0CityByCityNameProviderByNameParams, body PatchV0CityByCityNameProviderByNameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameProviders request GetV0CityByCityNameProviders(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*http.Response, error) // CreateProviderWithBody request with any body - CreateProviderWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateProviderWithBody(ctx context.Context, cityName string, params *CreateProviderParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - CreateProvider(ctx context.Context, cityName string, body CreateProviderJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateProvider(ctx context.Context, cityName string, params *CreateProviderParams, body CreateProviderJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameProvidersPublic request GetV0CityByCityNameProvidersPublic(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3980,32 +4583,32 @@ type ClientInterface interface { GetV0CityByCityNameReadiness(ctx context.Context, cityName string, params *GetV0CityByCityNameReadinessParams, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteV0CityByCityNameRigByName request - DeleteV0CityByCityNameRigByName(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteV0CityByCityNameRigByName(ctx context.Context, cityName string, name string, params *DeleteV0CityByCityNameRigByNameParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameRigByName request GetV0CityByCityNameRigByName(ctx context.Context, cityName string, name string, params *GetV0CityByCityNameRigByNameParams, reqEditors ...RequestEditorFn) (*http.Response, error) // PatchV0CityByCityNameRigByNameWithBody request with any body - PatchV0CityByCityNameRigByNameWithBody(ctx context.Context, cityName string, name string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PatchV0CityByCityNameRigByNameWithBody(ctx context.Context, cityName string, name string, params *PatchV0CityByCityNameRigByNameParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PatchV0CityByCityNameRigByName(ctx context.Context, cityName string, name string, body PatchV0CityByCityNameRigByNameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PatchV0CityByCityNameRigByName(ctx context.Context, cityName string, name string, params *PatchV0CityByCityNameRigByNameParams, body PatchV0CityByCityNameRigByNameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameRigByNameByAction request - PostV0CityByCityNameRigByNameByAction(ctx context.Context, cityName string, name string, action string, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameRigByNameByAction(ctx context.Context, cityName string, name string, action string, params *PostV0CityByCityNameRigByNameByActionParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameRigs request GetV0CityByCityNameRigs(ctx context.Context, cityName string, params *GetV0CityByCityNameRigsParams, reqEditors ...RequestEditorFn) (*http.Response, error) // CreateRigWithBody request with any body - CreateRigWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateRigWithBody(ctx context.Context, cityName string, params *CreateRigParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - CreateRig(ctx context.Context, cityName string, body CreateRigJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateRig(ctx context.Context, cityName string, params *CreateRigParams, body CreateRigJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameServiceByName request GetV0CityByCityNameServiceByName(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameServiceByNameRestart request - PostV0CityByCityNameServiceByNameRestart(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameServiceByNameRestart(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameServiceByNameRestartParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameServices request GetV0CityByCityNameServices(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -4014,9 +4617,9 @@ type ClientInterface interface { GetV0CityByCityNameSessionById(ctx context.Context, cityName string, id string, params *GetV0CityByCityNameSessionByIdParams, reqEditors ...RequestEditorFn) (*http.Response, error) // PatchV0CityByCityNameSessionByIdWithBody request with any body - PatchV0CityByCityNameSessionByIdWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PatchV0CityByCityNameSessionByIdWithBody(ctx context.Context, cityName string, id string, params *PatchV0CityByCityNameSessionByIdParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PatchV0CityByCityNameSessionById(ctx context.Context, cityName string, id string, body PatchV0CityByCityNameSessionByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PatchV0CityByCityNameSessionById(ctx context.Context, cityName string, id string, params *PatchV0CityByCityNameSessionByIdParams, body PatchV0CityByCityNameSessionByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameSessionByIdAgents request GetV0CityByCityNameSessionByIdAgents(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -4028,62 +4631,65 @@ type ClientInterface interface { PostV0CityByCityNameSessionByIdClose(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdCloseParams, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameSessionByIdKill request - PostV0CityByCityNameSessionByIdKill(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameSessionByIdKill(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdKillParams, reqEditors ...RequestEditorFn) (*http.Response, error) // SendSessionMessageWithBody request with any body - SendSessionMessageWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + SendSessionMessageWithBody(ctx context.Context, cityName string, id string, params *SendSessionMessageParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - SendSessionMessage(ctx context.Context, cityName string, id string, body SendSessionMessageJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + SendSessionMessage(ctx context.Context, cityName string, id string, params *SendSessionMessageParams, body SendSessionMessageJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameSessionByIdPending request GetV0CityByCityNameSessionByIdPending(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameSessionByIdRenameWithBody request with any body - PostV0CityByCityNameSessionByIdRenameWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameSessionByIdRenameWithBody(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdRenameParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostV0CityByCityNameSessionByIdRename(ctx context.Context, cityName string, id string, body PostV0CityByCityNameSessionByIdRenameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameSessionByIdRename(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdRenameParams, body PostV0CityByCityNameSessionByIdRenameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // RespondSessionWithBody request with any body - RespondSessionWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + RespondSessionWithBody(ctx context.Context, cityName string, id string, params *RespondSessionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - RespondSession(ctx context.Context, cityName string, id string, body RespondSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + RespondSession(ctx context.Context, cityName string, id string, params *RespondSessionParams, body RespondSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameSessionByIdStop request - PostV0CityByCityNameSessionByIdStop(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameSessionByIdStop(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdStopParams, reqEditors ...RequestEditorFn) (*http.Response, error) // StreamSession request StreamSession(ctx context.Context, cityName string, id string, params *StreamSessionParams, reqEditors ...RequestEditorFn) (*http.Response, error) // SubmitSessionWithBody request with any body - SubmitSessionWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + SubmitSessionWithBody(ctx context.Context, cityName string, id string, params *SubmitSessionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - SubmitSession(ctx context.Context, cityName string, id string, body SubmitSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + SubmitSession(ctx context.Context, cityName string, id string, params *SubmitSessionParams, body SubmitSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameSessionByIdSuspend request - PostV0CityByCityNameSessionByIdSuspend(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameSessionByIdSuspend(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdSuspendParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameSessionByIdTranscript request GetV0CityByCityNameSessionByIdTranscript(ctx context.Context, cityName string, id string, params *GetV0CityByCityNameSessionByIdTranscriptParams, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameSessionByIdWake request - PostV0CityByCityNameSessionByIdWake(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameSessionByIdWake(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdWakeParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameSessions request GetV0CityByCityNameSessions(ctx context.Context, cityName string, params *GetV0CityByCityNameSessionsParams, reqEditors ...RequestEditorFn) (*http.Response, error) // CreateSessionWithBody request with any body - CreateSessionWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateSessionWithBody(ctx context.Context, cityName string, params *CreateSessionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - CreateSession(ctx context.Context, cityName string, body CreateSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateSession(ctx context.Context, cityName string, params *CreateSessionParams, body CreateSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // PostV0CityByCityNameSlingWithBody request with any body - PostV0CityByCityNameSlingWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameSlingWithBody(ctx context.Context, cityName string, params *PostV0CityByCityNameSlingParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostV0CityByCityNameSling(ctx context.Context, cityName string, body PostV0CityByCityNameSlingJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostV0CityByCityNameSling(ctx context.Context, cityName string, params *PostV0CityByCityNameSlingParams, body PostV0CityByCityNameSlingJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetV0CityByCityNameStatus request GetV0CityByCityNameStatus(ctx context.Context, cityName string, params *GetV0CityByCityNameStatusParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // PostV0CityByCityNameUnregister request + PostV0CityByCityNameUnregister(ctx context.Context, cityName string, params *PostV0CityByCityNameUnregisterParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteV0CityByCityNameWorkflowByWorkflowId request DeleteV0CityByCityNameWorkflowByWorkflowId(ctx context.Context, cityName string, workflowId string, params *DeleteV0CityByCityNameWorkflowByWorkflowIdParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -4127,8 +4733,8 @@ func (c *Client) GetV0Cities(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } -func (c *Client) PostV0CityWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityRequestWithBody(c.Server, contentType, body) +func (c *Client) PostV0CityWithBody(ctx context.Context, params *PostV0CityParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityRequestWithBody(c.Server, params, contentType, body) if err != nil { return nil, err } @@ -4139,8 +4745,8 @@ func (c *Client) PostV0CityWithBody(ctx context.Context, contentType string, bod return c.Client.Do(req) } -func (c *Client) PostV0City(ctx context.Context, body PostV0CityJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityRequest(c.Server, body) +func (c *Client) PostV0City(ctx context.Context, params *PostV0CityParams, body PostV0CityJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityRequest(c.Server, params, body) if err != nil { return nil, err } @@ -4163,8 +4769,8 @@ func (c *Client) GetV0CityByCityName(ctx context.Context, cityName string, reqEd return c.Client.Do(req) } -func (c *Client) PatchV0CityByCityNameWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPatchV0CityByCityNameRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) PatchV0CityByCityNameWithBody(ctx context.Context, cityName string, params *PatchV0CityByCityNameParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchV0CityByCityNameRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -4175,8 +4781,8 @@ func (c *Client) PatchV0CityByCityNameWithBody(ctx context.Context, cityName str return c.Client.Do(req) } -func (c *Client) PatchV0CityByCityName(ctx context.Context, cityName string, body PatchV0CityByCityNameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPatchV0CityByCityNameRequest(c.Server, cityName, body) +func (c *Client) PatchV0CityByCityName(ctx context.Context, cityName string, params *PatchV0CityByCityNameParams, body PatchV0CityByCityNameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchV0CityByCityNameRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -4187,8 +4793,8 @@ func (c *Client) PatchV0CityByCityName(ctx context.Context, cityName string, bod return c.Client.Do(req) } -func (c *Client) DeleteV0CityByCityNameAgentByBase(ctx context.Context, cityName string, base string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteV0CityByCityNameAgentByBaseRequest(c.Server, cityName, base) +func (c *Client) DeleteV0CityByCityNameAgentByBase(ctx context.Context, cityName string, base string, params *DeleteV0CityByCityNameAgentByBaseParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteV0CityByCityNameAgentByBaseRequest(c.Server, cityName, base, params) if err != nil { return nil, err } @@ -4211,8 +4817,8 @@ func (c *Client) GetV0CityByCityNameAgentByBase(ctx context.Context, cityName st return c.Client.Do(req) } -func (c *Client) PatchV0CityByCityNameAgentByBaseWithBody(ctx context.Context, cityName string, base string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPatchV0CityByCityNameAgentByBaseRequestWithBody(c.Server, cityName, base, contentType, body) +func (c *Client) PatchV0CityByCityNameAgentByBaseWithBody(ctx context.Context, cityName string, base string, params *PatchV0CityByCityNameAgentByBaseParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchV0CityByCityNameAgentByBaseRequestWithBody(c.Server, cityName, base, params, contentType, body) if err != nil { return nil, err } @@ -4223,8 +4829,8 @@ func (c *Client) PatchV0CityByCityNameAgentByBaseWithBody(ctx context.Context, c return c.Client.Do(req) } -func (c *Client) PatchV0CityByCityNameAgentByBase(ctx context.Context, cityName string, base string, body PatchV0CityByCityNameAgentByBaseJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPatchV0CityByCityNameAgentByBaseRequest(c.Server, cityName, base, body) +func (c *Client) PatchV0CityByCityNameAgentByBase(ctx context.Context, cityName string, base string, params *PatchV0CityByCityNameAgentByBaseParams, body PatchV0CityByCityNameAgentByBaseJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchV0CityByCityNameAgentByBaseRequest(c.Server, cityName, base, params, body) if err != nil { return nil, err } @@ -4259,8 +4865,8 @@ func (c *Client) StreamAgentOutput(ctx context.Context, cityName string, base st return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameAgentByBaseByAction(ctx context.Context, cityName string, base string, action PostV0CityByCityNameAgentByBaseByActionParamsAction, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameAgentByBaseByActionRequest(c.Server, cityName, base, action) +func (c *Client) PostV0CityByCityNameAgentByBaseByAction(ctx context.Context, cityName string, base string, action PostV0CityByCityNameAgentByBaseByActionParamsAction, params *PostV0CityByCityNameAgentByBaseByActionParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameAgentByBaseByActionRequest(c.Server, cityName, base, action, params) if err != nil { return nil, err } @@ -4271,8 +4877,8 @@ func (c *Client) PostV0CityByCityNameAgentByBaseByAction(ctx context.Context, ci return c.Client.Do(req) } -func (c *Client) DeleteV0CityByCityNameAgentByDirByBase(ctx context.Context, cityName string, dir string, base string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteV0CityByCityNameAgentByDirByBaseRequest(c.Server, cityName, dir, base) +func (c *Client) DeleteV0CityByCityNameAgentByDirByBase(ctx context.Context, cityName string, dir string, base string, params *DeleteV0CityByCityNameAgentByDirByBaseParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteV0CityByCityNameAgentByDirByBaseRequest(c.Server, cityName, dir, base, params) if err != nil { return nil, err } @@ -4295,8 +4901,8 @@ func (c *Client) GetV0CityByCityNameAgentByDirByBase(ctx context.Context, cityNa return c.Client.Do(req) } -func (c *Client) PatchV0CityByCityNameAgentByDirByBaseWithBody(ctx context.Context, cityName string, dir string, base string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPatchV0CityByCityNameAgentByDirByBaseRequestWithBody(c.Server, cityName, dir, base, contentType, body) +func (c *Client) PatchV0CityByCityNameAgentByDirByBaseWithBody(ctx context.Context, cityName string, dir string, base string, params *PatchV0CityByCityNameAgentByDirByBaseParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchV0CityByCityNameAgentByDirByBaseRequestWithBody(c.Server, cityName, dir, base, params, contentType, body) if err != nil { return nil, err } @@ -4307,8 +4913,8 @@ func (c *Client) PatchV0CityByCityNameAgentByDirByBaseWithBody(ctx context.Conte return c.Client.Do(req) } -func (c *Client) PatchV0CityByCityNameAgentByDirByBase(ctx context.Context, cityName string, dir string, base string, body PatchV0CityByCityNameAgentByDirByBaseJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPatchV0CityByCityNameAgentByDirByBaseRequest(c.Server, cityName, dir, base, body) +func (c *Client) PatchV0CityByCityNameAgentByDirByBase(ctx context.Context, cityName string, dir string, base string, params *PatchV0CityByCityNameAgentByDirByBaseParams, body PatchV0CityByCityNameAgentByDirByBaseJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchV0CityByCityNameAgentByDirByBaseRequest(c.Server, cityName, dir, base, params, body) if err != nil { return nil, err } @@ -4343,8 +4949,8 @@ func (c *Client) StreamAgentOutputQualified(ctx context.Context, cityName string return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameAgentByDirByBaseByAction(ctx context.Context, cityName string, dir string, base string, action PostV0CityByCityNameAgentByDirByBaseByActionParamsAction, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameAgentByDirByBaseByActionRequest(c.Server, cityName, dir, base, action) +func (c *Client) PostV0CityByCityNameAgentByDirByBaseByAction(ctx context.Context, cityName string, dir string, base string, action PostV0CityByCityNameAgentByDirByBaseByActionParamsAction, params *PostV0CityByCityNameAgentByDirByBaseByActionParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameAgentByDirByBaseByActionRequest(c.Server, cityName, dir, base, action, params) if err != nil { return nil, err } @@ -4367,8 +4973,8 @@ func (c *Client) GetV0CityByCityNameAgents(ctx context.Context, cityName string, return c.Client.Do(req) } -func (c *Client) CreateAgentWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateAgentRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) CreateAgentWithBody(ctx context.Context, cityName string, params *CreateAgentParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateAgentRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -4379,8 +4985,8 @@ func (c *Client) CreateAgentWithBody(ctx context.Context, cityName string, conte return c.Client.Do(req) } -func (c *Client) CreateAgent(ctx context.Context, cityName string, body CreateAgentJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateAgentRequest(c.Server, cityName, body) +func (c *Client) CreateAgent(ctx context.Context, cityName string, params *CreateAgentParams, body CreateAgentJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateAgentRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -4391,8 +4997,8 @@ func (c *Client) CreateAgent(ctx context.Context, cityName string, body CreateAg return c.Client.Do(req) } -func (c *Client) DeleteV0CityByCityNameBeadById(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteV0CityByCityNameBeadByIdRequest(c.Server, cityName, id) +func (c *Client) DeleteV0CityByCityNameBeadById(ctx context.Context, cityName string, id string, params *DeleteV0CityByCityNameBeadByIdParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteV0CityByCityNameBeadByIdRequest(c.Server, cityName, id, params) if err != nil { return nil, err } @@ -4415,8 +5021,8 @@ func (c *Client) GetV0CityByCityNameBeadById(ctx context.Context, cityName strin return c.Client.Do(req) } -func (c *Client) PatchV0CityByCityNameBeadByIdWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPatchV0CityByCityNameBeadByIdRequestWithBody(c.Server, cityName, id, contentType, body) +func (c *Client) PatchV0CityByCityNameBeadByIdWithBody(ctx context.Context, cityName string, id string, params *PatchV0CityByCityNameBeadByIdParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchV0CityByCityNameBeadByIdRequestWithBody(c.Server, cityName, id, params, contentType, body) if err != nil { return nil, err } @@ -4427,8 +5033,8 @@ func (c *Client) PatchV0CityByCityNameBeadByIdWithBody(ctx context.Context, city return c.Client.Do(req) } -func (c *Client) PatchV0CityByCityNameBeadById(ctx context.Context, cityName string, id string, body PatchV0CityByCityNameBeadByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPatchV0CityByCityNameBeadByIdRequest(c.Server, cityName, id, body) +func (c *Client) PatchV0CityByCityNameBeadById(ctx context.Context, cityName string, id string, params *PatchV0CityByCityNameBeadByIdParams, body PatchV0CityByCityNameBeadByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchV0CityByCityNameBeadByIdRequest(c.Server, cityName, id, params, body) if err != nil { return nil, err } @@ -4439,8 +5045,8 @@ func (c *Client) PatchV0CityByCityNameBeadById(ctx context.Context, cityName str return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameBeadByIdAssignWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameBeadByIdAssignRequestWithBody(c.Server, cityName, id, contentType, body) +func (c *Client) PostV0CityByCityNameBeadByIdAssignWithBody(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdAssignParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameBeadByIdAssignRequestWithBody(c.Server, cityName, id, params, contentType, body) if err != nil { return nil, err } @@ -4451,8 +5057,8 @@ func (c *Client) PostV0CityByCityNameBeadByIdAssignWithBody(ctx context.Context, return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameBeadByIdAssign(ctx context.Context, cityName string, id string, body PostV0CityByCityNameBeadByIdAssignJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameBeadByIdAssignRequest(c.Server, cityName, id, body) +func (c *Client) PostV0CityByCityNameBeadByIdAssign(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdAssignParams, body PostV0CityByCityNameBeadByIdAssignJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameBeadByIdAssignRequest(c.Server, cityName, id, params, body) if err != nil { return nil, err } @@ -4463,8 +5069,8 @@ func (c *Client) PostV0CityByCityNameBeadByIdAssign(ctx context.Context, cityNam return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameBeadByIdClose(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameBeadByIdCloseRequest(c.Server, cityName, id) +func (c *Client) PostV0CityByCityNameBeadByIdClose(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdCloseParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameBeadByIdCloseRequest(c.Server, cityName, id, params) if err != nil { return nil, err } @@ -4487,8 +5093,8 @@ func (c *Client) GetV0CityByCityNameBeadByIdDeps(ctx context.Context, cityName s return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameBeadByIdReopen(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameBeadByIdReopenRequest(c.Server, cityName, id) +func (c *Client) PostV0CityByCityNameBeadByIdReopen(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdReopenParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameBeadByIdReopenRequest(c.Server, cityName, id, params) if err != nil { return nil, err } @@ -4499,8 +5105,8 @@ func (c *Client) PostV0CityByCityNameBeadByIdReopen(ctx context.Context, cityNam return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameBeadByIdUpdateWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameBeadByIdUpdateRequestWithBody(c.Server, cityName, id, contentType, body) +func (c *Client) PostV0CityByCityNameBeadByIdUpdateWithBody(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdUpdateParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameBeadByIdUpdateRequestWithBody(c.Server, cityName, id, params, contentType, body) if err != nil { return nil, err } @@ -4511,8 +5117,8 @@ func (c *Client) PostV0CityByCityNameBeadByIdUpdateWithBody(ctx context.Context, return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameBeadByIdUpdate(ctx context.Context, cityName string, id string, body PostV0CityByCityNameBeadByIdUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameBeadByIdUpdateRequest(c.Server, cityName, id, body) +func (c *Client) PostV0CityByCityNameBeadByIdUpdate(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdUpdateParams, body PostV0CityByCityNameBeadByIdUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameBeadByIdUpdateRequest(c.Server, cityName, id, params, body) if err != nil { return nil, err } @@ -4619,8 +5225,8 @@ func (c *Client) GetV0CityByCityNameConfigValidate(ctx context.Context, cityName return c.Client.Do(req) } -func (c *Client) DeleteV0CityByCityNameConvoyById(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteV0CityByCityNameConvoyByIdRequest(c.Server, cityName, id) +func (c *Client) DeleteV0CityByCityNameConvoyById(ctx context.Context, cityName string, id string, params *DeleteV0CityByCityNameConvoyByIdParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteV0CityByCityNameConvoyByIdRequest(c.Server, cityName, id, params) if err != nil { return nil, err } @@ -4643,8 +5249,8 @@ func (c *Client) GetV0CityByCityNameConvoyById(ctx context.Context, cityName str return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameConvoyByIdAddWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameConvoyByIdAddRequestWithBody(c.Server, cityName, id, contentType, body) +func (c *Client) PostV0CityByCityNameConvoyByIdAddWithBody(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdAddParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameConvoyByIdAddRequestWithBody(c.Server, cityName, id, params, contentType, body) if err != nil { return nil, err } @@ -4655,8 +5261,8 @@ func (c *Client) PostV0CityByCityNameConvoyByIdAddWithBody(ctx context.Context, return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameConvoyByIdAdd(ctx context.Context, cityName string, id string, body PostV0CityByCityNameConvoyByIdAddJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameConvoyByIdAddRequest(c.Server, cityName, id, body) +func (c *Client) PostV0CityByCityNameConvoyByIdAdd(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdAddParams, body PostV0CityByCityNameConvoyByIdAddJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameConvoyByIdAddRequest(c.Server, cityName, id, params, body) if err != nil { return nil, err } @@ -4679,8 +5285,8 @@ func (c *Client) GetV0CityByCityNameConvoyByIdCheck(ctx context.Context, cityNam return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameConvoyByIdClose(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameConvoyByIdCloseRequest(c.Server, cityName, id) +func (c *Client) PostV0CityByCityNameConvoyByIdClose(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdCloseParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameConvoyByIdCloseRequest(c.Server, cityName, id, params) if err != nil { return nil, err } @@ -4691,8 +5297,8 @@ func (c *Client) PostV0CityByCityNameConvoyByIdClose(ctx context.Context, cityNa return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameConvoyByIdRemoveWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameConvoyByIdRemoveRequestWithBody(c.Server, cityName, id, contentType, body) +func (c *Client) PostV0CityByCityNameConvoyByIdRemoveWithBody(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdRemoveParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameConvoyByIdRemoveRequestWithBody(c.Server, cityName, id, params, contentType, body) if err != nil { return nil, err } @@ -4703,8 +5309,8 @@ func (c *Client) PostV0CityByCityNameConvoyByIdRemoveWithBody(ctx context.Contex return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameConvoyByIdRemove(ctx context.Context, cityName string, id string, body PostV0CityByCityNameConvoyByIdRemoveJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameConvoyByIdRemoveRequest(c.Server, cityName, id, body) +func (c *Client) PostV0CityByCityNameConvoyByIdRemove(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdRemoveParams, body PostV0CityByCityNameConvoyByIdRemoveJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameConvoyByIdRemoveRequest(c.Server, cityName, id, params, body) if err != nil { return nil, err } @@ -4727,8 +5333,8 @@ func (c *Client) GetV0CityByCityNameConvoys(ctx context.Context, cityName string return c.Client.Do(req) } -func (c *Client) CreateConvoyWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateConvoyRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) CreateConvoyWithBody(ctx context.Context, cityName string, params *CreateConvoyParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateConvoyRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -4739,8 +5345,8 @@ func (c *Client) CreateConvoyWithBody(ctx context.Context, cityName string, cont return c.Client.Do(req) } -func (c *Client) CreateConvoy(ctx context.Context, cityName string, body CreateConvoyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateConvoyRequest(c.Server, cityName, body) +func (c *Client) CreateConvoy(ctx context.Context, cityName string, params *CreateConvoyParams, body CreateConvoyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateConvoyRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -4763,8 +5369,8 @@ func (c *Client) GetV0CityByCityNameEvents(ctx context.Context, cityName string, return c.Client.Do(req) } -func (c *Client) EmitEventWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewEmitEventRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) EmitEventWithBody(ctx context.Context, cityName string, params *EmitEventParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewEmitEventRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -4775,8 +5381,8 @@ func (c *Client) EmitEventWithBody(ctx context.Context, cityName string, content return c.Client.Do(req) } -func (c *Client) EmitEvent(ctx context.Context, cityName string, body EmitEventJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewEmitEventRequest(c.Server, cityName, body) +func (c *Client) EmitEvent(ctx context.Context, cityName string, params *EmitEventParams, body EmitEventJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewEmitEventRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -4799,8 +5405,8 @@ func (c *Client) StreamEvents(ctx context.Context, cityName string, params *Stre return c.Client.Do(req) } -func (c *Client) DeleteV0CityByCityNameExtmsgAdaptersWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteV0CityByCityNameExtmsgAdaptersRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) DeleteV0CityByCityNameExtmsgAdaptersWithBody(ctx context.Context, cityName string, params *DeleteV0CityByCityNameExtmsgAdaptersParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteV0CityByCityNameExtmsgAdaptersRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -4811,8 +5417,8 @@ func (c *Client) DeleteV0CityByCityNameExtmsgAdaptersWithBody(ctx context.Contex return c.Client.Do(req) } -func (c *Client) DeleteV0CityByCityNameExtmsgAdapters(ctx context.Context, cityName string, body DeleteV0CityByCityNameExtmsgAdaptersJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteV0CityByCityNameExtmsgAdaptersRequest(c.Server, cityName, body) +func (c *Client) DeleteV0CityByCityNameExtmsgAdapters(ctx context.Context, cityName string, params *DeleteV0CityByCityNameExtmsgAdaptersParams, body DeleteV0CityByCityNameExtmsgAdaptersJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteV0CityByCityNameExtmsgAdaptersRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -4835,8 +5441,8 @@ func (c *Client) GetV0CityByCityNameExtmsgAdapters(ctx context.Context, cityName return c.Client.Do(req) } -func (c *Client) RegisterExtmsgAdapterWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRegisterExtmsgAdapterRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) RegisterExtmsgAdapterWithBody(ctx context.Context, cityName string, params *RegisterExtmsgAdapterParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRegisterExtmsgAdapterRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -4847,8 +5453,8 @@ func (c *Client) RegisterExtmsgAdapterWithBody(ctx context.Context, cityName str return c.Client.Do(req) } -func (c *Client) RegisterExtmsgAdapter(ctx context.Context, cityName string, body RegisterExtmsgAdapterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRegisterExtmsgAdapterRequest(c.Server, cityName, body) +func (c *Client) RegisterExtmsgAdapter(ctx context.Context, cityName string, params *RegisterExtmsgAdapterParams, body RegisterExtmsgAdapterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRegisterExtmsgAdapterRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -4859,8 +5465,8 @@ func (c *Client) RegisterExtmsgAdapter(ctx context.Context, cityName string, bod return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameExtmsgBindWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameExtmsgBindRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) PostV0CityByCityNameExtmsgBindWithBody(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgBindParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameExtmsgBindRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -4871,8 +5477,8 @@ func (c *Client) PostV0CityByCityNameExtmsgBindWithBody(ctx context.Context, cit return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameExtmsgBind(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgBindJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameExtmsgBindRequest(c.Server, cityName, body) +func (c *Client) PostV0CityByCityNameExtmsgBind(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgBindParams, body PostV0CityByCityNameExtmsgBindJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameExtmsgBindRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -4907,8 +5513,8 @@ func (c *Client) GetV0CityByCityNameExtmsgGroups(ctx context.Context, cityName s return c.Client.Do(req) } -func (c *Client) EnsureExtmsgGroupWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewEnsureExtmsgGroupRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) EnsureExtmsgGroupWithBody(ctx context.Context, cityName string, params *EnsureExtmsgGroupParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewEnsureExtmsgGroupRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -4919,8 +5525,8 @@ func (c *Client) EnsureExtmsgGroupWithBody(ctx context.Context, cityName string, return c.Client.Do(req) } -func (c *Client) EnsureExtmsgGroup(ctx context.Context, cityName string, body EnsureExtmsgGroupJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewEnsureExtmsgGroupRequest(c.Server, cityName, body) +func (c *Client) EnsureExtmsgGroup(ctx context.Context, cityName string, params *EnsureExtmsgGroupParams, body EnsureExtmsgGroupJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewEnsureExtmsgGroupRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -4931,8 +5537,8 @@ func (c *Client) EnsureExtmsgGroup(ctx context.Context, cityName string, body En return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameExtmsgInboundWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameExtmsgInboundRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) PostV0CityByCityNameExtmsgInboundWithBody(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgInboundParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameExtmsgInboundRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -4943,8 +5549,8 @@ func (c *Client) PostV0CityByCityNameExtmsgInboundWithBody(ctx context.Context, return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameExtmsgInbound(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgInboundJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameExtmsgInboundRequest(c.Server, cityName, body) +func (c *Client) PostV0CityByCityNameExtmsgInbound(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgInboundParams, body PostV0CityByCityNameExtmsgInboundJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameExtmsgInboundRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -4955,8 +5561,8 @@ func (c *Client) PostV0CityByCityNameExtmsgInbound(ctx context.Context, cityName return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameExtmsgOutboundWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameExtmsgOutboundRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) PostV0CityByCityNameExtmsgOutboundWithBody(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgOutboundParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameExtmsgOutboundRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -4967,8 +5573,8 @@ func (c *Client) PostV0CityByCityNameExtmsgOutboundWithBody(ctx context.Context, return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameExtmsgOutbound(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgOutboundJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameExtmsgOutboundRequest(c.Server, cityName, body) +func (c *Client) PostV0CityByCityNameExtmsgOutbound(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgOutboundParams, body PostV0CityByCityNameExtmsgOutboundJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameExtmsgOutboundRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -4979,8 +5585,8 @@ func (c *Client) PostV0CityByCityNameExtmsgOutbound(ctx context.Context, cityNam return c.Client.Do(req) } -func (c *Client) DeleteV0CityByCityNameExtmsgParticipantsWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteV0CityByCityNameExtmsgParticipantsRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) DeleteV0CityByCityNameExtmsgParticipantsWithBody(ctx context.Context, cityName string, params *DeleteV0CityByCityNameExtmsgParticipantsParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteV0CityByCityNameExtmsgParticipantsRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -4991,8 +5597,8 @@ func (c *Client) DeleteV0CityByCityNameExtmsgParticipantsWithBody(ctx context.Co return c.Client.Do(req) } -func (c *Client) DeleteV0CityByCityNameExtmsgParticipants(ctx context.Context, cityName string, body DeleteV0CityByCityNameExtmsgParticipantsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteV0CityByCityNameExtmsgParticipantsRequest(c.Server, cityName, body) +func (c *Client) DeleteV0CityByCityNameExtmsgParticipants(ctx context.Context, cityName string, params *DeleteV0CityByCityNameExtmsgParticipantsParams, body DeleteV0CityByCityNameExtmsgParticipantsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteV0CityByCityNameExtmsgParticipantsRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -5003,8 +5609,8 @@ func (c *Client) DeleteV0CityByCityNameExtmsgParticipants(ctx context.Context, c return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameExtmsgParticipantsWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameExtmsgParticipantsRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) PostV0CityByCityNameExtmsgParticipantsWithBody(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgParticipantsParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameExtmsgParticipantsRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -5015,8 +5621,8 @@ func (c *Client) PostV0CityByCityNameExtmsgParticipantsWithBody(ctx context.Cont return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameExtmsgParticipants(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgParticipantsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameExtmsgParticipantsRequest(c.Server, cityName, body) +func (c *Client) PostV0CityByCityNameExtmsgParticipants(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgParticipantsParams, body PostV0CityByCityNameExtmsgParticipantsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameExtmsgParticipantsRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -5039,8 +5645,8 @@ func (c *Client) GetV0CityByCityNameExtmsgTranscript(ctx context.Context, cityNa return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameExtmsgTranscriptAckWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameExtmsgTranscriptAckRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) PostV0CityByCityNameExtmsgTranscriptAckWithBody(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgTranscriptAckParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameExtmsgTranscriptAckRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -5051,8 +5657,8 @@ func (c *Client) PostV0CityByCityNameExtmsgTranscriptAckWithBody(ctx context.Con return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameExtmsgTranscriptAck(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgTranscriptAckJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameExtmsgTranscriptAckRequest(c.Server, cityName, body) +func (c *Client) PostV0CityByCityNameExtmsgTranscriptAck(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgTranscriptAckParams, body PostV0CityByCityNameExtmsgTranscriptAckJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameExtmsgTranscriptAckRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -5063,8 +5669,8 @@ func (c *Client) PostV0CityByCityNameExtmsgTranscriptAck(ctx context.Context, ci return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameExtmsgUnbindWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameExtmsgUnbindRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) PostV0CityByCityNameExtmsgUnbindWithBody(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgUnbindParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameExtmsgUnbindRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -5075,8 +5681,8 @@ func (c *Client) PostV0CityByCityNameExtmsgUnbindWithBody(ctx context.Context, c return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameExtmsgUnbind(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgUnbindJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameExtmsgUnbindRequest(c.Server, cityName, body) +func (c *Client) PostV0CityByCityNameExtmsgUnbind(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgUnbindParams, body PostV0CityByCityNameExtmsgUnbindJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameExtmsgUnbindRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -5135,8 +5741,8 @@ func (c *Client) GetV0CityByCityNameFormulasByName(ctx context.Context, cityName return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameFormulasByNamePreviewWithBody(ctx context.Context, cityName string, name string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameFormulasByNamePreviewRequestWithBody(c.Server, cityName, name, contentType, body) +func (c *Client) PostV0CityByCityNameFormulasByNamePreviewWithBody(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameFormulasByNamePreviewParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameFormulasByNamePreviewRequestWithBody(c.Server, cityName, name, params, contentType, body) if err != nil { return nil, err } @@ -5147,8 +5753,8 @@ func (c *Client) PostV0CityByCityNameFormulasByNamePreviewWithBody(ctx context.C return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameFormulasByNamePreview(ctx context.Context, cityName string, name string, body PostV0CityByCityNameFormulasByNamePreviewJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameFormulasByNamePreviewRequest(c.Server, cityName, name, body) +func (c *Client) PostV0CityByCityNameFormulasByNamePreview(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameFormulasByNamePreviewParams, body PostV0CityByCityNameFormulasByNamePreviewJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameFormulasByNamePreviewRequest(c.Server, cityName, name, params, body) if err != nil { return nil, err } @@ -5351,8 +5957,8 @@ func (c *Client) GetV0CityByCityNameOrderByName(ctx context.Context, cityName st return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameOrderByNameDisable(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameOrderByNameDisableRequest(c.Server, cityName, name) +func (c *Client) PostV0CityByCityNameOrderByNameDisable(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameOrderByNameDisableParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameOrderByNameDisableRequest(c.Server, cityName, name, params) if err != nil { return nil, err } @@ -5363,8 +5969,8 @@ func (c *Client) PostV0CityByCityNameOrderByNameDisable(ctx context.Context, cit return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameOrderByNameEnable(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameOrderByNameEnableRequest(c.Server, cityName, name) +func (c *Client) PostV0CityByCityNameOrderByNameEnable(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameOrderByNameEnableParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameOrderByNameEnableRequest(c.Server, cityName, name, params) if err != nil { return nil, err } @@ -5435,8 +6041,8 @@ func (c *Client) GetV0CityByCityNamePacks(ctx context.Context, cityName string, return c.Client.Do(req) } -func (c *Client) DeleteV0CityByCityNamePatchesAgentByBase(ctx context.Context, cityName string, base string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteV0CityByCityNamePatchesAgentByBaseRequest(c.Server, cityName, base) +func (c *Client) DeleteV0CityByCityNamePatchesAgentByBase(ctx context.Context, cityName string, base string, params *DeleteV0CityByCityNamePatchesAgentByBaseParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteV0CityByCityNamePatchesAgentByBaseRequest(c.Server, cityName, base, params) if err != nil { return nil, err } @@ -5459,8 +6065,8 @@ func (c *Client) GetV0CityByCityNamePatchesAgentByBase(ctx context.Context, city return c.Client.Do(req) } -func (c *Client) DeleteV0CityByCityNamePatchesAgentByDirByBase(ctx context.Context, cityName string, dir string, base string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteV0CityByCityNamePatchesAgentByDirByBaseRequest(c.Server, cityName, dir, base) +func (c *Client) DeleteV0CityByCityNamePatchesAgentByDirByBase(ctx context.Context, cityName string, dir string, base string, params *DeleteV0CityByCityNamePatchesAgentByDirByBaseParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteV0CityByCityNamePatchesAgentByDirByBaseRequest(c.Server, cityName, dir, base, params) if err != nil { return nil, err } @@ -5495,8 +6101,8 @@ func (c *Client) GetV0CityByCityNamePatchesAgents(ctx context.Context, cityName return c.Client.Do(req) } -func (c *Client) PutV0CityByCityNamePatchesAgentsWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPutV0CityByCityNamePatchesAgentsRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) PutV0CityByCityNamePatchesAgentsWithBody(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesAgentsParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPutV0CityByCityNamePatchesAgentsRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -5507,8 +6113,8 @@ func (c *Client) PutV0CityByCityNamePatchesAgentsWithBody(ctx context.Context, c return c.Client.Do(req) } -func (c *Client) PutV0CityByCityNamePatchesAgents(ctx context.Context, cityName string, body PutV0CityByCityNamePatchesAgentsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPutV0CityByCityNamePatchesAgentsRequest(c.Server, cityName, body) +func (c *Client) PutV0CityByCityNamePatchesAgents(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesAgentsParams, body PutV0CityByCityNamePatchesAgentsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPutV0CityByCityNamePatchesAgentsRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -5519,8 +6125,8 @@ func (c *Client) PutV0CityByCityNamePatchesAgents(ctx context.Context, cityName return c.Client.Do(req) } -func (c *Client) DeleteV0CityByCityNamePatchesProviderByName(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteV0CityByCityNamePatchesProviderByNameRequest(c.Server, cityName, name) +func (c *Client) DeleteV0CityByCityNamePatchesProviderByName(ctx context.Context, cityName string, name string, params *DeleteV0CityByCityNamePatchesProviderByNameParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteV0CityByCityNamePatchesProviderByNameRequest(c.Server, cityName, name, params) if err != nil { return nil, err } @@ -5555,8 +6161,8 @@ func (c *Client) GetV0CityByCityNamePatchesProviders(ctx context.Context, cityNa return c.Client.Do(req) } -func (c *Client) PutV0CityByCityNamePatchesProvidersWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPutV0CityByCityNamePatchesProvidersRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) PutV0CityByCityNamePatchesProvidersWithBody(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesProvidersParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPutV0CityByCityNamePatchesProvidersRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -5567,8 +6173,8 @@ func (c *Client) PutV0CityByCityNamePatchesProvidersWithBody(ctx context.Context return c.Client.Do(req) } -func (c *Client) PutV0CityByCityNamePatchesProviders(ctx context.Context, cityName string, body PutV0CityByCityNamePatchesProvidersJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPutV0CityByCityNamePatchesProvidersRequest(c.Server, cityName, body) +func (c *Client) PutV0CityByCityNamePatchesProviders(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesProvidersParams, body PutV0CityByCityNamePatchesProvidersJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPutV0CityByCityNamePatchesProvidersRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -5579,8 +6185,8 @@ func (c *Client) PutV0CityByCityNamePatchesProviders(ctx context.Context, cityNa return c.Client.Do(req) } -func (c *Client) DeleteV0CityByCityNamePatchesRigByName(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteV0CityByCityNamePatchesRigByNameRequest(c.Server, cityName, name) +func (c *Client) DeleteV0CityByCityNamePatchesRigByName(ctx context.Context, cityName string, name string, params *DeleteV0CityByCityNamePatchesRigByNameParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteV0CityByCityNamePatchesRigByNameRequest(c.Server, cityName, name, params) if err != nil { return nil, err } @@ -5615,8 +6221,8 @@ func (c *Client) GetV0CityByCityNamePatchesRigs(ctx context.Context, cityName st return c.Client.Do(req) } -func (c *Client) PutV0CityByCityNamePatchesRigsWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPutV0CityByCityNamePatchesRigsRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) PutV0CityByCityNamePatchesRigsWithBody(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesRigsParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPutV0CityByCityNamePatchesRigsRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -5627,8 +6233,8 @@ func (c *Client) PutV0CityByCityNamePatchesRigsWithBody(ctx context.Context, cit return c.Client.Do(req) } -func (c *Client) PutV0CityByCityNamePatchesRigs(ctx context.Context, cityName string, body PutV0CityByCityNamePatchesRigsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPutV0CityByCityNamePatchesRigsRequest(c.Server, cityName, body) +func (c *Client) PutV0CityByCityNamePatchesRigs(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesRigsParams, body PutV0CityByCityNamePatchesRigsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPutV0CityByCityNamePatchesRigsRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -5651,8 +6257,8 @@ func (c *Client) GetV0CityByCityNameProviderReadiness(ctx context.Context, cityN return c.Client.Do(req) } -func (c *Client) DeleteV0CityByCityNameProviderByName(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteV0CityByCityNameProviderByNameRequest(c.Server, cityName, name) +func (c *Client) DeleteV0CityByCityNameProviderByName(ctx context.Context, cityName string, name string, params *DeleteV0CityByCityNameProviderByNameParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteV0CityByCityNameProviderByNameRequest(c.Server, cityName, name, params) if err != nil { return nil, err } @@ -5675,8 +6281,8 @@ func (c *Client) GetV0CityByCityNameProviderByName(ctx context.Context, cityName return c.Client.Do(req) } -func (c *Client) PatchV0CityByCityNameProviderByNameWithBody(ctx context.Context, cityName string, name string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPatchV0CityByCityNameProviderByNameRequestWithBody(c.Server, cityName, name, contentType, body) +func (c *Client) PatchV0CityByCityNameProviderByNameWithBody(ctx context.Context, cityName string, name string, params *PatchV0CityByCityNameProviderByNameParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchV0CityByCityNameProviderByNameRequestWithBody(c.Server, cityName, name, params, contentType, body) if err != nil { return nil, err } @@ -5687,8 +6293,8 @@ func (c *Client) PatchV0CityByCityNameProviderByNameWithBody(ctx context.Context return c.Client.Do(req) } -func (c *Client) PatchV0CityByCityNameProviderByName(ctx context.Context, cityName string, name string, body PatchV0CityByCityNameProviderByNameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPatchV0CityByCityNameProviderByNameRequest(c.Server, cityName, name, body) +func (c *Client) PatchV0CityByCityNameProviderByName(ctx context.Context, cityName string, name string, params *PatchV0CityByCityNameProviderByNameParams, body PatchV0CityByCityNameProviderByNameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchV0CityByCityNameProviderByNameRequest(c.Server, cityName, name, params, body) if err != nil { return nil, err } @@ -5711,8 +6317,8 @@ func (c *Client) GetV0CityByCityNameProviders(ctx context.Context, cityName stri return c.Client.Do(req) } -func (c *Client) CreateProviderWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateProviderRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) CreateProviderWithBody(ctx context.Context, cityName string, params *CreateProviderParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateProviderRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -5723,8 +6329,8 @@ func (c *Client) CreateProviderWithBody(ctx context.Context, cityName string, co return c.Client.Do(req) } -func (c *Client) CreateProvider(ctx context.Context, cityName string, body CreateProviderJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateProviderRequest(c.Server, cityName, body) +func (c *Client) CreateProvider(ctx context.Context, cityName string, params *CreateProviderParams, body CreateProviderJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateProviderRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -5759,8 +6365,8 @@ func (c *Client) GetV0CityByCityNameReadiness(ctx context.Context, cityName stri return c.Client.Do(req) } -func (c *Client) DeleteV0CityByCityNameRigByName(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteV0CityByCityNameRigByNameRequest(c.Server, cityName, name) +func (c *Client) DeleteV0CityByCityNameRigByName(ctx context.Context, cityName string, name string, params *DeleteV0CityByCityNameRigByNameParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteV0CityByCityNameRigByNameRequest(c.Server, cityName, name, params) if err != nil { return nil, err } @@ -5783,8 +6389,8 @@ func (c *Client) GetV0CityByCityNameRigByName(ctx context.Context, cityName stri return c.Client.Do(req) } -func (c *Client) PatchV0CityByCityNameRigByNameWithBody(ctx context.Context, cityName string, name string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPatchV0CityByCityNameRigByNameRequestWithBody(c.Server, cityName, name, contentType, body) +func (c *Client) PatchV0CityByCityNameRigByNameWithBody(ctx context.Context, cityName string, name string, params *PatchV0CityByCityNameRigByNameParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchV0CityByCityNameRigByNameRequestWithBody(c.Server, cityName, name, params, contentType, body) if err != nil { return nil, err } @@ -5795,8 +6401,8 @@ func (c *Client) PatchV0CityByCityNameRigByNameWithBody(ctx context.Context, cit return c.Client.Do(req) } -func (c *Client) PatchV0CityByCityNameRigByName(ctx context.Context, cityName string, name string, body PatchV0CityByCityNameRigByNameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPatchV0CityByCityNameRigByNameRequest(c.Server, cityName, name, body) +func (c *Client) PatchV0CityByCityNameRigByName(ctx context.Context, cityName string, name string, params *PatchV0CityByCityNameRigByNameParams, body PatchV0CityByCityNameRigByNameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchV0CityByCityNameRigByNameRequest(c.Server, cityName, name, params, body) if err != nil { return nil, err } @@ -5807,8 +6413,8 @@ func (c *Client) PatchV0CityByCityNameRigByName(ctx context.Context, cityName st return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameRigByNameByAction(ctx context.Context, cityName string, name string, action string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameRigByNameByActionRequest(c.Server, cityName, name, action) +func (c *Client) PostV0CityByCityNameRigByNameByAction(ctx context.Context, cityName string, name string, action string, params *PostV0CityByCityNameRigByNameByActionParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameRigByNameByActionRequest(c.Server, cityName, name, action, params) if err != nil { return nil, err } @@ -5831,8 +6437,8 @@ func (c *Client) GetV0CityByCityNameRigs(ctx context.Context, cityName string, p return c.Client.Do(req) } -func (c *Client) CreateRigWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateRigRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) CreateRigWithBody(ctx context.Context, cityName string, params *CreateRigParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateRigRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -5843,8 +6449,8 @@ func (c *Client) CreateRigWithBody(ctx context.Context, cityName string, content return c.Client.Do(req) } -func (c *Client) CreateRig(ctx context.Context, cityName string, body CreateRigJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateRigRequest(c.Server, cityName, body) +func (c *Client) CreateRig(ctx context.Context, cityName string, params *CreateRigParams, body CreateRigJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateRigRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -5867,8 +6473,8 @@ func (c *Client) GetV0CityByCityNameServiceByName(ctx context.Context, cityName return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameServiceByNameRestart(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameServiceByNameRestartRequest(c.Server, cityName, name) +func (c *Client) PostV0CityByCityNameServiceByNameRestart(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameServiceByNameRestartParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameServiceByNameRestartRequest(c.Server, cityName, name, params) if err != nil { return nil, err } @@ -5903,8 +6509,8 @@ func (c *Client) GetV0CityByCityNameSessionById(ctx context.Context, cityName st return c.Client.Do(req) } -func (c *Client) PatchV0CityByCityNameSessionByIdWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPatchV0CityByCityNameSessionByIdRequestWithBody(c.Server, cityName, id, contentType, body) +func (c *Client) PatchV0CityByCityNameSessionByIdWithBody(ctx context.Context, cityName string, id string, params *PatchV0CityByCityNameSessionByIdParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchV0CityByCityNameSessionByIdRequestWithBody(c.Server, cityName, id, params, contentType, body) if err != nil { return nil, err } @@ -5915,8 +6521,8 @@ func (c *Client) PatchV0CityByCityNameSessionByIdWithBody(ctx context.Context, c return c.Client.Do(req) } -func (c *Client) PatchV0CityByCityNameSessionById(ctx context.Context, cityName string, id string, body PatchV0CityByCityNameSessionByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPatchV0CityByCityNameSessionByIdRequest(c.Server, cityName, id, body) +func (c *Client) PatchV0CityByCityNameSessionById(ctx context.Context, cityName string, id string, params *PatchV0CityByCityNameSessionByIdParams, body PatchV0CityByCityNameSessionByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchV0CityByCityNameSessionByIdRequest(c.Server, cityName, id, params, body) if err != nil { return nil, err } @@ -5963,8 +6569,8 @@ func (c *Client) PostV0CityByCityNameSessionByIdClose(ctx context.Context, cityN return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameSessionByIdKill(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameSessionByIdKillRequest(c.Server, cityName, id) +func (c *Client) PostV0CityByCityNameSessionByIdKill(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdKillParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameSessionByIdKillRequest(c.Server, cityName, id, params) if err != nil { return nil, err } @@ -5975,8 +6581,8 @@ func (c *Client) PostV0CityByCityNameSessionByIdKill(ctx context.Context, cityNa return c.Client.Do(req) } -func (c *Client) SendSessionMessageWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSendSessionMessageRequestWithBody(c.Server, cityName, id, contentType, body) +func (c *Client) SendSessionMessageWithBody(ctx context.Context, cityName string, id string, params *SendSessionMessageParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSendSessionMessageRequestWithBody(c.Server, cityName, id, params, contentType, body) if err != nil { return nil, err } @@ -5987,8 +6593,8 @@ func (c *Client) SendSessionMessageWithBody(ctx context.Context, cityName string return c.Client.Do(req) } -func (c *Client) SendSessionMessage(ctx context.Context, cityName string, id string, body SendSessionMessageJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSendSessionMessageRequest(c.Server, cityName, id, body) +func (c *Client) SendSessionMessage(ctx context.Context, cityName string, id string, params *SendSessionMessageParams, body SendSessionMessageJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSendSessionMessageRequest(c.Server, cityName, id, params, body) if err != nil { return nil, err } @@ -6011,8 +6617,8 @@ func (c *Client) GetV0CityByCityNameSessionByIdPending(ctx context.Context, city return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameSessionByIdRenameWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameSessionByIdRenameRequestWithBody(c.Server, cityName, id, contentType, body) +func (c *Client) PostV0CityByCityNameSessionByIdRenameWithBody(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdRenameParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameSessionByIdRenameRequestWithBody(c.Server, cityName, id, params, contentType, body) if err != nil { return nil, err } @@ -6023,8 +6629,8 @@ func (c *Client) PostV0CityByCityNameSessionByIdRenameWithBody(ctx context.Conte return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameSessionByIdRename(ctx context.Context, cityName string, id string, body PostV0CityByCityNameSessionByIdRenameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameSessionByIdRenameRequest(c.Server, cityName, id, body) +func (c *Client) PostV0CityByCityNameSessionByIdRename(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdRenameParams, body PostV0CityByCityNameSessionByIdRenameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameSessionByIdRenameRequest(c.Server, cityName, id, params, body) if err != nil { return nil, err } @@ -6035,8 +6641,8 @@ func (c *Client) PostV0CityByCityNameSessionByIdRename(ctx context.Context, city return c.Client.Do(req) } -func (c *Client) RespondSessionWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRespondSessionRequestWithBody(c.Server, cityName, id, contentType, body) +func (c *Client) RespondSessionWithBody(ctx context.Context, cityName string, id string, params *RespondSessionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRespondSessionRequestWithBody(c.Server, cityName, id, params, contentType, body) if err != nil { return nil, err } @@ -6047,8 +6653,8 @@ func (c *Client) RespondSessionWithBody(ctx context.Context, cityName string, id return c.Client.Do(req) } -func (c *Client) RespondSession(ctx context.Context, cityName string, id string, body RespondSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRespondSessionRequest(c.Server, cityName, id, body) +func (c *Client) RespondSession(ctx context.Context, cityName string, id string, params *RespondSessionParams, body RespondSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRespondSessionRequest(c.Server, cityName, id, params, body) if err != nil { return nil, err } @@ -6059,8 +6665,8 @@ func (c *Client) RespondSession(ctx context.Context, cityName string, id string, return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameSessionByIdStop(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameSessionByIdStopRequest(c.Server, cityName, id) +func (c *Client) PostV0CityByCityNameSessionByIdStop(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdStopParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameSessionByIdStopRequest(c.Server, cityName, id, params) if err != nil { return nil, err } @@ -6083,8 +6689,8 @@ func (c *Client) StreamSession(ctx context.Context, cityName string, id string, return c.Client.Do(req) } -func (c *Client) SubmitSessionWithBody(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSubmitSessionRequestWithBody(c.Server, cityName, id, contentType, body) +func (c *Client) SubmitSessionWithBody(ctx context.Context, cityName string, id string, params *SubmitSessionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSubmitSessionRequestWithBody(c.Server, cityName, id, params, contentType, body) if err != nil { return nil, err } @@ -6095,8 +6701,8 @@ func (c *Client) SubmitSessionWithBody(ctx context.Context, cityName string, id return c.Client.Do(req) } -func (c *Client) SubmitSession(ctx context.Context, cityName string, id string, body SubmitSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSubmitSessionRequest(c.Server, cityName, id, body) +func (c *Client) SubmitSession(ctx context.Context, cityName string, id string, params *SubmitSessionParams, body SubmitSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSubmitSessionRequest(c.Server, cityName, id, params, body) if err != nil { return nil, err } @@ -6107,8 +6713,8 @@ func (c *Client) SubmitSession(ctx context.Context, cityName string, id string, return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameSessionByIdSuspend(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameSessionByIdSuspendRequest(c.Server, cityName, id) +func (c *Client) PostV0CityByCityNameSessionByIdSuspend(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdSuspendParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameSessionByIdSuspendRequest(c.Server, cityName, id, params) if err != nil { return nil, err } @@ -6131,8 +6737,8 @@ func (c *Client) GetV0CityByCityNameSessionByIdTranscript(ctx context.Context, c return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameSessionByIdWake(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameSessionByIdWakeRequest(c.Server, cityName, id) +func (c *Client) PostV0CityByCityNameSessionByIdWake(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdWakeParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameSessionByIdWakeRequest(c.Server, cityName, id, params) if err != nil { return nil, err } @@ -6155,8 +6761,8 @@ func (c *Client) GetV0CityByCityNameSessions(ctx context.Context, cityName strin return c.Client.Do(req) } -func (c *Client) CreateSessionWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateSessionRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) CreateSessionWithBody(ctx context.Context, cityName string, params *CreateSessionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateSessionRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -6167,8 +6773,8 @@ func (c *Client) CreateSessionWithBody(ctx context.Context, cityName string, con return c.Client.Do(req) } -func (c *Client) CreateSession(ctx context.Context, cityName string, body CreateSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateSessionRequest(c.Server, cityName, body) +func (c *Client) CreateSession(ctx context.Context, cityName string, params *CreateSessionParams, body CreateSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateSessionRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -6179,8 +6785,8 @@ func (c *Client) CreateSession(ctx context.Context, cityName string, body Create return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameSlingWithBody(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameSlingRequestWithBody(c.Server, cityName, contentType, body) +func (c *Client) PostV0CityByCityNameSlingWithBody(ctx context.Context, cityName string, params *PostV0CityByCityNameSlingParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameSlingRequestWithBody(c.Server, cityName, params, contentType, body) if err != nil { return nil, err } @@ -6191,8 +6797,8 @@ func (c *Client) PostV0CityByCityNameSlingWithBody(ctx context.Context, cityName return c.Client.Do(req) } -func (c *Client) PostV0CityByCityNameSling(ctx context.Context, cityName string, body PostV0CityByCityNameSlingJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostV0CityByCityNameSlingRequest(c.Server, cityName, body) +func (c *Client) PostV0CityByCityNameSling(ctx context.Context, cityName string, params *PostV0CityByCityNameSlingParams, body PostV0CityByCityNameSlingJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameSlingRequest(c.Server, cityName, params, body) if err != nil { return nil, err } @@ -6215,6 +6821,18 @@ func (c *Client) GetV0CityByCityNameStatus(ctx context.Context, cityName string, return c.Client.Do(req) } +func (c *Client) PostV0CityByCityNameUnregister(ctx context.Context, cityName string, params *PostV0CityByCityNameUnregisterParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostV0CityByCityNameUnregisterRequest(c.Server, cityName, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) DeleteV0CityByCityNameWorkflowByWorkflowId(ctx context.Context, cityName string, workflowId string, params *DeleteV0CityByCityNameWorkflowByWorkflowIdParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewDeleteV0CityByCityNameWorkflowByWorkflowIdRequest(c.Server, cityName, workflowId, params) if err != nil { @@ -6342,18 +6960,18 @@ func NewGetV0CitiesRequest(server string) (*http.Request, error) { } // NewPostV0CityRequest calls the generic PostV0City builder with application/json body -func NewPostV0CityRequest(server string, body PostV0CityJSONRequestBody) (*http.Request, error) { +func NewPostV0CityRequest(server string, params *PostV0CityParams, body PostV0CityJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPostV0CityRequestWithBody(server, "application/json", bodyReader) + return NewPostV0CityRequestWithBody(server, params, "application/json", bodyReader) } // NewPostV0CityRequestWithBody generates requests for PostV0City with any type of body -func NewPostV0CityRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +func NewPostV0CityRequestWithBody(server string, params *PostV0CityParams, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -6378,6 +6996,19 @@ func NewPostV0CityRequestWithBody(server string, contentType string, body io.Rea req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -6416,18 +7047,18 @@ func NewGetV0CityByCityNameRequest(server string, cityName string) (*http.Reques } // NewPatchV0CityByCityNameRequest calls the generic PatchV0CityByCityName builder with application/json body -func NewPatchV0CityByCityNameRequest(server string, cityName string, body PatchV0CityByCityNameJSONRequestBody) (*http.Request, error) { +func NewPatchV0CityByCityNameRequest(server string, cityName string, params *PatchV0CityByCityNameParams, body PatchV0CityByCityNameJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPatchV0CityByCityNameRequestWithBody(server, cityName, "application/json", bodyReader) + return NewPatchV0CityByCityNameRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewPatchV0CityByCityNameRequestWithBody generates requests for PatchV0CityByCityName with any type of body -func NewPatchV0CityByCityNameRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewPatchV0CityByCityNameRequestWithBody(server string, cityName string, params *PatchV0CityByCityNameParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -6459,11 +7090,24 @@ func NewPatchV0CityByCityNameRequestWithBody(server string, cityName string, con req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewDeleteV0CityByCityNameAgentByBaseRequest generates requests for DeleteV0CityByCityNameAgentByBase -func NewDeleteV0CityByCityNameAgentByBaseRequest(server string, cityName string, base string) (*http.Request, error) { +func NewDeleteV0CityByCityNameAgentByBaseRequest(server string, cityName string, base string, params *DeleteV0CityByCityNameAgentByBaseParams) (*http.Request, error) { var err error var pathParam0 string @@ -6500,6 +7144,19 @@ func NewDeleteV0CityByCityNameAgentByBaseRequest(server string, cityName string, return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -6545,18 +7202,18 @@ func NewGetV0CityByCityNameAgentByBaseRequest(server string, cityName string, ba } // NewPatchV0CityByCityNameAgentByBaseRequest calls the generic PatchV0CityByCityNameAgentByBase builder with application/json body -func NewPatchV0CityByCityNameAgentByBaseRequest(server string, cityName string, base string, body PatchV0CityByCityNameAgentByBaseJSONRequestBody) (*http.Request, error) { +func NewPatchV0CityByCityNameAgentByBaseRequest(server string, cityName string, base string, params *PatchV0CityByCityNameAgentByBaseParams, body PatchV0CityByCityNameAgentByBaseJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPatchV0CityByCityNameAgentByBaseRequestWithBody(server, cityName, base, "application/json", bodyReader) + return NewPatchV0CityByCityNameAgentByBaseRequestWithBody(server, cityName, base, params, "application/json", bodyReader) } // NewPatchV0CityByCityNameAgentByBaseRequestWithBody generates requests for PatchV0CityByCityNameAgentByBase with any type of body -func NewPatchV0CityByCityNameAgentByBaseRequestWithBody(server string, cityName string, base string, contentType string, body io.Reader) (*http.Request, error) { +func NewPatchV0CityByCityNameAgentByBaseRequestWithBody(server string, cityName string, base string, params *PatchV0CityByCityNameAgentByBaseParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -6595,6 +7252,19 @@ func NewPatchV0CityByCityNameAgentByBaseRequestWithBody(server string, cityName req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -6719,7 +7389,7 @@ func NewStreamAgentOutputRequest(server string, cityName string, base string) (* } // NewPostV0CityByCityNameAgentByBaseByActionRequest generates requests for PostV0CityByCityNameAgentByBaseByAction -func NewPostV0CityByCityNameAgentByBaseByActionRequest(server string, cityName string, base string, action PostV0CityByCityNameAgentByBaseByActionParamsAction) (*http.Request, error) { +func NewPostV0CityByCityNameAgentByBaseByActionRequest(server string, cityName string, base string, action PostV0CityByCityNameAgentByBaseByActionParamsAction, params *PostV0CityByCityNameAgentByBaseByActionParams) (*http.Request, error) { var err error var pathParam0 string @@ -6763,11 +7433,24 @@ func NewPostV0CityByCityNameAgentByBaseByActionRequest(server string, cityName s return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewDeleteV0CityByCityNameAgentByDirByBaseRequest generates requests for DeleteV0CityByCityNameAgentByDirByBase -func NewDeleteV0CityByCityNameAgentByDirByBaseRequest(server string, cityName string, dir string, base string) (*http.Request, error) { +func NewDeleteV0CityByCityNameAgentByDirByBaseRequest(server string, cityName string, dir string, base string, params *DeleteV0CityByCityNameAgentByDirByBaseParams) (*http.Request, error) { var err error var pathParam0 string @@ -6811,6 +7494,19 @@ func NewDeleteV0CityByCityNameAgentByDirByBaseRequest(server string, cityName st return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -6863,18 +7559,18 @@ func NewGetV0CityByCityNameAgentByDirByBaseRequest(server string, cityName strin } // NewPatchV0CityByCityNameAgentByDirByBaseRequest calls the generic PatchV0CityByCityNameAgentByDirByBase builder with application/json body -func NewPatchV0CityByCityNameAgentByDirByBaseRequest(server string, cityName string, dir string, base string, body PatchV0CityByCityNameAgentByDirByBaseJSONRequestBody) (*http.Request, error) { +func NewPatchV0CityByCityNameAgentByDirByBaseRequest(server string, cityName string, dir string, base string, params *PatchV0CityByCityNameAgentByDirByBaseParams, body PatchV0CityByCityNameAgentByDirByBaseJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPatchV0CityByCityNameAgentByDirByBaseRequestWithBody(server, cityName, dir, base, "application/json", bodyReader) + return NewPatchV0CityByCityNameAgentByDirByBaseRequestWithBody(server, cityName, dir, base, params, "application/json", bodyReader) } // NewPatchV0CityByCityNameAgentByDirByBaseRequestWithBody generates requests for PatchV0CityByCityNameAgentByDirByBase with any type of body -func NewPatchV0CityByCityNameAgentByDirByBaseRequestWithBody(server string, cityName string, dir string, base string, contentType string, body io.Reader) (*http.Request, error) { +func NewPatchV0CityByCityNameAgentByDirByBaseRequestWithBody(server string, cityName string, dir string, base string, params *PatchV0CityByCityNameAgentByDirByBaseParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -6920,6 +7616,19 @@ func NewPatchV0CityByCityNameAgentByDirByBaseRequestWithBody(server string, city req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -7058,7 +7767,7 @@ func NewStreamAgentOutputQualifiedRequest(server string, cityName string, dir st } // NewPostV0CityByCityNameAgentByDirByBaseByActionRequest generates requests for PostV0CityByCityNameAgentByDirByBaseByAction -func NewPostV0CityByCityNameAgentByDirByBaseByActionRequest(server string, cityName string, dir string, base string, action PostV0CityByCityNameAgentByDirByBaseByActionParamsAction) (*http.Request, error) { +func NewPostV0CityByCityNameAgentByDirByBaseByActionRequest(server string, cityName string, dir string, base string, action PostV0CityByCityNameAgentByDirByBaseByActionParamsAction, params *PostV0CityByCityNameAgentByDirByBaseByActionParams) (*http.Request, error) { var err error var pathParam0 string @@ -7109,6 +7818,19 @@ func NewPostV0CityByCityNameAgentByDirByBaseByActionRequest(server string, cityN return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -7249,18 +7971,18 @@ func NewGetV0CityByCityNameAgentsRequest(server string, cityName string, params } // NewCreateAgentRequest calls the generic CreateAgent builder with application/json body -func NewCreateAgentRequest(server string, cityName string, body CreateAgentJSONRequestBody) (*http.Request, error) { +func NewCreateAgentRequest(server string, cityName string, params *CreateAgentParams, body CreateAgentJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewCreateAgentRequestWithBody(server, cityName, "application/json", bodyReader) + return NewCreateAgentRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewCreateAgentRequestWithBody generates requests for CreateAgent with any type of body -func NewCreateAgentRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewCreateAgentRequestWithBody(server string, cityName string, params *CreateAgentParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -7292,11 +8014,24 @@ func NewCreateAgentRequestWithBody(server string, cityName string, contentType s req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewDeleteV0CityByCityNameBeadByIdRequest generates requests for DeleteV0CityByCityNameBeadById -func NewDeleteV0CityByCityNameBeadByIdRequest(server string, cityName string, id string) (*http.Request, error) { +func NewDeleteV0CityByCityNameBeadByIdRequest(server string, cityName string, id string, params *DeleteV0CityByCityNameBeadByIdParams) (*http.Request, error) { var err error var pathParam0 string @@ -7333,6 +8068,19 @@ func NewDeleteV0CityByCityNameBeadByIdRequest(server string, cityName string, id return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -7378,18 +8126,18 @@ func NewGetV0CityByCityNameBeadByIdRequest(server string, cityName string, id st } // NewPatchV0CityByCityNameBeadByIdRequest calls the generic PatchV0CityByCityNameBeadById builder with application/json body -func NewPatchV0CityByCityNameBeadByIdRequest(server string, cityName string, id string, body PatchV0CityByCityNameBeadByIdJSONRequestBody) (*http.Request, error) { +func NewPatchV0CityByCityNameBeadByIdRequest(server string, cityName string, id string, params *PatchV0CityByCityNameBeadByIdParams, body PatchV0CityByCityNameBeadByIdJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPatchV0CityByCityNameBeadByIdRequestWithBody(server, cityName, id, "application/json", bodyReader) + return NewPatchV0CityByCityNameBeadByIdRequestWithBody(server, cityName, id, params, "application/json", bodyReader) } // NewPatchV0CityByCityNameBeadByIdRequestWithBody generates requests for PatchV0CityByCityNameBeadById with any type of body -func NewPatchV0CityByCityNameBeadByIdRequestWithBody(server string, cityName string, id string, contentType string, body io.Reader) (*http.Request, error) { +func NewPatchV0CityByCityNameBeadByIdRequestWithBody(server string, cityName string, id string, params *PatchV0CityByCityNameBeadByIdParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -7428,22 +8176,35 @@ func NewPatchV0CityByCityNameBeadByIdRequestWithBody(server string, cityName str req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewPostV0CityByCityNameBeadByIdAssignRequest calls the generic PostV0CityByCityNameBeadByIdAssign builder with application/json body -func NewPostV0CityByCityNameBeadByIdAssignRequest(server string, cityName string, id string, body PostV0CityByCityNameBeadByIdAssignJSONRequestBody) (*http.Request, error) { +func NewPostV0CityByCityNameBeadByIdAssignRequest(server string, cityName string, id string, params *PostV0CityByCityNameBeadByIdAssignParams, body PostV0CityByCityNameBeadByIdAssignJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPostV0CityByCityNameBeadByIdAssignRequestWithBody(server, cityName, id, "application/json", bodyReader) + return NewPostV0CityByCityNameBeadByIdAssignRequestWithBody(server, cityName, id, params, "application/json", bodyReader) } // NewPostV0CityByCityNameBeadByIdAssignRequestWithBody generates requests for PostV0CityByCityNameBeadByIdAssign with any type of body -func NewPostV0CityByCityNameBeadByIdAssignRequestWithBody(server string, cityName string, id string, contentType string, body io.Reader) (*http.Request, error) { +func NewPostV0CityByCityNameBeadByIdAssignRequestWithBody(server string, cityName string, id string, params *PostV0CityByCityNameBeadByIdAssignParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -7482,11 +8243,24 @@ func NewPostV0CityByCityNameBeadByIdAssignRequestWithBody(server string, cityNam req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewPostV0CityByCityNameBeadByIdCloseRequest generates requests for PostV0CityByCityNameBeadByIdClose -func NewPostV0CityByCityNameBeadByIdCloseRequest(server string, cityName string, id string) (*http.Request, error) { +func NewPostV0CityByCityNameBeadByIdCloseRequest(server string, cityName string, id string, params *PostV0CityByCityNameBeadByIdCloseParams) (*http.Request, error) { var err error var pathParam0 string @@ -7523,6 +8297,19 @@ func NewPostV0CityByCityNameBeadByIdCloseRequest(server string, cityName string, return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -7568,7 +8355,7 @@ func NewGetV0CityByCityNameBeadByIdDepsRequest(server string, cityName string, i } // NewPostV0CityByCityNameBeadByIdReopenRequest generates requests for PostV0CityByCityNameBeadByIdReopen -func NewPostV0CityByCityNameBeadByIdReopenRequest(server string, cityName string, id string) (*http.Request, error) { +func NewPostV0CityByCityNameBeadByIdReopenRequest(server string, cityName string, id string, params *PostV0CityByCityNameBeadByIdReopenParams) (*http.Request, error) { var err error var pathParam0 string @@ -7605,22 +8392,35 @@ func NewPostV0CityByCityNameBeadByIdReopenRequest(server string, cityName string return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewPostV0CityByCityNameBeadByIdUpdateRequest calls the generic PostV0CityByCityNameBeadByIdUpdate builder with application/json body -func NewPostV0CityByCityNameBeadByIdUpdateRequest(server string, cityName string, id string, body PostV0CityByCityNameBeadByIdUpdateJSONRequestBody) (*http.Request, error) { +func NewPostV0CityByCityNameBeadByIdUpdateRequest(server string, cityName string, id string, params *PostV0CityByCityNameBeadByIdUpdateParams, body PostV0CityByCityNameBeadByIdUpdateJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPostV0CityByCityNameBeadByIdUpdateRequestWithBody(server, cityName, id, "application/json", bodyReader) + return NewPostV0CityByCityNameBeadByIdUpdateRequestWithBody(server, cityName, id, params, "application/json", bodyReader) } // NewPostV0CityByCityNameBeadByIdUpdateRequestWithBody generates requests for PostV0CityByCityNameBeadByIdUpdate with any type of body -func NewPostV0CityByCityNameBeadByIdUpdateRequestWithBody(server string, cityName string, id string, contentType string, body io.Reader) (*http.Request, error) { +func NewPostV0CityByCityNameBeadByIdUpdateRequestWithBody(server string, cityName string, id string, params *PostV0CityByCityNameBeadByIdUpdateParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -7659,6 +8459,19 @@ func NewPostV0CityByCityNameBeadByIdUpdateRequestWithBody(server string, cityNam req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -7892,15 +8705,24 @@ func NewCreateBeadRequestWithBody(server string, cityName string, params *Create if params != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + if params.IdempotencyKey != nil { - var headerParam0 string + var headerParam1 string - headerParam0, err = runtime.StyleParamWithOptions("simple", false, "Idempotency-Key", *params.IdempotencyKey, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + headerParam1, err = runtime.StyleParamWithOptions("simple", false, "Idempotency-Key", *params.IdempotencyKey, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) if err != nil { return nil, err } - req.Header.Set("Idempotency-Key", headerParam0) + req.Header.Set("Idempotency-Key", headerParam1) } } @@ -8124,7 +8946,7 @@ func NewGetV0CityByCityNameConfigValidateRequest(server string, cityName string) } // NewDeleteV0CityByCityNameConvoyByIdRequest generates requests for DeleteV0CityByCityNameConvoyById -func NewDeleteV0CityByCityNameConvoyByIdRequest(server string, cityName string, id string) (*http.Request, error) { +func NewDeleteV0CityByCityNameConvoyByIdRequest(server string, cityName string, id string, params *DeleteV0CityByCityNameConvoyByIdParams) (*http.Request, error) { var err error var pathParam0 string @@ -8161,6 +8983,19 @@ func NewDeleteV0CityByCityNameConvoyByIdRequest(server string, cityName string, return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -8206,18 +9041,18 @@ func NewGetV0CityByCityNameConvoyByIdRequest(server string, cityName string, id } // NewPostV0CityByCityNameConvoyByIdAddRequest calls the generic PostV0CityByCityNameConvoyByIdAdd builder with application/json body -func NewPostV0CityByCityNameConvoyByIdAddRequest(server string, cityName string, id string, body PostV0CityByCityNameConvoyByIdAddJSONRequestBody) (*http.Request, error) { +func NewPostV0CityByCityNameConvoyByIdAddRequest(server string, cityName string, id string, params *PostV0CityByCityNameConvoyByIdAddParams, body PostV0CityByCityNameConvoyByIdAddJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPostV0CityByCityNameConvoyByIdAddRequestWithBody(server, cityName, id, "application/json", bodyReader) + return NewPostV0CityByCityNameConvoyByIdAddRequestWithBody(server, cityName, id, params, "application/json", bodyReader) } // NewPostV0CityByCityNameConvoyByIdAddRequestWithBody generates requests for PostV0CityByCityNameConvoyByIdAdd with any type of body -func NewPostV0CityByCityNameConvoyByIdAddRequestWithBody(server string, cityName string, id string, contentType string, body io.Reader) (*http.Request, error) { +func NewPostV0CityByCityNameConvoyByIdAddRequestWithBody(server string, cityName string, id string, params *PostV0CityByCityNameConvoyByIdAddParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -8256,6 +9091,19 @@ func NewPostV0CityByCityNameConvoyByIdAddRequestWithBody(server string, cityName req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -8301,7 +9149,7 @@ func NewGetV0CityByCityNameConvoyByIdCheckRequest(server string, cityName string } // NewPostV0CityByCityNameConvoyByIdCloseRequest generates requests for PostV0CityByCityNameConvoyByIdClose -func NewPostV0CityByCityNameConvoyByIdCloseRequest(server string, cityName string, id string) (*http.Request, error) { +func NewPostV0CityByCityNameConvoyByIdCloseRequest(server string, cityName string, id string, params *PostV0CityByCityNameConvoyByIdCloseParams) (*http.Request, error) { var err error var pathParam0 string @@ -8338,22 +9186,35 @@ func NewPostV0CityByCityNameConvoyByIdCloseRequest(server string, cityName strin return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewPostV0CityByCityNameConvoyByIdRemoveRequest calls the generic PostV0CityByCityNameConvoyByIdRemove builder with application/json body -func NewPostV0CityByCityNameConvoyByIdRemoveRequest(server string, cityName string, id string, body PostV0CityByCityNameConvoyByIdRemoveJSONRequestBody) (*http.Request, error) { +func NewPostV0CityByCityNameConvoyByIdRemoveRequest(server string, cityName string, id string, params *PostV0CityByCityNameConvoyByIdRemoveParams, body PostV0CityByCityNameConvoyByIdRemoveJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPostV0CityByCityNameConvoyByIdRemoveRequestWithBody(server, cityName, id, "application/json", bodyReader) + return NewPostV0CityByCityNameConvoyByIdRemoveRequestWithBody(server, cityName, id, params, "application/json", bodyReader) } // NewPostV0CityByCityNameConvoyByIdRemoveRequestWithBody generates requests for PostV0CityByCityNameConvoyByIdRemove with any type of body -func NewPostV0CityByCityNameConvoyByIdRemoveRequestWithBody(server string, cityName string, id string, contentType string, body io.Reader) (*http.Request, error) { +func NewPostV0CityByCityNameConvoyByIdRemoveRequestWithBody(server string, cityName string, id string, params *PostV0CityByCityNameConvoyByIdRemoveParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -8392,6 +9253,19 @@ func NewPostV0CityByCityNameConvoyByIdRemoveRequestWithBody(server string, cityN req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -8500,18 +9374,18 @@ func NewGetV0CityByCityNameConvoysRequest(server string, cityName string, params } // NewCreateConvoyRequest calls the generic CreateConvoy builder with application/json body -func NewCreateConvoyRequest(server string, cityName string, body CreateConvoyJSONRequestBody) (*http.Request, error) { +func NewCreateConvoyRequest(server string, cityName string, params *CreateConvoyParams, body CreateConvoyJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewCreateConvoyRequestWithBody(server, cityName, "application/json", bodyReader) + return NewCreateConvoyRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewCreateConvoyRequestWithBody generates requests for CreateConvoy with any type of body -func NewCreateConvoyRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewCreateConvoyRequestWithBody(server string, cityName string, params *CreateConvoyParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -8543,6 +9417,19 @@ func NewCreateConvoyRequestWithBody(server string, cityName string, contentType req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -8699,18 +9586,18 @@ func NewGetV0CityByCityNameEventsRequest(server string, cityName string, params } // NewEmitEventRequest calls the generic EmitEvent builder with application/json body -func NewEmitEventRequest(server string, cityName string, body EmitEventJSONRequestBody) (*http.Request, error) { +func NewEmitEventRequest(server string, cityName string, params *EmitEventParams, body EmitEventJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewEmitEventRequestWithBody(server, cityName, "application/json", bodyReader) + return NewEmitEventRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewEmitEventRequestWithBody generates requests for EmitEvent with any type of body -func NewEmitEventRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewEmitEventRequestWithBody(server string, cityName string, params *EmitEventParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -8742,6 +9629,19 @@ func NewEmitEventRequestWithBody(server string, cityName string, contentType str req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -8817,18 +9717,18 @@ func NewStreamEventsRequest(server string, cityName string, params *StreamEvents } // NewDeleteV0CityByCityNameExtmsgAdaptersRequest calls the generic DeleteV0CityByCityNameExtmsgAdapters builder with application/json body -func NewDeleteV0CityByCityNameExtmsgAdaptersRequest(server string, cityName string, body DeleteV0CityByCityNameExtmsgAdaptersJSONRequestBody) (*http.Request, error) { +func NewDeleteV0CityByCityNameExtmsgAdaptersRequest(server string, cityName string, params *DeleteV0CityByCityNameExtmsgAdaptersParams, body DeleteV0CityByCityNameExtmsgAdaptersJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewDeleteV0CityByCityNameExtmsgAdaptersRequestWithBody(server, cityName, "application/json", bodyReader) + return NewDeleteV0CityByCityNameExtmsgAdaptersRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewDeleteV0CityByCityNameExtmsgAdaptersRequestWithBody generates requests for DeleteV0CityByCityNameExtmsgAdapters with any type of body -func NewDeleteV0CityByCityNameExtmsgAdaptersRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewDeleteV0CityByCityNameExtmsgAdaptersRequestWithBody(server string, cityName string, params *DeleteV0CityByCityNameExtmsgAdaptersParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -8860,6 +9760,19 @@ func NewDeleteV0CityByCityNameExtmsgAdaptersRequestWithBody(server string, cityN req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -8898,18 +9811,18 @@ func NewGetV0CityByCityNameExtmsgAdaptersRequest(server string, cityName string) } // NewRegisterExtmsgAdapterRequest calls the generic RegisterExtmsgAdapter builder with application/json body -func NewRegisterExtmsgAdapterRequest(server string, cityName string, body RegisterExtmsgAdapterJSONRequestBody) (*http.Request, error) { +func NewRegisterExtmsgAdapterRequest(server string, cityName string, params *RegisterExtmsgAdapterParams, body RegisterExtmsgAdapterJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewRegisterExtmsgAdapterRequestWithBody(server, cityName, "application/json", bodyReader) + return NewRegisterExtmsgAdapterRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewRegisterExtmsgAdapterRequestWithBody generates requests for RegisterExtmsgAdapter with any type of body -func NewRegisterExtmsgAdapterRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewRegisterExtmsgAdapterRequestWithBody(server string, cityName string, params *RegisterExtmsgAdapterParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -8941,22 +9854,35 @@ func NewRegisterExtmsgAdapterRequestWithBody(server string, cityName string, con req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewPostV0CityByCityNameExtmsgBindRequest calls the generic PostV0CityByCityNameExtmsgBind builder with application/json body -func NewPostV0CityByCityNameExtmsgBindRequest(server string, cityName string, body PostV0CityByCityNameExtmsgBindJSONRequestBody) (*http.Request, error) { +func NewPostV0CityByCityNameExtmsgBindRequest(server string, cityName string, params *PostV0CityByCityNameExtmsgBindParams, body PostV0CityByCityNameExtmsgBindJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPostV0CityByCityNameExtmsgBindRequestWithBody(server, cityName, "application/json", bodyReader) + return NewPostV0CityByCityNameExtmsgBindRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewPostV0CityByCityNameExtmsgBindRequestWithBody generates requests for PostV0CityByCityNameExtmsgBind with any type of body -func NewPostV0CityByCityNameExtmsgBindRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewPostV0CityByCityNameExtmsgBindRequestWithBody(server string, cityName string, params *PostV0CityByCityNameExtmsgBindParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -8988,6 +9914,19 @@ func NewPostV0CityByCityNameExtmsgBindRequestWithBody(server string, cityName st req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -9168,18 +10107,18 @@ func NewGetV0CityByCityNameExtmsgGroupsRequest(server string, cityName string, p } // NewEnsureExtmsgGroupRequest calls the generic EnsureExtmsgGroup builder with application/json body -func NewEnsureExtmsgGroupRequest(server string, cityName string, body EnsureExtmsgGroupJSONRequestBody) (*http.Request, error) { +func NewEnsureExtmsgGroupRequest(server string, cityName string, params *EnsureExtmsgGroupParams, body EnsureExtmsgGroupJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewEnsureExtmsgGroupRequestWithBody(server, cityName, "application/json", bodyReader) + return NewEnsureExtmsgGroupRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewEnsureExtmsgGroupRequestWithBody generates requests for EnsureExtmsgGroup with any type of body -func NewEnsureExtmsgGroupRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewEnsureExtmsgGroupRequestWithBody(server string, cityName string, params *EnsureExtmsgGroupParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -9211,22 +10150,35 @@ func NewEnsureExtmsgGroupRequestWithBody(server string, cityName string, content req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewPostV0CityByCityNameExtmsgInboundRequest calls the generic PostV0CityByCityNameExtmsgInbound builder with application/json body -func NewPostV0CityByCityNameExtmsgInboundRequest(server string, cityName string, body PostV0CityByCityNameExtmsgInboundJSONRequestBody) (*http.Request, error) { +func NewPostV0CityByCityNameExtmsgInboundRequest(server string, cityName string, params *PostV0CityByCityNameExtmsgInboundParams, body PostV0CityByCityNameExtmsgInboundJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPostV0CityByCityNameExtmsgInboundRequestWithBody(server, cityName, "application/json", bodyReader) + return NewPostV0CityByCityNameExtmsgInboundRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewPostV0CityByCityNameExtmsgInboundRequestWithBody generates requests for PostV0CityByCityNameExtmsgInbound with any type of body -func NewPostV0CityByCityNameExtmsgInboundRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewPostV0CityByCityNameExtmsgInboundRequestWithBody(server string, cityName string, params *PostV0CityByCityNameExtmsgInboundParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -9258,22 +10210,35 @@ func NewPostV0CityByCityNameExtmsgInboundRequestWithBody(server string, cityName req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewPostV0CityByCityNameExtmsgOutboundRequest calls the generic PostV0CityByCityNameExtmsgOutbound builder with application/json body -func NewPostV0CityByCityNameExtmsgOutboundRequest(server string, cityName string, body PostV0CityByCityNameExtmsgOutboundJSONRequestBody) (*http.Request, error) { +func NewPostV0CityByCityNameExtmsgOutboundRequest(server string, cityName string, params *PostV0CityByCityNameExtmsgOutboundParams, body PostV0CityByCityNameExtmsgOutboundJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPostV0CityByCityNameExtmsgOutboundRequestWithBody(server, cityName, "application/json", bodyReader) + return NewPostV0CityByCityNameExtmsgOutboundRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewPostV0CityByCityNameExtmsgOutboundRequestWithBody generates requests for PostV0CityByCityNameExtmsgOutbound with any type of body -func NewPostV0CityByCityNameExtmsgOutboundRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewPostV0CityByCityNameExtmsgOutboundRequestWithBody(server string, cityName string, params *PostV0CityByCityNameExtmsgOutboundParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -9305,22 +10270,35 @@ func NewPostV0CityByCityNameExtmsgOutboundRequestWithBody(server string, cityNam req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewDeleteV0CityByCityNameExtmsgParticipantsRequest calls the generic DeleteV0CityByCityNameExtmsgParticipants builder with application/json body -func NewDeleteV0CityByCityNameExtmsgParticipantsRequest(server string, cityName string, body DeleteV0CityByCityNameExtmsgParticipantsJSONRequestBody) (*http.Request, error) { +func NewDeleteV0CityByCityNameExtmsgParticipantsRequest(server string, cityName string, params *DeleteV0CityByCityNameExtmsgParticipantsParams, body DeleteV0CityByCityNameExtmsgParticipantsJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewDeleteV0CityByCityNameExtmsgParticipantsRequestWithBody(server, cityName, "application/json", bodyReader) + return NewDeleteV0CityByCityNameExtmsgParticipantsRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewDeleteV0CityByCityNameExtmsgParticipantsRequestWithBody generates requests for DeleteV0CityByCityNameExtmsgParticipants with any type of body -func NewDeleteV0CityByCityNameExtmsgParticipantsRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewDeleteV0CityByCityNameExtmsgParticipantsRequestWithBody(server string, cityName string, params *DeleteV0CityByCityNameExtmsgParticipantsParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -9350,24 +10328,37 @@ func NewDeleteV0CityByCityNameExtmsgParticipantsRequestWithBody(server string, c return nil, err } - req.Header.Add("Content-Type", contentType) + req.Header.Add("Content-Type", contentType) + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } return req, nil } // NewPostV0CityByCityNameExtmsgParticipantsRequest calls the generic PostV0CityByCityNameExtmsgParticipants builder with application/json body -func NewPostV0CityByCityNameExtmsgParticipantsRequest(server string, cityName string, body PostV0CityByCityNameExtmsgParticipantsJSONRequestBody) (*http.Request, error) { +func NewPostV0CityByCityNameExtmsgParticipantsRequest(server string, cityName string, params *PostV0CityByCityNameExtmsgParticipantsParams, body PostV0CityByCityNameExtmsgParticipantsJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPostV0CityByCityNameExtmsgParticipantsRequestWithBody(server, cityName, "application/json", bodyReader) + return NewPostV0CityByCityNameExtmsgParticipantsRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewPostV0CityByCityNameExtmsgParticipantsRequestWithBody generates requests for PostV0CityByCityNameExtmsgParticipants with any type of body -func NewPostV0CityByCityNameExtmsgParticipantsRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewPostV0CityByCityNameExtmsgParticipantsRequestWithBody(server string, cityName string, params *PostV0CityByCityNameExtmsgParticipantsParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -9399,6 +10390,19 @@ func NewPostV0CityByCityNameExtmsgParticipantsRequestWithBody(server string, cit req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -9539,18 +10543,18 @@ func NewGetV0CityByCityNameExtmsgTranscriptRequest(server string, cityName strin } // NewPostV0CityByCityNameExtmsgTranscriptAckRequest calls the generic PostV0CityByCityNameExtmsgTranscriptAck builder with application/json body -func NewPostV0CityByCityNameExtmsgTranscriptAckRequest(server string, cityName string, body PostV0CityByCityNameExtmsgTranscriptAckJSONRequestBody) (*http.Request, error) { +func NewPostV0CityByCityNameExtmsgTranscriptAckRequest(server string, cityName string, params *PostV0CityByCityNameExtmsgTranscriptAckParams, body PostV0CityByCityNameExtmsgTranscriptAckJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPostV0CityByCityNameExtmsgTranscriptAckRequestWithBody(server, cityName, "application/json", bodyReader) + return NewPostV0CityByCityNameExtmsgTranscriptAckRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewPostV0CityByCityNameExtmsgTranscriptAckRequestWithBody generates requests for PostV0CityByCityNameExtmsgTranscriptAck with any type of body -func NewPostV0CityByCityNameExtmsgTranscriptAckRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewPostV0CityByCityNameExtmsgTranscriptAckRequestWithBody(server string, cityName string, params *PostV0CityByCityNameExtmsgTranscriptAckParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -9582,22 +10586,35 @@ func NewPostV0CityByCityNameExtmsgTranscriptAckRequestWithBody(server string, ci req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewPostV0CityByCityNameExtmsgUnbindRequest calls the generic PostV0CityByCityNameExtmsgUnbind builder with application/json body -func NewPostV0CityByCityNameExtmsgUnbindRequest(server string, cityName string, body PostV0CityByCityNameExtmsgUnbindJSONRequestBody) (*http.Request, error) { +func NewPostV0CityByCityNameExtmsgUnbindRequest(server string, cityName string, params *PostV0CityByCityNameExtmsgUnbindParams, body PostV0CityByCityNameExtmsgUnbindJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPostV0CityByCityNameExtmsgUnbindRequestWithBody(server, cityName, "application/json", bodyReader) + return NewPostV0CityByCityNameExtmsgUnbindRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewPostV0CityByCityNameExtmsgUnbindRequestWithBody generates requests for PostV0CityByCityNameExtmsgUnbind with any type of body -func NewPostV0CityByCityNameExtmsgUnbindRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewPostV0CityByCityNameExtmsgUnbindRequestWithBody(server string, cityName string, params *PostV0CityByCityNameExtmsgUnbindParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -9629,6 +10646,19 @@ func NewPostV0CityByCityNameExtmsgUnbindRequestWithBody(server string, cityName req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -9975,18 +11005,18 @@ func NewGetV0CityByCityNameFormulasByNameRequest(server string, cityName string, } // NewPostV0CityByCityNameFormulasByNamePreviewRequest calls the generic PostV0CityByCityNameFormulasByNamePreview builder with application/json body -func NewPostV0CityByCityNameFormulasByNamePreviewRequest(server string, cityName string, name string, body PostV0CityByCityNameFormulasByNamePreviewJSONRequestBody) (*http.Request, error) { +func NewPostV0CityByCityNameFormulasByNamePreviewRequest(server string, cityName string, name string, params *PostV0CityByCityNameFormulasByNamePreviewParams, body PostV0CityByCityNameFormulasByNamePreviewJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPostV0CityByCityNameFormulasByNamePreviewRequestWithBody(server, cityName, name, "application/json", bodyReader) + return NewPostV0CityByCityNameFormulasByNamePreviewRequestWithBody(server, cityName, name, params, "application/json", bodyReader) } // NewPostV0CityByCityNameFormulasByNamePreviewRequestWithBody generates requests for PostV0CityByCityNameFormulasByNamePreview with any type of body -func NewPostV0CityByCityNameFormulasByNamePreviewRequestWithBody(server string, cityName string, name string, contentType string, body io.Reader) (*http.Request, error) { +func NewPostV0CityByCityNameFormulasByNamePreviewRequestWithBody(server string, cityName string, name string, params *PostV0CityByCityNameFormulasByNamePreviewParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -10025,6 +11055,19 @@ func NewPostV0CityByCityNameFormulasByNamePreviewRequestWithBody(server string, req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -10355,15 +11398,24 @@ func NewSendMailRequestWithBody(server string, cityName string, params *SendMail if params != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + if params.IdempotencyKey != nil { - var headerParam0 string + var headerParam1 string - headerParam0, err = runtime.StyleParamWithOptions("simple", false, "Idempotency-Key", *params.IdempotencyKey, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + headerParam1, err = runtime.StyleParamWithOptions("simple", false, "Idempotency-Key", *params.IdempotencyKey, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) if err != nil { return nil, err } - req.Header.Set("Idempotency-Key", headerParam0) + req.Header.Set("Idempotency-Key", headerParam1) } } @@ -10566,6 +11618,19 @@ func NewDeleteV0CityByCityNameMailByIdRequest(server string, cityName string, id return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -10692,6 +11757,19 @@ func NewPostV0CityByCityNameMailByIdArchiveRequest(server string, cityName strin return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -10755,6 +11833,19 @@ func NewPostV0CityByCityNameMailByIdMarkUnreadRequest(server string, cityName st return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -10818,6 +11909,19 @@ func NewPostV0CityByCityNameMailByIdReadRequest(server string, cityName string, return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -10894,6 +11998,19 @@ func NewReplyMailRequestWithBody(server string, cityName string, id string, para req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -11002,7 +12119,7 @@ func NewGetV0CityByCityNameOrderByNameRequest(server string, cityName string, na } // NewPostV0CityByCityNameOrderByNameDisableRequest generates requests for PostV0CityByCityNameOrderByNameDisable -func NewPostV0CityByCityNameOrderByNameDisableRequest(server string, cityName string, name string) (*http.Request, error) { +func NewPostV0CityByCityNameOrderByNameDisableRequest(server string, cityName string, name string, params *PostV0CityByCityNameOrderByNameDisableParams) (*http.Request, error) { var err error var pathParam0 string @@ -11039,11 +12156,24 @@ func NewPostV0CityByCityNameOrderByNameDisableRequest(server string, cityName st return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewPostV0CityByCityNameOrderByNameEnableRequest generates requests for PostV0CityByCityNameOrderByNameEnable -func NewPostV0CityByCityNameOrderByNameEnableRequest(server string, cityName string, name string) (*http.Request, error) { +func NewPostV0CityByCityNameOrderByNameEnableRequest(server string, cityName string, name string, params *PostV0CityByCityNameOrderByNameEnableParams) (*http.Request, error) { var err error var pathParam0 string @@ -11080,6 +12210,19 @@ func NewPostV0CityByCityNameOrderByNameEnableRequest(server string, cityName str return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -11358,7 +12501,7 @@ func NewGetV0CityByCityNamePacksRequest(server string, cityName string) (*http.R } // NewDeleteV0CityByCityNamePatchesAgentByBaseRequest generates requests for DeleteV0CityByCityNamePatchesAgentByBase -func NewDeleteV0CityByCityNamePatchesAgentByBaseRequest(server string, cityName string, base string) (*http.Request, error) { +func NewDeleteV0CityByCityNamePatchesAgentByBaseRequest(server string, cityName string, base string, params *DeleteV0CityByCityNamePatchesAgentByBaseParams) (*http.Request, error) { var err error var pathParam0 string @@ -11395,6 +12538,19 @@ func NewDeleteV0CityByCityNamePatchesAgentByBaseRequest(server string, cityName return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -11440,7 +12596,7 @@ func NewGetV0CityByCityNamePatchesAgentByBaseRequest(server string, cityName str } // NewDeleteV0CityByCityNamePatchesAgentByDirByBaseRequest generates requests for DeleteV0CityByCityNamePatchesAgentByDirByBase -func NewDeleteV0CityByCityNamePatchesAgentByDirByBaseRequest(server string, cityName string, dir string, base string) (*http.Request, error) { +func NewDeleteV0CityByCityNamePatchesAgentByDirByBaseRequest(server string, cityName string, dir string, base string, params *DeleteV0CityByCityNamePatchesAgentByDirByBaseParams) (*http.Request, error) { var err error var pathParam0 string @@ -11484,6 +12640,19 @@ func NewDeleteV0CityByCityNamePatchesAgentByDirByBaseRequest(server string, city return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -11570,18 +12739,18 @@ func NewGetV0CityByCityNamePatchesAgentsRequest(server string, cityName string) } // NewPutV0CityByCityNamePatchesAgentsRequest calls the generic PutV0CityByCityNamePatchesAgents builder with application/json body -func NewPutV0CityByCityNamePatchesAgentsRequest(server string, cityName string, body PutV0CityByCityNamePatchesAgentsJSONRequestBody) (*http.Request, error) { +func NewPutV0CityByCityNamePatchesAgentsRequest(server string, cityName string, params *PutV0CityByCityNamePatchesAgentsParams, body PutV0CityByCityNamePatchesAgentsJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPutV0CityByCityNamePatchesAgentsRequestWithBody(server, cityName, "application/json", bodyReader) + return NewPutV0CityByCityNamePatchesAgentsRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewPutV0CityByCityNamePatchesAgentsRequestWithBody generates requests for PutV0CityByCityNamePatchesAgents with any type of body -func NewPutV0CityByCityNamePatchesAgentsRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewPutV0CityByCityNamePatchesAgentsRequestWithBody(server string, cityName string, params *PutV0CityByCityNamePatchesAgentsParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -11613,11 +12782,24 @@ func NewPutV0CityByCityNamePatchesAgentsRequestWithBody(server string, cityName req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewDeleteV0CityByCityNamePatchesProviderByNameRequest generates requests for DeleteV0CityByCityNamePatchesProviderByName -func NewDeleteV0CityByCityNamePatchesProviderByNameRequest(server string, cityName string, name string) (*http.Request, error) { +func NewDeleteV0CityByCityNamePatchesProviderByNameRequest(server string, cityName string, name string, params *DeleteV0CityByCityNamePatchesProviderByNameParams) (*http.Request, error) { var err error var pathParam0 string @@ -11654,6 +12836,19 @@ func NewDeleteV0CityByCityNamePatchesProviderByNameRequest(server string, cityNa return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -11733,18 +12928,18 @@ func NewGetV0CityByCityNamePatchesProvidersRequest(server string, cityName strin } // NewPutV0CityByCityNamePatchesProvidersRequest calls the generic PutV0CityByCityNamePatchesProviders builder with application/json body -func NewPutV0CityByCityNamePatchesProvidersRequest(server string, cityName string, body PutV0CityByCityNamePatchesProvidersJSONRequestBody) (*http.Request, error) { +func NewPutV0CityByCityNamePatchesProvidersRequest(server string, cityName string, params *PutV0CityByCityNamePatchesProvidersParams, body PutV0CityByCityNamePatchesProvidersJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPutV0CityByCityNamePatchesProvidersRequestWithBody(server, cityName, "application/json", bodyReader) + return NewPutV0CityByCityNamePatchesProvidersRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewPutV0CityByCityNamePatchesProvidersRequestWithBody generates requests for PutV0CityByCityNamePatchesProviders with any type of body -func NewPutV0CityByCityNamePatchesProvidersRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewPutV0CityByCityNamePatchesProvidersRequestWithBody(server string, cityName string, params *PutV0CityByCityNamePatchesProvidersParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -11776,11 +12971,24 @@ func NewPutV0CityByCityNamePatchesProvidersRequestWithBody(server string, cityNa req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewDeleteV0CityByCityNamePatchesRigByNameRequest generates requests for DeleteV0CityByCityNamePatchesRigByName -func NewDeleteV0CityByCityNamePatchesRigByNameRequest(server string, cityName string, name string) (*http.Request, error) { +func NewDeleteV0CityByCityNamePatchesRigByNameRequest(server string, cityName string, name string, params *DeleteV0CityByCityNamePatchesRigByNameParams) (*http.Request, error) { var err error var pathParam0 string @@ -11817,6 +13025,19 @@ func NewDeleteV0CityByCityNamePatchesRigByNameRequest(server string, cityName st return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -11896,18 +13117,18 @@ func NewGetV0CityByCityNamePatchesRigsRequest(server string, cityName string) (* } // NewPutV0CityByCityNamePatchesRigsRequest calls the generic PutV0CityByCityNamePatchesRigs builder with application/json body -func NewPutV0CityByCityNamePatchesRigsRequest(server string, cityName string, body PutV0CityByCityNamePatchesRigsJSONRequestBody) (*http.Request, error) { +func NewPutV0CityByCityNamePatchesRigsRequest(server string, cityName string, params *PutV0CityByCityNamePatchesRigsParams, body PutV0CityByCityNamePatchesRigsJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPutV0CityByCityNamePatchesRigsRequestWithBody(server, cityName, "application/json", bodyReader) + return NewPutV0CityByCityNamePatchesRigsRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewPutV0CityByCityNamePatchesRigsRequestWithBody generates requests for PutV0CityByCityNamePatchesRigs with any type of body -func NewPutV0CityByCityNamePatchesRigsRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewPutV0CityByCityNamePatchesRigsRequestWithBody(server string, cityName string, params *PutV0CityByCityNamePatchesRigsParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -11939,6 +13160,19 @@ func NewPutV0CityByCityNamePatchesRigsRequestWithBody(server string, cityName st req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -12015,7 +13249,7 @@ func NewGetV0CityByCityNameProviderReadinessRequest(server string, cityName stri } // NewDeleteV0CityByCityNameProviderByNameRequest generates requests for DeleteV0CityByCityNameProviderByName -func NewDeleteV0CityByCityNameProviderByNameRequest(server string, cityName string, name string) (*http.Request, error) { +func NewDeleteV0CityByCityNameProviderByNameRequest(server string, cityName string, name string, params *DeleteV0CityByCityNameProviderByNameParams) (*http.Request, error) { var err error var pathParam0 string @@ -12052,6 +13286,19 @@ func NewDeleteV0CityByCityNameProviderByNameRequest(server string, cityName stri return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -12097,18 +13344,18 @@ func NewGetV0CityByCityNameProviderByNameRequest(server string, cityName string, } // NewPatchV0CityByCityNameProviderByNameRequest calls the generic PatchV0CityByCityNameProviderByName builder with application/json body -func NewPatchV0CityByCityNameProviderByNameRequest(server string, cityName string, name string, body PatchV0CityByCityNameProviderByNameJSONRequestBody) (*http.Request, error) { +func NewPatchV0CityByCityNameProviderByNameRequest(server string, cityName string, name string, params *PatchV0CityByCityNameProviderByNameParams, body PatchV0CityByCityNameProviderByNameJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPatchV0CityByCityNameProviderByNameRequestWithBody(server, cityName, name, "application/json", bodyReader) + return NewPatchV0CityByCityNameProviderByNameRequestWithBody(server, cityName, name, params, "application/json", bodyReader) } // NewPatchV0CityByCityNameProviderByNameRequestWithBody generates requests for PatchV0CityByCityNameProviderByName with any type of body -func NewPatchV0CityByCityNameProviderByNameRequestWithBody(server string, cityName string, name string, contentType string, body io.Reader) (*http.Request, error) { +func NewPatchV0CityByCityNameProviderByNameRequestWithBody(server string, cityName string, name string, params *PatchV0CityByCityNameProviderByNameParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -12147,6 +13394,19 @@ func NewPatchV0CityByCityNameProviderByNameRequestWithBody(server string, cityNa req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -12185,18 +13445,18 @@ func NewGetV0CityByCityNameProvidersRequest(server string, cityName string) (*ht } // NewCreateProviderRequest calls the generic CreateProvider builder with application/json body -func NewCreateProviderRequest(server string, cityName string, body CreateProviderJSONRequestBody) (*http.Request, error) { +func NewCreateProviderRequest(server string, cityName string, params *CreateProviderParams, body CreateProviderJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewCreateProviderRequestWithBody(server, cityName, "application/json", bodyReader) + return NewCreateProviderRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewCreateProviderRequestWithBody generates requests for CreateProvider with any type of body -func NewCreateProviderRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewCreateProviderRequestWithBody(server string, cityName string, params *CreateProviderParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -12228,6 +13488,19 @@ func NewCreateProviderRequestWithBody(server string, cityName string, contentTyp req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -12338,7 +13611,7 @@ func NewGetV0CityByCityNameReadinessRequest(server string, cityName string, para } // NewDeleteV0CityByCityNameRigByNameRequest generates requests for DeleteV0CityByCityNameRigByName -func NewDeleteV0CityByCityNameRigByNameRequest(server string, cityName string, name string) (*http.Request, error) { +func NewDeleteV0CityByCityNameRigByNameRequest(server string, cityName string, name string, params *DeleteV0CityByCityNameRigByNameParams) (*http.Request, error) { var err error var pathParam0 string @@ -12375,6 +13648,19 @@ func NewDeleteV0CityByCityNameRigByNameRequest(server string, cityName string, n return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -12442,18 +13728,18 @@ func NewGetV0CityByCityNameRigByNameRequest(server string, cityName string, name } // NewPatchV0CityByCityNameRigByNameRequest calls the generic PatchV0CityByCityNameRigByName builder with application/json body -func NewPatchV0CityByCityNameRigByNameRequest(server string, cityName string, name string, body PatchV0CityByCityNameRigByNameJSONRequestBody) (*http.Request, error) { +func NewPatchV0CityByCityNameRigByNameRequest(server string, cityName string, name string, params *PatchV0CityByCityNameRigByNameParams, body PatchV0CityByCityNameRigByNameJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPatchV0CityByCityNameRigByNameRequestWithBody(server, cityName, name, "application/json", bodyReader) + return NewPatchV0CityByCityNameRigByNameRequestWithBody(server, cityName, name, params, "application/json", bodyReader) } // NewPatchV0CityByCityNameRigByNameRequestWithBody generates requests for PatchV0CityByCityNameRigByName with any type of body -func NewPatchV0CityByCityNameRigByNameRequestWithBody(server string, cityName string, name string, contentType string, body io.Reader) (*http.Request, error) { +func NewPatchV0CityByCityNameRigByNameRequestWithBody(server string, cityName string, name string, params *PatchV0CityByCityNameRigByNameParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -12492,11 +13778,24 @@ func NewPatchV0CityByCityNameRigByNameRequestWithBody(server string, cityName st req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewPostV0CityByCityNameRigByNameByActionRequest generates requests for PostV0CityByCityNameRigByNameByAction -func NewPostV0CityByCityNameRigByNameByActionRequest(server string, cityName string, name string, action string) (*http.Request, error) { +func NewPostV0CityByCityNameRigByNameByActionRequest(server string, cityName string, name string, action string, params *PostV0CityByCityNameRigByNameByActionParams) (*http.Request, error) { var err error var pathParam0 string @@ -12530,14 +13829,27 @@ func NewPostV0CityByCityNameRigByNameByActionRequest(server string, cityName str operationPath = "." + operationPath } - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) - req, err := http.NewRequest("POST", queryURL.String(), nil) - if err != nil { - return nil, err } return req, nil @@ -12632,18 +13944,18 @@ func NewGetV0CityByCityNameRigsRequest(server string, cityName string, params *G } // NewCreateRigRequest calls the generic CreateRig builder with application/json body -func NewCreateRigRequest(server string, cityName string, body CreateRigJSONRequestBody) (*http.Request, error) { +func NewCreateRigRequest(server string, cityName string, params *CreateRigParams, body CreateRigJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewCreateRigRequestWithBody(server, cityName, "application/json", bodyReader) + return NewCreateRigRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewCreateRigRequestWithBody generates requests for CreateRig with any type of body -func NewCreateRigRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewCreateRigRequestWithBody(server string, cityName string, params *CreateRigParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -12675,6 +13987,19 @@ func NewCreateRigRequestWithBody(server string, cityName string, contentType str req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -12720,7 +14045,7 @@ func NewGetV0CityByCityNameServiceByNameRequest(server string, cityName string, } // NewPostV0CityByCityNameServiceByNameRestartRequest generates requests for PostV0CityByCityNameServiceByNameRestart -func NewPostV0CityByCityNameServiceByNameRestartRequest(server string, cityName string, name string) (*http.Request, error) { +func NewPostV0CityByCityNameServiceByNameRestartRequest(server string, cityName string, name string, params *PostV0CityByCityNameServiceByNameRestartParams) (*http.Request, error) { var err error var pathParam0 string @@ -12757,6 +14082,19 @@ func NewPostV0CityByCityNameServiceByNameRestartRequest(server string, cityName return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -12858,18 +14196,18 @@ func NewGetV0CityByCityNameSessionByIdRequest(server string, cityName string, id } // NewPatchV0CityByCityNameSessionByIdRequest calls the generic PatchV0CityByCityNameSessionById builder with application/json body -func NewPatchV0CityByCityNameSessionByIdRequest(server string, cityName string, id string, body PatchV0CityByCityNameSessionByIdJSONRequestBody) (*http.Request, error) { +func NewPatchV0CityByCityNameSessionByIdRequest(server string, cityName string, id string, params *PatchV0CityByCityNameSessionByIdParams, body PatchV0CityByCityNameSessionByIdJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPatchV0CityByCityNameSessionByIdRequestWithBody(server, cityName, id, "application/json", bodyReader) + return NewPatchV0CityByCityNameSessionByIdRequestWithBody(server, cityName, id, params, "application/json", bodyReader) } // NewPatchV0CityByCityNameSessionByIdRequestWithBody generates requests for PatchV0CityByCityNameSessionById with any type of body -func NewPatchV0CityByCityNameSessionByIdRequestWithBody(server string, cityName string, id string, contentType string, body io.Reader) (*http.Request, error) { +func NewPatchV0CityByCityNameSessionByIdRequestWithBody(server string, cityName string, id string, params *PatchV0CityByCityNameSessionByIdParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -12908,6 +14246,19 @@ func NewPatchV0CityByCityNameSessionByIdRequestWithBody(server string, cityName req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -13060,11 +14411,24 @@ func NewPostV0CityByCityNameSessionByIdCloseRequest(server string, cityName stri return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewPostV0CityByCityNameSessionByIdKillRequest generates requests for PostV0CityByCityNameSessionByIdKill -func NewPostV0CityByCityNameSessionByIdKillRequest(server string, cityName string, id string) (*http.Request, error) { +func NewPostV0CityByCityNameSessionByIdKillRequest(server string, cityName string, id string, params *PostV0CityByCityNameSessionByIdKillParams) (*http.Request, error) { var err error var pathParam0 string @@ -13101,22 +14465,35 @@ func NewPostV0CityByCityNameSessionByIdKillRequest(server string, cityName strin return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewSendSessionMessageRequest calls the generic SendSessionMessage builder with application/json body -func NewSendSessionMessageRequest(server string, cityName string, id string, body SendSessionMessageJSONRequestBody) (*http.Request, error) { +func NewSendSessionMessageRequest(server string, cityName string, id string, params *SendSessionMessageParams, body SendSessionMessageJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewSendSessionMessageRequestWithBody(server, cityName, id, "application/json", bodyReader) + return NewSendSessionMessageRequestWithBody(server, cityName, id, params, "application/json", bodyReader) } // NewSendSessionMessageRequestWithBody generates requests for SendSessionMessage with any type of body -func NewSendSessionMessageRequestWithBody(server string, cityName string, id string, contentType string, body io.Reader) (*http.Request, error) { +func NewSendSessionMessageRequestWithBody(server string, cityName string, id string, params *SendSessionMessageParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -13155,6 +14532,19 @@ func NewSendSessionMessageRequestWithBody(server string, cityName string, id str req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -13200,18 +14590,18 @@ func NewGetV0CityByCityNameSessionByIdPendingRequest(server string, cityName str } // NewPostV0CityByCityNameSessionByIdRenameRequest calls the generic PostV0CityByCityNameSessionByIdRename builder with application/json body -func NewPostV0CityByCityNameSessionByIdRenameRequest(server string, cityName string, id string, body PostV0CityByCityNameSessionByIdRenameJSONRequestBody) (*http.Request, error) { +func NewPostV0CityByCityNameSessionByIdRenameRequest(server string, cityName string, id string, params *PostV0CityByCityNameSessionByIdRenameParams, body PostV0CityByCityNameSessionByIdRenameJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPostV0CityByCityNameSessionByIdRenameRequestWithBody(server, cityName, id, "application/json", bodyReader) + return NewPostV0CityByCityNameSessionByIdRenameRequestWithBody(server, cityName, id, params, "application/json", bodyReader) } // NewPostV0CityByCityNameSessionByIdRenameRequestWithBody generates requests for PostV0CityByCityNameSessionByIdRename with any type of body -func NewPostV0CityByCityNameSessionByIdRenameRequestWithBody(server string, cityName string, id string, contentType string, body io.Reader) (*http.Request, error) { +func NewPostV0CityByCityNameSessionByIdRenameRequestWithBody(server string, cityName string, id string, params *PostV0CityByCityNameSessionByIdRenameParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -13250,22 +14640,35 @@ func NewPostV0CityByCityNameSessionByIdRenameRequestWithBody(server string, city req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewRespondSessionRequest calls the generic RespondSession builder with application/json body -func NewRespondSessionRequest(server string, cityName string, id string, body RespondSessionJSONRequestBody) (*http.Request, error) { +func NewRespondSessionRequest(server string, cityName string, id string, params *RespondSessionParams, body RespondSessionJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewRespondSessionRequestWithBody(server, cityName, id, "application/json", bodyReader) + return NewRespondSessionRequestWithBody(server, cityName, id, params, "application/json", bodyReader) } // NewRespondSessionRequestWithBody generates requests for RespondSession with any type of body -func NewRespondSessionRequestWithBody(server string, cityName string, id string, contentType string, body io.Reader) (*http.Request, error) { +func NewRespondSessionRequestWithBody(server string, cityName string, id string, params *RespondSessionParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -13304,11 +14707,24 @@ func NewRespondSessionRequestWithBody(server string, cityName string, id string, req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewPostV0CityByCityNameSessionByIdStopRequest generates requests for PostV0CityByCityNameSessionByIdStop -func NewPostV0CityByCityNameSessionByIdStopRequest(server string, cityName string, id string) (*http.Request, error) { +func NewPostV0CityByCityNameSessionByIdStopRequest(server string, cityName string, id string, params *PostV0CityByCityNameSessionByIdStopParams) (*http.Request, error) { var err error var pathParam0 string @@ -13345,6 +14761,19 @@ func NewPostV0CityByCityNameSessionByIdStopRequest(server string, cityName strin return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -13412,18 +14841,18 @@ func NewStreamSessionRequest(server string, cityName string, id string, params * } // NewSubmitSessionRequest calls the generic SubmitSession builder with application/json body -func NewSubmitSessionRequest(server string, cityName string, id string, body SubmitSessionJSONRequestBody) (*http.Request, error) { +func NewSubmitSessionRequest(server string, cityName string, id string, params *SubmitSessionParams, body SubmitSessionJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewSubmitSessionRequestWithBody(server, cityName, id, "application/json", bodyReader) + return NewSubmitSessionRequestWithBody(server, cityName, id, params, "application/json", bodyReader) } // NewSubmitSessionRequestWithBody generates requests for SubmitSession with any type of body -func NewSubmitSessionRequestWithBody(server string, cityName string, id string, contentType string, body io.Reader) (*http.Request, error) { +func NewSubmitSessionRequestWithBody(server string, cityName string, id string, params *SubmitSessionParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -13462,11 +14891,24 @@ func NewSubmitSessionRequestWithBody(server string, cityName string, id string, req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewPostV0CityByCityNameSessionByIdSuspendRequest generates requests for PostV0CityByCityNameSessionByIdSuspend -func NewPostV0CityByCityNameSessionByIdSuspendRequest(server string, cityName string, id string) (*http.Request, error) { +func NewPostV0CityByCityNameSessionByIdSuspendRequest(server string, cityName string, id string, params *PostV0CityByCityNameSessionByIdSuspendParams) (*http.Request, error) { var err error var pathParam0 string @@ -13503,6 +14945,19 @@ func NewPostV0CityByCityNameSessionByIdSuspendRequest(server string, cityName st return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -13602,7 +15057,7 @@ func NewGetV0CityByCityNameSessionByIdTranscriptRequest(server string, cityName } // NewPostV0CityByCityNameSessionByIdWakeRequest generates requests for PostV0CityByCityNameSessionByIdWake -func NewPostV0CityByCityNameSessionByIdWakeRequest(server string, cityName string, id string) (*http.Request, error) { +func NewPostV0CityByCityNameSessionByIdWakeRequest(server string, cityName string, id string, params *PostV0CityByCityNameSessionByIdWakeParams) (*http.Request, error) { var err error var pathParam0 string @@ -13639,6 +15094,19 @@ func NewPostV0CityByCityNameSessionByIdWakeRequest(server string, cityName strin return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -13763,18 +15231,18 @@ func NewGetV0CityByCityNameSessionsRequest(server string, cityName string, param } // NewCreateSessionRequest calls the generic CreateSession builder with application/json body -func NewCreateSessionRequest(server string, cityName string, body CreateSessionJSONRequestBody) (*http.Request, error) { +func NewCreateSessionRequest(server string, cityName string, params *CreateSessionParams, body CreateSessionJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewCreateSessionRequestWithBody(server, cityName, "application/json", bodyReader) + return NewCreateSessionRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewCreateSessionRequestWithBody generates requests for CreateSession with any type of body -func NewCreateSessionRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewCreateSessionRequestWithBody(server string, cityName string, params *CreateSessionParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -13806,22 +15274,35 @@ func NewCreateSessionRequestWithBody(server string, cityName string, contentType req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } // NewPostV0CityByCityNameSlingRequest calls the generic PostV0CityByCityNameSling builder with application/json body -func NewPostV0CityByCityNameSlingRequest(server string, cityName string, body PostV0CityByCityNameSlingJSONRequestBody) (*http.Request, error) { +func NewPostV0CityByCityNameSlingRequest(server string, cityName string, params *PostV0CityByCityNameSlingParams, body PostV0CityByCityNameSlingJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPostV0CityByCityNameSlingRequestWithBody(server, cityName, "application/json", bodyReader) + return NewPostV0CityByCityNameSlingRequestWithBody(server, cityName, params, "application/json", bodyReader) } // NewPostV0CityByCityNameSlingRequestWithBody generates requests for PostV0CityByCityNameSling with any type of body -func NewPostV0CityByCityNameSlingRequestWithBody(server string, cityName string, contentType string, body io.Reader) (*http.Request, error) { +func NewPostV0CityByCityNameSlingRequestWithBody(server string, cityName string, params *PostV0CityByCityNameSlingParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -13853,6 +15334,19 @@ func NewPostV0CityByCityNameSlingRequestWithBody(server string, cityName string, req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -13928,6 +15422,53 @@ func NewGetV0CityByCityNameStatusRequest(server string, cityName string, params return req, nil } +// NewPostV0CityByCityNameUnregisterRequest generates requests for PostV0CityByCityNameUnregister +func NewPostV0CityByCityNameUnregisterRequest(server string, cityName string, params *PostV0CityByCityNameUnregisterParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "cityName", cityName, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v0/city/%s/unregister", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + + return req, nil +} + // NewDeleteV0CityByCityNameWorkflowByWorkflowIdRequest generates requests for DeleteV0CityByCityNameWorkflowByWorkflowId func NewDeleteV0CityByCityNameWorkflowByWorkflowIdRequest(server string, cityName string, workflowId string, params *DeleteV0CityByCityNameWorkflowByWorkflowIdParams) (*http.Request, error) { var err error @@ -14020,6 +15561,19 @@ func NewDeleteV0CityByCityNameWorkflowByWorkflowIdRequest(server string, cityNam return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-GC-Request", params.XGCRequest, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-GC-Request", headerParam0) + + } + return req, nil } @@ -14443,28 +15997,28 @@ type ClientWithResponsesInterface interface { GetV0CitiesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetV0CitiesResponse, error) // PostV0CityWithBodyWithResponse request with any body - PostV0CityWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityResponse, error) + PostV0CityWithBodyWithResponse(ctx context.Context, params *PostV0CityParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityResponse, error) - PostV0CityWithResponse(ctx context.Context, body PostV0CityJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityResponse, error) + PostV0CityWithResponse(ctx context.Context, params *PostV0CityParams, body PostV0CityJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityResponse, error) // GetV0CityByCityNameWithResponse request GetV0CityByCityNameWithResponse(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameResponse, error) // PatchV0CityByCityNameWithBodyWithResponse request with any body - PatchV0CityByCityNameWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameResponse, error) + PatchV0CityByCityNameWithBodyWithResponse(ctx context.Context, cityName string, params *PatchV0CityByCityNameParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameResponse, error) - PatchV0CityByCityNameWithResponse(ctx context.Context, cityName string, body PatchV0CityByCityNameJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameResponse, error) + PatchV0CityByCityNameWithResponse(ctx context.Context, cityName string, params *PatchV0CityByCityNameParams, body PatchV0CityByCityNameJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameResponse, error) // DeleteV0CityByCityNameAgentByBaseWithResponse request - DeleteV0CityByCityNameAgentByBaseWithResponse(ctx context.Context, cityName string, base string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameAgentByBaseResponse, error) + DeleteV0CityByCityNameAgentByBaseWithResponse(ctx context.Context, cityName string, base string, params *DeleteV0CityByCityNameAgentByBaseParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameAgentByBaseResponse, error) // GetV0CityByCityNameAgentByBaseWithResponse request GetV0CityByCityNameAgentByBaseWithResponse(ctx context.Context, cityName string, base string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameAgentByBaseResponse, error) // PatchV0CityByCityNameAgentByBaseWithBodyWithResponse request with any body - PatchV0CityByCityNameAgentByBaseWithBodyWithResponse(ctx context.Context, cityName string, base string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameAgentByBaseResponse, error) + PatchV0CityByCityNameAgentByBaseWithBodyWithResponse(ctx context.Context, cityName string, base string, params *PatchV0CityByCityNameAgentByBaseParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameAgentByBaseResponse, error) - PatchV0CityByCityNameAgentByBaseWithResponse(ctx context.Context, cityName string, base string, body PatchV0CityByCityNameAgentByBaseJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameAgentByBaseResponse, error) + PatchV0CityByCityNameAgentByBaseWithResponse(ctx context.Context, cityName string, base string, params *PatchV0CityByCityNameAgentByBaseParams, body PatchV0CityByCityNameAgentByBaseJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameAgentByBaseResponse, error) // GetV0CityByCityNameAgentByBaseOutputWithResponse request GetV0CityByCityNameAgentByBaseOutputWithResponse(ctx context.Context, cityName string, base string, params *GetV0CityByCityNameAgentByBaseOutputParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameAgentByBaseOutputResponse, error) @@ -14473,18 +16027,18 @@ type ClientWithResponsesInterface interface { StreamAgentOutputWithResponse(ctx context.Context, cityName string, base string, reqEditors ...RequestEditorFn) (*StreamAgentOutputResponse, error) // PostV0CityByCityNameAgentByBaseByActionWithResponse request - PostV0CityByCityNameAgentByBaseByActionWithResponse(ctx context.Context, cityName string, base string, action PostV0CityByCityNameAgentByBaseByActionParamsAction, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameAgentByBaseByActionResponse, error) + PostV0CityByCityNameAgentByBaseByActionWithResponse(ctx context.Context, cityName string, base string, action PostV0CityByCityNameAgentByBaseByActionParamsAction, params *PostV0CityByCityNameAgentByBaseByActionParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameAgentByBaseByActionResponse, error) // DeleteV0CityByCityNameAgentByDirByBaseWithResponse request - DeleteV0CityByCityNameAgentByDirByBaseWithResponse(ctx context.Context, cityName string, dir string, base string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameAgentByDirByBaseResponse, error) + DeleteV0CityByCityNameAgentByDirByBaseWithResponse(ctx context.Context, cityName string, dir string, base string, params *DeleteV0CityByCityNameAgentByDirByBaseParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameAgentByDirByBaseResponse, error) // GetV0CityByCityNameAgentByDirByBaseWithResponse request GetV0CityByCityNameAgentByDirByBaseWithResponse(ctx context.Context, cityName string, dir string, base string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameAgentByDirByBaseResponse, error) // PatchV0CityByCityNameAgentByDirByBaseWithBodyWithResponse request with any body - PatchV0CityByCityNameAgentByDirByBaseWithBodyWithResponse(ctx context.Context, cityName string, dir string, base string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameAgentByDirByBaseResponse, error) + PatchV0CityByCityNameAgentByDirByBaseWithBodyWithResponse(ctx context.Context, cityName string, dir string, base string, params *PatchV0CityByCityNameAgentByDirByBaseParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameAgentByDirByBaseResponse, error) - PatchV0CityByCityNameAgentByDirByBaseWithResponse(ctx context.Context, cityName string, dir string, base string, body PatchV0CityByCityNameAgentByDirByBaseJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameAgentByDirByBaseResponse, error) + PatchV0CityByCityNameAgentByDirByBaseWithResponse(ctx context.Context, cityName string, dir string, base string, params *PatchV0CityByCityNameAgentByDirByBaseParams, body PatchV0CityByCityNameAgentByDirByBaseJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameAgentByDirByBaseResponse, error) // GetV0CityByCityNameAgentByDirByBaseOutputWithResponse request GetV0CityByCityNameAgentByDirByBaseOutputWithResponse(ctx context.Context, cityName string, dir string, base string, params *GetV0CityByCityNameAgentByDirByBaseOutputParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameAgentByDirByBaseOutputResponse, error) @@ -14493,45 +16047,45 @@ type ClientWithResponsesInterface interface { StreamAgentOutputQualifiedWithResponse(ctx context.Context, cityName string, dir string, base string, reqEditors ...RequestEditorFn) (*StreamAgentOutputQualifiedResponse, error) // PostV0CityByCityNameAgentByDirByBaseByActionWithResponse request - PostV0CityByCityNameAgentByDirByBaseByActionWithResponse(ctx context.Context, cityName string, dir string, base string, action PostV0CityByCityNameAgentByDirByBaseByActionParamsAction, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameAgentByDirByBaseByActionResponse, error) + PostV0CityByCityNameAgentByDirByBaseByActionWithResponse(ctx context.Context, cityName string, dir string, base string, action PostV0CityByCityNameAgentByDirByBaseByActionParamsAction, params *PostV0CityByCityNameAgentByDirByBaseByActionParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameAgentByDirByBaseByActionResponse, error) // GetV0CityByCityNameAgentsWithResponse request GetV0CityByCityNameAgentsWithResponse(ctx context.Context, cityName string, params *GetV0CityByCityNameAgentsParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameAgentsResponse, error) // CreateAgentWithBodyWithResponse request with any body - CreateAgentWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateAgentResponse, error) + CreateAgentWithBodyWithResponse(ctx context.Context, cityName string, params *CreateAgentParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateAgentResponse, error) - CreateAgentWithResponse(ctx context.Context, cityName string, body CreateAgentJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateAgentResponse, error) + CreateAgentWithResponse(ctx context.Context, cityName string, params *CreateAgentParams, body CreateAgentJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateAgentResponse, error) // DeleteV0CityByCityNameBeadByIdWithResponse request - DeleteV0CityByCityNameBeadByIdWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameBeadByIdResponse, error) + DeleteV0CityByCityNameBeadByIdWithResponse(ctx context.Context, cityName string, id string, params *DeleteV0CityByCityNameBeadByIdParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameBeadByIdResponse, error) // GetV0CityByCityNameBeadByIdWithResponse request GetV0CityByCityNameBeadByIdWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameBeadByIdResponse, error) // PatchV0CityByCityNameBeadByIdWithBodyWithResponse request with any body - PatchV0CityByCityNameBeadByIdWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameBeadByIdResponse, error) + PatchV0CityByCityNameBeadByIdWithBodyWithResponse(ctx context.Context, cityName string, id string, params *PatchV0CityByCityNameBeadByIdParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameBeadByIdResponse, error) - PatchV0CityByCityNameBeadByIdWithResponse(ctx context.Context, cityName string, id string, body PatchV0CityByCityNameBeadByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameBeadByIdResponse, error) + PatchV0CityByCityNameBeadByIdWithResponse(ctx context.Context, cityName string, id string, params *PatchV0CityByCityNameBeadByIdParams, body PatchV0CityByCityNameBeadByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameBeadByIdResponse, error) // PostV0CityByCityNameBeadByIdAssignWithBodyWithResponse request with any body - PostV0CityByCityNameBeadByIdAssignWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdAssignResponse, error) + PostV0CityByCityNameBeadByIdAssignWithBodyWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdAssignParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdAssignResponse, error) - PostV0CityByCityNameBeadByIdAssignWithResponse(ctx context.Context, cityName string, id string, body PostV0CityByCityNameBeadByIdAssignJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdAssignResponse, error) + PostV0CityByCityNameBeadByIdAssignWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdAssignParams, body PostV0CityByCityNameBeadByIdAssignJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdAssignResponse, error) // PostV0CityByCityNameBeadByIdCloseWithResponse request - PostV0CityByCityNameBeadByIdCloseWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdCloseResponse, error) + PostV0CityByCityNameBeadByIdCloseWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdCloseParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdCloseResponse, error) // GetV0CityByCityNameBeadByIdDepsWithResponse request GetV0CityByCityNameBeadByIdDepsWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameBeadByIdDepsResponse, error) // PostV0CityByCityNameBeadByIdReopenWithResponse request - PostV0CityByCityNameBeadByIdReopenWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdReopenResponse, error) + PostV0CityByCityNameBeadByIdReopenWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdReopenParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdReopenResponse, error) // PostV0CityByCityNameBeadByIdUpdateWithBodyWithResponse request with any body - PostV0CityByCityNameBeadByIdUpdateWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdUpdateResponse, error) + PostV0CityByCityNameBeadByIdUpdateWithBodyWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdUpdateParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdUpdateResponse, error) - PostV0CityByCityNameBeadByIdUpdateWithResponse(ctx context.Context, cityName string, id string, body PostV0CityByCityNameBeadByIdUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdUpdateResponse, error) + PostV0CityByCityNameBeadByIdUpdateWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdUpdateParams, body PostV0CityByCityNameBeadByIdUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdUpdateResponse, error) // GetV0CityByCityNameBeadsWithResponse request GetV0CityByCityNameBeadsWithResponse(ctx context.Context, cityName string, params *GetV0CityByCityNameBeadsParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameBeadsResponse, error) @@ -14557,63 +16111,63 @@ type ClientWithResponsesInterface interface { GetV0CityByCityNameConfigValidateWithResponse(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameConfigValidateResponse, error) // DeleteV0CityByCityNameConvoyByIdWithResponse request - DeleteV0CityByCityNameConvoyByIdWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameConvoyByIdResponse, error) + DeleteV0CityByCityNameConvoyByIdWithResponse(ctx context.Context, cityName string, id string, params *DeleteV0CityByCityNameConvoyByIdParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameConvoyByIdResponse, error) // GetV0CityByCityNameConvoyByIdWithResponse request GetV0CityByCityNameConvoyByIdWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameConvoyByIdResponse, error) // PostV0CityByCityNameConvoyByIdAddWithBodyWithResponse request with any body - PostV0CityByCityNameConvoyByIdAddWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdAddResponse, error) + PostV0CityByCityNameConvoyByIdAddWithBodyWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdAddParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdAddResponse, error) - PostV0CityByCityNameConvoyByIdAddWithResponse(ctx context.Context, cityName string, id string, body PostV0CityByCityNameConvoyByIdAddJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdAddResponse, error) + PostV0CityByCityNameConvoyByIdAddWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdAddParams, body PostV0CityByCityNameConvoyByIdAddJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdAddResponse, error) // GetV0CityByCityNameConvoyByIdCheckWithResponse request GetV0CityByCityNameConvoyByIdCheckWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameConvoyByIdCheckResponse, error) // PostV0CityByCityNameConvoyByIdCloseWithResponse request - PostV0CityByCityNameConvoyByIdCloseWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdCloseResponse, error) + PostV0CityByCityNameConvoyByIdCloseWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdCloseParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdCloseResponse, error) // PostV0CityByCityNameConvoyByIdRemoveWithBodyWithResponse request with any body - PostV0CityByCityNameConvoyByIdRemoveWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdRemoveResponse, error) + PostV0CityByCityNameConvoyByIdRemoveWithBodyWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdRemoveParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdRemoveResponse, error) - PostV0CityByCityNameConvoyByIdRemoveWithResponse(ctx context.Context, cityName string, id string, body PostV0CityByCityNameConvoyByIdRemoveJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdRemoveResponse, error) + PostV0CityByCityNameConvoyByIdRemoveWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdRemoveParams, body PostV0CityByCityNameConvoyByIdRemoveJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdRemoveResponse, error) // GetV0CityByCityNameConvoysWithResponse request GetV0CityByCityNameConvoysWithResponse(ctx context.Context, cityName string, params *GetV0CityByCityNameConvoysParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameConvoysResponse, error) // CreateConvoyWithBodyWithResponse request with any body - CreateConvoyWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateConvoyResponse, error) + CreateConvoyWithBodyWithResponse(ctx context.Context, cityName string, params *CreateConvoyParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateConvoyResponse, error) - CreateConvoyWithResponse(ctx context.Context, cityName string, body CreateConvoyJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateConvoyResponse, error) + CreateConvoyWithResponse(ctx context.Context, cityName string, params *CreateConvoyParams, body CreateConvoyJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateConvoyResponse, error) // GetV0CityByCityNameEventsWithResponse request GetV0CityByCityNameEventsWithResponse(ctx context.Context, cityName string, params *GetV0CityByCityNameEventsParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameEventsResponse, error) // EmitEventWithBodyWithResponse request with any body - EmitEventWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EmitEventResponse, error) + EmitEventWithBodyWithResponse(ctx context.Context, cityName string, params *EmitEventParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EmitEventResponse, error) - EmitEventWithResponse(ctx context.Context, cityName string, body EmitEventJSONRequestBody, reqEditors ...RequestEditorFn) (*EmitEventResponse, error) + EmitEventWithResponse(ctx context.Context, cityName string, params *EmitEventParams, body EmitEventJSONRequestBody, reqEditors ...RequestEditorFn) (*EmitEventResponse, error) // StreamEventsWithResponse request StreamEventsWithResponse(ctx context.Context, cityName string, params *StreamEventsParams, reqEditors ...RequestEditorFn) (*StreamEventsResponse, error) // DeleteV0CityByCityNameExtmsgAdaptersWithBodyWithResponse request with any body - DeleteV0CityByCityNameExtmsgAdaptersWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameExtmsgAdaptersResponse, error) + DeleteV0CityByCityNameExtmsgAdaptersWithBodyWithResponse(ctx context.Context, cityName string, params *DeleteV0CityByCityNameExtmsgAdaptersParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameExtmsgAdaptersResponse, error) - DeleteV0CityByCityNameExtmsgAdaptersWithResponse(ctx context.Context, cityName string, body DeleteV0CityByCityNameExtmsgAdaptersJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameExtmsgAdaptersResponse, error) + DeleteV0CityByCityNameExtmsgAdaptersWithResponse(ctx context.Context, cityName string, params *DeleteV0CityByCityNameExtmsgAdaptersParams, body DeleteV0CityByCityNameExtmsgAdaptersJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameExtmsgAdaptersResponse, error) // GetV0CityByCityNameExtmsgAdaptersWithResponse request GetV0CityByCityNameExtmsgAdaptersWithResponse(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameExtmsgAdaptersResponse, error) // RegisterExtmsgAdapterWithBodyWithResponse request with any body - RegisterExtmsgAdapterWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterExtmsgAdapterResponse, error) + RegisterExtmsgAdapterWithBodyWithResponse(ctx context.Context, cityName string, params *RegisterExtmsgAdapterParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterExtmsgAdapterResponse, error) - RegisterExtmsgAdapterWithResponse(ctx context.Context, cityName string, body RegisterExtmsgAdapterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterExtmsgAdapterResponse, error) + RegisterExtmsgAdapterWithResponse(ctx context.Context, cityName string, params *RegisterExtmsgAdapterParams, body RegisterExtmsgAdapterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterExtmsgAdapterResponse, error) // PostV0CityByCityNameExtmsgBindWithBodyWithResponse request with any body - PostV0CityByCityNameExtmsgBindWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgBindResponse, error) + PostV0CityByCityNameExtmsgBindWithBodyWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgBindParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgBindResponse, error) - PostV0CityByCityNameExtmsgBindWithResponse(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgBindJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgBindResponse, error) + PostV0CityByCityNameExtmsgBindWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgBindParams, body PostV0CityByCityNameExtmsgBindJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgBindResponse, error) // GetV0CityByCityNameExtmsgBindingsWithResponse request GetV0CityByCityNameExtmsgBindingsWithResponse(ctx context.Context, cityName string, params *GetV0CityByCityNameExtmsgBindingsParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameExtmsgBindingsResponse, error) @@ -14622,42 +16176,42 @@ type ClientWithResponsesInterface interface { GetV0CityByCityNameExtmsgGroupsWithResponse(ctx context.Context, cityName string, params *GetV0CityByCityNameExtmsgGroupsParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameExtmsgGroupsResponse, error) // EnsureExtmsgGroupWithBodyWithResponse request with any body - EnsureExtmsgGroupWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EnsureExtmsgGroupResponse, error) + EnsureExtmsgGroupWithBodyWithResponse(ctx context.Context, cityName string, params *EnsureExtmsgGroupParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EnsureExtmsgGroupResponse, error) - EnsureExtmsgGroupWithResponse(ctx context.Context, cityName string, body EnsureExtmsgGroupJSONRequestBody, reqEditors ...RequestEditorFn) (*EnsureExtmsgGroupResponse, error) + EnsureExtmsgGroupWithResponse(ctx context.Context, cityName string, params *EnsureExtmsgGroupParams, body EnsureExtmsgGroupJSONRequestBody, reqEditors ...RequestEditorFn) (*EnsureExtmsgGroupResponse, error) // PostV0CityByCityNameExtmsgInboundWithBodyWithResponse request with any body - PostV0CityByCityNameExtmsgInboundWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgInboundResponse, error) + PostV0CityByCityNameExtmsgInboundWithBodyWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgInboundParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgInboundResponse, error) - PostV0CityByCityNameExtmsgInboundWithResponse(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgInboundJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgInboundResponse, error) + PostV0CityByCityNameExtmsgInboundWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgInboundParams, body PostV0CityByCityNameExtmsgInboundJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgInboundResponse, error) // PostV0CityByCityNameExtmsgOutboundWithBodyWithResponse request with any body - PostV0CityByCityNameExtmsgOutboundWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgOutboundResponse, error) + PostV0CityByCityNameExtmsgOutboundWithBodyWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgOutboundParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgOutboundResponse, error) - PostV0CityByCityNameExtmsgOutboundWithResponse(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgOutboundJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgOutboundResponse, error) + PostV0CityByCityNameExtmsgOutboundWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgOutboundParams, body PostV0CityByCityNameExtmsgOutboundJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgOutboundResponse, error) // DeleteV0CityByCityNameExtmsgParticipantsWithBodyWithResponse request with any body - DeleteV0CityByCityNameExtmsgParticipantsWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameExtmsgParticipantsResponse, error) + DeleteV0CityByCityNameExtmsgParticipantsWithBodyWithResponse(ctx context.Context, cityName string, params *DeleteV0CityByCityNameExtmsgParticipantsParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameExtmsgParticipantsResponse, error) - DeleteV0CityByCityNameExtmsgParticipantsWithResponse(ctx context.Context, cityName string, body DeleteV0CityByCityNameExtmsgParticipantsJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameExtmsgParticipantsResponse, error) + DeleteV0CityByCityNameExtmsgParticipantsWithResponse(ctx context.Context, cityName string, params *DeleteV0CityByCityNameExtmsgParticipantsParams, body DeleteV0CityByCityNameExtmsgParticipantsJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameExtmsgParticipantsResponse, error) // PostV0CityByCityNameExtmsgParticipantsWithBodyWithResponse request with any body - PostV0CityByCityNameExtmsgParticipantsWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgParticipantsResponse, error) + PostV0CityByCityNameExtmsgParticipantsWithBodyWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgParticipantsParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgParticipantsResponse, error) - PostV0CityByCityNameExtmsgParticipantsWithResponse(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgParticipantsJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgParticipantsResponse, error) + PostV0CityByCityNameExtmsgParticipantsWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgParticipantsParams, body PostV0CityByCityNameExtmsgParticipantsJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgParticipantsResponse, error) // GetV0CityByCityNameExtmsgTranscriptWithResponse request GetV0CityByCityNameExtmsgTranscriptWithResponse(ctx context.Context, cityName string, params *GetV0CityByCityNameExtmsgTranscriptParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameExtmsgTranscriptResponse, error) // PostV0CityByCityNameExtmsgTranscriptAckWithBodyWithResponse request with any body - PostV0CityByCityNameExtmsgTranscriptAckWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgTranscriptAckResponse, error) + PostV0CityByCityNameExtmsgTranscriptAckWithBodyWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgTranscriptAckParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgTranscriptAckResponse, error) - PostV0CityByCityNameExtmsgTranscriptAckWithResponse(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgTranscriptAckJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgTranscriptAckResponse, error) + PostV0CityByCityNameExtmsgTranscriptAckWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgTranscriptAckParams, body PostV0CityByCityNameExtmsgTranscriptAckJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgTranscriptAckResponse, error) // PostV0CityByCityNameExtmsgUnbindWithBodyWithResponse request with any body - PostV0CityByCityNameExtmsgUnbindWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgUnbindResponse, error) + PostV0CityByCityNameExtmsgUnbindWithBodyWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgUnbindParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgUnbindResponse, error) - PostV0CityByCityNameExtmsgUnbindWithResponse(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgUnbindJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgUnbindResponse, error) + PostV0CityByCityNameExtmsgUnbindWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgUnbindParams, body PostV0CityByCityNameExtmsgUnbindJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgUnbindResponse, error) // GetV0CityByCityNameFormulaByNameWithResponse request GetV0CityByCityNameFormulaByNameWithResponse(ctx context.Context, cityName string, name string, params *GetV0CityByCityNameFormulaByNameParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameFormulaByNameResponse, error) @@ -14672,9 +16226,9 @@ type ClientWithResponsesInterface interface { GetV0CityByCityNameFormulasByNameWithResponse(ctx context.Context, cityName string, name string, params *GetV0CityByCityNameFormulasByNameParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameFormulasByNameResponse, error) // PostV0CityByCityNameFormulasByNamePreviewWithBodyWithResponse request with any body - PostV0CityByCityNameFormulasByNamePreviewWithBodyWithResponse(ctx context.Context, cityName string, name string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameFormulasByNamePreviewResponse, error) + PostV0CityByCityNameFormulasByNamePreviewWithBodyWithResponse(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameFormulasByNamePreviewParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameFormulasByNamePreviewResponse, error) - PostV0CityByCityNameFormulasByNamePreviewWithResponse(ctx context.Context, cityName string, name string, body PostV0CityByCityNameFormulasByNamePreviewJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameFormulasByNamePreviewResponse, error) + PostV0CityByCityNameFormulasByNamePreviewWithResponse(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameFormulasByNamePreviewParams, body PostV0CityByCityNameFormulasByNamePreviewJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameFormulasByNamePreviewResponse, error) // GetV0CityByCityNameFormulasByNameRunsWithResponse request GetV0CityByCityNameFormulasByNameRunsWithResponse(ctx context.Context, cityName string, name string, params *GetV0CityByCityNameFormulasByNameRunsParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameFormulasByNameRunsResponse, error) @@ -14723,10 +16277,10 @@ type ClientWithResponsesInterface interface { GetV0CityByCityNameOrderByNameWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameOrderByNameResponse, error) // PostV0CityByCityNameOrderByNameDisableWithResponse request - PostV0CityByCityNameOrderByNameDisableWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameOrderByNameDisableResponse, error) + PostV0CityByCityNameOrderByNameDisableWithResponse(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameOrderByNameDisableParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameOrderByNameDisableResponse, error) // PostV0CityByCityNameOrderByNameEnableWithResponse request - PostV0CityByCityNameOrderByNameEnableWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameOrderByNameEnableResponse, error) + PostV0CityByCityNameOrderByNameEnableWithResponse(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameOrderByNameEnableParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameOrderByNameEnableResponse, error) // GetV0CityByCityNameOrdersWithResponse request GetV0CityByCityNameOrdersWithResponse(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameOrdersResponse, error) @@ -14744,13 +16298,13 @@ type ClientWithResponsesInterface interface { GetV0CityByCityNamePacksWithResponse(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNamePacksResponse, error) // DeleteV0CityByCityNamePatchesAgentByBaseWithResponse request - DeleteV0CityByCityNamePatchesAgentByBaseWithResponse(ctx context.Context, cityName string, base string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNamePatchesAgentByBaseResponse, error) + DeleteV0CityByCityNamePatchesAgentByBaseWithResponse(ctx context.Context, cityName string, base string, params *DeleteV0CityByCityNamePatchesAgentByBaseParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNamePatchesAgentByBaseResponse, error) // GetV0CityByCityNamePatchesAgentByBaseWithResponse request GetV0CityByCityNamePatchesAgentByBaseWithResponse(ctx context.Context, cityName string, base string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNamePatchesAgentByBaseResponse, error) // DeleteV0CityByCityNamePatchesAgentByDirByBaseWithResponse request - DeleteV0CityByCityNamePatchesAgentByDirByBaseWithResponse(ctx context.Context, cityName string, dir string, base string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNamePatchesAgentByDirByBaseResponse, error) + DeleteV0CityByCityNamePatchesAgentByDirByBaseWithResponse(ctx context.Context, cityName string, dir string, base string, params *DeleteV0CityByCityNamePatchesAgentByDirByBaseParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNamePatchesAgentByDirByBaseResponse, error) // GetV0CityByCityNamePatchesAgentByDirByBaseWithResponse request GetV0CityByCityNamePatchesAgentByDirByBaseWithResponse(ctx context.Context, cityName string, dir string, base string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNamePatchesAgentByDirByBaseResponse, error) @@ -14759,12 +16313,12 @@ type ClientWithResponsesInterface interface { GetV0CityByCityNamePatchesAgentsWithResponse(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNamePatchesAgentsResponse, error) // PutV0CityByCityNamePatchesAgentsWithBodyWithResponse request with any body - PutV0CityByCityNamePatchesAgentsWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesAgentsResponse, error) + PutV0CityByCityNamePatchesAgentsWithBodyWithResponse(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesAgentsParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesAgentsResponse, error) - PutV0CityByCityNamePatchesAgentsWithResponse(ctx context.Context, cityName string, body PutV0CityByCityNamePatchesAgentsJSONRequestBody, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesAgentsResponse, error) + PutV0CityByCityNamePatchesAgentsWithResponse(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesAgentsParams, body PutV0CityByCityNamePatchesAgentsJSONRequestBody, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesAgentsResponse, error) // DeleteV0CityByCityNamePatchesProviderByNameWithResponse request - DeleteV0CityByCityNamePatchesProviderByNameWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNamePatchesProviderByNameResponse, error) + DeleteV0CityByCityNamePatchesProviderByNameWithResponse(ctx context.Context, cityName string, name string, params *DeleteV0CityByCityNamePatchesProviderByNameParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNamePatchesProviderByNameResponse, error) // GetV0CityByCityNamePatchesProviderByNameWithResponse request GetV0CityByCityNamePatchesProviderByNameWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNamePatchesProviderByNameResponse, error) @@ -14773,12 +16327,12 @@ type ClientWithResponsesInterface interface { GetV0CityByCityNamePatchesProvidersWithResponse(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNamePatchesProvidersResponse, error) // PutV0CityByCityNamePatchesProvidersWithBodyWithResponse request with any body - PutV0CityByCityNamePatchesProvidersWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesProvidersResponse, error) + PutV0CityByCityNamePatchesProvidersWithBodyWithResponse(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesProvidersParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesProvidersResponse, error) - PutV0CityByCityNamePatchesProvidersWithResponse(ctx context.Context, cityName string, body PutV0CityByCityNamePatchesProvidersJSONRequestBody, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesProvidersResponse, error) + PutV0CityByCityNamePatchesProvidersWithResponse(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesProvidersParams, body PutV0CityByCityNamePatchesProvidersJSONRequestBody, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesProvidersResponse, error) // DeleteV0CityByCityNamePatchesRigByNameWithResponse request - DeleteV0CityByCityNamePatchesRigByNameWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNamePatchesRigByNameResponse, error) + DeleteV0CityByCityNamePatchesRigByNameWithResponse(ctx context.Context, cityName string, name string, params *DeleteV0CityByCityNamePatchesRigByNameParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNamePatchesRigByNameResponse, error) // GetV0CityByCityNamePatchesRigByNameWithResponse request GetV0CityByCityNamePatchesRigByNameWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNamePatchesRigByNameResponse, error) @@ -14787,31 +16341,31 @@ type ClientWithResponsesInterface interface { GetV0CityByCityNamePatchesRigsWithResponse(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNamePatchesRigsResponse, error) // PutV0CityByCityNamePatchesRigsWithBodyWithResponse request with any body - PutV0CityByCityNamePatchesRigsWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesRigsResponse, error) + PutV0CityByCityNamePatchesRigsWithBodyWithResponse(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesRigsParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesRigsResponse, error) - PutV0CityByCityNamePatchesRigsWithResponse(ctx context.Context, cityName string, body PutV0CityByCityNamePatchesRigsJSONRequestBody, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesRigsResponse, error) + PutV0CityByCityNamePatchesRigsWithResponse(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesRigsParams, body PutV0CityByCityNamePatchesRigsJSONRequestBody, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesRigsResponse, error) // GetV0CityByCityNameProviderReadinessWithResponse request GetV0CityByCityNameProviderReadinessWithResponse(ctx context.Context, cityName string, params *GetV0CityByCityNameProviderReadinessParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameProviderReadinessResponse, error) // DeleteV0CityByCityNameProviderByNameWithResponse request - DeleteV0CityByCityNameProviderByNameWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameProviderByNameResponse, error) + DeleteV0CityByCityNameProviderByNameWithResponse(ctx context.Context, cityName string, name string, params *DeleteV0CityByCityNameProviderByNameParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameProviderByNameResponse, error) // GetV0CityByCityNameProviderByNameWithResponse request GetV0CityByCityNameProviderByNameWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameProviderByNameResponse, error) // PatchV0CityByCityNameProviderByNameWithBodyWithResponse request with any body - PatchV0CityByCityNameProviderByNameWithBodyWithResponse(ctx context.Context, cityName string, name string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameProviderByNameResponse, error) + PatchV0CityByCityNameProviderByNameWithBodyWithResponse(ctx context.Context, cityName string, name string, params *PatchV0CityByCityNameProviderByNameParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameProviderByNameResponse, error) - PatchV0CityByCityNameProviderByNameWithResponse(ctx context.Context, cityName string, name string, body PatchV0CityByCityNameProviderByNameJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameProviderByNameResponse, error) + PatchV0CityByCityNameProviderByNameWithResponse(ctx context.Context, cityName string, name string, params *PatchV0CityByCityNameProviderByNameParams, body PatchV0CityByCityNameProviderByNameJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameProviderByNameResponse, error) // GetV0CityByCityNameProvidersWithResponse request GetV0CityByCityNameProvidersWithResponse(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameProvidersResponse, error) // CreateProviderWithBodyWithResponse request with any body - CreateProviderWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateProviderResponse, error) + CreateProviderWithBodyWithResponse(ctx context.Context, cityName string, params *CreateProviderParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateProviderResponse, error) - CreateProviderWithResponse(ctx context.Context, cityName string, body CreateProviderJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateProviderResponse, error) + CreateProviderWithResponse(ctx context.Context, cityName string, params *CreateProviderParams, body CreateProviderJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateProviderResponse, error) // GetV0CityByCityNameProvidersPublicWithResponse request GetV0CityByCityNameProvidersPublicWithResponse(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameProvidersPublicResponse, error) @@ -14820,32 +16374,32 @@ type ClientWithResponsesInterface interface { GetV0CityByCityNameReadinessWithResponse(ctx context.Context, cityName string, params *GetV0CityByCityNameReadinessParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameReadinessResponse, error) // DeleteV0CityByCityNameRigByNameWithResponse request - DeleteV0CityByCityNameRigByNameWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameRigByNameResponse, error) + DeleteV0CityByCityNameRigByNameWithResponse(ctx context.Context, cityName string, name string, params *DeleteV0CityByCityNameRigByNameParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameRigByNameResponse, error) // GetV0CityByCityNameRigByNameWithResponse request GetV0CityByCityNameRigByNameWithResponse(ctx context.Context, cityName string, name string, params *GetV0CityByCityNameRigByNameParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameRigByNameResponse, error) // PatchV0CityByCityNameRigByNameWithBodyWithResponse request with any body - PatchV0CityByCityNameRigByNameWithBodyWithResponse(ctx context.Context, cityName string, name string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameRigByNameResponse, error) + PatchV0CityByCityNameRigByNameWithBodyWithResponse(ctx context.Context, cityName string, name string, params *PatchV0CityByCityNameRigByNameParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameRigByNameResponse, error) - PatchV0CityByCityNameRigByNameWithResponse(ctx context.Context, cityName string, name string, body PatchV0CityByCityNameRigByNameJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameRigByNameResponse, error) + PatchV0CityByCityNameRigByNameWithResponse(ctx context.Context, cityName string, name string, params *PatchV0CityByCityNameRigByNameParams, body PatchV0CityByCityNameRigByNameJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameRigByNameResponse, error) // PostV0CityByCityNameRigByNameByActionWithResponse request - PostV0CityByCityNameRigByNameByActionWithResponse(ctx context.Context, cityName string, name string, action string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameRigByNameByActionResponse, error) + PostV0CityByCityNameRigByNameByActionWithResponse(ctx context.Context, cityName string, name string, action string, params *PostV0CityByCityNameRigByNameByActionParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameRigByNameByActionResponse, error) // GetV0CityByCityNameRigsWithResponse request GetV0CityByCityNameRigsWithResponse(ctx context.Context, cityName string, params *GetV0CityByCityNameRigsParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameRigsResponse, error) // CreateRigWithBodyWithResponse request with any body - CreateRigWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateRigResponse, error) + CreateRigWithBodyWithResponse(ctx context.Context, cityName string, params *CreateRigParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateRigResponse, error) - CreateRigWithResponse(ctx context.Context, cityName string, body CreateRigJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateRigResponse, error) + CreateRigWithResponse(ctx context.Context, cityName string, params *CreateRigParams, body CreateRigJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateRigResponse, error) // GetV0CityByCityNameServiceByNameWithResponse request GetV0CityByCityNameServiceByNameWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameServiceByNameResponse, error) // PostV0CityByCityNameServiceByNameRestartWithResponse request - PostV0CityByCityNameServiceByNameRestartWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameServiceByNameRestartResponse, error) + PostV0CityByCityNameServiceByNameRestartWithResponse(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameServiceByNameRestartParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameServiceByNameRestartResponse, error) // GetV0CityByCityNameServicesWithResponse request GetV0CityByCityNameServicesWithResponse(ctx context.Context, cityName string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameServicesResponse, error) @@ -14854,9 +16408,9 @@ type ClientWithResponsesInterface interface { GetV0CityByCityNameSessionByIdWithResponse(ctx context.Context, cityName string, id string, params *GetV0CityByCityNameSessionByIdParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameSessionByIdResponse, error) // PatchV0CityByCityNameSessionByIdWithBodyWithResponse request with any body - PatchV0CityByCityNameSessionByIdWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameSessionByIdResponse, error) + PatchV0CityByCityNameSessionByIdWithBodyWithResponse(ctx context.Context, cityName string, id string, params *PatchV0CityByCityNameSessionByIdParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameSessionByIdResponse, error) - PatchV0CityByCityNameSessionByIdWithResponse(ctx context.Context, cityName string, id string, body PatchV0CityByCityNameSessionByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameSessionByIdResponse, error) + PatchV0CityByCityNameSessionByIdWithResponse(ctx context.Context, cityName string, id string, params *PatchV0CityByCityNameSessionByIdParams, body PatchV0CityByCityNameSessionByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameSessionByIdResponse, error) // GetV0CityByCityNameSessionByIdAgentsWithResponse request GetV0CityByCityNameSessionByIdAgentsWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameSessionByIdAgentsResponse, error) @@ -14868,62 +16422,65 @@ type ClientWithResponsesInterface interface { PostV0CityByCityNameSessionByIdCloseWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdCloseParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdCloseResponse, error) // PostV0CityByCityNameSessionByIdKillWithResponse request - PostV0CityByCityNameSessionByIdKillWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdKillResponse, error) + PostV0CityByCityNameSessionByIdKillWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdKillParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdKillResponse, error) // SendSessionMessageWithBodyWithResponse request with any body - SendSessionMessageWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SendSessionMessageResponse, error) + SendSessionMessageWithBodyWithResponse(ctx context.Context, cityName string, id string, params *SendSessionMessageParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SendSessionMessageResponse, error) - SendSessionMessageWithResponse(ctx context.Context, cityName string, id string, body SendSessionMessageJSONRequestBody, reqEditors ...RequestEditorFn) (*SendSessionMessageResponse, error) + SendSessionMessageWithResponse(ctx context.Context, cityName string, id string, params *SendSessionMessageParams, body SendSessionMessageJSONRequestBody, reqEditors ...RequestEditorFn) (*SendSessionMessageResponse, error) // GetV0CityByCityNameSessionByIdPendingWithResponse request GetV0CityByCityNameSessionByIdPendingWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameSessionByIdPendingResponse, error) // PostV0CityByCityNameSessionByIdRenameWithBodyWithResponse request with any body - PostV0CityByCityNameSessionByIdRenameWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdRenameResponse, error) + PostV0CityByCityNameSessionByIdRenameWithBodyWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdRenameParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdRenameResponse, error) - PostV0CityByCityNameSessionByIdRenameWithResponse(ctx context.Context, cityName string, id string, body PostV0CityByCityNameSessionByIdRenameJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdRenameResponse, error) + PostV0CityByCityNameSessionByIdRenameWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdRenameParams, body PostV0CityByCityNameSessionByIdRenameJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdRenameResponse, error) // RespondSessionWithBodyWithResponse request with any body - RespondSessionWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RespondSessionResponse, error) + RespondSessionWithBodyWithResponse(ctx context.Context, cityName string, id string, params *RespondSessionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RespondSessionResponse, error) - RespondSessionWithResponse(ctx context.Context, cityName string, id string, body RespondSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*RespondSessionResponse, error) + RespondSessionWithResponse(ctx context.Context, cityName string, id string, params *RespondSessionParams, body RespondSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*RespondSessionResponse, error) // PostV0CityByCityNameSessionByIdStopWithResponse request - PostV0CityByCityNameSessionByIdStopWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdStopResponse, error) + PostV0CityByCityNameSessionByIdStopWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdStopParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdStopResponse, error) // StreamSessionWithResponse request StreamSessionWithResponse(ctx context.Context, cityName string, id string, params *StreamSessionParams, reqEditors ...RequestEditorFn) (*StreamSessionResponse, error) // SubmitSessionWithBodyWithResponse request with any body - SubmitSessionWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SubmitSessionResponse, error) + SubmitSessionWithBodyWithResponse(ctx context.Context, cityName string, id string, params *SubmitSessionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SubmitSessionResponse, error) - SubmitSessionWithResponse(ctx context.Context, cityName string, id string, body SubmitSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*SubmitSessionResponse, error) + SubmitSessionWithResponse(ctx context.Context, cityName string, id string, params *SubmitSessionParams, body SubmitSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*SubmitSessionResponse, error) // PostV0CityByCityNameSessionByIdSuspendWithResponse request - PostV0CityByCityNameSessionByIdSuspendWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdSuspendResponse, error) + PostV0CityByCityNameSessionByIdSuspendWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdSuspendParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdSuspendResponse, error) // GetV0CityByCityNameSessionByIdTranscriptWithResponse request GetV0CityByCityNameSessionByIdTranscriptWithResponse(ctx context.Context, cityName string, id string, params *GetV0CityByCityNameSessionByIdTranscriptParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameSessionByIdTranscriptResponse, error) // PostV0CityByCityNameSessionByIdWakeWithResponse request - PostV0CityByCityNameSessionByIdWakeWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdWakeResponse, error) + PostV0CityByCityNameSessionByIdWakeWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdWakeParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdWakeResponse, error) // GetV0CityByCityNameSessionsWithResponse request GetV0CityByCityNameSessionsWithResponse(ctx context.Context, cityName string, params *GetV0CityByCityNameSessionsParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameSessionsResponse, error) // CreateSessionWithBodyWithResponse request with any body - CreateSessionWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateSessionResponse, error) + CreateSessionWithBodyWithResponse(ctx context.Context, cityName string, params *CreateSessionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateSessionResponse, error) - CreateSessionWithResponse(ctx context.Context, cityName string, body CreateSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateSessionResponse, error) + CreateSessionWithResponse(ctx context.Context, cityName string, params *CreateSessionParams, body CreateSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateSessionResponse, error) // PostV0CityByCityNameSlingWithBodyWithResponse request with any body - PostV0CityByCityNameSlingWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSlingResponse, error) + PostV0CityByCityNameSlingWithBodyWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameSlingParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSlingResponse, error) - PostV0CityByCityNameSlingWithResponse(ctx context.Context, cityName string, body PostV0CityByCityNameSlingJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSlingResponse, error) + PostV0CityByCityNameSlingWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameSlingParams, body PostV0CityByCityNameSlingJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSlingResponse, error) // GetV0CityByCityNameStatusWithResponse request GetV0CityByCityNameStatusWithResponse(ctx context.Context, cityName string, params *GetV0CityByCityNameStatusParams, reqEditors ...RequestEditorFn) (*GetV0CityByCityNameStatusResponse, error) + // PostV0CityByCityNameUnregisterWithResponse request + PostV0CityByCityNameUnregisterWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameUnregisterParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameUnregisterResponse, error) + // DeleteV0CityByCityNameWorkflowByWorkflowIdWithResponse request DeleteV0CityByCityNameWorkflowByWorkflowIdWithResponse(ctx context.Context, cityName string, workflowId string, params *DeleteV0CityByCityNameWorkflowByWorkflowIdParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameWorkflowByWorkflowIdResponse, error) @@ -14992,7 +16549,7 @@ func (r GetV0CitiesResponse) StatusCode() int { type PostV0CityResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *CityCreateResponse + JSON202 *CityCreateResponse ApplicationproblemJSONDefault *ErrorModel } @@ -18067,6 +19624,29 @@ func (r GetV0CityByCityNameStatusResponse) StatusCode() int { return 0 } +type PostV0CityByCityNameUnregisterResponse struct { + Body []byte + HTTPResponse *http.Response + JSON202 *CityUnregisterResponse + ApplicationproblemJSONDefault *ErrorModel +} + +// Status returns HTTPResponse.Status +func (r PostV0CityByCityNameUnregisterResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PostV0CityByCityNameUnregisterResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type DeleteV0CityByCityNameWorkflowByWorkflowIdResponse struct { Body []byte HTTPResponse *http.Response @@ -18223,16 +19803,16 @@ func (c *ClientWithResponses) GetV0CitiesWithResponse(ctx context.Context, reqEd } // PostV0CityWithBodyWithResponse request with arbitrary body returning *PostV0CityResponse -func (c *ClientWithResponses) PostV0CityWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityResponse, error) { - rsp, err := c.PostV0CityWithBody(ctx, contentType, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityWithBodyWithResponse(ctx context.Context, params *PostV0CityParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityResponse, error) { + rsp, err := c.PostV0CityWithBody(ctx, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePostV0CityResponse(rsp) } -func (c *ClientWithResponses) PostV0CityWithResponse(ctx context.Context, body PostV0CityJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityResponse, error) { - rsp, err := c.PostV0City(ctx, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityWithResponse(ctx context.Context, params *PostV0CityParams, body PostV0CityJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityResponse, error) { + rsp, err := c.PostV0City(ctx, params, body, reqEditors...) if err != nil { return nil, err } @@ -18249,16 +19829,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameWithResponse(ctx context.Contex } // PatchV0CityByCityNameWithBodyWithResponse request with arbitrary body returning *PatchV0CityByCityNameResponse -func (c *ClientWithResponses) PatchV0CityByCityNameWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameResponse, error) { - rsp, err := c.PatchV0CityByCityNameWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) PatchV0CityByCityNameWithBodyWithResponse(ctx context.Context, cityName string, params *PatchV0CityByCityNameParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameResponse, error) { + rsp, err := c.PatchV0CityByCityNameWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePatchV0CityByCityNameResponse(rsp) } -func (c *ClientWithResponses) PatchV0CityByCityNameWithResponse(ctx context.Context, cityName string, body PatchV0CityByCityNameJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameResponse, error) { - rsp, err := c.PatchV0CityByCityName(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) PatchV0CityByCityNameWithResponse(ctx context.Context, cityName string, params *PatchV0CityByCityNameParams, body PatchV0CityByCityNameJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameResponse, error) { + rsp, err := c.PatchV0CityByCityName(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -18266,8 +19846,8 @@ func (c *ClientWithResponses) PatchV0CityByCityNameWithResponse(ctx context.Cont } // DeleteV0CityByCityNameAgentByBaseWithResponse request returning *DeleteV0CityByCityNameAgentByBaseResponse -func (c *ClientWithResponses) DeleteV0CityByCityNameAgentByBaseWithResponse(ctx context.Context, cityName string, base string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameAgentByBaseResponse, error) { - rsp, err := c.DeleteV0CityByCityNameAgentByBase(ctx, cityName, base, reqEditors...) +func (c *ClientWithResponses) DeleteV0CityByCityNameAgentByBaseWithResponse(ctx context.Context, cityName string, base string, params *DeleteV0CityByCityNameAgentByBaseParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameAgentByBaseResponse, error) { + rsp, err := c.DeleteV0CityByCityNameAgentByBase(ctx, cityName, base, params, reqEditors...) if err != nil { return nil, err } @@ -18284,16 +19864,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameAgentByBaseWithResponse(ctx con } // PatchV0CityByCityNameAgentByBaseWithBodyWithResponse request with arbitrary body returning *PatchV0CityByCityNameAgentByBaseResponse -func (c *ClientWithResponses) PatchV0CityByCityNameAgentByBaseWithBodyWithResponse(ctx context.Context, cityName string, base string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameAgentByBaseResponse, error) { - rsp, err := c.PatchV0CityByCityNameAgentByBaseWithBody(ctx, cityName, base, contentType, body, reqEditors...) +func (c *ClientWithResponses) PatchV0CityByCityNameAgentByBaseWithBodyWithResponse(ctx context.Context, cityName string, base string, params *PatchV0CityByCityNameAgentByBaseParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameAgentByBaseResponse, error) { + rsp, err := c.PatchV0CityByCityNameAgentByBaseWithBody(ctx, cityName, base, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePatchV0CityByCityNameAgentByBaseResponse(rsp) } -func (c *ClientWithResponses) PatchV0CityByCityNameAgentByBaseWithResponse(ctx context.Context, cityName string, base string, body PatchV0CityByCityNameAgentByBaseJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameAgentByBaseResponse, error) { - rsp, err := c.PatchV0CityByCityNameAgentByBase(ctx, cityName, base, body, reqEditors...) +func (c *ClientWithResponses) PatchV0CityByCityNameAgentByBaseWithResponse(ctx context.Context, cityName string, base string, params *PatchV0CityByCityNameAgentByBaseParams, body PatchV0CityByCityNameAgentByBaseJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameAgentByBaseResponse, error) { + rsp, err := c.PatchV0CityByCityNameAgentByBase(ctx, cityName, base, params, body, reqEditors...) if err != nil { return nil, err } @@ -18319,8 +19899,8 @@ func (c *ClientWithResponses) StreamAgentOutputWithResponse(ctx context.Context, } // PostV0CityByCityNameAgentByBaseByActionWithResponse request returning *PostV0CityByCityNameAgentByBaseByActionResponse -func (c *ClientWithResponses) PostV0CityByCityNameAgentByBaseByActionWithResponse(ctx context.Context, cityName string, base string, action PostV0CityByCityNameAgentByBaseByActionParamsAction, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameAgentByBaseByActionResponse, error) { - rsp, err := c.PostV0CityByCityNameAgentByBaseByAction(ctx, cityName, base, action, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameAgentByBaseByActionWithResponse(ctx context.Context, cityName string, base string, action PostV0CityByCityNameAgentByBaseByActionParamsAction, params *PostV0CityByCityNameAgentByBaseByActionParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameAgentByBaseByActionResponse, error) { + rsp, err := c.PostV0CityByCityNameAgentByBaseByAction(ctx, cityName, base, action, params, reqEditors...) if err != nil { return nil, err } @@ -18328,8 +19908,8 @@ func (c *ClientWithResponses) PostV0CityByCityNameAgentByBaseByActionWithRespons } // DeleteV0CityByCityNameAgentByDirByBaseWithResponse request returning *DeleteV0CityByCityNameAgentByDirByBaseResponse -func (c *ClientWithResponses) DeleteV0CityByCityNameAgentByDirByBaseWithResponse(ctx context.Context, cityName string, dir string, base string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameAgentByDirByBaseResponse, error) { - rsp, err := c.DeleteV0CityByCityNameAgentByDirByBase(ctx, cityName, dir, base, reqEditors...) +func (c *ClientWithResponses) DeleteV0CityByCityNameAgentByDirByBaseWithResponse(ctx context.Context, cityName string, dir string, base string, params *DeleteV0CityByCityNameAgentByDirByBaseParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameAgentByDirByBaseResponse, error) { + rsp, err := c.DeleteV0CityByCityNameAgentByDirByBase(ctx, cityName, dir, base, params, reqEditors...) if err != nil { return nil, err } @@ -18346,16 +19926,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameAgentByDirByBaseWithResponse(ct } // PatchV0CityByCityNameAgentByDirByBaseWithBodyWithResponse request with arbitrary body returning *PatchV0CityByCityNameAgentByDirByBaseResponse -func (c *ClientWithResponses) PatchV0CityByCityNameAgentByDirByBaseWithBodyWithResponse(ctx context.Context, cityName string, dir string, base string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameAgentByDirByBaseResponse, error) { - rsp, err := c.PatchV0CityByCityNameAgentByDirByBaseWithBody(ctx, cityName, dir, base, contentType, body, reqEditors...) +func (c *ClientWithResponses) PatchV0CityByCityNameAgentByDirByBaseWithBodyWithResponse(ctx context.Context, cityName string, dir string, base string, params *PatchV0CityByCityNameAgentByDirByBaseParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameAgentByDirByBaseResponse, error) { + rsp, err := c.PatchV0CityByCityNameAgentByDirByBaseWithBody(ctx, cityName, dir, base, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePatchV0CityByCityNameAgentByDirByBaseResponse(rsp) } -func (c *ClientWithResponses) PatchV0CityByCityNameAgentByDirByBaseWithResponse(ctx context.Context, cityName string, dir string, base string, body PatchV0CityByCityNameAgentByDirByBaseJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameAgentByDirByBaseResponse, error) { - rsp, err := c.PatchV0CityByCityNameAgentByDirByBase(ctx, cityName, dir, base, body, reqEditors...) +func (c *ClientWithResponses) PatchV0CityByCityNameAgentByDirByBaseWithResponse(ctx context.Context, cityName string, dir string, base string, params *PatchV0CityByCityNameAgentByDirByBaseParams, body PatchV0CityByCityNameAgentByDirByBaseJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameAgentByDirByBaseResponse, error) { + rsp, err := c.PatchV0CityByCityNameAgentByDirByBase(ctx, cityName, dir, base, params, body, reqEditors...) if err != nil { return nil, err } @@ -18381,8 +19961,8 @@ func (c *ClientWithResponses) StreamAgentOutputQualifiedWithResponse(ctx context } // PostV0CityByCityNameAgentByDirByBaseByActionWithResponse request returning *PostV0CityByCityNameAgentByDirByBaseByActionResponse -func (c *ClientWithResponses) PostV0CityByCityNameAgentByDirByBaseByActionWithResponse(ctx context.Context, cityName string, dir string, base string, action PostV0CityByCityNameAgentByDirByBaseByActionParamsAction, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameAgentByDirByBaseByActionResponse, error) { - rsp, err := c.PostV0CityByCityNameAgentByDirByBaseByAction(ctx, cityName, dir, base, action, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameAgentByDirByBaseByActionWithResponse(ctx context.Context, cityName string, dir string, base string, action PostV0CityByCityNameAgentByDirByBaseByActionParamsAction, params *PostV0CityByCityNameAgentByDirByBaseByActionParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameAgentByDirByBaseByActionResponse, error) { + rsp, err := c.PostV0CityByCityNameAgentByDirByBaseByAction(ctx, cityName, dir, base, action, params, reqEditors...) if err != nil { return nil, err } @@ -18399,16 +19979,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameAgentsWithResponse(ctx context. } // CreateAgentWithBodyWithResponse request with arbitrary body returning *CreateAgentResponse -func (c *ClientWithResponses) CreateAgentWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateAgentResponse, error) { - rsp, err := c.CreateAgentWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) CreateAgentWithBodyWithResponse(ctx context.Context, cityName string, params *CreateAgentParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateAgentResponse, error) { + rsp, err := c.CreateAgentWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseCreateAgentResponse(rsp) } -func (c *ClientWithResponses) CreateAgentWithResponse(ctx context.Context, cityName string, body CreateAgentJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateAgentResponse, error) { - rsp, err := c.CreateAgent(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) CreateAgentWithResponse(ctx context.Context, cityName string, params *CreateAgentParams, body CreateAgentJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateAgentResponse, error) { + rsp, err := c.CreateAgent(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -18416,8 +19996,8 @@ func (c *ClientWithResponses) CreateAgentWithResponse(ctx context.Context, cityN } // DeleteV0CityByCityNameBeadByIdWithResponse request returning *DeleteV0CityByCityNameBeadByIdResponse -func (c *ClientWithResponses) DeleteV0CityByCityNameBeadByIdWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameBeadByIdResponse, error) { - rsp, err := c.DeleteV0CityByCityNameBeadById(ctx, cityName, id, reqEditors...) +func (c *ClientWithResponses) DeleteV0CityByCityNameBeadByIdWithResponse(ctx context.Context, cityName string, id string, params *DeleteV0CityByCityNameBeadByIdParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameBeadByIdResponse, error) { + rsp, err := c.DeleteV0CityByCityNameBeadById(ctx, cityName, id, params, reqEditors...) if err != nil { return nil, err } @@ -18434,16 +20014,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameBeadByIdWithResponse(ctx contex } // PatchV0CityByCityNameBeadByIdWithBodyWithResponse request with arbitrary body returning *PatchV0CityByCityNameBeadByIdResponse -func (c *ClientWithResponses) PatchV0CityByCityNameBeadByIdWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameBeadByIdResponse, error) { - rsp, err := c.PatchV0CityByCityNameBeadByIdWithBody(ctx, cityName, id, contentType, body, reqEditors...) +func (c *ClientWithResponses) PatchV0CityByCityNameBeadByIdWithBodyWithResponse(ctx context.Context, cityName string, id string, params *PatchV0CityByCityNameBeadByIdParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameBeadByIdResponse, error) { + rsp, err := c.PatchV0CityByCityNameBeadByIdWithBody(ctx, cityName, id, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePatchV0CityByCityNameBeadByIdResponse(rsp) } -func (c *ClientWithResponses) PatchV0CityByCityNameBeadByIdWithResponse(ctx context.Context, cityName string, id string, body PatchV0CityByCityNameBeadByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameBeadByIdResponse, error) { - rsp, err := c.PatchV0CityByCityNameBeadById(ctx, cityName, id, body, reqEditors...) +func (c *ClientWithResponses) PatchV0CityByCityNameBeadByIdWithResponse(ctx context.Context, cityName string, id string, params *PatchV0CityByCityNameBeadByIdParams, body PatchV0CityByCityNameBeadByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameBeadByIdResponse, error) { + rsp, err := c.PatchV0CityByCityNameBeadById(ctx, cityName, id, params, body, reqEditors...) if err != nil { return nil, err } @@ -18451,16 +20031,16 @@ func (c *ClientWithResponses) PatchV0CityByCityNameBeadByIdWithResponse(ctx cont } // PostV0CityByCityNameBeadByIdAssignWithBodyWithResponse request with arbitrary body returning *PostV0CityByCityNameBeadByIdAssignResponse -func (c *ClientWithResponses) PostV0CityByCityNameBeadByIdAssignWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdAssignResponse, error) { - rsp, err := c.PostV0CityByCityNameBeadByIdAssignWithBody(ctx, cityName, id, contentType, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameBeadByIdAssignWithBodyWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdAssignParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdAssignResponse, error) { + rsp, err := c.PostV0CityByCityNameBeadByIdAssignWithBody(ctx, cityName, id, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePostV0CityByCityNameBeadByIdAssignResponse(rsp) } -func (c *ClientWithResponses) PostV0CityByCityNameBeadByIdAssignWithResponse(ctx context.Context, cityName string, id string, body PostV0CityByCityNameBeadByIdAssignJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdAssignResponse, error) { - rsp, err := c.PostV0CityByCityNameBeadByIdAssign(ctx, cityName, id, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameBeadByIdAssignWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdAssignParams, body PostV0CityByCityNameBeadByIdAssignJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdAssignResponse, error) { + rsp, err := c.PostV0CityByCityNameBeadByIdAssign(ctx, cityName, id, params, body, reqEditors...) if err != nil { return nil, err } @@ -18468,8 +20048,8 @@ func (c *ClientWithResponses) PostV0CityByCityNameBeadByIdAssignWithResponse(ctx } // PostV0CityByCityNameBeadByIdCloseWithResponse request returning *PostV0CityByCityNameBeadByIdCloseResponse -func (c *ClientWithResponses) PostV0CityByCityNameBeadByIdCloseWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdCloseResponse, error) { - rsp, err := c.PostV0CityByCityNameBeadByIdClose(ctx, cityName, id, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameBeadByIdCloseWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdCloseParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdCloseResponse, error) { + rsp, err := c.PostV0CityByCityNameBeadByIdClose(ctx, cityName, id, params, reqEditors...) if err != nil { return nil, err } @@ -18486,8 +20066,8 @@ func (c *ClientWithResponses) GetV0CityByCityNameBeadByIdDepsWithResponse(ctx co } // PostV0CityByCityNameBeadByIdReopenWithResponse request returning *PostV0CityByCityNameBeadByIdReopenResponse -func (c *ClientWithResponses) PostV0CityByCityNameBeadByIdReopenWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdReopenResponse, error) { - rsp, err := c.PostV0CityByCityNameBeadByIdReopen(ctx, cityName, id, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameBeadByIdReopenWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdReopenParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdReopenResponse, error) { + rsp, err := c.PostV0CityByCityNameBeadByIdReopen(ctx, cityName, id, params, reqEditors...) if err != nil { return nil, err } @@ -18495,16 +20075,16 @@ func (c *ClientWithResponses) PostV0CityByCityNameBeadByIdReopenWithResponse(ctx } // PostV0CityByCityNameBeadByIdUpdateWithBodyWithResponse request with arbitrary body returning *PostV0CityByCityNameBeadByIdUpdateResponse -func (c *ClientWithResponses) PostV0CityByCityNameBeadByIdUpdateWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdUpdateResponse, error) { - rsp, err := c.PostV0CityByCityNameBeadByIdUpdateWithBody(ctx, cityName, id, contentType, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameBeadByIdUpdateWithBodyWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdUpdateParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdUpdateResponse, error) { + rsp, err := c.PostV0CityByCityNameBeadByIdUpdateWithBody(ctx, cityName, id, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePostV0CityByCityNameBeadByIdUpdateResponse(rsp) } -func (c *ClientWithResponses) PostV0CityByCityNameBeadByIdUpdateWithResponse(ctx context.Context, cityName string, id string, body PostV0CityByCityNameBeadByIdUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdUpdateResponse, error) { - rsp, err := c.PostV0CityByCityNameBeadByIdUpdate(ctx, cityName, id, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameBeadByIdUpdateWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameBeadByIdUpdateParams, body PostV0CityByCityNameBeadByIdUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameBeadByIdUpdateResponse, error) { + rsp, err := c.PostV0CityByCityNameBeadByIdUpdate(ctx, cityName, id, params, body, reqEditors...) if err != nil { return nil, err } @@ -18583,8 +20163,8 @@ func (c *ClientWithResponses) GetV0CityByCityNameConfigValidateWithResponse(ctx } // DeleteV0CityByCityNameConvoyByIdWithResponse request returning *DeleteV0CityByCityNameConvoyByIdResponse -func (c *ClientWithResponses) DeleteV0CityByCityNameConvoyByIdWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameConvoyByIdResponse, error) { - rsp, err := c.DeleteV0CityByCityNameConvoyById(ctx, cityName, id, reqEditors...) +func (c *ClientWithResponses) DeleteV0CityByCityNameConvoyByIdWithResponse(ctx context.Context, cityName string, id string, params *DeleteV0CityByCityNameConvoyByIdParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameConvoyByIdResponse, error) { + rsp, err := c.DeleteV0CityByCityNameConvoyById(ctx, cityName, id, params, reqEditors...) if err != nil { return nil, err } @@ -18601,16 +20181,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameConvoyByIdWithResponse(ctx cont } // PostV0CityByCityNameConvoyByIdAddWithBodyWithResponse request with arbitrary body returning *PostV0CityByCityNameConvoyByIdAddResponse -func (c *ClientWithResponses) PostV0CityByCityNameConvoyByIdAddWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdAddResponse, error) { - rsp, err := c.PostV0CityByCityNameConvoyByIdAddWithBody(ctx, cityName, id, contentType, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameConvoyByIdAddWithBodyWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdAddParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdAddResponse, error) { + rsp, err := c.PostV0CityByCityNameConvoyByIdAddWithBody(ctx, cityName, id, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePostV0CityByCityNameConvoyByIdAddResponse(rsp) } -func (c *ClientWithResponses) PostV0CityByCityNameConvoyByIdAddWithResponse(ctx context.Context, cityName string, id string, body PostV0CityByCityNameConvoyByIdAddJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdAddResponse, error) { - rsp, err := c.PostV0CityByCityNameConvoyByIdAdd(ctx, cityName, id, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameConvoyByIdAddWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdAddParams, body PostV0CityByCityNameConvoyByIdAddJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdAddResponse, error) { + rsp, err := c.PostV0CityByCityNameConvoyByIdAdd(ctx, cityName, id, params, body, reqEditors...) if err != nil { return nil, err } @@ -18627,8 +20207,8 @@ func (c *ClientWithResponses) GetV0CityByCityNameConvoyByIdCheckWithResponse(ctx } // PostV0CityByCityNameConvoyByIdCloseWithResponse request returning *PostV0CityByCityNameConvoyByIdCloseResponse -func (c *ClientWithResponses) PostV0CityByCityNameConvoyByIdCloseWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdCloseResponse, error) { - rsp, err := c.PostV0CityByCityNameConvoyByIdClose(ctx, cityName, id, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameConvoyByIdCloseWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdCloseParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdCloseResponse, error) { + rsp, err := c.PostV0CityByCityNameConvoyByIdClose(ctx, cityName, id, params, reqEditors...) if err != nil { return nil, err } @@ -18636,16 +20216,16 @@ func (c *ClientWithResponses) PostV0CityByCityNameConvoyByIdCloseWithResponse(ct } // PostV0CityByCityNameConvoyByIdRemoveWithBodyWithResponse request with arbitrary body returning *PostV0CityByCityNameConvoyByIdRemoveResponse -func (c *ClientWithResponses) PostV0CityByCityNameConvoyByIdRemoveWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdRemoveResponse, error) { - rsp, err := c.PostV0CityByCityNameConvoyByIdRemoveWithBody(ctx, cityName, id, contentType, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameConvoyByIdRemoveWithBodyWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdRemoveParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdRemoveResponse, error) { + rsp, err := c.PostV0CityByCityNameConvoyByIdRemoveWithBody(ctx, cityName, id, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePostV0CityByCityNameConvoyByIdRemoveResponse(rsp) } -func (c *ClientWithResponses) PostV0CityByCityNameConvoyByIdRemoveWithResponse(ctx context.Context, cityName string, id string, body PostV0CityByCityNameConvoyByIdRemoveJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdRemoveResponse, error) { - rsp, err := c.PostV0CityByCityNameConvoyByIdRemove(ctx, cityName, id, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameConvoyByIdRemoveWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameConvoyByIdRemoveParams, body PostV0CityByCityNameConvoyByIdRemoveJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameConvoyByIdRemoveResponse, error) { + rsp, err := c.PostV0CityByCityNameConvoyByIdRemove(ctx, cityName, id, params, body, reqEditors...) if err != nil { return nil, err } @@ -18662,16 +20242,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameConvoysWithResponse(ctx context } // CreateConvoyWithBodyWithResponse request with arbitrary body returning *CreateConvoyResponse -func (c *ClientWithResponses) CreateConvoyWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateConvoyResponse, error) { - rsp, err := c.CreateConvoyWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) CreateConvoyWithBodyWithResponse(ctx context.Context, cityName string, params *CreateConvoyParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateConvoyResponse, error) { + rsp, err := c.CreateConvoyWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseCreateConvoyResponse(rsp) } -func (c *ClientWithResponses) CreateConvoyWithResponse(ctx context.Context, cityName string, body CreateConvoyJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateConvoyResponse, error) { - rsp, err := c.CreateConvoy(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) CreateConvoyWithResponse(ctx context.Context, cityName string, params *CreateConvoyParams, body CreateConvoyJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateConvoyResponse, error) { + rsp, err := c.CreateConvoy(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -18688,16 +20268,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameEventsWithResponse(ctx context. } // EmitEventWithBodyWithResponse request with arbitrary body returning *EmitEventResponse -func (c *ClientWithResponses) EmitEventWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EmitEventResponse, error) { - rsp, err := c.EmitEventWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) EmitEventWithBodyWithResponse(ctx context.Context, cityName string, params *EmitEventParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EmitEventResponse, error) { + rsp, err := c.EmitEventWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseEmitEventResponse(rsp) } -func (c *ClientWithResponses) EmitEventWithResponse(ctx context.Context, cityName string, body EmitEventJSONRequestBody, reqEditors ...RequestEditorFn) (*EmitEventResponse, error) { - rsp, err := c.EmitEvent(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) EmitEventWithResponse(ctx context.Context, cityName string, params *EmitEventParams, body EmitEventJSONRequestBody, reqEditors ...RequestEditorFn) (*EmitEventResponse, error) { + rsp, err := c.EmitEvent(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -18714,16 +20294,16 @@ func (c *ClientWithResponses) StreamEventsWithResponse(ctx context.Context, city } // DeleteV0CityByCityNameExtmsgAdaptersWithBodyWithResponse request with arbitrary body returning *DeleteV0CityByCityNameExtmsgAdaptersResponse -func (c *ClientWithResponses) DeleteV0CityByCityNameExtmsgAdaptersWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameExtmsgAdaptersResponse, error) { - rsp, err := c.DeleteV0CityByCityNameExtmsgAdaptersWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) DeleteV0CityByCityNameExtmsgAdaptersWithBodyWithResponse(ctx context.Context, cityName string, params *DeleteV0CityByCityNameExtmsgAdaptersParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameExtmsgAdaptersResponse, error) { + rsp, err := c.DeleteV0CityByCityNameExtmsgAdaptersWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseDeleteV0CityByCityNameExtmsgAdaptersResponse(rsp) } -func (c *ClientWithResponses) DeleteV0CityByCityNameExtmsgAdaptersWithResponse(ctx context.Context, cityName string, body DeleteV0CityByCityNameExtmsgAdaptersJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameExtmsgAdaptersResponse, error) { - rsp, err := c.DeleteV0CityByCityNameExtmsgAdapters(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) DeleteV0CityByCityNameExtmsgAdaptersWithResponse(ctx context.Context, cityName string, params *DeleteV0CityByCityNameExtmsgAdaptersParams, body DeleteV0CityByCityNameExtmsgAdaptersJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameExtmsgAdaptersResponse, error) { + rsp, err := c.DeleteV0CityByCityNameExtmsgAdapters(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -18740,16 +20320,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameExtmsgAdaptersWithResponse(ctx } // RegisterExtmsgAdapterWithBodyWithResponse request with arbitrary body returning *RegisterExtmsgAdapterResponse -func (c *ClientWithResponses) RegisterExtmsgAdapterWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterExtmsgAdapterResponse, error) { - rsp, err := c.RegisterExtmsgAdapterWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) RegisterExtmsgAdapterWithBodyWithResponse(ctx context.Context, cityName string, params *RegisterExtmsgAdapterParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterExtmsgAdapterResponse, error) { + rsp, err := c.RegisterExtmsgAdapterWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseRegisterExtmsgAdapterResponse(rsp) } -func (c *ClientWithResponses) RegisterExtmsgAdapterWithResponse(ctx context.Context, cityName string, body RegisterExtmsgAdapterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterExtmsgAdapterResponse, error) { - rsp, err := c.RegisterExtmsgAdapter(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) RegisterExtmsgAdapterWithResponse(ctx context.Context, cityName string, params *RegisterExtmsgAdapterParams, body RegisterExtmsgAdapterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterExtmsgAdapterResponse, error) { + rsp, err := c.RegisterExtmsgAdapter(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -18757,16 +20337,16 @@ func (c *ClientWithResponses) RegisterExtmsgAdapterWithResponse(ctx context.Cont } // PostV0CityByCityNameExtmsgBindWithBodyWithResponse request with arbitrary body returning *PostV0CityByCityNameExtmsgBindResponse -func (c *ClientWithResponses) PostV0CityByCityNameExtmsgBindWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgBindResponse, error) { - rsp, err := c.PostV0CityByCityNameExtmsgBindWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameExtmsgBindWithBodyWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgBindParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgBindResponse, error) { + rsp, err := c.PostV0CityByCityNameExtmsgBindWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePostV0CityByCityNameExtmsgBindResponse(rsp) } -func (c *ClientWithResponses) PostV0CityByCityNameExtmsgBindWithResponse(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgBindJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgBindResponse, error) { - rsp, err := c.PostV0CityByCityNameExtmsgBind(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameExtmsgBindWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgBindParams, body PostV0CityByCityNameExtmsgBindJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgBindResponse, error) { + rsp, err := c.PostV0CityByCityNameExtmsgBind(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -18792,16 +20372,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameExtmsgGroupsWithResponse(ctx co } // EnsureExtmsgGroupWithBodyWithResponse request with arbitrary body returning *EnsureExtmsgGroupResponse -func (c *ClientWithResponses) EnsureExtmsgGroupWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EnsureExtmsgGroupResponse, error) { - rsp, err := c.EnsureExtmsgGroupWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) EnsureExtmsgGroupWithBodyWithResponse(ctx context.Context, cityName string, params *EnsureExtmsgGroupParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EnsureExtmsgGroupResponse, error) { + rsp, err := c.EnsureExtmsgGroupWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseEnsureExtmsgGroupResponse(rsp) } -func (c *ClientWithResponses) EnsureExtmsgGroupWithResponse(ctx context.Context, cityName string, body EnsureExtmsgGroupJSONRequestBody, reqEditors ...RequestEditorFn) (*EnsureExtmsgGroupResponse, error) { - rsp, err := c.EnsureExtmsgGroup(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) EnsureExtmsgGroupWithResponse(ctx context.Context, cityName string, params *EnsureExtmsgGroupParams, body EnsureExtmsgGroupJSONRequestBody, reqEditors ...RequestEditorFn) (*EnsureExtmsgGroupResponse, error) { + rsp, err := c.EnsureExtmsgGroup(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -18809,16 +20389,16 @@ func (c *ClientWithResponses) EnsureExtmsgGroupWithResponse(ctx context.Context, } // PostV0CityByCityNameExtmsgInboundWithBodyWithResponse request with arbitrary body returning *PostV0CityByCityNameExtmsgInboundResponse -func (c *ClientWithResponses) PostV0CityByCityNameExtmsgInboundWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgInboundResponse, error) { - rsp, err := c.PostV0CityByCityNameExtmsgInboundWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameExtmsgInboundWithBodyWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgInboundParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgInboundResponse, error) { + rsp, err := c.PostV0CityByCityNameExtmsgInboundWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePostV0CityByCityNameExtmsgInboundResponse(rsp) } -func (c *ClientWithResponses) PostV0CityByCityNameExtmsgInboundWithResponse(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgInboundJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgInboundResponse, error) { - rsp, err := c.PostV0CityByCityNameExtmsgInbound(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameExtmsgInboundWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgInboundParams, body PostV0CityByCityNameExtmsgInboundJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgInboundResponse, error) { + rsp, err := c.PostV0CityByCityNameExtmsgInbound(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -18826,16 +20406,16 @@ func (c *ClientWithResponses) PostV0CityByCityNameExtmsgInboundWithResponse(ctx } // PostV0CityByCityNameExtmsgOutboundWithBodyWithResponse request with arbitrary body returning *PostV0CityByCityNameExtmsgOutboundResponse -func (c *ClientWithResponses) PostV0CityByCityNameExtmsgOutboundWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgOutboundResponse, error) { - rsp, err := c.PostV0CityByCityNameExtmsgOutboundWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameExtmsgOutboundWithBodyWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgOutboundParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgOutboundResponse, error) { + rsp, err := c.PostV0CityByCityNameExtmsgOutboundWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePostV0CityByCityNameExtmsgOutboundResponse(rsp) } -func (c *ClientWithResponses) PostV0CityByCityNameExtmsgOutboundWithResponse(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgOutboundJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgOutboundResponse, error) { - rsp, err := c.PostV0CityByCityNameExtmsgOutbound(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameExtmsgOutboundWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgOutboundParams, body PostV0CityByCityNameExtmsgOutboundJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgOutboundResponse, error) { + rsp, err := c.PostV0CityByCityNameExtmsgOutbound(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -18843,16 +20423,16 @@ func (c *ClientWithResponses) PostV0CityByCityNameExtmsgOutboundWithResponse(ctx } // DeleteV0CityByCityNameExtmsgParticipantsWithBodyWithResponse request with arbitrary body returning *DeleteV0CityByCityNameExtmsgParticipantsResponse -func (c *ClientWithResponses) DeleteV0CityByCityNameExtmsgParticipantsWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameExtmsgParticipantsResponse, error) { - rsp, err := c.DeleteV0CityByCityNameExtmsgParticipantsWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) DeleteV0CityByCityNameExtmsgParticipantsWithBodyWithResponse(ctx context.Context, cityName string, params *DeleteV0CityByCityNameExtmsgParticipantsParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameExtmsgParticipantsResponse, error) { + rsp, err := c.DeleteV0CityByCityNameExtmsgParticipantsWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseDeleteV0CityByCityNameExtmsgParticipantsResponse(rsp) } -func (c *ClientWithResponses) DeleteV0CityByCityNameExtmsgParticipantsWithResponse(ctx context.Context, cityName string, body DeleteV0CityByCityNameExtmsgParticipantsJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameExtmsgParticipantsResponse, error) { - rsp, err := c.DeleteV0CityByCityNameExtmsgParticipants(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) DeleteV0CityByCityNameExtmsgParticipantsWithResponse(ctx context.Context, cityName string, params *DeleteV0CityByCityNameExtmsgParticipantsParams, body DeleteV0CityByCityNameExtmsgParticipantsJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameExtmsgParticipantsResponse, error) { + rsp, err := c.DeleteV0CityByCityNameExtmsgParticipants(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -18860,16 +20440,16 @@ func (c *ClientWithResponses) DeleteV0CityByCityNameExtmsgParticipantsWithRespon } // PostV0CityByCityNameExtmsgParticipantsWithBodyWithResponse request with arbitrary body returning *PostV0CityByCityNameExtmsgParticipantsResponse -func (c *ClientWithResponses) PostV0CityByCityNameExtmsgParticipantsWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgParticipantsResponse, error) { - rsp, err := c.PostV0CityByCityNameExtmsgParticipantsWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameExtmsgParticipantsWithBodyWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgParticipantsParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgParticipantsResponse, error) { + rsp, err := c.PostV0CityByCityNameExtmsgParticipantsWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePostV0CityByCityNameExtmsgParticipantsResponse(rsp) } -func (c *ClientWithResponses) PostV0CityByCityNameExtmsgParticipantsWithResponse(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgParticipantsJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgParticipantsResponse, error) { - rsp, err := c.PostV0CityByCityNameExtmsgParticipants(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameExtmsgParticipantsWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgParticipantsParams, body PostV0CityByCityNameExtmsgParticipantsJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgParticipantsResponse, error) { + rsp, err := c.PostV0CityByCityNameExtmsgParticipants(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -18886,16 +20466,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameExtmsgTranscriptWithResponse(ct } // PostV0CityByCityNameExtmsgTranscriptAckWithBodyWithResponse request with arbitrary body returning *PostV0CityByCityNameExtmsgTranscriptAckResponse -func (c *ClientWithResponses) PostV0CityByCityNameExtmsgTranscriptAckWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgTranscriptAckResponse, error) { - rsp, err := c.PostV0CityByCityNameExtmsgTranscriptAckWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameExtmsgTranscriptAckWithBodyWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgTranscriptAckParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgTranscriptAckResponse, error) { + rsp, err := c.PostV0CityByCityNameExtmsgTranscriptAckWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePostV0CityByCityNameExtmsgTranscriptAckResponse(rsp) } -func (c *ClientWithResponses) PostV0CityByCityNameExtmsgTranscriptAckWithResponse(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgTranscriptAckJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgTranscriptAckResponse, error) { - rsp, err := c.PostV0CityByCityNameExtmsgTranscriptAck(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameExtmsgTranscriptAckWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgTranscriptAckParams, body PostV0CityByCityNameExtmsgTranscriptAckJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgTranscriptAckResponse, error) { + rsp, err := c.PostV0CityByCityNameExtmsgTranscriptAck(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -18903,16 +20483,16 @@ func (c *ClientWithResponses) PostV0CityByCityNameExtmsgTranscriptAckWithRespons } // PostV0CityByCityNameExtmsgUnbindWithBodyWithResponse request with arbitrary body returning *PostV0CityByCityNameExtmsgUnbindResponse -func (c *ClientWithResponses) PostV0CityByCityNameExtmsgUnbindWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgUnbindResponse, error) { - rsp, err := c.PostV0CityByCityNameExtmsgUnbindWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameExtmsgUnbindWithBodyWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgUnbindParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgUnbindResponse, error) { + rsp, err := c.PostV0CityByCityNameExtmsgUnbindWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePostV0CityByCityNameExtmsgUnbindResponse(rsp) } -func (c *ClientWithResponses) PostV0CityByCityNameExtmsgUnbindWithResponse(ctx context.Context, cityName string, body PostV0CityByCityNameExtmsgUnbindJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgUnbindResponse, error) { - rsp, err := c.PostV0CityByCityNameExtmsgUnbind(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameExtmsgUnbindWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameExtmsgUnbindParams, body PostV0CityByCityNameExtmsgUnbindJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameExtmsgUnbindResponse, error) { + rsp, err := c.PostV0CityByCityNameExtmsgUnbind(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -18956,16 +20536,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameFormulasByNameWithResponse(ctx } // PostV0CityByCityNameFormulasByNamePreviewWithBodyWithResponse request with arbitrary body returning *PostV0CityByCityNameFormulasByNamePreviewResponse -func (c *ClientWithResponses) PostV0CityByCityNameFormulasByNamePreviewWithBodyWithResponse(ctx context.Context, cityName string, name string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameFormulasByNamePreviewResponse, error) { - rsp, err := c.PostV0CityByCityNameFormulasByNamePreviewWithBody(ctx, cityName, name, contentType, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameFormulasByNamePreviewWithBodyWithResponse(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameFormulasByNamePreviewParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameFormulasByNamePreviewResponse, error) { + rsp, err := c.PostV0CityByCityNameFormulasByNamePreviewWithBody(ctx, cityName, name, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePostV0CityByCityNameFormulasByNamePreviewResponse(rsp) } -func (c *ClientWithResponses) PostV0CityByCityNameFormulasByNamePreviewWithResponse(ctx context.Context, cityName string, name string, body PostV0CityByCityNameFormulasByNamePreviewJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameFormulasByNamePreviewResponse, error) { - rsp, err := c.PostV0CityByCityNameFormulasByNamePreview(ctx, cityName, name, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameFormulasByNamePreviewWithResponse(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameFormulasByNamePreviewParams, body PostV0CityByCityNameFormulasByNamePreviewJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameFormulasByNamePreviewResponse, error) { + rsp, err := c.PostV0CityByCityNameFormulasByNamePreview(ctx, cityName, name, params, body, reqEditors...) if err != nil { return nil, err } @@ -19115,8 +20695,8 @@ func (c *ClientWithResponses) GetV0CityByCityNameOrderByNameWithResponse(ctx con } // PostV0CityByCityNameOrderByNameDisableWithResponse request returning *PostV0CityByCityNameOrderByNameDisableResponse -func (c *ClientWithResponses) PostV0CityByCityNameOrderByNameDisableWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameOrderByNameDisableResponse, error) { - rsp, err := c.PostV0CityByCityNameOrderByNameDisable(ctx, cityName, name, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameOrderByNameDisableWithResponse(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameOrderByNameDisableParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameOrderByNameDisableResponse, error) { + rsp, err := c.PostV0CityByCityNameOrderByNameDisable(ctx, cityName, name, params, reqEditors...) if err != nil { return nil, err } @@ -19124,8 +20704,8 @@ func (c *ClientWithResponses) PostV0CityByCityNameOrderByNameDisableWithResponse } // PostV0CityByCityNameOrderByNameEnableWithResponse request returning *PostV0CityByCityNameOrderByNameEnableResponse -func (c *ClientWithResponses) PostV0CityByCityNameOrderByNameEnableWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameOrderByNameEnableResponse, error) { - rsp, err := c.PostV0CityByCityNameOrderByNameEnable(ctx, cityName, name, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameOrderByNameEnableWithResponse(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameOrderByNameEnableParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameOrderByNameEnableResponse, error) { + rsp, err := c.PostV0CityByCityNameOrderByNameEnable(ctx, cityName, name, params, reqEditors...) if err != nil { return nil, err } @@ -19178,8 +20758,8 @@ func (c *ClientWithResponses) GetV0CityByCityNamePacksWithResponse(ctx context.C } // DeleteV0CityByCityNamePatchesAgentByBaseWithResponse request returning *DeleteV0CityByCityNamePatchesAgentByBaseResponse -func (c *ClientWithResponses) DeleteV0CityByCityNamePatchesAgentByBaseWithResponse(ctx context.Context, cityName string, base string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNamePatchesAgentByBaseResponse, error) { - rsp, err := c.DeleteV0CityByCityNamePatchesAgentByBase(ctx, cityName, base, reqEditors...) +func (c *ClientWithResponses) DeleteV0CityByCityNamePatchesAgentByBaseWithResponse(ctx context.Context, cityName string, base string, params *DeleteV0CityByCityNamePatchesAgentByBaseParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNamePatchesAgentByBaseResponse, error) { + rsp, err := c.DeleteV0CityByCityNamePatchesAgentByBase(ctx, cityName, base, params, reqEditors...) if err != nil { return nil, err } @@ -19196,8 +20776,8 @@ func (c *ClientWithResponses) GetV0CityByCityNamePatchesAgentByBaseWithResponse( } // DeleteV0CityByCityNamePatchesAgentByDirByBaseWithResponse request returning *DeleteV0CityByCityNamePatchesAgentByDirByBaseResponse -func (c *ClientWithResponses) DeleteV0CityByCityNamePatchesAgentByDirByBaseWithResponse(ctx context.Context, cityName string, dir string, base string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNamePatchesAgentByDirByBaseResponse, error) { - rsp, err := c.DeleteV0CityByCityNamePatchesAgentByDirByBase(ctx, cityName, dir, base, reqEditors...) +func (c *ClientWithResponses) DeleteV0CityByCityNamePatchesAgentByDirByBaseWithResponse(ctx context.Context, cityName string, dir string, base string, params *DeleteV0CityByCityNamePatchesAgentByDirByBaseParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNamePatchesAgentByDirByBaseResponse, error) { + rsp, err := c.DeleteV0CityByCityNamePatchesAgentByDirByBase(ctx, cityName, dir, base, params, reqEditors...) if err != nil { return nil, err } @@ -19223,16 +20803,16 @@ func (c *ClientWithResponses) GetV0CityByCityNamePatchesAgentsWithResponse(ctx c } // PutV0CityByCityNamePatchesAgentsWithBodyWithResponse request with arbitrary body returning *PutV0CityByCityNamePatchesAgentsResponse -func (c *ClientWithResponses) PutV0CityByCityNamePatchesAgentsWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesAgentsResponse, error) { - rsp, err := c.PutV0CityByCityNamePatchesAgentsWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) PutV0CityByCityNamePatchesAgentsWithBodyWithResponse(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesAgentsParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesAgentsResponse, error) { + rsp, err := c.PutV0CityByCityNamePatchesAgentsWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePutV0CityByCityNamePatchesAgentsResponse(rsp) } -func (c *ClientWithResponses) PutV0CityByCityNamePatchesAgentsWithResponse(ctx context.Context, cityName string, body PutV0CityByCityNamePatchesAgentsJSONRequestBody, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesAgentsResponse, error) { - rsp, err := c.PutV0CityByCityNamePatchesAgents(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) PutV0CityByCityNamePatchesAgentsWithResponse(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesAgentsParams, body PutV0CityByCityNamePatchesAgentsJSONRequestBody, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesAgentsResponse, error) { + rsp, err := c.PutV0CityByCityNamePatchesAgents(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -19240,8 +20820,8 @@ func (c *ClientWithResponses) PutV0CityByCityNamePatchesAgentsWithResponse(ctx c } // DeleteV0CityByCityNamePatchesProviderByNameWithResponse request returning *DeleteV0CityByCityNamePatchesProviderByNameResponse -func (c *ClientWithResponses) DeleteV0CityByCityNamePatchesProviderByNameWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNamePatchesProviderByNameResponse, error) { - rsp, err := c.DeleteV0CityByCityNamePatchesProviderByName(ctx, cityName, name, reqEditors...) +func (c *ClientWithResponses) DeleteV0CityByCityNamePatchesProviderByNameWithResponse(ctx context.Context, cityName string, name string, params *DeleteV0CityByCityNamePatchesProviderByNameParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNamePatchesProviderByNameResponse, error) { + rsp, err := c.DeleteV0CityByCityNamePatchesProviderByName(ctx, cityName, name, params, reqEditors...) if err != nil { return nil, err } @@ -19267,16 +20847,16 @@ func (c *ClientWithResponses) GetV0CityByCityNamePatchesProvidersWithResponse(ct } // PutV0CityByCityNamePatchesProvidersWithBodyWithResponse request with arbitrary body returning *PutV0CityByCityNamePatchesProvidersResponse -func (c *ClientWithResponses) PutV0CityByCityNamePatchesProvidersWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesProvidersResponse, error) { - rsp, err := c.PutV0CityByCityNamePatchesProvidersWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) PutV0CityByCityNamePatchesProvidersWithBodyWithResponse(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesProvidersParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesProvidersResponse, error) { + rsp, err := c.PutV0CityByCityNamePatchesProvidersWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePutV0CityByCityNamePatchesProvidersResponse(rsp) } -func (c *ClientWithResponses) PutV0CityByCityNamePatchesProvidersWithResponse(ctx context.Context, cityName string, body PutV0CityByCityNamePatchesProvidersJSONRequestBody, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesProvidersResponse, error) { - rsp, err := c.PutV0CityByCityNamePatchesProviders(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) PutV0CityByCityNamePatchesProvidersWithResponse(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesProvidersParams, body PutV0CityByCityNamePatchesProvidersJSONRequestBody, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesProvidersResponse, error) { + rsp, err := c.PutV0CityByCityNamePatchesProviders(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -19284,8 +20864,8 @@ func (c *ClientWithResponses) PutV0CityByCityNamePatchesProvidersWithResponse(ct } // DeleteV0CityByCityNamePatchesRigByNameWithResponse request returning *DeleteV0CityByCityNamePatchesRigByNameResponse -func (c *ClientWithResponses) DeleteV0CityByCityNamePatchesRigByNameWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNamePatchesRigByNameResponse, error) { - rsp, err := c.DeleteV0CityByCityNamePatchesRigByName(ctx, cityName, name, reqEditors...) +func (c *ClientWithResponses) DeleteV0CityByCityNamePatchesRigByNameWithResponse(ctx context.Context, cityName string, name string, params *DeleteV0CityByCityNamePatchesRigByNameParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNamePatchesRigByNameResponse, error) { + rsp, err := c.DeleteV0CityByCityNamePatchesRigByName(ctx, cityName, name, params, reqEditors...) if err != nil { return nil, err } @@ -19311,16 +20891,16 @@ func (c *ClientWithResponses) GetV0CityByCityNamePatchesRigsWithResponse(ctx con } // PutV0CityByCityNamePatchesRigsWithBodyWithResponse request with arbitrary body returning *PutV0CityByCityNamePatchesRigsResponse -func (c *ClientWithResponses) PutV0CityByCityNamePatchesRigsWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesRigsResponse, error) { - rsp, err := c.PutV0CityByCityNamePatchesRigsWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) PutV0CityByCityNamePatchesRigsWithBodyWithResponse(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesRigsParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesRigsResponse, error) { + rsp, err := c.PutV0CityByCityNamePatchesRigsWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePutV0CityByCityNamePatchesRigsResponse(rsp) } -func (c *ClientWithResponses) PutV0CityByCityNamePatchesRigsWithResponse(ctx context.Context, cityName string, body PutV0CityByCityNamePatchesRigsJSONRequestBody, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesRigsResponse, error) { - rsp, err := c.PutV0CityByCityNamePatchesRigs(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) PutV0CityByCityNamePatchesRigsWithResponse(ctx context.Context, cityName string, params *PutV0CityByCityNamePatchesRigsParams, body PutV0CityByCityNamePatchesRigsJSONRequestBody, reqEditors ...RequestEditorFn) (*PutV0CityByCityNamePatchesRigsResponse, error) { + rsp, err := c.PutV0CityByCityNamePatchesRigs(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -19337,8 +20917,8 @@ func (c *ClientWithResponses) GetV0CityByCityNameProviderReadinessWithResponse(c } // DeleteV0CityByCityNameProviderByNameWithResponse request returning *DeleteV0CityByCityNameProviderByNameResponse -func (c *ClientWithResponses) DeleteV0CityByCityNameProviderByNameWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameProviderByNameResponse, error) { - rsp, err := c.DeleteV0CityByCityNameProviderByName(ctx, cityName, name, reqEditors...) +func (c *ClientWithResponses) DeleteV0CityByCityNameProviderByNameWithResponse(ctx context.Context, cityName string, name string, params *DeleteV0CityByCityNameProviderByNameParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameProviderByNameResponse, error) { + rsp, err := c.DeleteV0CityByCityNameProviderByName(ctx, cityName, name, params, reqEditors...) if err != nil { return nil, err } @@ -19355,16 +20935,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameProviderByNameWithResponse(ctx } // PatchV0CityByCityNameProviderByNameWithBodyWithResponse request with arbitrary body returning *PatchV0CityByCityNameProviderByNameResponse -func (c *ClientWithResponses) PatchV0CityByCityNameProviderByNameWithBodyWithResponse(ctx context.Context, cityName string, name string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameProviderByNameResponse, error) { - rsp, err := c.PatchV0CityByCityNameProviderByNameWithBody(ctx, cityName, name, contentType, body, reqEditors...) +func (c *ClientWithResponses) PatchV0CityByCityNameProviderByNameWithBodyWithResponse(ctx context.Context, cityName string, name string, params *PatchV0CityByCityNameProviderByNameParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameProviderByNameResponse, error) { + rsp, err := c.PatchV0CityByCityNameProviderByNameWithBody(ctx, cityName, name, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePatchV0CityByCityNameProviderByNameResponse(rsp) } -func (c *ClientWithResponses) PatchV0CityByCityNameProviderByNameWithResponse(ctx context.Context, cityName string, name string, body PatchV0CityByCityNameProviderByNameJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameProviderByNameResponse, error) { - rsp, err := c.PatchV0CityByCityNameProviderByName(ctx, cityName, name, body, reqEditors...) +func (c *ClientWithResponses) PatchV0CityByCityNameProviderByNameWithResponse(ctx context.Context, cityName string, name string, params *PatchV0CityByCityNameProviderByNameParams, body PatchV0CityByCityNameProviderByNameJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameProviderByNameResponse, error) { + rsp, err := c.PatchV0CityByCityNameProviderByName(ctx, cityName, name, params, body, reqEditors...) if err != nil { return nil, err } @@ -19381,16 +20961,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameProvidersWithResponse(ctx conte } // CreateProviderWithBodyWithResponse request with arbitrary body returning *CreateProviderResponse -func (c *ClientWithResponses) CreateProviderWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateProviderResponse, error) { - rsp, err := c.CreateProviderWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) CreateProviderWithBodyWithResponse(ctx context.Context, cityName string, params *CreateProviderParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateProviderResponse, error) { + rsp, err := c.CreateProviderWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseCreateProviderResponse(rsp) } -func (c *ClientWithResponses) CreateProviderWithResponse(ctx context.Context, cityName string, body CreateProviderJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateProviderResponse, error) { - rsp, err := c.CreateProvider(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) CreateProviderWithResponse(ctx context.Context, cityName string, params *CreateProviderParams, body CreateProviderJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateProviderResponse, error) { + rsp, err := c.CreateProvider(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -19416,8 +20996,8 @@ func (c *ClientWithResponses) GetV0CityByCityNameReadinessWithResponse(ctx conte } // DeleteV0CityByCityNameRigByNameWithResponse request returning *DeleteV0CityByCityNameRigByNameResponse -func (c *ClientWithResponses) DeleteV0CityByCityNameRigByNameWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameRigByNameResponse, error) { - rsp, err := c.DeleteV0CityByCityNameRigByName(ctx, cityName, name, reqEditors...) +func (c *ClientWithResponses) DeleteV0CityByCityNameRigByNameWithResponse(ctx context.Context, cityName string, name string, params *DeleteV0CityByCityNameRigByNameParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameRigByNameResponse, error) { + rsp, err := c.DeleteV0CityByCityNameRigByName(ctx, cityName, name, params, reqEditors...) if err != nil { return nil, err } @@ -19434,16 +21014,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameRigByNameWithResponse(ctx conte } // PatchV0CityByCityNameRigByNameWithBodyWithResponse request with arbitrary body returning *PatchV0CityByCityNameRigByNameResponse -func (c *ClientWithResponses) PatchV0CityByCityNameRigByNameWithBodyWithResponse(ctx context.Context, cityName string, name string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameRigByNameResponse, error) { - rsp, err := c.PatchV0CityByCityNameRigByNameWithBody(ctx, cityName, name, contentType, body, reqEditors...) +func (c *ClientWithResponses) PatchV0CityByCityNameRigByNameWithBodyWithResponse(ctx context.Context, cityName string, name string, params *PatchV0CityByCityNameRigByNameParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameRigByNameResponse, error) { + rsp, err := c.PatchV0CityByCityNameRigByNameWithBody(ctx, cityName, name, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePatchV0CityByCityNameRigByNameResponse(rsp) } -func (c *ClientWithResponses) PatchV0CityByCityNameRigByNameWithResponse(ctx context.Context, cityName string, name string, body PatchV0CityByCityNameRigByNameJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameRigByNameResponse, error) { - rsp, err := c.PatchV0CityByCityNameRigByName(ctx, cityName, name, body, reqEditors...) +func (c *ClientWithResponses) PatchV0CityByCityNameRigByNameWithResponse(ctx context.Context, cityName string, name string, params *PatchV0CityByCityNameRigByNameParams, body PatchV0CityByCityNameRigByNameJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameRigByNameResponse, error) { + rsp, err := c.PatchV0CityByCityNameRigByName(ctx, cityName, name, params, body, reqEditors...) if err != nil { return nil, err } @@ -19451,8 +21031,8 @@ func (c *ClientWithResponses) PatchV0CityByCityNameRigByNameWithResponse(ctx con } // PostV0CityByCityNameRigByNameByActionWithResponse request returning *PostV0CityByCityNameRigByNameByActionResponse -func (c *ClientWithResponses) PostV0CityByCityNameRigByNameByActionWithResponse(ctx context.Context, cityName string, name string, action string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameRigByNameByActionResponse, error) { - rsp, err := c.PostV0CityByCityNameRigByNameByAction(ctx, cityName, name, action, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameRigByNameByActionWithResponse(ctx context.Context, cityName string, name string, action string, params *PostV0CityByCityNameRigByNameByActionParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameRigByNameByActionResponse, error) { + rsp, err := c.PostV0CityByCityNameRigByNameByAction(ctx, cityName, name, action, params, reqEditors...) if err != nil { return nil, err } @@ -19469,16 +21049,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameRigsWithResponse(ctx context.Co } // CreateRigWithBodyWithResponse request with arbitrary body returning *CreateRigResponse -func (c *ClientWithResponses) CreateRigWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateRigResponse, error) { - rsp, err := c.CreateRigWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) CreateRigWithBodyWithResponse(ctx context.Context, cityName string, params *CreateRigParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateRigResponse, error) { + rsp, err := c.CreateRigWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseCreateRigResponse(rsp) } -func (c *ClientWithResponses) CreateRigWithResponse(ctx context.Context, cityName string, body CreateRigJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateRigResponse, error) { - rsp, err := c.CreateRig(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) CreateRigWithResponse(ctx context.Context, cityName string, params *CreateRigParams, body CreateRigJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateRigResponse, error) { + rsp, err := c.CreateRig(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -19495,8 +21075,8 @@ func (c *ClientWithResponses) GetV0CityByCityNameServiceByNameWithResponse(ctx c } // PostV0CityByCityNameServiceByNameRestartWithResponse request returning *PostV0CityByCityNameServiceByNameRestartResponse -func (c *ClientWithResponses) PostV0CityByCityNameServiceByNameRestartWithResponse(ctx context.Context, cityName string, name string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameServiceByNameRestartResponse, error) { - rsp, err := c.PostV0CityByCityNameServiceByNameRestart(ctx, cityName, name, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameServiceByNameRestartWithResponse(ctx context.Context, cityName string, name string, params *PostV0CityByCityNameServiceByNameRestartParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameServiceByNameRestartResponse, error) { + rsp, err := c.PostV0CityByCityNameServiceByNameRestart(ctx, cityName, name, params, reqEditors...) if err != nil { return nil, err } @@ -19522,16 +21102,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameSessionByIdWithResponse(ctx con } // PatchV0CityByCityNameSessionByIdWithBodyWithResponse request with arbitrary body returning *PatchV0CityByCityNameSessionByIdResponse -func (c *ClientWithResponses) PatchV0CityByCityNameSessionByIdWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameSessionByIdResponse, error) { - rsp, err := c.PatchV0CityByCityNameSessionByIdWithBody(ctx, cityName, id, contentType, body, reqEditors...) +func (c *ClientWithResponses) PatchV0CityByCityNameSessionByIdWithBodyWithResponse(ctx context.Context, cityName string, id string, params *PatchV0CityByCityNameSessionByIdParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameSessionByIdResponse, error) { + rsp, err := c.PatchV0CityByCityNameSessionByIdWithBody(ctx, cityName, id, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePatchV0CityByCityNameSessionByIdResponse(rsp) } -func (c *ClientWithResponses) PatchV0CityByCityNameSessionByIdWithResponse(ctx context.Context, cityName string, id string, body PatchV0CityByCityNameSessionByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameSessionByIdResponse, error) { - rsp, err := c.PatchV0CityByCityNameSessionById(ctx, cityName, id, body, reqEditors...) +func (c *ClientWithResponses) PatchV0CityByCityNameSessionByIdWithResponse(ctx context.Context, cityName string, id string, params *PatchV0CityByCityNameSessionByIdParams, body PatchV0CityByCityNameSessionByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchV0CityByCityNameSessionByIdResponse, error) { + rsp, err := c.PatchV0CityByCityNameSessionById(ctx, cityName, id, params, body, reqEditors...) if err != nil { return nil, err } @@ -19566,8 +21146,8 @@ func (c *ClientWithResponses) PostV0CityByCityNameSessionByIdCloseWithResponse(c } // PostV0CityByCityNameSessionByIdKillWithResponse request returning *PostV0CityByCityNameSessionByIdKillResponse -func (c *ClientWithResponses) PostV0CityByCityNameSessionByIdKillWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdKillResponse, error) { - rsp, err := c.PostV0CityByCityNameSessionByIdKill(ctx, cityName, id, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameSessionByIdKillWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdKillParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdKillResponse, error) { + rsp, err := c.PostV0CityByCityNameSessionByIdKill(ctx, cityName, id, params, reqEditors...) if err != nil { return nil, err } @@ -19575,16 +21155,16 @@ func (c *ClientWithResponses) PostV0CityByCityNameSessionByIdKillWithResponse(ct } // SendSessionMessageWithBodyWithResponse request with arbitrary body returning *SendSessionMessageResponse -func (c *ClientWithResponses) SendSessionMessageWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SendSessionMessageResponse, error) { - rsp, err := c.SendSessionMessageWithBody(ctx, cityName, id, contentType, body, reqEditors...) +func (c *ClientWithResponses) SendSessionMessageWithBodyWithResponse(ctx context.Context, cityName string, id string, params *SendSessionMessageParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SendSessionMessageResponse, error) { + rsp, err := c.SendSessionMessageWithBody(ctx, cityName, id, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseSendSessionMessageResponse(rsp) } -func (c *ClientWithResponses) SendSessionMessageWithResponse(ctx context.Context, cityName string, id string, body SendSessionMessageJSONRequestBody, reqEditors ...RequestEditorFn) (*SendSessionMessageResponse, error) { - rsp, err := c.SendSessionMessage(ctx, cityName, id, body, reqEditors...) +func (c *ClientWithResponses) SendSessionMessageWithResponse(ctx context.Context, cityName string, id string, params *SendSessionMessageParams, body SendSessionMessageJSONRequestBody, reqEditors ...RequestEditorFn) (*SendSessionMessageResponse, error) { + rsp, err := c.SendSessionMessage(ctx, cityName, id, params, body, reqEditors...) if err != nil { return nil, err } @@ -19601,16 +21181,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameSessionByIdPendingWithResponse( } // PostV0CityByCityNameSessionByIdRenameWithBodyWithResponse request with arbitrary body returning *PostV0CityByCityNameSessionByIdRenameResponse -func (c *ClientWithResponses) PostV0CityByCityNameSessionByIdRenameWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdRenameResponse, error) { - rsp, err := c.PostV0CityByCityNameSessionByIdRenameWithBody(ctx, cityName, id, contentType, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameSessionByIdRenameWithBodyWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdRenameParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdRenameResponse, error) { + rsp, err := c.PostV0CityByCityNameSessionByIdRenameWithBody(ctx, cityName, id, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePostV0CityByCityNameSessionByIdRenameResponse(rsp) } -func (c *ClientWithResponses) PostV0CityByCityNameSessionByIdRenameWithResponse(ctx context.Context, cityName string, id string, body PostV0CityByCityNameSessionByIdRenameJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdRenameResponse, error) { - rsp, err := c.PostV0CityByCityNameSessionByIdRename(ctx, cityName, id, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameSessionByIdRenameWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdRenameParams, body PostV0CityByCityNameSessionByIdRenameJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdRenameResponse, error) { + rsp, err := c.PostV0CityByCityNameSessionByIdRename(ctx, cityName, id, params, body, reqEditors...) if err != nil { return nil, err } @@ -19618,16 +21198,16 @@ func (c *ClientWithResponses) PostV0CityByCityNameSessionByIdRenameWithResponse( } // RespondSessionWithBodyWithResponse request with arbitrary body returning *RespondSessionResponse -func (c *ClientWithResponses) RespondSessionWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RespondSessionResponse, error) { - rsp, err := c.RespondSessionWithBody(ctx, cityName, id, contentType, body, reqEditors...) +func (c *ClientWithResponses) RespondSessionWithBodyWithResponse(ctx context.Context, cityName string, id string, params *RespondSessionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RespondSessionResponse, error) { + rsp, err := c.RespondSessionWithBody(ctx, cityName, id, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseRespondSessionResponse(rsp) } -func (c *ClientWithResponses) RespondSessionWithResponse(ctx context.Context, cityName string, id string, body RespondSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*RespondSessionResponse, error) { - rsp, err := c.RespondSession(ctx, cityName, id, body, reqEditors...) +func (c *ClientWithResponses) RespondSessionWithResponse(ctx context.Context, cityName string, id string, params *RespondSessionParams, body RespondSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*RespondSessionResponse, error) { + rsp, err := c.RespondSession(ctx, cityName, id, params, body, reqEditors...) if err != nil { return nil, err } @@ -19635,8 +21215,8 @@ func (c *ClientWithResponses) RespondSessionWithResponse(ctx context.Context, ci } // PostV0CityByCityNameSessionByIdStopWithResponse request returning *PostV0CityByCityNameSessionByIdStopResponse -func (c *ClientWithResponses) PostV0CityByCityNameSessionByIdStopWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdStopResponse, error) { - rsp, err := c.PostV0CityByCityNameSessionByIdStop(ctx, cityName, id, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameSessionByIdStopWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdStopParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdStopResponse, error) { + rsp, err := c.PostV0CityByCityNameSessionByIdStop(ctx, cityName, id, params, reqEditors...) if err != nil { return nil, err } @@ -19653,16 +21233,16 @@ func (c *ClientWithResponses) StreamSessionWithResponse(ctx context.Context, cit } // SubmitSessionWithBodyWithResponse request with arbitrary body returning *SubmitSessionResponse -func (c *ClientWithResponses) SubmitSessionWithBodyWithResponse(ctx context.Context, cityName string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SubmitSessionResponse, error) { - rsp, err := c.SubmitSessionWithBody(ctx, cityName, id, contentType, body, reqEditors...) +func (c *ClientWithResponses) SubmitSessionWithBodyWithResponse(ctx context.Context, cityName string, id string, params *SubmitSessionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SubmitSessionResponse, error) { + rsp, err := c.SubmitSessionWithBody(ctx, cityName, id, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseSubmitSessionResponse(rsp) } -func (c *ClientWithResponses) SubmitSessionWithResponse(ctx context.Context, cityName string, id string, body SubmitSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*SubmitSessionResponse, error) { - rsp, err := c.SubmitSession(ctx, cityName, id, body, reqEditors...) +func (c *ClientWithResponses) SubmitSessionWithResponse(ctx context.Context, cityName string, id string, params *SubmitSessionParams, body SubmitSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*SubmitSessionResponse, error) { + rsp, err := c.SubmitSession(ctx, cityName, id, params, body, reqEditors...) if err != nil { return nil, err } @@ -19670,8 +21250,8 @@ func (c *ClientWithResponses) SubmitSessionWithResponse(ctx context.Context, cit } // PostV0CityByCityNameSessionByIdSuspendWithResponse request returning *PostV0CityByCityNameSessionByIdSuspendResponse -func (c *ClientWithResponses) PostV0CityByCityNameSessionByIdSuspendWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdSuspendResponse, error) { - rsp, err := c.PostV0CityByCityNameSessionByIdSuspend(ctx, cityName, id, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameSessionByIdSuspendWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdSuspendParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdSuspendResponse, error) { + rsp, err := c.PostV0CityByCityNameSessionByIdSuspend(ctx, cityName, id, params, reqEditors...) if err != nil { return nil, err } @@ -19688,8 +21268,8 @@ func (c *ClientWithResponses) GetV0CityByCityNameSessionByIdTranscriptWithRespon } // PostV0CityByCityNameSessionByIdWakeWithResponse request returning *PostV0CityByCityNameSessionByIdWakeResponse -func (c *ClientWithResponses) PostV0CityByCityNameSessionByIdWakeWithResponse(ctx context.Context, cityName string, id string, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdWakeResponse, error) { - rsp, err := c.PostV0CityByCityNameSessionByIdWake(ctx, cityName, id, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameSessionByIdWakeWithResponse(ctx context.Context, cityName string, id string, params *PostV0CityByCityNameSessionByIdWakeParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSessionByIdWakeResponse, error) { + rsp, err := c.PostV0CityByCityNameSessionByIdWake(ctx, cityName, id, params, reqEditors...) if err != nil { return nil, err } @@ -19706,16 +21286,16 @@ func (c *ClientWithResponses) GetV0CityByCityNameSessionsWithResponse(ctx contex } // CreateSessionWithBodyWithResponse request with arbitrary body returning *CreateSessionResponse -func (c *ClientWithResponses) CreateSessionWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateSessionResponse, error) { - rsp, err := c.CreateSessionWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) CreateSessionWithBodyWithResponse(ctx context.Context, cityName string, params *CreateSessionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateSessionResponse, error) { + rsp, err := c.CreateSessionWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseCreateSessionResponse(rsp) } -func (c *ClientWithResponses) CreateSessionWithResponse(ctx context.Context, cityName string, body CreateSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateSessionResponse, error) { - rsp, err := c.CreateSession(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) CreateSessionWithResponse(ctx context.Context, cityName string, params *CreateSessionParams, body CreateSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateSessionResponse, error) { + rsp, err := c.CreateSession(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -19723,16 +21303,16 @@ func (c *ClientWithResponses) CreateSessionWithResponse(ctx context.Context, cit } // PostV0CityByCityNameSlingWithBodyWithResponse request with arbitrary body returning *PostV0CityByCityNameSlingResponse -func (c *ClientWithResponses) PostV0CityByCityNameSlingWithBodyWithResponse(ctx context.Context, cityName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSlingResponse, error) { - rsp, err := c.PostV0CityByCityNameSlingWithBody(ctx, cityName, contentType, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameSlingWithBodyWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameSlingParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSlingResponse, error) { + rsp, err := c.PostV0CityByCityNameSlingWithBody(ctx, cityName, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePostV0CityByCityNameSlingResponse(rsp) } -func (c *ClientWithResponses) PostV0CityByCityNameSlingWithResponse(ctx context.Context, cityName string, body PostV0CityByCityNameSlingJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSlingResponse, error) { - rsp, err := c.PostV0CityByCityNameSling(ctx, cityName, body, reqEditors...) +func (c *ClientWithResponses) PostV0CityByCityNameSlingWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameSlingParams, body PostV0CityByCityNameSlingJSONRequestBody, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameSlingResponse, error) { + rsp, err := c.PostV0CityByCityNameSling(ctx, cityName, params, body, reqEditors...) if err != nil { return nil, err } @@ -19748,6 +21328,15 @@ func (c *ClientWithResponses) GetV0CityByCityNameStatusWithResponse(ctx context. return ParseGetV0CityByCityNameStatusResponse(rsp) } +// PostV0CityByCityNameUnregisterWithResponse request returning *PostV0CityByCityNameUnregisterResponse +func (c *ClientWithResponses) PostV0CityByCityNameUnregisterWithResponse(ctx context.Context, cityName string, params *PostV0CityByCityNameUnregisterParams, reqEditors ...RequestEditorFn) (*PostV0CityByCityNameUnregisterResponse, error) { + rsp, err := c.PostV0CityByCityNameUnregister(ctx, cityName, params, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostV0CityByCityNameUnregisterResponse(rsp) +} + // DeleteV0CityByCityNameWorkflowByWorkflowIdWithResponse request returning *DeleteV0CityByCityNameWorkflowByWorkflowIdResponse func (c *ClientWithResponses) DeleteV0CityByCityNameWorkflowByWorkflowIdWithResponse(ctx context.Context, cityName string, workflowId string, params *DeleteV0CityByCityNameWorkflowByWorkflowIdParams, reqEditors ...RequestEditorFn) (*DeleteV0CityByCityNameWorkflowByWorkflowIdResponse, error) { rsp, err := c.DeleteV0CityByCityNameWorkflowByWorkflowId(ctx, cityName, workflowId, params, reqEditors...) @@ -19882,12 +21471,12 @@ func ParsePostV0CityResponse(rsp *http.Response) (*PostV0CityResponse, error) { } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: var dest CityCreateResponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON202 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: var dest ErrorModel @@ -24262,6 +25851,39 @@ func ParseGetV0CityByCityNameStatusResponse(rsp *http.Response) (*GetV0CityByCit return response, nil } +// ParsePostV0CityByCityNameUnregisterResponse parses an HTTP response from a PostV0CityByCityNameUnregisterWithResponse call +func ParsePostV0CityByCityNameUnregisterResponse(rsp *http.Response) (*PostV0CityByCityNameUnregisterResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PostV0CityByCityNameUnregisterResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: + var dest CityUnregisterResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON202 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: + var dest ErrorModel + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.ApplicationproblemJSONDefault = &dest + + } + + return response, nil +} + // ParseDeleteV0CityByCityNameWorkflowByWorkflowIdResponse parses an HTTP response from a DeleteV0CityByCityNameWorkflowByWorkflowIdWithResponse call func ParseDeleteV0CityByCityNameWorkflowByWorkflowIdResponse(rsp *http.Response) (*DeleteV0CityByCityNameWorkflowByWorkflowIdResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/internal/api/genclient_roundtrip_test.go b/internal/api/genclient_roundtrip_test.go index f7821a836d..8d684ef55b 100644 --- a/internal/api/genclient_roundtrip_test.go +++ b/internal/api/genclient_roundtrip_test.go @@ -194,7 +194,7 @@ func TestGenClientRoundTripConvoyCreate(t *testing.T) { Title: "round-trip convoy", Items: &items, } - resp, err := client.CreateConvoyWithResponse(context.Background(), state.CityName(), body) + resp, err := client.CreateConvoyWithResponse(context.Background(), state.CityName(), nil, body) if err != nil { t.Fatalf("CreateConvoy: %v", err) } diff --git a/internal/api/huma_handlers_supervisor.go b/internal/api/huma_handlers_supervisor.go index 0c7c92fbea..567784b1b1 100644 --- a/internal/api/huma_handlers_supervisor.go +++ b/internal/api/huma_handlers_supervisor.go @@ -1,14 +1,12 @@ package api import ( - "bytes" "context" "errors" "fmt" "log" "net/http" "os" - "os/exec" "path/filepath" "sort" "strings" @@ -16,8 +14,8 @@ import ( "github.com/danielgtaylor/huma/v2" "github.com/danielgtaylor/huma/v2/adapters/humago" + "github.com/gastownhall/gascity/internal/cityinit" "github.com/gastownhall/gascity/internal/citylayout" - "github.com/gastownhall/gascity/internal/config" "github.com/gastownhall/gascity/internal/events" ) @@ -79,10 +77,21 @@ type cityCreateRequest struct { BootstrapProfile string `json:"bootstrap_profile,omitempty" enum:"k8s-cell,kubernetes,kubernetes-cell,single-host-compat" doc:"Optional bootstrap profile."` } -// cityCreateResponse is the response body for POST /v0/city. +// cityCreateResponse is the response body for POST /v0/city. This +// endpoint is asynchronous: a 202 response means the city was +// scaffolded on disk and registered with the supervisor, but the +// supervisor reconciler still has to run the slow finalize work +// (pack materialization, bead store startup, formula resolution, +// agent validation). Clients observe completion by subscribing to +// /v0/events/stream and waiting for a city.ready event (payload +// CityReadyPayload.Name == this response's Name) or a +// city.init_failed event (CityInitFailedPayload.Name == this +// response's Name; Error field explains the failure). Polling is +// unnecessary. type cityCreateResponse struct { - OK bool `json:"ok" doc:"True on success."` - Path string `json:"path" doc:"Resolved absolute path of the created city."` + OK bool `json:"ok" doc:"True when scaffolding + registration succeeded. Does not imply the city is ready yet; watch /v0/events/stream for city.ready."` + Name string `json:"name" doc:"Resolved city name as persisted in city.toml. Use this to filter the event stream for completion."` + Path string `json:"path" doc:"Resolved absolute path of the created city directory."` } // SupervisorCityCreateInput is the input for POST /v0/city. @@ -90,9 +99,41 @@ type SupervisorCityCreateInput struct { Body cityCreateRequest } -// SupervisorCityCreateOutput is the response for POST /v0/city. +// SupervisorCityCreateOutput is the response for POST /v0/city. The +// Status field carries 202 Accepted to tell Huma to emit the async +// status code; see humaHandleCityCreate for the rationale and event +// contract. type SupervisorCityCreateOutput struct { - Body cityCreateResponse + Status int `json:"-"` + Body cityCreateResponse +} + +// cityUnregisterResponse is the response body for +// POST /v0/city/{cityName}/unregister. This endpoint is asynchronous: +// a 202 response means the city's registry entry was removed and the +// supervisor was signaled to reconcile, but the city's controller is +// not yet stopped. Clients observe completion by subscribing to +// /v0/events/stream and waiting for a city.unregistered event (or +// city.unregister_failed if the reconciler cannot stop the +// controller). +type cityUnregisterResponse struct { + OK bool `json:"ok" doc:"True when the registry entry was removed and the supervisor was signaled. Does not imply the city's controller has stopped yet; watch /v0/events/stream for city.unregistered."` + Name string `json:"name" doc:"Resolved registry name. Filter the event stream by this to observe completion."` + Path string `json:"path" doc:"Resolved absolute city directory. The directory itself is not modified; unregister only affects the supervisor's registry."` +} + +// SupervisorCityUnregisterInput is the input for +// POST /v0/city/{cityName}/unregister. +type SupervisorCityUnregisterInput struct { + CityName string `path:"cityName" doc:"Supervisor-registered city name."` +} + +// SupervisorCityUnregisterOutput is the response for +// POST /v0/city/{cityName}/unregister. The Status field carries +// 202 Accepted to tell Huma to emit the async status code. +type SupervisorCityUnregisterOutput struct { + Status int `json:"-"` + Body cityUnregisterResponse } // SupervisorEventListInput is the input for GET /v0/events (supervisor scope). @@ -117,12 +158,6 @@ type SupervisorEventStreamInput struct { AfterCursor string `query:"after_cursor" required:"false" doc:"Alternative to Last-Event-ID for browsers that can't set custom headers."` } -// cityInitExitAlreadyInitialized mirrors initExitAlreadyInitialized in -// cmd/gc/cmd_init.go. Duplicated here because the CLI's exit-code -// constant is declared in the main package and not importable; the two -// must stay in sync. -const cityInitExitAlreadyInitialized = 2 - // --- Huma API setup --- // newSupervisorHumaAPI builds a huma.API attached to mux for supervisor- @@ -178,7 +213,18 @@ func (sm *SupervisorMux) registerSupervisorRoutes() { huma.Get(sm.humaAPI, "/health", sm.humaHandleHealth) huma.Get(sm.humaAPI, "/v0/readiness", sm.humaHandleReadiness) huma.Get(sm.humaAPI, "/v0/provider-readiness", sm.humaHandleProviderReadiness) - huma.Post(sm.humaAPI, "/v0/city", sm.humaHandleCityCreate) + // Async mutation: returns 202 Accepted after scaffold + register; + // completion signaled via city.ready / city.init_failed events. + huma.Post(sm.humaAPI, "/v0/city", sm.humaHandleCityCreate, addMutationCSRFParam, func(op *huma.Operation) { + op.DefaultStatus = http.StatusAccepted + }) + // Async unregister: returns 202 after the registry entry is + // removed and the supervisor is signaled. city.unregistered / + // city.unregister_failed events signal completion on the event + // stream. + huma.Post(sm.humaAPI, "/v0/city/{cityName}/unregister", sm.humaHandleCityUnregister, addMutationCSRFParam, func(op *huma.Operation) { + op.DefaultStatus = http.StatusAccepted + }) huma.Get(sm.humaAPI, "/v0/events", sm.humaHandleEventList) registerSSEStringID(sm.humaAPI, huma.Operation{ @@ -276,17 +322,24 @@ func (sm *SupervisorMux) humaHandleProviderReadiness(ctx context.Context, input return out, nil } -// humaHandleCityCreate handles POST /v0/city — create a new city by -// shelling out to `gc init`. Stateless; does not require a running city. +// humaHandleCityCreate handles POST /v0/city asynchronously. Calls +// Initializer.Scaffold in-process to write the on-disk shape and +// register the city with the supervisor, then returns 202 Accepted +// immediately. The supervisor reconciler runs the slow finalize +// (prepareCityForSupervisor: pack materialization, bead store +// startup, formula resolution, agent validation) on its next tick +// and emits city.ready / city.init_failed events on the supervisor +// event bus when done. Clients observe completion via +// /v0/events/stream — no polling required. +// +// Rationale: full city init takes minutes (dolt startup, +// provider-readiness probes, pack fetch). Blocking the HTTP request +// until finalize completes exceeds reasonable client timeouts +// (MC's harness hit 120s). The fast scaffold+register path takes +// seconds; the async completion contract via SSE is the right shape +// for a long-running operation. See specs/architecture.md §1–§2 on +// the object model + typed events; §4 on the event registry. func (sm *SupervisorMux) humaHandleCityCreate(ctx context.Context, input *SupervisorCityCreateInput) (*SupervisorCityCreateOutput, error) { - // Dir/Provider emptiness is enforced by minLength:"1" tags on cityCreateRequest. - // BootstrapProfile membership is enforced by the enum tag. - // Provider membership against runtime-loaded builtins stays here — - // static enum can't express a runtime-loaded set. - if _, ok := config.BuiltinProviders()[input.Body.Provider]; !ok { - return nil, huma.Error422UnprocessableEntity(fmt.Sprintf("invalid: unknown provider %q", input.Body.Provider)) - } - dir := input.Body.Dir if !filepath.IsAbs(dir) { home, err := os.UserHomeDir() @@ -296,46 +349,90 @@ func (sm *SupervisorMux) humaHandleCityCreate(ctx context.Context, input *Superv dir = filepath.Join(home, dir) } - if err := os.MkdirAll(dir, 0o755); err != nil { - return nil, huma.Error500InternalServerError(fmt.Sprintf("internal: creating directory: %v", err)) - } + // Cheap pre-check that does not require an Initializer: if the + // target directory already looks like an initialized city on disk, + // return 409 before we try to scaffold. Keeps the API well-behaved + // in test configurations that build a SupervisorMux without an + // Initializer. if cityDirAlreadyInitialized(dir) { return nil, huma.Error409Conflict("conflict: city already initialized at " + dir) } - gcBin, err := os.Executable() - if err != nil { - return nil, huma.Error500InternalServerError(fmt.Sprintf("internal: finding gc binary: %v", err)) + if sm.initializer == nil { + return nil, huma.Error501NotImplemented("city creation is not available in this supervisor (no initializer wired)") } - args := []string{"init", dir, "--provider", input.Body.Provider} - if input.Body.BootstrapProfile != "" { - args = append(args, "--bootstrap-profile", input.Body.BootstrapProfile) + + result, err := sm.initializer.Scaffold(ctx, cityinit.InitRequest{ + Dir: dir, + Provider: input.Body.Provider, + BootstrapProfile: input.Body.BootstrapProfile, + // API callers observe provider readiness through + // GET /v0/provider-readiness; finalize's preflight is + // CLI-only anyway. + SkipProviderReadiness: true, + }) + switch { + case errors.Is(err, cityinit.ErrAlreadyInitialized): + return nil, huma.Error409Conflict("conflict: city already initialized at " + dir) + case errors.Is(err, cityinit.ErrInvalidProvider), + errors.Is(err, cityinit.ErrInvalidBootstrapProfile): + return nil, huma.Error422UnprocessableEntity(err.Error()) + case errors.Is(err, cityinit.ErrMissingDependency), + errors.Is(err, cityinit.ErrProviderNotReady): + return nil, huma.Error503ServiceUnavailable(err.Error()) + case err != nil: + return nil, huma.Error500InternalServerError(err.Error()) } - cctx, cancel := context.WithTimeout(ctx, 30*time.Second) - defer cancel() + out := &SupervisorCityCreateOutput{ + Status: http.StatusAccepted, + } + out.Body = cityCreateResponse{ + OK: true, + Name: result.CityName, + Path: result.CityPath, + } + return out, nil +} - cmd := exec.CommandContext(cctx, gcBin, args...) - var stderr bytes.Buffer - cmd.Stderr = &stderr +// humaHandleCityUnregister handles POST /v0/city/{cityName}/unregister +// asynchronously. Calls Initializer.Unregister in-process to remove +// the city from the supervisor's registry and signal reconcile, then +// returns 202 Accepted immediately. The supervisor reconciler stops +// the city's controller on its next tick and emits city.unregistered +// (or city.unregister_failed on stop failure) on the supervisor +// event bus. Clients observe completion via /v0/events/stream — no +// polling required. +// +// The city directory itself is not modified. Purging the directory +// is a separate concern. +// +// Error mapping: +// - ErrNotRegistered -> 404 Not Found +// - any other error -> 500 Internal Server Error +func (sm *SupervisorMux) humaHandleCityUnregister(ctx context.Context, input *SupervisorCityUnregisterInput) (*SupervisorCityUnregisterOutput, error) { + if sm.initializer == nil { + return nil, huma.Error501NotImplemented("city unregister is not available in this supervisor (no initializer wired)") + } + name := strings.TrimSpace(input.CityName) + if name == "" { + return nil, huma.Error400BadRequest("city_name is required") + } - if err := cmd.Run(); err != nil { - msg := stderr.String() - if msg == "" { - msg = err.Error() - } - // gc init exits with initExitAlreadyInitialized when the - // target already contains a city. Dispatch on the exit code - // rather than scraping stderr text. - var exitErr *exec.ExitError - if errors.As(err, &exitErr) && exitErr.ExitCode() == cityInitExitAlreadyInitialized { - return nil, huma.Error409Conflict("conflict: city already initialized at " + dir) - } - return nil, huma.Error500InternalServerError("init_failed: " + msg) + result, err := sm.initializer.Unregister(ctx, cityinit.UnregisterRequest{CityName: name}) + switch { + case errors.Is(err, cityinit.ErrNotRegistered): + return nil, huma.Error404NotFound("not_found: " + err.Error()) + case err != nil: + return nil, huma.Error500InternalServerError(err.Error()) } - out := &SupervisorCityCreateOutput{} - out.Body = cityCreateResponse{OK: true, Path: dir} + out := &SupervisorCityUnregisterOutput{Status: http.StatusAccepted} + out.Body = cityUnregisterResponse{ + OK: true, + Name: result.CityName, + Path: result.CityPath, + } return out, nil } @@ -380,10 +477,9 @@ func (sm *SupervisorMux) humaHandleEventList(_ context.Context, input *Superviso // Total is the full match count so clients can distinguish "limit // truncated" from "the server only had N events." out.Body.Total = len(wires) - // Limit clamp: when a caller asks for limit=N, return the N most - // recent events (the list is already chronologically ordered, so we - // take the tail). Critical for `gc events --seq` which computes the - // head cursor from the last event only. + // Limit clamp: take the N most recent events (wires is already + // chronologically ordered). Critical for `gc events --seq` which + // computes the head cursor from the last event only. if input.Limit > 0 && input.Limit < len(wires) { wires = wires[len(wires)-input.Limit:] } @@ -393,11 +489,16 @@ func (sm *SupervisorMux) humaHandleEventList(_ context.Context, input *Superviso // --- Supervisor global events stream (Fix 3g final wiring) --- -// precheckGlobalEventStream validates that the global event stream can -// actually deliver events before committing 200 headers. Two failure -// modes both produce 503 Problem Details instead of 200+EOF: +// precheckGlobalEventStream validates that the global event stream +// can actually deliver events before committing 200 headers. Two +// failure modes both produce 503 Problem Details instead of 200+EOF: // -// 1. No event providers registered at all (empty mux). +// 1. No event providers registered at all (empty mux). In practice +// this only happens when zero cities are registered in the +// supervisor — the TransientCityEventSource resolver extension +// surfaces event files for every registered city (running, +// pending, or failed) so any POST /v0/city → subscribe flow +// finds the newly-registered city in the mux. // 2. Providers exist but none can attach a watcher right now. // // The precheck attaches a watcher and closes it immediately — a diff --git a/internal/api/huma_optional_param.go b/internal/api/huma_optional_param.go new file mode 100644 index 0000000000..13f47baef4 --- /dev/null +++ b/internal/api/huma_optional_param.go @@ -0,0 +1,58 @@ +package api + +// OptionalParam wraps a query-parameter value with a presence flag so +// handlers can distinguish "parameter absent" from "parameter present +// with zero value" without reading raw URL values. +// +// This is the Huma-documented idiom for presence detection on query +// parameters. See https://huma.rocks/features/request-inputs/ and the +// sample in Huma's own huma_test.go. Huma v2 does not support pointer +// query parameters (see github.com/danielgtaylor/huma issue #288), +// which is why this wrapper exists instead of *T. +// +// Usage: +// +// type Input struct { +// Cursor OptionalParam[string] `query:"cursor"` +// } +// +// func h(ctx context.Context, in *Input) (*Out, error) { +// if in.Cursor.IsSet { ... } +// } +// +// The spec emits the underlying T's schema (not the wrapper's), so the +// wire contract is identical to a plain query:"..." field: the only +// difference is server-side presence detection. No architecture.md +// §3.5.1 violation — the handler does not read undeclared URL keys, +// only its own declared field. + +import ( + "reflect" + + "github.com/danielgtaylor/huma/v2" +) + +// OptionalParam holds a query-parameter value and a flag indicating +// whether the client explicitly supplied the parameter. +type OptionalParam[T any] struct { + Value T + IsSet bool +} + +// Schema returns the schema for the wrapped type so the OpenAPI spec +// emits a plain T rather than a struct shape. +func (o OptionalParam[T]) Schema(r huma.Registry) *huma.Schema { + return huma.SchemaFromType(r, reflect.TypeOf(o.Value)) +} + +// Receiver exposes the Value field so Huma's parameter binder writes +// directly into it during request parsing. +func (o *OptionalParam[T]) Receiver() reflect.Value { + return reflect.ValueOf(o).Elem().Field(0) +} + +// OnParamSet is called by Huma after parsing; the isSet flag reflects +// whether the raw parameter was present in the request. +func (o *OptionalParam[T]) OnParamSet(isSet bool, _ any) { + o.IsSet = isSet +} diff --git a/internal/api/huma_spec_framework.go b/internal/api/huma_spec_framework.go new file mode 100644 index 0000000000..752434f043 --- /dev/null +++ b/internal/api/huma_spec_framework.go @@ -0,0 +1,82 @@ +package api + +// Framework-level OpenAPI decoration. +// +// Some wire contract spans every operation rather than any one input +// or output struct: the X-GC-Request-Id response header, written by +// withRequestID middleware on every response, is one such case. OpenAPI +// 3.1 has no mechanism to declare a header "globally"; the canonical +// pattern is to define the header once in components.headers and $ref +// it from each operation's responses (see +// speakeasy.com/openapi/responses/headers). +// +// registerFrameworkHeaders walks the registered OpenAPI document once +// after all routes are registered and adds the $ref entries. Handlers +// don't need to know or declare anything; the middleware remains the +// single source of enforcement, and the spec describes it accurately. + +import ( + "github.com/danielgtaylor/huma/v2" +) + +const ( + requestIDHeaderName = "X-GC-Request-Id" + requestIDHeaderRef = "#/components/headers/" + requestIDHeaderName + requestIDDescription = "Opaque per-response identifier assigned by the server for log correlation. Every response carries this header." +) + +// registerFrameworkHeaders registers reusable response headers in +// components.headers and adds $ref pointers to every registered +// operation's responses. Call once after all routes are registered. +func registerFrameworkHeaders(api huma.API) { + spec := api.OpenAPI() + if spec == nil { + return + } + if spec.Components == nil { + spec.Components = &huma.Components{} + } + if spec.Components.Headers == nil { + spec.Components.Headers = map[string]*huma.Header{} + } + if _, ok := spec.Components.Headers[requestIDHeaderName]; !ok { + spec.Components.Headers[requestIDHeaderName] = &huma.Header{ + Description: requestIDDescription, + Schema: &huma.Schema{ + Type: "string", + Description: requestIDDescription, + }, + } + } + if spec.Paths == nil { + return + } + for _, item := range spec.Paths { + if item == nil { + continue + } + ops := []*huma.Operation{ + item.Get, item.Put, item.Post, item.Patch, item.Delete, + item.Head, item.Options, item.Trace, + } + for _, op := range ops { + if op == nil || op.Responses == nil { + continue + } + for _, resp := range op.Responses { + if resp == nil { + continue + } + if resp.Headers == nil { + resp.Headers = map[string]*huma.Param{} + } + if _, ok := resp.Headers[requestIDHeaderName]; ok { + continue + } + resp.Headers[requestIDHeaderName] = &huma.Param{ + Ref: requestIDHeaderRef, + } + } + } + } +} diff --git a/internal/api/openapi.json b/internal/api/openapi.json index e939ed7f91..0095c093c3 100644 --- a/internal/api/openapi.json +++ b/internal/api/openapi.json @@ -1,5 +1,14 @@ { "components": { + "headers": { + "X-GC-Request-Id": { + "description": "Opaque per-response identifier assigned by the server for log correlation. Every response carries this header.", + "schema": { + "description": "Opaque per-response identifier assigned by the server for log correlation. Every response carries this header.", + "type": "string" + } + } + }, "schemas": { "AdapterCapabilities": { "additionalProperties": false, @@ -1023,17 +1032,38 @@ "CityCreateResponse": { "additionalProperties": false, "properties": { + "name": { + "description": "Resolved city name as persisted in city.toml. Use this to filter the event stream for completion.", + "type": "string" + }, "ok": { - "description": "True on success.", + "description": "True when scaffolding + registration succeeded. Does not imply the city is ready yet; watch /v0/events/stream for city.ready.", "type": "boolean" }, "path": { - "description": "Resolved absolute path of the created city.", + "description": "Resolved absolute path of the created city directory.", "type": "string" } }, "required": [ "ok", + "name", + "path" + ], + "type": "object" + }, + "CityCreatedPayload": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "name", "path" ], "type": "object" @@ -1117,6 +1147,35 @@ ], "type": "object" }, + "CityInitFailedPayload": { + "additionalProperties": false, + "properties": { + "error": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "phases_completed": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + } + }, + "required": [ + "name", + "path", + "error" + ], + "type": "object" + }, "CityPatchInputBody": { "additionalProperties": false, "properties": { @@ -1127,6 +1186,97 @@ }, "type": "object" }, + "CityReadyPayload": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "name", + "path" + ], + "type": "object" + }, + "CityUnregisterFailedPayload": { + "additionalProperties": false, + "properties": { + "error": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "name", + "path", + "error" + ], + "type": "object" + }, + "CityUnregisterRequestedPayload": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "name", + "path" + ], + "type": "object" + }, + "CityUnregisterResponse": { + "additionalProperties": false, + "properties": { + "name": { + "description": "Resolved registry name. Filter the event stream by this to observe completion.", + "type": "string" + }, + "ok": { + "description": "True when the registry entry was removed and the supervisor was signaled. Does not imply the city's controller has stopped yet; watch /v0/events/stream for city.unregistered.", + "type": "boolean" + }, + "path": { + "description": "Resolved absolute city directory. The directory itself is not modified; unregister only affects the supervisor's registry.", + "type": "string" + } + }, + "required": [ + "ok", + "name", + "path" + ], + "type": "object" + }, + "CityUnregisteredPayload": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "name", + "path" + ], + "type": "object" + }, "ConfigAgentResponse": { "additionalProperties": false, "properties": { @@ -1865,6 +2015,24 @@ { "$ref": "#/components/schemas/BoundEventPayload" }, + { + "$ref": "#/components/schemas/CityCreatedPayload" + }, + { + "$ref": "#/components/schemas/CityInitFailedPayload" + }, + { + "$ref": "#/components/schemas/CityReadyPayload" + }, + { + "$ref": "#/components/schemas/CityUnregisterFailedPayload" + }, + { + "$ref": "#/components/schemas/CityUnregisterRequestedPayload" + }, + { + "$ref": "#/components/schemas/CityUnregisteredPayload" + }, { "$ref": "#/components/schemas/GroupCreatedEventPayload" }, @@ -6862,7 +7030,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -6872,7 +7045,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get health" @@ -6890,7 +7068,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -6900,7 +7083,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 cities" @@ -6909,6 +7097,19 @@ "/v0/city": { "post": { "operationId": "post-v0-city", + "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + } + ], "requestBody": { "content": { "application/json": { @@ -6920,7 +7121,7 @@ "required": true }, "responses": { - "200": { + "202": { "content": { "application/json": { "schema": { @@ -6928,7 +7129,12 @@ } } }, - "description": "OK" + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -6938,7 +7144,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city" @@ -6970,7 +7181,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -6980,7 +7196,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name" @@ -6988,6 +7209,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7020,7 +7252,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7030,7 +7267,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name" @@ -7040,6 +7282,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-agent-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7072,7 +7325,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7082,7 +7340,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name agent by base" @@ -7131,6 +7394,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -7142,7 +7408,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name agent by base" @@ -7150,6 +7421,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-agent-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7192,7 +7474,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7202,7 +7489,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name agent by base" @@ -7264,7 +7556,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7274,7 +7571,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name agent by base output" @@ -7375,7 +7677,19 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "GC-Agent-Status": { + "description": "Agent runtime status at the time streaming began. Emitted as \"stopped\" when the agent is not running (the stream then serves replayed transcript from the session log).", + "schema": { + "description": "Agent runtime status at the time streaming began. Emitted as \"stopped\" when the agent is not running (the stream then serves replayed transcript from the session log).", + "type": "string" + } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7385,7 +7699,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Stream agent output in real time" @@ -7395,6 +7714,17 @@ "post": { "operationId": "post-v0-city-by-city-name-agent-by-base-by-action", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7441,17 +7771,27 @@ } } }, - "description": "OK" - }, - "default": { - "content": { + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } + }, + "default": { + "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorModel" } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name agent by base by action" @@ -7461,6 +7801,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-agent-by-dir-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7503,7 +7854,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7513,7 +7869,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name agent by dir by base" @@ -7572,6 +7933,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -7583,7 +7947,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name agent by dir by base" @@ -7591,6 +7960,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-agent-by-dir-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7643,7 +8023,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7653,7 +8038,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name agent by dir by base" @@ -7725,7 +8115,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7735,7 +8130,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name agent by dir by base output" @@ -7846,7 +8246,19 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "GC-Agent-Status": { + "description": "Agent runtime status at the time streaming began. Emitted as \"stopped\" when the agent is not running (the stream then serves replayed transcript from the session log).", + "schema": { + "description": "Agent runtime status at the time streaming began. Emitted as \"stopped\" when the agent is not running (the stream then serves replayed transcript from the session log).", + "type": "string" + } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7856,7 +8268,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Stream agent output in real time (qualified name)" @@ -7866,6 +8283,17 @@ "post": { "operationId": "post-v0-city-by-city-name-agent-by-dir-by-base-by-action", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -7922,7 +8350,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -7932,7 +8365,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name agent by dir by base by action" @@ -8037,6 +8475,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8048,7 +8489,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name agents" @@ -8056,6 +8502,17 @@ "post": { "operationId": "create-agent", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8088,7 +8545,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8098,7 +8560,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create an agent" @@ -8108,6 +8575,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-bead-by-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8140,7 +8618,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8150,7 +8633,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name bead by ID" @@ -8199,6 +8687,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8210,7 +8701,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name bead by ID" @@ -8218,6 +8714,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-bead-by-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8260,7 +8767,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8270,7 +8782,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name bead by ID" @@ -8280,6 +8797,17 @@ "post": { "operationId": "post-v0-city-by-city-name-bead-by-id-assign", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8334,6 +8862,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8345,7 +8876,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name bead by ID assign" @@ -8355,6 +8891,17 @@ "post": { "operationId": "post-v0-city-by-city-name-bead-by-id-close", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8387,7 +8934,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8397,7 +8949,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name bead by ID close" @@ -8448,6 +9005,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8459,7 +9019,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name bead by ID deps" @@ -8469,6 +9034,17 @@ "post": { "operationId": "post-v0-city-by-city-name-bead-by-id-reopen", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8501,7 +9077,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8511,7 +9092,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name bead by ID reopen" @@ -8521,6 +9107,17 @@ "post": { "operationId": "post-v0-city-by-city-name-bead-by-id-update", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8563,7 +9160,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -8573,7 +9175,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name bead by ID update" @@ -8706,6 +9313,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8717,7 +9327,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name beads" @@ -8725,6 +9340,17 @@ "post": { "operationId": "create-bead", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -8775,6 +9401,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8786,7 +9415,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create a bead" @@ -8837,6 +9471,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8848,7 +9485,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name beads graph by root ID" @@ -8909,6 +9551,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8920,7 +9565,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name beads ready" @@ -8961,6 +9611,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -8972,7 +9625,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name config" @@ -9013,6 +9671,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9024,7 +9685,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name config explain" @@ -9056,7 +9722,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9066,7 +9737,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name config validate" @@ -9076,6 +9752,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-convoy-by-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9108,7 +9795,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9118,7 +9810,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name convoy by ID" @@ -9167,6 +9864,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9178,7 +9878,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name convoy by ID" @@ -9188,6 +9893,17 @@ "post": { "operationId": "post-v0-city-by-city-name-convoy-by-id-add", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9230,7 +9946,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9240,7 +9961,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name convoy by ID add" @@ -9291,6 +10017,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9302,7 +10031,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name convoy by ID check" @@ -9313,9 +10047,20 @@ "operationId": "post-v0-city-by-city-name-convoy-by-id-close", "parameters": [ { - "description": "City name.", - "in": "path", - "name": "cityName", + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, + { + "description": "City name.", + "in": "path", + "name": "cityName", "required": true, "schema": { "description": "City name.", @@ -9344,7 +10089,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9354,7 +10104,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name convoy by ID close" @@ -9364,6 +10119,17 @@ "post": { "operationId": "post-v0-city-by-city-name-convoy-by-id-remove", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9406,7 +10172,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9416,7 +10187,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name convoy by ID remove" @@ -9499,6 +10275,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9510,7 +10289,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name convoys" @@ -9518,6 +10302,17 @@ "post": { "operationId": "create-convoy", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9559,6 +10354,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9570,7 +10368,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create a convoy" @@ -9683,6 +10486,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9694,7 +10500,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name events" @@ -9702,6 +10513,17 @@ "post": { "operationId": "emit-event", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9734,7 +10556,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9744,7 +10571,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Emit an event" @@ -9854,7 +10686,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9864,7 +10701,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Stream city events in real time" @@ -9874,6 +10716,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-extmsg-adapters", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -9906,7 +10759,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -9916,7 +10774,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name extmsg adapters" @@ -9955,6 +10818,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -9966,7 +10832,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name extmsg adapters" @@ -9974,6 +10845,17 @@ "post": { "operationId": "register-extmsg-adapter", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10006,7 +10888,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10016,7 +10903,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Register an external messaging adapter" @@ -10026,6 +10918,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-bind", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10058,7 +10961,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10068,7 +10976,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg bind" @@ -10119,6 +11032,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -10130,7 +11046,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name extmsg bindings" @@ -10212,7 +11133,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10222,7 +11148,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name extmsg groups" @@ -10230,6 +11161,17 @@ "post": { "operationId": "ensure-extmsg-group", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10262,7 +11204,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10272,7 +11219,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Ensure an external messaging group exists" @@ -10282,6 +11234,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-inbound", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10314,7 +11277,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10324,7 +11292,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg inbound" @@ -10334,6 +11307,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-outbound", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10366,7 +11350,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10376,7 +11365,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg outbound" @@ -10386,6 +11380,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-extmsg-participants", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10418,7 +11423,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10428,7 +11438,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name extmsg participants" @@ -10436,6 +11451,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-participants", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10468,7 +11494,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10478,7 +11509,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg participants" @@ -10579,6 +11615,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -10590,7 +11629,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name extmsg transcript" @@ -10600,6 +11644,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-transcript-ack", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10632,7 +11687,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10642,7 +11702,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg transcript ack" @@ -10652,6 +11717,17 @@ "post": { "operationId": "post-v0-city-by-city-name-extmsg-unbind", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -10684,7 +11760,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10694,7 +11775,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name extmsg unbind" @@ -10767,7 +11853,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10777,7 +11868,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name formula by name" @@ -10829,7 +11925,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10839,7 +11940,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name formulas" @@ -10903,7 +12009,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10913,7 +12024,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name formulas feed" @@ -10986,7 +12102,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -10996,7 +12117,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name formulas by name" @@ -11006,6 +12132,17 @@ "post": { "operationId": "post-v0-city-by-city-name-formulas-by-name-preview", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11048,7 +12185,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11058,7 +12200,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name formulas by name preview" @@ -11134,7 +12281,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11144,7 +12296,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name formulas by name runs" @@ -11176,7 +12333,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11186,7 +12348,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name health" @@ -11299,6 +12466,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -11310,7 +12480,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name mail" @@ -11318,6 +12493,17 @@ "post": { "operationId": "send-mail", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11368,6 +12554,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -11379,7 +12568,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Send a mail message" @@ -11431,7 +12625,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11441,7 +12640,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name mail count" @@ -11502,6 +12706,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -11513,7 +12720,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name mail thread by ID" @@ -11523,6 +12735,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-mail-by-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11565,7 +12788,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11575,7 +12803,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name mail by ID" @@ -11634,6 +12867,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -11645,7 +12881,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name mail by ID" @@ -11655,6 +12896,17 @@ "post": { "operationId": "post-v0-city-by-city-name-mail-by-id-archive", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11697,7 +12949,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11707,7 +12964,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name mail by ID archive" @@ -11717,6 +12979,17 @@ "post": { "operationId": "post-v0-city-by-city-name-mail-by-id-mark-unread", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11759,7 +13032,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11769,7 +13047,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name mail by ID mark unread" @@ -11779,6 +13062,17 @@ "post": { "operationId": "post-v0-city-by-city-name-mail-by-id-read", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11821,7 +13115,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11831,7 +13130,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name mail by ID read" @@ -11841,6 +13145,17 @@ "post": { "operationId": "reply-mail", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -11902,6 +13217,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -11913,7 +13231,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Reply to a mail message" @@ -11965,7 +13288,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -11975,7 +13303,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name order history by bead ID" @@ -12017,7 +13350,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12027,7 +13365,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name order by name" @@ -12037,6 +13380,17 @@ "post": { "operationId": "post-v0-city-by-city-name-order-by-name-disable", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12069,7 +13423,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12079,7 +13438,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name order by name disable" @@ -12089,6 +13453,17 @@ "post": { "operationId": "post-v0-city-by-city-name-order-by-name-enable", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12121,7 +13496,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12131,7 +13511,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name order by name enable" @@ -12163,7 +13548,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12173,7 +13563,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name orders" @@ -12205,7 +13600,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12215,7 +13615,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name orders check" @@ -12279,7 +13684,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12289,7 +13699,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name orders feed" @@ -12355,7 +13770,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12365,7 +13785,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name orders history" @@ -12397,7 +13822,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12407,7 +13837,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name packs" @@ -12417,6 +13852,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-patches-agent-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12449,7 +13895,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12459,7 +13910,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name patches agent by base" @@ -12508,6 +13964,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -12519,7 +13978,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches agent by base" @@ -12529,6 +13993,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-patches-agent-by-dir-by-base", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12571,7 +14046,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12581,7 +14061,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name patches agent by dir by base" @@ -12640,6 +14125,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -12651,7 +14139,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches agent by dir by base" @@ -12692,6 +14185,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -12703,7 +14199,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches agents" @@ -12712,7 +14213,18 @@ "operationId": "put-v0-city-by-city-name-patches-agents", "parameters": [ { - "description": "City name.", + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, + { + "description": "City name.", "in": "path", "name": "cityName", "required": true, @@ -12743,7 +14255,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12753,7 +14270,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Put v0 city by city name patches agents" @@ -12763,6 +14285,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-patches-provider-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12795,7 +14328,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12805,7 +14343,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name patches provider by name" @@ -12854,6 +14397,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -12865,7 +14411,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches provider by name" @@ -12906,6 +14457,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -12917,7 +14471,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches providers" @@ -12925,6 +14484,17 @@ "put": { "operationId": "put-v0-city-by-city-name-patches-providers", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -12957,7 +14527,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -12967,7 +14542,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Put v0 city by city name patches providers" @@ -12977,6 +14557,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-patches-rig-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13009,7 +14600,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13019,7 +14615,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name patches rig by name" @@ -13068,6 +14669,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13079,7 +14683,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches rig by name" @@ -13120,6 +14729,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13131,7 +14743,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name patches rigs" @@ -13139,6 +14756,17 @@ "put": { "operationId": "put-v0-city-by-city-name-patches-rigs", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13171,7 +14799,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13181,7 +14814,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Put v0 city by city name patches rigs" @@ -13233,7 +14871,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13243,7 +14886,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name provider readiness" @@ -13253,6 +14901,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-provider-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13285,7 +14944,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13295,7 +14959,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name provider by name" @@ -13344,6 +15013,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13355,7 +15027,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name provider by name" @@ -13363,6 +15040,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-provider-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13405,7 +15093,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13415,7 +15108,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name provider by name" @@ -13456,6 +15154,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13467,7 +15168,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name providers" @@ -13475,6 +15181,17 @@ "post": { "operationId": "create-provider", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13507,7 +15224,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13517,7 +15239,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create a provider" @@ -13558,6 +15285,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13569,7 +15299,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name providers public" @@ -13621,7 +15356,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13631,7 +15371,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name readiness" @@ -13641,6 +15386,17 @@ "delete": { "operationId": "delete-v0-city-by-city-name-rig-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13673,7 +15429,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13683,7 +15444,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name rig by name" @@ -13742,6 +15508,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13753,7 +15522,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name rig by name" @@ -13761,6 +15535,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-rig-by-name", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13803,7 +15588,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13813,7 +15603,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Patch v0 city by city name rig by name" @@ -13823,6 +15618,17 @@ "post": { "operationId": "post-v0-city-by-city-name-rig-by-name-by-action", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13865,7 +15671,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -13875,7 +15686,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name rig by name by action" @@ -13946,6 +15762,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -13957,7 +15776,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name rigs" @@ -13965,6 +15789,17 @@ "post": { "operationId": "create-rig", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -13997,7 +15832,12 @@ } } }, - "description": "Created" + "description": "Created", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14007,7 +15847,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create a rig" @@ -14058,6 +15903,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14069,7 +15917,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name service by name" @@ -14079,6 +15932,17 @@ "post": { "operationId": "post-v0-city-by-city-name-service-by-name-restart", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14111,7 +15975,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14121,7 +15990,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name service by name restart" @@ -14162,6 +16036,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14173,7 +16050,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name services" @@ -14234,6 +16116,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14245,7 +16130,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name session by ID" @@ -14253,6 +16143,17 @@ "patch": { "operationId": "patch-v0-city-by-city-name-session-by-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14304,6 +16205,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14315,9 +16219,14 @@ } } }, - "description": "Error" - } - }, + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } + } + }, "summary": "Patch v0 city by city name session by ID" } }, @@ -14366,6 +16275,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14377,7 +16289,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name session by ID agents" @@ -14438,6 +16355,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14449,7 +16369,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name session by ID agents by agent ID" @@ -14459,6 +16384,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-close", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14501,7 +16437,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14511,7 +16452,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID close" @@ -14521,6 +16467,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-kill", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14553,7 +16510,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14563,7 +16525,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID kill" @@ -14573,6 +16540,17 @@ "post": { "operationId": "send-session-message", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14615,7 +16593,12 @@ } } }, - "description": "Accepted" + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14625,7 +16608,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Send a message to a session" @@ -14676,6 +16664,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14687,7 +16678,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name session by ID pending" @@ -14697,6 +16693,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-rename", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14748,6 +16755,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -14759,7 +16769,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID rename" @@ -14769,6 +16784,17 @@ "post": { "operationId": "respond-session", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14811,7 +16837,12 @@ } } }, - "description": "Accepted" + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14821,7 +16852,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Respond to a pending interaction" @@ -14831,6 +16867,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-stop", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -14863,7 +16910,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -14873,7 +16925,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID stop" @@ -15061,7 +17118,26 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "GC-Session-State": { + "description": "Session state at the time streaming began (e.g. active, closed).", + "schema": { + "description": "Session state at the time streaming began (e.g. active, closed).", + "type": "string" + } + }, + "GC-Session-Status": { + "description": "Runtime status at the time streaming began. Emitted as \"stopped\" when the session's underlying process is not running.", + "schema": { + "description": "Runtime status at the time streaming began. Emitted as \"stopped\" when the session's underlying process is not running.", + "type": "string" + } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15071,7 +17147,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Stream session output in real time" @@ -15081,6 +17162,17 @@ "post": { "operationId": "submit-session", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15123,7 +17215,12 @@ } } }, - "description": "Accepted" + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15133,7 +17230,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Submit a message to a session" @@ -15143,6 +17245,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-suspend", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15175,7 +17288,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15185,7 +17303,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID suspend" @@ -15266,6 +17389,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -15277,7 +17403,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name session by ID transcript" @@ -15287,6 +17418,17 @@ "post": { "operationId": "post-v0-city-by-city-name-session-by-id-wake", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15319,7 +17461,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15329,7 +17476,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name session by ID wake" @@ -15422,6 +17574,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -15433,7 +17588,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name sessions" @@ -15441,6 +17601,17 @@ "post": { "operationId": "create-session", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15473,7 +17644,12 @@ } } }, - "description": "Accepted" + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15483,7 +17659,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Create a session" @@ -15493,6 +17674,17 @@ "post": { "operationId": "post-v0-city-by-city-name-sling", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15525,7 +17717,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15535,7 +17732,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Post v0 city by city name sling" @@ -15596,6 +17798,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -15607,16 +17812,93 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name status" } }, + "/v0/city/{cityName}/unregister": { + "post": { + "operationId": "post-v0-city-by-city-name-unregister", + "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, + { + "description": "Supervisor-registered city name.", + "in": "path", + "name": "cityName", + "required": true, + "schema": { + "description": "Supervisor-registered city name.", + "type": "string" + } + } + ], + "responses": { + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CityUnregisterResponse" + } + } + }, + "description": "Accepted", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } + } + }, + "summary": "Post v0 city by city name unregister" + } + }, "/v0/city/{cityName}/workflow/{workflow_id}": { "delete": { "operationId": "delete-v0-city-by-city-name-workflow-by-workflow-id", "parameters": [ + { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "in": "header", + "name": "X-GC-Request", + "required": true, + "schema": { + "description": "Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks.", + "minLength": 1, + "type": "string" + } + }, { "description": "City name.", "in": "path", @@ -15679,7 +17961,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15689,7 +17976,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Delete v0 city by city name workflow by workflow ID" @@ -15758,6 +18050,9 @@ "minimum": 0, "type": "integer" } + }, + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" } } }, @@ -15769,7 +18064,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 city by city name workflow by workflow ID" @@ -15831,7 +18131,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15841,7 +18146,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 events" @@ -15938,7 +18248,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15948,7 +18263,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Stream tagged events from all running cities." @@ -15988,7 +18308,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -15998,7 +18323,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 provider readiness" @@ -16038,7 +18368,12 @@ } } }, - "description": "OK" + "description": "OK", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } }, "default": { "content": { @@ -16048,7 +18383,12 @@ } } }, - "description": "Error" + "description": "Error", + "headers": { + "X-GC-Request-Id": { + "$ref": "#/components/headers/X-GC-Request-Id" + } + } } }, "summary": "Get v0 readiness" diff --git a/internal/api/openapi_sync_test.go b/internal/api/openapi_sync_test.go index 724018c635..97fd3955c6 100644 --- a/internal/api/openapi_sync_test.go +++ b/internal/api/openapi_sync_test.go @@ -23,7 +23,7 @@ import ( // yields the authoritative contract for every HTTP endpoint the control // plane exposes. func TestOpenAPISpecInSync(t *testing.T) { - sm := api.NewSupervisorMux(emptyTestResolver{}, false, "", time.Time{}) + sm := api.NewSupervisorMux(emptyTestResolver{}, nil, false, "", time.Time{}) req := httptest.NewRequest(http.MethodGet, "/openapi.json", nil) rec := httptest.NewRecorder() sm.ServeHTTP(rec, req) @@ -133,7 +133,7 @@ func TestEventsSchemaPublished(t *testing.T) { } func TestOrderResponseSchemaKeepsMigrationFieldsOptional(t *testing.T) { - sm := api.NewSupervisorMux(emptyTestResolver{}, false, "", time.Time{}) + sm := api.NewSupervisorMux(emptyTestResolver{}, nil, false, "", time.Time{}) req := httptest.NewRequest(http.MethodGet, "/openapi.json", nil) rec := httptest.NewRecorder() sm.ServeHTTP(rec, req) diff --git a/internal/api/server.go b/internal/api/server.go index 55cdf92812..0ba278792a 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -258,7 +258,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - sm := NewSupervisorMux(&singleStateResolver{state: s.state}, s.readOnly, "test", time.Now()) + sm := NewSupervisorMux(&singleStateResolver{state: s.state}, nil, s.readOnly, "test", time.Now()) sm.cacheMu.Lock() sm.cache[s.state.CityName()] = cachedCityServer{state: s.state, srv: s} sm.cacheMu.Unlock() diff --git a/internal/api/sse.go b/internal/api/sse.go index 671c7862ff..3492910387 100644 --- a/internal/api/sse.go +++ b/internal/api/sse.go @@ -2,13 +2,9 @@ package api import ( "context" - "crypto/tls" "encoding/json" "fmt" - "io" - "mime/multipart" "net/http" - "net/url" "reflect" "slices" "strings" @@ -87,6 +83,7 @@ func registerSSE[I any]( precheck func(context.Context, *I) error, stream StreamFunc[I], ) { + normalizeSSEResponseHeaders(&op) typeToEvent := attachSSEResponseSchema(api, &op, eventTypeMap, huma.TypeInteger, "The event ID.") huma.Register(api, op, func(ctx context.Context, input *I) (*huma.StreamResponse, error) { @@ -97,22 +94,14 @@ func registerSSE[I any]( } return &huma.StreamResponse{ Body: func(hctx huma.Context) { - // Derive a cancelable context from the request context and - // plumb it into hctx so stream loops checking hctx.Context() - // exit promptly on send-error. - reqCtx, cancel := context.WithCancel(hctx.Context()) - defer cancel() - hctx = hctxWithCtx(reqCtx, hctx) - bw, encoder, flusher := beginSSEStream(hctx) - rawSend := func(msg sse.Message) error { + send := func(msg sse.Message) error { idLine := "" if msg.ID > 0 { idLine = fmt.Sprintf("id: %d\n", msg.ID) } return writeSSEFrame(bw, encoder, flusher, typeToEvent, idLine, msg.Data) } - send := cancelOnSendError(rawSend, cancel) stream(hctx, input, send) }, }, nil @@ -151,6 +140,7 @@ func registerSSEStringID[I any]( precheck func(context.Context, *I) error, stream StringIDStreamFunc[I], ) { + normalizeSSEResponseHeaders(&op) typeToEvent := attachSSEResponseSchema(api, &op, eventTypeMap, huma.TypeString, "The event ID (composite cursor).") huma.Register(api, op, func(ctx context.Context, input *I) (*huma.StreamResponse, error) { @@ -161,88 +151,73 @@ func registerSSEStringID[I any]( } return &huma.StreamResponse{ Body: func(hctx huma.Context) { - reqCtx, cancel := context.WithCancel(hctx.Context()) - defer cancel() - hctx = hctxWithCtx(reqCtx, hctx) - bw, encoder, flusher := beginSSEStream(hctx) - rawSend := func(msg StringIDMessage) error { + send := func(msg StringIDMessage) error { idLine := "" if msg.ID != "" { idLine = fmt.Sprintf("id: %s\n", msg.ID) } return writeSSEFrame(bw, encoder, flusher, typeToEvent, idLine, msg.Data) } - send := stringIDCancelOnSendError(rawSend, cancel) stream(hctx, input, send) }, }, nil }) } -// stringIDCancelOnSendError is the StringIDSender analog of cancelOnSendError. -// On first send failure it cancels the supplied context; subsequent calls -// short-circuit to the cached error. -func stringIDCancelOnSendError(send StringIDSender, cancel context.CancelFunc) StringIDSender { - var firstErr error - return func(msg StringIDMessage) error { - if firstErr != nil { - return firstErr +// sseStatusHeaders is the canonical catalog of custom response headers +// that stream handlers may emit via hctx.SetHeader. Each entry's key is +// the wire header name; the value is its human-readable description. +// Callers reference headers by name (see sseResponseHeaders) — the +// description travels with the name so a reader at the registration +// site sees only the list of headers the operation emits and each +// description has a single source of truth. +var sseStatusHeaders = map[string]string{ + "GC-Agent-Status": "Agent runtime status at the time streaming began. Emitted as \"stopped\" when the agent is not running (the stream then serves replayed transcript from the session log).", + "GC-Session-State": "Session state at the time streaming began (e.g. active, closed).", + "GC-Session-Status": "Runtime status at the time streaming began. Emitted as \"stopped\" when the session's underlying process is not running.", +} + +// sseResponseHeaders builds a Responses map declaring the named +// custom headers on the 200 response. Names must appear in +// sseStatusHeaders — the function panics if a caller references an +// undeclared header, so drift between SetHeader call sites and the +// declared contract surfaces at startup rather than in a stale spec. +func sseResponseHeaders(names ...string) map[string]*huma.Response { + headers := make(map[string]*huma.Param, len(names)) + for _, name := range names { + desc, ok := sseStatusHeaders[name] + if !ok { + panic("api: sse response header not in sseStatusHeaders catalog: " + name) } - if err := send(msg); err != nil { - firstErr = err - cancel() - return err + headers[name] = &huma.Param{ + Description: desc, + Schema: &huma.Schema{ + Type: "string", + Description: desc, + }, } - return nil + } + return map[string]*huma.Response{ + "200": {Headers: headers}, } } -// hctxWithCtx returns a huma.Context that reports the supplied ctx from -// its Context() method. Used by SSE registration to plumb a cancelable -// context into stream loops that poll hctx.Context().Done(). -// -// Note: we cannot embed huma.Context because the interface is literally -// named Context, which collides with our override method Context(). The -// override instead delegates every method explicitly. -func hctxWithCtx(ctx context.Context, hctx huma.Context) huma.Context { - return &hctxOverride{inner: hctx, ctx: ctx} -} - -// hctxOverride wraps a huma.Context to replace only the Context() method. -// All other methods delegate to the inner huma.Context. -type hctxOverride struct { - inner huma.Context - ctx context.Context -} - -func (h *hctxOverride) Operation() *huma.Operation { return h.inner.Operation() } -func (h *hctxOverride) Context() context.Context { return h.ctx } -func (h *hctxOverride) Method() string { return h.inner.Method() } -func (h *hctxOverride) Host() string { return h.inner.Host() } -func (h *hctxOverride) RemoteAddr() string { return h.inner.RemoteAddr() } -func (h *hctxOverride) URL() url.URL { return h.inner.URL() } -func (h *hctxOverride) Param(name string) string { return h.inner.Param(name) } -func (h *hctxOverride) Query(name string) string { return h.inner.Query(name) } -func (h *hctxOverride) Header(name string) string { return h.inner.Header(name) } -func (h *hctxOverride) EachHeader(cb func(name, value string)) { - h.inner.EachHeader(cb) -} -func (h *hctxOverride) BodyReader() io.Reader { return h.inner.BodyReader() } -func (h *hctxOverride) GetMultipartForm() (*multipart.Form, error) { - return h.inner.GetMultipartForm() -} - -func (h *hctxOverride) SetReadDeadline(deadline time.Time) error { - return h.inner.SetReadDeadline(deadline) +// normalizeSSEResponseHeaders ensures op.Responses["200"] exists with a +// non-nil Headers map so the pre-declared stream-status headers (set by +// the caller on the Operation literal) are preserved after +// attachSSEResponseSchema rebuilds Content. +func normalizeSSEResponseHeaders(op *huma.Operation) { + if op.Responses == nil { + op.Responses = map[string]*huma.Response{} + } + if op.Responses["200"] == nil { + op.Responses["200"] = &huma.Response{} + } + if op.Responses["200"].Headers == nil { + op.Responses["200"].Headers = map[string]*huma.Param{} + } } -func (h *hctxOverride) SetStatus(code int) { h.inner.SetStatus(code) } -func (h *hctxOverride) Status() int { return h.inner.Status() } -func (h *hctxOverride) AppendHeader(name, value string) { h.inner.AppendHeader(name, value) } -func (h *hctxOverride) SetHeader(name, value string) { h.inner.SetHeader(name, value) } -func (h *hctxOverride) BodyWriter() io.Writer { return h.inner.BodyWriter() } -func (h *hctxOverride) TLS() *tls.ConnectionState { return h.inner.TLS() } -func (h *hctxOverride) Version() huma.ProtoVersion { return h.inner.Version() } // attachSSEResponseSchema populates op.Responses with the text/event-stream // media block for the given event map. Returns the reverse-lookup map diff --git a/internal/api/supervisor.go b/internal/api/supervisor.go index 70f67beddb..e60a07538d 100644 --- a/internal/api/supervisor.go +++ b/internal/api/supervisor.go @@ -12,6 +12,7 @@ import ( "time" "github.com/danielgtaylor/huma/v2" + "github.com/gastownhall/gascity/internal/cityinit" "github.com/gastownhall/gascity/internal/events" ) @@ -33,6 +34,25 @@ type CityResolver interface { CityState(name string) State } +// TransientCityEventSource is an optional CityResolver extension +// that lets the supervisor-scope event multiplexer include event +// providers for cities that are registered but not yet (or no +// longer) in the Running set — newly scaffolded cities whose +// reconciler hasn't picked them up, cities currently running +// prepareCityForSupervisor, and cities whose init failed. Without +// this, /v0/events/stream subscribers can't observe city.created, +// city.ready, or city.init_failed for cities that aren't yet +// reporting Running=true through ListCities. +// +// Resolvers that implement this return one entry per transient +// city; the key is the city name, the value is an event provider +// backed by that city's .gc/events.jsonl file. The supervisor +// multiplexer adds these on top of the Running-city providers it +// already picks up via ListCities + CityState. +type TransientCityEventSource interface { + TransientCityEventProviders() map[string]events.Provider +} + // cachedCityServer pairs a State with its pre-built Server for caching. type cachedCityServer struct { state State @@ -53,11 +73,12 @@ type cachedCityServer struct { // to per-city Server.mux. Workspace services own their own HTTP // contracts and are explicitly excluded from the typed control plane. type SupervisorMux struct { - resolver CityResolver - readOnly bool - version string - startedAt time.Time - server *http.Server + resolver CityResolver + initializer cityinit.Initializer + readOnly bool + version string + startedAt time.Time + server *http.Server // Single Huma API (Phase 3.5 — Topology 1). Owns every typed // operation: supervisor-scope (/v0/cities, /health, /v0/readiness, @@ -76,21 +97,30 @@ type SupervisorMux struct { } // NewSupervisorMux creates a SupervisorMux that routes requests to cities -// resolved by the given CityResolver. -func NewSupervisorMux(resolver CityResolver, readOnly bool, version string, startedAt time.Time) *SupervisorMux { +// resolved by the given CityResolver. The initializer is invoked by the +// POST /v0/city handler to scaffold new cities in-process; passing nil +// is allowed for tests that don't exercise city creation (the handler +// returns 501 Not Implemented in that case). +func NewSupervisorMux(resolver CityResolver, initializer cityinit.Initializer, readOnly bool, version string, startedAt time.Time) *SupervisorMux { humaMux := http.NewServeMux() sm := &SupervisorMux{ - resolver: resolver, - readOnly: readOnly, - version: version, - startedAt: startedAt, - humaMux: humaMux, - humaAPI: newSupervisorHumaAPI(humaMux, readOnly), - cache: make(map[string]cachedCityServer), + resolver: resolver, + initializer: initializer, + readOnly: readOnly, + version: version, + startedAt: startedAt, + humaMux: humaMux, + humaAPI: newSupervisorHumaAPI(humaMux, readOnly), + cache: make(map[string]cachedCityServer), } sm.registerSupervisorRoutes() sm.registerCityRoutes() documentProblemTypes(sm.humaAPI.OpenAPI()) + // Declare framework-level response headers (X-GC-Request-Id) via + // components.headers + $ref on every operation. Middleware writes + // the header at runtime; the spec describes the contract. Must run + // after all routes are registered. + registerFrameworkHeaders(sm.humaAPI) // /svc/* workspace-service pass-through. This is the single remaining // non-Huma registration on the supervisor — untyped by design (the // proxy passes bodies through to external service processes, which @@ -239,7 +269,13 @@ func (sm *SupervisorMux) getCityServer(name string, state State) *Server { } // buildMultiplexer creates a Multiplexer from all running cities' -// event providers. +// event providers plus any transient-city providers surfaced by a +// resolver that implements TransientCityEventSource. Including +// transient (pending init, in-progress, or failed) cities matters +// for clients that POST /v0/city and wait for city.created / +// city.ready / city.init_failed events on /v0/events/stream without +// polling — the city's own events.jsonl exists from Scaffold +// onward, but the city isn't in Running=true yet. func (sm *SupervisorMux) buildMultiplexer() *events.Multiplexer { mux := events.NewMultiplexer() cities := sm.resolver.ListCities() @@ -257,6 +293,14 @@ func (sm *SupervisorMux) buildMultiplexer() *events.Multiplexer { } mux.Add(c.Name, ep) } + if transient, ok := sm.resolver.(TransientCityEventSource); ok { + for name, ep := range transient.TransientCityEventProviders() { + if ep == nil { + continue + } + mux.Add(name, ep) + } + } return mux } diff --git a/internal/api/supervisor_city_routes.go b/internal/api/supervisor_city_routes.go index ac1ad13d36..d93e1aaa28 100644 --- a/internal/api/supervisor_city_routes.go +++ b/internal/api/supervisor_city_routes.go @@ -81,6 +81,7 @@ func (sm *SupervisorMux) registerCityRoutes() { Path: cityScopePrefix + "/agent/{base}/output/stream", Summary: "Stream agent output in real time", Description: "Server-Sent Events stream of agent output (session log tail or tmux pane polling).", + Responses: sseResponseHeaders("GC-Agent-Status"), }, agentOutputEventMap, sseCityPrecheck(sm, (*Server).checkAgentOutputStream), sseCityStream(sm, (*Server).streamAgentOutput)) @@ -90,6 +91,7 @@ func (sm *SupervisorMux) registerCityRoutes() { Path: cityScopePrefix + "/agent/{dir}/{base}/output/stream", Summary: "Stream agent output in real time (qualified name)", Description: "Server-Sent Events stream of agent output for qualified (rig-prefixed) agent names.", + Responses: sseResponseHeaders("GC-Agent-Status"), }, agentOutputEventMap, sseCityPrecheck(sm, (*Server).checkAgentOutputStreamQualified), sseCityStream(sm, (*Server).streamAgentOutputQualified)) @@ -296,6 +298,7 @@ func (sm *SupervisorMux) registerCityRoutes() { "Streams turns (conversation format) or raw messages (JSONL format) " + "based on the format query parameter. Emits activity and pending events " + "for tool approval prompts.", + Responses: sseResponseHeaders("GC-Session-State", "GC-Session-Status"), }, sessionStreamEventMap(), sseCityPrecheck(sm, (*Server).checkSessionStream), sseCityStream(sm, (*Server).streamSession)) diff --git a/internal/api/supervisor_test.go b/internal/api/supervisor_test.go index 81321f7762..20dc4b6b1b 100644 --- a/internal/api/supervisor_test.go +++ b/internal/api/supervisor_test.go @@ -45,7 +45,7 @@ func (f *fakeCityResolver) CityState(name string) State { func newTestSupervisorMux(t *testing.T, cities map[string]*fakeState) *SupervisorMux { t.Helper() resolver := &fakeCityResolver{cities: cities} - return NewSupervisorMux(resolver, false, "test", time.Now()) + return NewSupervisorMux(resolver, nil, false, "test", time.Now()) } func TestSupervisorCitiesList(t *testing.T) { diff --git a/internal/api/test_helpers_test.go b/internal/api/test_helpers_test.go index 5a64abb584..36ea0c7773 100644 --- a/internal/api/test_helpers_test.go +++ b/internal/api/test_helpers_test.go @@ -20,13 +20,13 @@ import ( // non-default naming, use newTestSupervisorMux directly. func newTestCityHandler(t *testing.T, state State) http.Handler { t.Helper() - return wrapTestSupervisorMiddleware(NewSupervisorMux(&stateCityResolver{state: state}, false, "test", time.Now())) + return wrapTestSupervisorMiddleware(NewSupervisorMux(&stateCityResolver{state: state}, nil, false, "test", time.Now())) } // newTestCityHandlerReadOnly is newTestCityHandler but with readOnly=true. func newTestCityHandlerReadOnly(t *testing.T, state State) http.Handler { t.Helper() - return wrapTestSupervisorMiddleware(NewSupervisorMux(&stateCityResolver{state: state}, true, "test", time.Now())) + return wrapTestSupervisorMiddleware(NewSupervisorMux(&stateCityResolver{state: state}, nil, true, "test", time.Now())) } // wrapTestSupervisorMiddleware applies the same middleware the supervisor's @@ -71,7 +71,7 @@ func cityURL(state State, path string) string { // Server so handler dispatch runs against that exact instance. func newTestCityHandlerWith(t *testing.T, state State, srv *Server) http.Handler { t.Helper() - sm := NewSupervisorMux(&stateCityResolver{state: state}, false, "test", time.Now()) + sm := NewSupervisorMux(&stateCityResolver{state: state}, nil, false, "test", time.Now()) sm.cacheMu.Lock() sm.cache[state.CityName()] = cachedCityServer{state: state, srv: srv} sm.cacheMu.Unlock() diff --git a/internal/cityinit/cityinit.go b/internal/cityinit/cityinit.go new file mode 100644 index 0000000000..0d7fba4d9d --- /dev/null +++ b/internal/cityinit/cityinit.go @@ -0,0 +1,209 @@ +// Package cityinit is the domain contract for city scaffolding and +// finalization. It defines the typed request, result, and sentinel +// errors that both projections — the CLI (cmd/gc/cmd_init.go) and the +// HTTP API (internal/api/huma_handlers_supervisor.go:humaHandleCityCreate) — +// use when creating a new city. +// +// The Initializer interface is implemented in cmd/gc (where the +// scaffold + finalize body currently lives) and injected into the +// HTTP supervisor at construction. The HTTP handler calls +// Initializer.Init in-process; there is no subprocess, no +// 30-second deadline, no stderr-scraping for error dispatch. +// +// A follow-up refactor will physically move the scaffold/finalize +// body into this package so the domain logic lives in internal/ +// (per specs/architecture.md §1). Until then, injecting the +// implementation from cmd/gc at startup preserves the architectural +// intent that "the CLI and the HTTP API are projections over the +// shared object model" — both surfaces drive the same code path via +// the same typed contract. +package cityinit + +import ( + "context" + "errors" +) + +// Typed sentinel errors. Both projections map them to their own +// surface: the CLI renders human-readable blocker lists; the HTTP +// handler maps each to the appropriate status code (409, 400, 503, +// etc.). Error strings are suitable for display in either surface. +var ( + // ErrAlreadyInitialized indicates the target directory already + // contains a Gas City scaffold. The HTTP API maps this to 409 + // Conflict. The CLI can either ignore (idempotent reinit) or + // surface, depending on flags. + ErrAlreadyInitialized = errors.New("city already initialized") + + // ErrInvalidProvider indicates an unknown builtin provider. The + // HTTP API maps this to 400 Bad Request (or 422 Unprocessable + // Entity at the typed-input layer). + ErrInvalidProvider = errors.New("invalid provider") + + // ErrInvalidBootstrapProfile indicates an unrecognized + // bootstrap_profile value. + ErrInvalidBootstrapProfile = errors.New("invalid bootstrap profile") + + // ErrMissingDependency indicates a hard runtime dependency + // (tmux, git, dolt, bd, flock, jq, pgrep, lsof) is missing or + // too old. Maps to 503 Service Unavailable at the HTTP layer. + // The error message lists every missing dependency so the CLI + // can render install hints without another probe pass. + ErrMissingDependency = errors.New("missing hard dependency") + + // ErrProviderNotReady indicates at least one provider the city + // references is not ready (no auth, not installed, invalid + // config, or probe failure). Only returned when + // InitRequest.SkipProviderReadiness is false. Maps to 503 at + // the HTTP layer. + ErrProviderNotReady = errors.New("provider not ready") + + // ErrConfigLoad indicates the city was scaffolded but its + // on-disk configuration could not be re-parsed after write. + // Usually a bug in the scaffold step; maps to 500. + ErrConfigLoad = errors.New("loading city config") + + // ErrNotWired indicates the HTTP handler was called before a + // concrete Initializer was injected into the supervisor. This + // is a programmer-bug tripwire: every SupervisorMux constructed + // at runtime must have a non-nil Initializer. + ErrNotWired = errors.New("cityinit: no Initializer wired into supervisor") + + // ErrNotRegistered indicates Unregister was called for a city + // that is not in the supervisor registry. Maps to 404 Not Found + // at the HTTP layer. + ErrNotRegistered = errors.New("city not registered with supervisor") +) + +// InitRequest is the typed input. Both projections populate it from +// their own surface (CLI flags, HTTP request body) and hand it to +// Initializer.Init; neither duplicates validation or logic. +type InitRequest struct { + // Dir is the absolute path of the new city directory. Callers + // resolve relative paths before invoking Init (the CLI uses + // filepath.Abs; the API handler joins against $HOME when + // relative). + Dir string + + // Provider is the builtin provider key ("claude", "codex", + // "gemini", ...) for the city's default workspace. Empty iff + // StartCommand is set. + Provider string + + // StartCommand is an opt-in custom workspace provider command. + // Empty unless the caller wants a non-builtin workspace. + StartCommand string + + // BootstrapProfile is one of "", "k8s-cell", "kubernetes", + // "kubernetes-cell", "single-host-compat". + BootstrapProfile string + + // NameOverride is an explicit city name. Empty means derive + // from filepath.Base(Dir). + NameOverride string + + // SkipProviderReadiness skips the provider-auth preflight when + // true. The HTTP handler defaults to true (API callers poll + // readiness separately via GET /v0/provider-readiness). The + // CLI defaults to false so first-time users see auth-needed + // errors immediately. + SkipProviderReadiness bool + + // ConfigName selects the scaffold template. One of "tutorial" + // (default), "gastown", or "custom". Empty is treated as + // "tutorial". The CLI wizard resolves this; the HTTP API + // always leaves it empty. + ConfigName string +} + +// InitResult describes what Init produced. Callers build their own +// surface-specific response from it (CLI status messages, HTTP JSON +// body). +type InitResult struct { + // CityName is the name persisted to city.toml. + CityName string + + // CityPath is the absolute city directory (same as + // InitRequest.Dir after normalization). + CityPath string + + // ProviderUsed is the resolved provider name. + ProviderUsed string + + // Resumed is true when Init detected an existing scaffold and + // skipped to finalization only. + Resumed bool +} + +// Initializer is the domain contract for city lifecycle on the +// supervisor: scaffolding, finalization, and unregistration. Exactly +// one implementation exists per process, supplied at supervisor +// construction (see internal/api.NewSupervisorMux). Both projections +// — CLI and HTTP API — drive the same code path via this interface. +type Initializer interface { + // Init scaffolds + finalizes a new city. + // + // Preconditions: req.Dir is an absolute path; exactly one of + // req.Provider / req.StartCommand is set; req.BootstrapProfile + // is a known value. + // + // Postconditions on nil error: the directory contains a + // complete city scaffold; the bead provider is initialized; the + // city is registered with any running supervisor. + // + // Errors returned wrap one of the ErrXxx sentinels in this + // package so callers can dispatch via errors.Is. + Init(ctx context.Context, req InitRequest) (*InitResult, error) + + // Scaffold writes the new city's on-disk shape and registers it + // with the supervisor — the fast portion of Init. Used by the + // HTTP API handler behind POST /v0/city so it can return 202 + // Accepted immediately instead of blocking on the slow finalize + // work. The supervisor reconciler takes over from there; city + // readiness is signaled via city.ready / city.init_failed + // events on the supervisor event bus, not via the handler's + // response body. + // + // The implementation emits a city.created event before + // returning so subscribers of /v0/events/stream observe the + // new city before Finalize begins. + Scaffold(ctx context.Context, req InitRequest) (*InitResult, error) + + // Unregister removes the city from the supervisor's registry + // and signals the supervisor to reconcile. Used by the HTTP API + // handler behind POST /v0/city/{cityName}/unregister so it can + // return 202 Accepted immediately while the reconciler stops + // the controller asynchronously. + // + // Returns ErrNotRegistered if the named city is not in the + // registry. On nil error, emits a city.unregister_requested + // event to the city's event log so subscribers of + // /v0/events/stream observe the start of the teardown. The + // terminal completion event (city.unregistered or + // city.unregister_failed) is emitted by the supervisor + // reconciler once the city's controller finishes stopping. + // + // The city directory itself is NOT touched. Users that want to + // purge the directory remove it separately. + Unregister(ctx context.Context, req UnregisterRequest) (*UnregisterResult, error) +} + +// UnregisterRequest is the typed input for Initializer.Unregister. +type UnregisterRequest struct { + // CityName is the supervisor-registered name (effective name, + // e.g. workspace.name from city.toml, or directory basename if + // unset). Required; looked up in the registry by name. + CityName string +} + +// UnregisterResult describes what Unregister produced. Callers +// build their own surface-specific response from it. +type UnregisterResult struct { + // CityName is the resolved registry name. + CityName string + + // CityPath is the absolute city directory whose entry was + // removed from the registry. Useful for clients that want to + // filter completion events by path as well as name. + CityPath string +} diff --git a/internal/events/events.go b/internal/events/events.go index a3e00e7d3b..92847e851c 100644 --- a/internal/events/events.go +++ b/internal/events/events.go @@ -17,36 +17,42 @@ import ( // Event type constants. Only types we actually emit today. const ( - SessionWoke = "session.woke" - SessionStopped = "session.stopped" - SessionCrashed = "session.crashed" - BeadCreated = "bead.created" - BeadClosed = "bead.closed" - BeadUpdated = "bead.updated" - MailSent = "mail.sent" - MailRead = "mail.read" - MailArchived = "mail.archived" - MailMarkedRead = "mail.marked_read" - MailMarkedUnread = "mail.marked_unread" - MailReplied = "mail.replied" - MailDeleted = "mail.deleted" - SessionDraining = "session.draining" - SessionUndrained = "session.undrained" - SessionQuarantined = "session.quarantined" - SessionIdleKilled = "session.idle_killed" - SessionSuspended = "session.suspended" - SessionUpdated = "session.updated" - ConvoyCreated = "convoy.created" - ConvoyClosed = "convoy.closed" - ControllerStarted = "controller.started" - ControllerStopped = "controller.stopped" - CitySuspended = "city.suspended" - CityResumed = "city.resumed" - OrderFired = "order.fired" - OrderCompleted = "order.completed" - OrderFailed = "order.failed" - ProviderSwapped = "provider.swapped" - WorkerOperation = "worker.operation" + SessionWoke = "session.woke" + SessionStopped = "session.stopped" + SessionCrashed = "session.crashed" + BeadCreated = "bead.created" + BeadClosed = "bead.closed" + BeadUpdated = "bead.updated" + MailSent = "mail.sent" + MailRead = "mail.read" + MailArchived = "mail.archived" + MailMarkedRead = "mail.marked_read" + MailMarkedUnread = "mail.marked_unread" + MailReplied = "mail.replied" + MailDeleted = "mail.deleted" + SessionDraining = "session.draining" + SessionUndrained = "session.undrained" + SessionQuarantined = "session.quarantined" + SessionIdleKilled = "session.idle_killed" + SessionSuspended = "session.suspended" + SessionUpdated = "session.updated" + ConvoyCreated = "convoy.created" + ConvoyClosed = "convoy.closed" + ControllerStarted = "controller.started" + ControllerStopped = "controller.stopped" + CitySuspended = "city.suspended" + CityResumed = "city.resumed" + CityCreated = "city.created" + CityReady = "city.ready" + CityInitFailed = "city.init_failed" + CityUnregisterRequested = "city.unregister_requested" + CityUnregistered = "city.unregistered" + CityUnregisterFailed = "city.unregister_failed" + OrderFired = "order.fired" + OrderCompleted = "order.completed" + OrderFailed = "order.failed" + ProviderSwapped = "provider.swapped" + WorkerOperation = "worker.operation" // External messaging events. ExtMsgBound = "extmsg.bound" @@ -72,6 +78,8 @@ var KnownEventTypes = []string{ ConvoyCreated, ConvoyClosed, ControllerStarted, ControllerStopped, CitySuspended, CityResumed, + CityCreated, CityReady, CityInitFailed, + CityUnregisterRequested, CityUnregistered, CityUnregisterFailed, OrderFired, OrderCompleted, OrderFailed, ProviderSwapped, WorkerOperation, ExtMsgBound, ExtMsgUnbound, ExtMsgGroupCreated, diff --git a/specs/architecture.md b/specs/architecture.md index 9d1dbbe3a4..d343af97bd 100644 --- a/specs/architecture.md +++ b/specs/architecture.md @@ -20,9 +20,25 @@ Two architectural themes run through everything below: ## 1. The object model `internal/{beads, mail, convoy, formula, agent, events, session, -sling, graphroute, agentutil, pathutil, ...}` is the canonical -domain. All business logic lives there. The two surfaces below call -into it; neither re-implements validation, routing, or invariants. +sling, graphroute, agentutil, pathutil, cityinit, ...}` is the +canonical domain. All business logic lives there. The two surfaces +below call into it; neither re-implements validation, routing, or +invariants. + +City initialization is a worked example: the HTTP handler for +`POST /v0/city` does **not** shell out to `gc init`; it calls +`cityinit.Initializer.Scaffold` in-process, which is the same +entry point the CLI drives. The scaffolded city registers with +the supervisor synchronously before `202 Accepted` returns; the +reconciler runs the slow finalize later and publishes `city.ready` +/ `city.init_failed` events. Both projections live on the same +typed contract and error sentinels +(`cityinit.ErrAlreadyInitialized`, `ErrInvalidProvider`, +`ErrMissingDependency`, `ErrProviderNotReady`, +`ErrInvalidBootstrapProfile`). Long-running mutations in general +follow this shape: scaffold synchronously, return 202, publish +completion events — subscribers watch the event stream instead of +polling. ``` cmd/gc/cmd_*.go internal/api/handler_*.go @@ -145,6 +161,17 @@ schema-describes it. There is no second description of the endpoint anywhere — not in a router table, not in an OpenAPI YAML, not in a client stub. +Framework-level cross-cutting wire contract (CSRF header, request-ID +response header, per-stream status headers) does not live on +per-endpoint struct annotations; it lives on the registration +helpers (`cityPost`, `cityRegister`, `registerSSE`) or on a +post-registration spec walker (`registerFrameworkHeaders`). That is +not a second description — it is the same mechanism applied +at one layer up, and the OpenAPI spec that results still describes +every operation's full contract. See §3.5.2. Patterns and Huma +quirks that inform these helpers are documented in +[`specs/huma-usage.md`](./huma-usage.md). + ### 3.2 Spec is generated, never hand-written `internal/api/openapi.json` and `docs/schema/openapi.json` are @@ -260,11 +287,25 @@ Three anti-patterns are specifically forbidden: but may not read keys off `ctx.URL().Query()` or `ctx.Header()` that aren't present on the input struct. If a resolver needs a value, that value is a declared field — no exceptions. -- **Presence-vs-empty semantics not expressible in JSONSchema.** - If a handler behaves differently for "parameter absent" vs - "parameter present with empty value", the input field must be a - pointer type (`*string`) so the distinction appears on the wire - contract. Resolver-based presence flags hide the semantics. +- **Presence-vs-empty semantics via raw-URL inspection.** If a + handler behaves differently for "parameter absent" vs "parameter + present with empty value", the presence flag must come from + Huma's parameter binder — not from peeking at `ctx.URL().Query()` + inside a Resolver. Use the `huma.OptionalParam[T]`-style wrapper + (see `internal/api/huma_optional_param.go`): a custom type with + `Schema()`, `Receiver()`, and `OnParamSet(isSet bool, ...)` that + emits the underlying `T`'s schema on the wire and exposes an + `IsSet` flag to the handler. Huma v2 does not support pointer + query parameters (they panic at registration, see + `github.com/danielgtaylor/huma` issue #288); `OptionalParam` is + the framework-sanctioned idiom. + + Practical corollary: Huma's parameter binder treats `?cursor=` + (empty value) identically to an absent parameter + (`huma.go:881-882: isSet = value != ""`). A three-state contract + (absent / present-empty / present-nonempty) is therefore not + expressible against Huma; the wire contract collapses to + two states. Design APIs around that. The test a reviewer applies: does running an undeclared query parameter or an undeclared body field through the handler change @@ -279,6 +320,56 @@ of the framework, not a blessing of hidden contract. Callers that send undeclared parameters are sending noise; handlers that read them are violating this principle. +### 3.5.2 Framework-level headers declared once, not per-operation + +Wire contract that applies uniformly across every operation — +CSRF request headers, request-ID response headers, the custom +response headers SSE streams emit for runtime status — is still +real wire contract and must appear in the spec. OpenAPI 3.1 has +no mechanism to declare headers "globally" for all operations +(see +[speakeasy.com/openapi/responses/headers](https://www.speakeasy.com/openapi/responses/headers)); +the canonical pattern is: + +1. Define the header once. Request headers live as operation + parameters; response headers get a named entry in + `components.headers`. +2. Reference it from every operation it applies to (request + params go on the Operation's `Parameters`; response headers + use `{"$ref": "#/components/headers/NAME"}`). + +Rather than embedding the reference in 50+ input/output structs, +attach it at the single function every operation already flows +through: + +- **Request headers (e.g. `X-GC-Request`)** — `cityPost`, + `cityPut`, `cityPatch`, `cityDelete`, and `cityRegister` in + `internal/api/city_scope.go` pass `addMutationCSRFParam` as a + Huma operation handler. One line at the route helper covers + every current and future mutation endpoint. +- **Response headers (e.g. `X-GC-Request-Id`)** — + `registerFrameworkHeaders` in + `internal/api/huma_spec_framework.go` runs once after all + routes are registered. It populates `components.headers` and + walks every operation's responses to inject a `$ref` pointing + at the named component. +- **Per-stream custom response headers (e.g. `GC-Agent-Status`, + `GC-Session-State`, `GC-Session-Status`)** — catalogued in + `sseStatusHeaders` (`internal/api/sse.go`) and referenced by + name at each `registerSSE` call site via + `sseResponseHeaders("GC-Agent-Status")`. Colocated with the + operation where the handler emits the header, one catalog + entry per header. + +These patterns are not exceptions to §3.5.1; they are the +§3.5.1-compliant mechanism for cross-cutting concerns. The spec +still fully describes the contract — every operation's parameters +and response headers list the header explicitly — but the +declaration happens at one function call, not fifty struct +definitions. Middleware remains the single source of enforcement; +the spec remains the single source of description; the helpers +keep the two aligned. + ### 3.6 Raw pass-through for provider-native session frames Session transcript streaming and query endpoints forward @@ -546,9 +637,21 @@ type guard in the SPA. Every file-path citation in this spec is load-bearing. If you rename or remove a cited symbol (`events.KnownEventTypes`, `EventPayloadUnion`, `TestEveryKnownEventTypeHasRegisteredPayload`, -`cmd/gc/apiroute.go:apiClient()`, etc.), **update this spec in the -same commit**. A stale spec is worse than no spec — it misleads -future agents about what invariants hold. +`cmd/gc/apiroute.go:apiClient()`, `addMutationCSRFParam`, +`registerFrameworkHeaders`, `sseResponseHeaders`, +`OptionalParam`, `cityinit.Initializer`, `cityinit.InitRequest`, +`cityinit.InitResult`, `cityinit.UnregisterRequest`, +`cityinit.UnregisterResult`, `cityinit.ErrNotRegistered`, +`TransientCityEventSource`, etc.), **update this spec in the same +commit**. A stale spec is worse than no spec — it misleads future +agents about what invariants hold. + +Framework-specific patterns and Huma quirks are captured in +[`specs/huma-usage.md`](./huma-usage.md); update that file in the +same commit when you touch any of: `OptionalParam`, +`addMutationCSRFParam`, `registerFrameworkHeaders`, +`sseResponseHeaders`, the SSE hand-writing zone, or the +`cityPost`/`cityRegister` helper family. Line numbers are deliberately omitted so the spec survives refactors. Package names, type names, and test names are stable diff --git a/specs/huma-usage.md b/specs/huma-usage.md new file mode 100644 index 0000000000..7bb493beb6 --- /dev/null +++ b/specs/huma-usage.md @@ -0,0 +1,341 @@ +# Huma usage notes + +Gas City's HTTP + SSE control plane is built on Huma v2 +(`github.com/danielgtaylor/huma/v2`). This document captures the +framework-level patterns and quirks we've learned the hard way, so +future contributors can find the answer here instead of re-deriving +it from the framework source or stumbling into the same traps. + +Every pattern below is load-bearing in the current implementation; +removing one breaks a specific invariant described in +`specs/architecture.md`. + +## 1. Presence detection for query parameters + +**Problem.** Some endpoints need to distinguish "query parameter +absent" from "query parameter present with empty/zero value." The +naive answer — a pointer field like `Cursor *string` — panics at +registration because Huma v2 does not support pointer query +parameters (`huma.go:189`, issue +[#288](https://github.com/danielgtaylor/huma/issues/288)). + +**Solution.** Use the `OptionalParam[T]` wrapper. Gas City's copy +lives at `internal/api/huma_optional_param.go`: + +```go +type OptionalParam[T any] struct { + Value T + IsSet bool +} + +func (o OptionalParam[T]) Schema(r huma.Registry) *huma.Schema { + return huma.SchemaFromType(r, reflect.TypeOf(o.Value)) +} + +func (o *OptionalParam[T]) Receiver() reflect.Value { + return reflect.ValueOf(o).Elem().Field(0) +} + +func (o *OptionalParam[T]) OnParamSet(isSet bool, _ any) { + o.IsSet = isSet +} +``` + +Declared on an input struct: + +```go +type SessionListInput struct { + CityScope + Cursor OptionalParam[string] `query:"cursor"` +} +``` + +`Schema()` emits the wrapped `T`'s schema unchanged, so the wire +contract is identical to a plain `query:"cursor" string` field. +`IsSet` is populated by `OnParamSet`, which Huma's binder calls +after parsing. + +**The sharp edge.** Huma's parameter binder treats empty string +values as `isSet = false` (`huma.go:881-882`: +`isSet = value != ""`). `?cursor=` and no `cursor=` at all are +indistinguishable at the handler. Three-state semantics (absent / +present-empty / present-nonempty) are not expressible under Huma; +APIs must design around two-state (`IsSet && value != ""`). + +This constraint is intrinsic to the framework, not a Gas City +choice. Do not work around it by reading raw URL values in a +Resolver (that's a `specs/architecture.md` §3.5.1 violation). + +## 2. Pointer query params panic; Resolvers cannot rescue them + +Huma checks the kind of every query/header/path/cookie field at +registration (`huma.go:189`): + +```go +if f.Type.Kind() == reflect.Pointer { + panic("pointers are not supported for form/header/path/query parameters") +} +``` + +The TODO in the source acknowledges this is solvable but hasn't +been done. Until it is, every optional query parameter must be a +value type plus either `OptionalParam[T]` (for presence detection) +or a sentinel value the handler interprets. + +Resolvers (`huma.Resolver` / `huma.ResolverWithPath`) can validate +or normalize values the struct already declares, but they MUST NOT +read keys off `ctx.URL().Query()` or `ctx.Header()` that are not +declared fields. Hidden contract, §3.5.1 violation. + +## 3. Operation handlers — Huma's escape hatch for cross-cutting +ops-level metadata + +`huma.Get`/`Post`/`Put`/`Patch`/`Delete` take `operationHandlers +...func(op *Operation)` after the handler. They run AFTER Huma has +auto-populated `OperationID` / `Summary` / `Method` / `Path`, so +the handler can append to `op.Parameters` (or `op.Responses`, +`op.Tags`, etc.) without disturbing the auto-generated identity. + +Gas City uses this to declare the `X-GC-Request` CSRF header +without touching 50+ input structs. In +`internal/api/city_scope.go`: + +```go +func addMutationCSRFParam(op *huma.Operation) { + // idempotent append of the X-GC-Request header param +} + +func cityPost[I, O any] (sm *SupervisorMux, tail string, + fn func(*Server, context.Context, *I) (*O, error), +) { + huma.Post(sm.humaAPI, cityScopePrefix+tail, bindCity(sm, fn), + addMutationCSRFParam) +} +``` + +**The sharp edge.** If you skip `huma.Post` and call +`huma.Register` with a manually-built `huma.Operation`, you lose +auto-`OperationID`/Summary generation — the spec ends up with +empty `operationId` fields, and oapi-codegen falls back to +path-synthesized method names (`PostV0CityCityNameRigNameAction` +instead of `PostV0CityByCityNameRigByNameByAction`). The +regenerated Go client fails to compile against existing call +sites. Lesson: use the convenience helpers, not `huma.Register` +directly, whenever the auto-generated metadata is acceptable. + +## 4. OperationID generation + +`huma.go:2192`: + +```go +var GenerateOperationID = func(method, path string, response any) string { + // ... produces kebab-case IDs like "post-v0-city-by-city-name-rig-by-name-by-action" + return casing.Kebab(action + "-" + + reRemoveIDs.ReplaceAllString(path, "by-$1")) +} +``` + +`{param}` segments are replaced with `by-param`. The kebab ID +becomes the operationId in the OpenAPI spec, which oapi-codegen +PascalCases into Go method names. Explicit `OperationID` values +(e.g. `"create-agent"`) are preserved. + +Skipping `huma.Post` and registering via `huma.Register` directly +bypasses this generator. If you see a mutation endpoint in +`openapi.json` with no `operationId`, you're looking at a case +where someone built the Operation by hand and forgot to populate +`OperationID`. + +## 5. Response headers that apply to every operation + +OpenAPI 3.1 has no "global response header" mechanism. The +canonical pattern is to declare the header once in +`components.headers` and `$ref` it from each operation's +responses: + +```yaml +components: + headers: + X-Request-Id: + description: ... + schema: {type: string} + +paths: + /foo: + get: + responses: + "200": + headers: + X-Request-Id: + $ref: "#/components/headers/X-Request-Id" +``` + +Gas City does this in `internal/api/huma_spec_framework.go`: +`registerFrameworkHeaders` runs once after all routes are +registered, populates `api.OpenAPI().Components.Headers`, and +walks every operation's responses to inject `{Ref: "#/..."}` +entries. One named component backs 284 `$ref` entries, so the +spec stays readable and generated clients get a single typed +accessor. + +`huma.Header` is a type alias for `huma.Param` +(`openapi.go:588`), which is why `Response.Headers` is typed +`map[string]*Param`. Setting `Ref: "#/components/headers/NAME"` +on the Param produces the `$ref` emission in the generated YAML. + +## 6. Per-operation custom response headers (SSE streams) + +Some SSE endpoints emit custom headers like `GC-Agent-Status` +or `GC-Session-State` via `hctx.SetHeader`. These are not +global — they apply to specific streams — so they belong on +the operation's 200 response, not in +`components.headers`. But inlining the description at the +registration site means 3+ copies of the same description +string. + +Gas City's pattern (`internal/api/sse.go`): + +```go +var sseStatusHeaders = map[string]string{ + "GC-Agent-Status": "...", + "GC-Session-State": "...", + "GC-Session-Status": "...", +} + +func sseResponseHeaders(names ...string) map[string]*huma.Response { + // builds Responses["200"].Headers from the catalog; + // panics if a name is not in sseStatusHeaders. +} +``` + +Registration site: + +```go +registerSSE(sm.humaAPI, huma.Operation{ + OperationID: "stream-agent-output", + ... + Responses: sseResponseHeaders("GC-Agent-Status"), +}, ...) +``` + +The panic-on-unknown-name is load-bearing: it makes drift +between `hctx.SetHeader` call sites and the declared contract +surface at startup, not at "why isn't my client getting this +header" debug time. + +## 7. Middleware ordering vs request validation + +Middleware registered via `api.UseMiddleware` wraps the whole +request path; it runs BEFORE Huma's parameter validation. That's +why `humaCSRFMiddleware` can return `403 csrf: ...` when +`X-GC-Request` is missing — by the time Huma's validator sees +the request, the middleware has already short-circuited. + +Without the middleware, Huma's own validator would return +`422 Unprocessable Entity` with `"required header parameter is +missing"` for the same case. Both reject, but 403 is the +semantically correct status for CSRF rejection and is more +scannable in logs. + +**Implication.** A spec that declares a header `required:true` +must have a mechanism (middleware, handler code, Huma validator) +that actually enforces it. The spec describes the contract; it +does not enforce it. `TestGeneratedClientInSync` catches spec +drift but not enforcement drift. + +## 8. Convenience path auto-metadata — `_convenience_id` sentinel + +When `huma.Post` et al. run, they set `op.OperationID` to the +auto-generated kebab-case ID, then run user operation handlers, +then check: + +```go +if operation.OperationID == opID { + operation.Metadata["_convenience_id"] = opID + operation.Metadata["_convenience_id_out"] = o +} +``` + +The metadata sentinel lets `huma.Group` regenerate the ID if the +operation gets moved under a prefix. If you want to override the +auto-ID, do it inside an operation handler: + +```go +huma.Post(api, "/things", handler, func(op *huma.Operation) { + op.OperationID = "create-thing" +}) +``` + +The same trick applies to `Summary`. + +## 9. SSE: three hand-written zones around typed payloads + +`internal/api/sse.go` is the single sanctioned location for SSE +protocol framing. It hand-writes `id:` / `event:` / `data:` / +blank-line separators around a call to `encoder.Encode(payload)` +where payload is a typed, schema-registered struct. This is the +§3.4 carve-out described in the architecture spec. + +Three related hand-written helpers: + +1. `beginSSEStream(hctx)` — sets `Content-Type: text/event-stream`, + `Cache-Control: no-cache`, `Connection: keep-alive` via + `hctx.SetHeader`, then returns the body writer, JSON encoder, + and Flusher. +2. `writeSSEFrame(...)` — emits one frame: optional `id:` line, + `event:` line from the type→event reverse lookup, `data:` + line via `encoder.Encode(data)`, blank line, flush. +3. `attachSSEResponseSchema(...)` — populates + `op.Responses["200"].Content["text/event-stream"]` with a + `oneOf` schema listing every event variant. Called before + `huma.Register` so the spec describes the stream's event + shapes. + +Third-party SSE adapters (e.g. Huma's built-in `sse.Register`) +do not support precheck errors; an op like `stream-events` that +needs to 503 before committing stream headers has to use our +`registerSSE` wrapper instead. + +## 10. When to reach for `CreateHooks = nil` + +Huma's default `Config` installs a `SchemaLinkTransformer` that +adds `$schema` properties and `Link` response headers. Gas City +disables this with `cfg.CreateHooks = nil` in +`huma_handlers_supervisor.go:newSupervisorHumaAPI`. Reason: we +don't serve the schema at the Link target, and the extra header +clutters generated clients. + +If you need other CreateHooks, don't nil the slice — append your +hook to the default list. + +## 11. Adapter import path + +`humago` lives at +`github.com/danielgtaylor/huma/v2/adapters/humago`, not at +`github.com/danielgtaylor/huma/v2/humago`. The README examples in +some Huma versions are out of date on this; `pkg.go.dev` has the +real path. + +```go +import "github.com/danielgtaylor/huma/v2/adapters/humago" +``` + +## What we don't use from Huma + +- **`huma.Group`** — we have a single API per supervisor and a + per-city handler dispatcher. Group's prefix/middleware + composition would duplicate what `cityRegister` already does. +- **Huma's built-in `sse.Register`** — cannot return HTTP errors + before stream headers commit; we use our own `registerSSE` for + that reason (§9 above). +- **`huma.OperationTags`** convenience — we set `Tags` directly on + the Operation literal where present. + +## Upstream issues we're tracking + +- **#288** — pointer query params panic. Blocks the cleanest form + of optional parameters; `OptionalParam[T]` is the documented + workaround and will remain the idiom here even after a fix. +- Response-header "global declaration" ergonomics — OpenAPI 3.1 + limitation, not a Huma gap. Our `registerFrameworkHeaders` is + the `$ref` pattern applied programmatically. diff --git a/test/integration/huma_binary_test.go b/test/integration/huma_binary_test.go index 010567e812..41cba34ae3 100644 --- a/test/integration/huma_binary_test.go +++ b/test/integration/huma_binary_test.go @@ -31,12 +31,19 @@ import ( func TestHumaBinary_SupervisorBootsAndServesSpec(t *testing.T) { bin := buildGCBinary(t) - gcHome := t.TempDir() // macOS caps AF_UNIX paths at ~104 chars. t.TempDir() paths on - // macOS are long enough that /gc/supervisor.sock blows - // past the limit. Use a short /tmp-rooted dir for XDG_RUNTIME_DIR - // so the socket path stays under the limit. - runtimeDir := shortTempDir(t) + // macOS are long enough that /supervisor.sock blows past + // the limit. An isolated GC_HOME override keeps the supervisor + // socket under GC_HOME, so both GC_HOME and XDG_RUNTIME_DIR must + // live under the short /tmp-rooted test directory. + root := shortTempDir(t) + gcHome := filepath.Join(root, "home") + runtimeDir := filepath.Join(root, "run") + for _, dir := range []string{gcHome, runtimeDir} { + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatalf("mkdir %s: %v", dir, err) + } + } port := reserveFreePort(t) writeSupervisorConfig(t, gcHome, port) if err := seedDoltIdentityForRoot(gcHome); err != nil { @@ -283,3 +290,402 @@ func waitHTTP(t *testing.T, url string, deadline time.Duration) { } } } + +// TestHumaBinary_CityCreateAsync exercises the async POST /v0/city +// contract end-to-end against a live supervisor: subscribe to +// /v0/events/stream, POST /v0/city, verify the handler returns 202 +// immediately with {ok, name, path}, then assert a city.ready event +// for that city name arrives on the SSE stream. This is the test MC's +// live contract harness implicitly needs — without it, any +// regression in Scaffold, the reconciler's city.ready emission, or +// the supervisor event multiplexer would ship unnoticed. +// +// Build-tagged `integration`; run with: +// +// go test -tags=integration ./test/integration/ -run TestHumaBinary_CityCreateAsync +func TestHumaBinary_CityCreateAsync(t *testing.T) { + bin := buildGCBinary(t) + + root := shortTempDir(t) + gcHome := filepath.Join(root, "home") + runtimeDir := filepath.Join(root, "run") + for _, dir := range []string{gcHome, runtimeDir} { + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatalf("mkdir %s: %v", dir, err) + } + } + port := reserveFreePort(t) + writeSupervisorConfig(t, gcHome, port) + + baseURL := "http://127.0.0.1:" + strconv.Itoa(port) + env := append(os.Environ(), + "GC_HOME="+gcHome, + "XDG_RUNTIME_DIR="+runtimeDir, + ) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + cmd := exec.CommandContext(ctx, bin, "supervisor", "run") + cmd.Env = env + stderr, err := cmd.StderrPipe() + if err != nil { + t.Fatalf("stderr pipe: %v", err) + } + if err := cmd.Start(); err != nil { + t.Fatalf("start supervisor: %v", err) + } + var supervisorLog strings.Builder + go func() { _, _ = io.Copy(&supervisorLog, stderr) }() + t.Cleanup(func() { + cancel() + _ = cmd.Wait() + if t.Failed() { + t.Logf("supervisor stderr:\n%s", supervisorLog.String()) + } + }) + + waitHTTP(t, baseURL+"/health", 10*time.Second) + + // 1. POST /v0/city. Expected: 202 Accepted, body contains name + // matching the directory basename. We POST first because the + // supervisor event stream rejects subscriptions when no event + // providers are registered (503 no_providers), which is the + // case before any city exists. + cityDir := filepath.Join(gcHome, "async-test-city") + body := `{"dir":"` + cityDir + `","provider":"claude"}` + postReq, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+"/v0/city", strings.NewReader(body)) + if err != nil { + t.Fatalf("build post request: %v", err) + } + postReq.Header.Set("Content-Type", "application/json") + postReq.Header.Set("X-GC-Request", "true") + postStart := time.Now() + postResp, err := http.DefaultClient.Do(postReq) + if err != nil { + t.Fatalf("POST /v0/city: %v", err) + } + postDur := time.Since(postStart) + postBody, _ := io.ReadAll(postResp.Body) + _ = postResp.Body.Close() + if postResp.StatusCode != http.StatusAccepted { + t.Fatalf("POST /v0/city status = %d, want 202; body: %s", postResp.StatusCode, string(postBody)) + } + if postDur > 20*time.Second { + t.Errorf("POST /v0/city took %s, want fast scaffold response (<20s); async contract is broken", postDur) + } + var createResp struct { + OK bool `json:"ok"` + Name string `json:"name"` + Path string `json:"path"` + } + if err := json.Unmarshal(postBody, &createResp); err != nil { + t.Fatalf("decode create response: %v; body: %s", err, string(postBody)) + } + if !createResp.OK { + t.Errorf("ok = false; body: %s", string(postBody)) + } + if createResp.Name == "" { + t.Fatalf("empty city name in response; body: %s", string(postBody)) + } + if createResp.Path != cityDir { + t.Errorf("path = %q, want %q", createResp.Path, cityDir) + } + t.Logf("POST /v0/city returned 202 in %s for city %q", postDur.Round(time.Millisecond), createResp.Name) + + // 2. Subscribe to /v0/events/stream. No retry: Scaffold writes + // the city to cities.toml synchronously before POST returns, and + // TransientCityEventProviders reads cities.toml directly, so the + // mux contains this city's event provider by the time the client + // receives 202. after_cursor=0 requests replay from the start + // so the client doesn't miss city.ready if it fires between POST + // return and subscribe. + streamCtx, streamCancel := context.WithTimeout(context.Background(), 90*time.Second) + t.Cleanup(streamCancel) + streamReq, err := http.NewRequestWithContext(streamCtx, http.MethodGet, baseURL+"/v0/events/stream?after_cursor=0", nil) + if err != nil { + t.Fatalf("build stream request: %v", err) + } + streamReq.Header.Set("Accept", "text/event-stream") + streamResp, err := http.DefaultClient.Do(streamReq) + if err != nil { + t.Fatalf("GET /v0/events/stream: %v", err) + } + defer streamResp.Body.Close() //nolint:errcheck + if streamResp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(streamResp.Body) + t.Fatalf("GET /v0/events/stream status = %d, want 200; body: %s", streamResp.StatusCode, string(body)) + } + + // Collect events on a background goroutine; surface them via a + // channel so the test body can block until the expected one + // arrives (or a timeout fires). + eventLines := make(chan string, 128) + go readSSEFrames(streamResp.Body, eventLines) + + // 3. Wait for city.ready (or city.init_failed) on the SSE stream + // whose envelope Subject == createResp.Name. This is the async + // completion contract the MC live harness relies on. + deadline := time.After(120 * time.Second) + for { + select { + case <-deadline: + t.Fatalf("timed out waiting for city.ready for %q; collected %d lines so far", createResp.Name, len(eventLines)) + case line, ok := <-eventLines: + if !ok { + t.Fatalf("SSE stream closed before city.ready for %q arrived", createResp.Name) + } + // SSE "data:" lines carry JSON envelopes. Ignore + // heartbeats, comments, framing lines. + if !strings.HasPrefix(line, "data: ") { + continue + } + payload := strings.TrimPrefix(line, "data: ") + var env struct { + Type string `json:"type"` + Subject string `json:"subject"` + } + if err := json.Unmarshal([]byte(payload), &env); err != nil { + continue + } + if env.Subject != createResp.Name { + continue + } + switch env.Type { + case "city.ready": + t.Logf("received city.ready for %q — async contract satisfied", createResp.Name) + return + case "city.init_failed": + t.Fatalf("received city.init_failed for %q: %s", createResp.Name, payload) + } + } + } +} + +// TestHumaBinary_CityUnregisterAsync exercises the async +// POST /v0/city/{cityName}/unregister contract end-to-end against a +// live supervisor. Creates a city, waits for city.ready, then POSTs +// unregister and asserts city.unregistered arrives on the same SSE +// stream. Symmetric with TestHumaBinary_CityCreateAsync. +// +// Build-tagged `integration`; run with: +// +// go test -tags=integration ./test/integration/ -run TestHumaBinary_CityUnregisterAsync +func TestHumaBinary_CityUnregisterAsync(t *testing.T) { + bin := buildGCBinary(t) + + root := shortTempDir(t) + gcHome := filepath.Join(root, "home") + runtimeDir := filepath.Join(root, "run") + for _, dir := range []string{gcHome, runtimeDir} { + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatalf("mkdir %s: %v", dir, err) + } + } + port := reserveFreePort(t) + writeSupervisorConfig(t, gcHome, port) + + baseURL := "http://127.0.0.1:" + strconv.Itoa(port) + env := append(os.Environ(), + "GC_HOME="+gcHome, + "XDG_RUNTIME_DIR="+runtimeDir, + ) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + cmd := exec.CommandContext(ctx, bin, "supervisor", "run") + cmd.Env = env + stderr, err := cmd.StderrPipe() + if err != nil { + t.Fatalf("stderr pipe: %v", err) + } + if err := cmd.Start(); err != nil { + t.Fatalf("start supervisor: %v", err) + } + var supervisorLog strings.Builder + go func() { _, _ = io.Copy(&supervisorLog, stderr) }() + t.Cleanup(func() { + cancel() + _ = cmd.Wait() + if t.Failed() { + t.Logf("supervisor stderr:\n%s", supervisorLog.String()) + } + }) + + waitHTTP(t, baseURL+"/health", 10*time.Second) + + // 1. Create a city. + cityDir := filepath.Join(gcHome, "unregister-test-city") + body := `{"dir":"` + cityDir + `","provider":"claude"}` + postReq, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+"/v0/city", strings.NewReader(body)) + if err != nil { + t.Fatalf("build post request: %v", err) + } + postReq.Header.Set("Content-Type", "application/json") + postReq.Header.Set("X-GC-Request", "true") + postResp, err := http.DefaultClient.Do(postReq) + if err != nil { + t.Fatalf("POST /v0/city: %v", err) + } + postBody, _ := io.ReadAll(postResp.Body) + _ = postResp.Body.Close() + if postResp.StatusCode != http.StatusAccepted { + t.Fatalf("POST /v0/city status = %d, want 202; body: %s", postResp.StatusCode, string(postBody)) + } + var createResp struct { + Name string `json:"name"` + Path string `json:"path"` + } + if err := json.Unmarshal(postBody, &createResp); err != nil { + t.Fatalf("decode create response: %v; body: %s", err, string(postBody)) + } + + // 2. Subscribe to /v0/events/stream and wait for city.ready so + // we know the reconciler has fully adopted the city (the + // unregister reconcile path we're testing operates on the + // running set). + streamCtx, streamCancel := context.WithTimeout(context.Background(), 180*time.Second) + t.Cleanup(streamCancel) + streamReq, err := http.NewRequestWithContext(streamCtx, http.MethodGet, baseURL+"/v0/events/stream?after_cursor=0", nil) + if err != nil { + t.Fatalf("build stream request: %v", err) + } + streamReq.Header.Set("Accept", "text/event-stream") + streamResp, err := http.DefaultClient.Do(streamReq) + if err != nil { + t.Fatalf("GET /v0/events/stream: %v", err) + } + defer streamResp.Body.Close() //nolint:errcheck + if streamResp.StatusCode != http.StatusOK { + b, _ := io.ReadAll(streamResp.Body) + t.Fatalf("GET /v0/events/stream status = %d, want 200; body: %s", streamResp.StatusCode, string(b)) + } + + eventLines := make(chan string, 256) + go readSSEFrames(streamResp.Body, eventLines) + + readyDeadline := time.After(120 * time.Second) +ready: + for { + select { + case <-readyDeadline: + t.Fatalf("timed out waiting for city.ready for %q", createResp.Name) + case line, ok := <-eventLines: + if !ok { + t.Fatalf("SSE stream closed before city.ready for %q arrived", createResp.Name) + } + if !strings.HasPrefix(line, "data: ") { + continue + } + var env struct { + Type string `json:"type"` + Subject string `json:"subject"` + } + if err := json.Unmarshal([]byte(strings.TrimPrefix(line, "data: ")), &env); err != nil { + continue + } + if env.Subject == createResp.Name && env.Type == "city.ready" { + break ready + } + } + } + t.Logf("city %q ready; issuing unregister", createResp.Name) + + // 3. POST /v0/city/{cityName}/unregister. Expect 202. + unregURL := baseURL + "/v0/city/" + createResp.Name + "/unregister" + unregReq, err := http.NewRequestWithContext(ctx, http.MethodPost, unregURL, nil) + if err != nil { + t.Fatalf("build unregister request: %v", err) + } + unregReq.Header.Set("X-GC-Request", "true") + unregStart := time.Now() + unregResp, err := http.DefaultClient.Do(unregReq) + if err != nil { + t.Fatalf("POST unregister: %v", err) + } + unregDur := time.Since(unregStart) + unregBody, _ := io.ReadAll(unregResp.Body) + _ = unregResp.Body.Close() + if unregResp.StatusCode != http.StatusAccepted { + t.Fatalf("POST unregister status = %d, want 202; body: %s", unregResp.StatusCode, string(unregBody)) + } + if unregDur > 20*time.Second { + t.Errorf("POST unregister took %s, want fast response (<20s)", unregDur) + } + var unregBodyDecoded struct { + OK bool `json:"ok"` + Name string `json:"name"` + Path string `json:"path"` + } + if err := json.Unmarshal(unregBody, &unregBodyDecoded); err != nil { + t.Fatalf("decode unregister response: %v; body: %s", err, string(unregBody)) + } + // macOS resolves /tmp -> /private/tmp at some boundaries; strip + // either prefix so the test survives wherever the canonicalization + // kicked in. + canonicalize := func(p string) string { return strings.TrimPrefix(p, "/private") } + if !unregBodyDecoded.OK || unregBodyDecoded.Name != createResp.Name || canonicalize(unregBodyDecoded.Path) != canonicalize(createResp.Path) { + t.Errorf("unregister response mismatch: got %+v, want ok=true name=%q path=%q", unregBodyDecoded, createResp.Name, createResp.Path) + } + t.Logf("POST unregister returned 202 in %s", unregDur.Round(time.Millisecond)) + + // 4. Wait for city.unregistered (or city.unregister_failed) on + // the SSE stream. + unregDeadline := time.After(120 * time.Second) + for { + select { + case <-unregDeadline: + t.Fatalf("timed out waiting for city.unregistered for %q", createResp.Name) + case line, ok := <-eventLines: + if !ok { + t.Fatalf("SSE stream closed before city.unregistered for %q arrived", createResp.Name) + } + if !strings.HasPrefix(line, "data: ") { + continue + } + var env struct { + Type string `json:"type"` + Subject string `json:"subject"` + } + if err := json.Unmarshal([]byte(strings.TrimPrefix(line, "data: ")), &env); err != nil { + continue + } + if env.Subject != createResp.Name { + continue + } + switch env.Type { + case "city.unregistered": + t.Logf("received city.unregistered for %q — async unregister contract satisfied", createResp.Name) + return + case "city.unregister_failed": + t.Fatalf("received city.unregister_failed for %q: %s", createResp.Name, strings.TrimPrefix(line, "data: ")) + } + } + } +} + +// readSSEFrames scans a text/event-stream body line-by-line and ships +// each line to out. Returns when the underlying reader closes (EOF or +// connection drop). The channel is closed to signal "no more frames". +func readSSEFrames(body io.ReadCloser, out chan<- string) { + defer close(out) + buf := make([]byte, 0, 4096) + chunk := make([]byte, 4096) + for { + n, err := body.Read(chunk) + if n > 0 { + buf = append(buf, chunk[:n]...) + for { + i := strings.IndexByte(string(buf), '\n') + if i < 0 { + break + } + line := strings.TrimRight(string(buf[:i]), "\r") + buf = buf[i+1:] + out <- line + } + } + if err != nil { + return + } + } +} From 2718f1080aea2759548eb672e479a47ed5028b9e Mon Sep 17 00:00:00 2001 From: Chris Sells Date: Wed, 22 Apr 2026 10:31:59 -0700 Subject: [PATCH 06/85] Expand GC bead API contract --- cmd/gc/api_state.go | 123 ++-- cmd/gc/api_state_test.go | 65 ++ cmd/gc/dashboard/web/dist/dashboard.js | 10 +- cmd/gc/dashboard/web/src/api.ts | 34 +- cmd/gc/dashboard/web/src/panels/admin.ts | 18 +- cmd/gc/dashboard/web/src/panels/convoys.ts | 6 +- cmd/gc/dashboard/web/src/panels/issues.ts | 14 +- cmd/gc/dashboard/web/src/panels/mail.ts | 12 +- docs/schema/openapi.json | 124 +--- docs/schema/openapi.txt | 124 +--- internal/api/convoy_event_stream_test.go | 24 + internal/api/event_payloads.go | 100 +-- internal/api/genclient/client_gen.go | 190 +----- internal/api/handler_beads_test.go | 139 +++++ internal/api/huma_handlers_beads.go | 16 +- internal/api/huma_types_beads.go | 25 +- internal/api/openapi.json | 124 +--- internal/beads/bdstore.go | 24 +- internal/beads/bdstore_test.go | 12 +- internal/beads/caching_store.go | 32 +- internal/beads/caching_store_reads.go | 36 +- internal/beads/caching_store_test.go | 261 +++++++- internal/beads/caching_store_writes.go | 74 +++ test/integration/gc_live_contract_test.go | 695 +++++++++++++++++++++ 24 files changed, 1585 insertions(+), 697 deletions(-) create mode 100644 test/integration/gc_live_contract_test.go diff --git a/cmd/gc/api_state.go b/cmd/gc/api_state.go index d0ff7fcb26..0614571382 100644 --- a/cmd/gc/api_state.go +++ b/cmd/gc/api_state.go @@ -236,11 +236,6 @@ func (cs *controllerState) applyBeadEventToStores(evt events.Event) { if len(evt.Payload) == 0 { return } - // Skip events we emitted ourselves (reconciler-detected changes). - if evt.Actor == "cache-reconcile" { - return - } - cs.mu.RLock() stores := make([]beads.Store, 0, len(cs.beadStores)+1) for _, s := range cs.beadStores { @@ -256,7 +251,9 @@ func (cs *controllerState) applyBeadEventToStores(evt events.Event) { cached.ApplyEvent(evt.Type, evt.Payload) } } - cs.Poke() + if evt.Actor != "cache-reconcile" { + cs.Poke() + } } // update replaces the config, session provider, and reopens stores. @@ -449,20 +446,24 @@ func (cs *controllerState) Orders() []orders.Order { // EnableOrder creates or updates an override with enabled=true. func (cs *controllerState) EnableOrder(name, rig string) error { enabled := true - return cs.editor.MergeOrderOverride(config.OrderOverride{ - Name: name, - Rig: rig, - Enabled: &enabled, + return cs.mutateAndPoke(func() error { + return cs.editor.MergeOrderOverride(config.OrderOverride{ + Name: name, + Rig: rig, + Enabled: &enabled, + }) }) } // DisableOrder creates or updates an override with enabled=false. func (cs *controllerState) DisableOrder(name, rig string) error { enabled := false - return cs.editor.MergeOrderOverride(config.OrderOverride{ - Name: name, - Rig: rig, - Enabled: &enabled, + return cs.mutateAndPoke(func() error { + return cs.editor.MergeOrderOverride(config.OrderOverride{ + Name: name, + Rig: rig, + Enabled: &enabled, + }) }) } @@ -511,97 +512,127 @@ func (cs *controllerState) ResumeCity() error { // CreateAgent adds a new agent to city.toml. func (cs *controllerState) CreateAgent(a config.Agent) error { - return cs.editor.CreateAgent(a) + return cs.mutateAndPoke(func() error { + return cs.editor.CreateAgent(a) + }) } // UpdateAgent partially updates an existing agent definition in city.toml. func (cs *controllerState) UpdateAgent(name string, patch api.AgentUpdate) error { - return cs.editor.UpdateAgent(name, configedit.AgentUpdate{ - Provider: patch.Provider, - Scope: patch.Scope, - Suspended: patch.Suspended, + return cs.mutateAndPoke(func() error { + return cs.editor.UpdateAgent(name, configedit.AgentUpdate{ + Provider: patch.Provider, + Scope: patch.Scope, + Suspended: patch.Suspended, + }) }) } // DeleteAgent removes an agent from city.toml. func (cs *controllerState) DeleteAgent(name string) error { - return cs.editor.DeleteAgent(name) + return cs.mutateAndPoke(func() error { + return cs.editor.DeleteAgent(name) + }) } // CreateRig adds a new rig to city.toml. func (cs *controllerState) CreateRig(r config.Rig) error { - return cs.editor.CreateRig(r) + return cs.mutateAndPoke(func() error { + return cs.editor.CreateRig(r) + }) } // UpdateRig partially updates a rig in city.toml. func (cs *controllerState) UpdateRig(name string, patch api.RigUpdate) error { - return cs.editor.UpdateRig(name, configedit.RigUpdate{ - Path: patch.Path, - Prefix: patch.Prefix, - Suspended: patch.Suspended, + return cs.mutateAndPoke(func() error { + return cs.editor.UpdateRig(name, configedit.RigUpdate{ + Path: patch.Path, + Prefix: patch.Prefix, + Suspended: patch.Suspended, + }) }) } // DeleteRig removes a rig from city.toml. func (cs *controllerState) DeleteRig(name string) error { - return cs.editor.DeleteRig(name) + return cs.mutateAndPoke(func() error { + return cs.editor.DeleteRig(name) + }) } // CreateProvider adds a new city-level provider to city.toml. func (cs *controllerState) CreateProvider(name string, spec config.ProviderSpec) error { - return cs.editor.CreateProvider(name, spec) + return cs.mutateAndPoke(func() error { + return cs.editor.CreateProvider(name, spec) + }) } // UpdateProvider partially updates an existing city-level provider. func (cs *controllerState) UpdateProvider(name string, patch api.ProviderUpdate) error { - return cs.editor.UpdateProvider(name, configedit.ProviderUpdate{ - DisplayName: patch.DisplayName, - Base: patch.Base, - Command: patch.Command, - Args: patch.Args, - ArgsAppend: patch.ArgsAppend, - PromptMode: patch.PromptMode, - PromptFlag: patch.PromptFlag, - ReadyDelayMs: patch.ReadyDelayMs, - Env: patch.Env, - OptionsSchemaMerge: patch.OptionsSchemaMerge, - OptionsSchema: patch.OptionsSchema, + return cs.mutateAndPoke(func() error { + return cs.editor.UpdateProvider(name, configedit.ProviderUpdate{ + DisplayName: patch.DisplayName, + Base: patch.Base, + Command: patch.Command, + Args: patch.Args, + ArgsAppend: patch.ArgsAppend, + PromptMode: patch.PromptMode, + PromptFlag: patch.PromptFlag, + ReadyDelayMs: patch.ReadyDelayMs, + Env: patch.Env, + OptionsSchemaMerge: patch.OptionsSchemaMerge, + OptionsSchema: patch.OptionsSchema, + }) }) } // DeleteProvider removes a city-level provider from city.toml. func (cs *controllerState) DeleteProvider(name string) error { - return cs.editor.DeleteProvider(name) + return cs.mutateAndPoke(func() error { + return cs.editor.DeleteProvider(name) + }) } // SetAgentPatch creates or replaces an agent patch in city.toml. func (cs *controllerState) SetAgentPatch(patch config.AgentPatch) error { - return cs.editor.SetAgentPatch(patch) + return cs.mutateAndPoke(func() error { + return cs.editor.SetAgentPatch(patch) + }) } // DeleteAgentPatch removes an agent patch from city.toml. func (cs *controllerState) DeleteAgentPatch(name string) error { - return cs.editor.DeleteAgentPatch(name) + return cs.mutateAndPoke(func() error { + return cs.editor.DeleteAgentPatch(name) + }) } // SetRigPatch creates or replaces a rig patch in city.toml. func (cs *controllerState) SetRigPatch(patch config.RigPatch) error { - return cs.editor.SetRigPatch(patch) + return cs.mutateAndPoke(func() error { + return cs.editor.SetRigPatch(patch) + }) } // DeleteRigPatch removes a rig patch from city.toml. func (cs *controllerState) DeleteRigPatch(name string) error { - return cs.editor.DeleteRigPatch(name) + return cs.mutateAndPoke(func() error { + return cs.editor.DeleteRigPatch(name) + }) } // SetProviderPatch creates or replaces a provider patch in city.toml. func (cs *controllerState) SetProviderPatch(patch config.ProviderPatch) error { - return cs.editor.SetProviderPatch(patch) + return cs.mutateAndPoke(func() error { + return cs.editor.SetProviderPatch(patch) + }) } // DeleteProviderPatch removes a provider patch from city.toml. func (cs *controllerState) DeleteProviderPatch(name string) error { - return cs.editor.DeleteProviderPatch(name) + return cs.mutateAndPoke(func() error { + return cs.editor.DeleteProviderPatch(name) + }) } func (cs *controllerState) mutateAndPoke(mutate func() error) error { diff --git a/cmd/gc/api_state_test.go b/cmd/gc/api_state_test.go index c0755f4f32..b9e3854d16 100644 --- a/cmd/gc/api_state_test.go +++ b/cmd/gc/api_state_test.go @@ -134,6 +134,71 @@ func TestControllerStateUpdate(t *testing.T) { } } +func TestControllerStateCreateRigPokesReconciler(t *testing.T) { + t.Setenv("GC_BEADS", "file") + + cityDir := t.TempDir() + if err := os.WriteFile(filepath.Join(cityDir, "city.toml"), []byte("[workspace]\nname = \"city1\"\n"), 0o644); err != nil { + t.Fatalf("write city.toml: %v", err) + } + cfg := &config.City{ + Workspace: config.Workspace{Name: "city1"}, + } + cs := newControllerState(context.Background(), cfg, runtime.NewFake(), events.NewFake(), "city1", cityDir) + cs.pokeCh = make(chan struct{}, 1) + + if err := cs.CreateRig(config.Rig{Name: "rig1", Path: t.TempDir()}); err != nil { + t.Fatalf("CreateRig: %v", err) + } + + select { + case <-cs.pokeCh: + default: + t.Fatal("CreateRig did not poke the reconciler") + } +} + +func TestControllerStateAppliesCacheReconcileBeadEventsToStores(t *testing.T) { + backing := beads.NewMemStore() + created, err := backing.Create(beads.Bead{Title: "root"}) + if err != nil { + t.Fatalf("Create: %v", err) + } + cached := beads.NewCachingStoreForTest(backing, nil) + if err := cached.Prime(context.Background()); err != nil { + t.Fatalf("Prime: %v", err) + } + + updated := created + updated.Status = "in_progress" + payload, err := json.Marshal(updated) + if err != nil { + t.Fatalf("marshal updated bead: %v", err) + } + cs := &controllerState{ + beadStores: map[string]beads.Store{"alpha": cached}, + pokeCh: make(chan struct{}, 1), + } + + cs.applyBeadEventToStores(events.Event{ + Type: events.BeadUpdated, + Actor: "cache-reconcile", + Subject: created.ID, + Payload: payload, + }) + + items, err := cached.List(beads.ListQuery{AllowScan: true}) + if err != nil { + t.Fatalf("List: %v", err) + } + if len(items) != 1 || items[0].ID != created.ID { + t.Fatalf("cached items = %+v, want only %s", items, created.ID) + } + if items[0].Status != "in_progress" { + t.Fatalf("status after cache-reconcile event = %q, want in_progress", items[0].Status) + } +} + func TestControllerStateBuildStoresUsesScopeLocalFileStores(t *testing.T) { t.Setenv("GC_BEADS", "file") diff --git a/cmd/gc/dashboard/web/dist/dashboard.js b/cmd/gc/dashboard/web/dist/dashboard.js index ad2e593c11..ec9931ec7b 100644 --- a/cmd/gc/dashboard/web/dist/dashboard.js +++ b/cmd/gc/dashboard/web/dist/dashboard.js @@ -1,6 +1,6 @@ -(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))a(r);new MutationObserver(r=>{for(const i of r)if(i.type==="childList")for(const o of i.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&a(o)}).observe(document,{childList:!0,subtree:!0});function n(r){const i={};return r.integrity&&(i.integrity=r.integrity),r.referrerPolicy&&(i.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?i.credentials="include":r.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function a(r){if(r.ep)return;r.ep=!0;const i=n(r);fetch(r.href,i)}})();const En=/\{[^{}]+\}/g,kn=()=>{var e,t;return typeof process=="object"&&Number.parseInt((t=(e=process==null?void 0:process.versions)==null?void 0:e.node)==null?void 0:t.substring(0,2))>=18&&process.versions.undici};function Nn(){return Math.random().toString(36).slice(2,11)}function $n(e){let{baseUrl:t="",Request:n=globalThis.Request,fetch:a=globalThis.fetch,querySerializer:r,bodySerializer:i,headers:o,requestInitExt:l=void 0,...d}={...e};l=kn()?l:void 0,t=Nt(t);const p=[];async function f(u,y){const{baseUrl:m,fetch:h=a,Request:w=n,headers:C,params:b={},parseAs:E="json",querySerializer:N,bodySerializer:j=i??xn,body:B,...$}=y||{};let R=t;m&&(R=Nt(m)??t);let T=typeof r=="function"?r:Et(r);N&&(T=typeof N=="function"?N:Et({...typeof r=="object"?r:{},...N}));const ue=B===void 0?void 0:j(B,kt(o,C,b.header)),Ye=kt(ue===void 0||ue instanceof FormData?{}:{"Content-Type":"application/json"},o,C,b.header),Ze={redirect:"follow",...d,...$,body:ue,headers:Ye};let ee,fe,G=new n(Tn(u,{baseUrl:R,params:b,querySerializer:T}),Ze),x;for(const A in $)A in G||(G[A]=$[A]);if(p.length){ee=Nn(),fe=Object.freeze({baseUrl:R,fetch:h,parseAs:E,querySerializer:T,bodySerializer:j});for(const A of p)if(A&&typeof A=="object"&&typeof A.onRequest=="function"){const O=await A.onRequest({request:G,schemaPath:u,params:b,options:fe,id:ee});if(O)if(O instanceof n)G=O;else if(O instanceof Response){x=O;break}else throw new Error("onRequest: must return new Request() or Response() when modifying the request")}}if(!x){try{x=await h(G,l)}catch(A){let O=A;if(p.length)for(let q=p.length-1;q>=0;q--){const te=p[q];if(te&&typeof te=="object"&&typeof te.onError=="function"){const Ce=await te.onError({request:G,error:O,schemaPath:u,params:b,options:fe,id:ee});if(Ce){if(Ce instanceof Response){O=void 0,x=Ce;break}if(Ce instanceof Error){O=Ce;continue}throw new Error("onError: must return new Response() or instance of Error")}}}if(O)throw O}if(p.length)for(let A=p.length-1;A>=0;A--){const O=p[A];if(O&&typeof O=="object"&&typeof O.onResponse=="function"){const q=await O.onResponse({request:G,response:x,schemaPath:u,params:b,options:fe,id:ee});if(q){if(!(q instanceof Response))throw new Error("onResponse: must return new Response() when modifying the response");x=q}}}}if(x.status===204||G.method==="HEAD"||x.headers.get("Content-Length")==="0")return x.ok?{data:void 0,response:x}:{error:void 0,response:x};if(x.ok)return E==="stream"?{data:x.body,response:x}:{data:await x[E](),response:x};let pe=await x.text();try{pe=JSON.parse(pe)}catch{}return{error:pe,response:x}}return{request(u,y,m){return f(y,{...m,method:u.toUpperCase()})},GET(u,y){return f(u,{...y,method:"GET"})},PUT(u,y){return f(u,{...y,method:"PUT"})},POST(u,y){return f(u,{...y,method:"POST"})},DELETE(u,y){return f(u,{...y,method:"DELETE"})},OPTIONS(u,y){return f(u,{...y,method:"OPTIONS"})},HEAD(u,y){return f(u,{...y,method:"HEAD"})},PATCH(u,y){return f(u,{...y,method:"PATCH"})},TRACE(u,y){return f(u,{...y,method:"TRACE"})},use(...u){for(const y of u)if(y){if(typeof y!="object"||!("onRequest"in y||"onResponse"in y||"onError"in y))throw new Error("Middleware must be an object with one of `onRequest()`, `onResponse() or `onError()`");p.push(y)}},eject(...u){for(const y of u){const m=p.indexOf(y);m!==-1&&p.splice(m,1)}}}}function Fe(e,t,n){if(t==null)return"";if(typeof t=="object")throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");return`${e}=${(n==null?void 0:n.allowReserved)===!0?t:encodeURIComponent(t)}`}function Pt(e,t,n){if(!t||typeof t!="object")return"";const a=[],r={simple:",",label:".",matrix:";"}[n.style]||"&";if(n.style!=="deepObject"&&n.explode===!1){for(const l in t)a.push(l,n.allowReserved===!0?t[l]:encodeURIComponent(t[l]));const o=a.join(",");switch(n.style){case"form":return`${e}=${o}`;case"label":return`.${o}`;case"matrix":return`;${e}=${o}`;default:return o}}for(const o in t){const l=n.style==="deepObject"?`${e}[${o}]`:o;a.push(Fe(l,t[o],n))}const i=a.join(r);return n.style==="label"||n.style==="matrix"?`${r}${i}`:i}function jt(e,t,n){if(!Array.isArray(t))return"";if(n.explode===!1){const i={form:",",spaceDelimited:"%20",pipeDelimited:"|"}[n.style]||",",o=(n.allowReserved===!0?t:t.map(l=>encodeURIComponent(l))).join(i);switch(n.style){case"simple":return o;case"label":return`.${o}`;case"matrix":return`;${e}=${o}`;default:return`${e}=${o}`}}const a={simple:",",label:".",matrix:";"}[n.style]||"&",r=[];for(const i of t)n.style==="simple"||n.style==="label"?r.push(n.allowReserved===!0?i:encodeURIComponent(i)):r.push(Fe(e,i,n));return n.style==="label"||n.style==="matrix"?`${a}${r.join(a)}`:r.join(a)}function Et(e){return function(n){const a=[];if(n&&typeof n=="object")for(const r in n){const i=n[r];if(i!=null){if(Array.isArray(i)){if(i.length===0)continue;a.push(jt(r,i,{style:"form",explode:!0,...e==null?void 0:e.array,allowReserved:(e==null?void 0:e.allowReserved)||!1}));continue}if(typeof i=="object"){a.push(Pt(r,i,{style:"deepObject",explode:!0,...e==null?void 0:e.object,allowReserved:(e==null?void 0:e.allowReserved)||!1}));continue}a.push(Fe(r,i,e))}}return a.join("&")}}function Ln(e,t){let n=e;for(const a of e.match(En)??[]){let r=a.substring(1,a.length-1),i=!1,o="simple";if(r.endsWith("*")&&(i=!0,r=r.substring(0,r.length-1)),r.startsWith(".")?(o="label",r=r.substring(1)):r.startsWith(";")&&(o="matrix",r=r.substring(1)),!t||t[r]===void 0||t[r]===null)continue;const l=t[r];if(Array.isArray(l)){n=n.replace(a,jt(r,l,{style:o,explode:i}));continue}if(typeof l=="object"){n=n.replace(a,Pt(r,l,{style:o,explode:i}));continue}if(o==="matrix"){n=n.replace(a,`;${Fe(r,l)}`);continue}n=n.replace(a,o==="label"?`.${encodeURIComponent(l)}`:encodeURIComponent(l))}return n}function xn(e,t){return e instanceof FormData?e:t&&(t.get instanceof Function?t.get("Content-Type")??t.get("content-type"):t["Content-Type"]??t["content-type"])==="application/x-www-form-urlencoded"?new URLSearchParams(e).toString():JSON.stringify(e)}function Tn(e,t){var r;let n=`${t.baseUrl}${e}`;(r=t.params)!=null&&r.path&&(n=Ln(n,t.params.path));let a=t.querySerializer(t.params.query??{});return a.startsWith("?")&&(a=a.substring(1)),a&&(n+=`?${a}`),n}function kt(...e){const t=new Headers;for(const n of e){if(!n||typeof n!="object")continue;const a=n instanceof Headers?n.entries():Object.entries(n);for(const[r,i]of a)if(i===null)t.delete(r);else if(Array.isArray(i))for(const o of i)t.append(r,o);else i!==void 0&&t.set(r,i)}return t}function Nt(e){return e.endsWith("/")?e.substring(0,e.length-1):e}const An={bodySerializer:e=>JSON.stringify(e,(t,n)=>typeof n=="bigint"?n.toString():n)};function Rn({onRequest:e,onSseError:t,onSseEvent:n,responseTransformer:a,responseValidator:r,sseDefaultRetryDelay:i,sseMaxRetryAttempts:o,sseMaxRetryDelay:l,sseSleepFn:d,url:p,...f}){let u;const y=d??(w=>new Promise(C=>setTimeout(C,w)));return{stream:async function*(){let w=i??3e3,C=0;const b=f.signal??new AbortController().signal;for(;!b.aborted;){C++;const E=f.headers instanceof Headers?f.headers:new Headers(f.headers);u!==void 0&&E.set("Last-Event-ID",u);try{const N={redirect:"follow",...f,body:f.serializedBody,headers:E,signal:b};let j=new Request(p,N);e&&(j=await e(p,N));const $=await(f.fetch??globalThis.fetch)(j);if(!$.ok)throw new Error(`SSE failed: ${$.status} ${$.statusText}`);if(!$.body)throw new Error("No body in SSE response");const R=$.body.pipeThrough(new TextDecoderStream).getReader();let T="";const ue=()=>{try{R.cancel()}catch{}};b.addEventListener("abort",ue);try{for(;;){const{done:Ye,value:Ze}=await R.read();if(Ye)break;T+=Ze,T=T.replace(/\r\n?/g,` -`);const ee=T.split(` +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))a(r);new MutationObserver(r=>{for(const i of r)if(i.type==="childList")for(const o of i.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&a(o)}).observe(document,{childList:!0,subtree:!0});function n(r){const i={};return r.integrity&&(i.integrity=r.integrity),r.referrerPolicy&&(i.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?i.credentials="include":r.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function a(r){if(r.ep)return;r.ep=!0;const i=n(r);fetch(r.href,i)}})();const kn=/\{[^{}]+\}/g,Nn=()=>{var e,t;return typeof process=="object"&&Number.parseInt((t=(e=process==null?void 0:process.versions)==null?void 0:e.node)==null?void 0:t.substring(0,2))>=18&&process.versions.undici};function $n(){return Math.random().toString(36).slice(2,11)}function Ln(e){let{baseUrl:t="",Request:n=globalThis.Request,fetch:a=globalThis.fetch,querySerializer:r,bodySerializer:i,headers:o,requestInitExt:l=void 0,...d}={...e};l=Nn()?l:void 0,t=$t(t);const p=[];async function f(u,y){const{baseUrl:m,fetch:h=a,Request:w=n,headers:E,params:b={},parseAs:C="json",querySerializer:N,bodySerializer:I=i??Tn,body:M,...$}=y||{};let O=t;m&&(O=$t(m)??t);let A=typeof r=="function"?r:kt(r);N&&(A=typeof N=="function"?N:kt({...typeof r=="object"?r:{},...N}));const fe=M===void 0?void 0:I(M,Nt(o,E,b.header)),Ze=Nt(fe===void 0||fe instanceof FormData?{}:{"Content-Type":"application/json"},o,E,b.header),et={redirect:"follow",...d,...$,body:fe,headers:Ze};let te,pe,G=new n(An(u,{baseUrl:O,params:b,querySerializer:A}),et),x;for(const R in $)R in G||(G[R]=$[R]);if(p.length){te=$n(),pe=Object.freeze({baseUrl:O,fetch:h,parseAs:C,querySerializer:A,bodySerializer:I});for(const R of p)if(R&&typeof R=="object"&&typeof R.onRequest=="function"){const q=await R.onRequest({request:G,schemaPath:u,params:b,options:pe,id:te});if(q)if(q instanceof n)G=q;else if(q instanceof Response){x=q;break}else throw new Error("onRequest: must return new Request() or Response() when modifying the request")}}if(!x){try{x=await h(G,l)}catch(R){let q=R;if(p.length)for(let _=p.length-1;_>=0;_--){const ne=p[_];if(ne&&typeof ne=="object"&&typeof ne.onError=="function"){const Ce=await ne.onError({request:G,error:q,schemaPath:u,params:b,options:pe,id:te});if(Ce){if(Ce instanceof Response){q=void 0,x=Ce;break}if(Ce instanceof Error){q=Ce;continue}throw new Error("onError: must return new Response() or instance of Error")}}}if(q)throw q}if(p.length)for(let R=p.length-1;R>=0;R--){const q=p[R];if(q&&typeof q=="object"&&typeof q.onResponse=="function"){const _=await q.onResponse({request:G,response:x,schemaPath:u,params:b,options:pe,id:te});if(_){if(!(_ instanceof Response))throw new Error("onResponse: must return new Response() when modifying the response");x=_}}}}if(x.status===204||G.method==="HEAD"||x.headers.get("Content-Length")==="0")return x.ok?{data:void 0,response:x}:{error:void 0,response:x};if(x.ok)return C==="stream"?{data:x.body,response:x}:{data:await x[C](),response:x};let ye=await x.text();try{ye=JSON.parse(ye)}catch{}return{error:ye,response:x}}return{request(u,y,m){return f(y,{...m,method:u.toUpperCase()})},GET(u,y){return f(u,{...y,method:"GET"})},PUT(u,y){return f(u,{...y,method:"PUT"})},POST(u,y){return f(u,{...y,method:"POST"})},DELETE(u,y){return f(u,{...y,method:"DELETE"})},OPTIONS(u,y){return f(u,{...y,method:"OPTIONS"})},HEAD(u,y){return f(u,{...y,method:"HEAD"})},PATCH(u,y){return f(u,{...y,method:"PATCH"})},TRACE(u,y){return f(u,{...y,method:"TRACE"})},use(...u){for(const y of u)if(y){if(typeof y!="object"||!("onRequest"in y||"onResponse"in y||"onError"in y))throw new Error("Middleware must be an object with one of `onRequest()`, `onResponse() or `onError()`");p.push(y)}},eject(...u){for(const y of u){const m=p.indexOf(y);m!==-1&&p.splice(m,1)}}}}function He(e,t,n){if(t==null)return"";if(typeof t=="object")throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");return`${e}=${(n==null?void 0:n.allowReserved)===!0?t:encodeURIComponent(t)}`}function jt(e,t,n){if(!t||typeof t!="object")return"";const a=[],r={simple:",",label:".",matrix:";"}[n.style]||"&";if(n.style!=="deepObject"&&n.explode===!1){for(const l in t)a.push(l,n.allowReserved===!0?t[l]:encodeURIComponent(t[l]));const o=a.join(",");switch(n.style){case"form":return`${e}=${o}`;case"label":return`.${o}`;case"matrix":return`;${e}=${o}`;default:return o}}for(const o in t){const l=n.style==="deepObject"?`${e}[${o}]`:o;a.push(He(l,t[o],n))}const i=a.join(r);return n.style==="label"||n.style==="matrix"?`${r}${i}`:i}function It(e,t,n){if(!Array.isArray(t))return"";if(n.explode===!1){const i={form:",",spaceDelimited:"%20",pipeDelimited:"|"}[n.style]||",",o=(n.allowReserved===!0?t:t.map(l=>encodeURIComponent(l))).join(i);switch(n.style){case"simple":return o;case"label":return`.${o}`;case"matrix":return`;${e}=${o}`;default:return`${e}=${o}`}}const a={simple:",",label:".",matrix:";"}[n.style]||"&",r=[];for(const i of t)n.style==="simple"||n.style==="label"?r.push(n.allowReserved===!0?i:encodeURIComponent(i)):r.push(He(e,i,n));return n.style==="label"||n.style==="matrix"?`${a}${r.join(a)}`:r.join(a)}function kt(e){return function(n){const a=[];if(n&&typeof n=="object")for(const r in n){const i=n[r];if(i!=null){if(Array.isArray(i)){if(i.length===0)continue;a.push(It(r,i,{style:"form",explode:!0,...e==null?void 0:e.array,allowReserved:(e==null?void 0:e.allowReserved)||!1}));continue}if(typeof i=="object"){a.push(jt(r,i,{style:"deepObject",explode:!0,...e==null?void 0:e.object,allowReserved:(e==null?void 0:e.allowReserved)||!1}));continue}a.push(He(r,i,e))}}return a.join("&")}}function xn(e,t){let n=e;for(const a of e.match(kn)??[]){let r=a.substring(1,a.length-1),i=!1,o="simple";if(r.endsWith("*")&&(i=!0,r=r.substring(0,r.length-1)),r.startsWith(".")?(o="label",r=r.substring(1)):r.startsWith(";")&&(o="matrix",r=r.substring(1)),!t||t[r]===void 0||t[r]===null)continue;const l=t[r];if(Array.isArray(l)){n=n.replace(a,It(r,l,{style:o,explode:i}));continue}if(typeof l=="object"){n=n.replace(a,jt(r,l,{style:o,explode:i}));continue}if(o==="matrix"){n=n.replace(a,`;${He(r,l)}`);continue}n=n.replace(a,o==="label"?`.${encodeURIComponent(l)}`:encodeURIComponent(l))}return n}function Tn(e,t){return e instanceof FormData?e:t&&(t.get instanceof Function?t.get("Content-Type")??t.get("content-type"):t["Content-Type"]??t["content-type"])==="application/x-www-form-urlencoded"?new URLSearchParams(e).toString():JSON.stringify(e)}function An(e,t){var r;let n=`${t.baseUrl}${e}`;(r=t.params)!=null&&r.path&&(n=xn(n,t.params.path));let a=t.querySerializer(t.params.query??{});return a.startsWith("?")&&(a=a.substring(1)),a&&(n+=`?${a}`),n}function Nt(...e){const t=new Headers;for(const n of e){if(!n||typeof n!="object")continue;const a=n instanceof Headers?n.entries():Object.entries(n);for(const[r,i]of a)if(i===null)t.delete(r);else if(Array.isArray(i))for(const o of i)t.append(r,o);else i!==void 0&&t.set(r,i)}return t}function $t(e){return e.endsWith("/")?e.substring(0,e.length-1):e}const Rn={bodySerializer:e=>JSON.stringify(e,(t,n)=>typeof n=="bigint"?n.toString():n)};function On({onRequest:e,onSseError:t,onSseEvent:n,responseTransformer:a,responseValidator:r,sseDefaultRetryDelay:i,sseMaxRetryAttempts:o,sseMaxRetryDelay:l,sseSleepFn:d,url:p,...f}){let u;const y=d??(w=>new Promise(E=>setTimeout(E,w)));return{stream:async function*(){let w=i??3e3,E=0;const b=f.signal??new AbortController().signal;for(;!b.aborted;){E++;const C=f.headers instanceof Headers?f.headers:new Headers(f.headers);u!==void 0&&C.set("Last-Event-ID",u);try{const N={redirect:"follow",...f,body:f.serializedBody,headers:C,signal:b};let I=new Request(p,N);e&&(I=await e(p,N));const $=await(f.fetch??globalThis.fetch)(I);if(!$.ok)throw new Error(`SSE failed: ${$.status} ${$.statusText}`);if(!$.body)throw new Error("No body in SSE response");const O=$.body.pipeThrough(new TextDecoderStream).getReader();let A="";const fe=()=>{try{O.cancel()}catch{}};b.addEventListener("abort",fe);try{for(;;){const{done:Ze,value:et}=await O.read();if(Ze)break;A+=et,A=A.replace(/\r\n?/g,` +`);const te=A.split(` -`);T=ee.pop()??"";for(const fe of ee){const G=fe.split(` -`),x=[];let pe;for(const q of G)if(q.startsWith("data:"))x.push(q.replace(/^data:\s*/,""));else if(q.startsWith("event:"))pe=q.replace(/^event:\s*/,"");else if(q.startsWith("id:"))u=q.replace(/^id:\s*/,"");else if(q.startsWith("retry:")){const te=Number.parseInt(q.replace(/^retry:\s*/,""),10);Number.isNaN(te)||(w=te)}let A,O=!1;if(x.length){const q=x.join(` -`);try{A=JSON.parse(q),O=!0}catch{A=q}}O&&(r&&await r(A),a&&(A=await a(A))),n==null||n({data:A,event:pe,id:u,retry:w}),x.length&&(yield A)}}}finally{b.removeEventListener("abort",ue),R.releaseLock()}break}catch(N){if(t==null||t(N),o!==void 0&&C>=o)break;const j=Math.min(w*2**(C-1),l??3e4);await y(j)}}}()}}const On=e=>{switch(e){case"label":return".";case"matrix":return";";case"simple":return",";default:return"&"}},qn=e=>{switch(e){case"form":return",";case"pipeDelimited":return"|";case"spaceDelimited":return"%20";default:return","}},_n=e=>{switch(e){case"label":return".";case"matrix":return";";case"simple":return",";default:return"&"}},It=({allowReserved:e,explode:t,name:n,style:a,value:r})=>{if(!t){const l=(e?r:r.map(d=>encodeURIComponent(d))).join(qn(a));switch(a){case"label":return`.${l}`;case"matrix":return`;${n}=${l}`;case"simple":return l;default:return`${n}=${l}`}}const i=On(a),o=r.map(l=>a==="label"||a==="simple"?e?l:encodeURIComponent(l):He({allowReserved:e,name:n,value:l})).join(i);return a==="label"||a==="matrix"?i+o:o},He=({allowReserved:e,name:t,value:n})=>{if(n==null)return"";if(typeof n=="object")throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");return`${t}=${e?n:encodeURIComponent(n)}`},Bt=({allowReserved:e,explode:t,name:n,style:a,value:r,valueOnly:i})=>{if(r instanceof Date)return i?r.toISOString():`${n}=${r.toISOString()}`;if(a!=="deepObject"&&!t){let d=[];Object.entries(r).forEach(([f,u])=>{d=[...d,f,e?u:encodeURIComponent(u)]});const p=d.join(",");switch(a){case"form":return`${n}=${p}`;case"label":return`.${p}`;case"matrix":return`;${n}=${p}`;default:return p}}const o=_n(a),l=Object.entries(r).map(([d,p])=>He({allowReserved:e,name:a==="deepObject"?`${n}[${d}]`:d,value:p})).join(o);return a==="label"||a==="matrix"?o+l:l},Pn=/\{[^{}]+\}/g,jn=({path:e,url:t})=>{let n=t;const a=t.match(Pn);if(a)for(const r of a){let i=!1,o=r.substring(1,r.length-1),l="simple";o.endsWith("*")&&(i=!0,o=o.substring(0,o.length-1)),o.startsWith(".")?(o=o.substring(1),l="label"):o.startsWith(";")&&(o=o.substring(1),l="matrix");const d=e[o];if(d==null)continue;if(Array.isArray(d)){n=n.replace(r,It({explode:i,name:o,style:l,value:d}));continue}if(typeof d=="object"){n=n.replace(r,Bt({explode:i,name:o,style:l,value:d,valueOnly:!0}));continue}if(l==="matrix"){n=n.replace(r,`;${He({name:o,value:d})}`);continue}const p=encodeURIComponent(l==="label"?`.${d}`:d);n=n.replace(r,p)}return n},In=({baseUrl:e,path:t,query:n,querySerializer:a,url:r})=>{const i=r.startsWith("/")?r:`/${r}`;let o=(e??"")+i;t&&(o=jn({path:t,url:o}));let l=n?a(n):"";return l.startsWith("?")&&(l=l.substring(1)),l&&(o+=`?${l}`),o};function $t(e){const t=e.body!==void 0;if(t&&e.bodySerializer)return"serializedBody"in e?e.serializedBody!==void 0&&e.serializedBody!==""?e.serializedBody:null:e.body!==""?e.body:null;if(t)return e.body}const Bn=async(e,t)=>{const n=typeof t=="function"?await t(e):t;if(n)return e.scheme==="bearer"?`Bearer ${n}`:e.scheme==="basic"?`Basic ${btoa(n)}`:n},Mt=({parameters:e={},...t}={})=>a=>{const r=[];if(a&&typeof a=="object")for(const i in a){const o=a[i];if(o==null)continue;const l=e[i]||t;if(Array.isArray(o)){const d=It({allowReserved:l.allowReserved,explode:!0,name:i,style:"form",value:o,...l.array});d&&r.push(d)}else if(typeof o=="object"){const d=Bt({allowReserved:l.allowReserved,explode:!0,name:i,style:"deepObject",value:o,...l.object});d&&r.push(d)}else{const d=He({allowReserved:l.allowReserved,name:i,value:o});d&&r.push(d)}}return r.join("&")},Mn=e=>{var n;if(!e)return"stream";const t=(n=e.split(";")[0])==null?void 0:n.trim();if(t){if(t.startsWith("application/json")||t.endsWith("+json"))return"json";if(t==="multipart/form-data")return"formData";if(["application/","audio/","image/","video/"].some(a=>t.startsWith(a)))return"blob";if(t.startsWith("text/"))return"text"}},Un=(e,t)=>{var n,a;return t?!!(e.headers.has(t)||(n=e.query)!=null&&n[t]||(a=e.headers.get("Cookie"))!=null&&a.includes(`${t}=`)):!1},Dn=async({security:e,...t})=>{for(const n of e){if(Un(t,n.name))continue;const a=await Bn(n,t.auth);if(!a)continue;const r=n.name??"Authorization";switch(n.in){case"query":t.query||(t.query={}),t.query[r]=a;break;case"cookie":t.headers.append("Cookie",`${r}=${a}`);break;case"header":default:t.headers.set(r,a);break}}},Lt=e=>In({baseUrl:e.baseUrl,path:e.path,query:e.query,querySerializer:typeof e.querySerializer=="function"?e.querySerializer:Mt(e.querySerializer),url:e.url}),xt=(e,t)=>{var a;const n={...e,...t};return(a=n.baseUrl)!=null&&a.endsWith("/")&&(n.baseUrl=n.baseUrl.substring(0,n.baseUrl.length-1)),n.headers=Ut(e.headers,t.headers),n},zn=e=>{const t=[];return e.forEach((n,a)=>{t.push([a,n])}),t},Ut=(...e)=>{const t=new Headers;for(const n of e){if(!n)continue;const a=n instanceof Headers?zn(n):Object.entries(n);for(const[r,i]of a)if(i===null)t.delete(r);else if(Array.isArray(i))for(const o of i)t.append(r,o);else i!==void 0&&t.set(r,typeof i=="object"?JSON.stringify(i):i)}return t};class et{constructor(){this.fns=[]}clear(){this.fns=[]}eject(t){const n=this.getInterceptorIndex(t);this.fns[n]&&(this.fns[n]=null)}exists(t){const n=this.getInterceptorIndex(t);return!!this.fns[n]}getInterceptorIndex(t){return typeof t=="number"?this.fns[t]?t:-1:this.fns.indexOf(t)}update(t,n){const a=this.getInterceptorIndex(t);return this.fns[a]?(this.fns[a]=n,t):!1}use(t){return this.fns.push(t),this.fns.length-1}}const Gn=()=>({error:new et,request:new et,response:new et}),Wn=Mt({allowReserved:!1,array:{explode:!0,style:"form"},object:{explode:!0,style:"deepObject"}}),Fn={"Content-Type":"application/json"},Dt=(e={})=>({...An,headers:Fn,parseAs:"auto",querySerializer:Wn,...e}),Hn=(e={})=>{let t=xt(Dt(),e);const n=()=>({...t}),a=f=>(t=xt(t,f),n()),r=Gn(),i=async f=>{const u={...t,...f,fetch:f.fetch??t.fetch??globalThis.fetch,headers:Ut(t.headers,f.headers),serializedBody:void 0};u.security&&await Dn({...u,security:u.security}),u.requestValidator&&await u.requestValidator(u),u.body!==void 0&&u.bodySerializer&&(u.serializedBody=u.bodySerializer(u.body)),(u.body===void 0||u.serializedBody==="")&&u.headers.delete("Content-Type");const y=u,m=Lt(y);return{opts:y,url:m}},o=async f=>{const{opts:u,url:y}=await i(f),m={redirect:"follow",...u,body:$t(u)};let h=new Request(y,m);for(const $ of r.request.fns)$&&(h=await $(h,u));const w=u.fetch;let C;try{C=await w(h)}catch($){let R=$;for(const T of r.error.fns)T&&(R=await T($,void 0,h,u));if(R=R||{},u.throwOnError)throw R;return u.responseStyle==="data"?void 0:{error:R,request:h,response:void 0}}for(const $ of r.response.fns)$&&(C=await $(C,h,u));const b={request:h,response:C};if(C.ok){const $=(u.parseAs==="auto"?Mn(C.headers.get("Content-Type")):u.parseAs)??"json";if(C.status===204||C.headers.get("Content-Length")==="0"){let T;switch($){case"arrayBuffer":case"blob":case"text":T=await C[$]();break;case"formData":T=new FormData;break;case"stream":T=C.body;break;case"json":default:T={};break}return u.responseStyle==="data"?T:{data:T,...b}}let R;switch($){case"arrayBuffer":case"blob":case"formData":case"text":R=await C[$]();break;case"json":{const T=await C.text();R=T?JSON.parse(T):{};break}case"stream":return u.responseStyle==="data"?C.body:{data:C.body,...b}}return $==="json"&&(u.responseValidator&&await u.responseValidator(R),u.responseTransformer&&(R=await u.responseTransformer(R))),u.responseStyle==="data"?R:{data:R,...b}}const E=await C.text();let N;try{N=JSON.parse(E)}catch{}const j=N??E;let B=j;for(const $ of r.error.fns)$&&(B=await $(j,C,h,u));if(B=B||{},u.throwOnError)throw B;return u.responseStyle==="data"?void 0:{error:B,...b}},l=f=>u=>o({...u,method:f}),d=f=>async u=>{const{opts:y,url:m}=await i(u);return Rn({...y,body:y.body,headers:y.headers,method:f,onRequest:async(h,w)=>{let C=new Request(h,w);for(const b of r.request.fns)b&&(C=await b(C,y));return C},serializedBody:$t(y),url:m})};return{buildUrl:f=>Lt({...t,...f}),connect:l("CONNECT"),delete:l("DELETE"),get:l("GET"),getConfig:n,head:l("HEAD"),interceptors:r,options:l("OPTIONS"),patch:l("PATCH"),post:l("POST"),put:l("PUT"),request:o,setConfig:a,sse:{connect:d("CONNECT"),delete:d("DELETE"),get:d("GET"),head:d("HEAD"),options:d("OPTIONS"),patch:d("PATCH"),post:d("POST"),put:d("PUT"),trace:d("TRACE")},trace:l("TRACE")}},ce=Hn(Dt()),zt={debug:console.debug.bind(console),error:console.error.bind(console),info:console.info.bind(console),log:console.log.bind(console),warn:console.warn.bind(console)};let Tt=!1;function Jn(){Tt||typeof window>"u"||(Tt=!0,Ee("debug","debug"),Ee("info","info"),Ee("warn","warn"),Ee("error","error"),Ee("log","info"),window.addEventListener("error",e=>{ie("window","Unhandled error",{colno:e.colno,error:e.error,filename:e.filename,lineno:e.lineno,message:e.message})}),window.addEventListener("unhandledrejection",e=>{ie("window","Unhandled promise rejection",{reason:e.reason})}))}function be(e,t,n){Ve("debug",e,t,n)}function H(e,t,n){Ve("info",e,t,n)}function Je(e,t,n){Ve("warn",e,t,n)}function ie(e,t,n){Ve("error",e,t,n)}function Ve(e,t,n,a){const r=Gt(e,t,n,a);zt[e](`[dashboard][${t}] ${n}`,ze(a)),Wt(r)}function Ee(e,t){const n=zt[e];console[e]=(...a)=>{n(...a),Wt(Gt(t,"console",Kn(a),a.length>1?a.slice(1):a[0]))}}function Gt(e,t,n,a){return{city:Vn(),details:a===void 0?void 0:ze(a),level:e,message:n,scope:t,ts:new Date().toISOString(),url:typeof window>"u"?"":window.location.href}}function Vn(){return typeof window>"u"?"":(new URLSearchParams(window.location.search).get("city")??"").trim()}function Kn(e){if(e.length===0)return"console event";const[t]=e;return typeof t=="string"&&t.trim()!==""?t:t instanceof Error?t.message:"console event"}function Wt(e){const t=JSON.stringify(e);if(typeof navigator<"u"&&typeof navigator.sendBeacon=="function"){const n=new Blob([t],{type:"application/json"});if(navigator.sendBeacon("/__client-log",n))return}fetch("/__client-log",{body:t,credentials:"same-origin",headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})}function ze(e,t=0,n=new WeakSet){if(e==null)return e??null;if(typeof e=="string")return e.length>2e3?`${e.slice(0,1999)}…`:e;if(typeof e=="number"||typeof e=="boolean")return e;if(e instanceof Error)return{message:e.message,name:e.name,stack:e.stack};if(typeof e=="function")return`[function ${e.name||"anonymous"}]`;if(t>=4)return"[max-depth]";if(Array.isArray(e))return e.slice(0,20).map(a=>ze(a,t+1,n));if(typeof e=="object"){if(n.has(e))return"[circular]";n.add(e);const a={};for(const[r,i]of Object.entries(e).slice(0,40))a[r]=ze(i,t+1,n);return a}return String(e)}const ut=["cities","status","supervisor","crew","issues","mail","convoys","activity","admin","options"];let Ge=Jt(window.location.search),ft=[];const De=new Set(ut);function Qn(){return Ge}function pt(){return Ge=Jt(window.location.search),Ge}function ae(...e){e.forEach(t=>De.add(t))}function yt(){ae(...ut)}function Xn(e=!1){if(e)return De.clear(),new Set(ut);const t=new Set(De);return De.clear(),t}function Yn(e){ft=e.map(t=>({error:t.error,name:t.name,path:t.path,phasesCompleted:[...t.phasesCompleted??[]],running:t.running,status:t.status}))}function Ft(){return ft.map(e=>({error:e.error,name:e.name,path:e.path,phasesCompleted:[...e.phasesCompleted],running:e.running,status:e.status}))}function Ht(){const e=Ge;if(e==="")return{kind:"supervisor"};const t=ft.find(n=>n.name===e);return t?t.running?{kind:"running",city:t}:{kind:"not-running",city:t}:{kind:"unknown",name:e}}function Zn(e){if(e){if(e.startsWith("session.")||e.startsWith("agent.")){ae("status","crew","options");return}if(e.startsWith("bead.")){ae("status","issues","convoys","admin","options");return}if(e.startsWith("mail.")){ae("status","mail","options");return}if(e.startsWith("convoy.")){ae("status","convoys");return}if(e.startsWith("city.")){ae("cities","status","supervisor");return}if(e.startsWith("service.")||e.startsWith("provider.")||e.startsWith("rig.")){ae("admin");return}}}function Jt(e){return(new URLSearchParams(e).get("city")??"").trim()}function Vt(){const e=document.querySelector('meta[name="supervisor-url"]');return((e==null?void 0:e.content)??"").replace(/\/+$/,"")}function S(){return Qn()}const g=$n({baseUrl:Vt(),headers:{"X-GC-Request":"true"}});ce.setConfig({baseUrl:Vt(),headers:{"X-GC-Request":"true"}});g.use({async onError({error:e,request:t,schemaPath:n}){return ie("api","Request failed",{error:e,method:t.method,schemaPath:n,url:t.url}),e instanceof Error?e:new Error(String(e))},async onRequest({params:e,request:t,schemaPath:n}){be("api","Request start",{method:t.method,params:e,schemaPath:n,url:t.url})},async onResponse({request:e,response:t,schemaPath:n}){const a={method:e.method,ok:t.ok,schemaPath:n,status:t.status,url:e.url};if(!t.ok||t.status>=400){Je("api","Request response",a);return}be("api","Request response",a)}});function s(e,t={},n=[]){const a=document.createElement(e);for(const[r,i]of Object.entries(t))i===void 0||i===!1||(i===!0?a.setAttribute(r,""):a.setAttribute(r,String(i)));for(const r of n)r!=null&&a.append(typeof r=="string"?document.createTextNode(r):r);return a}function k(e){for(;e.firstChild;)e.removeChild(e.firstChild)}function c(e){return document.getElementById(e)}async function ea(){const e=c("city-tabs");if(!e)return;const{data:t,error:n}=await g.GET("/v0/cities");!n&&(t!=null&&t.items)&&Yn(t.items.map(l=>({error:l.error??void 0,name:l.name??"",path:l.path??void 0,phasesCompleted:l.phases_completed??[],running:l.running===!0,status:l.status??void 0})));const a=Ft();if(n||a.length===0)return;const r=S();k(e);const i=s("nav",{class:"city-tabs"}),o=window.location.pathname||"/";i.append(s("a",{href:o,class:`city-tab${r===""?" active":""}`},[s("span",{class:"city-dot running"})," Supervisor"]));for(const l of a){const d=l.running,p=l.name===r,f=s("a",{href:`${o}?city=${encodeURIComponent(l.name)}`,class:`city-tab${p?" active":""}${d?"":" stopped"}`},[s("span",{class:`city-dot${d?" running":""}`}),` ${l.name}`]);i.append(f)}e.append(i)}function mt(e,t=new Date){if(!e)return"";const n=new Date(e);if(isNaN(n.getTime()))return"";const a=Math.max(0,t.getTime()-n.getTime()),r=Math.floor(a/1e3);if(r<60)return`${r}s ago`;const i=Math.floor(r/60);if(i<60)return`${i}m ago`;const o=Math.floor(i/60);return o<24?`${o}h ago`:`${Math.floor(o/24)}d ago`}const Kt=300*1e3,ta=600*1e3;function U(e){if(!e)return"—";const t=new Date(e);if(Number.isNaN(t.getTime()))return"—";const n=new Date,a=t.getFullYear()===n.getFullYear()?{month:"short",day:"numeric",hour:"numeric",minute:"2-digit"}:{month:"short",day:"numeric",year:"numeric",hour:"numeric",minute:"2-digit"};return t.toLocaleString(void 0,a)}function qe(e){if(!e)return{display:"unknown",colorClass:"unknown"};const t=new Date(e);if(Number.isNaN(t.getTime()))return{display:"unknown",colorClass:"unknown"};const n=Math.max(0,Date.now()-t.getTime()),a=mt(e).replace(" ago","");return n=3?`${t[t.length-1]} (${t[0]}/${t[1]})`:`${t[0]}/${t[t.length-1]}`}function na(e){return!e||!e.includes("/")?"":e.split("/",1)[0]??""}function aa(e){return e.startsWith("agent.")||e.startsWith("session.")?"agent":e.startsWith("bead.")||e.startsWith("convoy.")||e.startsWith("order.")?"work":e.startsWith("mail.")?"comms":"system"}function sa(e){return{"session.started":"▶","session.ended":"■","session.crashed":"☠","session.suspended":"⏸","session.woke":"▶","agent.message":"💬","agent.output":"📝","agent.tool_call":"🛠","agent.tool_result":"✅","agent.error":"⚠","bead.created":"📿","bead.updated":"📝","bead.closed":"✅","convoy.created":"🚚","convoy.closed":"✅","mail.delivered":"📬","mail.read":"📨"}[e]??"📋"}function ra(e,t,n,a){const r=I(t);switch(e){case"session.started":return`${I(n)} started`;case"session.ended":return`${I(n)} ended`;case"session.crashed":return`${I(n)} crashed`;case"session.suspended":return`${I(n)} suspended`;case"session.woke":return`${I(n)} woke`;case"bead.created":return`${r} created bead ${n??""}`.trim();case"bead.updated":return`${r} updated bead ${n??""}`.trim();case"bead.closed":return`${r} closed bead ${n??""}`.trim();case"mail.delivered":return`${r} delivered mail`;case"mail.read":return`${r} read mail`;case"convoy.created":return`${r} created convoy ${n??""}`.trim();case"convoy.closed":return`${r} closed convoy ${n??""}`.trim();default:return a??n??e}}function Ke(e,t){return e?e.length<=t?e:`${e.slice(0,t-1)}…`:""}function Z(e){return typeof e!="number"||Number.isNaN(e)||e<=0?4:e}function Qt(e){switch(Z(e)){case 1:return"badge-red";case 2:return"badge-orange";case 3:return"badge-yellow";default:return"badge-muted"}}function oe(e){switch((e??"").toLowerCase()){case"open":case"running":case"ready":case"working":return"badge-green";case"in_progress":case"pending":case"stale":case"warning":return"badge-yellow";case"closed":case"stopped":return"badge-muted";case"error":case"failed":case"stuck":return"badge-red";default:return"badge-blue"}}async function ia(){var w,C,b;const e=S(),t=c("status-banner");if(!t)return;if(!e){await oa(t);return}la();const[n,a,r,i]=await Promise.all([g.GET("/v0/city/{cityName}/status",{params:{path:{cityName:e}}}),g.GET("/v0/city/{cityName}/sessions",{params:{path:{cityName:e},query:{state:"active",peek:!0}}}),g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:e},query:{status:"open",limit:500}}}),g.GET("/v0/city/{cityName}/convoys",{params:{path:{cityName:e},query:{limit:200}}})]);if(n.error||!n.data){k(t),t.append(s("div",{class:"banner-error"},[`Status unavailable for ${e}`]));return}const o=((w=a.data)==null?void 0:w.items)??[],l=((C=r.data)==null?void 0:C.items)??[],d=((b=i.data)==null?void 0:b.items)??[];ca(e,o);const p=o.filter(E=>!E.pool||!E.running||!E.last_active?!1:Date.now()-new Date(E.last_active).getTime()>=1800*1e3).length,f=l.filter(E=>E.assignee&&E.status!=="closed").length,u=l.filter(E=>Z(E.priority)<=2).length,y=o.filter(E=>!E.running).length,m=s("div",{class:"summary-stats"},[F(n.data.agents.running,"Agents"),F(n.data.work.in_progress,"Assigned"),F(n.data.work.open,"Beads"),F(d.length,"Convoys"),F(n.data.mail.unread,"Unread")]),h=s("div",{class:"summary-alerts"});K(h,p>0,"alert-red",`${p} stuck`),K(h,f>0,"alert-yellow",`${f} assigned`),K(h,u>0,"alert-red",`${u} P1/P2`),K(h,y>0,"alert-red",`${y} dead`),h.childNodes.length||h.append(s("span",{class:"alert-item alert-green"},["All clear"])),k(t),t.append(m,h)}async function oa(e){var u,y;da();const[t,n]=await Promise.all([g.GET("/health"),g.GET("/v0/cities")]),a=t.data,r=((u=n.data)==null?void 0:u.items)??[],i=(a==null?void 0:a.cities_total)??r.length,o=(a==null?void 0:a.cities_running)??r.filter(m=>m.running===!0).length,l=Math.max(i-o,0),d=r.filter(m=>!!m.error).length;if(k(e),t.error&&n.error){e.append(s("div",{class:"banner-error"},["Supervisor status unavailable"]));return}const p=s("div",{class:"summary-stats"},[F(i,"🏙️ Cities"),F(o,"🟢 Running"),F(l,"⏸ Stopped"),F(ua(a==null?void 0:a.uptime_sec),"⏱ Uptime")]),f=s("div",{class:"summary-alerts"});K(f,i===0,"alert-yellow","No registered cities"),K(f,l>0,"alert-yellow",`${l} ${l===1?"city":"cities"} not running`),K(f,d>0,"alert-red",`${d} ${d===1?"city":"cities"} reporting errors`),K(f,!!(a!=null&&a.startup&&!a.startup.ready),"alert-yellow",`⏳ Startup: ${((y=a==null?void 0:a.startup)==null?void 0:y.phase)||"starting"}`),f.childNodes.length||f.append(s("span",{class:"alert-item alert-green"},["✓ Supervisor ready"])),e.append(p,f)}function F(e,t){return s("div",{class:"stat"},[s("span",{class:"stat-value"},[String(e??0)]),s("span",{class:"stat-label"},[t])])}function K(e,t,n,a){t&&e.append(s("span",{class:`alert-item ${n}`},[a]))}function ca(e,t){const n=c("scope-banner"),a=c("scope-badge"),r=c("scope-status");if(!n||!a||!r)return;const i=t.find(l=>!l.rig&&!l.pool);if(!i){n.classList.remove("attached"),n.classList.add("detached"),a.className="badge badge-muted",a.textContent="Detached",k(r),r.append(V("Scope",e),V("Overseer","none"));return}n.classList.remove("attached","detached"),n.classList.add(i.attached?"attached":"detached"),a.className=`badge ${i.attached?"badge-green":"badge-muted"}`,a.textContent=i.attached?"Attached":"Detached",k(r);const o=i.last_active?Date.now()-new Date(i.last_active).getTime()(e.client??ce).sse.get({url:"/v0/city/{cityName}/events/stream",...e}),pa=e=>(e.client??ce).sse.get({url:"/v0/city/{cityName}/session/{id}/stream",...e}),ya=e=>((e==null?void 0:e.client)??ce).sse.get({url:"/v0/events/stream",...e});let se=0,st=null;function ma(e){st=e}function Xt(e){se=Math.max(0,e),document.body.dataset.pauseRefresh=se>0?"true":"false"}function W(){Xt(se+1)}function P(){const e=se>0;if(Xt(se-1),e&&se===0&&st)try{st()}catch(t){ie("ui","popPause listener threw",{error:String(t)})}}function gt(){return se>0}function At(e,t){const n=c("output-panel"),a=c("output-panel-cmd"),r=c("output-panel-content");!n||!a||!r||(a.textContent=e,r.textContent=t,n.classList.add("open"))}function Yt(){var e;(e=c("output-panel"))==null||e.classList.remove("open")}function v(e,t,n){const a=c("toast-container");if(!a)return;const r=document.createElement("div");r.className=`toast toast-${e}`,r.innerHTML=`${Rt(t)}
${Rt(n)}
`,a.append(r);const i=e==="error"?9e3:5e3;window.requestAnimationFrame(()=>{r.classList.add("show")}),window.setTimeout(()=>{r.classList.remove("show"),window.setTimeout(()=>{r.remove()},300)},i)}function _(e,t,n="Unexpected dashboard error"){const a=t instanceof Error?t.message:n;ie("ui",e,{error:t,fallbackMessage:n,message:a}),v("error",e,a)}function ga(){var e,t;document.addEventListener("click",n=>{const a=n.target,r=a==null?void 0:a.closest(".collapse-btn");if(r){const p=r.closest(".panel");p==null||p.classList.toggle("collapsed");return}const i=a==null?void 0:a.closest(".expand-btn");if(!i)return;const o=i.closest(".panel");if(!o)return;const l=o.classList.contains("expanded"),d=!!document.querySelector(".panel.expanded");if(document.querySelectorAll(".panel.expanded").forEach(p=>{p.classList.remove("expanded");const f=p.querySelector(".expand-btn");f&&(f.textContent="Expand")}),l){P();return}o.classList.add("expanded"),i.textContent="✕ Close",d||W()}),document.addEventListener("keydown",n=>{if(n.key!=="Escape")return;const a=document.querySelector(".panel.expanded");if(a){a.classList.remove("expanded");const r=a.querySelector(".expand-btn");r&&(r.textContent="Expand"),P()}}),(e=c("output-close-btn"))==null||e.addEventListener("click",()=>Yt()),(t=c("output-copy-btn"))==null||t.addEventListener("click",async()=>{var a;const n=((a=c("output-panel-content"))==null?void 0:a.textContent)??"";try{await navigator.clipboard.writeText(n),v("success","Copied","Output copied to clipboard")}catch{v("error","Copy failed","Clipboard write was rejected")}})}function Rt(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}function Zt(e){return typeof e=="object"&&e!==null}function en(e){return Zt(e)&&typeof e.timestamp=="string"}function tn(e){return Zt(e)&&typeof e.actor=="string"&&typeof e.seq=="number"&&typeof e.ts=="string"&&typeof e.type=="string"}function ba(e){return tn(e)}function ha(e){return tn(e)&&typeof e.city=="string"}const Ot=[1e3,2e3,4e3,8e3,15e3],va=15e3;function nn(e){return e{var o;let r=0,i=!1;for(;!n.signal.aborted;){try{const{stream:d}=await ya({client:ce,signal:n.signal,onSseEvent:p=>{var u;r=0,i=!1,(u=t==null?void 0:t.onStatus)==null||u.call(t,"live");const f=p.event??"tagged_event";if(f==="heartbeat"){if(!en(p.data)){_("Invalid supervisor heartbeat frame",p);return}e({event:"heartbeat",id:p.id,data:p.data});return}if(f==="tagged_event"){if(!ha(p.data)){_("Invalid supervisor event frame",p);return}e({event:"tagged_event",id:p.id,data:p.data});return}_(`Unexpected supervisor SSE event: ${f}`,p)}});for await(const p of d);if(n.signal.aborted)break}catch(d){if(n.signal.aborted)return;i||(_("Supervisor event stream failed",d),i=!0)}(o=t==null?void 0:t.onStatus)==null||o.call(t,"reconnecting");const l=nn(r);r+=1,await an(l,n.signal)}})(),{close:()=>n.abort()}}function Sa(e,t,n){var r;const a=new AbortController;return(r=n==null?void 0:n.onStatus)==null||r.call(n,"connecting"),(async()=>{var l;let i=0,o=!1;for(;!a.signal.aborted;){try{const{stream:p}=await fa({client:ce,path:{cityName:e},signal:a.signal,onSseEvent:f=>{var m;i=0,o=!1,(m=n==null?void 0:n.onStatus)==null||m.call(n,"live");const u=f.event??"event",y=f.id!==void 0?String(f.id):void 0;if(u==="heartbeat"){if(!en(f.data)){_("Invalid city heartbeat frame",f);return}t({event:"heartbeat",id:y,data:f.data});return}if(u==="event"){if(!ba(f.data)){_("Invalid city event frame",f);return}t({event:"event",id:y,data:f.data});return}_(`Unexpected city SSE event: ${u}`,f)}});for await(const f of p);if(a.signal.aborted)break}catch(p){if(a.signal.aborted)return;o||(_("City event stream failed",p),o=!0)}(l=n==null?void 0:n.onStatus)==null||l.call(n,"reconnecting");const d=nn(i);i+=1,await an(d,a.signal)}})(),{close:()=>a.abort()}}async function an(e,t){if(!t.aborted)return new Promise(n=>{const a=setTimeout(()=>{t.removeEventListener("abort",r),n()},e),r=()=>{clearTimeout(a),t.removeEventListener("abort",r),n()};t.addEventListener("abort",r)})}function Ca(e,t,n){const a=new AbortController;return(async()=>{try{const{stream:r}=await pa({client:ce,path:{cityName:e,id:t},signal:a.signal,onSseEvent:i=>{if(i.data===void 0){_("Session frame missing data",i);return}n({id:i.id!==void 0?String(i.id):void 0,type:i.event??"message",data:i.data})}});for await(const i of r);}catch(r){a.signal.aborted||_("Session stream failed",r)}})(),{close:()=>a.abort()}}function Ea(e){return e.event==="heartbeat"?"heartbeat":e.data.type}let xe=null,me="",Q="",_e=0;async function ka(){const e=S();if(!e){Na();return}const t=c("crew-loading"),n=c("crew-table"),a=c("crew-empty"),r=c("crew-tbody"),i=c("rigged-body"),o=c("pooled-body");if(!t||!n||!a||!r||!i||!o)return;rt("No crew configured"),t.style.display="block",n.style.display="none",a.style.display="none",k(r);const{data:l,error:d}=await g.GET("/v0/city/{cityName}/sessions",{params:{path:{cityName:e},query:{state:"active",peek:!0}}});if(d||!(l!=null&&l.items)){t.textContent="Failed to load crew",he(i,"No rigged agents"),he(o,"No pooled agents");return}const p=l.items,f=await Promise.all(p.map(async m=>{var w;return!!((w=(await g.GET("/v0/city/{cityName}/session/{id}/pending",{params:{path:{cityName:e,id:m.id}}})).data)!=null&&w.pending)})),u=new Map;await Promise.all(p.map(async m=>{var w;if(!m.active_bead||u.has(m.active_bead))return;const h=await g.GET("/v0/city/{cityName}/bead/{id}",{params:{path:{cityName:e,id:m.active_bead}}});u.set(m.active_bead,(w=h.data)!=null&&w.id?h.data.title??h.data.id:m.active_bead)}));const y=p;y.forEach((m,h)=>{const w=$a(m,f[h]??!1),C=m.active_bead?Ke(u.get(m.active_bead)??m.active_bead,24):"—",b=s("tr",{},[s("td",{},[m.template]),s("td",{},[m.rig??"city"]),s("td",{},[s("span",{class:`badge ${oe(w)}`},[w])]),s("td",{},[C]),s("td",{class:qe(m.last_active).colorClass?`activity-${qe(m.last_active).colorClass}`:""},[s("span",{class:"activity-dot"}),` ${qe(m.last_active).display}`]),s("td",{},[s("span",{class:`badge ${m.attached?"badge-green":"badge-muted"}`},[m.attached?"Attached":"Detached"])]),s("td",{},[La(m.template)," ",sn(m.id,m.template)])]);r.append(b)}),c("crew-count").textContent=String(y.length),t.style.display="none",y.length>0?n.style.display="table":(rt("No crew configured"),a.style.display="block"),xa(p,u),Ta(p)}function Na(){const e=c("crew-loading"),t=c("crew-table"),n=c("crew-empty"),a=c("crew-tbody"),r=c("rigged-body"),i=c("pooled-body");!e||!t||!n||!a||!r||!i||(Pe(),c("crew-count").textContent="0",c("rigged-count").textContent="0",c("pooled-count").textContent="0",e.style.display="none",t.style.display="none",n.style.display="block",rt("Select a city to view crew"),k(a),he(r,"Select a city to view rigged agents"),he(i,"Select a city to view pooled agents"))}function rt(e){var t,n;(n=(t=c("crew-empty"))==null?void 0:t.querySelector("p"))==null||n.replaceChildren(document.createTextNode(e))}function $a(e,t){return t?"questions":e.active_bead?"spinning":e.running?"idle":"finished"}function La(e){const t=s("button",{class:"attach-btn",type:"button"},["📎 Attach"]);return t.addEventListener("click",async()=>{const n=`gc agent attach ${e}`;try{await navigator.clipboard.writeText(n),v("success","Attach command copied",n)}catch{v("error","Copy failed",n)}}),t}function sn(e,t){const n=s("button",{class:"agent-log-link",type:"button","data-session-id":e},[t]);return n.addEventListener("click",()=>{Ra(e,t)}),n}function xa(e,t){const n=c("rigged-body"),a=c("rigged-count");if(!n||!a)return;const r=e.filter(o=>o.rig&&o.pool);if(a.textContent=String(r.length),r.length===0){he(n,"No rigged agents");return}const i=s("tbody");r.forEach(o=>{const l=qe(o.last_active),d=o.active_bead?l.colorClass==="red"?"Stuck":l.colorClass==="yellow"?"Stale":"Working":"Idle";i.append(s("tr",{class:`rigged-${d.toLowerCase()}`},[s("td",{},[sn(o.id,o.template)]),s("td",{},[s("span",{class:"badge badge-muted"},[o.pool??"pool"])]),s("td",{},[o.rig??"city"]),s("td",{class:"rigged-issue"},[o.active_bead?`${o.active_bead} ${t.get(o.active_bead)??""}`.trim():"—"]),s("td",{},[s("span",{class:`badge ${oe(d)}`},[d])]),s("td",{class:`activity-${l.colorClass}`},[s("span",{class:"activity-dot"}),` ${l.display}`])]))}),k(n),n.append(s("table",{},[s("thead",{},[s("tr",{},[s("th",{},["Agent"]),s("th",{},["Pool"]),s("th",{},["Rig"]),s("th",{},["Working On"]),s("th",{},["Status"]),s("th",{},["Activity"])])]),i]))}function Ta(e){const t=c("pooled-body"),n=c("pooled-count");if(!t||!n)return;const a=e.filter(i=>!i.rig&&i.pool);if(n.textContent=String(a.length),a.length===0){he(t,"No pooled agents");return}const r=s("tbody");a.forEach(i=>{r.append(s("tr",{},[s("td",{},[i.template]),s("td",{},[s("span",{class:`badge ${i.active_bead?"badge-yellow":"badge-green"}`},[i.active_bead?"Working":"Idle"])]),s("td",{class:"status-hint"},[Ke(i.last_output,80)||"—"]),s("td",{},[U(i.last_active)])]))}),k(t),t.append(s("table",{},[s("thead",{},[s("tr",{},[s("th",{},["Agent"]),s("th",{},["State"]),s("th",{},["Work"]),s("th",{},["Activity"])])]),r]))}function he(e,t){k(e),e.append(s("div",{class:"empty-state"},[s("p",{},[t])]))}function Aa(){var e,t;(e=c("log-drawer-close-btn"))==null||e.addEventListener("click",()=>Pe()),(t=c("log-drawer-older-btn"))==null||t.addEventListener("click",()=>{be("crew","Load older transcript clicked",{hasCursor:Q!=="",sessionID:me}),!(!me||!Q)&&on(me,!0)})}async function Ra(e,t){const n=c("agent-log-drawer"),a=c("log-drawer-agent-name"),r=c("log-drawer-messages"),i=c("log-drawer-loading");if(!n||!a||!r||!i)return;if(me===e&&n.style.display!=="none"){Pe();return}Pe(),me=e,Q="",_e=0,a.textContent=t,k(r),r.append(i),i.style.display="block",n.style.display="block",W(),await on(e,!1);const o=S();o&&(xe=Ca(o,e,l=>Oa(l)))}function Pe(){xe==null||xe.close(),xe=null,me="",Q="";const e=c("agent-log-drawer");e&&e.style.display!=="none"&&(e.style.display="none",P())}function rn(){Pe()}async function on(e,t){var p,f,u,y,m;const n=S(),a=c("log-drawer-messages"),r=c("log-drawer-loading"),i=c("log-drawer-older-btn"),o=c("log-drawer-count");if(!n||!a||!r||!i||!o)return;r.style.display="block";const l=await g.GET("/v0/city/{cityName}/session/{id}/transcript",{params:{path:{cityName:n,id:e},query:{tail:String(t?50:25),before:t?Q:void 0}}});if(r.style.display="none",l.error||!l.data){v("error","Transcript failed",((p=l.error)==null?void 0:p.detail)??"Could not load transcript");return}const d=document.createDocumentFragment();for(const h of l.data.turns??[])d.append(cn(h.role,h.text,h.timestamp)),_e+=1;t?a.prepend(d):(k(a),a.append(d)),a.append(r),r.style.display="none",o.textContent=String(_e),Q=((f=l.data.pagination)==null?void 0:f.truncated_before_message)??"",i.style.display=(u=l.data.pagination)!=null&&u.has_older_messages&&Q?"inline-flex":"none",be("crew","Transcript loaded",{hasOlderMessages:((y=l.data.pagination)==null?void 0:y.has_older_messages)??!1,nextBeforeCursor:Q,prepend:t,sessionID:e,turnCount:((m=l.data.turns)==null?void 0:m.length)??0})}function Oa(e){var r;const t=c("log-drawer-messages");if(!t)return;const n=e.data;if(e.type!=="message"||!((r=n==null?void 0:n.data)!=null&&r.message))return;t.append(cn(n.data.message.role??"agent",n.data.message.text??"",n.data.message.timestamp)),_e+=1,c("log-drawer-count").textContent=String(_e);const a=c("log-drawer-body");a&&(a.scrollTop=a.scrollHeight)}function cn(e,t,n){return s("div",{class:"log-msg"},[s("div",{class:"log-msg-header"},[s("span",{class:`log-msg-type log-msg-type-${qa(e)}`},[e]),s("span",{class:"log-msg-time"},[U(n)])]),s("div",{class:"log-msg-body"},[t])])}function qa(e){switch((e??"").toLowerCase()){case"assistant":case"agent":return"assistant";case"system":return"system";case"result":return"result";default:return"user"}}const _a=3e4,it=new Map,Te=new Map;async function Qe(e=!1){const t=S(),n=Date.now(),a=it.get(t);if(!e&&a&&n-a.fetchedAt<_a)return a;const r=Te.get(t);if(r)return r;const i=Pa(t).then(o=>(it.set(t,o),Te.delete(t),o)).catch(o=>{throw Te.delete(t),o});return Te.set(t,i),i}async function Pa(e){var l,d,p,f,u,y,m,h,w,C,b,E;const t={agents:[],rigs:[],sessions:[],beads:[],mail:[],fetchedAt:Date.now()};if(!e)return t;const[n,a,r,i]=await Promise.all([g.GET("/v0/city/{cityName}/config",{params:{path:{cityName:e}}}),g.GET("/v0/city/{cityName}/rigs",{params:{path:{cityName:e}}}),g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:e},query:{status:"open"}}}),g.GET("/v0/city/{cityName}/mail",{params:{path:{cityName:e}}})]);n.error&&Je("options","Config options request failed",{city:e,detail:n.error.detail??null});const o=(((l=n.data)==null?void 0:l.agents)??[]).map(N=>({id:N.name??"",label:N.name??"",recipient:N.name??""})).filter(N=>N.recipient!=="");return be("options","Fetched options",{agentOptions:o.map(N=>N.recipient),beads:((p=(d=r.data)==null?void 0:d.items)==null?void 0:p.length)??0,city:e,configAgents:((u=(f=n.data)==null?void 0:f.agents)==null?void 0:u.length)??0,mail:((m=(y=i.data)==null?void 0:y.items)==null?void 0:m.length)??0,rigs:((w=(h=a.data)==null?void 0:h.items)==null?void 0:w.length)??0}),{agents:[...new Set(o.map(N=>N.recipient))].sort(),rigs:(((C=a.data)==null?void 0:C.items)??[]).map(N=>N.name??"").filter(Boolean),sessions:o,beads:(((b=r.data)==null?void 0:b.items)??[]).map(N=>({id:N.id??"",title:N.title??""})),mail:(((E=i.data)==null?void 0:E.items)??[]).map(N=>({id:N.id??"",subject:N.subject??""})),fetchedAt:Date.now()}}function ja(){it.clear(),Te.clear()}let Ae=null,Re=null;function Ia(){var e,t,n,a,r,i,o,l,d,p;(e=c("action-modal-close-btn"))==null||e.addEventListener("click",()=>ke(null)),(t=c("action-modal-cancel-btn"))==null||t.addEventListener("click",()=>ke(null)),(a=(n=c("action-modal"))==null?void 0:n.querySelector(".modal-backdrop"))==null||a.addEventListener("click",()=>ke(null)),(r=c("action-form"))==null||r.addEventListener("submit",f=>{var h,w,C;f.preventDefault();const u=((h=c("action-bead-id"))==null?void 0:h.value.trim())??"",y=((w=c("action-target"))==null?void 0:w.value.trim())??"",m=((C=c("action-rig"))==null?void 0:C.value.trim())??"";!u||!y||ke({beadID:u,rig:m,target:y})}),(i=c("confirm-modal-close-btn"))==null||i.addEventListener("click",()=>Ne(!1)),(o=c("confirm-modal-cancel-btn"))==null||o.addEventListener("click",()=>Ne(!1)),(l=c("confirm-modal-confirm-btn"))==null||l.addEventListener("click",()=>Ne(!0)),(p=(d=c("confirm-modal"))==null?void 0:d.querySelector(".modal-backdrop"))==null||p.addEventListener("click",()=>Ne(!1)),document.addEventListener("keydown",f=>{if(f.key==="Escape"){if(ve("action-modal")){ke(null);return}ve("confirm-modal")&&Ne(!1)}})}async function bt(e){const t=c("action-modal"),n=c("action-form"),a=c("action-modal-title"),r=c("action-modal-submit-btn"),i=c("action-bead-group"),o=c("action-bead-id"),l=c("action-bead-hint"),d=c("action-target"),p=c("action-target-label"),f=c("action-rig-group"),u=c("action-rig"),y=c("action-modal-help"),m=c("action-target-list"),h=c("action-rig-list");if(!t||!n||!a||!r||!i||!o||!l||!d||!p||!f||!u||!y||!m||!h)return _("Action modal unavailable",new Error("missing action modal DOM")),null;const w=await Qe();return qt(m,w.agents),qt(h,w.rigs),a.textContent=e.title,r.textContent=Ma(e.mode),p.textContent=e.mode==="reassign"?"Assignee":"Target agent or pool",y.textContent=Ua(e.mode),o.value=e.beadID??"",o.readOnly=!!e.beadID,i.classList.toggle("readonly",o.readOnly),l.textContent=e.beadLabel??"",d.value=e.initialTarget??"",u.value=e.initialRig??"",f.hidden=e.mode==="reassign",u.disabled=e.mode==="reassign",ve("action-modal")||W(),t.style.display="flex",window.setTimeout(()=>{if(e.beadID){d.focus();return}o.focus()},0),new Promise(C=>{Ae=C})}async function Ba(e){const t=c("confirm-modal"),n=c("confirm-modal-title"),a=c("confirm-modal-body"),r=c("confirm-modal-confirm-btn");return!t||!n||!a||!r?(_("Confirm modal unavailable",new Error("missing confirm modal DOM")),!1):(n.textContent=e.title,a.textContent=e.body,r.textContent=e.confirmLabel,ve("confirm-modal")||W(),t.style.display="flex",new Promise(i=>{Re=i}))}function qt(e,t){k(e),t.forEach(n=>{e.append(s("option",{value:n}))})}function Ma(e){switch(e){case"assign":return"Assign";case"reassign":return"Reassign";default:return"Sling"}}function Ua(e){switch(e){case"assign":return"Launch a bead directly to a target, with an optional rig override.";case"reassign":return"Pick a new assignee from the active city sessions or type one manually.";default:return"Dispatch this bead to a target, with an optional rig constraint."}}function ke(e){const t=c("action-modal"),n=c("action-form");if(!t||!n)return;const a=ve("action-modal");t.style.display="none",n.reset(),c("action-rig").disabled=!1,c("action-bead-id").readOnly=!1,a&&P(),Ae==null||Ae(e),Ae=null}function Ne(e){const t=c("confirm-modal");if(!t)return;const n=ve("confirm-modal");t.style.display="none",n&&P(),Re==null||Re(e),Re=null}function ve(e){var t;return((t=c(e))==null?void 0:t.style.display)==="flex"}let We=[],ot="ready",we="all",Xe="";async function le(){var o,l,d,p;const e=S(),t=c("issues-list");if(!t)return;if(!e){Da();return}const[n,a,r]=await Promise.all([g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:e},query:{status:"open",limit:500}}}),g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:e},query:{status:"in_progress",limit:500}}}),Qe(!0)]);if(n.error&&a.error||!((o=n.data)!=null&&o.items)&&!((l=a.data)!=null&&l.items)){k(t),t.append(s("div",{class:"panel-error"},["Could not load beads."]));return}We=[...((d=n.data)==null?void 0:d.items)??[],...((p=a.data)==null?void 0:p.items)??[]].filter(f=>!za(f)).sort((f,u)=>{const y=Z(f.priority),m=Z(u.priority);return y!==m?y-m:(u.created_at??"").localeCompare(f.created_at??"")}),c("issues-count").textContent=String(We.length);const i=c("rig-filter-tabs");i&&(k(i),i.append(ct("all",we==="all")),r.rigs.forEach(f=>i.append(ct(f,we===f)))),ht()}function Da(){const e=c("issues-list"),t=c("rig-filter-tabs"),n=c("issue-detail");if(!e||!t||!n)return;ye();const a=n.style.display==="block";n.style.display="none",e.style.display="block",k(e),e.append(s("div",{class:"empty-state"},[s("p",{},["Select a city to view beads"])])),k(t),we="all",Xe="",We=[],t.append(ct("all",!0)),c("issues-count").textContent="0",a&&P()}function ht(){const e=c("issues-list");if(!e)return;k(e);const t=We.filter(a=>{const r=a.assignee?"progress":"ready",i=ot==="all"||ot===r,o=we==="all"||tt(a)===we;return i&&o});if(t.length===0){e.append(s("div",{class:"empty-state"},[s("p",{},["No beads"])]));return}const n=s("tbody");t.forEach(a=>{const r=s("tr",{class:`issue-row priority-${Z(a.priority)}`,"data-issue-id":a.id??"","data-status":a.assignee?"progress":"ready","data-rig":tt(a)},[s("td",{},[s("span",{class:`badge ${Qt(a.priority)}`},[`P${Z(a.priority)}`])]),s("td",{},[s("span",{class:"issue-id"},[a.id??""])]),s("td",{class:"issue-title"},[Ke(a.title??a.id??"",80)]),s("td",{class:"issue-rig"},[tt(a)]),s("td",{class:"issue-status"},[a.assignee?s("span",{class:"badge badge-blue",title:a.assignee},[a.assignee]):s("span",{class:"badge badge-green"},["Ready"])]),s("td",{class:"issue-age"},[U(a.created_at)]),s("td",{},[es(a.id??"")])]);r.addEventListener("click",i=>{i.target.closest(".sling-btn")||a.id&&de(a.id)}),n.append(r)}),e.append(s("table",{id:"work-table"},[s("thead",{},[s("tr",{},[s("th",{},["Pri"]),s("th",{},["ID"]),s("th",{},["Title"]),s("th",{},["Rig"]),s("th",{},["Status"]),s("th",{},["Age"]),s("th",{},["Actions"])])]),n]))}function ct(e,t){const n=s("button",{class:`rig-btn${t?" active":""}`,"data-rig":e},[e==="all"?"All":e]);return n.addEventListener("click",()=>{we=e,document.querySelectorAll(".rig-btn").forEach(a=>a.classList.remove("active")),n.classList.add("active"),ht()}),n}function tt(e){var t;return((t=e.id)==null?void 0:t.split("-")[0])??"city"}function za(e){return(e.issue_type??"").toLowerCase()==="convoy"?!0:(e.labels??[]).some(t=>t.startsWith("gc:queue")||t.startsWith("gc:message"))}function Ga(){var e,t,n,a,r,i,o;document.querySelectorAll(".tab-btn").forEach(l=>{l.addEventListener("click",d=>{const p=d.currentTarget;ot=p.dataset.tab??"ready",document.querySelectorAll(".tab-btn").forEach(f=>f.classList.remove("active")),p.classList.add("active"),ht()})}),(e=c("new-issue-btn"))==null||e.addEventListener("click",()=>ln()),(t=c("issue-modal-close-btn"))==null||t.addEventListener("click",()=>ye()),(n=c("issue-modal-cancel-btn"))==null||n.addEventListener("click",()=>ye()),(r=(a=c("issue-modal"))==null?void 0:a.querySelector(".modal-backdrop"))==null||r.addEventListener("click",()=>ye()),(i=c("issue-form"))==null||i.addEventListener("submit",l=>{l.preventDefault(),Wa()}),(o=c("issue-back-btn"))==null||o.addEventListener("click",()=>Ka()),document.addEventListener("keydown",l=>{var d;l.key==="Escape"&&((d=c("issue-modal"))==null?void 0:d.style.display)==="block"&&ye()})}function ln(){var t,n,a;if(!S()){v("info","No city selected","Select a city to create a bead");return}const e=c("issue-modal");e&&(e.style.display!=="block"&&W(),e.style.display="block",(n=(t=c("issues-panel"))==null?void 0:t.scrollIntoView)==null||n.call(t,{behavior:"smooth",block:"center"}),(a=c("issue-title"))==null||a.focus())}function ye(){var n;const e=c("issue-modal");if(!e)return;const t=e.style.display==="block";e.style.display="none",(n=c("issue-form"))==null||n.reset(),t&&P()}async function Wa(){var r,i,o;const e=((r=c("issue-title"))==null?void 0:r.value.trim())??"",t=((i=c("issue-description"))==null?void 0:i.value.trim())??"",n=Number(((o=c("issue-priority"))==null?void 0:o.value)??"2");if(!e)return;const a=await ts({title:e,description:t,priority:n});if(!a.ok){v("error","Create failed",a.error??"Could not create issue");return}v("success","Issue created",e),ye(),await le()}async function de(e){var l,d,p;const t=S();if(!t)return;Xe=e,((l=c("issue-detail"))==null?void 0:l.style.display)!=="block"&&W(),c("issues-list").style.display="none",c("issue-detail").style.display="block";const[n,a,r]=await Promise.all([g.GET("/v0/city/{cityName}/bead/{id}",{params:{path:{cityName:t,id:e}}}),g.GET("/v0/city/{cityName}/bead/{id}/deps",{params:{path:{cityName:t,id:e}}}),Qe()]);if(n.error||!n.data){v("error","Issue failed",((d=n.error)==null?void 0:d.detail)??"Could not load bead");return}const i=n.data;c("issue-detail-id").textContent=i.id??e,c("issue-detail-title-text").textContent=i.title??e,c("issue-detail-description").textContent=i.description||"(no description)";const o=c("issue-detail-priority");o.className=`badge ${Qt(i.priority)}`,o.textContent=`P${Z(i.priority)}`,c("issue-detail-status").textContent=i.status??"open",c("issue-detail-status").className=`issue-status ${i.status??"open"}`,c("issue-detail-type").textContent=i.issue_type?`Type: ${i.issue_type}`:"",c("issue-detail-owner").textContent=i.assignee?`Owner: ${i.assignee}`:"Owner: unassigned",c("issue-detail-created").textContent=i.created_at?`Created: ${U(i.created_at)}`:"",Ha(i,r.agents),Fa(((p=a.data)==null?void 0:p.children)??[])}function Fa(e){const t=c("issue-detail-deps"),n=c("issue-detail-depends-on"),a=c("issue-detail-blocks-section"),r=c("issue-detail-blocks");if(!(!t||!n||!a||!r)){if(k(n),k(r),e.length===0){t.style.display="none",a.style.display="none";return}t.style.display="block",e.forEach(i=>{const o=s("span",{class:"issue-dep-item","data-issue-id":i.id??""},[`→ ${i.id??""}`]);o.addEventListener("click",()=>{i.id&&de(i.id)}),n.append(o)}),a.style.display="none"}}function Ha(e,t){const n=c("issue-detail-actions");if(!n||!e.id)return;k(n);const a=s("div",{class:"issue-actions-bar"}),r=e.status==="closed"?nt("↺ Reopen","reopen",()=>void Xa(e.id)):nt("✓ Close","close",()=>void Qa(e.id));a.append(r),e.status!=="closed"&&a.append(nt("🚚 Sling","sling",()=>void dn(e.id)));const i=s("div",{class:"issue-action-group"},[s("label",{class:"issue-action-label"},["Priority"]),Ja(e.id,e.priority)]),o=s("div",{class:"issue-action-group"},[s("label",{class:"issue-action-label"},["Assign"]),Va(e.id,e.assignee,t)]);n.append(a,i,o)}function nt(e,t,n){const a=s("button",{class:`issue-action-btn ${t}`,type:"button"},[e]);return a.addEventListener("click",n),a}function Ja(e,t){const n=s("select",{class:"issue-action-select",id:"issue-action-priority"});return[1,2,3,4].forEach(a=>{const r=s("option",{value:a,selected:Z(t)===a},[`P${a}`]);n.append(r)}),n.addEventListener("change",()=>{Ya(e,Number(n.value))}),n}function Va(e,t,n){const a=s("select",{class:"issue-action-select",id:"issue-action-assignee"});return a.append(s("option",{value:""},["Unassigned"])),n.forEach(r=>{a.append(s("option",{value:r,selected:t===r},[r]))}),a.addEventListener("change",()=>{Za(e,a.value)}),a}function Ka(){const e=c("issue-detail"),t=(e==null?void 0:e.style.display)==="block";e.style.display="none",c("issues-list").style.display="block",Xe="",t&&P()}async function Qa(e){const t=S();if(!t)return;const n=await g.POST("/v0/city/{cityName}/bead/{id}/close",{params:{path:{cityName:t,id:e}}});if(n.error){v("error","Close failed",n.error.detail??"Could not close issue");return}v("success","Closed",e),await le(),await de(e)}async function Xa(e){const t=S();if(!t)return;const n=await g.POST("/v0/city/{cityName}/bead/{id}/reopen",{params:{path:{cityName:t,id:e}}});if(n.error){v("error","Reopen failed",n.error.detail??"Could not reopen issue");return}v("success","Reopened",e),await le(),await de(e)}async function Ya(e,t){const n=S();if(!n)return;const a=await g.POST("/v0/city/{cityName}/bead/{id}/update",{params:{path:{cityName:n,id:e}},body:{priority:t}});if(a.error){v("error","Priority failed",a.error.detail??"Could not update priority");return}v("success","Priority updated",`${e} → P${t}`),await le(),await de(e)}async function Za(e,t){const n=S();if(!n)return;const a=await g.POST("/v0/city/{cityName}/bead/{id}/assign",{params:{path:{cityName:n,id:e}},body:{assignee:t}});if(a.error){v("error","Assign failed",a.error.detail??"Could not update assignee");return}v("success","Assignment updated",t||"Unassigned"),await le(),await de(e)}async function dn(e){const t=S();if(!t)return;const n=await bt({beadID:e,beadLabel:e,mode:"sling",title:"Sling Bead"});if(!n)return;const a=await g.POST("/v0/city/{cityName}/sling",{params:{path:{cityName:t}},body:{bead:e,target:n.target,rig:n.rig||void 0}});if(a.error){v("error","Sling failed",a.error.detail??"Could not sling issue");return}v("success","Work assigned",`${e} → ${n.target}`),await le(),Xe===e&&await de(e)}function es(e){const t=s("button",{class:"sling-btn",type:"button","data-bead-id":e},["Sling"]);return t.addEventListener("click",n=>{n.stopPropagation(),dn(e)}),t}async function ts(e){const t=S();if(!t)return{ok:!1,error:"no city selected"};const{error:n}=await g.POST("/v0/city/{cityName}/beads",{params:{path:{cityName:t}},body:{title:e.title,description:e.description,rig:e.rig,priority:e.priority,assignee:e.assignee}});return n?{ok:!1,error:n.detail??n.title??"create failed"}:{ok:!0}}let M="inbox",Oe=[],L=null;async function Me(){const e=S(),t=c("mail-loading"),n=c("mail-threads"),a=c("mail-empty"),r=c("mail-all");if(!t||!n||!a||!r)return;if(!e){ns();return}vt("No mail in inbox"),t.style.display="block",n.style.display="none",a.style.display="none";const{data:i,error:o}=await g.GET("/v0/city/{cityName}/mail",{params:{path:{cityName:e},query:{status:"all",limit:200}}});if(t.style.display="none",o||!(i!=null&&i.items)){k(n),n.append(s("div",{class:"panel-error"},["Could not load mail."])),n.style.display="block";return}Oe=[...i.items].sort((l,d)=>(d.created_at??"").localeCompare(l.created_at??"")),c("mail-count").textContent=String(Oe.length),as(Oe),ss(Oe),os()}function ns(){const e=c("mail-loading"),t=c("mail-threads"),n=c("mail-empty"),a=c("mail-all");if(!e||!t||!n||!a)return;re()?(D(M),P()):D(M),L=null,Oe=[],c("mail-count").textContent="0",e.style.display="none",k(t),k(a),t.style.display="none",vt("Select a city to view mail"),n.style.display=M==="inbox"?"block":"none",a.append(s("div",{class:"empty-state"},[s("p",{},["Select a city to view mail traffic"])]))}function vt(e){var t,n;(n=(t=c("mail-empty"))==null?void 0:t.querySelector("p"))==null||n.replaceChildren(document.createTextNode(e))}function as(e){const t=c("mail-threads"),n=c("mail-empty");if(!t||!n)return;const a=ys(e);if(k(t),a.length===0){t.style.display="none",vt("No mail in inbox"),n.style.display="block";return}n.style.display="none",a.forEach(r=>{const i=r.messages[r.messages.length-1],o=(i.body??"").trim().slice(0,60),l=s("div",{class:`mail-thread${r.unreadCount>0?" mail-thread-unread":""}`},[s("div",{class:"mail-thread-header"},[s("div",{class:"mail-thread-left"},[s("span",{class:"mail-from"},[I(i.from)])]),s("div",{class:"mail-thread-center"},[s("span",{class:"mail-subject"},[r.subject||"(no subject)"]),o?s("span",{class:"mail-thread-preview"},[` — ${o}`]):null]),s("div",{class:"mail-thread-right"},[s("span",{class:"mail-time"},[mt(i.created_at)]),r.unreadCount>0?s("span",{class:"badge badge-unread"},[`${r.unreadCount} unread`]):null])])]);l.addEventListener("click",()=>{rs(r.id)}),t.append(l)}),t.style.display=M==="inbox"?"block":"none"}function ss(e){const t=c("mail-all");if(!t)return;if(k(t),e.length===0){t.append(s("div",{class:"empty-state"},[s("p",{},["No mail traffic"])]));return}const n=s("tbody");e.forEach(a=>{const r=s("tr",{class:`mail-row${a.read?"":" mail-unread"}`},[s("td",{class:"mail-from"},[I(a.from)]),s("td",{class:"mail-to"},[I(a.to)]),s("td",{},[s("span",{class:"mail-subject"},[a.subject??"(no subject)"])]),s("td",{class:"mail-time"},[U(a.created_at)])]);r.addEventListener("click",()=>{a.id&&is(a.id)}),n.append(r)}),t.append(s("table",{class:"mail-all-table"},[s("thead",{},[s("tr",{},[s("th",{},["From"]),s("th",{},["To"]),s("th",{},["Subject"]),s("th",{},["Time"])])]),n])),t.style.display=M==="all"?"block":"none"}async function rs(e){var i,o;const t=S();if(!t)return;const n=await g.GET("/v0/city/{cityName}/mail/thread/{id}",{params:{path:{cityName:t,id:e}}});if(n.error||!((i=n.data)!=null&&i.items)||n.data.items.length===0){v("error","Thread failed",((o=n.error)==null?void 0:o.detail)??"Could not load mail thread");return}const a=n.data.items,r=a[a.length-1]??a[0];L=r,un(r,a)}async function is(e){var a;const t=S();if(!t)return;const n=await g.GET("/v0/city/{cityName}/mail/{id}",{params:{path:{cityName:t,id:e}}});if(n.error||!n.data){v("error","Message failed",((a=n.error)==null?void 0:a.detail)??"Could not load message");return}L=n.data,await g.POST("/v0/city/{cityName}/mail/{id}/read",{params:{path:{cityName:t,id:e}}}),L.read=!0,un(L,[L]),Me()}function un(e,t){const n=re();c("mail-detail-subject").textContent=e.subject??"(no subject)",c("mail-detail-from").textContent=I(e.from),c("mail-detail-time").textContent=U(e.created_at);const a=c("mail-detail-body");a&&(k(a),t.forEach((r,i)=>{i>0&&a.append(s("hr")),a.append(s("div",{class:"mail-thread-msg-header"},[s("span",{class:"mail-from"},[I(r.from)]),s("span",{class:"mail-time"},[U(r.created_at)])]),s("div",{class:"mail-thread-msg-subject"},[r.subject??"(no subject)"]),s("pre",{},[r.body??""]))})),fn(),D("detail"),pn("mail-detail"),n||W()}function D(e){const t=c("mail-list"),n=c("mail-all"),a=c("mail-detail"),r=c("mail-compose");!t||!n||!a||!r||(t.style.display=e==="inbox"?"block":"none",n.style.display=e==="all"?"block":"none",a.style.display=e==="detail"?"block":"none",r.style.display=e==="compose"?"block":"none")}function os(){var e,t;((e=c("mail-compose"))==null?void 0:e.style.display)==="block"||((t=c("mail-detail"))==null?void 0:t.style.display)==="block"||D(M)}function cs(){var e,t,n,a,r,i,o,l;document.querySelectorAll(".mail-tab").forEach(d=>{d.addEventListener("click",p=>{const f=p.currentTarget;M=f.dataset.tab??"inbox",document.querySelectorAll(".mail-tab").forEach(u=>u.classList.remove("active")),f.classList.add("active"),D(M)})}),(e=c("mail-back-btn"))==null||e.addEventListener("click",()=>{const d=re();D(M),L=null,d&&P()}),(t=c("compose-mail-btn"))==null||t.addEventListener("click",()=>{lt()}),(n=c("compose-back-btn"))==null||n.addEventListener("click",()=>{const d=!!L,p=re();D(d?"detail":M),p&&!d&&P()}),(a=c("compose-cancel-btn"))==null||a.addEventListener("click",()=>{const d=re();D(M),d&&P()}),(r=c("mail-reply-btn"))==null||r.addEventListener("click",()=>{L!=null&&L.id&<(L)}),(i=c("mail-send-btn"))==null||i.addEventListener("click",()=>{ls()}),(o=c("mail-archive-btn"))==null||o.addEventListener("click",()=>{L!=null&&L.id&&ds(L.id)}),(l=c("mail-toggle-unread-btn"))==null||l.addEventListener("click",()=>{L!=null&&L.id&&us(L)})}async function lt(e){if(!S()){v("info","No city selected","Select a city to compose mail"),Je("mail","Compose blocked without city",{replyTo:(e==null?void 0:e.id)??null});return}const t=c("compose-to");if(!t)return;const n=re();k(t),t.append(s("option",{value:""},["Select recipient…"]));try{const a=await Qe();a.sessions.forEach(r=>{t.append(s("option",{value:r.recipient},[r.label]))}),H("mail","Compose options loaded",{city:S(),recipients:a.sessions.length,replyTo:(e==null?void 0:e.id)??null})}catch(a){ie("mail","Compose options failed",{city:S(),error:a}),_("Mail options failed",a,"Could not load recipients")}c("compose-subject").value=e?fs(e.subject??""):"",c("compose-body").value="",c("compose-reply-to").value=(e==null?void 0:e.id)??"",c("mail-compose-title").textContent=e?"Reply":"New Message",e!=null&&e.from&&(ps(t,e.from),t.value=e.from),D("compose"),pn("compose-subject"),H("mail","Compose form opened",{city:S(),replyTo:(e==null?void 0:e.id)??null,selectedRecipient:t.value||null}),n||W()}async function ls(){var l,d,p,f;const e=S();if(!e)return;const t=((l=c("compose-to"))==null?void 0:l.value)??"",n=((d=c("compose-subject"))==null?void 0:d.value.trim())??"",a=((p=c("compose-body"))==null?void 0:p.value)??"",r=((f=c("compose-reply-to"))==null?void 0:f.value)??"";if(!t||!n){v("error","Missing fields","Recipient and subject are required"),Je("mail","Send blocked by missing fields",{bodyLength:a.length,city:e,subject:n,to:t});return}H("mail","Send requested",{bodyLength:a.length,city:e,replyTo:r||null,subject:n,to:t});const i=r?await g.POST("/v0/city/{cityName}/mail/{id}/reply",{params:{path:{cityName:e,id:r}},body:{body:a,subject:n}}):await g.POST("/v0/city/{cityName}/mail",{params:{path:{cityName:e}},body:{to:t,subject:n,body:a,from:"dashboard"}});if(i.error){ie("mail","Send failed",{bodyLength:a.length,city:e,error:i.error,replyTo:r||null,subject:n,to:t}),v("error","Send failed",i.error.detail??"Could not send message");return}H("mail","Send succeeded",{bodyLength:a.length,city:e,replyTo:r||null,subject:n,to:t}),v("success","Message sent",n);const o=re();D("inbox"),L=null,o&&P(),await Me()}async function ds(e){var r;const t=S();if(!t)return;const n=await g.POST("/v0/city/{cityName}/mail/{id}/archive",{params:{path:{cityName:t,id:e}}});if(n.error){v("error","Archive failed",n.error.detail??"Could not archive message");return}v("success","Archived",e);const a=((r=c("mail-detail"))==null?void 0:r.style.display)==="block";D(M),L=null,a&&P(),await Me()}async function us(e){const t=S();if(!t||!e.id)return;const n=e.read?"/v0/city/{cityName}/mail/{id}/mark-unread":"/v0/city/{cityName}/mail/{id}/read",a=await g.POST(n,{params:{path:{cityName:t,id:e.id}}});if(a.error){v("error","Update failed",a.error.detail??"Could not update message");return}e.read=!e.read,L={...e},fn(),v("success","Updated",e.subject??e.id),await Me()}function fn(){const e=c("mail-toggle-unread-btn");e&&(e.textContent=L!=null&&L.read?"Mark unread":"Mark read")}function re(){var e,t;return((e=c("mail-detail"))==null?void 0:e.style.display)==="block"||((t=c("mail-compose"))==null?void 0:t.style.display)==="block"}function fs(e){return e?e.toLowerCase().startsWith("re:")?e:`Re: ${e}`:"Re:"}function ps(e,t){!t||[...e.options].some(n=>n.value===t)||e.append(s("option",{value:t},[t]))}function pn(e){var t,n;(n=(t=c("mail-panel"))==null?void 0:t.scrollIntoView)==null||n.call(t,{behavior:"smooth",block:"center"}),window.setTimeout(()=>{var a;(a=c(e))==null||a.focus()},0)}function ys(e){const t=new Map;e.forEach(i=>{i.id&&t.set(i.id,i)});function n(i){let o=i;const l=new Set;for(;o.reply_to&&o.id&&!l.has(o.id);){l.add(o.id);const d=t.get(o.reply_to);if(!d)break;o=d}return o.thread_id??o.id??Math.random().toString(36)}const a=new Map;e.forEach(i=>{const o=n(i),l=a.get(o)??{id:o,messages:[],subject:i.subject??"",unreadCount:0};l.messages.push(i),i.read||(l.unreadCount+=1),!l.subject&&i.subject&&(l.subject=i.subject),a.set(o,l)});const r=[...a.values()];return r.forEach(i=>{i.messages.sort((o,l)=>(o.created_at??"").localeCompare(l.created_at??""))}),r.sort((i,o)=>{var p,f;const l=((p=i.messages[i.messages.length-1])==null?void 0:p.created_at)??"";return(((f=o.messages[o.messages.length-1])==null?void 0:f.created_at)??"").localeCompare(l)}),r}let ge="";async function wt(){var o;const e=S(),t=c("convoy-list");if(!t)return;if(!e){ms();return}const n=await g.GET("/v0/city/{cityName}/convoys",{params:{path:{cityName:e},query:{limit:200}}});if(n.error||!((o=n.data)!=null&&o.items)){k(t),t.append(s("div",{class:"panel-error"},["Could not load convoys."]));return}const r=(await Promise.all(n.data.items.map(async l=>gs(e,l.id??"")))).filter(l=>l!==null);if(c("convoy-count").textContent=String(r.length),k(t),r.length===0){t.append(s("div",{class:"empty-state"},[s("p",{},["No active convoys"])]));return}const i=s("tbody");r.forEach(l=>{const d=s("tr",{class:"convoy-row","data-convoy-id":l.id},[s("td",{},[s("span",{class:`badge ${oe(yn(l))}`},[bs(l)])]),s("td",{},[s("span",{class:"convoy-id"},[l.id]),l.title?s("div",{class:"convoy-title"},[l.title]):null,l.assignees.length?s("div",{class:"convoy-assignees"},l.assignees.map(p=>s("span",{class:"assignee-chip"},[p]))):null]),s("td",{class:"convoy-progress-cell"},[s("div",{class:"convoy-progress-header"},[s("span",{class:"convoy-progress-fraction"},[`${l.closed}/${l.total}`]),l.total>0?s("span",{class:"convoy-progress-pct"},[`${l.progressPct}%`]):null]),l.total>0?s("div",{class:"progress-bar"},[s("div",{class:"progress-fill",style:`width: ${l.progressPct}%;`})]):null]),s("td",{class:"convoy-work-cell"},[s("div",{class:"convoy-work-breakdown"},[l.ready>0?s("span",{class:"work-chip work-ready"},[`${l.ready} ready`]):null,l.inProgress>0?s("span",{class:"work-chip work-inprogress"},[`${l.inProgress} active`]):null,l.closed===l.total&&l.total>0?s("span",{class:"work-chip work-done"},["all done"]):null])]),s("td",{class:`activity-${l.lastActivity.colorClass}`},[s("span",{class:"activity-dot"}),` ${l.lastActivity.display}`])]);d.addEventListener("click",()=>{gn(l.id)}),i.append(d)}),t.append(s("table",{},[s("thead",{},[s("tr",{},[s("th",{},["Status"]),s("th",{},["Convoy"]),s("th",{},["Progress"]),s("th",{},["Work"]),s("th",{},["Activity"])])]),i]))}function ms(){const e=c("convoy-list"),t=c("convoy-detail"),n=c("convoy-create-form");if(!e||!t||!n)return;const a=t.style.display==="block"||n.style.display==="block";ge="",c("convoy-count").textContent="0",t.style.display="none",n.style.display="none",c("convoy-add-issue-form").style.display="none",e.style.display="block",k(e),e.append(s("div",{class:"empty-state"},[s("p",{},["Select a city to view convoys"])])),a&&P()}async function gs(e,t){var f,u,y,m;if(!t)return null;const n=await g.GET("/v0/city/{cityName}/convoy/{id}",{params:{path:{cityName:e,id:t}}});if(n.error||!n.data)return null;const a=n.data.children??[],r=new Set;let i=0,o=0,l="";a.forEach(h=>{(h.status??"").toLowerCase()!=="closed"&&(h.assignee?(o+=1,r.add(h.assignee)):i+=1),l=[l,h.created_at??""].sort().slice(-1)[0]??l});const d=((f=n.data.progress)==null?void 0:f.total)??a.length,p=((u=n.data.progress)==null?void 0:u.closed)??a.filter(h=>h.status==="closed").length;return{id:t,title:((y=n.data.convoy)==null?void 0:y.title)??t,status:(m=n.data.convoy)==null?void 0:m.status,progressPct:d>0?Math.round(p/d*100):0,total:d,closed:p,ready:i,inProgress:o,assignees:[...r].sort(),lastActivity:qe(l)}}function yn(e){return e.total>0&&e.closed===e.total?"done":e.inProgress>0?"active":e.ready>0?"waiting":e.status??"open"}function bs(e){switch(yn(e)){case"done":return"✓ Done";case"active":return"Active";case"waiting":return"Waiting";default:return e.status??"Open"}}function hs(){var e,t,n,a,r,i,o,l;(e=c("new-convoy-btn"))==null||e.addEventListener("click",()=>{mn()}),(t=c("convoy-back-btn"))==null||t.addEventListener("click",()=>vs()),(n=c("convoy-create-back-btn"))==null||n.addEventListener("click",()=>dt()),(a=c("convoy-create-cancel-btn"))==null||a.addEventListener("click",()=>dt()),(r=c("convoy-create-submit-btn"))==null||r.addEventListener("click",()=>{ws()}),(i=c("convoy-add-issue-btn"))==null||i.addEventListener("click",()=>{c("convoy-add-issue-form").style.display="flex"}),(o=c("convoy-add-issue-cancel"))==null||o.addEventListener("click",()=>{c("convoy-add-issue-form").style.display="none"}),(l=c("convoy-add-issue-submit"))==null||l.addEventListener("click",()=>{Ss()})}function mn(){var n;if(!S()){v("info","No city selected","Select a city to create a convoy");return}const e=c("convoy-create-form"),t=(e==null?void 0:e.style.display)==="block";ge="",c("convoy-list").style.display="none",c("convoy-detail").style.display="none",e.style.display="block",c("convoy-create-name").value="",c("convoy-create-issues").value="",t||W(),bn("convoy-create-name"),(n=c("convoy-create-name"))==null||n.focus()}async function gn(e){var l,d,p,f,u,y,m,h;const t=S();if(!t)return;ge=e,((l=c("convoy-detail"))==null?void 0:l.style.display)!=="block"&&W(),c("convoy-list").style.display="none",c("convoy-create-form").style.display="none",c("convoy-detail").style.display="block",bn("convoy-detail"),c("convoy-detail-id").textContent=e,c("convoy-detail-title").textContent=`Convoy: ${e}`,c("convoy-issues-loading").style.display="block",c("convoy-issues-table").style.display="none",c("convoy-issues-empty").style.display="none",c("convoy-add-issue-form").style.display="none";const n=await g.GET("/v0/city/{cityName}/convoy/{id}",{params:{path:{cityName:t,id:e}}});if(c("convoy-issues-loading").style.display="none",n.error||!n.data){c("convoy-issues-empty").style.display="block",c("convoy-issues-empty").querySelector("p").textContent=((d=n.error)==null?void 0:d.detail)??"Failed to load convoy";return}const a=((p=n.data.progress)==null?void 0:p.total)??((f=n.data.children)==null?void 0:f.length)??0,r=((u=n.data.progress)==null?void 0:u.closed)??((y=n.data.children)==null?void 0:y.filter(w=>w.status==="closed").length)??0;c("convoy-detail-status").className=`badge ${oe(((m=n.data.convoy)==null?void 0:m.status)??"open")}`,c("convoy-detail-status").textContent=((h=n.data.convoy)==null?void 0:h.status)??"open",c("convoy-detail-progress").textContent=`${r}/${a}`;const i=c("convoy-issues-tbody");if(!i)return;k(i);const o=n.data.children??[];if(o.length===0){c("convoy-issues-empty").style.display="block";return}o.forEach(w=>{const C=w.assignee?w.assignee:w.status==="closed"?"done":"ready";i.append(s("tr",{},[s("td",{class:"convoy-issue-status"},[s("span",{class:`badge ${oe(w.status)}`},[w.status??"unknown"])]),s("td",{},[s("span",{class:"issue-id"},[w.id??""])]),s("td",{class:"issue-title"},[w.title??w.id??""]),s("td",{},[w.assignee?s("span",{class:"badge badge-blue"},[w.assignee]):s("span",{class:"badge badge-muted"},["Unassigned"])]),s("td",{},[C])]))}),c("convoy-issues-table").style.display="table"}function vs(){const e=c("convoy-detail"),t=(e==null?void 0:e.style.display)==="block";e.style.display="none",c("convoy-list").style.display="block",t&&P()}function dt(){const e=c("convoy-create-form"),t=(e==null?void 0:e.style.display)==="block";e.style.display="none",c("convoy-list").style.display="block",t&&P()}async function ws(){var r,i;const e=S();if(!e)return;const t=((r=c("convoy-create-name"))==null?void 0:r.value.trim())??"",n=(((i=c("convoy-create-issues"))==null?void 0:i.value)??"").split(/\s+/).map(o=>o.trim()).filter(Boolean);if(!t){v("error","Missing name","Convoy name is required");return}const a=await g.POST("/v0/city/{cityName}/convoys",{params:{path:{cityName:e}},body:{title:t,items:n}});if(a.error){v("error","Create failed",a.error.detail??"Could not create convoy");return}v("success","Convoy created",t),dt(),await wt()}async function Ss(){const e=S();if(!e||!ge)return;const t=c("convoy-add-issue-input"),n=(t==null?void 0:t.value.trim())??"";if(!n)return;const a=await g.POST("/v0/city/{cityName}/convoy/{id}/add",{params:{path:{cityName:e,id:ge}},body:{items:[n]}});if(a.error){v("error","Add failed",a.error.detail??"Could not add issue");return}t&&(t.value=""),c("convoy-add-issue-form").style.display="none",v("success","Issue added",n),await gn(ge),await wt()}function bn(e){var t,n;(n=(t=c("convoy-panel"))==null?void 0:t.scrollIntoView)==null||n.call(t,{behavior:"smooth",block:"center"}),window.setTimeout(()=>{var a;(a=c(e))==null||a.focus()},0)}const Cs=150,z=[];let X=null,je="all",Ie="all",Be="all";async function Es(e){z.splice(0,z.length,...vn(e)),Y()}async function ks(){var a;const e=S(),n=(((a=(e?await g.GET("/v0/city/{cityName}/events",{params:{path:{cityName:e},query:{since:"1h",limit:100}}}):await g.GET("/v0/events",{params:{query:{since:"1h"}}})).data)==null?void 0:a.items)??[]).map(r=>As(r)).filter(r=>r!==null);await Es(n)}function Ns(e,t){const n=S();X==null||X.close();const a=t?{onStatus:t}:void 0;X=(n?i=>Sa(n,i,a):i=>wa(i,a))(i=>{const o=Sn(i);e==null||e(i,o);const l=Ts(i);if(l){if(z.some(d=>d.id===l.id)){be("activity","Duplicate stream event ignored",{id:l.id,type:l.type});return}z.splice(0,z.length,...vn([l,...z])),Y()}})}function $s(){X==null||X.close(),X=null}function Y(){xs();const e=c("activity-feed");if(!e)return;k(e);const t=z.filter(a=>!(je!=="all"&&a.category!==je||Ie!=="all"&&a.rig!==Ie||Be!=="all"&&a.actor!==Be));if(c("activity-count").textContent=String(z.length),t.length===0){e.append(s("div",{class:"empty-state"},[s("p",{},["No recent activity"])]));return}const n=s("div",{class:"tl-timeline",id:"activity-timeline"});t.forEach(a=>{n.append(s("div",{class:`tl-entry ${_s(a.category)}`,"data-category":a.category,"data-rig":a.rig,"data-agent":a.actor??"","data-type":a.type,"data-ts":a.ts},[s("div",{class:"tl-rail"},[s("span",{class:"tl-time"},[mt(a.ts)]),s("span",{class:"tl-node"})]),s("div",{class:"tl-content"},[s("div",{class:"tl-header"},[s("span",{class:"tl-icon"},[sa(a.type)]),s("span",{class:"tl-summary"},[ra(a.type,a.actor,a.subject,a.message)])]),s("div",{class:"tl-meta"},[a.actor?s("span",{class:"tl-badge tl-badge-agent"},[I(a.actor)]):null,a.rig?s("span",{class:"tl-badge tl-badge-rig"},[a.rig]):null,s("span",{class:"tl-badge tl-badge-type"},[a.type])])])]))}),e.append(n)}function Ls(){var e,t;document.addEventListener("click",n=>{var r;const a=(r=n.target)==null?void 0:r.closest(".tl-filter-btn");a&&(je=a.dataset.value??"all",document.querySelectorAll(".tl-filter-btn").forEach(i=>i.classList.remove("active")),a.classList.add("active"),Y())}),(e=c("tl-rig-filter"))==null||e.addEventListener("change",n=>{Ie=n.currentTarget.value,Y()}),(t=c("tl-agent-filter"))==null||t.addEventListener("change",n=>{Be=n.currentTarget.value,Y()})}function xs(){const e=c("activity-filters");if(!e||(k(e),z.length===0))return;const t=[...new Set(z.map(i=>i.rig).filter(Boolean))].sort(),n=[...new Set(z.map(i=>i.actor).filter(Boolean))].sort(),a=s("select",{class:"tl-filter-select",id:"tl-rig-filter"});a.append(s("option",{value:"all"},["All rigs"])),t.forEach(i=>a.append(s("option",{value:i,selected:i===Ie},[i]))),a.addEventListener("change",()=>{Ie=a.value,Y()});const r=s("select",{class:"tl-filter-select",id:"tl-agent-filter"});r.append(s("option",{value:"all"},["All agents"])),n.forEach(i=>r.append(s("option",{value:i,selected:i===Be},[I(i)]))),r.addEventListener("change",()=>{Be=r.value,Y()}),e.append(s("div",{class:"tl-filters"},[s("div",{class:"tl-filter-group"},[s("label",{},["Category:"]),$e("all","All"),$e("agent","Agent"),$e("work","Work"),$e("comms","Comms"),$e("system","System")]),s("div",{class:"tl-filter-group"},[s("label",{},["Rig:"]),a]),s("div",{class:"tl-filter-group"},[s("label",{},["Agent:"]),r])]))}function $e(e,t){const n=s("button",{class:`tl-filter-btn${je===e?" active":""}`,"data-filter":"category","data-value":e,type:"button"},[t]);return n.addEventListener("click",()=>{je=e,Y()}),n}function Ts(e){return e.event==="heartbeat"?null:hn(e.data,e.id)}function As(e){return hn(e)}function hn(e,t){if(!e.type)return null;const n=wn(e)??S(),a=typeof e.seq=="number"?e.seq:0;return{id:qs(e,t),type:e.type,category:aa(e.type),actor:e.actor||void 0,subject:e.subject||void 0,message:e.message||void 0,ts:e.ts,scope:n,seq:a,rig:na(e.actor)||"city"in e&&e.city||""}}function vn(e){const t=new Map;return e.forEach(n=>{t.has(n.id)||t.set(n.id,n)}),[...t.values()].sort(Rs).slice(0,Cs)}function Rs(e,t){const n=Os(e.ts,t.ts);if(n!==0)return n;const a=e.scope.localeCompare(t.scope);if(a!==0)return a;const r=t.seq-e.seq;if(r!==0)return r;const i=e.type.localeCompare(t.type);if(i!==0)return i;const o=(e.actor??"").localeCompare(t.actor??"");return o!==0?o:(e.subject??"").localeCompare(t.subject??"")}function Os(e,t){const n=Number.isNaN(Date.parse(e))?0:Date.parse(e);return(Number.isNaN(Date.parse(t))?0:Date.parse(t))-n}function wn(e){if("city"in e&&typeof e.city=="string"&&e.city!=="")return e.city}function qs(e,t){const n=wn(e)??S();if(typeof e.seq=="number"&&e.seq>0)return`${n}:${e.seq}`;const a=[e.type,e.ts,e.actor??"",e.subject??"",e.message??"",t??""].join(":");return`${n}:${a}`}function Sn(e){return Ea(e)}function _s(e){switch(e){case"agent":return"activity-agent";case"work":return"activity-work";case"comms":return"activity-comms";default:return"activity-system"}}async function J(){var o,l,d,p,f,u;const e=S();if(!e){Ps();return}const[t,n,a,r,i]=await Promise.all([g.GET("/v0/city/{cityName}/services",{params:{path:{cityName:e}}}),g.GET("/v0/city/{cityName}/rigs",{params:{path:{cityName:e},query:{git:!0}}}),g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:e},query:{label:"gc:escalation",status:"open",limit:200}}}),g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:e},query:{status:"in_progress",limit:500}}}),g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:e},query:{label:"gc:queue",limit:200}}})]);Is(((o=t.data)==null?void 0:o.items)??null,(l=t.error)==null?void 0:l.detail),Bs(((d=n.data)==null?void 0:d.items)??null),Ms(((p=a.data)==null?void 0:p.items)??null),Us(((f=r.data)==null?void 0:f.items)??null),Ds(((u=i.data)==null?void 0:u.items)??null)}function Ps(){Le("services-body","services-count","Select a city to view services"),Le("rigs-body","rigs-count","Select a city to view rigs"),Le("escalations-body","escalations-count","Select a city to view escalations"),Le("assigned-body","assigned-count","Select a city to view assigned work"),Le("queues-body","queues-count","Select a city to view queues"),c("clear-assigned-btn").style.display="none"}function js(){var e,t;(e=c("open-assign-btn"))==null||e.addEventListener("click",()=>{Cn()}),(t=c("clear-assigned-btn"))==null||t.addEventListener("click",()=>{Ws()})}function Is(e,t){const n=c("services-body"),a=c("services-count");if(!n||!a)return;if(k(n),t){a.textContent="n/a",n.append(s("div",{class:"empty-state"},[s("p",{},[t])]));return}const r=e??[];if(a.textContent=String(r.length),r.length===0){n.append(s("div",{class:"empty-state"},[s("p",{},["No workspace services"])]));return}const i=s("tbody");r.forEach(o=>{const l=s("button",{class:"esc-btn",type:"button"},["Restart"]);l.addEventListener("click",()=>{Hs(o.service_name)}),i.append(s("tr",{},[s("td",{},[s("strong",{},[o.service_name])]),s("td",{},[o.kind??"—"]),s("td",{},[s("span",{class:`badge ${oe(o.state??o.publication_state)}`},[o.state??o.publication_state??"unknown"])]),s("td",{},[o.local_state]),s("td",{},[l])]))}),n.append(s("table",{},[s("thead",{},[s("tr",{},[s("th",{},["Name"]),s("th",{},["Kind"]),s("th",{},["Service"]),s("th",{},["Local"]),s("th",{},["Actions"])])]),i]))}function Bs(e){const t=c("rigs-body"),n=c("rigs-count");if(!t||!n)return;k(t);const a=e??[];if(n.textContent=String(a.length),a.length===0){t.append(s("div",{class:"empty-state"},[s("p",{},["No rigs configured"])]));return}const r=s("tbody");a.forEach(i=>{var d;const o=s("button",{class:"esc-btn",type:"button"},[i.suspended?"Resume":"Suspend"]);o.addEventListener("click",()=>{_t(i.name,i.suspended?"resume":"suspend")});const l=s("button",{class:"esc-btn",type:"button"},["Restart"]);l.addEventListener("click",()=>{_t(i.name,"restart")}),r.append(s("tr",{},[s("td",{},[s("span",{class:"rig-name"},[i.name])]),s("td",{},[String(i.agent_count-i.running_count)]),s("td",{},[String(i.running_count)]),s("td",{},[(d=i.git)!=null&&d.branch?`${i.git.branch}${i.git.clean?"":"*"}`:"—"]),s("td",{},[U(i.last_activity)]),s("td",{},[o," ",l])]))}),t.append(s("table",{},[s("thead",{},[s("tr",{},[s("th",{},["Name"]),s("th",{},["Idle"]),s("th",{},["Running"]),s("th",{},["Git"]),s("th",{},["Activity"]),s("th",{},["Actions"])])]),r]))}function Ms(e){const t=c("escalations-body"),n=c("escalations-count");if(!t||!n)return;k(t);const a=(e??[]).sort((i,o)=>(i.created_at??"").localeCompare(o.created_at??""));if(n.textContent=String(a.length),a.length===0){t.append(s("div",{class:"empty-state"},[s("p",{},["No escalations"])]));return}const r=s("tbody");a.forEach(i=>{const o=zs(i.labels??[]),l=(i.labels??[]).includes("acked"),d=s("button",{class:"esc-btn esc-ack-btn",type:"button"},["👍 Ack"]);d.addEventListener("click",()=>{Js(i)});const p=s("button",{class:"esc-btn esc-resolve-btn",type:"button"},["✓ Resolve"]);p.addEventListener("click",()=>{i.id&&Vs(i.id)});const f=s("button",{class:"esc-btn esc-reassign-btn",type:"button"},["↻ Reassign"]);f.addEventListener("click",()=>{i.id&&Ks(i.id)}),r.append(s("tr",{class:"escalation-row","data-escalation-id":i.id??""},[s("td",{},[s("span",{class:`badge ${Gs(o)}`},[o.toUpperCase()])]),s("td",{},[i.title??i.id??"",l?s("span",{class:"badge badge-cyan",style:"margin-left: 4px;"},["ACK"]):null]),s("td",{},[I(i.assignee)]),s("td",{},[U(i.created_at)]),s("td",{class:"escalation-actions"},[l?null:d,p,f])]))}),t.append(s("table",{},[s("thead",{},[s("tr",{},[s("th",{},["Severity"]),s("th",{},["Issue"]),s("th",{},["From"]),s("th",{},["Age"]),s("th",{},["Actions"])])]),r]))}function Us(e){const t=c("assigned-body"),n=c("assigned-count"),a=c("clear-assigned-btn");if(!t||!n||!a)return;k(t);const r=(e??[]).filter(o=>o.assignee);if(n.textContent=String(r.length),a.style.display=r.length>0?"inline-flex":"none",r.length===0){t.append(s("div",{class:"empty-state"},[s("p",{},["No assigned work"])]));return}const i=s("tbody");r.forEach(o=>{const l=s("button",{class:"unassign-btn",type:"button"},["Unassign"]);l.addEventListener("click",()=>{o.id&&Fs(o.id)}),i.append(s("tr",{},[s("td",{},[s("span",{class:"assigned-id"},[o.id??""])]),s("td",{class:"assigned-title"},[Ke(o.title??"",80)]),s("td",{class:"assigned-agent"},[I(o.assignee)]),s("td",{class:"assigned-age"},[U(o.created_at)]),s("td",{},[l])]))}),t.append(s("table",{},[s("thead",{},[s("tr",{},[s("th",{},["Bead"]),s("th",{},["Title"]),s("th",{},["Agent"]),s("th",{},["Since"]),s("th",{},[""])])]),i]))}function Ds(e){const t=c("queues-body"),n=c("queues-count");if(!t||!n)return;k(t);const a=e??[];if(n.textContent=String(a.length),a.length===0){t.append(s("div",{class:"empty-state"},[s("p",{},["No queues"])]));return}const r=s("tbody");a.forEach(i=>{r.append(s("tr",{},[s("td",{},[i.title??i.id??"queue"]),s("td",{},[i.id??"—"]),s("td",{},[s("span",{class:`badge ${oe(i.status)}`},[i.status??"open"])]),s("td",{},[I(i.assignee)]),s("td",{},[U(i.created_at)])]))}),t.append(s("table",{},[s("thead",{},[s("tr",{},[s("th",{},["Queue"]),s("th",{},["Bead"]),s("th",{},["Status"]),s("th",{},["Assignee"]),s("th",{},["Created"])])]),r]))}function Le(e,t,n){const a=c(e),r=c(t);!a||!r||(k(a),r.textContent="0",a.append(s("div",{class:"empty-state"},[s("p",{},[n])])))}function zs(e){for(const t of e)if(t.startsWith("severity:"))return t.slice(9);return"medium"}function Gs(e){switch(e){case"critical":return"badge-red";case"high":return"badge-orange";case"low":return"badge-muted";default:return"badge-yellow"}}async function Cn(e=""){const t=S();if(!t)return;const n=await bt({beadID:e||void 0,beadLabel:e||void 0,mode:"assign",title:"Assign Work"});if(!n)return;const a=await g.POST("/v0/city/{cityName}/sling",{params:{path:{cityName:t}},body:{bead:n.beadID,target:n.target,rig:n.rig||void 0}});if(a.error){v("error","Assign failed",a.error.detail??"Could not assign bead");return}v("success","Assigned",`${n.beadID} → ${n.target}`),await J()}async function Ws(){var r;const e=S();if(!e)return;const n=(((r=(await g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:e},query:{status:"in_progress",limit:500}}})).data)==null?void 0:r.items)??[]).filter(i=>i.assignee);if(n.length===0){v("info","Nothing to clear","No assigned work");return}await Ba({body:`Unassign ${n.length} active ${n.length===1?"bead":"beads"}?`,confirmLabel:"Unassign All",title:"Clear Assignments"})&&(await Promise.all(n.map(i=>g.POST("/v0/city/{cityName}/bead/{id}/assign",{params:{path:{cityName:e,id:i.id??""}},body:{assignee:""}}))),v("success","Cleared",`${n.length} assignments removed`),await J())}async function Fs(e){const t=S();if(!t)return;const n=await g.POST("/v0/city/{cityName}/bead/{id}/assign",{params:{path:{cityName:t,id:e}},body:{assignee:""}});if(n.error){v("error","Unassign failed",n.error.detail??"Could not unassign bead");return}v("success","Unassigned",e),await J()}async function Hs(e){const t=S();if(!t)return;const n=await g.POST("/v0/city/{cityName}/service/{name}/restart",{params:{path:{cityName:t,name:e}}});if(n.error){v("error","Service failed",n.error.detail??"Could not restart service");return}v("success","Service restarted",e),await J()}async function _t(e,t){const n=S();if(!n)return;const a=await g.POST("/v0/city/{cityName}/rig/{name}/{action}",{params:{path:{cityName:n,name:e,action:t}}});if(a.error){v("error","Rig action failed",a.error.detail??`Could not ${t} ${e}`);return}v("success","Rig updated",`${e}: ${t}`),await J()}async function Js(e){const t=S();if(!t||!e.id)return;const n=Array.from(new Set([...e.labels??[],"acked"])),a=await g.POST("/v0/city/{cityName}/bead/{id}/update",{params:{path:{cityName:t,id:e.id}},body:{labels:n}});if(a.error){v("error","Ack failed",a.error.detail??"Could not acknowledge escalation");return}v("success","Acknowledged",e.id),await J()}async function Vs(e){const t=S();if(!t)return;const n=await g.POST("/v0/city/{cityName}/bead/{id}/close",{params:{path:{cityName:t,id:e}}});if(n.error){v("error","Resolve failed",n.error.detail??"Could not resolve escalation");return}v("success","Resolved",e),await J()}async function Ks(e){const t=S();if(!t)return;const n=await bt({beadID:e,beadLabel:e,mode:"reassign",title:"Reassign Escalation"});if(!n)return;const a=await g.POST("/v0/city/{cityName}/bead/{id}/assign",{params:{path:{cityName:t,id:e}},body:{assignee:n.target}});if(a.error){v("error","Reassign failed",a.error.detail??"Could not reassign escalation");return}v("success","Reassigned",`${e} → ${n.target||"unassigned"}`),await J()}function Qs(e){const t=c("command-palette-overlay"),n=c("command-palette-input"),a=c("command-palette-results"),r=c("open-palette-btn");if(!t||!n||!a||!r)return;const i=t,o=n,l=a,d=r;let p=[],f=[],u=0;function y(){const b=S(),E=async(N,j)=>{const B=await j;At(N,JSON.stringify(B,null,2))};return[{name:"refresh",desc:"Refresh all panels",category:"Dashboard",run:()=>e.refreshAll()},{name:"supervisor health",desc:"Show supervisor health JSON",category:"Supervisor",run:()=>E("health",g.GET("/health"))},{name:"city list",desc:"Show managed cities JSON",category:"Supervisor",run:()=>E("cities",g.GET("/v0/cities"))},{name:"global events",desc:"Show recent supervisor events JSON",category:"Supervisor",run:()=>E("events",g.GET("/v0/events",{params:{query:{since:"1h"}}}))},...b?[{name:"new issue",desc:"Open the issue creation modal",category:"Work",run:()=>ln()},{name:"compose mail",desc:"Open the compose mail form",category:"Mail",run:()=>lt()},{name:"new convoy",desc:"Open the convoy creation form",category:"Convoys",run:()=>mn()},{name:"assign work",desc:"Open the assignment modal",category:"Assigned",run:()=>Cn()},{name:"status",desc:"Show current city status JSON",category:"Status",run:()=>E("status",g.GET("/v0/city/{cityName}/status",{params:{path:{cityName:b}}}))},{name:"agent list",desc:"Show current sessions JSON",category:"Status",run:()=>E("sessions",g.GET("/v0/city/{cityName}/sessions",{params:{path:{cityName:b},query:{state:"active",peek:!0}}}))},{name:"convoy list",desc:"Show current convoys JSON",category:"Convoys",run:()=>E("convoys",g.GET("/v0/city/{cityName}/convoys",{params:{path:{cityName:b},query:{limit:200}}}))},{name:"mail inbox",desc:"Show current mail JSON",category:"Mail",run:()=>E("mail",g.GET("/v0/city/{cityName}/mail",{params:{path:{cityName:b},query:{status:"all",limit:200}}}))},{name:"rig list",desc:"Show rig JSON",category:"Rigs",run:()=>E("rigs",g.GET("/v0/city/{cityName}/rigs",{params:{path:{cityName:b},query:{git:!0}}}))},{name:"list",desc:"Show open and in-progress beads JSON",category:"Beads",run:async()=>{var B,$;const[N,j]=await Promise.all([g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:b},query:{status:"open",limit:500}}}),g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:b},query:{status:"in_progress",limit:500}}})]);At("beads",JSON.stringify({open:((B=N.data)==null?void 0:B.items)??[],in_progress:(($=j.data)==null?void 0:$.items)??[]},null,2))}}]:[],{name:"close output",desc:"Hide the output panel",category:"Dashboard",run:()=>Yt()}].filter(N=>typeof N.run=="function")}function m(){k(l);const b=o.value.trim().toLowerCase();if(p=y(),f=p.filter(E=>b===""||E.name.includes(b)||E.desc.toLowerCase().includes(b)||E.category.toLowerCase().includes(b)),u>=f.length&&(u=0),f.length===0){l.append(s("div",{class:"command-palette-empty"},["No matching commands"]));return}f.forEach((E,N)=>{const j=s("button",{class:`command-item${N===u?" selected":""}`,type:"button"},[s("span",{class:"command-name"},[`gt ${E.name}`]),s("span",{class:"command-desc"},[E.desc]),s("span",{class:"command-category"},[E.category])]);j.addEventListener("click",()=>{C(N)}),l.append(j)})}function h(){i.classList.add("open"),o.value="",u=0,m(),o.focus()}function w(){i.classList.remove("open")}async function C(b){const E=f[b];w(),E&&(H("palette","Execute command",{category:E.category,city:S(),command:E.name}),await E.run())}d.addEventListener("click",()=>h()),i.addEventListener("click",b=>{b.target===i&&w()}),o.addEventListener("input",()=>m()),o.addEventListener("keydown",b=>{if(b.key==="ArrowDown"){u=Math.min(u+1,Math.max(f.length-1,0)),m(),b.preventDefault();return}if(b.key==="ArrowUp"){u=Math.max(u-1,0),m(),b.preventDefault();return}if(b.key==="Enter"){C(u),b.preventDefault();return}b.key==="Escape"&&w()}),document.addEventListener("keydown",b=>{(b.metaKey||b.ctrlKey)&&b.key.toLowerCase()==="k"&&(b.preventDefault(),i.classList.contains("open")?w():h())})}function Xs(){const e=c("supervisor-overview-panel"),t=c("supervisor-overview-body"),n=c("supervisor-city-count");if(!e||!t||!n)return;const a=S()==="";if(e.hidden=!a,!a)return;const r=Ft().sort((o,l)=>o.name.localeCompare(l.name));if(n.textContent=String(r.length),k(t),r.length===0){t.append(s("div",{class:"empty-state"},[s("p",{},["No managed cities available"])]));return}const i=s("tbody");r.forEach(o=>{const l=o.phasesCompleted.length>0?o.phasesCompleted.join(", "):"—",d=s("a",{class:"supervisor-city-link",href:`?city=${encodeURIComponent(o.name)}`},["Open"]);i.append(s("tr",{},[s("td",{},[s("strong",{},[o.name])]),s("td",{},[s("span",{class:`badge ${o.error?"badge-red":o.running?"badge-green":"badge-muted"}`},[o.error?"Error":o.running?"Running":"Stopped"])]),s("td",{},[o.status??"—"]),s("td",{class:"supervisor-city-phases"},[l]),s("td",{class:"supervisor-city-error"},[o.error??"—"]),s("td",{class:"supervisor-city-actions"},[d])]))}),t.append(s("table",{class:"supervisor-city-table"},[s("thead",{},[s("tr",{},[s("th",{},["City"]),s("th",{},["State"]),s("th",{},["Status"]),s("th",{},["Phases"]),s("th",{},["Error"]),s("th",{},[""])])]),i]))}const Ys=["convoy-panel","crew-panel","rigged-panel","mail-panel","escalations-panel","services-panel","rigs-panel","pooled-panel","queues-panel","beads-panel","assigned-panel","agent-log-drawer"];async function Zs(){gt()||await Se()}async function er(){gt()||await Se().catch(e=>_("Catch-up refresh failed",e))}async function tr(){yt(),await Se(!0)}function St(){const e=Ht();if(e.kind==="not-running"||e.kind==="unknown"){$s(),at("connecting");return}at("connecting"),Ns(t=>{const n=Sn(t);!n||n==="heartbeat"||(Zn(n),!gt()&&Se().catch(a=>_("Refresh failed",a)))},at)}function at(e){const t=Ct("connection-status");if(!t)return;const n={connecting:"Connecting…",live:"Live",reconnecting:"Reconnecting…"};t.replaceChildren(document.createTextNode(n[e])),t.classList.remove("connection-live","connection-connecting","connection-reconnecting"),t.classList.add(`connection-${e}`)}function nr(){ga(),Ia(),Aa(),Ga(),cs(),hs(),Ls(),js(),Qs({refreshAll:Zs})}async function ar(){Jn(),H("dashboard","Boot start",{city:S(),href:window.location.href}),nr(),rr(),ma(()=>{er()}),await tr(),St(),H("dashboard","Boot complete",{city:S(),href:window.location.href})}function Ct(e){return document.getElementById(e)}ar().catch(e=>_("Dashboard boot failed",e));function sr(){const e=S()!=="";or(e),Ue("new-convoy-btn",e,"Select a city to create a convoy"),Ue("new-issue-btn",e,"Select a city to create a bead"),Ue("compose-mail-btn",e,"Select a city to compose mail"),Ue("open-assign-btn",e,"Select a city to assign work")}function Ue(e,t,n){const a=Ct(e);a&&(a.dataset.defaultTitle===void 0&&(a.dataset.defaultTitle=a.title||""),a.disabled=!t,a.title=t?a.dataset.defaultTitle:n)}function rr(){document.addEventListener("click",e=>{var a;const t=(a=e.target)==null?void 0:a.closest("a.city-tab");if(!t)return;const n=t.href;!n||n===window.location.href||(e.preventDefault(),ir(n))}),window.addEventListener("popstate",()=>{H("dashboard","Popstate navigation",{href:window.location.href}),rn(),pt(),yt(),Se().catch(e=>_("Refresh failed",e)),St()})}async function ir(e){H("dashboard","Navigate city scope",{nextURL:e}),rn(),window.history.pushState({},"",e),pt(),yt(),await Se(),St()}function or(e){Ys.forEach(t=>{const n=Ct(t);if(!n)return;const a=!e&&n.classList.contains("expanded");if(n.hidden=!e,a){n.classList.remove("expanded");const r=n.querySelector(".expand-btn");r&&(r.textContent="Expand"),P()}})}async function Se(e=!1){pt(),sr();const t=Xn(e);if(t.size===0)return;t.has("options")&&ja(),t.has("cities")&&await ea().catch(l=>_("City tabs failed",l));const n=[],r=Ht().kind==="running";ne(n,t,"status",()=>ia()),ne(n,t,"activity",()=>ks()),r&&(ne(n,t,"crew",()=>ka()),ne(n,t,"issues",()=>le()),ne(n,t,"mail",()=>Me()),ne(n,t,"convoys",()=>wt()),ne(n,t,"admin",()=>J()));const o=(await Promise.allSettled(n)).find(l=>l.status==="rejected");o&&_("Panel refresh failed",o.reason),(t.has("supervisor")||t.has("cities"))&&Xs()}function ne(e,t,n,a){t.has(n)&&e.push(a())} +`);A=te.pop()??"";for(const pe of te){const G=pe.split(` +`),x=[];let ye;for(const _ of G)if(_.startsWith("data:"))x.push(_.replace(/^data:\s*/,""));else if(_.startsWith("event:"))ye=_.replace(/^event:\s*/,"");else if(_.startsWith("id:"))u=_.replace(/^id:\s*/,"");else if(_.startsWith("retry:")){const ne=Number.parseInt(_.replace(/^retry:\s*/,""),10);Number.isNaN(ne)||(w=ne)}let R,q=!1;if(x.length){const _=x.join(` +`);try{R=JSON.parse(_),q=!0}catch{R=_}}q&&(r&&await r(R),a&&(R=await a(R))),n==null||n({data:R,event:ye,id:u,retry:w}),x.length&&(yield R)}}}finally{b.removeEventListener("abort",fe),O.releaseLock()}break}catch(N){if(t==null||t(N),o!==void 0&&E>=o)break;const I=Math.min(w*2**(E-1),l??3e4);await y(I)}}}()}}const qn=e=>{switch(e){case"label":return".";case"matrix":return";";case"simple":return",";default:return"&"}},_n=e=>{switch(e){case"form":return",";case"pipeDelimited":return"|";case"spaceDelimited":return"%20";default:return","}},Pn=e=>{switch(e){case"label":return".";case"matrix":return";";case"simple":return",";default:return"&"}},Bt=({allowReserved:e,explode:t,name:n,style:a,value:r})=>{if(!t){const l=(e?r:r.map(d=>encodeURIComponent(d))).join(_n(a));switch(a){case"label":return`.${l}`;case"matrix":return`;${n}=${l}`;case"simple":return l;default:return`${n}=${l}`}}const i=qn(a),o=r.map(l=>a==="label"||a==="simple"?e?l:encodeURIComponent(l):Je({allowReserved:e,name:n,value:l})).join(i);return a==="label"||a==="matrix"?i+o:o},Je=({allowReserved:e,name:t,value:n})=>{if(n==null)return"";if(typeof n=="object")throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");return`${t}=${e?n:encodeURIComponent(n)}`},Mt=({allowReserved:e,explode:t,name:n,style:a,value:r,valueOnly:i})=>{if(r instanceof Date)return i?r.toISOString():`${n}=${r.toISOString()}`;if(a!=="deepObject"&&!t){let d=[];Object.entries(r).forEach(([f,u])=>{d=[...d,f,e?u:encodeURIComponent(u)]});const p=d.join(",");switch(a){case"form":return`${n}=${p}`;case"label":return`.${p}`;case"matrix":return`;${n}=${p}`;default:return p}}const o=Pn(a),l=Object.entries(r).map(([d,p])=>Je({allowReserved:e,name:a==="deepObject"?`${n}[${d}]`:d,value:p})).join(o);return a==="label"||a==="matrix"?o+l:l},jn=/\{[^{}]+\}/g,In=({path:e,url:t})=>{let n=t;const a=t.match(jn);if(a)for(const r of a){let i=!1,o=r.substring(1,r.length-1),l="simple";o.endsWith("*")&&(i=!0,o=o.substring(0,o.length-1)),o.startsWith(".")?(o=o.substring(1),l="label"):o.startsWith(";")&&(o=o.substring(1),l="matrix");const d=e[o];if(d==null)continue;if(Array.isArray(d)){n=n.replace(r,Bt({explode:i,name:o,style:l,value:d}));continue}if(typeof d=="object"){n=n.replace(r,Mt({explode:i,name:o,style:l,value:d,valueOnly:!0}));continue}if(l==="matrix"){n=n.replace(r,`;${Je({name:o,value:d})}`);continue}const p=encodeURIComponent(l==="label"?`.${d}`:d);n=n.replace(r,p)}return n},Bn=({baseUrl:e,path:t,query:n,querySerializer:a,url:r})=>{const i=r.startsWith("/")?r:`/${r}`;let o=(e??"")+i;t&&(o=In({path:t,url:o}));let l=n?a(n):"";return l.startsWith("?")&&(l=l.substring(1)),l&&(o+=`?${l}`),o};function Lt(e){const t=e.body!==void 0;if(t&&e.bodySerializer)return"serializedBody"in e?e.serializedBody!==void 0&&e.serializedBody!==""?e.serializedBody:null:e.body!==""?e.body:null;if(t)return e.body}const Mn=async(e,t)=>{const n=typeof t=="function"?await t(e):t;if(n)return e.scheme==="bearer"?`Bearer ${n}`:e.scheme==="basic"?`Basic ${btoa(n)}`:n},Ut=({parameters:e={},...t}={})=>a=>{const r=[];if(a&&typeof a=="object")for(const i in a){const o=a[i];if(o==null)continue;const l=e[i]||t;if(Array.isArray(o)){const d=Bt({allowReserved:l.allowReserved,explode:!0,name:i,style:"form",value:o,...l.array});d&&r.push(d)}else if(typeof o=="object"){const d=Mt({allowReserved:l.allowReserved,explode:!0,name:i,style:"deepObject",value:o,...l.object});d&&r.push(d)}else{const d=Je({allowReserved:l.allowReserved,name:i,value:o});d&&r.push(d)}}return r.join("&")},Un=e=>{var n;if(!e)return"stream";const t=(n=e.split(";")[0])==null?void 0:n.trim();if(t){if(t.startsWith("application/json")||t.endsWith("+json"))return"json";if(t==="multipart/form-data")return"formData";if(["application/","audio/","image/","video/"].some(a=>t.startsWith(a)))return"blob";if(t.startsWith("text/"))return"text"}},Dn=(e,t)=>{var n,a;return t?!!(e.headers.has(t)||(n=e.query)!=null&&n[t]||(a=e.headers.get("Cookie"))!=null&&a.includes(`${t}=`)):!1},zn=async({security:e,...t})=>{for(const n of e){if(Dn(t,n.name))continue;const a=await Mn(n,t.auth);if(!a)continue;const r=n.name??"Authorization";switch(n.in){case"query":t.query||(t.query={}),t.query[r]=a;break;case"cookie":t.headers.append("Cookie",`${r}=${a}`);break;case"header":default:t.headers.set(r,a);break}}},xt=e=>Bn({baseUrl:e.baseUrl,path:e.path,query:e.query,querySerializer:typeof e.querySerializer=="function"?e.querySerializer:Ut(e.querySerializer),url:e.url}),Tt=(e,t)=>{var a;const n={...e,...t};return(a=n.baseUrl)!=null&&a.endsWith("/")&&(n.baseUrl=n.baseUrl.substring(0,n.baseUrl.length-1)),n.headers=Dt(e.headers,t.headers),n},Wn=e=>{const t=[];return e.forEach((n,a)=>{t.push([a,n])}),t},Dt=(...e)=>{const t=new Headers;for(const n of e){if(!n)continue;const a=n instanceof Headers?Wn(n):Object.entries(n);for(const[r,i]of a)if(i===null)t.delete(r);else if(Array.isArray(i))for(const o of i)t.append(r,o);else i!==void 0&&t.set(r,typeof i=="object"?JSON.stringify(i):i)}return t};class tt{constructor(){this.fns=[]}clear(){this.fns=[]}eject(t){const n=this.getInterceptorIndex(t);this.fns[n]&&(this.fns[n]=null)}exists(t){const n=this.getInterceptorIndex(t);return!!this.fns[n]}getInterceptorIndex(t){return typeof t=="number"?this.fns[t]?t:-1:this.fns.indexOf(t)}update(t,n){const a=this.getInterceptorIndex(t);return this.fns[a]?(this.fns[a]=n,t):!1}use(t){return this.fns.push(t),this.fns.length-1}}const Gn=()=>({error:new tt,request:new tt,response:new tt}),Fn=Ut({allowReserved:!1,array:{explode:!0,style:"form"},object:{explode:!0,style:"deepObject"}}),Hn={"Content-Type":"application/json"},zt=(e={})=>({...Rn,headers:Hn,parseAs:"auto",querySerializer:Fn,...e}),Jn=(e={})=>{let t=Tt(zt(),e);const n=()=>({...t}),a=f=>(t=Tt(t,f),n()),r=Gn(),i=async f=>{const u={...t,...f,fetch:f.fetch??t.fetch??globalThis.fetch,headers:Dt(t.headers,f.headers),serializedBody:void 0};u.security&&await zn({...u,security:u.security}),u.requestValidator&&await u.requestValidator(u),u.body!==void 0&&u.bodySerializer&&(u.serializedBody=u.bodySerializer(u.body)),(u.body===void 0||u.serializedBody==="")&&u.headers.delete("Content-Type");const y=u,m=xt(y);return{opts:y,url:m}},o=async f=>{const{opts:u,url:y}=await i(f),m={redirect:"follow",...u,body:Lt(u)};let h=new Request(y,m);for(const $ of r.request.fns)$&&(h=await $(h,u));const w=u.fetch;let E;try{E=await w(h)}catch($){let O=$;for(const A of r.error.fns)A&&(O=await A($,void 0,h,u));if(O=O||{},u.throwOnError)throw O;return u.responseStyle==="data"?void 0:{error:O,request:h,response:void 0}}for(const $ of r.response.fns)$&&(E=await $(E,h,u));const b={request:h,response:E};if(E.ok){const $=(u.parseAs==="auto"?Un(E.headers.get("Content-Type")):u.parseAs)??"json";if(E.status===204||E.headers.get("Content-Length")==="0"){let A;switch($){case"arrayBuffer":case"blob":case"text":A=await E[$]();break;case"formData":A=new FormData;break;case"stream":A=E.body;break;case"json":default:A={};break}return u.responseStyle==="data"?A:{data:A,...b}}let O;switch($){case"arrayBuffer":case"blob":case"formData":case"text":O=await E[$]();break;case"json":{const A=await E.text();O=A?JSON.parse(A):{};break}case"stream":return u.responseStyle==="data"?E.body:{data:E.body,...b}}return $==="json"&&(u.responseValidator&&await u.responseValidator(O),u.responseTransformer&&(O=await u.responseTransformer(O))),u.responseStyle==="data"?O:{data:O,...b}}const C=await E.text();let N;try{N=JSON.parse(C)}catch{}const I=N??C;let M=I;for(const $ of r.error.fns)$&&(M=await $(I,E,h,u));if(M=M||{},u.throwOnError)throw M;return u.responseStyle==="data"?void 0:{error:M,...b}},l=f=>u=>o({...u,method:f}),d=f=>async u=>{const{opts:y,url:m}=await i(u);return On({...y,body:y.body,headers:y.headers,method:f,onRequest:async(h,w)=>{let E=new Request(h,w);for(const b of r.request.fns)b&&(E=await b(E,y));return E},serializedBody:Lt(y),url:m})};return{buildUrl:f=>xt({...t,...f}),connect:l("CONNECT"),delete:l("DELETE"),get:l("GET"),getConfig:n,head:l("HEAD"),interceptors:r,options:l("OPTIONS"),patch:l("PATCH"),post:l("POST"),put:l("PUT"),request:o,setConfig:a,sse:{connect:d("CONNECT"),delete:d("DELETE"),get:d("GET"),head:d("HEAD"),options:d("OPTIONS"),patch:d("PATCH"),post:d("POST"),put:d("PUT"),trace:d("TRACE")},trace:l("TRACE")}},le=Jn(zt()),Wt={debug:console.debug.bind(console),error:console.error.bind(console),info:console.info.bind(console),log:console.log.bind(console),warn:console.warn.bind(console)};let At=!1;function Vn(){At||typeof window>"u"||(At=!0,ke("debug","debug"),ke("info","info"),ke("warn","warn"),ke("error","error"),ke("log","info"),window.addEventListener("error",e=>{oe("window","Unhandled error",{colno:e.colno,error:e.error,filename:e.filename,lineno:e.lineno,message:e.message})}),window.addEventListener("unhandledrejection",e=>{oe("window","Unhandled promise rejection",{reason:e.reason})}))}function he(e,t,n){Ke("debug",e,t,n)}function J(e,t,n){Ke("info",e,t,n)}function Ve(e,t,n){Ke("warn",e,t,n)}function oe(e,t,n){Ke("error",e,t,n)}function Ke(e,t,n,a){const r=Gt(e,t,n,a);Wt[e](`[dashboard][${t}] ${n}`,We(a)),Ft(r)}function ke(e,t){const n=Wt[e];console[e]=(...a)=>{n(...a),Ft(Gt(t,"console",Qn(a),a.length>1?a.slice(1):a[0]))}}function Gt(e,t,n,a){return{city:Kn(),details:a===void 0?void 0:We(a),level:e,message:n,scope:t,ts:new Date().toISOString(),url:typeof window>"u"?"":window.location.href}}function Kn(){return typeof window>"u"?"":(new URLSearchParams(window.location.search).get("city")??"").trim()}function Qn(e){if(e.length===0)return"console event";const[t]=e;return typeof t=="string"&&t.trim()!==""?t:t instanceof Error?t.message:"console event"}function Ft(e){const t=JSON.stringify(e);if(typeof navigator<"u"&&typeof navigator.sendBeacon=="function"){const n=new Blob([t],{type:"application/json"});if(navigator.sendBeacon("/__client-log",n))return}fetch("/__client-log",{body:t,credentials:"same-origin",headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})}function We(e,t=0,n=new WeakSet){if(e==null)return e??null;if(typeof e=="string")return e.length>2e3?`${e.slice(0,1999)}…`:e;if(typeof e=="number"||typeof e=="boolean")return e;if(e instanceof Error)return{message:e.message,name:e.name,stack:e.stack};if(typeof e=="function")return`[function ${e.name||"anonymous"}]`;if(t>=4)return"[max-depth]";if(Array.isArray(e))return e.slice(0,20).map(a=>We(a,t+1,n));if(typeof e=="object"){if(n.has(e))return"[circular]";n.add(e);const a={};for(const[r,i]of Object.entries(e).slice(0,40))a[r]=We(i,t+1,n);return a}return String(e)}const ft=["cities","status","supervisor","crew","issues","mail","convoys","activity","admin","options"];let Ge=Vt(window.location.search),pt=[];const ze=new Set(ft);function Xn(){return Ge}function yt(){return Ge=Vt(window.location.search),Ge}function se(...e){e.forEach(t=>ze.add(t))}function mt(){se(...ft)}function Yn(e=!1){if(e)return ze.clear(),new Set(ft);const t=new Set(ze);return ze.clear(),t}function Zn(e){pt=e.map(t=>({error:t.error,name:t.name,path:t.path,phasesCompleted:[...t.phasesCompleted??[]],running:t.running,status:t.status}))}function Ht(){return pt.map(e=>({error:e.error,name:e.name,path:e.path,phasesCompleted:[...e.phasesCompleted],running:e.running,status:e.status}))}function Jt(){const e=Ge;if(e==="")return{kind:"supervisor"};const t=pt.find(n=>n.name===e);return t?t.running?{kind:"running",city:t}:{kind:"not-running",city:t}:{kind:"unknown",name:e}}function ea(e){if(e){if(e.startsWith("session.")||e.startsWith("agent.")){se("status","crew","options");return}if(e.startsWith("bead.")){se("status","issues","convoys","admin","options");return}if(e.startsWith("mail.")){se("status","mail","options");return}if(e.startsWith("convoy.")){se("status","convoys");return}if(e.startsWith("city.")){se("cities","status","supervisor");return}if(e.startsWith("service.")||e.startsWith("provider.")||e.startsWith("rig.")){se("admin");return}}}function Vt(e){return(new URLSearchParams(e).get("city")??"").trim()}function Kt(){const e=document.querySelector('meta[name="supervisor-url"]');return((e==null?void 0:e.content)??"").replace(/\/+$/,"")}function S(){return Xn()}const T={"X-GC-Request":"true"},g=Ln({baseUrl:Kt(),headers:T});le.setConfig({baseUrl:Kt(),headers:T});g.use({async onError({error:e,request:t,schemaPath:n}){return oe("api","Request failed",{error:e,method:t.method,schemaPath:n,url:t.url}),e instanceof Error?e:new Error(String(e))},async onRequest({params:e,request:t,schemaPath:n}){he("api","Request start",{method:t.method,params:e,schemaPath:n,url:t.url})},async onResponse({request:e,response:t,schemaPath:n}){const a={method:e.method,ok:t.ok,schemaPath:n,status:t.status,url:e.url};if(!t.ok||t.status>=400){Ve("api","Request response",a);return}he("api","Request response",a)}});function s(e,t={},n=[]){const a=document.createElement(e);for(const[r,i]of Object.entries(t))i===void 0||i===!1||(i===!0?a.setAttribute(r,""):a.setAttribute(r,String(i)));for(const r of n)r!=null&&a.append(typeof r=="string"?document.createTextNode(r):r);return a}function k(e){for(;e.firstChild;)e.removeChild(e.firstChild)}function c(e){return document.getElementById(e)}async function ta(){const e=c("city-tabs");if(!e)return;const{data:t,error:n}=await g.GET("/v0/cities");!n&&(t!=null&&t.items)&&Zn(t.items.map(l=>({error:l.error??void 0,name:l.name??"",path:l.path??void 0,phasesCompleted:l.phases_completed??[],running:l.running===!0,status:l.status??void 0})));const a=Ht();if(n||a.length===0)return;const r=S();k(e);const i=s("nav",{class:"city-tabs"}),o=window.location.pathname||"/";i.append(s("a",{href:o,class:`city-tab${r===""?" active":""}`},[s("span",{class:"city-dot running"})," Supervisor"]));for(const l of a){const d=l.running,p=l.name===r,f=s("a",{href:`${o}?city=${encodeURIComponent(l.name)}`,class:`city-tab${p?" active":""}${d?"":" stopped"}`},[s("span",{class:`city-dot${d?" running":""}`}),` ${l.name}`]);i.append(f)}e.append(i)}function gt(e,t=new Date){if(!e)return"";const n=new Date(e);if(isNaN(n.getTime()))return"";const a=Math.max(0,t.getTime()-n.getTime()),r=Math.floor(a/1e3);if(r<60)return`${r}s ago`;const i=Math.floor(r/60);if(i<60)return`${i}m ago`;const o=Math.floor(i/60);return o<24?`${o}h ago`:`${Math.floor(o/24)}d ago`}const Qt=300*1e3,na=600*1e3;function D(e){if(!e)return"—";const t=new Date(e);if(Number.isNaN(t.getTime()))return"—";const n=new Date,a=t.getFullYear()===n.getFullYear()?{month:"short",day:"numeric",hour:"numeric",minute:"2-digit"}:{month:"short",day:"numeric",year:"numeric",hour:"numeric",minute:"2-digit"};return t.toLocaleString(void 0,a)}function _e(e){if(!e)return{display:"unknown",colorClass:"unknown"};const t=new Date(e);if(Number.isNaN(t.getTime()))return{display:"unknown",colorClass:"unknown"};const n=Math.max(0,Date.now()-t.getTime()),a=gt(e).replace(" ago","");return n=3?`${t[t.length-1]} (${t[0]}/${t[1]})`:`${t[0]}/${t[t.length-1]}`}function aa(e){return!e||!e.includes("/")?"":e.split("/",1)[0]??""}function sa(e){return e.startsWith("agent.")||e.startsWith("session.")?"agent":e.startsWith("bead.")||e.startsWith("convoy.")||e.startsWith("order.")?"work":e.startsWith("mail.")?"comms":"system"}function ra(e){return{"session.started":"▶","session.ended":"■","session.crashed":"☠","session.suspended":"⏸","session.woke":"▶","agent.message":"💬","agent.output":"📝","agent.tool_call":"🛠","agent.tool_result":"✅","agent.error":"⚠","bead.created":"📿","bead.updated":"📝","bead.closed":"✅","convoy.created":"🚚","convoy.closed":"✅","mail.delivered":"📬","mail.read":"📨"}[e]??"📋"}function ia(e,t,n,a){const r=B(t);switch(e){case"session.started":return`${B(n)} started`;case"session.ended":return`${B(n)} ended`;case"session.crashed":return`${B(n)} crashed`;case"session.suspended":return`${B(n)} suspended`;case"session.woke":return`${B(n)} woke`;case"bead.created":return`${r} created bead ${n??""}`.trim();case"bead.updated":return`${r} updated bead ${n??""}`.trim();case"bead.closed":return`${r} closed bead ${n??""}`.trim();case"mail.delivered":return`${r} delivered mail`;case"mail.read":return`${r} read mail`;case"convoy.created":return`${r} created convoy ${n??""}`.trim();case"convoy.closed":return`${r} closed convoy ${n??""}`.trim();default:return a??n??e}}function Qe(e,t){return e?e.length<=t?e:`${e.slice(0,t-1)}…`:""}function ee(e){return typeof e!="number"||Number.isNaN(e)||e<=0?4:e}function Xt(e){switch(ee(e)){case 1:return"badge-red";case 2:return"badge-orange";case 3:return"badge-yellow";default:return"badge-muted"}}function ce(e){switch((e??"").toLowerCase()){case"open":case"running":case"ready":case"working":return"badge-green";case"in_progress":case"pending":case"stale":case"warning":return"badge-yellow";case"closed":case"stopped":return"badge-muted";case"error":case"failed":case"stuck":return"badge-red";default:return"badge-blue"}}async function oa(){var w,E,b;const e=S(),t=c("status-banner");if(!t)return;if(!e){await ca(t);return}da();const[n,a,r,i]=await Promise.all([g.GET("/v0/city/{cityName}/status",{params:{path:{cityName:e}}}),g.GET("/v0/city/{cityName}/sessions",{params:{path:{cityName:e},query:{state:"active",peek:!0}}}),g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:e},query:{status:"open",limit:500}}}),g.GET("/v0/city/{cityName}/convoys",{params:{path:{cityName:e},query:{limit:200}}})]);if(n.error||!n.data){k(t),t.append(s("div",{class:"banner-error"},[`Status unavailable for ${e}`]));return}const o=((w=a.data)==null?void 0:w.items)??[],l=((E=r.data)==null?void 0:E.items)??[],d=((b=i.data)==null?void 0:b.items)??[];la(e,o);const p=o.filter(C=>!C.pool||!C.running||!C.last_active?!1:Date.now()-new Date(C.last_active).getTime()>=1800*1e3).length,f=l.filter(C=>C.assignee&&C.status!=="closed").length,u=l.filter(C=>ee(C.priority)<=2).length,y=o.filter(C=>!C.running).length,m=s("div",{class:"summary-stats"},[H(n.data.agents.running,"Agents"),H(n.data.work.in_progress,"Assigned"),H(n.data.work.open,"Beads"),H(d.length,"Convoys"),H(n.data.mail.unread,"Unread")]),h=s("div",{class:"summary-alerts"});Q(h,p>0,"alert-red",`${p} stuck`),Q(h,f>0,"alert-yellow",`${f} assigned`),Q(h,u>0,"alert-red",`${u} P1/P2`),Q(h,y>0,"alert-red",`${y} dead`),h.childNodes.length||h.append(s("span",{class:"alert-item alert-green"},["All clear"])),k(t),t.append(m,h)}async function ca(e){var u,y;ua();const[t,n]=await Promise.all([g.GET("/health"),g.GET("/v0/cities")]),a=t.data,r=((u=n.data)==null?void 0:u.items)??[],i=(a==null?void 0:a.cities_total)??r.length,o=(a==null?void 0:a.cities_running)??r.filter(m=>m.running===!0).length,l=Math.max(i-o,0),d=r.filter(m=>!!m.error).length;if(k(e),t.error&&n.error){e.append(s("div",{class:"banner-error"},["Supervisor status unavailable"]));return}const p=s("div",{class:"summary-stats"},[H(i,"🏙️ Cities"),H(o,"🟢 Running"),H(l,"⏸ Stopped"),H(fa(a==null?void 0:a.uptime_sec),"⏱ Uptime")]),f=s("div",{class:"summary-alerts"});Q(f,i===0,"alert-yellow","No registered cities"),Q(f,l>0,"alert-yellow",`${l} ${l===1?"city":"cities"} not running`),Q(f,d>0,"alert-red",`${d} ${d===1?"city":"cities"} reporting errors`),Q(f,!!(a!=null&&a.startup&&!a.startup.ready),"alert-yellow",`⏳ Startup: ${((y=a==null?void 0:a.startup)==null?void 0:y.phase)||"starting"}`),f.childNodes.length||f.append(s("span",{class:"alert-item alert-green"},["✓ Supervisor ready"])),e.append(p,f)}function H(e,t){return s("div",{class:"stat"},[s("span",{class:"stat-value"},[String(e??0)]),s("span",{class:"stat-label"},[t])])}function Q(e,t,n,a){t&&e.append(s("span",{class:`alert-item ${n}`},[a]))}function la(e,t){const n=c("scope-banner"),a=c("scope-badge"),r=c("scope-status");if(!n||!a||!r)return;const i=t.find(l=>!l.rig&&!l.pool);if(!i){n.classList.remove("attached"),n.classList.add("detached"),a.className="badge badge-muted",a.textContent="Detached",k(r),r.append(K("Scope",e),K("Overseer","none"));return}n.classList.remove("attached","detached"),n.classList.add(i.attached?"attached":"detached"),a.className=`badge ${i.attached?"badge-green":"badge-muted"}`,a.textContent=i.attached?"Attached":"Detached",k(r);const o=i.last_active?Date.now()-new Date(i.last_active).getTime()(e.client??le).sse.get({url:"/v0/city/{cityName}/events/stream",...e}),ya=e=>(e.client??le).sse.get({url:"/v0/city/{cityName}/session/{id}/stream",...e}),ma=e=>((e==null?void 0:e.client)??le).sse.get({url:"/v0/events/stream",...e});let re=0,rt=null;function ga(e){rt=e}function Yt(e){re=Math.max(0,e),document.body.dataset.pauseRefresh=re>0?"true":"false"}function F(){Yt(re+1)}function j(){const e=re>0;if(Yt(re-1),e&&re===0&&rt)try{rt()}catch(t){oe("ui","popPause listener threw",{error:String(t)})}}function bt(){return re>0}function Rt(e,t){const n=c("output-panel"),a=c("output-panel-cmd"),r=c("output-panel-content");!n||!a||!r||(a.textContent=e,r.textContent=t,n.classList.add("open"))}function Zt(){var e;(e=c("output-panel"))==null||e.classList.remove("open")}function v(e,t,n){const a=c("toast-container");if(!a)return;const r=document.createElement("div");r.className=`toast toast-${e}`,r.innerHTML=`${Ot(t)}
${Ot(n)}
`,a.append(r);const i=e==="error"?9e3:5e3;window.requestAnimationFrame(()=>{r.classList.add("show")}),window.setTimeout(()=>{r.classList.remove("show"),window.setTimeout(()=>{r.remove()},300)},i)}function P(e,t,n="Unexpected dashboard error"){const a=t instanceof Error?t.message:n;oe("ui",e,{error:t,fallbackMessage:n,message:a}),v("error",e,a)}function ba(){var e,t;document.addEventListener("click",n=>{const a=n.target,r=a==null?void 0:a.closest(".collapse-btn");if(r){const p=r.closest(".panel");p==null||p.classList.toggle("collapsed");return}const i=a==null?void 0:a.closest(".expand-btn");if(!i)return;const o=i.closest(".panel");if(!o)return;const l=o.classList.contains("expanded"),d=!!document.querySelector(".panel.expanded");if(document.querySelectorAll(".panel.expanded").forEach(p=>{p.classList.remove("expanded");const f=p.querySelector(".expand-btn");f&&(f.textContent="Expand")}),l){j();return}o.classList.add("expanded"),i.textContent="✕ Close",d||F()}),document.addEventListener("keydown",n=>{if(n.key!=="Escape")return;const a=document.querySelector(".panel.expanded");if(a){a.classList.remove("expanded");const r=a.querySelector(".expand-btn");r&&(r.textContent="Expand"),j()}}),(e=c("output-close-btn"))==null||e.addEventListener("click",()=>Zt()),(t=c("output-copy-btn"))==null||t.addEventListener("click",async()=>{var a;const n=((a=c("output-panel-content"))==null?void 0:a.textContent)??"";try{await navigator.clipboard.writeText(n),v("success","Copied","Output copied to clipboard")}catch{v("error","Copy failed","Clipboard write was rejected")}})}function Ot(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}function en(e){return typeof e=="object"&&e!==null}function tn(e){return en(e)&&typeof e.timestamp=="string"}function nn(e){return en(e)&&typeof e.actor=="string"&&typeof e.seq=="number"&&typeof e.ts=="string"&&typeof e.type=="string"}function ha(e){return nn(e)}function va(e){return nn(e)&&typeof e.city=="string"}const qt=[1e3,2e3,4e3,8e3,15e3],wa=15e3;function an(e){return e{var o;let r=0,i=!1;for(;!n.signal.aborted;){try{const{stream:d}=await ma({client:le,signal:n.signal,onSseEvent:p=>{var u;r=0,i=!1,(u=t==null?void 0:t.onStatus)==null||u.call(t,"live");const f=p.event??"tagged_event";if(f==="heartbeat"){if(!tn(p.data)){P("Invalid supervisor heartbeat frame",p);return}e({event:"heartbeat",id:p.id,data:p.data});return}if(f==="tagged_event"){if(!va(p.data)){P("Invalid supervisor event frame",p);return}e({event:"tagged_event",id:p.id,data:p.data});return}P(`Unexpected supervisor SSE event: ${f}`,p)}});for await(const p of d);if(n.signal.aborted)break}catch(d){if(n.signal.aborted)return;i||(P("Supervisor event stream failed",d),i=!0)}(o=t==null?void 0:t.onStatus)==null||o.call(t,"reconnecting");const l=an(r);r+=1,await sn(l,n.signal)}})(),{close:()=>n.abort()}}function Ea(e,t,n){var r;const a=new AbortController;return(r=n==null?void 0:n.onStatus)==null||r.call(n,"connecting"),(async()=>{var l;let i=0,o=!1;for(;!a.signal.aborted;){try{const{stream:p}=await pa({client:le,path:{cityName:e},signal:a.signal,onSseEvent:f=>{var m;i=0,o=!1,(m=n==null?void 0:n.onStatus)==null||m.call(n,"live");const u=f.event??"event",y=f.id!==void 0?String(f.id):void 0;if(u==="heartbeat"){if(!tn(f.data)){P("Invalid city heartbeat frame",f);return}t({event:"heartbeat",id:y,data:f.data});return}if(u==="event"){if(!ha(f.data)){P("Invalid city event frame",f);return}t({event:"event",id:y,data:f.data});return}P(`Unexpected city SSE event: ${u}`,f)}});for await(const f of p);if(a.signal.aborted)break}catch(p){if(a.signal.aborted)return;o||(P("City event stream failed",p),o=!0)}(l=n==null?void 0:n.onStatus)==null||l.call(n,"reconnecting");const d=an(i);i+=1,await sn(d,a.signal)}})(),{close:()=>a.abort()}}async function sn(e,t){if(!t.aborted)return new Promise(n=>{const a=setTimeout(()=>{t.removeEventListener("abort",r),n()},e),r=()=>{clearTimeout(a),t.removeEventListener("abort",r),n()};t.addEventListener("abort",r)})}function Ca(e,t,n){const a=new AbortController;return(async()=>{try{const{stream:r}=await ya({client:le,path:{cityName:e,id:t},signal:a.signal,onSseEvent:i=>{if(i.data===void 0){P("Session frame missing data",i);return}n({id:i.id!==void 0?String(i.id):void 0,type:i.event??"message",data:i.data})}});for await(const i of r);}catch(r){a.signal.aborted||P("Session stream failed",r)}})(),{close:()=>a.abort()}}function ka(e){return e.event==="heartbeat"?"heartbeat":e.data.type}let Te=null,ge="",X="",Pe=0;async function Na(){const e=S();if(!e){$a();return}const t=c("crew-loading"),n=c("crew-table"),a=c("crew-empty"),r=c("crew-tbody"),i=c("rigged-body"),o=c("pooled-body");if(!t||!n||!a||!r||!i||!o)return;it("No crew configured"),t.style.display="block",n.style.display="none",a.style.display="none",k(r);const{data:l,error:d}=await g.GET("/v0/city/{cityName}/sessions",{params:{path:{cityName:e},query:{state:"active",peek:!0}}});if(d||!(l!=null&&l.items)){t.textContent="Failed to load crew",ve(i,"No rigged agents"),ve(o,"No pooled agents");return}const p=l.items,f=await Promise.all(p.map(async m=>{var w;return!!((w=(await g.GET("/v0/city/{cityName}/session/{id}/pending",{params:{path:{cityName:e,id:m.id}}})).data)!=null&&w.pending)})),u=new Map;await Promise.all(p.map(async m=>{var w;if(!m.active_bead||u.has(m.active_bead))return;const h=await g.GET("/v0/city/{cityName}/bead/{id}",{params:{path:{cityName:e,id:m.active_bead}}});u.set(m.active_bead,(w=h.data)!=null&&w.id?h.data.title??h.data.id:m.active_bead)}));const y=p;y.forEach((m,h)=>{const w=La(m,f[h]??!1),E=m.active_bead?Qe(u.get(m.active_bead)??m.active_bead,24):"—",b=s("tr",{},[s("td",{},[m.template]),s("td",{},[m.rig??"city"]),s("td",{},[s("span",{class:`badge ${ce(w)}`},[w])]),s("td",{},[E]),s("td",{class:_e(m.last_active).colorClass?`activity-${_e(m.last_active).colorClass}`:""},[s("span",{class:"activity-dot"}),` ${_e(m.last_active).display}`]),s("td",{},[s("span",{class:`badge ${m.attached?"badge-green":"badge-muted"}`},[m.attached?"Attached":"Detached"])]),s("td",{},[xa(m.template)," ",rn(m.id,m.template)])]);r.append(b)}),c("crew-count").textContent=String(y.length),t.style.display="none",y.length>0?n.style.display="table":(it("No crew configured"),a.style.display="block"),Ta(p,u),Aa(p)}function $a(){const e=c("crew-loading"),t=c("crew-table"),n=c("crew-empty"),a=c("crew-tbody"),r=c("rigged-body"),i=c("pooled-body");!e||!t||!n||!a||!r||!i||(je(),c("crew-count").textContent="0",c("rigged-count").textContent="0",c("pooled-count").textContent="0",e.style.display="none",t.style.display="none",n.style.display="block",it("Select a city to view crew"),k(a),ve(r,"Select a city to view rigged agents"),ve(i,"Select a city to view pooled agents"))}function it(e){var t,n;(n=(t=c("crew-empty"))==null?void 0:t.querySelector("p"))==null||n.replaceChildren(document.createTextNode(e))}function La(e,t){return t?"questions":e.active_bead?"spinning":e.running?"idle":"finished"}function xa(e){const t=s("button",{class:"attach-btn",type:"button"},["📎 Attach"]);return t.addEventListener("click",async()=>{const n=`gc agent attach ${e}`;try{await navigator.clipboard.writeText(n),v("success","Attach command copied",n)}catch{v("error","Copy failed",n)}}),t}function rn(e,t){const n=s("button",{class:"agent-log-link",type:"button","data-session-id":e},[t]);return n.addEventListener("click",()=>{Oa(e,t)}),n}function Ta(e,t){const n=c("rigged-body"),a=c("rigged-count");if(!n||!a)return;const r=e.filter(o=>o.rig&&o.pool);if(a.textContent=String(r.length),r.length===0){ve(n,"No rigged agents");return}const i=s("tbody");r.forEach(o=>{const l=_e(o.last_active),d=o.active_bead?l.colorClass==="red"?"Stuck":l.colorClass==="yellow"?"Stale":"Working":"Idle";i.append(s("tr",{class:`rigged-${d.toLowerCase()}`},[s("td",{},[rn(o.id,o.template)]),s("td",{},[s("span",{class:"badge badge-muted"},[o.pool??"pool"])]),s("td",{},[o.rig??"city"]),s("td",{class:"rigged-issue"},[o.active_bead?`${o.active_bead} ${t.get(o.active_bead)??""}`.trim():"—"]),s("td",{},[s("span",{class:`badge ${ce(d)}`},[d])]),s("td",{class:`activity-${l.colorClass}`},[s("span",{class:"activity-dot"}),` ${l.display}`])]))}),k(n),n.append(s("table",{},[s("thead",{},[s("tr",{},[s("th",{},["Agent"]),s("th",{},["Pool"]),s("th",{},["Rig"]),s("th",{},["Working On"]),s("th",{},["Status"]),s("th",{},["Activity"])])]),i]))}function Aa(e){const t=c("pooled-body"),n=c("pooled-count");if(!t||!n)return;const a=e.filter(i=>!i.rig&&i.pool);if(n.textContent=String(a.length),a.length===0){ve(t,"No pooled agents");return}const r=s("tbody");a.forEach(i=>{r.append(s("tr",{},[s("td",{},[i.template]),s("td",{},[s("span",{class:`badge ${i.active_bead?"badge-yellow":"badge-green"}`},[i.active_bead?"Working":"Idle"])]),s("td",{class:"status-hint"},[Qe(i.last_output,80)||"—"]),s("td",{},[D(i.last_active)])]))}),k(t),t.append(s("table",{},[s("thead",{},[s("tr",{},[s("th",{},["Agent"]),s("th",{},["State"]),s("th",{},["Work"]),s("th",{},["Activity"])])]),r]))}function ve(e,t){k(e),e.append(s("div",{class:"empty-state"},[s("p",{},[t])]))}function Ra(){var e,t;(e=c("log-drawer-close-btn"))==null||e.addEventListener("click",()=>je()),(t=c("log-drawer-older-btn"))==null||t.addEventListener("click",()=>{he("crew","Load older transcript clicked",{hasCursor:X!=="",sessionID:ge}),!(!ge||!X)&&cn(ge,!0)})}async function Oa(e,t){const n=c("agent-log-drawer"),a=c("log-drawer-agent-name"),r=c("log-drawer-messages"),i=c("log-drawer-loading");if(!n||!a||!r||!i)return;if(ge===e&&n.style.display!=="none"){je();return}je(),ge=e,X="",Pe=0,a.textContent=t,k(r),r.append(i),i.style.display="block",n.style.display="block",F(),await cn(e,!1);const o=S();o&&(Te=Ca(o,e,l=>qa(l)))}function je(){Te==null||Te.close(),Te=null,ge="",X="";const e=c("agent-log-drawer");e&&e.style.display!=="none"&&(e.style.display="none",j())}function on(){je()}async function cn(e,t){var p,f,u,y,m;const n=S(),a=c("log-drawer-messages"),r=c("log-drawer-loading"),i=c("log-drawer-older-btn"),o=c("log-drawer-count");if(!n||!a||!r||!i||!o)return;r.style.display="block";const l=await g.GET("/v0/city/{cityName}/session/{id}/transcript",{params:{path:{cityName:n,id:e},query:{tail:String(t?50:25),before:t?X:void 0}}});if(r.style.display="none",l.error||!l.data){v("error","Transcript failed",((p=l.error)==null?void 0:p.detail)??"Could not load transcript");return}const d=document.createDocumentFragment();for(const h of l.data.turns??[])d.append(ln(h.role,h.text,h.timestamp)),Pe+=1;t?a.prepend(d):(k(a),a.append(d)),a.append(r),r.style.display="none",o.textContent=String(Pe),X=((f=l.data.pagination)==null?void 0:f.truncated_before_message)??"",i.style.display=(u=l.data.pagination)!=null&&u.has_older_messages&&X?"inline-flex":"none",he("crew","Transcript loaded",{hasOlderMessages:((y=l.data.pagination)==null?void 0:y.has_older_messages)??!1,nextBeforeCursor:X,prepend:t,sessionID:e,turnCount:((m=l.data.turns)==null?void 0:m.length)??0})}function qa(e){var r;const t=c("log-drawer-messages");if(!t)return;const n=e.data;if(e.type!=="message"||!((r=n==null?void 0:n.data)!=null&&r.message))return;t.append(ln(n.data.message.role??"agent",n.data.message.text??"",n.data.message.timestamp)),Pe+=1,c("log-drawer-count").textContent=String(Pe);const a=c("log-drawer-body");a&&(a.scrollTop=a.scrollHeight)}function ln(e,t,n){return s("div",{class:"log-msg"},[s("div",{class:"log-msg-header"},[s("span",{class:`log-msg-type log-msg-type-${_a(e)}`},[e]),s("span",{class:"log-msg-time"},[D(n)])]),s("div",{class:"log-msg-body"},[t])])}function _a(e){switch((e??"").toLowerCase()){case"assistant":case"agent":return"assistant";case"system":return"system";case"result":return"result";default:return"user"}}const Pa=3e4,ot=new Map,Ae=new Map;async function Xe(e=!1){const t=S(),n=Date.now(),a=ot.get(t);if(!e&&a&&n-a.fetchedAt(ot.set(t,o),Ae.delete(t),o)).catch(o=>{throw Ae.delete(t),o});return Ae.set(t,i),i}async function ja(e){var l,d,p,f,u,y,m,h,w,E,b,C;const t={agents:[],rigs:[],sessions:[],beads:[],mail:[],fetchedAt:Date.now()};if(!e)return t;const[n,a,r,i]=await Promise.all([g.GET("/v0/city/{cityName}/config",{params:{path:{cityName:e}}}),g.GET("/v0/city/{cityName}/rigs",{params:{path:{cityName:e}}}),g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:e},query:{status:"open"}}}),g.GET("/v0/city/{cityName}/mail",{params:{path:{cityName:e}}})]);n.error&&Ve("options","Config options request failed",{city:e,detail:n.error.detail??null});const o=(((l=n.data)==null?void 0:l.agents)??[]).map(N=>({id:N.name??"",label:N.name??"",recipient:N.name??""})).filter(N=>N.recipient!=="");return he("options","Fetched options",{agentOptions:o.map(N=>N.recipient),beads:((p=(d=r.data)==null?void 0:d.items)==null?void 0:p.length)??0,city:e,configAgents:((u=(f=n.data)==null?void 0:f.agents)==null?void 0:u.length)??0,mail:((m=(y=i.data)==null?void 0:y.items)==null?void 0:m.length)??0,rigs:((w=(h=a.data)==null?void 0:h.items)==null?void 0:w.length)??0}),{agents:[...new Set(o.map(N=>N.recipient))].sort(),rigs:(((E=a.data)==null?void 0:E.items)??[]).map(N=>N.name??"").filter(Boolean),sessions:o,beads:(((b=r.data)==null?void 0:b.items)??[]).map(N=>({id:N.id??"",title:N.title??""})),mail:(((C=i.data)==null?void 0:C.items)??[]).map(N=>({id:N.id??"",subject:N.subject??""})),fetchedAt:Date.now()}}function Ia(){ot.clear(),Ae.clear()}let Re=null,Oe=null;function Ba(){var e,t,n,a,r,i,o,l,d,p;(e=c("action-modal-close-btn"))==null||e.addEventListener("click",()=>Ne(null)),(t=c("action-modal-cancel-btn"))==null||t.addEventListener("click",()=>Ne(null)),(a=(n=c("action-modal"))==null?void 0:n.querySelector(".modal-backdrop"))==null||a.addEventListener("click",()=>Ne(null)),(r=c("action-form"))==null||r.addEventListener("submit",f=>{var h,w,E;f.preventDefault();const u=((h=c("action-bead-id"))==null?void 0:h.value.trim())??"",y=((w=c("action-target"))==null?void 0:w.value.trim())??"",m=((E=c("action-rig"))==null?void 0:E.value.trim())??"";!u||!y||Ne({beadID:u,rig:m,target:y})}),(i=c("confirm-modal-close-btn"))==null||i.addEventListener("click",()=>$e(!1)),(o=c("confirm-modal-cancel-btn"))==null||o.addEventListener("click",()=>$e(!1)),(l=c("confirm-modal-confirm-btn"))==null||l.addEventListener("click",()=>$e(!0)),(p=(d=c("confirm-modal"))==null?void 0:d.querySelector(".modal-backdrop"))==null||p.addEventListener("click",()=>$e(!1)),document.addEventListener("keydown",f=>{if(f.key==="Escape"){if(we("action-modal")){Ne(null);return}we("confirm-modal")&&$e(!1)}})}async function ht(e){const t=c("action-modal"),n=c("action-form"),a=c("action-modal-title"),r=c("action-modal-submit-btn"),i=c("action-bead-group"),o=c("action-bead-id"),l=c("action-bead-hint"),d=c("action-target"),p=c("action-target-label"),f=c("action-rig-group"),u=c("action-rig"),y=c("action-modal-help"),m=c("action-target-list"),h=c("action-rig-list");if(!t||!n||!a||!r||!i||!o||!l||!d||!p||!f||!u||!y||!m||!h)return P("Action modal unavailable",new Error("missing action modal DOM")),null;const w=await Xe();return _t(m,w.agents),_t(h,w.rigs),a.textContent=e.title,r.textContent=Ua(e.mode),p.textContent=e.mode==="reassign"?"Assignee":"Target agent or pool",y.textContent=Da(e.mode),o.value=e.beadID??"",o.readOnly=!!e.beadID,i.classList.toggle("readonly",o.readOnly),l.textContent=e.beadLabel??"",d.value=e.initialTarget??"",u.value=e.initialRig??"",f.hidden=e.mode==="reassign",u.disabled=e.mode==="reassign",we("action-modal")||F(),t.style.display="flex",window.setTimeout(()=>{if(e.beadID){d.focus();return}o.focus()},0),new Promise(E=>{Re=E})}async function Ma(e){const t=c("confirm-modal"),n=c("confirm-modal-title"),a=c("confirm-modal-body"),r=c("confirm-modal-confirm-btn");return!t||!n||!a||!r?(P("Confirm modal unavailable",new Error("missing confirm modal DOM")),!1):(n.textContent=e.title,a.textContent=e.body,r.textContent=e.confirmLabel,we("confirm-modal")||F(),t.style.display="flex",new Promise(i=>{Oe=i}))}function _t(e,t){k(e),t.forEach(n=>{e.append(s("option",{value:n}))})}function Ua(e){switch(e){case"assign":return"Assign";case"reassign":return"Reassign";default:return"Sling"}}function Da(e){switch(e){case"assign":return"Launch a bead directly to a target, with an optional rig override.";case"reassign":return"Pick a new assignee from the active city sessions or type one manually.";default:return"Dispatch this bead to a target, with an optional rig constraint."}}function Ne(e){const t=c("action-modal"),n=c("action-form");if(!t||!n)return;const a=we("action-modal");t.style.display="none",n.reset(),c("action-rig").disabled=!1,c("action-bead-id").readOnly=!1,a&&j(),Re==null||Re(e),Re=null}function $e(e){const t=c("confirm-modal");if(!t)return;const n=we("confirm-modal");t.style.display="none",n&&j(),Oe==null||Oe(e),Oe=null}function we(e){var t;return((t=c(e))==null?void 0:t.style.display)==="flex"}let Fe=[],ct="ready",Se="all",Ye="";async function de(){var o,l,d,p;const e=S(),t=c("issues-list");if(!t)return;if(!e){za();return}const[n,a,r]=await Promise.all([g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:e},query:{status:"open",limit:500}}}),g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:e},query:{status:"in_progress",limit:500}}}),Xe(!0)]);if(n.error&&a.error||!((o=n.data)!=null&&o.items)&&!((l=a.data)!=null&&l.items)){k(t),t.append(s("div",{class:"panel-error"},["Could not load beads."]));return}Fe=[...((d=n.data)==null?void 0:d.items)??[],...((p=a.data)==null?void 0:p.items)??[]].filter(f=>!Wa(f)).sort((f,u)=>{const y=ee(f.priority),m=ee(u.priority);return y!==m?y-m:(u.created_at??"").localeCompare(f.created_at??"")}),c("issues-count").textContent=String(Fe.length);const i=c("rig-filter-tabs");i&&(k(i),i.append(lt("all",Se==="all")),r.rigs.forEach(f=>i.append(lt(f,Se===f)))),vt()}function za(){const e=c("issues-list"),t=c("rig-filter-tabs"),n=c("issue-detail");if(!e||!t||!n)return;me();const a=n.style.display==="block";n.style.display="none",e.style.display="block",k(e),e.append(s("div",{class:"empty-state"},[s("p",{},["Select a city to view beads"])])),k(t),Se="all",Ye="",Fe=[],t.append(lt("all",!0)),c("issues-count").textContent="0",a&&j()}function vt(){const e=c("issues-list");if(!e)return;k(e);const t=Fe.filter(a=>{const r=a.assignee?"progress":"ready",i=ct==="all"||ct===r,o=Se==="all"||nt(a)===Se;return i&&o});if(t.length===0){e.append(s("div",{class:"empty-state"},[s("p",{},["No beads"])]));return}const n=s("tbody");t.forEach(a=>{const r=s("tr",{class:`issue-row priority-${ee(a.priority)}`,"data-issue-id":a.id??"","data-status":a.assignee?"progress":"ready","data-rig":nt(a)},[s("td",{},[s("span",{class:`badge ${Xt(a.priority)}`},[`P${ee(a.priority)}`])]),s("td",{},[s("span",{class:"issue-id"},[a.id??""])]),s("td",{class:"issue-title"},[Qe(a.title??a.id??"",80)]),s("td",{class:"issue-rig"},[nt(a)]),s("td",{class:"issue-status"},[a.assignee?s("span",{class:"badge badge-blue",title:a.assignee},[a.assignee]):s("span",{class:"badge badge-green"},["Ready"])]),s("td",{class:"issue-age"},[D(a.created_at)]),s("td",{},[ts(a.id??"")])]);r.addEventListener("click",i=>{i.target.closest(".sling-btn")||a.id&&ue(a.id)}),n.append(r)}),e.append(s("table",{id:"work-table"},[s("thead",{},[s("tr",{},[s("th",{},["Pri"]),s("th",{},["ID"]),s("th",{},["Title"]),s("th",{},["Rig"]),s("th",{},["Status"]),s("th",{},["Age"]),s("th",{},["Actions"])])]),n]))}function lt(e,t){const n=s("button",{class:`rig-btn${t?" active":""}`,"data-rig":e},[e==="all"?"All":e]);return n.addEventListener("click",()=>{Se=e,document.querySelectorAll(".rig-btn").forEach(a=>a.classList.remove("active")),n.classList.add("active"),vt()}),n}function nt(e){var t;return((t=e.id)==null?void 0:t.split("-")[0])??"city"}function Wa(e){return(e.issue_type??"").toLowerCase()==="convoy"?!0:(e.labels??[]).some(t=>t.startsWith("gc:queue")||t.startsWith("gc:message"))}function Ga(){var e,t,n,a,r,i,o;document.querySelectorAll(".tab-btn").forEach(l=>{l.addEventListener("click",d=>{const p=d.currentTarget;ct=p.dataset.tab??"ready",document.querySelectorAll(".tab-btn").forEach(f=>f.classList.remove("active")),p.classList.add("active"),vt()})}),(e=c("new-issue-btn"))==null||e.addEventListener("click",()=>dn()),(t=c("issue-modal-close-btn"))==null||t.addEventListener("click",()=>me()),(n=c("issue-modal-cancel-btn"))==null||n.addEventListener("click",()=>me()),(r=(a=c("issue-modal"))==null?void 0:a.querySelector(".modal-backdrop"))==null||r.addEventListener("click",()=>me()),(i=c("issue-form"))==null||i.addEventListener("submit",l=>{l.preventDefault(),Fa()}),(o=c("issue-back-btn"))==null||o.addEventListener("click",()=>Qa()),document.addEventListener("keydown",l=>{var d;l.key==="Escape"&&((d=c("issue-modal"))==null?void 0:d.style.display)==="block"&&me()})}function dn(){var t,n,a;if(!S()){v("info","No city selected","Select a city to create a bead");return}const e=c("issue-modal");e&&(e.style.display!=="block"&&F(),e.style.display="block",(n=(t=c("issues-panel"))==null?void 0:t.scrollIntoView)==null||n.call(t,{behavior:"smooth",block:"center"}),(a=c("issue-title"))==null||a.focus())}function me(){var n;const e=c("issue-modal");if(!e)return;const t=e.style.display==="block";e.style.display="none",(n=c("issue-form"))==null||n.reset(),t&&j()}async function Fa(){var r,i,o;const e=((r=c("issue-title"))==null?void 0:r.value.trim())??"",t=((i=c("issue-description"))==null?void 0:i.value.trim())??"",n=Number(((o=c("issue-priority"))==null?void 0:o.value)??"2");if(!e)return;const a=await ns({title:e,description:t,priority:n});if(!a.ok){v("error","Create failed",a.error??"Could not create issue");return}v("success","Issue created",e),me(),await de()}async function ue(e){var l,d,p;const t=S();if(!t)return;Ye=e,((l=c("issue-detail"))==null?void 0:l.style.display)!=="block"&&F(),c("issues-list").style.display="none",c("issue-detail").style.display="block";const[n,a,r]=await Promise.all([g.GET("/v0/city/{cityName}/bead/{id}",{params:{path:{cityName:t,id:e}}}),g.GET("/v0/city/{cityName}/bead/{id}/deps",{params:{path:{cityName:t,id:e}}}),Xe()]);if(n.error||!n.data){v("error","Issue failed",((d=n.error)==null?void 0:d.detail)??"Could not load bead");return}const i=n.data;c("issue-detail-id").textContent=i.id??e,c("issue-detail-title-text").textContent=i.title??e,c("issue-detail-description").textContent=i.description||"(no description)";const o=c("issue-detail-priority");o.className=`badge ${Xt(i.priority)}`,o.textContent=`P${ee(i.priority)}`,c("issue-detail-status").textContent=i.status??"open",c("issue-detail-status").className=`issue-status ${i.status??"open"}`,c("issue-detail-type").textContent=i.issue_type?`Type: ${i.issue_type}`:"",c("issue-detail-owner").textContent=i.assignee?`Owner: ${i.assignee}`:"Owner: unassigned",c("issue-detail-created").textContent=i.created_at?`Created: ${D(i.created_at)}`:"",Ja(i,r.agents),Ha(((p=a.data)==null?void 0:p.children)??[])}function Ha(e){const t=c("issue-detail-deps"),n=c("issue-detail-depends-on"),a=c("issue-detail-blocks-section"),r=c("issue-detail-blocks");if(!(!t||!n||!a||!r)){if(k(n),k(r),e.length===0){t.style.display="none",a.style.display="none";return}t.style.display="block",e.forEach(i=>{const o=s("span",{class:"issue-dep-item","data-issue-id":i.id??""},[`→ ${i.id??""}`]);o.addEventListener("click",()=>{i.id&&ue(i.id)}),n.append(o)}),a.style.display="none"}}function Ja(e,t){const n=c("issue-detail-actions");if(!n||!e.id)return;k(n);const a=s("div",{class:"issue-actions-bar"}),r=e.status==="closed"?at("↺ Reopen","reopen",()=>void Ya(e.id)):at("✓ Close","close",()=>void Xa(e.id));a.append(r),e.status!=="closed"&&a.append(at("🚚 Sling","sling",()=>void un(e.id)));const i=s("div",{class:"issue-action-group"},[s("label",{class:"issue-action-label"},["Priority"]),Va(e.id,e.priority)]),o=s("div",{class:"issue-action-group"},[s("label",{class:"issue-action-label"},["Assign"]),Ka(e.id,e.assignee,t)]);n.append(a,i,o)}function at(e,t,n){const a=s("button",{class:`issue-action-btn ${t}`,type:"button"},[e]);return a.addEventListener("click",n),a}function Va(e,t){const n=s("select",{class:"issue-action-select",id:"issue-action-priority"});return[1,2,3,4].forEach(a=>{const r=s("option",{value:a,selected:ee(t)===a},[`P${a}`]);n.append(r)}),n.addEventListener("change",()=>{Za(e,Number(n.value))}),n}function Ka(e,t,n){const a=s("select",{class:"issue-action-select",id:"issue-action-assignee"});return a.append(s("option",{value:""},["Unassigned"])),n.forEach(r=>{a.append(s("option",{value:r,selected:t===r},[r]))}),a.addEventListener("change",()=>{es(e,a.value)}),a}function Qa(){const e=c("issue-detail"),t=(e==null?void 0:e.style.display)==="block";e.style.display="none",c("issues-list").style.display="block",Ye="",t&&j()}async function Xa(e){const t=S();if(!t)return;const n=await g.POST("/v0/city/{cityName}/bead/{id}/close",{params:{path:{cityName:t,id:e},header:T}});if(n.error){v("error","Close failed",n.error.detail??"Could not close issue");return}v("success","Closed",e),await de(),await ue(e)}async function Ya(e){const t=S();if(!t)return;const n=await g.POST("/v0/city/{cityName}/bead/{id}/reopen",{params:{path:{cityName:t,id:e},header:T}});if(n.error){v("error","Reopen failed",n.error.detail??"Could not reopen issue");return}v("success","Reopened",e),await de(),await ue(e)}async function Za(e,t){const n=S();if(!n)return;const a=await g.POST("/v0/city/{cityName}/bead/{id}/update",{params:{path:{cityName:n,id:e},header:T},body:{priority:t}});if(a.error){v("error","Priority failed",a.error.detail??"Could not update priority");return}v("success","Priority updated",`${e} → P${t}`),await de(),await ue(e)}async function es(e,t){const n=S();if(!n)return;const a=await g.POST("/v0/city/{cityName}/bead/{id}/assign",{params:{path:{cityName:n,id:e},header:T},body:{assignee:t}});if(a.error){v("error","Assign failed",a.error.detail??"Could not update assignee");return}v("success","Assignment updated",t||"Unassigned"),await de(),await ue(e)}async function un(e){const t=S();if(!t)return;const n=await ht({beadID:e,beadLabel:e,mode:"sling",title:"Sling Bead"});if(!n)return;const a=await g.POST("/v0/city/{cityName}/sling",{params:{path:{cityName:t},header:T},body:{bead:e,target:n.target,rig:n.rig||void 0}});if(a.error){v("error","Sling failed",a.error.detail??"Could not sling issue");return}v("success","Work assigned",`${e} → ${n.target}`),await de(),Ye===e&&await ue(e)}function ts(e){const t=s("button",{class:"sling-btn",type:"button","data-bead-id":e},["Sling"]);return t.addEventListener("click",n=>{n.stopPropagation(),un(e)}),t}async function ns(e){const t=S();if(!t)return{ok:!1,error:"no city selected"};const{error:n}=await g.POST("/v0/city/{cityName}/beads",{params:{path:{cityName:t},header:T},body:{title:e.title,description:e.description,rig:e.rig,priority:e.priority,assignee:e.assignee}});return n?{ok:!1,error:n.detail??n.title??"create failed"}:{ok:!0}}let U="inbox",qe=[],L=null;async function Ue(){const e=S(),t=c("mail-loading"),n=c("mail-threads"),a=c("mail-empty"),r=c("mail-all");if(!t||!n||!a||!r)return;if(!e){as();return}wt("No mail in inbox"),t.style.display="block",n.style.display="none",a.style.display="none";const{data:i,error:o}=await g.GET("/v0/city/{cityName}/mail",{params:{path:{cityName:e},query:{status:"all",limit:200}}});if(t.style.display="none",o||!(i!=null&&i.items)){k(n),n.append(s("div",{class:"panel-error"},["Could not load mail."])),n.style.display="block";return}qe=[...i.items].sort((l,d)=>(d.created_at??"").localeCompare(l.created_at??"")),c("mail-count").textContent=String(qe.length),ss(qe),rs(qe),cs()}function as(){const e=c("mail-loading"),t=c("mail-threads"),n=c("mail-empty"),a=c("mail-all");if(!e||!t||!n||!a)return;ie()?(z(U),j()):z(U),L=null,qe=[],c("mail-count").textContent="0",e.style.display="none",k(t),k(a),t.style.display="none",wt("Select a city to view mail"),n.style.display=U==="inbox"?"block":"none",a.append(s("div",{class:"empty-state"},[s("p",{},["Select a city to view mail traffic"])]))}function wt(e){var t,n;(n=(t=c("mail-empty"))==null?void 0:t.querySelector("p"))==null||n.replaceChildren(document.createTextNode(e))}function ss(e){const t=c("mail-threads"),n=c("mail-empty");if(!t||!n)return;const a=ms(e);if(k(t),a.length===0){t.style.display="none",wt("No mail in inbox"),n.style.display="block";return}n.style.display="none",a.forEach(r=>{const i=r.messages[r.messages.length-1],o=(i.body??"").trim().slice(0,60),l=s("div",{class:`mail-thread${r.unreadCount>0?" mail-thread-unread":""}`},[s("div",{class:"mail-thread-header"},[s("div",{class:"mail-thread-left"},[s("span",{class:"mail-from"},[B(i.from)])]),s("div",{class:"mail-thread-center"},[s("span",{class:"mail-subject"},[r.subject||"(no subject)"]),o?s("span",{class:"mail-thread-preview"},[` — ${o}`]):null]),s("div",{class:"mail-thread-right"},[s("span",{class:"mail-time"},[gt(i.created_at)]),r.unreadCount>0?s("span",{class:"badge badge-unread"},[`${r.unreadCount} unread`]):null])])]);l.addEventListener("click",()=>{is(r.id)}),t.append(l)}),t.style.display=U==="inbox"?"block":"none"}function rs(e){const t=c("mail-all");if(!t)return;if(k(t),e.length===0){t.append(s("div",{class:"empty-state"},[s("p",{},["No mail traffic"])]));return}const n=s("tbody");e.forEach(a=>{const r=s("tr",{class:`mail-row${a.read?"":" mail-unread"}`},[s("td",{class:"mail-from"},[B(a.from)]),s("td",{class:"mail-to"},[B(a.to)]),s("td",{},[s("span",{class:"mail-subject"},[a.subject??"(no subject)"])]),s("td",{class:"mail-time"},[D(a.created_at)])]);r.addEventListener("click",()=>{a.id&&os(a.id)}),n.append(r)}),t.append(s("table",{class:"mail-all-table"},[s("thead",{},[s("tr",{},[s("th",{},["From"]),s("th",{},["To"]),s("th",{},["Subject"]),s("th",{},["Time"])])]),n])),t.style.display=U==="all"?"block":"none"}async function is(e){var i,o;const t=S();if(!t)return;const n=await g.GET("/v0/city/{cityName}/mail/thread/{id}",{params:{path:{cityName:t,id:e}}});if(n.error||!((i=n.data)!=null&&i.items)||n.data.items.length===0){v("error","Thread failed",((o=n.error)==null?void 0:o.detail)??"Could not load mail thread");return}const a=n.data.items,r=a[a.length-1]??a[0];L=r,fn(r,a)}async function os(e){var a;const t=S();if(!t)return;const n=await g.GET("/v0/city/{cityName}/mail/{id}",{params:{path:{cityName:t,id:e}}});if(n.error||!n.data){v("error","Message failed",((a=n.error)==null?void 0:a.detail)??"Could not load message");return}L=n.data,await g.POST("/v0/city/{cityName}/mail/{id}/read",{params:{path:{cityName:t,id:e},header:T}}),L.read=!0,fn(L,[L]),Ue()}function fn(e,t){const n=ie();c("mail-detail-subject").textContent=e.subject??"(no subject)",c("mail-detail-from").textContent=B(e.from),c("mail-detail-time").textContent=D(e.created_at);const a=c("mail-detail-body");a&&(k(a),t.forEach((r,i)=>{i>0&&a.append(s("hr")),a.append(s("div",{class:"mail-thread-msg-header"},[s("span",{class:"mail-from"},[B(r.from)]),s("span",{class:"mail-time"},[D(r.created_at)])]),s("div",{class:"mail-thread-msg-subject"},[r.subject??"(no subject)"]),s("pre",{},[r.body??""]))})),pn(),z("detail"),yn("mail-detail"),n||F()}function z(e){const t=c("mail-list"),n=c("mail-all"),a=c("mail-detail"),r=c("mail-compose");!t||!n||!a||!r||(t.style.display=e==="inbox"?"block":"none",n.style.display=e==="all"?"block":"none",a.style.display=e==="detail"?"block":"none",r.style.display=e==="compose"?"block":"none")}function cs(){var e,t;((e=c("mail-compose"))==null?void 0:e.style.display)==="block"||((t=c("mail-detail"))==null?void 0:t.style.display)==="block"||z(U)}function ls(){var e,t,n,a,r,i,o,l;document.querySelectorAll(".mail-tab").forEach(d=>{d.addEventListener("click",p=>{const f=p.currentTarget;U=f.dataset.tab??"inbox",document.querySelectorAll(".mail-tab").forEach(u=>u.classList.remove("active")),f.classList.add("active"),z(U)})}),(e=c("mail-back-btn"))==null||e.addEventListener("click",()=>{const d=ie();z(U),L=null,d&&j()}),(t=c("compose-mail-btn"))==null||t.addEventListener("click",()=>{dt()}),(n=c("compose-back-btn"))==null||n.addEventListener("click",()=>{const d=!!L,p=ie();z(d?"detail":U),p&&!d&&j()}),(a=c("compose-cancel-btn"))==null||a.addEventListener("click",()=>{const d=ie();z(U),d&&j()}),(r=c("mail-reply-btn"))==null||r.addEventListener("click",()=>{L!=null&&L.id&&dt(L)}),(i=c("mail-send-btn"))==null||i.addEventListener("click",()=>{ds()}),(o=c("mail-archive-btn"))==null||o.addEventListener("click",()=>{L!=null&&L.id&&us(L.id)}),(l=c("mail-toggle-unread-btn"))==null||l.addEventListener("click",()=>{L!=null&&L.id&&fs(L)})}async function dt(e){if(!S()){v("info","No city selected","Select a city to compose mail"),Ve("mail","Compose blocked without city",{replyTo:(e==null?void 0:e.id)??null});return}const t=c("compose-to");if(!t)return;const n=ie();k(t),t.append(s("option",{value:""},["Select recipient…"]));try{const a=await Xe();a.sessions.forEach(r=>{t.append(s("option",{value:r.recipient},[r.label]))}),J("mail","Compose options loaded",{city:S(),recipients:a.sessions.length,replyTo:(e==null?void 0:e.id)??null})}catch(a){oe("mail","Compose options failed",{city:S(),error:a}),P("Mail options failed",a,"Could not load recipients")}c("compose-subject").value=e?ps(e.subject??""):"",c("compose-body").value="",c("compose-reply-to").value=(e==null?void 0:e.id)??"",c("mail-compose-title").textContent=e?"Reply":"New Message",e!=null&&e.from&&(ys(t,e.from),t.value=e.from),z("compose"),yn("compose-subject"),J("mail","Compose form opened",{city:S(),replyTo:(e==null?void 0:e.id)??null,selectedRecipient:t.value||null}),n||F()}async function ds(){var l,d,p,f;const e=S();if(!e)return;const t=((l=c("compose-to"))==null?void 0:l.value)??"",n=((d=c("compose-subject"))==null?void 0:d.value.trim())??"",a=((p=c("compose-body"))==null?void 0:p.value)??"",r=((f=c("compose-reply-to"))==null?void 0:f.value)??"";if(!t||!n){v("error","Missing fields","Recipient and subject are required"),Ve("mail","Send blocked by missing fields",{bodyLength:a.length,city:e,subject:n,to:t});return}J("mail","Send requested",{bodyLength:a.length,city:e,replyTo:r||null,subject:n,to:t});const i=r?await g.POST("/v0/city/{cityName}/mail/{id}/reply",{params:{path:{cityName:e,id:r},header:T},body:{body:a,subject:n}}):await g.POST("/v0/city/{cityName}/mail",{params:{path:{cityName:e},header:T},body:{to:t,subject:n,body:a,from:"dashboard"}});if(i.error){oe("mail","Send failed",{bodyLength:a.length,city:e,error:i.error,replyTo:r||null,subject:n,to:t}),v("error","Send failed",i.error.detail??"Could not send message");return}J("mail","Send succeeded",{bodyLength:a.length,city:e,replyTo:r||null,subject:n,to:t}),v("success","Message sent",n);const o=ie();z("inbox"),L=null,o&&j(),await Ue()}async function us(e){var r;const t=S();if(!t)return;const n=await g.POST("/v0/city/{cityName}/mail/{id}/archive",{params:{path:{cityName:t,id:e},header:T}});if(n.error){v("error","Archive failed",n.error.detail??"Could not archive message");return}v("success","Archived",e);const a=((r=c("mail-detail"))==null?void 0:r.style.display)==="block";z(U),L=null,a&&j(),await Ue()}async function fs(e){const t=S();if(!t||!e.id)return;const n=e.read?"/v0/city/{cityName}/mail/{id}/mark-unread":"/v0/city/{cityName}/mail/{id}/read",a=await g.POST(n,{params:{path:{cityName:t,id:e.id},header:T}});if(a.error){v("error","Update failed",a.error.detail??"Could not update message");return}e.read=!e.read,L={...e},pn(),v("success","Updated",e.subject??e.id),await Ue()}function pn(){const e=c("mail-toggle-unread-btn");e&&(e.textContent=L!=null&&L.read?"Mark unread":"Mark read")}function ie(){var e,t;return((e=c("mail-detail"))==null?void 0:e.style.display)==="block"||((t=c("mail-compose"))==null?void 0:t.style.display)==="block"}function ps(e){return e?e.toLowerCase().startsWith("re:")?e:`Re: ${e}`:"Re:"}function ys(e,t){!t||[...e.options].some(n=>n.value===t)||e.append(s("option",{value:t},[t]))}function yn(e){var t,n;(n=(t=c("mail-panel"))==null?void 0:t.scrollIntoView)==null||n.call(t,{behavior:"smooth",block:"center"}),window.setTimeout(()=>{var a;(a=c(e))==null||a.focus()},0)}function ms(e){const t=new Map;e.forEach(i=>{i.id&&t.set(i.id,i)});function n(i){let o=i;const l=new Set;for(;o.reply_to&&o.id&&!l.has(o.id);){l.add(o.id);const d=t.get(o.reply_to);if(!d)break;o=d}return o.thread_id??o.id??Math.random().toString(36)}const a=new Map;e.forEach(i=>{const o=n(i),l=a.get(o)??{id:o,messages:[],subject:i.subject??"",unreadCount:0};l.messages.push(i),i.read||(l.unreadCount+=1),!l.subject&&i.subject&&(l.subject=i.subject),a.set(o,l)});const r=[...a.values()];return r.forEach(i=>{i.messages.sort((o,l)=>(o.created_at??"").localeCompare(l.created_at??""))}),r.sort((i,o)=>{var p,f;const l=((p=i.messages[i.messages.length-1])==null?void 0:p.created_at)??"";return(((f=o.messages[o.messages.length-1])==null?void 0:f.created_at)??"").localeCompare(l)}),r}let be="";async function St(){var o;const e=S(),t=c("convoy-list");if(!t)return;if(!e){gs();return}const n=await g.GET("/v0/city/{cityName}/convoys",{params:{path:{cityName:e},query:{limit:200}}});if(n.error||!((o=n.data)!=null&&o.items)){k(t),t.append(s("div",{class:"panel-error"},["Could not load convoys."]));return}const r=(await Promise.all(n.data.items.map(async l=>bs(e,l.id??"")))).filter(l=>l!==null);if(c("convoy-count").textContent=String(r.length),k(t),r.length===0){t.append(s("div",{class:"empty-state"},[s("p",{},["No active convoys"])]));return}const i=s("tbody");r.forEach(l=>{const d=s("tr",{class:"convoy-row","data-convoy-id":l.id},[s("td",{},[s("span",{class:`badge ${ce(mn(l))}`},[hs(l)])]),s("td",{},[s("span",{class:"convoy-id"},[l.id]),l.title?s("div",{class:"convoy-title"},[l.title]):null,l.assignees.length?s("div",{class:"convoy-assignees"},l.assignees.map(p=>s("span",{class:"assignee-chip"},[p]))):null]),s("td",{class:"convoy-progress-cell"},[s("div",{class:"convoy-progress-header"},[s("span",{class:"convoy-progress-fraction"},[`${l.closed}/${l.total}`]),l.total>0?s("span",{class:"convoy-progress-pct"},[`${l.progressPct}%`]):null]),l.total>0?s("div",{class:"progress-bar"},[s("div",{class:"progress-fill",style:`width: ${l.progressPct}%;`})]):null]),s("td",{class:"convoy-work-cell"},[s("div",{class:"convoy-work-breakdown"},[l.ready>0?s("span",{class:"work-chip work-ready"},[`${l.ready} ready`]):null,l.inProgress>0?s("span",{class:"work-chip work-inprogress"},[`${l.inProgress} active`]):null,l.closed===l.total&&l.total>0?s("span",{class:"work-chip work-done"},["all done"]):null])]),s("td",{class:`activity-${l.lastActivity.colorClass}`},[s("span",{class:"activity-dot"}),` ${l.lastActivity.display}`])]);d.addEventListener("click",()=>{bn(l.id)}),i.append(d)}),t.append(s("table",{},[s("thead",{},[s("tr",{},[s("th",{},["Status"]),s("th",{},["Convoy"]),s("th",{},["Progress"]),s("th",{},["Work"]),s("th",{},["Activity"])])]),i]))}function gs(){const e=c("convoy-list"),t=c("convoy-detail"),n=c("convoy-create-form");if(!e||!t||!n)return;const a=t.style.display==="block"||n.style.display==="block";be="",c("convoy-count").textContent="0",t.style.display="none",n.style.display="none",c("convoy-add-issue-form").style.display="none",e.style.display="block",k(e),e.append(s("div",{class:"empty-state"},[s("p",{},["Select a city to view convoys"])])),a&&j()}async function bs(e,t){var f,u,y,m;if(!t)return null;const n=await g.GET("/v0/city/{cityName}/convoy/{id}",{params:{path:{cityName:e,id:t}}});if(n.error||!n.data)return null;const a=n.data.children??[],r=new Set;let i=0,o=0,l="";a.forEach(h=>{(h.status??"").toLowerCase()!=="closed"&&(h.assignee?(o+=1,r.add(h.assignee)):i+=1),l=[l,h.created_at??""].sort().slice(-1)[0]??l});const d=((f=n.data.progress)==null?void 0:f.total)??a.length,p=((u=n.data.progress)==null?void 0:u.closed)??a.filter(h=>h.status==="closed").length;return{id:t,title:((y=n.data.convoy)==null?void 0:y.title)??t,status:(m=n.data.convoy)==null?void 0:m.status,progressPct:d>0?Math.round(p/d*100):0,total:d,closed:p,ready:i,inProgress:o,assignees:[...r].sort(),lastActivity:_e(l)}}function mn(e){return e.total>0&&e.closed===e.total?"done":e.inProgress>0?"active":e.ready>0?"waiting":e.status??"open"}function hs(e){switch(mn(e)){case"done":return"✓ Done";case"active":return"Active";case"waiting":return"Waiting";default:return e.status??"Open"}}function vs(){var e,t,n,a,r,i,o,l;(e=c("new-convoy-btn"))==null||e.addEventListener("click",()=>{gn()}),(t=c("convoy-back-btn"))==null||t.addEventListener("click",()=>ws()),(n=c("convoy-create-back-btn"))==null||n.addEventListener("click",()=>ut()),(a=c("convoy-create-cancel-btn"))==null||a.addEventListener("click",()=>ut()),(r=c("convoy-create-submit-btn"))==null||r.addEventListener("click",()=>{Ss()}),(i=c("convoy-add-issue-btn"))==null||i.addEventListener("click",()=>{c("convoy-add-issue-form").style.display="flex"}),(o=c("convoy-add-issue-cancel"))==null||o.addEventListener("click",()=>{c("convoy-add-issue-form").style.display="none"}),(l=c("convoy-add-issue-submit"))==null||l.addEventListener("click",()=>{Es()})}function gn(){var n;if(!S()){v("info","No city selected","Select a city to create a convoy");return}const e=c("convoy-create-form"),t=(e==null?void 0:e.style.display)==="block";be="",c("convoy-list").style.display="none",c("convoy-detail").style.display="none",e.style.display="block",c("convoy-create-name").value="",c("convoy-create-issues").value="",t||F(),hn("convoy-create-name"),(n=c("convoy-create-name"))==null||n.focus()}async function bn(e){var l,d,p,f,u,y,m,h;const t=S();if(!t)return;be=e,((l=c("convoy-detail"))==null?void 0:l.style.display)!=="block"&&F(),c("convoy-list").style.display="none",c("convoy-create-form").style.display="none",c("convoy-detail").style.display="block",hn("convoy-detail"),c("convoy-detail-id").textContent=e,c("convoy-detail-title").textContent=`Convoy: ${e}`,c("convoy-issues-loading").style.display="block",c("convoy-issues-table").style.display="none",c("convoy-issues-empty").style.display="none",c("convoy-add-issue-form").style.display="none";const n=await g.GET("/v0/city/{cityName}/convoy/{id}",{params:{path:{cityName:t,id:e}}});if(c("convoy-issues-loading").style.display="none",n.error||!n.data){c("convoy-issues-empty").style.display="block",c("convoy-issues-empty").querySelector("p").textContent=((d=n.error)==null?void 0:d.detail)??"Failed to load convoy";return}const a=((p=n.data.progress)==null?void 0:p.total)??((f=n.data.children)==null?void 0:f.length)??0,r=((u=n.data.progress)==null?void 0:u.closed)??((y=n.data.children)==null?void 0:y.filter(w=>w.status==="closed").length)??0;c("convoy-detail-status").className=`badge ${ce(((m=n.data.convoy)==null?void 0:m.status)??"open")}`,c("convoy-detail-status").textContent=((h=n.data.convoy)==null?void 0:h.status)??"open",c("convoy-detail-progress").textContent=`${r}/${a}`;const i=c("convoy-issues-tbody");if(!i)return;k(i);const o=n.data.children??[];if(o.length===0){c("convoy-issues-empty").style.display="block";return}o.forEach(w=>{const E=w.assignee?w.assignee:w.status==="closed"?"done":"ready";i.append(s("tr",{},[s("td",{class:"convoy-issue-status"},[s("span",{class:`badge ${ce(w.status)}`},[w.status??"unknown"])]),s("td",{},[s("span",{class:"issue-id"},[w.id??""])]),s("td",{class:"issue-title"},[w.title??w.id??""]),s("td",{},[w.assignee?s("span",{class:"badge badge-blue"},[w.assignee]):s("span",{class:"badge badge-muted"},["Unassigned"])]),s("td",{},[E])]))}),c("convoy-issues-table").style.display="table"}function ws(){const e=c("convoy-detail"),t=(e==null?void 0:e.style.display)==="block";e.style.display="none",c("convoy-list").style.display="block",t&&j()}function ut(){const e=c("convoy-create-form"),t=(e==null?void 0:e.style.display)==="block";e.style.display="none",c("convoy-list").style.display="block",t&&j()}async function Ss(){var r,i;const e=S();if(!e)return;const t=((r=c("convoy-create-name"))==null?void 0:r.value.trim())??"",n=(((i=c("convoy-create-issues"))==null?void 0:i.value)??"").split(/\s+/).map(o=>o.trim()).filter(Boolean);if(!t){v("error","Missing name","Convoy name is required");return}const a=await g.POST("/v0/city/{cityName}/convoys",{params:{path:{cityName:e},header:T},body:{title:t,items:n}});if(a.error){v("error","Create failed",a.error.detail??"Could not create convoy");return}v("success","Convoy created",t),ut(),await St()}async function Es(){const e=S();if(!e||!be)return;const t=c("convoy-add-issue-input"),n=(t==null?void 0:t.value.trim())??"";if(!n)return;const a=await g.POST("/v0/city/{cityName}/convoy/{id}/add",{params:{path:{cityName:e,id:be},header:T},body:{items:[n]}});if(a.error){v("error","Add failed",a.error.detail??"Could not add issue");return}t&&(t.value=""),c("convoy-add-issue-form").style.display="none",v("success","Issue added",n),await bn(be),await St()}function hn(e){var t,n;(n=(t=c("convoy-panel"))==null?void 0:t.scrollIntoView)==null||n.call(t,{behavior:"smooth",block:"center"}),window.setTimeout(()=>{var a;(a=c(e))==null||a.focus()},0)}const Cs=150,W=[];let Y=null,Ie="all",Be="all",Me="all";async function ks(e){W.splice(0,W.length,...wn(e)),Z()}async function Ns(){var a;const e=S(),n=(((a=(e?await g.GET("/v0/city/{cityName}/events",{params:{path:{cityName:e},query:{since:"1h",limit:100}}}):await g.GET("/v0/events",{params:{query:{since:"1h"}}})).data)==null?void 0:a.items)??[]).map(r=>Rs(r)).filter(r=>r!==null);await ks(n)}function $s(e,t){const n=S();Y==null||Y.close();const a=t?{onStatus:t}:void 0;Y=(n?i=>Ea(n,i,a):i=>Sa(i,a))(i=>{const o=En(i);e==null||e(i,o);const l=As(i);if(l){if(W.some(d=>d.id===l.id)){he("activity","Duplicate stream event ignored",{id:l.id,type:l.type});return}W.splice(0,W.length,...wn([l,...W])),Z()}})}function Ls(){Y==null||Y.close(),Y=null}function Z(){Ts();const e=c("activity-feed");if(!e)return;k(e);const t=W.filter(a=>!(Ie!=="all"&&a.category!==Ie||Be!=="all"&&a.rig!==Be||Me!=="all"&&a.actor!==Me));if(c("activity-count").textContent=String(W.length),t.length===0){e.append(s("div",{class:"empty-state"},[s("p",{},["No recent activity"])]));return}const n=s("div",{class:"tl-timeline",id:"activity-timeline"});t.forEach(a=>{n.append(s("div",{class:`tl-entry ${Ps(a.category)}`,"data-category":a.category,"data-rig":a.rig,"data-agent":a.actor??"","data-type":a.type,"data-ts":a.ts},[s("div",{class:"tl-rail"},[s("span",{class:"tl-time"},[gt(a.ts)]),s("span",{class:"tl-node"})]),s("div",{class:"tl-content"},[s("div",{class:"tl-header"},[s("span",{class:"tl-icon"},[ra(a.type)]),s("span",{class:"tl-summary"},[ia(a.type,a.actor,a.subject,a.message)])]),s("div",{class:"tl-meta"},[a.actor?s("span",{class:"tl-badge tl-badge-agent"},[B(a.actor)]):null,a.rig?s("span",{class:"tl-badge tl-badge-rig"},[a.rig]):null,s("span",{class:"tl-badge tl-badge-type"},[a.type])])])]))}),e.append(n)}function xs(){var e,t;document.addEventListener("click",n=>{var r;const a=(r=n.target)==null?void 0:r.closest(".tl-filter-btn");a&&(Ie=a.dataset.value??"all",document.querySelectorAll(".tl-filter-btn").forEach(i=>i.classList.remove("active")),a.classList.add("active"),Z())}),(e=c("tl-rig-filter"))==null||e.addEventListener("change",n=>{Be=n.currentTarget.value,Z()}),(t=c("tl-agent-filter"))==null||t.addEventListener("change",n=>{Me=n.currentTarget.value,Z()})}function Ts(){const e=c("activity-filters");if(!e||(k(e),W.length===0))return;const t=[...new Set(W.map(i=>i.rig).filter(Boolean))].sort(),n=[...new Set(W.map(i=>i.actor).filter(Boolean))].sort(),a=s("select",{class:"tl-filter-select",id:"tl-rig-filter"});a.append(s("option",{value:"all"},["All rigs"])),t.forEach(i=>a.append(s("option",{value:i,selected:i===Be},[i]))),a.addEventListener("change",()=>{Be=a.value,Z()});const r=s("select",{class:"tl-filter-select",id:"tl-agent-filter"});r.append(s("option",{value:"all"},["All agents"])),n.forEach(i=>r.append(s("option",{value:i,selected:i===Me},[B(i)]))),r.addEventListener("change",()=>{Me=r.value,Z()}),e.append(s("div",{class:"tl-filters"},[s("div",{class:"tl-filter-group"},[s("label",{},["Category:"]),Le("all","All"),Le("agent","Agent"),Le("work","Work"),Le("comms","Comms"),Le("system","System")]),s("div",{class:"tl-filter-group"},[s("label",{},["Rig:"]),a]),s("div",{class:"tl-filter-group"},[s("label",{},["Agent:"]),r])]))}function Le(e,t){const n=s("button",{class:`tl-filter-btn${Ie===e?" active":""}`,"data-filter":"category","data-value":e,type:"button"},[t]);return n.addEventListener("click",()=>{Ie=e,Z()}),n}function As(e){return e.event==="heartbeat"?null:vn(e.data,e.id)}function Rs(e){return vn(e)}function vn(e,t){if(!e.type)return null;const n=Sn(e)??S(),a=typeof e.seq=="number"?e.seq:0;return{id:_s(e,t),type:e.type,category:sa(e.type),actor:e.actor||void 0,subject:e.subject||void 0,message:e.message||void 0,ts:e.ts,scope:n,seq:a,rig:aa(e.actor)||"city"in e&&e.city||""}}function wn(e){const t=new Map;return e.forEach(n=>{t.has(n.id)||t.set(n.id,n)}),[...t.values()].sort(Os).slice(0,Cs)}function Os(e,t){const n=qs(e.ts,t.ts);if(n!==0)return n;const a=e.scope.localeCompare(t.scope);if(a!==0)return a;const r=t.seq-e.seq;if(r!==0)return r;const i=e.type.localeCompare(t.type);if(i!==0)return i;const o=(e.actor??"").localeCompare(t.actor??"");return o!==0?o:(e.subject??"").localeCompare(t.subject??"")}function qs(e,t){const n=Number.isNaN(Date.parse(e))?0:Date.parse(e);return(Number.isNaN(Date.parse(t))?0:Date.parse(t))-n}function Sn(e){if("city"in e&&typeof e.city=="string"&&e.city!=="")return e.city}function _s(e,t){const n=Sn(e)??S();if(typeof e.seq=="number"&&e.seq>0)return`${n}:${e.seq}`;const a=[e.type,e.ts,e.actor??"",e.subject??"",e.message??"",t??""].join(":");return`${n}:${a}`}function En(e){return ka(e)}function Ps(e){switch(e){case"agent":return"activity-agent";case"work":return"activity-work";case"comms":return"activity-comms";default:return"activity-system"}}async function V(){var o,l,d,p,f,u;const e=S();if(!e){js();return}const[t,n,a,r,i]=await Promise.all([g.GET("/v0/city/{cityName}/services",{params:{path:{cityName:e}}}),g.GET("/v0/city/{cityName}/rigs",{params:{path:{cityName:e},query:{git:!0}}}),g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:e},query:{label:"gc:escalation",status:"open",limit:200}}}),g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:e},query:{status:"in_progress",limit:500}}}),g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:e},query:{label:"gc:queue",limit:200}}})]);Bs(((o=t.data)==null?void 0:o.items)??null,(l=t.error)==null?void 0:l.detail),Ms(((d=n.data)==null?void 0:d.items)??null),Us(((p=a.data)==null?void 0:p.items)??null),Ds(((f=r.data)==null?void 0:f.items)??null),zs(((u=i.data)==null?void 0:u.items)??null)}function js(){xe("services-body","services-count","Select a city to view services"),xe("rigs-body","rigs-count","Select a city to view rigs"),xe("escalations-body","escalations-count","Select a city to view escalations"),xe("assigned-body","assigned-count","Select a city to view assigned work"),xe("queues-body","queues-count","Select a city to view queues"),c("clear-assigned-btn").style.display="none"}function Is(){var e,t;(e=c("open-assign-btn"))==null||e.addEventListener("click",()=>{Cn()}),(t=c("clear-assigned-btn"))==null||t.addEventListener("click",()=>{Fs()})}function Bs(e,t){const n=c("services-body"),a=c("services-count");if(!n||!a)return;if(k(n),t){a.textContent="n/a",n.append(s("div",{class:"empty-state"},[s("p",{},[t])]));return}const r=e??[];if(a.textContent=String(r.length),r.length===0){n.append(s("div",{class:"empty-state"},[s("p",{},["No workspace services"])]));return}const i=s("tbody");r.forEach(o=>{const l=s("button",{class:"esc-btn",type:"button"},["Restart"]);l.addEventListener("click",()=>{Js(o.service_name)}),i.append(s("tr",{},[s("td",{},[s("strong",{},[o.service_name])]),s("td",{},[o.kind??"—"]),s("td",{},[s("span",{class:`badge ${ce(o.state??o.publication_state)}`},[o.state??o.publication_state??"unknown"])]),s("td",{},[o.local_state]),s("td",{},[l])]))}),n.append(s("table",{},[s("thead",{},[s("tr",{},[s("th",{},["Name"]),s("th",{},["Kind"]),s("th",{},["Service"]),s("th",{},["Local"]),s("th",{},["Actions"])])]),i]))}function Ms(e){const t=c("rigs-body"),n=c("rigs-count");if(!t||!n)return;k(t);const a=e??[];if(n.textContent=String(a.length),a.length===0){t.append(s("div",{class:"empty-state"},[s("p",{},["No rigs configured"])]));return}const r=s("tbody");a.forEach(i=>{var d;const o=s("button",{class:"esc-btn",type:"button"},[i.suspended?"Resume":"Suspend"]);o.addEventListener("click",()=>{Pt(i.name,i.suspended?"resume":"suspend")});const l=s("button",{class:"esc-btn",type:"button"},["Restart"]);l.addEventListener("click",()=>{Pt(i.name,"restart")}),r.append(s("tr",{},[s("td",{},[s("span",{class:"rig-name"},[i.name])]),s("td",{},[String(i.agent_count-i.running_count)]),s("td",{},[String(i.running_count)]),s("td",{},[(d=i.git)!=null&&d.branch?`${i.git.branch}${i.git.clean?"":"*"}`:"—"]),s("td",{},[D(i.last_activity)]),s("td",{},[o," ",l])]))}),t.append(s("table",{},[s("thead",{},[s("tr",{},[s("th",{},["Name"]),s("th",{},["Idle"]),s("th",{},["Running"]),s("th",{},["Git"]),s("th",{},["Activity"]),s("th",{},["Actions"])])]),r]))}function Us(e){const t=c("escalations-body"),n=c("escalations-count");if(!t||!n)return;k(t);const a=(e??[]).sort((i,o)=>(i.created_at??"").localeCompare(o.created_at??""));if(n.textContent=String(a.length),a.length===0){t.append(s("div",{class:"empty-state"},[s("p",{},["No escalations"])]));return}const r=s("tbody");a.forEach(i=>{const o=Ws(i.labels??[]),l=(i.labels??[]).includes("acked"),d=s("button",{class:"esc-btn esc-ack-btn",type:"button"},["👍 Ack"]);d.addEventListener("click",()=>{Vs(i)});const p=s("button",{class:"esc-btn esc-resolve-btn",type:"button"},["✓ Resolve"]);p.addEventListener("click",()=>{i.id&&Ks(i.id)});const f=s("button",{class:"esc-btn esc-reassign-btn",type:"button"},["↻ Reassign"]);f.addEventListener("click",()=>{i.id&&Qs(i.id)}),r.append(s("tr",{class:"escalation-row","data-escalation-id":i.id??""},[s("td",{},[s("span",{class:`badge ${Gs(o)}`},[o.toUpperCase()])]),s("td",{},[i.title??i.id??"",l?s("span",{class:"badge badge-cyan",style:"margin-left: 4px;"},["ACK"]):null]),s("td",{},[B(i.assignee)]),s("td",{},[D(i.created_at)]),s("td",{class:"escalation-actions"},[l?null:d,p,f])]))}),t.append(s("table",{},[s("thead",{},[s("tr",{},[s("th",{},["Severity"]),s("th",{},["Issue"]),s("th",{},["From"]),s("th",{},["Age"]),s("th",{},["Actions"])])]),r]))}function Ds(e){const t=c("assigned-body"),n=c("assigned-count"),a=c("clear-assigned-btn");if(!t||!n||!a)return;k(t);const r=(e??[]).filter(o=>o.assignee);if(n.textContent=String(r.length),a.style.display=r.length>0?"inline-flex":"none",r.length===0){t.append(s("div",{class:"empty-state"},[s("p",{},["No assigned work"])]));return}const i=s("tbody");r.forEach(o=>{const l=s("button",{class:"unassign-btn",type:"button"},["Unassign"]);l.addEventListener("click",()=>{o.id&&Hs(o.id)}),i.append(s("tr",{},[s("td",{},[s("span",{class:"assigned-id"},[o.id??""])]),s("td",{class:"assigned-title"},[Qe(o.title??"",80)]),s("td",{class:"assigned-agent"},[B(o.assignee)]),s("td",{class:"assigned-age"},[D(o.created_at)]),s("td",{},[l])]))}),t.append(s("table",{},[s("thead",{},[s("tr",{},[s("th",{},["Bead"]),s("th",{},["Title"]),s("th",{},["Agent"]),s("th",{},["Since"]),s("th",{},[""])])]),i]))}function zs(e){const t=c("queues-body"),n=c("queues-count");if(!t||!n)return;k(t);const a=e??[];if(n.textContent=String(a.length),a.length===0){t.append(s("div",{class:"empty-state"},[s("p",{},["No queues"])]));return}const r=s("tbody");a.forEach(i=>{r.append(s("tr",{},[s("td",{},[i.title??i.id??"queue"]),s("td",{},[i.id??"—"]),s("td",{},[s("span",{class:`badge ${ce(i.status)}`},[i.status??"open"])]),s("td",{},[B(i.assignee)]),s("td",{},[D(i.created_at)])]))}),t.append(s("table",{},[s("thead",{},[s("tr",{},[s("th",{},["Queue"]),s("th",{},["Bead"]),s("th",{},["Status"]),s("th",{},["Assignee"]),s("th",{},["Created"])])]),r]))}function xe(e,t,n){const a=c(e),r=c(t);!a||!r||(k(a),r.textContent="0",a.append(s("div",{class:"empty-state"},[s("p",{},[n])])))}function Ws(e){for(const t of e)if(t.startsWith("severity:"))return t.slice(9);return"medium"}function Gs(e){switch(e){case"critical":return"badge-red";case"high":return"badge-orange";case"low":return"badge-muted";default:return"badge-yellow"}}async function Cn(e=""){const t=S();if(!t)return;const n=await ht({beadID:e||void 0,beadLabel:e||void 0,mode:"assign",title:"Assign Work"});if(!n)return;const a=await g.POST("/v0/city/{cityName}/sling",{params:{path:{cityName:t},header:T},body:{bead:n.beadID,target:n.target,rig:n.rig||void 0}});if(a.error){v("error","Assign failed",a.error.detail??"Could not assign bead");return}v("success","Assigned",`${n.beadID} → ${n.target}`),await V()}async function Fs(){var r;const e=S();if(!e)return;const n=(((r=(await g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:e},query:{status:"in_progress",limit:500}}})).data)==null?void 0:r.items)??[]).filter(i=>i.assignee);if(n.length===0){v("info","Nothing to clear","No assigned work");return}await Ma({body:`Unassign ${n.length} active ${n.length===1?"bead":"beads"}?`,confirmLabel:"Unassign All",title:"Clear Assignments"})&&(await Promise.all(n.map(i=>g.POST("/v0/city/{cityName}/bead/{id}/assign",{params:{path:{cityName:e,id:i.id??""},header:T},body:{assignee:""}}))),v("success","Cleared",`${n.length} assignments removed`),await V())}async function Hs(e){const t=S();if(!t)return;const n=await g.POST("/v0/city/{cityName}/bead/{id}/assign",{params:{path:{cityName:t,id:e},header:T},body:{assignee:""}});if(n.error){v("error","Unassign failed",n.error.detail??"Could not unassign bead");return}v("success","Unassigned",e),await V()}async function Js(e){const t=S();if(!t)return;const n=await g.POST("/v0/city/{cityName}/service/{name}/restart",{params:{path:{cityName:t,name:e},header:T}});if(n.error){v("error","Service failed",n.error.detail??"Could not restart service");return}v("success","Service restarted",e),await V()}async function Pt(e,t){const n=S();if(!n)return;const a=await g.POST("/v0/city/{cityName}/rig/{name}/{action}",{params:{path:{cityName:n,name:e,action:t},header:T}});if(a.error){v("error","Rig action failed",a.error.detail??`Could not ${t} ${e}`);return}v("success","Rig updated",`${e}: ${t}`),await V()}async function Vs(e){const t=S();if(!t||!e.id)return;const n=Array.from(new Set([...e.labels??[],"acked"])),a=await g.POST("/v0/city/{cityName}/bead/{id}/update",{params:{path:{cityName:t,id:e.id},header:T},body:{labels:n}});if(a.error){v("error","Ack failed",a.error.detail??"Could not acknowledge escalation");return}v("success","Acknowledged",e.id),await V()}async function Ks(e){const t=S();if(!t)return;const n=await g.POST("/v0/city/{cityName}/bead/{id}/close",{params:{path:{cityName:t,id:e},header:T}});if(n.error){v("error","Resolve failed",n.error.detail??"Could not resolve escalation");return}v("success","Resolved",e),await V()}async function Qs(e){const t=S();if(!t)return;const n=await ht({beadID:e,beadLabel:e,mode:"reassign",title:"Reassign Escalation"});if(!n)return;const a=await g.POST("/v0/city/{cityName}/bead/{id}/assign",{params:{path:{cityName:t,id:e},header:T},body:{assignee:n.target}});if(a.error){v("error","Reassign failed",a.error.detail??"Could not reassign escalation");return}v("success","Reassigned",`${e} → ${n.target||"unassigned"}`),await V()}function Xs(e){const t=c("command-palette-overlay"),n=c("command-palette-input"),a=c("command-palette-results"),r=c("open-palette-btn");if(!t||!n||!a||!r)return;const i=t,o=n,l=a,d=r;let p=[],f=[],u=0;function y(){const b=S(),C=async(N,I)=>{const M=await I;Rt(N,JSON.stringify(M,null,2))};return[{name:"refresh",desc:"Refresh all panels",category:"Dashboard",run:()=>e.refreshAll()},{name:"supervisor health",desc:"Show supervisor health JSON",category:"Supervisor",run:()=>C("health",g.GET("/health"))},{name:"city list",desc:"Show managed cities JSON",category:"Supervisor",run:()=>C("cities",g.GET("/v0/cities"))},{name:"global events",desc:"Show recent supervisor events JSON",category:"Supervisor",run:()=>C("events",g.GET("/v0/events",{params:{query:{since:"1h"}}}))},...b?[{name:"new issue",desc:"Open the issue creation modal",category:"Work",run:()=>dn()},{name:"compose mail",desc:"Open the compose mail form",category:"Mail",run:()=>dt()},{name:"new convoy",desc:"Open the convoy creation form",category:"Convoys",run:()=>gn()},{name:"assign work",desc:"Open the assignment modal",category:"Assigned",run:()=>Cn()},{name:"status",desc:"Show current city status JSON",category:"Status",run:()=>C("status",g.GET("/v0/city/{cityName}/status",{params:{path:{cityName:b}}}))},{name:"agent list",desc:"Show current sessions JSON",category:"Status",run:()=>C("sessions",g.GET("/v0/city/{cityName}/sessions",{params:{path:{cityName:b},query:{state:"active",peek:!0}}}))},{name:"convoy list",desc:"Show current convoys JSON",category:"Convoys",run:()=>C("convoys",g.GET("/v0/city/{cityName}/convoys",{params:{path:{cityName:b},query:{limit:200}}}))},{name:"mail inbox",desc:"Show current mail JSON",category:"Mail",run:()=>C("mail",g.GET("/v0/city/{cityName}/mail",{params:{path:{cityName:b},query:{status:"all",limit:200}}}))},{name:"rig list",desc:"Show rig JSON",category:"Rigs",run:()=>C("rigs",g.GET("/v0/city/{cityName}/rigs",{params:{path:{cityName:b},query:{git:!0}}}))},{name:"list",desc:"Show open and in-progress beads JSON",category:"Beads",run:async()=>{var M,$;const[N,I]=await Promise.all([g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:b},query:{status:"open",limit:500}}}),g.GET("/v0/city/{cityName}/beads",{params:{path:{cityName:b},query:{status:"in_progress",limit:500}}})]);Rt("beads",JSON.stringify({open:((M=N.data)==null?void 0:M.items)??[],in_progress:(($=I.data)==null?void 0:$.items)??[]},null,2))}}]:[],{name:"close output",desc:"Hide the output panel",category:"Dashboard",run:()=>Zt()}].filter(N=>typeof N.run=="function")}function m(){k(l);const b=o.value.trim().toLowerCase();if(p=y(),f=p.filter(C=>b===""||C.name.includes(b)||C.desc.toLowerCase().includes(b)||C.category.toLowerCase().includes(b)),u>=f.length&&(u=0),f.length===0){l.append(s("div",{class:"command-palette-empty"},["No matching commands"]));return}f.forEach((C,N)=>{const I=s("button",{class:`command-item${N===u?" selected":""}`,type:"button"},[s("span",{class:"command-name"},[`gt ${C.name}`]),s("span",{class:"command-desc"},[C.desc]),s("span",{class:"command-category"},[C.category])]);I.addEventListener("click",()=>{E(N)}),l.append(I)})}function h(){i.classList.add("open"),o.value="",u=0,m(),o.focus()}function w(){i.classList.remove("open")}async function E(b){const C=f[b];w(),C&&(J("palette","Execute command",{category:C.category,city:S(),command:C.name}),await C.run())}d.addEventListener("click",()=>h()),i.addEventListener("click",b=>{b.target===i&&w()}),o.addEventListener("input",()=>m()),o.addEventListener("keydown",b=>{if(b.key==="ArrowDown"){u=Math.min(u+1,Math.max(f.length-1,0)),m(),b.preventDefault();return}if(b.key==="ArrowUp"){u=Math.max(u-1,0),m(),b.preventDefault();return}if(b.key==="Enter"){E(u),b.preventDefault();return}b.key==="Escape"&&w()}),document.addEventListener("keydown",b=>{(b.metaKey||b.ctrlKey)&&b.key.toLowerCase()==="k"&&(b.preventDefault(),i.classList.contains("open")?w():h())})}function Ys(){const e=c("supervisor-overview-panel"),t=c("supervisor-overview-body"),n=c("supervisor-city-count");if(!e||!t||!n)return;const a=S()==="";if(e.hidden=!a,!a)return;const r=Ht().sort((o,l)=>o.name.localeCompare(l.name));if(n.textContent=String(r.length),k(t),r.length===0){t.append(s("div",{class:"empty-state"},[s("p",{},["No managed cities available"])]));return}const i=s("tbody");r.forEach(o=>{const l=o.phasesCompleted.length>0?o.phasesCompleted.join(", "):"—",d=s("a",{class:"supervisor-city-link",href:`?city=${encodeURIComponent(o.name)}`},["Open"]);i.append(s("tr",{},[s("td",{},[s("strong",{},[o.name])]),s("td",{},[s("span",{class:`badge ${o.error?"badge-red":o.running?"badge-green":"badge-muted"}`},[o.error?"Error":o.running?"Running":"Stopped"])]),s("td",{},[o.status??"—"]),s("td",{class:"supervisor-city-phases"},[l]),s("td",{class:"supervisor-city-error"},[o.error??"—"]),s("td",{class:"supervisor-city-actions"},[d])]))}),t.append(s("table",{class:"supervisor-city-table"},[s("thead",{},[s("tr",{},[s("th",{},["City"]),s("th",{},["State"]),s("th",{},["Status"]),s("th",{},["Phases"]),s("th",{},["Error"]),s("th",{},[""])])]),i]))}const Zs=["convoy-panel","crew-panel","rigged-panel","mail-panel","escalations-panel","services-panel","rigs-panel","pooled-panel","queues-panel","beads-panel","assigned-panel","agent-log-drawer"];async function er(){bt()||await Ee()}async function tr(){bt()||await Ee().catch(e=>P("Catch-up refresh failed",e))}async function nr(){mt(),await Ee(!0)}function Et(){const e=Jt();if(e.kind==="not-running"||e.kind==="unknown"){Ls(),st("connecting");return}st("connecting"),$s(t=>{const n=En(t);!n||n==="heartbeat"||(ea(n),!bt()&&Ee().catch(a=>P("Refresh failed",a)))},st)}function st(e){const t=Ct("connection-status");if(!t)return;const n={connecting:"Connecting…",live:"Live",reconnecting:"Reconnecting…"};t.replaceChildren(document.createTextNode(n[e])),t.classList.remove("connection-live","connection-connecting","connection-reconnecting"),t.classList.add(`connection-${e}`)}function ar(){ba(),Ba(),Ra(),Ga(),ls(),vs(),xs(),Is(),Xs({refreshAll:er})}async function sr(){Vn(),J("dashboard","Boot start",{city:S(),href:window.location.href}),ar(),ir(),ga(()=>{tr()}),await nr(),Et(),J("dashboard","Boot complete",{city:S(),href:window.location.href})}function Ct(e){return document.getElementById(e)}sr().catch(e=>P("Dashboard boot failed",e));function rr(){const e=S()!=="";cr(e),De("new-convoy-btn",e,"Select a city to create a convoy"),De("new-issue-btn",e,"Select a city to create a bead"),De("compose-mail-btn",e,"Select a city to compose mail"),De("open-assign-btn",e,"Select a city to assign work")}function De(e,t,n){const a=Ct(e);a&&(a.dataset.defaultTitle===void 0&&(a.dataset.defaultTitle=a.title||""),a.disabled=!t,a.title=t?a.dataset.defaultTitle:n)}function ir(){document.addEventListener("click",e=>{var a;const t=(a=e.target)==null?void 0:a.closest("a.city-tab");if(!t)return;const n=t.href;!n||n===window.location.href||(e.preventDefault(),or(n))}),window.addEventListener("popstate",()=>{J("dashboard","Popstate navigation",{href:window.location.href}),on(),yt(),mt(),Ee().catch(e=>P("Refresh failed",e)),Et()})}async function or(e){J("dashboard","Navigate city scope",{nextURL:e}),on(),window.history.pushState({},"",e),yt(),mt(),await Ee(),Et()}function cr(e){Zs.forEach(t=>{const n=Ct(t);if(!n)return;const a=!e&&n.classList.contains("expanded");if(n.hidden=!e,a){n.classList.remove("expanded");const r=n.querySelector(".expand-btn");r&&(r.textContent="Expand"),j()}})}async function Ee(e=!1){yt(),rr();const t=Yn(e);if(t.size===0)return;t.has("options")&&Ia(),t.has("cities")&&await ta().catch(l=>P("City tabs failed",l));const n=[],r=Jt().kind==="running";ae(n,t,"status",()=>oa()),ae(n,t,"activity",()=>Ns()),r&&(ae(n,t,"crew",()=>Na()),ae(n,t,"issues",()=>de()),ae(n,t,"mail",()=>Ue()),ae(n,t,"convoys",()=>St()),ae(n,t,"admin",()=>V()));const o=(await Promise.allSettled(n)).find(l=>l.status==="rejected");o&&P("Panel refresh failed",o.reason),(t.has("supervisor")||t.has("cities"))&&Ys()}function ae(e,t,n,a){t.has(n)&&e.push(a())} diff --git a/cmd/gc/dashboard/web/src/api.ts b/cmd/gc/dashboard/web/src/api.ts index 269f4fcf85..87d6eb57c0 100644 --- a/cmd/gc/dashboard/web/src/api.ts +++ b/cmd/gc/dashboard/web/src/api.ts @@ -58,11 +58,11 @@ export type CityInfoRecord = DashboardSchema["CityInfo"]; // The supervisor's CSRF middleware requires `X-GC-Request: true` on // every mutation. Attaching it as a default request editor means // callers never have to remember the header. +export const mutationHeaders = { "X-GC-Request": "true" } as const; + export const api = createClient({ baseUrl: supervisorBaseURL(), - headers: { - "X-GC-Request": "true", - }, + headers: mutationHeaders, }); // Configure the hey-api SSE client with the same base URL + CSRF @@ -71,9 +71,7 @@ export const api = createClient({ // client instance. sseClient.setConfig({ baseUrl: supervisorBaseURL(), - headers: { - "X-GC-Request": "true", - }, + headers: mutationHeaders, }); api.use({ @@ -134,13 +132,15 @@ export function cityAPI(cityName: string) { beadAssign(id: string, assignee: string) { return api.POST("/v0/city/{cityName}/bead/{id}/assign", { - params: { path: { cityName, id } }, + params: { path: { cityName, id }, header: mutationHeaders }, body: { assignee }, }); }, beadClose(id: string) { - return api.POST("/v0/city/{cityName}/bead/{id}/close", { params: { path: { cityName, id } } }); + return api.POST("/v0/city/{cityName}/bead/{id}/close", { + params: { path: { cityName, id }, header: mutationHeaders }, + }); }, beadDeps(id: string) { @@ -148,12 +148,14 @@ export function cityAPI(cityName: string) { }, beadReopen(id: string) { - return api.POST("/v0/city/{cityName}/bead/{id}/reopen", { params: { path: { cityName, id } } }); + return api.POST("/v0/city/{cityName}/bead/{id}/reopen", { + params: { path: { cityName, id }, header: mutationHeaders }, + }); }, beadUpdate(id: string, body: { labels?: string[]; priority?: number }) { return api.POST("/v0/city/{cityName}/bead/{id}/update", { - params: { path: { cityName, id } }, + params: { path: { cityName, id }, header: mutationHeaders }, body, }); }, @@ -172,7 +174,7 @@ export function cityAPI(cityName: string) { title: string; }) { return api.POST("/v0/city/{cityName}/beads", { - params: { path: { cityName } }, + params: { path: { cityName }, header: mutationHeaders }, body, }); }, @@ -183,7 +185,7 @@ export function cityAPI(cityName: string) { convoyAdd(id: string, items: string[]) { return api.POST("/v0/city/{cityName}/convoy/{id}/add", { - params: { path: { cityName, id } }, + params: { path: { cityName, id }, header: mutationHeaders }, body: { items }, }); }, @@ -196,7 +198,7 @@ export function cityAPI(cityName: string) { createConvoy(title: string, items: string[]) { return api.POST("/v0/city/{cityName}/convoys", { - params: { path: { cityName } }, + params: { path: { cityName }, header: mutationHeaders }, body: { title, items }, }); }, @@ -224,7 +226,7 @@ export function cityAPI(cityName: string) { rigAction(name: string, action: string) { return api.POST("/v0/city/{cityName}/rig/{name}/{action}", { - params: { path: { cityName, name, action } }, + params: { path: { cityName, name, action }, header: mutationHeaders }, }); }, @@ -234,7 +236,7 @@ export function cityAPI(cityName: string) { serviceRestart(name: string) { return api.POST("/v0/city/{cityName}/service/{name}/restart", { - params: { path: { cityName, name } }, + params: { path: { cityName, name }, header: mutationHeaders }, }); }, @@ -252,7 +254,7 @@ export function cityAPI(cityName: string) { sling(body: { bead: string; rig?: string; target: string }) { return api.POST("/v0/city/{cityName}/sling", { - params: { path: { cityName } }, + params: { path: { cityName }, header: mutationHeaders }, body, }); }, diff --git a/cmd/gc/dashboard/web/src/panels/admin.ts b/cmd/gc/dashboard/web/src/panels/admin.ts index 600716590d..4fb76943c1 100644 --- a/cmd/gc/dashboard/web/src/panels/admin.ts +++ b/cmd/gc/dashboard/web/src/panels/admin.ts @@ -1,5 +1,5 @@ import type { BeadRecord, RigRecord, ServiceStatusRecord } from "../api"; -import { api, cityScope } from "../api"; +import { api, cityScope, mutationHeaders } from "../api"; import { promptActionDialog, promptConfirmDialog } from "../modals"; import { byId, clear, el } from "../util/dom"; import { formatAgentAddress, formatTimestamp, statusBadgeClass, truncate } from "../util/legacy"; @@ -309,7 +309,7 @@ export async function openAssignModal(beadID = ""): Promise { }); if (!selection) return; const res = await api.POST("/v0/city/{cityName}/sling", { - params: { path: { cityName: city } }, + params: { path: { cityName: city }, header: mutationHeaders }, body: { bead: selection.beadID, target: selection.target, rig: selection.rig || undefined }, }); if (res.error) { @@ -339,7 +339,7 @@ async function clearAllAssigned(): Promise { if (!confirmed) return; await Promise.all(items.map((bead) => api.POST("/v0/city/{cityName}/bead/{id}/assign", { - params: { path: { cityName: city, id: bead.id ?? "" } }, + params: { path: { cityName: city, id: bead.id ?? "" }, header: mutationHeaders }, body: { assignee: "" }, }), )); @@ -351,7 +351,7 @@ async function unassignBead(beadID: string): Promise { const city = cityScope(); if (!city) return; const res = await api.POST("/v0/city/{cityName}/bead/{id}/assign", { - params: { path: { cityName: city, id: beadID } }, + params: { path: { cityName: city, id: beadID }, header: mutationHeaders }, body: { assignee: "" }, }); if (res.error) { @@ -366,7 +366,7 @@ async function restartService(service: string): Promise { const city = cityScope(); if (!city) return; const res = await api.POST("/v0/city/{cityName}/service/{name}/restart", { - params: { path: { cityName: city, name: service } }, + params: { path: { cityName: city, name: service }, header: mutationHeaders }, }); if (res.error) { showToast("error", "Service failed", res.error.detail ?? "Could not restart service"); @@ -380,7 +380,7 @@ async function rigAction(rig: string, action: string): Promise { const city = cityScope(); if (!city) return; const res = await api.POST("/v0/city/{cityName}/rig/{name}/{action}", { - params: { path: { cityName: city, name: rig, action } }, + params: { path: { cityName: city, name: rig, action }, header: mutationHeaders }, }); if (res.error) { showToast("error", "Rig action failed", res.error.detail ?? `Could not ${action} ${rig}`); @@ -395,7 +395,7 @@ async function ackEscalation(issue: BeadRecord): Promise { if (!city || !issue.id) return; const labels = Array.from(new Set([...(issue.labels ?? []), "acked"])); const res = await api.POST("/v0/city/{cityName}/bead/{id}/update", { - params: { path: { cityName: city, id: issue.id } }, + params: { path: { cityName: city, id: issue.id }, header: mutationHeaders }, body: { labels }, }); if (res.error) { @@ -410,7 +410,7 @@ async function closeBead(issueID: string): Promise { const city = cityScope(); if (!city) return; const res = await api.POST("/v0/city/{cityName}/bead/{id}/close", { - params: { path: { cityName: city, id: issueID } }, + params: { path: { cityName: city, id: issueID }, header: mutationHeaders }, }); if (res.error) { showToast("error", "Resolve failed", res.error.detail ?? "Could not resolve escalation"); @@ -431,7 +431,7 @@ async function reassignBead(issueID: string): Promise { }); if (!selection) return; const res = await api.POST("/v0/city/{cityName}/bead/{id}/assign", { - params: { path: { cityName: city, id: issueID } }, + params: { path: { cityName: city, id: issueID }, header: mutationHeaders }, body: { assignee: selection.target }, }); if (res.error) { diff --git a/cmd/gc/dashboard/web/src/panels/convoys.ts b/cmd/gc/dashboard/web/src/panels/convoys.ts index 87e158fc88..63478c919f 100644 --- a/cmd/gc/dashboard/web/src/panels/convoys.ts +++ b/cmd/gc/dashboard/web/src/panels/convoys.ts @@ -1,4 +1,4 @@ -import { api, cityScope } from "../api"; +import { api, cityScope, mutationHeaders } from "../api"; import { byId, clear, el } from "../util/dom"; import { calculateActivity, statusBadgeClass } from "../util/legacy"; import { popPause, pushPause, showToast } from "../ui"; @@ -298,7 +298,7 @@ async function createConvoy(): Promise { return; } const res = await api.POST("/v0/city/{cityName}/convoys", { - params: { path: { cityName: city } }, + params: { path: { cityName: city }, header: mutationHeaders }, body: { title, items }, }); if (res.error) { @@ -317,7 +317,7 @@ async function addIssueToConvoy(): Promise { const item = input?.value.trim() ?? ""; if (!item) return; const res = await api.POST("/v0/city/{cityName}/convoy/{id}/add", { - params: { path: { cityName: city, id: currentConvoyID } }, + params: { path: { cityName: city, id: currentConvoyID }, header: mutationHeaders }, body: { items: [item] }, }); if (res.error) { diff --git a/cmd/gc/dashboard/web/src/panels/issues.ts b/cmd/gc/dashboard/web/src/panels/issues.ts index 1c99685311..2997f0ecb2 100644 --- a/cmd/gc/dashboard/web/src/panels/issues.ts +++ b/cmd/gc/dashboard/web/src/panels/issues.ts @@ -1,5 +1,5 @@ import type { BeadRecord } from "../api"; -import { api, cityScope } from "../api"; +import { api, cityScope, mutationHeaders } from "../api"; import { promptActionDialog } from "../modals"; import { byId, clear, el } from "../util/dom"; import { beadPriority, formatTimestamp, priorityBadgeClass, truncate } from "../util/legacy"; @@ -347,7 +347,7 @@ async function closeIssue(issueID: string): Promise { const city = cityScope(); if (!city) return; const res = await api.POST("/v0/city/{cityName}/bead/{id}/close", { - params: { path: { cityName: city, id: issueID } }, + params: { path: { cityName: city, id: issueID }, header: mutationHeaders }, }); if (res.error) { showToast("error", "Close failed", res.error.detail ?? "Could not close issue"); @@ -362,7 +362,7 @@ async function reopenIssue(issueID: string): Promise { const city = cityScope(); if (!city) return; const res = await api.POST("/v0/city/{cityName}/bead/{id}/reopen", { - params: { path: { cityName: city, id: issueID } }, + params: { path: { cityName: city, id: issueID }, header: mutationHeaders }, }); if (res.error) { showToast("error", "Reopen failed", res.error.detail ?? "Could not reopen issue"); @@ -377,7 +377,7 @@ async function updateIssuePriority(issueID: string, priority: number): Promise { const city = cityScope(); if (!city) return; const res = await api.POST("/v0/city/{cityName}/bead/{id}/assign", { - params: { path: { cityName: city, id: issueID } }, + params: { path: { cityName: city, id: issueID }, header: mutationHeaders }, body: { assignee }, }); if (res.error) { @@ -416,7 +416,7 @@ async function slingIssue(issueID: string): Promise { }); if (!selection) return; const res = await api.POST("/v0/city/{cityName}/sling", { - params: { path: { cityName: city } }, + params: { path: { cityName: city }, header: mutationHeaders }, body: { bead: issueID, target: selection.target, rig: selection.rig || undefined }, }); if (res.error) { @@ -449,7 +449,7 @@ export async function createIssue(input: { const city = cityScope(); if (!city) return { ok: false, error: "no city selected" }; const { error } = await api.POST("/v0/city/{cityName}/beads", { - params: { path: { cityName: city } }, + params: { path: { cityName: city }, header: mutationHeaders }, body: { title: input.title, description: input.description, diff --git a/cmd/gc/dashboard/web/src/panels/mail.ts b/cmd/gc/dashboard/web/src/panels/mail.ts index 3b77727463..69c77a3a6e 100644 --- a/cmd/gc/dashboard/web/src/panels/mail.ts +++ b/cmd/gc/dashboard/web/src/panels/mail.ts @@ -1,5 +1,5 @@ import type { MailRecord } from "../api"; -import { api, cityScope } from "../api"; +import { api, cityScope, mutationHeaders } from "../api"; import { logError, logInfo, logWarn } from "../logger"; import { byId, clear, el } from "../util/dom"; import { formatAgentAddress, formatTimestamp } from "../util/legacy"; @@ -188,7 +188,7 @@ async function openMessage(messageID: string): Promise { } currentMessage = res.data; await api.POST("/v0/city/{cityName}/mail/{id}/read", { - params: { path: { cityName: city, id: messageID } }, + params: { path: { cityName: city, id: messageID }, header: mutationHeaders }, }); currentMessage.read = true; showMailDetail(currentMessage, [currentMessage]); @@ -356,11 +356,11 @@ async function sendCurrentMessage(): Promise { const response = replyTo ? await api.POST("/v0/city/{cityName}/mail/{id}/reply", { - params: { path: { cityName: city, id: replyTo } }, + params: { path: { cityName: city, id: replyTo }, header: mutationHeaders }, body: { body, subject }, }) : await api.POST("/v0/city/{cityName}/mail", { - params: { path: { cityName: city } }, + params: { path: { cityName: city }, header: mutationHeaders }, body: { to, subject, body, from: "dashboard" }, }); @@ -396,7 +396,7 @@ async function archiveMessage(id: string): Promise { const city = cityScope(); if (!city) return; const res = await api.POST("/v0/city/{cityName}/mail/{id}/archive", { - params: { path: { cityName: city, id } }, + params: { path: { cityName: city, id }, header: mutationHeaders }, }); if (res.error) { showToast("error", "Archive failed", res.error.detail ?? "Could not archive message"); @@ -417,7 +417,7 @@ async function toggleUnread(message: MailRecord): Promise { ? "/v0/city/{cityName}/mail/{id}/mark-unread" : "/v0/city/{cityName}/mail/{id}/read"; const res = await api.POST(route, { - params: { path: { cityName: city, id: message.id } }, + params: { path: { cityName: city, id: message.id }, header: mutationHeaders }, }); if (res.error) { showToast("error", "Update failed", res.error.detail ?? "Could not update message"); diff --git a/docs/schema/openapi.json b/docs/schema/openapi.json index 0095c093c3..bd3188b4f1 100644 --- a/docs/schema/openapi.json +++ b/docs/schema/openapi.json @@ -827,6 +827,17 @@ "null" ] }, + "metadata": { + "additionalProperties": { + "type": "string" + }, + "description": "Metadata key-value pairs to set at create time.", + "type": "object" + }, + "parent": { + "description": "Parent bead ID.", + "type": "string" + }, "priority": { "description": "Bead priority.", "format": "int64", @@ -941,6 +952,13 @@ "description": "Metadata key-value pairs to set.", "type": "object" }, + "parent": { + "description": "Parent bead ID. Use null or an empty string to clear.", + "type": [ + "string", + "null" + ] + }, "priority": { "description": "Bead priority.", "format": "int64", @@ -1052,22 +1070,6 @@ ], "type": "object" }, - "CityCreatedPayload": { - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "path": { - "type": "string" - } - }, - "required": [ - "name", - "path" - ], - "type": "object" - }, "CityGetResponse": { "additionalProperties": false, "properties": { @@ -1147,7 +1149,7 @@ ], "type": "object" }, - "CityInitFailedPayload": { + "CityLifecyclePayload": { "additionalProperties": false, "properties": { "error": { @@ -1171,8 +1173,7 @@ }, "required": [ "name", - "path", - "error" + "path" ], "type": "object" }, @@ -1186,58 +1187,6 @@ }, "type": "object" }, - "CityReadyPayload": { - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "path": { - "type": "string" - } - }, - "required": [ - "name", - "path" - ], - "type": "object" - }, - "CityUnregisterFailedPayload": { - "additionalProperties": false, - "properties": { - "error": { - "type": "string" - }, - "name": { - "type": "string" - }, - "path": { - "type": "string" - } - }, - "required": [ - "name", - "path", - "error" - ], - "type": "object" - }, - "CityUnregisterRequestedPayload": { - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "path": { - "type": "string" - } - }, - "required": [ - "name", - "path" - ], - "type": "object" - }, "CityUnregisterResponse": { "additionalProperties": false, "properties": { @@ -1261,22 +1210,6 @@ ], "type": "object" }, - "CityUnregisteredPayload": { - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "path": { - "type": "string" - } - }, - "required": [ - "name", - "path" - ], - "type": "object" - }, "ConfigAgentResponse": { "additionalProperties": false, "properties": { @@ -2016,22 +1949,7 @@ "$ref": "#/components/schemas/BoundEventPayload" }, { - "$ref": "#/components/schemas/CityCreatedPayload" - }, - { - "$ref": "#/components/schemas/CityInitFailedPayload" - }, - { - "$ref": "#/components/schemas/CityReadyPayload" - }, - { - "$ref": "#/components/schemas/CityUnregisterFailedPayload" - }, - { - "$ref": "#/components/schemas/CityUnregisterRequestedPayload" - }, - { - "$ref": "#/components/schemas/CityUnregisteredPayload" + "$ref": "#/components/schemas/CityLifecyclePayload" }, { "$ref": "#/components/schemas/GroupCreatedEventPayload" diff --git a/docs/schema/openapi.txt b/docs/schema/openapi.txt index 0095c093c3..bd3188b4f1 100644 --- a/docs/schema/openapi.txt +++ b/docs/schema/openapi.txt @@ -827,6 +827,17 @@ "null" ] }, + "metadata": { + "additionalProperties": { + "type": "string" + }, + "description": "Metadata key-value pairs to set at create time.", + "type": "object" + }, + "parent": { + "description": "Parent bead ID.", + "type": "string" + }, "priority": { "description": "Bead priority.", "format": "int64", @@ -941,6 +952,13 @@ "description": "Metadata key-value pairs to set.", "type": "object" }, + "parent": { + "description": "Parent bead ID. Use null or an empty string to clear.", + "type": [ + "string", + "null" + ] + }, "priority": { "description": "Bead priority.", "format": "int64", @@ -1052,22 +1070,6 @@ ], "type": "object" }, - "CityCreatedPayload": { - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "path": { - "type": "string" - } - }, - "required": [ - "name", - "path" - ], - "type": "object" - }, "CityGetResponse": { "additionalProperties": false, "properties": { @@ -1147,7 +1149,7 @@ ], "type": "object" }, - "CityInitFailedPayload": { + "CityLifecyclePayload": { "additionalProperties": false, "properties": { "error": { @@ -1171,8 +1173,7 @@ }, "required": [ "name", - "path", - "error" + "path" ], "type": "object" }, @@ -1186,58 +1187,6 @@ }, "type": "object" }, - "CityReadyPayload": { - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "path": { - "type": "string" - } - }, - "required": [ - "name", - "path" - ], - "type": "object" - }, - "CityUnregisterFailedPayload": { - "additionalProperties": false, - "properties": { - "error": { - "type": "string" - }, - "name": { - "type": "string" - }, - "path": { - "type": "string" - } - }, - "required": [ - "name", - "path", - "error" - ], - "type": "object" - }, - "CityUnregisterRequestedPayload": { - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "path": { - "type": "string" - } - }, - "required": [ - "name", - "path" - ], - "type": "object" - }, "CityUnregisterResponse": { "additionalProperties": false, "properties": { @@ -1261,22 +1210,6 @@ ], "type": "object" }, - "CityUnregisteredPayload": { - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "path": { - "type": "string" - } - }, - "required": [ - "name", - "path" - ], - "type": "object" - }, "ConfigAgentResponse": { "additionalProperties": false, "properties": { @@ -2016,22 +1949,7 @@ "$ref": "#/components/schemas/BoundEventPayload" }, { - "$ref": "#/components/schemas/CityCreatedPayload" - }, - { - "$ref": "#/components/schemas/CityInitFailedPayload" - }, - { - "$ref": "#/components/schemas/CityReadyPayload" - }, - { - "$ref": "#/components/schemas/CityUnregisterFailedPayload" - }, - { - "$ref": "#/components/schemas/CityUnregisterRequestedPayload" - }, - { - "$ref": "#/components/schemas/CityUnregisteredPayload" + "$ref": "#/components/schemas/CityLifecyclePayload" }, { "$ref": "#/components/schemas/GroupCreatedEventPayload" diff --git a/internal/api/convoy_event_stream_test.go b/internal/api/convoy_event_stream_test.go index 0cbb931fc2..9aca4436fa 100644 --- a/internal/api/convoy_event_stream_test.go +++ b/internal/api/convoy_event_stream_test.go @@ -2,6 +2,7 @@ package api import ( "encoding/json" + "reflect" "testing" "time" @@ -9,6 +10,29 @@ import ( "github.com/gastownhall/gascity/internal/events" ) +func TestCityLifecycleEventsSharePayloadTypeForOneOfValidation(t *testing.T) { + registered := events.RegisteredPayloadTypes() + cityEvents := []string{ + events.CityCreated, + events.CityReady, + events.CityInitFailed, + events.CityUnregisterRequested, + events.CityUnregistered, + events.CityUnregisterFailed, + } + + firstType := reflect.TypeOf(registered[cityEvents[0]]) + if firstType == nil { + t.Fatalf("%s payload type is not registered", cityEvents[0]) + } + for _, eventType := range cityEvents[1:] { + got := reflect.TypeOf(registered[eventType]) + if got != firstType { + t.Fatalf("%s payload type = %v, want shared %v so EventPayload oneOf has a single city lifecycle branch", eventType, got, firstType) + } + } +} + func TestWorkflowEventScope(t *testing.T) { info := workflowStoreInfo{ref: "rig:alpha", scopeKind: "rig", scopeRef: "alpha"} diff --git a/internal/api/event_payloads.go b/internal/api/event_payloads.go index df2aeda5af..fffb9755e3 100644 --- a/internal/api/event_payloads.go +++ b/internal/api/event_payloads.go @@ -28,90 +28,40 @@ type MailEventPayload struct { // IsEventPayload marks MailEventPayload as an events.Payload variant. func (MailEventPayload) IsEventPayload() {} -// CityCreatedPayload is emitted on city.created when the supervisor's -// POST /v0/city handler has scaffolded and registered a new city. -// Consumers subscribed to /v0/events/stream use this event to learn -// about newly-created cities before they are fully initialized. The -// matching city.ready / city.init_failed event follows once the -// supervisor reconciler finishes preparing the city (or gives up). -type CityCreatedPayload struct { - Name string `json:"name"` - Path string `json:"path"` +// CityLifecyclePayload is emitted by city lifecycle events. Keeping all +// same-shaped city lifecycle payloads on one Go type keeps the generated +// EventPayload oneOf unambiguous for validators that only see the payload +// object, not the enclosing event type. +type CityLifecyclePayload struct { + Name string `json:"name"` + Path string `json:"path"` + Error string `json:"error,omitempty"` + PhasesCompleted []string `json:"phases_completed,omitempty"` } -// IsEventPayload marks CityCreatedPayload as an events.Payload variant. -func (CityCreatedPayload) IsEventPayload() {} +// IsEventPayload marks CityLifecyclePayload as an events.Payload variant. +func (CityLifecyclePayload) IsEventPayload() {} -// CityReadyPayload is emitted on city.ready when the supervisor -// reconciler has finished preparing a city (bead store started, -// formulas resolved, agents validated). The city is now in the -// running inventory and ready to accept work. -type CityReadyPayload struct { - Name string `json:"name"` - Path string `json:"path"` -} +// CityCreatedPayload is emitted on city.created when the supervisor's +// POST /v0/city handler has scaffolded and registered a new city. +type CityCreatedPayload = CityLifecyclePayload -// IsEventPayload marks CityReadyPayload as an events.Payload variant. -func (CityReadyPayload) IsEventPayload() {} +// CityReadyPayload is emitted on city.ready when the supervisor +// reconciler has finished preparing a city. +type CityReadyPayload = CityLifecyclePayload // CityInitFailedPayload is emitted on city.init_failed when the -// supervisor reconciler fails to bring up a city. The payload carries -// a human-readable error string sourced from the reconciler step that -// failed (validate rigs, startBeadsLifecycle, etc.) plus the phases -// the reconciler completed before the failure. -type CityInitFailedPayload struct { - Name string `json:"name"` - Path string `json:"path"` - Error string `json:"error"` - PhasesCompleted []string `json:"phases_completed,omitempty"` -} +// supervisor reconciler fails to bring up a city. +type CityInitFailedPayload = CityLifecyclePayload -// IsEventPayload marks CityInitFailedPayload as an events.Payload variant. -func (CityInitFailedPayload) IsEventPayload() {} - -// CityUnregisterRequestedPayload is emitted on -// city.unregister_requested when a client POSTs -// /v0/city/{cityName}/unregister. Subscribers see this event before -// the supervisor reconciler stops the city's controller, then see -// city.unregistered (success) or city.unregister_failed (stop -// failure) once the reconciler completes. -type CityUnregisterRequestedPayload struct { - Name string `json:"name"` - Path string `json:"path"` -} - -// IsEventPayload marks CityUnregisterRequestedPayload as an -// events.Payload variant. -func (CityUnregisterRequestedPayload) IsEventPayload() {} - -// CityUnregisteredPayload is emitted on city.unregistered when the -// supervisor reconciler has removed a city from its running set -// after the city was removed from the registry. The controller is -// stopped; the city directory is untouched on disk. -type CityUnregisteredPayload struct { - Name string `json:"name"` - Path string `json:"path"` -} +// CityUnregisterRequestedPayload is emitted when unregister starts. +type CityUnregisterRequestedPayload = CityLifecyclePayload -// IsEventPayload marks CityUnregisteredPayload as an events.Payload -// variant. -func (CityUnregisteredPayload) IsEventPayload() {} - -// CityUnregisterFailedPayload is emitted on city.unregister_failed -// when the supervisor reconciler cannot stop a city's controller -// after its registry entry was removed. The Error field carries a -// human-readable description of what failed (e.g. "controller did -// not stop within timeout"). Operators can inspect the city's -// controller process and retry. -type CityUnregisterFailedPayload struct { - Name string `json:"name"` - Path string `json:"path"` - Error string `json:"error"` -} +// CityUnregisteredPayload is emitted when unregister completes. +type CityUnregisteredPayload = CityLifecyclePayload -// IsEventPayload marks CityUnregisterFailedPayload as an -// events.Payload variant. -func (CityUnregisterFailedPayload) IsEventPayload() {} +// CityUnregisterFailedPayload is emitted when unregister fails. +type CityUnregisterFailedPayload = CityLifecyclePayload // BeadEventPayload is the shape of every bead.* event payload // (BeadCreated, BeadUpdated, BeadClosed). The payload carries a full diff --git a/internal/api/genclient/client_gen.go b/internal/api/genclient/client_gen.go index 4144e52ec4..dae325d154 100644 --- a/internal/api/genclient/client_gen.go +++ b/internal/api/genclient/client_gen.go @@ -419,6 +419,12 @@ type BeadCreateInputBody struct { // Labels Bead labels. Labels *[]string `json:"labels,omitempty"` + // Metadata Metadata key-value pairs to set at create time. + Metadata *map[string]string `json:"metadata,omitempty"` + + // Parent Parent bead ID. + Parent *string `json:"parent,omitempty"` + // Priority Bead priority. Priority *int64 `json:"priority,omitempty"` @@ -463,6 +469,9 @@ type BeadUpdateBody struct { // Metadata Metadata key-value pairs to set. Metadata *map[string]string `json:"metadata,omitempty"` + // Parent Parent bead ID. Use null or an empty string to clear. + Parent *string `json:"parent,omitempty"` + // Priority Bead priority. Priority *int64 `json:"priority,omitempty"` @@ -516,12 +525,6 @@ type CityCreateResponse struct { Path string `json:"path"` } -// CityCreatedPayload defines model for CityCreatedPayload. -type CityCreatedPayload struct { - Name string `json:"name"` - Path string `json:"path"` -} - // CityGetResponse defines model for CityGetResponse. type CityGetResponse struct { AgentCount int64 `json:"agent_count"` @@ -545,9 +548,9 @@ type CityInfo struct { Status *string `json:"status,omitempty"` } -// CityInitFailedPayload defines model for CityInitFailedPayload. -type CityInitFailedPayload struct { - Error string `json:"error"` +// CityLifecyclePayload defines model for CityLifecyclePayload. +type CityLifecyclePayload struct { + Error *string `json:"error,omitempty"` Name string `json:"name"` Path string `json:"path"` PhasesCompleted *[]string `json:"phases_completed,omitempty"` @@ -559,25 +562,6 @@ type CityPatchInputBody struct { Suspended *bool `json:"suspended,omitempty"` } -// CityReadyPayload defines model for CityReadyPayload. -type CityReadyPayload struct { - Name string `json:"name"` - Path string `json:"path"` -} - -// CityUnregisterFailedPayload defines model for CityUnregisterFailedPayload. -type CityUnregisterFailedPayload struct { - Error string `json:"error"` - Name string `json:"name"` - Path string `json:"path"` -} - -// CityUnregisterRequestedPayload defines model for CityUnregisterRequestedPayload. -type CityUnregisterRequestedPayload struct { - Name string `json:"name"` - Path string `json:"path"` -} - // CityUnregisterResponse defines model for CityUnregisterResponse. type CityUnregisterResponse struct { // Name Resolved registry name. Filter the event stream by this to observe completion. @@ -590,12 +574,6 @@ type CityUnregisterResponse struct { Path string `json:"path"` } -// CityUnregisteredPayload defines model for CityUnregisteredPayload. -type CityUnregisteredPayload struct { - Name string `json:"name"` - Path string `json:"path"` -} - // ConfigAgentResponse defines model for ConfigAgentResponse. type ConfigAgentResponse struct { Dir *string `json:"dir,omitempty"` @@ -3778,152 +3756,22 @@ func (t *EventPayload) MergeBoundEventPayload(v BoundEventPayload) error { return err } -// AsCityCreatedPayload returns the union data inside the EventPayload as a CityCreatedPayload -func (t EventPayload) AsCityCreatedPayload() (CityCreatedPayload, error) { - var body CityCreatedPayload - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromCityCreatedPayload overwrites any union data inside the EventPayload as the provided CityCreatedPayload -func (t *EventPayload) FromCityCreatedPayload(v CityCreatedPayload) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeCityCreatedPayload performs a merge with any union data inside the EventPayload, using the provided CityCreatedPayload -func (t *EventPayload) MergeCityCreatedPayload(v CityCreatedPayload) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -// AsCityInitFailedPayload returns the union data inside the EventPayload as a CityInitFailedPayload -func (t EventPayload) AsCityInitFailedPayload() (CityInitFailedPayload, error) { - var body CityInitFailedPayload - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromCityInitFailedPayload overwrites any union data inside the EventPayload as the provided CityInitFailedPayload -func (t *EventPayload) FromCityInitFailedPayload(v CityInitFailedPayload) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeCityInitFailedPayload performs a merge with any union data inside the EventPayload, using the provided CityInitFailedPayload -func (t *EventPayload) MergeCityInitFailedPayload(v CityInitFailedPayload) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -// AsCityReadyPayload returns the union data inside the EventPayload as a CityReadyPayload -func (t EventPayload) AsCityReadyPayload() (CityReadyPayload, error) { - var body CityReadyPayload - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromCityReadyPayload overwrites any union data inside the EventPayload as the provided CityReadyPayload -func (t *EventPayload) FromCityReadyPayload(v CityReadyPayload) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeCityReadyPayload performs a merge with any union data inside the EventPayload, using the provided CityReadyPayload -func (t *EventPayload) MergeCityReadyPayload(v CityReadyPayload) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -// AsCityUnregisterFailedPayload returns the union data inside the EventPayload as a CityUnregisterFailedPayload -func (t EventPayload) AsCityUnregisterFailedPayload() (CityUnregisterFailedPayload, error) { - var body CityUnregisterFailedPayload - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromCityUnregisterFailedPayload overwrites any union data inside the EventPayload as the provided CityUnregisterFailedPayload -func (t *EventPayload) FromCityUnregisterFailedPayload(v CityUnregisterFailedPayload) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeCityUnregisterFailedPayload performs a merge with any union data inside the EventPayload, using the provided CityUnregisterFailedPayload -func (t *EventPayload) MergeCityUnregisterFailedPayload(v CityUnregisterFailedPayload) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -// AsCityUnregisterRequestedPayload returns the union data inside the EventPayload as a CityUnregisterRequestedPayload -func (t EventPayload) AsCityUnregisterRequestedPayload() (CityUnregisterRequestedPayload, error) { - var body CityUnregisterRequestedPayload - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromCityUnregisterRequestedPayload overwrites any union data inside the EventPayload as the provided CityUnregisterRequestedPayload -func (t *EventPayload) FromCityUnregisterRequestedPayload(v CityUnregisterRequestedPayload) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeCityUnregisterRequestedPayload performs a merge with any union data inside the EventPayload, using the provided CityUnregisterRequestedPayload -func (t *EventPayload) MergeCityUnregisterRequestedPayload(v CityUnregisterRequestedPayload) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -// AsCityUnregisteredPayload returns the union data inside the EventPayload as a CityUnregisteredPayload -func (t EventPayload) AsCityUnregisteredPayload() (CityUnregisteredPayload, error) { - var body CityUnregisteredPayload +// AsCityLifecyclePayload returns the union data inside the EventPayload as a CityLifecyclePayload +func (t EventPayload) AsCityLifecyclePayload() (CityLifecyclePayload, error) { + var body CityLifecyclePayload err := json.Unmarshal(t.union, &body) return body, err } -// FromCityUnregisteredPayload overwrites any union data inside the EventPayload as the provided CityUnregisteredPayload -func (t *EventPayload) FromCityUnregisteredPayload(v CityUnregisteredPayload) error { +// FromCityLifecyclePayload overwrites any union data inside the EventPayload as the provided CityLifecyclePayload +func (t *EventPayload) FromCityLifecyclePayload(v CityLifecyclePayload) error { b, err := json.Marshal(v) t.union = b return err } -// MergeCityUnregisteredPayload performs a merge with any union data inside the EventPayload, using the provided CityUnregisteredPayload -func (t *EventPayload) MergeCityUnregisteredPayload(v CityUnregisteredPayload) error { +// MergeCityLifecyclePayload performs a merge with any union data inside the EventPayload, using the provided CityLifecyclePayload +func (t *EventPayload) MergeCityLifecyclePayload(v CityLifecyclePayload) error { b, err := json.Marshal(v) if err != nil { return err diff --git a/internal/api/handler_beads_test.go b/internal/api/handler_beads_test.go index 02924bb150..444def387b 100644 --- a/internal/api/handler_beads_test.go +++ b/internal/api/handler_beads_test.go @@ -582,6 +582,56 @@ func TestBeadUpdate(t *testing.T) { } } +func TestBeadCreatePersistsMetadataAndParent(t *testing.T) { + state := newFakeState(t) + store := state.stores["myrig"] + parent, err := store.Create(beads.Bead{Title: "Parent"}) + if err != nil { + t.Fatalf("Create(parent): %v", err) + } + h := newTestCityHandler(t, state) + + body := `{ + "rig":"myrig", + "title":"Child", + "type":"feature", + "parent":"` + parent.ID + `", + "metadata":{ + "mc.contract.role":"child", + "mc.contract.run_id":"run-1" + } + }` + req := newPostRequest(cityURL(state, "/beads"), bytes.NewBufferString(body)) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + + if rec.Code != http.StatusCreated { + t.Fatalf("create status = %d, want %d, body: %s", rec.Code, http.StatusCreated, rec.Body.String()) + } + + var created beads.Bead + if err := json.NewDecoder(rec.Body).Decode(&created); err != nil { + t.Fatalf("decode created bead: %v", err) + } + if created.ParentID != parent.ID { + t.Fatalf("response parent = %q, want %q", created.ParentID, parent.ID) + } + if created.Metadata["mc.contract.run_id"] != "run-1" { + t.Fatalf("response metadata = %#v, want mc.contract.run_id=run-1", created.Metadata) + } + + got, err := store.Get(created.ID) + if err != nil { + t.Fatalf("Get(created): %v", err) + } + if got.ParentID != parent.ID { + t.Fatalf("stored parent = %q, want %q", got.ParentID, parent.ID) + } + if got.Metadata["mc.contract.role"] != "child" || got.Metadata["mc.contract.run_id"] != "run-1" { + t.Fatalf("stored metadata = %#v, want MC metadata", got.Metadata) + } +} + func TestBeadUpdateUsesRoutePrefixStore(t *testing.T) { state, alphaStore, betaStore := configureBeadRouteState(t) created, err := betaStore.Create(beads.Bead{Title: "Routed beta bead"}) @@ -614,6 +664,51 @@ func TestBeadUpdateUsesRoutePrefixStore(t *testing.T) { } } +func TestBeadUpdateSetsAndClearsParent(t *testing.T) { + state := newFakeState(t) + store := state.stores["myrig"] + parent, err := store.Create(beads.Bead{Title: "Parent"}) + if err != nil { + t.Fatalf("Create(parent): %v", err) + } + child, err := store.Create(beads.Bead{Title: "Child"}) + if err != nil { + t.Fatalf("Create(child): %v", err) + } + h := newTestCityHandler(t, state) + + body := `{"parent":"` + parent.ID + `"}` + req := newPostRequest(cityURL(state, "/bead/")+child.ID+"/update", bytes.NewBufferString(body)) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("set parent status = %d, want %d, body: %s", rec.Code, http.StatusOK, rec.Body.String()) + } + got, err := store.Get(child.ID) + if err != nil { + t.Fatalf("Get(child): %v", err) + } + if got.ParentID != parent.ID { + t.Fatalf("parent after set = %q, want %q", got.ParentID, parent.ID) + } + + req = newPostRequest(cityURL(state, "/bead/")+child.ID+"/update", bytes.NewBufferString(`{"parent":null}`)) + rec = httptest.NewRecorder() + h.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("clear parent status = %d, want %d, body: %s", rec.Code, http.StatusOK, rec.Body.String()) + } + got, err = store.Get(child.ID) + if err != nil { + t.Fatalf("Get(child after clear): %v", err) + } + if got.ParentID != "" { + t.Fatalf("parent after clear = %q, want empty", got.ParentID) + } +} + func TestBeadDepsUsesRoutePrefixStore(t *testing.T) { state, alphaStore, betaStore := configureBeadRouteState(t) parent, err := betaStore.Create(beads.Bead{Title: "Parent"}) @@ -1169,6 +1264,50 @@ func TestBeadCreateValidation(t *testing.T) { } } +func TestBeadUpdateParentOpenAPISchemaAllowsNull(t *testing.T) { + data, err := os.ReadFile("openapi.json") + if err != nil { + t.Fatalf("read openapi.json: %v", err) + } + var spec map[string]any + if err := json.Unmarshal(data, &spec); err != nil { + t.Fatalf("parse openapi.json: %v", err) + } + components, ok := spec["components"].(map[string]any) + if !ok { + t.Fatal("openapi components missing") + } + schemas, ok := components["schemas"].(map[string]any) + if !ok { + t.Fatal("openapi schemas missing") + } + beadUpdate, ok := schemas["BeadUpdateBody"].(map[string]any) + if !ok { + t.Fatal("BeadUpdateBody schema missing") + } + properties, ok := beadUpdate["properties"].(map[string]any) + if !ok { + t.Fatal("BeadUpdateBody properties missing") + } + parent, ok := properties["parent"].(map[string]any) + if !ok { + t.Fatal("BeadUpdateBody parent property missing") + } + typeValues, ok := parent["type"].([]any) + if !ok { + t.Fatalf("parent type = %#v, want [\"string\", \"null\"]", parent["type"]) + } + seen := map[string]bool{} + for _, value := range typeValues { + if s, ok := value.(string); ok { + seen[s] = true + } + } + if !seen["string"] || !seen["null"] { + t.Fatalf("parent type = %#v, want string and null", parent["type"]) + } +} + func TestPackList(t *testing.T) { state := newFakeState(t) state.cfg.Packs = map[string]config.PackSource{ diff --git a/internal/api/huma_handlers_beads.go b/internal/api/huma_handlers_beads.go index 719d248bc5..3e7f7eb826 100644 --- a/internal/api/huma_handlers_beads.go +++ b/internal/api/huma_handlers_beads.go @@ -310,6 +310,8 @@ func (s *Server) humaHandleBeadCreate(ctx context.Context, input *BeadCreateInpu Assignee: assignee, Description: input.Body.Description, Labels: input.Body.Labels, + ParentID: input.Body.Parent, + Metadata: input.Body.Metadata, }) if err != nil { s.idem.unreserve(idemKey) @@ -421,6 +423,14 @@ func (s *Server) humaHandleBeadUpdate(ctx context.Context, input *BeadUpdateInpu Description: body.Description, Labels: body.Labels, RemoveLabels: body.RemoveLabels, + Metadata: body.Metadata, + } + if body.parentSet { + parent := "" + if body.Parent != nil { + parent = *body.Parent + } + opts.ParentID = &parent } for _, store := range s.beadStoresForID(id) { @@ -447,12 +457,6 @@ func (s *Server) humaHandleBeadUpdate(ctx context.Context, input *BeadUpdateInpu } return nil, huma.Error500InternalServerError(err.Error()) } - // Apply metadata key-value pairs if provided. - if len(body.Metadata) > 0 { - if err := store.SetMetadataBatch(id, body.Metadata); err != nil { - return nil, huma.Error500InternalServerError(err.Error()) - } - } resp := &OKResponse{} resp.Body.Status = "updated" return resp, nil diff --git a/internal/api/huma_types_beads.go b/internal/api/huma_types_beads.go index 79ea5ab180..e22488f6fb 100644 --- a/internal/api/huma_types_beads.go +++ b/internal/api/huma_types_beads.go @@ -53,13 +53,15 @@ type BeadCreateInput struct { CityScope IdempotencyKey string `header:"Idempotency-Key" required:"false" doc:"Idempotency key for safe retries."` Body struct { - Rig string `json:"rig,omitempty" doc:"Rig name."` - Title string `json:"title" doc:"Bead title." minLength:"1"` - Type string `json:"type,omitempty" doc:"Bead type."` - Priority *int `json:"priority,omitempty" doc:"Bead priority."` - Assignee string `json:"assignee,omitempty" doc:"Assigned agent."` - Description string `json:"description,omitempty" doc:"Bead description."` - Labels []string `json:"labels,omitempty" doc:"Bead labels."` + Rig string `json:"rig,omitempty" doc:"Rig name."` + Title string `json:"title" doc:"Bead title." minLength:"1"` + Type string `json:"type,omitempty" doc:"Bead type."` + Priority *int `json:"priority,omitempty" doc:"Bead priority."` + Assignee string `json:"assignee,omitempty" doc:"Assigned agent."` + Description string `json:"description,omitempty" doc:"Bead description."` + Labels []string `json:"labels,omitempty" doc:"Bead labels."` + Parent string `json:"parent,omitempty" doc:"Parent bead ID."` + Metadata map[string]string `json:"metadata,omitempty" doc:"Metadata key-value pairs to set at create time."` } } @@ -92,7 +94,9 @@ type beadUpdateBody struct { Description *string `json:"description,omitempty" doc:"Bead description."` Labels []string `json:"labels,omitempty" doc:"Bead labels."` RemoveLabels []string `json:"remove_labels,omitempty" doc:"Labels to remove."` + Parent *string `json:"parent,omitempty" nullable:"true" doc:"Parent bead ID. Use null or an empty string to clear."` Metadata map[string]string `json:"metadata,omitempty" doc:"Metadata key-value pairs to set."` + parentSet bool } // UnmarshalJSON rejects `"priority": null` explicitly. Standard Go JSON decoding @@ -116,6 +120,13 @@ func (b *beadUpdateBody) UnmarshalJSON(data []byte) error { return err } *b = beadUpdateBody(a) + if p, ok := raw["parent"]; ok { + b.parentSet = true + if bytes.Equal(bytes.TrimSpace(p), []byte("null")) { + parent := "" + b.Parent = &parent + } + } return nil } diff --git a/internal/api/openapi.json b/internal/api/openapi.json index 0095c093c3..bd3188b4f1 100644 --- a/internal/api/openapi.json +++ b/internal/api/openapi.json @@ -827,6 +827,17 @@ "null" ] }, + "metadata": { + "additionalProperties": { + "type": "string" + }, + "description": "Metadata key-value pairs to set at create time.", + "type": "object" + }, + "parent": { + "description": "Parent bead ID.", + "type": "string" + }, "priority": { "description": "Bead priority.", "format": "int64", @@ -941,6 +952,13 @@ "description": "Metadata key-value pairs to set.", "type": "object" }, + "parent": { + "description": "Parent bead ID. Use null or an empty string to clear.", + "type": [ + "string", + "null" + ] + }, "priority": { "description": "Bead priority.", "format": "int64", @@ -1052,22 +1070,6 @@ ], "type": "object" }, - "CityCreatedPayload": { - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "path": { - "type": "string" - } - }, - "required": [ - "name", - "path" - ], - "type": "object" - }, "CityGetResponse": { "additionalProperties": false, "properties": { @@ -1147,7 +1149,7 @@ ], "type": "object" }, - "CityInitFailedPayload": { + "CityLifecyclePayload": { "additionalProperties": false, "properties": { "error": { @@ -1171,8 +1173,7 @@ }, "required": [ "name", - "path", - "error" + "path" ], "type": "object" }, @@ -1186,58 +1187,6 @@ }, "type": "object" }, - "CityReadyPayload": { - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "path": { - "type": "string" - } - }, - "required": [ - "name", - "path" - ], - "type": "object" - }, - "CityUnregisterFailedPayload": { - "additionalProperties": false, - "properties": { - "error": { - "type": "string" - }, - "name": { - "type": "string" - }, - "path": { - "type": "string" - } - }, - "required": [ - "name", - "path", - "error" - ], - "type": "object" - }, - "CityUnregisterRequestedPayload": { - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "path": { - "type": "string" - } - }, - "required": [ - "name", - "path" - ], - "type": "object" - }, "CityUnregisterResponse": { "additionalProperties": false, "properties": { @@ -1261,22 +1210,6 @@ ], "type": "object" }, - "CityUnregisteredPayload": { - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "path": { - "type": "string" - } - }, - "required": [ - "name", - "path" - ], - "type": "object" - }, "ConfigAgentResponse": { "additionalProperties": false, "properties": { @@ -2016,22 +1949,7 @@ "$ref": "#/components/schemas/BoundEventPayload" }, { - "$ref": "#/components/schemas/CityCreatedPayload" - }, - { - "$ref": "#/components/schemas/CityInitFailedPayload" - }, - { - "$ref": "#/components/schemas/CityReadyPayload" - }, - { - "$ref": "#/components/schemas/CityUnregisterFailedPayload" - }, - { - "$ref": "#/components/schemas/CityUnregisterRequestedPayload" - }, - { - "$ref": "#/components/schemas/CityUnregisteredPayload" + "$ref": "#/components/schemas/CityLifecyclePayload" }, { "$ref": "#/components/schemas/GroupCreatedEventPayload" diff --git a/internal/beads/bdstore.go b/internal/beads/bdstore.go index 2b799b118c..a620ba6322 100644 --- a/internal/beads/bdstore.go +++ b/internal/beads/bdstore.go @@ -456,8 +456,28 @@ func (s *BdStore) Create(b Bead) (Bead, error) { if created.Priority == nil && b.Priority != nil { created.Priority = cloneIntPtr(b.Priority) } - if created.Metadata == nil && len(metadata) > 0 { - created.Metadata = metadata + if created.ParentID == "" { + created.ParentID = b.ParentID + } + if created.Description == "" { + created.Description = b.Description + } + if len(created.Labels) == 0 && len(b.Labels) > 0 { + created.Labels = append([]string(nil), b.Labels...) + } + if len(created.Needs) == 0 && len(b.Needs) > 0 { + created.Needs = append([]string(nil), b.Needs...) + } + if len(metadata) > 0 { + if created.Metadata == nil { + created.Metadata = maps.Clone(metadata) + } else { + for key, value := range metadata { + if _, ok := created.Metadata[key]; !ok { + created.Metadata[key] = value + } + } + } } return created, nil } diff --git a/internal/beads/bdstore_test.go b/internal/beads/bdstore_test.go index 4d748f4a8e..8a52be0007 100644 --- a/internal/beads/bdstore_test.go +++ b/internal/beads/bdstore_test.go @@ -969,10 +969,10 @@ func TestBdStoreCreateWithLabels(t *testing.T) { var gotArgs []string runner := func(_, _ string, args ...string) ([]byte, error) { gotArgs = args - return []byte(`{"id":"bd-x","title":"test","status":"open","issue_type":"convoy","created_at":"2025-01-15T10:30:00Z","labels":["owned"]}`), nil + return []byte(`{"id":"bd-x","title":"test","status":"open","issue_type":"convoy","created_at":"2025-01-15T10:30:00Z"}`), nil } s := beads.NewBdStore("/city", runner) - _, err := s.Create(beads.Bead{Title: "test", Type: "convoy", Labels: []string{"owned"}}) + created, err := s.Create(beads.Bead{Title: "test", Type: "convoy", Labels: []string{"owned"}}) if err != nil { t.Fatal(err) } @@ -980,6 +980,9 @@ func TestBdStoreCreateWithLabels(t *testing.T) { if !strings.Contains(args, "--labels owned") { t.Errorf("args = %q, want to contain '--labels owned'", args) } + if len(created.Labels) != 1 || created.Labels[0] != "owned" { + t.Errorf("created.Labels = %#v, want [owned]", created.Labels) + } } func TestBdStoreCreateWithMultipleLabelsUsesSingleFlag(t *testing.T) { @@ -1009,7 +1012,7 @@ func TestBdStoreCreateWithParentID(t *testing.T) { return []byte(`{"id":"bd-x","title":"test","status":"open","issue_type":"task","created_at":"2025-01-15T10:30:00Z"}`), nil } s := beads.NewBdStore("/city", runner) - _, err := s.Create(beads.Bead{Title: "test", ParentID: "bd-parent-1"}) + created, err := s.Create(beads.Bead{Title: "test", ParentID: "bd-parent-1"}) if err != nil { t.Fatal(err) } @@ -1017,6 +1020,9 @@ func TestBdStoreCreateWithParentID(t *testing.T) { if !strings.Contains(args, "--parent bd-parent-1") { t.Errorf("args = %q, want to contain '--parent bd-parent-1'", args) } + if created.ParentID != "bd-parent-1" { + t.Errorf("created.ParentID = %q, want bd-parent-1", created.ParentID) + } } func TestBdStoreDepAddParentChildAlreadyParentedIsNoop(t *testing.T) { diff --git a/internal/beads/caching_store.go b/internal/beads/caching_store.go index 52cddf6c26..6ce8320af9 100644 --- a/internal/beads/caching_store.go +++ b/internal/beads/caching_store.go @@ -30,6 +30,7 @@ type CachingStore struct { dirty map[string]struct{} state cacheState lastFreshAt time.Time + mutationSeq uint64 reconciling atomic.Bool syncFailures int @@ -109,6 +110,10 @@ func newCachingStore(backing Store, onChange func(eventType, beadID string, payl // cachePartial state: filtered active queries and Get hit cache for primed // beads, while closed-bead queries still delegate to the backing store. func (c *CachingStore) PrimeActive() error { + c.mu.RLock() + startSeq := c.mutationSeq + c.mu.RUnlock() + var all []Bead for _, status := range []string{"open", "in_progress"} { beads, err := c.backing.List(ListQuery{Status: status}) @@ -121,6 +126,11 @@ func (c *CachingStore) PrimeActive() error { c.mu.Lock() defer c.mu.Unlock() for _, b := range all { + if c.mutationSeq != startSeq { + if _, exists := c.beads[b.ID]; exists { + continue + } + } c.beads[b.ID] = cloneBead(b) } if c.state == cacheUninitialized { @@ -135,6 +145,10 @@ func (c *CachingStore) PrimeActive() error { // Retries up to 3 times on failure since bd list can time out under // concurrent dolt load. func (c *CachingStore) Prime(_ context.Context) error { + c.mu.RLock() + startSeq := c.mutationSeq + c.mu.RUnlock() + var all []Bead var err error for attempt := 1; attempt <= 3; attempt++ { @@ -164,9 +178,21 @@ func (c *CachingStore) Prime(_ context.Context) error { now := time.Now() c.mu.Lock() defer c.mu.Unlock() - c.beads = beadMap - c.deps = depMap - c.dirty = make(map[string]struct{}) + if c.mutationSeq == startSeq { + c.beads = beadMap + c.deps = depMap + c.dirty = make(map[string]struct{}) + } else { + for id, b := range beadMap { + if _, exists := c.beads[id]; exists { + continue + } + c.beads[id] = b + if deps, ok := depMap[id]; ok { + c.deps[id] = deps + } + } + } c.state = cacheLive c.syncFailures = 0 c.stats.SyncFailures = 0 diff --git a/internal/beads/caching_store_reads.go b/internal/beads/caching_store_reads.go index 2c76b06007..0f69790278 100644 --- a/internal/beads/caching_store_reads.go +++ b/internal/beads/caching_store_reads.go @@ -14,7 +14,7 @@ func (c *CachingStore) List(query ListQuery) ([]Bead, error) { if !query.HasFilter() && !query.AllowScan { return nil, fmt.Errorf("listing beads: %w", ErrQueryRequiresScan) } - if query.Live { + if query.Live || query.ParentID != "" { return c.backing.List(query) } @@ -89,26 +89,24 @@ func (c *CachingStore) ListOpen(status ...string) ([]Bead, error) { func (c *CachingStore) Get(id string) (Bead, error) { c.mu.RLock() if c.state == cacheLive || c.state == cachePartial { - if _, ok := c.dirty[id]; ok { - c.mu.RUnlock() - fresh, err := c.backing.Get(id) - if err != nil { - return Bead{}, err + cached, cachedOK := c.beads[id] + c.mu.RUnlock() + + fresh, err := c.backing.Get(id) + if err != nil { + if cachedOK && !errors.Is(err, ErrNotFound) { + c.recordProblem("refresh bead during get", fmt.Errorf("%s: %w", id, err)) + return cloneBead(cached), nil } - c.mu.Lock() - c.beads[id] = cloneBead(fresh) - delete(c.dirty, id) - c.markFreshLocked(time.Now()) - c.updateStatsLocked() - c.mu.Unlock() - return fresh, nil + return Bead{}, err } - if b, ok := c.beads[id]; ok { - c.mu.RUnlock() - return cloneBead(b), nil - } - c.mu.RUnlock() - return c.backing.Get(id) + c.mu.Lock() + c.beads[id] = cloneBead(fresh) + delete(c.dirty, id) + c.markFreshLocked(time.Now()) + c.updateStatsLocked() + c.mu.Unlock() + return fresh, nil } c.mu.RUnlock() return c.backing.Get(id) diff --git a/internal/beads/caching_store_test.go b/internal/beads/caching_store_test.go index c64250009d..1fb7a0ce2c 100644 --- a/internal/beads/caching_store_test.go +++ b/internal/beads/caching_store_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "sync" "testing" "time" @@ -64,6 +65,189 @@ func TestCachingStoreReadThrough(t *testing.T) { } } +func TestCachingStorePrimePreservesConcurrentUpdate(t *testing.T) { + mem := beads.NewMemStore() + original, err := mem.Create(beads.Bead{Title: "before prime"}) + if err != nil { + t.Fatalf("Create: %v", err) + } + started := make(chan struct{}) + release := make(chan struct{}) + backing := &primeRaceStore{ + Store: mem, + started: started, + release: release, + stale: []beads.Bead{original}, + } + cs := beads.NewCachingStoreForTest(backing, nil) + + done := make(chan error, 1) + go func() { + done <- cs.Prime(context.Background()) + }() + + <-started + title := "after update" + if err := cs.Update(original.ID, beads.UpdateOpts{Title: &title}); err != nil { + t.Fatalf("Update: %v", err) + } + close(release) + if err := <-done; err != nil { + t.Fatalf("Prime: %v", err) + } + + got, err := cs.Get(original.ID) + if err != nil { + t.Fatalf("Get: %v", err) + } + if got.Title != title { + t.Fatalf("title after concurrent prime = %q, want %q", got.Title, title) + } +} + +func TestCachingStoreGetRefreshesStaleCachedBead(t *testing.T) { + mem := beads.NewMemStore() + original, err := mem.Create(beads.Bead{Title: "before update"}) + if err != nil { + t.Fatalf("Create: %v", err) + } + cs := beads.NewCachingStoreForTest(mem, nil) + if err := cs.Prime(context.Background()); err != nil { + t.Fatalf("Prime: %v", err) + } + + status := "in_progress" + if err := mem.Update(original.ID, beads.UpdateOpts{Status: &status}); err != nil { + t.Fatalf("backing Update: %v", err) + } + + got, err := cs.Get(original.ID) + if err != nil { + t.Fatalf("Get: %v", err) + } + if got.Status != "in_progress" { + t.Fatalf("status = %q, want in_progress", got.Status) + } +} + +func TestCachingStoreParentListUsesBackingStore(t *testing.T) { + mem := beads.NewMemStore() + parent, err := mem.Create(beads.Bead{Title: "parent"}) + if err != nil { + t.Fatalf("Create(parent): %v", err) + } + cs := beads.NewCachingStoreForTest(mem, nil) + if err := cs.Prime(context.Background()); err != nil { + t.Fatalf("Prime: %v", err) + } + child, err := mem.Create(beads.Bead{Title: "child", ParentID: parent.ID}) + if err != nil { + t.Fatalf("Create(child): %v", err) + } + + got, err := cs.List(beads.ListQuery{ParentID: parent.ID}) + if err != nil { + t.Fatalf("List(parent): %v", err) + } + if len(got) != 1 || got[0].ID != child.ID { + t.Fatalf("children = %#v, want child %s", got, child.ID) + } +} + +func TestCachingStoreUpdateReflectsWriteIntentWhenImmediateReadIsStale(t *testing.T) { + mem := beads.NewMemStore() + original, err := mem.Create(beads.Bead{ + Title: "root", + Labels: []string{"root", "needs-update"}, + Metadata: map[string]string{"mc.contract.run_id": "r1"}, + }) + if err != nil { + t.Fatalf("Create: %v", err) + } + backing := &staleReadAfterUpdateStore{ + Store: mem, + stale: original, + } + cs := beads.NewCachingStoreForTest(backing, nil) + if err := cs.Prime(context.Background()); err != nil { + t.Fatalf("Prime: %v", err) + } + + status := "in_progress" + if err := cs.Update(original.ID, beads.UpdateOpts{ + Status: &status, + Labels: []string{"verified"}, + RemoveLabels: []string{"needs-update"}, + Metadata: map[string]string{ + "mc.contract.metadata_update": "true", + }, + }); err != nil { + t.Fatalf("Update: %v", err) + } + + got, err := cs.Get(original.ID) + if err != nil { + t.Fatalf("Get: %v", err) + } + if got.Status != "in_progress" { + t.Fatalf("status = %q, want in_progress", got.Status) + } + if got.Metadata["mc.contract.metadata_update"] != "true" || got.Metadata["mc.contract.run_id"] != "r1" { + t.Fatalf("metadata = %#v, want original plus update", got.Metadata) + } + if !containsString(got.Labels, "verified") || containsString(got.Labels, "needs-update") { + t.Fatalf("labels = %#v, want verified without needs-update", got.Labels) + } +} + +type staleReadAfterUpdateStore struct { + beads.Store + mu sync.Mutex + stale beads.Bead + returnOld bool +} + +func (s *staleReadAfterUpdateStore) Update(id string, opts beads.UpdateOpts) error { + if err := s.Store.Update(id, opts); err != nil { + return err + } + s.mu.Lock() + s.returnOld = true + s.mu.Unlock() + return nil +} + +func (s *staleReadAfterUpdateStore) Get(id string) (beads.Bead, error) { + s.mu.Lock() + if s.returnOld && id == s.stale.ID { + s.returnOld = false + stale := s.stale + s.mu.Unlock() + return stale, nil + } + s.mu.Unlock() + return s.Store.Get(id) +} + +type primeRaceStore struct { + beads.Store + started chan struct{} + release chan struct{} + stale []beads.Bead + once sync.Once +} + +func (s *primeRaceStore) List(query beads.ListQuery) ([]beads.Bead, error) { + if !query.AllowScan { + return s.Store.List(query) + } + s.once.Do(func() { + close(s.started) + }) + <-s.release + return append([]beads.Bead(nil), s.stale...), nil +} + func TestCachingStoreGetFallsBackForClosedBeadsAfterPrime(t *testing.T) { t.Parallel() mem := beads.NewMemStore() @@ -255,6 +439,45 @@ func TestCachingStoreWriteThrough(t *testing.T) { } } +func TestCachingStoreCloseNotifiesWhenBeadIsMissingFromCache(t *testing.T) { + t.Parallel() + mem := beads.NewMemStore() + cs := beads.NewCachingStoreForTest(mem, nil) + if err := cs.Prime(context.Background()); err != nil { + t.Fatalf("Prime: %v", err) + } + created, err := mem.Create(beads.Bead{Title: "external"}) + if err != nil { + t.Fatalf("Create: %v", err) + } + + var events []string + cs = beads.NewCachingStoreForTest(mem, func(eventType, beadID string, payload json.RawMessage) { + var b beads.Bead + if err := json.Unmarshal(payload, &b); err != nil { + t.Fatalf("unmarshal callback payload: %v", err) + } + events = append(events, eventType+":"+beadID+":"+b.Status) + }) + if err := cs.Prime(context.Background()); err != nil { + t.Fatalf("Prime after callback install: %v", err) + } + if err := cs.Delete(created.ID); err != nil { + t.Fatalf("Delete setup: %v", err) + } + created, err = mem.Create(beads.Bead{Title: "external"}) + if err != nil { + t.Fatalf("Create second: %v", err) + } + + if err := cs.Close(created.ID); err != nil { + t.Fatalf("Close: %v", err) + } + if len(events) != 1 || events[0] != "bead.closed:"+created.ID+":closed" { + t.Fatalf("events = %#v, want bead.closed for missing cached bead", events) + } +} + func TestCachingStoreApplyEvent(t *testing.T) { t.Parallel() mem := beads.NewMemStore() @@ -270,10 +493,7 @@ func TestCachingStoreApplyEvent(t *testing.T) { payload, _ := json.Marshal(newBead) cs.ApplyEvent("bead.created", payload) - got, err := cs.Get("ext-1") - if err != nil { - t.Fatalf("Get after ApplyEvent create: %v", err) - } + got := requireCachedBead(t, cs, "ext-1", false) if got.Title != "External" { t.Fatalf("title = %q, want External", got.Title) } @@ -283,7 +503,7 @@ func TestCachingStoreApplyEvent(t *testing.T) { payload, _ = json.Marshal(updated) cs.ApplyEvent("bead.updated", payload) - got, _ = cs.Get(b1.ID) + got = requireCachedBead(t, cs, b1.ID, false) if got.Title != "Modified by agent" { t.Fatalf("title after update event = %q, want Modified by agent", got.Title) } @@ -302,7 +522,7 @@ func TestCachingStoreApplyEvent(t *testing.T) { payload, _ = json.Marshal(closed) cs.ApplyEvent("bead.closed", payload) - got, _ = cs.Get(b1.ID) + got = requireCachedBead(t, cs, b1.ID, true) if got.Status != "closed" { t.Fatalf("status after close event = %q, want closed", got.Status) } @@ -356,10 +576,7 @@ func TestCachingStoreApplyEventCoercesNonStringMetadata(t *testing.T) { t.Fatalf("ProblemCount = %d, want 0 (last problem: %s)", stats.ProblemCount, stats.LastProblem) } - got, err := cs.Get(b1.ID) - if err != nil { - t.Fatalf("Get after ApplyEvent update: %v", err) - } + got := requireCachedBead(t, cs, b1.ID, false) if got.Type != "session" { t.Fatalf("Type = %q, want session", got.Type) } @@ -374,6 +591,21 @@ func TestCachingStoreApplyEventCoercesNonStringMetadata(t *testing.T) { } } +func requireCachedBead(t *testing.T, cs *beads.CachingStore, id string, includeClosed bool) beads.Bead { + t.Helper() + items, err := cs.List(beads.ListQuery{AllowScan: true, IncludeClosed: includeClosed}) + if err != nil { + t.Fatalf("List cached beads: %v", err) + } + for _, item := range items { + if item.ID == id { + return item + } + } + t.Fatalf("cached bead %q missing from %#v", id, items) + return beads.Bead{} +} + func TestCachingStoreApplyEventIgnoredWhenDegraded(t *testing.T) { t.Parallel() mem := beads.NewMemStore() @@ -512,3 +744,12 @@ func (s *failingIncludeClosedMetadataStore) List(query beads.ListQuery) ([]beads } func strPtr(s string) *string { return &s } + +func containsString(values []string, want string) bool { + for _, value := range values { + if value == want { + return true + } + } + return false +} diff --git a/internal/beads/caching_store_writes.go b/internal/beads/caching_store_writes.go index 7d479ca865..d0096de168 100644 --- a/internal/beads/caching_store_writes.go +++ b/internal/beads/caching_store_writes.go @@ -14,6 +14,7 @@ func (c *CachingStore) Create(b Bead) (Bead, error) { } c.mu.Lock() + c.mutationSeq++ c.beads[created.ID] = cloneBead(created) delete(c.dirty, created.ID) c.markFreshLocked(time.Now()) @@ -40,7 +41,10 @@ func (c *CachingStore) Update(id string, opts UpdateOpts) error { return nil } + fresh = applyUpdateOptsToBead(fresh, opts) + c.mu.Lock() + c.mutationSeq++ c.beads[id] = cloneBead(fresh) delete(c.dirty, id) c.markFreshLocked(time.Now()) @@ -51,6 +55,56 @@ func (c *CachingStore) Update(id string, opts UpdateOpts) error { return nil } +func applyUpdateOptsToBead(b Bead, opts UpdateOpts) Bead { + b = cloneBead(b) + if opts.Title != nil { + b.Title = *opts.Title + } + if opts.Status != nil { + b.Status = *opts.Status + } + if opts.Type != nil { + b.Type = *opts.Type + } + if opts.Priority != nil { + b.Priority = cloneIntPtr(opts.Priority) + } + if opts.Description != nil { + b.Description = *opts.Description + } + if opts.ParentID != nil { + b.ParentID = *opts.ParentID + } + if opts.Assignee != nil { + b.Assignee = *opts.Assignee + } + if len(opts.Metadata) > 0 { + if b.Metadata == nil { + b.Metadata = make(map[string]string, len(opts.Metadata)) + } + for k, v := range opts.Metadata { + b.Metadata[k] = v + } + } + if len(opts.Labels) > 0 { + b.Labels = append(b.Labels, opts.Labels...) + } + if len(opts.RemoveLabels) > 0 { + remove := make(map[string]bool, len(opts.RemoveLabels)) + for _, label := range opts.RemoveLabels { + remove[label] = true + } + kept := b.Labels[:0] + for _, label := range b.Labels { + if !remove[label] { + kept = append(kept, label) + } + } + b.Labels = kept + } + return b +} + // Close marks a bead as closed in the backing store and cache. func (c *CachingStore) Close(id string) error { if err := c.backing.Close(id); err != nil { @@ -59,7 +113,16 @@ func (c *CachingStore) Close(id string) error { var closed Bead var found bool + if fresh, err := c.backing.Get(id); err == nil { + closed = fresh + closed.Status = "closed" + found = true + } else if !errors.Is(err, ErrNotFound) { + c.recordProblem("refresh bead after close", fmt.Errorf("%s: %w", id, err)) + } + c.mu.Lock() + c.mutationSeq++ if b, ok := c.beads[id]; ok { b.Status = "closed" c.beads[id] = b @@ -68,6 +131,11 @@ func (c *CachingStore) Close(id string) error { found = true c.markFreshLocked(time.Now()) c.updateStatsLocked() + } else if found { + c.beads[id] = cloneBead(closed) + delete(c.dirty, id) + c.markFreshLocked(time.Now()) + c.updateStatsLocked() } c.mu.Unlock() @@ -103,6 +171,7 @@ func (c *CachingStore) CloseAll(ids []string, metadata map[string]string) (int, notifications := make([]cacheNotification, 0, len(refreshed)) c.mu.Lock() + c.mutationSeq++ if refreshErr != nil { c.recordProblemLocked("close-all refresh", refreshErr) } @@ -137,6 +206,7 @@ func (c *CachingStore) SetMetadata(id, key, value string) error { } c.mu.Lock() + c.mutationSeq++ if b, ok := c.beads[id]; ok { if b.Metadata == nil { b.Metadata = make(map[string]string) @@ -158,6 +228,7 @@ func (c *CachingStore) SetMetadataBatch(id string, kvs map[string]string) error } c.mu.Lock() + c.mutationSeq++ if b, ok := c.beads[id]; ok { if b.Metadata == nil { b.Metadata = make(map[string]string, len(kvs)) @@ -181,6 +252,7 @@ func (c *CachingStore) DepAdd(issueID, dependsOnID, depType string) error { } c.mu.Lock() + c.mutationSeq++ deps := c.deps[issueID] for i, d := range deps { if d.DependsOnID == dependsOnID { @@ -208,6 +280,7 @@ func (c *CachingStore) DepRemove(issueID, dependsOnID string) error { } c.mu.Lock() + c.mutationSeq++ deps := c.deps[issueID] for i, d := range deps { if d.DependsOnID == dependsOnID { @@ -229,6 +302,7 @@ func (c *CachingStore) Delete(id string) error { } c.mu.Lock() + c.mutationSeq++ delete(c.beads, id) delete(c.deps, id) delete(c.dirty, id) diff --git a/test/integration/gc_live_contract_test.go b/test/integration/gc_live_contract_test.go new file mode 100644 index 0000000000..85eaea24a8 --- /dev/null +++ b/test/integration/gc_live_contract_test.go @@ -0,0 +1,695 @@ +//go:build integration + +package integration + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "testing" + "time" + + "github.com/gastownhall/gascity/internal/beads" + "github.com/pb33f/libopenapi" + openapivalidator "github.com/pb33f/libopenapi-validator" +) + +// TestGCLiveContract_BeadsAndEvents ports the MC live GC contract's +// API-only coverage into this repo. It boots a real supervisor, creates an +// isolated city and rig through the HTTP API, validates responses against the +// live OpenAPI document, exercises the bead lifecycle MC depends on, validates +// city and supervisor event list schemas, and unregisters the city through the +// API. +func TestGCLiveContract_BeadsAndEvents(t *testing.T) { + bin := buildGCBinary(t) + + root := shortTempDir(t) + gcHome := filepath.Join(root, "home") + runtimeDir := filepath.Join(root, "run") + for _, dir := range []string{gcHome, runtimeDir} { + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatalf("mkdir %s: %v", dir, err) + } + } + if err := seedDoltIdentityForRoot(gcHome); err != nil { + t.Fatalf("seed dolt identity: %v", err) + } + port := reserveFreePort(t) + writeSupervisorConfig(t, gcHome, port) + + baseURL := "http://127.0.0.1:" + strconv.Itoa(port) + env := integrationEnvFor(gcHome, runtimeDir, true) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + cmd := exec.CommandContext(ctx, bin, "supervisor", "run") + cmd.Env = env + stderr, err := cmd.StderrPipe() + if err != nil { + t.Fatalf("stderr pipe: %v", err) + } + if err := cmd.Start(); err != nil { + t.Fatalf("start supervisor: %v", err) + } + var supervisorLog strings.Builder + go func() { _, _ = io.Copy(&supervisorLog, stderr) }() + t.Cleanup(func() { + cancel() + _ = cmd.Wait() + if t.Failed() { + t.Logf("supervisor stderr:\n%s", supervisorLog.String()) + } + }) + + waitHTTP(t, baseURL+"/health", 10*time.Second) + + specBytes := liveContractRequest(t, baseURL, nil, http.MethodGet, "/openapi.json", nil, http.StatusOK) + assertLiveContractSpec(t, specBytes) + validator := liveContractValidator(t, specBytes) + + cityName := "mc-live-contract-" + strconv.FormatInt(time.Now().UnixNano(), 36) + cityDir := filepath.Join(root, "cities", cityName) + createCity := liveContractJSON[struct { + OK bool `json:"ok"` + Name string `json:"name"` + Path string `json:"path"` + }](t, baseURL, validator, http.MethodPost, "/v0/city", map[string]string{ + "dir": cityDir, + "provider": "claude", + }, http.StatusAccepted) + if !createCity.OK || createCity.Name != cityName || createCity.Path != cityDir { + t.Fatalf("city create response = %+v, want ok=true name=%q path=%q", createCity, cityName, cityDir) + } + + cityBase := "/v0/city/" + url.PathEscape(cityName) + waitForLiveContractEvent(t, baseURL, validator, "/v0/events", cityName, "city.ready", 120*time.Second) + liveContractJSON[struct { + Status string `json:"status"` + }](t, baseURL, validator, http.MethodGet, cityBase+"/health", nil, http.StatusOK) + assertLiveContractStreamOpens(t, baseURL, "/v0/events/stream") + assertLiveContractStreamOpens(t, baseURL, cityBase+"/events/stream") + + rigName := "alpha" + rigDir := filepath.Join(cityDir, rigName) + if err := os.MkdirAll(rigDir, 0o755); err != nil { + t.Fatalf("mkdir rig: %v", err) + } + liveContractJSON[struct { + Status string `json:"status"` + Rig string `json:"rig"` + }](t, baseURL, validator, http.MethodPost, cityBase+"/rigs", map[string]string{ + "name": rigName, + "path": rigDir, + "prefix": "mc-" + strconv.FormatInt(time.Now().UnixNano(), 36), + }, http.StatusCreated) + waitForLiveContractRig(t, baseURL, validator, cityBase, rigName, rigDir, 30*time.Second) + + runID := strconv.FormatInt(time.Now().UnixNano(), 36) + rootBead := liveContractJSON[beads.Bead](t, baseURL, validator, http.MethodPost, cityBase+"/beads", map[string]any{ + "description": "Root fixture created by TestGCLiveContract_BeadsAndEvents", + "labels": []string{"mc-live-contract", "root"}, + "metadata": map[string]string{ + "mc.contract.role": "root", + "mc.contract.run_id": runID, + }, + "priority": 2, + "rig": rigName, + "title": "MC live contract root " + runID, + "type": "feature", + }, http.StatusCreated) + if rootBead.ID == "" || rootBead.Status != "open" || rootBead.Type != "feature" { + t.Fatalf("root bead = %+v, want id, open status, feature type", rootBead) + } + if rootBead.Metadata["mc.contract.run_id"] != runID { + t.Fatalf("root metadata = %#v, want run_id=%q", rootBead.Metadata, runID) + } + + liveContractJSON[struct { + Status string `json:"status"` + }](t, baseURL, validator, http.MethodPost, cityBase+"/bead/"+url.PathEscape(rootBead.ID)+"/update", map[string]any{ + "metadata": map[string]string{ + "mc.contract.metadata_update": "true", + "mc_permission_mode": "default", + "mc_starred": "true", + }, + "status": "in_progress", + }, http.StatusOK) + updatedRoot := liveContractJSON[beads.Bead](t, baseURL, validator, http.MethodGet, cityBase+"/bead/"+url.PathEscape(rootBead.ID), nil, http.StatusOK) + if updatedRoot.Status != "in_progress" || updatedRoot.Metadata["mc.contract.metadata_update"] != "true" { + t.Fatalf("updated root = %+v, want in_progress plus metadata update", updatedRoot) + } + + liveContractJSON[struct { + Status string `json:"status"` + }](t, baseURL, validator, http.MethodPost, cityBase+"/bead/"+url.PathEscape(rootBead.ID)+"/close", nil, http.StatusOK) + closedRoot := liveContractJSON[beads.Bead](t, baseURL, validator, http.MethodGet, cityBase+"/bead/"+url.PathEscape(rootBead.ID), nil, http.StatusOK) + if closedRoot.Status != "closed" { + t.Fatalf("closed root status = %q, want closed", closedRoot.Status) + } + liveContractJSON[struct { + Status string `json:"status"` + }](t, baseURL, validator, http.MethodPost, cityBase+"/bead/"+url.PathEscape(rootBead.ID)+"/reopen", nil, http.StatusOK) + reopenedRoot := liveContractJSON[beads.Bead](t, baseURL, validator, http.MethodGet, cityBase+"/bead/"+url.PathEscape(rootBead.ID), nil, http.StatusOK) + if reopenedRoot.Status != "open" { + t.Fatalf("reopened root status = %q, want open", reopenedRoot.Status) + } + + childBead := liveContractJSON[beads.Bead](t, baseURL, validator, http.MethodPost, cityBase+"/beads", map[string]any{ + "description": "Child fixture that exercises parent and update semantics", + "labels": []string{"mc-live-contract", "child", "needs-update"}, + "metadata": map[string]string{ + "mc.contract.role": "child", + "mc.contract.run_id": runID, + }, + "parent": rootBead.ID, + "priority": 1, + "rig": rigName, + "title": "MC live contract child " + runID, + "type": "task", + }, http.StatusCreated) + siblingBead := liveContractJSON[beads.Bead](t, baseURL, validator, http.MethodPost, cityBase+"/beads", map[string]any{ + "description": "Sibling fixture for list and filter coverage", + "labels": []string{"mc-live-contract", "sibling"}, + "metadata": map[string]string{ + "mc.contract.role": "sibling", + "mc.contract.run_id": runID, + }, + "parent": rootBead.ID, + "priority": 3, + "rig": rigName, + "title": "MC live contract sibling " + runID, + "type": "bug", + }, http.StatusCreated) + if childBead.ParentID != rootBead.ID || childBead.Type != "task" { + t.Fatalf("child bead = %+v, want parent=%q type=task", childBead, rootBead.ID) + } + if siblingBead.ParentID != rootBead.ID || siblingBead.Type != "bug" { + t.Fatalf("sibling bead = %+v, want parent=%q type=bug", siblingBead, rootBead.ID) + } + + liveContractJSON[struct { + Status string `json:"status"` + }](t, baseURL, validator, http.MethodPost, cityBase+"/bead/"+url.PathEscape(childBead.ID)+"/update", map[string]any{ + "description": "Updated child fixture", + "labels": []string{"verified"}, + "metadata": map[string]string{"mc.contract.updated": "true"}, + "parent": "", + "priority": 4, + "remove_labels": []string{"needs-update"}, + "status": "in_progress", + "title": "MC live contract child updated " + runID, + "type": "bug", + }, http.StatusOK) + updatedChild := liveContractJSON[beads.Bead](t, baseURL, validator, http.MethodGet, cityBase+"/bead/"+url.PathEscape(childBead.ID), nil, http.StatusOK) + if updatedChild.ParentID != "" || updatedChild.Status != "in_progress" || updatedChild.Type != "bug" || updatedChild.Priority == nil || *updatedChild.Priority != 4 { + t.Fatalf("updated child = %+v, want cleared parent, in_progress, bug, priority 4", updatedChild) + } + if !containsString(updatedChild.Labels, "verified") || containsString(updatedChild.Labels, "needs-update") { + t.Fatalf("updated child labels = %#v, want verified without needs-update", updatedChild.Labels) + } + if updatedChild.Metadata["mc.contract.updated"] != "true" { + t.Fatalf("updated child metadata = %#v, want mc.contract.updated=true", updatedChild.Metadata) + } + + liveContractJSON[struct { + Status string `json:"status"` + }](t, baseURL, validator, http.MethodPost, cityBase+"/bead/"+url.PathEscape(childBead.ID)+"/update", map[string]any{ + "metadata": map[string]string{"mc.contract.parent_restored": "true"}, + "parent": rootBead.ID, + }, http.StatusOK) + restoredChild := liveContractJSON[beads.Bead](t, baseURL, validator, http.MethodGet, cityBase+"/bead/"+url.PathEscape(childBead.ID), nil, http.StatusOK) + if restoredChild.ParentID != rootBead.ID { + t.Fatalf("restored child parent = %q, want %q", restoredChild.ParentID, rootBead.ID) + } + + deps := liveContractJSON[struct { + Children []beads.Bead `json:"children"` + }](t, baseURL, validator, http.MethodGet, cityBase+"/bead/"+url.PathEscape(rootBead.ID)+"/deps", nil, http.StatusOK) + if !beadListContains(deps.Children, childBead.ID) { + t.Fatalf("deps children = %#v, want child %s", deps.Children, childBead.ID) + } + graph := liveContractJSON[struct { + Beads []beads.Bead `json:"beads"` + }](t, baseURL, validator, http.MethodGet, cityBase+"/beads/graph/"+url.PathEscape(rootBead.ID), nil, http.StatusOK) + if !beadListContains(graph.Beads, rootBead.ID) { + t.Fatalf("graph beads = %#v, want root %s", graph.Beads, rootBead.ID) + } + list := liveContractJSON[struct { + Items []beads.Bead `json:"items"` + Total int `json:"total"` + }](t, baseURL, validator, http.MethodGet, cityBase+"/beads?label=mc-live-contract&limit=50&rig="+url.QueryEscape(rigName), nil, http.StatusOK) + if list.Total < 3 || !beadListContains(list.Items, rootBead.ID) || !beadListContains(list.Items, siblingBead.ID) { + t.Fatalf("filtered beads = %+v, want root and sibling", list) + } + + waitForLiveContractEvent(t, baseURL, validator, cityBase+"/events", cityName, "city.ready", 10*time.Second) + liveContractJSON[contractEventList](t, baseURL, validator, http.MethodGet, "/v0/events?limit=50", nil, http.StatusOK) + runLiveContractReadSweep(t, baseURL, validator, specBytes, cityName, rigName) + + for _, id := range []string{siblingBead.ID, childBead.ID, rootBead.ID} { + liveContractJSON[struct { + Status string `json:"status"` + }](t, baseURL, validator, http.MethodDelete, cityBase+"/bead/"+url.PathEscape(id), nil, http.StatusOK) + } + liveContractJSON[struct { + Status string `json:"status"` + }](t, baseURL, validator, http.MethodDelete, cityBase+"/rig/"+url.PathEscape(rigName), nil, http.StatusOK) + + unregister := liveContractJSON[struct { + OK bool `json:"ok"` + Name string `json:"name"` + Path string `json:"path"` + }](t, baseURL, validator, http.MethodPost, cityBase+"/unregister", nil, http.StatusAccepted) + if !unregister.OK || unregister.Name != cityName { + t.Fatalf("unregister response = %+v, want ok=true name=%q", unregister, cityName) + } + waitForCityAbsent(t, baseURL, validator, cityName, 45*time.Second) +} + +type contractEventList struct { + Items []contractEvent `json:"items"` + Total int `json:"total"` +} + +type contractEvent struct { + Type string `json:"type"` + Subject string `json:"subject"` + City string `json:"city"` + Payload struct { + Name string `json:"name"` + Path string `json:"path"` + Error string `json:"error"` + } `json:"payload"` +} + +type liveContractReadProbe struct { + pathTemplate string + path string + skipReason string +} + +type contractRigList struct { + Items []contractRig `json:"items"` + Total int `json:"total"` +} + +type contractRig struct { + Name string `json:"name"` + Path string `json:"path"` +} + +func liveContractValidator(t *testing.T, specBytes []byte) openapivalidator.Validator { + t.Helper() + doc, err := libopenapi.NewDocument(specBytes) + if err != nil { + t.Fatalf("build OpenAPI document: %v", err) + } + v, errs := openapivalidator.NewValidator(doc) + if len(errs) > 0 { + t.Fatalf("construct OpenAPI validator: %v", errs) + } + return v +} + +func liveContractJSON[T any](t *testing.T, baseURL string, v openapivalidator.Validator, method, path string, body any, wantStatus int) T { + t.Helper() + raw := liveContractRequest(t, baseURL, v, method, path, body, wantStatus) + var out T + if err := json.Unmarshal(raw, &out); err != nil { + t.Fatalf("%s %s decode response: %v\nbody: %s", method, path, err, string(raw)) + } + return out +} + +func liveContractRequest(t *testing.T, baseURL string, v openapivalidator.Validator, method, path string, body any, wantStatus int) []byte { + t.Helper() + req, err := liveContractHTTPRequest(baseURL, method, path, body) + if err != nil { + t.Fatalf("%s %s build request: %v", method, path, err) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatalf("%s %s: %v", method, path, err) + } + defer resp.Body.Close() //nolint:errcheck + raw, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("%s %s read response: %v", method, path, err) + } + if resp.StatusCode != wantStatus { + t.Fatalf("%s %s status = %d, want %d; body: %s", method, path, resp.StatusCode, wantStatus, string(raw)) + } + if v != nil { + validateLiveContractResponse(t, v, req, resp, raw) + } + return raw +} + +func liveContractHTTPRequest(baseURL, method, path string, body any) (*http.Request, error) { + var reader io.Reader + if body != nil { + raw, err := json.Marshal(body) + if err != nil { + return nil, err + } + reader = bytes.NewReader(raw) + } + req, err := http.NewRequest(method, baseURL+path, reader) + if err != nil { + return nil, err + } + req.Header.Set("Accept", "application/json") + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + if method != http.MethodGet { + req.Header.Set("X-GC-Request", "live-contract") + } + return req, nil +} + +func assertLiveContractStreamOpens(t *testing.T, baseURL, path string) { + t.Helper() + ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second) + defer cancel() + req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+path, nil) + if err != nil { + t.Fatalf("build stream request %s: %v", path, err) + } + req.Header.Set("Accept", "text/event-stream") + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatalf("GET %s stream: %v", path, err) + } + defer resp.Body.Close() //nolint:errcheck + if resp.StatusCode != http.StatusOK { + raw, _ := io.ReadAll(resp.Body) + t.Fatalf("GET %s stream status = %d, want 200; body: %s", path, resp.StatusCode, string(raw)) + } + contentType := resp.Header.Get("Content-Type") + if !strings.Contains(contentType, "text/event-stream") { + t.Fatalf("GET %s stream content-type = %q, want text/event-stream", path, contentType) + } +} + +func validateLiveContractResponse(t *testing.T, v openapivalidator.Validator, req *http.Request, resp *http.Response, raw []byte) { + t.Helper() + resp.Body = io.NopCloser(bytes.NewReader(raw)) + ok, valErrs := v.ValidateHttpResponse(req, resp) + _ = resp.Body.Close() + if ok { + return + } + var details strings.Builder + for _, ve := range valErrs { + fmt.Fprintf(&details, "%s - %s\n", ve.Message, ve.Reason) + for _, se := range ve.SchemaValidationErrors { + fmt.Fprintf(&details, " %s at %s\n", se.Reason, se.FieldPath) + } + } + t.Fatalf("%s %s response does not match OpenAPI schema:\n%sbody: %s", req.Method, req.URL.Path, details.String(), string(raw)) +} + +func waitForLiveContractEvent(t *testing.T, baseURL string, v openapivalidator.Validator, path, subject, eventType string, timeout time.Duration) { + t.Helper() + deadline := time.Now().Add(timeout) + var lastErr error + for time.Now().Before(deadline) { + events, err := liveContractEventList(baseURL, v, path) + if err != nil { + lastErr = err + time.Sleep(250 * time.Millisecond) + continue + } + for _, event := range events.Items { + if event.Subject == subject && event.Type == eventType { + return + } + if event.Subject == subject && strings.HasSuffix(event.Type, "_failed") { + t.Fatalf("event %s for %s failed: %+v", event.Type, subject, event.Payload) + } + } + time.Sleep(250 * time.Millisecond) + } + t.Fatalf("timed out waiting for %s for %s from %s; last error: %v", eventType, subject, path, lastErr) +} + +func liveContractEventList(baseURL string, v openapivalidator.Validator, path string) (contractEventList, error) { + req, err := liveContractHTTPRequest(baseURL, http.MethodGet, path, nil) + if err != nil { + return contractEventList{}, err + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return contractEventList{}, err + } + defer resp.Body.Close() //nolint:errcheck + raw, err := io.ReadAll(resp.Body) + if err != nil { + return contractEventList{}, err + } + if resp.StatusCode != http.StatusOK { + return contractEventList{}, fmt.Errorf("GET %s status %d: %s", path, resp.StatusCode, string(raw)) + } + if v != nil { + resp.Body = io.NopCloser(bytes.NewReader(raw)) + ok, valErrs := v.ValidateHttpResponse(req, resp) + _ = resp.Body.Close() + if !ok { + return contractEventList{}, fmt.Errorf("GET %s response does not match OpenAPI schema: %v; body: %s", path, valErrs, string(raw)) + } + } + var events contractEventList + if err := json.Unmarshal(raw, &events); err != nil { + return contractEventList{}, err + } + return events, nil +} + +func waitForCityAbsent(t *testing.T, baseURL string, v openapivalidator.Validator, cityName string, timeout time.Duration) { + t.Helper() + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + cities := liveContractJSON[struct { + Items []struct { + Name string `json:"name"` + } `json:"items"` + Total int `json:"total"` + }](t, baseURL, v, http.MethodGet, "/v0/cities", nil, http.StatusOK) + found := false + for _, city := range cities.Items { + if city.Name == cityName { + found = true + break + } + } + if !found { + return + } + time.Sleep(500 * time.Millisecond) + } + t.Fatalf("timed out waiting for city %q to disappear from /v0/cities", cityName) +} + +func waitForLiveContractRig(t *testing.T, baseURL string, v openapivalidator.Validator, cityBase, rigName, rigDir string, timeout time.Duration) { + t.Helper() + deadline := time.Now().Add(timeout) + var lastErr error + for time.Now().Before(deadline) { + rigs, err := liveContractRigList(baseURL, v, cityBase) + if err != nil { + lastErr = err + time.Sleep(250 * time.Millisecond) + continue + } + for _, rig := range rigs.Items { + if rig.Name == rigName && rig.Path == rigDir { + return + } + } + time.Sleep(250 * time.Millisecond) + } + t.Fatalf("timed out waiting for rig %q at %q; last error: %v", rigName, rigDir, lastErr) +} + +func liveContractRigList(baseURL string, v openapivalidator.Validator, cityBase string) (contractRigList, error) { + req, err := liveContractHTTPRequest(baseURL, http.MethodGet, cityBase+"/rigs", nil) + if err != nil { + return contractRigList{}, err + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return contractRigList{}, err + } + defer resp.Body.Close() //nolint:errcheck + raw, err := io.ReadAll(resp.Body) + if err != nil { + return contractRigList{}, err + } + if resp.StatusCode != http.StatusOK { + return contractRigList{}, fmt.Errorf("GET %s/rigs status %d: %s", cityBase, resp.StatusCode, string(raw)) + } + if v != nil { + resp.Body = io.NopCloser(bytes.NewReader(raw)) + ok, valErrs := v.ValidateHttpResponse(req, resp) + _ = resp.Body.Close() + if !ok { + return contractRigList{}, fmt.Errorf("GET %s/rigs response does not match OpenAPI schema: %v", cityBase, valErrs) + } + } + var rigs contractRigList + if err := json.Unmarshal(raw, &rigs); err != nil { + return rigs, err + } + return rigs, nil +} + +func runLiveContractReadSweep(t *testing.T, baseURL string, v openapivalidator.Validator, specBytes []byte, cityName, rigName string) { + t.Helper() + probes := collectLiveContractReadProbes(t, specBytes, cityName, rigName) + if len(probes) == 0 { + t.Fatal("OpenAPI read sweep found no GET probes") + } + for _, probe := range probes { + t.Run("GET "+probe.pathTemplate, func(t *testing.T) { + if probe.skipReason != "" { + t.Skip(probe.skipReason) + } + liveContractRequest(t, baseURL, v, http.MethodGet, probe.path, nil, http.StatusOK) + }) + } +} + +func collectLiveContractReadProbes(t *testing.T, specBytes []byte, cityName, rigName string) []liveContractReadProbe { + t.Helper() + var spec struct { + Paths map[string]map[string]json.RawMessage `json:"paths"` + } + if err := json.Unmarshal(specBytes, &spec); err != nil { + t.Fatalf("decode OpenAPI paths: %v", err) + } + + probes := make([]liveContractReadProbe, 0, len(spec.Paths)) + for pathTemplate, pathItem := range spec.Paths { + if _, ok := pathItem["get"]; !ok { + continue + } + if strings.Contains(pathTemplate, "/stream") || hasLiveContractUnboundPathParams(pathTemplate) { + continue + } + path := strings.ReplaceAll(pathTemplate, "{cityName}", url.PathEscape(cityName)) + path = appendLiveContractDefaultQuery(path, pathTemplate, rigName) + probes = append(probes, liveContractReadProbe{ + pathTemplate: pathTemplate, + path: path, + skipReason: liveContractProbeSkipReason(pathTemplate), + }) + } + return probes +} + +func hasLiveContractUnboundPathParams(pathTemplate string) bool { + for _, part := range strings.Split(pathTemplate, "/") { + if strings.HasPrefix(part, "{") && strings.HasSuffix(part, "}") && part != "{cityName}" { + return true + } + } + return false +} + +func appendLiveContractDefaultQuery(path, pathTemplate, rigName string) string { + query := url.Values{} + switch { + case strings.HasSuffix(pathTemplate, "/formulas") || strings.HasSuffix(pathTemplate, "/formulas/feed"): + query.Set("scope_kind", "rig") + query.Set("scope_ref", rigName) + case strings.HasSuffix(pathTemplate, "/orders/feed"): + query.Set("limit", "25") + query.Set("scope_kind", "rig") + query.Set("scope_ref", rigName) + case strings.HasSuffix(pathTemplate, "/readiness") || strings.HasSuffix(pathTemplate, "/provider-readiness"): + query.Set("fresh", "false") + case strings.HasSuffix(pathTemplate, "/beads"): + query.Set("limit", "50") + case strings.HasSuffix(pathTemplate, "/mail"): + query.Set("limit", "50") + } + if len(query) == 0 { + return path + } + return path + "?" + query.Encode() +} + +func liveContractProbeSkipReason(pathTemplate string) string { + switch { + case strings.HasSuffix(pathTemplate, "/extmsg/bindings"), + strings.HasSuffix(pathTemplate, "/extmsg/groups"), + strings.HasSuffix(pathTemplate, "/extmsg/transcript"): + return "requires a real session/conversation identity" + case strings.HasSuffix(pathTemplate, "/orders/history"): + return "requires a scoped order name fixture" + default: + return "" + } +} + +func assertLiveContractSpec(t *testing.T, specBytes []byte) { + t.Helper() + var spec struct { + Components struct { + Schemas map[string]struct { + OneOf []map[string]string `json:"oneOf"` + Properties map[string]any `json:"properties"` + } `json:"schemas"` + } `json:"components"` + } + if err := json.Unmarshal(specBytes, &spec); err != nil { + t.Fatalf("decode OpenAPI spec: %v", err) + } + for _, field := range []string{"metadata", "parent"} { + if _, ok := spec.Components.Schemas["BeadCreateInputBody"].Properties[field]; !ok { + t.Fatalf("BeadCreateInputBody missing %q property", field) + } + } + if _, ok := spec.Components.Schemas["BeadUpdateBody"].Properties["parent"]; !ok { + t.Fatal("BeadUpdateBody missing parent property") + } + + var cityPayloadRefs []string + for _, branch := range spec.Components.Schemas["EventPayload"].OneOf { + ref := branch["$ref"] + if strings.Contains(ref, "City") { + cityPayloadRefs = append(cityPayloadRefs, ref) + } + } + if len(cityPayloadRefs) != 1 || !strings.Contains(cityPayloadRefs[0], "CityLifecyclePayload") { + t.Fatalf("EventPayload city lifecycle branches = %#v, want exactly CityLifecyclePayload", cityPayloadRefs) + } +} + +func containsString(items []string, want string) bool { + for _, item := range items { + if item == want { + return true + } + } + return false +} + +func beadListContains(items []beads.Bead, id string) bool { + for _, item := range items { + if item.ID == id { + return true + } + } + return false +} From c5f8eff97478969762ee9a310d032367dcace556 Mon Sep 17 00:00:00 2001 From: Chris Sells Date: Wed, 22 Apr 2026 12:05:47 -0700 Subject: [PATCH 07/85] api: fix MC bead graph projections --- internal/api/handler_beads.go | 93 +++++++++++++++++++++++ internal/api/handler_beads_graph_test.go | 83 ++++++++++++++++++++ internal/api/huma_handlers_beads.go | 21 ++--- internal/beads/bdstore.go | 85 ++++++++++++++++----- internal/beads/bdstore_test.go | 86 +++++++++++++++++++++ internal/beads/caching_store_reads.go | 23 +++++- internal/beads/caching_store_test.go | 35 +++++++++ test/integration/gc_live_contract_test.go | 41 +++++++++- 8 files changed, 435 insertions(+), 32 deletions(-) diff --git a/internal/api/handler_beads.go b/internal/api/handler_beads.go index 56ca76c37b..e4141241d8 100644 --- a/internal/api/handler_beads.go +++ b/internal/api/handler_beads.go @@ -232,6 +232,99 @@ type BeadGraphResponse struct { Deps []workflowDepResponse `json:"deps"` } +func collectBeadGraph(store beads.Store, root beads.Bead) ([]beads.Bead, []workflowDepResponse, error) { + graphBeads := make([]beads.Bead, 0, 1) + beadIndex := make(map[string]beads.Bead) + + upsert := func(b beads.Bead) { + if b.ID == "" { + return + } + if existing, ok := beadIndex[b.ID]; ok { + if existing.ParentID == "" && b.ParentID != "" { + existing.ParentID = b.ParentID + beadIndex[b.ID] = existing + for i := range graphBeads { + if graphBeads[i].ID == b.ID { + graphBeads[i].ParentID = b.ParentID + break + } + } + } + return + } + beadIndex[b.ID] = b + graphBeads = append(graphBeads, b) + } + upsert(root) + + metadataChildren, err := store.List(beads.ListQuery{ + Metadata: map[string]string{"gc.root_bead_id": root.ID}, + IncludeClosed: true, + }) + if err != nil { + return nil, nil, fmt.Errorf("listing metadata children for bead %q: %w", root.ID, err) + } + for _, child := range metadataChildren { + upsert(child) + } + + parentEdges := make([]workflowDepResponse, 0) + seenEdges := make(map[string]bool) + addParentEdge := func(parentID, childID string) { + if parentID == "" || childID == "" { + return + } + edge := workflowDepResponse{From: parentID, To: childID, Kind: "parent-child"} + key := edge.From + "|" + edge.To + "|" + edge.Kind + if seenEdges[key] { + return + } + seenEdges[key] = true + parentEdges = append(parentEdges, edge) + } + + for i := 0; i < len(graphBeads); i++ { + parent := graphBeads[i] + children, err := store.List(beads.ListQuery{ + ParentID: parent.ID, + IncludeClosed: true, + Sort: beads.SortCreatedAsc, + }) + if err != nil { + return nil, nil, fmt.Errorf("listing child beads for bead %q: %w", parent.ID, err) + } + for _, child := range children { + if child.ParentID == "" { + child.ParentID = parent.ID + } + addParentEdge(parent.ID, child.ID) + upsert(child) + } + } + + return graphBeads, parentEdges, nil +} + +func mergeWorkflowDeps(primary, extra []workflowDepResponse) []workflowDepResponse { + if len(extra) == 0 { + return primary + } + seen := make(map[string]bool, len(primary)+len(extra)) + for _, edge := range primary { + seen[edge.From+"|"+edge.To+"|"+edge.Kind] = true + } + for _, edge := range extra { + key := edge.From + "|" + edge.To + "|" + edge.Kind + if seen[key] { + continue + } + primary = append(primary, edge) + seen[key] = true + } + return primary +} + // beadPrefix extracts the alphabetic prefix from a bead ID (e.g., "ga" from "ga-5b8i"). func beadPrefix(id string) string { for i, c := range id { diff --git a/internal/api/handler_beads_graph_test.go b/internal/api/handler_beads_graph_test.go index 035c497560..b69f01f5cd 100644 --- a/internal/api/handler_beads_graph_test.go +++ b/internal/api/handler_beads_graph_test.go @@ -2,6 +2,7 @@ package api import ( "encoding/json" + "errors" "net/http" "net/http/httptest" "testing" @@ -92,6 +93,88 @@ func TestBeadGraphReturnsRootAndChildren(t *testing.T) { } } +func TestBeadGraphIncludesParentChildChildrenAndEdges(t *testing.T) { + state := newFakeState(t) + store := state.stores["myrig"] + h := newTestCityHandler(t, state) + + root, err := store.Create(beads.Bead{Title: "Root", Type: "feature"}) + if err != nil { + t.Fatalf("Create(root): %v", err) + } + child, err := store.Create(beads.Bead{Title: "Child", Type: "task", ParentID: root.ID}) + if err != nil { + t.Fatalf("Create(child): %v", err) + } + sibling, err := store.Create(beads.Bead{Title: "Sibling", Type: "bug", ParentID: root.ID}) + if err != nil { + t.Fatalf("Create(sibling): %v", err) + } + + rec, resp := getGraph(t, h, state, root.ID) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want %d, body: %s", rec.Code, http.StatusOK, rec.Body.String()) + } + beadIDs := map[string]bool{} + for _, b := range resp.Beads { + beadIDs[b.ID] = true + } + for _, id := range []string{root.ID, child.ID, sibling.ID} { + if !beadIDs[id] { + t.Fatalf("graph beads missing %s; got %#v", id, resp.Beads) + } + } + + edges := map[string]bool{} + for _, dep := range resp.Deps { + edges[dep.From+"|"+dep.To+"|"+dep.Kind] = true + } + for _, id := range []string{child.ID, sibling.ID} { + key := root.ID + "|" + id + "|parent-child" + if !edges[key] { + t.Fatalf("graph deps missing %s; got %#v", key, resp.Deps) + } + } +} + +func TestBeadGraphReturnsErrorWhenGraphListFails(t *testing.T) { + state := newFakeState(t) + base := state.stores["myrig"] + root, err := base.Create(beads.Bead{Title: "Root", Type: "feature"}) + if err != nil { + t.Fatalf("Create(root): %v", err) + } + state.stores["myrig"] = &failingBeadStore{ + Store: base, + listErr: errors.New("list failed"), + } + h := newTestCityHandler(t, state) + + rec, _ := getGraph(t, h, state, root.ID) + + if rec.Code != http.StatusInternalServerError { + t.Fatalf("status = %d, want %d, body: %s", rec.Code, http.StatusInternalServerError, rec.Body.String()) + } +} + +func TestBeadGraphReturnsErrorWhenDepListFails(t *testing.T) { + state := newFakeState(t) + base := state.stores["myrig"] + root, err := base.Create(beads.Bead{Title: "Root", Type: "feature"}) + if err != nil { + t.Fatalf("Create(root): %v", err) + } + state.stores["myrig"] = depListFailStore{Store: base} + h := newTestCityHandler(t, state) + + rec, _ := getGraph(t, h, state, root.ID) + + if rec.Code != http.StatusInternalServerError { + t.Fatalf("status = %d, want %d, body: %s", rec.Code, http.StatusInternalServerError, rec.Body.String()) + } +} + func TestBeadGraphReturnsDeps(t *testing.T) { state := newFakeState(t) store := state.stores["myrig"] diff --git a/internal/api/huma_handlers_beads.go b/internal/api/huma_handlers_beads.go index 3e7f7eb826..ec3918a3ac 100644 --- a/internal/api/huma_handlers_beads.go +++ b/internal/api/huma_handlers_beads.go @@ -181,25 +181,20 @@ func (s *Server) humaHandleBeadGraph(_ context.Context, input *BeadGraphInput) ( return nil, huma.Error404NotFound("bead " + rootID + " not found") } - all, err := foundStore.List(beads.ListQuery{ - Metadata: map[string]string{"gc.root_bead_id": rootID}, - IncludeClosed: true, - }) + graphBeads, parentEdges, err := collectBeadGraph(foundStore, root) if err != nil { return nil, huma.Error500InternalServerError(err.Error()) } - - graphBeads := []beads.Bead{root} - beadIndex := map[string]beads.Bead{root.ID: root} - for _, b := range all { - if b.ID == root.ID { - continue - } - graphBeads = append(graphBeads, b) + beadIndex := make(map[string]beads.Bead, len(graphBeads)) + for _, b := range graphBeads { beadIndex[b.ID] = b } - deps, _ := collectWorkflowDeps(foundStore, beadIndex) + deps, depPartial := collectWorkflowDeps(foundStore, beadIndex) + if depPartial { + return nil, huma.Error500InternalServerError("listing bead graph dependencies failed") + } + deps = mergeWorkflowDeps(deps, parentEdges) return &IndexOutput[BeadGraphResponse]{ Index: s.latestIndex(), diff --git a/internal/beads/bdstore.go b/internal/beads/bdstore.go index a620ba6322..b044621a64 100644 --- a/internal/beads/bdstore.go +++ b/internal/beads/bdstore.go @@ -290,21 +290,29 @@ func (m *StringMap) UnmarshalJSON(data []byte) error { // bdIssue is the JSON shape returned by bd CLI commands. We decode only the // fields Gas City cares about; all others are silently ignored. type bdIssue struct { - ID string `json:"id"` - Title string `json:"title"` - Status string `json:"status"` - IssueType string `json:"issue_type"` - Priority *int `json:"priority,omitempty"` - CreatedAt time.Time `json:"created_at"` - Assignee string `json:"assignee"` - From string `json:"from"` - ParentID string `json:"parent"` - Ref string `json:"ref"` - Needs []string `json:"needs"` - Description string `json:"description"` - Labels []string `json:"labels"` - Metadata StringMap `json:"metadata,omitempty"` - Dependencies []Dep `json:"dependencies,omitempty"` + ID string `json:"id"` + Title string `json:"title"` + Status string `json:"status"` + IssueType string `json:"issue_type"` + Priority *int `json:"priority,omitempty"` + CreatedAt time.Time `json:"created_at"` + Assignee string `json:"assignee"` + From string `json:"from"` + ParentID string `json:"parent"` + Ref string `json:"ref"` + Needs []string `json:"needs"` + Description string `json:"description"` + Labels []string `json:"labels"` + Metadata StringMap `json:"metadata,omitempty"` + Dependencies []bdIssueDep `json:"dependencies,omitempty"` +} + +type bdIssueDep struct { + IssueID string `json:"issue_id"` + DependsOnID string `json:"depends_on_id"` + Type string `json:"type"` + ID string `json:"id"` + DependencyType string `json:"dependency_type"` } // parseIssuesTolerant unmarshals a JSON array of bdIssue objects, skipping @@ -354,6 +362,16 @@ func (b *bdIssue) toBead() Bead { if from == "" && b.Metadata != nil { from = b.Metadata["from"] } + deps := b.normalizedDependencies() + parentID := b.ParentID + if parentID == "" { + for _, dep := range deps { + if dep.IssueID == b.ID && dep.Type == "parent-child" { + parentID = dep.DependsOnID + break + } + } + } return Bead{ ID: b.ID, Title: b.Title, @@ -363,14 +381,47 @@ func (b *bdIssue) toBead() Bead { CreatedAt: b.CreatedAt.Truncate(time.Second), Assignee: b.Assignee, From: from, - ParentID: b.ParentID, + ParentID: parentID, Ref: b.Ref, Needs: b.Needs, Description: b.Description, Labels: b.Labels, Metadata: b.Metadata, - Dependencies: append([]Dep(nil), b.Dependencies...), + Dependencies: deps, + } +} + +func (b *bdIssue) normalizedDependencies() []Dep { + if len(b.Dependencies) == 0 { + return nil + } + deps := make([]Dep, 0, len(b.Dependencies)) + for _, raw := range b.Dependencies { + issueID := strings.TrimSpace(raw.IssueID) + if issueID == "" && raw.ID != "" { + issueID = b.ID + } + dependsOnID := strings.TrimSpace(raw.DependsOnID) + if dependsOnID == "" { + dependsOnID = strings.TrimSpace(raw.ID) + } + depType := strings.TrimSpace(raw.Type) + if depType == "" { + depType = strings.TrimSpace(raw.DependencyType) + } + if issueID == "" || dependsOnID == "" { + continue + } + if depType == "" { + depType = "blocks" + } + deps = append(deps, Dep{ + IssueID: issueID, + DependsOnID: dependsOnID, + Type: depType, + }) } + return deps } // isBdNotFound returns true if the error from bd CLI indicates a "not found" condition. diff --git a/internal/beads/bdstore_test.go b/internal/beads/bdstore_test.go index 8a52be0007..402f15d749 100644 --- a/internal/beads/bdstore_test.go +++ b/internal/beads/bdstore_test.go @@ -1047,6 +1047,92 @@ func TestBdStoreDepAddParentChildAlreadyParentedIsNoop(t *testing.T) { } } +func TestBdStoreGetNormalizesShowStyleDependencies(t *testing.T) { + runner := fakeRunner(map[string]struct { + out []byte + err error + }{ + `bd show --json bd-child`: { + out: []byte(`[ + { + "id":"bd-child", + "title":"child", + "status":"open", + "issue_type":"task", + "created_at":"2025-01-15T10:30:00Z", + "dependencies":[ + { + "id":"bd-parent", + "title":"parent", + "status":"open", + "issue_type":"task", + "dependency_type":"parent-child" + }, + { + "issue_id":"", + "depends_on_id":"", + "type":"" + } + ], + "parent":"bd-parent" + } + ]`), + }, + }) + s := beads.NewBdStore("/city", runner) + + got, err := s.Get("bd-child") + if err != nil { + t.Fatalf("Get: %v", err) + } + if len(got.Dependencies) != 1 { + t.Fatalf("Dependencies = %#v, want one normalized dependency", got.Dependencies) + } + dep := got.Dependencies[0] + if dep.IssueID != "bd-child" || dep.DependsOnID != "bd-parent" || dep.Type != "parent-child" { + t.Fatalf("dependency = %+v, want child -> parent parent-child", dep) + } +} + +func TestBdStoreListInfersParentFromParentChildDependency(t *testing.T) { + runner := fakeRunner(map[string]struct { + out []byte + err error + }{ + `bd list --json --label=mc-live-contract --include-infra --include-gates --limit 50`: { + out: []byte(`[ + { + "id":"bd-child", + "title":"child", + "status":"open", + "issue_type":"task", + "created_at":"2025-01-15T10:30:00Z", + "labels":["mc-live-contract"], + "dependencies":[ + { + "issue_id":"bd-child", + "depends_on_id":"bd-parent", + "type":"parent-child" + } + ] + } + ]`), + }, + }) + s := beads.NewBdStore("/city", runner) + + got, err := s.List(beads.ListQuery{Label: "mc-live-contract", Limit: 50}) + if err != nil { + t.Fatalf("List: %v", err) + } + if len(got) != 1 { + t.Fatalf("List returned %d beads, want 1", len(got)) + } + if got[0].ParentID != "bd-parent" { + t.Fatalf("ParentID = %q, want bd-parent", got[0].ParentID) + } +} + func TestBdStoreCreateNoLabelsNoParent(t *testing.T) { var gotArgs []string runner := func(_, _ string, args ...string) ([]byte, error) { diff --git a/internal/beads/caching_store_reads.go b/internal/beads/caching_store_reads.go index 0f69790278..88fdc222a4 100644 --- a/internal/beads/caching_store_reads.go +++ b/internal/beads/caching_store_reads.go @@ -15,7 +15,11 @@ func (c *CachingStore) List(query ListQuery) ([]Bead, error) { return nil, fmt.Errorf("listing beads: %w", ErrQueryRequiresScan) } if query.Live || query.ParentID != "" { - return c.backing.List(query) + items, err := c.backing.List(query) + if err == nil { + c.refreshCachedBeads(items) + } + return items, err } c.mu.RLock() @@ -76,6 +80,23 @@ func (c *CachingStore) List(query ListQuery) ([]Bead, error) { return c.backing.List(query) } +func (c *CachingStore) refreshCachedBeads(items []Bead) { + if len(items) == 0 { + return + } + c.mu.Lock() + defer c.mu.Unlock() + if c.state != cacheLive && c.state != cachePartial { + return + } + for _, item := range items { + c.beads[item.ID] = cloneBead(item) + delete(c.dirty, item.ID) + } + c.markFreshLocked(time.Now()) + c.updateStatsLocked() +} + // ListOpen returns all cached beads, optionally filtered by status. func (c *CachingStore) ListOpen(status ...string) ([]Bead, error) { query := ListQuery{AllowScan: true} diff --git a/internal/beads/caching_store_test.go b/internal/beads/caching_store_test.go index 1fb7a0ce2c..e49b294587 100644 --- a/internal/beads/caching_store_test.go +++ b/internal/beads/caching_store_test.go @@ -154,6 +154,41 @@ func TestCachingStoreParentListUsesBackingStore(t *testing.T) { } } +func TestCachingStoreParentListRefreshesCachedChildren(t *testing.T) { + mem := beads.NewMemStore() + parent, err := mem.Create(beads.Bead{Title: "parent"}) + if err != nil { + t.Fatalf("Create(parent): %v", err) + } + child, err := mem.Create(beads.Bead{Title: "child", Labels: []string{"mc-live-contract"}}) + if err != nil { + t.Fatalf("Create(child): %v", err) + } + cs := beads.NewCachingStoreForTest(mem, nil) + if err := cs.Prime(context.Background()); err != nil { + t.Fatalf("Prime: %v", err) + } + if err := mem.Update(child.ID, beads.UpdateOpts{ParentID: &parent.ID}); err != nil { + t.Fatalf("backing Update(parent): %v", err) + } + + children, err := cs.List(beads.ListQuery{ParentID: parent.ID}) + if err != nil { + t.Fatalf("List(parent): %v", err) + } + if len(children) != 1 || children[0].ParentID != parent.ID { + t.Fatalf("children = %#v, want refreshed parent %s", children, parent.ID) + } + + labeled, err := cs.List(beads.ListQuery{Label: "mc-live-contract"}) + if err != nil { + t.Fatalf("List(label): %v", err) + } + if len(labeled) != 1 || labeled[0].ParentID != parent.ID { + t.Fatalf("cached label result = %#v, want parent %s", labeled, parent.ID) + } +} + func TestCachingStoreUpdateReflectsWriteIntentWhenImmediateReadIsStale(t *testing.T) { mem := beads.NewMemStore() original, err := mem.Create(beads.Bead{ diff --git a/test/integration/gc_live_contract_test.go b/test/integration/gc_live_contract_test.go index 85eaea24a8..77c1a7e3e1 100644 --- a/test/integration/gc_live_contract_test.go +++ b/test/integration/gc_live_contract_test.go @@ -238,11 +238,23 @@ func TestGCLiveContract_BeadsAndEvents(t *testing.T) { t.Fatalf("deps children = %#v, want child %s", deps.Children, childBead.ID) } graph := liveContractJSON[struct { - Beads []beads.Bead `json:"beads"` + Beads []beads.Bead `json:"beads"` + Deps []contractGraphDep `json:"deps"` }](t, baseURL, validator, http.MethodGet, cityBase+"/beads/graph/"+url.PathEscape(rootBead.ID), nil, http.StatusOK) if !beadListContains(graph.Beads, rootBead.ID) { t.Fatalf("graph beads = %#v, want root %s", graph.Beads, rootBead.ID) } + if !beadListContains(graph.Beads, childBead.ID) { + t.Fatalf("graph beads = %#v, want child %s", graph.Beads, childBead.ID) + } + if !beadListContains(graph.Beads, siblingBead.ID) { + t.Fatalf("graph beads = %#v, want sibling %s", graph.Beads, siblingBead.ID) + } + for _, childID := range []string{childBead.ID, siblingBead.ID} { + if !liveContractGraphHasEdge(graph.Deps, rootBead.ID, childID, "parent-child") { + t.Fatalf("graph deps = %#v, want parent-child edge %s -> %s", graph.Deps, rootBead.ID, childID) + } + } list := liveContractJSON[struct { Items []beads.Bead `json:"items"` Total int `json:"total"` @@ -250,6 +262,9 @@ func TestGCLiveContract_BeadsAndEvents(t *testing.T) { if list.Total < 3 || !beadListContains(list.Items, rootBead.ID) || !beadListContains(list.Items, siblingBead.ID) { t.Fatalf("filtered beads = %+v, want root and sibling", list) } + if listedSibling, ok := findLiveContractBead(list.Items, siblingBead.ID); ok && listedSibling.ParentID != rootBead.ID { + t.Fatalf("filtered sibling parent = %q, want %q", listedSibling.ParentID, rootBead.ID) + } waitForLiveContractEvent(t, baseURL, validator, cityBase+"/events", cityName, "city.ready", 10*time.Second) liveContractJSON[contractEventList](t, baseURL, validator, http.MethodGet, "/v0/events?limit=50", nil, http.StatusOK) @@ -307,6 +322,12 @@ type contractRig struct { Path string `json:"path"` } +type contractGraphDep struct { + From string `json:"from"` + To string `json:"to"` + Kind string `json:"kind"` +} + func liveContractValidator(t *testing.T, specBytes []byte) openapivalidator.Validator { t.Helper() doc, err := libopenapi.NewDocument(specBytes) @@ -693,3 +714,21 @@ func beadListContains(items []beads.Bead, id string) bool { } return false } + +func findLiveContractBead(items []beads.Bead, id string) (beads.Bead, bool) { + for _, item := range items { + if item.ID == id { + return item, true + } + } + return beads.Bead{}, false +} + +func liveContractGraphHasEdge(items []contractGraphDep, from, to, kind string) bool { + for _, item := range items { + if item.From == from && item.To == to && item.Kind == kind { + return true + } + } + return false +} From 0f600eace995426f33f66790e7bb2cf270ccbdeb Mon Sep 17 00:00:00 2001 From: Chris Sells Date: Wed, 22 Apr 2026 13:53:52 -0700 Subject: [PATCH 08/85] docs: move API architecture guidance into engdocs --- AGENTS.md | 19 +++++----- cmd/gc/cityinit_impl.go | 2 +- cmd/gc/dashboard/web/openapi-ts.config.ts | 2 +- cmd/gc/dashboard/web/src/sse.ts | 2 +- .../architecture/api-control-plane.md | 35 +++++++++++-------- engdocs/architecture/index.md | 11 +++--- {specs => engdocs/contributors}/huma-usage.md | 13 ++++--- engdocs/contributors/index.md | 2 ++ internal/api/genclient/doc.go | 2 +- internal/api/huma_handlers_supervisor.go | 2 +- internal/api/huma_optional_param.go | 6 ++-- internal/api/huma_types_formulas.go | 4 +-- internal/cityinit/cityinit.go | 2 +- 13 files changed, 59 insertions(+), 43 deletions(-) rename specs/architecture.md => engdocs/architecture/api-control-plane.md (96%) rename {specs => engdocs/contributors}/huma-usage.md (96%) diff --git a/AGENTS.md b/AGENTS.md index 49c2b34ff5..dccabf9b92 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,8 +23,8 @@ becomes one configuration among many. has `*_test.go` files next to the code. Integration tests that need real infrastructure (tmux, filesystem) go in `test/` with build tags. -**The spec is a reference, not a blueprint.** When the DX conflicts -with the spec, DX wins. We update the spec to match. +**The architecture docs are a reference, not a blueprint.** When the DX +conflicts with the docs, DX wins. We update the docs to match. ## Architecture @@ -89,9 +89,10 @@ Capabilities activate progressively via config presence. | 7 | Orders | | 8 | Full orchestration | -## Architecture specs +## Architecture docs -Read **`specs/architecture.md`** before touching: +Read **`engdocs/architecture/api-control-plane.md`** and +**`engdocs/contributors/huma-usage.md`** before touching: - `internal/api/` (HTTP + SSE API layer) - `cmd/gc/` (CLI) — especially anything that constructs events, @@ -104,7 +105,7 @@ Read **`specs/architecture.md`** before touching: `cmd/gc/dashboard/web/src/generated/` Load-bearing invariants enforced by CI (violating any fails the -build; full rationale is in the spec): +build; full rationale is in the architecture docs): - **Object model at the center.** `internal/{beads, mail, convoy, formula, agent, events, session, sling, ...}` is the canonical @@ -113,9 +114,9 @@ build; full rationale is in the spec): domain logic. - **Typed wire.** No hand-written JSON on any HTTP or SSE wire path; no `map[string]any` or `json.RawMessage` on wire types - (two documented exceptions live in the spec). All endpoints are - Huma-registered; the OpenAPI spec is generated, never - hand-written (`TestOpenAPISpecInSync`). + (documented exceptions live in the API control-plane doc). All + endpoints are Huma-registered; the OpenAPI spec is generated, + never hand-written (`TestOpenAPISpecInSync`). - **Typed events.** Every constant in `events.KnownEventTypes` must have a registered payload via `events.RegisterPayload(constant, sample)`. Use @@ -131,7 +132,7 @@ These decisions are final. Do not revisit them. `city.toml`, `.gc/` runtime state, and `rigs/` infrastructure. - **Fresh binary, not a Gas Town fork.** We build `gc` from scratch. - **TOML for config.** `pack.toml` (definition) and `city.toml` (deployment) are the config files. -- **Tutorials win over spec.** When the spec disagrees, we update the spec. +- **Tutorials win over architecture docs.** When the docs disagree, we update the docs. - **No premature abstraction.** Don't build interfaces until two implementations exist. - **Mayor is overseer, not worker.** The mayor plans; coding agents work. diff --git a/cmd/gc/cityinit_impl.go b/cmd/gc/cityinit_impl.go index 64262bc173..4cef1a3656 100644 --- a/cmd/gc/cityinit_impl.go +++ b/cmd/gc/cityinit_impl.go @@ -8,7 +8,7 @@ package main // // The long-term plan is to move doInit/finalizeInit and their // helpers into internal/cityinit so the domain logic physically -// lives in the object model (per specs/architecture.md §1). This +// lives in the object model (per engdocs/architecture/api-control-plane.md §1). This // bridge is the minimum viable unblocker: the HTTP API no longer // shells out, both surfaces drive the same in-process code path via // the same typed contract, and the refactor of where the body lives diff --git a/cmd/gc/dashboard/web/openapi-ts.config.ts b/cmd/gc/dashboard/web/openapi-ts.config.ts index e191f2f22a..1ccb2ab929 100644 --- a/cmd/gc/dashboard/web/openapi-ts.config.ts +++ b/cmd/gc/dashboard/web/openapi-ts.config.ts @@ -6,7 +6,7 @@ import { defineConfig } from "@hey-api/openapi-ts"; // path construction, request/response typing, event discrimination, // retry, and auth headers all flow through generated code. // -// See specs/architecture.md §6 "Tooling landscape" for the rationale. +// See engdocs/architecture/api-control-plane.md §6 "Tooling landscape" for the rationale. export default defineConfig({ input: "../../../../internal/api/openapi.json", output: { diff --git a/cmd/gc/dashboard/web/src/sse.ts b/cmd/gc/dashboard/web/src/sse.ts index 37c764321b..98995e3eae 100644 --- a/cmd/gc/dashboard/web/src/sse.ts +++ b/cmd/gc/dashboard/web/src/sse.ts @@ -15,7 +15,7 @@ // narrow on actual runtime shape and drop malformed frames with a // UI error report — same discipline as the pre-migration decoder. // -// See specs/architecture.md §6 "Tooling landscape" (TypeScript +// See engdocs/architecture/api-control-plane.md §6 "Tooling landscape" (TypeScript // section) for why hey-api is the SSE tool even though openapi-fetch // still drives REST. diff --git a/specs/architecture.md b/engdocs/architecture/api-control-plane.md similarity index 96% rename from specs/architecture.md rename to engdocs/architecture/api-control-plane.md index d343af97bd..e0dd0b69da 100644 --- a/specs/architecture.md +++ b/engdocs/architecture/api-control-plane.md @@ -1,11 +1,16 @@ -# Gas City Architecture +--- +title: "API Control Plane" +description: "Current-state architecture for Gas City's CLI, HTTP, SSE, generated client, and typed-wire contract." +--- -This spec captures the architectural invariants Gas City has -converged on. It is a normative document: future contributions that -violate these invariants are wrong unless a conscious decision in -this spec changes. Plans in `plans/archive/` describe the journeys -that produced these invariants; this spec describes the -destination. +> Last verified against code: 2026-04-22 + +This architecture doc captures the API control-plane invariants Gas +City has converged on. It is normative current-state documentation: +future contributions that violate these invariants are wrong unless a +conscious decision updates this document. Plans in `plans/archive/` +describe the journeys that produced these invariants; this document +describes the destination. Two architectural themes run through everything below: @@ -14,7 +19,7 @@ Two architectural themes run through everything below: surfaces. 2. **Typed data end-to-end.** Go structs with annotations drive a generated OpenAPI 3.1 contract; every wire-visible shape appears - in the spec; consumers in any language code against the same + in the OpenAPI spec; consumers in any language code against the same contract. Zero opacity on the wire. ## 1. The object model @@ -170,7 +175,7 @@ not a second description — it is the same mechanism applied at one layer up, and the OpenAPI spec that results still describes every operation's full contract. See §3.5.2. Patterns and Huma quirks that inform these helpers are documented in -[`specs/huma-usage.md`](./huma-usage.md). +[Huma Usage Notes](../contributors/huma-usage.md). ### 3.2 Spec is generated, never hand-written @@ -634,7 +639,7 @@ type guard in the SPA. ## 8. Maintenance rule -Every file-path citation in this spec is load-bearing. If you +Every file-path citation in this document is load-bearing. If you rename or remove a cited symbol (`events.KnownEventTypes`, `EventPayloadUnion`, `TestEveryKnownEventTypeHasRegisteredPayload`, `cmd/gc/apiroute.go:apiClient()`, `addMutationCSRFParam`, @@ -642,13 +647,13 @@ rename or remove a cited symbol (`events.KnownEventTypes`, `OptionalParam`, `cityinit.Initializer`, `cityinit.InitRequest`, `cityinit.InitResult`, `cityinit.UnregisterRequest`, `cityinit.UnregisterResult`, `cityinit.ErrNotRegistered`, -`TransientCityEventSource`, etc.), **update this spec in the same -commit**. A stale spec is worse than no spec — it misleads future -agents about what invariants hold. +`TransientCityEventSource`, etc.), **update this document in the same +commit**. Stale architecture docs are worse than no docs — they +mislead future agents about what invariants hold. Framework-specific patterns and Huma quirks are captured in -[`specs/huma-usage.md`](./huma-usage.md); update that file in the -same commit when you touch any of: `OptionalParam`, +[Huma Usage Notes](../contributors/huma-usage.md); update that file +in the same commit when you touch any of: `OptionalParam`, `addMutationCSRFParam`, `registerFrameworkHeaders`, `sseResponseHeaders`, the SSE hand-writing zone, or the `cityPost`/`cityRegister` helper family. diff --git a/engdocs/architecture/index.md b/engdocs/architecture/index.md index d3561bdb0e..25fd3a19a8 100644 --- a/engdocs/architecture/index.md +++ b/engdocs/architecture/index.md @@ -48,9 +48,12 @@ Each is provably composable from the primitives. ### Infrastructure -12. **[Controller](./controller.md)** — the main loop: config watch, +12. **[API Control Plane](./api-control-plane.md)** — CLI/API projections, + typed HTTP + SSE wire contract, generated clients, and event payload + registry +13. **[Controller](./controller.md)** — the main loop: config watch, reconciliation tick, order dispatch -13. **[Orders](./orders.md)** — trigger-conditioned formula/exec +14. **[Orders](./orders.md)** — trigger-conditioned formula/exec dispatch, rig-scoped labels ### End-to-End Traces @@ -58,9 +61,9 @@ Each is provably composable from the primitives. These trace a concrete operation through all layers. The most effective way to understand how the system fits together. -14. **[Life of a Bead](./life-of-a-bead.md)** — create → hook → claim → +15. **[Life of a Bead](./life-of-a-bead.md)** — create → hook → claim → execute → close -15. **[Life of a Molecule](./life-of-a-molecule.md)** — formula parse → +16. **[Life of a Molecule](./life-of-a-molecule.md)** — formula parse → dispatch → molecule create → step execution → completion ## Document Types diff --git a/specs/huma-usage.md b/engdocs/contributors/huma-usage.md similarity index 96% rename from specs/huma-usage.md rename to engdocs/contributors/huma-usage.md index 7bb493beb6..b8ab77058f 100644 --- a/specs/huma-usage.md +++ b/engdocs/contributors/huma-usage.md @@ -1,4 +1,7 @@ -# Huma usage notes +--- +title: "Huma Usage Notes" +description: "Contributor notes for Huma v2 patterns, quirks, and generated OpenAPI behavior in Gas City's HTTP and SSE API." +--- Gas City's HTTP + SSE control plane is built on Huma v2 (`github.com/danielgtaylor/huma/v2`). This document captures the @@ -8,7 +11,7 @@ it from the framework source or stumbling into the same traps. Every pattern below is load-bearing in the current implementation; removing one breaks a specific invariant described in -`specs/architecture.md`. +[API Control Plane](../architecture/api-control-plane.md). ## 1. Presence detection for query parameters @@ -64,7 +67,8 @@ APIs must design around two-state (`IsSet && value != ""`). This constraint is intrinsic to the framework, not a Gas City choice. Do not work around it by reading raw URL values in a -Resolver (that's a `specs/architecture.md` §3.5.1 violation). +Resolver (that's an [API Control Plane](../architecture/api-control-plane.md) +§3.5.1 violation). ## 2. Pointer query params panic; Resolvers cannot rescue them @@ -274,7 +278,8 @@ The same trick applies to `Summary`. protocol framing. It hand-writes `id:` / `event:` / `data:` / blank-line separators around a call to `encoder.Encode(payload)` where payload is a typed, schema-registered struct. This is the -§3.4 carve-out described in the architecture spec. +§3.4 carve-out described in the +[API Control Plane](../architecture/api-control-plane.md). Three related hand-written helpers: diff --git a/engdocs/contributors/index.md b/engdocs/contributors/index.md index 38fe7ad999..a28236b841 100644 --- a/engdocs/contributors/index.md +++ b/engdocs/contributors/index.md @@ -9,6 +9,8 @@ description: The shortest path for new contributors to get productive in Gas Cit - [Architecture Overview](../architecture/index.md) - [Primitive Test](primitive-test.md) - [Reconciler Debugging](reconciler-debugging.md) +- [Huma Usage Notes](huma-usage.md) when touching `internal/api/`, + OpenAPI generation, or SSE registration - [`CONTRIBUTING.md`](https://github.com/gastownhall/gascity/blob/main/CONTRIBUTING.md) - [`TESTING.md`](https://github.com/gastownhall/gascity/blob/main/TESTING.md) diff --git a/internal/api/genclient/doc.go b/internal/api/genclient/doc.go index 257ab6a001..9b1fef3812 100644 --- a/internal/api/genclient/doc.go +++ b/internal/api/genclient/doc.go @@ -2,7 +2,7 @@ // API. It is produced by `cmd/gen-client` from the live OpenAPI 3.0 // downgrade of the server's spec, processed through oapi-codegen v2.6.0. // -// See specs/architecture.md §2 "The generated Go client" for the +// See engdocs/architecture/api-control-plane.md §2 "The generated Go client" for the // three legitimate in-tree consumers of this package // (internal/api/client.go for CLI mutation coordination, // cmd/gc/cmd_events.go for direct event read/stream access, and diff --git a/internal/api/huma_handlers_supervisor.go b/internal/api/huma_handlers_supervisor.go index 567784b1b1..5aba1edd82 100644 --- a/internal/api/huma_handlers_supervisor.go +++ b/internal/api/huma_handlers_supervisor.go @@ -337,7 +337,7 @@ func (sm *SupervisorMux) humaHandleProviderReadiness(ctx context.Context, input // until finalize completes exceeds reasonable client timeouts // (MC's harness hit 120s). The fast scaffold+register path takes // seconds; the async completion contract via SSE is the right shape -// for a long-running operation. See specs/architecture.md §1–§2 on +// for a long-running operation. See engdocs/architecture/api-control-plane.md §1–§2 on // the object model + typed events; §4 on the event registry. func (sm *SupervisorMux) humaHandleCityCreate(ctx context.Context, input *SupervisorCityCreateInput) (*SupervisorCityCreateOutput, error) { dir := input.Body.Dir diff --git a/internal/api/huma_optional_param.go b/internal/api/huma_optional_param.go index 13f47baef4..0bad36a341 100644 --- a/internal/api/huma_optional_param.go +++ b/internal/api/huma_optional_param.go @@ -22,9 +22,9 @@ package api // // The spec emits the underlying T's schema (not the wrapper's), so the // wire contract is identical to a plain query:"..." field: the only -// difference is server-side presence detection. No architecture.md -// §3.5.1 violation — the handler does not read undeclared URL keys, -// only its own declared field. +// difference is server-side presence detection. This stays within +// engdocs/architecture/api-control-plane.md §3.5.1: the handler does +// not read undeclared URL keys, only its own declared field. import ( "reflect" diff --git a/internal/api/huma_types_formulas.go b/internal/api/huma_types_formulas.go index 5e5101a70a..2dd2627b8f 100644 --- a/internal/api/huma_types_formulas.go +++ b/internal/api/huma_types_formulas.go @@ -138,7 +138,7 @@ type FormulaRunsInput struct { // their defaults. Callers that need to supply variable values use // POST /v0/city/{cityName}/formulas/{name}/preview (FormulaPreviewInput) // so the variable dictionary is a spec-visible typed body rather than -// a dynamic wildcard query scheme. See architecture.md §3.5.1. +// a dynamic wildcard query scheme. See engdocs/architecture/api-control-plane.md §3.5.1. type FormulaDetailInput struct { CityScope Name string `path:"name" doc:"Formula name."` @@ -170,7 +170,7 @@ func (i *FormulaDetailInput) Resolve(ctx huma.Context, _ *huma.PathBuffer) []err // input surface spec-visible. A prior revision accepted dynamic // var.* query parameters via a huma.Resolver; that scheme was // removed because OpenAPI 3.1 cannot describe wildcard query keys. -// See architecture.md §3.5.1. +// See engdocs/architecture/api-control-plane.md §3.5.1. type FormulaPreviewBody struct { ScopeKind string `json:"scope_kind,omitempty" doc:"Scope kind (city or rig)."` ScopeRef string `json:"scope_ref,omitempty" doc:"Scope reference."` diff --git a/internal/cityinit/cityinit.go b/internal/cityinit/cityinit.go index 0d7fba4d9d..4ba0ccc236 100644 --- a/internal/cityinit/cityinit.go +++ b/internal/cityinit/cityinit.go @@ -12,7 +12,7 @@ // // A follow-up refactor will physically move the scaffold/finalize // body into this package so the domain logic lives in internal/ -// (per specs/architecture.md §1). Until then, injecting the +// (per engdocs/architecture/api-control-plane.md §1). Until then, injecting the // implementation from cmd/gc at startup preserves the architectural // intent that "the CLI and the HTTP API are projections over the // shared object model" — both surfaces drive the same code path via From 99e419835b6e38919c3dcf78a4d791d3bc4ed408 Mon Sep 17 00:00:00 2001 From: Chris Sells Date: Wed, 22 Apr 2026 14:16:53 -0700 Subject: [PATCH 09/85] test: cover GC supervisor city API paths --- cmd/gc/api_state_test.go | 242 ++++++++++++++++++ cmd/gc/city_registry_test.go | 77 ++++++ cmd/gc/cityinit_impl_test.go | 233 +++++++++++++++++ cmd/gc/cmd_supervisor_city_test.go | 29 +++ internal/api/huma_handlers_supervisor_test.go | 167 ++++++++++++ 5 files changed, 748 insertions(+) create mode 100644 cmd/gc/cityinit_impl_test.go diff --git a/cmd/gc/api_state_test.go b/cmd/gc/api_state_test.go index b9e3854d16..34781b8af2 100644 --- a/cmd/gc/api_state_test.go +++ b/cmd/gc/api_state_test.go @@ -9,6 +9,7 @@ import ( "sync" "testing" + "github.com/gastownhall/gascity/internal/api" "github.com/gastownhall/gascity/internal/beads" "github.com/gastownhall/gascity/internal/config" "github.com/gastownhall/gascity/internal/configedit" @@ -714,6 +715,247 @@ func TestControllerStateMutationsPokeController(t *testing.T) { } }, }, + { + name: "enable order", + mutate: func(cs *controllerState) error { + return cs.EnableOrder("nightly", "rig1") + }, + verify: func(t *testing.T, cfg *config.City) { + t.Helper() + if len(cfg.Orders.Overrides) != 1 || cfg.Orders.Overrides[0].Name != "nightly" || cfg.Orders.Overrides[0].Rig != "rig1" { + t.Fatalf("order overrides = %+v, want nightly/rig1", cfg.Orders.Overrides) + } + if cfg.Orders.Overrides[0].Enabled == nil || !*cfg.Orders.Overrides[0].Enabled { + t.Fatalf("order override enabled = %v, want true", cfg.Orders.Overrides[0].Enabled) + } + }, + }, + { + name: "disable order", + mutate: func(cs *controllerState) error { + return cs.DisableOrder("nightly", "rig1") + }, + verify: func(t *testing.T, cfg *config.City) { + t.Helper() + if len(cfg.Orders.Overrides) != 1 || cfg.Orders.Overrides[0].Enabled == nil || *cfg.Orders.Overrides[0].Enabled { + t.Fatalf("order overrides = %+v, want disabled nightly override", cfg.Orders.Overrides) + } + }, + }, + { + name: "create agent", + mutate: func(cs *controllerState) error { + return cs.CreateAgent(config.Agent{Name: "helper", Dir: "rig1", Provider: "codex"}) + }, + verify: func(t *testing.T, cfg *config.City) { + t.Helper() + if len(cfg.Agents) != 2 { + t.Fatalf("agents = %+v, want two", cfg.Agents) + } + if cfg.Agents[1].QualifiedName() != "rig1/helper" || cfg.Agents[1].Provider != "codex" { + t.Fatalf("created agent = %+v, want rig1/helper with codex provider", cfg.Agents[1]) + } + }, + }, + { + name: "update agent", + mutate: func(cs *controllerState) error { + return cs.UpdateAgent("rig1/worker", api.AgentUpdate{Provider: "codex", Scope: "rig", Suspended: boolPtr(true)}) + }, + verify: func(t *testing.T, cfg *config.City) { + t.Helper() + if cfg.Agents[0].Provider != "codex" || cfg.Agents[0].Scope != "rig" || !cfg.Agents[0].Suspended { + t.Fatalf("updated agent = %+v, want provider/scope/suspended", cfg.Agents[0]) + } + }, + }, + { + name: "delete agent", + mutate: func(cs *controllerState) error { + return cs.DeleteAgent("rig1/worker") + }, + verify: func(t *testing.T, cfg *config.City) { + t.Helper() + if len(cfg.Agents) != 0 { + t.Fatalf("agents = %+v, want none", cfg.Agents) + } + }, + }, + { + name: "create rig", + mutate: func(cs *controllerState) error { + return cs.CreateRig(config.Rig{Name: "rig2", Path: t.TempDir(), Prefix: "r2"}) + }, + verify: func(t *testing.T, cfg *config.City) { + t.Helper() + if len(cfg.Rigs) != 2 { + t.Fatalf("rigs = %+v, want two", cfg.Rigs) + } + if cfg.Rigs[1].Name != "rig2" || cfg.Rigs[1].Prefix != "r2" { + t.Fatalf("created rig = %+v, want rig2/r2", cfg.Rigs[1]) + } + }, + }, + { + name: "update rig", + mutate: func(cs *controllerState) error { + return cs.UpdateRig("rig1", api.RigUpdate{Path: t.TempDir(), Prefix: "rg", Suspended: boolPtr(true)}) + }, + verify: func(t *testing.T, cfg *config.City) { + t.Helper() + if cfg.Rigs[0].Prefix != "rg" || !cfg.Rigs[0].Suspended { + t.Fatalf("updated rig = %+v, want prefix/suspended", cfg.Rigs[0]) + } + }, + }, + { + name: "delete rig", + mutate: func(cs *controllerState) error { + return cs.DeleteRig("rig1") + }, + verify: func(t *testing.T, cfg *config.City) { + t.Helper() + if len(cfg.Rigs) != 0 || len(cfg.Agents) != 0 { + t.Fatalf("config after DeleteRig: rigs=%+v agents=%+v, want none", cfg.Rigs, cfg.Agents) + } + }, + }, + { + name: "create provider", + mutate: func(cs *controllerState) error { + return cs.CreateProvider("codex-local", config.ProviderSpec{Command: "codex", PromptMode: "arg"}) + }, + verify: func(t *testing.T, cfg *config.City) { + t.Helper() + spec, ok := cfg.Providers["codex-local"] + if !ok || spec.Command != "codex" || spec.PromptMode != "arg" { + t.Fatalf("providers = %+v, want codex-local provider", cfg.Providers) + } + }, + }, + { + name: "update provider", + initial: func(cfg *config.City) { + cfg.Providers = map[string]config.ProviderSpec{"codex-local": {Command: "codex"}} + }, + mutate: func(cs *controllerState) error { + return cs.UpdateProvider("codex-local", api.ProviderUpdate{ + DisplayName: stringPtr("Codex Local"), + Command: stringPtr("codex-wrapper"), + Args: []string{"--quiet"}, + PromptMode: stringPtr("flag"), + PromptFlag: stringPtr("--prompt"), + ReadyDelayMs: intPtr(25), + Env: map[string]string{"GC_TEST": "1"}, + }) + }, + verify: func(t *testing.T, cfg *config.City) { + t.Helper() + spec := cfg.Providers["codex-local"] + if spec.DisplayName != "Codex Local" || spec.Command != "codex-wrapper" || spec.PromptMode != "flag" || spec.PromptFlag != "--prompt" || spec.ReadyDelayMs != 25 { + t.Fatalf("updated provider = %+v, want scalar updates", spec) + } + if len(spec.Args) != 1 || spec.Args[0] != "--quiet" || spec.Env["GC_TEST"] != "1" { + t.Fatalf("updated provider args/env = args:%+v env:%+v, want replacement args and merged env", spec.Args, spec.Env) + } + }, + }, + { + name: "delete provider", + initial: func(cfg *config.City) { + cfg.Providers = map[string]config.ProviderSpec{"codex-local": {Command: "codex"}} + }, + mutate: func(cs *controllerState) error { + return cs.DeleteProvider("codex-local") + }, + verify: func(t *testing.T, cfg *config.City) { + t.Helper() + if len(cfg.Providers) != 0 { + t.Fatalf("providers = %+v, want none", cfg.Providers) + } + }, + }, + { + name: "set agent patch", + mutate: func(cs *controllerState) error { + return cs.SetAgentPatch(config.AgentPatch{Dir: "rig1", Name: "worker", Suspended: boolPtr(true)}) + }, + verify: func(t *testing.T, cfg *config.City) { + t.Helper() + if len(cfg.Patches.Agents) != 1 || cfg.Patches.Agents[0].Suspended == nil || !*cfg.Patches.Agents[0].Suspended { + t.Fatalf("agent patches = %+v, want suspended patch", cfg.Patches.Agents) + } + }, + }, + { + name: "delete agent patch", + initial: func(cfg *config.City) { + cfg.Patches.Agents = []config.AgentPatch{{Dir: "rig1", Name: "worker", Suspended: boolPtr(true)}} + }, + mutate: func(cs *controllerState) error { + return cs.DeleteAgentPatch("rig1/worker") + }, + verify: func(t *testing.T, cfg *config.City) { + t.Helper() + if len(cfg.Patches.Agents) != 0 { + t.Fatalf("agent patches = %+v, want none", cfg.Patches.Agents) + } + }, + }, + { + name: "set rig patch", + mutate: func(cs *controllerState) error { + return cs.SetRigPatch(config.RigPatch{Name: "rig1", Prefix: stringPtr("rp")}) + }, + verify: func(t *testing.T, cfg *config.City) { + t.Helper() + if len(cfg.Patches.Rigs) != 1 || cfg.Patches.Rigs[0].Prefix == nil || *cfg.Patches.Rigs[0].Prefix != "rp" { + t.Fatalf("rig patches = %+v, want prefix patch", cfg.Patches.Rigs) + } + }, + }, + { + name: "delete rig patch", + initial: func(cfg *config.City) { + cfg.Patches.Rigs = []config.RigPatch{{Name: "rig1", Prefix: stringPtr("rp")}} + }, + mutate: func(cs *controllerState) error { + return cs.DeleteRigPatch("rig1") + }, + verify: func(t *testing.T, cfg *config.City) { + t.Helper() + if len(cfg.Patches.Rigs) != 0 { + t.Fatalf("rig patches = %+v, want none", cfg.Patches.Rigs) + } + }, + }, + { + name: "set provider patch", + mutate: func(cs *controllerState) error { + return cs.SetProviderPatch(config.ProviderPatch{Name: "codex-local", Command: stringPtr("codex-wrapper")}) + }, + verify: func(t *testing.T, cfg *config.City) { + t.Helper() + if len(cfg.Patches.Providers) != 1 || cfg.Patches.Providers[0].Command == nil || *cfg.Patches.Providers[0].Command != "codex-wrapper" { + t.Fatalf("provider patches = %+v, want command patch", cfg.Patches.Providers) + } + }, + }, + { + name: "delete provider patch", + initial: func(cfg *config.City) { + cfg.Patches.Providers = []config.ProviderPatch{{Name: "codex-local", Command: stringPtr("codex-wrapper")}} + }, + mutate: func(cs *controllerState) error { + return cs.DeleteProviderPatch("codex-local") + }, + verify: func(t *testing.T, cfg *config.City) { + t.Helper() + if len(cfg.Patches.Providers) != 0 { + t.Fatalf("provider patches = %+v, want none", cfg.Patches.Providers) + } + }, + }, } for _, tc := range cases { diff --git a/cmd/gc/city_registry_test.go b/cmd/gc/city_registry_test.go index 341aea369d..405eb6baf6 100644 --- a/cmd/gc/city_registry_test.go +++ b/cmd/gc/city_registry_test.go @@ -1,9 +1,15 @@ package main import ( + "io" + "os" + "path/filepath" "sync" "testing" "time" + + "github.com/gastownhall/gascity/internal/events" + "github.com/gastownhall/gascity/internal/supervisor" ) func TestCityRegistryEmptySnapshot(t *testing.T) { @@ -223,6 +229,77 @@ func TestCityRegistryTombstonedBeforeRemove(t *testing.T) { } } +func TestCityRegistryTransientCityEventProvidersIncludesRegisteredAndPendingCities(t *testing.T) { + t.Setenv("GC_HOME", t.TempDir()) + + registeredPath := writeCityEventLog(t, "registered-only") + pendingPath := writeCityEventLog(t, "pending-city") + runningPath := writeCityEventLog(t, "running-city") + + regFile := supervisor.NewRegistry(supervisor.RegistryPath()) + if err := regFile.Register(registeredPath, "registered-only"); err != nil { + t.Fatal(err) + } + if err := regFile.Register(runningPath, "running-city"); err != nil { + t.Fatal(err) + } + + reg := newCityRegistry() + reg.Add(pendingPath, &managedCity{ + name: "pending-city", + status: "loading_config", + cr: &CityRuntime{cityName: "pending-city"}, + }) + reg.Add(runningPath, &managedCity{ + name: "running-city", + started: true, + cr: &CityRuntime{cityName: "running-city"}, + }) + + providers := reg.TransientCityEventProviders() + t.Cleanup(func() { + for _, p := range providers { + p.Close() //nolint:errcheck + } + }) + + if _, ok := providers["registered-only"]; !ok { + t.Fatalf("registered-only missing from transient providers: %#v", providers) + } + if _, ok := providers["pending-city"]; !ok { + t.Fatalf("pending-city missing from transient providers: %#v", providers) + } + if _, ok := providers["running-city"]; ok { + t.Fatalf("running-city should be handled by running-city multiplexer path, not transient providers") + } + for name, provider := range providers { + list, err := provider.List(events.Filter{Type: events.CityCreated}) + if err != nil { + t.Fatalf("List(%s): %v", name, err) + } + if len(list) != 1 { + t.Fatalf("List(%s) returned %d city.created events, want 1", name, len(list)) + } + } +} + +func writeCityEventLog(t *testing.T, name string) string { + t.Helper() + path := filepath.Join(t.TempDir(), name) + if err := os.MkdirAll(filepath.Join(path, ".gc"), 0o755); err != nil { + t.Fatal(err) + } + rec, err := events.NewFileRecorder(filepath.Join(path, ".gc", "events.jsonl"), io.Discard) + if err != nil { + t.Fatal(err) + } + rec.Record(events.Event{Type: events.CityCreated, Actor: "gc", Subject: name}) + if err := rec.Close(); err != nil { + t.Fatal(err) + } + return path +} + func TestCityRegistrySnapshotImmutability(t *testing.T) { reg := newCityRegistry() cs := &controllerState{} diff --git a/cmd/gc/cityinit_impl_test.go b/cmd/gc/cityinit_impl_test.go new file mode 100644 index 0000000000..c2a74056c6 --- /dev/null +++ b/cmd/gc/cityinit_impl_test.go @@ -0,0 +1,233 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/gastownhall/gascity/internal/api" + "github.com/gastownhall/gascity/internal/cityinit" + "github.com/gastownhall/gascity/internal/events" + "github.com/gastownhall/gascity/internal/supervisor" +) + +func TestValidateInitRequest(t *testing.T) { + absDir := filepath.Join(t.TempDir(), "city") + tests := []struct { + name string + req cityinit.InitRequest + wantErr error + wantContains string + }{ + { + name: "missing dir", + req: cityinit.InitRequest{Provider: "codex"}, + wantErr: cityinit.ErrInvalidProvider, + }, + { + name: "relative dir", + req: cityinit.InitRequest{Dir: "relative", Provider: "codex"}, + wantContains: "dir must be absolute", + }, + { + name: "missing provider and start command", + req: cityinit.InitRequest{Dir: absDir}, + wantErr: cityinit.ErrInvalidProvider, + }, + { + name: "unknown provider", + req: cityinit.InitRequest{Dir: absDir, Provider: "not-a-provider"}, + wantErr: cityinit.ErrInvalidProvider, + }, + { + name: "bad bootstrap profile", + req: cityinit.InitRequest{Dir: absDir, Provider: "codex", BootstrapProfile: "moon-base"}, + wantErr: cityinit.ErrInvalidBootstrapProfile, + }, + { + name: "builtin provider", + req: cityinit.InitRequest{Dir: absDir, Provider: "codex"}, + wantErr: nil, + }, + { + name: "custom start command", + req: cityinit.InitRequest{Dir: absDir, StartCommand: "custom-agent"}, + wantErr: nil, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := validateInitRequest(&tc.req) + if tc.wantErr == nil { + if tc.wantContains != "" { + if err == nil || !strings.Contains(err.Error(), tc.wantContains) { + t.Fatalf("validateInitRequest() error = %v, want message containing %q", err, tc.wantContains) + } + return + } + if err != nil { + t.Fatalf("validateInitRequest() error = %v, want nil", err) + } + return + } + if !errors.Is(err, tc.wantErr) { + t.Fatalf("validateInitRequest() error = %v, want %v", err, tc.wantErr) + } + }) + } +} + +func TestLocalInitializerScaffoldCreatesCityRegistersAndEmitsCreated(t *testing.T) { + t.Setenv("GC_HOME", t.TempDir()) + cityPath := filepath.Join(t.TempDir(), "api-city") + + result, err := localInitializer{}.Scaffold(context.Background(), cityinit.InitRequest{ + Dir: cityPath, + Provider: "codex", + BootstrapProfile: bootstrapProfileSingleHostCompat, + NameOverride: "api-city", + }) + if err != nil { + t.Fatalf("Scaffold: %v", err) + } + if result.CityName != "api-city" || result.CityPath != cityPath || result.ProviderUsed != "codex" { + t.Fatalf("Scaffold result = %+v, want api-city/%s/codex", result, cityPath) + } + if _, err := os.Stat(filepath.Join(cityPath, "city.toml")); err != nil { + t.Fatalf("city.toml missing after Scaffold: %v", err) + } + + reg := supervisor.NewRegistry(supervisor.RegistryPath()) + entries, err := reg.List() + if err != nil { + t.Fatal(err) + } + if len(entries) != 1 { + t.Fatalf("registry entries = %+v, want one", entries) + } + if entries[0].EffectiveName() != "api-city" { + t.Fatalf("registry effective name = %q, want api-city", entries[0].EffectiveName()) + } + assertSameTestPath(t, entries[0].Path, cityPath) + + evts, err := events.ReadFiltered(filepath.Join(cityPath, ".gc", "events.jsonl"), events.Filter{Type: events.CityCreated}) + if err != nil { + t.Fatalf("ReadFiltered city.created: %v", err) + } + if len(evts) != 1 { + t.Fatalf("city.created events = %d, want 1: %+v", len(evts), evts) + } + var payload api.CityCreatedPayload + if err := json.Unmarshal(evts[0].Payload, &payload); err != nil { + t.Fatalf("unmarshal city.created payload: %v", err) + } + if payload.Name != "api-city" { + t.Fatalf("payload name = %q, want api-city", payload.Name) + } + assertSameTestPath(t, payload.Path, cityPath) + + _, err = localInitializer{}.Scaffold(context.Background(), cityinit.InitRequest{ + Dir: cityPath, + Provider: "codex", + }) + if !errors.Is(err, cityinit.ErrAlreadyInitialized) { + t.Fatalf("second Scaffold error = %v, want ErrAlreadyInitialized", err) + } +} + +func TestLocalInitializerInitScaffoldsAndFinalizes(t *testing.T) { + cityPath := filepath.Join(t.TempDir(), "init-city") + + result, err := localInitializer{}.Init(context.Background(), cityinit.InitRequest{ + Dir: cityPath, + StartCommand: "true", + NameOverride: "init-city", + SkipProviderReadiness: true, + }) + if err != nil { + t.Fatalf("Init: %v", err) + } + if result.CityName != "init-city" || result.CityPath != cityPath { + t.Fatalf("Init result = %+v, want init-city/%s", result, cityPath) + } + if _, err := os.Stat(filepath.Join(cityPath, ".gc")); err != nil { + t.Fatalf(".gc missing after Init finalization: %v", err) + } + + _, err = localInitializer{}.Init(context.Background(), cityinit.InitRequest{ + Dir: cityPath, + StartCommand: "true", + }) + if !errors.Is(err, cityinit.ErrAlreadyInitialized) { + t.Fatalf("second Init error = %v, want ErrAlreadyInitialized", err) + } +} + +func TestLocalInitializerUnregisterRemovesRegistryAndEmitsEvent(t *testing.T) { + t.Setenv("GC_HOME", t.TempDir()) + cityPath := filepath.Join(t.TempDir(), "bright-lights") + if err := os.MkdirAll(filepath.Join(cityPath, ".gc"), 0o755); err != nil { + t.Fatal(err) + } + reg := supervisor.NewRegistry(supervisor.RegistryPath()) + if err := reg.Register(cityPath, "bright-lights"); err != nil { + t.Fatal(err) + } + + result, err := localInitializer{}.Unregister(context.Background(), cityinit.UnregisterRequest{ + CityName: " bright-lights ", + }) + if err != nil { + t.Fatalf("Unregister: %v", err) + } + if result.CityName != "bright-lights" { + t.Fatalf("CityName = %q, want bright-lights", result.CityName) + } + assertSameTestPath(t, result.CityPath, cityPath) + + entries, err := reg.List() + if err != nil { + t.Fatal(err) + } + if len(entries) != 0 { + t.Fatalf("registry entries after unregister = %+v, want empty", entries) + } + + evts, err := events.ReadFiltered(filepath.Join(cityPath, ".gc", "events.jsonl"), events.Filter{Type: events.CityUnregisterRequested}) + if err != nil { + t.Fatalf("ReadFiltered: %v", err) + } + if len(evts) != 1 { + t.Fatalf("unregister_requested events = %d, want 1: %+v", len(evts), evts) + } + if evts[0].Actor != "gc" || evts[0].Subject != "bright-lights" { + t.Fatalf("event actor/subject = %q/%q, want gc/bright-lights", evts[0].Actor, evts[0].Subject) + } + var payload api.CityUnregisterRequestedPayload + if err := json.Unmarshal(evts[0].Payload, &payload); err != nil { + t.Fatalf("unmarshal payload: %v", err) + } + if payload.Name != "bright-lights" { + t.Fatalf("payload name = %q, want bright-lights", payload.Name) + } + assertSameTestPath(t, payload.Path, cityPath) +} + +func TestLocalInitializerUnregisterMissingCity(t *testing.T) { + t.Setenv("GC_HOME", t.TempDir()) + + _, err := localInitializer{}.Unregister(context.Background(), cityinit.UnregisterRequest{CityName: "missing"}) + if !errors.Is(err, cityinit.ErrNotRegistered) { + t.Fatalf("Unregister missing error = %v, want ErrNotRegistered", err) + } + + _, err = localInitializer{}.Unregister(context.Background(), cityinit.UnregisterRequest{}) + if !errors.Is(err, cityinit.ErrNotRegistered) { + t.Fatalf("Unregister blank error = %v, want ErrNotRegistered", err) + } +} diff --git a/cmd/gc/cmd_supervisor_city_test.go b/cmd/gc/cmd_supervisor_city_test.go index 3abd059bd4..4626889a8e 100644 --- a/cmd/gc/cmd_supervisor_city_test.go +++ b/cmd/gc/cmd_supervisor_city_test.go @@ -106,6 +106,35 @@ func TestRegisterCityWithSupervisorKeepsRegistrationWhenCityNeverBecomesReady(t } } +func TestRegisterCityForAPIRegistersWithoutWaitingForReadiness(t *testing.T) { + t.Setenv("GC_HOME", t.TempDir()) + + cityPath := filepath.Join(t.TempDir(), "bright-lights") + if err := os.MkdirAll(cityPath, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(cityPath, "city.toml"), []byte("[workspace]\nname = \"bright-lights\"\n"), 0o644); err != nil { + t.Fatal(err) + } + + if err := registerCityForAPI(cityPath, "api-name"); err != nil { + t.Fatalf("registerCityForAPI: %v", err) + } + + reg := supervisor.NewRegistry(supervisor.RegistryPath()) + entries, err := reg.List() + if err != nil { + t.Fatal(err) + } + if len(entries) != 1 { + t.Fatalf("registry entries = %+v, want one", entries) + } + assertSameTestPath(t, entries[0].Path, cityPath) + if entries[0].EffectiveName() != "api-name" { + t.Fatalf("effective name = %q, want api-name", entries[0].EffectiveName()) + } +} + func TestRegisterCityWithSupervisorRetriesControllerLockInitFailure(t *testing.T) { gcHome := t.TempDir() t.Setenv("GC_HOME", gcHome) diff --git a/internal/api/huma_handlers_supervisor_test.go b/internal/api/huma_handlers_supervisor_test.go index b97fed7097..0d442c6c06 100644 --- a/internal/api/huma_handlers_supervisor_test.go +++ b/internal/api/huma_handlers_supervisor_test.go @@ -1,16 +1,55 @@ package api import ( + "context" + "errors" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" + "time" + "github.com/gastownhall/gascity/internal/cityinit" "github.com/gastownhall/gascity/internal/citylayout" ) +type fakeInitializer struct { + scaffoldReq cityinit.InitRequest + scaffoldResult *cityinit.InitResult + scaffoldErr error + + unregisterReq cityinit.UnregisterRequest + unregisterResult *cityinit.UnregisterResult + unregisterErr error +} + +func (f *fakeInitializer) Init(context.Context, cityinit.InitRequest) (*cityinit.InitResult, error) { + return nil, errors.New("Init should not be called by supervisor tests") +} + +func (f *fakeInitializer) Scaffold(_ context.Context, req cityinit.InitRequest) (*cityinit.InitResult, error) { + f.scaffoldReq = req + if f.scaffoldErr != nil { + return nil, f.scaffoldErr + } + return f.scaffoldResult, nil +} + +func (f *fakeInitializer) Unregister(_ context.Context, req cityinit.UnregisterRequest) (*cityinit.UnregisterResult, error) { + f.unregisterReq = req + if f.unregisterErr != nil { + return nil, f.unregisterErr + } + return f.unregisterResult, nil +} + +func newTestSupervisorMuxWithInitializer(t *testing.T, init cityinit.Initializer) *SupervisorMux { + t.Helper() + return NewSupervisorMux(&fakeCityResolver{cities: map[string]*fakeState{}}, init, false, "test", time.Now()) +} + func TestSupervisorCityCreateConflictsWhenTargetAlreadyInitialized(t *testing.T) { tests := []struct { name string @@ -60,6 +99,134 @@ func TestSupervisorCityCreateConflictsWhenTargetAlreadyInitialized(t *testing.T) } } +func TestSupervisorCityCreateScaffoldsViaInitializer(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + cityPath := filepath.Join(home, "mc-city") + init := &fakeInitializer{ + scaffoldResult: &cityinit.InitResult{ + CityName: "mc-city", + CityPath: cityPath, + ProviderUsed: "codex", + }, + } + sm := newTestSupervisorMuxWithInitializer(t, init) + + req := httptest.NewRequest(http.MethodPost, "/v0/city", strings.NewReader(`{ + "dir":"mc-city", + "provider":"codex", + "bootstrap_profile":"single-host-compat" + }`)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-GC-Request", "test") + rec := httptest.NewRecorder() + + sm.ServeHTTP(rec, req) + + if rec.Code != http.StatusAccepted { + t.Fatalf("status = %d, want %d; body=%s", rec.Code, http.StatusAccepted, rec.Body.String()) + } + if init.scaffoldReq.Dir != cityPath { + t.Fatalf("Scaffold Dir = %q, want %q", init.scaffoldReq.Dir, cityPath) + } + if init.scaffoldReq.Provider != "codex" || init.scaffoldReq.BootstrapProfile != "single-host-compat" { + t.Fatalf("Scaffold request = %+v, want codex + single-host-compat", init.scaffoldReq) + } + if !init.scaffoldReq.SkipProviderReadiness { + t.Fatal("Scaffold request should skip provider readiness for API callers") + } + if body := rec.Body.String(); !strings.Contains(body, `"name":"mc-city"`) || !strings.Contains(body, `"path":"`+cityPath+`"`) { + t.Fatalf("body = %s, want name and path", body) + } +} + +func TestSupervisorCityCreateMapsInitializerErrors(t *testing.T) { + cityPath := filepath.Join(t.TempDir(), "mc-city") + tests := []struct { + name string + err error + want int + }{ + {name: "already initialized", err: cityinit.ErrAlreadyInitialized, want: http.StatusConflict}, + {name: "invalid provider", err: cityinit.ErrInvalidProvider, want: http.StatusUnprocessableEntity}, + {name: "invalid bootstrap", err: cityinit.ErrInvalidBootstrapProfile, want: http.StatusUnprocessableEntity}, + {name: "missing dependency", err: cityinit.ErrMissingDependency, want: http.StatusServiceUnavailable}, + {name: "provider not ready", err: cityinit.ErrProviderNotReady, want: http.StatusServiceUnavailable}, + {name: "generic", err: errors.New("boom"), want: http.StatusInternalServerError}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + init := &fakeInitializer{scaffoldErr: tc.err} + sm := newTestSupervisorMuxWithInitializer(t, init) + req := httptest.NewRequest(http.MethodPost, "/v0/city", strings.NewReader(`{"dir":"`+cityPath+`","provider":"codex"}`)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-GC-Request", "test") + rec := httptest.NewRecorder() + + sm.ServeHTTP(rec, req) + + if rec.Code != tc.want { + t.Fatalf("status = %d, want %d; body=%s", rec.Code, tc.want, rec.Body.String()) + } + }) + } +} + +func TestSupervisorCityCreateWithoutInitializerReturns501(t *testing.T) { + sm := newTestSupervisorMux(t, map[string]*fakeState{}) + cityPath := filepath.Join(t.TempDir(), "mc-city") + req := httptest.NewRequest(http.MethodPost, "/v0/city", strings.NewReader(`{"dir":"`+cityPath+`","provider":"codex"}`)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-GC-Request", "test") + rec := httptest.NewRecorder() + + sm.ServeHTTP(rec, req) + + if rec.Code != http.StatusNotImplemented { + t.Fatalf("status = %d, want %d; body=%s", rec.Code, http.StatusNotImplemented, rec.Body.String()) + } +} + +func TestSupervisorCityUnregisterUsesInitializer(t *testing.T) { + init := &fakeInitializer{ + unregisterResult: &cityinit.UnregisterResult{ + CityName: "mc-city", + CityPath: "/tmp/mc-city", + }, + } + sm := newTestSupervisorMuxWithInitializer(t, init) + req := httptest.NewRequest(http.MethodPost, "/v0/city/mc-city/unregister", nil) + req.Header.Set("X-GC-Request", "test") + rec := httptest.NewRecorder() + + sm.ServeHTTP(rec, req) + + if rec.Code != http.StatusAccepted { + t.Fatalf("status = %d, want %d; body=%s", rec.Code, http.StatusAccepted, rec.Body.String()) + } + if init.unregisterReq.CityName != "mc-city" { + t.Fatalf("Unregister CityName = %q, want mc-city", init.unregisterReq.CityName) + } + if body := rec.Body.String(); !strings.Contains(body, `"name":"mc-city"`) || !strings.Contains(body, `"path":"/tmp/mc-city"`) { + t.Fatalf("body = %s, want name and path", body) + } +} + +func TestSupervisorCityUnregisterMapsNotRegistered(t *testing.T) { + init := &fakeInitializer{unregisterErr: cityinit.ErrNotRegistered} + sm := newTestSupervisorMuxWithInitializer(t, init) + req := httptest.NewRequest(http.MethodPost, "/v0/city/missing/unregister", nil) + req.Header.Set("X-GC-Request", "test") + rec := httptest.NewRecorder() + + sm.ServeHTTP(rec, req) + + if rec.Code != http.StatusNotFound { + t.Fatalf("status = %d, want %d; body=%s", rec.Code, http.StatusNotFound, rec.Body.String()) + } +} + func TestCityDirAlreadyInitializedAllowsConfigOnlyBootstrap(t *testing.T) { dir := t.TempDir() if err := os.WriteFile(filepath.Join(dir, citylayout.CityConfigFile), []byte("[workspace]\nname = \"alpha\"\n"), 0o644); err != nil { From b128c1103115ccd026da4bdb203ce9bd5cc29152 Mon Sep 17 00:00:00 2001 From: Chris Sells Date: Wed, 22 Apr 2026 16:24:40 -0700 Subject: [PATCH 10/85] Expose typed event stream envelope schemas --- cmd/gen-client/main.go | 4 +- docs/schema/openapi.json | 4154 +++++++++++++++- docs/schema/openapi.txt | 4154 +++++++++++++++- internal/api/event_envelope_schemas.go | 162 + internal/api/genclient/client_gen.go | 5599 ++++++++++++++++++---- internal/api/huma_handlers_supervisor.go | 8 +- internal/api/huma_sse_test.go | 380 ++ internal/api/openapi.json | 4154 +++++++++++++++- internal/api/sse.go | 29 +- internal/api/supervisor_city_routes.go | 5 +- internal/api/supervisor_test.go | 334 ++ 11 files changed, 18079 insertions(+), 904 deletions(-) create mode 100644 internal/api/event_envelope_schemas.go diff --git a/cmd/gen-client/main.go b/cmd/gen-client/main.go index c683d43a1b..98c85f1596 100644 --- a/cmd/gen-client/main.go +++ b/cmd/gen-client/main.go @@ -11,6 +11,8 @@ // The routes we register ARE the routes we expose. Every schema and // path in the generated client matches what the server publishes to // external consumers — no hidden rename, no hidden path rewrite. +// skip-prune keeps documentation-only compatibility components +// available to in-tree callers that still deserialize those shapes. // 3. Write the generated client to internal/api/genclient/client_gen.go. // // Usage: @@ -66,7 +68,7 @@ func run() error { // Step 3: invoke oapi-codegen. Output goes to stdout — the caller // redirects it to internal/api/genclient/client_gen.go. - cmd := exec.Command("oapi-codegen", "-generate", "types,client", "-package", "genclient", tmp.Name()) + cmd := exec.Command("oapi-codegen", "-generate", "types,client,skip-prune", "-package", "genclient", tmp.Name()) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { diff --git a/docs/schema/openapi.json b/docs/schema/openapi.json index bd3188b4f1..377d0abcf5 100644 --- a/docs/schema/openapi.json +++ b/docs/schema/openapi.json @@ -6431,6 +6431,4156 @@ ], "type": "string" }, + "TypedEventStreamEnvelope": { + "description": "Discriminated union of city event stream envelopes. Each variant constrains the envelope type and payload schema together.", + "discriminator": { + "mapping": { + "bead.closed": "#/components/schemas/TypedEventStreamEnvelopeBeadClosed", + "bead.created": "#/components/schemas/TypedEventStreamEnvelopeBeadCreated", + "bead.updated": "#/components/schemas/TypedEventStreamEnvelopeBeadUpdated", + "city.created": "#/components/schemas/TypedEventStreamEnvelopeCityCreated", + "city.init_failed": "#/components/schemas/TypedEventStreamEnvelopeCityInitFailed", + "city.ready": "#/components/schemas/TypedEventStreamEnvelopeCityReady", + "city.resumed": "#/components/schemas/TypedEventStreamEnvelopeCityResumed", + "city.suspended": "#/components/schemas/TypedEventStreamEnvelopeCitySuspended", + "city.unregister_failed": "#/components/schemas/TypedEventStreamEnvelopeCityUnregisterFailed", + "city.unregister_requested": "#/components/schemas/TypedEventStreamEnvelopeCityUnregisterRequested", + "city.unregistered": "#/components/schemas/TypedEventStreamEnvelopeCityUnregistered", + "controller.started": "#/components/schemas/TypedEventStreamEnvelopeControllerStarted", + "controller.stopped": "#/components/schemas/TypedEventStreamEnvelopeControllerStopped", + "convoy.closed": "#/components/schemas/TypedEventStreamEnvelopeConvoyClosed", + "convoy.created": "#/components/schemas/TypedEventStreamEnvelopeConvoyCreated", + "extmsg.adapter_added": "#/components/schemas/TypedEventStreamEnvelopeExtmsgAdapterAdded", + "extmsg.adapter_removed": "#/components/schemas/TypedEventStreamEnvelopeExtmsgAdapterRemoved", + "extmsg.bound": "#/components/schemas/TypedEventStreamEnvelopeExtmsgBound", + "extmsg.group_created": "#/components/schemas/TypedEventStreamEnvelopeExtmsgGroupCreated", + "extmsg.inbound": "#/components/schemas/TypedEventStreamEnvelopeExtmsgInbound", + "extmsg.outbound": "#/components/schemas/TypedEventStreamEnvelopeExtmsgOutbound", + "extmsg.unbound": "#/components/schemas/TypedEventStreamEnvelopeExtmsgUnbound", + "mail.archived": "#/components/schemas/TypedEventStreamEnvelopeMailArchived", + "mail.deleted": "#/components/schemas/TypedEventStreamEnvelopeMailDeleted", + "mail.marked_read": "#/components/schemas/TypedEventStreamEnvelopeMailMarkedRead", + "mail.marked_unread": "#/components/schemas/TypedEventStreamEnvelopeMailMarkedUnread", + "mail.read": "#/components/schemas/TypedEventStreamEnvelopeMailRead", + "mail.replied": "#/components/schemas/TypedEventStreamEnvelopeMailReplied", + "mail.sent": "#/components/schemas/TypedEventStreamEnvelopeMailSent", + "order.completed": "#/components/schemas/TypedEventStreamEnvelopeOrderCompleted", + "order.failed": "#/components/schemas/TypedEventStreamEnvelopeOrderFailed", + "order.fired": "#/components/schemas/TypedEventStreamEnvelopeOrderFired", + "provider.swapped": "#/components/schemas/TypedEventStreamEnvelopeProviderSwapped", + "session.crashed": "#/components/schemas/TypedEventStreamEnvelopeSessionCrashed", + "session.draining": "#/components/schemas/TypedEventStreamEnvelopeSessionDraining", + "session.idle_killed": "#/components/schemas/TypedEventStreamEnvelopeSessionIdleKilled", + "session.quarantined": "#/components/schemas/TypedEventStreamEnvelopeSessionQuarantined", + "session.stopped": "#/components/schemas/TypedEventStreamEnvelopeSessionStopped", + "session.suspended": "#/components/schemas/TypedEventStreamEnvelopeSessionSuspended", + "session.undrained": "#/components/schemas/TypedEventStreamEnvelopeSessionUndrained", + "session.updated": "#/components/schemas/TypedEventStreamEnvelopeSessionUpdated", + "session.woke": "#/components/schemas/TypedEventStreamEnvelopeSessionWoke", + "worker.operation": "#/components/schemas/TypedEventStreamEnvelopeWorkerOperation" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeBeadClosed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeBeadCreated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeBeadUpdated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityCreated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityInitFailed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityReady" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityResumed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCitySuspended" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityUnregisterFailed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityUnregisterRequested" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityUnregistered" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeControllerStarted" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeControllerStopped" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeConvoyClosed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeConvoyCreated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgAdapterAdded" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgAdapterRemoved" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgBound" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgGroupCreated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgInbound" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgOutbound" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgUnbound" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailArchived" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailDeleted" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailMarkedRead" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailMarkedUnread" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailRead" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailReplied" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailSent" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeOrderCompleted" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeOrderFailed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeOrderFired" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeProviderSwapped" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionCrashed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionDraining" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionIdleKilled" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionQuarantined" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionStopped" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionSuspended" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionUndrained" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionUpdated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionWoke" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeWorkerOperation" + } + ], + "title": "Typed city event stream envelope" + }, + "TypedEventStreamEnvelopeBeadClosed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.closed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope bead.closed", + "type": "object" + }, + "TypedEventStreamEnvelopeBeadCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope bead.created", + "type": "object" + }, + "TypedEventStreamEnvelopeBeadUpdated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.updated", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope bead.updated", + "type": "object" + }, + "TypedEventStreamEnvelopeCityCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.created", + "type": "object" + }, + "TypedEventStreamEnvelopeCityInitFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.init_failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.init_failed", + "type": "object" + }, + "TypedEventStreamEnvelopeCityReady": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.ready", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.ready", + "type": "object" + }, + "TypedEventStreamEnvelopeCityResumed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.resumed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.resumed", + "type": "object" + }, + "TypedEventStreamEnvelopeCitySuspended": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.suspended", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.suspended", + "type": "object" + }, + "TypedEventStreamEnvelopeCityUnregisterFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregister_failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.unregister_failed", + "type": "object" + }, + "TypedEventStreamEnvelopeCityUnregisterRequested": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregister_requested", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.unregister_requested", + "type": "object" + }, + "TypedEventStreamEnvelopeCityUnregistered": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregistered", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.unregistered", + "type": "object" + }, + "TypedEventStreamEnvelopeControllerStarted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "controller.started", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope controller.started", + "type": "object" + }, + "TypedEventStreamEnvelopeControllerStopped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "controller.stopped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope controller.stopped", + "type": "object" + }, + "TypedEventStreamEnvelopeConvoyClosed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "convoy.closed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope convoy.closed", + "type": "object" + }, + "TypedEventStreamEnvelopeConvoyCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "convoy.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope convoy.created", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgAdapterAdded": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/AdapterEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.adapter_added", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.adapter_added", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgAdapterRemoved": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/AdapterEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.adapter_removed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.adapter_removed", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgBound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BoundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.bound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.bound", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgGroupCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/GroupCreatedEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.group_created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.group_created", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgInbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/InboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.inbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.inbound", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgOutbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/OutboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.outbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.outbound", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgUnbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/UnboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.unbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.unbound", + "type": "object" + }, + "TypedEventStreamEnvelopeMailArchived": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.archived", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.archived", + "type": "object" + }, + "TypedEventStreamEnvelopeMailDeleted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.deleted", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.deleted", + "type": "object" + }, + "TypedEventStreamEnvelopeMailMarkedRead": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.marked_read", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.marked_read", + "type": "object" + }, + "TypedEventStreamEnvelopeMailMarkedUnread": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.marked_unread", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.marked_unread", + "type": "object" + }, + "TypedEventStreamEnvelopeMailRead": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.read", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.read", + "type": "object" + }, + "TypedEventStreamEnvelopeMailReplied": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.replied", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.replied", + "type": "object" + }, + "TypedEventStreamEnvelopeMailSent": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.sent", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.sent", + "type": "object" + }, + "TypedEventStreamEnvelopeOrderCompleted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.completed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope order.completed", + "type": "object" + }, + "TypedEventStreamEnvelopeOrderFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope order.failed", + "type": "object" + }, + "TypedEventStreamEnvelopeOrderFired": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.fired", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope order.fired", + "type": "object" + }, + "TypedEventStreamEnvelopeProviderSwapped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "provider.swapped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope provider.swapped", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionCrashed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.crashed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.crashed", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionDraining": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.draining", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.draining", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionIdleKilled": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.idle_killed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.idle_killed", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionQuarantined": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.quarantined", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.quarantined", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionStopped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.stopped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.stopped", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionSuspended": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.suspended", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.suspended", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionUndrained": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.undrained", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.undrained", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionUpdated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.updated", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.updated", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionWoke": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.woke", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.woke", + "type": "object" + }, + "TypedEventStreamEnvelopeWorkerOperation": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/WorkerOperationEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "worker.operation", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope worker.operation", + "type": "object" + }, + "TypedTaggedEventStreamEnvelope": { + "description": "Discriminated union of supervisor event stream envelopes. Each variant constrains the envelope type and payload schema together and includes the source city.", + "discriminator": { + "mapping": { + "bead.closed": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadClosed", + "bead.created": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadCreated", + "bead.updated": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadUpdated", + "city.created": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityCreated", + "city.init_failed": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityInitFailed", + "city.ready": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityReady", + "city.resumed": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityResumed", + "city.suspended": "#/components/schemas/TypedTaggedEventStreamEnvelopeCitySuspended", + "city.unregister_failed": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregisterFailed", + "city.unregister_requested": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregisterRequested", + "city.unregistered": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregistered", + "controller.started": "#/components/schemas/TypedTaggedEventStreamEnvelopeControllerStarted", + "controller.stopped": "#/components/schemas/TypedTaggedEventStreamEnvelopeControllerStopped", + "convoy.closed": "#/components/schemas/TypedTaggedEventStreamEnvelopeConvoyClosed", + "convoy.created": "#/components/schemas/TypedTaggedEventStreamEnvelopeConvoyCreated", + "extmsg.adapter_added": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded", + "extmsg.adapter_removed": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved", + "extmsg.bound": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgBound", + "extmsg.group_created": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgGroupCreated", + "extmsg.inbound": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgInbound", + "extmsg.outbound": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgOutbound", + "extmsg.unbound": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgUnbound", + "mail.archived": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailArchived", + "mail.deleted": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailDeleted", + "mail.marked_read": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailMarkedRead", + "mail.marked_unread": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailMarkedUnread", + "mail.read": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailRead", + "mail.replied": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailReplied", + "mail.sent": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailSent", + "order.completed": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderCompleted", + "order.failed": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderFailed", + "order.fired": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderFired", + "provider.swapped": "#/components/schemas/TypedTaggedEventStreamEnvelopeProviderSwapped", + "session.crashed": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionCrashed", + "session.draining": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionDraining", + "session.idle_killed": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionIdleKilled", + "session.quarantined": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionQuarantined", + "session.stopped": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionStopped", + "session.suspended": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionSuspended", + "session.undrained": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionUndrained", + "session.updated": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionUpdated", + "session.woke": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionWoke", + "worker.operation": "#/components/schemas/TypedTaggedEventStreamEnvelopeWorkerOperation" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadClosed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadCreated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadUpdated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityCreated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityInitFailed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityReady" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityResumed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCitySuspended" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregisterFailed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregisterRequested" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregistered" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeControllerStarted" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeControllerStopped" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeConvoyClosed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeConvoyCreated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgBound" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgGroupCreated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgInbound" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgOutbound" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgUnbound" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailArchived" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailDeleted" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailMarkedRead" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailMarkedUnread" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailRead" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailReplied" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailSent" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderCompleted" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderFailed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderFired" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeProviderSwapped" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionCrashed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionDraining" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionIdleKilled" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionQuarantined" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionStopped" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionSuspended" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionUndrained" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionUpdated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionWoke" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeWorkerOperation" + } + ], + "title": "Typed supervisor event stream envelope" + }, + "TypedTaggedEventStreamEnvelopeBeadClosed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.closed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope bead.closed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeBeadCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope bead.created", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeBeadUpdated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.updated", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope bead.updated", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.created", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityInitFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.init_failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.init_failed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityReady": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.ready", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.ready", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityResumed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.resumed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.resumed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCitySuspended": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.suspended", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.suspended", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityUnregisterFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregister_failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.unregister_failed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityUnregisterRequested": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregister_requested", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.unregister_requested", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityUnregistered": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregistered", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.unregistered", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeControllerStarted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "controller.started", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope controller.started", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeControllerStopped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "controller.stopped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope controller.stopped", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeConvoyClosed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "convoy.closed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope convoy.closed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeConvoyCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "convoy.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope convoy.created", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/AdapterEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.adapter_added", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.adapter_added", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/AdapterEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.adapter_removed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.adapter_removed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgBound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BoundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.bound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.bound", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgGroupCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/GroupCreatedEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.group_created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.group_created", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgInbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/InboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.inbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.inbound", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgOutbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/OutboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.outbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.outbound", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgUnbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/UnboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.unbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.unbound", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailArchived": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.archived", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.archived", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailDeleted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.deleted", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.deleted", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailMarkedRead": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.marked_read", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.marked_read", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailMarkedUnread": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.marked_unread", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.marked_unread", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailRead": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.read", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.read", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailReplied": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.replied", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.replied", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailSent": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.sent", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.sent", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeOrderCompleted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.completed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope order.completed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeOrderFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope order.failed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeOrderFired": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.fired", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope order.fired", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeProviderSwapped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "provider.swapped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope provider.swapped", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionCrashed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.crashed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.crashed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionDraining": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.draining", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.draining", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionIdleKilled": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.idle_killed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.idle_killed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionQuarantined": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.quarantined", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.quarantined", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionStopped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.stopped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.stopped", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionSuspended": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.suspended", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.suspended", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionUndrained": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.undrained", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.undrained", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionUpdated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.updated", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.updated", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionWoke": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.woke", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.woke", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeWorkerOperation": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/WorkerOperationEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "worker.operation", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope worker.operation", + "type": "object" + }, "UnboundEventPayload": { "additionalProperties": false, "properties": { @@ -10548,7 +14698,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/EventStreamEnvelope" + "$ref": "#/components/schemas/TypedEventStreamEnvelope" }, "event": { "const": "event", @@ -18136,7 +22286,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/TaggedEventStreamEnvelope" + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelope" }, "event": { "const": "tagged_event", diff --git a/docs/schema/openapi.txt b/docs/schema/openapi.txt index bd3188b4f1..377d0abcf5 100644 --- a/docs/schema/openapi.txt +++ b/docs/schema/openapi.txt @@ -6431,6 +6431,4156 @@ ], "type": "string" }, + "TypedEventStreamEnvelope": { + "description": "Discriminated union of city event stream envelopes. Each variant constrains the envelope type and payload schema together.", + "discriminator": { + "mapping": { + "bead.closed": "#/components/schemas/TypedEventStreamEnvelopeBeadClosed", + "bead.created": "#/components/schemas/TypedEventStreamEnvelopeBeadCreated", + "bead.updated": "#/components/schemas/TypedEventStreamEnvelopeBeadUpdated", + "city.created": "#/components/schemas/TypedEventStreamEnvelopeCityCreated", + "city.init_failed": "#/components/schemas/TypedEventStreamEnvelopeCityInitFailed", + "city.ready": "#/components/schemas/TypedEventStreamEnvelopeCityReady", + "city.resumed": "#/components/schemas/TypedEventStreamEnvelopeCityResumed", + "city.suspended": "#/components/schemas/TypedEventStreamEnvelopeCitySuspended", + "city.unregister_failed": "#/components/schemas/TypedEventStreamEnvelopeCityUnregisterFailed", + "city.unregister_requested": "#/components/schemas/TypedEventStreamEnvelopeCityUnregisterRequested", + "city.unregistered": "#/components/schemas/TypedEventStreamEnvelopeCityUnregistered", + "controller.started": "#/components/schemas/TypedEventStreamEnvelopeControllerStarted", + "controller.stopped": "#/components/schemas/TypedEventStreamEnvelopeControllerStopped", + "convoy.closed": "#/components/schemas/TypedEventStreamEnvelopeConvoyClosed", + "convoy.created": "#/components/schemas/TypedEventStreamEnvelopeConvoyCreated", + "extmsg.adapter_added": "#/components/schemas/TypedEventStreamEnvelopeExtmsgAdapterAdded", + "extmsg.adapter_removed": "#/components/schemas/TypedEventStreamEnvelopeExtmsgAdapterRemoved", + "extmsg.bound": "#/components/schemas/TypedEventStreamEnvelopeExtmsgBound", + "extmsg.group_created": "#/components/schemas/TypedEventStreamEnvelopeExtmsgGroupCreated", + "extmsg.inbound": "#/components/schemas/TypedEventStreamEnvelopeExtmsgInbound", + "extmsg.outbound": "#/components/schemas/TypedEventStreamEnvelopeExtmsgOutbound", + "extmsg.unbound": "#/components/schemas/TypedEventStreamEnvelopeExtmsgUnbound", + "mail.archived": "#/components/schemas/TypedEventStreamEnvelopeMailArchived", + "mail.deleted": "#/components/schemas/TypedEventStreamEnvelopeMailDeleted", + "mail.marked_read": "#/components/schemas/TypedEventStreamEnvelopeMailMarkedRead", + "mail.marked_unread": "#/components/schemas/TypedEventStreamEnvelopeMailMarkedUnread", + "mail.read": "#/components/schemas/TypedEventStreamEnvelopeMailRead", + "mail.replied": "#/components/schemas/TypedEventStreamEnvelopeMailReplied", + "mail.sent": "#/components/schemas/TypedEventStreamEnvelopeMailSent", + "order.completed": "#/components/schemas/TypedEventStreamEnvelopeOrderCompleted", + "order.failed": "#/components/schemas/TypedEventStreamEnvelopeOrderFailed", + "order.fired": "#/components/schemas/TypedEventStreamEnvelopeOrderFired", + "provider.swapped": "#/components/schemas/TypedEventStreamEnvelopeProviderSwapped", + "session.crashed": "#/components/schemas/TypedEventStreamEnvelopeSessionCrashed", + "session.draining": "#/components/schemas/TypedEventStreamEnvelopeSessionDraining", + "session.idle_killed": "#/components/schemas/TypedEventStreamEnvelopeSessionIdleKilled", + "session.quarantined": "#/components/schemas/TypedEventStreamEnvelopeSessionQuarantined", + "session.stopped": "#/components/schemas/TypedEventStreamEnvelopeSessionStopped", + "session.suspended": "#/components/schemas/TypedEventStreamEnvelopeSessionSuspended", + "session.undrained": "#/components/schemas/TypedEventStreamEnvelopeSessionUndrained", + "session.updated": "#/components/schemas/TypedEventStreamEnvelopeSessionUpdated", + "session.woke": "#/components/schemas/TypedEventStreamEnvelopeSessionWoke", + "worker.operation": "#/components/schemas/TypedEventStreamEnvelopeWorkerOperation" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeBeadClosed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeBeadCreated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeBeadUpdated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityCreated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityInitFailed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityReady" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityResumed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCitySuspended" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityUnregisterFailed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityUnregisterRequested" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityUnregistered" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeControllerStarted" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeControllerStopped" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeConvoyClosed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeConvoyCreated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgAdapterAdded" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgAdapterRemoved" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgBound" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgGroupCreated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgInbound" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgOutbound" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgUnbound" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailArchived" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailDeleted" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailMarkedRead" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailMarkedUnread" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailRead" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailReplied" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailSent" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeOrderCompleted" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeOrderFailed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeOrderFired" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeProviderSwapped" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionCrashed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionDraining" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionIdleKilled" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionQuarantined" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionStopped" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionSuspended" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionUndrained" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionUpdated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionWoke" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeWorkerOperation" + } + ], + "title": "Typed city event stream envelope" + }, + "TypedEventStreamEnvelopeBeadClosed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.closed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope bead.closed", + "type": "object" + }, + "TypedEventStreamEnvelopeBeadCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope bead.created", + "type": "object" + }, + "TypedEventStreamEnvelopeBeadUpdated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.updated", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope bead.updated", + "type": "object" + }, + "TypedEventStreamEnvelopeCityCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.created", + "type": "object" + }, + "TypedEventStreamEnvelopeCityInitFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.init_failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.init_failed", + "type": "object" + }, + "TypedEventStreamEnvelopeCityReady": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.ready", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.ready", + "type": "object" + }, + "TypedEventStreamEnvelopeCityResumed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.resumed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.resumed", + "type": "object" + }, + "TypedEventStreamEnvelopeCitySuspended": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.suspended", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.suspended", + "type": "object" + }, + "TypedEventStreamEnvelopeCityUnregisterFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregister_failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.unregister_failed", + "type": "object" + }, + "TypedEventStreamEnvelopeCityUnregisterRequested": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregister_requested", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.unregister_requested", + "type": "object" + }, + "TypedEventStreamEnvelopeCityUnregistered": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregistered", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.unregistered", + "type": "object" + }, + "TypedEventStreamEnvelopeControllerStarted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "controller.started", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope controller.started", + "type": "object" + }, + "TypedEventStreamEnvelopeControllerStopped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "controller.stopped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope controller.stopped", + "type": "object" + }, + "TypedEventStreamEnvelopeConvoyClosed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "convoy.closed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope convoy.closed", + "type": "object" + }, + "TypedEventStreamEnvelopeConvoyCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "convoy.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope convoy.created", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgAdapterAdded": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/AdapterEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.adapter_added", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.adapter_added", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgAdapterRemoved": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/AdapterEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.adapter_removed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.adapter_removed", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgBound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BoundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.bound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.bound", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgGroupCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/GroupCreatedEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.group_created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.group_created", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgInbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/InboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.inbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.inbound", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgOutbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/OutboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.outbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.outbound", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgUnbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/UnboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.unbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.unbound", + "type": "object" + }, + "TypedEventStreamEnvelopeMailArchived": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.archived", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.archived", + "type": "object" + }, + "TypedEventStreamEnvelopeMailDeleted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.deleted", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.deleted", + "type": "object" + }, + "TypedEventStreamEnvelopeMailMarkedRead": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.marked_read", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.marked_read", + "type": "object" + }, + "TypedEventStreamEnvelopeMailMarkedUnread": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.marked_unread", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.marked_unread", + "type": "object" + }, + "TypedEventStreamEnvelopeMailRead": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.read", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.read", + "type": "object" + }, + "TypedEventStreamEnvelopeMailReplied": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.replied", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.replied", + "type": "object" + }, + "TypedEventStreamEnvelopeMailSent": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.sent", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.sent", + "type": "object" + }, + "TypedEventStreamEnvelopeOrderCompleted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.completed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope order.completed", + "type": "object" + }, + "TypedEventStreamEnvelopeOrderFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope order.failed", + "type": "object" + }, + "TypedEventStreamEnvelopeOrderFired": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.fired", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope order.fired", + "type": "object" + }, + "TypedEventStreamEnvelopeProviderSwapped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "provider.swapped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope provider.swapped", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionCrashed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.crashed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.crashed", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionDraining": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.draining", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.draining", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionIdleKilled": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.idle_killed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.idle_killed", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionQuarantined": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.quarantined", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.quarantined", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionStopped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.stopped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.stopped", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionSuspended": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.suspended", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.suspended", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionUndrained": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.undrained", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.undrained", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionUpdated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.updated", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.updated", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionWoke": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.woke", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.woke", + "type": "object" + }, + "TypedEventStreamEnvelopeWorkerOperation": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/WorkerOperationEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "worker.operation", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope worker.operation", + "type": "object" + }, + "TypedTaggedEventStreamEnvelope": { + "description": "Discriminated union of supervisor event stream envelopes. Each variant constrains the envelope type and payload schema together and includes the source city.", + "discriminator": { + "mapping": { + "bead.closed": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadClosed", + "bead.created": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadCreated", + "bead.updated": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadUpdated", + "city.created": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityCreated", + "city.init_failed": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityInitFailed", + "city.ready": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityReady", + "city.resumed": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityResumed", + "city.suspended": "#/components/schemas/TypedTaggedEventStreamEnvelopeCitySuspended", + "city.unregister_failed": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregisterFailed", + "city.unregister_requested": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregisterRequested", + "city.unregistered": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregistered", + "controller.started": "#/components/schemas/TypedTaggedEventStreamEnvelopeControllerStarted", + "controller.stopped": "#/components/schemas/TypedTaggedEventStreamEnvelopeControllerStopped", + "convoy.closed": "#/components/schemas/TypedTaggedEventStreamEnvelopeConvoyClosed", + "convoy.created": "#/components/schemas/TypedTaggedEventStreamEnvelopeConvoyCreated", + "extmsg.adapter_added": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded", + "extmsg.adapter_removed": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved", + "extmsg.bound": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgBound", + "extmsg.group_created": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgGroupCreated", + "extmsg.inbound": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgInbound", + "extmsg.outbound": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgOutbound", + "extmsg.unbound": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgUnbound", + "mail.archived": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailArchived", + "mail.deleted": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailDeleted", + "mail.marked_read": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailMarkedRead", + "mail.marked_unread": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailMarkedUnread", + "mail.read": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailRead", + "mail.replied": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailReplied", + "mail.sent": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailSent", + "order.completed": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderCompleted", + "order.failed": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderFailed", + "order.fired": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderFired", + "provider.swapped": "#/components/schemas/TypedTaggedEventStreamEnvelopeProviderSwapped", + "session.crashed": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionCrashed", + "session.draining": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionDraining", + "session.idle_killed": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionIdleKilled", + "session.quarantined": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionQuarantined", + "session.stopped": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionStopped", + "session.suspended": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionSuspended", + "session.undrained": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionUndrained", + "session.updated": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionUpdated", + "session.woke": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionWoke", + "worker.operation": "#/components/schemas/TypedTaggedEventStreamEnvelopeWorkerOperation" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadClosed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadCreated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadUpdated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityCreated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityInitFailed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityReady" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityResumed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCitySuspended" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregisterFailed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregisterRequested" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregistered" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeControllerStarted" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeControllerStopped" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeConvoyClosed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeConvoyCreated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgBound" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgGroupCreated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgInbound" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgOutbound" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgUnbound" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailArchived" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailDeleted" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailMarkedRead" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailMarkedUnread" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailRead" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailReplied" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailSent" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderCompleted" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderFailed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderFired" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeProviderSwapped" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionCrashed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionDraining" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionIdleKilled" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionQuarantined" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionStopped" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionSuspended" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionUndrained" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionUpdated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionWoke" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeWorkerOperation" + } + ], + "title": "Typed supervisor event stream envelope" + }, + "TypedTaggedEventStreamEnvelopeBeadClosed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.closed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope bead.closed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeBeadCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope bead.created", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeBeadUpdated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.updated", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope bead.updated", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.created", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityInitFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.init_failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.init_failed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityReady": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.ready", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.ready", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityResumed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.resumed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.resumed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCitySuspended": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.suspended", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.suspended", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityUnregisterFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregister_failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.unregister_failed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityUnregisterRequested": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregister_requested", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.unregister_requested", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityUnregistered": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregistered", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.unregistered", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeControllerStarted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "controller.started", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope controller.started", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeControllerStopped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "controller.stopped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope controller.stopped", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeConvoyClosed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "convoy.closed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope convoy.closed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeConvoyCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "convoy.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope convoy.created", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/AdapterEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.adapter_added", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.adapter_added", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/AdapterEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.adapter_removed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.adapter_removed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgBound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BoundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.bound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.bound", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgGroupCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/GroupCreatedEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.group_created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.group_created", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgInbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/InboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.inbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.inbound", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgOutbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/OutboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.outbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.outbound", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgUnbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/UnboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.unbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.unbound", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailArchived": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.archived", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.archived", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailDeleted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.deleted", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.deleted", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailMarkedRead": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.marked_read", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.marked_read", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailMarkedUnread": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.marked_unread", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.marked_unread", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailRead": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.read", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.read", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailReplied": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.replied", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.replied", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailSent": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.sent", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.sent", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeOrderCompleted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.completed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope order.completed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeOrderFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope order.failed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeOrderFired": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.fired", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope order.fired", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeProviderSwapped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "provider.swapped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope provider.swapped", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionCrashed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.crashed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.crashed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionDraining": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.draining", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.draining", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionIdleKilled": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.idle_killed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.idle_killed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionQuarantined": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.quarantined", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.quarantined", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionStopped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.stopped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.stopped", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionSuspended": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.suspended", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.suspended", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionUndrained": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.undrained", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.undrained", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionUpdated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.updated", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.updated", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionWoke": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.woke", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.woke", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeWorkerOperation": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/WorkerOperationEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "worker.operation", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope worker.operation", + "type": "object" + }, "UnboundEventPayload": { "additionalProperties": false, "properties": { @@ -10548,7 +14698,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/EventStreamEnvelope" + "$ref": "#/components/schemas/TypedEventStreamEnvelope" }, "event": { "const": "event", @@ -18136,7 +22286,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/TaggedEventStreamEnvelope" + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelope" }, "event": { "const": "tagged_event", diff --git a/internal/api/event_envelope_schemas.go b/internal/api/event_envelope_schemas.go new file mode 100644 index 0000000000..d40848ba69 --- /dev/null +++ b/internal/api/event_envelope_schemas.go @@ -0,0 +1,162 @@ +package api + +import ( + "reflect" + "sort" + "strings" + + "github.com/danielgtaylor/huma/v2" + "github.com/gastownhall/gascity/internal/events" +) + +type typedEventStreamEnvelopeSchema struct{} + +func (typedEventStreamEnvelopeSchema) Schema(r huma.Registry) *huma.Schema { + return registerTypedEventEnvelopeSchema(r, typedEventEnvelopeSchemaConfig{ + name: "TypedEventStreamEnvelope", + title: "Typed city event stream envelope", + description: "Discriminated union of city event stream envelopes. Each variant constrains the envelope type and payload schema together.", + }) +} + +type typedTaggedEventStreamEnvelopeSchema struct{} + +func (typedTaggedEventStreamEnvelopeSchema) Schema(r huma.Registry) *huma.Schema { + return registerTypedEventEnvelopeSchema(r, typedEventEnvelopeSchemaConfig{ + name: "TypedTaggedEventStreamEnvelope", + title: "Typed supervisor event stream envelope", + description: "Discriminated union of supervisor event stream envelopes. Each variant constrains the envelope type and payload schema together and includes the source city.", + includeCity: true, + }) +} + +type typedEventEnvelopeSchemaConfig struct { + name string + title string + description string + includeCity bool +} + +type typedEventEnvelopeVariant struct { + eventType string + payloadType reflect.Type +} + +func registerEventEnvelopeCompatibilitySchemas(r huma.Registry) { + r.Schema(reflect.TypeOf(eventStreamEnvelope{}), true, "EventStreamEnvelope") + r.Schema(reflect.TypeOf(taggedEventStreamEnvelope{}), true, "TaggedEventStreamEnvelope") + _ = typedEventStreamEnvelopeSchema{}.Schema(r) + _ = typedTaggedEventStreamEnvelopeSchema{}.Schema(r) +} + +func registerTypedEventEnvelopeSchema(r huma.Registry, cfg typedEventEnvelopeSchemaConfig) *huma.Schema { + if _, ok := r.Map()[cfg.name]; !ok { + variants := typedEventEnvelopeVariants() + oneOf := make([]*huma.Schema, 0, len(variants)) + mapping := make(map[string]string, len(variants)) + for _, variant := range variants { + variantName := cfg.name + eventTypeSchemaSuffix(variant.eventType) + ref := schemaRefPrefix + variantName + if _, ok := r.Map()[variantName]; !ok { + r.Map()[variantName] = typedEventEnvelopeVariantSchema(r, variant, cfg) + } + oneOf = append(oneOf, &huma.Schema{Ref: ref}) + mapping[variant.eventType] = ref + } + r.Map()[cfg.name] = &huma.Schema{ + Title: cfg.title, + Description: cfg.description, + OneOf: oneOf, + Discriminator: &huma.Discriminator{ + PropertyName: "type", + Mapping: mapping, + }, + } + } + return &huma.Schema{Ref: schemaRefPrefix + cfg.name} +} + +func typedEventEnvelopeVariants() []typedEventEnvelopeVariant { + registered := events.RegisteredPayloadTypes() + variants := make([]typedEventEnvelopeVariant, 0, len(events.KnownEventTypes)) + for _, eventType := range events.KnownEventTypes { + payload, ok := registered[eventType] + if !ok { + panic("api: known event type has no registered payload: " + eventType) + } + payloadType := reflect.TypeOf(payload) + if payloadType == nil { + panic("api: known event type has nil registered payload: " + eventType) + } + variants = append(variants, typedEventEnvelopeVariant{ + eventType: eventType, + payloadType: payloadType, + }) + } + sort.Slice(variants, func(i, j int) bool { + return variants[i].eventType < variants[j].eventType + }) + return variants +} + +func typedEventEnvelopeVariantSchema(r huma.Registry, variant typedEventEnvelopeVariant, cfg typedEventEnvelopeSchemaConfig) *huma.Schema { + properties := map[string]*huma.Schema{ + "seq": { + Type: huma.TypeInteger, + Format: "int64", + Minimum: float64Ptr(0), + }, + "type": { + Type: huma.TypeString, + Extensions: map[string]any{ + "const": variant.eventType, + }, + }, + "ts": { + Type: huma.TypeString, + Format: "date-time", + }, + "actor": { + Type: huma.TypeString, + }, + "subject": { + Type: huma.TypeString, + }, + "message": { + Type: huma.TypeString, + }, + "workflow": r.Schema(reflect.TypeOf(workflowEventProjection{}), true, "WorkflowEventProjection"), + "payload": r.Schema(variant.payloadType, true, variant.payloadType.Name()), + } + required := []string{"seq", "type", "ts", "actor", "payload"} + if cfg.includeCity { + properties["city"] = &huma.Schema{Type: huma.TypeString} + required = append(required, "city") + } + return &huma.Schema{ + Title: cfg.name + " " + variant.eventType, + Type: huma.TypeObject, + AdditionalProperties: false, + Properties: properties, + Required: required, + } +} + +func eventTypeSchemaSuffix(eventType string) string { + parts := strings.FieldsFunc(eventType, func(r rune) bool { + return r == '.' || r == '_' || r == '-' + }) + var out strings.Builder + for _, part := range parts { + if part == "" { + continue + } + out.WriteString(strings.ToUpper(part[:1])) + out.WriteString(part[1:]) + } + return out.String() +} + +func float64Ptr(v float64) *float64 { + return &v +} diff --git a/internal/api/genclient/client_gen.go b/internal/api/genclient/client_gen.go index dae325d154..2f98ea2935 100644 --- a/internal/api/genclient/client_gen.go +++ b/internal/api/genclient/client_gen.go @@ -7,6 +7,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -2229,6 +2230,11 @@ type SessionResponse struct { Title string `json:"title"` } +// SessionStreamCommonEvent Non-message events emitted on the session SSE stream: activity transitions, pending interactions, and keepalive heartbeats. The concrete variant is identified by the SSE event name. +type SessionStreamCommonEvent struct { + union json.RawMessage +} + // SessionStreamMessageEvent defines model for SessionStreamMessageEvent. type SessionStreamMessageEvent struct { Format string `json:"format"` @@ -2512,1188 +2518,4874 @@ type TranscriptMessageKind string // TranscriptProvenance Provenance of a transcript entry (freshly observed vs. replayed from persisted history). type TranscriptProvenance string -// UnboundEventPayload defines model for UnboundEventPayload. -type UnboundEventPayload struct { - Count int64 `json:"count"` - SessionId string `json:"session_id"` +// TypedEventStreamEnvelope Discriminated union of city event stream envelopes. Each variant constrains the envelope type and payload schema together. +type TypedEventStreamEnvelope struct { + union json.RawMessage } -// WireEvent defines model for WireEvent. -type WireEvent struct { - Actor string `json:"actor"` - Message *string `json:"message,omitempty"` - Payload *EventPayload `json:"payload,omitempty"` - Seq int64 `json:"seq"` - Subject *string `json:"subject,omitempty"` - Ts time.Time `json:"ts"` - Type string `json:"type"` +// TypedEventStreamEnvelopeBeadClosed defines model for TypedEventStreamEnvelopeBeadClosed. +type TypedEventStreamEnvelopeBeadClosed struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload BeadEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` } -// WireTaggedEvent defines model for WireTaggedEvent. -type WireTaggedEvent struct { - Actor string `json:"actor"` - City string `json:"city"` - Message *string `json:"message,omitempty"` - Payload *EventPayload `json:"payload,omitempty"` - Seq int64 `json:"seq"` - Subject *string `json:"subject,omitempty"` - Ts time.Time `json:"ts"` - Type string `json:"type"` +// TypedEventStreamEnvelopeBeadCreated defines model for TypedEventStreamEnvelopeBeadCreated. +type TypedEventStreamEnvelopeBeadCreated struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload BeadEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` } -// WorkerOperationEventPayload defines model for WorkerOperationEventPayload. -type WorkerOperationEventPayload struct { - Delivered *bool `json:"delivered,omitempty"` - DurationMs int64 `json:"duration_ms"` - Error *string `json:"error,omitempty"` - FinishedAt time.Time `json:"finished_at"` - OpId string `json:"op_id"` - Operation string `json:"operation"` - Provider *string `json:"provider,omitempty"` - Queued *bool `json:"queued,omitempty"` - Result string `json:"result"` - SessionId *string `json:"session_id,omitempty"` - SessionName *string `json:"session_name,omitempty"` - StartedAt time.Time `json:"started_at"` - Template *string `json:"template,omitempty"` - Transport *string `json:"transport,omitempty"` +// TypedEventStreamEnvelopeBeadUpdated defines model for TypedEventStreamEnvelopeBeadUpdated. +type TypedEventStreamEnvelopeBeadUpdated struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload BeadEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` } -// WorkflowAttemptSummary defines model for WorkflowAttemptSummary. -type WorkflowAttemptSummary struct { - ActiveAttempt int64 `json:"active_attempt"` - AttemptCount int64 `json:"attempt_count"` - MaxAttempts *int64 `json:"max_attempts,omitempty"` +// TypedEventStreamEnvelopeCityCreated defines model for TypedEventStreamEnvelopeCityCreated. +type TypedEventStreamEnvelopeCityCreated struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload CityLifecyclePayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` } -// WorkflowBeadResponse defines model for WorkflowBeadResponse. -type WorkflowBeadResponse struct { - Assignee *string `json:"assignee,omitempty"` - Attempt *int64 `json:"attempt,omitempty"` - Id string `json:"id"` - Kind string `json:"kind"` - LogicalBeadId *string `json:"logical_bead_id,omitempty"` - Metadata map[string]string `json:"metadata"` - ScopeRef *string `json:"scope_ref,omitempty"` - Status string `json:"status"` - StepRef *string `json:"step_ref,omitempty"` - Title string `json:"title"` +// TypedEventStreamEnvelopeCityInitFailed defines model for TypedEventStreamEnvelopeCityInitFailed. +type TypedEventStreamEnvelopeCityInitFailed struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload CityLifecyclePayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` } -// WorkflowDeleteResponse defines model for WorkflowDeleteResponse. -type WorkflowDeleteResponse struct { - // Closed Number of beads closed. - Closed int64 `json:"closed"` - - // Deleted Number of beads deleted. - Deleted int64 `json:"deleted"` - - // Partial True when one or more teardown steps failed; Closed/Deleted still reflect what succeeded. - Partial *bool `json:"partial,omitempty"` - - // PartialErrors Human-readable errors from failed teardown steps. - PartialErrors *[]string `json:"partial_errors,omitempty"` - - // WorkflowId Workflow ID. - WorkflowId string `json:"workflow_id"` +// TypedEventStreamEnvelopeCityReady defines model for TypedEventStreamEnvelopeCityReady. +type TypedEventStreamEnvelopeCityReady struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload CityLifecyclePayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` } -// WorkflowDepResponse defines model for WorkflowDepResponse. -type WorkflowDepResponse struct { - From string `json:"from"` - Kind *string `json:"kind,omitempty"` - To string `json:"to"` +// TypedEventStreamEnvelopeCityResumed defines model for TypedEventStreamEnvelopeCityResumed. +type TypedEventStreamEnvelopeCityResumed struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` } -// WorkflowEventProjection defines model for WorkflowEventProjection. -type WorkflowEventProjection struct { - AttemptSummary *WorkflowAttemptSummary `json:"attempt_summary,omitempty"` - Bead WorkflowBeadResponse `json:"bead"` - ChangedFields *[]string `json:"changed_fields"` - EventSeq int64 `json:"event_seq"` - EventTs string `json:"event_ts"` - EventType string `json:"event_type"` - LogicalNodeId string `json:"logical_node_id"` - RequiresResync *bool `json:"requires_resync,omitempty"` - RootBeadId string `json:"root_bead_id"` - RootStoreRef string `json:"root_store_ref"` - ScopeKind string `json:"scope_kind"` - ScopeRef string `json:"scope_ref"` - Type string `json:"type"` - WatchGeneration string `json:"watch_generation"` - WorkflowId string `json:"workflow_id"` - WorkflowSeq int64 `json:"workflow_seq"` +// TypedEventStreamEnvelopeCitySuspended defines model for TypedEventStreamEnvelopeCitySuspended. +type TypedEventStreamEnvelopeCitySuspended struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` } -// WorkflowSnapshotResponse defines model for WorkflowSnapshotResponse. -type WorkflowSnapshotResponse struct { - Beads *[]WorkflowBeadResponse `json:"beads"` - Deps *[]WorkflowDepResponse `json:"deps"` - LogicalEdges *[]WorkflowDepResponse `json:"logical_edges"` - LogicalNodes *[]LogicalNode `json:"logical_nodes"` - Partial bool `json:"partial"` - ResolvedRootStore string `json:"resolved_root_store"` - RootBeadId string `json:"root_bead_id"` - RootStoreRef string `json:"root_store_ref"` - ScopeGroups *[]ScopeGroup `json:"scope_groups"` - ScopeKind string `json:"scope_kind"` - ScopeRef string `json:"scope_ref"` - SnapshotEventSeq *int64 `json:"snapshot_event_seq,omitempty"` - SnapshotVersion int64 `json:"snapshot_version"` - StoresScanned *[]string `json:"stores_scanned"` - WorkflowId string `json:"workflow_id"` +// TypedEventStreamEnvelopeCityUnregisterFailed defines model for TypedEventStreamEnvelopeCityUnregisterFailed. +type TypedEventStreamEnvelopeCityUnregisterFailed struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload CityLifecyclePayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` } -// WorkspaceResponse defines model for WorkspaceResponse. -type WorkspaceResponse struct { - DeclaredName *string `json:"declared_name,omitempty"` - DeclaredPrefix *string `json:"declared_prefix,omitempty"` - Name string `json:"name"` - Prefix *string `json:"prefix,omitempty"` - Provider *string `json:"provider,omitempty"` - SessionTemplate *string `json:"session_template,omitempty"` - Suspended bool `json:"suspended"` +// TypedEventStreamEnvelopeCityUnregisterRequested defines model for TypedEventStreamEnvelopeCityUnregisterRequested. +type TypedEventStreamEnvelopeCityUnregisterRequested struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload CityLifecyclePayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` } -// PostV0CityParams defines parameters for PostV0City. -type PostV0CityParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// TypedEventStreamEnvelopeCityUnregistered defines model for TypedEventStreamEnvelopeCityUnregistered. +type TypedEventStreamEnvelopeCityUnregistered struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload CityLifecyclePayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` } -// PatchV0CityByCityNameParams defines parameters for PatchV0CityByCityName. -type PatchV0CityByCityNameParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// TypedEventStreamEnvelopeControllerStarted defines model for TypedEventStreamEnvelopeControllerStarted. +type TypedEventStreamEnvelopeControllerStarted struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` } -// DeleteV0CityByCityNameAgentByBaseParams defines parameters for DeleteV0CityByCityNameAgentByBase. -type DeleteV0CityByCityNameAgentByBaseParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// TypedEventStreamEnvelopeControllerStopped defines model for TypedEventStreamEnvelopeControllerStopped. +type TypedEventStreamEnvelopeControllerStopped struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` } -// PatchV0CityByCityNameAgentByBaseParams defines parameters for PatchV0CityByCityNameAgentByBase. -type PatchV0CityByCityNameAgentByBaseParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// TypedEventStreamEnvelopeConvoyClosed defines model for TypedEventStreamEnvelopeConvoyClosed. +type TypedEventStreamEnvelopeConvoyClosed struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` } -// GetV0CityByCityNameAgentByBaseOutputParams defines parameters for GetV0CityByCityNameAgentByBaseOutput. -type GetV0CityByCityNameAgentByBaseOutputParams struct { - // Tail Number of recent compaction segments to return. This API parameter keeps compaction-segment semantics even though gc session logs --tail counts displayed transcript entries. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. - Tail *string `form:"tail,omitempty" json:"tail,omitempty"` - - // Before Message UUID cursor for loading older messages. - Before *string `form:"before,omitempty" json:"before,omitempty"` +// TypedEventStreamEnvelopeConvoyCreated defines model for TypedEventStreamEnvelopeConvoyCreated. +type TypedEventStreamEnvelopeConvoyCreated struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` } -// PostV0CityByCityNameAgentByBaseByActionParams defines parameters for PostV0CityByCityNameAgentByBaseByAction. -type PostV0CityByCityNameAgentByBaseByActionParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. +// TypedEventStreamEnvelopeExtmsgAdapterAdded defines model for TypedEventStreamEnvelopeExtmsgAdapterAdded. +type TypedEventStreamEnvelopeExtmsgAdapterAdded struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload AdapterEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeExtmsgAdapterRemoved defines model for TypedEventStreamEnvelopeExtmsgAdapterRemoved. +type TypedEventStreamEnvelopeExtmsgAdapterRemoved struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload AdapterEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeExtmsgBound defines model for TypedEventStreamEnvelopeExtmsgBound. +type TypedEventStreamEnvelopeExtmsgBound struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload BoundEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeExtmsgGroupCreated defines model for TypedEventStreamEnvelopeExtmsgGroupCreated. +type TypedEventStreamEnvelopeExtmsgGroupCreated struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload GroupCreatedEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeExtmsgInbound defines model for TypedEventStreamEnvelopeExtmsgInbound. +type TypedEventStreamEnvelopeExtmsgInbound struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload InboundEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeExtmsgOutbound defines model for TypedEventStreamEnvelopeExtmsgOutbound. +type TypedEventStreamEnvelopeExtmsgOutbound struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload OutboundEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeExtmsgUnbound defines model for TypedEventStreamEnvelopeExtmsgUnbound. +type TypedEventStreamEnvelopeExtmsgUnbound struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload UnboundEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeMailArchived defines model for TypedEventStreamEnvelopeMailArchived. +type TypedEventStreamEnvelopeMailArchived struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload MailEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeMailDeleted defines model for TypedEventStreamEnvelopeMailDeleted. +type TypedEventStreamEnvelopeMailDeleted struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload MailEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeMailMarkedRead defines model for TypedEventStreamEnvelopeMailMarkedRead. +type TypedEventStreamEnvelopeMailMarkedRead struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload MailEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeMailMarkedUnread defines model for TypedEventStreamEnvelopeMailMarkedUnread. +type TypedEventStreamEnvelopeMailMarkedUnread struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload MailEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeMailRead defines model for TypedEventStreamEnvelopeMailRead. +type TypedEventStreamEnvelopeMailRead struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload MailEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeMailReplied defines model for TypedEventStreamEnvelopeMailReplied. +type TypedEventStreamEnvelopeMailReplied struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload MailEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeMailSent defines model for TypedEventStreamEnvelopeMailSent. +type TypedEventStreamEnvelopeMailSent struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload MailEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeOrderCompleted defines model for TypedEventStreamEnvelopeOrderCompleted. +type TypedEventStreamEnvelopeOrderCompleted struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeOrderFailed defines model for TypedEventStreamEnvelopeOrderFailed. +type TypedEventStreamEnvelopeOrderFailed struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeOrderFired defines model for TypedEventStreamEnvelopeOrderFired. +type TypedEventStreamEnvelopeOrderFired struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeProviderSwapped defines model for TypedEventStreamEnvelopeProviderSwapped. +type TypedEventStreamEnvelopeProviderSwapped struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeSessionCrashed defines model for TypedEventStreamEnvelopeSessionCrashed. +type TypedEventStreamEnvelopeSessionCrashed struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeSessionDraining defines model for TypedEventStreamEnvelopeSessionDraining. +type TypedEventStreamEnvelopeSessionDraining struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeSessionIdleKilled defines model for TypedEventStreamEnvelopeSessionIdleKilled. +type TypedEventStreamEnvelopeSessionIdleKilled struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeSessionQuarantined defines model for TypedEventStreamEnvelopeSessionQuarantined. +type TypedEventStreamEnvelopeSessionQuarantined struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeSessionStopped defines model for TypedEventStreamEnvelopeSessionStopped. +type TypedEventStreamEnvelopeSessionStopped struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeSessionSuspended defines model for TypedEventStreamEnvelopeSessionSuspended. +type TypedEventStreamEnvelopeSessionSuspended struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeSessionUndrained defines model for TypedEventStreamEnvelopeSessionUndrained. +type TypedEventStreamEnvelopeSessionUndrained struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeSessionUpdated defines model for TypedEventStreamEnvelopeSessionUpdated. +type TypedEventStreamEnvelopeSessionUpdated struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeSessionWoke defines model for TypedEventStreamEnvelopeSessionWoke. +type TypedEventStreamEnvelopeSessionWoke struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedEventStreamEnvelopeWorkerOperation defines model for TypedEventStreamEnvelopeWorkerOperation. +type TypedEventStreamEnvelopeWorkerOperation struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload WorkerOperationEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelope Discriminated union of supervisor event stream envelopes. Each variant constrains the envelope type and payload schema together and includes the source city. +type TypedTaggedEventStreamEnvelope struct { + union json.RawMessage +} + +// TypedTaggedEventStreamEnvelopeBeadClosed defines model for TypedTaggedEventStreamEnvelopeBeadClosed. +type TypedTaggedEventStreamEnvelopeBeadClosed struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload BeadEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeBeadCreated defines model for TypedTaggedEventStreamEnvelopeBeadCreated. +type TypedTaggedEventStreamEnvelopeBeadCreated struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload BeadEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeBeadUpdated defines model for TypedTaggedEventStreamEnvelopeBeadUpdated. +type TypedTaggedEventStreamEnvelopeBeadUpdated struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload BeadEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeCityCreated defines model for TypedTaggedEventStreamEnvelopeCityCreated. +type TypedTaggedEventStreamEnvelopeCityCreated struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload CityLifecyclePayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeCityInitFailed defines model for TypedTaggedEventStreamEnvelopeCityInitFailed. +type TypedTaggedEventStreamEnvelopeCityInitFailed struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload CityLifecyclePayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeCityReady defines model for TypedTaggedEventStreamEnvelopeCityReady. +type TypedTaggedEventStreamEnvelopeCityReady struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload CityLifecyclePayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeCityResumed defines model for TypedTaggedEventStreamEnvelopeCityResumed. +type TypedTaggedEventStreamEnvelopeCityResumed struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeCitySuspended defines model for TypedTaggedEventStreamEnvelopeCitySuspended. +type TypedTaggedEventStreamEnvelopeCitySuspended struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeCityUnregisterFailed defines model for TypedTaggedEventStreamEnvelopeCityUnregisterFailed. +type TypedTaggedEventStreamEnvelopeCityUnregisterFailed struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload CityLifecyclePayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeCityUnregisterRequested defines model for TypedTaggedEventStreamEnvelopeCityUnregisterRequested. +type TypedTaggedEventStreamEnvelopeCityUnregisterRequested struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload CityLifecyclePayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeCityUnregistered defines model for TypedTaggedEventStreamEnvelopeCityUnregistered. +type TypedTaggedEventStreamEnvelopeCityUnregistered struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload CityLifecyclePayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeControllerStarted defines model for TypedTaggedEventStreamEnvelopeControllerStarted. +type TypedTaggedEventStreamEnvelopeControllerStarted struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeControllerStopped defines model for TypedTaggedEventStreamEnvelopeControllerStopped. +type TypedTaggedEventStreamEnvelopeControllerStopped struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeConvoyClosed defines model for TypedTaggedEventStreamEnvelopeConvoyClosed. +type TypedTaggedEventStreamEnvelopeConvoyClosed struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeConvoyCreated defines model for TypedTaggedEventStreamEnvelopeConvoyCreated. +type TypedTaggedEventStreamEnvelopeConvoyCreated struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded defines model for TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded. +type TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload AdapterEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved defines model for TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved. +type TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload AdapterEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeExtmsgBound defines model for TypedTaggedEventStreamEnvelopeExtmsgBound. +type TypedTaggedEventStreamEnvelopeExtmsgBound struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload BoundEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeExtmsgGroupCreated defines model for TypedTaggedEventStreamEnvelopeExtmsgGroupCreated. +type TypedTaggedEventStreamEnvelopeExtmsgGroupCreated struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload GroupCreatedEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeExtmsgInbound defines model for TypedTaggedEventStreamEnvelopeExtmsgInbound. +type TypedTaggedEventStreamEnvelopeExtmsgInbound struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload InboundEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeExtmsgOutbound defines model for TypedTaggedEventStreamEnvelopeExtmsgOutbound. +type TypedTaggedEventStreamEnvelopeExtmsgOutbound struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload OutboundEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeExtmsgUnbound defines model for TypedTaggedEventStreamEnvelopeExtmsgUnbound. +type TypedTaggedEventStreamEnvelopeExtmsgUnbound struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload UnboundEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeMailArchived defines model for TypedTaggedEventStreamEnvelopeMailArchived. +type TypedTaggedEventStreamEnvelopeMailArchived struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload MailEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeMailDeleted defines model for TypedTaggedEventStreamEnvelopeMailDeleted. +type TypedTaggedEventStreamEnvelopeMailDeleted struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload MailEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeMailMarkedRead defines model for TypedTaggedEventStreamEnvelopeMailMarkedRead. +type TypedTaggedEventStreamEnvelopeMailMarkedRead struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload MailEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeMailMarkedUnread defines model for TypedTaggedEventStreamEnvelopeMailMarkedUnread. +type TypedTaggedEventStreamEnvelopeMailMarkedUnread struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload MailEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeMailRead defines model for TypedTaggedEventStreamEnvelopeMailRead. +type TypedTaggedEventStreamEnvelopeMailRead struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload MailEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeMailReplied defines model for TypedTaggedEventStreamEnvelopeMailReplied. +type TypedTaggedEventStreamEnvelopeMailReplied struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload MailEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeMailSent defines model for TypedTaggedEventStreamEnvelopeMailSent. +type TypedTaggedEventStreamEnvelopeMailSent struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload MailEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeOrderCompleted defines model for TypedTaggedEventStreamEnvelopeOrderCompleted. +type TypedTaggedEventStreamEnvelopeOrderCompleted struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeOrderFailed defines model for TypedTaggedEventStreamEnvelopeOrderFailed. +type TypedTaggedEventStreamEnvelopeOrderFailed struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeOrderFired defines model for TypedTaggedEventStreamEnvelopeOrderFired. +type TypedTaggedEventStreamEnvelopeOrderFired struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeProviderSwapped defines model for TypedTaggedEventStreamEnvelopeProviderSwapped. +type TypedTaggedEventStreamEnvelopeProviderSwapped struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeSessionCrashed defines model for TypedTaggedEventStreamEnvelopeSessionCrashed. +type TypedTaggedEventStreamEnvelopeSessionCrashed struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeSessionDraining defines model for TypedTaggedEventStreamEnvelopeSessionDraining. +type TypedTaggedEventStreamEnvelopeSessionDraining struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeSessionIdleKilled defines model for TypedTaggedEventStreamEnvelopeSessionIdleKilled. +type TypedTaggedEventStreamEnvelopeSessionIdleKilled struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeSessionQuarantined defines model for TypedTaggedEventStreamEnvelopeSessionQuarantined. +type TypedTaggedEventStreamEnvelopeSessionQuarantined struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeSessionStopped defines model for TypedTaggedEventStreamEnvelopeSessionStopped. +type TypedTaggedEventStreamEnvelopeSessionStopped struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeSessionSuspended defines model for TypedTaggedEventStreamEnvelopeSessionSuspended. +type TypedTaggedEventStreamEnvelopeSessionSuspended struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeSessionUndrained defines model for TypedTaggedEventStreamEnvelopeSessionUndrained. +type TypedTaggedEventStreamEnvelopeSessionUndrained struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeSessionUpdated defines model for TypedTaggedEventStreamEnvelopeSessionUpdated. +type TypedTaggedEventStreamEnvelopeSessionUpdated struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeSessionWoke defines model for TypedTaggedEventStreamEnvelopeSessionWoke. +type TypedTaggedEventStreamEnvelopeSessionWoke struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload NoPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// TypedTaggedEventStreamEnvelopeWorkerOperation defines model for TypedTaggedEventStreamEnvelopeWorkerOperation. +type TypedTaggedEventStreamEnvelopeWorkerOperation struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload WorkerOperationEventPayload `json:"payload"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` + Workflow *WorkflowEventProjection `json:"workflow,omitempty"` +} + +// UnboundEventPayload defines model for UnboundEventPayload. +type UnboundEventPayload struct { + Count int64 `json:"count"` + SessionId string `json:"session_id"` +} + +// WireEvent defines model for WireEvent. +type WireEvent struct { + Actor string `json:"actor"` + Message *string `json:"message,omitempty"` + Payload *EventPayload `json:"payload,omitempty"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` +} + +// WireTaggedEvent defines model for WireTaggedEvent. +type WireTaggedEvent struct { + Actor string `json:"actor"` + City string `json:"city"` + Message *string `json:"message,omitempty"` + Payload *EventPayload `json:"payload,omitempty"` + Seq int64 `json:"seq"` + Subject *string `json:"subject,omitempty"` + Ts time.Time `json:"ts"` + Type string `json:"type"` +} + +// WorkerOperationEventPayload defines model for WorkerOperationEventPayload. +type WorkerOperationEventPayload struct { + Delivered *bool `json:"delivered,omitempty"` + DurationMs int64 `json:"duration_ms"` + Error *string `json:"error,omitempty"` + FinishedAt time.Time `json:"finished_at"` + OpId string `json:"op_id"` + Operation string `json:"operation"` + Provider *string `json:"provider,omitempty"` + Queued *bool `json:"queued,omitempty"` + Result string `json:"result"` + SessionId *string `json:"session_id,omitempty"` + SessionName *string `json:"session_name,omitempty"` + StartedAt time.Time `json:"started_at"` + Template *string `json:"template,omitempty"` + Transport *string `json:"transport,omitempty"` +} + +// WorkflowAttemptSummary defines model for WorkflowAttemptSummary. +type WorkflowAttemptSummary struct { + ActiveAttempt int64 `json:"active_attempt"` + AttemptCount int64 `json:"attempt_count"` + MaxAttempts *int64 `json:"max_attempts,omitempty"` +} + +// WorkflowBeadResponse defines model for WorkflowBeadResponse. +type WorkflowBeadResponse struct { + Assignee *string `json:"assignee,omitempty"` + Attempt *int64 `json:"attempt,omitempty"` + Id string `json:"id"` + Kind string `json:"kind"` + LogicalBeadId *string `json:"logical_bead_id,omitempty"` + Metadata map[string]string `json:"metadata"` + ScopeRef *string `json:"scope_ref,omitempty"` + Status string `json:"status"` + StepRef *string `json:"step_ref,omitempty"` + Title string `json:"title"` +} + +// WorkflowDeleteResponse defines model for WorkflowDeleteResponse. +type WorkflowDeleteResponse struct { + // Closed Number of beads closed. + Closed int64 `json:"closed"` + + // Deleted Number of beads deleted. + Deleted int64 `json:"deleted"` + + // Partial True when one or more teardown steps failed; Closed/Deleted still reflect what succeeded. + Partial *bool `json:"partial,omitempty"` + + // PartialErrors Human-readable errors from failed teardown steps. + PartialErrors *[]string `json:"partial_errors,omitempty"` + + // WorkflowId Workflow ID. + WorkflowId string `json:"workflow_id"` +} + +// WorkflowDepResponse defines model for WorkflowDepResponse. +type WorkflowDepResponse struct { + From string `json:"from"` + Kind *string `json:"kind,omitempty"` + To string `json:"to"` +} + +// WorkflowEventProjection defines model for WorkflowEventProjection. +type WorkflowEventProjection struct { + AttemptSummary *WorkflowAttemptSummary `json:"attempt_summary,omitempty"` + Bead WorkflowBeadResponse `json:"bead"` + ChangedFields *[]string `json:"changed_fields"` + EventSeq int64 `json:"event_seq"` + EventTs string `json:"event_ts"` + EventType string `json:"event_type"` + LogicalNodeId string `json:"logical_node_id"` + RequiresResync *bool `json:"requires_resync,omitempty"` + RootBeadId string `json:"root_bead_id"` + RootStoreRef string `json:"root_store_ref"` + ScopeKind string `json:"scope_kind"` + ScopeRef string `json:"scope_ref"` + Type string `json:"type"` + WatchGeneration string `json:"watch_generation"` + WorkflowId string `json:"workflow_id"` + WorkflowSeq int64 `json:"workflow_seq"` +} + +// WorkflowSnapshotResponse defines model for WorkflowSnapshotResponse. +type WorkflowSnapshotResponse struct { + Beads *[]WorkflowBeadResponse `json:"beads"` + Deps *[]WorkflowDepResponse `json:"deps"` + LogicalEdges *[]WorkflowDepResponse `json:"logical_edges"` + LogicalNodes *[]LogicalNode `json:"logical_nodes"` + Partial bool `json:"partial"` + ResolvedRootStore string `json:"resolved_root_store"` + RootBeadId string `json:"root_bead_id"` + RootStoreRef string `json:"root_store_ref"` + ScopeGroups *[]ScopeGroup `json:"scope_groups"` + ScopeKind string `json:"scope_kind"` + ScopeRef string `json:"scope_ref"` + SnapshotEventSeq *int64 `json:"snapshot_event_seq,omitempty"` + SnapshotVersion int64 `json:"snapshot_version"` + StoresScanned *[]string `json:"stores_scanned"` + WorkflowId string `json:"workflow_id"` +} + +// WorkspaceResponse defines model for WorkspaceResponse. +type WorkspaceResponse struct { + DeclaredName *string `json:"declared_name,omitempty"` + DeclaredPrefix *string `json:"declared_prefix,omitempty"` + Name string `json:"name"` + Prefix *string `json:"prefix,omitempty"` + Provider *string `json:"provider,omitempty"` + SessionTemplate *string `json:"session_template,omitempty"` + Suspended bool `json:"suspended"` +} + +// PostV0CityParams defines parameters for PostV0City. +type PostV0CityParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PatchV0CityByCityNameParams defines parameters for PatchV0CityByCityName. +type PatchV0CityByCityNameParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// DeleteV0CityByCityNameAgentByBaseParams defines parameters for DeleteV0CityByCityNameAgentByBase. +type DeleteV0CityByCityNameAgentByBaseParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PatchV0CityByCityNameAgentByBaseParams defines parameters for PatchV0CityByCityNameAgentByBase. +type PatchV0CityByCityNameAgentByBaseParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameAgentByBaseOutputParams defines parameters for GetV0CityByCityNameAgentByBaseOutput. +type GetV0CityByCityNameAgentByBaseOutputParams struct { + // Tail Number of recent compaction segments to return. This API parameter keeps compaction-segment semantics even though gc session logs --tail counts displayed transcript entries. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. + Tail *string `form:"tail,omitempty" json:"tail,omitempty"` + + // Before Message UUID cursor for loading older messages. + Before *string `form:"before,omitempty" json:"before,omitempty"` +} + +// PostV0CityByCityNameAgentByBaseByActionParams defines parameters for PostV0CityByCityNameAgentByBaseByAction. +type PostV0CityByCityNameAgentByBaseByActionParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameAgentByBaseByActionParamsAction defines parameters for PostV0CityByCityNameAgentByBaseByAction. +type PostV0CityByCityNameAgentByBaseByActionParamsAction string + +// DeleteV0CityByCityNameAgentByDirByBaseParams defines parameters for DeleteV0CityByCityNameAgentByDirByBase. +type DeleteV0CityByCityNameAgentByDirByBaseParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PatchV0CityByCityNameAgentByDirByBaseParams defines parameters for PatchV0CityByCityNameAgentByDirByBase. +type PatchV0CityByCityNameAgentByDirByBaseParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameAgentByDirByBaseOutputParams defines parameters for GetV0CityByCityNameAgentByDirByBaseOutput. +type GetV0CityByCityNameAgentByDirByBaseOutputParams struct { + // Tail Number of recent compaction segments to return. This API parameter keeps compaction-segment semantics even though gc session logs --tail counts displayed transcript entries. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. + Tail *string `form:"tail,omitempty" json:"tail,omitempty"` + + // Before Message UUID cursor for loading older messages. + Before *string `form:"before,omitempty" json:"before,omitempty"` +} + +// PostV0CityByCityNameAgentByDirByBaseByActionParams defines parameters for PostV0CityByCityNameAgentByDirByBaseByAction. +type PostV0CityByCityNameAgentByDirByBaseByActionParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameAgentByDirByBaseByActionParamsAction defines parameters for PostV0CityByCityNameAgentByDirByBaseByAction. +type PostV0CityByCityNameAgentByDirByBaseByActionParamsAction string + +// GetV0CityByCityNameAgentsParams defines parameters for GetV0CityByCityNameAgents. +type GetV0CityByCityNameAgentsParams struct { + // Index Event sequence number; when provided, blocks until a newer event arrives. + Index *string `form:"index,omitempty" json:"index,omitempty"` + + // Wait How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. + Wait *string `form:"wait,omitempty" json:"wait,omitempty"` + + // Pool Filter by pool name. + Pool *string `form:"pool,omitempty" json:"pool,omitempty"` + + // Rig Filter by rig name. + Rig *string `form:"rig,omitempty" json:"rig,omitempty"` + + // Running Filter by running state. Omit to return all agents. + Running *GetV0CityByCityNameAgentsParamsRunning `form:"running,omitempty" json:"running,omitempty"` + + // Peek Include last output preview. + Peek *bool `form:"peek,omitempty" json:"peek,omitempty"` +} + +// GetV0CityByCityNameAgentsParamsRunning defines parameters for GetV0CityByCityNameAgents. +type GetV0CityByCityNameAgentsParamsRunning string + +// CreateAgentParams defines parameters for CreateAgent. +type CreateAgentParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// DeleteV0CityByCityNameBeadByIdParams defines parameters for DeleteV0CityByCityNameBeadById. +type DeleteV0CityByCityNameBeadByIdParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PatchV0CityByCityNameBeadByIdParams defines parameters for PatchV0CityByCityNameBeadById. +type PatchV0CityByCityNameBeadByIdParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameBeadByIdAssignParams defines parameters for PostV0CityByCityNameBeadByIdAssign. +type PostV0CityByCityNameBeadByIdAssignParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameBeadByIdCloseParams defines parameters for PostV0CityByCityNameBeadByIdClose. +type PostV0CityByCityNameBeadByIdCloseParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameBeadByIdReopenParams defines parameters for PostV0CityByCityNameBeadByIdReopen. +type PostV0CityByCityNameBeadByIdReopenParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameBeadByIdUpdateParams defines parameters for PostV0CityByCityNameBeadByIdUpdate. +type PostV0CityByCityNameBeadByIdUpdateParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameBeadsParams defines parameters for GetV0CityByCityNameBeads. +type GetV0CityByCityNameBeadsParams struct { + // Index Event sequence number; when provided, blocks until a newer event arrives. + Index *string `form:"index,omitempty" json:"index,omitempty"` + + // Wait How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. + Wait *string `form:"wait,omitempty" json:"wait,omitempty"` + + // Cursor Pagination cursor from a previous response's next_cursor field. + Cursor *string `form:"cursor,omitempty" json:"cursor,omitempty"` + + // Limit Maximum number of results to return. 0 = server default. + Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` + + // Status Filter by bead status. + Status *string `form:"status,omitempty" json:"status,omitempty"` + + // Type Filter by bead type. + Type *string `form:"type,omitempty" json:"type,omitempty"` + + // Label Filter by label. + Label *string `form:"label,omitempty" json:"label,omitempty"` + + // Assignee Filter by assignee. + Assignee *string `form:"assignee,omitempty" json:"assignee,omitempty"` + + // Rig Filter by rig. + Rig *string `form:"rig,omitempty" json:"rig,omitempty"` +} + +// CreateBeadParams defines parameters for CreateBead. +type CreateBeadParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` + + // IdempotencyKey Idempotency key for safe retries. + IdempotencyKey *string `json:"Idempotency-Key,omitempty"` +} + +// GetV0CityByCityNameBeadsReadyParams defines parameters for GetV0CityByCityNameBeadsReady. +type GetV0CityByCityNameBeadsReadyParams struct { + // Index Event sequence number; when provided, blocks until a newer event arrives. + Index *string `form:"index,omitempty" json:"index,omitempty"` + + // Wait How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. + Wait *string `form:"wait,omitempty" json:"wait,omitempty"` +} + +// DeleteV0CityByCityNameConvoyByIdParams defines parameters for DeleteV0CityByCityNameConvoyById. +type DeleteV0CityByCityNameConvoyByIdParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameConvoyByIdAddParams defines parameters for PostV0CityByCityNameConvoyByIdAdd. +type PostV0CityByCityNameConvoyByIdAddParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameConvoyByIdCloseParams defines parameters for PostV0CityByCityNameConvoyByIdClose. +type PostV0CityByCityNameConvoyByIdCloseParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameConvoyByIdRemoveParams defines parameters for PostV0CityByCityNameConvoyByIdRemove. +type PostV0CityByCityNameConvoyByIdRemoveParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameConvoysParams defines parameters for GetV0CityByCityNameConvoys. +type GetV0CityByCityNameConvoysParams struct { + // Index Event sequence number; when provided, blocks until a newer event arrives. + Index *string `form:"index,omitempty" json:"index,omitempty"` + + // Wait How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. + Wait *string `form:"wait,omitempty" json:"wait,omitempty"` + + // Cursor Pagination cursor from a previous response's next_cursor field. + Cursor *string `form:"cursor,omitempty" json:"cursor,omitempty"` + + // Limit Maximum number of results to return. 0 = server default. + Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` +} + +// CreateConvoyParams defines parameters for CreateConvoy. +type CreateConvoyParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameEventsParams defines parameters for GetV0CityByCityNameEvents. +type GetV0CityByCityNameEventsParams struct { + // Index Event sequence number; when provided, blocks until a newer event arrives. + Index *string `form:"index,omitempty" json:"index,omitempty"` + + // Wait How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. + Wait *string `form:"wait,omitempty" json:"wait,omitempty"` + + // Cursor Pagination cursor from a previous response's next_cursor field. + Cursor *string `form:"cursor,omitempty" json:"cursor,omitempty"` + + // Limit Maximum number of results to return. 0 = server default. + Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` + + // Type Filter by event type. + Type *string `form:"type,omitempty" json:"type,omitempty"` + + // Actor Filter by actor. + Actor *string `form:"actor,omitempty" json:"actor,omitempty"` + + // Since Filter events since duration ago (Go duration string, e.g. 5m). + Since *string `form:"since,omitempty" json:"since,omitempty"` +} + +// EmitEventParams defines parameters for EmitEvent. +type EmitEventParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// StreamEventsParams defines parameters for StreamEvents. +type StreamEventsParams struct { + // AfterSeq Reconnect position: only deliver events after this sequence number. + AfterSeq *string `form:"after_seq,omitempty" json:"after_seq,omitempty"` + + // LastEventID SSE reconnect position from the last received event ID. + LastEventID *string `json:"Last-Event-ID,omitempty"` +} + +// DeleteV0CityByCityNameExtmsgAdaptersParams defines parameters for DeleteV0CityByCityNameExtmsgAdapters. +type DeleteV0CityByCityNameExtmsgAdaptersParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// RegisterExtmsgAdapterParams defines parameters for RegisterExtmsgAdapter. +type RegisterExtmsgAdapterParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameExtmsgBindParams defines parameters for PostV0CityByCityNameExtmsgBind. +type PostV0CityByCityNameExtmsgBindParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameExtmsgBindingsParams defines parameters for GetV0CityByCityNameExtmsgBindings. +type GetV0CityByCityNameExtmsgBindingsParams struct { + // SessionId Session ID to list bindings for. + SessionId *string `form:"session_id,omitempty" json:"session_id,omitempty"` +} + +// GetV0CityByCityNameExtmsgGroupsParams defines parameters for GetV0CityByCityNameExtmsgGroups. +type GetV0CityByCityNameExtmsgGroupsParams struct { + // ScopeId Scope ID. + ScopeId *string `form:"scope_id,omitempty" json:"scope_id,omitempty"` + + // Provider Provider name. + Provider *string `form:"provider,omitempty" json:"provider,omitempty"` + + // AccountId Account ID. + AccountId *string `form:"account_id,omitempty" json:"account_id,omitempty"` + + // ConversationId Conversation ID. + ConversationId *string `form:"conversation_id,omitempty" json:"conversation_id,omitempty"` + + // Kind Conversation kind. + Kind *string `form:"kind,omitempty" json:"kind,omitempty"` +} + +// EnsureExtmsgGroupParams defines parameters for EnsureExtmsgGroup. +type EnsureExtmsgGroupParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameExtmsgInboundParams defines parameters for PostV0CityByCityNameExtmsgInbound. +type PostV0CityByCityNameExtmsgInboundParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameExtmsgOutboundParams defines parameters for PostV0CityByCityNameExtmsgOutbound. +type PostV0CityByCityNameExtmsgOutboundParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// DeleteV0CityByCityNameExtmsgParticipantsParams defines parameters for DeleteV0CityByCityNameExtmsgParticipants. +type DeleteV0CityByCityNameExtmsgParticipantsParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameExtmsgParticipantsParams defines parameters for PostV0CityByCityNameExtmsgParticipants. +type PostV0CityByCityNameExtmsgParticipantsParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameExtmsgTranscriptParams defines parameters for GetV0CityByCityNameExtmsgTranscript. +type GetV0CityByCityNameExtmsgTranscriptParams struct { + // ScopeId Scope ID. + ScopeId *string `form:"scope_id,omitempty" json:"scope_id,omitempty"` + + // Provider Provider name. + Provider *string `form:"provider,omitempty" json:"provider,omitempty"` + + // AccountId Account ID. + AccountId *string `form:"account_id,omitempty" json:"account_id,omitempty"` + + // ConversationId Conversation ID. + ConversationId *string `form:"conversation_id,omitempty" json:"conversation_id,omitempty"` + + // ParentConversationId Parent conversation ID. + ParentConversationId *string `form:"parent_conversation_id,omitempty" json:"parent_conversation_id,omitempty"` + + // Kind Conversation kind. + Kind *string `form:"kind,omitempty" json:"kind,omitempty"` +} + +// PostV0CityByCityNameExtmsgTranscriptAckParams defines parameters for PostV0CityByCityNameExtmsgTranscriptAck. +type PostV0CityByCityNameExtmsgTranscriptAckParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameExtmsgUnbindParams defines parameters for PostV0CityByCityNameExtmsgUnbind. +type PostV0CityByCityNameExtmsgUnbindParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameFormulaByNameParams defines parameters for GetV0CityByCityNameFormulaByName. +type GetV0CityByCityNameFormulaByNameParams struct { + // ScopeKind Scope kind (city or rig). + ScopeKind *string `form:"scope_kind,omitempty" json:"scope_kind,omitempty"` + + // ScopeRef Scope reference. + ScopeRef *string `form:"scope_ref,omitempty" json:"scope_ref,omitempty"` + + // Target Target agent for preview compilation. + Target string `form:"target" json:"target"` +} + +// GetV0CityByCityNameFormulasParams defines parameters for GetV0CityByCityNameFormulas. +type GetV0CityByCityNameFormulasParams struct { + // ScopeKind Scope kind (city or rig). + ScopeKind *string `form:"scope_kind,omitempty" json:"scope_kind,omitempty"` + + // ScopeRef Scope reference. + ScopeRef *string `form:"scope_ref,omitempty" json:"scope_ref,omitempty"` +} + +// GetV0CityByCityNameFormulasFeedParams defines parameters for GetV0CityByCityNameFormulasFeed. +type GetV0CityByCityNameFormulasFeedParams struct { + // ScopeKind Scope kind (city or rig). + ScopeKind *string `form:"scope_kind,omitempty" json:"scope_kind,omitempty"` + + // ScopeRef Scope reference. + ScopeRef *string `form:"scope_ref,omitempty" json:"scope_ref,omitempty"` + + // Limit Maximum number of feed items to return. 0 = default. + Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` +} + +// GetV0CityByCityNameFormulasByNameParams defines parameters for GetV0CityByCityNameFormulasByName. +type GetV0CityByCityNameFormulasByNameParams struct { + // ScopeKind Scope kind (city or rig). + ScopeKind *string `form:"scope_kind,omitempty" json:"scope_kind,omitempty"` + + // ScopeRef Scope reference. + ScopeRef *string `form:"scope_ref,omitempty" json:"scope_ref,omitempty"` + + // Target Target agent for preview compilation. + Target string `form:"target" json:"target"` +} + +// PostV0CityByCityNameFormulasByNamePreviewParams defines parameters for PostV0CityByCityNameFormulasByNamePreview. +type PostV0CityByCityNameFormulasByNamePreviewParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameFormulasByNameRunsParams defines parameters for GetV0CityByCityNameFormulasByNameRuns. +type GetV0CityByCityNameFormulasByNameRunsParams struct { + // ScopeKind Scope kind (city or rig). + ScopeKind *string `form:"scope_kind,omitempty" json:"scope_kind,omitempty"` + + // ScopeRef Scope reference. + ScopeRef *string `form:"scope_ref,omitempty" json:"scope_ref,omitempty"` + + // Limit Maximum number of recent runs to return. 0 = default. + Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` +} + +// GetV0CityByCityNameMailParams defines parameters for GetV0CityByCityNameMail. +type GetV0CityByCityNameMailParams struct { + // Index Event sequence number; when provided, blocks until a newer event arrives. + Index *string `form:"index,omitempty" json:"index,omitempty"` + + // Wait How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. + Wait *string `form:"wait,omitempty" json:"wait,omitempty"` + + // Cursor Pagination cursor from a previous response's next_cursor field. + Cursor *string `form:"cursor,omitempty" json:"cursor,omitempty"` + + // Limit Maximum number of results to return. 0 = server default. + Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` + + // Agent Filter by agent name. + Agent *string `form:"agent,omitempty" json:"agent,omitempty"` + + // Status Filter by status (unread, all). + Status *string `form:"status,omitempty" json:"status,omitempty"` + + // Rig Filter by rig name. + Rig *string `form:"rig,omitempty" json:"rig,omitempty"` +} + +// SendMailParams defines parameters for SendMail. +type SendMailParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` + + // IdempotencyKey Idempotency key for safe retries. + IdempotencyKey *string `json:"Idempotency-Key,omitempty"` +} + +// GetV0CityByCityNameMailCountParams defines parameters for GetV0CityByCityNameMailCount. +type GetV0CityByCityNameMailCountParams struct { + // Agent Filter by agent name. + Agent *string `form:"agent,omitempty" json:"agent,omitempty"` + + // Rig Filter by rig name. + Rig *string `form:"rig,omitempty" json:"rig,omitempty"` +} + +// GetV0CityByCityNameMailThreadByIdParams defines parameters for GetV0CityByCityNameMailThreadById. +type GetV0CityByCityNameMailThreadByIdParams struct { + // Rig Filter by rig. + Rig *string `form:"rig,omitempty" json:"rig,omitempty"` +} + +// DeleteV0CityByCityNameMailByIdParams defines parameters for DeleteV0CityByCityNameMailById. +type DeleteV0CityByCityNameMailByIdParams struct { + // Rig Rig hint. + Rig *string `form:"rig,omitempty" json:"rig,omitempty"` + + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameMailByIdParams defines parameters for GetV0CityByCityNameMailById. +type GetV0CityByCityNameMailByIdParams struct { + // Rig Rig hint for O(1) lookup. + Rig *string `form:"rig,omitempty" json:"rig,omitempty"` +} + +// PostV0CityByCityNameMailByIdArchiveParams defines parameters for PostV0CityByCityNameMailByIdArchive. +type PostV0CityByCityNameMailByIdArchiveParams struct { + // Rig Rig hint. + Rig *string `form:"rig,omitempty" json:"rig,omitempty"` + + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameMailByIdMarkUnreadParams defines parameters for PostV0CityByCityNameMailByIdMarkUnread. +type PostV0CityByCityNameMailByIdMarkUnreadParams struct { + // Rig Rig hint. + Rig *string `form:"rig,omitempty" json:"rig,omitempty"` + + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameMailByIdReadParams defines parameters for PostV0CityByCityNameMailByIdRead. +type PostV0CityByCityNameMailByIdReadParams struct { + // Rig Rig hint. + Rig *string `form:"rig,omitempty" json:"rig,omitempty"` + + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// ReplyMailParams defines parameters for ReplyMail. +type ReplyMailParams struct { + // Rig Rig hint. + Rig *string `form:"rig,omitempty" json:"rig,omitempty"` + + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameOrderHistoryByBeadIdParams defines parameters for GetV0CityByCityNameOrderHistoryByBeadId. +type GetV0CityByCityNameOrderHistoryByBeadIdParams struct { + // StoreRef Store reference for disambiguating store-local bead IDs. + StoreRef *string `form:"store_ref,omitempty" json:"store_ref,omitempty"` +} + +// PostV0CityByCityNameOrderByNameDisableParams defines parameters for PostV0CityByCityNameOrderByNameDisable. +type PostV0CityByCityNameOrderByNameDisableParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameOrderByNameEnableParams defines parameters for PostV0CityByCityNameOrderByNameEnable. +type PostV0CityByCityNameOrderByNameEnableParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameOrdersFeedParams defines parameters for GetV0CityByCityNameOrdersFeed. +type GetV0CityByCityNameOrdersFeedParams struct { + // ScopeKind Scope kind (city or rig). + ScopeKind *string `form:"scope_kind,omitempty" json:"scope_kind,omitempty"` + + // ScopeRef Scope reference. + ScopeRef *string `form:"scope_ref,omitempty" json:"scope_ref,omitempty"` + + // Limit Maximum number of feed items to return. + Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` +} + +// GetV0CityByCityNameOrdersHistoryParams defines parameters for GetV0CityByCityNameOrdersHistory. +type GetV0CityByCityNameOrdersHistoryParams struct { + // ScopedName Scoped order name. + ScopedName string `form:"scoped_name" json:"scoped_name"` + + // Limit Maximum number of history entries. 0 = default. + Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` + + // Before Return entries before this RFC3339 timestamp. + Before *string `form:"before,omitempty" json:"before,omitempty"` +} + +// DeleteV0CityByCityNamePatchesAgentByBaseParams defines parameters for DeleteV0CityByCityNamePatchesAgentByBase. +type DeleteV0CityByCityNamePatchesAgentByBaseParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// DeleteV0CityByCityNamePatchesAgentByDirByBaseParams defines parameters for DeleteV0CityByCityNamePatchesAgentByDirByBase. +type DeleteV0CityByCityNamePatchesAgentByDirByBaseParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PutV0CityByCityNamePatchesAgentsParams defines parameters for PutV0CityByCityNamePatchesAgents. +type PutV0CityByCityNamePatchesAgentsParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// DeleteV0CityByCityNamePatchesProviderByNameParams defines parameters for DeleteV0CityByCityNamePatchesProviderByName. +type DeleteV0CityByCityNamePatchesProviderByNameParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PutV0CityByCityNamePatchesProvidersParams defines parameters for PutV0CityByCityNamePatchesProviders. +type PutV0CityByCityNamePatchesProvidersParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// DeleteV0CityByCityNamePatchesRigByNameParams defines parameters for DeleteV0CityByCityNamePatchesRigByName. +type DeleteV0CityByCityNamePatchesRigByNameParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PutV0CityByCityNamePatchesRigsParams defines parameters for PutV0CityByCityNamePatchesRigs. +type PutV0CityByCityNamePatchesRigsParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameProviderReadinessParams defines parameters for GetV0CityByCityNameProviderReadiness. +type GetV0CityByCityNameProviderReadinessParams struct { + // Providers Comma-separated provider names to check (default: claude,codex,gemini). + Providers *string `form:"providers,omitempty" json:"providers,omitempty"` + + // Fresh Force fresh probe, bypassing cache. + Fresh *bool `form:"fresh,omitempty" json:"fresh,omitempty"` +} + +// DeleteV0CityByCityNameProviderByNameParams defines parameters for DeleteV0CityByCityNameProviderByName. +type DeleteV0CityByCityNameProviderByNameParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PatchV0CityByCityNameProviderByNameParams defines parameters for PatchV0CityByCityNameProviderByName. +type PatchV0CityByCityNameProviderByNameParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// CreateProviderParams defines parameters for CreateProvider. +type CreateProviderParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameReadinessParams defines parameters for GetV0CityByCityNameReadiness. +type GetV0CityByCityNameReadinessParams struct { + // Items Comma-separated readiness items to check (default: claude,codex,gemini,github_cli). + Items *string `form:"items,omitempty" json:"items,omitempty"` + + // Fresh Force fresh probe, bypassing cache. + Fresh *bool `form:"fresh,omitempty" json:"fresh,omitempty"` +} + +// DeleteV0CityByCityNameRigByNameParams defines parameters for DeleteV0CityByCityNameRigByName. +type DeleteV0CityByCityNameRigByNameParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameRigByNameParams defines parameters for GetV0CityByCityNameRigByName. +type GetV0CityByCityNameRigByNameParams struct { + // Git Include git status. + Git *bool `form:"git,omitempty" json:"git,omitempty"` +} + +// PatchV0CityByCityNameRigByNameParams defines parameters for PatchV0CityByCityNameRigByName. +type PatchV0CityByCityNameRigByNameParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameRigByNameByActionParams defines parameters for PostV0CityByCityNameRigByNameByAction. +type PostV0CityByCityNameRigByNameByActionParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameRigsParams defines parameters for GetV0CityByCityNameRigs. +type GetV0CityByCityNameRigsParams struct { + // Index Event sequence number; when provided, blocks until a newer event arrives. + Index *string `form:"index,omitempty" json:"index,omitempty"` + + // Wait How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. + Wait *string `form:"wait,omitempty" json:"wait,omitempty"` + + // Git Include git status. + Git *bool `form:"git,omitempty" json:"git,omitempty"` +} + +// CreateRigParams defines parameters for CreateRig. +type CreateRigParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameServiceByNameRestartParams defines parameters for PostV0CityByCityNameServiceByNameRestart. +type PostV0CityByCityNameServiceByNameRestartParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameSessionByIdParams defines parameters for GetV0CityByCityNameSessionById. +type GetV0CityByCityNameSessionByIdParams struct { + // Peek Include last output preview. + Peek *bool `form:"peek,omitempty" json:"peek,omitempty"` +} + +// PatchV0CityByCityNameSessionByIdParams defines parameters for PatchV0CityByCityNameSessionById. +type PatchV0CityByCityNameSessionByIdParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameSessionByIdCloseParams defines parameters for PostV0CityByCityNameSessionByIdClose. +type PostV0CityByCityNameSessionByIdCloseParams struct { + // Delete Permanently delete bead after closing. + Delete *bool `form:"delete,omitempty" json:"delete,omitempty"` + + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameSessionByIdKillParams defines parameters for PostV0CityByCityNameSessionByIdKill. +type PostV0CityByCityNameSessionByIdKillParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// SendSessionMessageParams defines parameters for SendSessionMessage. +type SendSessionMessageParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameSessionByIdRenameParams defines parameters for PostV0CityByCityNameSessionByIdRename. +type PostV0CityByCityNameSessionByIdRenameParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// RespondSessionParams defines parameters for RespondSession. +type RespondSessionParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameSessionByIdStopParams defines parameters for PostV0CityByCityNameSessionByIdStop. +type PostV0CityByCityNameSessionByIdStopParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// StreamSessionParams defines parameters for StreamSession. +type StreamSessionParams struct { + // Format Transcript format: conversation (default) or raw. + Format *string `form:"format,omitempty" json:"format,omitempty"` +} + +// SubmitSessionParams defines parameters for SubmitSession. +type SubmitSessionParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameSessionByIdSuspendParams defines parameters for PostV0CityByCityNameSessionByIdSuspend. +type PostV0CityByCityNameSessionByIdSuspendParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameSessionByIdTranscriptParams defines parameters for GetV0CityByCityNameSessionByIdTranscript. +type GetV0CityByCityNameSessionByIdTranscriptParams struct { + // Tail Number of recent compaction segments to return. This API parameter keeps compaction-segment semantics even though gc session logs --tail counts displayed transcript entries. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. + Tail *string `form:"tail,omitempty" json:"tail,omitempty"` + + // Format Transcript format: conversation (default) or raw. + Format *string `form:"format,omitempty" json:"format,omitempty"` + + // Before Pagination cursor: return entries before this UUID. + Before *string `form:"before,omitempty" json:"before,omitempty"` +} + +// PostV0CityByCityNameSessionByIdWakeParams defines parameters for PostV0CityByCityNameSessionByIdWake. +type PostV0CityByCityNameSessionByIdWakeParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameSessionsParams defines parameters for GetV0CityByCityNameSessions. +type GetV0CityByCityNameSessionsParams struct { + // Cursor Pagination cursor from a previous response's next_cursor field. + Cursor *string `form:"cursor,omitempty" json:"cursor,omitempty"` + + // Limit Maximum number of results to return. 0 = server default. + Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` + + // State Filter by session state (e.g. active, closed). + State *string `form:"state,omitempty" json:"state,omitempty"` + + // Template Filter by session template (agent qualified name). + Template *string `form:"template,omitempty" json:"template,omitempty"` + + // Peek Include last output preview. + Peek *bool `form:"peek,omitempty" json:"peek,omitempty"` +} + +// CreateSessionParams defines parameters for CreateSession. +type CreateSessionParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// PostV0CityByCityNameSlingParams defines parameters for PostV0CityByCityNameSling. +type PostV0CityByCityNameSlingParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// GetV0CityByCityNameStatusParams defines parameters for GetV0CityByCityNameStatus. +type GetV0CityByCityNameStatusParams struct { + // Index Event sequence number; when provided, blocks until a newer event arrives. + Index *string `form:"index,omitempty" json:"index,omitempty"` + + // Wait How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. + Wait *string `form:"wait,omitempty" json:"wait,omitempty"` +} + +// PostV0CityByCityNameUnregisterParams defines parameters for PostV0CityByCityNameUnregister. +type PostV0CityByCityNameUnregisterParams struct { + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + XGCRequest string `json:"X-GC-Request"` +} + +// DeleteV0CityByCityNameWorkflowByWorkflowIdParams defines parameters for DeleteV0CityByCityNameWorkflowByWorkflowId. +type DeleteV0CityByCityNameWorkflowByWorkflowIdParams struct { + // ScopeKind Scope kind (city or rig). + ScopeKind *string `form:"scope_kind,omitempty" json:"scope_kind,omitempty"` + + // ScopeRef Scope reference. + ScopeRef *string `form:"scope_ref,omitempty" json:"scope_ref,omitempty"` + + // Delete Permanently delete beads from store. + Delete *bool `form:"delete,omitempty" json:"delete,omitempty"` + + // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. XGCRequest string `json:"X-GC-Request"` } -// PostV0CityByCityNameAgentByBaseByActionParamsAction defines parameters for PostV0CityByCityNameAgentByBaseByAction. -type PostV0CityByCityNameAgentByBaseByActionParamsAction string +// GetV0CityByCityNameWorkflowByWorkflowIdParams defines parameters for GetV0CityByCityNameWorkflowByWorkflowId. +type GetV0CityByCityNameWorkflowByWorkflowIdParams struct { + // ScopeKind Scope kind (city or rig). + ScopeKind *string `form:"scope_kind,omitempty" json:"scope_kind,omitempty"` + + // ScopeRef Scope reference. + ScopeRef *string `form:"scope_ref,omitempty" json:"scope_ref,omitempty"` +} + +// GetV0EventsParams defines parameters for GetV0Events. +type GetV0EventsParams struct { + // Type Filter by event type. + Type *string `form:"type,omitempty" json:"type,omitempty"` + + // Actor Filter by actor. + Actor *string `form:"actor,omitempty" json:"actor,omitempty"` + + // Since Filter to events within the last Go duration (e.g. "5m"). + Since *string `form:"since,omitempty" json:"since,omitempty"` + + // Limit Maximum number of trailing events to return. 0 = no limit. Used by 'gc events --seq' to compute the head cursor cheaply. + Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` +} + +// StreamSupervisorEventsParams defines parameters for StreamSupervisorEvents. +type StreamSupervisorEventsParams struct { + // AfterCursor Alternative to Last-Event-ID for browsers that can't set custom headers. + AfterCursor *string `form:"after_cursor,omitempty" json:"after_cursor,omitempty"` + + // LastEventID Reconnect cursor (composite per-city cursor). + LastEventID *string `json:"Last-Event-ID,omitempty"` +} + +// GetV0ProviderReadinessParams defines parameters for GetV0ProviderReadiness. +type GetV0ProviderReadinessParams struct { + // Providers Comma-separated list of providers to probe. + Providers *string `form:"providers,omitempty" json:"providers,omitempty"` + + // Fresh Force fresh probe, bypassing cache. + Fresh *bool `form:"fresh,omitempty" json:"fresh,omitempty"` +} + +// GetV0ReadinessParams defines parameters for GetV0Readiness. +type GetV0ReadinessParams struct { + // Items Comma-separated list of readiness items to check. + Items *string `form:"items,omitempty" json:"items,omitempty"` + + // Fresh Force fresh probe, bypassing cache. + Fresh *bool `form:"fresh,omitempty" json:"fresh,omitempty"` +} + +// PostV0CityJSONRequestBody defines body for PostV0City for application/json ContentType. +type PostV0CityJSONRequestBody = CityCreateRequest + +// PatchV0CityByCityNameJSONRequestBody defines body for PatchV0CityByCityName for application/json ContentType. +type PatchV0CityByCityNameJSONRequestBody = CityPatchInputBody + +// PatchV0CityByCityNameAgentByBaseJSONRequestBody defines body for PatchV0CityByCityNameAgentByBase for application/json ContentType. +type PatchV0CityByCityNameAgentByBaseJSONRequestBody = AgentUpdateInputBody + +// PatchV0CityByCityNameAgentByDirByBaseJSONRequestBody defines body for PatchV0CityByCityNameAgentByDirByBase for application/json ContentType. +type PatchV0CityByCityNameAgentByDirByBaseJSONRequestBody = AgentUpdateQualifiedInputBody + +// CreateAgentJSONRequestBody defines body for CreateAgent for application/json ContentType. +type CreateAgentJSONRequestBody = AgentCreateInputBody + +// PatchV0CityByCityNameBeadByIdJSONRequestBody defines body for PatchV0CityByCityNameBeadById for application/json ContentType. +type PatchV0CityByCityNameBeadByIdJSONRequestBody = BeadUpdateBody + +// PostV0CityByCityNameBeadByIdAssignJSONRequestBody defines body for PostV0CityByCityNameBeadByIdAssign for application/json ContentType. +type PostV0CityByCityNameBeadByIdAssignJSONRequestBody = BeadAssignInputBody + +// PostV0CityByCityNameBeadByIdUpdateJSONRequestBody defines body for PostV0CityByCityNameBeadByIdUpdate for application/json ContentType. +type PostV0CityByCityNameBeadByIdUpdateJSONRequestBody = BeadUpdateBody + +// CreateBeadJSONRequestBody defines body for CreateBead for application/json ContentType. +type CreateBeadJSONRequestBody = BeadCreateInputBody + +// PostV0CityByCityNameConvoyByIdAddJSONRequestBody defines body for PostV0CityByCityNameConvoyByIdAdd for application/json ContentType. +type PostV0CityByCityNameConvoyByIdAddJSONRequestBody = ConvoyAddInputBody + +// PostV0CityByCityNameConvoyByIdRemoveJSONRequestBody defines body for PostV0CityByCityNameConvoyByIdRemove for application/json ContentType. +type PostV0CityByCityNameConvoyByIdRemoveJSONRequestBody = ConvoyRemoveInputBody + +// CreateConvoyJSONRequestBody defines body for CreateConvoy for application/json ContentType. +type CreateConvoyJSONRequestBody = ConvoyCreateInputBody + +// EmitEventJSONRequestBody defines body for EmitEvent for application/json ContentType. +type EmitEventJSONRequestBody = EventEmitRequest + +// DeleteV0CityByCityNameExtmsgAdaptersJSONRequestBody defines body for DeleteV0CityByCityNameExtmsgAdapters for application/json ContentType. +type DeleteV0CityByCityNameExtmsgAdaptersJSONRequestBody = ExtMsgAdapterUnregisterInputBody + +// RegisterExtmsgAdapterJSONRequestBody defines body for RegisterExtmsgAdapter for application/json ContentType. +type RegisterExtmsgAdapterJSONRequestBody = ExtMsgAdapterRegisterInputBody + +// PostV0CityByCityNameExtmsgBindJSONRequestBody defines body for PostV0CityByCityNameExtmsgBind for application/json ContentType. +type PostV0CityByCityNameExtmsgBindJSONRequestBody = ExtMsgBindInputBody + +// EnsureExtmsgGroupJSONRequestBody defines body for EnsureExtmsgGroup for application/json ContentType. +type EnsureExtmsgGroupJSONRequestBody = ExtMsgGroupEnsureInputBody + +// PostV0CityByCityNameExtmsgInboundJSONRequestBody defines body for PostV0CityByCityNameExtmsgInbound for application/json ContentType. +type PostV0CityByCityNameExtmsgInboundJSONRequestBody = ExtMsgInboundInputBody + +// PostV0CityByCityNameExtmsgOutboundJSONRequestBody defines body for PostV0CityByCityNameExtmsgOutbound for application/json ContentType. +type PostV0CityByCityNameExtmsgOutboundJSONRequestBody = ExtMsgOutboundInputBody + +// DeleteV0CityByCityNameExtmsgParticipantsJSONRequestBody defines body for DeleteV0CityByCityNameExtmsgParticipants for application/json ContentType. +type DeleteV0CityByCityNameExtmsgParticipantsJSONRequestBody = ExtMsgParticipantRemoveInputBody + +// PostV0CityByCityNameExtmsgParticipantsJSONRequestBody defines body for PostV0CityByCityNameExtmsgParticipants for application/json ContentType. +type PostV0CityByCityNameExtmsgParticipantsJSONRequestBody = ExtMsgParticipantUpsertInputBody + +// PostV0CityByCityNameExtmsgTranscriptAckJSONRequestBody defines body for PostV0CityByCityNameExtmsgTranscriptAck for application/json ContentType. +type PostV0CityByCityNameExtmsgTranscriptAckJSONRequestBody = ExtMsgTranscriptAckInputBody + +// PostV0CityByCityNameExtmsgUnbindJSONRequestBody defines body for PostV0CityByCityNameExtmsgUnbind for application/json ContentType. +type PostV0CityByCityNameExtmsgUnbindJSONRequestBody = ExtMsgUnbindInputBody + +// PostV0CityByCityNameFormulasByNamePreviewJSONRequestBody defines body for PostV0CityByCityNameFormulasByNamePreview for application/json ContentType. +type PostV0CityByCityNameFormulasByNamePreviewJSONRequestBody = FormulaPreviewBody + +// SendMailJSONRequestBody defines body for SendMail for application/json ContentType. +type SendMailJSONRequestBody = MailSendInputBody + +// ReplyMailJSONRequestBody defines body for ReplyMail for application/json ContentType. +type ReplyMailJSONRequestBody = MailReplyInputBody + +// PutV0CityByCityNamePatchesAgentsJSONRequestBody defines body for PutV0CityByCityNamePatchesAgents for application/json ContentType. +type PutV0CityByCityNamePatchesAgentsJSONRequestBody = AgentPatchSetInputBody + +// PutV0CityByCityNamePatchesProvidersJSONRequestBody defines body for PutV0CityByCityNamePatchesProviders for application/json ContentType. +type PutV0CityByCityNamePatchesProvidersJSONRequestBody = ProviderPatchSetInputBody + +// PutV0CityByCityNamePatchesRigsJSONRequestBody defines body for PutV0CityByCityNamePatchesRigs for application/json ContentType. +type PutV0CityByCityNamePatchesRigsJSONRequestBody = RigPatchSetInputBody + +// PatchV0CityByCityNameProviderByNameJSONRequestBody defines body for PatchV0CityByCityNameProviderByName for application/json ContentType. +type PatchV0CityByCityNameProviderByNameJSONRequestBody = ProviderUpdateInputBody + +// CreateProviderJSONRequestBody defines body for CreateProvider for application/json ContentType. +type CreateProviderJSONRequestBody = ProviderCreateInputBody + +// PatchV0CityByCityNameRigByNameJSONRequestBody defines body for PatchV0CityByCityNameRigByName for application/json ContentType. +type PatchV0CityByCityNameRigByNameJSONRequestBody = RigUpdateInputBody + +// CreateRigJSONRequestBody defines body for CreateRig for application/json ContentType. +type CreateRigJSONRequestBody = RigCreateInputBody + +// PatchV0CityByCityNameSessionByIdJSONRequestBody defines body for PatchV0CityByCityNameSessionById for application/json ContentType. +type PatchV0CityByCityNameSessionByIdJSONRequestBody = SessionPatchBody + +// SendSessionMessageJSONRequestBody defines body for SendSessionMessage for application/json ContentType. +type SendSessionMessageJSONRequestBody = SessionMessageInputBody + +// PostV0CityByCityNameSessionByIdRenameJSONRequestBody defines body for PostV0CityByCityNameSessionByIdRename for application/json ContentType. +type PostV0CityByCityNameSessionByIdRenameJSONRequestBody = SessionRenameInputBody + +// RespondSessionJSONRequestBody defines body for RespondSession for application/json ContentType. +type RespondSessionJSONRequestBody = SessionRespondInputBody + +// SubmitSessionJSONRequestBody defines body for SubmitSession for application/json ContentType. +type SubmitSessionJSONRequestBody = SessionSubmitInputBody + +// CreateSessionJSONRequestBody defines body for CreateSession for application/json ContentType. +type CreateSessionJSONRequestBody = SessionCreateBody + +// PostV0CityByCityNameSlingJSONRequestBody defines body for PostV0CityByCityNameSling for application/json ContentType. +type PostV0CityByCityNameSlingJSONRequestBody = SlingInputBody + +// AsAdapterEventPayload returns the union data inside the EventPayload as a AdapterEventPayload +func (t EventPayload) AsAdapterEventPayload() (AdapterEventPayload, error) { + var body AdapterEventPayload + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromAdapterEventPayload overwrites any union data inside the EventPayload as the provided AdapterEventPayload +func (t *EventPayload) FromAdapterEventPayload(v AdapterEventPayload) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeAdapterEventPayload performs a merge with any union data inside the EventPayload, using the provided AdapterEventPayload +func (t *EventPayload) MergeAdapterEventPayload(v AdapterEventPayload) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsBeadEventPayload returns the union data inside the EventPayload as a BeadEventPayload +func (t EventPayload) AsBeadEventPayload() (BeadEventPayload, error) { + var body BeadEventPayload + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromBeadEventPayload overwrites any union data inside the EventPayload as the provided BeadEventPayload +func (t *EventPayload) FromBeadEventPayload(v BeadEventPayload) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeBeadEventPayload performs a merge with any union data inside the EventPayload, using the provided BeadEventPayload +func (t *EventPayload) MergeBeadEventPayload(v BeadEventPayload) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsBoundEventPayload returns the union data inside the EventPayload as a BoundEventPayload +func (t EventPayload) AsBoundEventPayload() (BoundEventPayload, error) { + var body BoundEventPayload + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromBoundEventPayload overwrites any union data inside the EventPayload as the provided BoundEventPayload +func (t *EventPayload) FromBoundEventPayload(v BoundEventPayload) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeBoundEventPayload performs a merge with any union data inside the EventPayload, using the provided BoundEventPayload +func (t *EventPayload) MergeBoundEventPayload(v BoundEventPayload) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsCityLifecyclePayload returns the union data inside the EventPayload as a CityLifecyclePayload +func (t EventPayload) AsCityLifecyclePayload() (CityLifecyclePayload, error) { + var body CityLifecyclePayload + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromCityLifecyclePayload overwrites any union data inside the EventPayload as the provided CityLifecyclePayload +func (t *EventPayload) FromCityLifecyclePayload(v CityLifecyclePayload) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeCityLifecyclePayload performs a merge with any union data inside the EventPayload, using the provided CityLifecyclePayload +func (t *EventPayload) MergeCityLifecyclePayload(v CityLifecyclePayload) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsGroupCreatedEventPayload returns the union data inside the EventPayload as a GroupCreatedEventPayload +func (t EventPayload) AsGroupCreatedEventPayload() (GroupCreatedEventPayload, error) { + var body GroupCreatedEventPayload + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromGroupCreatedEventPayload overwrites any union data inside the EventPayload as the provided GroupCreatedEventPayload +func (t *EventPayload) FromGroupCreatedEventPayload(v GroupCreatedEventPayload) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeGroupCreatedEventPayload performs a merge with any union data inside the EventPayload, using the provided GroupCreatedEventPayload +func (t *EventPayload) MergeGroupCreatedEventPayload(v GroupCreatedEventPayload) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsInboundEventPayload returns the union data inside the EventPayload as a InboundEventPayload +func (t EventPayload) AsInboundEventPayload() (InboundEventPayload, error) { + var body InboundEventPayload + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromInboundEventPayload overwrites any union data inside the EventPayload as the provided InboundEventPayload +func (t *EventPayload) FromInboundEventPayload(v InboundEventPayload) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeInboundEventPayload performs a merge with any union data inside the EventPayload, using the provided InboundEventPayload +func (t *EventPayload) MergeInboundEventPayload(v InboundEventPayload) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsMailEventPayload returns the union data inside the EventPayload as a MailEventPayload +func (t EventPayload) AsMailEventPayload() (MailEventPayload, error) { + var body MailEventPayload + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromMailEventPayload overwrites any union data inside the EventPayload as the provided MailEventPayload +func (t *EventPayload) FromMailEventPayload(v MailEventPayload) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeMailEventPayload performs a merge with any union data inside the EventPayload, using the provided MailEventPayload +func (t *EventPayload) MergeMailEventPayload(v MailEventPayload) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsNoPayload returns the union data inside the EventPayload as a NoPayload +func (t EventPayload) AsNoPayload() (NoPayload, error) { + var body NoPayload + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromNoPayload overwrites any union data inside the EventPayload as the provided NoPayload +func (t *EventPayload) FromNoPayload(v NoPayload) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeNoPayload performs a merge with any union data inside the EventPayload, using the provided NoPayload +func (t *EventPayload) MergeNoPayload(v NoPayload) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsOutboundEventPayload returns the union data inside the EventPayload as a OutboundEventPayload +func (t EventPayload) AsOutboundEventPayload() (OutboundEventPayload, error) { + var body OutboundEventPayload + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromOutboundEventPayload overwrites any union data inside the EventPayload as the provided OutboundEventPayload +func (t *EventPayload) FromOutboundEventPayload(v OutboundEventPayload) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeOutboundEventPayload performs a merge with any union data inside the EventPayload, using the provided OutboundEventPayload +func (t *EventPayload) MergeOutboundEventPayload(v OutboundEventPayload) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsUnboundEventPayload returns the union data inside the EventPayload as a UnboundEventPayload +func (t EventPayload) AsUnboundEventPayload() (UnboundEventPayload, error) { + var body UnboundEventPayload + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromUnboundEventPayload overwrites any union data inside the EventPayload as the provided UnboundEventPayload +func (t *EventPayload) FromUnboundEventPayload(v UnboundEventPayload) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeUnboundEventPayload performs a merge with any union data inside the EventPayload, using the provided UnboundEventPayload +func (t *EventPayload) MergeUnboundEventPayload(v UnboundEventPayload) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsWorkerOperationEventPayload returns the union data inside the EventPayload as a WorkerOperationEventPayload +func (t EventPayload) AsWorkerOperationEventPayload() (WorkerOperationEventPayload, error) { + var body WorkerOperationEventPayload + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromWorkerOperationEventPayload overwrites any union data inside the EventPayload as the provided WorkerOperationEventPayload +func (t *EventPayload) FromWorkerOperationEventPayload(v WorkerOperationEventPayload) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeWorkerOperationEventPayload performs a merge with any union data inside the EventPayload, using the provided WorkerOperationEventPayload +func (t *EventPayload) MergeWorkerOperationEventPayload(v WorkerOperationEventPayload) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t EventPayload) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *EventPayload) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsSessionActivityEvent returns the union data inside the SessionStreamCommonEvent as a SessionActivityEvent +func (t SessionStreamCommonEvent) AsSessionActivityEvent() (SessionActivityEvent, error) { + var body SessionActivityEvent + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromSessionActivityEvent overwrites any union data inside the SessionStreamCommonEvent as the provided SessionActivityEvent +func (t *SessionStreamCommonEvent) FromSessionActivityEvent(v SessionActivityEvent) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeSessionActivityEvent performs a merge with any union data inside the SessionStreamCommonEvent, using the provided SessionActivityEvent +func (t *SessionStreamCommonEvent) MergeSessionActivityEvent(v SessionActivityEvent) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsPendingInteraction returns the union data inside the SessionStreamCommonEvent as a PendingInteraction +func (t SessionStreamCommonEvent) AsPendingInteraction() (PendingInteraction, error) { + var body PendingInteraction + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromPendingInteraction overwrites any union data inside the SessionStreamCommonEvent as the provided PendingInteraction +func (t *SessionStreamCommonEvent) FromPendingInteraction(v PendingInteraction) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergePendingInteraction performs a merge with any union data inside the SessionStreamCommonEvent, using the provided PendingInteraction +func (t *SessionStreamCommonEvent) MergePendingInteraction(v PendingInteraction) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsHeartbeatEvent returns the union data inside the SessionStreamCommonEvent as a HeartbeatEvent +func (t SessionStreamCommonEvent) AsHeartbeatEvent() (HeartbeatEvent, error) { + var body HeartbeatEvent + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromHeartbeatEvent overwrites any union data inside the SessionStreamCommonEvent as the provided HeartbeatEvent +func (t *SessionStreamCommonEvent) FromHeartbeatEvent(v HeartbeatEvent) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeHeartbeatEvent performs a merge with any union data inside the SessionStreamCommonEvent, using the provided HeartbeatEvent +func (t *SessionStreamCommonEvent) MergeHeartbeatEvent(v HeartbeatEvent) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t SessionStreamCommonEvent) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *SessionStreamCommonEvent) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsTypedEventStreamEnvelopeBeadClosed returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeBeadClosed +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeBeadClosed() (TypedEventStreamEnvelopeBeadClosed, error) { + var body TypedEventStreamEnvelopeBeadClosed + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTypedEventStreamEnvelopeBeadClosed overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeBeadClosed +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeBeadClosed(v TypedEventStreamEnvelopeBeadClosed) error { + v.Type = "bead.closed" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTypedEventStreamEnvelopeBeadClosed performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeBeadClosed +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeBeadClosed(v TypedEventStreamEnvelopeBeadClosed) error { + v.Type = "bead.closed" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedEventStreamEnvelopeBeadCreated returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeBeadCreated +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeBeadCreated() (TypedEventStreamEnvelopeBeadCreated, error) { + var body TypedEventStreamEnvelopeBeadCreated + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTypedEventStreamEnvelopeBeadCreated overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeBeadCreated +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeBeadCreated(v TypedEventStreamEnvelopeBeadCreated) error { + v.Type = "bead.created" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTypedEventStreamEnvelopeBeadCreated performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeBeadCreated +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeBeadCreated(v TypedEventStreamEnvelopeBeadCreated) error { + v.Type = "bead.created" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedEventStreamEnvelopeBeadUpdated returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeBeadUpdated +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeBeadUpdated() (TypedEventStreamEnvelopeBeadUpdated, error) { + var body TypedEventStreamEnvelopeBeadUpdated + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTypedEventStreamEnvelopeBeadUpdated overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeBeadUpdated +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeBeadUpdated(v TypedEventStreamEnvelopeBeadUpdated) error { + v.Type = "bead.updated" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTypedEventStreamEnvelopeBeadUpdated performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeBeadUpdated +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeBeadUpdated(v TypedEventStreamEnvelopeBeadUpdated) error { + v.Type = "bead.updated" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedEventStreamEnvelopeCityCreated returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeCityCreated +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeCityCreated() (TypedEventStreamEnvelopeCityCreated, error) { + var body TypedEventStreamEnvelopeCityCreated + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTypedEventStreamEnvelopeCityCreated overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeCityCreated +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeCityCreated(v TypedEventStreamEnvelopeCityCreated) error { + v.Type = "city.created" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTypedEventStreamEnvelopeCityCreated performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeCityCreated +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeCityCreated(v TypedEventStreamEnvelopeCityCreated) error { + v.Type = "city.created" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedEventStreamEnvelopeCityInitFailed returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeCityInitFailed +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeCityInitFailed() (TypedEventStreamEnvelopeCityInitFailed, error) { + var body TypedEventStreamEnvelopeCityInitFailed + err := json.Unmarshal(t.union, &body) + return body, err +} -// DeleteV0CityByCityNameAgentByDirByBaseParams defines parameters for DeleteV0CityByCityNameAgentByDirByBase. -type DeleteV0CityByCityNameAgentByDirByBaseParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedEventStreamEnvelopeCityInitFailed overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeCityInitFailed +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeCityInitFailed(v TypedEventStreamEnvelopeCityInitFailed) error { + v.Type = "city.init_failed" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTypedEventStreamEnvelopeCityInitFailed performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeCityInitFailed +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeCityInitFailed(v TypedEventStreamEnvelopeCityInitFailed) error { + v.Type = "city.init_failed" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedEventStreamEnvelopeCityReady returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeCityReady +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeCityReady() (TypedEventStreamEnvelopeCityReady, error) { + var body TypedEventStreamEnvelopeCityReady + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTypedEventStreamEnvelopeCityReady overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeCityReady +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeCityReady(v TypedEventStreamEnvelopeCityReady) error { + v.Type = "city.ready" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTypedEventStreamEnvelopeCityReady performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeCityReady +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeCityReady(v TypedEventStreamEnvelopeCityReady) error { + v.Type = "city.ready" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedEventStreamEnvelopeCityResumed returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeCityResumed +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeCityResumed() (TypedEventStreamEnvelopeCityResumed, error) { + var body TypedEventStreamEnvelopeCityResumed + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTypedEventStreamEnvelopeCityResumed overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeCityResumed +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeCityResumed(v TypedEventStreamEnvelopeCityResumed) error { + v.Type = "city.resumed" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTypedEventStreamEnvelopeCityResumed performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeCityResumed +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeCityResumed(v TypedEventStreamEnvelopeCityResumed) error { + v.Type = "city.resumed" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedEventStreamEnvelopeCitySuspended returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeCitySuspended +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeCitySuspended() (TypedEventStreamEnvelopeCitySuspended, error) { + var body TypedEventStreamEnvelopeCitySuspended + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTypedEventStreamEnvelopeCitySuspended overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeCitySuspended +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeCitySuspended(v TypedEventStreamEnvelopeCitySuspended) error { + v.Type = "city.suspended" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTypedEventStreamEnvelopeCitySuspended performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeCitySuspended +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeCitySuspended(v TypedEventStreamEnvelopeCitySuspended) error { + v.Type = "city.suspended" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedEventStreamEnvelopeCityUnregisterFailed returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeCityUnregisterFailed +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeCityUnregisterFailed() (TypedEventStreamEnvelopeCityUnregisterFailed, error) { + var body TypedEventStreamEnvelopeCityUnregisterFailed + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTypedEventStreamEnvelopeCityUnregisterFailed overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeCityUnregisterFailed +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeCityUnregisterFailed(v TypedEventStreamEnvelopeCityUnregisterFailed) error { + v.Type = "city.unregister_failed" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTypedEventStreamEnvelopeCityUnregisterFailed performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeCityUnregisterFailed +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeCityUnregisterFailed(v TypedEventStreamEnvelopeCityUnregisterFailed) error { + v.Type = "city.unregister_failed" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedEventStreamEnvelopeCityUnregisterRequested returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeCityUnregisterRequested +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeCityUnregisterRequested() (TypedEventStreamEnvelopeCityUnregisterRequested, error) { + var body TypedEventStreamEnvelopeCityUnregisterRequested + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTypedEventStreamEnvelopeCityUnregisterRequested overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeCityUnregisterRequested +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeCityUnregisterRequested(v TypedEventStreamEnvelopeCityUnregisterRequested) error { + v.Type = "city.unregister_requested" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTypedEventStreamEnvelopeCityUnregisterRequested performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeCityUnregisterRequested +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeCityUnregisterRequested(v TypedEventStreamEnvelopeCityUnregisterRequested) error { + v.Type = "city.unregister_requested" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedEventStreamEnvelopeCityUnregistered returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeCityUnregistered +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeCityUnregistered() (TypedEventStreamEnvelopeCityUnregistered, error) { + var body TypedEventStreamEnvelopeCityUnregistered + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTypedEventStreamEnvelopeCityUnregistered overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeCityUnregistered +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeCityUnregistered(v TypedEventStreamEnvelopeCityUnregistered) error { + v.Type = "city.unregistered" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTypedEventStreamEnvelopeCityUnregistered performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeCityUnregistered +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeCityUnregistered(v TypedEventStreamEnvelopeCityUnregistered) error { + v.Type = "city.unregistered" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedEventStreamEnvelopeControllerStarted returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeControllerStarted +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeControllerStarted() (TypedEventStreamEnvelopeControllerStarted, error) { + var body TypedEventStreamEnvelopeControllerStarted + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTypedEventStreamEnvelopeControllerStarted overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeControllerStarted +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeControllerStarted(v TypedEventStreamEnvelopeControllerStarted) error { + v.Type = "controller.started" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTypedEventStreamEnvelopeControllerStarted performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeControllerStarted +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeControllerStarted(v TypedEventStreamEnvelopeControllerStarted) error { + v.Type = "controller.started" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedEventStreamEnvelopeControllerStopped returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeControllerStopped +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeControllerStopped() (TypedEventStreamEnvelopeControllerStopped, error) { + var body TypedEventStreamEnvelopeControllerStopped + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTypedEventStreamEnvelopeControllerStopped overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeControllerStopped +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeControllerStopped(v TypedEventStreamEnvelopeControllerStopped) error { + v.Type = "controller.stopped" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTypedEventStreamEnvelopeControllerStopped performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeControllerStopped +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeControllerStopped(v TypedEventStreamEnvelopeControllerStopped) error { + v.Type = "controller.stopped" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedEventStreamEnvelopeConvoyClosed returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeConvoyClosed +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeConvoyClosed() (TypedEventStreamEnvelopeConvoyClosed, error) { + var body TypedEventStreamEnvelopeConvoyClosed + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTypedEventStreamEnvelopeConvoyClosed overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeConvoyClosed +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeConvoyClosed(v TypedEventStreamEnvelopeConvoyClosed) error { + v.Type = "convoy.closed" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTypedEventStreamEnvelopeConvoyClosed performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeConvoyClosed +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeConvoyClosed(v TypedEventStreamEnvelopeConvoyClosed) error { + v.Type = "convoy.closed" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedEventStreamEnvelopeConvoyCreated returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeConvoyCreated +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeConvoyCreated() (TypedEventStreamEnvelopeConvoyCreated, error) { + var body TypedEventStreamEnvelopeConvoyCreated + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTypedEventStreamEnvelopeConvoyCreated overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeConvoyCreated +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeConvoyCreated(v TypedEventStreamEnvelopeConvoyCreated) error { + v.Type = "convoy.created" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTypedEventStreamEnvelopeConvoyCreated performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeConvoyCreated +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeConvoyCreated(v TypedEventStreamEnvelopeConvoyCreated) error { + v.Type = "convoy.created" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedEventStreamEnvelopeExtmsgAdapterAdded returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeExtmsgAdapterAdded +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeExtmsgAdapterAdded() (TypedEventStreamEnvelopeExtmsgAdapterAdded, error) { + var body TypedEventStreamEnvelopeExtmsgAdapterAdded + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTypedEventStreamEnvelopeExtmsgAdapterAdded overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeExtmsgAdapterAdded +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeExtmsgAdapterAdded(v TypedEventStreamEnvelopeExtmsgAdapterAdded) error { + v.Type = "extmsg.adapter_added" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTypedEventStreamEnvelopeExtmsgAdapterAdded performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeExtmsgAdapterAdded +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeExtmsgAdapterAdded(v TypedEventStreamEnvelopeExtmsgAdapterAdded) error { + v.Type = "extmsg.adapter_added" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedEventStreamEnvelopeExtmsgAdapterRemoved returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeExtmsgAdapterRemoved +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeExtmsgAdapterRemoved() (TypedEventStreamEnvelopeExtmsgAdapterRemoved, error) { + var body TypedEventStreamEnvelopeExtmsgAdapterRemoved + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTypedEventStreamEnvelopeExtmsgAdapterRemoved overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeExtmsgAdapterRemoved +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeExtmsgAdapterRemoved(v TypedEventStreamEnvelopeExtmsgAdapterRemoved) error { + v.Type = "extmsg.adapter_removed" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTypedEventStreamEnvelopeExtmsgAdapterRemoved performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeExtmsgAdapterRemoved +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeExtmsgAdapterRemoved(v TypedEventStreamEnvelopeExtmsgAdapterRemoved) error { + v.Type = "extmsg.adapter_removed" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedEventStreamEnvelopeExtmsgBound returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeExtmsgBound +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeExtmsgBound() (TypedEventStreamEnvelopeExtmsgBound, error) { + var body TypedEventStreamEnvelopeExtmsgBound + err := json.Unmarshal(t.union, &body) + return body, err } -// PatchV0CityByCityNameAgentByDirByBaseParams defines parameters for PatchV0CityByCityNameAgentByDirByBase. -type PatchV0CityByCityNameAgentByDirByBaseParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedEventStreamEnvelopeExtmsgBound overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeExtmsgBound +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeExtmsgBound(v TypedEventStreamEnvelopeExtmsgBound) error { + v.Type = "extmsg.bound" + b, err := json.Marshal(v) + t.union = b + return err } -// GetV0CityByCityNameAgentByDirByBaseOutputParams defines parameters for GetV0CityByCityNameAgentByDirByBaseOutput. -type GetV0CityByCityNameAgentByDirByBaseOutputParams struct { - // Tail Number of recent compaction segments to return. This API parameter keeps compaction-segment semantics even though gc session logs --tail counts displayed transcript entries. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. - Tail *string `form:"tail,omitempty" json:"tail,omitempty"` +// MergeTypedEventStreamEnvelopeExtmsgBound performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeExtmsgBound +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeExtmsgBound(v TypedEventStreamEnvelopeExtmsgBound) error { + v.Type = "extmsg.bound" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Before Message UUID cursor for loading older messages. - Before *string `form:"before,omitempty" json:"before,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// PostV0CityByCityNameAgentByDirByBaseByActionParams defines parameters for PostV0CityByCityNameAgentByDirByBaseByAction. -type PostV0CityByCityNameAgentByDirByBaseByActionParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedEventStreamEnvelopeExtmsgGroupCreated returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeExtmsgGroupCreated +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeExtmsgGroupCreated() (TypedEventStreamEnvelopeExtmsgGroupCreated, error) { + var body TypedEventStreamEnvelopeExtmsgGroupCreated + err := json.Unmarshal(t.union, &body) + return body, err } -// PostV0CityByCityNameAgentByDirByBaseByActionParamsAction defines parameters for PostV0CityByCityNameAgentByDirByBaseByAction. -type PostV0CityByCityNameAgentByDirByBaseByActionParamsAction string +// FromTypedEventStreamEnvelopeExtmsgGroupCreated overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeExtmsgGroupCreated +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeExtmsgGroupCreated(v TypedEventStreamEnvelopeExtmsgGroupCreated) error { + v.Type = "extmsg.group_created" + b, err := json.Marshal(v) + t.union = b + return err +} -// GetV0CityByCityNameAgentsParams defines parameters for GetV0CityByCityNameAgents. -type GetV0CityByCityNameAgentsParams struct { - // Index Event sequence number; when provided, blocks until a newer event arrives. - Index *string `form:"index,omitempty" json:"index,omitempty"` +// MergeTypedEventStreamEnvelopeExtmsgGroupCreated performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeExtmsgGroupCreated +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeExtmsgGroupCreated(v TypedEventStreamEnvelopeExtmsgGroupCreated) error { + v.Type = "extmsg.group_created" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Wait How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. - Wait *string `form:"wait,omitempty" json:"wait,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // Pool Filter by pool name. - Pool *string `form:"pool,omitempty" json:"pool,omitempty"` +// AsTypedEventStreamEnvelopeExtmsgInbound returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeExtmsgInbound +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeExtmsgInbound() (TypedEventStreamEnvelopeExtmsgInbound, error) { + var body TypedEventStreamEnvelopeExtmsgInbound + err := json.Unmarshal(t.union, &body) + return body, err +} - // Rig Filter by rig name. - Rig *string `form:"rig,omitempty" json:"rig,omitempty"` +// FromTypedEventStreamEnvelopeExtmsgInbound overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeExtmsgInbound +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeExtmsgInbound(v TypedEventStreamEnvelopeExtmsgInbound) error { + v.Type = "extmsg.inbound" + b, err := json.Marshal(v) + t.union = b + return err +} - // Running Filter by running state. Omit to return all agents. - Running *GetV0CityByCityNameAgentsParamsRunning `form:"running,omitempty" json:"running,omitempty"` +// MergeTypedEventStreamEnvelopeExtmsgInbound performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeExtmsgInbound +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeExtmsgInbound(v TypedEventStreamEnvelopeExtmsgInbound) error { + v.Type = "extmsg.inbound" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Peek Include last output preview. - Peek *bool `form:"peek,omitempty" json:"peek,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// GetV0CityByCityNameAgentsParamsRunning defines parameters for GetV0CityByCityNameAgents. -type GetV0CityByCityNameAgentsParamsRunning string +// AsTypedEventStreamEnvelopeExtmsgOutbound returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeExtmsgOutbound +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeExtmsgOutbound() (TypedEventStreamEnvelopeExtmsgOutbound, error) { + var body TypedEventStreamEnvelopeExtmsgOutbound + err := json.Unmarshal(t.union, &body) + return body, err +} -// CreateAgentParams defines parameters for CreateAgent. -type CreateAgentParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedEventStreamEnvelopeExtmsgOutbound overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeExtmsgOutbound +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeExtmsgOutbound(v TypedEventStreamEnvelopeExtmsgOutbound) error { + v.Type = "extmsg.outbound" + b, err := json.Marshal(v) + t.union = b + return err } -// DeleteV0CityByCityNameBeadByIdParams defines parameters for DeleteV0CityByCityNameBeadById. -type DeleteV0CityByCityNameBeadByIdParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// MergeTypedEventStreamEnvelopeExtmsgOutbound performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeExtmsgOutbound +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeExtmsgOutbound(v TypedEventStreamEnvelopeExtmsgOutbound) error { + v.Type = "extmsg.outbound" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// PatchV0CityByCityNameBeadByIdParams defines parameters for PatchV0CityByCityNameBeadById. -type PatchV0CityByCityNameBeadByIdParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedEventStreamEnvelopeExtmsgUnbound returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeExtmsgUnbound +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeExtmsgUnbound() (TypedEventStreamEnvelopeExtmsgUnbound, error) { + var body TypedEventStreamEnvelopeExtmsgUnbound + err := json.Unmarshal(t.union, &body) + return body, err } -// PostV0CityByCityNameBeadByIdAssignParams defines parameters for PostV0CityByCityNameBeadByIdAssign. -type PostV0CityByCityNameBeadByIdAssignParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedEventStreamEnvelopeExtmsgUnbound overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeExtmsgUnbound +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeExtmsgUnbound(v TypedEventStreamEnvelopeExtmsgUnbound) error { + v.Type = "extmsg.unbound" + b, err := json.Marshal(v) + t.union = b + return err } -// PostV0CityByCityNameBeadByIdCloseParams defines parameters for PostV0CityByCityNameBeadByIdClose. -type PostV0CityByCityNameBeadByIdCloseParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// MergeTypedEventStreamEnvelopeExtmsgUnbound performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeExtmsgUnbound +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeExtmsgUnbound(v TypedEventStreamEnvelopeExtmsgUnbound) error { + v.Type = "extmsg.unbound" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// PostV0CityByCityNameBeadByIdReopenParams defines parameters for PostV0CityByCityNameBeadByIdReopen. -type PostV0CityByCityNameBeadByIdReopenParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedEventStreamEnvelopeMailArchived returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeMailArchived +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeMailArchived() (TypedEventStreamEnvelopeMailArchived, error) { + var body TypedEventStreamEnvelopeMailArchived + err := json.Unmarshal(t.union, &body) + return body, err } -// PostV0CityByCityNameBeadByIdUpdateParams defines parameters for PostV0CityByCityNameBeadByIdUpdate. -type PostV0CityByCityNameBeadByIdUpdateParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedEventStreamEnvelopeMailArchived overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeMailArchived +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeMailArchived(v TypedEventStreamEnvelopeMailArchived) error { + v.Type = "mail.archived" + b, err := json.Marshal(v) + t.union = b + return err } -// GetV0CityByCityNameBeadsParams defines parameters for GetV0CityByCityNameBeads. -type GetV0CityByCityNameBeadsParams struct { - // Index Event sequence number; when provided, blocks until a newer event arrives. - Index *string `form:"index,omitempty" json:"index,omitempty"` +// MergeTypedEventStreamEnvelopeMailArchived performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeMailArchived +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeMailArchived(v TypedEventStreamEnvelopeMailArchived) error { + v.Type = "mail.archived" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Wait How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. - Wait *string `form:"wait,omitempty" json:"wait,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // Cursor Pagination cursor from a previous response's next_cursor field. - Cursor *string `form:"cursor,omitempty" json:"cursor,omitempty"` +// AsTypedEventStreamEnvelopeMailDeleted returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeMailDeleted +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeMailDeleted() (TypedEventStreamEnvelopeMailDeleted, error) { + var body TypedEventStreamEnvelopeMailDeleted + err := json.Unmarshal(t.union, &body) + return body, err +} - // Limit Maximum number of results to return. 0 = server default. - Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` +// FromTypedEventStreamEnvelopeMailDeleted overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeMailDeleted +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeMailDeleted(v TypedEventStreamEnvelopeMailDeleted) error { + v.Type = "mail.deleted" + b, err := json.Marshal(v) + t.union = b + return err +} - // Status Filter by bead status. - Status *string `form:"status,omitempty" json:"status,omitempty"` +// MergeTypedEventStreamEnvelopeMailDeleted performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeMailDeleted +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeMailDeleted(v TypedEventStreamEnvelopeMailDeleted) error { + v.Type = "mail.deleted" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Type Filter by bead type. - Type *string `form:"type,omitempty" json:"type,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // Label Filter by label. - Label *string `form:"label,omitempty" json:"label,omitempty"` +// AsTypedEventStreamEnvelopeMailMarkedRead returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeMailMarkedRead +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeMailMarkedRead() (TypedEventStreamEnvelopeMailMarkedRead, error) { + var body TypedEventStreamEnvelopeMailMarkedRead + err := json.Unmarshal(t.union, &body) + return body, err +} - // Assignee Filter by assignee. - Assignee *string `form:"assignee,omitempty" json:"assignee,omitempty"` +// FromTypedEventStreamEnvelopeMailMarkedRead overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeMailMarkedRead +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeMailMarkedRead(v TypedEventStreamEnvelopeMailMarkedRead) error { + v.Type = "mail.marked_read" + b, err := json.Marshal(v) + t.union = b + return err +} - // Rig Filter by rig. - Rig *string `form:"rig,omitempty" json:"rig,omitempty"` +// MergeTypedEventStreamEnvelopeMailMarkedRead performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeMailMarkedRead +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeMailMarkedRead(v TypedEventStreamEnvelopeMailMarkedRead) error { + v.Type = "mail.marked_read" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// CreateBeadParams defines parameters for CreateBead. -type CreateBeadParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedEventStreamEnvelopeMailMarkedUnread returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeMailMarkedUnread +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeMailMarkedUnread() (TypedEventStreamEnvelopeMailMarkedUnread, error) { + var body TypedEventStreamEnvelopeMailMarkedUnread + err := json.Unmarshal(t.union, &body) + return body, err +} - // IdempotencyKey Idempotency key for safe retries. - IdempotencyKey *string `json:"Idempotency-Key,omitempty"` +// FromTypedEventStreamEnvelopeMailMarkedUnread overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeMailMarkedUnread +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeMailMarkedUnread(v TypedEventStreamEnvelopeMailMarkedUnread) error { + v.Type = "mail.marked_unread" + b, err := json.Marshal(v) + t.union = b + return err } -// GetV0CityByCityNameBeadsReadyParams defines parameters for GetV0CityByCityNameBeadsReady. -type GetV0CityByCityNameBeadsReadyParams struct { - // Index Event sequence number; when provided, blocks until a newer event arrives. - Index *string `form:"index,omitempty" json:"index,omitempty"` +// MergeTypedEventStreamEnvelopeMailMarkedUnread performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeMailMarkedUnread +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeMailMarkedUnread(v TypedEventStreamEnvelopeMailMarkedUnread) error { + v.Type = "mail.marked_unread" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Wait How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. - Wait *string `form:"wait,omitempty" json:"wait,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// DeleteV0CityByCityNameConvoyByIdParams defines parameters for DeleteV0CityByCityNameConvoyById. -type DeleteV0CityByCityNameConvoyByIdParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedEventStreamEnvelopeMailRead returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeMailRead +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeMailRead() (TypedEventStreamEnvelopeMailRead, error) { + var body TypedEventStreamEnvelopeMailRead + err := json.Unmarshal(t.union, &body) + return body, err } -// PostV0CityByCityNameConvoyByIdAddParams defines parameters for PostV0CityByCityNameConvoyByIdAdd. -type PostV0CityByCityNameConvoyByIdAddParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedEventStreamEnvelopeMailRead overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeMailRead +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeMailRead(v TypedEventStreamEnvelopeMailRead) error { + v.Type = "mail.read" + b, err := json.Marshal(v) + t.union = b + return err } -// PostV0CityByCityNameConvoyByIdCloseParams defines parameters for PostV0CityByCityNameConvoyByIdClose. -type PostV0CityByCityNameConvoyByIdCloseParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// MergeTypedEventStreamEnvelopeMailRead performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeMailRead +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeMailRead(v TypedEventStreamEnvelopeMailRead) error { + v.Type = "mail.read" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// PostV0CityByCityNameConvoyByIdRemoveParams defines parameters for PostV0CityByCityNameConvoyByIdRemove. -type PostV0CityByCityNameConvoyByIdRemoveParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedEventStreamEnvelopeMailReplied returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeMailReplied +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeMailReplied() (TypedEventStreamEnvelopeMailReplied, error) { + var body TypedEventStreamEnvelopeMailReplied + err := json.Unmarshal(t.union, &body) + return body, err } -// GetV0CityByCityNameConvoysParams defines parameters for GetV0CityByCityNameConvoys. -type GetV0CityByCityNameConvoysParams struct { - // Index Event sequence number; when provided, blocks until a newer event arrives. - Index *string `form:"index,omitempty" json:"index,omitempty"` +// FromTypedEventStreamEnvelopeMailReplied overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeMailReplied +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeMailReplied(v TypedEventStreamEnvelopeMailReplied) error { + v.Type = "mail.replied" + b, err := json.Marshal(v) + t.union = b + return err +} - // Wait How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. - Wait *string `form:"wait,omitempty" json:"wait,omitempty"` +// MergeTypedEventStreamEnvelopeMailReplied performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeMailReplied +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeMailReplied(v TypedEventStreamEnvelopeMailReplied) error { + v.Type = "mail.replied" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Cursor Pagination cursor from a previous response's next_cursor field. - Cursor *string `form:"cursor,omitempty" json:"cursor,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // Limit Maximum number of results to return. 0 = server default. - Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` +// AsTypedEventStreamEnvelopeMailSent returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeMailSent +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeMailSent() (TypedEventStreamEnvelopeMailSent, error) { + var body TypedEventStreamEnvelopeMailSent + err := json.Unmarshal(t.union, &body) + return body, err } -// CreateConvoyParams defines parameters for CreateConvoy. -type CreateConvoyParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedEventStreamEnvelopeMailSent overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeMailSent +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeMailSent(v TypedEventStreamEnvelopeMailSent) error { + v.Type = "mail.sent" + b, err := json.Marshal(v) + t.union = b + return err } -// GetV0CityByCityNameEventsParams defines parameters for GetV0CityByCityNameEvents. -type GetV0CityByCityNameEventsParams struct { - // Index Event sequence number; when provided, blocks until a newer event arrives. - Index *string `form:"index,omitempty" json:"index,omitempty"` +// MergeTypedEventStreamEnvelopeMailSent performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeMailSent +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeMailSent(v TypedEventStreamEnvelopeMailSent) error { + v.Type = "mail.sent" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Wait How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. - Wait *string `form:"wait,omitempty" json:"wait,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // Cursor Pagination cursor from a previous response's next_cursor field. - Cursor *string `form:"cursor,omitempty" json:"cursor,omitempty"` +// AsTypedEventStreamEnvelopeOrderCompleted returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeOrderCompleted +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeOrderCompleted() (TypedEventStreamEnvelopeOrderCompleted, error) { + var body TypedEventStreamEnvelopeOrderCompleted + err := json.Unmarshal(t.union, &body) + return body, err +} - // Limit Maximum number of results to return. 0 = server default. - Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` +// FromTypedEventStreamEnvelopeOrderCompleted overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeOrderCompleted +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeOrderCompleted(v TypedEventStreamEnvelopeOrderCompleted) error { + v.Type = "order.completed" + b, err := json.Marshal(v) + t.union = b + return err +} - // Type Filter by event type. - Type *string `form:"type,omitempty" json:"type,omitempty"` +// MergeTypedEventStreamEnvelopeOrderCompleted performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeOrderCompleted +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeOrderCompleted(v TypedEventStreamEnvelopeOrderCompleted) error { + v.Type = "order.completed" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Actor Filter by actor. - Actor *string `form:"actor,omitempty" json:"actor,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // Since Filter events since duration ago (Go duration string, e.g. 5m). - Since *string `form:"since,omitempty" json:"since,omitempty"` +// AsTypedEventStreamEnvelopeOrderFailed returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeOrderFailed +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeOrderFailed() (TypedEventStreamEnvelopeOrderFailed, error) { + var body TypedEventStreamEnvelopeOrderFailed + err := json.Unmarshal(t.union, &body) + return body, err } -// EmitEventParams defines parameters for EmitEvent. -type EmitEventParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedEventStreamEnvelopeOrderFailed overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeOrderFailed +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeOrderFailed(v TypedEventStreamEnvelopeOrderFailed) error { + v.Type = "order.failed" + b, err := json.Marshal(v) + t.union = b + return err } -// StreamEventsParams defines parameters for StreamEvents. -type StreamEventsParams struct { - // AfterSeq Reconnect position: only deliver events after this sequence number. - AfterSeq *string `form:"after_seq,omitempty" json:"after_seq,omitempty"` +// MergeTypedEventStreamEnvelopeOrderFailed performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeOrderFailed +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeOrderFailed(v TypedEventStreamEnvelopeOrderFailed) error { + v.Type = "order.failed" + b, err := json.Marshal(v) + if err != nil { + return err + } - // LastEventID SSE reconnect position from the last received event ID. - LastEventID *string `json:"Last-Event-ID,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// DeleteV0CityByCityNameExtmsgAdaptersParams defines parameters for DeleteV0CityByCityNameExtmsgAdapters. -type DeleteV0CityByCityNameExtmsgAdaptersParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedEventStreamEnvelopeOrderFired returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeOrderFired +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeOrderFired() (TypedEventStreamEnvelopeOrderFired, error) { + var body TypedEventStreamEnvelopeOrderFired + err := json.Unmarshal(t.union, &body) + return body, err } -// RegisterExtmsgAdapterParams defines parameters for RegisterExtmsgAdapter. -type RegisterExtmsgAdapterParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedEventStreamEnvelopeOrderFired overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeOrderFired +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeOrderFired(v TypedEventStreamEnvelopeOrderFired) error { + v.Type = "order.fired" + b, err := json.Marshal(v) + t.union = b + return err } -// PostV0CityByCityNameExtmsgBindParams defines parameters for PostV0CityByCityNameExtmsgBind. -type PostV0CityByCityNameExtmsgBindParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` -} +// MergeTypedEventStreamEnvelopeOrderFired performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeOrderFired +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeOrderFired(v TypedEventStreamEnvelopeOrderFired) error { + v.Type = "order.fired" + b, err := json.Marshal(v) + if err != nil { + return err + } -// GetV0CityByCityNameExtmsgBindingsParams defines parameters for GetV0CityByCityNameExtmsgBindings. -type GetV0CityByCityNameExtmsgBindingsParams struct { - // SessionId Session ID to list bindings for. - SessionId *string `form:"session_id,omitempty" json:"session_id,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// GetV0CityByCityNameExtmsgGroupsParams defines parameters for GetV0CityByCityNameExtmsgGroups. -type GetV0CityByCityNameExtmsgGroupsParams struct { - // ScopeId Scope ID. - ScopeId *string `form:"scope_id,omitempty" json:"scope_id,omitempty"` - - // Provider Provider name. - Provider *string `form:"provider,omitempty" json:"provider,omitempty"` +// AsTypedEventStreamEnvelopeProviderSwapped returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeProviderSwapped +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeProviderSwapped() (TypedEventStreamEnvelopeProviderSwapped, error) { + var body TypedEventStreamEnvelopeProviderSwapped + err := json.Unmarshal(t.union, &body) + return body, err +} - // AccountId Account ID. - AccountId *string `form:"account_id,omitempty" json:"account_id,omitempty"` +// FromTypedEventStreamEnvelopeProviderSwapped overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeProviderSwapped +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeProviderSwapped(v TypedEventStreamEnvelopeProviderSwapped) error { + v.Type = "provider.swapped" + b, err := json.Marshal(v) + t.union = b + return err +} - // ConversationId Conversation ID. - ConversationId *string `form:"conversation_id,omitempty" json:"conversation_id,omitempty"` +// MergeTypedEventStreamEnvelopeProviderSwapped performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeProviderSwapped +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeProviderSwapped(v TypedEventStreamEnvelopeProviderSwapped) error { + v.Type = "provider.swapped" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Kind Conversation kind. - Kind *string `form:"kind,omitempty" json:"kind,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// EnsureExtmsgGroupParams defines parameters for EnsureExtmsgGroup. -type EnsureExtmsgGroupParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedEventStreamEnvelopeSessionCrashed returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeSessionCrashed +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeSessionCrashed() (TypedEventStreamEnvelopeSessionCrashed, error) { + var body TypedEventStreamEnvelopeSessionCrashed + err := json.Unmarshal(t.union, &body) + return body, err } -// PostV0CityByCityNameExtmsgInboundParams defines parameters for PostV0CityByCityNameExtmsgInbound. -type PostV0CityByCityNameExtmsgInboundParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedEventStreamEnvelopeSessionCrashed overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeSessionCrashed +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeSessionCrashed(v TypedEventStreamEnvelopeSessionCrashed) error { + v.Type = "session.crashed" + b, err := json.Marshal(v) + t.union = b + return err } -// PostV0CityByCityNameExtmsgOutboundParams defines parameters for PostV0CityByCityNameExtmsgOutbound. -type PostV0CityByCityNameExtmsgOutboundParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// MergeTypedEventStreamEnvelopeSessionCrashed performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeSessionCrashed +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeSessionCrashed(v TypedEventStreamEnvelopeSessionCrashed) error { + v.Type = "session.crashed" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// DeleteV0CityByCityNameExtmsgParticipantsParams defines parameters for DeleteV0CityByCityNameExtmsgParticipants. -type DeleteV0CityByCityNameExtmsgParticipantsParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedEventStreamEnvelopeSessionDraining returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeSessionDraining +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeSessionDraining() (TypedEventStreamEnvelopeSessionDraining, error) { + var body TypedEventStreamEnvelopeSessionDraining + err := json.Unmarshal(t.union, &body) + return body, err } -// PostV0CityByCityNameExtmsgParticipantsParams defines parameters for PostV0CityByCityNameExtmsgParticipants. -type PostV0CityByCityNameExtmsgParticipantsParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedEventStreamEnvelopeSessionDraining overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeSessionDraining +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeSessionDraining(v TypedEventStreamEnvelopeSessionDraining) error { + v.Type = "session.draining" + b, err := json.Marshal(v) + t.union = b + return err } -// GetV0CityByCityNameExtmsgTranscriptParams defines parameters for GetV0CityByCityNameExtmsgTranscript. -type GetV0CityByCityNameExtmsgTranscriptParams struct { - // ScopeId Scope ID. - ScopeId *string `form:"scope_id,omitempty" json:"scope_id,omitempty"` +// MergeTypedEventStreamEnvelopeSessionDraining performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeSessionDraining +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeSessionDraining(v TypedEventStreamEnvelopeSessionDraining) error { + v.Type = "session.draining" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Provider Provider name. - Provider *string `form:"provider,omitempty" json:"provider,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // AccountId Account ID. - AccountId *string `form:"account_id,omitempty" json:"account_id,omitempty"` +// AsTypedEventStreamEnvelopeSessionIdleKilled returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeSessionIdleKilled +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeSessionIdleKilled() (TypedEventStreamEnvelopeSessionIdleKilled, error) { + var body TypedEventStreamEnvelopeSessionIdleKilled + err := json.Unmarshal(t.union, &body) + return body, err +} - // ConversationId Conversation ID. - ConversationId *string `form:"conversation_id,omitempty" json:"conversation_id,omitempty"` +// FromTypedEventStreamEnvelopeSessionIdleKilled overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeSessionIdleKilled +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeSessionIdleKilled(v TypedEventStreamEnvelopeSessionIdleKilled) error { + v.Type = "session.idle_killed" + b, err := json.Marshal(v) + t.union = b + return err +} - // ParentConversationId Parent conversation ID. - ParentConversationId *string `form:"parent_conversation_id,omitempty" json:"parent_conversation_id,omitempty"` +// MergeTypedEventStreamEnvelopeSessionIdleKilled performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeSessionIdleKilled +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeSessionIdleKilled(v TypedEventStreamEnvelopeSessionIdleKilled) error { + v.Type = "session.idle_killed" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Kind Conversation kind. - Kind *string `form:"kind,omitempty" json:"kind,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// PostV0CityByCityNameExtmsgTranscriptAckParams defines parameters for PostV0CityByCityNameExtmsgTranscriptAck. -type PostV0CityByCityNameExtmsgTranscriptAckParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedEventStreamEnvelopeSessionQuarantined returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeSessionQuarantined +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeSessionQuarantined() (TypedEventStreamEnvelopeSessionQuarantined, error) { + var body TypedEventStreamEnvelopeSessionQuarantined + err := json.Unmarshal(t.union, &body) + return body, err } -// PostV0CityByCityNameExtmsgUnbindParams defines parameters for PostV0CityByCityNameExtmsgUnbind. -type PostV0CityByCityNameExtmsgUnbindParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedEventStreamEnvelopeSessionQuarantined overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeSessionQuarantined +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeSessionQuarantined(v TypedEventStreamEnvelopeSessionQuarantined) error { + v.Type = "session.quarantined" + b, err := json.Marshal(v) + t.union = b + return err } -// GetV0CityByCityNameFormulaByNameParams defines parameters for GetV0CityByCityNameFormulaByName. -type GetV0CityByCityNameFormulaByNameParams struct { - // ScopeKind Scope kind (city or rig). - ScopeKind *string `form:"scope_kind,omitempty" json:"scope_kind,omitempty"` - - // ScopeRef Scope reference. - ScopeRef *string `form:"scope_ref,omitempty" json:"scope_ref,omitempty"` +// MergeTypedEventStreamEnvelopeSessionQuarantined performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeSessionQuarantined +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeSessionQuarantined(v TypedEventStreamEnvelopeSessionQuarantined) error { + v.Type = "session.quarantined" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Target Target agent for preview compilation. - Target string `form:"target" json:"target"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// GetV0CityByCityNameFormulasParams defines parameters for GetV0CityByCityNameFormulas. -type GetV0CityByCityNameFormulasParams struct { - // ScopeKind Scope kind (city or rig). - ScopeKind *string `form:"scope_kind,omitempty" json:"scope_kind,omitempty"` - - // ScopeRef Scope reference. - ScopeRef *string `form:"scope_ref,omitempty" json:"scope_ref,omitempty"` +// AsTypedEventStreamEnvelopeSessionStopped returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeSessionStopped +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeSessionStopped() (TypedEventStreamEnvelopeSessionStopped, error) { + var body TypedEventStreamEnvelopeSessionStopped + err := json.Unmarshal(t.union, &body) + return body, err } -// GetV0CityByCityNameFormulasFeedParams defines parameters for GetV0CityByCityNameFormulasFeed. -type GetV0CityByCityNameFormulasFeedParams struct { - // ScopeKind Scope kind (city or rig). - ScopeKind *string `form:"scope_kind,omitempty" json:"scope_kind,omitempty"` - - // ScopeRef Scope reference. - ScopeRef *string `form:"scope_ref,omitempty" json:"scope_ref,omitempty"` - - // Limit Maximum number of feed items to return. 0 = default. - Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` +// FromTypedEventStreamEnvelopeSessionStopped overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeSessionStopped +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeSessionStopped(v TypedEventStreamEnvelopeSessionStopped) error { + v.Type = "session.stopped" + b, err := json.Marshal(v) + t.union = b + return err } -// GetV0CityByCityNameFormulasByNameParams defines parameters for GetV0CityByCityNameFormulasByName. -type GetV0CityByCityNameFormulasByNameParams struct { - // ScopeKind Scope kind (city or rig). - ScopeKind *string `form:"scope_kind,omitempty" json:"scope_kind,omitempty"` +// MergeTypedEventStreamEnvelopeSessionStopped performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeSessionStopped +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeSessionStopped(v TypedEventStreamEnvelopeSessionStopped) error { + v.Type = "session.stopped" + b, err := json.Marshal(v) + if err != nil { + return err + } - // ScopeRef Scope reference. - ScopeRef *string `form:"scope_ref,omitempty" json:"scope_ref,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // Target Target agent for preview compilation. - Target string `form:"target" json:"target"` +// AsTypedEventStreamEnvelopeSessionSuspended returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeSessionSuspended +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeSessionSuspended() (TypedEventStreamEnvelopeSessionSuspended, error) { + var body TypedEventStreamEnvelopeSessionSuspended + err := json.Unmarshal(t.union, &body) + return body, err } -// PostV0CityByCityNameFormulasByNamePreviewParams defines parameters for PostV0CityByCityNameFormulasByNamePreview. -type PostV0CityByCityNameFormulasByNamePreviewParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedEventStreamEnvelopeSessionSuspended overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeSessionSuspended +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeSessionSuspended(v TypedEventStreamEnvelopeSessionSuspended) error { + v.Type = "session.suspended" + b, err := json.Marshal(v) + t.union = b + return err } -// GetV0CityByCityNameFormulasByNameRunsParams defines parameters for GetV0CityByCityNameFormulasByNameRuns. -type GetV0CityByCityNameFormulasByNameRunsParams struct { - // ScopeKind Scope kind (city or rig). - ScopeKind *string `form:"scope_kind,omitempty" json:"scope_kind,omitempty"` +// MergeTypedEventStreamEnvelopeSessionSuspended performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeSessionSuspended +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeSessionSuspended(v TypedEventStreamEnvelopeSessionSuspended) error { + v.Type = "session.suspended" + b, err := json.Marshal(v) + if err != nil { + return err + } - // ScopeRef Scope reference. - ScopeRef *string `form:"scope_ref,omitempty" json:"scope_ref,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // Limit Maximum number of recent runs to return. 0 = default. - Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` +// AsTypedEventStreamEnvelopeSessionUndrained returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeSessionUndrained +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeSessionUndrained() (TypedEventStreamEnvelopeSessionUndrained, error) { + var body TypedEventStreamEnvelopeSessionUndrained + err := json.Unmarshal(t.union, &body) + return body, err } -// GetV0CityByCityNameMailParams defines parameters for GetV0CityByCityNameMail. -type GetV0CityByCityNameMailParams struct { - // Index Event sequence number; when provided, blocks until a newer event arrives. - Index *string `form:"index,omitempty" json:"index,omitempty"` +// FromTypedEventStreamEnvelopeSessionUndrained overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeSessionUndrained +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeSessionUndrained(v TypedEventStreamEnvelopeSessionUndrained) error { + v.Type = "session.undrained" + b, err := json.Marshal(v) + t.union = b + return err +} - // Wait How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. - Wait *string `form:"wait,omitempty" json:"wait,omitempty"` +// MergeTypedEventStreamEnvelopeSessionUndrained performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeSessionUndrained +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeSessionUndrained(v TypedEventStreamEnvelopeSessionUndrained) error { + v.Type = "session.undrained" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Cursor Pagination cursor from a previous response's next_cursor field. - Cursor *string `form:"cursor,omitempty" json:"cursor,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // Limit Maximum number of results to return. 0 = server default. - Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` +// AsTypedEventStreamEnvelopeSessionUpdated returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeSessionUpdated +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeSessionUpdated() (TypedEventStreamEnvelopeSessionUpdated, error) { + var body TypedEventStreamEnvelopeSessionUpdated + err := json.Unmarshal(t.union, &body) + return body, err +} - // Agent Filter by agent name. - Agent *string `form:"agent,omitempty" json:"agent,omitempty"` +// FromTypedEventStreamEnvelopeSessionUpdated overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeSessionUpdated +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeSessionUpdated(v TypedEventStreamEnvelopeSessionUpdated) error { + v.Type = "session.updated" + b, err := json.Marshal(v) + t.union = b + return err +} - // Status Filter by status (unread, all). - Status *string `form:"status,omitempty" json:"status,omitempty"` +// MergeTypedEventStreamEnvelopeSessionUpdated performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeSessionUpdated +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeSessionUpdated(v TypedEventStreamEnvelopeSessionUpdated) error { + v.Type = "session.updated" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Rig Filter by rig name. - Rig *string `form:"rig,omitempty" json:"rig,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// SendMailParams defines parameters for SendMail. -type SendMailParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedEventStreamEnvelopeSessionWoke returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeSessionWoke +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeSessionWoke() (TypedEventStreamEnvelopeSessionWoke, error) { + var body TypedEventStreamEnvelopeSessionWoke + err := json.Unmarshal(t.union, &body) + return body, err +} - // IdempotencyKey Idempotency key for safe retries. - IdempotencyKey *string `json:"Idempotency-Key,omitempty"` +// FromTypedEventStreamEnvelopeSessionWoke overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeSessionWoke +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeSessionWoke(v TypedEventStreamEnvelopeSessionWoke) error { + v.Type = "session.woke" + b, err := json.Marshal(v) + t.union = b + return err } -// GetV0CityByCityNameMailCountParams defines parameters for GetV0CityByCityNameMailCount. -type GetV0CityByCityNameMailCountParams struct { - // Agent Filter by agent name. - Agent *string `form:"agent,omitempty" json:"agent,omitempty"` +// MergeTypedEventStreamEnvelopeSessionWoke performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeSessionWoke +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeSessionWoke(v TypedEventStreamEnvelopeSessionWoke) error { + v.Type = "session.woke" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Rig Filter by rig name. - Rig *string `form:"rig,omitempty" json:"rig,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// GetV0CityByCityNameMailThreadByIdParams defines parameters for GetV0CityByCityNameMailThreadById. -type GetV0CityByCityNameMailThreadByIdParams struct { - // Rig Filter by rig. - Rig *string `form:"rig,omitempty" json:"rig,omitempty"` +// AsTypedEventStreamEnvelopeWorkerOperation returns the union data inside the TypedEventStreamEnvelope as a TypedEventStreamEnvelopeWorkerOperation +func (t TypedEventStreamEnvelope) AsTypedEventStreamEnvelopeWorkerOperation() (TypedEventStreamEnvelopeWorkerOperation, error) { + var body TypedEventStreamEnvelopeWorkerOperation + err := json.Unmarshal(t.union, &body) + return body, err } -// DeleteV0CityByCityNameMailByIdParams defines parameters for DeleteV0CityByCityNameMailById. -type DeleteV0CityByCityNameMailByIdParams struct { - // Rig Rig hint. - Rig *string `form:"rig,omitempty" json:"rig,omitempty"` +// FromTypedEventStreamEnvelopeWorkerOperation overwrites any union data inside the TypedEventStreamEnvelope as the provided TypedEventStreamEnvelopeWorkerOperation +func (t *TypedEventStreamEnvelope) FromTypedEventStreamEnvelopeWorkerOperation(v TypedEventStreamEnvelopeWorkerOperation) error { + v.Type = "worker.operation" + b, err := json.Marshal(v) + t.union = b + return err +} - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// MergeTypedEventStreamEnvelopeWorkerOperation performs a merge with any union data inside the TypedEventStreamEnvelope, using the provided TypedEventStreamEnvelopeWorkerOperation +func (t *TypedEventStreamEnvelope) MergeTypedEventStreamEnvelopeWorkerOperation(v TypedEventStreamEnvelopeWorkerOperation) error { + v.Type = "worker.operation" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// GetV0CityByCityNameMailByIdParams defines parameters for GetV0CityByCityNameMailById. -type GetV0CityByCityNameMailByIdParams struct { - // Rig Rig hint for O(1) lookup. - Rig *string `form:"rig,omitempty" json:"rig,omitempty"` +func (t TypedEventStreamEnvelope) Discriminator() (string, error) { + var discriminator struct { + Discriminator string `json:"type"` + } + err := json.Unmarshal(t.union, &discriminator) + return discriminator.Discriminator, err +} + +func (t TypedEventStreamEnvelope) ValueByDiscriminator() (interface{}, error) { + discriminator, err := t.Discriminator() + if err != nil { + return nil, err + } + switch discriminator { + case "bead.closed": + return t.AsTypedEventStreamEnvelopeBeadClosed() + case "bead.created": + return t.AsTypedEventStreamEnvelopeBeadCreated() + case "bead.updated": + return t.AsTypedEventStreamEnvelopeBeadUpdated() + case "city.created": + return t.AsTypedEventStreamEnvelopeCityCreated() + case "city.init_failed": + return t.AsTypedEventStreamEnvelopeCityInitFailed() + case "city.ready": + return t.AsTypedEventStreamEnvelopeCityReady() + case "city.resumed": + return t.AsTypedEventStreamEnvelopeCityResumed() + case "city.suspended": + return t.AsTypedEventStreamEnvelopeCitySuspended() + case "city.unregister_failed": + return t.AsTypedEventStreamEnvelopeCityUnregisterFailed() + case "city.unregister_requested": + return t.AsTypedEventStreamEnvelopeCityUnregisterRequested() + case "city.unregistered": + return t.AsTypedEventStreamEnvelopeCityUnregistered() + case "controller.started": + return t.AsTypedEventStreamEnvelopeControllerStarted() + case "controller.stopped": + return t.AsTypedEventStreamEnvelopeControllerStopped() + case "convoy.closed": + return t.AsTypedEventStreamEnvelopeConvoyClosed() + case "convoy.created": + return t.AsTypedEventStreamEnvelopeConvoyCreated() + case "extmsg.adapter_added": + return t.AsTypedEventStreamEnvelopeExtmsgAdapterAdded() + case "extmsg.adapter_removed": + return t.AsTypedEventStreamEnvelopeExtmsgAdapterRemoved() + case "extmsg.bound": + return t.AsTypedEventStreamEnvelopeExtmsgBound() + case "extmsg.group_created": + return t.AsTypedEventStreamEnvelopeExtmsgGroupCreated() + case "extmsg.inbound": + return t.AsTypedEventStreamEnvelopeExtmsgInbound() + case "extmsg.outbound": + return t.AsTypedEventStreamEnvelopeExtmsgOutbound() + case "extmsg.unbound": + return t.AsTypedEventStreamEnvelopeExtmsgUnbound() + case "mail.archived": + return t.AsTypedEventStreamEnvelopeMailArchived() + case "mail.deleted": + return t.AsTypedEventStreamEnvelopeMailDeleted() + case "mail.marked_read": + return t.AsTypedEventStreamEnvelopeMailMarkedRead() + case "mail.marked_unread": + return t.AsTypedEventStreamEnvelopeMailMarkedUnread() + case "mail.read": + return t.AsTypedEventStreamEnvelopeMailRead() + case "mail.replied": + return t.AsTypedEventStreamEnvelopeMailReplied() + case "mail.sent": + return t.AsTypedEventStreamEnvelopeMailSent() + case "order.completed": + return t.AsTypedEventStreamEnvelopeOrderCompleted() + case "order.failed": + return t.AsTypedEventStreamEnvelopeOrderFailed() + case "order.fired": + return t.AsTypedEventStreamEnvelopeOrderFired() + case "provider.swapped": + return t.AsTypedEventStreamEnvelopeProviderSwapped() + case "session.crashed": + return t.AsTypedEventStreamEnvelopeSessionCrashed() + case "session.draining": + return t.AsTypedEventStreamEnvelopeSessionDraining() + case "session.idle_killed": + return t.AsTypedEventStreamEnvelopeSessionIdleKilled() + case "session.quarantined": + return t.AsTypedEventStreamEnvelopeSessionQuarantined() + case "session.stopped": + return t.AsTypedEventStreamEnvelopeSessionStopped() + case "session.suspended": + return t.AsTypedEventStreamEnvelopeSessionSuspended() + case "session.undrained": + return t.AsTypedEventStreamEnvelopeSessionUndrained() + case "session.updated": + return t.AsTypedEventStreamEnvelopeSessionUpdated() + case "session.woke": + return t.AsTypedEventStreamEnvelopeSessionWoke() + case "worker.operation": + return t.AsTypedEventStreamEnvelopeWorkerOperation() + default: + return nil, errors.New("unknown discriminator value: " + discriminator) + } } -// PostV0CityByCityNameMailByIdArchiveParams defines parameters for PostV0CityByCityNameMailByIdArchive. -type PostV0CityByCityNameMailByIdArchiveParams struct { - // Rig Rig hint. - Rig *string `form:"rig,omitempty" json:"rig,omitempty"` +func (t TypedEventStreamEnvelope) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +func (t *TypedEventStreamEnvelope) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err } -// PostV0CityByCityNameMailByIdMarkUnreadParams defines parameters for PostV0CityByCityNameMailByIdMarkUnread. -type PostV0CityByCityNameMailByIdMarkUnreadParams struct { - // Rig Rig hint. - Rig *string `form:"rig,omitempty" json:"rig,omitempty"` +// AsTypedTaggedEventStreamEnvelopeBeadClosed returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeBeadClosed +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeBeadClosed() (TypedTaggedEventStreamEnvelopeBeadClosed, error) { + var body TypedTaggedEventStreamEnvelopeBeadClosed + err := json.Unmarshal(t.union, &body) + return body, err +} - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedTaggedEventStreamEnvelopeBeadClosed overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeBeadClosed +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeBeadClosed(v TypedTaggedEventStreamEnvelopeBeadClosed) error { + v.Type = "bead.closed" + b, err := json.Marshal(v) + t.union = b + return err } -// PostV0CityByCityNameMailByIdReadParams defines parameters for PostV0CityByCityNameMailByIdRead. -type PostV0CityByCityNameMailByIdReadParams struct { - // Rig Rig hint. - Rig *string `form:"rig,omitempty" json:"rig,omitempty"` +// MergeTypedTaggedEventStreamEnvelopeBeadClosed performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeBeadClosed +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeBeadClosed(v TypedTaggedEventStreamEnvelopeBeadClosed) error { + v.Type = "bead.closed" + b, err := json.Marshal(v) + if err != nil { + return err + } - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// ReplyMailParams defines parameters for ReplyMail. -type ReplyMailParams struct { - // Rig Rig hint. - Rig *string `form:"rig,omitempty" json:"rig,omitempty"` +// AsTypedTaggedEventStreamEnvelopeBeadCreated returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeBeadCreated +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeBeadCreated() (TypedTaggedEventStreamEnvelopeBeadCreated, error) { + var body TypedTaggedEventStreamEnvelopeBeadCreated + err := json.Unmarshal(t.union, &body) + return body, err +} - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedTaggedEventStreamEnvelopeBeadCreated overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeBeadCreated +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeBeadCreated(v TypedTaggedEventStreamEnvelopeBeadCreated) error { + v.Type = "bead.created" + b, err := json.Marshal(v) + t.union = b + return err } -// GetV0CityByCityNameOrderHistoryByBeadIdParams defines parameters for GetV0CityByCityNameOrderHistoryByBeadId. -type GetV0CityByCityNameOrderHistoryByBeadIdParams struct { - // StoreRef Store reference for disambiguating store-local bead IDs. - StoreRef *string `form:"store_ref,omitempty" json:"store_ref,omitempty"` +// MergeTypedTaggedEventStreamEnvelopeBeadCreated performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeBeadCreated +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeBeadCreated(v TypedTaggedEventStreamEnvelopeBeadCreated) error { + v.Type = "bead.created" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// PostV0CityByCityNameOrderByNameDisableParams defines parameters for PostV0CityByCityNameOrderByNameDisable. -type PostV0CityByCityNameOrderByNameDisableParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedTaggedEventStreamEnvelopeBeadUpdated returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeBeadUpdated +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeBeadUpdated() (TypedTaggedEventStreamEnvelopeBeadUpdated, error) { + var body TypedTaggedEventStreamEnvelopeBeadUpdated + err := json.Unmarshal(t.union, &body) + return body, err } -// PostV0CityByCityNameOrderByNameEnableParams defines parameters for PostV0CityByCityNameOrderByNameEnable. -type PostV0CityByCityNameOrderByNameEnableParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedTaggedEventStreamEnvelopeBeadUpdated overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeBeadUpdated +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeBeadUpdated(v TypedTaggedEventStreamEnvelopeBeadUpdated) error { + v.Type = "bead.updated" + b, err := json.Marshal(v) + t.union = b + return err } -// GetV0CityByCityNameOrdersFeedParams defines parameters for GetV0CityByCityNameOrdersFeed. -type GetV0CityByCityNameOrdersFeedParams struct { - // ScopeKind Scope kind (city or rig). - ScopeKind *string `form:"scope_kind,omitempty" json:"scope_kind,omitempty"` +// MergeTypedTaggedEventStreamEnvelopeBeadUpdated performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeBeadUpdated +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeBeadUpdated(v TypedTaggedEventStreamEnvelopeBeadUpdated) error { + v.Type = "bead.updated" + b, err := json.Marshal(v) + if err != nil { + return err + } - // ScopeRef Scope reference. - ScopeRef *string `form:"scope_ref,omitempty" json:"scope_ref,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // Limit Maximum number of feed items to return. - Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` +// AsTypedTaggedEventStreamEnvelopeCityCreated returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeCityCreated +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeCityCreated() (TypedTaggedEventStreamEnvelopeCityCreated, error) { + var body TypedTaggedEventStreamEnvelopeCityCreated + err := json.Unmarshal(t.union, &body) + return body, err } -// GetV0CityByCityNameOrdersHistoryParams defines parameters for GetV0CityByCityNameOrdersHistory. -type GetV0CityByCityNameOrdersHistoryParams struct { - // ScopedName Scoped order name. - ScopedName string `form:"scoped_name" json:"scoped_name"` +// FromTypedTaggedEventStreamEnvelopeCityCreated overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeCityCreated +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeCityCreated(v TypedTaggedEventStreamEnvelopeCityCreated) error { + v.Type = "city.created" + b, err := json.Marshal(v) + t.union = b + return err +} - // Limit Maximum number of history entries. 0 = default. - Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` +// MergeTypedTaggedEventStreamEnvelopeCityCreated performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeCityCreated +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeCityCreated(v TypedTaggedEventStreamEnvelopeCityCreated) error { + v.Type = "city.created" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Before Return entries before this RFC3339 timestamp. - Before *string `form:"before,omitempty" json:"before,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTypedTaggedEventStreamEnvelopeCityInitFailed returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeCityInitFailed +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeCityInitFailed() (TypedTaggedEventStreamEnvelopeCityInitFailed, error) { + var body TypedTaggedEventStreamEnvelopeCityInitFailed + err := json.Unmarshal(t.union, &body) + return body, err } -// DeleteV0CityByCityNamePatchesAgentByBaseParams defines parameters for DeleteV0CityByCityNamePatchesAgentByBase. -type DeleteV0CityByCityNamePatchesAgentByBaseParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedTaggedEventStreamEnvelopeCityInitFailed overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeCityInitFailed +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeCityInitFailed(v TypedTaggedEventStreamEnvelopeCityInitFailed) error { + v.Type = "city.init_failed" + b, err := json.Marshal(v) + t.union = b + return err } -// DeleteV0CityByCityNamePatchesAgentByDirByBaseParams defines parameters for DeleteV0CityByCityNamePatchesAgentByDirByBase. -type DeleteV0CityByCityNamePatchesAgentByDirByBaseParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// MergeTypedTaggedEventStreamEnvelopeCityInitFailed performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeCityInitFailed +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeCityInitFailed(v TypedTaggedEventStreamEnvelopeCityInitFailed) error { + v.Type = "city.init_failed" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// PutV0CityByCityNamePatchesAgentsParams defines parameters for PutV0CityByCityNamePatchesAgents. -type PutV0CityByCityNamePatchesAgentsParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedTaggedEventStreamEnvelopeCityReady returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeCityReady +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeCityReady() (TypedTaggedEventStreamEnvelopeCityReady, error) { + var body TypedTaggedEventStreamEnvelopeCityReady + err := json.Unmarshal(t.union, &body) + return body, err } -// DeleteV0CityByCityNamePatchesProviderByNameParams defines parameters for DeleteV0CityByCityNamePatchesProviderByName. -type DeleteV0CityByCityNamePatchesProviderByNameParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedTaggedEventStreamEnvelopeCityReady overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeCityReady +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeCityReady(v TypedTaggedEventStreamEnvelopeCityReady) error { + v.Type = "city.ready" + b, err := json.Marshal(v) + t.union = b + return err } -// PutV0CityByCityNamePatchesProvidersParams defines parameters for PutV0CityByCityNamePatchesProviders. -type PutV0CityByCityNamePatchesProvidersParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// MergeTypedTaggedEventStreamEnvelopeCityReady performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeCityReady +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeCityReady(v TypedTaggedEventStreamEnvelopeCityReady) error { + v.Type = "city.ready" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// DeleteV0CityByCityNamePatchesRigByNameParams defines parameters for DeleteV0CityByCityNamePatchesRigByName. -type DeleteV0CityByCityNamePatchesRigByNameParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedTaggedEventStreamEnvelopeCityResumed returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeCityResumed +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeCityResumed() (TypedTaggedEventStreamEnvelopeCityResumed, error) { + var body TypedTaggedEventStreamEnvelopeCityResumed + err := json.Unmarshal(t.union, &body) + return body, err } -// PutV0CityByCityNamePatchesRigsParams defines parameters for PutV0CityByCityNamePatchesRigs. -type PutV0CityByCityNamePatchesRigsParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedTaggedEventStreamEnvelopeCityResumed overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeCityResumed +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeCityResumed(v TypedTaggedEventStreamEnvelopeCityResumed) error { + v.Type = "city.resumed" + b, err := json.Marshal(v) + t.union = b + return err } -// GetV0CityByCityNameProviderReadinessParams defines parameters for GetV0CityByCityNameProviderReadiness. -type GetV0CityByCityNameProviderReadinessParams struct { - // Providers Comma-separated provider names to check (default: claude,codex,gemini). - Providers *string `form:"providers,omitempty" json:"providers,omitempty"` +// MergeTypedTaggedEventStreamEnvelopeCityResumed performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeCityResumed +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeCityResumed(v TypedTaggedEventStreamEnvelopeCityResumed) error { + v.Type = "city.resumed" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Fresh Force fresh probe, bypassing cache. - Fresh *bool `form:"fresh,omitempty" json:"fresh,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// DeleteV0CityByCityNameProviderByNameParams defines parameters for DeleteV0CityByCityNameProviderByName. -type DeleteV0CityByCityNameProviderByNameParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedTaggedEventStreamEnvelopeCitySuspended returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeCitySuspended +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeCitySuspended() (TypedTaggedEventStreamEnvelopeCitySuspended, error) { + var body TypedTaggedEventStreamEnvelopeCitySuspended + err := json.Unmarshal(t.union, &body) + return body, err } -// PatchV0CityByCityNameProviderByNameParams defines parameters for PatchV0CityByCityNameProviderByName. -type PatchV0CityByCityNameProviderByNameParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedTaggedEventStreamEnvelopeCitySuspended overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeCitySuspended +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeCitySuspended(v TypedTaggedEventStreamEnvelopeCitySuspended) error { + v.Type = "city.suspended" + b, err := json.Marshal(v) + t.union = b + return err } -// CreateProviderParams defines parameters for CreateProvider. -type CreateProviderParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` -} +// MergeTypedTaggedEventStreamEnvelopeCitySuspended performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeCitySuspended +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeCitySuspended(v TypedTaggedEventStreamEnvelopeCitySuspended) error { + v.Type = "city.suspended" + b, err := json.Marshal(v) + if err != nil { + return err + } -// GetV0CityByCityNameReadinessParams defines parameters for GetV0CityByCityNameReadiness. -type GetV0CityByCityNameReadinessParams struct { - // Items Comma-separated readiness items to check (default: claude,codex,gemini,github_cli). - Items *string `form:"items,omitempty" json:"items,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // Fresh Force fresh probe, bypassing cache. - Fresh *bool `form:"fresh,omitempty" json:"fresh,omitempty"` +// AsTypedTaggedEventStreamEnvelopeCityUnregisterFailed returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeCityUnregisterFailed +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeCityUnregisterFailed() (TypedTaggedEventStreamEnvelopeCityUnregisterFailed, error) { + var body TypedTaggedEventStreamEnvelopeCityUnregisterFailed + err := json.Unmarshal(t.union, &body) + return body, err } -// DeleteV0CityByCityNameRigByNameParams defines parameters for DeleteV0CityByCityNameRigByName. -type DeleteV0CityByCityNameRigByNameParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedTaggedEventStreamEnvelopeCityUnregisterFailed overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeCityUnregisterFailed +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeCityUnregisterFailed(v TypedTaggedEventStreamEnvelopeCityUnregisterFailed) error { + v.Type = "city.unregister_failed" + b, err := json.Marshal(v) + t.union = b + return err } -// GetV0CityByCityNameRigByNameParams defines parameters for GetV0CityByCityNameRigByName. -type GetV0CityByCityNameRigByNameParams struct { - // Git Include git status. - Git *bool `form:"git,omitempty" json:"git,omitempty"` +// MergeTypedTaggedEventStreamEnvelopeCityUnregisterFailed performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeCityUnregisterFailed +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeCityUnregisterFailed(v TypedTaggedEventStreamEnvelopeCityUnregisterFailed) error { + v.Type = "city.unregister_failed" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// PatchV0CityByCityNameRigByNameParams defines parameters for PatchV0CityByCityNameRigByName. -type PatchV0CityByCityNameRigByNameParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedTaggedEventStreamEnvelopeCityUnregisterRequested returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeCityUnregisterRequested +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeCityUnregisterRequested() (TypedTaggedEventStreamEnvelopeCityUnregisterRequested, error) { + var body TypedTaggedEventStreamEnvelopeCityUnregisterRequested + err := json.Unmarshal(t.union, &body) + return body, err } -// PostV0CityByCityNameRigByNameByActionParams defines parameters for PostV0CityByCityNameRigByNameByAction. -type PostV0CityByCityNameRigByNameByActionParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedTaggedEventStreamEnvelopeCityUnregisterRequested overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeCityUnregisterRequested +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeCityUnregisterRequested(v TypedTaggedEventStreamEnvelopeCityUnregisterRequested) error { + v.Type = "city.unregister_requested" + b, err := json.Marshal(v) + t.union = b + return err } -// GetV0CityByCityNameRigsParams defines parameters for GetV0CityByCityNameRigs. -type GetV0CityByCityNameRigsParams struct { - // Index Event sequence number; when provided, blocks until a newer event arrives. - Index *string `form:"index,omitempty" json:"index,omitempty"` +// MergeTypedTaggedEventStreamEnvelopeCityUnregisterRequested performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeCityUnregisterRequested +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeCityUnregisterRequested(v TypedTaggedEventStreamEnvelopeCityUnregisterRequested) error { + v.Type = "city.unregister_requested" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Wait How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. - Wait *string `form:"wait,omitempty" json:"wait,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // Git Include git status. - Git *bool `form:"git,omitempty" json:"git,omitempty"` +// AsTypedTaggedEventStreamEnvelopeCityUnregistered returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeCityUnregistered +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeCityUnregistered() (TypedTaggedEventStreamEnvelopeCityUnregistered, error) { + var body TypedTaggedEventStreamEnvelopeCityUnregistered + err := json.Unmarshal(t.union, &body) + return body, err } -// CreateRigParams defines parameters for CreateRig. -type CreateRigParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedTaggedEventStreamEnvelopeCityUnregistered overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeCityUnregistered +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeCityUnregistered(v TypedTaggedEventStreamEnvelopeCityUnregistered) error { + v.Type = "city.unregistered" + b, err := json.Marshal(v) + t.union = b + return err } -// PostV0CityByCityNameServiceByNameRestartParams defines parameters for PostV0CityByCityNameServiceByNameRestart. -type PostV0CityByCityNameServiceByNameRestartParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// MergeTypedTaggedEventStreamEnvelopeCityUnregistered performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeCityUnregistered +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeCityUnregistered(v TypedTaggedEventStreamEnvelopeCityUnregistered) error { + v.Type = "city.unregistered" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// GetV0CityByCityNameSessionByIdParams defines parameters for GetV0CityByCityNameSessionById. -type GetV0CityByCityNameSessionByIdParams struct { - // Peek Include last output preview. - Peek *bool `form:"peek,omitempty" json:"peek,omitempty"` +// AsTypedTaggedEventStreamEnvelopeControllerStarted returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeControllerStarted +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeControllerStarted() (TypedTaggedEventStreamEnvelopeControllerStarted, error) { + var body TypedTaggedEventStreamEnvelopeControllerStarted + err := json.Unmarshal(t.union, &body) + return body, err } -// PatchV0CityByCityNameSessionByIdParams defines parameters for PatchV0CityByCityNameSessionById. -type PatchV0CityByCityNameSessionByIdParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedTaggedEventStreamEnvelopeControllerStarted overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeControllerStarted +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeControllerStarted(v TypedTaggedEventStreamEnvelopeControllerStarted) error { + v.Type = "controller.started" + b, err := json.Marshal(v) + t.union = b + return err } -// PostV0CityByCityNameSessionByIdCloseParams defines parameters for PostV0CityByCityNameSessionByIdClose. -type PostV0CityByCityNameSessionByIdCloseParams struct { - // Delete Permanently delete bead after closing. - Delete *bool `form:"delete,omitempty" json:"delete,omitempty"` +// MergeTypedTaggedEventStreamEnvelopeControllerStarted performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeControllerStarted +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeControllerStarted(v TypedTaggedEventStreamEnvelopeControllerStarted) error { + v.Type = "controller.started" + b, err := json.Marshal(v) + if err != nil { + return err + } - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// PostV0CityByCityNameSessionByIdKillParams defines parameters for PostV0CityByCityNameSessionByIdKill. -type PostV0CityByCityNameSessionByIdKillParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedTaggedEventStreamEnvelopeControllerStopped returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeControllerStopped +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeControllerStopped() (TypedTaggedEventStreamEnvelopeControllerStopped, error) { + var body TypedTaggedEventStreamEnvelopeControllerStopped + err := json.Unmarshal(t.union, &body) + return body, err } -// SendSessionMessageParams defines parameters for SendSessionMessage. -type SendSessionMessageParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedTaggedEventStreamEnvelopeControllerStopped overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeControllerStopped +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeControllerStopped(v TypedTaggedEventStreamEnvelopeControllerStopped) error { + v.Type = "controller.stopped" + b, err := json.Marshal(v) + t.union = b + return err } -// PostV0CityByCityNameSessionByIdRenameParams defines parameters for PostV0CityByCityNameSessionByIdRename. -type PostV0CityByCityNameSessionByIdRenameParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` -} +// MergeTypedTaggedEventStreamEnvelopeControllerStopped performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeControllerStopped +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeControllerStopped(v TypedTaggedEventStreamEnvelopeControllerStopped) error { + v.Type = "controller.stopped" + b, err := json.Marshal(v) + if err != nil { + return err + } -// RespondSessionParams defines parameters for RespondSession. -type RespondSessionParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// PostV0CityByCityNameSessionByIdStopParams defines parameters for PostV0CityByCityNameSessionByIdStop. -type PostV0CityByCityNameSessionByIdStopParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedTaggedEventStreamEnvelopeConvoyClosed returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeConvoyClosed +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeConvoyClosed() (TypedTaggedEventStreamEnvelopeConvoyClosed, error) { + var body TypedTaggedEventStreamEnvelopeConvoyClosed + err := json.Unmarshal(t.union, &body) + return body, err } -// StreamSessionParams defines parameters for StreamSession. -type StreamSessionParams struct { - // Format Transcript format: conversation (default) or raw. - Format *string `form:"format,omitempty" json:"format,omitempty"` +// FromTypedTaggedEventStreamEnvelopeConvoyClosed overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeConvoyClosed +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeConvoyClosed(v TypedTaggedEventStreamEnvelopeConvoyClosed) error { + v.Type = "convoy.closed" + b, err := json.Marshal(v) + t.union = b + return err } -// SubmitSessionParams defines parameters for SubmitSession. -type SubmitSessionParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// MergeTypedTaggedEventStreamEnvelopeConvoyClosed performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeConvoyClosed +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeConvoyClosed(v TypedTaggedEventStreamEnvelopeConvoyClosed) error { + v.Type = "convoy.closed" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// PostV0CityByCityNameSessionByIdSuspendParams defines parameters for PostV0CityByCityNameSessionByIdSuspend. -type PostV0CityByCityNameSessionByIdSuspendParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedTaggedEventStreamEnvelopeConvoyCreated returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeConvoyCreated +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeConvoyCreated() (TypedTaggedEventStreamEnvelopeConvoyCreated, error) { + var body TypedTaggedEventStreamEnvelopeConvoyCreated + err := json.Unmarshal(t.union, &body) + return body, err } -// GetV0CityByCityNameSessionByIdTranscriptParams defines parameters for GetV0CityByCityNameSessionByIdTranscript. -type GetV0CityByCityNameSessionByIdTranscriptParams struct { - // Tail Number of recent compaction segments to return. This API parameter keeps compaction-segment semantics even though gc session logs --tail counts displayed transcript entries. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. - Tail *string `form:"tail,omitempty" json:"tail,omitempty"` +// FromTypedTaggedEventStreamEnvelopeConvoyCreated overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeConvoyCreated +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeConvoyCreated(v TypedTaggedEventStreamEnvelopeConvoyCreated) error { + v.Type = "convoy.created" + b, err := json.Marshal(v) + t.union = b + return err +} - // Format Transcript format: conversation (default) or raw. - Format *string `form:"format,omitempty" json:"format,omitempty"` +// MergeTypedTaggedEventStreamEnvelopeConvoyCreated performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeConvoyCreated +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeConvoyCreated(v TypedTaggedEventStreamEnvelopeConvoyCreated) error { + v.Type = "convoy.created" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Before Pagination cursor: return entries before this UUID. - Before *string `form:"before,omitempty" json:"before,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// PostV0CityByCityNameSessionByIdWakeParams defines parameters for PostV0CityByCityNameSessionByIdWake. -type PostV0CityByCityNameSessionByIdWakeParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// AsTypedTaggedEventStreamEnvelopeExtmsgAdapterAdded returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeExtmsgAdapterAdded() (TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded, error) { + var body TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded + err := json.Unmarshal(t.union, &body) + return body, err } -// GetV0CityByCityNameSessionsParams defines parameters for GetV0CityByCityNameSessions. -type GetV0CityByCityNameSessionsParams struct { - // Cursor Pagination cursor from a previous response's next_cursor field. - Cursor *string `form:"cursor,omitempty" json:"cursor,omitempty"` - - // Limit Maximum number of results to return. 0 = server default. - Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` +// FromTypedTaggedEventStreamEnvelopeExtmsgAdapterAdded overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeExtmsgAdapterAdded(v TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded) error { + v.Type = "extmsg.adapter_added" + b, err := json.Marshal(v) + t.union = b + return err +} - // State Filter by session state (e.g. active, closed). - State *string `form:"state,omitempty" json:"state,omitempty"` +// MergeTypedTaggedEventStreamEnvelopeExtmsgAdapterAdded performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeExtmsgAdapterAdded(v TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded) error { + v.Type = "extmsg.adapter_added" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Template Filter by session template (agent qualified name). - Template *string `form:"template,omitempty" json:"template,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // Peek Include last output preview. - Peek *bool `form:"peek,omitempty" json:"peek,omitempty"` +// AsTypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved() (TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved, error) { + var body TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved + err := json.Unmarshal(t.union, &body) + return body, err } -// CreateSessionParams defines parameters for CreateSession. -type CreateSessionParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// FromTypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved(v TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved) error { + v.Type = "extmsg.adapter_removed" + b, err := json.Marshal(v) + t.union = b + return err } -// PostV0CityByCityNameSlingParams defines parameters for PostV0CityByCityNameSling. -type PostV0CityByCityNameSlingParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// MergeTypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved(v TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved) error { + v.Type = "extmsg.adapter_removed" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// GetV0CityByCityNameStatusParams defines parameters for GetV0CityByCityNameStatus. -type GetV0CityByCityNameStatusParams struct { - // Index Event sequence number; when provided, blocks until a newer event arrives. - Index *string `form:"index,omitempty" json:"index,omitempty"` +// AsTypedTaggedEventStreamEnvelopeExtmsgBound returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeExtmsgBound +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeExtmsgBound() (TypedTaggedEventStreamEnvelopeExtmsgBound, error) { + var body TypedTaggedEventStreamEnvelopeExtmsgBound + err := json.Unmarshal(t.union, &body) + return body, err +} - // Wait How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. - Wait *string `form:"wait,omitempty" json:"wait,omitempty"` +// FromTypedTaggedEventStreamEnvelopeExtmsgBound overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeExtmsgBound +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeExtmsgBound(v TypedTaggedEventStreamEnvelopeExtmsgBound) error { + v.Type = "extmsg.bound" + b, err := json.Marshal(v) + t.union = b + return err } -// PostV0CityByCityNameUnregisterParams defines parameters for PostV0CityByCityNameUnregister. -type PostV0CityByCityNameUnregisterParams struct { - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` +// MergeTypedTaggedEventStreamEnvelopeExtmsgBound performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeExtmsgBound +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeExtmsgBound(v TypedTaggedEventStreamEnvelopeExtmsgBound) error { + v.Type = "extmsg.bound" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// DeleteV0CityByCityNameWorkflowByWorkflowIdParams defines parameters for DeleteV0CityByCityNameWorkflowByWorkflowId. -type DeleteV0CityByCityNameWorkflowByWorkflowIdParams struct { - // ScopeKind Scope kind (city or rig). - ScopeKind *string `form:"scope_kind,omitempty" json:"scope_kind,omitempty"` +// AsTypedTaggedEventStreamEnvelopeExtmsgGroupCreated returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeExtmsgGroupCreated +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeExtmsgGroupCreated() (TypedTaggedEventStreamEnvelopeExtmsgGroupCreated, error) { + var body TypedTaggedEventStreamEnvelopeExtmsgGroupCreated + err := json.Unmarshal(t.union, &body) + return body, err +} - // ScopeRef Scope reference. - ScopeRef *string `form:"scope_ref,omitempty" json:"scope_ref,omitempty"` +// FromTypedTaggedEventStreamEnvelopeExtmsgGroupCreated overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeExtmsgGroupCreated +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeExtmsgGroupCreated(v TypedTaggedEventStreamEnvelopeExtmsgGroupCreated) error { + v.Type = "extmsg.group_created" + b, err := json.Marshal(v) + t.union = b + return err +} - // Delete Permanently delete beads from store. - Delete *bool `form:"delete,omitempty" json:"delete,omitempty"` +// MergeTypedTaggedEventStreamEnvelopeExtmsgGroupCreated performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeExtmsgGroupCreated +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeExtmsgGroupCreated(v TypedTaggedEventStreamEnvelopeExtmsgGroupCreated) error { + v.Type = "extmsg.group_created" + b, err := json.Marshal(v) + if err != nil { + return err + } - // XGCRequest Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. - XGCRequest string `json:"X-GC-Request"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// GetV0CityByCityNameWorkflowByWorkflowIdParams defines parameters for GetV0CityByCityNameWorkflowByWorkflowId. -type GetV0CityByCityNameWorkflowByWorkflowIdParams struct { - // ScopeKind Scope kind (city or rig). - ScopeKind *string `form:"scope_kind,omitempty" json:"scope_kind,omitempty"` +// AsTypedTaggedEventStreamEnvelopeExtmsgInbound returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeExtmsgInbound +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeExtmsgInbound() (TypedTaggedEventStreamEnvelopeExtmsgInbound, error) { + var body TypedTaggedEventStreamEnvelopeExtmsgInbound + err := json.Unmarshal(t.union, &body) + return body, err +} - // ScopeRef Scope reference. - ScopeRef *string `form:"scope_ref,omitempty" json:"scope_ref,omitempty"` +// FromTypedTaggedEventStreamEnvelopeExtmsgInbound overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeExtmsgInbound +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeExtmsgInbound(v TypedTaggedEventStreamEnvelopeExtmsgInbound) error { + v.Type = "extmsg.inbound" + b, err := json.Marshal(v) + t.union = b + return err } -// GetV0EventsParams defines parameters for GetV0Events. -type GetV0EventsParams struct { - // Type Filter by event type. - Type *string `form:"type,omitempty" json:"type,omitempty"` +// MergeTypedTaggedEventStreamEnvelopeExtmsgInbound performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeExtmsgInbound +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeExtmsgInbound(v TypedTaggedEventStreamEnvelopeExtmsgInbound) error { + v.Type = "extmsg.inbound" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Actor Filter by actor. - Actor *string `form:"actor,omitempty" json:"actor,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // Since Filter to events within the last Go duration (e.g. "5m"). - Since *string `form:"since,omitempty" json:"since,omitempty"` +// AsTypedTaggedEventStreamEnvelopeExtmsgOutbound returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeExtmsgOutbound +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeExtmsgOutbound() (TypedTaggedEventStreamEnvelopeExtmsgOutbound, error) { + var body TypedTaggedEventStreamEnvelopeExtmsgOutbound + err := json.Unmarshal(t.union, &body) + return body, err +} - // Limit Maximum number of trailing events to return. 0 = no limit. Used by 'gc events --seq' to compute the head cursor cheaply. - Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"` +// FromTypedTaggedEventStreamEnvelopeExtmsgOutbound overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeExtmsgOutbound +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeExtmsgOutbound(v TypedTaggedEventStreamEnvelopeExtmsgOutbound) error { + v.Type = "extmsg.outbound" + b, err := json.Marshal(v) + t.union = b + return err } -// StreamSupervisorEventsParams defines parameters for StreamSupervisorEvents. -type StreamSupervisorEventsParams struct { - // AfterCursor Alternative to Last-Event-ID for browsers that can't set custom headers. - AfterCursor *string `form:"after_cursor,omitempty" json:"after_cursor,omitempty"` +// MergeTypedTaggedEventStreamEnvelopeExtmsgOutbound performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeExtmsgOutbound +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeExtmsgOutbound(v TypedTaggedEventStreamEnvelopeExtmsgOutbound) error { + v.Type = "extmsg.outbound" + b, err := json.Marshal(v) + if err != nil { + return err + } - // LastEventID Reconnect cursor (composite per-city cursor). - LastEventID *string `json:"Last-Event-ID,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// GetV0ProviderReadinessParams defines parameters for GetV0ProviderReadiness. -type GetV0ProviderReadinessParams struct { - // Providers Comma-separated list of providers to probe. - Providers *string `form:"providers,omitempty" json:"providers,omitempty"` +// AsTypedTaggedEventStreamEnvelopeExtmsgUnbound returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeExtmsgUnbound +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeExtmsgUnbound() (TypedTaggedEventStreamEnvelopeExtmsgUnbound, error) { + var body TypedTaggedEventStreamEnvelopeExtmsgUnbound + err := json.Unmarshal(t.union, &body) + return body, err +} - // Fresh Force fresh probe, bypassing cache. - Fresh *bool `form:"fresh,omitempty" json:"fresh,omitempty"` +// FromTypedTaggedEventStreamEnvelopeExtmsgUnbound overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeExtmsgUnbound +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeExtmsgUnbound(v TypedTaggedEventStreamEnvelopeExtmsgUnbound) error { + v.Type = "extmsg.unbound" + b, err := json.Marshal(v) + t.union = b + return err } -// GetV0ReadinessParams defines parameters for GetV0Readiness. -type GetV0ReadinessParams struct { - // Items Comma-separated list of readiness items to check. - Items *string `form:"items,omitempty" json:"items,omitempty"` +// MergeTypedTaggedEventStreamEnvelopeExtmsgUnbound performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeExtmsgUnbound +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeExtmsgUnbound(v TypedTaggedEventStreamEnvelopeExtmsgUnbound) error { + v.Type = "extmsg.unbound" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Fresh Force fresh probe, bypassing cache. - Fresh *bool `form:"fresh,omitempty" json:"fresh,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// PostV0CityJSONRequestBody defines body for PostV0City for application/json ContentType. -type PostV0CityJSONRequestBody = CityCreateRequest +// AsTypedTaggedEventStreamEnvelopeMailArchived returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeMailArchived +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeMailArchived() (TypedTaggedEventStreamEnvelopeMailArchived, error) { + var body TypedTaggedEventStreamEnvelopeMailArchived + err := json.Unmarshal(t.union, &body) + return body, err +} -// PatchV0CityByCityNameJSONRequestBody defines body for PatchV0CityByCityName for application/json ContentType. -type PatchV0CityByCityNameJSONRequestBody = CityPatchInputBody +// FromTypedTaggedEventStreamEnvelopeMailArchived overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeMailArchived +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeMailArchived(v TypedTaggedEventStreamEnvelopeMailArchived) error { + v.Type = "mail.archived" + b, err := json.Marshal(v) + t.union = b + return err +} -// PatchV0CityByCityNameAgentByBaseJSONRequestBody defines body for PatchV0CityByCityNameAgentByBase for application/json ContentType. -type PatchV0CityByCityNameAgentByBaseJSONRequestBody = AgentUpdateInputBody +// MergeTypedTaggedEventStreamEnvelopeMailArchived performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeMailArchived +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeMailArchived(v TypedTaggedEventStreamEnvelopeMailArchived) error { + v.Type = "mail.archived" + b, err := json.Marshal(v) + if err != nil { + return err + } -// PatchV0CityByCityNameAgentByDirByBaseJSONRequestBody defines body for PatchV0CityByCityNameAgentByDirByBase for application/json ContentType. -type PatchV0CityByCityNameAgentByDirByBaseJSONRequestBody = AgentUpdateQualifiedInputBody + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// CreateAgentJSONRequestBody defines body for CreateAgent for application/json ContentType. -type CreateAgentJSONRequestBody = AgentCreateInputBody +// AsTypedTaggedEventStreamEnvelopeMailDeleted returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeMailDeleted +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeMailDeleted() (TypedTaggedEventStreamEnvelopeMailDeleted, error) { + var body TypedTaggedEventStreamEnvelopeMailDeleted + err := json.Unmarshal(t.union, &body) + return body, err +} -// PatchV0CityByCityNameBeadByIdJSONRequestBody defines body for PatchV0CityByCityNameBeadById for application/json ContentType. -type PatchV0CityByCityNameBeadByIdJSONRequestBody = BeadUpdateBody +// FromTypedTaggedEventStreamEnvelopeMailDeleted overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeMailDeleted +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeMailDeleted(v TypedTaggedEventStreamEnvelopeMailDeleted) error { + v.Type = "mail.deleted" + b, err := json.Marshal(v) + t.union = b + return err +} -// PostV0CityByCityNameBeadByIdAssignJSONRequestBody defines body for PostV0CityByCityNameBeadByIdAssign for application/json ContentType. -type PostV0CityByCityNameBeadByIdAssignJSONRequestBody = BeadAssignInputBody +// MergeTypedTaggedEventStreamEnvelopeMailDeleted performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeMailDeleted +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeMailDeleted(v TypedTaggedEventStreamEnvelopeMailDeleted) error { + v.Type = "mail.deleted" + b, err := json.Marshal(v) + if err != nil { + return err + } -// PostV0CityByCityNameBeadByIdUpdateJSONRequestBody defines body for PostV0CityByCityNameBeadByIdUpdate for application/json ContentType. -type PostV0CityByCityNameBeadByIdUpdateJSONRequestBody = BeadUpdateBody + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// CreateBeadJSONRequestBody defines body for CreateBead for application/json ContentType. -type CreateBeadJSONRequestBody = BeadCreateInputBody +// AsTypedTaggedEventStreamEnvelopeMailMarkedRead returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeMailMarkedRead +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeMailMarkedRead() (TypedTaggedEventStreamEnvelopeMailMarkedRead, error) { + var body TypedTaggedEventStreamEnvelopeMailMarkedRead + err := json.Unmarshal(t.union, &body) + return body, err +} -// PostV0CityByCityNameConvoyByIdAddJSONRequestBody defines body for PostV0CityByCityNameConvoyByIdAdd for application/json ContentType. -type PostV0CityByCityNameConvoyByIdAddJSONRequestBody = ConvoyAddInputBody +// FromTypedTaggedEventStreamEnvelopeMailMarkedRead overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeMailMarkedRead +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeMailMarkedRead(v TypedTaggedEventStreamEnvelopeMailMarkedRead) error { + v.Type = "mail.marked_read" + b, err := json.Marshal(v) + t.union = b + return err +} -// PostV0CityByCityNameConvoyByIdRemoveJSONRequestBody defines body for PostV0CityByCityNameConvoyByIdRemove for application/json ContentType. -type PostV0CityByCityNameConvoyByIdRemoveJSONRequestBody = ConvoyRemoveInputBody +// MergeTypedTaggedEventStreamEnvelopeMailMarkedRead performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeMailMarkedRead +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeMailMarkedRead(v TypedTaggedEventStreamEnvelopeMailMarkedRead) error { + v.Type = "mail.marked_read" + b, err := json.Marshal(v) + if err != nil { + return err + } -// CreateConvoyJSONRequestBody defines body for CreateConvoy for application/json ContentType. -type CreateConvoyJSONRequestBody = ConvoyCreateInputBody + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// EmitEventJSONRequestBody defines body for EmitEvent for application/json ContentType. -type EmitEventJSONRequestBody = EventEmitRequest +// AsTypedTaggedEventStreamEnvelopeMailMarkedUnread returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeMailMarkedUnread +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeMailMarkedUnread() (TypedTaggedEventStreamEnvelopeMailMarkedUnread, error) { + var body TypedTaggedEventStreamEnvelopeMailMarkedUnread + err := json.Unmarshal(t.union, &body) + return body, err +} -// DeleteV0CityByCityNameExtmsgAdaptersJSONRequestBody defines body for DeleteV0CityByCityNameExtmsgAdapters for application/json ContentType. -type DeleteV0CityByCityNameExtmsgAdaptersJSONRequestBody = ExtMsgAdapterUnregisterInputBody +// FromTypedTaggedEventStreamEnvelopeMailMarkedUnread overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeMailMarkedUnread +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeMailMarkedUnread(v TypedTaggedEventStreamEnvelopeMailMarkedUnread) error { + v.Type = "mail.marked_unread" + b, err := json.Marshal(v) + t.union = b + return err +} -// RegisterExtmsgAdapterJSONRequestBody defines body for RegisterExtmsgAdapter for application/json ContentType. -type RegisterExtmsgAdapterJSONRequestBody = ExtMsgAdapterRegisterInputBody +// MergeTypedTaggedEventStreamEnvelopeMailMarkedUnread performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeMailMarkedUnread +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeMailMarkedUnread(v TypedTaggedEventStreamEnvelopeMailMarkedUnread) error { + v.Type = "mail.marked_unread" + b, err := json.Marshal(v) + if err != nil { + return err + } -// PostV0CityByCityNameExtmsgBindJSONRequestBody defines body for PostV0CityByCityNameExtmsgBind for application/json ContentType. -type PostV0CityByCityNameExtmsgBindJSONRequestBody = ExtMsgBindInputBody + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// EnsureExtmsgGroupJSONRequestBody defines body for EnsureExtmsgGroup for application/json ContentType. -type EnsureExtmsgGroupJSONRequestBody = ExtMsgGroupEnsureInputBody +// AsTypedTaggedEventStreamEnvelopeMailRead returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeMailRead +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeMailRead() (TypedTaggedEventStreamEnvelopeMailRead, error) { + var body TypedTaggedEventStreamEnvelopeMailRead + err := json.Unmarshal(t.union, &body) + return body, err +} -// PostV0CityByCityNameExtmsgInboundJSONRequestBody defines body for PostV0CityByCityNameExtmsgInbound for application/json ContentType. -type PostV0CityByCityNameExtmsgInboundJSONRequestBody = ExtMsgInboundInputBody +// FromTypedTaggedEventStreamEnvelopeMailRead overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeMailRead +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeMailRead(v TypedTaggedEventStreamEnvelopeMailRead) error { + v.Type = "mail.read" + b, err := json.Marshal(v) + t.union = b + return err +} -// PostV0CityByCityNameExtmsgOutboundJSONRequestBody defines body for PostV0CityByCityNameExtmsgOutbound for application/json ContentType. -type PostV0CityByCityNameExtmsgOutboundJSONRequestBody = ExtMsgOutboundInputBody +// MergeTypedTaggedEventStreamEnvelopeMailRead performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeMailRead +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeMailRead(v TypedTaggedEventStreamEnvelopeMailRead) error { + v.Type = "mail.read" + b, err := json.Marshal(v) + if err != nil { + return err + } -// DeleteV0CityByCityNameExtmsgParticipantsJSONRequestBody defines body for DeleteV0CityByCityNameExtmsgParticipants for application/json ContentType. -type DeleteV0CityByCityNameExtmsgParticipantsJSONRequestBody = ExtMsgParticipantRemoveInputBody + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// PostV0CityByCityNameExtmsgParticipantsJSONRequestBody defines body for PostV0CityByCityNameExtmsgParticipants for application/json ContentType. -type PostV0CityByCityNameExtmsgParticipantsJSONRequestBody = ExtMsgParticipantUpsertInputBody +// AsTypedTaggedEventStreamEnvelopeMailReplied returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeMailReplied +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeMailReplied() (TypedTaggedEventStreamEnvelopeMailReplied, error) { + var body TypedTaggedEventStreamEnvelopeMailReplied + err := json.Unmarshal(t.union, &body) + return body, err +} -// PostV0CityByCityNameExtmsgTranscriptAckJSONRequestBody defines body for PostV0CityByCityNameExtmsgTranscriptAck for application/json ContentType. -type PostV0CityByCityNameExtmsgTranscriptAckJSONRequestBody = ExtMsgTranscriptAckInputBody +// FromTypedTaggedEventStreamEnvelopeMailReplied overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeMailReplied +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeMailReplied(v TypedTaggedEventStreamEnvelopeMailReplied) error { + v.Type = "mail.replied" + b, err := json.Marshal(v) + t.union = b + return err +} -// PostV0CityByCityNameExtmsgUnbindJSONRequestBody defines body for PostV0CityByCityNameExtmsgUnbind for application/json ContentType. -type PostV0CityByCityNameExtmsgUnbindJSONRequestBody = ExtMsgUnbindInputBody +// MergeTypedTaggedEventStreamEnvelopeMailReplied performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeMailReplied +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeMailReplied(v TypedTaggedEventStreamEnvelopeMailReplied) error { + v.Type = "mail.replied" + b, err := json.Marshal(v) + if err != nil { + return err + } -// PostV0CityByCityNameFormulasByNamePreviewJSONRequestBody defines body for PostV0CityByCityNameFormulasByNamePreview for application/json ContentType. -type PostV0CityByCityNameFormulasByNamePreviewJSONRequestBody = FormulaPreviewBody + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// SendMailJSONRequestBody defines body for SendMail for application/json ContentType. -type SendMailJSONRequestBody = MailSendInputBody +// AsTypedTaggedEventStreamEnvelopeMailSent returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeMailSent +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeMailSent() (TypedTaggedEventStreamEnvelopeMailSent, error) { + var body TypedTaggedEventStreamEnvelopeMailSent + err := json.Unmarshal(t.union, &body) + return body, err +} -// ReplyMailJSONRequestBody defines body for ReplyMail for application/json ContentType. -type ReplyMailJSONRequestBody = MailReplyInputBody +// FromTypedTaggedEventStreamEnvelopeMailSent overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeMailSent +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeMailSent(v TypedTaggedEventStreamEnvelopeMailSent) error { + v.Type = "mail.sent" + b, err := json.Marshal(v) + t.union = b + return err +} -// PutV0CityByCityNamePatchesAgentsJSONRequestBody defines body for PutV0CityByCityNamePatchesAgents for application/json ContentType. -type PutV0CityByCityNamePatchesAgentsJSONRequestBody = AgentPatchSetInputBody +// MergeTypedTaggedEventStreamEnvelopeMailSent performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeMailSent +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeMailSent(v TypedTaggedEventStreamEnvelopeMailSent) error { + v.Type = "mail.sent" + b, err := json.Marshal(v) + if err != nil { + return err + } -// PutV0CityByCityNamePatchesProvidersJSONRequestBody defines body for PutV0CityByCityNamePatchesProviders for application/json ContentType. -type PutV0CityByCityNamePatchesProvidersJSONRequestBody = ProviderPatchSetInputBody + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// PutV0CityByCityNamePatchesRigsJSONRequestBody defines body for PutV0CityByCityNamePatchesRigs for application/json ContentType. -type PutV0CityByCityNamePatchesRigsJSONRequestBody = RigPatchSetInputBody +// AsTypedTaggedEventStreamEnvelopeOrderCompleted returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeOrderCompleted +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeOrderCompleted() (TypedTaggedEventStreamEnvelopeOrderCompleted, error) { + var body TypedTaggedEventStreamEnvelopeOrderCompleted + err := json.Unmarshal(t.union, &body) + return body, err +} -// PatchV0CityByCityNameProviderByNameJSONRequestBody defines body for PatchV0CityByCityNameProviderByName for application/json ContentType. -type PatchV0CityByCityNameProviderByNameJSONRequestBody = ProviderUpdateInputBody +// FromTypedTaggedEventStreamEnvelopeOrderCompleted overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeOrderCompleted +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeOrderCompleted(v TypedTaggedEventStreamEnvelopeOrderCompleted) error { + v.Type = "order.completed" + b, err := json.Marshal(v) + t.union = b + return err +} -// CreateProviderJSONRequestBody defines body for CreateProvider for application/json ContentType. -type CreateProviderJSONRequestBody = ProviderCreateInputBody +// MergeTypedTaggedEventStreamEnvelopeOrderCompleted performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeOrderCompleted +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeOrderCompleted(v TypedTaggedEventStreamEnvelopeOrderCompleted) error { + v.Type = "order.completed" + b, err := json.Marshal(v) + if err != nil { + return err + } -// PatchV0CityByCityNameRigByNameJSONRequestBody defines body for PatchV0CityByCityNameRigByName for application/json ContentType. -type PatchV0CityByCityNameRigByNameJSONRequestBody = RigUpdateInputBody + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// CreateRigJSONRequestBody defines body for CreateRig for application/json ContentType. -type CreateRigJSONRequestBody = RigCreateInputBody +// AsTypedTaggedEventStreamEnvelopeOrderFailed returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeOrderFailed +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeOrderFailed() (TypedTaggedEventStreamEnvelopeOrderFailed, error) { + var body TypedTaggedEventStreamEnvelopeOrderFailed + err := json.Unmarshal(t.union, &body) + return body, err +} -// PatchV0CityByCityNameSessionByIdJSONRequestBody defines body for PatchV0CityByCityNameSessionById for application/json ContentType. -type PatchV0CityByCityNameSessionByIdJSONRequestBody = SessionPatchBody +// FromTypedTaggedEventStreamEnvelopeOrderFailed overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeOrderFailed +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeOrderFailed(v TypedTaggedEventStreamEnvelopeOrderFailed) error { + v.Type = "order.failed" + b, err := json.Marshal(v) + t.union = b + return err +} -// SendSessionMessageJSONRequestBody defines body for SendSessionMessage for application/json ContentType. -type SendSessionMessageJSONRequestBody = SessionMessageInputBody +// MergeTypedTaggedEventStreamEnvelopeOrderFailed performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeOrderFailed +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeOrderFailed(v TypedTaggedEventStreamEnvelopeOrderFailed) error { + v.Type = "order.failed" + b, err := json.Marshal(v) + if err != nil { + return err + } -// PostV0CityByCityNameSessionByIdRenameJSONRequestBody defines body for PostV0CityByCityNameSessionByIdRename for application/json ContentType. -type PostV0CityByCityNameSessionByIdRenameJSONRequestBody = SessionRenameInputBody + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// RespondSessionJSONRequestBody defines body for RespondSession for application/json ContentType. -type RespondSessionJSONRequestBody = SessionRespondInputBody +// AsTypedTaggedEventStreamEnvelopeOrderFired returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeOrderFired +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeOrderFired() (TypedTaggedEventStreamEnvelopeOrderFired, error) { + var body TypedTaggedEventStreamEnvelopeOrderFired + err := json.Unmarshal(t.union, &body) + return body, err +} -// SubmitSessionJSONRequestBody defines body for SubmitSession for application/json ContentType. -type SubmitSessionJSONRequestBody = SessionSubmitInputBody +// FromTypedTaggedEventStreamEnvelopeOrderFired overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeOrderFired +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeOrderFired(v TypedTaggedEventStreamEnvelopeOrderFired) error { + v.Type = "order.fired" + b, err := json.Marshal(v) + t.union = b + return err +} -// CreateSessionJSONRequestBody defines body for CreateSession for application/json ContentType. -type CreateSessionJSONRequestBody = SessionCreateBody +// MergeTypedTaggedEventStreamEnvelopeOrderFired performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeOrderFired +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeOrderFired(v TypedTaggedEventStreamEnvelopeOrderFired) error { + v.Type = "order.fired" + b, err := json.Marshal(v) + if err != nil { + return err + } -// PostV0CityByCityNameSlingJSONRequestBody defines body for PostV0CityByCityNameSling for application/json ContentType. -type PostV0CityByCityNameSlingJSONRequestBody = SlingInputBody + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// AsAdapterEventPayload returns the union data inside the EventPayload as a AdapterEventPayload -func (t EventPayload) AsAdapterEventPayload() (AdapterEventPayload, error) { - var body AdapterEventPayload +// AsTypedTaggedEventStreamEnvelopeProviderSwapped returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeProviderSwapped +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeProviderSwapped() (TypedTaggedEventStreamEnvelopeProviderSwapped, error) { + var body TypedTaggedEventStreamEnvelopeProviderSwapped err := json.Unmarshal(t.union, &body) return body, err } -// FromAdapterEventPayload overwrites any union data inside the EventPayload as the provided AdapterEventPayload -func (t *EventPayload) FromAdapterEventPayload(v AdapterEventPayload) error { +// FromTypedTaggedEventStreamEnvelopeProviderSwapped overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeProviderSwapped +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeProviderSwapped(v TypedTaggedEventStreamEnvelopeProviderSwapped) error { + v.Type = "provider.swapped" b, err := json.Marshal(v) t.union = b return err } -// MergeAdapterEventPayload performs a merge with any union data inside the EventPayload, using the provided AdapterEventPayload -func (t *EventPayload) MergeAdapterEventPayload(v AdapterEventPayload) error { +// MergeTypedTaggedEventStreamEnvelopeProviderSwapped performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeProviderSwapped +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeProviderSwapped(v TypedTaggedEventStreamEnvelopeProviderSwapped) error { + v.Type = "provider.swapped" b, err := json.Marshal(v) if err != nil { return err @@ -3704,22 +7396,24 @@ func (t *EventPayload) MergeAdapterEventPayload(v AdapterEventPayload) error { return err } -// AsBeadEventPayload returns the union data inside the EventPayload as a BeadEventPayload -func (t EventPayload) AsBeadEventPayload() (BeadEventPayload, error) { - var body BeadEventPayload +// AsTypedTaggedEventStreamEnvelopeSessionCrashed returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeSessionCrashed +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeSessionCrashed() (TypedTaggedEventStreamEnvelopeSessionCrashed, error) { + var body TypedTaggedEventStreamEnvelopeSessionCrashed err := json.Unmarshal(t.union, &body) return body, err } -// FromBeadEventPayload overwrites any union data inside the EventPayload as the provided BeadEventPayload -func (t *EventPayload) FromBeadEventPayload(v BeadEventPayload) error { +// FromTypedTaggedEventStreamEnvelopeSessionCrashed overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeSessionCrashed +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeSessionCrashed(v TypedTaggedEventStreamEnvelopeSessionCrashed) error { + v.Type = "session.crashed" b, err := json.Marshal(v) t.union = b return err } -// MergeBeadEventPayload performs a merge with any union data inside the EventPayload, using the provided BeadEventPayload -func (t *EventPayload) MergeBeadEventPayload(v BeadEventPayload) error { +// MergeTypedTaggedEventStreamEnvelopeSessionCrashed performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeSessionCrashed +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeSessionCrashed(v TypedTaggedEventStreamEnvelopeSessionCrashed) error { + v.Type = "session.crashed" b, err := json.Marshal(v) if err != nil { return err @@ -3730,22 +7424,24 @@ func (t *EventPayload) MergeBeadEventPayload(v BeadEventPayload) error { return err } -// AsBoundEventPayload returns the union data inside the EventPayload as a BoundEventPayload -func (t EventPayload) AsBoundEventPayload() (BoundEventPayload, error) { - var body BoundEventPayload +// AsTypedTaggedEventStreamEnvelopeSessionDraining returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeSessionDraining +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeSessionDraining() (TypedTaggedEventStreamEnvelopeSessionDraining, error) { + var body TypedTaggedEventStreamEnvelopeSessionDraining err := json.Unmarshal(t.union, &body) return body, err } -// FromBoundEventPayload overwrites any union data inside the EventPayload as the provided BoundEventPayload -func (t *EventPayload) FromBoundEventPayload(v BoundEventPayload) error { +// FromTypedTaggedEventStreamEnvelopeSessionDraining overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeSessionDraining +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeSessionDraining(v TypedTaggedEventStreamEnvelopeSessionDraining) error { + v.Type = "session.draining" b, err := json.Marshal(v) t.union = b return err } -// MergeBoundEventPayload performs a merge with any union data inside the EventPayload, using the provided BoundEventPayload -func (t *EventPayload) MergeBoundEventPayload(v BoundEventPayload) error { +// MergeTypedTaggedEventStreamEnvelopeSessionDraining performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeSessionDraining +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeSessionDraining(v TypedTaggedEventStreamEnvelopeSessionDraining) error { + v.Type = "session.draining" b, err := json.Marshal(v) if err != nil { return err @@ -3756,22 +7452,24 @@ func (t *EventPayload) MergeBoundEventPayload(v BoundEventPayload) error { return err } -// AsCityLifecyclePayload returns the union data inside the EventPayload as a CityLifecyclePayload -func (t EventPayload) AsCityLifecyclePayload() (CityLifecyclePayload, error) { - var body CityLifecyclePayload +// AsTypedTaggedEventStreamEnvelopeSessionIdleKilled returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeSessionIdleKilled +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeSessionIdleKilled() (TypedTaggedEventStreamEnvelopeSessionIdleKilled, error) { + var body TypedTaggedEventStreamEnvelopeSessionIdleKilled err := json.Unmarshal(t.union, &body) return body, err } -// FromCityLifecyclePayload overwrites any union data inside the EventPayload as the provided CityLifecyclePayload -func (t *EventPayload) FromCityLifecyclePayload(v CityLifecyclePayload) error { +// FromTypedTaggedEventStreamEnvelopeSessionIdleKilled overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeSessionIdleKilled +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeSessionIdleKilled(v TypedTaggedEventStreamEnvelopeSessionIdleKilled) error { + v.Type = "session.idle_killed" b, err := json.Marshal(v) t.union = b return err } -// MergeCityLifecyclePayload performs a merge with any union data inside the EventPayload, using the provided CityLifecyclePayload -func (t *EventPayload) MergeCityLifecyclePayload(v CityLifecyclePayload) error { +// MergeTypedTaggedEventStreamEnvelopeSessionIdleKilled performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeSessionIdleKilled +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeSessionIdleKilled(v TypedTaggedEventStreamEnvelopeSessionIdleKilled) error { + v.Type = "session.idle_killed" b, err := json.Marshal(v) if err != nil { return err @@ -3782,22 +7480,24 @@ func (t *EventPayload) MergeCityLifecyclePayload(v CityLifecyclePayload) error { return err } -// AsGroupCreatedEventPayload returns the union data inside the EventPayload as a GroupCreatedEventPayload -func (t EventPayload) AsGroupCreatedEventPayload() (GroupCreatedEventPayload, error) { - var body GroupCreatedEventPayload +// AsTypedTaggedEventStreamEnvelopeSessionQuarantined returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeSessionQuarantined +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeSessionQuarantined() (TypedTaggedEventStreamEnvelopeSessionQuarantined, error) { + var body TypedTaggedEventStreamEnvelopeSessionQuarantined err := json.Unmarshal(t.union, &body) return body, err } -// FromGroupCreatedEventPayload overwrites any union data inside the EventPayload as the provided GroupCreatedEventPayload -func (t *EventPayload) FromGroupCreatedEventPayload(v GroupCreatedEventPayload) error { +// FromTypedTaggedEventStreamEnvelopeSessionQuarantined overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeSessionQuarantined +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeSessionQuarantined(v TypedTaggedEventStreamEnvelopeSessionQuarantined) error { + v.Type = "session.quarantined" b, err := json.Marshal(v) t.union = b return err } -// MergeGroupCreatedEventPayload performs a merge with any union data inside the EventPayload, using the provided GroupCreatedEventPayload -func (t *EventPayload) MergeGroupCreatedEventPayload(v GroupCreatedEventPayload) error { +// MergeTypedTaggedEventStreamEnvelopeSessionQuarantined performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeSessionQuarantined +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeSessionQuarantined(v TypedTaggedEventStreamEnvelopeSessionQuarantined) error { + v.Type = "session.quarantined" b, err := json.Marshal(v) if err != nil { return err @@ -3808,22 +7508,24 @@ func (t *EventPayload) MergeGroupCreatedEventPayload(v GroupCreatedEventPayload) return err } -// AsInboundEventPayload returns the union data inside the EventPayload as a InboundEventPayload -func (t EventPayload) AsInboundEventPayload() (InboundEventPayload, error) { - var body InboundEventPayload +// AsTypedTaggedEventStreamEnvelopeSessionStopped returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeSessionStopped +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeSessionStopped() (TypedTaggedEventStreamEnvelopeSessionStopped, error) { + var body TypedTaggedEventStreamEnvelopeSessionStopped err := json.Unmarshal(t.union, &body) return body, err } -// FromInboundEventPayload overwrites any union data inside the EventPayload as the provided InboundEventPayload -func (t *EventPayload) FromInboundEventPayload(v InboundEventPayload) error { +// FromTypedTaggedEventStreamEnvelopeSessionStopped overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeSessionStopped +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeSessionStopped(v TypedTaggedEventStreamEnvelopeSessionStopped) error { + v.Type = "session.stopped" b, err := json.Marshal(v) t.union = b return err } -// MergeInboundEventPayload performs a merge with any union data inside the EventPayload, using the provided InboundEventPayload -func (t *EventPayload) MergeInboundEventPayload(v InboundEventPayload) error { +// MergeTypedTaggedEventStreamEnvelopeSessionStopped performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeSessionStopped +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeSessionStopped(v TypedTaggedEventStreamEnvelopeSessionStopped) error { + v.Type = "session.stopped" b, err := json.Marshal(v) if err != nil { return err @@ -3834,22 +7536,24 @@ func (t *EventPayload) MergeInboundEventPayload(v InboundEventPayload) error { return err } -// AsMailEventPayload returns the union data inside the EventPayload as a MailEventPayload -func (t EventPayload) AsMailEventPayload() (MailEventPayload, error) { - var body MailEventPayload +// AsTypedTaggedEventStreamEnvelopeSessionSuspended returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeSessionSuspended +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeSessionSuspended() (TypedTaggedEventStreamEnvelopeSessionSuspended, error) { + var body TypedTaggedEventStreamEnvelopeSessionSuspended err := json.Unmarshal(t.union, &body) return body, err } -// FromMailEventPayload overwrites any union data inside the EventPayload as the provided MailEventPayload -func (t *EventPayload) FromMailEventPayload(v MailEventPayload) error { +// FromTypedTaggedEventStreamEnvelopeSessionSuspended overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeSessionSuspended +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeSessionSuspended(v TypedTaggedEventStreamEnvelopeSessionSuspended) error { + v.Type = "session.suspended" b, err := json.Marshal(v) t.union = b return err } -// MergeMailEventPayload performs a merge with any union data inside the EventPayload, using the provided MailEventPayload -func (t *EventPayload) MergeMailEventPayload(v MailEventPayload) error { +// MergeTypedTaggedEventStreamEnvelopeSessionSuspended performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeSessionSuspended +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeSessionSuspended(v TypedTaggedEventStreamEnvelopeSessionSuspended) error { + v.Type = "session.suspended" b, err := json.Marshal(v) if err != nil { return err @@ -3860,22 +7564,24 @@ func (t *EventPayload) MergeMailEventPayload(v MailEventPayload) error { return err } -// AsNoPayload returns the union data inside the EventPayload as a NoPayload -func (t EventPayload) AsNoPayload() (NoPayload, error) { - var body NoPayload +// AsTypedTaggedEventStreamEnvelopeSessionUndrained returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeSessionUndrained +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeSessionUndrained() (TypedTaggedEventStreamEnvelopeSessionUndrained, error) { + var body TypedTaggedEventStreamEnvelopeSessionUndrained err := json.Unmarshal(t.union, &body) return body, err } -// FromNoPayload overwrites any union data inside the EventPayload as the provided NoPayload -func (t *EventPayload) FromNoPayload(v NoPayload) error { +// FromTypedTaggedEventStreamEnvelopeSessionUndrained overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeSessionUndrained +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeSessionUndrained(v TypedTaggedEventStreamEnvelopeSessionUndrained) error { + v.Type = "session.undrained" b, err := json.Marshal(v) t.union = b return err } -// MergeNoPayload performs a merge with any union data inside the EventPayload, using the provided NoPayload -func (t *EventPayload) MergeNoPayload(v NoPayload) error { +// MergeTypedTaggedEventStreamEnvelopeSessionUndrained performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeSessionUndrained +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeSessionUndrained(v TypedTaggedEventStreamEnvelopeSessionUndrained) error { + v.Type = "session.undrained" b, err := json.Marshal(v) if err != nil { return err @@ -3886,22 +7592,24 @@ func (t *EventPayload) MergeNoPayload(v NoPayload) error { return err } -// AsOutboundEventPayload returns the union data inside the EventPayload as a OutboundEventPayload -func (t EventPayload) AsOutboundEventPayload() (OutboundEventPayload, error) { - var body OutboundEventPayload +// AsTypedTaggedEventStreamEnvelopeSessionUpdated returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeSessionUpdated +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeSessionUpdated() (TypedTaggedEventStreamEnvelopeSessionUpdated, error) { + var body TypedTaggedEventStreamEnvelopeSessionUpdated err := json.Unmarshal(t.union, &body) return body, err } -// FromOutboundEventPayload overwrites any union data inside the EventPayload as the provided OutboundEventPayload -func (t *EventPayload) FromOutboundEventPayload(v OutboundEventPayload) error { +// FromTypedTaggedEventStreamEnvelopeSessionUpdated overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeSessionUpdated +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeSessionUpdated(v TypedTaggedEventStreamEnvelopeSessionUpdated) error { + v.Type = "session.updated" b, err := json.Marshal(v) t.union = b return err } -// MergeOutboundEventPayload performs a merge with any union data inside the EventPayload, using the provided OutboundEventPayload -func (t *EventPayload) MergeOutboundEventPayload(v OutboundEventPayload) error { +// MergeTypedTaggedEventStreamEnvelopeSessionUpdated performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeSessionUpdated +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeSessionUpdated(v TypedTaggedEventStreamEnvelopeSessionUpdated) error { + v.Type = "session.updated" b, err := json.Marshal(v) if err != nil { return err @@ -3912,22 +7620,24 @@ func (t *EventPayload) MergeOutboundEventPayload(v OutboundEventPayload) error { return err } -// AsUnboundEventPayload returns the union data inside the EventPayload as a UnboundEventPayload -func (t EventPayload) AsUnboundEventPayload() (UnboundEventPayload, error) { - var body UnboundEventPayload +// AsTypedTaggedEventStreamEnvelopeSessionWoke returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeSessionWoke +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeSessionWoke() (TypedTaggedEventStreamEnvelopeSessionWoke, error) { + var body TypedTaggedEventStreamEnvelopeSessionWoke err := json.Unmarshal(t.union, &body) return body, err } -// FromUnboundEventPayload overwrites any union data inside the EventPayload as the provided UnboundEventPayload -func (t *EventPayload) FromUnboundEventPayload(v UnboundEventPayload) error { +// FromTypedTaggedEventStreamEnvelopeSessionWoke overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeSessionWoke +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeSessionWoke(v TypedTaggedEventStreamEnvelopeSessionWoke) error { + v.Type = "session.woke" b, err := json.Marshal(v) t.union = b return err } -// MergeUnboundEventPayload performs a merge with any union data inside the EventPayload, using the provided UnboundEventPayload -func (t *EventPayload) MergeUnboundEventPayload(v UnboundEventPayload) error { +// MergeTypedTaggedEventStreamEnvelopeSessionWoke performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeSessionWoke +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeSessionWoke(v TypedTaggedEventStreamEnvelopeSessionWoke) error { + v.Type = "session.woke" b, err := json.Marshal(v) if err != nil { return err @@ -3938,22 +7648,24 @@ func (t *EventPayload) MergeUnboundEventPayload(v UnboundEventPayload) error { return err } -// AsWorkerOperationEventPayload returns the union data inside the EventPayload as a WorkerOperationEventPayload -func (t EventPayload) AsWorkerOperationEventPayload() (WorkerOperationEventPayload, error) { - var body WorkerOperationEventPayload +// AsTypedTaggedEventStreamEnvelopeWorkerOperation returns the union data inside the TypedTaggedEventStreamEnvelope as a TypedTaggedEventStreamEnvelopeWorkerOperation +func (t TypedTaggedEventStreamEnvelope) AsTypedTaggedEventStreamEnvelopeWorkerOperation() (TypedTaggedEventStreamEnvelopeWorkerOperation, error) { + var body TypedTaggedEventStreamEnvelopeWorkerOperation err := json.Unmarshal(t.union, &body) return body, err } -// FromWorkerOperationEventPayload overwrites any union data inside the EventPayload as the provided WorkerOperationEventPayload -func (t *EventPayload) FromWorkerOperationEventPayload(v WorkerOperationEventPayload) error { +// FromTypedTaggedEventStreamEnvelopeWorkerOperation overwrites any union data inside the TypedTaggedEventStreamEnvelope as the provided TypedTaggedEventStreamEnvelopeWorkerOperation +func (t *TypedTaggedEventStreamEnvelope) FromTypedTaggedEventStreamEnvelopeWorkerOperation(v TypedTaggedEventStreamEnvelopeWorkerOperation) error { + v.Type = "worker.operation" b, err := json.Marshal(v) t.union = b return err } -// MergeWorkerOperationEventPayload performs a merge with any union data inside the EventPayload, using the provided WorkerOperationEventPayload -func (t *EventPayload) MergeWorkerOperationEventPayload(v WorkerOperationEventPayload) error { +// MergeTypedTaggedEventStreamEnvelopeWorkerOperation performs a merge with any union data inside the TypedTaggedEventStreamEnvelope, using the provided TypedTaggedEventStreamEnvelopeWorkerOperation +func (t *TypedTaggedEventStreamEnvelope) MergeTypedTaggedEventStreamEnvelopeWorkerOperation(v TypedTaggedEventStreamEnvelopeWorkerOperation) error { + v.Type = "worker.operation" b, err := json.Marshal(v) if err != nil { return err @@ -3964,12 +7676,117 @@ func (t *EventPayload) MergeWorkerOperationEventPayload(v WorkerOperationEventPa return err } -func (t EventPayload) MarshalJSON() ([]byte, error) { +func (t TypedTaggedEventStreamEnvelope) Discriminator() (string, error) { + var discriminator struct { + Discriminator string `json:"type"` + } + err := json.Unmarshal(t.union, &discriminator) + return discriminator.Discriminator, err +} + +func (t TypedTaggedEventStreamEnvelope) ValueByDiscriminator() (interface{}, error) { + discriminator, err := t.Discriminator() + if err != nil { + return nil, err + } + switch discriminator { + case "bead.closed": + return t.AsTypedTaggedEventStreamEnvelopeBeadClosed() + case "bead.created": + return t.AsTypedTaggedEventStreamEnvelopeBeadCreated() + case "bead.updated": + return t.AsTypedTaggedEventStreamEnvelopeBeadUpdated() + case "city.created": + return t.AsTypedTaggedEventStreamEnvelopeCityCreated() + case "city.init_failed": + return t.AsTypedTaggedEventStreamEnvelopeCityInitFailed() + case "city.ready": + return t.AsTypedTaggedEventStreamEnvelopeCityReady() + case "city.resumed": + return t.AsTypedTaggedEventStreamEnvelopeCityResumed() + case "city.suspended": + return t.AsTypedTaggedEventStreamEnvelopeCitySuspended() + case "city.unregister_failed": + return t.AsTypedTaggedEventStreamEnvelopeCityUnregisterFailed() + case "city.unregister_requested": + return t.AsTypedTaggedEventStreamEnvelopeCityUnregisterRequested() + case "city.unregistered": + return t.AsTypedTaggedEventStreamEnvelopeCityUnregistered() + case "controller.started": + return t.AsTypedTaggedEventStreamEnvelopeControllerStarted() + case "controller.stopped": + return t.AsTypedTaggedEventStreamEnvelopeControllerStopped() + case "convoy.closed": + return t.AsTypedTaggedEventStreamEnvelopeConvoyClosed() + case "convoy.created": + return t.AsTypedTaggedEventStreamEnvelopeConvoyCreated() + case "extmsg.adapter_added": + return t.AsTypedTaggedEventStreamEnvelopeExtmsgAdapterAdded() + case "extmsg.adapter_removed": + return t.AsTypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved() + case "extmsg.bound": + return t.AsTypedTaggedEventStreamEnvelopeExtmsgBound() + case "extmsg.group_created": + return t.AsTypedTaggedEventStreamEnvelopeExtmsgGroupCreated() + case "extmsg.inbound": + return t.AsTypedTaggedEventStreamEnvelopeExtmsgInbound() + case "extmsg.outbound": + return t.AsTypedTaggedEventStreamEnvelopeExtmsgOutbound() + case "extmsg.unbound": + return t.AsTypedTaggedEventStreamEnvelopeExtmsgUnbound() + case "mail.archived": + return t.AsTypedTaggedEventStreamEnvelopeMailArchived() + case "mail.deleted": + return t.AsTypedTaggedEventStreamEnvelopeMailDeleted() + case "mail.marked_read": + return t.AsTypedTaggedEventStreamEnvelopeMailMarkedRead() + case "mail.marked_unread": + return t.AsTypedTaggedEventStreamEnvelopeMailMarkedUnread() + case "mail.read": + return t.AsTypedTaggedEventStreamEnvelopeMailRead() + case "mail.replied": + return t.AsTypedTaggedEventStreamEnvelopeMailReplied() + case "mail.sent": + return t.AsTypedTaggedEventStreamEnvelopeMailSent() + case "order.completed": + return t.AsTypedTaggedEventStreamEnvelopeOrderCompleted() + case "order.failed": + return t.AsTypedTaggedEventStreamEnvelopeOrderFailed() + case "order.fired": + return t.AsTypedTaggedEventStreamEnvelopeOrderFired() + case "provider.swapped": + return t.AsTypedTaggedEventStreamEnvelopeProviderSwapped() + case "session.crashed": + return t.AsTypedTaggedEventStreamEnvelopeSessionCrashed() + case "session.draining": + return t.AsTypedTaggedEventStreamEnvelopeSessionDraining() + case "session.idle_killed": + return t.AsTypedTaggedEventStreamEnvelopeSessionIdleKilled() + case "session.quarantined": + return t.AsTypedTaggedEventStreamEnvelopeSessionQuarantined() + case "session.stopped": + return t.AsTypedTaggedEventStreamEnvelopeSessionStopped() + case "session.suspended": + return t.AsTypedTaggedEventStreamEnvelopeSessionSuspended() + case "session.undrained": + return t.AsTypedTaggedEventStreamEnvelopeSessionUndrained() + case "session.updated": + return t.AsTypedTaggedEventStreamEnvelopeSessionUpdated() + case "session.woke": + return t.AsTypedTaggedEventStreamEnvelopeSessionWoke() + case "worker.operation": + return t.AsTypedTaggedEventStreamEnvelopeWorkerOperation() + default: + return nil, errors.New("unknown discriminator value: " + discriminator) + } +} + +func (t TypedTaggedEventStreamEnvelope) MarshalJSON() ([]byte, error) { b, err := t.union.MarshalJSON() return b, err } -func (t *EventPayload) UnmarshalJSON(b []byte) error { +func (t *TypedTaggedEventStreamEnvelope) UnmarshalJSON(b []byte) error { err := t.union.UnmarshalJSON(b) return err } diff --git a/internal/api/huma_handlers_supervisor.go b/internal/api/huma_handlers_supervisor.go index 5aba1edd82..cd834a6f62 100644 --- a/internal/api/huma_handlers_supervisor.go +++ b/internal/api/huma_handlers_supervisor.go @@ -174,6 +174,7 @@ func newSupervisorHumaAPI(mux *http.ServeMux, readOnly bool) huma.API { // Force-register documentation-only union schemas so they appear in // components.schemas even though no handler names them directly. _ = SessionStreamCommonEvent{}.Schema(api.OpenAPI().Components.Schemas) + registerEventEnvelopeCompatibilitySchemas(api.OpenAPI().Components.Schemas) api.UseMiddleware(humaCSRFMiddleware(api)) if readOnly { @@ -233,8 +234,11 @@ func (sm *SupervisorMux) registerSupervisorRoutes() { Path: "/v0/events/stream", Summary: "Stream tagged events from all running cities.", }, map[string]any{ - "tagged_event": &taggedEventStreamEnvelope{}, - "heartbeat": HeartbeatEvent{}, + "tagged_event": sseEventContract{ + runtimeSample: &taggedEventStreamEnvelope{}, + schemaSample: typedTaggedEventStreamEnvelopeSchema{}, + }, + "heartbeat": HeartbeatEvent{}, }, sm.precheckGlobalEventStream, sm.streamGlobalEvents) } diff --git a/internal/api/huma_sse_test.go b/internal/api/huma_sse_test.go index d4232e9d42..3278ffb521 100644 --- a/internal/api/huma_sse_test.go +++ b/internal/api/huma_sse_test.go @@ -2,8 +2,14 @@ package api import ( "encoding/json" + "net/http" + "net/http/httptest" + "reflect" + "sort" "strings" "testing" + + "github.com/gastownhall/gascity/internal/events" ) // TestEventStreamSchemaInSpec verifies that the events/stream endpoint @@ -84,3 +90,377 @@ func TestSSEEndpointsHaveSchemasInSpec(t *testing.T) { }) } } + +func TestEventStreamsUseTypedEnvelopeUnions(t *testing.T) { + tests := []struct { + path string + eventName string + wantRef string + }{ + { + path: "/v0/events/stream", + eventName: "tagged_event", + wantRef: "#/components/schemas/TypedTaggedEventStreamEnvelope", + }, + { + path: "/v0/city/{cityName}/events/stream", + eventName: "event", + wantRef: "#/components/schemas/TypedEventStreamEnvelope", + }, + } + + for _, source := range eventStreamSpecCases(t) { + t.Run(source.name, func(t *testing.T) { + for _, tt := range tests { + t.Run(tt.path, func(t *testing.T) { + gotRef := sseEventDataRef(t, source.spec, tt.path, tt.eventName) + if gotRef != tt.wantRef { + t.Fatalf("%s %s data ref = %q, want %q", tt.path, tt.eventName, gotRef, tt.wantRef) + } + }) + } + + schemas := componentSchemas(t, source.spec) + for _, name := range []string{ + "EventStreamEnvelope", + "TaggedEventStreamEnvelope", + "TypedEventStreamEnvelope", + "TypedTaggedEventStreamEnvelope", + } { + if _, ok := schemas[name]; !ok { + t.Fatalf("components.schemas missing %s", name) + } + } + }) + } +} + +func TestTypedEventEnvelopeUnionsCoverKnownEventTypes(t *testing.T) { + for _, source := range eventStreamSpecCases(t) { + t.Run(source.name, func(t *testing.T) { + for _, tc := range []struct { + schemaName string + cityField bool + }{ + {schemaName: "TypedEventStreamEnvelope"}, + {schemaName: "TypedTaggedEventStreamEnvelope", cityField: true}, + } { + t.Run(tc.schemaName, func(t *testing.T) { + assertTypedEventEnvelopeUnion(t, source.spec, tc.schemaName, tc.cityField) + }) + } + }) + } +} + +func eventStreamSpecCases(t *testing.T) []struct { + name string + spec map[string]any +} { + t.Helper() + return []struct { + name string + spec map[string]any + }{ + {name: "committed", spec: readCommittedOpenAPISpec(t)}, + {name: "live-supervisor", spec: readLiveSupervisorOpenAPISpec(t)}, + } +} + +func readLiveSupervisorOpenAPISpec(t *testing.T) map[string]any { + t.Helper() + + sm := newTestSupervisorMux(t, map[string]*fakeState{}) + req := httptest.NewRequest(http.MethodGet, "/openapi.json", nil) + rec := httptest.NewRecorder() + sm.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("GET /openapi.json status = %d, want 200; body=%s", rec.Code, rec.Body.String()) + } + var spec map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &spec); err != nil { + t.Fatalf("decode live openapi.json: %v", err) + } + return spec +} + +func sseEventDataRef(t *testing.T, spec map[string]any, path, eventName string) string { + t.Helper() + + paths, ok := spec["paths"].(map[string]any) + if !ok { + t.Fatal("OpenAPI paths missing") + } + pathItem, ok := paths[path].(map[string]any) + if !ok { + t.Fatalf("path %s missing", path) + } + get, ok := pathItem["get"].(map[string]any) + if !ok { + t.Fatalf("GET %s missing", path) + } + responses, ok := get["responses"].(map[string]any) + if !ok { + t.Fatalf("GET %s responses missing", path) + } + ok200, ok := responses["200"].(map[string]any) + if !ok { + t.Fatalf("GET %s 200 response missing", path) + } + content, ok := ok200["content"].(map[string]any) + if !ok { + t.Fatalf("GET %s 200 content missing", path) + } + stream, ok := content["text/event-stream"].(map[string]any) + if !ok { + t.Fatalf("GET %s text/event-stream content missing", path) + } + schema, ok := stream["schema"].(map[string]any) + if !ok { + t.Fatalf("GET %s stream schema missing", path) + } + items, ok := schema["items"].(map[string]any) + if !ok { + t.Fatalf("GET %s stream items schema missing", path) + } + oneOf, ok := items["oneOf"].([]any) + if !ok { + t.Fatalf("GET %s stream oneOf missing", path) + } + for _, branch := range oneOf { + branchSchema, ok := branch.(map[string]any) + if !ok { + continue + } + properties, ok := branchSchema["properties"].(map[string]any) + if !ok { + continue + } + eventProperty, ok := properties["event"].(map[string]any) + if !ok { + continue + } + if got, _ := eventProperty["const"].(string); got != eventName { + continue + } + dataProperty, ok := properties["data"].(map[string]any) + if !ok { + t.Fatalf("GET %s event %s data property missing", path, eventName) + } + ref, _ := dataProperty["$ref"].(string) + return ref + } + t.Fatalf("GET %s SSE event %s not found", path, eventName) + return "" +} + +func assertTypedEventEnvelopeUnion(t *testing.T, spec map[string]any, schemaName string, cityField bool) { + t.Helper() + + schemas := componentSchemas(t, spec) + union, ok := schemas[schemaName] + if !ok { + t.Fatalf("components.schemas missing %s", schemaName) + } + oneOf, ok := union["oneOf"].([]any) + if !ok { + t.Fatalf("%s oneOf missing", schemaName) + } + discriminator := typedEventDiscriminatorMapping(t, union, schemaName) + + expectedPayloadRefs := expectedEventPayloadRefs(t) + seen := map[string]int{} + for _, branch := range oneOf { + refObj, ok := branch.(map[string]any) + if !ok { + t.Fatalf("%s oneOf branch is not an object: %#v", schemaName, branch) + } + ref, _ := refObj["$ref"].(string) + if ref == "" { + t.Fatalf("%s oneOf branch missing $ref: %#v", schemaName, branch) + } + variant := schemaByRef(t, schemas, ref) + properties, ok := variant["properties"].(map[string]any) + if !ok { + t.Fatalf("%s variant %s properties missing", schemaName, ref) + } + typeProperty, ok := properties["type"].(map[string]any) + if !ok { + t.Fatalf("%s variant %s type property missing", schemaName, ref) + } + eventType := constOrSingleEnum(t, typeProperty) + wantPayloadRef, ok := expectedPayloadRefs[eventType] + if !ok { + t.Fatalf("%s variant %s has unknown event type %q", schemaName, ref, eventType) + } + seen[eventType]++ + if gotRef := discriminator[eventType]; gotRef != ref { + t.Fatalf("%s discriminator mapping for %s = %q, want %q", schemaName, eventType, gotRef, ref) + } + + payloadProperty, ok := properties["payload"].(map[string]any) + if !ok { + t.Fatalf("%s variant %s payload property missing", schemaName, ref) + } + gotPayloadRef, _ := payloadProperty["$ref"].(string) + if gotPayloadRef != wantPayloadRef { + t.Fatalf("%s variant %s payload ref = %q, want %q", schemaName, eventType, gotPayloadRef, wantPayloadRef) + } + + wantRequired := []string{"seq", "type", "ts", "actor", "payload"} + wantProperties := []string{"seq", "type", "ts", "actor", "subject", "message", "workflow", "payload"} + if cityField { + wantRequired = append(wantRequired, "city") + wantProperties = append(wantProperties, "city") + } + assertProperties(t, schemaName, eventType, properties, wantProperties) + assertRequiredFields(t, schemaName, eventType, variant, wantRequired) + } + + var missing, duplicate []string + for _, eventType := range events.KnownEventTypes { + switch seen[eventType] { + case 0: + missing = append(missing, eventType) + case 1: + default: + duplicate = append(duplicate, eventType) + } + } + sort.Strings(missing) + sort.Strings(duplicate) + if len(missing) > 0 || len(duplicate) > 0 { + t.Fatalf("%s event coverage mismatch; missing=%v duplicate=%v", schemaName, missing, duplicate) + } + if len(discriminator) != len(events.KnownEventTypes) { + t.Fatalf("%s discriminator mapping count = %d, want %d", schemaName, len(discriminator), len(events.KnownEventTypes)) + } + for eventType := range discriminator { + if seen[eventType] == 0 { + t.Fatalf("%s discriminator maps unknown event type %q", schemaName, eventType) + } + } +} + +func typedEventDiscriminatorMapping(t *testing.T, union map[string]any, schemaName string) map[string]string { + t.Helper() + + rawDiscriminator, ok := union["discriminator"].(map[string]any) + if !ok { + t.Fatalf("%s discriminator missing", schemaName) + } + if got, _ := rawDiscriminator["propertyName"].(string); got != "type" { + t.Fatalf("%s discriminator.propertyName = %q, want type", schemaName, got) + } + rawMapping, ok := rawDiscriminator["mapping"].(map[string]any) + if !ok { + t.Fatalf("%s discriminator.mapping missing", schemaName) + } + mapping := make(map[string]string, len(rawMapping)) + for eventType, rawRef := range rawMapping { + ref, ok := rawRef.(string) + if !ok || ref == "" { + t.Fatalf("%s discriminator mapping for %s is not a ref: %#v", schemaName, eventType, rawRef) + } + mapping[eventType] = ref + } + return mapping +} + +func componentSchemas(t *testing.T, spec map[string]any) map[string]map[string]any { + t.Helper() + + components, ok := spec["components"].(map[string]any) + if !ok { + t.Fatal("OpenAPI components missing") + } + rawSchemas, ok := components["schemas"].(map[string]any) + if !ok { + t.Fatal("OpenAPI components.schemas missing") + } + schemas := make(map[string]map[string]any, len(rawSchemas)) + for name, raw := range rawSchemas { + schema, ok := raw.(map[string]any) + if !ok { + continue + } + schemas[name] = schema + } + return schemas +} + +func expectedEventPayloadRefs(t *testing.T) map[string]string { + t.Helper() + + registered := events.RegisteredPayloadTypes() + expected := make(map[string]string, len(events.KnownEventTypes)) + for _, eventType := range events.KnownEventTypes { + sample, ok := registered[eventType] + if !ok { + t.Fatalf("%s payload not registered", eventType) + } + expected[eventType] = "#/components/schemas/" + reflect.TypeOf(sample).Name() + } + return expected +} + +func schemaByRef(t *testing.T, schemas map[string]map[string]any, ref string) map[string]any { + t.Helper() + + const prefix = "#/components/schemas/" + if !strings.HasPrefix(ref, prefix) { + t.Fatalf("schema ref %q does not have prefix %q", ref, prefix) + } + name := strings.TrimPrefix(ref, prefix) + schema, ok := schemas[name] + if !ok { + t.Fatalf("schema ref %q target missing", ref) + } + return schema +} + +func constOrSingleEnum(t *testing.T, schema map[string]any) string { + t.Helper() + + if value, ok := schema["const"].(string); ok { + return value + } + enum, ok := schema["enum"].([]any) + if !ok || len(enum) != 1 { + t.Fatalf("schema has neither string const nor single-value enum: %#v", schema) + } + value, ok := enum[0].(string) + if !ok { + t.Fatalf("schema single enum value is not a string: %#v", enum[0]) + } + return value +} + +func assertProperties(t *testing.T, schemaName, eventType string, properties map[string]any, fields []string) { + t.Helper() + + for _, field := range fields { + if _, ok := properties[field]; !ok { + t.Fatalf("%s variant %s missing property %q", schemaName, eventType, field) + } + } +} + +func assertRequiredFields(t *testing.T, schemaName, eventType string, schema map[string]any, fields []string) { + t.Helper() + + rawRequired, ok := schema["required"].([]any) + if !ok { + t.Fatalf("%s variant %s required fields missing", schemaName, eventType) + } + required := map[string]bool{} + for _, raw := range rawRequired { + name, _ := raw.(string) + required[name] = true + } + for _, field := range fields { + if !required[field] { + t.Fatalf("%s variant %s missing required field %q; required=%v", schemaName, eventType, field, rawRequired) + } + } +} diff --git a/internal/api/openapi.json b/internal/api/openapi.json index bd3188b4f1..377d0abcf5 100644 --- a/internal/api/openapi.json +++ b/internal/api/openapi.json @@ -6431,6 +6431,4156 @@ ], "type": "string" }, + "TypedEventStreamEnvelope": { + "description": "Discriminated union of city event stream envelopes. Each variant constrains the envelope type and payload schema together.", + "discriminator": { + "mapping": { + "bead.closed": "#/components/schemas/TypedEventStreamEnvelopeBeadClosed", + "bead.created": "#/components/schemas/TypedEventStreamEnvelopeBeadCreated", + "bead.updated": "#/components/schemas/TypedEventStreamEnvelopeBeadUpdated", + "city.created": "#/components/schemas/TypedEventStreamEnvelopeCityCreated", + "city.init_failed": "#/components/schemas/TypedEventStreamEnvelopeCityInitFailed", + "city.ready": "#/components/schemas/TypedEventStreamEnvelopeCityReady", + "city.resumed": "#/components/schemas/TypedEventStreamEnvelopeCityResumed", + "city.suspended": "#/components/schemas/TypedEventStreamEnvelopeCitySuspended", + "city.unregister_failed": "#/components/schemas/TypedEventStreamEnvelopeCityUnregisterFailed", + "city.unregister_requested": "#/components/schemas/TypedEventStreamEnvelopeCityUnregisterRequested", + "city.unregistered": "#/components/schemas/TypedEventStreamEnvelopeCityUnregistered", + "controller.started": "#/components/schemas/TypedEventStreamEnvelopeControllerStarted", + "controller.stopped": "#/components/schemas/TypedEventStreamEnvelopeControllerStopped", + "convoy.closed": "#/components/schemas/TypedEventStreamEnvelopeConvoyClosed", + "convoy.created": "#/components/schemas/TypedEventStreamEnvelopeConvoyCreated", + "extmsg.adapter_added": "#/components/schemas/TypedEventStreamEnvelopeExtmsgAdapterAdded", + "extmsg.adapter_removed": "#/components/schemas/TypedEventStreamEnvelopeExtmsgAdapterRemoved", + "extmsg.bound": "#/components/schemas/TypedEventStreamEnvelopeExtmsgBound", + "extmsg.group_created": "#/components/schemas/TypedEventStreamEnvelopeExtmsgGroupCreated", + "extmsg.inbound": "#/components/schemas/TypedEventStreamEnvelopeExtmsgInbound", + "extmsg.outbound": "#/components/schemas/TypedEventStreamEnvelopeExtmsgOutbound", + "extmsg.unbound": "#/components/schemas/TypedEventStreamEnvelopeExtmsgUnbound", + "mail.archived": "#/components/schemas/TypedEventStreamEnvelopeMailArchived", + "mail.deleted": "#/components/schemas/TypedEventStreamEnvelopeMailDeleted", + "mail.marked_read": "#/components/schemas/TypedEventStreamEnvelopeMailMarkedRead", + "mail.marked_unread": "#/components/schemas/TypedEventStreamEnvelopeMailMarkedUnread", + "mail.read": "#/components/schemas/TypedEventStreamEnvelopeMailRead", + "mail.replied": "#/components/schemas/TypedEventStreamEnvelopeMailReplied", + "mail.sent": "#/components/schemas/TypedEventStreamEnvelopeMailSent", + "order.completed": "#/components/schemas/TypedEventStreamEnvelopeOrderCompleted", + "order.failed": "#/components/schemas/TypedEventStreamEnvelopeOrderFailed", + "order.fired": "#/components/schemas/TypedEventStreamEnvelopeOrderFired", + "provider.swapped": "#/components/schemas/TypedEventStreamEnvelopeProviderSwapped", + "session.crashed": "#/components/schemas/TypedEventStreamEnvelopeSessionCrashed", + "session.draining": "#/components/schemas/TypedEventStreamEnvelopeSessionDraining", + "session.idle_killed": "#/components/schemas/TypedEventStreamEnvelopeSessionIdleKilled", + "session.quarantined": "#/components/schemas/TypedEventStreamEnvelopeSessionQuarantined", + "session.stopped": "#/components/schemas/TypedEventStreamEnvelopeSessionStopped", + "session.suspended": "#/components/schemas/TypedEventStreamEnvelopeSessionSuspended", + "session.undrained": "#/components/schemas/TypedEventStreamEnvelopeSessionUndrained", + "session.updated": "#/components/schemas/TypedEventStreamEnvelopeSessionUpdated", + "session.woke": "#/components/schemas/TypedEventStreamEnvelopeSessionWoke", + "worker.operation": "#/components/schemas/TypedEventStreamEnvelopeWorkerOperation" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeBeadClosed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeBeadCreated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeBeadUpdated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityCreated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityInitFailed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityReady" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityResumed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCitySuspended" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityUnregisterFailed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityUnregisterRequested" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeCityUnregistered" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeControllerStarted" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeControllerStopped" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeConvoyClosed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeConvoyCreated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgAdapterAdded" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgAdapterRemoved" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgBound" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgGroupCreated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgInbound" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgOutbound" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeExtmsgUnbound" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailArchived" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailDeleted" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailMarkedRead" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailMarkedUnread" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailRead" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailReplied" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeMailSent" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeOrderCompleted" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeOrderFailed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeOrderFired" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeProviderSwapped" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionCrashed" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionDraining" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionIdleKilled" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionQuarantined" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionStopped" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionSuspended" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionUndrained" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionUpdated" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeSessionWoke" + }, + { + "$ref": "#/components/schemas/TypedEventStreamEnvelopeWorkerOperation" + } + ], + "title": "Typed city event stream envelope" + }, + "TypedEventStreamEnvelopeBeadClosed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.closed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope bead.closed", + "type": "object" + }, + "TypedEventStreamEnvelopeBeadCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope bead.created", + "type": "object" + }, + "TypedEventStreamEnvelopeBeadUpdated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.updated", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope bead.updated", + "type": "object" + }, + "TypedEventStreamEnvelopeCityCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.created", + "type": "object" + }, + "TypedEventStreamEnvelopeCityInitFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.init_failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.init_failed", + "type": "object" + }, + "TypedEventStreamEnvelopeCityReady": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.ready", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.ready", + "type": "object" + }, + "TypedEventStreamEnvelopeCityResumed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.resumed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.resumed", + "type": "object" + }, + "TypedEventStreamEnvelopeCitySuspended": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.suspended", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.suspended", + "type": "object" + }, + "TypedEventStreamEnvelopeCityUnregisterFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregister_failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.unregister_failed", + "type": "object" + }, + "TypedEventStreamEnvelopeCityUnregisterRequested": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregister_requested", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.unregister_requested", + "type": "object" + }, + "TypedEventStreamEnvelopeCityUnregistered": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregistered", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope city.unregistered", + "type": "object" + }, + "TypedEventStreamEnvelopeControllerStarted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "controller.started", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope controller.started", + "type": "object" + }, + "TypedEventStreamEnvelopeControllerStopped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "controller.stopped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope controller.stopped", + "type": "object" + }, + "TypedEventStreamEnvelopeConvoyClosed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "convoy.closed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope convoy.closed", + "type": "object" + }, + "TypedEventStreamEnvelopeConvoyCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "convoy.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope convoy.created", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgAdapterAdded": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/AdapterEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.adapter_added", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.adapter_added", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgAdapterRemoved": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/AdapterEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.adapter_removed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.adapter_removed", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgBound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BoundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.bound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.bound", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgGroupCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/GroupCreatedEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.group_created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.group_created", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgInbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/InboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.inbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.inbound", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgOutbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/OutboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.outbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.outbound", + "type": "object" + }, + "TypedEventStreamEnvelopeExtmsgUnbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/UnboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.unbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope extmsg.unbound", + "type": "object" + }, + "TypedEventStreamEnvelopeMailArchived": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.archived", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.archived", + "type": "object" + }, + "TypedEventStreamEnvelopeMailDeleted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.deleted", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.deleted", + "type": "object" + }, + "TypedEventStreamEnvelopeMailMarkedRead": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.marked_read", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.marked_read", + "type": "object" + }, + "TypedEventStreamEnvelopeMailMarkedUnread": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.marked_unread", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.marked_unread", + "type": "object" + }, + "TypedEventStreamEnvelopeMailRead": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.read", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.read", + "type": "object" + }, + "TypedEventStreamEnvelopeMailReplied": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.replied", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.replied", + "type": "object" + }, + "TypedEventStreamEnvelopeMailSent": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.sent", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope mail.sent", + "type": "object" + }, + "TypedEventStreamEnvelopeOrderCompleted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.completed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope order.completed", + "type": "object" + }, + "TypedEventStreamEnvelopeOrderFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope order.failed", + "type": "object" + }, + "TypedEventStreamEnvelopeOrderFired": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.fired", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope order.fired", + "type": "object" + }, + "TypedEventStreamEnvelopeProviderSwapped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "provider.swapped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope provider.swapped", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionCrashed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.crashed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.crashed", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionDraining": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.draining", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.draining", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionIdleKilled": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.idle_killed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.idle_killed", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionQuarantined": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.quarantined", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.quarantined", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionStopped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.stopped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.stopped", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionSuspended": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.suspended", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.suspended", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionUndrained": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.undrained", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.undrained", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionUpdated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.updated", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.updated", + "type": "object" + }, + "TypedEventStreamEnvelopeSessionWoke": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.woke", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope session.woke", + "type": "object" + }, + "TypedEventStreamEnvelopeWorkerOperation": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/WorkerOperationEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "worker.operation", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload" + ], + "title": "TypedEventStreamEnvelope worker.operation", + "type": "object" + }, + "TypedTaggedEventStreamEnvelope": { + "description": "Discriminated union of supervisor event stream envelopes. Each variant constrains the envelope type and payload schema together and includes the source city.", + "discriminator": { + "mapping": { + "bead.closed": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadClosed", + "bead.created": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadCreated", + "bead.updated": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadUpdated", + "city.created": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityCreated", + "city.init_failed": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityInitFailed", + "city.ready": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityReady", + "city.resumed": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityResumed", + "city.suspended": "#/components/schemas/TypedTaggedEventStreamEnvelopeCitySuspended", + "city.unregister_failed": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregisterFailed", + "city.unregister_requested": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregisterRequested", + "city.unregistered": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregistered", + "controller.started": "#/components/schemas/TypedTaggedEventStreamEnvelopeControllerStarted", + "controller.stopped": "#/components/schemas/TypedTaggedEventStreamEnvelopeControllerStopped", + "convoy.closed": "#/components/schemas/TypedTaggedEventStreamEnvelopeConvoyClosed", + "convoy.created": "#/components/schemas/TypedTaggedEventStreamEnvelopeConvoyCreated", + "extmsg.adapter_added": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded", + "extmsg.adapter_removed": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved", + "extmsg.bound": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgBound", + "extmsg.group_created": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgGroupCreated", + "extmsg.inbound": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgInbound", + "extmsg.outbound": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgOutbound", + "extmsg.unbound": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgUnbound", + "mail.archived": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailArchived", + "mail.deleted": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailDeleted", + "mail.marked_read": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailMarkedRead", + "mail.marked_unread": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailMarkedUnread", + "mail.read": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailRead", + "mail.replied": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailReplied", + "mail.sent": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailSent", + "order.completed": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderCompleted", + "order.failed": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderFailed", + "order.fired": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderFired", + "provider.swapped": "#/components/schemas/TypedTaggedEventStreamEnvelopeProviderSwapped", + "session.crashed": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionCrashed", + "session.draining": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionDraining", + "session.idle_killed": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionIdleKilled", + "session.quarantined": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionQuarantined", + "session.stopped": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionStopped", + "session.suspended": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionSuspended", + "session.undrained": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionUndrained", + "session.updated": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionUpdated", + "session.woke": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionWoke", + "worker.operation": "#/components/schemas/TypedTaggedEventStreamEnvelopeWorkerOperation" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadClosed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadCreated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeBeadUpdated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityCreated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityInitFailed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityReady" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityResumed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCitySuspended" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregisterFailed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregisterRequested" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeCityUnregistered" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeControllerStarted" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeControllerStopped" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeConvoyClosed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeConvoyCreated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgBound" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgGroupCreated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgInbound" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgOutbound" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeExtmsgUnbound" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailArchived" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailDeleted" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailMarkedRead" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailMarkedUnread" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailRead" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailReplied" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeMailSent" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderCompleted" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderFailed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeOrderFired" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeProviderSwapped" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionCrashed" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionDraining" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionIdleKilled" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionQuarantined" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionStopped" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionSuspended" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionUndrained" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionUpdated" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeSessionWoke" + }, + { + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelopeWorkerOperation" + } + ], + "title": "Typed supervisor event stream envelope" + }, + "TypedTaggedEventStreamEnvelopeBeadClosed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.closed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope bead.closed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeBeadCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope bead.created", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeBeadUpdated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BeadEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "bead.updated", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope bead.updated", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.created", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityInitFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.init_failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.init_failed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityReady": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.ready", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.ready", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityResumed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.resumed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.resumed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCitySuspended": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.suspended", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.suspended", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityUnregisterFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregister_failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.unregister_failed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityUnregisterRequested": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregister_requested", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.unregister_requested", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeCityUnregistered": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/CityLifecyclePayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "city.unregistered", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope city.unregistered", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeControllerStarted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "controller.started", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope controller.started", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeControllerStopped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "controller.stopped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope controller.stopped", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeConvoyClosed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "convoy.closed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope convoy.closed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeConvoyCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "convoy.created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope convoy.created", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/AdapterEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.adapter_added", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.adapter_added", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/AdapterEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.adapter_removed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.adapter_removed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgBound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/BoundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.bound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.bound", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgGroupCreated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/GroupCreatedEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.group_created", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.group_created", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgInbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/InboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.inbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.inbound", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgOutbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/OutboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.outbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.outbound", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeExtmsgUnbound": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/UnboundEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "extmsg.unbound", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope extmsg.unbound", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailArchived": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.archived", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.archived", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailDeleted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.deleted", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.deleted", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailMarkedRead": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.marked_read", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.marked_read", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailMarkedUnread": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.marked_unread", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.marked_unread", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailRead": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.read", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.read", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailReplied": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.replied", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.replied", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeMailSent": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/MailEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "mail.sent", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope mail.sent", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeOrderCompleted": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.completed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope order.completed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeOrderFailed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.failed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope order.failed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeOrderFired": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "order.fired", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope order.fired", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeProviderSwapped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "provider.swapped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope provider.swapped", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionCrashed": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.crashed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.crashed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionDraining": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.draining", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.draining", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionIdleKilled": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.idle_killed", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.idle_killed", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionQuarantined": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.quarantined", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.quarantined", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionStopped": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.stopped", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.stopped", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionSuspended": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.suspended", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.suspended", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionUndrained": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.undrained", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.undrained", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionUpdated": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.updated", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.updated", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeSessionWoke": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/NoPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "session.woke", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope session.woke", + "type": "object" + }, + "TypedTaggedEventStreamEnvelopeWorkerOperation": { + "additionalProperties": false, + "properties": { + "actor": { + "type": "string" + }, + "city": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/WorkerOperationEventPayload" + }, + "seq": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "ts": { + "format": "date-time", + "type": "string" + }, + "type": { + "const": "worker.operation", + "type": "string" + }, + "workflow": { + "$ref": "#/components/schemas/WorkflowEventProjection" + } + }, + "required": [ + "seq", + "type", + "ts", + "actor", + "payload", + "city" + ], + "title": "TypedTaggedEventStreamEnvelope worker.operation", + "type": "object" + }, "UnboundEventPayload": { "additionalProperties": false, "properties": { @@ -10548,7 +14698,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/EventStreamEnvelope" + "$ref": "#/components/schemas/TypedEventStreamEnvelope" }, "event": { "const": "event", @@ -18136,7 +22286,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/TaggedEventStreamEnvelope" + "$ref": "#/components/schemas/TypedTaggedEventStreamEnvelope" }, "event": { "const": "tagged_event", diff --git a/internal/api/sse.go b/internal/api/sse.go index 3492910387..61d5b4e09a 100644 --- a/internal/api/sse.go +++ b/internal/api/sse.go @@ -63,6 +63,20 @@ type StringIDSender func(msg StringIDMessage) error // to StreamFunc. type StringIDStreamFunc[I any] func(hctx huma.Context, input *I, send StringIDSender) +type sseEventContract struct { + runtimeSample any + schemaSample any +} + +func (c sseEventContract) sseRuntimeSample() any { return c.runtimeSample } + +func (c sseEventContract) sseSchemaSample() any { return c.schemaSample } + +type sseSchemaOverride interface { + sseRuntimeSample() any + sseSchemaSample() any +} + // registerSSE registers an SSE operation like huma's sse.Register but with a // precheck hook that can return an HTTP error before the response is committed. // @@ -243,8 +257,10 @@ func attachSSEResponseSchema( typeToEvent := make(map[reflect.Type]string, len(eventTypeMap)) dataSchemas := make([]*huma.Schema, 0, len(eventTypeMap)) for k, v := range eventTypeMap { - vt := derefType(reflect.TypeOf(v)) - typeToEvent[vt] = k + runtimeSample, schemaSample := sseContractSamples(v) + runtimeType := derefType(reflect.TypeOf(runtimeSample)) + schemaType := derefType(reflect.TypeOf(schemaSample)) + typeToEvent[runtimeType] = k required := []string{"data"} if k != "" && k != "message" { required = append(required, "event") @@ -264,7 +280,7 @@ func attachSSEResponseSchema( "const": k, }, }, - "data": api.OpenAPI().Components.Schemas.Schema(vt, true, k), + "data": api.OpenAPI().Components.Schemas.Schema(schemaType, true, k), "retry": { Type: huma.TypeInteger, Description: "The retry time in milliseconds.", @@ -295,6 +311,13 @@ func attachSSEResponseSchema( return typeToEvent } +func sseContractSamples(v any) (any, any) { + if override, ok := v.(sseSchemaOverride); ok { + return override.sseRuntimeSample(), override.sseSchemaSample() + } + return v, v +} + // beginSSEStream sets the standard SSE headers on the huma response and // returns the underlying writer + JSON encoder + flusher the send // function will use per frame. diff --git a/internal/api/supervisor_city_routes.go b/internal/api/supervisor_city_routes.go index d93e1aaa28..eeb61a8949 100644 --- a/internal/api/supervisor_city_routes.go +++ b/internal/api/supervisor_city_routes.go @@ -312,7 +312,10 @@ func (sm *SupervisorMux) registerCityRoutes() { Description: "Server-Sent Events stream of city events with optional workflow projections. " + "Supports reconnection via Last-Event-ID header or after_seq query param.", }, map[string]any{ - "event": eventStreamEnvelope{}, + "event": sseEventContract{ + runtimeSample: eventStreamEnvelope{}, + schemaSample: typedEventStreamEnvelopeSchema{}, + }, "heartbeat": HeartbeatEvent{}, }, sseCityPrecheck(sm, (*Server).checkEventStream), diff --git a/internal/api/supervisor_test.go b/internal/api/supervisor_test.go index 20dc4b6b1b..88176aebcf 100644 --- a/internal/api/supervisor_test.go +++ b/internal/api/supervisor_test.go @@ -387,6 +387,87 @@ func TestSupervisorPerCityEventStream(t *testing.T) { } } +func TestSupervisorPerCityEventStreamEmitsTypedEnvelopePayloadObject(t *testing.T) { + s := newFakeState(t) + s.cityName = "gc-work" + + sm := newTestSupervisorMux(t, map[string]*fakeState{ + "gc-work": s, + }) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + req := httptest.NewRequest("GET", "/v0/city/gc-work/events/stream", nil).WithContext(ctx) + rec := httptest.NewRecorder() + + done := make(chan struct{}) + go func() { + defer close(done) + sm.ServeHTTP(rec, req) + }() + + time.Sleep(50 * time.Millisecond) + payload, err := json.Marshal(MailEventPayload{Rig: "myrig"}) + if err != nil { + t.Fatalf("marshal payload: %v", err) + } + s.eventProv.(*events.Fake).Record(events.Event{ + Type: events.MailSent, + Actor: "tester", + Subject: "mail-1", + Payload: payload, + }) + + time.Sleep(100 * time.Millisecond) + cancel() + <-done + + frame := firstSSETestFrame(t, rec.Body.String(), "event") + if frame.ID != "1" { + t.Fatalf("SSE id = %q, want 1; body=%s", frame.ID, rec.Body.String()) + } + data := decodeSSETestData(t, frame) + if data["type"] != events.MailSent { + t.Fatalf("data.type = %v, want %s; data=%v", data["type"], events.MailSent, data) + } + if _, ok := data["city"]; ok { + t.Fatalf("per-city event data unexpectedly includes city: %v", data) + } + payloadObject, ok := data["payload"].(map[string]any) + if !ok { + t.Fatalf("data.payload = %#v, want JSON object", data["payload"]) + } + if payloadObject["rig"] != "myrig" { + t.Fatalf("payload.rig = %v, want myrig; payload=%v", payloadObject["rig"], payloadObject) + } +} + +func TestSupervisorPerCityEventStreamEmitsNoPayloadObject(t *testing.T) { + s := newFakeState(t) + s.cityName = "gc-work" + + sm := newTestSupervisorMux(t, map[string]*fakeState{ + "gc-work": s, + }) + + frame := firstSSEFrameAfterRecord(t, sm, "/v0/city/gc-work/events/stream", "event", func() { + s.eventProv.(*events.Fake).Record(events.Event{ + Type: events.SessionWoke, + Actor: "tester", + Subject: "session-1", + }) + }) + data := decodeSSETestData(t, frame) + if data["type"] != events.SessionWoke { + t.Fatalf("data.type = %v, want %s; data=%v", data["type"], events.SessionWoke, data) + } + payloadObject := assertJSONPayloadObject(t, data["payload"]) + if len(payloadObject) != 0 { + t.Fatalf("data.payload = %v, want empty object for NoPayload", payloadObject) + } +} + func TestSupervisorGlobalEventList(t *testing.T) { s1 := newFakeState(t) s1.cityName = "alpha" @@ -431,6 +512,71 @@ func TestSupervisorGlobalEventList(t *testing.T) { } } +func TestSupervisorEventListsEmitTypedPayloadObjects(t *testing.T) { + s := newFakeState(t) + s.cityName = "alpha" + payload, err := json.Marshal(MailEventPayload{Rig: "myrig"}) + if err != nil { + t.Fatalf("marshal payload: %v", err) + } + s.eventProv.(*events.Fake).Record(events.Event{ + Type: events.MailSent, + Actor: "tester", + Subject: "mail-1", + Payload: payload, + }) + s.eventProv.(*events.Fake).Record(events.Event{ + Type: events.SessionWoke, + Actor: "tester", + Subject: "session-1", + }) + + sm := newTestSupervisorMux(t, map[string]*fakeState{"alpha": s}) + + for _, tt := range []struct { + name string + path string + wantCity string + }{ + {name: "per-city", path: "/v0/city/alpha/events"}, + {name: "supervisor", path: "/v0/events", wantCity: "alpha"}, + } { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest("GET", tt.path, nil) + rec := httptest.NewRecorder() + sm.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String()) + } + + var resp struct { + Items []map[string]any `json:"items"` + Total int `json:"total"` + } + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode: %v", err) + } + if resp.Total != 2 { + t.Fatalf("total = %d, want 2; items=%v", resp.Total, resp.Items) + } + + mail := eventListItemByType(t, resp.Items, events.MailSent) + if tt.wantCity != "" && mail["city"] != tt.wantCity { + t.Fatalf("mail city = %v, want %s; item=%v", mail["city"], tt.wantCity, mail) + } + mailPayload := assertJSONPayloadObject(t, mail["payload"]) + if mailPayload["rig"] != "myrig" { + t.Fatalf("mail payload.rig = %v, want myrig; payload=%v", mailPayload["rig"], mailPayload) + } + + noPayload := assertJSONPayloadObject(t, eventListItemByType(t, resp.Items, events.SessionWoke)["payload"]) + if len(noPayload) != 0 { + t.Fatalf("session.woke payload = %v, want empty object", noPayload) + } + }) + } +} + func TestSupervisorGlobalEventListWithFilter(t *testing.T) { s1 := newFakeState(t) s1.cityName = "alpha" @@ -599,6 +745,93 @@ func TestSupervisorGlobalEventStreamCompositeCursor(t *testing.T) { } } +func TestSupervisorGlobalEventStreamEmitsTypedTaggedEnvelopePayloadObject(t *testing.T) { + s := newFakeState(t) + s.cityName = "alpha" + + sm := newTestSupervisorMux(t, map[string]*fakeState{ + "alpha": s, + }) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + req := httptest.NewRequest("GET", "/v0/events/stream", nil).WithContext(ctx) + rec := httptest.NewRecorder() + + done := make(chan struct{}) + go func() { + defer close(done) + sm.ServeHTTP(rec, req) + }() + + time.Sleep(50 * time.Millisecond) + payload, err := json.Marshal(MailEventPayload{Rig: "myrig"}) + if err != nil { + t.Fatalf("marshal payload: %v", err) + } + s.eventProv.(*events.Fake).Record(events.Event{ + Type: events.MailSent, + Actor: "tester", + Subject: "mail-1", + Payload: payload, + }) + + time.Sleep(100 * time.Millisecond) + cancel() + <-done + + frame := firstSSETestFrame(t, rec.Body.String(), "tagged_event") + if frame.ID != "alpha:1" { + t.Fatalf("SSE id = %q, want alpha:1; body=%s", frame.ID, rec.Body.String()) + } + data := decodeSSETestData(t, frame) + if data["type"] != events.MailSent { + t.Fatalf("data.type = %v, want %s; data=%v", data["type"], events.MailSent, data) + } + if data["city"] != "alpha" { + t.Fatalf("data.city = %v, want alpha; data=%v", data["city"], data) + } + payloadObject, ok := data["payload"].(map[string]any) + if !ok { + t.Fatalf("data.payload = %#v, want JSON object", data["payload"]) + } + if payloadObject["rig"] != "myrig" { + t.Fatalf("payload.rig = %v, want myrig; payload=%v", payloadObject["rig"], payloadObject) + } +} + +func TestSupervisorGlobalEventStreamEmitsNoPayloadObject(t *testing.T) { + s := newFakeState(t) + s.cityName = "alpha" + + sm := newTestSupervisorMux(t, map[string]*fakeState{ + "alpha": s, + }) + + frame := firstSSEFrameAfterRecord(t, sm, "/v0/events/stream", "tagged_event", func() { + s.eventProv.(*events.Fake).Record(events.Event{ + Type: events.SessionWoke, + Actor: "tester", + Subject: "session-1", + }) + }) + if frame.ID != "alpha:1" { + t.Fatalf("SSE id = %q, want alpha:1", frame.ID) + } + data := decodeSSETestData(t, frame) + if data["type"] != events.SessionWoke { + t.Fatalf("data.type = %v, want %s; data=%v", data["type"], events.SessionWoke, data) + } + if data["city"] != "alpha" { + t.Fatalf("data.city = %v, want alpha; data=%v", data["city"], data) + } + payloadObject := assertJSONPayloadObject(t, data["payload"]) + if len(payloadObject) != 0 { + t.Fatalf("data.payload = %v, want empty object for NoPayload", payloadObject) + } +} + func TestSupervisorGlobalEventStreamProjectsWorkflowMetadata(t *testing.T) { s1 := newFakeState(t) s1.cityName = "alpha" @@ -657,3 +890,104 @@ func TestSupervisorGlobalEventStreamProjectsWorkflowMetadata(t *testing.T) { t.Fatalf("global SSE body missing city tag: %s", body) } } + +type sseTestFrame struct { + Event string + ID string + Data string +} + +func firstSSETestFrame(t *testing.T, body, eventName string) sseTestFrame { + t.Helper() + + for _, frame := range parseSSETestFrames(body) { + if frame.Event == eventName { + return frame + } + } + t.Fatalf("SSE event %q not found in body: %s", eventName, body) + return sseTestFrame{} +} + +func firstSSEFrameAfterRecord(t *testing.T, h http.Handler, path, eventName string, record func()) sseTestFrame { + t.Helper() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + req := httptest.NewRequest("GET", path, nil).WithContext(ctx) + rec := httptest.NewRecorder() + done := make(chan struct{}) + go func() { + defer close(done) + h.ServeHTTP(rec, req) + }() + + time.Sleep(50 * time.Millisecond) + record() + time.Sleep(100 * time.Millisecond) + cancel() + <-done + + return firstSSETestFrame(t, rec.Body.String(), eventName) +} + +func parseSSETestFrames(body string) []sseTestFrame { + var frames []sseTestFrame + var current sseTestFrame + flush := func() { + if current.Event != "" || current.ID != "" || current.Data != "" { + frames = append(frames, current) + current = sseTestFrame{} + } + } + + scanner := bufio.NewScanner(strings.NewReader(body)) + for scanner.Scan() { + line := scanner.Text() + switch { + case line == "": + flush() + case strings.HasPrefix(line, "event: "): + current.Event = strings.TrimPrefix(line, "event: ") + case strings.HasPrefix(line, "id: "): + current.ID = strings.TrimPrefix(line, "id: ") + case strings.HasPrefix(line, "data: "): + current.Data = strings.TrimPrefix(line, "data: ") + } + } + flush() + return frames +} + +func decodeSSETestData(t *testing.T, frame sseTestFrame) map[string]any { + t.Helper() + + var data map[string]any + if err := json.Unmarshal([]byte(frame.Data), &data); err != nil { + t.Fatalf("decode SSE data for event %q: %v; data=%s", frame.Event, err, frame.Data) + } + return data +} + +func assertJSONPayloadObject(t *testing.T, raw any) map[string]any { + t.Helper() + + payloadObject, ok := raw.(map[string]any) + if !ok { + t.Fatalf("payload = %#v, want JSON object", raw) + } + return payloadObject +} + +func eventListItemByType(t *testing.T, items []map[string]any, eventType string) map[string]any { + t.Helper() + + for _, item := range items { + if item["type"] == eventType { + return item + } + } + t.Fatalf("event type %s not found in items: %v", eventType, items) + return nil +} From 339354a05ba2e99f778745728edb6c7a8818cfbf Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Thu, 23 Apr 2026 21:32:15 +0000 Subject: [PATCH 11/85] fix: address PR 1119 review blockers Fix supervisor unregister naming, transient event provider lifetimes, bead cache consistency, authoritative bead create responses, and review follow-up docs/tooling regressions. --- Makefile | 18 +- cmd/gc/api_state.go | 28 ++ cmd/gc/api_state_test.go | 16 +- cmd/gc/city_registry.go | 45 +- cmd/gc/city_registry_test.go | 3 + cmd/gc/cmd_supervisor.go | 16 +- cmd/gc/cmd_supervisor_city_test.go | 52 ++ cmd/gc/controller.go | 1 + docs/reference/exec-beads-provider.md | 9 +- engdocs/architecture/beads.md | 8 +- engdocs/architecture/life-of-a-bead.md | 7 +- internal/api/handler_beads_test.go | 63 +++ internal/api/huma_handlers_beads.go | 6 + internal/beads/bdstore.go | 18 - internal/beads/bdstore_test.go | 43 +- internal/beads/caching_store.go | 21 +- internal/beads/caching_store_events.go | 4 + internal/beads/caching_store_reads.go | 2 + internal/beads/caching_store_reconcile.go | 1 + internal/beads/caching_store_test.go | 75 +++ internal/beads/caching_store_writes.go | 63 +-- specs/architecture.md | 555 ++++++++++++++++++++++ 22 files changed, 950 insertions(+), 104 deletions(-) create mode 100644 specs/architecture.md diff --git a/Makefile b/Makefile index 2f10834391..d3830bf7b1 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ LDFLAGS := -X main.version=$(VERSION) \ -X main.commit=$(COMMIT) \ -X main.date=$(BUILD_TIME) -.PHONY: build check check-all check-bd check-docker check-docs check-dolt check-version-tag lint fmt-check fmt vet test test-cmd-gc-process test-worker-core test-worker-core-phase2 test-worker-core-phase2-real-transport test-worker-inference-phase3 test-acceptance test-acceptance-b test-acceptance-c test-acceptance-all test-tutorial-goldens test-tutorial-regression test-tutorial test-integration test-integration-shards test-integration-shards-cover test-integration-packages test-integration-packages-cover test-integration-review-formulas test-integration-review-formulas-cover test-integration-review-formulas-basic test-integration-review-formulas-basic-cover test-integration-review-formulas-retries test-integration-review-formulas-retries-cover test-integration-review-formulas-recovery test-integration-review-formulas-recovery-cover test-integration-bdstore test-integration-bdstore-cover test-integration-rest test-integration-rest-cover test-integration-rest-smoke test-integration-rest-smoke-cover test-integration-rest-full test-integration-rest-full-cover test-mcp-mail test-docker test-k8s test-cover cover install install-tools install-buildx setup clean generate check-schema docker-base docker-agent docker-controller docs-dev +.PHONY: build check check-all check-bd check-docker check-docs check-dolt check-version-tag lint fmt-check fmt vet test test-cmd-gc-process test-worker-core test-worker-core-phase2 test-worker-core-phase2-real-transport test-worker-inference-phase3 test-acceptance test-acceptance-b test-acceptance-c test-acceptance-all test-tutorial-goldens test-tutorial-regression test-tutorial test-integration test-integration-shards test-integration-shards-cover test-integration-packages test-integration-packages-cover test-integration-review-formulas test-integration-review-formulas-cover test-integration-review-formulas-basic test-integration-review-formulas-basic-cover test-integration-review-formulas-retries test-integration-review-formulas-retries-cover test-integration-review-formulas-recovery test-integration-review-formulas-recovery-cover test-integration-bdstore test-integration-bdstore-cover test-integration-rest test-integration-rest-cover test-integration-rest-smoke test-integration-rest-smoke-cover test-integration-rest-full test-integration-rest-full-cover test-mcp-mail test-docker test-k8s test-cover cover install install-tools install-buildx setup clean generate check-schema docker-base docker-agent docker-controller docs-dev dashboard-smoke ## build: compile gc binary with version metadata build: @@ -415,6 +415,22 @@ dashboard-check: dashboard-build cd cmd/gc/dashboard/web && npm run typecheck $(TEST_ENV) go test ./cmd/gc/dashboard/... +## dashboard-smoke: serve the built SPA bundle via Vite preview and verify it responds +dashboard-smoke: dashboard-build + @PORT=$$(python3 -c 'import socket; sock = socket.socket(); sock.bind(("127.0.0.1", 0)); print(sock.getsockname()[1]); sock.close()'); \ + LOG=$$(mktemp); \ + ( cd cmd/gc/dashboard/web && exec npm run preview -- --host 127.0.0.1 --strictPort --port $$PORT >"$$LOG" 2>&1 ) & \ + PID=$$!; \ + trap 'kill $$PID >/dev/null 2>&1 || true; wait $$PID >/dev/null 2>&1 || true; rm -f "$$LOG"' EXIT INT TERM; \ + for attempt in $$(seq 1 40); do \ + if curl -fsS "http://127.0.0.1:$$PORT/" >/dev/null; then \ + exit 0; \ + fi; \ + sleep 0.25; \ + done; \ + cat "$$LOG" >&2; \ + exit 1 + ## dashboard-ci: rebuild the SPA bundle and fail if the tracked dist/ is stale. ## Used by CI to enforce that cmd/gc/dashboard/web/dist/ matches the source. dashboard-ci: dashboard-check diff --git a/cmd/gc/api_state.go b/cmd/gc/api_state.go index 0614571382..e63dff99bb 100644 --- a/cmd/gc/api_state.go +++ b/cmd/gc/api_state.go @@ -10,6 +10,7 @@ import ( "path/filepath" "strings" "sync" + "sync/atomic" "time" "github.com/gastownhall/gascity/internal/api" @@ -45,6 +46,7 @@ type controllerState struct { startedAt time.Time ct crashTracker // nil if crash tracking disabled pokeCh chan struct{} // nil when poke is not available; triggers immediate reconciler tick + configDirty *atomic.Bool // optional dirty flag shared with the reconciler reload path services workspacesvc.Registry extmsgSvc *extmsg.Services adapterReg *extmsg.AdapterRegistry @@ -639,10 +641,36 @@ func (cs *controllerState) mutateAndPoke(mutate func() error) error { if err := mutate(); err != nil { return err } + if err := cs.refreshConfigSnapshot(); err != nil { + return err + } + if cs.configDirty != nil { + cs.configDirty.Store(true) + } cs.Poke() return nil } +func (cs *controllerState) refreshConfigSnapshot() error { + if cs.cityPath == "" || cs.cfg == nil { + return nil + } + + tomlPath := filepath.Join(cs.cityPath, "city.toml") + nextCfg, _, err := config.LoadWithIncludes(fsys.OSFS{}, tomlPath, extraConfigFiles...) + if err != nil { + return fmt.Errorf("loading updated city config: %w", err) + } + applyFeatureFlags(nextCfg) + applyRuntimeCityIdentity(nextCfg, cs.cityName) + + cs.mu.RLock() + sp := cs.sp + cs.mu.RUnlock() + cs.update(nextCfg, sp) + return nil +} + // Poke signals the controller to trigger an immediate reconciler tick. // Non-blocking: if a poke is already pending, additional pokes are dropped. func (cs *controllerState) Poke() { diff --git a/cmd/gc/api_state_test.go b/cmd/gc/api_state_test.go index 34781b8af2..b0897d12f0 100644 --- a/cmd/gc/api_state_test.go +++ b/cmd/gc/api_state_test.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" "sync" + "sync/atomic" "testing" "github.com/gastownhall/gascity/internal/api" @@ -147,6 +148,7 @@ func TestControllerStateCreateRigPokesReconciler(t *testing.T) { } cs := newControllerState(context.Background(), cfg, runtime.NewFake(), events.NewFake(), "city1", cityDir) cs.pokeCh = make(chan struct{}, 1) + cs.configDirty = &atomic.Bool{} if err := cs.CreateRig(config.Rig{Name: "rig1", Path: t.TempDir()}); err != nil { t.Fatalf("CreateRig: %v", err) @@ -157,6 +159,12 @@ func TestControllerStateCreateRigPokesReconciler(t *testing.T) { default: t.Fatal("CreateRig did not poke the reconciler") } + if !cs.configDirty.Load() { + t.Fatal("CreateRig did not mark config dirty") + } + if got := cs.Config(); got == nil || len(got.Rigs) != 1 || got.Rigs[0].Name != "rig1" { + t.Fatalf("Config() rigs = %+v, want in-memory rig snapshot to include rig1", got.Rigs) + } } func TestControllerStateAppliesCacheReconcileBeadEventsToStores(t *testing.T) { @@ -985,6 +993,9 @@ func TestControllerStateMutationsPokeController(t *testing.T) { default: t.Fatal("expected controller mutation to poke reconciler") } + if cs.configDirty == nil || !cs.configDirty.Load() { + t.Fatal("expected controller mutation to mark config dirty") + } got, err := config.Load(fsys.OSFS{}, tomlPath) if err != nil { @@ -1074,8 +1085,9 @@ func newControllerStateMutationHarness(t *testing.T) (*controllerState, string) } return &controllerState{ - editor: configedit.NewEditor(fsys.OSFS{}, tomlPath), - pokeCh: make(chan struct{}, 1), + editor: configedit.NewEditor(fsys.OSFS{}, tomlPath), + pokeCh: make(chan struct{}, 1), + configDirty: &atomic.Bool{}, }, tomlPath } diff --git a/cmd/gc/city_registry.go b/cmd/gc/city_registry.go index c46a7e508b..8a34c55c4a 100644 --- a/cmd/gc/city_registry.go +++ b/cmd/gc/city_registry.go @@ -1,6 +1,7 @@ package main import ( + "context" "io" "path/filepath" "sync" @@ -237,15 +238,49 @@ func (r *cityRegistry) TransientCityEventProviders() map[string]events.Provider out := make(map[string]events.Provider, len(paths)) for name, path := range paths { evPath := filepath.Join(path, ".gc", "events.jsonl") - fr, err := events.NewFileRecorder(evPath, io.Discard) - if err != nil { - continue - } - out[name] = fr + out[name] = transientCityEventProvider{path: evPath} } return out } +type transientCityEventProvider struct { + path string +} + +func (p transientCityEventProvider) Record(e events.Event) { + recorder, err := events.NewFileRecorder(p.path, io.Discard) + if err != nil { + return + } + recorder.Record(e) + recorder.Close() //nolint:errcheck // best-effort +} + +func (p transientCityEventProvider) List(filter events.Filter) ([]events.Event, error) { + return events.ReadFiltered(p.path, filter) +} + +func (p transientCityEventProvider) LatestSeq() (uint64, error) { + return events.ReadLatestSeq(p.path) +} + +func (p transientCityEventProvider) Watch(ctx context.Context, afterSeq uint64) (events.Watcher, error) { + recorder, err := events.NewFileRecorder(p.path, io.Discard) + if err != nil { + return nil, err + } + watcher, err := recorder.Watch(ctx, afterSeq) + recorder.Close() //nolint:errcheck // watcher only needs the path + if err != nil { + return nil, err + } + return watcher, nil +} + +func (transientCityEventProvider) Close() error { + return nil +} + // CityState returns the api.State for a named city, or nil if not found/not running. // Lock-free read from the atomic snapshot. func (r *cityRegistry) CityState(name string) api.State { diff --git a/cmd/gc/city_registry_test.go b/cmd/gc/city_registry_test.go index 405eb6baf6..9dbdaebc2b 100644 --- a/cmd/gc/city_registry_test.go +++ b/cmd/gc/city_registry_test.go @@ -273,6 +273,9 @@ func TestCityRegistryTransientCityEventProvidersIncludesRegisteredAndPendingCiti t.Fatalf("running-city should be handled by running-city multiplexer path, not transient providers") } for name, provider := range providers { + if _, ok := provider.(*events.FileRecorder); ok { + t.Fatalf("provider %q should not retain a live file recorder", name) + } list, err := provider.List(events.Filter{Type: events.CityCreated}) if err != nil { t.Fatalf("List(%s): %v", name, err) diff --git a/cmd/gc/cmd_supervisor.go b/cmd/gc/cmd_supervisor.go index 84706297c8..7ff88e975d 100644 --- a/cmd/gc/cmd_supervisor.go +++ b/cmd/gc/cmd_supervisor.go @@ -904,8 +904,11 @@ func reconcileCities( for i, mc := range toStop { path := toStopPaths[i] - name := filepath.Base(path) - fmt.Fprintf(stdout, "Unregistered city '%s', stopping...\n", name) //nolint:errcheck + cityName := mc.name + if cityName == "" { + cityName = filepath.Base(path) + } + fmt.Fprintf(stdout, "Unregistered city '%s', stopping...\n", cityName) //nolint:errcheck stopErr := stopManagedCity(mc, path, stderr) // Clear backoff so re-registering starts immediately. cr.BatchUpdate(func( @@ -927,19 +930,19 @@ func reconcileCities( evType := events.CityUnregistered var payload []byte if stopErr == nil { - fmt.Fprintf(stdout, "City '%s' stopped.\n", name) //nolint:errcheck - p, _ := json.Marshal(api.CityUnregisteredPayload{Name: name, Path: path}) + fmt.Fprintf(stdout, "City '%s' stopped.\n", cityName) //nolint:errcheck + p, _ := json.Marshal(api.CityUnregisteredPayload{Name: cityName, Path: path}) payload = p } else { evType = events.CityUnregisterFailed - p, _ := json.Marshal(api.CityUnregisterFailedPayload{Name: name, Path: path, Error: stopErr.Error()}) + p, _ := json.Marshal(api.CityUnregisterFailedPayload{Name: cityName, Path: path, Error: stopErr.Error()}) payload = p } if fr, frErr := events.NewFileRecorder(filepath.Join(path, ".gc", "events.jsonl"), stderr); frErr == nil { fr.Record(events.Event{ Type: evType, Actor: "gc", - Subject: name, + Subject: cityName, Payload: payload, }) fr.Close() //nolint:errcheck // best-effort @@ -1319,6 +1322,7 @@ func reconcileCities( } cs.ct = cityRuntime.crashTrack() cs.pokeCh = pokeCh + cs.configDirty = configDirty cs.services = cityRuntime.svc cs.startBeadEventWatcher(cityCtx) cityRuntime.setControllerState(cs) diff --git a/cmd/gc/cmd_supervisor_city_test.go b/cmd/gc/cmd_supervisor_city_test.go index 4626889a8e..fb62b3f890 100644 --- a/cmd/gc/cmd_supervisor_city_test.go +++ b/cmd/gc/cmd_supervisor_city_test.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "encoding/json" "io" "net" "os" @@ -1137,6 +1138,57 @@ func TestUnregisterCityFromSupervisorReturnsReloadFailureWhenCityDirMissing(t *t } } +func TestReconcileCitiesUnregisterEventUsesManagedCityName(t *testing.T) { + t.Setenv("GC_HOME", t.TempDir()) + + cityPath := filepath.Join(t.TempDir(), "basename-city") + if err := os.MkdirAll(cityPath, 0o755); err != nil { + t.Fatal(err) + } + + done := make(chan struct{}) + close(done) + registry := newCityRegistry() + registry.Add(cityPath, &managedCity{ + name: "effective-city", + started: true, + cancel: func() {}, + done: done, + }) + + reg := supervisor.NewRegistry(supervisor.RegistryPath()) + var stdout, stderr bytes.Buffer + reconcileCities(reg, registry, supervisor.PublicationConfig{}, &stdout, &stderr) + + recorded, err := events.ReadAll(filepath.Join(cityPath, ".gc", "events.jsonl")) + if err != nil { + t.Fatalf("ReadAll(events): %v", err) + } + if len(recorded) != 1 { + t.Fatalf("recorded %d events, want 1", len(recorded)) + } + got := recorded[0] + if got.Type != events.CityUnregistered { + t.Fatalf("event.Type = %q, want %q", got.Type, events.CityUnregistered) + } + if got.Subject != "effective-city" { + t.Fatalf("event.Subject = %q, want effective-city", got.Subject) + } + var payload struct { + Name string `json:"name"` + Path string `json:"path"` + } + if err := json.Unmarshal(got.Payload, &payload); err != nil { + t.Fatalf("json.Unmarshal(payload): %v", err) + } + if payload.Name != "effective-city" { + t.Fatalf("payload.Name = %q, want effective-city", payload.Name) + } + if payload.Path != cityPath { + t.Fatalf("payload.Path = %q, want %q", payload.Path, cityPath) + } +} + func TestUnregisterCityFromSupervisorRestoresRegistrationWhenControllerStopWaitFails(t *testing.T) { gcHome := t.TempDir() t.Setenv("GC_HOME", gcHome) diff --git a/cmd/gc/controller.go b/cmd/gc/controller.go index c550df4808..cb0664b705 100644 --- a/cmd/gc/controller.go +++ b/cmd/gc/controller.go @@ -1135,6 +1135,7 @@ func runController( cs := newControllerState(ctx, cfg, sp, eventProv, cityName, cityPath) cs.ct = cr.crashTrack() cs.pokeCh = pokeCh + cs.configDirty = configDirty cs.services = cr.svc cs.startBeadEventWatcher(ctx) cr.setControllerState(cs) diff --git a/docs/reference/exec-beads-provider.md b/docs/reference/exec-beads-provider.md index c64629db24..138e58f372 100644 --- a/docs/reference/exec-beads-provider.md +++ b/docs/reference/exec-beads-provider.md @@ -296,10 +296,11 @@ Stdout: the root bead ID as plain text (e.g., `WP-42\n`). ### Status Mapping -Gas City preserves backend status values at the API boundary. Providers -should emit the native status string they support, such as bd's `open`, -`in_progress`, `blocked`, `review`, `testing`, and `closed`. An empty -status is treated as `open`. +Gas City's `beads.Store` surface uses the SDK's three-state vocabulary: +`open`, `in_progress`, and `closed`. Backends that expose a richer status +set must map it onto those three values. The built-in `BdStore`, for +example, maps bd's `blocked`, `review`, and `testing` states to `open`. +An empty status is also treated as `open`. ## Implementation Plan diff --git a/engdocs/architecture/beads.md b/engdocs/architecture/beads.md index b3b30a04eb..8ef533a6c8 100644 --- a/engdocs/architecture/beads.md +++ b/engdocs/architecture/beads.md @@ -180,10 +180,10 @@ enforced by the conformance suite in `internal/beads/beadstest/conformance.go`. `internal/beads/` (and `test/integration/`) directly invokes the bd binary. -14. **BdStore preserves bd's status vocabulary.** bd uses open, - in_progress, blocked, review, testing, closed. Gas City preserves - non-empty status values at the API boundary and only normalizes an - empty backend status to open. +14. **BdStore maps backend statuses onto Gas City's three-state contract.** + bd uses open, in_progress, blocked, review, testing, closed. Gas City + exposes open, in_progress, closed, so BdStore maps blocked/review/testing + to open and normalizes an empty backend status to open. 15. **FileStore uses atomic writes.** Persistence writes go to a temp file first, then `os.Rename` to the target path -- never partial diff --git a/engdocs/architecture/life-of-a-bead.md b/engdocs/architecture/life-of-a-bead.md index 37b46739f3..d6cea8c4ec 100644 --- a/engdocs/architecture/life-of-a-bead.md +++ b/engdocs/architecture/life-of-a-bead.md @@ -347,9 +347,10 @@ mechanism. ``` **Status mapping.** The bd CLI uses six statuses (open, in_progress, -blocked, review, testing, closed). Gas City preserves those non-empty -status values at the API boundary so clients can round-trip bd semantics. -An empty status from a backend is normalized to the store default, open. +blocked, review, testing, closed), but the `beads.Store` contract stays +three-state: open, in_progress, closed. `BdStore` therefore maps bd's +blocked/review/testing values to open. An empty status from a backend is +also normalized to open. ## Code Map diff --git a/internal/api/handler_beads_test.go b/internal/api/handler_beads_test.go index 444def387b..d90542d1a9 100644 --- a/internal/api/handler_beads_test.go +++ b/internal/api/handler_beads_test.go @@ -95,6 +95,28 @@ func (s *prefixedAliasStore) Create(b beads.Bead) (beads.Bead, error) { return s.beadToAlias(created), nil } +type sparseCreateStore struct { + *beads.MemStore +} + +func newSparseCreateStore() *sparseCreateStore { + return &sparseCreateStore{MemStore: beads.NewMemStore()} +} + +func (s *sparseCreateStore) Create(b beads.Bead) (beads.Bead, error) { + created, err := s.MemStore.Create(b) + if err != nil { + return beads.Bead{}, err + } + return beads.Bead{ + ID: created.ID, + Title: created.Title, + Type: created.Type, + Status: created.Status, + CreatedAt: created.CreatedAt, + }, nil +} + func (s *prefixedAliasStore) Get(id string) (beads.Bead, error) { s.getCalls++ b, err := s.base.Get(s.aliasToBase(id)) @@ -632,6 +654,47 @@ func TestBeadCreatePersistsMetadataAndParent(t *testing.T) { } } +func TestBeadCreateResponseUsesAuthoritativeStoredBead(t *testing.T) { + state := newFakeState(t) + store := newSparseCreateStore() + state.stores["myrig"] = store + parent, err := store.Create(beads.Bead{Title: "Parent"}) + if err != nil { + t.Fatalf("Create(parent): %v", err) + } + h := newTestCityHandler(t, state) + + body := `{ + "rig":"myrig", + "title":"Child", + "type":"feature", + "parent":"` + parent.ID + `", + "labels":["urgent"], + "metadata":{"mc.contract.run_id":"run-1"} + }` + req := newPostRequest(cityURL(state, "/beads"), bytes.NewBufferString(body)) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + + if rec.Code != http.StatusCreated { + t.Fatalf("create status = %d, want %d, body: %s", rec.Code, http.StatusCreated, rec.Body.String()) + } + + var created beads.Bead + if err := json.NewDecoder(rec.Body).Decode(&created); err != nil { + t.Fatalf("decode created bead: %v", err) + } + if created.ParentID != parent.ID { + t.Fatalf("response parent = %q, want %q", created.ParentID, parent.ID) + } + if len(created.Labels) != 1 || created.Labels[0] != "urgent" { + t.Fatalf("response labels = %#v, want [urgent]", created.Labels) + } + if created.Metadata["mc.contract.run_id"] != "run-1" { + t.Fatalf("response metadata = %#v, want mc.contract.run_id=run-1", created.Metadata) + } +} + func TestBeadUpdateUsesRoutePrefixStore(t *testing.T) { state, alphaStore, betaStore := configureBeadRouteState(t) created, err := betaStore.Create(beads.Bead{Title: "Routed beta bead"}) diff --git a/internal/api/huma_handlers_beads.go b/internal/api/huma_handlers_beads.go index ec3918a3ac..f64461eb22 100644 --- a/internal/api/huma_handlers_beads.go +++ b/internal/api/huma_handlers_beads.go @@ -312,6 +312,12 @@ func (s *Server) humaHandleBeadCreate(ctx context.Context, input *BeadCreateInpu s.idem.unreserve(idemKey) return nil, huma.Error500InternalServerError(err.Error()) } + + // Some stores return a minimal create envelope and require a follow-up + // read for the canonical persisted bead state. + if persisted, getErr := store.Get(b.ID); getErr == nil { + b = persisted + } s.idem.storeResponse(idemKey, bodyHash, b) return &IndexOutput[beads.Bead]{ diff --git a/internal/beads/bdstore.go b/internal/beads/bdstore.go index b044621a64..78b2a81402 100644 --- a/internal/beads/bdstore.go +++ b/internal/beads/bdstore.go @@ -507,27 +507,9 @@ func (s *BdStore) Create(b Bead) (Bead, error) { if created.Priority == nil && b.Priority != nil { created.Priority = cloneIntPtr(b.Priority) } - if created.ParentID == "" { - created.ParentID = b.ParentID - } - if created.Description == "" { - created.Description = b.Description - } - if len(created.Labels) == 0 && len(b.Labels) > 0 { - created.Labels = append([]string(nil), b.Labels...) - } - if len(created.Needs) == 0 && len(b.Needs) > 0 { - created.Needs = append([]string(nil), b.Needs...) - } if len(metadata) > 0 { if created.Metadata == nil { created.Metadata = maps.Clone(metadata) - } else { - for key, value := range metadata { - if _, ok := created.Metadata[key]; !ok { - created.Metadata[key] = value - } - } } } return created, nil diff --git a/internal/beads/bdstore_test.go b/internal/beads/bdstore_test.go index 402f15d749..8f49b14f5a 100644 --- a/internal/beads/bdstore_test.go +++ b/internal/beads/bdstore_test.go @@ -980,8 +980,8 @@ func TestBdStoreCreateWithLabels(t *testing.T) { if !strings.Contains(args, "--labels owned") { t.Errorf("args = %q, want to contain '--labels owned'", args) } - if len(created.Labels) != 1 || created.Labels[0] != "owned" { - t.Errorf("created.Labels = %#v, want [owned]", created.Labels) + if len(created.Labels) != 0 { + t.Errorf("created.Labels = %#v, want empty until backend confirms labels", created.Labels) } } @@ -1020,8 +1020,43 @@ func TestBdStoreCreateWithParentID(t *testing.T) { if !strings.Contains(args, "--parent bd-parent-1") { t.Errorf("args = %q, want to contain '--parent bd-parent-1'", args) } - if created.ParentID != "bd-parent-1" { - t.Errorf("created.ParentID = %q, want bd-parent-1", created.ParentID) + if created.ParentID != "" { + t.Errorf("created.ParentID = %q, want empty until backend confirms parent", created.ParentID) + } +} + +func TestBdStoreCreateDoesNotBackfillUnconfirmedFields(t *testing.T) { + runner := func(_, _ string, _ ...string) ([]byte, error) { + return []byte(`{"id":"bd-x","title":"test","status":"open","issue_type":"task","created_at":"2025-01-15T10:30:00Z","metadata":{"accepted":"true"}}`), nil + } + s := beads.NewBdStore("/city", runner) + created, err := s.Create(beads.Bead{ + Title: "test", + Description: "local description", + ParentID: "bd-parent-1", + Labels: []string{"owned"}, + Needs: []string{"bd-2"}, + Metadata: map[string]string{ + "local": "value", + }, + }) + if err != nil { + t.Fatal(err) + } + if created.Description != "" { + t.Fatalf("created.Description = %q, want empty until backend confirms it", created.Description) + } + if created.ParentID != "" { + t.Fatalf("created.ParentID = %q, want empty until backend confirms it", created.ParentID) + } + if len(created.Labels) != 0 { + t.Fatalf("created.Labels = %#v, want empty until backend confirms them", created.Labels) + } + if len(created.Needs) != 0 { + t.Fatalf("created.Needs = %#v, want empty until backend confirms them", created.Needs) + } + if len(created.Metadata) != 1 || created.Metadata["accepted"] != "true" { + t.Fatalf("created.Metadata = %#v, want backend metadata only", created.Metadata) } } diff --git a/internal/beads/caching_store.go b/internal/beads/caching_store.go index 6ce8320af9..77b72e9458 100644 --- a/internal/beads/caching_store.go +++ b/internal/beads/caching_store.go @@ -28,6 +28,7 @@ type CachingStore struct { beads map[string]Bead deps map[string][]Dep dirty map[string]struct{} + deletedSeq map[string]uint64 state cacheState lastFreshAt time.Time mutationSeq uint64 @@ -93,11 +94,12 @@ func NewCachingStoreForTest(backing Store, onChange func(eventType, beadID strin func newCachingStore(backing Store, onChange func(eventType, beadID string, payload json.RawMessage)) *CachingStore { return &CachingStore{ - backing: backing, - beads: make(map[string]Bead), - deps: make(map[string][]Dep), - dirty: make(map[string]struct{}), - onChange: onChange, + backing: backing, + beads: make(map[string]Bead), + deps: make(map[string][]Dep), + dirty: make(map[string]struct{}), + deletedSeq: make(map[string]uint64), + onChange: onChange, problemf: func(msg string) { log.Printf("beads cache: %s", msg) }, @@ -127,11 +129,15 @@ func (c *CachingStore) PrimeActive() error { defer c.mu.Unlock() for _, b := range all { if c.mutationSeq != startSeq { + if c.deletedSeq[b.ID] > startSeq { + continue + } if _, exists := c.beads[b.ID]; exists { continue } } c.beads[b.ID] = cloneBead(b) + delete(c.deletedSeq, b.ID) } if c.state == cacheUninitialized { c.state = cachePartial @@ -182,12 +188,17 @@ func (c *CachingStore) Prime(_ context.Context) error { c.beads = beadMap c.deps = depMap c.dirty = make(map[string]struct{}) + c.deletedSeq = make(map[string]uint64) } else { for id, b := range beadMap { + if c.deletedSeq[id] > startSeq { + continue + } if _, exists := c.beads[id]; exists { continue } c.beads[id] = b + delete(c.deletedSeq, id) if deps, ok := depMap[id]; ok { c.deps[id] = deps } diff --git a/internal/beads/caching_store_events.go b/internal/beads/caching_store_events.go index 3d2c932fae..3603bc2179 100644 --- a/internal/beads/caching_store_events.go +++ b/internal/beads/caching_store_events.go @@ -34,17 +34,20 @@ func (c *CachingStore) ApplyEvent(eventType string, payload json.RawMessage) { if _, exists := c.beads[b.ID]; !exists { c.beads[b.ID] = cloneBead(b) delete(c.dirty, b.ID) + delete(c.deletedSeq, b.ID) c.updateStatsLocked() } case "bead.updated": c.beads[b.ID] = cloneBead(b) delete(c.dirty, b.ID) + delete(c.deletedSeq, b.ID) case "bead.closed": if _, exists := c.beads[b.ID]; !exists { c.updateStatsLocked() } c.beads[b.ID] = cloneBead(b) delete(c.dirty, b.ID) + delete(c.deletedSeq, b.ID) default: return } @@ -62,6 +65,7 @@ func (c *CachingStore) ApplyDepEvent(beadID string, deps []Dep) { } c.deps[beadID] = cloneDeps(deps) delete(c.dirty, beadID) + delete(c.deletedSeq, beadID) c.markFreshLocked(time.Now()) c.updateStatsLocked() } diff --git a/internal/beads/caching_store_reads.go b/internal/beads/caching_store_reads.go index 88fdc222a4..d0565472cf 100644 --- a/internal/beads/caching_store_reads.go +++ b/internal/beads/caching_store_reads.go @@ -92,6 +92,7 @@ func (c *CachingStore) refreshCachedBeads(items []Bead) { for _, item := range items { c.beads[item.ID] = cloneBead(item) delete(c.dirty, item.ID) + delete(c.deletedSeq, item.ID) } c.markFreshLocked(time.Now()) c.updateStatsLocked() @@ -124,6 +125,7 @@ func (c *CachingStore) Get(id string) (Bead, error) { c.mu.Lock() c.beads[id] = cloneBead(fresh) delete(c.dirty, id) + delete(c.deletedSeq, id) c.markFreshLocked(time.Now()) c.updateStatsLocked() c.mu.Unlock() diff --git a/internal/beads/caching_store_reconcile.go b/internal/beads/caching_store_reconcile.go index 304af232ad..6116a2e4e6 100644 --- a/internal/beads/caching_store_reconcile.go +++ b/internal/beads/caching_store_reconcile.go @@ -136,6 +136,7 @@ func (c *CachingStore) runReconciliation() { c.beads = freshByID c.deps = nextDeps c.dirty = make(map[string]struct{}) + c.deletedSeq = make(map[string]uint64) c.syncFailures = 0 if c.state == cacheDegraded { c.state = cacheLive diff --git a/internal/beads/caching_store_test.go b/internal/beads/caching_store_test.go index e49b294587..05145e09ed 100644 --- a/internal/beads/caching_store_test.go +++ b/internal/beads/caching_store_test.go @@ -105,6 +105,45 @@ func TestCachingStorePrimePreservesConcurrentUpdate(t *testing.T) { } } +func TestCachingStorePrimeDoesNotResurrectConcurrentDelete(t *testing.T) { + mem := beads.NewMemStore() + original, err := mem.Create(beads.Bead{Title: "before delete"}) + if err != nil { + t.Fatalf("Create: %v", err) + } + started := make(chan struct{}) + release := make(chan struct{}) + backing := &primeRaceStore{ + Store: mem, + started: started, + release: release, + stale: []beads.Bead{original}, + } + cs := beads.NewCachingStoreForTest(backing, nil) + + done := make(chan error, 1) + go func() { + done <- cs.Prime(context.Background()) + }() + + <-started + if err := cs.Delete(original.ID); err != nil { + t.Fatalf("Delete: %v", err) + } + close(release) + if err := <-done; err != nil { + t.Fatalf("Prime: %v", err) + } + + got, err := cs.ListOpen() + if err != nil { + t.Fatalf("ListOpen: %v", err) + } + if len(got) != 0 { + t.Fatalf("ListOpen = %#v, want deleted bead to stay absent", got) + } +} + func TestCachingStoreGetRefreshesStaleCachedBead(t *testing.T) { mem := beads.NewMemStore() original, err := mem.Create(beads.Bead{Title: "before update"}) @@ -235,6 +274,42 @@ func TestCachingStoreUpdateReflectsWriteIntentWhenImmediateReadIsStale(t *testin } } +func TestCachingStoreUpdateDoesNotDuplicateAuthoritativeLabels(t *testing.T) { + mem := beads.NewMemStore() + original, err := mem.Create(beads.Bead{ + Title: "root", + Labels: []string{"root"}, + }) + if err != nil { + t.Fatalf("Create: %v", err) + } + cs := beads.NewCachingStoreForTest(mem, nil) + if err := cs.Prime(context.Background()); err != nil { + t.Fatalf("Prime: %v", err) + } + + if err := cs.Update(original.ID, beads.UpdateOpts{Labels: []string{"verified"}}); err != nil { + t.Fatalf("Update: %v", err) + } + + got, err := cs.ListOpen() + if err != nil { + t.Fatalf("ListOpen: %v", err) + } + if len(got) != 1 { + t.Fatalf("ListOpen returned %d beads, want 1", len(got)) + } + verifiedCount := 0 + for _, label := range got[0].Labels { + if label == "verified" { + verifiedCount++ + } + } + if verifiedCount != 1 { + t.Fatalf("labels = %#v, want exactly one verified label", got[0].Labels) + } +} + type staleReadAfterUpdateStore struct { beads.Store mu sync.Mutex diff --git a/internal/beads/caching_store_writes.go b/internal/beads/caching_store_writes.go index d0096de168..c53793cec6 100644 --- a/internal/beads/caching_store_writes.go +++ b/internal/beads/caching_store_writes.go @@ -17,6 +17,7 @@ func (c *CachingStore) Create(b Bead) (Bead, error) { c.mutationSeq++ c.beads[created.ID] = cloneBead(created) delete(c.dirty, created.ID) + delete(c.deletedSeq, created.ID) c.markFreshLocked(time.Now()) c.updateStatsLocked() c.mu.Unlock() @@ -41,12 +42,11 @@ func (c *CachingStore) Update(id string, opts UpdateOpts) error { return nil } - fresh = applyUpdateOptsToBead(fresh, opts) - c.mu.Lock() c.mutationSeq++ c.beads[id] = cloneBead(fresh) delete(c.dirty, id) + delete(c.deletedSeq, id) c.markFreshLocked(time.Now()) c.updateStatsLocked() c.mu.Unlock() @@ -55,56 +55,6 @@ func (c *CachingStore) Update(id string, opts UpdateOpts) error { return nil } -func applyUpdateOptsToBead(b Bead, opts UpdateOpts) Bead { - b = cloneBead(b) - if opts.Title != nil { - b.Title = *opts.Title - } - if opts.Status != nil { - b.Status = *opts.Status - } - if opts.Type != nil { - b.Type = *opts.Type - } - if opts.Priority != nil { - b.Priority = cloneIntPtr(opts.Priority) - } - if opts.Description != nil { - b.Description = *opts.Description - } - if opts.ParentID != nil { - b.ParentID = *opts.ParentID - } - if opts.Assignee != nil { - b.Assignee = *opts.Assignee - } - if len(opts.Metadata) > 0 { - if b.Metadata == nil { - b.Metadata = make(map[string]string, len(opts.Metadata)) - } - for k, v := range opts.Metadata { - b.Metadata[k] = v - } - } - if len(opts.Labels) > 0 { - b.Labels = append(b.Labels, opts.Labels...) - } - if len(opts.RemoveLabels) > 0 { - remove := make(map[string]bool, len(opts.RemoveLabels)) - for _, label := range opts.RemoveLabels { - remove[label] = true - } - kept := b.Labels[:0] - for _, label := range b.Labels { - if !remove[label] { - kept = append(kept, label) - } - } - b.Labels = kept - } - return b -} - // Close marks a bead as closed in the backing store and cache. func (c *CachingStore) Close(id string) error { if err := c.backing.Close(id); err != nil { @@ -127,6 +77,7 @@ func (c *CachingStore) Close(id string) error { b.Status = "closed" c.beads[id] = b delete(c.dirty, id) + delete(c.deletedSeq, id) closed = cloneBead(b) found = true c.markFreshLocked(time.Now()) @@ -134,6 +85,7 @@ func (c *CachingStore) Close(id string) error { } else if found { c.beads[id] = cloneBead(closed) delete(c.dirty, id) + delete(c.deletedSeq, id) c.markFreshLocked(time.Now()) c.updateStatsLocked() } @@ -182,6 +134,7 @@ func (c *CachingStore) CloseAll(ids []string, metadata map[string]string) (int, previous, hadPrevious := c.beads[item.id] c.beads[item.id] = cloneBead(item.bead) delete(c.dirty, item.id) + delete(c.deletedSeq, item.id) if item.bead.Status == "closed" { delete(c.deps, item.id) } @@ -214,6 +167,7 @@ func (c *CachingStore) SetMetadata(id, key, value string) error { b.Metadata[key] = value c.beads[id] = b delete(c.dirty, id) + delete(c.deletedSeq, id) } c.markFreshLocked(time.Now()) c.updateStatsLocked() @@ -238,6 +192,7 @@ func (c *CachingStore) SetMetadataBatch(id string, kvs map[string]string) error } c.beads[id] = b delete(c.dirty, id) + delete(c.deletedSeq, id) } c.markFreshLocked(time.Now()) c.updateStatsLocked() @@ -259,6 +214,7 @@ func (c *CachingStore) DepAdd(issueID, dependsOnID, depType string) error { deps[i].Type = depType c.deps[issueID] = deps delete(c.dirty, issueID) + delete(c.deletedSeq, issueID) c.markFreshLocked(time.Now()) c.updateStatsLocked() c.mu.Unlock() @@ -267,6 +223,7 @@ func (c *CachingStore) DepAdd(issueID, dependsOnID, depType string) error { } c.deps[issueID] = append(deps, Dep{IssueID: issueID, DependsOnID: dependsOnID, Type: depType}) delete(c.dirty, issueID) + delete(c.deletedSeq, issueID) c.markFreshLocked(time.Now()) c.updateStatsLocked() c.mu.Unlock() @@ -286,6 +243,7 @@ func (c *CachingStore) DepRemove(issueID, dependsOnID string) error { if d.DependsOnID == dependsOnID { c.deps[issueID] = append(deps[:i], deps[i+1:]...) delete(c.dirty, issueID) + delete(c.deletedSeq, issueID) break } } @@ -306,6 +264,7 @@ func (c *CachingStore) Delete(id string) error { delete(c.beads, id) delete(c.deps, id) delete(c.dirty, id) + c.deletedSeq[id] = c.mutationSeq c.markFreshLocked(time.Now()) c.updateStatsLocked() c.mu.Unlock() diff --git a/specs/architecture.md b/specs/architecture.md new file mode 100644 index 0000000000..9d1dbbe3a4 --- /dev/null +++ b/specs/architecture.md @@ -0,0 +1,555 @@ +# Gas City Architecture + +This spec captures the architectural invariants Gas City has +converged on. It is a normative document: future contributions that +violate these invariants are wrong unless a conscious decision in +this spec changes. Plans in `plans/archive/` describe the journeys +that produced these invariants; this spec describes the +destination. + +Two architectural themes run through everything below: + +1. **The object model is the center; the CLI and the HTTP + SSE API + are projections over it.** One canonical domain, two typed + surfaces. +2. **Typed data end-to-end.** Go structs with annotations drive a + generated OpenAPI 3.1 contract; every wire-visible shape appears + in the spec; consumers in any language code against the same + contract. Zero opacity on the wire. + +## 1. The object model + +`internal/{beads, mail, convoy, formula, agent, events, session, +sling, graphroute, agentutil, pathutil, ...}` is the canonical +domain. All business logic lives there. The two surfaces below call +into it; neither re-implements validation, routing, or invariants. + +``` +cmd/gc/cmd_*.go internal/api/handler_*.go + (arg parsing, (Huma input/output types, + text formatting, handler bodies, + exit codes) typed error returns) + \ / + \ / + v v + internal/sling/ internal/convoy/ + internal/agentutil/ internal/graphroute/ + internal/pathutil/ + | + v + internal/{beads, config, formula, molecule, agent, events, ...} +``` + +### Invariants + +- **Domain code has no I/O surfacing.** No `fmt.Fprintf`, no + `io.Writer` parameters, no HTTP responses. Domain functions + return values and errors. Text formatting is a CLI concern; JSON + shaping is an API concern. +- **Narrow interfaces over flag bags.** Domain-side dependencies + use focused interfaces (`AgentResolver`, `BeadRouter`, + `Notifier`, `BranchResolver`) validated at construction. +- **Intent-based APIs.** Callers express intent (`RouteBead`, + `LaunchFormula`, `AttachFormula`, `ExpandConvoy`); implementation + decides how (shell command, direct store, API call). No + god-struct option bags passed around. +- **No upward dependencies.** A lower layer never imports from a + higher layer. + +## 2. Projections: CLI and HTTP + SSE API + +### CLI projection (`cmd/gc/`) + +The CLI calls the core library directly. It is not a generic +remote client; it coexists with a local supervisor in the same city +by routing through HTTP only when lock coordination requires it. + +Concretely, `cmd/gc/apiroute.go:apiClient()` implements this rule: + +- **No running local supervisor** → CLI calls the core library + directly against the on-disk stores. +- **Running local supervisor with mutations allowed** → CLI routes + the mutation through the local HTTP API via the generated Go + client. The supervisor executes the mutation under its own + locks; the CLI's result is consistent with the supervisor's + state. + +Remote access is not the first-class reason this path exists. A +`--base-url http://remote:port` invocation is a side effect of the +same mechanism, not its purpose. The generated client is "library +calls dispatched over HTTP when we have to cross a process +boundary we didn't create." + +### API projection (`internal/api/`) + +Every HTTP + SSE endpoint is registered through Huma against +annotated Go types. Huma generates the OpenAPI 3.1 spec from those +types; the spec drives everything downstream. + +### The generated Go client + +`internal/api/genclient/` has three in-tree consumer categories, +governed by a structural rule: **direct consumption is allowed for +endpoints that (a) do not participate in write-side fallback (no +`ShouldFallback` path) and (b) do not require domain-type conversion +at the adapter seam.** Anything that fails either test goes through +`internal/api/client.go`. + +1. **CLI mutation coordination** via `internal/api/client.go`, used + by `cmd/gc/apiroute.go` as described above. This is the only + consumer for paths that mutate state and could race an in-process + supervisor, or that need domain-type conversion (e.g. typed + `session.SubmitIntent` from a string wire field). The adapter + also owns local-file fallback when the controller isn't running. +2. **Read/stream CLI surfaces that import genclient directly** — + currently `cmd/gc/cmd_events.go`, which calls typed methods for + event listing and SSE following. Events have no write-side + fallback (no bus without a controller) and need no domain-type + conversion, so they satisfy the structural rule. Future + read-only CLI surfaces that meet the same two conditions are + allowed to import genclient directly; no case-by-case approval + needed. +3. **Layer 2 conformance probe** — + `genclient_roundtrip_test.go` exercises every generated method + against a real supervisor so spec/reality drift fails CI. + +The generated client is not promoted as a public Go SDK for +external consumers. External Go consumers, if they ever appear, +get a supported surface at that point; until then the `internal/` +location is load-bearing. + +### The dashboard projection + +The dashboard is a static TypeScript SPA served by a tiny Go +binary (`cmd/gc/dashboard/`) whose only jobs are to embed the +compiled bundle and inject the supervisor URL into `index.html`. +The SPA talks directly to the supervisor's typed OpenAPI endpoints +from the browser — the dashboard server is NOT an API proxy. The +dashboard server also hosts one narrow operational debug endpoint +(`/__client-log`) that accepts browser error logs for centralized +debugging; this endpoint is intentionally outside the typed HTTP + +SSE control plane and may use standard `encoding/json` for body +decoding. + +## 3. The typed-wire principle + +The invariants below apply to every operation under `internal/api/` +except the `/svc/*` workspace-service proxy (see §5). + +### 3.1 Annotations drive the live implementation + +Each endpoint is a Go function whose signature (typed input struct, +typed output struct) plus a `huma.Operation` value IS the endpoint +definition. Huma binds it, validates it, routes it, serializes it, +schema-describes it. There is no second description of the endpoint +anywhere — not in a router table, not in an OpenAPI YAML, not in a +client stub. + +### 3.2 Spec is generated, never hand-written + +`internal/api/openapi.json` and `docs/schema/openapi.json` are +outputs of `cmd/genspec`, which reads the live Huma registration +from a `SupervisorMux`. The pre-commit hook regenerates both on +every Go-file commit. `TestOpenAPISpecInSync` fails CI if the +committed spec drifts from what the supervisor serves. + +### 3.3 The routes we register ARE the routes we expose + +Per-city operations live at `/v0/city/{cityName}/...`. +Supervisor-scope operations live at their top-level paths. No +shadow mapping. No `prefix-strip-and-forward`. No client-side +path-rewrite helpers. The existence of such a helper is direct +evidence the spec disagrees with reality and is a bug to fix. + +### 3.4 No hand-constructed JSON for domain data + +Every wire byte that represents a domain value comes from encoding +a typed Go struct (schema-registered with Huma) through the +standard JSON encoder, directly or via Huma's own serialization +machinery. This principle forbids three anti-patterns specifically: + +- `json.Marshal(map[string]any{...})` — untyped input. +- `fmt.Sprintf`-built JSON strings — hand-constructed shape. +- `json.Marshal(anyInterfaceValue)` where the interface carries + values whose types are not schema-registered — hides the shape + from the spec. + +The test a reviewer applies: *is there any line in your code that +produces JSON-shaped output from non-typed or map-typed input?* If +yes, violation. If every JSON byte comes from `encoder.Encode` of a +typed, schema-registered struct, the principle holds. + +Protocol framing around domain data — HTTP status codes, HTTP +response headers, SSE `id:` / `event:` / `data:` / retry line +separators, chunked-encoding bytes — is not domain data and is not +in scope for this principle. The carve-out is direction-symmetric +and covers two specific files: `internal/api/sse.go` (emitter) +hand-writes the SSE protocol-text lines around a typed +`encoder.Encode(data)` call on a registered struct, and +`cmd/gc/cmd_events.go:sseDecoder` (consumer) hand-parses the same +SSE protocol-text lines and `json.Unmarshal`s the `data:` payload +into typed `genclient.*` structs. In both directions the domain +payload IS framework-encoded/decoded; the surrounding protocol +literals are not JSON at all. + +New SSE endpoints must register through `registerSSE` / +`registerSSEStringID`; ad-hoc SSE handlers outside those helpers +are not covered by this carve-out. + +Edge cases that are NOT wire and therefore exempt: + +- SQL/BLOB (de)serialization in storage packages. +- Hashing request bodies for idempotency keys. +- Parsing stored JSONL transcript/log files from disk. +- Parsing external-tool output we don't own (provider CLI stdout, + provider auth files like `~/.codex/auth.json`). +- Internal event-bus `[]byte` payloads between in-process emitters + and consumers (these become typed at the wire via the registry — + see §4). + +Custom `MarshalJSON` / `UnmarshalJSON` on wire types are forbidden +with two narrow, documented exceptions: + +- **`SessionRawMessageFrame`** (`internal/api/session_frame_types.go`) + — the raw-frame pass-through for provider-native session + transcripts; forwards arbitrary JSON the provider wrote. See §3.6. +- **`EventPayloadUnion`** (`internal/api/convoy_event_stream.go`) + — the wire wrapper around `events.Payload` that emits the typed + payload as a named `oneOf` component. Its `MarshalJSON` emits + the concrete variant directly (so the wire sees `{"rig":...}` + rather than a wrapper object); its Schema method registers and + refs the named component. Required to get a single named + `EventPayload` component schema that Go and TS clients can both + consume. + +### 3.5 Typed structs for every shape knowable at compile time + +Every response field, every SSE event payload, every input body is +a named Go struct with real fields and Huma tags. No +`json.RawMessage` or `map[string]any` in the typed control plane, +with exactly one class of exception (§3.6). + +"Heterogeneous", "opaque", "clients render it generically", "we'll +figure out the union later", and "it's just internal" are not +qualifying exceptions. If our code constructs the map, we know the +keys. Make it a struct. + +### 3.5.1 No hidden inputs — every accepted parameter appears in the spec + +Every input a handler reads MUST be a typed field on its Huma +input struct (`path:`, `query:`, `header:`, or `Body`). The +generated OpenAPI spec is the complete and exhaustive description +of the inputs an endpoint accepts. Running a request through a +handler must not produce a different outcome than running the same +request through the spec. + +Three anti-patterns are specifically forbidden: + +- **Dynamic or wildcard query parameters.** Any scheme where a + handler accepts query keys matching a pattern (`var.*`, `meta_*`, + `x-*`) rather than declared names. OpenAPI 3.1 cannot express + wildcard query keys; accepting them creates a hidden contract + the spec cannot describe. When a handler needs an open-ended + string-to-string dictionary as input, move the input into a + typed request body field (`Vars map[string]string` on a POST + body). Dictionary bodies have a schema; dictionary query + parameters do not. +- **Resolvers that read raw URL query or header values that + aren't declared input fields.** `huma.Resolver` implementations + may validate or normalize values the struct already declares, + but may not read keys off `ctx.URL().Query()` or `ctx.Header()` + that aren't present on the input struct. If a resolver needs a + value, that value is a declared field — no exceptions. +- **Presence-vs-empty semantics not expressible in JSONSchema.** + If a handler behaves differently for "parameter absent" vs + "parameter present with empty value", the input field must be a + pointer type (`*string`) so the distinction appears on the wire + contract. Resolver-based presence flags hide the semantics. + +The test a reviewer applies: does running an undeclared query +parameter or an undeclared body field through the handler change +its behavior? If yes, violation. The spec is the contract; the +handler does not get a second, private contract the spec doesn't +know about. + +Huma does not reject undeclared query parameters by default +(they are silently ignored). That is not permission to rely on +them — silent acceptance of undeclared parameters is a property +of the framework, not a blessing of hidden contract. Callers that +send undeclared parameters are sending noise; handlers that read +them are violating this principle. + +### 3.6 Raw pass-through for provider-native session frames + +Session transcript streaming and query endpoints forward +provider-native frames with full fidelity. Each response/envelope +identifies the producing provider via a `provider` field whose +value is one of the known provider keys (`claude`, `codex`, +`gemini`, `open-code`, etc.); each frame's JSON is emitted verbatim +as the provider wrote it, with no GC-side interpretation. +Consumers parse frames using provider-specific logic on their side, +keyed by the provider identifier on the envelope. + +The single JSON-pass-through wire type is `SessionRawMessageFrame` +(`internal/api/session_frame_types.go`). Its Schema method emits +an "any JSON value" schema because Gas City does not own the +shape of provider frames. Publishing typed wire schemas for +provider frames would claim a contract we don't own: a provider +could change its frame shape tomorrow and the spec would silently +lie until regenerated. Honest opacity with a provider discriminator +is the right design. + +Passing through externally-authored shapes is not a license to +also opacify our own shapes that happen to be nested near them. +Every GC-owned field on the same envelope as the raw frames +(envelope metadata, provider identifier, session info) stays +typed. + +### 3.7 Every event type has a typed wire payload + +See §4. + +### 3.8 Error responses are typed too + +Every error returned by a Huma handler is a +`huma.StatusError`-producing call with a real problem-details +body. No `apiError{}` shortcuts. No hand-written `writeError`. + +For the outermost panic-recovery middleware (which must run before +Huma enters the stack), error bodies are pre-serialized +`application/problem+json` byte constants — one `var` declaration +per well-known error, no runtime `json.Marshal`. The constants +live in `internal/api/middleware.go` as `problemBody` values. + +### 3.9 `/svc/*` is the only exclusion + +`/svc/*` is a raw pass-through to external service processes that +own their own contracts. It is explicitly not a typed API +surface. This is the single carved-out path inside `internal/api/`. +If `/svc/*` ever becomes typed, it gets its own migration. + +## 4. Event typing (the registry) + +Events are a first-class part of the typed wire contract. Both the +SSE streams (`/v0/events/stream`, +`/v0/city/{cityName}/events/stream`) and the list endpoints +(`GET /v0/events`, `GET /v0/city/{cityName}/events`) describe their +`payload` field as a named `oneOf` union covering every registered +`events.Payload` shape. There is no opaque `payload: {}` anywhere +on the wire. + +### Mechanism + +- **Bus layer (`internal/events`)** stores payloads as `[]byte` so + it stays domain-agnostic. `events.Event` and `events.TaggedEvent` + are bus-internal types only; they are never returned directly + from an HTTP handler. +- **Registry (`internal/events/payload.go`)** holds the event-type + → Go-type mapping. `events.RegisterPayload(typeConst, sample)` + associates a constant with a sample value of a type implementing + the sealed `events.Payload` interface. `events.DecodePayload` + turns bus bytes back into the registered typed value. +- **Emitters** take values of `events.Payload` rather than + `map[string]any`. The sealed interface keeps ad-hoc shapes out + of emission sites at compile time. +- **Wire projection** — the API-layer `WireEvent` / + `WireTaggedEvent` types (list) and `eventStreamEnvelope` / + `taggedEventStreamEnvelope` (SSE) carry a typed `Payload` field + wrapped in `EventPayloadUnion`. `EventPayloadUnion.Schema` + registers a named `EventPayload` component whose schema is a + `oneOf` of every registered payload type. + +### Registry coverage + +Every constant in `events.KnownEventTypes` MUST have a registered +payload. Events that carry no structured data register +`events.NoPayload` — a typed empty struct that still produces a +named schema variant so the wire stays uniform across event types. + +`TestEveryKnownEventTypeHasRegisteredPayload` fails CI if a new +constant is added without registration; that's how the registry +discipline stays load-bearing rather than best-effort. + +**Decode-failure policy (uniform across list and stream).** Decode +failures and unregistered event types are omitted from list and +stream output and logged via `log.Printf`; the wire never carries +a degraded envelope with nil payload. A malformed event is a CI +bug (the registry-coverage test above catches it before prod); +emitting a typed envelope with `payload: null` would train +consumers to tolerate broken payloads, defeating the point of +§3.4. Clean omission plus a loud log is the contract. + +### Discrimination design + +The envelope carries a plain `type: string` field; the `payload` +field is the discriminated `oneOf` union. Consumers switch on +`type` and narrow `payload` explicitly: + +```typescript +if (event.type === "mail.sent") { + use(event.payload as MailEventPayload); +} +``` + +Envelope-level discrimination — each event-type constant pinned +as a `type` const in its own envelope variant, with OpenAPI 3.1 +discriminators giving consumers automatic narrowing — would be +nicer. It is not the design because no current Go OpenAPI client +generator produces a workable Go type from envelope-level +`oneOf`: + +- **oapi-codegen** collapses the envelope to a `json.RawMessage` + wrapper that loses all field access — `cmd/gc/cmd_events.go`'s + field-based construction breaks. +- **ogen** drops `text/event-stream` operations entirely — + the events streams disappear from the generated client. + +The payload-field-union design is the current ceiling. Every +payload variant is still fully typed on the wire; consumers narrow +explicitly rather than getting automatic discriminator narrowing. +See §6 for the full tooling note. + +## 5. Developer workflow + +The invariants above exist so the developer's contribution to the +HTTP + SSE surface is Go code only. Tooling produces everything +else. + +### Adding or changing a REST operation + +1. Edit or add input/output struct types with Huma tags + (`json:"..."`, `minLength:"1"`, `required:"true"`, etc.). +2. Write the handler function; register via `huma.Register` (or + the `cityGet` / `cityPost` / `cityPatch` / etc. helpers in + `internal/api/city_scope.go` for per-city scoped operations). +3. Commit. Pre-commit regenerates `internal/api/openapi.json`, + `docs/schema/openapi.json`, `internal/api/genclient/`, and the + TS types under `cmd/gc/dashboard/web/src/generated/`. Mintlify + publishes the spec on the next docs build. + +### Adding or changing an event type + +1. Add the constant to `internal/events/events.go` and append it + to `events.KnownEventTypes`. +2. Define a typed payload struct implementing `events.Payload` (a + trivial `IsEventPayload()` method), or use `events.NoPayload` + for events whose envelope fields alone capture the semantics. +3. Call `events.RegisterPayload(constant, sample)` from an + `init()` in the domain package that owns the event (e.g. + `internal/api/event_payloads.go` for mail/bead; + `internal/extmsg/events.go` for extmsg). +4. Commit. Pre-commit regenerates the discriminated-union wire + schema; generated clients gain the new typed variant + automatically. + +### CI guards + +Skipping any step lands on a CI failure, not a production bug: + +| Miss | Caught by | +|---|---| +| Spec not regenerated after Go-type change | `TestOpenAPISpecInSync` | +| Generated Go client out of sync with spec | `TestGeneratedClientInSync` | +| Handler response field undeclared in spec | Layer 1 response-validation tests | +| Spec/client method-shape drift | Layer 2 round-trip tests (`genclient_roundtrip_test.go`) | +| End-to-end binary wire regression | Layer 3 integration tests (`//go:build integration`) | +| New event-type constant without registered payload | `TestEveryKnownEventTypeHasRegisteredPayload` | +| Hard-coded SPA `/v0/...` path outside typed client | TypeScript build (`satisfies SpecPath` in `api.ts`) | + +## 6. Tooling landscape + +Principle 7's "payload-field-level discrimination rather than +envelope-level" is a Go-tooling constraint, not a principled +preference. The TypeScript and Go ecosystems differ on what they +support; this section records what we evaluated and what we use +per language. + +### Go (server-side Huma, client via oapi-codegen) + +- **Huma v2** — server framework. Generates OpenAPI 3.1 from + annotated Go types; we use it for every typed endpoint. Emits a + 3.0 downgrade on request for consumers that still need 3.0. +- **oapi-codegen** — our current Go client generator. Supports + OpenAPI 3.0 (we feed it the downgrade from Huma). When given + envelope-level `oneOf`, it generates `struct { union + json.RawMessage }` with `AsX`/`FromX`/`MergeX` accessor methods. + That shape breaks field-based construction in + `cmd/gc/cmd_events.go`. It does generate typed request methods + for SSE endpoints, but does not parse SSE frames — the caller + handles framing. +- **ogen** — evaluated via spike. Refuses `text/event-stream` + content type entirely; every SSE endpoint is dropped from the + generated client. With `ignore_not_implemented: all`, ogen + produces clean REST types but drops SSE operations Gas City is + built on. Not viable. +- **openapi-generator** (Java-based) — breaks the pure-Go toolchain + and generates less-idiomatic Go. +- **Commercial SDK generators** (Speakeasy, Fern, Stainless) — + generate typed Go SSE clients including envelope-level `oneOf` + handling. Not open source; paid plans start at ~$250/mo. + +The payload-field-union `EventPayload` design (Principle 7) is the +current ceiling under open-source Go tooling. Revisit if +oapi-codegen's experimental 3.1/3.2-aware branch stabilizes or if +another open-source Go generator ships envelope-level `oneOf` plus +SSE that works with our shape. + +### TypeScript (dashboard SPA) + +- **`openapi-fetch`** — typed `fetch` wrapper, the tool the + dashboard uses for every REST call site. Typed path/body/response + against `openapi-typescript`-generated `schema.d.ts`. Minimal + runtime, well-documented, keeps REST call-site code short. Does + not handle SSE — that's what drives the dual-tool design below. +- **`@hey-api/openapi-ts`** — open-source generator the dashboard + uses exclusively for SSE. Generates typed stream functions using + `fetch()` + `ReadableStream` (not `EventSource`), which means + custom auth headers work, retry with exponential backoff is + built in, and each stream has typed discriminated-union response + types keyed by the SSE `event` name. `sse.ts` is a thin callback + bridge over the generated `streamSupervisorEvents`, + `streamEvents`, and `streamSession` functions; the per-frame + JSON parsing, line buffering, and retry are all framework code. +- **`openapi-typescript-codegen`** — unmaintained. +- **OpenAPI Generator** (Java) — same pure-toolchain concern as Go. + +The dual-tool design is pragmatic, not aspirational: each library +handles what it's good at. `openapi-fetch` is the minimal typed +surface for REST consumers (kept because it has zero impact on +call-site code and the ecosystem has shifted to hey-api slowly +enough that we'd gain nothing by churning every REST call today). +`@hey-api/openapi-ts` is the only open-source TS tool that +generates typed SSE stream clients, and it handles every aspect of +the SSE wire that used to be hand-rolled in `sse.ts`. + +The Go-side `oneOf` ceiling described above does not apply to +TypeScript consumers. SSE frames come typed and discriminated +through the generated stream functions; consumers get automatic +`switch (frame.event)` narrowing with no hand-written parser or +type guard in the SPA. + +## 7. What is out of scope + +- **`/svc/*` proxy.** See §3.9. +- **Outbound HTTP** (`internal/extmsg/http_adapter.go`, + `internal/workspacesvc/proxy_process.go`). Not typed API + endpoints; we consume someone else's contract. +- **Storage-layer (de)serialization** (SQL BLOBs, JSONL log files, + external-tool auth files). Not on our wire. +- **Generated Go client as a Go SDK surface.** Stays in + `internal/` until external consumers show up. +- **WebSocket transport.** HTTP + SSE only. OpenAPI 3.1 + Huma + covers SSE end-to-end, so AsyncAPI / Modelina are not in play. + +## 8. Maintenance rule + +Every file-path citation in this spec is load-bearing. If you +rename or remove a cited symbol (`events.KnownEventTypes`, +`EventPayloadUnion`, `TestEveryKnownEventTypeHasRegisteredPayload`, +`cmd/gc/apiroute.go:apiClient()`, etc.), **update this spec in the +same commit**. A stale spec is worse than no spec — it misleads +future agents about what invariants hold. + +Line numbers are deliberately omitted so the spec survives +refactors. Package names, type names, and test names are stable +anchors. From 99a8504f72221bd90c0edd0942bfbbbb931fcf18 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Thu, 23 Apr 2026 23:16:35 +0000 Subject: [PATCH 12/85] fix: address follow-up review findings --- cmd/gc/api_state.go | 21 ++++- cmd/gc/api_state_test.go | 47 +++++++++++ docs/reference/api.md | 4 +- internal/api/huma_handlers_supervisor.go | 9 +- internal/api/huma_handlers_supervisor_test.go | 2 - internal/beads/caching_store_reads.go | 59 ++++++++++++- internal/beads/caching_store_reconcile.go | 9 ++ .../caching_store_reconcile_internal_test.go | 82 +++++++++++++++++++ internal/beads/caching_store_test.go | 40 +++++++++ internal/cityinit/cityinit.go | 9 +- 10 files changed, 265 insertions(+), 17 deletions(-) create mode 100644 internal/beads/caching_store_reconcile_internal_test.go diff --git a/cmd/gc/api_state.go b/cmd/gc/api_state.go index e63dff99bb..f799824f06 100644 --- a/cmd/gc/api_state.go +++ b/cmd/gc/api_state.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/json" + "errors" "fmt" "io" "log" @@ -638,11 +639,29 @@ func (cs *controllerState) DeleteProviderPatch(name string) error { } func (cs *controllerState) mutateAndPoke(mutate func() error) error { + var ( + tomlPath string + previous []byte + ) + if cs.cityPath != "" { + tomlPath = filepath.Join(cs.cityPath, "city.toml") + var err error + previous, err = os.ReadFile(tomlPath) + if err != nil { + return fmt.Errorf("reading current city config: %w", err) + } + } if err := mutate(); err != nil { return err } if err := cs.refreshConfigSnapshot(); err != nil { - return err + if tomlPath != "" { + if restoreErr := fsys.WriteFileAtomic(fsys.OSFS{}, tomlPath, previous, 0o644); restoreErr != nil { + restoreFailure := fmt.Errorf("restoring previous city config: %w", restoreErr) + return fmt.Errorf("refreshing updated city config: %w", errors.Join(err, restoreFailure)) + } + } + return fmt.Errorf("refreshing updated city config: %w", err) } if cs.configDirty != nil { cs.configDirty.Store(true) diff --git a/cmd/gc/api_state_test.go b/cmd/gc/api_state_test.go index b0897d12f0..c61a0ca73b 100644 --- a/cmd/gc/api_state_test.go +++ b/cmd/gc/api_state_test.go @@ -167,6 +167,53 @@ func TestControllerStateCreateRigPokesReconciler(t *testing.T) { } } +func TestControllerStateMutationRollsBackWhenRefreshFails(t *testing.T) { + t.Setenv("GC_BEADS", "file") + + cityDir := t.TempDir() + if err := os.WriteFile(filepath.Join(cityDir, "broken.toml"), []byte("["), 0o644); err != nil { + t.Fatalf("write broken include: %v", err) + } + + original := []byte("include = [\"broken.toml\"]\n\n[workspace]\nname = \"city1\"\n") + tomlPath := filepath.Join(cityDir, "city.toml") + if err := os.WriteFile(tomlPath, original, 0o644); err != nil { + t.Fatalf("write city.toml: %v", err) + } + + cfg := &config.City{ + Workspace: config.Workspace{Name: "city1"}, + } + cs := newControllerState(context.Background(), cfg, runtime.NewFake(), events.NewFake(), "city1", cityDir) + cs.pokeCh = make(chan struct{}, 1) + cs.configDirty = &atomic.Bool{} + + err := cs.CreateRig(config.Rig{Name: "rig1", Path: t.TempDir()}) + if err == nil { + t.Fatal("CreateRig should fail when refreshing the updated snapshot fails") + } + + restored, readErr := os.ReadFile(tomlPath) + if readErr != nil { + t.Fatalf("read restored city.toml: %v", readErr) + } + if string(restored) != string(original) { + t.Fatalf("city.toml = %q, want rollback to %q", restored, original) + } + + select { + case <-cs.pokeCh: + t.Fatal("CreateRig should not poke the reconciler after rollback") + default: + } + if cs.configDirty.Load() { + t.Fatal("CreateRig should not mark config dirty after rollback") + } + if got := cs.Config(); got == nil || len(got.Rigs) != 0 { + t.Fatalf("Config() rigs = %+v, want rollback to preserve in-memory config", got.Rigs) + } +} + func TestControllerStateAppliesCacheReconcileBeadEventsToStores(t *testing.T) { backing := beads.NewMemStore() created, err := backing.Create(beads.Bead{Title: "root"}) diff --git a/docs/reference/api.md b/docs/reference/api.md index c74cc5f0cf..14fa31b8f2 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -142,7 +142,9 @@ On the same `/v0/events/stream` the client will see (in order): `prepareCityForSupervisor` successfully. Matching event: `subject == name` and `type == "city.ready"`. - `city.init_failed` (`CityInitFailedPayload`) — the reconciler - gave up. The payload's `error` field describes why. + gave up. The payload's `error` field describes why, including + deferred dependency or provider-readiness blockers that the async + API does not fail synchronously. Exactly one of `city.ready` or `city.init_failed` lands per successful `POST`. Clients wait for either; no polling of diff --git a/internal/api/huma_handlers_supervisor.go b/internal/api/huma_handlers_supervisor.go index cd834a6f62..dbb6a8c4bf 100644 --- a/internal/api/huma_handlers_supervisor.go +++ b/internal/api/huma_handlers_supervisor.go @@ -370,9 +370,9 @@ func (sm *SupervisorMux) humaHandleCityCreate(ctx context.Context, input *Superv Dir: dir, Provider: input.Body.Provider, BootstrapProfile: input.Body.BootstrapProfile, - // API callers observe provider readiness through - // GET /v0/provider-readiness; finalize's preflight is - // CLI-only anyway. + // The async API defers dependency/provider blockers to the + // reconciler's terminal city.init_failed event instead of + // failing POST synchronously. SkipProviderReadiness: true, }) switch { @@ -381,9 +381,6 @@ func (sm *SupervisorMux) humaHandleCityCreate(ctx context.Context, input *Superv case errors.Is(err, cityinit.ErrInvalidProvider), errors.Is(err, cityinit.ErrInvalidBootstrapProfile): return nil, huma.Error422UnprocessableEntity(err.Error()) - case errors.Is(err, cityinit.ErrMissingDependency), - errors.Is(err, cityinit.ErrProviderNotReady): - return nil, huma.Error503ServiceUnavailable(err.Error()) case err != nil: return nil, huma.Error500InternalServerError(err.Error()) } diff --git a/internal/api/huma_handlers_supervisor_test.go b/internal/api/huma_handlers_supervisor_test.go index 0d442c6c06..f604464964 100644 --- a/internal/api/huma_handlers_supervisor_test.go +++ b/internal/api/huma_handlers_supervisor_test.go @@ -150,8 +150,6 @@ func TestSupervisorCityCreateMapsInitializerErrors(t *testing.T) { {name: "already initialized", err: cityinit.ErrAlreadyInitialized, want: http.StatusConflict}, {name: "invalid provider", err: cityinit.ErrInvalidProvider, want: http.StatusUnprocessableEntity}, {name: "invalid bootstrap", err: cityinit.ErrInvalidBootstrapProfile, want: http.StatusUnprocessableEntity}, - {name: "missing dependency", err: cityinit.ErrMissingDependency, want: http.StatusServiceUnavailable}, - {name: "provider not ready", err: cityinit.ErrProviderNotReady, want: http.StatusServiceUnavailable}, {name: "generic", err: errors.New("boom"), want: http.StatusInternalServerError}, } diff --git a/internal/beads/caching_store_reads.go b/internal/beads/caching_store_reads.go index d0565472cf..5c523836ce 100644 --- a/internal/beads/caching_store_reads.go +++ b/internal/beads/caching_store_reads.go @@ -17,7 +17,7 @@ func (c *CachingStore) List(query ListQuery) ([]Bead, error) { if query.Live || query.ParentID != "" { items, err := c.backing.List(query) if err == nil { - c.refreshCachedBeads(items) + c.refreshCachedBeads(query, items) } return items, err } @@ -80,8 +80,21 @@ func (c *CachingStore) List(query ListQuery) ([]Bead, error) { return c.backing.List(query) } -func (c *CachingStore) refreshCachedBeads(items []Bead) { - if len(items) == 0 { +func (c *CachingStore) refreshCachedBeads(query ListQuery, items []Bead) { + refreshedParents := make(map[string]Bead) + removedParents := make(map[string]struct{}) + for _, id := range c.staleParentCacheIDs(query.ParentID, items) { + fresh, err := c.backing.Get(id) + switch { + case err == nil: + refreshedParents[id] = cloneBead(fresh) + case errors.Is(err, ErrNotFound): + removedParents[id] = struct{}{} + default: + c.recordProblem("refresh parent cache during list", fmt.Errorf("%s: %w", id, err)) + } + } + if len(items) == 0 && len(refreshedParents) == 0 && len(removedParents) == 0 { return } c.mu.Lock() @@ -94,10 +107,50 @@ func (c *CachingStore) refreshCachedBeads(items []Bead) { delete(c.dirty, item.ID) delete(c.deletedSeq, item.ID) } + for id, bead := range refreshedParents { + c.beads[id] = bead + delete(c.dirty, id) + delete(c.deletedSeq, id) + } + for id := range removedParents { + delete(c.beads, id) + delete(c.deps, id) + delete(c.dirty, id) + delete(c.deletedSeq, id) + } c.markFreshLocked(time.Now()) c.updateStatsLocked() } +func (c *CachingStore) staleParentCacheIDs(parentID string, fresh []Bead) []string { + if parentID == "" { + return nil + } + + freshIDs := make(map[string]struct{}, len(fresh)) + for _, item := range fresh { + freshIDs[item.ID] = struct{}{} + } + + c.mu.RLock() + defer c.mu.RUnlock() + if c.state != cacheLive && c.state != cachePartial { + return nil + } + + var stale []string + for id, bead := range c.beads { + if bead.ParentID != parentID { + continue + } + if _, ok := freshIDs[id]; ok { + continue + } + stale = append(stale, id) + } + return stale +} + // ListOpen returns all cached beads, optionally filtered by status. func (c *CachingStore) ListOpen(status ...string) ([]Bead, error) { query := ListQuery{AllowScan: true} diff --git a/internal/beads/caching_store_reconcile.go b/internal/beads/caching_store_reconcile.go index 6116a2e4e6..ea462e754d 100644 --- a/internal/beads/caching_store_reconcile.go +++ b/internal/beads/caching_store_reconcile.go @@ -62,6 +62,11 @@ func (c *CachingStore) nextReconcileDelay(now time.Time) time.Duration { func (c *CachingStore) runReconciliation() { start := time.Now() + + c.mu.RLock() + startSeq := c.mutationSeq + c.mu.RUnlock() + fresh, err := c.backing.List(ListQuery{AllowScan: true}) if err != nil { c.mu.Lock() @@ -86,6 +91,10 @@ func (c *CachingStore) runReconciliation() { } c.mu.Lock() + if c.mutationSeq != startSeq { + c.mu.Unlock() + return + } var adds, removes, updates int64 notifications := make([]cacheNotification, 0, len(freshByID)) diff --git a/internal/beads/caching_store_reconcile_internal_test.go b/internal/beads/caching_store_reconcile_internal_test.go new file mode 100644 index 0000000000..ccb8ee1ada --- /dev/null +++ b/internal/beads/caching_store_reconcile_internal_test.go @@ -0,0 +1,82 @@ +package beads + +import ( + "context" + "sync" + "testing" +) + +type reconcileRaceStore struct { + Store + started chan struct{} + release chan struct{} + stale []Bead + + mu sync.Mutex + block bool + once sync.Once +} + +func (s *reconcileRaceStore) List(query ListQuery) ([]Bead, error) { + if !query.AllowScan { + return s.Store.List(query) + } + + s.mu.Lock() + block := s.block + s.mu.Unlock() + if !block { + return s.Store.List(query) + } + + s.once.Do(func() { + close(s.started) + }) + <-s.release + return append([]Bead(nil), s.stale...), nil +} + +func TestCachingStoreReconciliationPreservesConcurrentMutation(t *testing.T) { + mem := NewMemStore() + original, err := mem.Create(Bead{Title: "before reconcile"}) + if err != nil { + t.Fatalf("Create: %v", err) + } + + backing := &reconcileRaceStore{ + Store: mem, + started: make(chan struct{}), + release: make(chan struct{}), + stale: []Bead{original}, + } + cs := NewCachingStoreForTest(backing, nil) + if err := cs.Prime(context.Background()); err != nil { + t.Fatalf("Prime: %v", err) + } + + backing.mu.Lock() + backing.block = true + backing.mu.Unlock() + + done := make(chan struct{}) + go func() { + cs.runReconciliation() + close(done) + }() + + <-backing.started + title := "after concurrent update" + if err := cs.Update(original.ID, UpdateOpts{Title: &title}); err != nil { + t.Fatalf("Update: %v", err) + } + close(backing.release) + <-done + + items, err := cs.ListOpen() + if err != nil { + t.Fatalf("ListOpen: %v", err) + } + if len(items) != 1 || items[0].Title != title { + t.Fatalf("ListOpen = %#v, want updated title %q", items, title) + } +} diff --git a/internal/beads/caching_store_test.go b/internal/beads/caching_store_test.go index 05145e09ed..5df0273c1c 100644 --- a/internal/beads/caching_store_test.go +++ b/internal/beads/caching_store_test.go @@ -228,6 +228,46 @@ func TestCachingStoreParentListRefreshesCachedChildren(t *testing.T) { } } +func TestCachingStoreParentListRefreshesReparentedChildren(t *testing.T) { + mem := beads.NewMemStore() + oldParent, err := mem.Create(beads.Bead{Title: "old-parent"}) + if err != nil { + t.Fatalf("Create(old parent): %v", err) + } + newParent, err := mem.Create(beads.Bead{Title: "new-parent"}) + if err != nil { + t.Fatalf("Create(new parent): %v", err) + } + child, err := mem.Create(beads.Bead{Title: "child", ParentID: oldParent.ID, Labels: []string{"mc-live-contract"}}) + if err != nil { + t.Fatalf("Create(child): %v", err) + } + cs := beads.NewCachingStoreForTest(mem, nil) + if err := cs.Prime(context.Background()); err != nil { + t.Fatalf("Prime: %v", err) + } + + if err := mem.Update(child.ID, beads.UpdateOpts{ParentID: &newParent.ID}); err != nil { + t.Fatalf("backing Update(parent): %v", err) + } + + children, err := cs.List(beads.ListQuery{ParentID: oldParent.ID}) + if err != nil { + t.Fatalf("List(old parent): %v", err) + } + if len(children) != 0 { + t.Fatalf("old parent children = %#v, want empty after reparent", children) + } + + labeled, err := cs.List(beads.ListQuery{Label: "mc-live-contract"}) + if err != nil { + t.Fatalf("List(label): %v", err) + } + if len(labeled) != 1 || labeled[0].ParentID != newParent.ID { + t.Fatalf("cached label result = %#v, want parent %s", labeled, newParent.ID) + } +} + func TestCachingStoreUpdateReflectsWriteIntentWhenImmediateReadIsStale(t *testing.T) { mem := beads.NewMemStore() original, err := mem.Create(beads.Bead{ diff --git a/internal/cityinit/cityinit.go b/internal/cityinit/cityinit.go index 4ba0ccc236..903dc68df7 100644 --- a/internal/cityinit/cityinit.go +++ b/internal/cityinit/cityinit.go @@ -103,10 +103,11 @@ type InitRequest struct { NameOverride string // SkipProviderReadiness skips the provider-auth preflight when - // true. The HTTP handler defaults to true (API callers poll - // readiness separately via GET /v0/provider-readiness). The - // CLI defaults to false so first-time users see auth-needed - // errors immediately. + // true. The async HTTP create handler defaults to true and + // surfaces dependency/provider blockers later via the terminal + // city.init_failed event on /v0/events/stream. The CLI defaults + // to false so first-time users see auth-needed errors + // immediately. SkipProviderReadiness bool // ConfigName selects the scaffold template. One of "tutorial" From 3a3b37d1473d362d1f704addfa4744909c6650c8 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 24 Apr 2026 00:37:33 +0000 Subject: [PATCH 13/85] fix: harden cache reconciliation and city rollback paths --- cmd/gc/api_state.go | 98 ++++++++++++-- cmd/gc/api_state_test.go | 53 ++++++++ cmd/gc/cityinit_impl.go | 72 ++++++----- cmd/gc/cityinit_impl_test.go | 90 +++++++++++++ cmd/gc/cmd_supervisor_city.go | 16 ++- internal/beads/caching_store.go | 16 +++ internal/beads/caching_store_events.go | 12 +- internal/beads/caching_store_reads.go | 37 +++--- internal/beads/caching_store_reconcile.go | 75 +++++++++++ .../caching_store_reconcile_internal_test.go | 121 ++++++++++++++++++ internal/beads/caching_store_test.go | 46 ++++--- internal/beads/caching_store_writes.go | 91 +++++++++++-- 12 files changed, 637 insertions(+), 90 deletions(-) diff --git a/cmd/gc/api_state.go b/cmd/gc/api_state.go index f799824f06..27f4ad5221 100644 --- a/cmd/gc/api_state.go +++ b/cmd/gc/api_state.go @@ -53,6 +53,13 @@ type controllerState struct { adapterReg *extmsg.AdapterRegistry } +type configMutationSnapshot struct { + cityPath string + files map[string][]byte + existed map[string]bool + agentFiles map[string]struct{} +} + // newControllerState creates a controllerState with per-rig stores. // BdStores are wrapped with CachingStore for in-memory reads. func newControllerState( @@ -638,25 +645,98 @@ func (cs *controllerState) DeleteProviderPatch(name string) error { }) } +func captureConfigMutationSnapshot(cityPath string) (*configMutationSnapshot, error) { + snapshot := &configMutationSnapshot{ + cityPath: cityPath, + files: make(map[string][]byte), + existed: make(map[string]bool), + agentFiles: make(map[string]struct{}), + } + + capture := func(path string) error { + data, err := os.ReadFile(path) + switch { + case err == nil: + snapshot.files[path] = data + snapshot.existed[path] = true + case os.IsNotExist(err): + snapshot.existed[path] = false + default: + return fmt.Errorf("reading %s: %w", path, err) + } + return nil + } + + for _, path := range []string{ + filepath.Join(cityPath, "city.toml"), + filepath.Join(cityPath, ".gc", "site.toml"), + } { + if err := capture(path); err != nil { + return nil, err + } + } + + agentFiles, err := filepath.Glob(filepath.Join(cityPath, "agents", "*", "agent.toml")) + if err != nil { + return nil, fmt.Errorf("listing agent overrides: %w", err) + } + for _, path := range agentFiles { + snapshot.agentFiles[path] = struct{}{} + if err := capture(path); err != nil { + return nil, err + } + } + + return snapshot, nil +} + +func (s *configMutationSnapshot) restore() error { + var restoreErr error + + currentAgentFiles, err := filepath.Glob(filepath.Join(s.cityPath, "agents", "*", "agent.toml")) + if err != nil { + restoreErr = errors.Join(restoreErr, fmt.Errorf("listing current agent overrides: %w", err)) + } else { + for _, path := range currentAgentFiles { + if _, existed := s.agentFiles[path]; existed { + continue + } + if err := os.Remove(path); err != nil && !os.IsNotExist(err) { + restoreErr = errors.Join(restoreErr, fmt.Errorf("removing %s: %w", path, err)) + } + } + } + + for path, existed := range s.existed { + if !existed { + if err := os.Remove(path); err != nil && !os.IsNotExist(err) { + restoreErr = errors.Join(restoreErr, fmt.Errorf("removing %s: %w", path, err)) + } + continue + } + if err := fsys.WriteFileAtomic(fsys.OSFS{}, path, s.files[path], 0o644); err != nil { + restoreErr = errors.Join(restoreErr, fmt.Errorf("restoring %s: %w", path, err)) + } + } + + return restoreErr +} + func (cs *controllerState) mutateAndPoke(mutate func() error) error { - var ( - tomlPath string - previous []byte - ) + var snapshot *configMutationSnapshot if cs.cityPath != "" { - tomlPath = filepath.Join(cs.cityPath, "city.toml") var err error - previous, err = os.ReadFile(tomlPath) + snapshot, err = captureConfigMutationSnapshot(cs.cityPath) if err != nil { - return fmt.Errorf("reading current city config: %w", err) + return fmt.Errorf("snapshotting current city config: %w", err) } } if err := mutate(); err != nil { return err } if err := cs.refreshConfigSnapshot(); err != nil { - if tomlPath != "" { - if restoreErr := fsys.WriteFileAtomic(fsys.OSFS{}, tomlPath, previous, 0o644); restoreErr != nil { + if snapshot != nil { + if restoreErr := snapshot.restore(); restoreErr != nil { restoreFailure := fmt.Errorf("restoring previous city config: %w", restoreErr) return fmt.Errorf("refreshing updated city config: %w", errors.Join(err, restoreFailure)) } diff --git a/cmd/gc/api_state_test.go b/cmd/gc/api_state_test.go index c61a0ca73b..f78d033a58 100644 --- a/cmd/gc/api_state_test.go +++ b/cmd/gc/api_state_test.go @@ -200,6 +200,9 @@ func TestControllerStateMutationRollsBackWhenRefreshFails(t *testing.T) { if string(restored) != string(original) { t.Fatalf("city.toml = %q, want rollback to %q", restored, original) } + if _, err := os.Stat(filepath.Join(cityDir, ".gc", "site.toml")); !os.IsNotExist(err) { + t.Fatalf(".gc/site.toml stat err = %v, want file removed on rollback", err) + } select { case <-cs.pokeCh: @@ -214,6 +217,56 @@ func TestControllerStateMutationRollsBackWhenRefreshFails(t *testing.T) { } } +func TestControllerStateMutationRollsBackAgentOverrideWhenRefreshFails(t *testing.T) { + t.Setenv("GC_BEADS", "file") + + cityDir := t.TempDir() + if err := os.WriteFile(filepath.Join(cityDir, "broken.toml"), []byte("["), 0o644); err != nil { + t.Fatalf("write broken include: %v", err) + } + if err := os.WriteFile(filepath.Join(cityDir, "pack.toml"), []byte("[pack]\nname = \"city1\"\nschema = 2\n"), 0o644); err != nil { + t.Fatalf("write pack.toml: %v", err) + } + agentDir := filepath.Join(cityDir, "agents", "worker") + if err := os.MkdirAll(agentDir, 0o755); err != nil { + t.Fatalf("mkdir agent dir: %v", err) + } + if err := os.WriteFile(filepath.Join(agentDir, "prompt.template.md"), []byte("You are the worker.\n"), 0o644); err != nil { + t.Fatalf("write prompt template: %v", err) + } + + original := []byte("include = [\"broken.toml\"]\n\n[workspace]\nname = \"city1\"\n") + tomlPath := filepath.Join(cityDir, "city.toml") + if err := os.WriteFile(tomlPath, original, 0o644); err != nil { + t.Fatalf("write city.toml: %v", err) + } + + cs := newControllerState(context.Background(), &config.City{ + Workspace: config.Workspace{Name: "city1"}, + }, runtime.NewFake(), events.NewFake(), "city1", cityDir) + cs.pokeCh = make(chan struct{}, 1) + cs.configDirty = &atomic.Bool{} + + err := cs.SuspendAgent("worker") + if err == nil { + t.Fatal("SuspendAgent should fail when refreshing the updated snapshot fails") + } + + if _, err := os.Stat(filepath.Join(agentDir, "agent.toml")); !os.IsNotExist(err) { + t.Fatalf("agent.toml stat err = %v, want file removed on rollback", err) + } + restored, readErr := os.ReadFile(tomlPath) + if readErr != nil { + t.Fatalf("read restored city.toml: %v", readErr) + } + if string(restored) != string(original) { + t.Fatalf("city.toml = %q, want rollback to %q", restored, original) + } + if cs.configDirty.Load() { + t.Fatal("SuspendAgent should not mark config dirty after rollback") + } +} + func TestControllerStateAppliesCacheReconcileBeadEventsToStores(t *testing.T) { backing := beads.NewMemStore() created, err := backing.Create(beads.Bead{Title: "root"}) diff --git a/cmd/gc/cityinit_impl.go b/cmd/gc/cityinit_impl.go index 4cef1a3656..1ca71cc5a4 100644 --- a/cmd/gc/cityinit_impl.go +++ b/cmd/gc/cityinit_impl.go @@ -43,6 +43,31 @@ func NewInitializer() cityinit.Initializer { return localInitializer{} } +func ensureCityEventLog(cityPath string) { + if fr, err := events.NewFileRecorder(filepath.Join(cityPath, ".gc", "events.jsonl"), io.Discard); err == nil { + fr.Close() //nolint:errcheck // best-effort + } +} + +func recordCityEvent(cityPath, eventType, subject string, payload any) { + fr, err := events.NewFileRecorder(filepath.Join(cityPath, ".gc", "events.jsonl"), io.Discard) + if err != nil { + return + } + defer fr.Close() //nolint:errcheck // best-effort + + raw, err := json.Marshal(payload) + if err != nil { + return + } + fr.Record(events.Event{ + Type: eventType, + Actor: "gc", + Subject: subject, + Payload: raw, + }) +} + // Scaffold runs the fast portion of city creation so the HTTP API // handler can return 202 Accepted without blocking on the slow // finalize work. Writes the on-disk shape (via doInit), then @@ -81,8 +106,7 @@ func (localInitializer) Scaffold(_ context.Context, req cityinit.InitRequest) (* cityName := resolveCityName(req.NameOverride, "", dir) - // Create .gc/events.jsonl immediately and emit city.created before - // the supervisor reconciler picks up the city. Two reasons: + // Create .gc/events.jsonl immediately before registration. Two reasons: // // 1. The supervisor event multiplexer (see // internal/api/supervisor.go:buildMultiplexer) includes @@ -98,21 +122,10 @@ func (localInitializer) Scaffold(_ context.Context, req cityinit.InitRequest) (* // POST /v0/city → subscribe works even when no other cities // exist yet (the fresh-supervisor scenario). // - // Best-effort: a failure to open the recorder does not fail the - // Scaffold call — the reconciler creates its own recorder later - // and city.ready/city.init_failed will land there. The city is - // still usable, just without the leading city.created marker. - if fr, frErr := events.NewFileRecorder(filepath.Join(dir, ".gc", "events.jsonl"), io.Discard); frErr == nil { - if payload, mErr := json.Marshal(api.CityCreatedPayload{Name: cityName, Path: dir}); mErr == nil { - fr.Record(events.Event{ - Type: events.CityCreated, - Actor: "gc", - Subject: cityName, - Payload: payload, - }) - } - fr.Close() //nolint:errcheck // best-effort - } + // The file creation is best-effort. city.created itself is emitted + // only after registration succeeds so synchronous failures do not + // leak a "created" event for a city the supervisor never adopted. + ensureCityEventLog(dir) // Register the city with the supervisor without blocking on the // reconciler's tick. The standard registerCityWithSupervisor @@ -124,6 +137,7 @@ func (localInitializer) Scaffold(_ context.Context, req cityinit.InitRequest) (* if err := registerCityForAPI(dir, req.NameOverride); err != nil { return nil, fmt.Errorf("register with supervisor: %w", err) } + recordCityEvent(dir, events.CityCreated, cityName, api.CityCreatedPayload{Name: cityName, Path: dir}) return &cityinit.InitResult{ CityName: cityName, @@ -220,7 +234,7 @@ func (localInitializer) Unregister(_ context.Context, req cityinit.UnregisterReq return nil, fmt.Errorf("%w: city_name is required", cityinit.ErrNotRegistered) } - reg := supervisor.NewRegistry(supervisor.RegistryPath()) + reg := newSupervisorRegistry() entries, err := reg.List() if err != nil { return nil, fmt.Errorf("reading supervisor registry: %w", err) @@ -238,22 +252,6 @@ func (localInitializer) Unregister(_ context.Context, req cityinit.UnregisterReq return nil, fmt.Errorf("%w: %q", cityinit.ErrNotRegistered, name) } - // Emit city.unregister_requested so subscribers that connected - // before the unregister call see the start of the teardown. The - // city's event file exists for any city that got far enough into - // Scaffold; best-effort if it's missing. - if fr, frErr := events.NewFileRecorder(filepath.Join(match.Path, ".gc", "events.jsonl"), io.Discard); frErr == nil { - if payload, mErr := json.Marshal(api.CityUnregisterRequestedPayload{Name: match.EffectiveName(), Path: match.Path}); mErr == nil { - fr.Record(events.Event{ - Type: events.CityUnregisterRequested, - Actor: "gc", - Subject: match.EffectiveName(), - Payload: payload, - }) - } - fr.Close() //nolint:errcheck // best-effort - } - if err := reg.Unregister(match.Path); err != nil { // Should not happen — we just read this entry — but wrap to // satisfy the ErrNotRegistered contract if it does. @@ -262,6 +260,12 @@ func (localInitializer) Unregister(_ context.Context, req cityinit.UnregisterReq } return nil, fmt.Errorf("removing %q from supervisor registry: %w", name, err) } + recordCityEvent( + match.Path, + events.CityUnregisterRequested, + match.EffectiveName(), + api.CityUnregisterRequestedPayload{Name: match.EffectiveName(), Path: match.Path}, + ) // Wake the reconciler; same fire-and-forget signal the Scaffold // path uses. If the supervisor isn't reachable the periodic diff --git a/cmd/gc/cityinit_impl_test.go b/cmd/gc/cityinit_impl_test.go index c2a74056c6..f2e76a2aed 100644 --- a/cmd/gc/cityinit_impl_test.go +++ b/cmd/gc/cityinit_impl_test.go @@ -15,6 +15,28 @@ import ( "github.com/gastownhall/gascity/internal/supervisor" ) +type fakeSupervisorRegistry struct { + entries []supervisor.CityEntry + listErr error + registerErr error + unregisterErr error +} + +func (f *fakeSupervisorRegistry) List() ([]supervisor.CityEntry, error) { + if f.listErr != nil { + return nil, f.listErr + } + return append([]supervisor.CityEntry(nil), f.entries...), nil +} + +func (f *fakeSupervisorRegistry) Register(string, string) error { + return f.registerErr +} + +func (f *fakeSupervisorRegistry) Unregister(string) error { + return f.unregisterErr +} + func TestValidateInitRequest(t *testing.T) { absDir := filepath.Join(t.TempDir(), "city") tests := []struct { @@ -140,6 +162,37 @@ func TestLocalInitializerScaffoldCreatesCityRegistersAndEmitsCreated(t *testing. } } +func TestLocalInitializerScaffoldDoesNotEmitCreatedWhenRegisterFails(t *testing.T) { + t.Setenv("GC_HOME", t.TempDir()) + cityPath := filepath.Join(t.TempDir(), "api-city") + + oldNewSupervisorRegistry := newSupervisorRegistry + newSupervisorRegistry = func() supervisorRegistry { + return &fakeSupervisorRegistry{registerErr: errors.New("boom")} + } + t.Cleanup(func() { + newSupervisorRegistry = oldNewSupervisorRegistry + }) + + _, err := localInitializer{}.Scaffold(context.Background(), cityinit.InitRequest{ + Dir: cityPath, + Provider: "codex", + BootstrapProfile: bootstrapProfileSingleHostCompat, + NameOverride: "api-city", + }) + if err == nil || !strings.Contains(err.Error(), "register with supervisor") { + t.Fatalf("Scaffold error = %v, want register with supervisor failure", err) + } + + evts, readErr := events.ReadFiltered(filepath.Join(cityPath, ".gc", "events.jsonl"), events.Filter{Type: events.CityCreated}) + if readErr != nil && !os.IsNotExist(readErr) { + t.Fatalf("ReadFiltered city.created: %v", readErr) + } + if len(evts) != 0 { + t.Fatalf("city.created events = %d, want 0: %+v", len(evts), evts) + } +} + func TestLocalInitializerInitScaffoldsAndFinalizes(t *testing.T) { cityPath := filepath.Join(t.TempDir(), "init-city") @@ -218,6 +271,43 @@ func TestLocalInitializerUnregisterRemovesRegistryAndEmitsEvent(t *testing.T) { assertSameTestPath(t, payload.Path, cityPath) } +func TestLocalInitializerUnregisterDoesNotEmitEventWhenRegistryWriteFails(t *testing.T) { + t.Setenv("GC_HOME", t.TempDir()) + cityPath := filepath.Join(t.TempDir(), "bright-lights") + if err := os.MkdirAll(filepath.Join(cityPath, ".gc"), 0o755); err != nil { + t.Fatal(err) + } + + oldNewSupervisorRegistry := newSupervisorRegistry + newSupervisorRegistry = func() supervisorRegistry { + return &fakeSupervisorRegistry{ + entries: []supervisor.CityEntry{{ + Path: cityPath, + Name: "bright-lights", + }}, + unregisterErr: errors.New("boom"), + } + } + t.Cleanup(func() { + newSupervisorRegistry = oldNewSupervisorRegistry + }) + + _, err := localInitializer{}.Unregister(context.Background(), cityinit.UnregisterRequest{ + CityName: "bright-lights", + }) + if err == nil || !strings.Contains(err.Error(), "removing \"bright-lights\" from supervisor registry") { + t.Fatalf("Unregister error = %v, want registry removal failure", err) + } + + evts, readErr := events.ReadFiltered(filepath.Join(cityPath, ".gc", "events.jsonl"), events.Filter{Type: events.CityUnregisterRequested}) + if readErr != nil && !os.IsNotExist(readErr) { + t.Fatalf("ReadFiltered city.unregister_requested: %v", readErr) + } + if len(evts) != 0 { + t.Fatalf("city.unregister_requested events = %d, want 0: %+v", len(evts), evts) + } +} + func TestLocalInitializerUnregisterMissingCity(t *testing.T) { t.Setenv("GC_HOME", t.TempDir()) diff --git a/cmd/gc/cmd_supervisor_city.go b/cmd/gc/cmd_supervisor_city.go index a2c9ac5733..98fc37b6b5 100644 --- a/cmd/gc/cmd_supervisor_city.go +++ b/cmd/gc/cmd_supervisor_city.go @@ -30,6 +30,16 @@ var ( supervisorCityErrorHook = supervisorCityError ) +type supervisorRegistry interface { + List() ([]supervisor.CityEntry, error) + Register(cityPath, effectiveName string) error + Unregister(cityPath string) error +} + +var newSupervisorRegistry = func() supervisorRegistry { + return supervisor.NewRegistry(supervisor.RegistryPath()) +} + func supervisorCityStartTimeout(cityPath string) time.Duration { timeout := supervisorCityReadyTimeout cfg, err := loadCityConfig(cityPath, io.Discard) @@ -251,15 +261,15 @@ func registerCityWithSupervisorNamed(cityPath, nameOverride string, stdout, stde // lifecycle, etc.) which is the exact behavior the async contract // was designed to avoid. The reconciler picks up the city on its // next tick; subscribers to /v0/events/stream see city.created -// (written by Scaffold), then city.ready or city.init_failed when -// the reconciler completes. +// only after registration succeeds, then city.ready or +// city.init_failed when the reconciler completes. func registerCityForAPI(cityPath, nameOverride string) error { cityPath = normalizePathForCompare(cityPath) name, err := registeredCityName(cityPath, nameOverride) if err != nil { return err } - reg := supervisor.NewRegistry(supervisor.RegistryPath()) + reg := newSupervisorRegistry() if err := reg.Register(cityPath, name); err != nil { return err } diff --git a/internal/beads/caching_store.go b/internal/beads/caching_store.go index 77b72e9458..2cc9a46ebb 100644 --- a/internal/beads/caching_store.go +++ b/internal/beads/caching_store.go @@ -28,6 +28,7 @@ type CachingStore struct { beads map[string]Bead deps map[string][]Dep dirty map[string]struct{} + beadSeq map[string]uint64 deletedSeq map[string]uint64 state cacheState lastFreshAt time.Time @@ -98,6 +99,7 @@ func newCachingStore(backing Store, onChange func(eventType, beadID string, payl beads: make(map[string]Bead), deps: make(map[string][]Dep), dirty: make(map[string]struct{}), + beadSeq: make(map[string]uint64), deletedSeq: make(map[string]uint64), onChange: onChange, problemf: func(msg string) { @@ -106,6 +108,18 @@ func newCachingStore(backing Store, onChange func(eventType, beadID string, payl } } +func (c *CachingStore) noteMutationLocked(ids ...string) uint64 { + c.mutationSeq++ + seq := c.mutationSeq + for _, id := range ids { + if id == "" { + continue + } + c.beadSeq[id] = seq + } + return seq +} + // PrimeActive loads all non-closed beads (open + in_progress) into the // cache. These are fast indexed queries that populate enough data for // startup paths without waiting for a full scan. The cache enters @@ -188,6 +202,7 @@ func (c *CachingStore) Prime(_ context.Context) error { c.beads = beadMap c.deps = depMap c.dirty = make(map[string]struct{}) + c.beadSeq = make(map[string]uint64) c.deletedSeq = make(map[string]uint64) } else { for id, b := range beadMap { @@ -199,6 +214,7 @@ func (c *CachingStore) Prime(_ context.Context) error { } c.beads[id] = b delete(c.deletedSeq, id) + delete(c.beadSeq, id) if deps, ok := depMap[id]; ok { c.deps[id] = deps } diff --git a/internal/beads/caching_store_events.go b/internal/beads/caching_store_events.go index 3603bc2179..3bdb3c50df 100644 --- a/internal/beads/caching_store_events.go +++ b/internal/beads/caching_store_events.go @@ -29,30 +29,39 @@ func (c *CachingStore) ApplyEvent(eventType string, payload json.RawMessage) { return } + mutated := false switch eventType { case "bead.created": if _, exists := c.beads[b.ID]; !exists { + c.noteMutationLocked(b.ID) c.beads[b.ID] = cloneBead(b) delete(c.dirty, b.ID) delete(c.deletedSeq, b.ID) c.updateStatsLocked() + mutated = true } case "bead.updated": + c.noteMutationLocked(b.ID) c.beads[b.ID] = cloneBead(b) delete(c.dirty, b.ID) delete(c.deletedSeq, b.ID) + mutated = true case "bead.closed": + c.noteMutationLocked(b.ID) if _, exists := c.beads[b.ID]; !exists { c.updateStatsLocked() } c.beads[b.ID] = cloneBead(b) delete(c.dirty, b.ID) delete(c.deletedSeq, b.ID) + mutated = true default: return } - c.markFreshLocked(time.Now()) + if mutated { + c.markFreshLocked(time.Now()) + } } // ApplyDepEvent updates the dep cache for a bead. Call after dep @@ -63,6 +72,7 @@ func (c *CachingStore) ApplyDepEvent(beadID string, deps []Dep) { if c.state != cacheLive { return } + c.noteMutationLocked(beadID) c.deps[beadID] = cloneDeps(deps) delete(c.dirty, beadID) delete(c.deletedSeq, beadID) diff --git a/internal/beads/caching_store_reads.go b/internal/beads/caching_store_reads.go index 5c523836ce..d5c04a1a95 100644 --- a/internal/beads/caching_store_reads.go +++ b/internal/beads/caching_store_reads.go @@ -164,25 +164,28 @@ func (c *CachingStore) ListOpen(status ...string) ([]Bead, error) { func (c *CachingStore) Get(id string) (Bead, error) { c.mu.RLock() if c.state == cacheLive || c.state == cachePartial { - cached, cachedOK := c.beads[id] - c.mu.RUnlock() - - fresh, err := c.backing.Get(id) - if err != nil { - if cachedOK && !errors.Is(err, ErrNotFound) { - c.recordProblem("refresh bead during get", fmt.Errorf("%s: %w", id, err)) - return cloneBead(cached), nil + if _, ok := c.dirty[id]; ok { + c.mu.RUnlock() + fresh, err := c.backing.Get(id) + if err != nil { + return Bead{}, err } - return Bead{}, err + c.mu.Lock() + c.beads[id] = cloneBead(fresh) + delete(c.dirty, id) + delete(c.deletedSeq, id) + delete(c.beadSeq, id) + c.markFreshLocked(time.Now()) + c.updateStatsLocked() + c.mu.Unlock() + return fresh, nil } - c.mu.Lock() - c.beads[id] = cloneBead(fresh) - delete(c.dirty, id) - delete(c.deletedSeq, id) - c.markFreshLocked(time.Now()) - c.updateStatsLocked() - c.mu.Unlock() - return fresh, nil + if b, ok := c.beads[id]; ok { + c.mu.RUnlock() + return cloneBead(b), nil + } + c.mu.RUnlock() + return c.backing.Get(id) } c.mu.RUnlock() return c.backing.Get(id) diff --git a/internal/beads/caching_store_reconcile.go b/internal/beads/caching_store_reconcile.go index ea462e754d..227187a44c 100644 --- a/internal/beads/caching_store_reconcile.go +++ b/internal/beads/caching_store_reconcile.go @@ -92,7 +92,81 @@ func (c *CachingStore) runReconciliation() { c.mu.Lock() if c.mutationSeq != startSeq { + var adds, removes, updates int64 + notifications := make([]cacheNotification, 0, len(freshByID)) + + for id, freshBead := range freshByID { + if c.deletedSeq[id] > startSeq || c.beadSeq[id] > startSeq { + continue + } + + old, exists := c.beads[id] + switch { + case !exists: + adds++ + notifications = append(notifications, cacheNotification{ + eventType: "bead.created", + bead: cloneBead(freshBead), + }) + case beadChanged(old, freshBead): + updates++ + notifications = append(notifications, cacheNotification{ + eventType: "bead.updated", + bead: cloneBead(freshBead), + }) + case depErr == nil && depsChanged(c.deps[id], depMap[id]): + updates++ + notifications = append(notifications, cacheNotification{ + eventType: "bead.updated", + bead: cloneBead(freshBead), + }) + } + + c.beads[id] = cloneBead(freshBead) + if depErr == nil { + c.deps[id] = cloneDeps(depMap[id]) + } + delete(c.dirty, id) + delete(c.deletedSeq, id) + delete(c.beadSeq, id) + } + + for id, old := range c.beads { + if _, exists := freshByID[id]; exists { + continue + } + if c.deletedSeq[id] > startSeq || c.beadSeq[id] > startSeq { + continue + } + removes++ + closed := cloneBead(old) + closed.Status = "closed" + notifications = append(notifications, cacheNotification{ + eventType: "bead.closed", + bead: closed, + }) + delete(c.beads, id) + delete(c.deps, id) + delete(c.dirty, id) + delete(c.deletedSeq, id) + delete(c.beadSeq, id) + } + + c.syncFailures = 0 + if c.state == cacheDegraded { + c.state = cacheLive + } + now := time.Now() + durMs := float64(time.Since(start).Microseconds()) / 1000.0 + c.stats.LastReconcileAt = now + c.stats.LastReconcileMs = durMs + c.stats.Adds += adds + c.stats.Removes += removes + c.stats.Updates += updates + c.markFreshLocked(now) + c.updateStatsLocked() c.mu.Unlock() + c.notifyChanges(notifications) return } @@ -145,6 +219,7 @@ func (c *CachingStore) runReconciliation() { c.beads = freshByID c.deps = nextDeps c.dirty = make(map[string]struct{}) + c.beadSeq = make(map[string]uint64) c.deletedSeq = make(map[string]uint64) c.syncFailures = 0 if c.state == cacheDegraded { diff --git a/internal/beads/caching_store_reconcile_internal_test.go b/internal/beads/caching_store_reconcile_internal_test.go index ccb8ee1ada..eb39a91fb0 100644 --- a/internal/beads/caching_store_reconcile_internal_test.go +++ b/internal/beads/caching_store_reconcile_internal_test.go @@ -2,6 +2,7 @@ package beads import ( "context" + "encoding/json" "sync" "testing" ) @@ -33,6 +34,8 @@ func (s *reconcileRaceStore) List(query ListQuery) ([]Bead, error) { close(s.started) }) <-s.release + s.mu.Lock() + defer s.mu.Unlock() return append([]Bead(nil), s.stale...), nil } @@ -80,3 +83,121 @@ func TestCachingStoreReconciliationPreservesConcurrentMutation(t *testing.T) { t.Fatalf("ListOpen = %#v, want updated title %q", items, title) } } + +func TestCachingStoreReconciliationPreservesConcurrentEvent(t *testing.T) { + mem := NewMemStore() + original, err := mem.Create(Bead{Title: "before reconcile"}) + if err != nil { + t.Fatalf("Create: %v", err) + } + + backing := &reconcileRaceStore{ + Store: mem, + started: make(chan struct{}), + release: make(chan struct{}), + stale: []Bead{original}, + } + cs := NewCachingStoreForTest(backing, nil) + if err := cs.Prime(context.Background()); err != nil { + t.Fatalf("Prime: %v", err) + } + + backing.mu.Lock() + backing.block = true + backing.mu.Unlock() + + done := make(chan struct{}) + go func() { + cs.runReconciliation() + close(done) + }() + + <-backing.started + eventBead := cloneBead(original) + eventBead.Title = "after concurrent event" + payload, err := json.Marshal(eventBead) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + cs.ApplyEvent("bead.updated", payload) + close(backing.release) + <-done + + items, err := cs.ListOpen() + if err != nil { + t.Fatalf("ListOpen: %v", err) + } + if len(items) != 1 || items[0].Title != eventBead.Title { + t.Fatalf("ListOpen = %#v, want event title %q", items, eventBead.Title) + } +} + +func TestCachingStoreReconciliationMergesFreshDataWithConcurrentMutation(t *testing.T) { + mem := NewMemStore() + mutated, err := mem.Create(Bead{Title: "before mutate"}) + if err != nil { + t.Fatalf("Create(mutated): %v", err) + } + refreshed, err := mem.Create(Bead{Title: "before refresh"}) + if err != nil { + t.Fatalf("Create(refreshed): %v", err) + } + + backing := &reconcileRaceStore{ + Store: mem, + started: make(chan struct{}), + release: make(chan struct{}), + stale: []Bead{mutated, refreshed}, + } + cs := NewCachingStoreForTest(backing, nil) + if err := cs.Prime(context.Background()); err != nil { + t.Fatalf("Prime: %v", err) + } + + backing.mu.Lock() + backing.block = true + backing.mu.Unlock() + + done := make(chan struct{}) + go func() { + cs.runReconciliation() + close(done) + }() + + <-backing.started + title := "after concurrent update" + if err := cs.Update(mutated.ID, UpdateOpts{Title: &title}); err != nil { + t.Fatalf("Update(mutated): %v", err) + } + refreshedTitle := "after reconcile refresh" + if err := mem.Update(refreshed.ID, UpdateOpts{Title: &refreshedTitle}); err != nil { + t.Fatalf("Update(refreshed backing): %v", err) + } + refreshedBead, err := mem.Get(refreshed.ID) + if err != nil { + t.Fatalf("Get(refreshed backing): %v", err) + } + backing.mu.Lock() + backing.stale = []Bead{ + cloneBead(mutated), + cloneBead(refreshedBead), + } + backing.mu.Unlock() + close(backing.release) + <-done + + items, err := cs.ListOpen() + if err != nil { + t.Fatalf("ListOpen: %v", err) + } + gotTitles := map[string]string{} + for _, item := range items { + gotTitles[item.ID] = item.Title + } + if gotTitles[mutated.ID] != title { + t.Fatalf("mutated title = %q, want %q", gotTitles[mutated.ID], title) + } + if gotTitles[refreshed.ID] != refreshedTitle { + t.Fatalf("refreshed title = %q, want %q", gotTitles[refreshed.ID], refreshedTitle) + } +} diff --git a/internal/beads/caching_store_test.go b/internal/beads/caching_store_test.go index 5df0273c1c..c821b5226a 100644 --- a/internal/beads/caching_store_test.go +++ b/internal/beads/caching_store_test.go @@ -144,28 +144,30 @@ func TestCachingStorePrimeDoesNotResurrectConcurrentDelete(t *testing.T) { } } -func TestCachingStoreGetRefreshesStaleCachedBead(t *testing.T) { - mem := beads.NewMemStore() - original, err := mem.Create(beads.Bead{Title: "before update"}) - if err != nil { - t.Fatalf("Create: %v", err) - } - cs := beads.NewCachingStoreForTest(mem, nil) +func TestCachingStoreCreateRefreshesSparseBead(t *testing.T) { + backing := &sparseCreateStore{Store: beads.NewMemStore()} + cs := beads.NewCachingStoreForTest(backing, nil) if err := cs.Prime(context.Background()); err != nil { t.Fatalf("Prime: %v", err) } - status := "in_progress" - if err := mem.Update(original.ID, beads.UpdateOpts{Status: &status}); err != nil { - t.Fatalf("backing Update: %v", err) - } - - got, err := cs.Get(original.ID) + created, err := cs.Create(beads.Bead{ + Title: "new task", + ParentID: "parent-1", + Labels: []string{"urgent"}, + Metadata: map[string]string{"k": "v"}, + }) if err != nil { - t.Fatalf("Get: %v", err) + t.Fatalf("Create: %v", err) } - if got.Status != "in_progress" { - t.Fatalf("status = %q, want in_progress", got.Status) + if created.ParentID != "parent-1" { + t.Fatalf("ParentID = %q, want parent-1", created.ParentID) + } + if len(created.Labels) != 1 || created.Labels[0] != "urgent" { + t.Fatalf("Labels = %#v, want [urgent]", created.Labels) + } + if created.Metadata["k"] != "v" { + t.Fatalf("Metadata = %#v, want k=v", created.Metadata) } } @@ -357,6 +359,18 @@ type staleReadAfterUpdateStore struct { returnOld bool } +type sparseCreateStore struct { + beads.Store +} + +func (s *sparseCreateStore) Create(b beads.Bead) (beads.Bead, error) { + created, err := s.Store.Create(b) + if err != nil { + return beads.Bead{}, err + } + return beads.Bead{ID: created.ID, Title: created.Title}, nil +} + func (s *staleReadAfterUpdateStore) Update(id string, opts beads.UpdateOpts) error { if err := s.Store.Update(id, opts); err != nil { return err diff --git a/internal/beads/caching_store_writes.go b/internal/beads/caching_store_writes.go index c53793cec6..d86409660e 100644 --- a/internal/beads/caching_store_writes.go +++ b/internal/beads/caching_store_writes.go @@ -13,8 +13,14 @@ func (c *CachingStore) Create(b Bead) (Bead, error) { return created, err } + if fresh, err := c.backing.Get(created.ID); err == nil { + created = fresh + } else if !errors.Is(err, ErrNotFound) { + c.recordProblem("refresh bead after create", fmt.Errorf("%s: %w", created.ID, err)) + } + c.mu.Lock() - c.mutationSeq++ + c.noteMutationLocked(created.ID) c.beads[created.ID] = cloneBead(created) delete(c.dirty, created.ID) delete(c.deletedSeq, created.ID) @@ -41,9 +47,10 @@ func (c *CachingStore) Update(id string, opts UpdateOpts) error { c.recordProblem("refresh bead after update", fmt.Errorf("%s: %w", id, err)) return nil } + fresh = applyUpdateOptsToBead(fresh, opts) c.mu.Lock() - c.mutationSeq++ + c.noteMutationLocked(id) c.beads[id] = cloneBead(fresh) delete(c.dirty, id) delete(c.deletedSeq, id) @@ -72,7 +79,7 @@ func (c *CachingStore) Close(id string) error { } c.mu.Lock() - c.mutationSeq++ + c.noteMutationLocked(id) if b, ok := c.beads[id]; ok { b.Status = "closed" c.beads[id] = b @@ -123,7 +130,7 @@ func (c *CachingStore) CloseAll(ids []string, metadata map[string]string) (int, notifications := make([]cacheNotification, 0, len(refreshed)) c.mu.Lock() - c.mutationSeq++ + c.noteMutationLocked(ids...) if refreshErr != nil { c.recordProblemLocked("close-all refresh", refreshErr) } @@ -159,7 +166,7 @@ func (c *CachingStore) SetMetadata(id, key, value string) error { } c.mu.Lock() - c.mutationSeq++ + c.noteMutationLocked(id) if b, ok := c.beads[id]; ok { if b.Metadata == nil { b.Metadata = make(map[string]string) @@ -182,7 +189,7 @@ func (c *CachingStore) SetMetadataBatch(id string, kvs map[string]string) error } c.mu.Lock() - c.mutationSeq++ + c.noteMutationLocked(id) if b, ok := c.beads[id]; ok { if b.Metadata == nil { b.Metadata = make(map[string]string, len(kvs)) @@ -207,7 +214,7 @@ func (c *CachingStore) DepAdd(issueID, dependsOnID, depType string) error { } c.mu.Lock() - c.mutationSeq++ + c.noteMutationLocked(issueID) deps := c.deps[issueID] for i, d := range deps { if d.DependsOnID == dependsOnID { @@ -237,7 +244,7 @@ func (c *CachingStore) DepRemove(issueID, dependsOnID string) error { } c.mu.Lock() - c.mutationSeq++ + c.noteMutationLocked(issueID) deps := c.deps[issueID] for i, d := range deps { if d.DependsOnID == dependsOnID { @@ -260,13 +267,77 @@ func (c *CachingStore) Delete(id string) error { } c.mu.Lock() - c.mutationSeq++ + seq := c.noteMutationLocked(id) delete(c.beads, id) delete(c.deps, id) delete(c.dirty, id) - c.deletedSeq[id] = c.mutationSeq + delete(c.beadSeq, id) + c.deletedSeq[id] = seq c.markFreshLocked(time.Now()) c.updateStatsLocked() c.mu.Unlock() return nil } + +func applyUpdateOptsToBead(bead Bead, opts UpdateOpts) Bead { + if opts.Title != nil { + bead.Title = *opts.Title + } + if opts.Status != nil { + bead.Status = *opts.Status + } + if opts.Type != nil { + bead.Type = *opts.Type + } + if opts.Priority != nil { + bead.Priority = cloneIntPtr(opts.Priority) + } + if opts.Description != nil { + bead.Description = *opts.Description + } + if opts.ParentID != nil { + bead.ParentID = *opts.ParentID + } + if opts.Assignee != nil { + bead.Assignee = *opts.Assignee + } + if len(opts.Metadata) > 0 { + if bead.Metadata == nil { + bead.Metadata = make(map[string]string, len(opts.Metadata)) + } + for key, value := range opts.Metadata { + bead.Metadata[key] = value + } + } + if len(opts.Labels) > 0 || len(opts.RemoveLabels) > 0 { + remove := make(map[string]struct{}, len(opts.RemoveLabels)) + for _, label := range opts.RemoveLabels { + remove[label] = struct{}{} + } + + labels := make([]string, 0, len(bead.Labels)+len(opts.Labels)) + seen := make(map[string]struct{}, len(bead.Labels)+len(opts.Labels)) + for _, label := range bead.Labels { + if _, drop := remove[label]; drop { + continue + } + if _, exists := seen[label]; exists { + continue + } + labels = append(labels, label) + seen[label] = struct{}{} + } + for _, label := range opts.Labels { + if _, drop := remove[label]; drop { + continue + } + if _, exists := seen[label]; exists { + continue + } + labels = append(labels, label) + seen[label] = struct{}{} + } + bead.Labels = labels + } + return bead +} From 9fc34239ad04c64fe806c6318175b44d6f243e91 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 24 Apr 2026 00:56:58 +0000 Subject: [PATCH 14/85] fix: tighten async city lifecycle contracts --- cmd/gc/city_registry.go | 7 +++++++ cmd/gc/city_registry_test.go | 23 +++++++++++++++++++++++ cmd/gc/cityinit_impl.go | 8 +++++--- cmd/gc/cityinit_impl_test.go | 18 ++++++++++++++++++ cmd/gc/cmd_supervisor_city.go | 21 ++++++--------------- cmd/gc/controller.go | 6 +++++- 6 files changed, 64 insertions(+), 19 deletions(-) diff --git a/cmd/gc/city_registry.go b/cmd/gc/city_registry.go index 8a34c55c4a..ed1fbd7486 100644 --- a/cmd/gc/city_registry.go +++ b/cmd/gc/city_registry.go @@ -3,6 +3,7 @@ package main import ( "context" "io" + "os" "path/filepath" "sync" "sync/atomic" @@ -238,6 +239,12 @@ func (r *cityRegistry) TransientCityEventProviders() map[string]events.Provider out := make(map[string]events.Provider, len(paths)) for name, path := range paths { evPath := filepath.Join(path, ".gc", "events.jsonl") + if _, err := os.Stat(evPath); err != nil { + continue + } + if _, err := events.ReadLatestSeq(evPath); err != nil { + continue + } out[name] = transientCityEventProvider{path: evPath} } return out diff --git a/cmd/gc/city_registry_test.go b/cmd/gc/city_registry_test.go index 9dbdaebc2b..a35623b3e8 100644 --- a/cmd/gc/city_registry_test.go +++ b/cmd/gc/city_registry_test.go @@ -286,6 +286,29 @@ func TestCityRegistryTransientCityEventProvidersIncludesRegisteredAndPendingCiti } } +func TestCityRegistryTransientCityEventProvidersSkipMissingLogs(t *testing.T) { + t.Setenv("GC_HOME", t.TempDir()) + + missingPath := filepath.Join(t.TempDir(), "missing-city") + if err := os.MkdirAll(missingPath, 0o755); err != nil { + t.Fatal(err) + } + + regFile := supervisor.NewRegistry(supervisor.RegistryPath()) + if err := regFile.Register(missingPath, "missing-city"); err != nil { + t.Fatal(err) + } + + reg := newCityRegistry() + providers := reg.TransientCityEventProviders() + if _, ok := providers["missing-city"]; ok { + t.Fatalf("missing-city should be skipped when events.jsonl is absent: %#v", providers) + } + if _, err := os.Stat(filepath.Join(missingPath, ".gc", "events.jsonl")); !os.IsNotExist(err) { + t.Fatalf("events.jsonl stat = %v, want not exists", err) + } +} + func writeCityEventLog(t *testing.T, name string) string { t.Helper() path := filepath.Join(t.TempDir(), name) diff --git a/cmd/gc/cityinit_impl.go b/cmd/gc/cityinit_impl.go index 1ca71cc5a4..253ccf1947 100644 --- a/cmd/gc/cityinit_impl.go +++ b/cmd/gc/cityinit_impl.go @@ -131,13 +131,15 @@ func (localInitializer) Scaffold(_ context.Context, req cityinit.InitRequest) (* // reconciler's tick. The standard registerCityWithSupervisor // waits for prepareCityForSupervisor to complete, which is the // very blocking behavior the async POST /v0/city contract - // exists to avoid. registerCityForAPI fires a reload signal at - // the supervisor and returns immediately; the reconciler picks - // up the city on its own schedule. + // exists to avoid. if err := registerCityForAPI(dir, req.NameOverride); err != nil { + if cleanupErr := os.RemoveAll(dir); cleanupErr != nil { + return nil, errors.Join(fmt.Errorf("register with supervisor: %w", err), fmt.Errorf("cleaning scaffold after failed registration: %w", cleanupErr)) + } return nil, fmt.Errorf("register with supervisor: %w", err) } recordCityEvent(dir, events.CityCreated, cityName, api.CityCreatedPayload{Name: cityName, Path: dir}) + reloadSupervisorNoWaitHook() return &cityinit.InitResult{ CityName: cityName, diff --git a/cmd/gc/cityinit_impl_test.go b/cmd/gc/cityinit_impl_test.go index f2e76a2aed..038afafb34 100644 --- a/cmd/gc/cityinit_impl_test.go +++ b/cmd/gc/cityinit_impl_test.go @@ -107,6 +107,18 @@ func TestValidateInitRequest(t *testing.T) { func TestLocalInitializerScaffoldCreatesCityRegistersAndEmitsCreated(t *testing.T) { t.Setenv("GC_HOME", t.TempDir()) cityPath := filepath.Join(t.TempDir(), "api-city") + reloadSawCreated := 0 + + oldReloadSupervisorNoWaitHook := reloadSupervisorNoWaitHook + reloadSupervisorNoWaitHook = func() { + evts, err := events.ReadFiltered(filepath.Join(cityPath, ".gc", "events.jsonl"), events.Filter{Type: events.CityCreated}) + if err == nil { + reloadSawCreated = len(evts) + } + } + t.Cleanup(func() { + reloadSupervisorNoWaitHook = oldReloadSupervisorNoWaitHook + }) result, err := localInitializer{}.Scaffold(context.Background(), cityinit.InitRequest{ Dir: cityPath, @@ -152,6 +164,9 @@ func TestLocalInitializerScaffoldCreatesCityRegistersAndEmitsCreated(t *testing. t.Fatalf("payload name = %q, want api-city", payload.Name) } assertSameTestPath(t, payload.Path, cityPath) + if reloadSawCreated != 1 { + t.Fatalf("reload saw %d city.created events, want 1 before wake", reloadSawCreated) + } _, err = localInitializer{}.Scaffold(context.Background(), cityinit.InitRequest{ Dir: cityPath, @@ -183,6 +198,9 @@ func TestLocalInitializerScaffoldDoesNotEmitCreatedWhenRegisterFails(t *testing. if err == nil || !strings.Contains(err.Error(), "register with supervisor") { t.Fatalf("Scaffold error = %v, want register with supervisor failure", err) } + if _, statErr := os.Stat(cityPath); !os.IsNotExist(statErr) { + t.Fatalf("cityPath stat after failed registration = %v, want not exists", statErr) + } evts, readErr := events.ReadFiltered(filepath.Join(cityPath, ".gc", "events.jsonl"), events.Filter{Type: events.CityCreated}) if readErr != nil && !os.IsNotExist(readErr) { diff --git a/cmd/gc/cmd_supervisor_city.go b/cmd/gc/cmd_supervisor_city.go index 98fc37b6b5..0be16f7fbf 100644 --- a/cmd/gc/cmd_supervisor_city.go +++ b/cmd/gc/cmd_supervisor_city.go @@ -28,6 +28,7 @@ var ( var ( registerCityWithSupervisorTestHook func(cityPath, commandName string, stdout, stderr io.Writer) (bool, int) supervisorCityErrorHook = supervisorCityError + reloadSupervisorNoWaitHook = reloadSupervisorNoWait ) type supervisorRegistry interface { @@ -252,17 +253,11 @@ func registerCityWithSupervisorNamed(cityPath, nameOverride string, stdout, stde return 0 } -// registerCityForAPI is the fire-and-forget variant of -// registerCityWithSupervisor for the async POST /v0/city path. -// Writes the city to the supervisor registry and signals the -// reconciler to wake up, but does NOT wait for the reconciler to -// finish processing the city — if we waited, POST would block on -// the full prepareCityForSupervisor (pack materialization, beads -// lifecycle, etc.) which is the exact behavior the async contract -// was designed to avoid. The reconciler picks up the city on its -// next tick; subscribers to /v0/events/stream see city.created -// only after registration succeeds, then city.ready or -// city.init_failed when the reconciler completes. +// registerCityForAPI is the registry-write portion of async +// POST /v0/city. It records the city in the supervisor registry but +// intentionally does NOT wait for readiness. Callers are responsible +// for emitting any lifecycle events they need before waking the +// reconciler, so event ordering stays deterministic. func registerCityForAPI(cityPath, nameOverride string) error { cityPath = normalizePathForCompare(cityPath) name, err := registeredCityName(cityPath, nameOverride) @@ -273,10 +268,6 @@ func registerCityForAPI(cityPath, nameOverride string) error { if err := reg.Register(cityPath, name); err != nil { return err } - // Best-effort wake: if the supervisor isn't reachable, fall - // through; the periodic ticker will pick up the registry entry - // on its next interval. - reloadSupervisorNoWait() return nil } diff --git a/cmd/gc/controller.go b/cmd/gc/controller.go index cb0664b705..d71e2afbc4 100644 --- a/cmd/gc/controller.go +++ b/cmd/gc/controller.go @@ -1152,7 +1152,11 @@ func runController( if readOnly { fmt.Fprintf(stderr, "api: binding to %s — mutation endpoints disabled (non-localhost)\n", bind) //nolint:errcheck } - apiMux := api.NewSupervisorMux(&singleCityStateResolver{state: cs}, NewInitializer(), readOnly, "controller", time.Now()) + // Standalone controller mode serves one existing city. It does + // not own the supervisor registry/reconciler path required by + // async POST /v0/city, so leave the initializer nil and let the + // handler return 501 for create/unregister routes. + apiMux := api.NewSupervisorMux(&singleCityStateResolver{state: cs}, nil, readOnly, "controller", time.Now()) addr := net.JoinHostPort(bind, strconv.Itoa(cfg.API.Port)) apiLis, apiErr := net.Listen("tcp", addr) if apiErr != nil { From 2dd19c8b62d1afce9804972e5b2e272f5b904a2d Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 24 Apr 2026 01:21:07 +0000 Subject: [PATCH 15/85] fix: seal cache and scaffold race regressions --- cmd/gc/cityinit_impl.go | 15 +- cmd/gc/cityinit_impl_test.go | 49 ++++++ internal/beads/caching_store_reads.go | 56 +++++- internal/beads/caching_store_test.go | 239 ++++++++++++++++++++++++++ 4 files changed, 353 insertions(+), 6 deletions(-) diff --git a/cmd/gc/cityinit_impl.go b/cmd/gc/cityinit_impl.go index 253ccf1947..1447a19e61 100644 --- a/cmd/gc/cityinit_impl.go +++ b/cmd/gc/cityinit_impl.go @@ -80,6 +80,12 @@ func (localInitializer) Scaffold(_ context.Context, req cityinit.InitRequest) (* return nil, err } dir := req.Dir + dirExisted := false + if _, err := os.Stat(dir); err == nil { + dirExisted = true + } else if !os.IsNotExist(err) { + return nil, fmt.Errorf("stat directory %q: %w", dir, err) + } if err := os.MkdirAll(dir, 0o755); err != nil { return nil, fmt.Errorf("creating directory %q: %w", dir, err) } @@ -133,8 +139,10 @@ func (localInitializer) Scaffold(_ context.Context, req cityinit.InitRequest) (* // very blocking behavior the async POST /v0/city contract // exists to avoid. if err := registerCityForAPI(dir, req.NameOverride); err != nil { - if cleanupErr := os.RemoveAll(dir); cleanupErr != nil { - return nil, errors.Join(fmt.Errorf("register with supervisor: %w", err), fmt.Errorf("cleaning scaffold after failed registration: %w", cleanupErr)) + if !dirExisted { + if cleanupErr := os.RemoveAll(dir); cleanupErr != nil { + return nil, errors.Join(fmt.Errorf("register with supervisor: %w", err), fmt.Errorf("cleaning scaffold after failed registration: %w", cleanupErr)) + } } return nil, fmt.Errorf("register with supervisor: %w", err) } @@ -294,6 +302,9 @@ func validateInitRequest(req *cityinit.InitRequest) error { if req.Provider == "" && req.StartCommand == "" { return fmt.Errorf("%w: provider or start_command required", cityinit.ErrInvalidProvider) } + if req.Provider != "" && req.StartCommand != "" { + return fmt.Errorf("%w: provider and start_command are mutually exclusive", cityinit.ErrInvalidProvider) + } if req.Provider != "" { if _, ok := config.BuiltinProviders()[req.Provider]; !ok { return fmt.Errorf("%w: unknown provider %q", cityinit.ErrInvalidProvider, req.Provider) diff --git a/cmd/gc/cityinit_impl_test.go b/cmd/gc/cityinit_impl_test.go index 038afafb34..be69abe0ce 100644 --- a/cmd/gc/cityinit_impl_test.go +++ b/cmd/gc/cityinit_impl_test.go @@ -60,6 +60,12 @@ func TestValidateInitRequest(t *testing.T) { req: cityinit.InitRequest{Dir: absDir}, wantErr: cityinit.ErrInvalidProvider, }, + { + name: "provider and start command are mutually exclusive", + req: cityinit.InitRequest{Dir: absDir, Provider: "codex", StartCommand: "custom-agent"}, + wantErr: cityinit.ErrInvalidProvider, + wantContains: "mutually exclusive", + }, { name: "unknown provider", req: cityinit.InitRequest{Dir: absDir, Provider: "not-a-provider"}, @@ -100,6 +106,11 @@ func TestValidateInitRequest(t *testing.T) { if !errors.Is(err, tc.wantErr) { t.Fatalf("validateInitRequest() error = %v, want %v", err, tc.wantErr) } + if tc.wantContains != "" { + if err == nil || !strings.Contains(err.Error(), tc.wantContains) { + t.Fatalf("validateInitRequest() error = %v, want message containing %q", err, tc.wantContains) + } + } }) } } @@ -211,6 +222,44 @@ func TestLocalInitializerScaffoldDoesNotEmitCreatedWhenRegisterFails(t *testing. } } +func TestLocalInitializerScaffoldPreservesExistingDirectoryWhenRegisterFails(t *testing.T) { + t.Setenv("GC_HOME", t.TempDir()) + cityPath := filepath.Join(t.TempDir(), "api-city") + keepPath := filepath.Join(cityPath, "keep.txt") + if err := os.MkdirAll(cityPath, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(keepPath, []byte("keep"), 0o644); err != nil { + t.Fatal(err) + } + + oldNewSupervisorRegistry := newSupervisorRegistry + newSupervisorRegistry = func() supervisorRegistry { + return &fakeSupervisorRegistry{registerErr: errors.New("boom")} + } + t.Cleanup(func() { + newSupervisorRegistry = oldNewSupervisorRegistry + }) + + _, err := localInitializer{}.Scaffold(context.Background(), cityinit.InitRequest{ + Dir: cityPath, + Provider: "codex", + BootstrapProfile: bootstrapProfileSingleHostCompat, + NameOverride: "api-city", + }) + if err == nil || !strings.Contains(err.Error(), "register with supervisor") { + t.Fatalf("Scaffold error = %v, want register with supervisor failure", err) + } + + data, readErr := os.ReadFile(keepPath) + if readErr != nil { + t.Fatalf("ReadFile(%q): %v", keepPath, readErr) + } + if string(data) != "keep" { + t.Fatalf("keep.txt = %q, want keep", string(data)) + } +} + func TestLocalInitializerInitScaffoldsAndFinalizes(t *testing.T) { cityPath := filepath.Join(t.TempDir(), "init-city") diff --git a/internal/beads/caching_store_reads.go b/internal/beads/caching_store_reads.go index d5c04a1a95..3413e3e988 100644 --- a/internal/beads/caching_store_reads.go +++ b/internal/beads/caching_store_reads.go @@ -15,9 +15,12 @@ func (c *CachingStore) List(query ListQuery) ([]Bead, error) { return nil, fmt.Errorf("listing beads: %w", ErrQueryRequiresScan) } if query.Live || query.ParentID != "" { + c.mu.RLock() + startSeq := c.mutationSeq + c.mu.RUnlock() items, err := c.backing.List(query) if err == nil { - c.refreshCachedBeads(query, items) + items = c.refreshCachedBeads(query, startSeq, items) } return items, err } @@ -80,7 +83,7 @@ func (c *CachingStore) List(query ListQuery) ([]Bead, error) { return c.backing.List(query) } -func (c *CachingStore) refreshCachedBeads(query ListQuery, items []Bead) { +func (c *CachingStore) refreshCachedBeads(query ListQuery, startSeq uint64, items []Bead) []Bead { refreshedParents := make(map[string]Bead) removedParents := make(map[string]struct{}) for _, id := range c.staleParentCacheIDs(query.ParentID, items) { @@ -95,31 +98,55 @@ func (c *CachingStore) refreshCachedBeads(query ListQuery, items []Bead) { } } if len(items) == 0 && len(refreshedParents) == 0 && len(removedParents) == 0 { - return + return items } c.mu.Lock() defer c.mu.Unlock() if c.state != cacheLive && c.state != cachePartial { - return + return items } + refreshed := make([]Bead, 0, len(items)) for _, item := range items { + switch { + case c.deletedSeq[item.ID] > startSeq: + continue + case c.beadSeq[item.ID] > startSeq: + current, ok := c.beads[item.ID] + if ok && query.Matches(current) { + refreshed = append(refreshed, cloneBead(current)) + } + continue + } c.beads[item.ID] = cloneBead(item) delete(c.dirty, item.ID) delete(c.deletedSeq, item.ID) + delete(c.beadSeq, item.ID) + if query.Matches(item) { + refreshed = append(refreshed, cloneBead(item)) + } } for id, bead := range refreshedParents { + if c.deletedSeq[id] > startSeq || c.beadSeq[id] > startSeq { + continue + } c.beads[id] = bead delete(c.dirty, id) delete(c.deletedSeq, id) + delete(c.beadSeq, id) } for id := range removedParents { + if c.deletedSeq[id] > startSeq || c.beadSeq[id] > startSeq { + continue + } delete(c.beads, id) delete(c.deps, id) delete(c.dirty, id) delete(c.deletedSeq, id) + delete(c.beadSeq, id) } c.markFreshLocked(time.Now()) c.updateStatsLocked() + return refreshed } func (c *CachingStore) staleParentCacheIDs(parentID string, fresh []Bead) []string { @@ -165,12 +192,33 @@ func (c *CachingStore) Get(id string) (Bead, error) { c.mu.RLock() if c.state == cacheLive || c.state == cachePartial { if _, ok := c.dirty[id]; ok { + startSeq := c.mutationSeq c.mu.RUnlock() fresh, err := c.backing.Get(id) if err != nil { return Bead{}, err } c.mu.Lock() + if c.state != cacheLive && c.state != cachePartial { + c.mu.Unlock() + return fresh, nil + } + switch { + case c.deletedSeq[id] > startSeq: + c.mu.Unlock() + return Bead{}, ErrNotFound + case c.beadSeq[id] > startSeq: + if _, stillDirty := c.dirty[id]; stillDirty { + c.mu.Unlock() + return c.backing.Get(id) + } + if current, ok := c.beads[id]; ok { + c.mu.Unlock() + return cloneBead(current), nil + } + c.mu.Unlock() + return Bead{}, ErrNotFound + } c.beads[id] = cloneBead(fresh) delete(c.dirty, id) delete(c.deletedSeq, id) diff --git a/internal/beads/caching_store_test.go b/internal/beads/caching_store_test.go index c821b5226a..675227e778 100644 --- a/internal/beads/caching_store_test.go +++ b/internal/beads/caching_store_test.go @@ -270,6 +270,193 @@ func TestCachingStoreParentListRefreshesReparentedChildren(t *testing.T) { } } +func TestCachingStoreParentListPreservesConcurrentUpdate(t *testing.T) { + mem := beads.NewMemStore() + parent, err := mem.Create(beads.Bead{Title: "parent"}) + if err != nil { + t.Fatalf("Create(parent): %v", err) + } + child, err := mem.Create(beads.Bead{Title: "before", ParentID: parent.ID}) + if err != nil { + t.Fatalf("Create(child): %v", err) + } + backing := &parentListRaceStore{ + Store: mem, + parentID: parent.ID, + started: make(chan struct{}), + release: make(chan struct{}), + stale: []beads.Bead{child}, + } + cs := beads.NewCachingStoreForTest(backing, nil) + if err := cs.Prime(context.Background()); err != nil { + t.Fatalf("Prime: %v", err) + } + + childrenCh := make(chan []beads.Bead, 1) + errCh := make(chan error, 1) + go func() { + children, listErr := cs.List(beads.ListQuery{ParentID: parent.ID}) + if listErr != nil { + errCh <- listErr + return + } + childrenCh <- children + }() + + <-backing.started + title := "after concurrent update" + if err := cs.Update(child.ID, beads.UpdateOpts{Title: &title}); err != nil { + t.Fatalf("Update: %v", err) + } + close(backing.release) + + select { + case listErr := <-errCh: + t.Fatalf("List(parent): %v", listErr) + case children := <-childrenCh: + if len(children) != 1 || children[0].Title != title { + t.Fatalf("List(parent) = %#v, want updated title %q", children, title) + } + } + + got, err := cs.Get(child.ID) + if err != nil { + t.Fatalf("Get: %v", err) + } + if got.Title != title { + t.Fatalf("Get title = %q, want %q", got.Title, title) + } +} + +func TestCachingStoreParentListDoesNotResurrectConcurrentDelete(t *testing.T) { + mem := beads.NewMemStore() + parent, err := mem.Create(beads.Bead{Title: "parent"}) + if err != nil { + t.Fatalf("Create(parent): %v", err) + } + child, err := mem.Create(beads.Bead{Title: "before", ParentID: parent.ID}) + if err != nil { + t.Fatalf("Create(child): %v", err) + } + backing := &parentListRaceStore{ + Store: mem, + parentID: parent.ID, + started: make(chan struct{}), + release: make(chan struct{}), + stale: []beads.Bead{child}, + } + cs := beads.NewCachingStoreForTest(backing, nil) + if err := cs.Prime(context.Background()); err != nil { + t.Fatalf("Prime: %v", err) + } + + childrenCh := make(chan []beads.Bead, 1) + errCh := make(chan error, 1) + go func() { + children, listErr := cs.List(beads.ListQuery{ParentID: parent.ID}) + if listErr != nil { + errCh <- listErr + return + } + childrenCh <- children + }() + + <-backing.started + if err := cs.Delete(child.ID); err != nil { + t.Fatalf("Delete: %v", err) + } + close(backing.release) + + select { + case listErr := <-errCh: + t.Fatalf("List(parent): %v", listErr) + case children := <-childrenCh: + if len(children) != 0 { + t.Fatalf("List(parent) = %#v, want deleted child omitted", children) + } + } + + if _, err := cs.Get(child.ID); !errors.Is(err, beads.ErrNotFound) { + t.Fatalf("Get after delete error = %v, want ErrNotFound", err) + } +} + +func TestCachingStoreDirtyGetPreservesConcurrentEvent(t *testing.T) { + mem := beads.NewMemStore() + original, err := mem.Create(beads.Bead{Title: "before"}) + if err != nil { + t.Fatalf("Create: %v", err) + } + backing := &dirtyGetRaceStore{Store: mem} + cs := beads.NewCachingStoreForTest(backing, nil) + if err := cs.Prime(context.Background()); err != nil { + t.Fatalf("Prime: %v", err) + } + + firstTitle := "after first update" + backing.mu.Lock() + backing.failNextGet = true + backing.mu.Unlock() + if err := cs.Update(original.ID, beads.UpdateOpts{Title: &firstTitle}); err != nil { + t.Fatalf("Update(first): %v", err) + } + + stale, err := mem.Get(original.ID) + if err != nil { + t.Fatalf("Get(backing stale): %v", err) + } + backing.mu.Lock() + backing.stale = stale + backing.started = make(chan struct{}) + backing.release = make(chan struct{}) + backing.blockNextGet = true + backing.mu.Unlock() + + gotCh := make(chan beads.Bead, 1) + errCh := make(chan error, 1) + go func() { + got, getErr := cs.Get(original.ID) + if getErr != nil { + errCh <- getErr + return + } + gotCh <- got + }() + + <-backing.started + secondTitle := "after concurrent event" + if err := mem.Update(original.ID, beads.UpdateOpts{Title: &secondTitle}); err != nil { + t.Fatalf("Update(backing second): %v", err) + } + eventBead, err := mem.Get(original.ID) + if err != nil { + t.Fatalf("Get(backing second): %v", err) + } + payload, err := json.Marshal(eventBead) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + cs.ApplyEvent("bead.updated", payload) + close(backing.release) + + select { + case getErr := <-errCh: + t.Fatalf("Get: %v", getErr) + case got := <-gotCh: + if got.Title != secondTitle { + t.Fatalf("Get title = %q, want %q", got.Title, secondTitle) + } + } + + got, err := cs.Get(original.ID) + if err != nil { + t.Fatalf("Get cached: %v", err) + } + if got.Title != secondTitle { + t.Fatalf("cached title = %q, want %q", got.Title, secondTitle) + } +} + func TestCachingStoreUpdateReflectsWriteIntentWhenImmediateReadIsStale(t *testing.T) { mem := beads.NewMemStore() original, err := mem.Create(beads.Bead{ @@ -412,6 +599,58 @@ func (s *primeRaceStore) List(query beads.ListQuery) ([]beads.Bead, error) { return append([]beads.Bead(nil), s.stale...), nil } +type parentListRaceStore struct { + beads.Store + parentID string + started chan struct{} + release chan struct{} + stale []beads.Bead + once sync.Once +} + +func (s *parentListRaceStore) List(query beads.ListQuery) ([]beads.Bead, error) { + if query.ParentID != s.parentID { + return s.Store.List(query) + } + s.once.Do(func() { + close(s.started) + }) + <-s.release + return append([]beads.Bead(nil), s.stale...), nil +} + +type dirtyGetRaceStore struct { + beads.Store + mu sync.Mutex + failNextGet bool + blockNextGet bool + started chan struct{} + release chan struct{} + stale beads.Bead +} + +func (s *dirtyGetRaceStore) Get(id string) (beads.Bead, error) { + s.mu.Lock() + switch { + case s.failNextGet: + s.failNextGet = false + s.mu.Unlock() + return beads.Bead{}, errors.New("transient get failure") + case s.blockNextGet && id == s.stale.ID: + s.blockNextGet = false + started := s.started + release := s.release + stale := s.stale + s.mu.Unlock() + close(started) + <-release + return stale, nil + default: + s.mu.Unlock() + return s.Store.Get(id) + } +} + func TestCachingStoreGetFallsBackForClosedBeadsAfterPrime(t *testing.T) { t.Parallel() mem := beads.NewMemStore() From de3ca664f90ee8b9c4602f21daa3761f889143fb Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 24 Apr 2026 01:35:04 +0000 Subject: [PATCH 16/85] fix: fully roll back failed city scaffolds --- cmd/gc/cityinit_impl.go | 165 ++++++++++++++++++++++++++++++++++- cmd/gc/cityinit_impl_test.go | 20 +++++ 2 files changed, 182 insertions(+), 3 deletions(-) diff --git a/cmd/gc/cityinit_impl.go b/cmd/gc/cityinit_impl.go index 1447a19e61..2536359951 100644 --- a/cmd/gc/cityinit_impl.go +++ b/cmd/gc/cityinit_impl.go @@ -22,10 +22,12 @@ import ( "io" "os" "path/filepath" + "sort" "strings" "github.com/gastownhall/gascity/internal/api" "github.com/gastownhall/gascity/internal/cityinit" + "github.com/gastownhall/gascity/internal/citylayout" "github.com/gastownhall/gascity/internal/config" "github.com/gastownhall/gascity/internal/events" "github.com/gastownhall/gascity/internal/fsys" @@ -68,6 +70,154 @@ func recordCityEvent(cityPath, eventType, subject string, payload any) { }) } +type scaffoldRollbackEntry struct { + mode os.FileMode + data []byte + linkTarget string +} + +type scaffoldRollbackState struct { + root string + entries map[string]scaffoldRollbackEntry +} + +func captureScaffoldRollbackState(root string) (*scaffoldRollbackState, error) { + state := &scaffoldRollbackState{ + root: root, + entries: make(map[string]scaffoldRollbackEntry), + } + for _, rel := range scaffoldManagedPaths() { + if err := state.capture(rel); err != nil { + return nil, err + } + } + return state, nil +} + +func scaffoldManagedPaths() []string { + seen := make(map[string]struct{}, len(initConventionDirs)+5) + paths := make([]string, 0, len(initConventionDirs)+5) + add := func(rel string) { + if rel == "" { + return + } + if _, ok := seen[rel]; ok { + return + } + seen[rel] = struct{}{} + paths = append(paths, rel) + } + + add(citylayout.RuntimeRoot) + add("hooks") + add(citylayout.CityConfigFile) + add("pack.toml") + add(".gitignore") + for _, rel := range initConventionDirs { + add(rel) + } + return paths +} + +func (s *scaffoldRollbackState) capture(rel string) error { + abs := filepath.Join(s.root, rel) + _, err := os.Lstat(abs) + if os.IsNotExist(err) { + return nil + } + if err != nil { + return fmt.Errorf("snapshot %q: %w", abs, err) + } + return filepath.Walk(abs, func(path string, info os.FileInfo, walkErr error) error { + if walkErr != nil { + return fmt.Errorf("snapshot %q: %w", path, walkErr) + } + relPath, err := filepath.Rel(s.root, path) + if err != nil { + return fmt.Errorf("relative path for %q: %w", path, err) + } + entry := scaffoldRollbackEntry{mode: info.Mode()} + if info.Mode()&os.ModeSymlink != 0 { + target, err := os.Readlink(path) + if err != nil { + return fmt.Errorf("readlink %q: %w", path, err) + } + entry.linkTarget = target + } else if !info.IsDir() { + data, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("read %q: %w", path, err) + } + entry.data = data + } + s.entries[filepath.Clean(relPath)] = entry + return nil + }) +} + +func (s *scaffoldRollbackState) restore() error { + current, err := captureScaffoldRollbackState(s.root) + if err != nil { + return err + } + + var toRemove []string + for rel, entry := range current.entries { + previous, ok := s.entries[rel] + if !ok || entry.mode.IsDir() != previous.mode.IsDir() || (entry.mode&os.ModeSymlink != 0) != (previous.mode&os.ModeSymlink != 0) { + toRemove = append(toRemove, rel) + } + } + sort.Slice(toRemove, func(i, j int) bool { + return len(toRemove[i]) > len(toRemove[j]) + }) + + var errs []error + for _, rel := range toRemove { + if err := os.Remove(filepath.Join(s.root, rel)); err != nil && !os.IsNotExist(err) { + errs = append(errs, fmt.Errorf("remove %q: %w", filepath.Join(s.root, rel), err)) + } + } + + var rels []string + for rel := range s.entries { + rels = append(rels, rel) + } + sort.Strings(rels) + + for _, rel := range rels { + entry := s.entries[rel] + abs := filepath.Join(s.root, rel) + switch { + case entry.mode.IsDir(): + if err := os.MkdirAll(abs, entry.mode.Perm()); err != nil { + errs = append(errs, fmt.Errorf("restore dir %q: %w", abs, err)) + } + case entry.mode&os.ModeSymlink != 0: + if err := os.MkdirAll(filepath.Dir(abs), 0o755); err != nil { + errs = append(errs, fmt.Errorf("restore parent dir for %q: %w", abs, err)) + continue + } + if err := os.Symlink(entry.linkTarget, abs); err != nil && !os.IsExist(err) { + errs = append(errs, fmt.Errorf("restore symlink %q: %w", abs, err)) + } + default: + if err := os.MkdirAll(filepath.Dir(abs), 0o755); err != nil { + errs = append(errs, fmt.Errorf("restore parent dir for %q: %w", abs, err)) + continue + } + if err := os.WriteFile(abs, entry.data, entry.mode.Perm()); err != nil { + errs = append(errs, fmt.Errorf("restore file %q: %w", abs, err)) + } + } + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + return nil +} + // Scaffold runs the fast portion of city creation so the HTTP API // handler can return 202 Accepted without blocking on the slow // finalize work. Writes the on-disk shape (via doInit), then @@ -81,8 +231,13 @@ func (localInitializer) Scaffold(_ context.Context, req cityinit.InitRequest) (* } dir := req.Dir dirExisted := false + var rollbackState *scaffoldRollbackState if _, err := os.Stat(dir); err == nil { dirExisted = true + rollbackState, err = captureScaffoldRollbackState(dir) + if err != nil { + return nil, fmt.Errorf("snapshot rollback state for %q: %w", dir, err) + } } else if !os.IsNotExist(err) { return nil, fmt.Errorf("stat directory %q: %w", dir, err) } @@ -139,10 +294,14 @@ func (localInitializer) Scaffold(_ context.Context, req cityinit.InitRequest) (* // very blocking behavior the async POST /v0/city contract // exists to avoid. if err := registerCityForAPI(dir, req.NameOverride); err != nil { - if !dirExisted { - if cleanupErr := os.RemoveAll(dir); cleanupErr != nil { - return nil, errors.Join(fmt.Errorf("register with supervisor: %w", err), fmt.Errorf("cleaning scaffold after failed registration: %w", cleanupErr)) + if dirExisted { + if rollbackState != nil { + if cleanupErr := rollbackState.restore(); cleanupErr != nil { + return nil, errors.Join(fmt.Errorf("register with supervisor: %w", err), fmt.Errorf("restoring existing directory after failed registration: %w", cleanupErr)) + } } + } else if cleanupErr := os.RemoveAll(dir); cleanupErr != nil { + return nil, errors.Join(fmt.Errorf("register with supervisor: %w", err), fmt.Errorf("cleaning scaffold after failed registration: %w", cleanupErr)) } return nil, fmt.Errorf("register with supervisor: %w", err) } diff --git a/cmd/gc/cityinit_impl_test.go b/cmd/gc/cityinit_impl_test.go index be69abe0ce..292f387c22 100644 --- a/cmd/gc/cityinit_impl_test.go +++ b/cmd/gc/cityinit_impl_test.go @@ -258,6 +258,26 @@ func TestLocalInitializerScaffoldPreservesExistingDirectoryWhenRegisterFails(t * if string(data) != "keep" { t.Fatalf("keep.txt = %q, want keep", string(data)) } + if _, statErr := os.Stat(filepath.Join(cityPath, "city.toml")); !os.IsNotExist(statErr) { + t.Fatalf("city.toml stat after failed registration = %v, want not exists", statErr) + } + if _, statErr := os.Stat(filepath.Join(cityPath, ".gc")); !os.IsNotExist(statErr) { + t.Fatalf(".gc stat after failed registration = %v, want not exists", statErr) + } + + newSupervisorRegistry = oldNewSupervisorRegistry + result, err := localInitializer{}.Scaffold(context.Background(), cityinit.InitRequest{ + Dir: cityPath, + Provider: "codex", + BootstrapProfile: bootstrapProfileSingleHostCompat, + NameOverride: "api-city", + }) + if err != nil { + t.Fatalf("Scaffold retry: %v", err) + } + if result.CityName != "api-city" { + t.Fatalf("Scaffold retry city name = %q, want api-city", result.CityName) + } } func TestLocalInitializerInitScaffoldsAndFinalizes(t *testing.T) { From 44be8d495c551042dc03676c4ca0aba24cec2dcb Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 24 Apr 2026 01:46:40 +0000 Subject: [PATCH 17/85] fix: preserve concurrent edits during scaffold rollback --- cmd/gc/cityinit_impl.go | 157 +++++++++++++++++++++++++---------- cmd/gc/cityinit_impl_test.go | 17 ++++ 2 files changed, 131 insertions(+), 43 deletions(-) diff --git a/cmd/gc/cityinit_impl.go b/cmd/gc/cityinit_impl.go index 2536359951..9a2c8bc0dc 100644 --- a/cmd/gc/cityinit_impl.go +++ b/cmd/gc/cityinit_impl.go @@ -15,6 +15,7 @@ package main // is a follow-up. import ( + "bytes" "context" "encoding/json" "errors" @@ -24,6 +25,7 @@ import ( "path/filepath" "sort" "strings" + "syscall" "github.com/gastownhall/gascity/internal/api" "github.com/gastownhall/gascity/internal/cityinit" @@ -76,22 +78,28 @@ type scaffoldRollbackEntry struct { linkTarget string } -type scaffoldRollbackState struct { +type scaffoldSnapshot struct { root string entries map[string]scaffoldRollbackEntry } -func captureScaffoldRollbackState(root string) (*scaffoldRollbackState, error) { - state := &scaffoldRollbackState{ +type scaffoldRollbackState struct { + root string + before map[string]scaffoldRollbackEntry + after map[string]scaffoldRollbackEntry +} + +func captureScaffoldSnapshot(root string) (*scaffoldSnapshot, error) { + snapshot := &scaffoldSnapshot{ root: root, entries: make(map[string]scaffoldRollbackEntry), } for _, rel := range scaffoldManagedPaths() { - if err := state.capture(rel); err != nil { + if err := snapshot.capture(rel); err != nil { return nil, err } } - return state, nil + return snapshot, nil } func scaffoldManagedPaths() []string { @@ -119,7 +127,18 @@ func scaffoldManagedPaths() []string { return paths } -func (s *scaffoldRollbackState) capture(rel string) error { +func newScaffoldRollbackState(root string) (*scaffoldRollbackState, error) { + snapshot, err := captureScaffoldSnapshot(root) + if err != nil { + return nil, err + } + return &scaffoldRollbackState{ + root: root, + before: snapshot.entries, + }, nil +} + +func (s *scaffoldSnapshot) capture(rel string) error { abs := filepath.Join(s.root, rel) _, err := os.Lstat(abs) if os.IsNotExist(err) { @@ -155,60 +174,99 @@ func (s *scaffoldRollbackState) capture(rel string) error { }) } -func (s *scaffoldRollbackState) restore() error { - current, err := captureScaffoldRollbackState(s.root) +func (s *scaffoldRollbackState) markScaffoldState() error { + snapshot, err := captureScaffoldSnapshot(s.root) if err != nil { return err } + s.after = snapshot.entries + return nil +} - var toRemove []string - for rel, entry := range current.entries { - previous, ok := s.entries[rel] - if !ok || entry.mode.IsDir() != previous.mode.IsDir() || (entry.mode&os.ModeSymlink != 0) != (previous.mode&os.ModeSymlink != 0) { - toRemove = append(toRemove, rel) - } - } - sort.Slice(toRemove, func(i, j int) bool { - return len(toRemove[i]) > len(toRemove[j]) - }) +func rollbackEntryEqual(a, b scaffoldRollbackEntry) bool { + return a.mode == b.mode && a.linkTarget == b.linkTarget && bytes.Equal(a.data, b.data) +} - var errs []error - for _, rel := range toRemove { - if err := os.Remove(filepath.Join(s.root, rel)); err != nil && !os.IsNotExist(err) { - errs = append(errs, fmt.Errorf("remove %q: %w", filepath.Join(s.root, rel), err)) +func restoreRollbackEntry(abs string, entry scaffoldRollbackEntry) error { + switch { + case entry.mode.IsDir(): + return os.MkdirAll(abs, entry.mode.Perm()) + case entry.mode&os.ModeSymlink != 0: + if err := os.MkdirAll(filepath.Dir(abs), 0o755); err != nil { + return err + } + if err := os.Remove(abs); err != nil && !os.IsNotExist(err) { + return err + } + return os.Symlink(entry.linkTarget, abs) + default: + if err := os.MkdirAll(filepath.Dir(abs), 0o755); err != nil { + return err } + return os.WriteFile(abs, entry.data, entry.mode.Perm()) } +} - var rels []string - for rel := range s.entries { - rels = append(rels, rel) +func (s *scaffoldRollbackState) restore() error { + current, err := captureScaffoldSnapshot(s.root) + if err != nil { + return err } - sort.Strings(rels) - for _, rel := range rels { - entry := s.entries[rel] - abs := filepath.Join(s.root, rel) + var errs []error + var createdDirs []string + for rel, after := range s.after { + before, hadBefore := s.before[rel] + currentEntry, existsNow := current.entries[rel] switch { - case entry.mode.IsDir(): - if err := os.MkdirAll(abs, entry.mode.Perm()); err != nil { - errs = append(errs, fmt.Errorf("restore dir %q: %w", abs, err)) - } - case entry.mode&os.ModeSymlink != 0: - if err := os.MkdirAll(filepath.Dir(abs), 0o755); err != nil { - errs = append(errs, fmt.Errorf("restore parent dir for %q: %w", abs, err)) + case !hadBefore: + if after.mode.IsDir() { + createdDirs = append(createdDirs, rel) continue } - if err := os.Symlink(entry.linkTarget, abs); err != nil && !os.IsExist(err) { - errs = append(errs, fmt.Errorf("restore symlink %q: %w", abs, err)) + if existsNow && rollbackEntryEqual(currentEntry, after) { + if err := os.Remove(filepath.Join(s.root, rel)); err != nil && !os.IsNotExist(err) { + errs = append(errs, fmt.Errorf("remove %q: %w", filepath.Join(s.root, rel), err)) + } } + case rollbackEntryEqual(before, after): + continue default: - if err := os.MkdirAll(filepath.Dir(abs), 0o755); err != nil { - errs = append(errs, fmt.Errorf("restore parent dir for %q: %w", abs, err)) + if after.mode.IsDir() { continue } - if err := os.WriteFile(abs, entry.data, entry.mode.Perm()); err != nil { - errs = append(errs, fmt.Errorf("restore file %q: %w", abs, err)) + if existsNow && rollbackEntryEqual(currentEntry, after) { + if err := restoreRollbackEntry(filepath.Join(s.root, rel), before); err != nil { + errs = append(errs, fmt.Errorf("restore %q: %w", filepath.Join(s.root, rel), err)) + } + } + } + } + + for rel, before := range s.before { + if _, hadAfter := s.after[rel]; hadAfter { + continue + } + if before.mode.IsDir() { + continue + } + if _, existsNow := current.entries[rel]; existsNow { + continue + } + if err := restoreRollbackEntry(filepath.Join(s.root, rel), before); err != nil { + errs = append(errs, fmt.Errorf("restore %q: %w", filepath.Join(s.root, rel), err)) + } + } + + sort.Slice(createdDirs, func(i, j int) bool { + return len(createdDirs[i]) > len(createdDirs[j]) + }) + for _, rel := range createdDirs { + if err := os.Remove(filepath.Join(s.root, rel)); err != nil && !os.IsNotExist(err) { + if errors.Is(err, syscall.ENOTEMPTY) { + continue } + errs = append(errs, fmt.Errorf("remove %q: %w", filepath.Join(s.root, rel), err)) } } @@ -234,7 +292,7 @@ func (localInitializer) Scaffold(_ context.Context, req cityinit.InitRequest) (* var rollbackState *scaffoldRollbackState if _, err := os.Stat(dir); err == nil { dirExisted = true - rollbackState, err = captureScaffoldRollbackState(dir) + rollbackState, err = newScaffoldRollbackState(dir) if err != nil { return nil, fmt.Errorf("snapshot rollback state for %q: %w", dir, err) } @@ -259,6 +317,14 @@ func (localInitializer) Scaffold(_ context.Context, req cityinit.InitRequest) (* return nil, cityinit.ErrAlreadyInitialized } if code := doInit(fsys.OSFS{}, dir, wiz, req.NameOverride, io.Discard, io.Discard); code != 0 { + if dirExisted && rollbackState != nil { + if markErr := rollbackState.markScaffoldState(); markErr != nil { + return nil, errors.Join(fmt.Errorf("scaffold failed (exit %d)", code), fmt.Errorf("snapshot scaffold state for rollback: %w", markErr)) + } + if cleanupErr := rollbackState.restore(); cleanupErr != nil { + return nil, errors.Join(fmt.Errorf("scaffold failed (exit %d)", code), fmt.Errorf("restoring existing directory after scaffold failure: %w", cleanupErr)) + } + } if code == initExitAlreadyInitialized { return nil, cityinit.ErrAlreadyInitialized } @@ -287,6 +353,11 @@ func (localInitializer) Scaffold(_ context.Context, req cityinit.InitRequest) (* // only after registration succeeds so synchronous failures do not // leak a "created" event for a city the supervisor never adopted. ensureCityEventLog(dir) + if dirExisted && rollbackState != nil { + if err := rollbackState.markScaffoldState(); err != nil { + return nil, fmt.Errorf("snapshot scaffold state for %q: %w", dir, err) + } + } // Register the city with the supervisor without blocking on the // reconciler's tick. The standard registerCityWithSupervisor diff --git a/cmd/gc/cityinit_impl_test.go b/cmd/gc/cityinit_impl_test.go index 292f387c22..fa75d8ca7a 100644 --- a/cmd/gc/cityinit_impl_test.go +++ b/cmd/gc/cityinit_impl_test.go @@ -226,12 +226,19 @@ func TestLocalInitializerScaffoldPreservesExistingDirectoryWhenRegisterFails(t * t.Setenv("GC_HOME", t.TempDir()) cityPath := filepath.Join(t.TempDir(), "api-city") keepPath := filepath.Join(cityPath, "keep.txt") + hooksKeepPath := filepath.Join(cityPath, "hooks", "custom.json") if err := os.MkdirAll(cityPath, 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(keepPath, []byte("keep"), 0o644); err != nil { t.Fatal(err) } + if err := os.MkdirAll(filepath.Dir(hooksKeepPath), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(hooksKeepPath, []byte(`{"custom":true}`), 0o644); err != nil { + t.Fatal(err) + } oldNewSupervisorRegistry := newSupervisorRegistry newSupervisorRegistry = func() supervisorRegistry { @@ -258,12 +265,22 @@ func TestLocalInitializerScaffoldPreservesExistingDirectoryWhenRegisterFails(t * if string(data) != "keep" { t.Fatalf("keep.txt = %q, want keep", string(data)) } + hooksData, hooksReadErr := os.ReadFile(hooksKeepPath) + if hooksReadErr != nil { + t.Fatalf("ReadFile(%q): %v", hooksKeepPath, hooksReadErr) + } + if string(hooksData) != `{"custom":true}` { + t.Fatalf("custom hook file = %q, want preserved content", string(hooksData)) + } if _, statErr := os.Stat(filepath.Join(cityPath, "city.toml")); !os.IsNotExist(statErr) { t.Fatalf("city.toml stat after failed registration = %v, want not exists", statErr) } if _, statErr := os.Stat(filepath.Join(cityPath, ".gc")); !os.IsNotExist(statErr) { t.Fatalf(".gc stat after failed registration = %v, want not exists", statErr) } + if _, statErr := os.Stat(filepath.Join(cityPath, "hooks", "claude.json")); !os.IsNotExist(statErr) { + t.Fatalf("hooks/claude.json stat after failed registration = %v, want not exists", statErr) + } newSupervisorRegistry = oldNewSupervisorRegistry result, err := localInitializer{}.Scaffold(context.Background(), cityinit.InitRequest{ From 4369b2f86c6164c8aaafb385624ecc0cadcdc729 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 24 Apr 2026 07:57:56 -1000 Subject: [PATCH 18/85] Default Codex model to GPT-5.5 (#1171) ## Summary - add `gpt-5.5` as a Codex model schema choice - make `gpt-5.5` the built-in Codex model default - update provider/default-command tests for the new default ## Tests - `go test ./internal/worker/builtin ./internal/config` - `go test ./cmd/gc -run 'TestPhase2|TestResolveProvider|TestTemplate'`\n --- cmd/gc/phase2_reporting_test.go | 48 ++++++++++++++++++++++++-- cmd/gc/template_resolve_phase2_test.go | 2 +- internal/config/options_test.go | 21 +++++++++++ internal/config/resolve_test.go | 6 +++- internal/worker/builtin/profiles.go | 2 ++ 5 files changed, 75 insertions(+), 4 deletions(-) diff --git a/cmd/gc/phase2_reporting_test.go b/cmd/gc/phase2_reporting_test.go index 320b2d47a9..63de8f1236 100644 --- a/cmd/gc/phase2_reporting_test.go +++ b/cmd/gc/phase2_reporting_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + "github.com/gastownhall/gascity/internal/config" "github.com/gastownhall/gascity/internal/runtime" workertest "github.com/gastownhall/gascity/internal/worker/workertest" ) @@ -178,11 +179,11 @@ func inputOverrideDefaultsResult(tc phase2ProviderCase, prepared *preparedStart) return workertest.Fail(tc.profileID, workertest.RequirementInputOverrideDefaults, "ResolvedProvider = nil, want provider defaults for override comparison").WithEvidence(evidence) } - defaultArgs := prepared.candidate.tp.ResolvedProvider.ResolveDefaultArgs() + defaultArgs := defaultArgsExceptOption(prepared.candidate.tp.ResolvedProvider, "model") switch { case !containsOrderedArgs(prepared.cfg.Command, defaultArgs): return workertest.Fail(tc.profileID, workertest.RequirementInputOverrideDefaults, - fmt.Sprintf("Command = %q, want default args %v", prepared.cfg.Command, defaultArgs)).WithEvidence(evidence) + fmt.Sprintf("Command = %q, want non-model default args %v", prepared.cfg.Command, defaultArgs)).WithEvidence(evidence) case !containsOrderedArgs(prepared.cfg.Command, tc.wantModelOverrideArgs): return workertest.Fail(tc.profileID, workertest.RequirementInputOverrideDefaults, fmt.Sprintf("Command = %q, want model override args %v", prepared.cfg.Command, tc.wantModelOverrideArgs)).WithEvidence(evidence) @@ -198,6 +199,49 @@ func inputOverrideDefaultsResult(tc phase2ProviderCase, prepared *preparedStart) } } +func defaultArgsExceptOption(provider *config.ResolvedProvider, optionKey string) []string { + if provider == nil { + return nil + } + defaultArgs := provider.ResolveDefaultArgs() + defaultValue := provider.EffectiveDefaults[optionKey] + for _, opt := range provider.OptionsSchema { + if opt.Key == optionKey && defaultValue == "" { + defaultValue = opt.Default + } + if opt.Key != optionKey || defaultValue == "" { + continue + } + for _, choice := range opt.Choices { + if choice.Value == defaultValue { + return removeContiguousArgs(defaultArgs, choice.FlagArgs) + } + } + } + return defaultArgs +} + +func removeContiguousArgs(args, remove []string) []string { + if len(args) == 0 || len(remove) == 0 || len(remove) > len(args) { + return args + } + for i := 0; i <= len(args)-len(remove); i++ { + matched := true + for j := range remove { + if args[i+j] != remove[j] { + matched = false + break + } + } + if matched { + out := append([]string{}, args[:i]...) + out = append(out, args[i+len(remove):]...) + return out + } + } + return args +} + func phase2TemplateEvidence(tc phase2ProviderCase, tp TemplateParams) map[string]string { evidence := map[string]string{ "family": tc.family, diff --git a/cmd/gc/template_resolve_phase2_test.go b/cmd/gc/template_resolve_phase2_test.go index 5eba590d87..a1145425d7 100644 --- a/cmd/gc/template_resolve_phase2_test.go +++ b/cmd/gc/template_resolve_phase2_test.go @@ -67,7 +67,7 @@ func selectedPhase2ProviderCases(t *testing.T) []phase2ProviderCase { { profileID: "codex/tmux-cli", family: "codex", - wantCommand: "codex --dangerously-bypass-approvals-and-sandbox -c model_reasoning_effort=xhigh", + wantCommand: "codex --dangerously-bypass-approvals-and-sandbox --model gpt-5.5 -c model_reasoning_effort=xhigh", wantReadyDelayMs: 3000, wantReadyPromptPrefix: "› ", wantProcessNames: []string{"codex"}, diff --git a/internal/config/options_test.go b/internal/config/options_test.go index 359caed140..3e9faa8eaa 100644 --- a/internal/config/options_test.go +++ b/internal/config/options_test.go @@ -714,6 +714,13 @@ func TestBuiltinProviders_CodexHasNilArgsAndOptionDefaults(t *testing.T) { t.Errorf("codex OptionDefaults[permission_mode] = %q, want unrestricted", codex.OptionDefaults["permission_mode"]) } + if codex.OptionDefaults["model"] != "gpt-5.5" { + t.Errorf("codex OptionDefaults[model] = %q, want gpt-5.5", + codex.OptionDefaults["model"]) + } + if !schemaHasChoice(codex.OptionsSchema, "model", "gpt-5.5") { + t.Error("codex OptionsSchema missing model choice gpt-5.5") + } } func TestBuiltinProviders_GeminiHasNilArgsAndOptionDefaults(t *testing.T) { @@ -769,3 +776,17 @@ func TestValidateOptionDefaults_InvalidValue(t *testing.T) { t.Errorf("unexpected error: %v", err) } } + +func schemaHasChoice(schema []ProviderOption, key, value string) bool { + for _, opt := range schema { + if opt.Key != key { + continue + } + for _, choice := range opt.Choices { + if choice.Value == value { + return true + } + } + } + return false +} diff --git a/internal/config/resolve_test.go b/internal/config/resolve_test.go index 7d6f97b624..bd53da55b3 100644 --- a/internal/config/resolve_test.go +++ b/internal/config/resolve_test.go @@ -112,7 +112,11 @@ func TestResolveProviderWorkspaceProvider(t *testing.T) { t.Errorf("CommandString() = %q, want %q", rp.CommandString(), "codex") } defaultArgs := rp.ResolveDefaultArgs() - codexWantArgs := []string{"--dangerously-bypass-approvals-and-sandbox", "-c", "model_reasoning_effort=xhigh"} + codexWantArgs := []string{ + "--dangerously-bypass-approvals-and-sandbox", + "--model", "gpt-5.5", + "-c", "model_reasoning_effort=xhigh", + } if len(defaultArgs) != len(codexWantArgs) { t.Errorf("ResolveDefaultArgs() = %v, want %v", defaultArgs, codexWantArgs) } else { diff --git a/internal/worker/builtin/profiles.go b/internal/worker/builtin/profiles.go index b3dbfd5821..2e30cad529 100644 --- a/internal/worker/builtin/profiles.go +++ b/internal/worker/builtin/profiles.go @@ -149,6 +149,7 @@ var builtinProviderSpecs = map[string]BuiltinProviderSpec{ Command: "codex", OptionDefaults: map[string]string{ "permission_mode": "unrestricted", + "model": "gpt-5.5", "effort": "xhigh", }, PromptMode: "arg", @@ -184,6 +185,7 @@ var builtinProviderSpecs = map[string]BuiltinProviderSpec{ Type: "select", Choices: []BuiltinOptionChoice{ {Value: "", Label: "Default"}, + {Value: "gpt-5.5", Label: "GPT-5.5", FlagArgs: []string{"--model", "gpt-5.5"}}, {Value: "o3", Label: "o3", FlagArgs: []string{"--model", "o3"}}, {Value: "o4-mini", Label: "o4-mini", FlagArgs: []string{"--model", "o4-mini"}}, }, From efa58692c0f89501375d22bf10d44932d14d4849 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 24 Apr 2026 18:05:28 +0000 Subject: [PATCH 19/85] test: fix supervisor constructor call after rebase --- internal/api/handler_sling_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/api/handler_sling_test.go b/internal/api/handler_sling_test.go index 5b4ab296bc..82949689dd 100644 --- a/internal/api/handler_sling_test.go +++ b/internal/api/handler_sling_test.go @@ -378,7 +378,7 @@ func TestSlingProblemTypesDocumentedInOpenAPI(t *testing.T) { func TestDocumentProblemTypesIsIdempotent(t *testing.T) { state := newFakeMutatorState(t) - sm := NewSupervisorMux(&stateCityResolver{state: state}, false, "test", time.Now()) + sm := NewSupervisorMux(&stateCityResolver{state: state}, nil, false, "test", time.Now()) oapi := sm.humaAPI.OpenAPI() documentProblemTypes(oapi) From 5a85a45f00fc0085a40c9b4a8fa554723fc8509a Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 24 Apr 2026 18:44:46 +0000 Subject: [PATCH 20/85] test: make isolated city init tests hermetic --- cmd/gc/cityinit_impl_test.go | 1 + test/integration/huma_binary_test.go | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/cmd/gc/cityinit_impl_test.go b/cmd/gc/cityinit_impl_test.go index fa75d8ca7a..7046445593 100644 --- a/cmd/gc/cityinit_impl_test.go +++ b/cmd/gc/cityinit_impl_test.go @@ -298,6 +298,7 @@ func TestLocalInitializerScaffoldPreservesExistingDirectoryWhenRegisterFails(t * } func TestLocalInitializerInitScaffoldsAndFinalizes(t *testing.T) { + configureTestDoltIdentityEnv(t) cityPath := filepath.Join(t.TempDir(), "init-city") result, err := localInitializer{}.Init(context.Background(), cityinit.InitRequest{ diff --git a/test/integration/huma_binary_test.go b/test/integration/huma_binary_test.go index 41cba34ae3..902087883d 100644 --- a/test/integration/huma_binary_test.go +++ b/test/integration/huma_binary_test.go @@ -316,12 +316,12 @@ func TestHumaBinary_CityCreateAsync(t *testing.T) { } port := reserveFreePort(t) writeSupervisorConfig(t, gcHome, port) + if err := seedDoltIdentityForRoot(gcHome); err != nil { + t.Fatalf("seed dolt identity: %v", err) + } baseURL := "http://127.0.0.1:" + strconv.Itoa(port) - env := append(os.Environ(), - "GC_HOME="+gcHome, - "XDG_RUNTIME_DIR="+runtimeDir, - ) + env := integrationEnvFor(gcHome, runtimeDir, true) ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -483,12 +483,12 @@ func TestHumaBinary_CityUnregisterAsync(t *testing.T) { } port := reserveFreePort(t) writeSupervisorConfig(t, gcHome, port) + if err := seedDoltIdentityForRoot(gcHome); err != nil { + t.Fatalf("seed dolt identity: %v", err) + } baseURL := "http://127.0.0.1:" + strconv.Itoa(port) - env := append(os.Environ(), - "GC_HOME="+gcHome, - "XDG_RUNTIME_DIR="+runtimeDir, - ) + env := integrationEnvFor(gcHome, runtimeDir, true) ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) From 32b2a64b5efd0976a8d404215b3a9d97c9fdd9db Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 24 Apr 2026 17:41:25 +0000 Subject: [PATCH 21/85] fix: atomically materialize system pack files --- cmd/gc/embed_builtin_packs.go | 40 ++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/cmd/gc/embed_builtin_packs.go b/cmd/gc/embed_builtin_packs.go index fc5f223635..2cc68e2dab 100644 --- a/cmd/gc/embed_builtin_packs.go +++ b/cmd/gc/embed_builtin_packs.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "io" "io/fs" "os" "path/filepath" @@ -160,10 +161,47 @@ func materializeFS(embedded fs.FS, root, dstDir string) error { if isExecutableScriptFilename(path) { perm = 0o755 } - return os.WriteFile(dst, data, perm) + return writeFileAtomic(dst, data, perm) }) } +// writeFileAtomic replaces path without exposing a truncated file to readers. +// System pack skills are watched by provider CLIs, so direct os.WriteFile can +// surface transient invalid SKILL.md warnings during gc start/prime. +func writeFileAtomic(path string, data []byte, perm os.FileMode) error { + tmp, err := os.CreateTemp(filepath.Dir(path), "."+filepath.Base(path)+".tmp-*") + if err != nil { + return err + } + tmpName := tmp.Name() + cleanup := true + defer func() { + if cleanup { + _ = os.Remove(tmpName) + } + }() + + if n, err := tmp.Write(data); err != nil { + _ = tmp.Close() + return err + } else if n != len(data) { + _ = tmp.Close() + return io.ErrShortWrite + } + if err := tmp.Chmod(perm); err != nil { + _ = tmp.Close() + return err + } + if err := tmp.Close(); err != nil { + return err + } + if err := os.Rename(tmpName, path); err != nil { + return err + } + cleanup = false + return nil +} + // isExecutableScriptFilename reports whether a materialized pack asset // should be marked executable. Shell, Python, and bash interpreters all // rely on shebang-based direct execution, so the file needs +x regardless From 241867dd0e31201fe0a91f24d549633e6d438e1a Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 24 Apr 2026 16:05:52 -1000 Subject: [PATCH 22/85] tune dolt-gc-nudge: unconditional GC every 1h (perf evidence) (#1196) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Change `dolt-gc-nudge` interval from **6h → 1h** - Change default `GC_DOLT_GC_THRESHOLD_BYTES` from **2 GiB → 0** (run unconditionally; knob preserved as optional escape hatch) - Update runbook + script comments with empirical evidence ## Why A beads perf harness run (2026-04) shows Dolt's auto-GC **does not fire under bd's fork-per-op CLI workload** even with `auto_gc_behavior.enable: true` and the managed `config.yaml` we ship. Unbounded commit-graph growth causes two prod symptoms: - Disk bloat — ~120 GB after a few days of agent work - Tail latency — at 143 MB of accumulated history: **p99 = 16.9s, max = 18.6s**; cost scales roughly linearly with history size, extrapolating to multi-minute ops at GB scale Manual `CALL DOLT_GC()` on a 148 MB store reclaims 43% in 4.6s, so a periodic nudge is both necessary and cheap. The pre-tune defaults (2 GiB threshold + 6h cooldown) allow bloat to climb well into the tail-latency danger zone between nudges. At 1h unconditional GC, the commit graph stays bounded and p99 stays low. ## Test plan - [x] `go test ./cmd/gc/ -run 'DoltGCNudge|Order'` passes locally (existing tests already set `GC_DOLT_GC_THRESHOLD_BYTES=0` explicitly, so no test changes needed) - [ ] CI green - [ ] Prod observation post-merge: `gc doctor` reports clean `dolt-noms-size` on cities that previously bloated 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) --- docs/troubleshooting/dolt-bloat-recovery.md | 18 ++++++++----- examples/dolt/commands/gc-nudge/run.sh | 30 ++++++++++++--------- examples/dolt/orders/dolt-gc-nudge.toml | 4 +-- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/docs/troubleshooting/dolt-bloat-recovery.md b/docs/troubleshooting/dolt-bloat-recovery.md index 12eda71b99..23559cec0f 100644 --- a/docs/troubleshooting/dolt-bloat-recovery.md +++ b/docs/troubleshooting/dolt-bloat-recovery.md @@ -85,13 +85,17 @@ If GC finishes but the size barely moves, the chunks are nearly all live floor; newer releases ship improved auto-GC heuristics and default archive compression. - **Let the dolt pack's `dolt-gc-nudge` order run continuously.** It - ships embedded in the dolt pack and fires every 6h by default. The - nudge catches the "bloated-but-stable" corner case where no single - write burst crosses Dolt's 125 MB auto-GC threshold. To opt out on a - given city, add `dolt-gc-nudge` to the city's `[orders] skip = [...]` - list (or to a rig-level `[[order.override]]`). Tune the trigger size - via the `GC_DOLT_GC_THRESHOLD_BYTES` environment variable - (default: 2 GiB) in the city's environment. + ships embedded in the dolt pack and fires `CALL DOLT_GC()` every 1h + by default, unconditionally. Empirical evidence (beads perf harness, + 2026-04) shows Dolt's auto-GC does not fire under bd's fork-per-op + CLI workload even with `auto_gc_behavior.enable: true`, so the nudge + is the only mechanism bounding commit-graph growth. GC is idempotent + and near-free when there's nothing to reclaim, so running it every + hour is cheap. To opt out on a given city, add `dolt-gc-nudge` to the + city's `[orders] skip = [...]` list (or to a rig-level + `[[order.override]]`). To skip GC on small databases, set + `GC_DOLT_GC_THRESHOLD_BYTES` to a positive byte count in the city's + environment (default: 0 — run unconditionally). - **Mind `orders.max_timeout` if you set one.** The nudge order asks for a 24-hour timeout to accommodate serialized `CALL DOLT_GC()` runs on large stores. A city-level `orders.max_timeout` below 24h will cap the diff --git a/examples/dolt/commands/gc-nudge/run.sh b/examples/dolt/commands/gc-nudge/run.sh index 37af4e0d8c..8c22c62919 100755 --- a/examples/dolt/commands/gc-nudge/run.sh +++ b/examples/dolt/commands/gc-nudge/run.sh @@ -1,16 +1,19 @@ #!/bin/sh -# gc dolt gc-nudge — Size-triggered CALL DOLT_GC() to compact a bloated -# Dolt database. +# gc dolt gc-nudge — periodic CALL DOLT_GC() to bound the Dolt commit graph. # -# Why this exists: Dolt's auto-GC (default-on in 1.75+) fires on *growth* -# — 125 MB delta since last GC. A database that bloated once and then -# stabilized never auto-GCs on its own. This command closes that corner: -# it checks disk size on each registered rig's Dolt database, and if any -# are above the configured threshold, issues CALL DOLT_GC() against the -# managed sql-server. +# Why this exists: empirical evidence (beads perf harness, 2026-04) shows +# Dolt's auto-GC does not fire under the beads-CLI fork-per-op workload even +# with `auto_gc_behavior.enable: true`. Unbounded commit-graph growth causes +# both disk bloat (~120 GB after a few days) and tail-latency degradation +# (p99 at 143 MB of history → 16.9s, extrapolates to minutes at GB scale). +# Manual CALL DOLT_GC() reclaims ~43% in seconds, so a periodic nudge is +# both necessary and cheap. # -# Runs from the dolt pack's dolt-gc-nudge order on a slow cooldown (6h by -# default). Intended to be idempotent and cheap when nothing needs GC. +# Policy: fire CALL DOLT_GC() unconditionally on every cooldown tick +# (default 1h). The GC is idempotent and near-free when there's nothing +# to reclaim. A threshold knob remains as an optional escape hatch. +# +# Runs from the dolt pack's dolt-gc-nudge order. # # Environment: # GC_CITY_PATH (required) — city root @@ -19,8 +22,9 @@ # GC_DOLT_USER (default: root) # GC_DOLT_PASSWORD (optional) # GC_DOLT_GC_THRESHOLD_BYTES -# (default: 2147483648 = 2 GiB) — minimum .dolt/ size that triggers GC. -# Set to 0 to force GC on every tick (useful for tests). +# (default: 0 — run unconditionally). Set a positive byte count to +# skip GC on databases below that size; useful for test suites that +# don't want GC noise on tiny fixtures. # GC_DOLT_GC_CALL_TIMEOUT_SECS # (default: 1800) — wall-clock bound for one `CALL DOLT_GC()` invocation. # GC_DOLT_GC_DRY_RUN (optional) — when set, prints what would happen @@ -90,7 +94,7 @@ fi : "${GC_DOLT_USER:=root}" host="${GC_DOLT_HOST:-127.0.0.1}" -threshold="${GC_DOLT_GC_THRESHOLD_BYTES:-2147483648}" +threshold="${GC_DOLT_GC_THRESHOLD_BYTES:-0}" gc_call_timeout="${GC_DOLT_GC_CALL_TIMEOUT_SECS:-1800}" dry_run="${GC_DOLT_GC_DRY_RUN:-}" diff --git a/examples/dolt/orders/dolt-gc-nudge.toml b/examples/dolt/orders/dolt-gc-nudge.toml index 297f938dd1..a44b78c638 100644 --- a/examples/dolt/orders/dolt-gc-nudge.toml +++ b/examples/dolt/orders/dolt-gc-nudge.toml @@ -1,6 +1,6 @@ [order] -description = "Size-triggered CALL DOLT_GC() when a Dolt database grows past threshold" +description = "Periodic CALL DOLT_GC() to bound commit-graph size (Dolt's auto-GC doesn't fire on bd workloads)" trigger = "cooldown" -interval = "6h" +interval = "1h" exec = "gc dolt gc-nudge" timeout = "24h" From 62e4881e7beaa14ab3aefc42c760e4eb4899552a Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Sat, 25 Apr 2026 02:20:01 +0000 Subject: [PATCH 23/85] fix: persist supervisor provider env vars --- cmd/gc/cmd_start.go | 4 +- cmd/gc/cmd_start_test.go | 26 +++++++ cmd/gc/cmd_supervisor_lifecycle.go | 117 ++++++++++++++++++++++++++++- cmd/gc/cmd_supervisor_test.go | 65 ++++++++++++++++ 4 files changed, 209 insertions(+), 3 deletions(-) diff --git a/cmd/gc/cmd_start.go b/cmd/gc/cmd_start.go index da1bd15369..189f13fea8 100644 --- a/cmd/gc/cmd_start.go +++ b/cmd/gc/cmd_start.go @@ -981,7 +981,7 @@ func passthroughEnv() map[string]string { } else if home := os.Getenv("HOME"); home != "" { m["XDG_STATE_HOME"] = filepath.Join(home, ".local", "state") } - // Pass through all GC_* and ANTHROPIC_* vars. Agent credentials are + // Pass through GC_* vars and provider credential env. Agent credentials are // included in the global baseline because the SDK cannot know which // agent uses which provider (zero hardcoded roles); the trust boundary // is the managed session itself. @@ -990,7 +990,7 @@ func passthroughEnv() map[string]string { if !ok || val == "" { continue } - if strings.HasPrefix(key, "GC_") || strings.HasPrefix(key, "ANTHROPIC_") { + if strings.HasPrefix(key, "GC_") || isProviderCredentialEnv(key) { m[key] = val } } diff --git a/cmd/gc/cmd_start_test.go b/cmd/gc/cmd_start_test.go index d3e0ee02e3..10a049f53e 100644 --- a/cmd/gc/cmd_start_test.go +++ b/cmd/gc/cmd_start_test.go @@ -211,6 +211,32 @@ func TestPassthroughEnvIncludesClaudeAuthContext(t *testing.T) { } } +func TestPassthroughEnvIncludesProviderCredentialEnv(t *testing.T) { + t.Setenv("ANTHROPIC_API_KEY", "sk-ant-123") + t.Setenv("OPENAI_API_KEY", "sk-openai-123") + t.Setenv("OPENAI_BASE_URL", "https://openai.example.test") + t.Setenv("GEMINI_API_KEY", "gemini-123") + t.Setenv("GOOGLE_API_KEY", "google-123") + t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "/tmp/google-credentials.json") + t.Setenv("GOOGLE_CLOUD_PROJECT", "gc-project") + + got := passthroughEnv() + + for key, want := range map[string]string{ + "ANTHROPIC_API_KEY": "sk-ant-123", + "OPENAI_API_KEY": "sk-openai-123", + "OPENAI_BASE_URL": "https://openai.example.test", + "GEMINI_API_KEY": "gemini-123", + "GOOGLE_API_KEY": "google-123", + "GOOGLE_APPLICATION_CREDENTIALS": "/tmp/google-credentials.json", + "GOOGLE_CLOUD_PROJECT": "gc-project", + } { + if got[key] != want { + t.Errorf("passthroughEnv()[%s] = %q, want %q", key, got[key], want) + } + } +} + func TestPassthroughEnvXDGFallbackFromHOME(t *testing.T) { t.Setenv("HOME", "/tmp/gc-home") // Explicitly unset XDG vars so fallback logic fires. diff --git a/cmd/gc/cmd_supervisor_lifecycle.go b/cmd/gc/cmd_supervisor_lifecycle.go index a4e0c77df9..269311e5f7 100644 --- a/cmd/gc/cmd_supervisor_lifecycle.go +++ b/cmd/gc/cmd_supervisor_lifecycle.go @@ -15,6 +15,7 @@ import ( "path/filepath" "regexp" goruntime "runtime" + "sort" "strconv" "strings" "text/template" @@ -337,6 +338,12 @@ type supervisorServiceData struct { LaunchdLabel string SafeName string Path string + ExtraEnv []supervisorServiceEnvVar +} + +type supervisorServiceEnvVar struct { + Name string + Value string } func buildSupervisorServiceData() (*supervisorServiceData, error) { @@ -358,6 +365,7 @@ func buildSupervisorServiceData() (*supervisorServiceData, error) { LaunchdLabel: supervisorLaunchdLabel(), SafeName: sanitizeServiceName(filepath.Base(home)), Path: searchpath.ExpandPath(homeDir, goruntime.GOOS, os.Getenv("PATH")), + ExtraEnv: supervisorServiceExtraEnv(), }, nil } @@ -368,6 +376,103 @@ func sanitizeServiceName(name string) string { return strings.Trim(name, "-") } +var supervisorServiceEnvNameRE = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) + +// Keep persistent service-file env narrow. Provider credentials and user +// context need to survive launchd/systemd startup; arbitrary shell state can +// be opted in with GC_SUPERVISOR_ENV. +var supervisorServiceEnvKeys = map[string]bool{ + "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": true, + "CLAUDE_CODE_EFFORT_LEVEL": true, + "CLAUDE_CODE_OAUTH_TOKEN": true, + "CLAUDE_CODE_SUBAGENT_MODEL": true, + "CLAUDE_CONFIG_DIR": true, + "HOME": true, + "LANG": true, + "LC_ALL": true, + "LC_CTYPE": true, + "LOGNAME": true, + "SHELL": true, + "USER": true, + "XDG_CONFIG_HOME": true, + "XDG_STATE_HOME": true, +} + +var providerCredentialEnvPrefixes = []string{ + "ANTHROPIC_", + "GEMINI_", + "GOOGLE_", + "OPENAI_", +} + +var supervisorServiceFixedEnvKeys = map[string]bool{ + "GC_HOME": true, + "PATH": true, + "XDG_RUNTIME_DIR": true, +} + +func supervisorServiceExtraEnv() []supervisorServiceEnvVar { + env := make(map[string]string) + for _, entry := range os.Environ() { + key, val, ok := strings.Cut(entry, "=") + if !ok || val == "" || !shouldPersistSupervisorEnv(key) { + continue + } + env[key] = val + } + for _, key := range supervisorServiceExplicitEnvKeys(os.Getenv("GC_SUPERVISOR_ENV")) { + if val := os.Getenv(key); val != "" { + env[key] = val + } + } + + keys := make([]string, 0, len(env)) + for key := range env { + keys = append(keys, key) + } + sort.Strings(keys) + out := make([]supervisorServiceEnvVar, 0, len(keys)) + for _, key := range keys { + out = append(out, supervisorServiceEnvVar{Name: key, Value: env[key]}) + } + return out +} + +func shouldPersistSupervisorEnv(key string) bool { + if !supervisorServiceEnvNameRE.MatchString(key) || supervisorServiceFixedEnvKeys[key] { + return false + } + if supervisorServiceEnvKeys[key] { + return true + } + return isProviderCredentialEnv(key) +} + +func isProviderCredentialEnv(key string) bool { + for _, prefix := range providerCredentialEnvPrefixes { + if strings.HasPrefix(key, prefix) { + return true + } + } + return false +} + +func supervisorServiceExplicitEnvKeys(raw string) []string { + fields := strings.Fields(strings.NewReplacer(",", " ", ";", " ").Replace(raw)) + out := make([]string, 0, len(fields)) + seen := make(map[string]bool, len(fields)) + for _, field := range fields { + key := strings.TrimSpace(field) + if key == "" || seen[key] || !supervisorServiceEnvNameRE.MatchString(key) || supervisorServiceFixedEnvKeys[key] { + continue + } + seen[key] = true + out = append(out, key) + } + sort.Strings(out) + return out +} + const ( defaultSupervisorLaunchdLabel = "com.gascity.supervisor" defaultSupervisorSystemdUnit = "gascity-supervisor.service" @@ -436,6 +541,10 @@ const supervisorLaunchdTemplate = ` {{end}} PATH {{xmlesc .Path}} + {{range .ExtraEnv}} + {{xmlesc .Name}} + {{xmlesc .Value}} + {{end}} @@ -454,6 +563,8 @@ StandardError=append:{{.LogPath}} Environment=GC_HOME="{{.GCHome}}" {{if .XDGRuntimeDir}}Environment=XDG_RUNTIME_DIR="{{.XDGRuntimeDir}}" {{end}}Environment=PATH="{{.Path}}" +{{range .ExtraEnv}}Environment={{systemdenv .Name .Value}} +{{end}} [Install] WantedBy=default.target @@ -464,8 +575,12 @@ func xmlEscape(s string) string { return r.Replace(s) } +func systemdEnv(name, value string) string { + return name + "=" + strconv.Quote(value) +} + func renderSupervisorTemplate(tmplStr string, data *supervisorServiceData) (string, error) { - funcMap := template.FuncMap{"xmlesc": xmlEscape} + funcMap := template.FuncMap{"xmlesc": xmlEscape, "systemdenv": systemdEnv} tmpl, err := template.New("service").Funcs(funcMap).Parse(tmplStr) if err != nil { return "", err diff --git a/cmd/gc/cmd_supervisor_test.go b/cmd/gc/cmd_supervisor_test.go index 4df9fd192f..9b7709e97c 100644 --- a/cmd/gc/cmd_supervisor_test.go +++ b/cmd/gc/cmd_supervisor_test.go @@ -203,6 +203,10 @@ func TestRenderSupervisorLaunchdTemplate(t *testing.T) { XDGRuntimeDir: "/tmp/gc-run", LaunchdLabel: defaultSupervisorLaunchdLabel, Path: "/usr/local/bin:/usr/bin:/bin", + ExtraEnv: []supervisorServiceEnvVar{ + {Name: "ANTHROPIC_API_KEY", Value: `sk-&<"'>`}, + {Name: "OPENAI_API_KEY", Value: "sk-openai-123"}, + }, } content, err := renderSupervisorTemplate(supervisorLaunchdTemplate, data) @@ -220,6 +224,10 @@ func TestRenderSupervisorLaunchdTemplate(t *testing.T) { "XDG_RUNTIME_DIR", "/tmp/gc-run", "PATH", + "ANTHROPIC_API_KEY", + "sk-&<"'>", + "OPENAI_API_KEY", + "sk-openai-123", } { if !strings.Contains(content, check) { t.Fatalf("launchd template missing %q", check) @@ -235,6 +243,10 @@ func TestRenderSupervisorSystemdTemplate(t *testing.T) { XDGRuntimeDir: "/tmp/gc-run", LaunchdLabel: defaultSupervisorLaunchdLabel, Path: "/usr/local/bin:/usr/bin:/bin", + ExtraEnv: []supervisorServiceEnvVar{ + {Name: "ANTHROPIC_API_KEY", Value: `sk-"ant"\value`}, + {Name: "OPENAI_API_KEY", Value: "sk-openai-123"}, + }, } content, err := renderSupervisorTemplate(supervisorSystemdTemplate, data) @@ -249,6 +261,8 @@ func TestRenderSupervisorSystemdTemplate(t *testing.T) { `Environment=GC_HOME="/home/user/.gc"`, `Environment=XDG_RUNTIME_DIR="/tmp/gc-run"`, `Environment=PATH="/usr/local/bin:/usr/bin:/bin"`, + `Environment=ANTHROPIC_API_KEY="sk-\"ant\"\\value"`, + `Environment=OPENAI_API_KEY="sk-openai-123"`, } { if !strings.Contains(content, check) { t.Fatalf("systemd template missing %q", check) @@ -256,6 +270,57 @@ func TestRenderSupervisorSystemdTemplate(t *testing.T) { } } +func TestBuildSupervisorServiceDataIncludesProviderEnv(t *testing.T) { + homeDir := t.TempDir() + t.Setenv("HOME", homeDir) + t.Setenv("GC_HOME", filepath.Join(homeDir, ".gc")) + t.Setenv("PATH", "/usr/local/bin:/usr/bin:/bin") + t.Setenv("XDG_RUNTIME_DIR", "/tmp/gc-run") + t.Setenv("ANTHROPIC_API_KEY", "sk-ant-123") + t.Setenv("ANTHROPIC_BASE_URL", "https://anthropic.example.test") + t.Setenv("OPENAI_API_KEY", "sk-openai-123") + t.Setenv("GEMINI_API_KEY", "gemini-123") + t.Setenv("GOOGLE_CLOUD_PROJECT", "gc-project") + t.Setenv("CLAUDE_CONFIG_DIR", filepath.Join(homeDir, ".claude")) + t.Setenv("GC_SUPERVISOR_ENV", "CUSTOM_PROVIDER_TOKEN,IGNORED_EMPTY") + t.Setenv("CUSTOM_PROVIDER_TOKEN", "custom-token") + t.Setenv("IGNORED_EMPTY", "") + t.Setenv("UNRELATED_SECRET", "do-not-persist") + + data, err := buildSupervisorServiceData() + if err != nil { + t.Fatalf("buildSupervisorServiceData: %v", err) + } + + got := supervisorServiceEnvMap(data.ExtraEnv) + for key, want := range map[string]string{ + "ANTHROPIC_API_KEY": "sk-ant-123", + "ANTHROPIC_BASE_URL": "https://anthropic.example.test", + "OPENAI_API_KEY": "sk-openai-123", + "GEMINI_API_KEY": "gemini-123", + "GOOGLE_CLOUD_PROJECT": "gc-project", + "CLAUDE_CONFIG_DIR": filepath.Join(homeDir, ".claude"), + "CUSTOM_PROVIDER_TOKEN": "custom-token", + } { + if got[key] != want { + t.Fatalf("ExtraEnv[%s] = %q, want %q (all env: %#v)", key, got[key], want, got) + } + } + for _, key := range []string{"GC_HOME", "PATH", "XDG_RUNTIME_DIR", "IGNORED_EMPTY", "UNRELATED_SECRET"} { + if _, ok := got[key]; ok { + t.Fatalf("ExtraEnv should not include %s: %#v", key, got) + } + } +} + +func supervisorServiceEnvMap(vars []supervisorServiceEnvVar) map[string]string { + m := make(map[string]string, len(vars)) + for _, item := range vars { + m[item.Name] = item.Value + } + return m +} + func TestBuildSupervisorServiceDataExpandsUserManagedPath(t *testing.T) { homeDir := t.TempDir() nvmBin := filepath.Join(homeDir, ".nvm", "versions", "node", "v22.14.0", "bin") From c3e6f1746e1c2799e02529a013e1bb7d768b88f4 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 24 Apr 2026 18:01:40 +0000 Subject: [PATCH 24/85] fix: avoid no-op system pack rewrites --- cmd/gc/embed_builtin_packs.go | 48 +----- cmd/gc/embed_builtin_packs_test.go | 88 +++++++++++ cmd/gc/hooks.go | 6 +- cmd/gc/hooks_test.go | 63 ++++++++ internal/fsys/atomic.go | 110 +++++++++++++- internal/fsys/atomic_internal_test.go | 157 +++++++++++++++++++ internal/fsys/atomic_test.go | 208 ++++++++++++++++++++++++++ internal/fsys/fake.go | 136 ++++++++++++++--- internal/fsys/fake_test.go | 79 ++++++++++ internal/fsys/read_regular_unix.go | 53 +++++++ 10 files changed, 876 insertions(+), 72 deletions(-) create mode 100644 internal/fsys/atomic_internal_test.go create mode 100644 internal/fsys/read_regular_unix.go diff --git a/cmd/gc/embed_builtin_packs.go b/cmd/gc/embed_builtin_packs.go index 2cc68e2dab..d55fbf1c33 100644 --- a/cmd/gc/embed_builtin_packs.go +++ b/cmd/gc/embed_builtin_packs.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "io" "io/fs" "os" "path/filepath" @@ -15,6 +14,7 @@ import ( "github.com/gastownhall/gascity/examples/gastown/packs/maintenance" "github.com/gastownhall/gascity/internal/bootstrap/packs/core" "github.com/gastownhall/gascity/internal/citylayout" + "github.com/gastownhall/gascity/internal/fsys" "github.com/gastownhall/gascity/internal/orders" ) @@ -39,9 +39,10 @@ var builtinPacks = []builtinPack{ } // MaterializeBuiltinPacks writes all embedded pack files to -// .gc/system/packs/{name}/ in the city directory. Files are always -// overwritten to stay in sync with the gc binary version. Shell scripts -// get 0755; everything else 0644. +// .gc/system/packs/{name}/ in the city directory. Files whose content and mode +// already match are left in place; changed content or mode is repaired with an +// atomic rename so readers never observe a truncated file. Shell scripts get +// 0755; everything else 0644. // Idempotent: safe to call on every gc start and gc init. func MaterializeBuiltinPacks(cityPath string) error { for _, bp := range builtinPacks { @@ -161,47 +162,10 @@ func materializeFS(embedded fs.FS, root, dstDir string) error { if isExecutableScriptFilename(path) { perm = 0o755 } - return writeFileAtomic(dst, data, perm) + return fsys.WriteFileIfContentOrModeChangedAtomic(fsys.OSFS{}, dst, data, perm) }) } -// writeFileAtomic replaces path without exposing a truncated file to readers. -// System pack skills are watched by provider CLIs, so direct os.WriteFile can -// surface transient invalid SKILL.md warnings during gc start/prime. -func writeFileAtomic(path string, data []byte, perm os.FileMode) error { - tmp, err := os.CreateTemp(filepath.Dir(path), "."+filepath.Base(path)+".tmp-*") - if err != nil { - return err - } - tmpName := tmp.Name() - cleanup := true - defer func() { - if cleanup { - _ = os.Remove(tmpName) - } - }() - - if n, err := tmp.Write(data); err != nil { - _ = tmp.Close() - return err - } else if n != len(data) { - _ = tmp.Close() - return io.ErrShortWrite - } - if err := tmp.Chmod(perm); err != nil { - _ = tmp.Close() - return err - } - if err := tmp.Close(); err != nil { - return err - } - if err := os.Rename(tmpName, path); err != nil { - return err - } - cleanup = false - return nil -} - // isExecutableScriptFilename reports whether a materialized pack asset // should be marked executable. Shell, Python, and bash interpreters all // rely on shebang-based direct execution, so the file needs +x regardless diff --git a/cmd/gc/embed_builtin_packs_test.go b/cmd/gc/embed_builtin_packs_test.go index 1530459ee4..a93035b0f7 100644 --- a/cmd/gc/embed_builtin_packs_test.go +++ b/cmd/gc/embed_builtin_packs_test.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" "testing" + "time" "github.com/gastownhall/gascity/internal/citylayout" "github.com/gastownhall/gascity/internal/config" @@ -325,6 +326,93 @@ func TestMaterializeBuiltinPacks_Idempotent(t *testing.T) { } } +func TestMaterializeBuiltinPacks_DoesNotRewriteUnchangedFiles(t *testing.T) { + dir := t.TempDir() + + if err := MaterializeBuiltinPacks(dir); err != nil { + t.Fatalf("MaterializeBuiltinPacks() error: %v", err) + } + + path := filepath.Join(dir, citylayout.SystemPacksRoot, "core", "skills", "gc-dashboard", "SKILL.md") + past := time.Unix(123456789, 0) + if err := os.Chtimes(path, past, past); err != nil { + t.Fatalf("Chtimes(%s): %v", path, err) + } + + if err := MaterializeBuiltinPacks(dir); err != nil { + t.Fatalf("MaterializeBuiltinPacks() second call error: %v", err) + } + + info, err := os.Stat(path) + if err != nil { + t.Fatalf("Stat(%s): %v", path, err) + } + if !info.ModTime().Equal(past) { + t.Fatalf("unchanged file was rewritten: modtime = %s, want %s", info.ModTime(), past) + } +} + +func TestMaterializeBuiltinPacks_RestoresModeWhenContentUnchanged(t *testing.T) { + dir := t.TempDir() + + if err := MaterializeBuiltinPacks(dir); err != nil { + t.Fatalf("MaterializeBuiltinPacks() error: %v", err) + } + + path := filepath.Join(dir, citylayout.SystemPacksRoot, "bd", "doctor", "check-bd", "run.sh") + if err := os.Chmod(path, 0o644); err != nil { + t.Fatalf("Chmod(%s): %v", path, err) + } + + if err := MaterializeBuiltinPacks(dir); err != nil { + t.Fatalf("MaterializeBuiltinPacks() second call error: %v", err) + } + + info, err := os.Stat(path) + if err != nil { + t.Fatalf("Stat(%s): %v", path, err) + } + if info.Mode().Perm() != 0o755 { + t.Fatalf("script mode was not restored: %v", info.Mode().Perm()) + } +} + +func TestMaterializeBuiltinPacks_ReplacesMatchingSymlink(t *testing.T) { + dir := t.TempDir() + + if err := MaterializeBuiltinPacks(dir); err != nil { + t.Fatalf("MaterializeBuiltinPacks() error: %v", err) + } + + path := filepath.Join(dir, citylayout.SystemPacksRoot, "core", "skills", "gc-dashboard", "SKILL.md") + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("ReadFile(%s): %v", path, err) + } + target := filepath.Join(dir, "outside-skill.md") + if err := os.WriteFile(target, data, 0o644); err != nil { + t.Fatalf("WriteFile(%s): %v", target, err) + } + if err := os.Remove(path); err != nil { + t.Fatalf("Remove(%s): %v", path, err) + } + if err := os.Symlink(target, path); err != nil { + t.Skipf("Symlink: %v", err) + } + + if err := MaterializeBuiltinPacks(dir); err != nil { + t.Fatalf("MaterializeBuiltinPacks() second call error: %v", err) + } + + info, err := os.Lstat(path) + if err != nil { + t.Fatalf("Lstat(%s): %v", path, err) + } + if info.Mode()&os.ModeSymlink != 0 { + t.Fatalf("matching symlink was preserved, want regular file") + } +} + func TestMaterializedBuiltinPackOrdersScanWithoutWarnings(t *testing.T) { dir := t.TempDir() diff --git a/cmd/gc/hooks.go b/cmd/gc/hooks.go index b2c7b37456..48d0764845 100644 --- a/cmd/gc/hooks.go +++ b/cmd/gc/hooks.go @@ -4,6 +4,8 @@ import ( "fmt" "os" "path/filepath" + + "github.com/gastownhall/gascity/internal/fsys" ) // beadHooks maps bd hook filenames to the Gas City event types they emit. @@ -53,7 +55,7 @@ title=$(echo "$DATA" | grep -o '"title":"[^"]*"' | head -1 | cut -d'"' -f4) // installBeadHooks writes bd hook scripts into dir/.beads/hooks/ so that // bd mutations (create, close, update) emit events to the Gas City event -// log. Idempotent — overwrites existing hooks. Returns nil on success. +// log. Idempotent — leaves matching hooks in place. Returns nil on success. func installBeadHooks(dir string) error { hooksDir := filepath.Join(dir, ".beads", "hooks") if err := os.MkdirAll(hooksDir, 0o755); err != nil { @@ -66,7 +68,7 @@ func installBeadHooks(dir string) error { if filename == "on_close" { content = closeHookScript() } - if err := os.WriteFile(path, []byte(content), 0o755); err != nil { + if err := fsys.WriteFileIfContentOrModeChangedAtomic(fsys.OSFS{}, path, []byte(content), 0o755); err != nil { return fmt.Errorf("writing hook %s: %w", filename, err) } } diff --git a/cmd/gc/hooks_test.go b/cmd/gc/hooks_test.go index 1c263acff9..be3034c531 100644 --- a/cmd/gc/hooks_test.go +++ b/cmd/gc/hooks_test.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" "testing" + "time" ) func TestInstallBeadHooksCreatesScripts(t *testing.T) { @@ -98,6 +99,68 @@ func TestInstallBeadHooksIdempotent(t *testing.T) { } } +func TestInstallBeadHooksDoesNotRewriteUnchangedHooks(t *testing.T) { + dir := t.TempDir() + + if err := installBeadHooks(dir); err != nil { + t.Fatalf("first install: %v", err) + } + + path := filepath.Join(dir, ".beads", "hooks", "on_create") + past := time.Unix(123456789, 0) + if err := os.Chtimes(path, past, past); err != nil { + t.Fatalf("Chtimes: %v", err) + } + + if err := installBeadHooks(dir); err != nil { + t.Fatalf("second install: %v", err) + } + + info, err := os.Stat(path) + if err != nil { + t.Fatal(err) + } + if !info.ModTime().Equal(past) { + t.Fatalf("unchanged hook was rewritten: modtime = %s, want %s", info.ModTime(), past) + } +} + +func TestInstallBeadHooksReplacesMatchingSymlink(t *testing.T) { + dir := t.TempDir() + + if err := installBeadHooks(dir); err != nil { + t.Fatalf("first install: %v", err) + } + + path := filepath.Join(dir, ".beads", "hooks", "on_create") + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("ReadFile(%s): %v", path, err) + } + target := filepath.Join(dir, "outside-hook") + if err := os.WriteFile(target, data, 0o755); err != nil { + t.Fatalf("WriteFile(%s): %v", target, err) + } + if err := os.Remove(path); err != nil { + t.Fatalf("Remove(%s): %v", path, err) + } + if err := os.Symlink(target, path); err != nil { + t.Skipf("Symlink: %v", err) + } + + if err := installBeadHooks(dir); err != nil { + t.Fatalf("second install: %v", err) + } + + info, err := os.Lstat(path) + if err != nil { + t.Fatalf("Lstat(%s): %v", path, err) + } + if info.Mode()&os.ModeSymlink != 0 { + t.Fatalf("matching symlink was preserved, want regular file") + } +} + func TestInstallBeadHooksCreatesDirectories(t *testing.T) { dir := t.TempDir() // No pre-existing .beads/ directory. diff --git a/internal/fsys/atomic.go b/internal/fsys/atomic.go index dd87452481..46af5e6141 100644 --- a/internal/fsys/atomic.go +++ b/internal/fsys/atomic.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "os" + "reflect" "strconv" "time" ) @@ -34,14 +35,109 @@ func WriteFileAtomic(fs FS, path string, data []byte, perm os.FileMode) error { } // WriteFileIfChangedAtomic writes data to path atomically only when the -// existing on-disk bytes differ. Returns nil with no write when the -// content already matches. A read error other than "not exist" is -// ignored and the write proceeds — this is a best-effort optimization to -// avoid churning mtime (and fsnotify watchers) on no-op writes, not a -// safety check. +// existing on-disk bytes differ. Returns nil with no write when the content +// already matches on a stable regular file. Read or stat errors are ignored +// and the write proceeds — this is a best-effort optimization to avoid +// churning mtime on no-op writes, not a safety check. func WriteFileIfChangedAtomic(fs FS, path string, data []byte, perm os.FileMode) error { - if existing, err := fs.ReadFile(path); err == nil && bytes.Equal(existing, data) { - return nil + if info, err := fs.Lstat(path); err == nil && info.Mode().IsRegular() { + if snapshot, err := readRegularFileSnapshot(fs, path); err == nil && bytes.Equal(snapshot.data, data) { + if info, err := fs.Lstat(path); err == nil && info.Mode().IsRegular() { + if !snapshot.hasID { + return WriteFileAtomic(fs, path, data, perm) + } + currentID, ok := fileIdentityFromInfo(info) + if !ok || currentID != snapshot.id { + return WriteFileAtomic(fs, path, data, perm) + } + return nil + } + } } return WriteFileAtomic(fs, path, data, perm) } + +// WriteFileIfContentOrModeChangedAtomic writes data to path atomically when +// the existing on-disk bytes, file type, or permissions differ. Returns nil +// with no write when the path is already a regular file with matching content +// and mode. Symlinks and other non-regular entries are replaced without first +// reading through them. Read or stat errors are ignored and the write proceeds. +func WriteFileIfContentOrModeChangedAtomic(fs FS, path string, data []byte, perm os.FileMode) error { + if info, err := fs.Lstat(path); err == nil && info.Mode().IsRegular() && comparableMode(info.Mode()) == comparableMode(perm) { + if snapshot, err := readRegularFileSnapshot(fs, path); err == nil && bytes.Equal(snapshot.data, data) { + if info, err := fs.Lstat(path); err == nil && info.Mode().IsRegular() && comparableMode(info.Mode()) == comparableMode(perm) { + if !snapshot.hasID { + return WriteFileAtomic(fs, path, data, perm) + } + currentID, ok := fileIdentityFromInfo(info) + if !ok || currentID != snapshot.id { + return WriteFileAtomic(fs, path, data, perm) + } + return nil + } + } + } + return WriteFileAtomic(fs, path, data, perm) +} + +type regularFileSnapshotReader interface { + readRegularFileSnapshot(name string) (regularFileSnapshot, error) +} + +type regularFileSnapshot struct { + data []byte + id fileIdentity + hasID bool +} + +type fileIdentity struct { + dev uint64 + ino uint64 +} + +func readRegularFileSnapshot(fs FS, path string) (regularFileSnapshot, error) { + if reader, ok := fs.(regularFileSnapshotReader); ok { + return reader.readRegularFileSnapshot(path) + } + return regularFileSnapshot{}, &os.PathError{Op: "open", Path: path, Err: os.ErrInvalid} +} + +func comparableMode(mode os.FileMode) os.FileMode { + return mode & (os.ModePerm | os.ModeSetuid | os.ModeSetgid | os.ModeSticky) +} + +func fileIdentityFromInfo(info os.FileInfo) (fileIdentity, bool) { + stat := reflect.Indirect(reflect.ValueOf(info.Sys())) + if !stat.IsValid() { + return fileIdentity{}, false + } + dev := stat.FieldByName("Dev") + ino := stat.FieldByName("Ino") + if !dev.IsValid() || !ino.IsValid() { + return fileIdentity{}, false + } + devValue, ok := numericFieldToUint64(dev) + if !ok { + return fileIdentity{}, false + } + inoValue, ok := numericFieldToUint64(ino) + if !ok { + return fileIdentity{}, false + } + return fileIdentity{dev: devValue, ino: inoValue}, true +} + +func numericFieldToUint64(v reflect.Value) (uint64, bool) { + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + value := v.Int() + if value < 0 { + return 0, false + } + return uint64(value), true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint(), true + default: + return 0, false + } +} diff --git a/internal/fsys/atomic_internal_test.go b/internal/fsys/atomic_internal_test.go new file mode 100644 index 0000000000..602bb6007d --- /dev/null +++ b/internal/fsys/atomic_internal_test.go @@ -0,0 +1,157 @@ +package fsys + +import ( + "os" + "testing" + "time" +) + +func TestWriteFileIfContentOrModeChangedAtomic_RewritesWhenIdentityChanges(t *testing.T) { + fs := &identityChangingFS{data: []byte("#!/bin/sh\n")} + + if err := WriteFileIfContentOrModeChangedAtomic(fs, "/script.sh", fs.data, 0o755); err != nil { + t.Fatalf("WriteFileIfContentOrModeChangedAtomic: %v", err) + } + + if !fs.renamed { + t.Fatalf("identity-changing file was not rewritten") + } +} + +func TestWriteFileIfChangedAtomic_RewritesWhenIdentityChanges(t *testing.T) { + fs := &identityChangingFS{data: []byte("hello = true\n")} + + if err := WriteFileIfChangedAtomic(fs, "/config.toml", fs.data, 0o644); err != nil { + t.Fatalf("WriteFileIfChangedAtomic: %v", err) + } + + if !fs.renamed { + t.Fatalf("identity-changing file was not rewritten") + } +} + +func TestWriteFileIfContentOrModeChangedAtomic_RewritesWithoutSnapshotIdentity(t *testing.T) { + fs := &noIdentitySnapshotFS{data: []byte("#!/bin/sh\n")} + + if err := WriteFileIfContentOrModeChangedAtomic(fs, "/script.sh", fs.data, 0o755); err != nil { + t.Fatalf("WriteFileIfContentOrModeChangedAtomic: %v", err) + } + + if !fs.renamed { + t.Fatalf("no-identity snapshot was not rewritten") + } +} + +func TestWriteFileIfChangedAtomic_RewritesWithoutSnapshotIdentity(t *testing.T) { + fs := &noIdentitySnapshotFS{data: []byte("hello = true\n")} + + if err := WriteFileIfChangedAtomic(fs, "/config.toml", fs.data, 0o644); err != nil { + t.Fatalf("WriteFileIfChangedAtomic: %v", err) + } + + if !fs.renamed { + t.Fatalf("no-identity snapshot was not rewritten") + } +} + +type identityChangingFS struct { + data []byte + snapshotErr error + renamed bool + lstats int +} + +func (f *identityChangingFS) MkdirAll(string, os.FileMode) error { return nil } + +func (f *identityChangingFS) WriteFile(string, []byte, os.FileMode) error { return nil } + +func (f *identityChangingFS) ReadFile(string) ([]byte, error) { return f.data, nil } + +func (f *identityChangingFS) Stat(string) (os.FileInfo, error) { + return identityFileInfo{mode: 0o755, id: fileIdentity{dev: 1, ino: 1}}, nil +} + +func (f *identityChangingFS) Lstat(string) (os.FileInfo, error) { + f.lstats++ + id := fileIdentity{dev: 1, ino: 1} + if f.lstats > 1 { + id = fileIdentity{dev: 1, ino: 2} + } + return identityFileInfo{mode: 0o755, id: id}, nil +} + +func (f *identityChangingFS) ReadDir(string) ([]os.DirEntry, error) { return nil, nil } + +func (f *identityChangingFS) Rename(string, string) error { + f.renamed = true + return nil +} + +func (f *identityChangingFS) Remove(string) error { return nil } + +func (f *identityChangingFS) Chmod(string, os.FileMode) error { return nil } + +func (f *identityChangingFS) readRegularFileSnapshot(string) (regularFileSnapshot, error) { + if f.snapshotErr != nil { + return regularFileSnapshot{}, f.snapshotErr + } + return regularFileSnapshot{ + data: f.data, + id: fileIdentity{dev: 1, ino: 1}, + hasID: true, + }, nil +} + +type identityFileInfo struct { + mode os.FileMode + id fileIdentity +} + +func (i identityFileInfo) Name() string { return "script.sh" } +func (i identityFileInfo) Size() int64 { return int64(len("#!/bin/sh\n")) } +func (i identityFileInfo) Mode() os.FileMode { return i.mode } +func (i identityFileInfo) ModTime() time.Time { return time.Time{} } +func (i identityFileInfo) IsDir() bool { return false } +func (i identityFileInfo) Sys() any { return struct{ Dev, Ino uint64 }{i.id.dev, i.id.ino} } + +var _ FS = (*identityChangingFS)(nil) + +type noIdentitySnapshotFS struct { + data []byte + snapshotErr error + renamed bool +} + +func (f *noIdentitySnapshotFS) MkdirAll(string, os.FileMode) error { return nil } + +func (f *noIdentitySnapshotFS) WriteFile(string, []byte, os.FileMode) error { return nil } + +func (f *noIdentitySnapshotFS) ReadFile(string) ([]byte, error) { return f.data, nil } + +func (f *noIdentitySnapshotFS) Stat(string) (os.FileInfo, error) { + return identityFileInfo{mode: 0o755, id: fileIdentity{dev: 1, ino: 1}}, nil +} + +func (f *noIdentitySnapshotFS) Lstat(string) (os.FileInfo, error) { + return identityFileInfo{mode: 0o755, id: fileIdentity{dev: 1, ino: 1}}, nil +} + +func (f *noIdentitySnapshotFS) ReadDir(string) ([]os.DirEntry, error) { return nil, nil } + +func (f *noIdentitySnapshotFS) Rename(string, string) error { + f.renamed = true + return nil +} + +func (f *noIdentitySnapshotFS) Remove(string) error { return nil } + +func (f *noIdentitySnapshotFS) Chmod(string, os.FileMode) error { return nil } + +func (f *noIdentitySnapshotFS) readRegularFileSnapshot(string) (regularFileSnapshot, error) { + if f.snapshotErr != nil { + return regularFileSnapshot{}, f.snapshotErr + } + return regularFileSnapshot{data: f.data}, nil +} + +var _ FS = (*noIdentitySnapshotFS)(nil) diff --git a/internal/fsys/atomic_test.go b/internal/fsys/atomic_test.go index d7554dc0e6..ed2b5d7583 100644 --- a/internal/fsys/atomic_test.go +++ b/internal/fsys/atomic_test.go @@ -4,6 +4,7 @@ import ( "os" "path/filepath" "testing" + "time" "github.com/gastownhall/gascity/internal/fsys" ) @@ -57,3 +58,210 @@ func TestWriteFileAtomic_Overwrite(t *testing.T) { } } } + +func TestWriteFileIfChangedAtomic_SkipsMatchingContent(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "test.toml") + data := []byte("hello = true\n") + + if err := fsys.WriteFileAtomic(fsys.OSFS{}, path, data, 0o644); err != nil { + t.Fatalf("WriteFileAtomic: %v", err) + } + past := time.Unix(123456789, 0) + if err := os.Chtimes(path, past, past); err != nil { + t.Fatalf("Chtimes: %v", err) + } + + if err := fsys.WriteFileIfChangedAtomic(fsys.OSFS{}, path, data, 0o644); err != nil { + t.Fatalf("WriteFileIfChangedAtomic: %v", err) + } + + info, err := os.Stat(path) + if err != nil { + t.Fatal(err) + } + if !info.ModTime().Equal(past) { + t.Fatalf("file was rewritten: modtime = %s, want %s", info.ModTime(), past) + } +} + +func TestWriteFileIfChangedAtomic_SkipsMatchingContentWhenModeDiffers(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "test.toml") + data := []byte("hello = true\n") + + if err := fsys.WriteFileAtomic(fsys.OSFS{}, path, data, 0o644); err != nil { + t.Fatalf("WriteFileAtomic: %v", err) + } + past := time.Unix(123456789, 0) + if err := os.Chtimes(path, past, past); err != nil { + t.Fatalf("Chtimes: %v", err) + } + + if err := fsys.WriteFileIfChangedAtomic(fsys.OSFS{}, path, data, 0o755); err != nil { + t.Fatalf("WriteFileIfChangedAtomic: %v", err) + } + + info, err := os.Stat(path) + if err != nil { + t.Fatal(err) + } + if !info.ModTime().Equal(past) { + t.Fatalf("file was rewritten: modtime = %s, want %s", info.ModTime(), past) + } + if info.Mode().Perm() != 0o644 { + t.Fatalf("mode = %v, want unchanged 0644", info.Mode().Perm()) + } +} + +func TestWriteFileIfChangedAtomic_ReplacesMatchingSymlink(t *testing.T) { + dir := t.TempDir() + target := filepath.Join(dir, "target.toml") + link := filepath.Join(dir, "link.toml") + data := []byte("hello = true\n") + + if err := os.WriteFile(target, data, 0o644); err != nil { + t.Fatal(err) + } + if err := os.Symlink(target, link); err != nil { + t.Fatal(err) + } + + if err := fsys.WriteFileIfChangedAtomic(fsys.OSFS{}, link, data, 0o644); err != nil { + t.Fatalf("WriteFileIfChangedAtomic: %v", err) + } + + info, err := os.Lstat(link) + if err != nil { + t.Fatal(err) + } + if info.Mode()&os.ModeSymlink != 0 { + t.Fatalf("matching symlink was preserved, want replacement with regular file") + } +} + +func TestWriteFileIfContentOrModeChangedAtomic_RepairsModeMismatch(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "script.sh") + data := []byte("#!/bin/sh\n") + + if err := os.WriteFile(path, data, 0o644); err != nil { + t.Fatal(err) + } + if err := os.Chmod(path, 0o644); err != nil { + t.Fatal(err) + } + + if err := fsys.WriteFileIfContentOrModeChangedAtomic(fsys.OSFS{}, path, data, 0o755); err != nil { + t.Fatalf("WriteFileIfContentOrModeChangedAtomic: %v", err) + } + + info, err := os.Stat(path) + if err != nil { + t.Fatal(err) + } + if info.Mode().Perm() != 0o755 { + t.Fatalf("mode = %v, want 0755", info.Mode().Perm()) + } +} + +func TestWriteFileIfContentOrModeChangedAtomic_RepairsSpecialModeBits(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "script.sh") + data := []byte("#!/bin/sh\n") + + if err := os.WriteFile(path, data, 0o755); err != nil { + t.Fatal(err) + } + if err := os.Chmod(path, 0o4755); err != nil { + t.Fatal(err) + } + info, err := os.Stat(path) + if err != nil { + t.Fatal(err) + } + if info.Mode()&os.ModeSetuid == 0 { + t.Skipf("filesystem did not preserve setuid bit in test mode: %v", info.Mode()) + } + + if err := fsys.WriteFileIfContentOrModeChangedAtomic(fsys.OSFS{}, path, data, 0o755); err != nil { + t.Fatalf("WriteFileIfContentOrModeChangedAtomic: %v", err) + } + + info, err = os.Stat(path) + if err != nil { + t.Fatal(err) + } + if info.Mode()&os.ModeSetuid != 0 { + t.Fatalf("setuid bit was not repaired: mode = %v", info.Mode()) + } + if info.Mode().Perm() != 0o755 { + t.Fatalf("mode = %v, want 0755", info.Mode().Perm()) + } +} + +func TestWriteFileIfContentOrModeChangedAtomic_ReplacesMatchingSymlink(t *testing.T) { + dir := t.TempDir() + target := filepath.Join(dir, "target.sh") + link := filepath.Join(dir, "link.sh") + data := []byte("#!/bin/sh\n") + + if err := os.WriteFile(target, data, 0o755); err != nil { + t.Fatal(err) + } + if err := os.Symlink(target, link); err != nil { + t.Fatal(err) + } + + if err := fsys.WriteFileIfContentOrModeChangedAtomic(fsys.OSFS{}, link, data, 0o755); err != nil { + t.Fatalf("WriteFileIfContentOrModeChangedAtomic: %v", err) + } + + info, err := os.Lstat(link) + if err != nil { + t.Fatal(err) + } + if info.Mode()&os.ModeSymlink != 0 { + t.Fatalf("matching symlink was preserved, want replacement with regular file") + } +} + +func TestWriteFileIfContentOrModeChangedAtomic_LstatsBeforeRead(t *testing.T) { + fake := fsys.NewFake() + fake.Files["/target.sh"] = []byte("#!/bin/sh\n") + fake.Symlinks["/link.sh"] = "/target.sh" + + if err := fsys.WriteFileIfContentOrModeChangedAtomic(fake, "/link.sh", []byte("#!/bin/sh\n"), 0o755); err != nil { + t.Fatalf("WriteFileIfContentOrModeChangedAtomic: %v", err) + } + + for i, call := range fake.Calls { + if call.Method == "Lstat" && call.Path == "/link.sh" { + return + } + if call.Method == "ReadFile" && call.Path == "/link.sh" { + t.Fatalf("ReadFile called before Lstat at call %d: %+v", i, fake.Calls) + } + } + t.Fatalf("Lstat(/link.sh) not called; calls=%+v", fake.Calls) +} + +func TestWriteFileIfContentOrModeChangedAtomic_FakeSkipsMatchingContentAndMode(t *testing.T) { + fake := fsys.NewFake() + data := []byte("hello = true\n") + + if err := fsys.WriteFileIfContentOrModeChangedAtomic(fake, "/test.toml", data, 0o644); err != nil { + t.Fatalf("initial WriteFileIfContentOrModeChangedAtomic: %v", err) + } + fake.Calls = nil + + if err := fsys.WriteFileIfContentOrModeChangedAtomic(fake, "/test.toml", data, 0o644); err != nil { + t.Fatalf("second WriteFileIfContentOrModeChangedAtomic: %v", err) + } + + for _, call := range fake.Calls { + if call.Method == "WriteFile" || call.Method == "Rename" || call.Method == "Chmod" { + t.Fatalf("matching fake file should not be rewritten; calls=%+v", fake.Calls) + } + } +} diff --git a/internal/fsys/fake.go b/internal/fsys/fake.go index 0a5bf4f0b5..fff8280b64 100644 --- a/internal/fsys/fake.go +++ b/internal/fsys/fake.go @@ -1,6 +1,7 @@ package fsys import ( + "hash/fnv" "io/fs" "os" "path/filepath" @@ -14,6 +15,7 @@ import ( type Fake struct { Dirs map[string]bool // pre-populated directories Files map[string][]byte // pre-populated files + Modes map[string]os.FileMode Symlinks map[string]string // pre-populated symlinks (path -> target) Errors map[string]error // path → injected error (checked first) Calls []Call // spy log @@ -21,7 +23,7 @@ type Fake struct { // Call records a single method invocation on [Fake]. type Call struct { - Method string // "MkdirAll", "WriteFile", "ReadFile", "Stat", "ReadDir", "Rename", "Remove", or "Chmod" + Method string // "MkdirAll", "WriteFile", "ReadFile", "ReadRegularFile", "Stat", "ReadDir", "Rename", "Remove", or "Chmod" Path string // path argument } @@ -30,33 +32,50 @@ func NewFake() *Fake { return &Fake{ Dirs: make(map[string]bool), Files: make(map[string][]byte), + Modes: make(map[string]os.FileMode), Symlinks: make(map[string]string), Errors: make(map[string]error), } } // MkdirAll records the call and adds the directory (and parents) to Dirs. -func (f *Fake) MkdirAll(path string, _ os.FileMode) error { +func (f *Fake) MkdirAll(path string, perm os.FileMode) error { f.Calls = append(f.Calls, Call{Method: "MkdirAll", Path: path}) if err, ok := f.Errors[path]; ok { return err } + if f.Dirs == nil { + f.Dirs = make(map[string]bool) + } + if f.Modes == nil { + f.Modes = make(map[string]os.FileMode) + } // Record this directory and all parents. for p := filepath.Clean(path); p != "." && p != "/" && p != string(filepath.Separator); p = filepath.Dir(p) { + if !f.Dirs[p] { + f.Modes[p] = perm.Perm() + } f.Dirs[p] = true } return nil } // WriteFile records the call and stores the data in Files. -func (f *Fake) WriteFile(name string, data []byte, _ os.FileMode) error { +func (f *Fake) WriteFile(name string, data []byte, perm os.FileMode) error { f.Calls = append(f.Calls, Call{Method: "WriteFile", Path: name}) if err, ok := f.Errors[name]; ok { return err } cp := make([]byte, len(data)) copy(cp, data) + if f.Files == nil { + f.Files = make(map[string][]byte) + } + if f.Modes == nil { + f.Modes = make(map[string]os.FileMode) + } f.Files[name] = cp + f.Modes[name] = perm.Perm() return nil } @@ -74,6 +93,37 @@ func (f *Fake) ReadFile(name string) ([]byte, error) { return nil, &os.PathError{Op: "read", Path: name, Err: os.ErrNotExist} } +// ReadRegularFile records the call and returns file contents without following +// symlinks or accepting directories. +func (f *Fake) ReadRegularFile(name string) ([]byte, error) { + f.Calls = append(f.Calls, Call{Method: "ReadRegularFile", Path: name}) + if err, ok := f.Errors[name]; ok { + return nil, err + } + if _, ok := f.Symlinks[name]; ok { + return nil, &os.PathError{Op: "open", Path: name, Err: os.ErrInvalid} + } + if f.Dirs[name] { + return nil, &os.PathError{Op: "open", Path: name, Err: os.ErrInvalid} + } + if data, ok := f.Files[name]; ok { + cp := make([]byte, len(data)) + copy(cp, data) + return cp, nil + } + return nil, &os.PathError{Op: "open", Path: name, Err: os.ErrNotExist} +} + +// readRegularFileSnapshot returns regular file contents plus a stable fake +// identity for the path. +func (f *Fake) readRegularFileSnapshot(name string) (regularFileSnapshot, error) { + data, err := f.ReadRegularFile(name) + if err != nil { + return regularFileSnapshot{}, err + } + return regularFileSnapshot{data: data, id: fakeIdentity(name), hasID: true}, nil +} + // Stat records the call and returns info based on Dirs/Files maps. // Symlinks are followed — use Lstat to detect them without following. func (f *Fake) Stat(name string) (os.FileInfo, error) { @@ -83,18 +133,18 @@ func (f *Fake) Stat(name string) (os.FileInfo, error) { } if target, ok := f.Symlinks[name]; ok { if f.Dirs[target] { - return fakeFileInfo{name: filepath.Base(name), dir: true}, nil + return fakeFileInfo{name: filepath.Base(name), dir: true, mode: f.modeFor(target), id: fakeIdentity(target), hasID: true}, nil } if data, ok := f.Files[target]; ok { - return fakeFileInfo{name: filepath.Base(name), size: int64(len(data))}, nil + return fakeFileInfo{name: filepath.Base(name), size: int64(len(data)), mode: f.modeFor(target), id: fakeIdentity(target), hasID: true}, nil } return nil, &os.PathError{Op: "stat", Path: name, Err: os.ErrNotExist} } if f.Dirs[name] { - return fakeFileInfo{name: filepath.Base(name), dir: true}, nil + return fakeFileInfo{name: filepath.Base(name), dir: true, mode: f.modeFor(name), id: fakeIdentity(name), hasID: true}, nil } if data, ok := f.Files[name]; ok { - return fakeFileInfo{name: filepath.Base(name), size: int64(len(data))}, nil + return fakeFileInfo{name: filepath.Base(name), size: int64(len(data)), mode: f.modeFor(name), id: fakeIdentity(name), hasID: true}, nil } return nil, &os.PathError{Op: "stat", Path: name, Err: os.ErrNotExist} } @@ -107,13 +157,13 @@ func (f *Fake) Lstat(name string) (os.FileInfo, error) { return nil, err } if _, ok := f.Symlinks[name]; ok { - return fakeFileInfo{name: filepath.Base(name), symlink: true}, nil + return fakeFileInfo{name: filepath.Base(name), symlink: true, id: fakeIdentity(name), hasID: true}, nil } if f.Dirs[name] { - return fakeFileInfo{name: filepath.Base(name), dir: true}, nil + return fakeFileInfo{name: filepath.Base(name), dir: true, mode: f.modeFor(name), id: fakeIdentity(name), hasID: true}, nil } if data, ok := f.Files[name]; ok { - return fakeFileInfo{name: filepath.Base(name), size: int64(len(data))}, nil + return fakeFileInfo{name: filepath.Base(name), size: int64(len(data)), mode: f.modeFor(name), id: fakeIdentity(name), hasID: true}, nil } return nil, &os.PathError{Op: "lstat", Path: name, Err: os.ErrNotExist} } @@ -135,7 +185,7 @@ func (f *Fake) ReadDir(name string) ([]os.DirEntry, error) { base := filepath.Base(d) if !seen[base] { seen[base] = true - entries = append(entries, fakeDirEntry{name: base, dir: true}) + entries = append(entries, fakeDirEntry{name: base, dir: true, mode: f.modeFor(d), id: fakeIdentity(d), hasID: true}) } } } @@ -145,7 +195,7 @@ func (f *Fake) ReadDir(name string) ([]os.DirEntry, error) { base := filepath.Base(p) if !seen[base] { seen[base] = true - entries = append(entries, fakeDirEntry{name: base, size: int64(len(data))}) + entries = append(entries, fakeDirEntry{name: base, size: int64(len(data)), mode: f.modeFor(p), id: fakeIdentity(p), hasID: true}) } } } @@ -165,6 +215,13 @@ func (f *Fake) Rename(oldpath, newpath string) error { if data, ok := f.Files[oldpath]; ok { f.Files[newpath] = data delete(f.Files, oldpath) + if mode, ok := f.Modes[oldpath]; ok { + f.Modes[newpath] = mode + } else { + delete(f.Modes, newpath) + } + delete(f.Modes, oldpath) + delete(f.Symlinks, newpath) return nil } return &os.PathError{Op: "rename", Path: oldpath, Err: os.ErrNotExist} @@ -178,36 +235,56 @@ func (f *Fake) Remove(name string) error { } if _, ok := f.Files[name]; ok { delete(f.Files, name) + delete(f.Modes, name) + return nil + } + if _, ok := f.Symlinks[name]; ok { + delete(f.Symlinks, name) return nil } if f.Dirs[name] { delete(f.Dirs, name) + delete(f.Modes, name) return nil } return &os.PathError{Op: "remove", Path: name, Err: os.ErrNotExist} } -// Chmod records the call. Mode is not tracked — the spy log is sufficient -// for tests that care about which paths were chmodded. -func (f *Fake) Chmod(name string, _ os.FileMode) error { +// Chmod records the call and updates the stored mode. +func (f *Fake) Chmod(name string, mode os.FileMode) error { f.Calls = append(f.Calls, Call{Method: "Chmod", Path: name}) if err, ok := f.Errors[name]; ok { return err } + if f.Modes == nil { + f.Modes = make(map[string]os.FileMode) + } if _, ok := f.Files[name]; ok { + f.Modes[name] = mode.Perm() return nil } if f.Dirs[name] { + f.Modes[name] = mode.Perm() return nil } return &os.PathError{Op: "chmod", Path: name, Err: os.ErrNotExist} } +func (f *Fake) modeFor(name string) os.FileMode { + if mode, ok := f.Modes[name]; ok { + return mode + } + return 0o755 +} + // --- fake os.FileInfo --- type fakeFileInfo struct { name string size int64 + mode os.FileMode + id fileIdentity + hasID bool dir bool symlink bool } @@ -218,18 +295,29 @@ func (fi fakeFileInfo) Mode() os.FileMode { if fi.symlink { return 0o777 | os.ModeSymlink } - return 0o755 + if fi.dir { + return fi.mode | os.ModeDir + } + return fi.mode } func (fi fakeFileInfo) ModTime() time.Time { return time.Time{} } func (fi fakeFileInfo) IsDir() bool { return fi.dir } -func (fi fakeFileInfo) Sys() any { return nil } +func (fi fakeFileInfo) Sys() any { + if !fi.hasID { + return nil + } + return struct{ Dev, Ino uint64 }{fi.id.dev, fi.id.ino} +} // --- fake os.DirEntry --- type fakeDirEntry struct { - name string - size int64 - dir bool + name string + size int64 + mode os.FileMode + id fileIdentity + hasID bool + dir bool } func (de fakeDirEntry) Name() string { return de.name } @@ -242,7 +330,13 @@ func (de fakeDirEntry) Type() fs.FileMode { } func (de fakeDirEntry) Info() (fs.FileInfo, error) { - return fakeFileInfo{name: de.name, size: de.size, dir: de.dir}, nil + return fakeFileInfo{name: de.name, size: de.size, mode: de.mode, id: de.id, hasID: de.hasID, dir: de.dir}, nil +} + +func fakeIdentity(name string) fileIdentity { + h := fnv.New64a() + _, _ = h.Write([]byte(name)) + return fileIdentity{dev: 1, ino: h.Sum64()} } var ( diff --git a/internal/fsys/fake_test.go b/internal/fsys/fake_test.go index 6eaf083ac2..eae07f515e 100644 --- a/internal/fsys/fake_test.go +++ b/internal/fsys/fake_test.go @@ -23,6 +23,22 @@ func TestFakeStatDir(t *testing.T) { } } +func TestFakeStatDirModeIncludesDirBit(t *testing.T) { + f := NewFake() + f.Dirs["/city/.gc"] = true + + fi, err := f.Stat("/city/.gc") + if err != nil { + t.Fatalf("Stat existing dir: %v", err) + } + if fi.Mode().IsRegular() { + t.Fatalf("directory mode reports regular file: %v", fi.Mode()) + } + if fi.Mode()&os.ModeDir == 0 { + t.Fatalf("directory mode missing ModeDir bit: %v", fi.Mode()) + } +} + func TestFakeStatFile(t *testing.T) { f := NewFake() f.Files["/city/city.toml"] = []byte("hello") @@ -114,6 +130,17 @@ func TestFakeWriteFile(t *testing.T) { } } +func TestFakeWriteFileInitializesModes(t *testing.T) { + f := &Fake{Files: map[string][]byte{}} + + if err := f.WriteFile("/city/run.sh", []byte("#!/bin/sh\n"), 0o755); err != nil { + t.Fatalf("WriteFile: %v", err) + } + if f.Modes["/city/run.sh"] != 0o755 { + t.Fatalf("mode = %v, want 0755", f.Modes["/city/run.sh"]) + } +} + func TestFakeWriteFileError(t *testing.T) { f := NewFake() injected := fmt.Errorf("read-only fs") @@ -159,6 +186,28 @@ func TestFakeReadDir(t *testing.T) { } } +func TestFakeReadDirInfoReportsTrackedMode(t *testing.T) { + f := NewFake() + if err := f.WriteFile("/city/rigs/run.sh", []byte("#!/bin/sh\n"), 0o755); err != nil { + t.Fatalf("WriteFile: %v", err) + } + + entries, err := f.ReadDir("/city/rigs") + if err != nil { + t.Fatalf("ReadDir: %v", err) + } + if len(entries) != 1 { + t.Fatalf("got %d entries, want 1", len(entries)) + } + info, err := entries[0].Info() + if err != nil { + t.Fatalf("Info: %v", err) + } + if info.Mode().Perm() != 0o755 { + t.Fatalf("ReadDir entry mode = %v, want 0755", info.Mode().Perm()) + } +} + func TestFakeReadDirError(t *testing.T) { f := NewFake() injected := fmt.Errorf("no such directory") @@ -203,6 +252,36 @@ func TestFakeRename(t *testing.T) { } } +func TestFakeRenameClearsStaleDestinationMode(t *testing.T) { + f := NewFake() + f.Files["/city/generated.tmp"] = []byte("new") + f.Files["/city/generated"] = []byte("old") + f.Modes["/city/generated"] = 0o644 + + if err := f.Rename("/city/generated.tmp", "/city/generated"); err != nil { + t.Fatalf("Rename: %v", err) + } + + info, err := f.Stat("/city/generated") + if err != nil { + t.Fatalf("Stat: %v", err) + } + if info.Mode().Perm() != 0o755 { + t.Fatalf("renamed file mode = %v, want default 0755", info.Mode().Perm()) + } +} + +func TestFakeChmodInitializesModes(t *testing.T) { + f := &Fake{Files: map[string][]byte{"/city/run.sh": []byte("#!/bin/sh\n")}} + + if err := f.Chmod("/city/run.sh", 0o755); err != nil { + t.Fatalf("Chmod: %v", err) + } + if f.Modes["/city/run.sh"] != 0o755 { + t.Fatalf("mode = %v, want 0755", f.Modes["/city/run.sh"]) + } +} + func TestFakeRenameError(t *testing.T) { f := NewFake() injected := fmt.Errorf("cross-device link") diff --git a/internal/fsys/read_regular_unix.go b/internal/fsys/read_regular_unix.go new file mode 100644 index 0000000000..5002e49bc7 --- /dev/null +++ b/internal/fsys/read_regular_unix.go @@ -0,0 +1,53 @@ +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris + +package fsys + +import ( + "io" + "os" + + "golang.org/x/sys/unix" +) + +// ReadRegularFile reads name without following a final symlink. +func (OSFS) ReadRegularFile(name string) ([]byte, error) { + snapshot, err := (OSFS{}).readRegularFileSnapshot(name) + if err != nil { + return nil, err + } + return snapshot.data, nil +} + +// readRegularFileSnapshot reads name without following a final symlink and +// returns the opened file identity for post-read stability checks. +func (OSFS) readRegularFileSnapshot(name string) (regularFileSnapshot, error) { + fd, err := unix.Open(name, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0) + if err != nil { + return regularFileSnapshot{}, &os.PathError{Op: "open", Path: name, Err: err} + } + file := os.NewFile(uintptr(fd), name) + if file == nil { + _ = unix.Close(fd) + return regularFileSnapshot{}, &os.PathError{Op: "open", Path: name, Err: os.ErrInvalid} + } + defer func() { + _ = file.Close() + }() + + var stat unix.Stat_t + if err := unix.Fstat(fd, &stat); err != nil { + return regularFileSnapshot{}, &os.PathError{Op: "stat", Path: name, Err: err} + } + if stat.Mode&unix.S_IFMT != unix.S_IFREG { + return regularFileSnapshot{}, &os.PathError{Op: "open", Path: name, Err: os.ErrInvalid} + } + data, err := io.ReadAll(file) + if err != nil { + return regularFileSnapshot{}, &os.PathError{Op: "read", Path: name, Err: err} + } + return regularFileSnapshot{ + data: data, + id: fileIdentity{dev: stat.Dev, ino: stat.Ino}, + hasID: true, + }, nil +} From 4367ec3dfff98e3513b3cb357602d140aa35b781 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Sat, 25 Apr 2026 03:12:01 +0000 Subject: [PATCH 25/85] fix: secure supervisor service files --- cmd/gc/cmd_supervisor_lifecycle.go | 24 +++++-- cmd/gc/cmd_supervisor_test.go | 104 +++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 4 deletions(-) diff --git a/cmd/gc/cmd_supervisor_lifecycle.go b/cmd/gc/cmd_supervisor_lifecycle.go index 269311e5f7..8ee7a962e7 100644 --- a/cmd/gc/cmd_supervisor_lifecycle.go +++ b/cmd/gc/cmd_supervisor_lifecycle.go @@ -43,6 +43,8 @@ var ( } ) +const supervisorServiceFileMode os.FileMode = 0o600 + func newSupervisorRunCmd(stdout, stderr io.Writer) *cobra.Command { return &cobra.Command{ Use: "run", @@ -592,6 +594,20 @@ func renderSupervisorTemplate(tmplStr string, data *supervisorServiceData) (stri return buf.String(), nil } +func writeSupervisorServiceFile(path string, content []byte) error { + if _, err := os.Stat(path); err == nil { + if err := os.Chmod(path, supervisorServiceFileMode); err != nil { + return err + } + } else if !os.IsNotExist(err) { + return err + } + if err := os.WriteFile(path, content, supervisorServiceFileMode); err != nil { + return err + } + return os.Chmod(path, supervisorServiceFileMode) +} + func supervisorLaunchdPlistPath() string { home, _ := os.UserHomeDir() return filepath.Join(home, "Library", "LaunchAgents", supervisorLaunchdLabel()+".plist") @@ -809,7 +825,7 @@ func rollbackNewSupervisorLaunchdInstall(path string, restoreLegacy bool) error func restorePreviousSupervisorLaunchdInstall(path string, previousContent []byte) error { var errs []error _ = supervisorLaunchctlRun("unload", path) - if err := os.WriteFile(path, previousContent, 0o644); err != nil { + if err := writeSupervisorServiceFile(path, previousContent); err != nil { errs = append(errs, fmt.Errorf("restoring previous plist %s: %w", path, err)) } else if err := supervisorLaunchctlRun("load", path); err != nil { errs = append(errs, fmt.Errorf("reloading previous plist %s: %w", path, err)) @@ -840,7 +856,7 @@ func restorePreviousSupervisorSystemdInstall(path, service string, previousConte if restart { _ = supervisorSystemctlRun("--user", "stop", service) } - if err := os.WriteFile(path, previousContent, 0o644); err != nil { + if err := writeSupervisorServiceFile(path, previousContent); err != nil { errs = append(errs, fmt.Errorf("restoring previous unit %s: %w", path, err)) return errors.Join(errs...) } @@ -877,7 +893,7 @@ func installSupervisorLaunchd(data *supervisorServiceData, stdout, stderr io.Wri fmt.Fprintf(stderr, "gc supervisor install: %v\n", err) //nolint:errcheck // best-effort stderr return 1 } - if err := os.WriteFile(path, []byte(content), 0o644); err != nil { + if err := writeSupervisorServiceFile(path, []byte(content)); err != nil { fmt.Fprintf(stderr, "gc supervisor install: writing plist: %v\n", err) //nolint:errcheck // best-effort stderr return 1 } @@ -944,7 +960,7 @@ func installSupervisorSystemd(data *supervisorServiceData, stdout, stderr io.Wri return 1 } contentChanged := string(existing) != content - if err := os.WriteFile(path, []byte(content), 0o644); err != nil { + if err := writeSupervisorServiceFile(path, []byte(content)); err != nil { fmt.Fprintf(stderr, "gc supervisor install: writing unit: %v\n", err) //nolint:errcheck // best-effort stderr return 1 } diff --git a/cmd/gc/cmd_supervisor_test.go b/cmd/gc/cmd_supervisor_test.go index 9b7709e97c..51e5731991 100644 --- a/cmd/gc/cmd_supervisor_test.go +++ b/cmd/gc/cmd_supervisor_test.go @@ -637,6 +637,57 @@ func TestInstallSupervisorSystemdRestartsWhenUnitChangesAndServiceActive(t *test if strings.Contains(joined, "--user start gascity-supervisor.service") { t.Fatalf("systemctl calls = %v, should restart instead of start when unit changes under an active service", calls) } + info, err := os.Stat(path) + if err != nil { + t.Fatalf("Stat(%q): %v", path, err) + } + if got := info.Mode().Perm(); got != 0o600 { + t.Fatalf("systemd unit mode after warm upgrade = %03o, want 600", got) + } +} + +func TestInstallSupervisorSystemdWritesPrivateUnitFile(t *testing.T) { + if goruntime.GOOS != "linux" { + t.Skip("systemd path only applies on linux") + } + homeDir := t.TempDir() + t.Setenv("HOME", homeDir) + t.Setenv("GC_HOME", filepath.Join(homeDir, ".gc")) + + data := &supervisorServiceData{ + GCPath: "/tmp/gc-new", + LogPath: "/tmp/gc-home/supervisor.log", + GCHome: "/tmp/gc-home", + Path: "/usr/local/bin:/usr/bin:/bin", + ExtraEnv: []supervisorServiceEnvVar{ + {Name: "OPENAI_API_KEY", Value: "sk-openai-123"}, + }, + } + + oldRun := supervisorSystemctlRun + oldActive := supervisorSystemctlActive + supervisorSystemctlRun = func(_ ...string) error { + return nil + } + supervisorSystemctlActive = func(_ string) bool { + return false + } + t.Cleanup(func() { + supervisorSystemctlRun = oldRun + supervisorSystemctlActive = oldActive + }) + + var stdout, stderr bytes.Buffer + if code := installSupervisorSystemd(data, &stdout, &stderr); code != 0 { + t.Fatalf("installSupervisorSystemd code = %d, want 0; stderr=%q", code, stderr.String()) + } + info, err := os.Stat(supervisorSystemdServicePath()) + if err != nil { + t.Fatalf("Stat(%q): %v", supervisorSystemdServicePath(), err) + } + if got := info.Mode().Perm(); got != 0o600 { + t.Fatalf("systemd unit mode = %03o, want 600", got) + } } func TestInstallSupervisorSystemdStartsInactiveService(t *testing.T) { @@ -1252,6 +1303,13 @@ func TestInstallSupervisorSystemdRestoresPreviousCurrentUnitWhenUpdateFails(t *t if !bytes.Equal(gotContent, oldContent) { t.Fatalf("restored systemd unit = %q, want original %q", gotContent, oldContent) } + info, err := os.Stat(currentPath) + if err != nil { + t.Fatalf("Stat(%q): %v", currentPath, err) + } + if got := info.Mode().Perm(); got != 0o600 { + t.Fatalf("restored systemd unit mode = %03o, want 600", got) + } if startCalls != 2 { t.Fatalf("systemctl start call count = %d, want 2 (failed install + rollback restore); calls=%v", startCalls, calls) } @@ -1418,6 +1476,45 @@ func TestInstallSupervisorLaunchdRemovesMatchingLegacyDefaultPlistForIsolatedGCH } } +func TestInstallSupervisorLaunchdWritesPrivatePlist(t *testing.T) { + homeDir := t.TempDir() + gcHome := filepath.Join(t.TempDir(), "isolated-home") + t.Setenv("HOME", homeDir) + t.Setenv("GC_HOME", gcHome) + + data := &supervisorServiceData{ + GCPath: "/tmp/gc-new", + LogPath: filepath.Join(gcHome, "supervisor.log"), + GCHome: gcHome, + LaunchdLabel: supervisorLaunchdLabel(), + Path: "/usr/local/bin:/usr/bin:/bin", + ExtraEnv: []supervisorServiceEnvVar{ + {Name: "OPENAI_API_KEY", Value: "sk-openai-123"}, + }, + } + + oldRun := supervisorLaunchctlRun + supervisorLaunchctlRun = func(_ ...string) error { + return nil + } + t.Cleanup(func() { + supervisorLaunchctlRun = oldRun + }) + + var stdout, stderr bytes.Buffer + if code := installSupervisorLaunchd(data, &stdout, &stderr); code != 0 { + t.Fatalf("installSupervisorLaunchd code = %d, want 0; stderr=%q", code, stderr.String()) + } + path := supervisorLaunchdPlistPath() + info, err := os.Stat(path) + if err != nil { + t.Fatalf("Stat(%q): %v", path, err) + } + if got := info.Mode().Perm(); got != 0o600 { + t.Fatalf("launchd plist mode = %03o, want 600", got) + } +} + func TestInstallSupervisorLaunchdIgnoresLegacyUnloadFailures(t *testing.T) { homeDir := t.TempDir() gcHome := filepath.Join(t.TempDir(), "isolated-home") @@ -1592,6 +1689,13 @@ func TestInstallSupervisorLaunchdRestoresPreviousCurrentPlistWhenUpdateFails(t * if !bytes.Equal(gotContent, oldContent) { t.Fatalf("restored launchd plist = %q, want original %q", gotContent, oldContent) } + info, err := os.Stat(currentPath) + if err != nil { + t.Fatalf("Stat(%q): %v", currentPath, err) + } + if got := info.Mode().Perm(); got != 0o600 { + t.Fatalf("restored launchd plist mode = %03o, want 600", got) + } if loadCalls != 2 { t.Fatalf("launchctl load call count = %d, want 2 (failed install + rollback restore); calls=%v", loadCalls, calls) } From 584c265b491a920c27e0764d8aaeb102f3436f8a Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Sat, 25 Apr 2026 04:52:48 +0000 Subject: [PATCH 26/85] fix: refresh cache from bd hook events --- internal/beads/caching_store_events.go | 91 +++++++++++++++++++++-- internal/beads/caching_store_test.go | 99 ++++++++++++++++++++++++-- 2 files changed, 176 insertions(+), 14 deletions(-) diff --git a/internal/beads/caching_store_events.go b/internal/beads/caching_store_events.go index 3bdb3c50df..8ef6a0d4d4 100644 --- a/internal/beads/caching_store_events.go +++ b/internal/beads/caching_store_events.go @@ -2,6 +2,7 @@ package beads import ( "encoding/json" + "errors" "fmt" "maps" "slices" @@ -17,17 +18,37 @@ func (c *CachingStore) ApplyEvent(eventType string, payload json.RawMessage) { return } - b, err := decodeCacheEvent(payload) + patch, fields, err := decodeCacheEvent(payload) if err != nil { c.recordProblem(fmt.Sprintf("apply %s event", eventType), err) return } + c.mu.RLock() + if c.state != cacheLive { + c.mu.RUnlock() + return + } + _, cached := c.beads[patch.ID] + c.mu.RUnlock() + + b := patch + if !cached { + if fresh, err := c.backing.Get(patch.ID); err == nil { + b = fresh + } else if !errors.Is(err, ErrNotFound) { + c.recordProblem(fmt.Sprintf("refresh %s event", eventType), err) + } + } + c.mu.Lock() defer c.mu.Unlock() if c.state != cacheLive { return } + if current, ok := c.beads[patch.ID]; ok { + b = mergeCacheEventPatch(current, patch, fields) + } mutated := false switch eventType { @@ -37,9 +58,9 @@ func (c *CachingStore) ApplyEvent(eventType string, payload json.RawMessage) { c.beads[b.ID] = cloneBead(b) delete(c.dirty, b.ID) delete(c.deletedSeq, b.ID) - c.updateStatsLocked() - mutated = true } + c.updateStatsLocked() + mutated = true case "bead.updated": c.noteMutationLocked(b.ID) c.beads[b.ID] = cloneBead(b) @@ -80,27 +101,83 @@ func (c *CachingStore) ApplyDepEvent(beadID string, deps []Dep) { c.updateStatsLocked() } -func decodeCacheEvent(payload json.RawMessage) (Bead, error) { +func mergeCacheEventPatch(base, patch Bead, fields map[string]json.RawMessage) Bead { + merged := cloneBead(base) + if hasCacheEventField(fields, "title") { + merged.Title = patch.Title + } + if hasCacheEventField(fields, "status") { + merged.Status = patch.Status + } + if hasCacheEventField(fields, "issue_type") || hasCacheEventField(fields, "type") { + merged.Type = patch.Type + } + if hasCacheEventField(fields, "priority") { + merged.Priority = cloneIntPtr(patch.Priority) + } + if hasCacheEventField(fields, "created_at") { + merged.CreatedAt = patch.CreatedAt + } + if hasCacheEventField(fields, "assignee") { + merged.Assignee = patch.Assignee + } + if hasCacheEventField(fields, "from") { + merged.From = patch.From + } + if hasCacheEventField(fields, "parent") { + merged.ParentID = patch.ParentID + } + if hasCacheEventField(fields, "ref") { + merged.Ref = patch.Ref + } + if hasCacheEventField(fields, "needs") { + merged.Needs = slices.Clone(patch.Needs) + } + if hasCacheEventField(fields, "description") { + merged.Description = patch.Description + } + if hasCacheEventField(fields, "labels") { + merged.Labels = slices.Clone(patch.Labels) + } + if hasCacheEventField(fields, "metadata") { + merged.Metadata = maps.Clone(patch.Metadata) + } + if hasCacheEventField(fields, "dependencies") { + merged.Dependencies = slices.Clone(patch.Dependencies) + } + return merged +} + +func hasCacheEventField(fields map[string]json.RawMessage, name string) bool { + _, ok := fields[name] + return ok +} + +func decodeCacheEvent(payload json.RawMessage) (Bead, map[string]json.RawMessage, error) { + var fields map[string]json.RawMessage + if err := json.Unmarshal(payload, &fields); err != nil { + return Bead{}, nil, err + } var wire struct { Bead Metadata StringMap `json:"metadata,omitempty"` TypeCompat string `json:"type,omitempty"` } if err := json.Unmarshal(payload, &wire); err != nil { - return Bead{}, err + return Bead{}, nil, err } b := wire.Bead if wire.Metadata != nil { b.Metadata = map[string]string(wire.Metadata) } if b.ID == "" { - return Bead{}, fmt.Errorf("missing bead id") + return Bead{}, nil, fmt.Errorf("missing bead id") } // bd hook payloads use "issue_type" while exec-style payloads may use "type". if b.Type == "" && wire.TypeCompat != "" { b.Type = wire.TypeCompat } - return b, nil + return b, fields, nil } func (c *CachingStore) notifyChange(eventType string, b Bead) { diff --git a/internal/beads/caching_store_test.go b/internal/beads/caching_store_test.go index 675227e778..efb98c681e 100644 --- a/internal/beads/caching_store_test.go +++ b/internal/beads/caching_store_test.go @@ -902,7 +902,14 @@ func TestCachingStoreApplyEvent(t *testing.T) { } // Apply an update event. - updated := beads.Bead{ID: b1.ID, Title: "Modified by agent", Status: "open", Metadata: map[string]string{"gc.step_ref": "mol.review"}} + updatedTitle := "Modified by agent" + if err := mem.Update(b1.ID, beads.UpdateOpts{ + Title: &updatedTitle, + Metadata: map[string]string{"gc.step_ref": "mol.review"}, + }); err != nil { + t.Fatalf("Update backing: %v", err) + } + updated := beads.Bead{ID: b1.ID, Title: updatedTitle, Status: "open", Metadata: map[string]string{"gc.step_ref": "mol.review"}} payload, _ = json.Marshal(updated) cs.ApplyEvent("bead.updated", payload) @@ -915,9 +922,20 @@ func TestCachingStoreApplyEvent(t *testing.T) { } // Apply a close event with the full closed bead payload. + closedTitle := "Closed by agent" + if err := mem.Update(b1.ID, beads.UpdateOpts{ + Title: &closedTitle, + Labels: []string{"done"}, + Metadata: map[string]string{"gc.outcome": "pass"}, + }); err != nil { + t.Fatalf("Update backing before close: %v", err) + } + if err := mem.Close(b1.ID); err != nil { + t.Fatalf("Close backing: %v", err) + } closed := beads.Bead{ ID: b1.ID, - Title: "Closed by agent", + Title: closedTitle, Status: "closed", Labels: []string{"done"}, Metadata: map[string]string{"gc.outcome": "pass"}, @@ -943,21 +961,88 @@ func TestCachingStoreApplyEvent(t *testing.T) { } } -func TestCachingStoreApplyEventCoercesNonStringMetadata(t *testing.T) { +func TestCachingStoreApplyEventRefreshesPartialHookPayload(t *testing.T) { t.Parallel() mem := beads.NewMemStore() - b1, err := mem.Create(beads.Bead{Title: "Existing"}) + parent, err := mem.Create(beads.Bead{Title: "parent"}) if err != nil { - t.Fatalf("Create: %v", err) + t.Fatalf("Create parent: %v", err) + } + child, err := mem.Create(beads.Bead{ + Title: "child", + ParentID: parent.ID, + Labels: []string{"mc-live-contract"}, + }) + if err != nil { + t.Fatalf("Create child: %v", err) + } + + backing := &eventGetFailStore{Store: mem} + cs := beads.NewCachingStoreForTest(backing, nil) + if err := cs.Prime(context.Background()); err != nil { + t.Fatalf("Prime: %v", err) + } + backing.failGet = true + + updatedTitle := "child updated externally" + if err := mem.Update(child.ID, beads.UpdateOpts{Title: &updatedTitle}); err != nil { + t.Fatalf("Update backing: %v", err) + } + payload, err := json.Marshal(map[string]any{ + "id": child.ID, + "title": updatedTitle, + "status": "open", + "issue_type": "task", + "owner": "agent@example.com", + "updated_at": "2026-04-25T04:45:55Z", + }) + if err != nil { + t.Fatalf("marshal payload: %v", err) } + cs.ApplyEvent("bead.updated", payload) + if stats := cs.Stats(); stats.ProblemCount != 0 { + t.Fatalf("ProblemCount = %d, want 0 (last problem: %s)", stats.ProblemCount, stats.LastProblem) + } + + labeled, err := cs.List(beads.ListQuery{Label: "mc-live-contract"}) + if err != nil { + t.Fatalf("List(label): %v", err) + } + if len(labeled) != 1 || labeled[0].ID != child.ID { + t.Fatalf("labeled = %#v, want child %s", labeled, child.ID) + } + if labeled[0].ParentID != parent.ID { + t.Fatalf("ParentID = %q, want %q", labeled[0].ParentID, parent.ID) + } + if labeled[0].Title != updatedTitle { + t.Fatalf("Title = %q, want %q", labeled[0].Title, updatedTitle) + } +} + +type eventGetFailStore struct { + beads.Store + failGet bool +} + +func (s *eventGetFailStore) Get(id string) (beads.Bead, error) { + if s.failGet { + return beads.Bead{}, errors.New("unexpected event backing get") + } + return s.Store.Get(id) +} + +func TestCachingStoreApplyEventCoercesNonStringMetadata(t *testing.T) { + t.Parallel() + mem := beads.NewMemStore() + cs := beads.NewCachingStoreForTest(mem, nil) if err := cs.Prime(context.Background()); err != nil { t.Fatalf("Prime: %v", err) } payload, err := json.Marshal(map[string]any{ - "id": b1.ID, + "id": "ext-1", "title": "mayor", "status": "open", "issue_type": "session", @@ -979,7 +1064,7 @@ func TestCachingStoreApplyEventCoercesNonStringMetadata(t *testing.T) { t.Fatalf("ProblemCount = %d, want 0 (last problem: %s)", stats.ProblemCount, stats.LastProblem) } - got := requireCachedBead(t, cs, b1.ID, false) + got := requireCachedBead(t, cs, "ext-1", false) if got.Type != "session" { t.Fatalf("Type = %q, want session", got.Type) } From 0b8196d99080c6fa7c8d4ab1b557f0ef5661057a Mon Sep 17 00:00:00 2001 From: Casey Boyle Date: Fri, 24 Apr 2026 12:24:11 -0500 Subject: [PATCH 27/85] fix(dolt-health): exclude rig-local Dolt servers from zombie scan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The zombie scan flagged every dolt sql-server PID except the main city server as a zombie. Rig-local Dolt servers configured via dolt.port in config.yaml are legitimate — the scan now reads rig configs from the metadata cache and excludes PIDs listening on known rig ports. Closes #1217 Co-Authored-By: Claude Opus 4.6 --- examples/dolt/commands/health/run.sh | 17 ++++ examples/dolt/health_test.go | 113 +++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/examples/dolt/commands/health/run.sh b/examples/dolt/commands/health/run.sh index 3e3385b67b..6bb4e8c2f0 100755 --- a/examples/dolt/commands/health/run.sh +++ b/examples/dolt/commands/health/run.sh @@ -235,6 +235,9 @@ fi # positives from processes that merely mention "dolt" in their args # (e.g., Claude sessions whose prompt text contains "dolt sql-server"). # +# Rig-local Dolt servers (configured via dolt.port in config.yaml) +# are legitimate — exclude any PID listening on a known rig port. +# # GC_HEALTH_SKIP_ZOMBIE_SCAN is a test-only escape hatch. Zombie # enumeration spawns one `ps` per matching process, which on shared # dev machines with many accumulated dolt processes dominates the @@ -244,8 +247,22 @@ fi zombie_count=0 zombie_pids="" if [ "${GC_HEALTH_SKIP_ZOMBIE_SCAN:-0}" != "1" ]; then + # Collect PIDs of legitimate rig-local Dolt servers. + rig_dolt_pids="" + while IFS= read -r meta; do + [ -f "$meta" ] || continue + config_file="$(dirname "$meta")/config.yaml" + [ -f "$config_file" ] || continue + rig_port=$(grep '^dolt\.port:' "$config_file" 2>/dev/null | sed 's/^dolt\.port:[[:space:]]*//' | head -1) + case "$rig_port" in ''|*[!0-9]*) continue ;; esac + [ "$rig_port" = "$GC_DOLT_PORT" ] && continue + rig_pid=$(managed_runtime_listener_pid "$rig_port" || true) + [ -n "$rig_pid" ] && rig_dolt_pids="$rig_dolt_pids $rig_pid " + done < "$_meta_cache" + for p in $(pgrep -x dolt 2>/dev/null || true); do [ "$p" = "$server_pid" ] && continue + case "$rig_dolt_pids" in *" $p "*) continue ;; esac cmd=$(ps -p "$p" -o args= 2>/dev/null || true) case "$cmd" in *sql-server*) ;; diff --git a/examples/dolt/health_test.go b/examples/dolt/health_test.go index f72e3a0a51..74b3a40986 100644 --- a/examples/dolt/health_test.go +++ b/examples/dolt/health_test.go @@ -688,6 +688,119 @@ func writeExecutable(t *testing.T, path, contents string) { } } +// TestHealthScriptZombieScanExcludesRigLocalServers verifies that +// Dolt processes on rig-configured ports are not flagged as zombies. +// Regression guard for the bug where deacon patrol killed rig-local +// Dolt servers because the zombie scan treated every non-city-server +// dolt sql-server PID as a zombie. +func TestHealthScriptZombieScanExcludesRigLocalServers(t *testing.T) { + cityPath := t.TempDir() + fakeBin := t.TempDir() + + mainPort := "19901" + rigPort := "19902" + + mainPID := "424201" + rigPID := "424202" + zombiePID := "424203" + + // City .beads directory with metadata. + if err := os.MkdirAll(filepath.Join(cityPath, ".beads"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(cityPath, ".beads", "metadata.json"), + []byte(`{"dolt_database":"city"}`), 0o644); err != nil { + t.Fatal(err) + } + + // Rig directory with config.yaml containing dolt.port. + rigBeads := filepath.Join(cityPath, "rigs", "enterprise", ".beads") + if err := os.MkdirAll(rigBeads, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(rigBeads, "metadata.json"), + []byte(`{"dolt_database":"enterprise"}`), 0o644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(rigBeads, "config.yaml"), + []byte("dolt.port: "+rigPort+"\n"), 0o644); err != nil { + t.Fatal(err) + } + + // Fake gc: fail so metadata_files() falls back to find. + writeExecutable(t, filepath.Join(fakeBin, "gc"), "#!/bin/sh\nexit 1\n") + + // Fake pgrep: returns rig PID and zombie PID (main PID excluded + // by server_pid check, not by pgrep filtering). + writeExecutable(t, filepath.Join(fakeBin, "pgrep"), + fmt.Sprintf("#!/bin/sh\necho %s\necho %s\necho %s\n", mainPID, rigPID, zombiePID)) + + // Fake lsof: maps ports to PIDs. + writeExecutable(t, filepath.Join(fakeBin, "lsof"), + fmt.Sprintf(`#!/bin/sh +for arg in "$@"; do + case "$arg" in + -iTCP:%s) echo %s; exit 0 ;; + -iTCP:%s) echo %s; exit 0 ;; + esac +done +exit 1 +`, mainPort, mainPID, rigPort, rigPID)) + + // Fake ps: handles pid_is_running (-o pid=) and zombie scan (-o args=). + writeExecutable(t, filepath.Join(fakeBin, "ps"), `#!/bin/sh +if [ "$1" = "-p" ] && [ "$3" = "-o" ]; then + case "$4" in + pid=) printf ' %s\n' "$2"; exit 0 ;; + args=) echo "dolt sql-server"; exit 0 ;; + esac +fi +exit 1 +`) + + // Fake nc: unreachable (no real server). + writeExecutable(t, filepath.Join(fakeBin, "nc"), "#!/bin/sh\nexit 1\n") + + // Fake dolt: SELECT 1 fails (no real server). + writeExecutable(t, filepath.Join(fakeBin, "dolt"), "#!/bin/sh\nexit 1\n") + + root := repoRoot(t) + cmd := exec.Command("sh", filepath.Join(root, healthScript), "--json") + cmd.Env = append( + filteredEnv("GC_CITY_PATH", "GC_PACK_DIR", "GC_DOLT_HOST", "GC_DOLT_PORT", + "GC_DOLT_USER", "GC_DOLT_PASSWORD", "GC_HEALTH_SKIP_ZOMBIE_SCAN", "PATH"), + "GC_CITY_PATH="+cityPath, + "GC_PACK_DIR="+root, + "GC_DOLT_HOST=127.0.0.1", + "GC_DOLT_PORT="+mainPort, + "GC_DOLT_USER=root", + "GC_DOLT_PASSWORD=", + "PATH="+fakeBin+string(os.PathListSeparator)+os.Getenv("PATH"), + ) + + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("health.sh failed: %v\n%s", err, out) + } + + output := string(out) + + // The true zombie (424203) should be counted. + if !strings.Contains(output, `"zombie_count": 1`) { + t.Errorf("expected zombie_count 1; got:\n%s", output) + } + + // The rig PID (424202) must NOT appear in zombie_pids. + if strings.Contains(output, rigPID) { + t.Errorf("rig-local Dolt PID %s should not be in zombie_pids; got:\n%s", rigPID, output) + } + + // The true zombie PID (424203) must appear in zombie_pids. + if !strings.Contains(output, zombiePID) { + t.Errorf("true zombie PID %s should be in zombie_pids; got:\n%s", zombiePID, output) + } +} + // TestHealthScriptJSONAlwaysExitsZero guards the JSON-mode exit // contract. Automation consumers (notably the deacon patrol formula) // parse the JSON payload and key health decisions off `server.reachable`. From 3c024cc9af19cc716c7a9645542f717e37452a43 Mon Sep 17 00:00:00 2001 From: julianknutsen Date: Sat, 25 Apr 2026 20:44:44 +0000 Subject: [PATCH 28/85] ci: publish asset-based Homebrew formula --- .github/workflows/homebrew-tap-smoke.yml | 2 +- .github/workflows/release.yml | 136 +++++++++++++++++++++++ .goreleaser.yml | 11 +- RELEASING.md | 19 +++- docs/getting-started/installation.md | 5 +- 5 files changed, 156 insertions(+), 17 deletions(-) diff --git a/.github/workflows/homebrew-tap-smoke.yml b/.github/workflows/homebrew-tap-smoke.yml index bffbaeba8a..e8b8ee4921 100644 --- a/.github/workflows/homebrew-tap-smoke.yml +++ b/.github/workflows/homebrew-tap-smoke.yml @@ -34,7 +34,7 @@ jobs: run: brew uninstall --force gascity || true - name: Install gascity from the live tap - run: brew install --build-from-source gastownhall/gascity/gascity + run: brew install gastownhall/gascity/gascity - name: Run formula test block run: brew test gastownhall/gascity/gascity diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f072cce5c6..f0c73e93e3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,8 @@ on: push: tags: - "v*" + # Manual dispatch is only for rerunning a release from a v* tag. Publishing + # jobs below are tag-gated and skip branch refs. workflow_dispatch: concurrency: @@ -16,6 +18,7 @@ permissions: jobs: release: name: Release + if: ${{ github.repository == 'gastownhall/gascity' && startsWith(github.ref, 'refs/tags/v') }} runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -48,3 +51,136 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GORELEASER_CURRENT_TAG: ${{ github.ref_name }} + + update-homebrew-formula: + name: Update Homebrew formula + if: ${{ github.repository == 'gastownhall/gascity' && startsWith(github.ref, 'refs/tags/v') }} + needs: release + runs-on: ubuntu-latest + env: + HAS_HOMEBREW_APP: ${{ secrets.HOMEBREW_TAP_APP_ID != '' && secrets.HOMEBREW_TAP_APP_PRIVATE_KEY != '' }} + HAS_HOMEBREW_PAT: ${{ secrets.HOMEBREW_TAP_TOKEN != '' }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Extract version from tag + id: version + run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + + - name: Mint Homebrew tap token + id: homebrew-token + if: ${{ env.HAS_HOMEBREW_APP == 'true' }} + uses: actions/create-github-app-token@v3 + with: + app-id: ${{ secrets.HOMEBREW_TAP_APP_ID }} + private-key: ${{ secrets.HOMEBREW_TAP_APP_PRIVATE_KEY }} + owner: gastownhall + repositories: homebrew-gascity + permission-contents: write + + - name: Generate and push Homebrew formula + if: ${{ env.HAS_HOMEBREW_APP == 'true' || env.HAS_HOMEBREW_PAT == 'true' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HOMEBREW_TAP_TOKEN: ${{ steps.homebrew-token.outputs.token || secrets.HOMEBREW_TAP_TOKEN }} + run: | + version="${{ steps.version.outputs.version }}" + tag="v${version}" + base_url="https://github.com/gastownhall/gascity/releases/download/${tag}" + + gh release download "${tag}" --pattern "gascity_${version}_checksums.txt" --dir /tmp + checksums_file="/tmp/gascity_${version}_checksums.txt" + + get_sha256() { + local sha + sha=$(grep "$1" "$checksums_file" | awk '{print $1}') + if [ -z "$sha" ]; then + echo "ERROR: missing checksum for $1" >&2 + exit 1 + fi + echo "$sha" + } + + darwin_arm64_sha=$(get_sha256 "gascity_${version}_darwin_arm64.tar.gz") + darwin_amd64_sha=$(get_sha256 "gascity_${version}_darwin_amd64.tar.gz") + linux_amd64_sha=$(get_sha256 "gascity_${version}_linux_amd64.tar.gz") + linux_arm64_sha=$(get_sha256 "gascity_${version}_linux_arm64.tar.gz") + + cat > /tmp/gascity.rb < # create a new city + gc start # start an existing city + EOS + end + + test do + assert_match version.to_s, shell_output("#{bin}/gc version") + end + end + FORMULA + sed -i 's/^ //' /tmp/gascity.rb + + cd /tmp + git clone "https://x-access-token:${HOMEBREW_TAP_TOKEN}@github.com/gastownhall/homebrew-gascity.git" + cp gascity.rb homebrew-gascity/Formula/gascity.rb + cd homebrew-gascity + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add Formula/gascity.rb + git commit -m "gascity ${version}" || echo "No changes to commit" + git push + + - name: Skip Homebrew formula update + if: ${{ env.HAS_HOMEBREW_APP != 'true' && env.HAS_HOMEBREW_PAT != 'true' }} + run: echo "No Homebrew tap credential configured; skipping tap update." diff --git a/.goreleaser.yml b/.goreleaser.yml index e34054defc..96b259b454 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -21,14 +21,9 @@ release: prerelease: auto replace_existing_artifacts: true -# Homebrew distribution is handled by a hand-authored, source-built formula -# that lives outside this repo (in `gastownhall/homebrew-gascity` and, once -# merged, `Homebrew/homebrew-core`). Removed the autogenerated binary-install -# `brews:` block because: -# - homebrew-core rejects binary-only formulae; it requires a source build. -# - Keeping both a GoReleaser-stamped tap formula and a hand-authored -# core formula guarantees drift between the two sources of truth. -# See RELEASING.md for the new flow. +# Homebrew tap distribution is generated by .github/workflows/release.yml after +# GoReleaser uploads all release archives. The tap formula installs the release +# assets directly; no source build or Go toolchain is required for users. changelog: sort: asc diff --git a/RELEASING.md b/RELEASING.md index e37bab072e..60cb49c3f9 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -5,7 +5,7 @@ | Channel | Mechanism | Automatic? | |---------|-----------|------------| | **GitHub Release** | GoReleaser via `release.yml` on tag push | Yes | -| **Homebrew tap** (`gastownhall/gascity`) | GoReleaser `brews:` block writes to the tap on tag push | Yes | +| **Homebrew tap** (`gastownhall/gascity`) | `release.yml` writes an asset-based formula after archives upload | Yes | | **Homebrew core** (`Homebrew/homebrew-core`) | BrewTestBot autobump, once listed | Yes (~3h delay) | The homebrew-core submission is [in progress](https://github.com/Homebrew/homebrew-core). Until it lands and is added to the autobump list, users install via `brew install gastownhall/gascity/gascity`. @@ -45,7 +45,8 @@ Version numbers live **only** in the git tag — there is no `Version` constant 1. **Reject `replace` directives in `go.mod`** — they break `go install ...@latest` and bottle builds in homebrew-core. 2. **`make check-version-tag`** — asserts the tag is a clean `vMAJOR.MINOR.PATCH` with no pre-release suffix. RC/beta tags will fail the release. Pre-release tags should be cut on a dedicated branch or not trigger this workflow. -3. **GoReleaser** — builds binaries for linux/darwin × amd64/arm64, creates the GitHub Release with grouped changelog (`feat:` → Features, `fix:` → Bug Fixes, others → Others), and writes the Homebrew tap formula via the `brews:` block in `.goreleaser.yml`. +3. **GoReleaser** — builds binaries for linux/darwin × amd64/arm64 and creates the GitHub Release with grouped changelog (`feat:` → Features, `fix:` → Bug Fixes, others → Others). +4. **Homebrew tap update** — downloads the published checksums and writes an asset-based formula to `gastownhall/homebrew-gascity`. Forks skip publish/announce steps automatically via the `--skip=publish --skip=announce` flag (the workflow checks `github.repository != 'gastownhall/gascity'`). @@ -58,9 +59,15 @@ grep '^replace' go.mod # should print nothing ## Homebrew tap (`gastownhall/gascity`) -The GoReleaser `brews:` block automatically overwrites `Formula/gascity.rb` in the `gastownhall/homebrew-gascity` repo on every tag push, using `HOMEBREW_TAP_TOKEN`. No manual action required. +The release workflow automatically overwrites `Formula/gascity.rb` in the `gastownhall/homebrew-gascity` repo on every tag push. It prefers the GitHub App credentials `HOMEBREW_TAP_APP_ID` and `HOMEBREW_TAP_APP_PRIVATE_KEY`, and falls back to the legacy `HOMEBREW_TAP_TOKEN` while the app rollout is in progress. -**This section will change when homebrew-core lands.** The `brews:` block will be removed, the tap will be deprecated, and releases will flow only through the source-built formula in homebrew-core. +The tap formula installs prebuilt release assets, so users do not need Go or a source build: + +```bash +brew install gastownhall/gascity/gascity +``` + +**This section will change when homebrew-core lands.** The tap update job can be disabled, the tap can be deprecated, and releases can flow only through the source-built formula in homebrew-core. ## Homebrew core (planned) @@ -80,7 +87,7 @@ Manual `brew bump-formula-pr` is refused for autobump formulae. If the bot stall | `CHANGELOG.md` | `[Unreleased]` → `[X.Y.Z] - DATE` | `scripts/bump-version.sh` | | Git tag `vX.Y.Z` | Created and pushed | `scripts/bump-version.sh` | | GitHub Release page | Created with binaries + grouped changelog | GoReleaser in `release.yml` | -| `gastownhall/homebrew-gascity/Formula/gascity.rb` | `url` + `sha256` updated | GoReleaser in `release.yml` | +| `gastownhall/homebrew-gascity/Formula/gascity.rb` | asset URLs + `sha256` updated | `update-homebrew-formula` in `release.yml` | ## Troubleshooting @@ -98,7 +105,7 @@ Check `.github/workflows/release.yml` still matches `tags: v*`. Verify the tag w ### Tap formula not updated -Check `HOMEBREW_TAP_TOKEN` in repo secrets. It needs `contents: write` on `gastownhall/homebrew-gascity`. The workflow logs will show the exact error. +Check the Homebrew tap credential in repo secrets. Preferred: `HOMEBREW_TAP_APP_ID` and `HOMEBREW_TAP_APP_PRIVATE_KEY` for a GitHub App installed on `gastownhall/homebrew-gascity` with contents write. Legacy fallback: `HOMEBREW_TAP_TOKEN` with contents write on the tap. The workflow logs will show the exact error. ### Homebrew shows old version after a release diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 9736df2abe..fc861d4086 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -40,8 +40,9 @@ The exact versions CI pins are in [`deps.env`](https://github.com/gastownhall/ga brew install gastownhall/gascity/gascity ``` -This taps the `gastownhall/gascity` formula, builds or fetches the `gc` binary, -and installs all six runtime dependencies (tmux, jq, git, dolt, flock, beads). +This taps the `gastownhall/gascity` formula, downloads the matching `gc` +release asset, and installs all six runtime dependencies (tmux, jq, git, dolt, +flock, beads). Verify the installation: From 07005b57beeb256a22586fbec56d148c9bdf334e Mon Sep 17 00:00:00 2001 From: julianknutsen Date: Sat, 25 Apr 2026 20:49:35 +0000 Subject: [PATCH 29/85] docs: document gascity core and emergency tap paths --- RELEASING.md | 8 +++++++- docs/getting-started/installation.md | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index 60cb49c3f9..b70ae40a89 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -67,7 +67,13 @@ The tap formula installs prebuilt release assets, so users do not need Go or a s brew install gastownhall/gascity/gascity ``` -**This section will change when homebrew-core lands.** The tap update job can be disabled, the tap can be deprecated, and releases can flow only through the source-built formula in homebrew-core. +The intended long-term user-facing Homebrew path is homebrew-core: + +```bash +brew install gascity +``` + +Until the core formula lands, the tap is the public install path. After core lands, keep the tap available for emergency updates while normal releases flow through homebrew-core. ## Homebrew core (planned) diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index fc861d4086..044c2413e6 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -44,6 +44,10 @@ This taps the `gastownhall/gascity` formula, downloads the matching `gc` release asset, and installs all six runtime dependencies (tmux, jq, git, dolt, flock, beads). +Once Gas City is accepted into homebrew-core, the normal install path will be +`brew install gascity`; the `gastownhall/gascity` tap remains available for +emergency updates. + Verify the installation: ```bash From 48a1e9b9202c045103d72b052c43c148e1bd23c5 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Sat, 25 Apr 2026 04:56:49 +0000 Subject: [PATCH 30/85] managed dolt: disable upstream load-avg auto-GC scheduler (workaround dolt#10944) (#1200) --- cmd/gc/dolt_start_managed.go | 18 +++++++ cmd/gc/dolt_start_managed_test.go | 64 +++++++++++++++++++++++ examples/bd/assets/scripts/gc-beads-bd.sh | 11 ++++ 3 files changed, 93 insertions(+) create mode 100644 cmd/gc/dolt_start_managed_test.go diff --git a/cmd/gc/dolt_start_managed.go b/cmd/gc/dolt_start_managed.go index fc4e581a01..4f7431142b 100644 --- a/cmd/gc/dolt_start_managed.go +++ b/cmd/gc/dolt_start_managed.go @@ -73,6 +73,7 @@ func startManagedDoltProcessWithOptions(cityPath, host, port, user, logLevel str cmd.Stderr = logFile cmd.Stdin = nil cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + cmd.Env = doltServerEnv(os.Environ()) if err := cmd.Start(); err != nil { _ = logFile.Close() return report, fmt.Errorf("start dolt sql-server: %w", err) @@ -208,3 +209,20 @@ func terminateManagedDoltPID(pid int) error { time.Sleep(250 * time.Millisecond) return nil } + +// doltServerEnv augments the parent environment with overrides we need +// applied to every managed dolt sql-server we launch. Currently it +// disables Dolt's load-average auto-GC scheduler, which on multi-core +// hosts (>~16 CPUs) silently prevents auto-GC from ever running. See +// https://github.com/dolthub/dolt/issues/10944. Users who explicitly +// set DOLT_GC_SCHEDULER are respected. +func doltServerEnv(parent []string) []string { + const key = "DOLT_GC_SCHEDULER" + prefix := key + "=" + for _, kv := range parent { + if strings.HasPrefix(kv, prefix) { + return parent + } + } + return append(append([]string(nil), parent...), prefix+"NONE") +} diff --git a/cmd/gc/dolt_start_managed_test.go b/cmd/gc/dolt_start_managed_test.go new file mode 100644 index 0000000000..a7fd474e72 --- /dev/null +++ b/cmd/gc/dolt_start_managed_test.go @@ -0,0 +1,64 @@ +package main + +import "testing" + +func TestDoltServerEnv_AppendsDefaultWhenMissing(t *testing.T) { + parent := []string{"PATH=/usr/bin", "HOME=/home/test"} + out := doltServerEnv(parent) + + want := "DOLT_GC_SCHEDULER=NONE" + found := false + for _, kv := range out { + if kv == want { + found = true + break + } + } + if !found { + t.Fatalf("expected %q in env, got %v", want, out) + } + // Original entries preserved. + for _, kv := range parent { + var hit bool + for _, got := range out { + if got == kv { + hit = true + break + } + } + if !hit { + t.Fatalf("parent entry %q missing from output env %v", kv, out) + } + } +} + +func TestDoltServerEnv_RespectsUserOverride(t *testing.T) { + parent := []string{"PATH=/usr/bin", "DOLT_GC_SCHEDULER=LOADAVG", "HOME=/home/test"} + out := doltServerEnv(parent) + + // User-provided value must be preserved exactly. + count := 0 + for _, kv := range out { + if kv == "DOLT_GC_SCHEDULER=LOADAVG" { + count++ + } + if kv == "DOLT_GC_SCHEDULER=NONE" { + t.Fatalf("user override clobbered by default: %v", out) + } + } + if count != 1 { + t.Fatalf("expected exactly one DOLT_GC_SCHEDULER=LOADAVG entry, got %d in %v", count, out) + } +} + +func TestDoltServerEnv_RespectsEmptyUserValue(t *testing.T) { + // An explicit empty value (DOLT_GC_SCHEDULER=) is still a user + // override and we must not replace it. + parent := []string{"DOLT_GC_SCHEDULER="} + out := doltServerEnv(parent) + for _, kv := range out { + if kv == "DOLT_GC_SCHEDULER=NONE" { + t.Fatalf("explicit empty-value override clobbered: %v", out) + } + } +} diff --git a/examples/bd/assets/scripts/gc-beads-bd.sh b/examples/bd/assets/scripts/gc-beads-bd.sh index 69d3639b14..c81be04222 100755 --- a/examples/bd/assets/scripts/gc-beads-bd.sh +++ b/examples/bd/assets/scripts/gc-beads-bd.sh @@ -1511,6 +1511,17 @@ op_start() { log_offset=$(wc -c < "$LOG_FILE" 2>/dev/null || echo 0) fi + # Disable Dolt's load-average auto-GC scheduler. Dolt 1.86.0+ + # ships a loadAvgGCScheduler whose threshold formula scales + # inversely with CPU count (10/CPUs), so on multi-core hosts the + # gate is essentially always tripped and CALL DOLT_GC() is + # queued but never executed; auto_gc_behavior.enable: true in + # config.yaml has no effect. See + # https://github.com/dolthub/dolt/issues/10944. Users who + # explicitly set DOLT_GC_SCHEDULER are respected. + : "${DOLT_GC_SCHEDULER:=NONE}" + export DOLT_GC_SCHEDULER + # Start dolt sql-server with config file. Close the startup lock fd in # the child so the flock is released when this starter exits. nohup sh -c 'exec 9>&-; exec dolt sql-server --config "$1"' sh "$CONFIG_FILE" >> "$LOG_FILE" 2>&1 & From 81ab8dd6dfa8ab3e5bf6c696f1424be12c356e6c Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Sat, 25 Apr 2026 21:03:30 +0000 Subject: [PATCH 31/85] Fix gc-beads-bd empty GC scheduler override --- cmd/gc/dolt_start_managed_test.go | 28 ++++++++++++++++++++- docs/troubleshooting/dolt-bloat-recovery.md | 19 ++++++++------ examples/bd/assets/scripts/gc-beads-bd.sh | 2 +- examples/dolt/commands/gc-nudge/run.sh | 15 +++++------ 4 files changed, 47 insertions(+), 17 deletions(-) diff --git a/cmd/gc/dolt_start_managed_test.go b/cmd/gc/dolt_start_managed_test.go index a7fd474e72..a7058f93ac 100644 --- a/cmd/gc/dolt_start_managed_test.go +++ b/cmd/gc/dolt_start_managed_test.go @@ -1,6 +1,12 @@ package main -import "testing" +import ( + "os" + "path/filepath" + "runtime" + "strings" + "testing" +) func TestDoltServerEnv_AppendsDefaultWhenMissing(t *testing.T) { parent := []string{"PATH=/usr/bin", "HOME=/home/test"} @@ -62,3 +68,23 @@ func TestDoltServerEnv_RespectsEmptyUserValue(t *testing.T) { } } } + +func TestGCBeadsBDScript_RespectsEmptyUserValue(t *testing.T) { + _, thisFile, _, ok := runtime.Caller(0) + if !ok { + t.Fatal("runtime.Caller(0) failed") + } + scriptPath := filepath.Join(filepath.Dir(thisFile), "..", "..", "examples", "bd", "assets", "scripts", "gc-beads-bd.sh") + data, err := os.ReadFile(scriptPath) + if err != nil { + t.Fatalf("read %s: %v", scriptPath, err) + } + script := string(data) + + if !strings.Contains(script, `${DOLT_GC_SCHEDULER=NONE}`) { + t.Fatalf("gc-beads-bd.sh must default DOLT_GC_SCHEDULER only when unset") + } + if strings.Contains(script, `${DOLT_GC_SCHEDULER:=NONE}`) { + t.Fatalf("gc-beads-bd.sh must not clobber an explicitly empty DOLT_GC_SCHEDULER") + } +} diff --git a/docs/troubleshooting/dolt-bloat-recovery.md b/docs/troubleshooting/dolt-bloat-recovery.md index 23559cec0f..7589902215 100644 --- a/docs/troubleshooting/dolt-bloat-recovery.md +++ b/docs/troubleshooting/dolt-bloat-recovery.md @@ -86,14 +86,17 @@ If GC finishes but the size barely moves, the chunks are nearly all live heuristics and default archive compression. - **Let the dolt pack's `dolt-gc-nudge` order run continuously.** It ships embedded in the dolt pack and fires `CALL DOLT_GC()` every 1h - by default, unconditionally. Empirical evidence (beads perf harness, - 2026-04) shows Dolt's auto-GC does not fire under bd's fork-per-op - CLI workload even with `auto_gc_behavior.enable: true`, so the nudge - is the only mechanism bounding commit-graph growth. GC is idempotent - and near-free when there's nothing to reclaim, so running it every - hour is cheap. To opt out on a given city, add `dolt-gc-nudge` to the - city's `[orders] skip = [...]` list (or to a rig-level - `[[order.override]]`). To skip GC on small databases, set + by default, unconditionally. Gas City's managed-Dolt launch path now + forces `DOLT_GC_SCHEDULER=NONE`, which restores Dolt's configured + auto-GC behavior on multi-core hosts affected by + [dolthub/dolt#10944](https://github.com/dolthub/dolt/issues/10944). + The hourly nudge remains valuable as a belt-and-suspenders backstop + for the bd workload and as an unconditional recovery path if the + threshold-triggered auto-GC has nothing to do for a while. GC is + idempotent and near-free when there's nothing to reclaim, so running + it every hour is cheap. To opt out on a given city, add + `dolt-gc-nudge` to the city's `[orders] skip = [...]` list (or to a + rig-level `[[order.override]]`). To skip GC on small databases, set `GC_DOLT_GC_THRESHOLD_BYTES` to a positive byte count in the city's environment (default: 0 — run unconditionally). - **Mind `orders.max_timeout` if you set one.** The nudge order asks diff --git a/examples/bd/assets/scripts/gc-beads-bd.sh b/examples/bd/assets/scripts/gc-beads-bd.sh index c81be04222..56cb7b79df 100755 --- a/examples/bd/assets/scripts/gc-beads-bd.sh +++ b/examples/bd/assets/scripts/gc-beads-bd.sh @@ -1519,7 +1519,7 @@ op_start() { # config.yaml has no effect. See # https://github.com/dolthub/dolt/issues/10944. Users who # explicitly set DOLT_GC_SCHEDULER are respected. - : "${DOLT_GC_SCHEDULER:=NONE}" + : "${DOLT_GC_SCHEDULER=NONE}" export DOLT_GC_SCHEDULER # Start dolt sql-server with config file. Close the startup lock fd in diff --git a/examples/dolt/commands/gc-nudge/run.sh b/examples/dolt/commands/gc-nudge/run.sh index 8c22c62919..0eb597f759 100755 --- a/examples/dolt/commands/gc-nudge/run.sh +++ b/examples/dolt/commands/gc-nudge/run.sh @@ -1,13 +1,14 @@ #!/bin/sh # gc dolt gc-nudge — periodic CALL DOLT_GC() to bound the Dolt commit graph. # -# Why this exists: empirical evidence (beads perf harness, 2026-04) shows -# Dolt's auto-GC does not fire under the beads-CLI fork-per-op workload even -# with `auto_gc_behavior.enable: true`. Unbounded commit-graph growth causes -# both disk bloat (~120 GB after a few days) and tail-latency degradation -# (p99 at 143 MB of history → 16.9s, extrapolates to minutes at GB scale). -# Manual CALL DOLT_GC() reclaims ~43% in seconds, so a periodic nudge is -# both necessary and cheap. +# Why this exists: Gas City's managed-Dolt launch path now forces +# `DOLT_GC_SCHEDULER=NONE` to work around +# https://github.com/dolthub/dolt/issues/10944, so threshold-triggered +# auto-GC can fire again on multi-core hosts. We still keep an hourly +# nudge because the bd workload can accumulate history quickly, and an +# unconditional `CALL DOLT_GC()` remains a cheap belt-and-suspenders +# backstop for reclaiming orphan chunks before they turn into disk bloat +# and tail-latency spikes. # # Policy: fire CALL DOLT_GC() unconditionally on every cooldown tick # (default 1h). The GC is idempotent and near-free when there's nothing From 2ea55afaa28117d02afe2324160ce975be1eec99 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Sat, 25 Apr 2026 22:41:34 +0000 Subject: [PATCH 32/85] Reduce cmd/gc test runtime under two minutes (#1245) --- .github/workflows/ci.yml | 24 +++++++++++++++++++ Makefile | 6 ++--- cmd/gc/api_state.go | 3 +++ cmd/gc/beads_provider_lifecycle.go | 3 +++ cmd/gc/beads_provider_lifecycle_test.go | 28 ++++++++++++++++++++++- cmd/gc/build_desired_state_test.go | 2 ++ cmd/gc/city_runtime.go | 7 ++++-- cmd/gc/city_runtime_test.go | 1 + cmd/gc/cityinit_impl_test.go | 1 + cmd/gc/cmd_beads_city_test.go | 4 ++++ cmd/gc/cmd_dolt_state_test.go | 11 +++++---- cmd/gc/cmd_mail.go | 4 ++-- cmd/gc/cmd_mail_test.go | 10 ++++++++ cmd/gc/cmd_rig_endpoint_test.go | 6 ++--- cmd/gc/cmd_session_logs_test.go | 1 + cmd/gc/cmd_stop_test.go | 1 + cmd/gc/cmd_supervisor_city_test.go | 2 +- cmd/gc/cmd_wait_test.go | 3 ++- cmd/gc/dolt_gc_nudge_script_test.go | 4 ++++ cmd/gc/dolt_preflight_cleanup_test.go | 1 + cmd/gc/dolt_project_id_test.go | 6 ++--- cmd/gc/fast_loop_helpers_test.go | 6 ++++- cmd/gc/live_submit_probe_test.go | 2 +- cmd/gc/main.go | 3 +++ cmd/gc/main_test.go | 1 + cmd/gc/pool_test.go | 2 ++ cmd/gc/session_lifecycle_parallel_test.go | 2 ++ cmd/gc/test_gc_binary_test.go | 8 ------- cmd/gc/worker_handle_test.go | 2 ++ 29 files changed, 124 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8ac61c4f5..1c094e90c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -148,6 +148,30 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} verbose: true + cmd-gc-process: + name: cmd/gc process suite + needs: changes + if: >- + needs.changes.outputs.worker_phase2 == 'true' || + needs.changes.outputs.beads == 'true' || + needs.changes.outputs.packs == 'true' + runs-on: ubuntu-latest + timeout-minutes: 20 + env: + DOLT_VERSION: "1.86.1" + BD_VERSION: "v1.0.0" + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: ./.github/actions/setup-gascity-ubuntu + with: + dolt-version: ${{ env.DOLT_VERSION }} + bd-version: ${{ env.BD_VERSION }} + install-claude-cli: "true" + - name: Install tools + run: make install-tools + - name: Run cmd/gc process suite + run: make test-cmd-gc-process + # Runs always, but remains non-blocking while integration/provider paths are still stabilizing. integration-shards: name: Integration / ${{ matrix.shard_name }} diff --git a/Makefile b/Makefile index d3830bf7b1..62bb5f257e 100644 --- a/Makefile +++ b/Makefile @@ -163,7 +163,7 @@ TEST_ENV = env -i \ ## test: run fast unit tests (skip integration-tagged and GC_FAST_UNIT-gated process tests) ## The skipped cmd/gc process-backed scenarios remain covered by -## `make test-cmd-gc-process` locally and the CI `test-integration-packages` shard. +## `make test-cmd-gc-process` locally and the CI `cmd/gc process suite` job. ## Wrapped in $(TEST_ENV) — see comment above for why. test: $(TEST_ENV) GC_FAST_UNIT=1 go test ./... @@ -171,7 +171,7 @@ test: ## test-cmd-gc-process: run the full non-short cmd/gc suite, including the ## process-backed lifecycle coverage routed out of the default fast loop test-cmd-gc-process: - $(TEST_ENV) GC_FAST_UNIT=0 go test ./cmd/gc + $(TEST_ENV) GC_FAST_UNIT=0 go test -count=1 -timeout 20m ./cmd/gc ## test-worker-core: run deterministic worker transcript and continuation conformance test-worker-core: @@ -348,7 +348,7 @@ UNIT_COVER_PKGS := $(shell go list -f '{{if or .TestGoFiles .XTestGoFiles}}{{.Im ## test-cover: run fast unit-test coverage without the integration-tagged package sweep ## The skipped cmd/gc process-backed scenarios remain covered by -## `make test-cmd-gc-process` locally and the CI `test-integration-packages` shard. +## `make test-cmd-gc-process` locally and the CI `cmd/gc process suite` job. test-cover: $(TEST_ENV) GC_FAST_UNIT=1 go test -timeout 8m -coverprofile=coverage.txt $(UNIT_COVER_PKGS) diff --git a/cmd/gc/api_state.go b/cmd/gc/api_state.go index 27f4ad5221..dcd3d9fe7c 100644 --- a/cmd/gc/api_state.go +++ b/cmd/gc/api_state.go @@ -130,6 +130,9 @@ func wrapWithCachingStore(ctx context.Context, store beads.Store, ep events.Prov if err := cs.PrimeActive(); err != nil { log.Printf("caching-store: pre-prime failed: %v", err) } + if ctx.Done() == nil { + return cs + } // Full prime runs async — backfills remaining beads for List() // callers (convergence reconcile, sweep, API handlers). go func() { diff --git a/cmd/gc/beads_provider_lifecycle.go b/cmd/gc/beads_provider_lifecycle.go index 40a1fbb52c..419f43982d 100644 --- a/cmd/gc/beads_provider_lifecycle.go +++ b/cmd/gc/beads_provider_lifecycle.go @@ -428,6 +428,9 @@ func ensureBeadsProvider(cityPath string) error { // Called by gc stop after agents have been terminated. // For exec providers, fires "stop". For file providers, always available. func shutdownBeadsProvider(cityPath string) error { + if cityUsesBdStoreContract(cityPath) && strings.TrimSpace(os.Getenv("GC_DOLT")) == "skip" { + return nil + } provider := beadsProvider(cityPath) if strings.HasPrefix(provider, "exec:") { if providerUsesBdStoreContract(provider) && isExternalDolt(cityPath) { diff --git a/cmd/gc/beads_provider_lifecycle_test.go b/cmd/gc/beads_provider_lifecycle_test.go index 0cee60408d..c7e3a388c1 100644 --- a/cmd/gc/beads_provider_lifecycle_test.go +++ b/cmd/gc/beads_provider_lifecycle_test.go @@ -3613,6 +3613,7 @@ esac } func TestGcBeadsBdInitBackfillsRepoIDMigrationWhenMetadataExistsWithoutProjectID(t *testing.T) { + skipSlowCmdGCTest(t, "runs the materialized gc-beads-bd init script with GC_BIN helper; run make test-cmd-gc-process for full coverage") cityPath := t.TempDir() if err := os.MkdirAll(filepath.Join(cityPath, ".gc"), 0o755); err != nil { t.Fatal(err) @@ -5967,13 +5968,36 @@ func TestGcBeadsBdStartConcurrentWaitPassesRemainingExistingManagedBudget(t *tes t.Fatal(err) } invocationFile := filepath.Join(t.TempDir(), "gc-invocation") + nowFile := filepath.Join(t.TempDir(), "gc-now-ms") fakeGC := filepath.Join(binDir, "gc") fakeGCScript := fmt.Sprintf(`#!/bin/sh set -eu invocation_file=%q +now_file=%q subcmd="$1 $2" shift 2 case "$subcmd" in + "dolt-state now-ms") + if [ -f "$now_file" ]; then + step=$(cat "$now_file") + else + step=0 + fi + case "$step" in + 0) + printf '1000000\n' + printf '1\n' > "$now_file" + ;; + 1) + printf '1000000\n' + printf '2\n' > "$now_file" + ;; + *) + printf '1001000\n' + printf '3\n' > "$now_file" + ;; + esac + ;; "dolt-state runtime-layout") while [ "$#" -gt 0 ]; do case "$1" in @@ -6044,7 +6068,7 @@ case "$subcmd" in exit 64 ;; esac -`, invocationFile, layout.PackStateDir, layout.DataDir, layout.LogFile, layout.StateFile, layout.PIDFile, layout.LockFile, layout.ConfigFile) +`, invocationFile, nowFile, layout.PackStateDir, layout.DataDir, layout.LogFile, layout.StateFile, layout.PIDFile, layout.LockFile, layout.ConfigFile) if err := os.WriteFile(fakeGC, []byte(fakeGCScript), 0o755); err != nil { t.Fatal(err) } @@ -6353,6 +6377,7 @@ func TestGcBeadsBdRecoverHelperPreservesReadOnlyWarning(t *testing.T) { } func TestManagedDoltConfigGoWriterMatchesShellFallbackSemantics(t *testing.T) { + skipSlowCmdGCTest(t, "starts the materialized gc-beads-bd shell fallback; run make test-cmd-gc-process for full coverage") cityPath := t.TempDir() if err := os.MkdirAll(filepath.Join(cityPath, ".gc"), 0o755); err != nil { t.Fatal(err) @@ -7763,6 +7788,7 @@ func TestNormalizeCanonicalBdScopeFilesMaterializesMissingMetadata(t *testing.T) } func TestGcBeadsBdStartFallsBackToShellManagedConfigWriterWhenGCBinUnset(t *testing.T) { + skipSlowCmdGCTest(t, "starts the materialized gc-beads-bd shell fallback; run make test-cmd-gc-process for full coverage") cityPath := t.TempDir() if err := os.MkdirAll(filepath.Join(cityPath, ".gc"), 0o755); err != nil { t.Fatal(err) diff --git a/cmd/gc/build_desired_state_test.go b/cmd/gc/build_desired_state_test.go index 21c24180f0..e7e54f1e6c 100644 --- a/cmd/gc/build_desired_state_test.go +++ b/cmd/gc/build_desired_state_test.go @@ -536,6 +536,7 @@ func TestBuildDesiredState_RoutedQueueDoesNotCreateOneSessionPerBead(t *testing. } func TestBuildDesiredState_MinZeroDefaultScaleCheckRoutedWorkCreatesPoolSession(t *testing.T) { + skipSlowCmdGCTest(t, "uses real bd subprocesses for routed-work scale checks; run make test-cmd-gc-process for full coverage") bdPath, err := findPreferredBinary("bd", "/home/ubuntu/.local/bin/bd") if err != nil { t.Skip("bd not installed") @@ -2207,6 +2208,7 @@ func TestBuildDesiredState_PoolCheckUsesExplicitRigPassword(t *testing.T) { } func TestBuildDesiredState_PoolCheckUsesManagedCityDoltPortWhenRigHasNoOverride(t *testing.T) { + skipSlowCmdGCTest(t, "uses a live managed-dolt port probe for scale_check coverage; run make test-cmd-gc-process for full coverage") t.Setenv("GC_BEADS", "bd") cityPath := t.TempDir() rigPath := filepath.Join(cityPath, "myrig") diff --git a/cmd/gc/city_runtime.go b/cmd/gc/city_runtime.go index 403f4ce0d9..828df44b2d 100644 --- a/cmd/gc/city_runtime.go +++ b/cmd/gc/city_runtime.go @@ -271,14 +271,17 @@ func (cr *CityRuntime) run(ctx context.Context) { lastProviderName = v } - cityRoot := filepath.Dir(cr.tomlPath) + cityRoot := cr.cityPath + if cityRoot == "" && cr.tomlPath != "" { + cityRoot = filepath.Dir(cr.tomlPath) + } // Enforce restrictive permissions on .gc/ and its subdirectories. enforceGCPermissions(cr.cityPath, cr.stderr) // Open standalone city bead store when controllerState is unavailable. // When controllerState is present, it manages the cached city store. - if cr.cs == nil { + if cr.cs == nil && cityRoot != "" { if store, err := openCityStoreAt(cityRoot); err != nil { fmt.Fprintf(cr.stderr, "%s: city bead store: %v (auto-suspend disabled)\n", cr.logPrefix, err) //nolint:errcheck // best-effort stderr } else { diff --git a/cmd/gc/city_runtime_test.go b/cmd/gc/city_runtime_test.go index dc731639a1..059bc246e9 100644 --- a/cmd/gc/city_runtime_test.go +++ b/cmd/gc/city_runtime_test.go @@ -1216,6 +1216,7 @@ func TestCityRuntimeBeadReconcileTick_SweepRespectsLiveAssignedWork(t *testing.T } func TestCityRuntimeTick_RefreshesManualSessionOverlayAfterSync(t *testing.T) { + skipSlowCmdGCTest(t, "runs a full runtime tick/reconcile path; run make test-cmd-gc-process for full coverage") cityPath := t.TempDir() if err := os.MkdirAll(filepath.Join(cityPath, "prompts"), 0o755); err != nil { t.Fatalf("mkdir prompts: %v", err) diff --git a/cmd/gc/cityinit_impl_test.go b/cmd/gc/cityinit_impl_test.go index 7046445593..b941ef99c1 100644 --- a/cmd/gc/cityinit_impl_test.go +++ b/cmd/gc/cityinit_impl_test.go @@ -298,6 +298,7 @@ func TestLocalInitializerScaffoldPreservesExistingDirectoryWhenRegisterFails(t * } func TestLocalInitializerInitScaffoldsAndFinalizes(t *testing.T) { + skipSlowCmdGCTest(t, "runs the full local init scaffold/finalize path; run make test-cmd-gc-process for full coverage") configureTestDoltIdentityEnv(t) cityPath := filepath.Join(t.TempDir(), "init-city") diff --git a/cmd/gc/cmd_beads_city_test.go b/cmd/gc/cmd_beads_city_test.go index 59961e126d..25f865b927 100644 --- a/cmd/gc/cmd_beads_city_test.go +++ b/cmd/gc/cmd_beads_city_test.go @@ -87,6 +87,7 @@ func TestDoBeadsCityEndpointSupportsExecGcBeadsBdProvider(t *testing.T) { } func TestDoBeadsCityUseExternalWritesVerifiedCityAndInheritedRigs(t *testing.T) { + skipSlowCmdGCTest(t, "exercises managed bd provider transition behavior; run make test-cmd-gc-process for full coverage") t.Setenv("GC_BEADS", "bd") cityDir := t.TempDir() @@ -184,6 +185,7 @@ func TestDoBeadsCityUseExternalWritesVerifiedCityAndInheritedRigs(t *testing.T) } func TestDoBeadsCityUseExternalUpdatesIncludedInheritedRigs(t *testing.T) { + skipSlowCmdGCTest(t, "exercises managed bd provider transition behavior; run make test-cmd-gc-process for full coverage") t.Setenv("GC_BEADS", "bd") cityDir := t.TempDir() @@ -414,6 +416,7 @@ func TestDoBeadsCityUseExternalStopFailureKeepsExternalConfig(t *testing.T) { } func TestDoBeadsCityUseExternalRewritesCompatRigWithRelativePath(t *testing.T) { + skipSlowCmdGCTest(t, "exercises managed bd provider transition behavior; run make test-cmd-gc-process for full coverage") t.Setenv("GC_BEADS", "bd") cityDir := t.TempDir() @@ -509,6 +512,7 @@ func TestDoBeadsCityUseExternalPreservesCompatOnlyExplicitRigs(t *testing.T) { } func TestDoBeadsCityUseExternalAdoptUnverifiedSkipsValidation(t *testing.T) { + skipSlowCmdGCTest(t, "exercises managed bd provider transition behavior; run make test-cmd-gc-process for full coverage") t.Setenv("GC_BEADS", "bd") cityDir := t.TempDir() diff --git a/cmd/gc/cmd_dolt_state_test.go b/cmd/gc/cmd_dolt_state_test.go index e5a3a43f45..2b64a257d4 100644 --- a/cmd/gc/cmd_dolt_state_test.go +++ b/cmd/gc/cmd_dolt_state_test.go @@ -328,6 +328,7 @@ func TestDoltStateAllocatePortCmdReusesLiveProviderState(t *testing.T) { } func TestStartTCPListenerProcessInDirRegistersCleanup(t *testing.T) { + skipSlowCmdGCTest(t, "spawns a TCP listener process and verifies cleanup; run make test-cmd-gc-process for full coverage") port := reserveRandomTCPPort(t) dir := t.TempDir() var proc *exec.Cmd @@ -1202,7 +1203,7 @@ func TestDoltStatePreflightCleanCmdRemovesStaleArtifacts(t *testing.T) { } func TestDoltStatePreflightCleanCmdPreservesLiveArtifacts(t *testing.T) { - skipSlowCmdGCTest(t, "spawns managed dolt holder processes; run without -short or via integration packages") + skipSlowCmdGCTest(t, "spawns managed dolt holder processes; run make test-cmd-gc-process for full coverage") if _, err := exec.LookPath("lsof"); err != nil { t.Skip("lsof not installed") } @@ -1249,6 +1250,7 @@ func TestDoltStatePreflightCleanCmdPreservesLiveArtifacts(t *testing.T) { func startTCPListenerProcessInDir(t *testing.T, port int, dir string) *exec.Cmd { t.Helper() + skipSlowCmdGCTest(t, "spawns a TCP listener process to emulate managed dolt; run make test-cmd-gc-process for full coverage") if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatalf("MkdirAll(%s): %v", dir, err) } @@ -1297,6 +1299,7 @@ while True: func startLockedDelayedTCPListenerProcessInDir(t *testing.T, lockFile string, port int, dir string, delay time.Duration) *exec.Cmd { t.Helper() + skipSlowCmdGCTest(t, "spawns a delayed TCP listener process to emulate managed dolt recovery; run make test-cmd-gc-process for full coverage") if err := os.MkdirAll(filepath.Dir(lockFile), 0o755); err != nil { t.Fatalf("MkdirAll(%s): %v", filepath.Dir(lockFile), err) } @@ -2131,7 +2134,7 @@ func TestDoltStateStopManagedCmdDoesNotKillImposterPortHolder(t *testing.T) { } func TestDoltStateRecoverManagedCmdReportsReadOnlyAndRestarts(t *testing.T) { - skipSlowCmdGCTest(t, "spawns managed dolt recovery processes; run without -short or via integration packages") + skipSlowCmdGCTest(t, "spawns managed dolt recovery processes; run make test-cmd-gc-process for full coverage") cityPath := t.TempDir() layout, err := resolveManagedDoltRuntimeLayout(cityPath) if err != nil { @@ -2488,7 +2491,7 @@ esac } func TestDoltStateRecoverManagedCmdClearsPublishedStateWhenPreflightCleanupFails(t *testing.T) { - skipSlowCmdGCTest(t, "spawns managed dolt recovery processes; run without -short or via integration packages") + skipSlowCmdGCTest(t, "spawns managed dolt recovery processes; run make test-cmd-gc-process for full coverage") cityPath := t.TempDir() layout, err := resolveManagedDoltRuntimeLayout(cityPath) if err != nil { @@ -2563,7 +2566,7 @@ func TestDoltStateRecoverManagedCmdClearsPublishedStateWhenPreflightCleanupFails } func TestDoltStateRecoverManagedCmdFailsWhenPostStartHealthFails(t *testing.T) { - skipSlowCmdGCTest(t, "spawns managed dolt recovery processes; run without -short or via integration packages") + skipSlowCmdGCTest(t, "spawns managed dolt recovery processes; run make test-cmd-gc-process for full coverage") cityPath := t.TempDir() layout, err := resolveManagedDoltRuntimeLayout(cityPath) if err != nil { diff --git a/cmd/gc/cmd_mail.go b/cmd/gc/cmd_mail.go index d874525b9d..8188c493c7 100644 --- a/cmd/gc/cmd_mail.go +++ b/cmd/gc/cmd_mail.go @@ -529,8 +529,8 @@ func resolveMailTargetsForCommand(identifier string, stderr io.Writer, cmdName s if identifier == "" || identifier == "human" { return resolvedMailTarget{display: "human", recipients: []string{"human"}}, true } - if rawTarget, ok := resolveRawMailTargetForStorelessProvider(identifier, stderr, cmdName); ok { - return rawTarget, true + if isStorelessMailProvider() { + return resolveRawMailTargetForStorelessProvider(identifier, stderr, cmdName) } store, code := openCityStore(stderr, cmdName) if store == nil { diff --git a/cmd/gc/cmd_mail_test.go b/cmd/gc/cmd_mail_test.go index 0855c46b9d..2e51d16a61 100644 --- a/cmd/gc/cmd_mail_test.go +++ b/cmd/gc/cmd_mail_test.go @@ -489,6 +489,11 @@ func TestResolveDefaultMailTargetsForCommand_StorelessProviderUsesFirstCandidate t.Setenv("GC_ALIAS", "codeprobe-worker-1") t.Setenv("GC_SESSION_ID", "codeprobe-worker-gc-1941") t.Setenv("GC_AGENT", "codeprobe-worker") + prev := openMailTargetStore + openMailTargetStore = func() (beads.Store, error) { + return nil, fmt.Errorf("not in a city directory") + } + t.Cleanup(func() { openMailTargetStore = prev }) var stderr bytes.Buffer target, ok := resolveDefaultMailTargetsForCommand(&stderr, "gc mail inbox") @@ -568,6 +573,11 @@ func TestDefaultMailIdentityFallsBackToHumanWithoutAliasSessionOrAgent(t *testin func TestResolveMailAddressForCommand_AllowsStorelessMailProvider(t *testing.T) { t.Setenv("GC_MAIL", "fake") + prev := openMailTargetStore + openMailTargetStore = func() (beads.Store, error) { + return nil, fmt.Errorf("not in a city directory") + } + t.Cleanup(func() { openMailTargetStore = prev }) var stderr bytes.Buffer address, ok := resolveMailAddressForCommand("robot", &stderr, "gc mail inbox") diff --git a/cmd/gc/cmd_rig_endpoint_test.go b/cmd/gc/cmd_rig_endpoint_test.go index c10c0ebcba..a41fb3d8c7 100644 --- a/cmd/gc/cmd_rig_endpoint_test.go +++ b/cmd/gc/cmd_rig_endpoint_test.go @@ -1066,7 +1066,7 @@ func TestCanonicalValidationPasswordUsesCredentialsFileOverride(t *testing.T) { } func TestVerifyExternalDoltEndpointRejectsEmptyExternalDoltDatabase(t *testing.T) { - skipSlowCmdGCTest(t, "requires a managed external dolt endpoint; run without -short or via integration packages") + skipSlowCmdGCTest(t, "requires a managed external dolt endpoint; run make test-cmd-gc-process for full coverage") doltPath, err := exec.LookPath("dolt") if err != nil { t.Skip("dolt not installed") @@ -1167,7 +1167,7 @@ func TestVerifyExternalDoltEndpointRejectsEmptyExternalDoltDatabase(t *testing.T } func TestVerifyExternalDoltEndpointRejectsProjectIdentityMismatch(t *testing.T) { - skipSlowCmdGCTest(t, "requires a managed external dolt endpoint; run without -short or via integration packages") + skipSlowCmdGCTest(t, "requires a managed external dolt endpoint; run make test-cmd-gc-process for full coverage") doltPath, err := exec.LookPath("dolt") if err != nil { t.Skip("dolt not installed") @@ -1271,7 +1271,7 @@ func TestVerifyExternalDoltEndpointRejectsProjectIdentityMismatch(t *testing.T) } func TestVerifyExternalDoltEndpointRejectsMissingLocalProjectID(t *testing.T) { - skipSlowCmdGCTest(t, "requires a managed external dolt endpoint; run without -short or via integration packages") + skipSlowCmdGCTest(t, "requires a managed external dolt endpoint; run make test-cmd-gc-process for full coverage") doltPath, err := exec.LookPath("dolt") if err != nil { t.Skip("dolt not installed") diff --git a/cmd/gc/cmd_session_logs_test.go b/cmd/gc/cmd_session_logs_test.go index 29240902a8..0a03159f69 100644 --- a/cmd/gc/cmd_session_logs_test.go +++ b/cmd/gc/cmd_session_logs_test.go @@ -306,6 +306,7 @@ func TestResolveSessionLogPathFallsBackWhenSessionKeyFileMissing(t *testing.T) { } func TestResolveStoredSessionLogSource_UniqueWorkDirFallsBackBeyondLatestAlias(t *testing.T) { + skipSlowCmdGCTest(t, "probes provider transcript lookup before workdir fallback; run make test-cmd-gc-process for full coverage") store := beads.NewMemStore() workDir := t.TempDir() searchBase := t.TempDir() diff --git a/cmd/gc/cmd_stop_test.go b/cmd/gc/cmd_stop_test.go index fe6700e060..fc0643cf54 100644 --- a/cmd/gc/cmd_stop_test.go +++ b/cmd/gc/cmd_stop_test.go @@ -126,6 +126,7 @@ func TestCmdStopWaitsForStandaloneControllerExit(t *testing.T) { } func TestStopCityManagedBeadsProviderIfRunningStopsDefaultBD(t *testing.T) { + skipSlowCmdGCTest(t, "exercises managed bd provider shutdown; run make test-cmd-gc-process for full coverage") t.Setenv("GC_BEADS", "bd") cityDir := t.TempDir() diff --git a/cmd/gc/cmd_supervisor_city_test.go b/cmd/gc/cmd_supervisor_city_test.go index fb62b3f890..a31e434a44 100644 --- a/cmd/gc/cmd_supervisor_city_test.go +++ b/cmd/gc/cmd_supervisor_city_test.go @@ -215,7 +215,7 @@ func TestRegisterCityWithSupervisorRetriesControllerLockInitFailure(t *testing.T } func TestRegisterCityWithSupervisorKeepsRegistrationWhenReloadFails(t *testing.T) { - skipSlowCmdGCTest(t, "exercises supervisor registration retry behavior; run without -short for scenario coverage") + skipSlowCmdGCTest(t, "exercises supervisor registration retry behavior; run make test-cmd-gc-process for scenario coverage") gcHome := t.TempDir() t.Setenv("GC_HOME", gcHome) diff --git a/cmd/gc/cmd_wait_test.go b/cmd/gc/cmd_wait_test.go index 387f50fea7..5a52123a6d 100644 --- a/cmd/gc/cmd_wait_test.go +++ b/cmd/gc/cmd_wait_test.go @@ -72,7 +72,7 @@ func waitTestEnv(overrides map[string]string) []string { func waitTestRealBDPath(t *testing.T) string { t.Helper() - skipSlowCmdGCTest(t, "requires a managed bd lifecycle city; run without -short or via integration packages") + skipSlowCmdGCTest(t, "requires a managed bd lifecycle city; run make test-cmd-gc-process for full coverage") waitTestRealBDPathOnce.Do(func() { for _, dir := range filepath.SplitList(os.Getenv("PATH")) { if strings.TrimSpace(dir) == "" { @@ -1270,6 +1270,7 @@ func setupFreshManagedBdWaitTestCity(t *testing.T) (string, string) { func setupManagedBdWaitTestCity(t *testing.T) (string, string) { t.Helper() + skipSlowCmdGCTest(t, "requires a managed bd/dolt lifecycle city; run make test-cmd-gc-process for full coverage") configureIsolatedRuntimeEnv(t) bdPath := waitTestRealBDPath(t) diff --git a/cmd/gc/dolt_gc_nudge_script_test.go b/cmd/gc/dolt_gc_nudge_script_test.go index 43af1bf2a2..527c22eea6 100644 --- a/cmd/gc/dolt_gc_nudge_script_test.go +++ b/cmd/gc/dolt_gc_nudge_script_test.go @@ -286,6 +286,7 @@ func TestDoltGCNudgeDefaultCallTimeoutMatchesOrderBudget(t *testing.T) { } func TestDoltGCNudgeBoundsGCCall(t *testing.T) { + skipSlowCmdGCTest(t, "runs dolt GC nudge shell timeout coverage; run make test-cmd-gc-process for full coverage") if _, err := exec.LookPath("timeout"); err != nil { if _, gtimeoutErr := exec.LookPath("gtimeout"); gtimeoutErr != nil { t.Skip("timeout/gtimeout not available") @@ -344,6 +345,7 @@ func TestDoltGCNudgeFailsClosedWithoutBoundedRunner(t *testing.T) { } func TestDoltGCNudgeFallbackLockHonorsFlockHolder(t *testing.T) { + skipSlowCmdGCTest(t, "runs dolt GC nudge shell lock contention coverage; run make test-cmd-gc-process for full coverage") cityPath := writeDoltGCNudgeCity(t) sleepPath, err := exec.LookPath("sleep") if err != nil { @@ -396,6 +398,7 @@ func TestDoltGCNudgeFallbackLockHonorsFlockHolder(t *testing.T) { } func TestDoltGCNudgeLockNormalizesLocalHostAliases(t *testing.T) { + skipSlowCmdGCTest(t, "runs dolt GC nudge shell lock contention coverage; run make test-cmd-gc-process for full coverage") cityPath := writeDoltGCNudgeCity(t) sleepPath, err := exec.LookPath("sleep") if err != nil { @@ -453,6 +456,7 @@ func TestDoltGCNudgeLockNormalizesLocalHostAliases(t *testing.T) { } func TestDoltGCNudgeLockIgnoresDifferentTmpDirs(t *testing.T) { + skipSlowCmdGCTest(t, "runs dolt GC nudge shell lock contention coverage; run make test-cmd-gc-process for full coverage") cityPath := writeDoltGCNudgeCity(t) sleepPath, err := exec.LookPath("sleep") if err != nil { diff --git a/cmd/gc/dolt_preflight_cleanup_test.go b/cmd/gc/dolt_preflight_cleanup_test.go index 1e3c9a1085..0d9f3b0be1 100644 --- a/cmd/gc/dolt_preflight_cleanup_test.go +++ b/cmd/gc/dolt_preflight_cleanup_test.go @@ -83,6 +83,7 @@ func TestFileOpenedByAnyProcessBoundsLsof(t *testing.T) { } func TestRemoveStaleManagedDoltLocksWithoutLsofUsesAvailableState(t *testing.T) { + skipSlowCmdGCTest(t, "runs managed-dolt preflight cleanup against filesystem locks; run make test-cmd-gc-process for full coverage") dataDir := t.TempDir() lockFile := filepath.Join(dataDir, "hq", ".dolt", "noms", "LOCK") if err := os.MkdirAll(filepath.Dir(lockFile), 0o755); err != nil { diff --git a/cmd/gc/dolt_project_id_test.go b/cmd/gc/dolt_project_id_test.go index 32d7ba50dc..0a389722a1 100644 --- a/cmd/gc/dolt_project_id_test.go +++ b/cmd/gc/dolt_project_id_test.go @@ -14,7 +14,7 @@ import ( ) func TestEnsureManagedDoltProjectIDGeneratesLocalIdentityWhenMetadataAndDatabaseMissing(t *testing.T) { - skipSlowCmdGCTest(t, "requires a managed dolt server; run without -short or via integration packages") + skipSlowCmdGCTest(t, "requires a managed dolt server; run make test-cmd-gc-process for full coverage") doltPath := os.Getenv("GC_DOLT_REAL_BINARY") var err error if doltPath == "" { @@ -258,7 +258,7 @@ func TestManagedDoltWaitReadyWithPasswordUsesDirectQueryProbe(t *testing.T) { } func TestRecoverManagedDoltProcessWithPasswordUsesDirectHelpersAgainstRealServer(t *testing.T) { - skipSlowCmdGCTest(t, "requires a managed dolt server; run without -short or via integration packages") + skipSlowCmdGCTest(t, "requires a managed dolt server; run make test-cmd-gc-process for full coverage") cityPath := t.TempDir() layout, err := resolveManagedDoltRuntimeLayout(cityPath) if err != nil { @@ -305,7 +305,7 @@ func TestRecoverManagedDoltProcessWithPasswordUsesDirectHelpersAgainstRealServer } func TestEnsureManagedDoltProjectIDGeneratesLocalIdentityWithPasswordedServer(t *testing.T) { - skipSlowCmdGCTest(t, "requires a managed dolt server; run without -short or via integration packages") + skipSlowCmdGCTest(t, "requires a managed dolt server; run make test-cmd-gc-process for full coverage") cityDir := t.TempDir() metadataPath := filepath.Join(cityDir, ".beads", "metadata.json") if err := os.MkdirAll(filepath.Dir(metadataPath), 0o755); err != nil { diff --git a/cmd/gc/fast_loop_helpers_test.go b/cmd/gc/fast_loop_helpers_test.go index 9ecd5ba247..0e92f2f36d 100644 --- a/cmd/gc/fast_loop_helpers_test.go +++ b/cmd/gc/fast_loop_helpers_test.go @@ -14,7 +14,10 @@ import ( func skipSlowCmdGCTest(t *testing.T, reason string) { t.Helper() - if os.Getenv("GC_FAST_UNIT") == "1" || testing.Short() { + if testing.Short() || strings.TrimSpace(os.Getenv("GC_FAST_UNIT")) != "0" { + if strings.TrimSpace(os.Getenv("GC_FAST_UNIT")) == "" && !strings.Contains(reason, "test-cmd-gc-process") { + reason += "; set GC_FAST_UNIT=0 or run make test-cmd-gc-process for full process coverage" + } t.Skip(reason) } } @@ -80,6 +83,7 @@ func reserveRandomTCPPort(t *testing.T) int { func startTCPListenerProcess(t *testing.T, port int) *exec.Cmd { t.Helper() + skipSlowCmdGCTest(t, "spawns a TCP listener process to emulate managed dolt; run make test-cmd-gc-process for full coverage") cmd := exec.Command("python3", "-c", ` import signal import socket diff --git a/cmd/gc/live_submit_probe_test.go b/cmd/gc/live_submit_probe_test.go index 1043ab4f7c..f44f8c0c45 100644 --- a/cmd/gc/live_submit_probe_test.go +++ b/cmd/gc/live_submit_probe_test.go @@ -21,7 +21,7 @@ import ( func preferRealBDOnPath(t *testing.T) { t.Helper() - skipSlowCmdGCTest(t, "requires a live bd-managed session probe; run without -short") + skipSlowCmdGCTest(t, "requires a live bd-managed session probe; run make test-cmd-gc-process for full coverage") currentPath := os.Getenv("PATH") pathEntries := filepath.SplitList(currentPath) diff --git a/cmd/gc/main.go b/cmd/gc/main.go index a092f19bb4..73a0f7cccf 100644 --- a/cmd/gc/main.go +++ b/cmd/gc/main.go @@ -275,6 +275,9 @@ var cliStoreCache struct { // agents don't open the store repeatedly. Silently falls back to legacy // naming if the store is unavailable. func cliSessionName(cityPath, cityName, agentName, sessionTemplate string) string { + if strings.TrimSpace(cityPath) == "" { + return sessionName(nil, cityName, agentName, sessionTemplate) + } cliStoreCache.mu.Lock() if cliStoreCache.path != cityPath { cliStoreCache.store, _ = openCityStoreAt(cityPath) diff --git a/cmd/gc/main_test.go b/cmd/gc/main_test.go index f081ed0d35..8e5387c642 100644 --- a/cmd/gc/main_test.go +++ b/cmd/gc/main_test.go @@ -160,6 +160,7 @@ func TestMain(m *testing.M) { } func TestTutorial01(t *testing.T) { + skipSlowCmdGCTest(t, "runs tutorial testscript scenarios; run make test-cmd-gc-process for full coverage") testscript.Run(t, newTestscriptParams(t)) } diff --git a/cmd/gc/pool_test.go b/cmd/gc/pool_test.go index ff4bff01c5..b5e8ffa3af 100644 --- a/cmd/gc/pool_test.go +++ b/cmd/gc/pool_test.go @@ -101,6 +101,7 @@ func TestEvaluatePoolNonInteger(t *testing.T) { } func TestEvaluatePoolDefaultScaleCheckCountsRoutedReadyWork(t *testing.T) { + skipSlowCmdGCTest(t, "uses real bd and jq for default scale_check coverage; run make test-cmd-gc-process for full coverage") bdPath, err := findPreferredBinary("bd", "/home/ubuntu/.local/bin/bd") if err != nil { t.Skip("bd not installed") @@ -144,6 +145,7 @@ func TestEvaluatePoolDefaultScaleCheckCountsRoutedReadyWork(t *testing.T) { } func TestEvaluatePoolDefaultScaleCheckCountsRoutedActiveUnassignedWork(t *testing.T) { + skipSlowCmdGCTest(t, "uses real bd and jq for default scale_check coverage; run make test-cmd-gc-process for full coverage") bdPath, err := findPreferredBinary("bd", "/home/ubuntu/.local/bin/bd") if err != nil { t.Skip("bd not installed") diff --git a/cmd/gc/session_lifecycle_parallel_test.go b/cmd/gc/session_lifecycle_parallel_test.go index 73b4cfef40..da9e4c25b6 100644 --- a/cmd/gc/session_lifecycle_parallel_test.go +++ b/cmd/gc/session_lifecycle_parallel_test.go @@ -541,6 +541,7 @@ func TestPrepareStartCandidate_UsesSessionIDForTaskWorkDir(t *testing.T) { } func TestExecutePlannedStarts_FreshWakeAfterDrainRetainsStartupContext(t *testing.T) { + skipSlowCmdGCTest(t, "waits through stale session-key detection; run make test-cmd-gc-process for full coverage") sp := runtime.NewFake() store := beads.NewMemStore() clk := &clock.Fake{Time: time.Date(2026, 4, 7, 12, 0, 0, 0, time.UTC)} @@ -2134,6 +2135,7 @@ func (p *dieAfterStartProvider) IsRunning(name string) bool { } func TestExecutePreparedStartWave_StaleSessionKeyDetected(t *testing.T) { + skipSlowCmdGCTest(t, "waits through stale session-key detection; run make test-cmd-gc-process for full coverage") sp := &dieAfterStartProvider{Fake: runtime.NewFake()} item := preparedStart{ candidate: startCandidate{ diff --git a/cmd/gc/test_gc_binary_test.go b/cmd/gc/test_gc_binary_test.go index b9fea9965e..c3cac08e29 100644 --- a/cmd/gc/test_gc_binary_test.go +++ b/cmd/gc/test_gc_binary_test.go @@ -24,9 +24,6 @@ func currentGCBinaryForTests(t *testing.T) string { return } binPath := filepath.Join(buildDir, "gc") - goModCache := filepath.Join(buildDir, "gomodcache") - goCache := filepath.Join(buildDir, "gocache") - goPath := filepath.Join(buildDir, "gopath") wd, err := os.Getwd() if err != nil { testGCBinaryErr = fmt.Errorf("getwd: %w", err) @@ -34,11 +31,6 @@ func currentGCBinaryForTests(t *testing.T) string { } cmd := exec.Command("go", "build", "-o", binPath, ".") cmd.Dir = wd - cmd.Env = append(os.Environ(), - "GOMODCACHE="+goModCache, - "GOCACHE="+goCache, - "GOPATH="+goPath, - ) out, err := cmd.CombinedOutput() if err != nil { testGCBinaryErr = fmt.Errorf("go build -o %s .: %w\n%s", binPath, err, string(out)) diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index d821c06a54..748a0b1ee6 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -29,6 +29,7 @@ func (s *failingSessionLookupStore) List(beads.ListQuery) ([]beads.Bead, error) } func TestWorkerHandleForSessionWithConfigUsesResolvedProviderOnFirstStart(t *testing.T) { + skipSlowCmdGCTest(t, "waits through stale session-key detection; run make test-cmd-gc-process for full coverage") cityDir := t.TempDir() writePhase0InterfaceCity(t, cityDir, `[workspace] name = "test-city" @@ -143,6 +144,7 @@ func TestResolvedWorkerRuntimeWithConfigUsesProviderLaunchCommand(t *testing.T) } func TestWorkerHandleForSessionWithConfigUsesResolvedProviderOnResume(t *testing.T) { + skipSlowCmdGCTest(t, "waits through stale session-key detection; run make test-cmd-gc-process for full coverage") cityDir := t.TempDir() writePhase0InterfaceCity(t, cityDir, `[workspace] name = "test-city" From 72139e9811adfc9e78f82aadc13d9337393febb8 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Sat, 25 Apr 2026 23:22:41 +0000 Subject: [PATCH 33/85] fix: preserve cmd/gc process coverage and dolt cleanup --- .github/workflows/ci.yml | 14 ++++++++++---- cmd/gc/beads_provider_lifecycle.go | 2 +- cmd/gc/beads_provider_lifecycle_test.go | 25 +++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c094e90c5..f212193348 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,7 @@ jobs: packs: ${{ steps.filter.outputs.packs }} worker: ${{ steps.filter.outputs.worker }} worker_phase2: ${{ steps.filter.outputs.worker_phase2 }} + cmd_gc_process: ${{ steps.filter.outputs.cmd_gc_process }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 @@ -70,6 +71,14 @@ jobs: - 'internal/runtime/**' - 'internal/config/**' - 'cmd/gc/**' + cmd_gc_process: + - 'go.mod' + - 'go.sum' + - '.github/workflows/**' + - 'Makefile' + - 'cmd/gc/**' + - 'internal/**' + - 'examples/gastown/packs/**' # Always runs: lint, fmt, vet, unit tests, docs, acceptance, coverage. check: @@ -151,10 +160,7 @@ jobs: cmd-gc-process: name: cmd/gc process suite needs: changes - if: >- - needs.changes.outputs.worker_phase2 == 'true' || - needs.changes.outputs.beads == 'true' || - needs.changes.outputs.packs == 'true' + if: needs.changes.outputs.cmd_gc_process == 'true' runs-on: ubuntu-latest timeout-minutes: 20 env: diff --git a/cmd/gc/beads_provider_lifecycle.go b/cmd/gc/beads_provider_lifecycle.go index 419f43982d..f7ce2f6284 100644 --- a/cmd/gc/beads_provider_lifecycle.go +++ b/cmd/gc/beads_provider_lifecycle.go @@ -429,7 +429,7 @@ func ensureBeadsProvider(cityPath string) error { // For exec providers, fires "stop". For file providers, always available. func shutdownBeadsProvider(cityPath string) error { if cityUsesBdStoreContract(cityPath) && strings.TrimSpace(os.Getenv("GC_DOLT")) == "skip" { - return nil + return clearManagedDoltRuntimeStateIfOwned(cityPath) } provider := beadsProvider(cityPath) if strings.HasPrefix(provider, "exec:") { diff --git a/cmd/gc/beads_provider_lifecycle_test.go b/cmd/gc/beads_provider_lifecycle_test.go index c7e3a388c1..64ee4754dc 100644 --- a/cmd/gc/beads_provider_lifecycle_test.go +++ b/cmd/gc/beads_provider_lifecycle_test.go @@ -758,6 +758,31 @@ func TestShutdownBeadsProvider_bd_skip(t *testing.T) { } } +func TestShutdownBeadsProviderBdSkipClearsPublishedRuntimeState(t *testing.T) { + dir := t.TempDir() + if err := os.MkdirAll(filepath.Join(dir, ".gc"), 0o755); err != nil { + t.Fatal(err) + } + MaterializeBuiltinPacks(dir) //nolint:errcheck + if err := writeDoltState(dir, doltRuntimeState{ + Running: true, + PID: os.Getpid(), + Port: 33123, + DataDir: filepath.Join(dir, ".beads", "dolt"), + StartedAt: time.Now().UTC().Format(time.RFC3339), + }); err != nil { + t.Fatalf("writeDoltState: %v", err) + } + t.Setenv("GC_BEADS", "bd") + t.Setenv("GC_DOLT", "skip") + if err := shutdownBeadsProvider(dir); err != nil { + t.Fatalf("shutdownBeadsProvider() error = %v", err) + } + if _, err := os.Stat(managedDoltStatePath(dir)); !os.IsNotExist(err) { + t.Fatalf("published dolt runtime state still present, stat err = %v", err) + } +} + func TestCurrentDoltPortPrefersRuntimeState(t *testing.T) { cityDir := t.TempDir() if err := os.MkdirAll(filepath.Join(cityDir, ".gc", "runtime", "packs", "dolt"), 0o755); err != nil { From 4630840b11a1f92e71693ac8ae6f2db65196fd8f Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Sun, 26 Apr 2026 18:25:54 +0000 Subject: [PATCH 34/85] test: cover settings rebuild in API resume regression --- docs/reference/cli.md | 168 ++++++++++++++++++++++ internal/api/handler_session_chat_test.go | 12 ++ 2 files changed, 180 insertions(+) diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 1f49fec05a..c77664fcdc 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -24,6 +24,7 @@ gc [flags] | [gc beads](#gc-beads) | Manage the beads provider | | [gc build-image](#gc-build-image) | Build a prebaked agent container image | | [gc cities](#gc-cities) | List registered cities | +| [gc completion](#gc-completion) | Generate the autocompletion script for the specified shell | | [gc config](#gc-config) | Inspect and validate city configuration | | [gc converge](#gc-converge) | Manage convergence loops (bounded iterative refinement) | | [gc convoy](#gc-convoy) | Manage convoys — graphs of related work | @@ -52,6 +53,7 @@ gc [flags] | [gc runtime](#gc-runtime) | Process-intrinsic runtime operations | | [gc service](#gc-service) | Inspect workspace services | | [gc session](#gc-session) | Manage interactive chat sessions | +| [gc shell](#gc-shell) | Manage the Gas City shell integration hook | | [gc skill](#gc-skill) | List visible skills | | [gc sling](#gc-sling) | Route work to a session config or agent | | [gc start](#gc-start) | Start the city under the machine-wide supervisor | @@ -304,6 +306,127 @@ List registered cities gc cities list ``` +## gc completion + +Generate the autocompletion script for gc for the specified shell. +See each sub-command's help for details on how to use the generated script. + +``` +gc completion +``` + +| Subcommand | Description | +|------------|-------------| +| [gc completion bash](#gc-completion-bash) | Generate the autocompletion script for bash | +| [gc completion fish](#gc-completion-fish) | Generate the autocompletion script for fish | +| [gc completion powershell](#gc-completion-powershell) | Generate the autocompletion script for powershell | +| [gc completion zsh](#gc-completion-zsh) | Generate the autocompletion script for zsh | + +## gc completion bash + +Generate the autocompletion script for the bash shell. + +This script depends on the 'bash-completion' package. +If it is not installed already, you can install it via your OS's package manager. + +To load completions in your current shell session: + + source <(gc completion bash) + +To load completions for every new session, execute once: + +#### Linux: + + gc completion bash > /etc/bash_completion.d/gc + +#### macOS: + + gc completion bash > $(brew --prefix)/etc/bash_completion.d/gc + +You will need to start a new shell for this setup to take effect. + +``` +gc completion bash +``` + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--no-descriptions` | bool | | disable completion descriptions | + +## gc completion fish + +Generate the autocompletion script for the fish shell. + +To load completions in your current shell session: + + gc completion fish | source + +To load completions for every new session, execute once: + + gc completion fish > ~/.config/fish/completions/gc.fish + +You will need to start a new shell for this setup to take effect. + +``` +gc completion fish [flags] +``` + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--no-descriptions` | bool | | disable completion descriptions | + +## gc completion powershell + +Generate the autocompletion script for powershell. + +To load completions in your current shell session: + + gc completion powershell | Out-String | Invoke-Expression + +To load completions for every new session, add the output of the above command +to your powershell profile. + +``` +gc completion powershell [flags] +``` + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--no-descriptions` | bool | | disable completion descriptions | + +## gc completion zsh + +Generate the autocompletion script for the zsh shell. + +If shell completion is not already enabled in your environment you will need +to enable it. You can execute the following once: + + echo "autoload -U compinit; compinit" >> ~/.zshrc + +To load completions in your current shell session: + + source <(gc completion zsh) + +To load completions for every new session, execute once: + +#### Linux: + + gc completion zsh > "${fpath[1]}/_gc" + +#### macOS: + + gc completion zsh > $(brew --prefix)/share/zsh/site-functions/_gc + +You will need to start a new shell for this setup to take effect. + +``` +gc completion zsh [flags] +``` + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--no-descriptions` | bool | | disable completion descriptions | + ## gc config Inspect, validate, and debug the resolved city configuration. @@ -2249,6 +2372,51 @@ gc session wake gc-42 gc session wake mayor ``` +## gc shell + +The shell integration adds a completion hook to your shell RC file that +provides tab-completion for gc commands and flags. + +Subcommands: install, remove, status. + +``` +gc shell +``` + +| Subcommand | Description | +|------------|-------------| +| [gc shell install](#gc-shell-install) | Install or update shell integration | +| [gc shell remove](#gc-shell-remove) | Remove shell integration | +| [gc shell status](#gc-shell-status) | Show shell integration status | + +## gc shell install + +Install or update the gc shell completion hook. + +If no shell is specified, the shell is detected from $SHELL. +The completion script is written to ~/.gc/completions/ and a source line +is added to your shell RC file. + +``` +gc shell install [bash|zsh|fish] +``` + +## gc shell remove + +Remove the gc shell completion hook from your shell RC file and delete the completion script. + +``` +gc shell remove +``` + +## gc shell status + +Show shell integration status + +``` +gc shell status +``` + ## gc skill List skills visible to the current city. diff --git a/internal/api/handler_session_chat_test.go b/internal/api/handler_session_chat_test.go index de87e00b70..107ae3aea2 100644 --- a/internal/api/handler_session_chat_test.go +++ b/internal/api/handler_session_chat_test.go @@ -1,6 +1,8 @@ package api import ( + "os" + "path/filepath" "strings" "testing" @@ -125,6 +127,13 @@ func TestBuildSessionResumeRebuildsBareStoredCommandForPoolClaudeAgent(t *testin fs := newSessionFakeState(t) claude := config.BuiltinProviders()["claude"] maxActive := 3 + gcDir := filepath.Join(fs.cityPath, ".gc") + if err := os.MkdirAll(gcDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(gcDir, "settings.json"), []byte(`{"hooks":{}}`), 0o644); err != nil { + t.Fatal(err) + } fs.cfg = &config.City{ Workspace: config.Workspace{Name: "test-city"}, Agents: []config.Agent{ @@ -157,4 +166,7 @@ func TestBuildSessionResumeRebuildsBareStoredCommandForPoolClaudeAgent(t *testin if !strings.Contains(cmd, "--resume abc-123") { t.Fatalf("resume command missing resume flag:\n got: %s", cmd) } + if !strings.Contains(cmd, "--settings") { + t.Fatalf("resume command missing settings arg:\n got: %s", cmd) + } } From e4891b931b0986e0989d5c037cbd002348568f9a Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Sun, 26 Apr 2026 18:40:24 +0000 Subject: [PATCH 35/85] test(profiles): lock cursor readiness hints Add a regression test for the builtin cursor provider so future cleanup cannot silently drop its readiness prefix or readiness delay and recreate the startup deadline_exceeded loop. --- internal/config/provider_test.go | 28 +++++++++++++++++++ internal/runtime/tmux/startup_test.go | 39 +++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/internal/config/provider_test.go b/internal/config/provider_test.go index 99e47ef8cb..00ccaa0d8d 100644 --- a/internal/config/provider_test.go +++ b/internal/config/provider_test.go @@ -139,6 +139,34 @@ func TestBuiltinProvidersGemini(t *testing.T) { } } +func TestBuiltinProvidersCursor(t *testing.T) { + p := BuiltinProviders()["cursor"] + if p.Command != "cursor-agent" { + t.Errorf("Command = %q, want %q", p.Command, "cursor-agent") + } + if len(p.Args) != 1 || p.Args[0] != "-f" { + t.Errorf("Args = %v, want [-f]", p.Args) + } + if p.PromptMode != "arg" { + t.Errorf("PromptMode = %q, want %q", p.PromptMode, "arg") + } + if p.ReadyPromptPrefix != "\u2192 " { + t.Errorf("ReadyPromptPrefix = %q, want %q", p.ReadyPromptPrefix, "\u2192 ") + } + if p.ReadyDelayMs != 10000 { + t.Errorf("ReadyDelayMs = %d, want 10000", p.ReadyDelayMs) + } + if len(p.ProcessNames) != 1 || p.ProcessNames[0] != "cursor-agent" { + t.Errorf("ProcessNames = %v, want [cursor-agent]", p.ProcessNames) + } + if !derefBool(p.SupportsHooks) { + t.Error("SupportsHooks = false, want true") + } + if p.InstructionsFile != "AGENTS.md" { + t.Errorf("InstructionsFile = %q, want %q", p.InstructionsFile, "AGENTS.md") + } +} + func TestBuiltinProvidersReturnsNewMap(t *testing.T) { a := BuiltinProviders() b := BuiltinProviders() diff --git a/internal/runtime/tmux/startup_test.go b/internal/runtime/tmux/startup_test.go index 8add1e286d..da2996c252 100644 --- a/internal/runtime/tmux/startup_test.go +++ b/internal/runtime/tmux/startup_test.go @@ -663,6 +663,45 @@ func TestDoStartSession_ProcessNamesAndReadyPrefix(t *testing.T) { }) } +func TestDoStartSession_CursorReadinessHintsTriggerRuntimeWait(t *testing.T) { + ops := &fakeStartOps{ + hasSessionResult: true, + } + + cfg := runtime.Config{ + Command: "cursor-agent", + ProcessNames: []string{"cursor-agent"}, + ReadyPromptPrefix: "\u2192 ", + ReadyDelayMs: 10000, + } + + err := doStartSession(context.Background(), ops, "test", cfg, DefaultConfig().SetupTimeout) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + assertCallSequence(t, ops, []string{ + "createSession", + "setRemainOnExit", + "waitForCommand", + "acceptStartupDialogs", + "waitForReady", + "acceptStartupDialogs", + "hasSession", + }) + + wfr := ops.calls[4] + if wfr.rc.Tmux.ReadyPromptPrefix != "\u2192 " { + t.Errorf("rc.ReadyPromptPrefix = %q, want %q", wfr.rc.Tmux.ReadyPromptPrefix, "\u2192 ") + } + if wfr.rc.Tmux.ReadyDelayMs != 10000 { + t.Errorf("rc.ReadyDelayMs = %d, want %d", wfr.rc.Tmux.ReadyDelayMs, 10000) + } + if len(wfr.rc.Tmux.ProcessNames) != 1 || wfr.rc.Tmux.ProcessNames[0] != "cursor-agent" { + t.Errorf("rc.ProcessNames = %v, want [cursor-agent]", wfr.rc.Tmux.ProcessNames) + } +} + func TestDoStartSession_ProcessNamesAndReadyDelayRechecksDialogs(t *testing.T) { ops := &fakeStartOps{ hasSessionResult: true, From 4b578dc877f9c6e83c80924d42cd8b8f68362fea Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Sun, 26 Apr 2026 18:55:57 +0000 Subject: [PATCH 36/85] fix(dolt-health): parse quoted rig ports --- examples/dolt/commands/health/run.sh | 2 +- examples/dolt/health_test.go | 26 ++++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/examples/dolt/commands/health/run.sh b/examples/dolt/commands/health/run.sh index 6bb4e8c2f0..4490dffb44 100755 --- a/examples/dolt/commands/health/run.sh +++ b/examples/dolt/commands/health/run.sh @@ -253,7 +253,7 @@ if [ "${GC_HEALTH_SKIP_ZOMBIE_SCAN:-0}" != "1" ]; then [ -f "$meta" ] || continue config_file="$(dirname "$meta")/config.yaml" [ -f "$config_file" ] || continue - rig_port=$(grep '^dolt\.port:' "$config_file" 2>/dev/null | sed 's/^dolt\.port:[[:space:]]*//' | head -1) + rig_port=$(grep '^dolt\.port:' "$config_file" 2>/dev/null | sed "s/^dolt\\.port:[[:space:]]*//; s/[[:space:]]*#.*$//; s/['\\\"]//g; s/[[:space:]]*$//" | head -1) case "$rig_port" in ''|*[!0-9]*) continue ;; esac [ "$rig_port" = "$GC_DOLT_PORT" ] && continue rig_pid=$(managed_runtime_listener_pid "$rig_port" || true) diff --git a/examples/dolt/health_test.go b/examples/dolt/health_test.go index 74b3a40986..5c7addf3fc 100644 --- a/examples/dolt/health_test.go +++ b/examples/dolt/health_test.go @@ -693,7 +693,7 @@ func writeExecutable(t *testing.T, path, contents string) { // Regression guard for the bug where deacon patrol killed rig-local // Dolt servers because the zombie scan treated every non-city-server // dolt sql-server PID as a zombie. -func TestHealthScriptZombieScanExcludesRigLocalServers(t *testing.T) { +func runHealthScriptZombieScanExcludesRigLocalServers(t *testing.T, rigConfig string) { cityPath := t.TempDir() fakeBin := t.TempDir() @@ -723,7 +723,7 @@ func TestHealthScriptZombieScanExcludesRigLocalServers(t *testing.T) { t.Fatal(err) } if err := os.WriteFile(filepath.Join(rigBeads, "config.yaml"), - []byte("dolt.port: "+rigPort+"\n"), 0o644); err != nil { + []byte(rigConfig), 0o644); err != nil { t.Fatal(err) } @@ -801,6 +801,28 @@ exit 1 } } +func TestHealthScriptZombieScanExcludesRigLocalServers(t *testing.T) { + tests := []struct { + name string + rigConfig string + }{ + { + name: "bare port", + rigConfig: "dolt.port: 19902\n", + }, + { + name: "quoted port", + rigConfig: "dolt.port: \"19902\"\n", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runHealthScriptZombieScanExcludesRigLocalServers(t, tc.rigConfig) + }) + } +} + // TestHealthScriptJSONAlwaysExitsZero guards the JSON-mode exit // contract. Automation consumers (notably the deacon patrol formula) // parse the JSON payload and key health decisions off `server.reachable`. From 811c368b6682c3229bc9ea087faa0acaa74b6e49 Mon Sep 17 00:00:00 2001 From: Jo Stevens Date: Sun, 26 Apr 2026 12:01:16 -0700 Subject: [PATCH 37/85] fix: use --use-db in gc-nudge dolt invocation + darwin stat.Dev cast (#1222) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Fix `gc-nudge` DOLT_GC() failure: replace `--database` (invalid in dolt 1.86.3) with `--use-db` flag - Fix darwin build: cast `stat.Dev` from `int32` to `uint64` in `fsys.readRegularFileSnapshot` ## Changes - `examples/dolt/commands/gc-nudge/run.sh` — use `--use-db` instead of `--database` when calling `dolt sql` - `internal/fsys/read_regular_unix.go` — cast `stat.Dev` to `uint64` for darwin compatibility - `cmd/gc/dolt_gc_nudge_script_test.go` — update test assertions to match new flag ## Test plan - [x] All gc-nudge script tests updated and pass - [x] `stat.Dev` cast verified on darwin Closes ga-66a 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- cmd/gc/dolt_gc_nudge_script_test.go | 28 +++++++++++++------------- examples/dolt/commands/gc-nudge/run.sh | 3 ++- internal/fsys/read_regular_unix.go | 2 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/cmd/gc/dolt_gc_nudge_script_test.go b/cmd/gc/dolt_gc_nudge_script_test.go index 527c22eea6..c4c32bf63f 100644 --- a/cmd/gc/dolt_gc_nudge_script_test.go +++ b/cmd/gc/dolt_gc_nudge_script_test.go @@ -597,10 +597,10 @@ func TestDoltGCNudgeSkipsExternalRigDatabaseWithoutLocalData(t *testing.T) { if len(lines) != 1 { t.Fatalf("dolt argv lines = %d, want 1 for local managed db only:\n%s", len(lines), argv) } - if !strings.Contains(lines[0], "--database testdb") { + if !strings.Contains(lines[0], "--use-db testdb") { t.Fatalf("dolt argv = %q, want local managed testdb", lines[0]) } - if strings.Contains(argv, "--database extdb") { + if strings.Contains(argv, "--use-db extdb") { t.Fatalf("dolt argv should not target external rig db:\n%s", argv) } } @@ -634,7 +634,7 @@ func TestDoltGCNudgeDefaultsMissingDatabaseMetadataToBeads(t *testing.T) { } argv := strings.TrimSpace(readFileString(t, argvCapture)) - if !strings.Contains(argv, "--database beads") { + if !strings.Contains(argv, "--use-db beads") { t.Fatalf("dolt argv = %q, want default beads database", argv) } } @@ -673,10 +673,10 @@ func TestDoltGCNudgeSkipsInvalidDatabaseMetadata(t *testing.T) { if len(lines) != 1 { t.Fatalf("dolt argv lines = %d, want 1 valid database:\n%s", len(lines), argv) } - if !strings.Contains(lines[0], "--database testdb") { + if !strings.Contains(lines[0], "--use-db testdb") { t.Fatalf("dolt argv = %q, want local managed testdb", lines[0]) } - if strings.Contains(argv, "--database --help") { + if strings.Contains(argv, "--use-db --help") { t.Fatalf("dolt argv should not target invalid database:\n%s", argv) } } @@ -714,10 +714,10 @@ func TestDoltGCNudgeSkipsSystemDatabaseMetadata(t *testing.T) { } argv := strings.TrimSpace(readFileString(t, argvCapture)) - if strings.Contains(argv, "--database mysql") { + if strings.Contains(argv, "--use-db mysql") { t.Fatalf("dolt argv should not target system database:\n%s", argv) } - if !strings.Contains(argv, "--database testdb") { + if !strings.Contains(argv, "--use-db testdb") { t.Fatalf("dolt argv = %q, want valid testdb", argv) } } @@ -751,7 +751,7 @@ func TestDoltGCNudgeAllowsHyphenatedDatabaseMetadata(t *testing.T) { } argv := strings.TrimSpace(readFileString(t, argvCapture)) - if !strings.Contains(argv, "--database frontend-db") { + if !strings.Contains(argv, "--use-db frontend-db") { t.Fatalf("dolt argv = %q, want hyphenated database", argv) } } @@ -790,7 +790,7 @@ func TestDoltGCNudgeHonorsDataDirOverride(t *testing.T) { } argv := strings.TrimSpace(readFileString(t, argvCapture)) - if !strings.Contains(argv, "--database testdb") { + if !strings.Contains(argv, "--use-db testdb") { t.Fatalf("dolt argv = %q, want override-backed testdb", argv) } } @@ -821,7 +821,7 @@ func TestDoltGCNudgeDiscoversOrphanDatabaseDirs(t *testing.T) { } argv := strings.TrimSpace(readFileString(t, argvCapture)) - if !strings.Contains(argv, "--database orphan-db") { + if !strings.Contains(argv, "--use-db orphan-db") { t.Fatalf("dolt argv = %q, want orphan database", argv) } } @@ -873,7 +873,7 @@ func TestDoltGCNudgeAggregateThresholdTriggersSubthresholdDatabases(t *testing.T } argv := strings.TrimSpace(readFileString(t, argvCapture)) - if !strings.Contains(argv, "--database testdb") || !strings.Contains(argv, "--database rigdb") { + if !strings.Contains(argv, "--use-db testdb") || !strings.Contains(argv, "--use-db rigdb") { t.Fatalf("dolt argv = %q, want both subthreshold databases under aggregate trigger", argv) } } @@ -915,10 +915,10 @@ func TestDoltGCNudgeFallbackFindsLocalRigOutsideRigsDir(t *testing.T) { if len(lines) != 2 { t.Fatalf("dolt argv lines = %d, want 2 databases from fallback scan:\n%s", len(lines), argv) } - if !strings.Contains(argv, "--database testdb") { + if !strings.Contains(argv, "--use-db testdb") { t.Fatalf("dolt argv = %q, want city database", argv) } - if !strings.Contains(argv, "--database frontenddb") { + if !strings.Contains(argv, "--use-db frontenddb") { t.Fatalf("dolt argv = %q, want rig database outside rigs/ dir", argv) } } @@ -944,7 +944,7 @@ func TestDoltGCNudgeWarnsWhenRigListFailsBeforeFallback(t *testing.T) { if !strings.Contains(string(out), "gc rig list failed rc=7") { t.Fatalf("gc-nudge output = %q, want rig-list failure warning", out) } - if !strings.Contains(readFileString(t, argvCapture), "--database testdb") { + if !strings.Contains(readFileString(t, argvCapture), "--use-db testdb") { t.Fatalf("gc-nudge did not fall back to local metadata scan; output:\n%s", out) } } diff --git a/examples/dolt/commands/gc-nudge/run.sh b/examples/dolt/commands/gc-nudge/run.sh index 0eb597f759..302c988d3e 100755 --- a/examples/dolt/commands/gc-nudge/run.sh +++ b/examples/dolt/commands/gc-nudge/run.sh @@ -296,7 +296,8 @@ run_dolt_gc_for_db() { run_bounded "$gc_call_timeout" \ dolt --host "$host" --port "$GC_DOLT_PORT" \ --user "$GC_DOLT_USER" --no-tls \ - sql --database "$db" -q "CALL DOLT_GC()" || cmd_rc=$? + --use-db "$db" \ + sql -q "CALL DOLT_GC()" || cmd_rc=$? elapsed=$(( $(date +%s) - start )) after=$(dir_bytes "$db_dir") diff --git a/internal/fsys/read_regular_unix.go b/internal/fsys/read_regular_unix.go index 5002e49bc7..4ecb675509 100644 --- a/internal/fsys/read_regular_unix.go +++ b/internal/fsys/read_regular_unix.go @@ -47,7 +47,7 @@ func (OSFS) readRegularFileSnapshot(name string) (regularFileSnapshot, error) { } return regularFileSnapshot{ data: data, - id: fileIdentity{dev: stat.Dev, ino: stat.Ino}, + id: fileIdentity{dev: uint64(stat.Dev), ino: stat.Ino}, //nolint:unconvert // int32 on darwin, uint64 on linux hasID: true, }, nil } From 65404c4a7bcd5f5701ac5b556ce9ab4c9043dfac Mon Sep 17 00:00:00 2001 From: Casey Boyle Date: Sun, 26 Apr 2026 14:51:56 -0500 Subject: [PATCH 38/85] fix(fsys): cast stat.Dev to uint64 for darwin compatibility (#1208) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes a build break on darwin introduced in c3e6f174. `unix.Stat_t.Dev` is `int32` on darwin / BSDs and `uint64` on Linux; the new `readRegularFileSnapshot` (`internal/fsys/read_regular_unix.go`) assigned it directly to `fileIdentity.dev` (`uint64`), so the build fails on darwin with: ``` cannot use stat.Dev (variable of type int32) as uint64 value in struct literal ``` This keeps the direct cast in the unix snapshot path, aligns the reflective identity path with the same signed-device bit preservation, adds in-package coverage for both synthetic signed-device handling and the real snapshot-to-Lstat identity flow, and wires a Darwin compile guard into `make test` / `make test-cover`. Closes #1207 ## Testing - [x] `go test ./internal/fsys` - [x] `make test-fsys-darwin-compile` - [x] `make check` (maintainer follow-up; local pre-commit hook) - [ ] `make check-docs` — N/A, no docs touched - [ ] `make test-integration` — N/A, no runtime/controller/workflow change ## Checklist - [x] Linked an issue (#1207) - [x] Added targeted `internal/fsys` regression coverage - [x] Added a default-path Darwin compile guard - [x] No user-facing surface — internal helper - [x] No breaking change Co-authored-by: Julian Knutsen --- Makefile | 13 ++++- internal/fsys/atomic.go | 14 +++-- internal/fsys/atomic_internal_test.go | 55 +++++++++++++++++++ .../fsys/read_regular_unix_internal_test.go | 38 +++++++++++++ 4 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 internal/fsys/read_regular_unix_internal_test.go diff --git a/Makefile b/Makefile index 62bb5f257e..60cdcf50c5 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ LDFLAGS := -X main.version=$(VERSION) \ -X main.commit=$(COMMIT) \ -X main.date=$(BUILD_TIME) -.PHONY: build check check-all check-bd check-docker check-docs check-dolt check-version-tag lint fmt-check fmt vet test test-cmd-gc-process test-worker-core test-worker-core-phase2 test-worker-core-phase2-real-transport test-worker-inference-phase3 test-acceptance test-acceptance-b test-acceptance-c test-acceptance-all test-tutorial-goldens test-tutorial-regression test-tutorial test-integration test-integration-shards test-integration-shards-cover test-integration-packages test-integration-packages-cover test-integration-review-formulas test-integration-review-formulas-cover test-integration-review-formulas-basic test-integration-review-formulas-basic-cover test-integration-review-formulas-retries test-integration-review-formulas-retries-cover test-integration-review-formulas-recovery test-integration-review-formulas-recovery-cover test-integration-bdstore test-integration-bdstore-cover test-integration-rest test-integration-rest-cover test-integration-rest-smoke test-integration-rest-smoke-cover test-integration-rest-full test-integration-rest-full-cover test-mcp-mail test-docker test-k8s test-cover cover install install-tools install-buildx setup clean generate check-schema docker-base docker-agent docker-controller docs-dev dashboard-smoke +.PHONY: build check check-all check-bd check-docker check-docs check-dolt check-version-tag lint fmt-check fmt vet test test-fsys-darwin-compile test-cmd-gc-process test-worker-core test-worker-core-phase2 test-worker-core-phase2-real-transport test-worker-inference-phase3 test-acceptance test-acceptance-b test-acceptance-c test-acceptance-all test-tutorial-goldens test-tutorial-regression test-tutorial test-integration test-integration-shards test-integration-shards-cover test-integration-packages test-integration-packages-cover test-integration-review-formulas test-integration-review-formulas-cover test-integration-review-formulas-basic test-integration-review-formulas-basic-cover test-integration-review-formulas-retries test-integration-review-formulas-retries-cover test-integration-review-formulas-recovery test-integration-review-formulas-recovery-cover test-integration-bdstore test-integration-bdstore-cover test-integration-rest test-integration-rest-cover test-integration-rest-smoke test-integration-rest-smoke-cover test-integration-rest-full test-integration-rest-full-cover test-mcp-mail test-docker test-k8s test-cover cover install install-tools install-buildx setup clean generate check-schema docker-base docker-agent docker-controller docs-dev dashboard-smoke ## build: compile gc binary with version metadata build: @@ -165,9 +165,16 @@ TEST_ENV = env -i \ ## The skipped cmd/gc process-backed scenarios remain covered by ## `make test-cmd-gc-process` locally and the CI `cmd/gc process suite` job. ## Wrapped in $(TEST_ENV) — see comment above for why. -test: +test: test-fsys-darwin-compile $(TEST_ENV) GC_FAST_UNIT=1 go test ./... +## test-fsys-darwin-compile: cross-compile internal/fsys for macOS so +## unix.Stat_t field-type regressions fail in the default fast test path. +test-fsys-darwin-compile: + @tmp=$$(mktemp -d); \ + trap 'rm -rf "$$tmp"' EXIT; \ + $(TEST_ENV) GOOS=darwin GOARCH=arm64 go test -c -o "$$tmp/fsys.test" ./internal/fsys + ## test-cmd-gc-process: run the full non-short cmd/gc suite, including the ## process-backed lifecycle coverage routed out of the default fast loop test-cmd-gc-process: @@ -349,7 +356,7 @@ UNIT_COVER_PKGS := $(shell go list -f '{{if or .TestGoFiles .XTestGoFiles}}{{.Im ## test-cover: run fast unit-test coverage without the integration-tagged package sweep ## The skipped cmd/gc process-backed scenarios remain covered by ## `make test-cmd-gc-process` locally and the CI `cmd/gc process suite` job. -test-cover: +test-cover: test-fsys-darwin-compile $(TEST_ENV) GC_FAST_UNIT=1 go test -timeout 8m -coverprofile=coverage.txt $(UNIT_COVER_PKGS) ## cover: run tests and show coverage report diff --git a/internal/fsys/atomic.go b/internal/fsys/atomic.go index 46af5e6141..5534a5606c 100644 --- a/internal/fsys/atomic.go +++ b/internal/fsys/atomic.go @@ -107,7 +107,13 @@ func comparableMode(mode os.FileMode) os.FileMode { } func fileIdentityFromInfo(info os.FileInfo) (fileIdentity, bool) { - stat := reflect.Indirect(reflect.ValueOf(info.Sys())) + return fileIdentityFromSys(info.Sys()) +} + +func fileIdentityFromSys(sys any) (fileIdentity, bool) { + // Signed stat fields follow Go's direct int-to-uint conversion so the + // Fstat and Lstat paths agree on device identity across Unix variants. + stat := reflect.Indirect(reflect.ValueOf(sys)) if !stat.IsValid() { return fileIdentity{}, false } @@ -130,11 +136,7 @@ func fileIdentityFromInfo(info os.FileInfo) (fileIdentity, bool) { func numericFieldToUint64(v reflect.Value) (uint64, bool) { switch v.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - value := v.Int() - if value < 0 { - return 0, false - } - return uint64(value), true + return uint64(v.Int()), true case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v.Uint(), true default: diff --git a/internal/fsys/atomic_internal_test.go b/internal/fsys/atomic_internal_test.go index 602bb6007d..08cc8e9149 100644 --- a/internal/fsys/atomic_internal_test.go +++ b/internal/fsys/atomic_internal_test.go @@ -54,6 +54,61 @@ func TestWriteFileIfChangedAtomic_RewritesWithoutSnapshotIdentity(t *testing.T) } } +func TestFileIdentityFromSys_NormalizesSignedDeviceField(t *testing.T) { + id, ok := fileIdentityFromSys(struct { + Dev int32 + Ino uint64 + }{ + Dev: 7, + Ino: 11, + }) + if !ok { + t.Fatalf("fileIdentityFromSys returned ok=false for signed Dev field") + } + + want := fileIdentity{dev: 7, ino: 11} + if id != want { + t.Fatalf("fileIdentityFromSys = %#v, want %#v", id, want) + } +} + +func TestFileIdentityFromSys_NormalizesSignedDeviceFieldPointer(t *testing.T) { + id, ok := fileIdentityFromSys(&struct { + Dev int32 + Ino uint64 + }{ + Dev: 7, + Ino: 11, + }) + if !ok { + t.Fatalf("fileIdentityFromSys returned ok=false for pointer-shaped signed Dev field") + } + + want := fileIdentity{dev: 7, ino: 11} + if id != want { + t.Fatalf("fileIdentityFromSys = %#v, want %#v", id, want) + } +} + +func TestFileIdentityFromSys_PreservesNegativeSignedDeviceFieldBits(t *testing.T) { + id, ok := fileIdentityFromSys(struct { + Dev int32 + Ino uint64 + }{ + Dev: -1, + Ino: 11, + }) + if !ok { + t.Fatalf("fileIdentityFromSys returned ok=false for negative signed Dev field") + } + + dev := int32(-1) + want := fileIdentity{dev: uint64(dev), ino: 11} + if id != want { + t.Fatalf("fileIdentityFromSys = %#v, want %#v", id, want) + } +} + type identityChangingFS struct { data []byte snapshotErr error diff --git a/internal/fsys/read_regular_unix_internal_test.go b/internal/fsys/read_regular_unix_internal_test.go new file mode 100644 index 0000000000..e1c181a554 --- /dev/null +++ b/internal/fsys/read_regular_unix_internal_test.go @@ -0,0 +1,38 @@ +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris + +package fsys + +import ( + "os" + "path/filepath" + "testing" +) + +func TestReadRegularFileSnapshot_MatchesFileIdentityFromInfo(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "config.toml") + data := []byte("hello = true\n") + if err := os.WriteFile(path, data, 0o644); err != nil { + t.Fatalf("WriteFile: %v", err) + } + + snapshot, err := (OSFS{}).readRegularFileSnapshot(path) + if err != nil { + t.Fatalf("readRegularFileSnapshot: %v", err) + } + if !snapshot.hasID { + t.Fatalf("snapshot missing identity") + } + + info, err := os.Lstat(path) + if err != nil { + t.Fatalf("Lstat: %v", err) + } + id, ok := fileIdentityFromInfo(info) + if !ok { + t.Fatalf("fileIdentityFromInfo returned ok=false") + } + if snapshot.id != id { + t.Fatalf("snapshot.id = %#v, want %#v", snapshot.id, id) + } +} From 0ef7ce4e154a1a3254d6da246e55f081b38b0811 Mon Sep 17 00:00:00 2001 From: Helge Tesdal Date: Mon, 20 Apr 2026 23:18:29 +0200 Subject: [PATCH 39/85] fix: include protocolVersion in ACP initialize handshake --- internal/runtime/acp/protocol.go | 6 ++++-- internal/runtime/acp/protocol_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/internal/runtime/acp/protocol.go b/internal/runtime/acp/protocol.go index fff3d5a78c..ddf10535dd 100644 --- a/internal/runtime/acp/protocol.go +++ b/internal/runtime/acp/protocol.go @@ -66,7 +66,8 @@ type ServerInfo struct { // InitializeParams is the params for the "initialize" request. type InitializeParams struct { - ClientInfo ClientInfo `json:"clientInfo"` + ProtocolVersion int `json:"protocolVersion"` + ClientInfo ClientInfo `json:"clientInfo"` } // InitializeResult is the result of the "initialize" request. @@ -123,7 +124,8 @@ func newNotification(method string) JSONRPCMessage { // newInitializeRequest creates an "initialize" request. func newInitializeRequest() (JSONRPCMessage, int64) { return newRequest("initialize", InitializeParams{ - ClientInfo: ClientInfo{Name: "gc", Version: "1.0"}, + ProtocolVersion: 1, + ClientInfo: ClientInfo{Name: "gc", Version: "1.0"}, }) } diff --git a/internal/runtime/acp/protocol_test.go b/internal/runtime/acp/protocol_test.go index ebf09c7ebb..75b2994cd0 100644 --- a/internal/runtime/acp/protocol_test.go +++ b/internal/runtime/acp/protocol_test.go @@ -243,3 +243,29 @@ func TestNewRequest_IncrementingIDs(t *testing.T) { t.Errorf("IDs should be incrementing: %d, %d", id1, id2) } } + +func TestInitializeRequest_IncludesProtocolVersion(t *testing.T) { + msg, _ := newInitializeRequest() + data, err := json.Marshal(msg) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + // Verify raw JSON contains protocolVersion (not omitted via omitempty). + if !strings.Contains(string(data), `"protocolVersion":1`) { + t.Errorf("raw JSON should contain \"protocolVersion\":1, got %s", data) + } + + var decoded JSONRPCMessage + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + + var params InitializeParams + if err := json.Unmarshal(decoded.Params, ¶ms); err != nil { + t.Fatalf("Unmarshal params: %v", err) + } + if params.ProtocolVersion != 1 { + t.Errorf("protocolVersion = %d, want 1", params.ProtocolVersion) + } +} From 9188803fff17e48415298a13c249a940098d39b6 Mon Sep 17 00:00:00 2001 From: Helge Tesdal Date: Mon, 20 Apr 2026 23:18:38 +0200 Subject: [PATCH 40/85] feat: add ACPCommand/ACPArgs for transport-specific provider commands Providers like OpenCode need different commands for ACP vs tmux transport (e.g. 'opencode acp' for ACP, bare 'opencode' for tmux). The existing Args field applies to both transports with no override. Add ACPCommand and ACPArgs fields to ProviderSpec and ResolvedProvider. When transport is 'acp', BuildProviderLaunchCommand uses ACPCommandString() which falls back field-by-field to Command/Args, allowing partial overrides. --- cmd/gc/cmd_session.go | 2 +- cmd/gc/template_resolve.go | 7 +- cmd/gc/worker_handle.go | 8 +- docs/reference/config.md | 2 + docs/schema/city-schema.json | 11 +++ docs/schema/city-schema.txt | 11 +++ internal/api/handler_session_create.go | 2 +- .../api/huma_handlers_sessions_command.go | 2 +- internal/api/session_resolution.go | 2 +- internal/api/session_resolved_config.go | 8 +- internal/api/session_runtime.go | 2 +- internal/config/field_sync_test.go | 2 + internal/config/launch_command.go | 9 ++- internal/config/launch_command_test.go | 36 ++++++++- internal/config/pack.go | 1 + internal/config/provider.go | 28 +++++++ internal/config/provider_test.go | 73 +++++++++++++++++++ internal/config/resolve.go | 17 +++++ internal/config/resolve_test.go | 2 + internal/worker/builtin/profiles.go | 5 ++ 20 files changed, 217 insertions(+), 13 deletions(-) diff --git a/cmd/gc/cmd_session.go b/cmd/gc/cmd_session.go index bf6c7ea6e7..6eba6f9c17 100644 --- a/cmd/gc/cmd_session.go +++ b/cmd/gc/cmd_session.go @@ -405,7 +405,7 @@ func resolvedSessionCommand(cityPath string, resolved *config.ResolvedProvider, if resolved == nil { return "", fmt.Errorf("resolved provider is nil") } - launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, optionOverrides) + launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, optionOverrides, "") if err != nil { return "", fmt.Errorf("resolving provider launch command: %w", err) } diff --git a/cmd/gc/template_resolve.go b/cmd/gc/template_resolve.go index b2eee21319..6d1c25645e 100644 --- a/cmd/gc/template_resolve.go +++ b/cmd/gc/template_resolve.go @@ -147,7 +147,12 @@ func resolveTemplate(p *agentBuildParams, cfgAgent *config.Agent, qualifiedName // Step 5: Build copy_files and command with settings args + schema defaults. var copyFiles []runtime.CopyEntry - command := resolved.CommandString() + var command string + if cfgAgent.Session == "acp" { + command = resolved.ACPCommandString() + } else { + command = resolved.CommandString() + } // Append schema-derived default args (e.g., --dangerously-skip-permissions // from EffectiveDefaults["permission_mode"] = "unrestricted"). if defaultArgs := resolved.ResolveDefaultArgs(); len(defaultArgs) > 0 { diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index 2366790437..32e44ec27c 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -125,7 +125,11 @@ func resolvedWorkerSessionConfigWithConfig( } command = strings.TrimSpace(command) if command == "" { - command = strings.TrimSpace(resolved.CommandString()) + if transport == "acp" { + command = strings.TrimSpace(resolved.ACPCommandString()) + } else { + command = strings.TrimSpace(resolved.CommandString()) + } } providerName := strings.TrimSpace(resolved.Name) if providerName == "" { @@ -324,7 +328,7 @@ func resolvedWorkerRuntimeWithConfig(cityPath string, cfg *config.City, info ses command := strings.TrimSpace(info.Command) if !shouldPreserveStoredRuntimeCommand(command, resolved.CommandString()) { - launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, nil) + launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, nil, "") command = resolved.CommandString() if err == nil { command = launchCommand.Command diff --git a/docs/reference/config.md b/docs/reference/config.md index 4181018c32..cc952139d0 100644 --- a/docs/reference/config.md +++ b/docs/reference/config.md @@ -476,6 +476,8 @@ ProviderSpec defines a named provider's startup parameters. | `options_schema` | []ProviderOption | | | OptionsSchema declares the configurable options this provider supports. Each option maps to CLI args via its Choices[].FlagArgs field. Serialized via a dedicated DTO (not directly to JSON) so FlagArgs stays server-side. | | `print_args` | []string | | | PrintArgs are CLI arguments that enable one-shot non-interactive mode. The provider prints its response to stdout and exits. When empty, the provider does not support one-shot invocation. Examples: ["-p"] (claude, gemini), ["exec"] (codex) | | `title_model` | string | | | TitleModel is the OptionsSchema model key used for title generation. Resolved via the "model" option in OptionsSchema to get FlagArgs. Defaults to the cheapest/fastest model for each provider. Examples: "haiku" (claude), "o4-mini" (codex), "gemini-2.5-flash" (gemini) | +| `acp_command` | string | | | ACPCommand overrides Command when the session transport is ACP. When empty, Command is used for both tmux and ACP transports. | +| `acp_args` | []string | | | ACPArgs overrides Args when the session transport is ACP. When nil, Args is used for both tmux and ACP transports. | ## Rig diff --git a/docs/schema/city-schema.json b/docs/schema/city-schema.json index e6aaa40de6..8a6e252173 100644 --- a/docs/schema/city-schema.json +++ b/docs/schema/city-schema.json @@ -1693,6 +1693,17 @@ "title_model": { "type": "string", "description": "TitleModel is the OptionsSchema model key used for title generation.\nResolved via the \"model\" option in OptionsSchema to get FlagArgs.\nDefaults to the cheapest/fastest model for each provider.\nExamples: \"haiku\" (claude), \"o4-mini\" (codex), \"gemini-2.5-flash\" (gemini)" + }, + "acp_command": { + "type": "string", + "description": "ACPCommand overrides Command when the session transport is ACP.\nWhen empty, Command is used for both tmux and ACP transports." + }, + "acp_args": { + "items": { + "type": "string" + }, + "type": "array", + "description": "ACPArgs overrides Args when the session transport is ACP.\nWhen nil, Args is used for both tmux and ACP transports." } }, "additionalProperties": false, diff --git a/docs/schema/city-schema.txt b/docs/schema/city-schema.txt index e6aaa40de6..8a6e252173 100644 --- a/docs/schema/city-schema.txt +++ b/docs/schema/city-schema.txt @@ -1693,6 +1693,17 @@ "title_model": { "type": "string", "description": "TitleModel is the OptionsSchema model key used for title generation.\nResolved via the \"model\" option in OptionsSchema to get FlagArgs.\nDefaults to the cheapest/fastest model for each provider.\nExamples: \"haiku\" (claude), \"o4-mini\" (codex), \"gemini-2.5-flash\" (gemini)" + }, + "acp_command": { + "type": "string", + "description": "ACPCommand overrides Command when the session transport is ACP.\nWhen empty, Command is used for both tmux and ACP transports." + }, + "acp_args": { + "items": { + "type": "string" + }, + "type": "array", + "description": "ACPArgs overrides Args when the session transport is ACP.\nWhen nil, Args is used for both tmux and ACP transports." } }, "additionalProperties": false, diff --git a/internal/api/handler_session_create.go b/internal/api/handler_session_create.go index 62826eefab..084852d3cd 100644 --- a/internal/api/handler_session_create.go +++ b/internal/api/handler_session_create.go @@ -274,7 +274,7 @@ func (s *Server) createProviderSession(w http.ResponseWriter, r *http.Request, s return } - launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, body.Options) + launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, body.Options, "") if err != nil { s.idem.unreserve(idemKey) if errors.Is(err, config.ErrUnknownOption) { diff --git a/internal/api/huma_handlers_sessions_command.go b/internal/api/huma_handlers_sessions_command.go index 6a1b16e054..efb590b7f8 100644 --- a/internal/api/huma_handlers_sessions_command.go +++ b/internal/api/huma_handlers_sessions_command.go @@ -234,7 +234,7 @@ func (s *Server) humaCreateProviderSession(ctx context.Context, store beads.Stor return nil, humaSessionManagerError(err) } - launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, body.Options) + launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, body.Options, "") if err != nil { return nil, huma.Error400BadRequest(err.Error()) } diff --git a/internal/api/session_resolution.go b/internal/api/session_resolution.go index da195f85b7..7a79941c50 100644 --- a/internal/api/session_resolution.go +++ b/internal/api/session_resolution.go @@ -285,7 +285,7 @@ func (s *Server) materializeNamedSessionWithContext(ctx context.Context, store b if err != nil { return "", err } - launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, nil) + launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, nil, transport) if err != nil { return "", err } diff --git a/internal/api/session_resolved_config.go b/internal/api/session_resolved_config.go index bd7621aee2..dfb712a2c2 100644 --- a/internal/api/session_resolved_config.go +++ b/internal/api/session_resolved_config.go @@ -17,6 +17,12 @@ func resolvedSessionConfigForProvider( if resolved == nil { return worker.ResolvedSessionConfig{}, fmt.Errorf("%w: resolved provider is required", worker.ErrHandleConfig) } + // Use the ACP-specific command when the session uses ACP transport, + // falling back to the default command for tmux sessions. + resolvedCommand := resolved.CommandString() + if transport == "acp" { + resolvedCommand = resolved.ACPCommandString() + } return worker.NormalizeResolvedSessionConfig(worker.ResolvedSessionConfig{ Alias: alias, ExplicitName: explicitName, @@ -25,7 +31,7 @@ func resolvedSessionConfigForProvider( Transport: transport, Metadata: metadata, Runtime: worker.ResolvedRuntime{ - Command: firstNonEmptyString(command, resolved.CommandString(), resolved.Name), + Command: firstNonEmptyString(command, resolvedCommand, resolved.Name), WorkDir: workDir, Provider: resolved.Name, SessionEnv: resolved.Env, diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index 7fd64520fd..f379879909 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -134,7 +134,7 @@ func (s *Server) resolvedSessionRuntimeCommand(resolved *config.ResolvedProvider if command := strings.TrimSpace(storedCommand); shouldPreserveStoredRuntimeCommand(command, resolved.CommandString()) { return command, nil } - launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, nil) + launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, nil, "") if err != nil { return "", fmt.Errorf("building provider launch command: %w", err) } diff --git a/internal/config/field_sync_test.go b/internal/config/field_sync_test.go index 4fe8ad63d4..55a636a706 100644 --- a/internal/config/field_sync_test.go +++ b/internal/config/field_sync_test.go @@ -457,6 +457,8 @@ func TestProviderFieldSync(t *testing.T) { "SessionIDFlag": "internal session-id config, not patched", "PrintArgs": "internal print-mode args, not patched", "TitleModel": "internal title-model key, not patched", + "ACPCommand": "ACP transport override, not patched (set via builtin or city.toml)", + "ACPArgs": "ACP transport override, not patched (set via builtin or city.toml)", } // Fields on ProviderPatch that don't map 1:1 to ProviderSpec. diff --git a/internal/config/launch_command.go b/internal/config/launch_command.go index 3cbab9bbc3..78d43161eb 100644 --- a/internal/config/launch_command.go +++ b/internal/config/launch_command.go @@ -22,12 +22,19 @@ type ProviderLaunchCommand struct { // for session startup. It starts from the raw provider command, applies // schema-managed defaults plus any explicit option overrides, and appends a // provider-owned settings file when present. -func BuildProviderLaunchCommand(cityPath string, resolved *ResolvedProvider, optionOverrides map[string]string) (ProviderLaunchCommand, error) { +// +// When transport is "acp", the ACP-specific command (ACPCommand/ACPArgs) is +// used as the base instead of the default Command/Args. Pass "" for the +// default (tmux) transport. +func BuildProviderLaunchCommand(cityPath string, resolved *ResolvedProvider, optionOverrides map[string]string, transport string) (ProviderLaunchCommand, error) { if resolved == nil { return ProviderLaunchCommand{}, fmt.Errorf("resolved provider is nil") } command := resolved.CommandString() + if transport == "acp" { + command = resolved.ACPCommandString() + } if len(resolved.OptionsSchema) > 0 { mergedOptions := make(map[string]string, len(resolved.EffectiveDefaults)+len(optionOverrides)) for key, value := range resolved.EffectiveDefaults { diff --git a/internal/config/launch_command_test.go b/internal/config/launch_command_test.go index 39b4c0256d..e6f9fc1fb3 100644 --- a/internal/config/launch_command_test.go +++ b/internal/config/launch_command_test.go @@ -20,7 +20,7 @@ func TestBuildProviderLaunchCommandAddsDefaultsAndSettings(t *testing.T) { spec := BuiltinProviders()["claude"] rp := specToResolved("claude", &spec) - got, err := BuildProviderLaunchCommand(dir, rp, nil) + got, err := BuildProviderLaunchCommand(dir, rp, nil, "") if err != nil { t.Fatalf("BuildProviderLaunchCommand: %v", err) } @@ -44,7 +44,7 @@ func TestBuildProviderLaunchCommandAppliesOptionOverrides(t *testing.T) { got, err := BuildProviderLaunchCommand("", rp, map[string]string{ "permission_mode": "plan", "effort": "low", - }) + }, "") if err != nil { t.Fatalf("BuildProviderLaunchCommand: %v", err) } @@ -65,7 +65,7 @@ func TestBuildProviderLaunchCommandIgnoresInitialMessageOverride(t *testing.T) { got, err := BuildProviderLaunchCommand("", rp, map[string]string{ "initial_message": "hello", "effort": "low", - }) + }, "") if err != nil { t.Fatalf("BuildProviderLaunchCommand: %v", err) } @@ -75,3 +75,33 @@ func TestBuildProviderLaunchCommandIgnoresInitialMessageOverride(t *testing.T) { t.Fatalf("Command = %q, want %q", got.Command, want) } } + +func TestBuildProviderLaunchCommandUsesACPCommand(t *testing.T) { + rp := &ResolvedProvider{ + Command: "opencode", + ACPCommand: "opencode", + ACPArgs: []string{"acp"}, + } + + t.Run("acp transport uses ACPCommandString", func(t *testing.T) { + got, err := BuildProviderLaunchCommand("", rp, nil, "acp") + if err != nil { + t.Fatalf("BuildProviderLaunchCommand: %v", err) + } + want := "opencode acp" + if got.Command != want { + t.Fatalf("Command = %q, want %q", got.Command, want) + } + }) + + t.Run("default transport uses CommandString", func(t *testing.T) { + got, err := BuildProviderLaunchCommand("", rp, nil, "") + if err != nil { + t.Fatalf("BuildProviderLaunchCommand: %v", err) + } + want := "opencode" + if got.Command != want { + t.Fatalf("Command = %q, want %q", got.Command, want) + } + }) +} diff --git a/internal/config/pack.go b/internal/config/pack.go index acaa516748..d38d9bf010 100644 --- a/internal/config/pack.go +++ b/internal/config/pack.go @@ -1559,6 +1559,7 @@ func deepCopyProviderSpec(in ProviderSpec) ProviderSpec { out.OptionDefaults = deepCopyStringMap(in.OptionDefaults) out.OptionsSchema = deepCopyProviderOptions(in.OptionsSchema) out.PrintArgs = append([]string(nil), in.PrintArgs...) + out.ACPArgs = append([]string(nil), in.ACPArgs...) out.Base = copyStringPtr(in.Base) out.EmitsPermissionWarning = copyBoolPtr(in.EmitsPermissionWarning) out.SupportsACP = copyBoolPtr(in.SupportsACP) diff --git a/internal/config/provider.go b/internal/config/provider.go index 7e9e6e327c..32682a7e2a 100644 --- a/internal/config/provider.go +++ b/internal/config/provider.go @@ -127,6 +127,12 @@ type ProviderSpec struct { // Defaults to the cheapest/fastest model for each provider. // Examples: "haiku" (claude), "o4-mini" (codex), "gemini-2.5-flash" (gemini) TitleModel string `toml:"title_model,omitempty"` + // ACPCommand overrides Command when the session transport is ACP. + // When empty, Command is used for both tmux and ACP transports. + ACPCommand string `toml:"acp_command,omitempty"` + // ACPArgs overrides Args when the session transport is ACP. + // When nil, Args is used for both tmux and ACP transports. + ACPArgs []string `toml:"acp_args,omitempty"` } // Reserved prefixes for the Base field. @@ -187,6 +193,8 @@ type ResolvedProvider struct { OptionsSchema []ProviderOption PrintArgs []string TitleModel string + ACPCommand string + ACPArgs []string // EffectiveDefaults is the fully-merged option default map. // Computed from: schema Default -> provider OptionDefaults -> agent OptionDefaults. // Used by ResolveDefaultArgs() to produce CLI flags and by the API to @@ -202,6 +210,24 @@ func (rp *ResolvedProvider) CommandString() string { return rp.Command + " " + shellquote.Join(rp.Args) } +// ACPCommandString returns the command line for ACP transport sessions. +// Each field falls back independently: ACPCommand defaults to Command, +// and ACPArgs defaults to Args, so partial overrides are supported. +func (rp *ResolvedProvider) ACPCommandString() string { + cmd := rp.ACPCommand + args := rp.ACPArgs + if cmd == "" { + cmd = rp.Command + } + if args == nil { + args = rp.Args + } + if len(args) == 0 { + return cmd + } + return cmd + " " + shellquote.Join(args) +} + // TitleModelFlagArgs resolves the TitleModel key against the "model" // OptionsSchema entry. Returns the CLI flag args for the title model, // or nil if TitleModel is empty or not found in the schema. @@ -307,6 +333,8 @@ func providerSpecFromWorker(spec workerbuiltin.BuiltinProviderSpec) ProviderSpec OptionsSchema: providerOptionsFromWorker(spec.OptionsSchema), PrintArgs: cloneStrings(spec.PrintArgs), TitleModel: spec.TitleModel, + ACPCommand: spec.ACPCommand, + ACPArgs: cloneStrings(spec.ACPArgs), } } diff --git a/internal/config/provider_test.go b/internal/config/provider_test.go index 00ccaa0d8d..888db6926c 100644 --- a/internal/config/provider_test.go +++ b/internal/config/provider_test.go @@ -267,3 +267,76 @@ func TestCommandStringQuotesShellMetacharacters(t *testing.T) { t.Errorf("CommandString() = %q, want %q", got, want) } } + +func TestACPCommandString(t *testing.T) { + tests := []struct { + name string + rp ResolvedProvider + want string + }{ + { + name: "FullOverride", + rp: ResolvedProvider{ + Command: "opencode", + Args: []string{"--verbose"}, + ACPCommand: "opencode-acp", + ACPArgs: []string{"--json-rpc"}, + }, + want: "opencode-acp --json-rpc", + }, + { + name: "FallbackToCommand", + rp: ResolvedProvider{ + Command: "opencode", + Args: []string{"--verbose"}, + }, + want: "opencode --verbose", + }, + { + name: "PartialOverride_CommandOnly", + rp: ResolvedProvider{ + Command: "opencode", + Args: []string{"--verbose"}, + ACPCommand: "opencode-acp", + }, + want: "opencode-acp --verbose", + }, + { + name: "PartialOverride_ArgsOnly", + rp: ResolvedProvider{ + Command: "opencode", + Args: []string{"--verbose"}, + ACPArgs: []string{"--json-rpc"}, + }, + want: "opencode --json-rpc", + }, + { + name: "EmptyACPArgs", + rp: ResolvedProvider{ + Command: "opencode", + Args: []string{"--verbose"}, + ACPCommand: "opencode-acp", + ACPArgs: []string{}, + }, + want: "opencode-acp", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.rp.ACPCommandString() + if got != tt.want { + t.Errorf("ACPCommandString() = %q, want %q", got, tt.want) + } + }) + } + + // Verify FallbackToCommand produces same result as CommandString(). + t.Run("FallbackMatchesCommandString", func(t *testing.T) { + rp := &ResolvedProvider{Command: "opencode", Args: []string{"--verbose"}} + if rp.ACPCommandString() != rp.CommandString() { + t.Errorf("ACPCommandString() = %q, but CommandString() = %q — should match when no ACP overrides", + rp.ACPCommandString(), rp.CommandString()) + } + }) +} diff --git a/internal/config/resolve.go b/internal/config/resolve.go index db34e8a7df..e47b7c2872 100644 --- a/internal/config/resolve.go +++ b/internal/config/resolve.go @@ -250,6 +250,9 @@ func MergeProviderOverBuiltin(base, city ProviderSpec) ProviderSpec { if city.TitleModel != "" { result.TitleModel = city.TitleModel } + if city.ACPCommand != "" { + result.ACPCommand = city.ACPCommand + } // Slice fields: replace entirely when non-nil. if city.Args != nil { @@ -274,6 +277,9 @@ func MergeProviderOverBuiltin(base, city ProviderSpec) ProviderSpec { if city.PrintArgs != nil { result.PrintArgs = city.PrintArgs } + if city.ACPArgs != nil { + result.ACPArgs = city.ACPArgs + } // Map fields: merge additively (city keys win). if city.PermissionModes != nil { @@ -486,6 +492,7 @@ func specToResolved(name string, spec *ProviderSpec) *ResolvedProvider { ResumeCommand: spec.ResumeCommand, SessionIDFlag: spec.SessionIDFlag, TitleModel: spec.TitleModel, + ACPCommand: spec.ACPCommand, } // Deep-copy OptionsSchema to avoid aliasing the spec's slice. if len(spec.OptionsSchema) > 0 { @@ -553,6 +560,10 @@ func specToResolved(name string, spec *ProviderSpec) *ResolvedProvider { rp.PrintArgs = make([]string, len(spec.PrintArgs)) copy(rp.PrintArgs, spec.PrintArgs) } + if spec.ACPArgs != nil { + rp.ACPArgs = make([]string, len(spec.ACPArgs)) + copy(rp.ACPArgs, spec.ACPArgs) + } return rp } @@ -700,6 +711,12 @@ func resolvedChainToSpec(r ResolvedProvider, leaf ProviderSpec) ProviderSpec { if r.TitleModel != "" { out.TitleModel = r.TitleModel } + if r.ACPCommand != "" { + out.ACPCommand = r.ACPCommand + } + if r.ACPArgs != nil { + out.ACPArgs = append([]string(nil), r.ACPArgs...) + } if r.PrintArgs != nil { out.PrintArgs = append([]string(nil), r.PrintArgs...) } diff --git a/internal/config/resolve_test.go b/internal/config/resolve_test.go index bd53da55b3..8b5424a6ab 100644 --- a/internal/config/resolve_test.go +++ b/internal/config/resolve_test.go @@ -1239,6 +1239,8 @@ func TestMergeProviderOverBuiltinFieldSync(t *testing.T) { OptionsSchema: []ProviderOption{{Key: "model"}}, PrintArgs: []string{"-p"}, TitleModel: "haiku", + ACPCommand: "custom-acp", + ACPArgs: []string{"acp-mode"}, } // Verify every field on city is non-zero (catches new fields not added to test data). diff --git a/internal/worker/builtin/profiles.go b/internal/worker/builtin/profiles.go index 16bcdba4f8..1472e79376 100644 --- a/internal/worker/builtin/profiles.go +++ b/internal/worker/builtin/profiles.go @@ -55,6 +55,8 @@ type BuiltinProviderSpec struct { OptionsSchema []BuiltinProviderOption PrintArgs []string TitleModel string + ACPCommand string + ACPArgs []string } // ProfileIdentity captures the explicit production identity for a canonical @@ -302,6 +304,8 @@ var builtinProviderSpecs = map[string]BuiltinProviderSpec{ SupportsACP: true, SupportsHooks: true, InstructionsFile: "AGENTS.md", + ACPCommand: "opencode", + ACPArgs: []string{"acp"}, }, "auggie": { DisplayName: "Auggie CLI", @@ -389,6 +393,7 @@ func cloneBuiltinProviderSpec(spec BuiltinProviderSpec) BuiltinProviderSpec { spec.OptionDefaults = cloneStringMap(spec.OptionDefaults) spec.PrintArgs = cloneStrings(spec.PrintArgs) spec.OptionsSchema = cloneBuiltinOptions(spec.OptionsSchema) + spec.ACPArgs = cloneStrings(spec.ACPArgs) return spec } From 7c63487386ec3cea635ae4f182928ea6cfb7cfbc Mon Sep 17 00:00:00 2001 From: Helge Tesdal Date: Mon, 20 Apr 2026 23:30:00 +0200 Subject: [PATCH 41/85] fix: track ACPCommand/ACPArgs in provider provenance --- internal/config/chain.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/config/chain.go b/internal/config/chain.go index a7a74238f0..69985d519b 100644 --- a/internal/config/chain.go +++ b/internal/config/chain.go @@ -385,6 +385,8 @@ func recordScalarProvenance(spec ProviderSpec, layer string, into map[string]str set("resume_style", spec.ResumeStyle) set("resume_command", spec.ResumeCommand) set("session_id_flag", spec.SessionIDFlag) + set("acp_command", spec.ACPCommand) + setSlice("acp_args", spec.ACPArgs) set("title_model", spec.TitleModel) set("options_schema_merge", spec.OptionsSchemaMerge) setSlice("print_args", spec.PrintArgs) From b8572631e65837fc5e37b89d0b46ad3791445b55 Mon Sep 17 00:00:00 2001 From: Helge Tesdal Date: Mon, 20 Apr 2026 23:39:43 +0200 Subject: [PATCH 42/85] fix: preserve nil-vs-empty ACPArgs in deepCopy and resolvedChainToSpec --- internal/config/pack.go | 5 ++++- internal/config/resolve.go | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/config/pack.go b/internal/config/pack.go index d38d9bf010..745de5580c 100644 --- a/internal/config/pack.go +++ b/internal/config/pack.go @@ -1559,7 +1559,10 @@ func deepCopyProviderSpec(in ProviderSpec) ProviderSpec { out.OptionDefaults = deepCopyStringMap(in.OptionDefaults) out.OptionsSchema = deepCopyProviderOptions(in.OptionsSchema) out.PrintArgs = append([]string(nil), in.PrintArgs...) - out.ACPArgs = append([]string(nil), in.ACPArgs...) + if in.ACPArgs != nil { + out.ACPArgs = make([]string, len(in.ACPArgs)) + copy(out.ACPArgs, in.ACPArgs) + } out.Base = copyStringPtr(in.Base) out.EmitsPermissionWarning = copyBoolPtr(in.EmitsPermissionWarning) out.SupportsACP = copyBoolPtr(in.SupportsACP) diff --git a/internal/config/resolve.go b/internal/config/resolve.go index e47b7c2872..560615d4d8 100644 --- a/internal/config/resolve.go +++ b/internal/config/resolve.go @@ -715,7 +715,8 @@ func resolvedChainToSpec(r ResolvedProvider, leaf ProviderSpec) ProviderSpec { out.ACPCommand = r.ACPCommand } if r.ACPArgs != nil { - out.ACPArgs = append([]string(nil), r.ACPArgs...) + out.ACPArgs = make([]string, len(r.ACPArgs)) + copy(out.ACPArgs, r.ACPArgs) } if r.PrintArgs != nil { out.PrintArgs = append([]string(nil), r.PrintArgs...) From 1adf9dfeb60e25b3b3be976bf1f5b50efa16ca6b Mon Sep 17 00:00:00 2001 From: Helge Tesdal Date: Mon, 20 Apr 2026 23:53:14 +0200 Subject: [PATCH 43/85] fix: preserve stored ACP command on session resume shouldPreserveStoredRuntimeCommand only compared against CommandString(), so ACP sessions with ACPCommand != Command would have their stored command overwritten with a tmux command on resume. Now also checks against ACPCommandString(). --- cmd/gc/worker_handle.go | 3 ++- internal/api/session_runtime.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index 32e44ec27c..d198b5ee7a 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -327,7 +327,8 @@ func resolvedWorkerRuntimeWithConfig(cityPath string, cfg *config.City, info ses } command := strings.TrimSpace(info.Command) - if !shouldPreserveStoredRuntimeCommand(command, resolved.CommandString()) { + if !shouldPreserveStoredRuntimeCommand(command, resolved.CommandString()) && + !shouldPreserveStoredRuntimeCommand(command, resolved.ACPCommandString()) { launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, nil, "") command = resolved.CommandString() if err == nil { diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index f379879909..7eda30377a 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -131,7 +131,8 @@ func (s *Server) buildSessionResume(info session.Info) (string, runtime.Config) } func (s *Server) resolvedSessionRuntimeCommand(resolved *config.ResolvedProvider, storedCommand string) (string, error) { - if command := strings.TrimSpace(storedCommand); shouldPreserveStoredRuntimeCommand(command, resolved.CommandString()) { + if command := strings.TrimSpace(storedCommand); shouldPreserveStoredRuntimeCommand(command, resolved.CommandString()) || + shouldPreserveStoredRuntimeCommand(command, resolved.ACPCommandString()) { return command, nil } launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, nil, "") From fa6454a9a4a2183d0e684cc6ae4313d836139986 Mon Sep 17 00:00:00 2001 From: Helge Tesdal Date: Tue, 21 Apr 2026 01:42:06 +0200 Subject: [PATCH 44/85] fix: wrap supervisor session provider with auto-provider for ACP agents The supervisor's reconcileCities created session providers via newSessionProviderByName directly, bypassing the auto-provider wrapping logic in newSessionProviderFromContext. This meant all ACP sessions were routed to the tmux provider, which couldn't handle them and timed out. Apply the same wrapping logic: when the city-level provider is not ACP but some agents use session="acp", create an auto provider that routes ACP sessions to the ACP backend and everything else to the default. --- cmd/gc/cmd_supervisor.go | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/cmd/gc/cmd_supervisor.go b/cmd/gc/cmd_supervisor.go index 7ff88e975d..abbaa90584 100644 --- a/cmd/gc/cmd_supervisor.go +++ b/cmd/gc/cmd_supervisor.go @@ -28,6 +28,7 @@ import ( "github.com/gastownhall/gascity/internal/fsys" "github.com/gastownhall/gascity/internal/hooks" "github.com/gastownhall/gascity/internal/runtime" + sessionauto "github.com/gastownhall/gascity/internal/runtime/auto" "github.com/gastownhall/gascity/internal/supervisor" "github.com/gastownhall/gascity/internal/telemetry" "github.com/gastownhall/gascity/internal/workspacesvc" @@ -1210,10 +1211,31 @@ func reconcileCities( var sp runtime.Provider spErr := runPostPrepareStep("creating_session_provider", func() error { - var err error - sp, err = newSessionProviderByName( - effectiveProviderName(cfg.Session.Provider), cfg.Session, cityName, path) - return err + providerName := effectiveProviderName(cfg.Session.Provider) + baseSP, err := newSessionProviderByName(providerName, cfg.Session, cityName, path) + if err != nil { + return err + } + // When the city-level provider is not ACP but some agents + // use session = "acp", wrap in an auto provider that routes + // ACP sessions to the ACP backend and everything else to + // the default backend. This mirrors the logic in + // newSessionProviderFromContext for CLI commands. + if providerName != "acp" && hasACPAgents(cfg.Agents) { + acpSP, acpErr := newSessionProviderByName("acp", cfg.Session, cityName, path) + if acpErr != nil { + return fmt.Errorf("acp provider: %w", acpErr) + } + autoSP := sessionauto.New(baseSP, acpSP) + snapshot := loadProviderSessionSnapshot(sessionProviderContextForCity(cfg, path, providerName)) + for _, sessName := range configuredACPSessionNames(snapshot, cityName, cfg.Workspace.SessionTemplate, cfg.Agents) { + autoSP.RouteACP(sessName) + } + sp = autoSP + } else { + sp = baseSP + } + return nil }) if spErr != nil { cr.BatchUpdate(func( From 9ee8a1b028dcc78b4b726105663d96a280007117 Mon Sep 17 00:00:00 2001 From: Helge Tesdal Date: Tue, 21 Apr 2026 01:42:14 +0200 Subject: [PATCH 45/85] fix: send required params in session/new and use correct field in session/prompt session/new sent nil params, but OpenCode requires {cwd, mcpServers}. Add SessionNewParams struct and thread workDir through handshake(). session/prompt used "messages" wrapping content in {role, content} objects, but OpenCode expects "prompt" as a flat array of content blocks. Change SessionPromptParams to use Prompt []ContentBlock with json:"prompt" tag and update the fake ACP server in tests. Add tests verifying session/new includes cwd and mcpServers, and session/prompt uses the "prompt" field name (not "messages"). --- internal/runtime/acp/acp.go | 6 +- internal/runtime/acp/acp_test.go | 7 +-- internal/runtime/acp/protocol.go | 30 +++++----- internal/runtime/acp/protocol_test.go | 83 +++++++++++++++++++++------ 4 files changed, 87 insertions(+), 39 deletions(-) diff --git a/internal/runtime/acp/acp.go b/internal/runtime/acp/acp.go index 80db580268..c7492044fa 100644 --- a/internal/runtime/acp/acp.go +++ b/internal/runtime/acp/acp.go @@ -263,7 +263,7 @@ func (p *Provider) Start(ctx context.Context, name string, cfg runtime.Config) e hsTimeoutCtx, hsTimeoutCancel := context.WithTimeout(hsCtx, p.cfg.handshakeTimeout()) defer hsTimeoutCancel() - if err := p.handshake(hsTimeoutCtx, sc); err != nil { + if err := p.handshake(hsTimeoutCtx, sc, cfg.WorkDir); err != nil { // Handshake failed — kill the process. The monitor goroutine // handles listener/socket cleanup when the process exits. _ = stdinPipe.Close() @@ -301,7 +301,7 @@ func (p *Provider) Start(ctx context.Context, name string, cfg runtime.Config) e } // handshake performs the ACP initialize → initialized → session/new sequence. -func (p *Provider) handshake(ctx context.Context, sc *sessionConn) error { +func (p *Provider) handshake(ctx context.Context, sc *sessionConn, workDir string) error { // Step 1: Send "initialize" request. initReq, _ := newInitializeRequest() ch, err := sc.sendRequest(initReq) @@ -328,7 +328,7 @@ func (p *Provider) handshake(ctx context.Context, sc *sessionConn) error { } // Step 3: Send "session/new" request. - newReq, _ := newSessionNewRequest() + newReq, _ := newSessionNewRequest(workDir) ch, err = sc.sendRequest(newReq) if err != nil { return fmt.Errorf("sending session/new: %w", err) diff --git a/internal/runtime/acp/acp_test.go b/internal/runtime/acp/acp_test.go index d9b5c177b0..6189bd1f7b 100644 --- a/internal/runtime/acp/acp_test.go +++ b/internal/runtime/acp/acp_test.go @@ -87,11 +87,10 @@ for line in sys.stdin: respond(msg_id, {"sessionId": session_id}) elif method == "session/prompt": params = msg.get("params", {}) - messages = params.get("messages", []) + blocks = params.get("prompt", []) text = "" - for m in messages: - for c in m.get("content", []): - text += c.get("text", "") + for b in blocks: + text += b.get("text", "") # Send update notification with echoed text notify("session/update", { "sessionId": session_id, diff --git a/internal/runtime/acp/protocol.go b/internal/runtime/acp/protocol.go index ddf10535dd..ae77e3b670 100644 --- a/internal/runtime/acp/protocol.go +++ b/internal/runtime/acp/protocol.go @@ -75,6 +75,12 @@ type InitializeResult struct { ServerInfo ServerInfo `json:"serverInfo"` } +// SessionNewParams is the params for the "session/new" request. +type SessionNewParams struct { + Cwd string `json:"cwd"` + McpServers []any `json:"mcpServers"` +} + // SessionNewResult is the result of the "session/new" request. type SessionNewResult struct { SessionID string `json:"sessionId"` @@ -82,14 +88,8 @@ type SessionNewResult struct { // SessionPromptParams is the params for the "session/prompt" request. type SessionPromptParams struct { - SessionID string `json:"sessionId"` - Messages []PromptMessage `json:"messages"` -} - -// PromptMessage is a message within a session/prompt request. -type PromptMessage struct { - Role string `json:"role"` - Content []ContentBlock `json:"content"` + SessionID string `json:"sessionId"` + Prompt []ContentBlock `json:"prompt"` } // SessionUpdateParams is the params for "session/update" notifications. @@ -135,8 +135,11 @@ func newInitializedNotification() JSONRPCMessage { } // newSessionNewRequest creates a "session/new" request. -func newSessionNewRequest() (JSONRPCMessage, int64) { - return newRequest("session/new", nil) +func newSessionNewRequest(workDir string) (JSONRPCMessage, int64) { + return newRequest("session/new", SessionNewParams{ + Cwd: workDir, + McpServers: []any{}, + }) } // newSessionPromptRequest creates a "session/prompt" request from @@ -159,12 +162,7 @@ func newSessionPromptRequest(sessionID string, content []runtime.ContentBlock) ( } return newRequest("session/prompt", SessionPromptParams{ SessionID: sessionID, - Messages: []PromptMessage{ - { - Role: "user", - Content: blocks, - }, - }, + Prompt: blocks, }) } diff --git a/internal/runtime/acp/protocol_test.go b/internal/runtime/acp/protocol_test.go index 75b2994cd0..1534aeb9e3 100644 --- a/internal/runtime/acp/protocol_test.go +++ b/internal/runtime/acp/protocol_test.go @@ -146,14 +146,14 @@ func TestSessionPromptRequest_Structure(t *testing.T) { if params.SessionID != "sess-1" { t.Errorf("sessionId = %q, want %q", params.SessionID, "sess-1") } - if len(params.Messages) != 1 { - t.Fatalf("messages len = %d, want 1", len(params.Messages)) + if len(params.Prompt) != 1 { + t.Fatalf("prompt len = %d, want 1", len(params.Prompt)) } - if params.Messages[0].Role != "user" { - t.Errorf("role = %q, want %q", params.Messages[0].Role, "user") + if params.Prompt[0].Type != "text" { + t.Errorf("type = %q, want %q", params.Prompt[0].Type, "text") } - if len(params.Messages[0].Content) != 1 || params.Messages[0].Content[0].Text != "hello world" { - t.Errorf("content text = %q, want %q", params.Messages[0].Content[0].Text, "hello world") + if params.Prompt[0].Text != "hello world" { + t.Errorf("text = %q, want %q", params.Prompt[0].Text, "hello world") } } @@ -170,14 +170,14 @@ func TestSessionPromptRequest_MultiBlock(t *testing.T) { var params SessionPromptParams _ = json.Unmarshal(decoded.Params, ¶ms) - if len(params.Messages[0].Content) != 2 { - t.Fatalf("content blocks = %d, want 2", len(params.Messages[0].Content)) + if len(params.Prompt) != 2 { + t.Fatalf("prompt blocks = %d, want 2", len(params.Prompt)) } - if params.Messages[0].Content[0].Text != "first" { - t.Errorf("block[0] = %q, want %q", params.Messages[0].Content[0].Text, "first") + if params.Prompt[0].Text != "first" { + t.Errorf("block[0] = %q, want %q", params.Prompt[0].Text, "first") } - if params.Messages[0].Content[1].Text != "second" { - t.Errorf("block[1] = %q, want %q", params.Messages[0].Content[1].Text, "second") + if params.Prompt[1].Text != "second" { + t.Errorf("block[1] = %q, want %q", params.Prompt[1].Text, "second") } } @@ -199,10 +199,10 @@ func TestSessionPromptRequest_FilePath(t *testing.T) { var params SessionPromptParams _ = json.Unmarshal(decoded.Params, ¶ms) - if len(params.Messages[0].Content) != 1 { - t.Fatalf("content blocks = %d, want 1", len(params.Messages[0].Content)) + if len(params.Prompt) != 1 { + t.Fatalf("prompt blocks = %d, want 1", len(params.Prompt)) } - block := params.Messages[0].Content[0] + block := params.Prompt[0] if block.Type != "text" { t.Errorf("type = %q, want %q", block.Type, "text") } @@ -226,7 +226,7 @@ func TestSessionPromptRequest_FilePathError(t *testing.T) { var params SessionPromptParams _ = json.Unmarshal(decoded.Params, ¶ms) - block := params.Messages[0].Content[0] + block := params.Prompt[0] if !strings.Contains(block.Text, "Error reading") { t.Errorf("block should contain error, got %q", block.Text) } @@ -269,3 +269,54 @@ func TestInitializeRequest_IncludesProtocolVersion(t *testing.T) { t.Errorf("protocolVersion = %d, want 1", params.ProtocolVersion) } } + +func TestSessionNewRequest_IncludesCwdAndMcpServers(t *testing.T) { + msg, _ := newSessionNewRequest("/home/user/project") + data, err := json.Marshal(msg) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + var decoded JSONRPCMessage + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + + if decoded.Method != "session/new" { + t.Errorf("method = %q, want %q", decoded.Method, "session/new") + } + + var params SessionNewParams + if err := json.Unmarshal(decoded.Params, ¶ms); err != nil { + t.Fatalf("Unmarshal params: %v", err) + } + if params.Cwd != "/home/user/project" { + t.Errorf("cwd = %q, want %q", params.Cwd, "/home/user/project") + } + if params.McpServers == nil { + t.Fatal("mcpServers should be non-nil empty array") + } + if len(params.McpServers) != 0 { + t.Errorf("mcpServers len = %d, want 0", len(params.McpServers)) + } + // Verify raw JSON has [] not null for mcpServers. + if !strings.Contains(string(data), `"mcpServers":[]`) { + t.Errorf("raw JSON should contain \"mcpServers\":[], got %s", data) + } +} + +func TestSessionPromptRequest_UsesPromptFieldNotMessages(t *testing.T) { + msg, _ := newSessionPromptRequest("sess-1", runtime.TextContent("test")) + data, err := json.Marshal(msg) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + raw := string(data) + if !strings.Contains(raw, `"prompt":[`) { + t.Errorf("raw JSON should contain \"prompt\":[ field, got %s", raw) + } + if strings.Contains(raw, `"messages"`) { + t.Errorf("raw JSON should NOT contain \"messages\" field, got %s", raw) + } +} From 5f4971e238d65ba3800b485f4ab3331a00a66697 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Tue, 21 Apr 2026 20:58:17 +0000 Subject: [PATCH 46/85] fix: propagate ACP transport through session startup --- cmd/gc/cmd_session.go | 6 +- cmd/gc/cmd_session_test.go | 21 +- cmd/gc/cmd_supervisor.go | 25 +- cmd/gc/providers.go | 91 ++++++- cmd/gc/providers_test.go | 226 +++++++++++++++++- cmd/gc/session_bead_snapshot.go | 15 ++ cmd/gc/session_template_start.go | 4 +- cmd/gc/worker_handle.go | 23 +- cmd/gc/worker_handle_test.go | 146 +++++++++++ docs/schema/openapi.json | 17 ++ docs/schema/openapi.txt | 17 ++ internal/api/handler_session_chat_test.go | 178 ++++++++++++++ internal/api/handler_session_create.go | 5 +- internal/api/handler_sessions_test.go | 142 +++++++++++ .../api/huma_handlers_sessions_command.go | 5 +- internal/api/openapi.json | 17 ++ internal/api/session_runtime.go | 37 +-- internal/api/session_transport.go | 32 +++ internal/config/field_sync_test.go | 2 - internal/config/launch_command_test.go | 9 +- internal/config/patch.go | 18 ++ internal/config/patch_test.go | 34 ++- internal/config/provider.go | 9 + internal/config/provider_test.go | 7 + internal/config/resolve_test.go | 25 ++ internal/session/manager.go | 20 +- internal/session/manager_test.go | 152 ++++++++++++ internal/worker/builtin/profiles.go | 1 - 28 files changed, 1199 insertions(+), 85 deletions(-) create mode 100644 internal/api/session_transport.go diff --git a/cmd/gc/cmd_session.go b/cmd/gc/cmd_session.go index 6eba6f9c17..82e91b48f1 100644 --- a/cmd/gc/cmd_session.go +++ b/cmd/gc/cmd_session.go @@ -223,7 +223,7 @@ func cmdSessionNew(args []string, alias, title, titleHint string, noAttach bool, if err != nil { titleProvider = nil } - sessionCommand, err := resolvedSessionCommand(cityPath, resolved, nil) + sessionCommand, err := resolvedSessionCommand(cityPath, resolved, nil, found.Session) if err != nil { fmt.Fprintf(stderr, "gc session new: %v\n", err) //nolint:errcheck // best-effort stderr return 1 @@ -401,11 +401,11 @@ func maybeAutoTitle(store beads.Store, beadID, userTitle, titleHint string, prov }) } -func resolvedSessionCommand(cityPath string, resolved *config.ResolvedProvider, optionOverrides map[string]string) (string, error) { +func resolvedSessionCommand(cityPath string, resolved *config.ResolvedProvider, optionOverrides map[string]string, transport string) (string, error) { if resolved == nil { return "", fmt.Errorf("resolved provider is nil") } - launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, optionOverrides, "") + launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, optionOverrides, transport) if err != nil { return "", fmt.Errorf("resolving provider launch command: %w", err) } diff --git a/cmd/gc/cmd_session_test.go b/cmd/gc/cmd_session_test.go index 83b950d1a8..9bbd25fe23 100644 --- a/cmd/gc/cmd_session_test.go +++ b/cmd/gc/cmd_session_test.go @@ -1297,7 +1297,7 @@ func TestResolvedSessionCommandIncludesDefaultsAndSettings(t *testing.T) { EffectiveDefaults: config.ComputeEffectiveDefaults(claude.OptionsSchema, claude.OptionDefaults, nil), } - got, err := resolvedSessionCommand(cityPath, resolved, nil) + got, err := resolvedSessionCommand(cityPath, resolved, nil, "") if err != nil { t.Fatalf("resolvedSessionCommand: %v", err) } @@ -1326,7 +1326,7 @@ func TestResolvedSessionCommandAppliesOverridesOverDefaults(t *testing.T) { got, err := resolvedSessionCommand(cityPath, resolved, map[string]string{ "permission_mode": "plan", "effort": "low", - }) + }, "") if err != nil { t.Fatalf("resolvedSessionCommand: %v", err) } @@ -1340,3 +1340,20 @@ func TestResolvedSessionCommandAppliesOverridesOverDefaults(t *testing.T) { t.Fatalf("command %q should include effort=low override", got) } } + +func TestResolvedSessionCommandUsesACPTransportCommand(t *testing.T) { + resolved := &config.ResolvedProvider{ + Name: "opencode", + Command: "/bin/echo", + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + + got, err := resolvedSessionCommand("", resolved, nil, "acp") + if err != nil { + t.Fatalf("resolvedSessionCommand: %v", err) + } + if got != "/bin/echo acp" { + t.Fatalf("command = %q, want %q", got, "/bin/echo acp") + } +} diff --git a/cmd/gc/cmd_supervisor.go b/cmd/gc/cmd_supervisor.go index abbaa90584..b9d762eb4c 100644 --- a/cmd/gc/cmd_supervisor.go +++ b/cmd/gc/cmd_supervisor.go @@ -28,7 +28,6 @@ import ( "github.com/gastownhall/gascity/internal/fsys" "github.com/gastownhall/gascity/internal/hooks" "github.com/gastownhall/gascity/internal/runtime" - sessionauto "github.com/gastownhall/gascity/internal/runtime/auto" "github.com/gastownhall/gascity/internal/supervisor" "github.com/gastownhall/gascity/internal/telemetry" "github.com/gastownhall/gascity/internal/workspacesvc" @@ -1212,29 +1211,13 @@ func reconcileCities( var sp runtime.Provider spErr := runPostPrepareStep("creating_session_provider", func() error { providerName := effectiveProviderName(cfg.Session.Provider) - baseSP, err := newSessionProviderByName(providerName, cfg.Session, cityName, path) + ctx := sessionProviderContextForCity(cfg, path, providerName) + snapshot := loadProviderSessionSnapshot(ctx) + resolvedSP, err := newSessionProviderFromContextWithError(ctx, snapshot) if err != nil { return err } - // When the city-level provider is not ACP but some agents - // use session = "acp", wrap in an auto provider that routes - // ACP sessions to the ACP backend and everything else to - // the default backend. This mirrors the logic in - // newSessionProviderFromContext for CLI commands. - if providerName != "acp" && hasACPAgents(cfg.Agents) { - acpSP, acpErr := newSessionProviderByName("acp", cfg.Session, cityName, path) - if acpErr != nil { - return fmt.Errorf("acp provider: %w", acpErr) - } - autoSP := sessionauto.New(baseSP, acpSP) - snapshot := loadProviderSessionSnapshot(sessionProviderContextForCity(cfg, path, providerName)) - for _, sessName := range configuredACPSessionNames(snapshot, cityName, cfg.Workspace.SessionTemplate, cfg.Agents) { - autoSP.RouteACP(sessName) - } - sp = autoSP - } else { - sp = baseSP - } + sp = resolvedSP return nil }) if spErr != nil { diff --git a/cmd/gc/providers.go b/cmd/gc/providers.go index 5e9f77b9b9..02d54eeaef 100644 --- a/cmd/gc/providers.go +++ b/cmd/gc/providers.go @@ -161,7 +161,7 @@ func newSessionProviderForCity(cfg *config.City, cityPath string) runtime.Provid } func loadProviderSessionSnapshot(ctx sessionProviderContext) *sessionBeadSnapshot { - if ctx.cityPath == "" || ctx.providerName == "acp" || !hasACPAgents(ctx.agents) { + if ctx.cityPath == "" || ctx.providerName == "acp" { return nil } store, err := openSessionProviderStore(ctx.cityPath) @@ -176,28 +176,35 @@ func loadProviderSessionSnapshot(ctx sessionProviderContext) *sessionBeadSnapsho } func newSessionProviderFromContext(ctx sessionProviderContext, sessionBeads *sessionBeadSnapshot) runtime.Provider { - sp, err := newSessionProviderByName(ctx.providerName, ctx.sc, ctx.cityName, ctx.cityPath) + sp, err := newSessionProviderFromContextWithError(ctx, sessionBeads) if err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) //nolint:errcheck // best-effort stderr os.Exit(1) } + return sp +} + +func newSessionProviderFromContextWithError(ctx sessionProviderContext, sessionBeads *sessionBeadSnapshot) (runtime.Provider, error) { + sp, err := newSessionProviderByName(ctx.providerName, ctx.sc, ctx.cityName, ctx.cityPath) + if err != nil { + return nil, err + } // If the city-level provider is not ACP but some agents need ACP, // wrap in an auto provider that routes per-session. // NOTE: agents comes from loadCityConfig which applies pack overrides, // so the Session field from overrides is already resolved here. - if ctx.providerName != "acp" && hasACPAgents(ctx.agents) { + if ctx.providerName != "acp" && needsACPProviderWrapper(sessionBeads, ctx.cfg) { acpSP, acpErr := newSessionProviderByName("acp", ctx.sc, ctx.cityName, ctx.cityPath) if acpErr != nil { - fmt.Fprintf(os.Stderr, "acp provider: %v\n", acpErr) //nolint:errcheck // best-effort stderr - os.Exit(1) + return nil, fmt.Errorf("acp provider: %w", acpErr) } autoSP := sessionauto.New(sp, acpSP) - for _, sessName := range configuredACPSessionNames(sessionBeads, ctx.cityName, ctx.sessionTemplate, ctx.agents) { + for _, sessName := range configuredACPRouteNames(sessionBeads, ctx.cityName, ctx.cfg) { autoSP.RouteACP(sessName) } - return autoSP + return autoSP, nil } - return sp + return sp, nil } // hasACPAgents reports whether any agent in the config uses session = "acp". @@ -230,6 +237,74 @@ func configuredACPSessionNames(snapshot *sessionBeadSnapshot, cityName, sessionT return names } +func needsACPProviderWrapper(snapshot *sessionBeadSnapshot, cfg *config.City) bool { + return len(observedACPSessionNames(snapshot)) > 0 || (cfg != nil && hasACPAgents(cfg.Agents)) +} + +func observedACPSessionNames(snapshot *sessionBeadSnapshot) []string { + if snapshot == nil { + return nil + } + names := make([]string, 0, len(snapshot.open)) + seen := make(map[string]bool, len(snapshot.open)) + for _, bead := range snapshot.Open() { + if !beadUsesACPTransport(bead) { + continue + } + sessionName := strings.TrimSpace(bead.Metadata["session_name"]) + if sessionName == "" || seen[sessionName] { + continue + } + seen[sessionName] = true + names = append(names, sessionName) + } + return names +} + +func beadUsesACPTransport(bead beads.Bead) bool { + transport := strings.TrimSpace(bead.Metadata["transport"]) + if transport != "" { + return transport == "acp" + } + return strings.TrimSpace(bead.Metadata["provider"]) == "acp" +} + +func configuredACPRouteNames(snapshot *sessionBeadSnapshot, cityName string, cfg *config.City) []string { + names := observedACPSessionNames(snapshot) + seen := make(map[string]bool, len(names)) + for _, name := range names { + seen[name] = true + } + if cfg == nil { + return names + } + for _, name := range configuredACPSessionNames(snapshot, cityName, cfg.Workspace.SessionTemplate, cfg.Agents) { + if name == "" || seen[name] { + continue + } + seen[name] = true + names = append(names, name) + } + for _, named := range cfg.NamedSessions { + agentCfg := config.FindAgent(cfg, named.TemplateQualifiedName()) + if agentCfg == nil || agentCfg.Session != "acp" { + continue + } + sessionName := config.NamedSessionRuntimeName(cityName, cfg.Workspace, named.QualifiedName()) + if snapshot != nil { + if snapName := snapshot.FindSessionNameByNamedIdentity(named.QualifiedName()); snapName != "" { + sessionName = snapName + } + } + if sessionName == "" || seen[sessionName] { + continue + } + seen[sessionName] = true + names = append(names, sessionName) + } + return names +} + // displayProviderName returns a human-readable provider name for logging. func displayProviderName(name string) string { if name == "" { diff --git a/cmd/gc/providers_test.go b/cmd/gc/providers_test.go index 6dcf126d4d..152161c000 100644 --- a/cmd/gc/providers_test.go +++ b/cmd/gc/providers_test.go @@ -240,6 +240,94 @@ func TestConfiguredACPSessionNames_UsesProvidedSnapshot(t *testing.T) { } } +func TestSessionBeadSnapshotFindSessionNameByNamedIdentity(t *testing.T) { + snapshot := newSessionBeadSnapshot([]beads.Bead{{ + Type: sessionBeadType, + Labels: []string{sessionBeadLabel}, + Metadata: map[string]string{ + "template": "reviewer-template", + "configured_named_identity": "reviewer", + "session_name": "custom-reviewer", + }, + }}) + + if got := snapshot.FindSessionNameByNamedIdentity("reviewer"); got != "custom-reviewer" { + t.Fatalf("FindSessionNameByNamedIdentity(reviewer) = %q, want %q", got, "custom-reviewer") + } +} + +func TestConfiguredACPRouteNames_IncludeNamedSessionRuntimeNames(t *testing.T) { + cfg := &config.City{ + Workspace: config.Workspace{ + Name: "test-city", + }, + Agents: []config.Agent{ + {Name: "reviewer-template", Session: "acp"}, + {Name: "mayor"}, + }, + NamedSessions: []config.NamedSession{ + {Name: "reviewer", Template: "reviewer-template"}, + }, + } + + t.Run("deterministic fallback", func(t *testing.T) { + got := configuredACPRouteNames(nil, "test-city", cfg) + want := []string{ + agent.SessionNameFor("test-city", "reviewer-template", ""), + config.NamedSessionRuntimeName("test-city", cfg.Workspace, "reviewer"), + } + if len(got) != len(want) { + t.Fatalf("configuredACPRouteNames len = %d, want %d (%v)", len(got), len(want), got) + } + for i := range want { + if got[i] != want[i] { + t.Fatalf("configuredACPRouteNames[%d] = %q, want %q", i, got[i], want[i]) + } + } + }) + + t.Run("snapshot override", func(t *testing.T) { + snapshot := newSessionBeadSnapshot([]beads.Bead{{ + Type: sessionBeadType, + Labels: []string{sessionBeadLabel}, + Metadata: map[string]string{ + "template": "reviewer-template", + "configured_named_identity": "reviewer", + "session_name": "custom-reviewer", + }, + }}) + + got := configuredACPRouteNames(snapshot, "test-city", cfg) + want := []string{"custom-reviewer"} + if len(got) != len(want) { + t.Fatalf("configuredACPRouteNames len = %d, want %d (%v)", len(got), len(want), got) + } + for i := range want { + if got[i] != want[i] { + t.Fatalf("configuredACPRouteNames[%d] = %q, want %q", i, got[i], want[i]) + } + } + }) +} + +func TestConfiguredACPRouteNames_IncludeObservedACPProviderSessions(t *testing.T) { + snapshot := newSessionBeadSnapshot([]beads.Bead{{ + Type: sessionBeadType, + Labels: []string{sessionBeadLabel}, + Metadata: map[string]string{ + "template": "opencode", + "provider": "opencode", + "transport": "acp", + "session_name": "provider-session", + }, + }}) + + got := configuredACPRouteNames(snapshot, "test-city", nil) + if len(got) != 1 || got[0] != "provider-session" { + t.Fatalf("configuredACPRouteNames() = %v, want [provider-session]", got) + } +} + func TestNewSessionProvider_PreregistersACPBeadAndLegacyNames(t *testing.T) { t.Setenv("GC_BEADS", "file") t.Setenv("GC_SESSION", "fake") @@ -281,7 +369,76 @@ func TestNewSessionProvider_PreregistersACPBeadAndLegacyNames(t *testing.T) { } } -func TestLoadProviderSessionSnapshotSkipsStoreWithoutACPAgents(t *testing.T) { +func TestNewSessionProvider_PreregistersACPNamedSessionRuntimeName(t *testing.T) { + t.Setenv("GC_BEADS", "file") + t.Setenv("GC_SESSION", "fake") + + cityDir := t.TempDir() + t.Setenv("GC_CITY", cityDir) + writeACPNamedSessionRouteCityTOML(t, cityDir, "test-city") + + sp := newSessionProvider() + namedRuntime := config.NamedSessionRuntimeName("test-city", config.Workspace{}, "reviewer") + if err := sp.Attach(namedRuntime); err == nil || !strings.Contains(err.Error(), "ACP transport") { + t.Fatalf("Attach(%q) error = %v, want ACP transport error", namedRuntime, err) + } +} + +func TestNewSessionProviderDoesNotWrapUnusedACPDefaultProvidersWithoutACPAgents(t *testing.T) { + ctx := sessionProviderContextForCity(&config.City{ + Workspace: config.Workspace{ + Name: "test-city", + Provider: "opencode", + }, + Providers: map[string]config.ProviderSpec{ + "opencode": { + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: boolPtr(true), + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + }, t.TempDir(), "fake") + + sp := newSessionProviderFromContext(ctx, nil) + if _, ok := sp.(interface{ RouteACP(string) }); ok { + t.Fatalf("provider = %T, want plain provider without ACP routing", sp) + } +} + +func TestNewSessionProviderRoutesObservedACPProviderSessionsWithoutACPAgents(t *testing.T) { + t.Setenv("GC_BEADS", "file") + t.Setenv("GC_SESSION", "fake") + + cityDir := t.TempDir() + t.Setenv("GC_CITY", cityDir) + writeACPProviderRouteCityTOML(t, cityDir, "test-city") + + store, err := openCityStoreAt(cityDir) + if err != nil { + t.Fatalf("openCityStoreAt: %v", err) + } + if _, err := store.Create(beads.Bead{ + Type: sessionBeadType, + Labels: []string{sessionBeadLabel}, + Metadata: map[string]string{ + "template": "opencode", + "provider": "opencode", + "transport": "acp", + "session_name": "provider-session", + }, + }); err != nil { + t.Fatalf("Create(provider session bead): %v", err) + } + + sp := newSessionProvider() + if err := sp.Attach("provider-session"); err == nil || !strings.Contains(err.Error(), "ACP transport") { + t.Fatalf("Attach(provider-session) error = %v, want ACP transport error", err) + } +} + +func TestLoadProviderSessionSnapshotLoadsStoreWithoutACPAgents(t *testing.T) { oldOpen := openSessionProviderStore t.Cleanup(func() { openSessionProviderStore = oldOpen }) @@ -298,11 +455,11 @@ func TestLoadProviderSessionSnapshotSkipsStoreWithoutACPAgents(t *testing.T) { {Name: "mayor"}, }, }) - if snapshot != nil { - t.Fatalf("loadProviderSessionSnapshot() = %#v, want nil", snapshot) + if snapshot == nil { + t.Fatal("loadProviderSessionSnapshot() = nil, want empty snapshot") } - if calls != 0 { - t.Fatalf("openSessionProviderStore called %d times, want 0", calls) + if calls != 1 { + t.Fatalf("openSessionProviderStore called %d times, want 1", calls) } } @@ -379,3 +536,62 @@ start_command = "echo" t.Fatalf("WriteFile(city.toml): %v", err) } } + +func writeACPNamedSessionRouteCityTOML(t *testing.T, dir, cityName string) { + t.Helper() + if err := os.MkdirAll(filepath.Join(dir, ".gc"), 0o755); err != nil { + t.Fatalf("MkdirAll(.gc): %v", err) + } + data := []byte(`[workspace] +name = "` + cityName + `" + +[beads] +provider = "file" + +[[agent]] +name = "reviewer-template" +provider = "claude" +start_command = "echo" +session = "acp" + +[[named_session]] +name = "reviewer" +template = "reviewer-template" + +[[agent]] +name = "mayor" +provider = "claude" +start_command = "echo" +`) + if err := os.WriteFile(filepath.Join(dir, "city.toml"), data, 0o644); err != nil { + t.Fatalf("WriteFile(city.toml): %v", err) + } +} + +func writeACPProviderRouteCityTOML(t *testing.T, dir, cityName string) { + t.Helper() + if err := os.MkdirAll(filepath.Join(dir, ".gc"), 0o755); err != nil { + t.Fatalf("MkdirAll(.gc): %v", err) + } + data := []byte(`[workspace] +name = "` + cityName + `" + +[beads] +provider = "file" + +[[agent]] +name = "mayor" +provider = "codex" +start_command = "echo" + +[providers.opencode] +command = "/bin/echo" +path_check = "true" +supports_acp = true +acp_command = "/bin/echo" +acp_args = ["acp"] +`) + if err := os.WriteFile(filepath.Join(dir, "city.toml"), data, 0o644); err != nil { + t.Fatalf("WriteFile(city.toml): %v", err) + } +} diff --git a/cmd/gc/session_bead_snapshot.go b/cmd/gc/session_bead_snapshot.go index 1e787572d7..4906256c6a 100644 --- a/cmd/gc/session_bead_snapshot.go +++ b/cmd/gc/session_bead_snapshot.go @@ -115,3 +115,18 @@ func (s *sessionBeadSnapshot) FindSessionNameByTemplate(template string) string } return s.sessionNameByTemplateHint[template] } + +func (s *sessionBeadSnapshot) FindSessionNameByNamedIdentity(identity string) string { + if s == nil || strings.TrimSpace(identity) == "" { + return "" + } + for _, bead := range s.open { + if strings.TrimSpace(bead.Metadata["configured_named_identity"]) != identity { + continue + } + if sessionName := strings.TrimSpace(bead.Metadata["session_name"]); sessionName != "" { + return sessionName + } + } + return "" +} diff --git a/cmd/gc/session_template_start.go b/cmd/gc/session_template_start.go index 44f5c3631c..538882f5fc 100644 --- a/cmd/gc/session_template_start.go +++ b/cmd/gc/session_template_start.go @@ -119,7 +119,7 @@ func materializeSessionForTemplateWithOptions( if err != nil { return "", err } - sessionCommand, err := resolvedSessionCommand(cityPath, resolved, nil) + sessionCommand, err := resolvedSessionCommand(cityPath, resolved, nil, spec.Agent.Session) if err != nil { return "", err } @@ -272,7 +272,7 @@ func materializeSessionForAgentConfig(cityPath string, cfg *config.City, store b if err != nil { return "", err } - sessionCommand, err := resolvedSessionCommand(cityPath, resolved, nil) + sessionCommand, err := resolvedSessionCommand(cityPath, resolved, nil, agentCfg.Session) if err != nil { return "", err } diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index d198b5ee7a..b9bd1fc815 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -321,16 +321,19 @@ func resolvedWorkerRuntimeWithConfig(cityPath string, cfg *config.City, info ses if cfg == nil { return nil } - resolved := resolveWorkerRuntimeWithConfig(cfg, info, sessionKind) + resolved, transport := resolveWorkerRuntimeProviderWithConfig(cfg, info, sessionKind) if resolved == nil { return nil } command := strings.TrimSpace(info.Command) - if !shouldPreserveStoredRuntimeCommand(command, resolved.CommandString()) && - !shouldPreserveStoredRuntimeCommand(command, resolved.ACPCommandString()) { - launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, nil, "") - command = resolved.CommandString() + resolvedCommand := resolved.CommandString() + if transport == "acp" { + resolvedCommand = resolved.ACPCommandString() + } + if !shouldPreserveStoredRuntimeCommand(command, resolvedCommand) { + launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, nil, transport) + command = resolvedCommand if err == nil { command = launchCommand.Command } @@ -383,22 +386,22 @@ func shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand string) b return strings.HasPrefix(storedCommand, resolvedCommand+" ") } -func resolveWorkerRuntimeWithConfig(cfg *config.City, info session.Info, sessionKind string) *config.ResolvedProvider { +func resolveWorkerRuntimeProviderWithConfig(cfg *config.City, info session.Info, sessionKind string) (*config.ResolvedProvider, string) { if cfg == nil { - return nil + return nil, "" } if sessionKind != "provider" { if found, ok := resolveAgentIdentity(cfg, info.Template, ""); ok { if resolved, err := config.ResolveProvider(&found, &cfg.Workspace, cfg.Providers, exec.LookPath); err == nil { - return resolved + return resolved, strings.TrimSpace(info.Transport) } } } resolved, err := config.ResolveProvider(&config.Agent{Provider: info.Template}, &cfg.Workspace, cfg.Providers, exec.LookPath) if err != nil { - return nil + return nil, "" } - return resolved + return resolved, strings.TrimSpace(info.Transport) } func workerDeliveryIntentForSubmitIntent(intent session.SubmitIntent) worker.DeliveryIntent { diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index ba59b02fd9..2ab8dbc0f8 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -199,6 +199,152 @@ func TestResolvedWorkerRuntimeResumesPoolSessionPreservesLaunchFlags(t *testing. } } +func TestResolvedWorkerRuntimeWithConfigUsesStoredTemplateACPTransport(t *testing.T) { + cityDir := t.TempDir() + writePhase0InterfaceCity(t, cityDir, `[workspace] +name = "test-city" + +[beads] +provider = "file" + +[[agent]] +name = "worker" +provider = "stub" +session = "acp" + +[providers.stub] +command = "/bin/echo" +supports_acp = true +acp_command = "/bin/echo" +acp_args = ["acp"] +`) + + cfg, err := loadCityConfig(cityDir) + if err != nil { + t.Fatalf("loadCityConfig: %v", err) + } + + resolved := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + Template: "worker", + Command: "/bin/echo", + Transport: "acp", + WorkDir: cityDir, + }, "") + if resolved == nil { + t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") + } + if got, want := resolved.Command, "/bin/echo acp"; got != want { + t.Fatalf("Command = %q, want %q", got, want) + } +} + +func TestResolvedWorkerRuntimeWithConfigKeepsDefaultTransportWithoutStoredTemplateACPMetadata(t *testing.T) { + cityDir := t.TempDir() + writePhase0InterfaceCity(t, cityDir, `[workspace] +name = "test-city" + +[beads] +provider = "file" + +[[agent]] +name = "worker" +provider = "stub" +session = "acp" + +[providers.stub] +command = "/bin/echo" +supports_acp = true +acp_command = "/bin/echo" +acp_args = ["acp"] +`) + + cfg, err := loadCityConfig(cityDir) + if err != nil { + t.Fatalf("loadCityConfig: %v", err) + } + + resolved := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + Template: "worker", + Command: "/bin/echo", + WorkDir: cityDir, + }, "") + if resolved == nil { + t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") + } + if got, want := resolved.Command, "/bin/echo"; got != want { + t.Fatalf("Command = %q, want %q", got, want) + } +} + +func TestResolvedWorkerRuntimeWithConfigUsesStoredACPTransportForProviderSession(t *testing.T) { + cityDir := t.TempDir() + writePhase0InterfaceCity(t, cityDir, `[workspace] +name = "test-city" + +[beads] +provider = "file" + +[providers.opencode] +command = "/bin/echo" +path_check = "true" +supports_acp = true +acp_command = "/bin/echo" +acp_args = ["acp"] +`) + + cfg, err := loadCityConfig(cityDir) + if err != nil { + t.Fatalf("loadCityConfig: %v", err) + } + + resolved := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + Template: "opencode", + Command: "/bin/echo", + Transport: "acp", + WorkDir: cityDir, + }, "provider") + if resolved == nil { + t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") + } + if got, want := resolved.Command, "/bin/echo acp"; got != want { + t.Fatalf("Command = %q, want %q", got, want) + } +} + +func TestResolvedWorkerRuntimeWithConfigKeepsDefaultTransportForLegacyProviderSessionWithoutMetadata(t *testing.T) { + cityDir := t.TempDir() + writePhase0InterfaceCity(t, cityDir, `[workspace] +name = "test-city" + +[beads] +provider = "file" + +[providers.opencode] +command = "/bin/echo" +path_check = "true" +supports_acp = true +acp_command = "/bin/echo" +acp_args = ["acp"] +`) + + cfg, err := loadCityConfig(cityDir) + if err != nil { + t.Fatalf("loadCityConfig: %v", err) + } + + resolved := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + Template: "opencode", + Command: "/bin/echo", + WorkDir: cityDir, + }, "provider") + if resolved == nil { + t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") + } + if got, want := resolved.Command, "/bin/echo"; got != want { + t.Fatalf("Command = %q, want %q", got, want) + } +} + func TestWorkerHandleForSessionWithConfigUsesResolvedProviderOnResume(t *testing.T) { skipSlowCmdGCTest(t, "waits through stale session-key detection; run make test-cmd-gc-process for full coverage") cityDir := t.TempDir() diff --git a/docs/schema/openapi.json b/docs/schema/openapi.json index 377d0abcf5..771b40da87 100644 --- a/docs/schema/openapi.json +++ b/docs/schema/openapi.json @@ -4598,6 +4598,21 @@ "ProviderPatch": { "additionalProperties": false, "properties": { + "ACPArgs": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "ACPCommand": { + "type": [ + "string", + "null" + ] + }, "Args": { "items": { "type": "string" @@ -4679,7 +4694,9 @@ "Name", "Base", "Command", + "ACPCommand", "Args", + "ACPArgs", "ArgsAppend", "OptionsSchemaMerge", "PromptMode", diff --git a/docs/schema/openapi.txt b/docs/schema/openapi.txt index 377d0abcf5..771b40da87 100644 --- a/docs/schema/openapi.txt +++ b/docs/schema/openapi.txt @@ -4598,6 +4598,21 @@ "ProviderPatch": { "additionalProperties": false, "properties": { + "ACPArgs": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "ACPCommand": { + "type": [ + "string", + "null" + ] + }, "Args": { "items": { "type": "string" @@ -4679,7 +4694,9 @@ "Name", "Base", "Command", + "ACPCommand", "Args", + "ACPArgs", "ArgsAppend", "OptionsSchemaMerge", "PromptMode", diff --git a/internal/api/handler_session_chat_test.go b/internal/api/handler_session_chat_test.go index 107ae3aea2..fdea344390 100644 --- a/internal/api/handler_session_chat_test.go +++ b/internal/api/handler_session_chat_test.go @@ -7,6 +7,8 @@ import ( "testing" "github.com/gastownhall/gascity/internal/config" + "github.com/gastownhall/gascity/internal/runtime" + sessionauto "github.com/gastownhall/gascity/internal/runtime/auto" "github.com/gastownhall/gascity/internal/session" "github.com/gastownhall/gascity/internal/shellquote" ) @@ -170,3 +172,179 @@ func TestBuildSessionResumeRebuildsBareStoredCommandForPoolClaudeAgent(t *testin t.Fatalf("resume command missing settings arg:\n got: %s", cmd) } } + +func TestBuildSessionResumeUsesStoredACPCommandForProviderSession(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg = &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Providers: map[string]config.ProviderSpec{ + "opencode": { + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + } + + state := &stateWithSessionProvider{ + fakeState: fs, + provider: sessionauto.New(runtime.NewFake(), runtime.NewFake()), + } + srv := New(state) + info := session.Info{ + ID: "gc-1", + Template: "opencode", + Command: "/bin/echo", + Provider: "opencode", + Transport: "acp", + WorkDir: "/tmp/workdir", + } + + cmd, _ := srv.buildSessionResume(info) + if got, want := cmd, "/bin/echo acp"; got != want { + t.Fatalf("resume command = %q, want %q", got, want) + } +} + +func TestBuildSessionResumeKeepsDefaultCommandWithoutACPTransportProvider(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg = &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Providers: map[string]config.ProviderSpec{ + "opencode": { + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + } + + srv := New(fs) + info := session.Info{ + ID: "gc-1", + Template: "opencode", + Command: "/bin/echo", + Provider: "opencode", + WorkDir: "/tmp/workdir", + } + + cmd, _ := srv.buildSessionResume(info) + if got, want := cmd, "/bin/echo"; got != want { + t.Fatalf("resume command = %q, want %q", got, want) + } +} + +func TestBuildSessionResumeKeepsDefaultCommandForLegacyProviderSessionWithoutTransportMetadata(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg = &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Providers: map[string]config.ProviderSpec{ + "opencode": { + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + } + + state := &stateWithSessionProvider{ + fakeState: fs, + provider: sessionauto.New(runtime.NewFake(), runtime.NewFake()), + } + srv := New(state) + info := session.Info{ + ID: "gc-1", + Template: "opencode", + Command: "/bin/echo", + Provider: "opencode", + WorkDir: "/tmp/workdir", + } + + cmd, _ := srv.buildSessionResume(info) + if got, want := cmd, "/bin/echo"; got != want { + t.Fatalf("resume command = %q, want %q", got, want) + } +} + +func TestBuildSessionResumeUsesStoredACPTransportForTemplateSession(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg = &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Agents: []config.Agent{ + {Name: "worker", Provider: "opencode", Session: "acp"}, + }, + Providers: map[string]config.ProviderSpec{ + "opencode": { + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + } + + srv := New(fs) + info := session.Info{ + ID: "gc-1", + Template: "worker", + Command: "/bin/echo", + Provider: "opencode", + Transport: "acp", + WorkDir: "/tmp/workdir", + } + + cmd, _ := srv.buildSessionResume(info) + if got, want := cmd, "/bin/echo acp"; got != want { + t.Fatalf("resume command = %q, want %q", got, want) + } +} + +func TestBuildSessionResumeKeepsDefaultCommandForLegacyTemplateSessionWithoutTransportMetadata(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg = &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Agents: []config.Agent{ + {Name: "worker", Provider: "opencode", Session: "acp"}, + }, + Providers: map[string]config.ProviderSpec{ + "opencode": { + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + } + + srv := New(fs) + info := session.Info{ + ID: "gc-1", + Template: "worker", + Command: "/bin/echo", + Provider: "opencode", + WorkDir: "/tmp/workdir", + } + + cmd, _ := srv.buildSessionResume(info) + if got, want := cmd, "/bin/echo"; got != want { + t.Fatalf("resume command = %q, want %q", got, want) + } +} diff --git a/internal/api/handler_session_create.go b/internal/api/handler_session_create.go index 084852d3cd..9b71d10f4a 100644 --- a/internal/api/handler_session_create.go +++ b/internal/api/handler_session_create.go @@ -274,7 +274,8 @@ func (s *Server) createProviderSession(w http.ResponseWriter, r *http.Request, s return } - launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, body.Options, "") + transport := providerSessionTransport(resolved, s.state.SessionProvider()) + launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, body.Options, transport) if err != nil { s.idem.unreserve(idemKey) if errors.Is(err, config.ErrUnknownOption) { @@ -286,7 +287,7 @@ func (s *Server) createProviderSession(w http.ResponseWriter, r *http.Request, s } command := launchCommand.Command - resolvedCfg, err := resolvedSessionConfigForProvider(alias, "", template, title, "", map[string]string{ + resolvedCfg, err := resolvedSessionConfigForProvider(alias, "", template, title, transport, map[string]string{ "session_origin": "manual", }, resolved, command, workDir) if err != nil { diff --git a/internal/api/handler_sessions_test.go b/internal/api/handler_sessions_test.go index 83cb25056a..7c5dbc2102 100644 --- a/internal/api/handler_sessions_test.go +++ b/internal/api/handler_sessions_test.go @@ -19,6 +19,7 @@ import ( "github.com/gastownhall/gascity/internal/config" "github.com/gastownhall/gascity/internal/events" "github.com/gastownhall/gascity/internal/runtime" + sessionauto "github.com/gastownhall/gascity/internal/runtime/auto" "github.com/gastownhall/gascity/internal/session" "github.com/gastownhall/gascity/internal/sessionlog" "github.com/gastownhall/gascity/internal/worker" @@ -1431,6 +1432,147 @@ func TestHandleProviderSessionCreateWithMessageUsesProviderDefaultNudge(t *testi } } +func TestHandleProviderSessionCreateUsesACPTransportCommand(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg.Providers["opencode"] = config.ProviderSpec{ + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + defaultSP := runtime.NewFake() + acpSP := runtime.NewFake() + state := &stateWithSessionProvider{ + fakeState: fs, + provider: sessionauto.New(defaultSP, acpSP), + } + srv := New(state) + h := newTestCityHandlerWith(t, state, srv) + + req := newPostRequest(cityURL(fs, "/sessions"), strings.NewReader(`{"kind":"provider","name":"opencode"}`)) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + + if rec.Code != http.StatusCreated { + t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusCreated, rec.Body.String()) + } + + var resp sessionResponse + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode: %v", err) + } + start := acpSP.LastStartConfig(resp.SessionName) + if start == nil { + t.Fatalf("LastStartConfig(%q) = nil", resp.SessionName) + } + if got, want := start.Command, "/bin/echo acp"; got != want { + t.Fatalf("start command = %q, want %q", got, want) + } + bead, err := fs.cityBeadStore.Get(resp.ID) + if err != nil { + t.Fatalf("Get(%s): %v", resp.ID, err) + } + if got, want := bead.Metadata["transport"], "acp"; got != want { + t.Fatalf("transport metadata = %q, want %q", got, want) + } + if defaultSP.IsRunning(resp.SessionName) { + t.Fatalf("default backend should not own ACP session %q", resp.SessionName) + } +} + +func TestHumaCreateProviderSessionUsesACPTransportCommand(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg.Providers["opencode"] = config.ProviderSpec{ + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + defaultSP := runtime.NewFake() + acpSP := runtime.NewFake() + state := &stateWithSessionProvider{ + fakeState: fs, + provider: sessionauto.New(defaultSP, acpSP), + } + srv := New(state) + + out, err := srv.humaCreateProviderSession(context.Background(), fs.cityBeadStore, sessionCreateBody{ + Kind: "provider", + Name: "opencode", + }, "opencode") + if err != nil { + t.Fatalf("humaCreateProviderSession: %v", err) + } + if got, want := out.Status, http.StatusCreated; got != want { + t.Fatalf("status = %d, want %d", got, want) + } + start := acpSP.LastStartConfig(out.Body.SessionName) + if start == nil { + t.Fatalf("LastStartConfig(%q) = nil", out.Body.SessionName) + } + if got, want := start.Command, "/bin/echo acp"; got != want { + t.Fatalf("start command = %q, want %q", got, want) + } + bead, err := fs.cityBeadStore.Get(out.Body.ID) + if err != nil { + t.Fatalf("Get(%s): %v", out.Body.ID, err) + } + if got, want := bead.Metadata["transport"], "acp"; got != want { + t.Fatalf("transport metadata = %q, want %q", got, want) + } + if defaultSP.IsRunning(out.Body.SessionName) { + t.Fatalf("default backend should not own ACP session %q", out.Body.SessionName) + } +} + +func TestHandleProviderSessionCreateKeepsDefaultTransportWithoutACPProvider(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg.Providers["opencode"] = config.ProviderSpec{ + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + srv := New(fs) + h := newTestCityHandlerWith(t, fs, srv) + + req := newPostRequest(cityURL(fs, "/sessions"), strings.NewReader(`{"kind":"provider","name":"opencode"}`)) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + + if rec.Code != http.StatusCreated { + t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusCreated, rec.Body.String()) + } + + var resp sessionResponse + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode: %v", err) + } + start := fs.sp.LastStartConfig(resp.SessionName) + if start == nil { + t.Fatalf("LastStartConfig(%q) = nil", resp.SessionName) + } + if got, want := start.Command, "/bin/echo"; got != want { + t.Fatalf("start command = %q, want %q", got, want) + } + bead, err := fs.cityBeadStore.Get(resp.ID) + if err != nil { + t.Fatalf("Get(%s): %v", resp.ID, err) + } + if got := bead.Metadata["transport"]; got != "" { + t.Fatalf("transport metadata = %q, want empty", got) + } +} + func TestHandleProviderSessionCreateWithMessageRollsBackOnDeliveryFailure(t *testing.T) { fs := newSessionFakeState(t) provider := &failNudgeProvider{Fake: runtime.NewFake(), err: errors.New("nudge failed")} diff --git a/internal/api/huma_handlers_sessions_command.go b/internal/api/huma_handlers_sessions_command.go index efb590b7f8..7d506980c6 100644 --- a/internal/api/huma_handlers_sessions_command.go +++ b/internal/api/huma_handlers_sessions_command.go @@ -234,7 +234,8 @@ func (s *Server) humaCreateProviderSession(ctx context.Context, store beads.Stor return nil, humaSessionManagerError(err) } - launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, body.Options, "") + transport := providerSessionTransport(resolved, s.state.SessionProvider()) + launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, body.Options, transport) if err != nil { return nil, huma.Error400BadRequest(err.Error()) } @@ -257,7 +258,7 @@ func (s *Server) humaCreateProviderSession(ctx context.Context, store beads.Stor command, workDir, resolved.Name, - "", + transport, resolved.Env, resume, hints, diff --git a/internal/api/openapi.json b/internal/api/openapi.json index 377d0abcf5..771b40da87 100644 --- a/internal/api/openapi.json +++ b/internal/api/openapi.json @@ -4598,6 +4598,21 @@ "ProviderPatch": { "additionalProperties": false, "properties": { + "ACPArgs": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "ACPCommand": { + "type": [ + "string", + "null" + ] + }, "Args": { "items": { "type": "string" @@ -4679,7 +4694,9 @@ "Name", "Base", "Command", + "ACPCommand", "Args", + "ACPArgs", "ArgsAppend", "OptionsSchemaMerge", "PromptMode", diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index 7eda30377a..b6a71ed078 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -113,33 +113,41 @@ func (s *Server) resolveSessionTemplate(template string) (*config.ResolvedProvid func (s *Server) buildSessionResume(info session.Info) (string, runtime.Config) { cmd := session.BuildResumeCommand(info) - resolved, workDir := s.resolveSessionRuntime(info) + resolved, workDir, transport := s.resolveSessionRuntime(info) if resolved == nil { return cmd, runtime.Config{WorkDir: info.WorkDir} } resolvedInfo := info - if command, err := s.resolvedSessionRuntimeCommand(resolved, info.Command); err == nil { + if command, err := s.resolvedSessionRuntimeCommand(resolved, transport, info.Command); err == nil { resolvedInfo.Command = command } else { - resolvedInfo.Command = firstNonEmptyString(info.Command, resolved.CommandString(), resolved.Name) + resolvedCommand := resolved.CommandString() + if transport == "acp" { + resolvedCommand = resolved.ACPCommandString() + } + resolvedInfo.Command = firstNonEmptyString(info.Command, resolvedCommand, resolved.Name) } resolvedInfo.Provider = resolved.Name + resolvedInfo.Transport = transport resolvedInfo.ResumeFlag = resolved.ResumeFlag resolvedInfo.ResumeStyle = resolved.ResumeStyle resolvedInfo.ResumeCommand = resolved.ResumeCommand return session.BuildResumeCommand(resolvedInfo), sessionResumeHints(resolved, workDir) } -func (s *Server) resolvedSessionRuntimeCommand(resolved *config.ResolvedProvider, storedCommand string) (string, error) { - if command := strings.TrimSpace(storedCommand); shouldPreserveStoredRuntimeCommand(command, resolved.CommandString()) || - shouldPreserveStoredRuntimeCommand(command, resolved.ACPCommandString()) { +func (s *Server) resolvedSessionRuntimeCommand(resolved *config.ResolvedProvider, transport, storedCommand string) (string, error) { + resolvedCommand := resolved.CommandString() + if transport == "acp" { + resolvedCommand = resolved.ACPCommandString() + } + if command := strings.TrimSpace(storedCommand); shouldPreserveStoredRuntimeCommand(command, resolvedCommand) { return command, nil } - launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, nil, "") + launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, nil, transport) if err != nil { return "", fmt.Errorf("building provider launch command: %w", err) } - return firstNonEmptyString(launchCommand.Command, resolved.CommandString(), resolved.Name), nil + return firstNonEmptyString(launchCommand.Command, resolvedCommand, resolved.Name), nil } func shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand string) bool { @@ -164,11 +172,11 @@ func shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand string) b } func (s *Server) resolveWorkerSessionRuntime(info session.Info, _ string) (*worker.ResolvedRuntime, error) { - resolved, workDir := s.resolveSessionRuntime(info) + resolved, workDir, transport := s.resolveSessionRuntime(info) if resolved == nil { return nil, nil } - command, err := s.resolvedSessionRuntimeCommand(resolved, info.Command) + command, err := s.resolvedSessionRuntimeCommand(resolved, transport, info.Command) if err != nil { return nil, err } @@ -191,7 +199,7 @@ func (s *Server) resolveWorkerSessionRuntime(info session.Info, _ string) (*work return &runtimeCfg, nil } -func (s *Server) resolveSessionRuntime(info session.Info) (*config.ResolvedProvider, string) { +func (s *Server) resolveSessionRuntime(info session.Info) (*config.ResolvedProvider, string, string) { kind := s.sessionKind(info.ID) if kind != "provider" { resolved, workDir, _, _, err := s.resolveSessionTemplate(info.Template) @@ -199,19 +207,20 @@ func (s *Server) resolveSessionRuntime(info session.Info) (*config.ResolvedProvi if info.WorkDir != "" { workDir = info.WorkDir } - return resolved, workDir + return resolved, workDir, strings.TrimSpace(info.Transport) } } resolved, err := s.resolveBareProvider(info.Template) if err != nil { - return nil, "" + return nil, "", "" } workDir := info.WorkDir if workDir == "" { workDir = s.state.CityPath() } - return resolved, workDir + transport := strings.TrimSpace(info.Transport) + return resolved, workDir, transport } // sessionKind reads the persisted mc_session_kind from bead metadata. diff --git a/internal/api/session_transport.go b/internal/api/session_transport.go new file mode 100644 index 0000000000..4902b7b609 --- /dev/null +++ b/internal/api/session_transport.go @@ -0,0 +1,32 @@ +package api + +import ( + "github.com/gastownhall/gascity/internal/config" + "github.com/gastownhall/gascity/internal/runtime" + sessionacp "github.com/gastownhall/gascity/internal/runtime/acp" +) + +type acpRoutingProvider interface { + RouteACP(name string) +} + +func providerSessionTransport(resolved *config.ResolvedProvider, sp runtime.Provider) string { + if resolved == nil || resolved.DefaultSessionTransport() != "acp" { + return "" + } + if transportSupportsACP(sp) { + return "acp" + } + return "" +} + +func transportSupportsACP(sp runtime.Provider) bool { + if sp == nil { + return false + } + if _, ok := sp.(acpRoutingProvider); ok { + return true + } + _, ok := sp.(*sessionacp.Provider) + return ok +} diff --git a/internal/config/field_sync_test.go b/internal/config/field_sync_test.go index 55a636a706..4fe8ad63d4 100644 --- a/internal/config/field_sync_test.go +++ b/internal/config/field_sync_test.go @@ -457,8 +457,6 @@ func TestProviderFieldSync(t *testing.T) { "SessionIDFlag": "internal session-id config, not patched", "PrintArgs": "internal print-mode args, not patched", "TitleModel": "internal title-model key, not patched", - "ACPCommand": "ACP transport override, not patched (set via builtin or city.toml)", - "ACPArgs": "ACP transport override, not patched (set via builtin or city.toml)", } // Fields on ProviderPatch that don't map 1:1 to ProviderSpec. diff --git a/internal/config/launch_command_test.go b/internal/config/launch_command_test.go index e6f9fc1fb3..befc7e73ca 100644 --- a/internal/config/launch_command_test.go +++ b/internal/config/launch_command_test.go @@ -78,9 +78,8 @@ func TestBuildProviderLaunchCommandIgnoresInitialMessageOverride(t *testing.T) { func TestBuildProviderLaunchCommandUsesACPCommand(t *testing.T) { rp := &ResolvedProvider{ - Command: "opencode", - ACPCommand: "opencode", - ACPArgs: []string{"acp"}, + Command: "custom-opencode", + ACPArgs: []string{"acp"}, } t.Run("acp transport uses ACPCommandString", func(t *testing.T) { @@ -88,7 +87,7 @@ func TestBuildProviderLaunchCommandUsesACPCommand(t *testing.T) { if err != nil { t.Fatalf("BuildProviderLaunchCommand: %v", err) } - want := "opencode acp" + want := "custom-opencode acp" if got.Command != want { t.Fatalf("Command = %q, want %q", got.Command, want) } @@ -99,7 +98,7 @@ func TestBuildProviderLaunchCommandUsesACPCommand(t *testing.T) { if err != nil { t.Fatalf("BuildProviderLaunchCommand: %v", err) } - want := "opencode" + want := "custom-opencode" if got.Command != want { t.Fatalf("Command = %q, want %q", got.Command, want) } diff --git a/internal/config/patch.go b/internal/config/patch.go index 2b3afbdebf..3a84559fbd 100644 --- a/internal/config/patch.go +++ b/internal/config/patch.go @@ -180,8 +180,12 @@ type ProviderPatch struct { Base **string `toml:"base,omitempty"` // Command overrides the provider command. Command *string `toml:"command,omitempty"` + // ACPCommand overrides the provider command for ACP transport sessions. + ACPCommand *string `toml:"acp_command,omitempty"` // Args overrides the provider args. Args []string `toml:"args,omitempty"` + // ACPArgs overrides the provider args for ACP transport sessions. + ACPArgs []string `toml:"acp_args,omitempty"` // ArgsAppend overrides the provider args_append list. ArgsAppend []string `toml:"args_append,omitempty"` // OptionsSchemaMerge overrides the options_schema merge mode. @@ -451,10 +455,17 @@ func applyProviderPatch(cfg *City, patch *ProviderPatch) error { if patch.Command != nil { newSpec.Command = *patch.Command } + if patch.ACPCommand != nil { + newSpec.ACPCommand = *patch.ACPCommand + } if len(patch.Args) > 0 { newSpec.Args = make([]string, len(patch.Args)) copy(newSpec.Args, patch.Args) } + if patch.ACPArgs != nil { + newSpec.ACPArgs = make([]string, len(patch.ACPArgs)) + copy(newSpec.ACPArgs, patch.ACPArgs) + } if len(patch.ArgsAppend) > 0 { newSpec.ArgsAppend = make([]string, len(patch.ArgsAppend)) copy(newSpec.ArgsAppend, patch.ArgsAppend) @@ -487,10 +498,17 @@ func applyProviderPatch(cfg *City, patch *ProviderPatch) error { if patch.Command != nil { spec.Command = *patch.Command } + if patch.ACPCommand != nil { + spec.ACPCommand = *patch.ACPCommand + } if len(patch.Args) > 0 { spec.Args = make([]string, len(patch.Args)) copy(spec.Args, patch.Args) } + if patch.ACPArgs != nil { + spec.ACPArgs = make([]string, len(patch.ACPArgs)) + copy(spec.ACPArgs, patch.ACPArgs) + } if len(patch.ArgsAppend) > 0 { spec.ArgsAppend = make([]string, len(patch.ArgsAppend)) copy(spec.ArgsAppend, patch.ArgsAppend) diff --git a/internal/config/patch_test.go b/internal/config/patch_test.go index 7e42789d6a..e6277effb4 100644 --- a/internal/config/patch_test.go +++ b/internal/config/patch_test.go @@ -260,6 +260,8 @@ func TestApplyPatches_ProviderDeepMerge(t *testing.T) { Providers: map[string]ProviderSpec{ "custom": { Command: "agent", + ACPCommand: "agent-acp", + ACPArgs: []string{"serve"}, PromptMode: "arg", Env: map[string]string{"KEY": "val"}, }, @@ -268,10 +270,12 @@ func TestApplyPatches_ProviderDeepMerge(t *testing.T) { err := ApplyPatches(cfg, Patches{ Providers: []ProviderPatch{ { - Name: "custom", - Command: ptrStr("new-agent"), - Env: map[string]string{"KEY2": "val2"}, - EnvRemove: []string{"KEY"}, + Name: "custom", + Command: ptrStr("new-agent"), + ACPCommand: ptrStr("new-agent-acp"), + ACPArgs: []string{"rpc", "--stdio"}, + Env: map[string]string{"KEY2": "val2"}, + EnvRemove: []string{"KEY"}, }, }, }) @@ -282,6 +286,12 @@ func TestApplyPatches_ProviderDeepMerge(t *testing.T) { if p.Command != "new-agent" { t.Errorf("Command = %q, want %q", p.Command, "new-agent") } + if p.ACPCommand != "new-agent-acp" { + t.Errorf("ACPCommand = %q, want %q", p.ACPCommand, "new-agent-acp") + } + if got := strings.Join(p.ACPArgs, " "); got != "rpc --stdio" { + t.Errorf("ACPArgs = %q, want %q", got, "rpc --stdio") + } if p.PromptMode != "arg" { t.Errorf("PromptMode = %q, want %q (unchanged)", p.PromptMode, "arg") } @@ -298,6 +308,8 @@ func TestApplyPatches_ProviderReplace(t *testing.T) { Providers: map[string]ProviderSpec{ "custom": { Command: "old-agent", + ACPCommand: "old-agent-acp", + ACPArgs: []string{"serve"}, PromptMode: "arg", Env: map[string]string{"SECRET": "hidden"}, }, @@ -306,9 +318,11 @@ func TestApplyPatches_ProviderReplace(t *testing.T) { err := ApplyPatches(cfg, Patches{ Providers: []ProviderPatch{ { - Name: "custom", - Replace: true, - Command: ptrStr("new-agent"), + Name: "custom", + Replace: true, + Command: ptrStr("new-agent"), + ACPCommand: ptrStr("new-agent-acp"), + ACPArgs: []string{"rpc"}, }, }, }) @@ -319,6 +333,12 @@ func TestApplyPatches_ProviderReplace(t *testing.T) { if p.Command != "new-agent" { t.Errorf("Command = %q, want %q", p.Command, "new-agent") } + if p.ACPCommand != "new-agent-acp" { + t.Errorf("ACPCommand = %q, want %q", p.ACPCommand, "new-agent-acp") + } + if got := strings.Join(p.ACPArgs, " "); got != "rpc" { + t.Errorf("ACPArgs = %q, want %q", got, "rpc") + } // Replace clears fields not in patch. if p.PromptMode != "" { t.Errorf("PromptMode = %q, want empty (replaced)", p.PromptMode) diff --git a/internal/config/provider.go b/internal/config/provider.go index 32682a7e2a..60b3bc1d9e 100644 --- a/internal/config/provider.go +++ b/internal/config/provider.go @@ -228,6 +228,15 @@ func (rp *ResolvedProvider) ACPCommandString() string { return cmd + " " + shellquote.Join(args) } +// DefaultSessionTransport returns the transport used for provider-backed +// sessions when no template-level session override exists. +func (rp *ResolvedProvider) DefaultSessionTransport() string { + if rp != nil && rp.SupportsACP { + return "acp" + } + return "" +} + // TitleModelFlagArgs resolves the TitleModel key against the "model" // OptionsSchema entry. Returns the CLI flag args for the title model, // or nil if TitleModel is empty or not found in the schema. diff --git a/internal/config/provider_test.go b/internal/config/provider_test.go index 888db6926c..e6585e4a24 100644 --- a/internal/config/provider_test.go +++ b/internal/config/provider_test.go @@ -1,6 +1,7 @@ package config import ( + "reflect" "testing" ) @@ -185,6 +186,12 @@ func TestBuiltinProvidersOpenCode(t *testing.T) { if p.Command != "opencode" { t.Errorf("Command = %q, want %q", p.Command, "opencode") } + if p.ACPCommand != "" { + t.Errorf("ACPCommand = %q, want empty fallback to Command", p.ACPCommand) + } + if !reflect.DeepEqual(p.ACPArgs, []string{"acp"}) { + t.Errorf("ACPArgs = %v, want [acp]", p.ACPArgs) + } if p.PromptMode != "none" { t.Errorf("PromptMode = %q, want %q", p.PromptMode, "none") } diff --git a/internal/config/resolve_test.go b/internal/config/resolve_test.go index 8b5424a6ab..175270a04f 100644 --- a/internal/config/resolve_test.go +++ b/internal/config/resolve_test.go @@ -860,6 +860,31 @@ func TestMergeProviderOverBuiltin(t *testing.T) { } } +func TestResolveProviderBuiltinOpenCodeCustomCommandKeepsACPArgsOnCustomBinary(t *testing.T) { + base := "builtin:opencode" + cityProviders := map[string]ProviderSpec{ + "custom-opencode": { + Base: &base, + Command: "custom-opencode", + }, + } + agent := &Agent{Name: "worker", Provider: "custom-opencode"} + + rp, err := ResolveProvider(agent, nil, cityProviders, lookPathOnly("custom-opencode")) + if err != nil { + t.Fatalf("ResolveProvider: %v", err) + } + if rp.Command != "custom-opencode" { + t.Fatalf("Command = %q, want custom-opencode", rp.Command) + } + if rp.ACPCommand != "" { + t.Fatalf("ACPCommand = %q, want empty fallback to Command", rp.ACPCommand) + } + if got := rp.ACPCommandString(); got != "custom-opencode acp" { + t.Fatalf("ACPCommandString() = %q, want custom-opencode acp", got) + } +} + // --- Tri-state capability bool tests --- // // These verify the three-way *bool semantics for SupportsHooks, diff --git a/internal/session/manager.go b/internal/session/manager.go index b2e70d00f7..7fd835c6c6 100644 --- a/internal/session/manager.go +++ b/internal/session/manager.go @@ -65,6 +65,7 @@ type Info struct { Title string Alias string Provider string + Transport string Command string // resolved command stored at creation WorkDir string SessionName string // tmux session name @@ -158,12 +159,27 @@ func (m *Manager) transportForBead(b beads.Bead, sessName string) (string, bool) if transport != "" { return transport, false } + if strings.TrimSpace(b.Metadata["pending_create_claim"]) == "true" { + if m.transportResolver != nil { + transport = normalizeTransport(b.Metadata["provider"], m.transportResolver(strings.TrimSpace(b.Metadata["template"]))) + if transport != "" { + return transport, true + } + } + return "", false + } if detector, ok := m.sp.(transportDetector); ok { transport = normalizeTransport(b.Metadata["provider"], detector.DetectTransport(sessName)) if transport != "" { return transport, true } } + if m.sp != nil && m.sp.IsRunning(sessName) { + return "", false + } + // Stopped legacy sessions without persisted transport metadata must keep + // their stored runtime semantics. Only pending-create beads use config + // inference because they have not materialized yet. return "", false } @@ -1138,8 +1154,9 @@ func (m *Manager) infoFromBead(b beads.Bead) Info { sessName = sessionNameFor(b.ID) } closed := b.Status == "closed" + transport := transportFromMetadata(b) if !closed { - transport, _ := m.transportForBead(b, sessName) + transport, _ = m.transportForBead(b, sessName) _ = m.routeACPIfNeeded(b.Metadata["provider"], transport, sessName) } @@ -1160,6 +1177,7 @@ func (m *Manager) infoFromBead(b beads.Bead) Info { Title: b.Title, Alias: b.Metadata["alias"], Provider: b.Metadata["provider"], + Transport: transport, Command: b.Metadata["command"], WorkDir: b.Metadata["work_dir"], SessionName: sessName, diff --git a/internal/session/manager_test.go b/internal/session/manager_test.go index 21eb116f10..96ea3f1074 100644 --- a/internal/session/manager_test.go +++ b/internal/session/manager_test.go @@ -2166,6 +2166,158 @@ func TestGetDoesNotPersistGuessedTransportForLegacySession(t *testing.T) { } } +func TestGetUsesConfiguredTransportForPendingCreateWithoutRuntimeProbe(t *testing.T) { + store := beads.NewMemStore() + sp := runtime.NewFake() + + deferred, err := store.Create(beads.Bead{ + Title: "deferred acp", + Type: BeadType, + Labels: []string{ + LabelSession, + "template:helper", + }, + Metadata: map[string]string{ + "template": "helper", + "state": string(StateCreating), + "pending_create_claim": "true", + "provider": "claude", + "work_dir": "/tmp", + "command": "claude", + }, + }) + if err != nil { + t.Fatalf("Create deferred bead: %v", err) + } + + mgr := NewManagerWithTransportResolver(store, sp, func(template string) string { + if template == "helper" { + return "acp" + } + return "" + }) + + info, err := mgr.Get(deferred.ID) + if err != nil { + t.Fatalf("Get: %v", err) + } + if got := info.Transport; got != "acp" { + t.Fatalf("Transport = %q, want acp", got) + } + if len(sp.Calls) != 0 { + t.Fatalf("runtime calls = %#v, want none for pending create", sp.Calls) + } +} + +func TestGetPrefersLiveTransportDetectionOverConfiguredTransportInference(t *testing.T) { + store := beads.NewMemStore() + defaultSP := runtime.NewFake() + acpSP := runtime.NewFake() + autoSP := sessionauto.New(defaultSP, acpSP) + + legacy, err := store.Create(beads.Bead{ + Title: "legacy tmux", + Type: BeadType, + Labels: []string{ + LabelSession, + "template:helper", + }, + Metadata: map[string]string{ + "template": "helper", + "state": string(StateActive), + "provider": "claude", + "work_dir": "/tmp", + "command": "claude", + }, + }) + if err != nil { + t.Fatalf("Create legacy bead: %v", err) + } + sessName := sessionNameFor(legacy.ID) + if err := store.SetMetadata(legacy.ID, "session_name", sessName); err != nil { + t.Fatalf("SetMetadata(session_name): %v", err) + } + if err := defaultSP.Start(context.Background(), sessName, runtime.Config{WorkDir: "/tmp"}); err != nil { + t.Fatalf("Start default session: %v", err) + } + + mgr := NewManagerWithTransportResolver(store, autoSP, func(template string) string { + if template == "helper" { + return "acp" + } + return "" + }) + + info, err := mgr.Get(legacy.ID) + if err != nil { + t.Fatalf("Get: %v", err) + } + if got := info.Transport; got != "" { + t.Fatalf("Transport = %q, want empty for live tmux session", got) + } + + updated, err := store.Get(legacy.ID) + if err != nil { + t.Fatalf("Get updated bead: %v", err) + } + if got := updated.Metadata["transport"]; got != "" { + t.Fatalf("transport metadata = %q, want empty for live tmux session", got) + } +} + +func TestGetDoesNotInferConfiguredTransportForStoppedLegacySession(t *testing.T) { + store := beads.NewMemStore() + defaultSP := runtime.NewFake() + acpSP := runtime.NewFake() + autoSP := sessionauto.New(defaultSP, acpSP) + + legacy, err := store.Create(beads.Bead{ + Title: "legacy tmux", + Type: BeadType, + Labels: []string{ + LabelSession, + "template:helper", + }, + Metadata: map[string]string{ + "template": "helper", + "state": string(StateAsleep), + "provider": "claude", + "work_dir": "/tmp", + "command": "claude", + }, + }) + if err != nil { + t.Fatalf("Create legacy bead: %v", err) + } + sessName := sessionNameFor(legacy.ID) + if err := store.SetMetadata(legacy.ID, "session_name", sessName); err != nil { + t.Fatalf("SetMetadata(session_name): %v", err) + } + + mgr := NewManagerWithTransportResolver(store, autoSP, func(template string) string { + if template == "helper" { + return "acp" + } + return "" + }) + + info, err := mgr.Get(legacy.ID) + if err != nil { + t.Fatalf("Get: %v", err) + } + if got := info.Transport; got != "" { + t.Fatalf("Transport = %q, want empty for stopped legacy tmux session", got) + } + + updated, err := store.Get(legacy.ID) + if err != nil { + t.Fatalf("Get updated bead: %v", err) + } + if got := updated.Metadata["transport"]; got != "" { + t.Fatalf("transport metadata = %q, want empty for stopped legacy tmux session", got) + } +} + func TestSendConvergesWhenSessionAlreadyResumed(t *testing.T) { store := beads.NewMemStore() sp := runtime.NewFake() diff --git a/internal/worker/builtin/profiles.go b/internal/worker/builtin/profiles.go index 1472e79376..7638b18492 100644 --- a/internal/worker/builtin/profiles.go +++ b/internal/worker/builtin/profiles.go @@ -304,7 +304,6 @@ var builtinProviderSpecs = map[string]BuiltinProviderSpec{ SupportsACP: true, SupportsHooks: true, InstructionsFile: "AGENTS.md", - ACPCommand: "opencode", ACPArgs: []string{"acp"}, }, "auggie": { From de95cc48889ebdd5a21ab1430f5b7697e15ff16c Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 00:57:52 +0000 Subject: [PATCH 47/85] fix: propagate ACP MCP session config --- cmd/gc/mcp_integration.go | 21 +-- cmd/gc/providers.go | 50 ++++++- cmd/gc/providers_test.go | 6 +- cmd/gc/template_resolve.go | 6 + cmd/gc/worker_handle.go | 57 +++++++- cmd/gc/worker_handle_test.go | 16 +++ internal/api/handler_session_create.go | 10 +- .../api/huma_handlers_sessions_command.go | 6 +- internal/api/session_resolution.go | 6 +- internal/api/session_resolved_config.go | 4 +- internal/api/session_resolved_config_test.go | 2 + internal/api/session_runtime.go | 64 ++++++++- internal/api/worker_factory_test.go | 49 +++++++ internal/materialize/mcp_runtime.go | 127 ++++++++++++++++++ internal/runtime/acp/acp.go | 6 +- internal/runtime/acp/protocol.go | 113 +++++++++++++++- internal/runtime/acp/protocol_test.go | 84 +++++++++++- internal/runtime/fingerprint.go | 28 ++++ internal/runtime/fingerprint_test.go | 36 +++++ internal/runtime/mcp.go | 77 +++++++++++ internal/runtime/runtime.go | 4 + internal/worker/handle_clone.go | 1 + 22 files changed, 731 insertions(+), 42 deletions(-) create mode 100644 internal/materialize/mcp_runtime.go create mode 100644 internal/runtime/mcp.go diff --git a/cmd/gc/mcp_integration.go b/cmd/gc/mcp_integration.go index 03b7ed518a..906964f2b6 100644 --- a/cmd/gc/mcp_integration.go +++ b/cmd/gc/mcp_integration.go @@ -38,24 +38,6 @@ type resolvedMCPProjection struct { Projection materialize.MCPProjection } -func buildMCPTemplateData(cityPath, qualifiedName, workDir string, agent *config.Agent, rigs []config.Rig) map[string]string { - rigName := configuredRigName(cityPath, agent, rigs) - rigRoot := rigRootForName(rigName, rigs) - return buildTemplateData(PromptContext{ - CityRoot: cityPath, - AgentName: qualifiedName, - TemplateName: templateNameFor(agent, qualifiedName), - RigName: rigName, - RigRoot: rigRoot, - WorkDir: workDir, - IssuePrefix: findRigPrefix(rigName, rigs), - DefaultBranch: defaultBranchFor(workDir), - WorkQuery: agent.EffectiveWorkQuery(), - SlingQuery: agent.EffectiveSlingQuery(), - Env: agent.Env, - }) -} - func supportsMCPProviderKind(kind string) bool { switch strings.TrimSpace(kind) { case materialize.MCPProviderClaude, materialize.MCPProviderCodex, materialize.MCPProviderGemini: @@ -71,8 +53,7 @@ func loadEffectiveMCPForAgent( agent *config.Agent, qualifiedName, workDir string, ) (materialize.MCPCatalog, error) { - templateData := buildMCPTemplateData(cityPath, qualifiedName, workDir, agent, cfg.Rigs) - catalog, err := materialize.EffectiveMCPForAgent(cfg, agent, templateData) + catalog, err := materialize.EffectiveMCPForSession(cfg, cityPath, agent, qualifiedName, workDir) if err != nil { return materialize.MCPCatalog{}, fmt.Errorf("loading effective MCP: %w", err) } diff --git a/cmd/gc/providers.go b/cmd/gc/providers.go index 02d54eeaef..5d9bb161a3 100644 --- a/cmd/gc/providers.go +++ b/cmd/gc/providers.go @@ -238,7 +238,55 @@ func configuredACPSessionNames(snapshot *sessionBeadSnapshot, cityName, sessionT } func needsACPProviderWrapper(snapshot *sessionBeadSnapshot, cfg *config.City) bool { - return len(observedACPSessionNames(snapshot)) > 0 || (cfg != nil && hasACPAgents(cfg.Agents)) + if len(observedACPSessionNames(snapshot)) > 0 { + return true + } + if cfg == nil { + return false + } + return hasACPAgents(cfg.Agents) || hasACPProviderTargets(cfg) +} + +func hasACPProviderTargets(cfg *config.City) bool { + if cfg == nil { + return false + } + candidates := map[string]bool{} + add := func(name string) { + name = strings.TrimSpace(name) + if name != "" { + candidates[name] = true + } + } + add(cfg.Workspace.Provider) + for name := range cfg.Providers { + add(name) + } + for _, agentCfg := range cfg.Agents { + add(agentCfg.Provider) + } + for name := range candidates { + if providerSupportsACP(cfg, name) { + return true + } + } + return false +} + +func providerSupportsACP(cfg *config.City, providerName string) bool { + if cfg == nil || strings.TrimSpace(providerName) == "" { + return false + } + resolved, err := config.ResolveProvider( + &config.Agent{Provider: providerName}, + &cfg.Workspace, + cfg.Providers, + func(name string) (string, error) { return name, nil }, + ) + if err != nil { + return false + } + return resolved.DefaultSessionTransport() == "acp" } func observedACPSessionNames(snapshot *sessionBeadSnapshot) []string { diff --git a/cmd/gc/providers_test.go b/cmd/gc/providers_test.go index 152161c000..a35a99284c 100644 --- a/cmd/gc/providers_test.go +++ b/cmd/gc/providers_test.go @@ -384,7 +384,7 @@ func TestNewSessionProvider_PreregistersACPNamedSessionRuntimeName(t *testing.T) } } -func TestNewSessionProviderDoesNotWrapUnusedACPDefaultProvidersWithoutACPAgents(t *testing.T) { +func TestNewSessionProviderWrapsACPProvidersWithoutACPAgents(t *testing.T) { ctx := sessionProviderContextForCity(&config.City{ Workspace: config.Workspace{ Name: "test-city", @@ -402,8 +402,8 @@ func TestNewSessionProviderDoesNotWrapUnusedACPDefaultProvidersWithoutACPAgents( }, t.TempDir(), "fake") sp := newSessionProviderFromContext(ctx, nil) - if _, ok := sp.(interface{ RouteACP(string) }); ok { - t.Fatalf("provider = %T, want plain provider without ACP routing", sp) + if _, ok := sp.(interface{ RouteACP(string) }); !ok { + t.Fatalf("provider = %T, want ACP-routing wrapper", sp) } } diff --git a/cmd/gc/template_resolve.go b/cmd/gc/template_resolve.go index 6d1c25645e..94dc2c5320 100644 --- a/cmd/gc/template_resolve.go +++ b/cmd/gc/template_resolve.go @@ -103,6 +103,9 @@ type TemplateParams struct { // identity-stamped templates (pool workers, dependency floors) from the // resolver's default stamping on ordinary sessions. EnvIdentityStamped bool + // MCPServers is the effective ACP session/new MCP server set for this + // concrete session context. + MCPServers []runtime.MCPServerConfig } // DisplayName returns the name to use for log messages and event subjects. @@ -473,6 +476,7 @@ func resolveTemplate(p *agentBuildParams, cfgAgent *config.Agent, qualifiedName ) } } + mcpServers := materialize.RuntimeMCPServers(mcpCatalog.Servers) // Step 12: Build startup hints. hints := agent.StartupHints{ @@ -509,6 +513,7 @@ func resolveTemplate(p *agentBuildParams, cfgAgent *config.Agent, qualifiedName WakeMode: cfgAgent.WakeMode, IsACP: cfgAgent.Session == "acp", HookEnabled: hasHooks, + MCPServers: mcpServers, }, nil } @@ -583,6 +588,7 @@ func templateParamsToConfig(tp TemplateParams) runtime.Config { PromptSuffix: promptSuffix, PromptFlag: promptFlag, Env: env, + MCPServers: tp.MCPServers, WorkDir: tp.WorkDir, ReadyPromptPrefix: tp.Hints.ReadyPromptPrefix, ReadyDelayMs: tp.Hints.ReadyDelayMs, diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index b9bd1fc815..25ae68b6a8 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -9,6 +9,7 @@ import ( "github.com/gastownhall/gascity/internal/beads" "github.com/gastownhall/gascity/internal/config" + "github.com/gastownhall/gascity/internal/materialize" "github.com/gastownhall/gascity/internal/runtime" "github.com/gastownhall/gascity/internal/session" "github.com/gastownhall/gascity/internal/worker" @@ -77,6 +78,40 @@ func workerSessionCreateHints(resolved *config.ResolvedProvider) runtime.Config } } +func resolvedRuntimeMCPServersWithConfig( + cityPath string, + cfg *config.City, + alias, template, provider, workDir string, + metadata map[string]string, +) ([]runtime.MCPServerConfig, error) { + if cfg == nil || strings.TrimSpace(workDir) == "" { + return nil, nil + } + identity := strings.TrimSpace(metadata["agent_name"]) + if identity == "" { + identity = strings.TrimSpace(alias) + } + if identity == "" { + identity = strings.TrimSpace(template) + } + if identity == "" { + identity = strings.TrimSpace(provider) + } + if agentCfg := findAgentByTemplate(cfg, template); agentCfg != nil { + catalog, err := materialize.EffectiveMCPForSession(cfg, cityPath, agentCfg, identity, workDir) + if err != nil { + return nil, fmt.Errorf("loading effective MCP: %w", err) + } + return materialize.RuntimeMCPServers(catalog.Servers), nil + } + synthetic := &config.Agent{Provider: provider} + catalog, err := materialize.EffectiveMCPForSession(cfg, cityPath, synthetic, identity, workDir) + if err != nil { + return nil, fmt.Errorf("loading effective MCP: %w", err) + } + return materialize.RuntimeMCPServers(catalog.Servers), nil +} + func newWorkerSessionHandleForResolvedRuntimeWithConfig( cityPath string, store beads.Store, @@ -90,6 +125,10 @@ func newWorkerSessionHandleForResolvedRuntimeWithConfig( if err != nil { return nil, err } + mcpServers, err := resolvedRuntimeMCPServersWithConfig(cityPath, cfg, alias, template, provider, workDir, metadata) + if err != nil { + return nil, err + } sessionCfg, err := resolvedWorkerSessionConfigWithConfig( command, provider, @@ -101,6 +140,7 @@ func newWorkerSessionHandleForResolvedRuntimeWithConfig( transport, resolved, metadata, + mcpServers, ) if err != nil { return nil, err @@ -119,6 +159,7 @@ func resolvedWorkerSessionConfigWithConfig( transport string, resolved *config.ResolvedProvider, metadata map[string]string, + mcpServers []runtime.MCPServerConfig, ) (worker.ResolvedSessionConfig, error) { if resolved == nil { return worker.ResolvedSessionConfig{}, fmt.Errorf("resolved provider is required") @@ -156,7 +197,11 @@ func resolvedWorkerSessionConfigWithConfig( ResumeCommand: resolved.ResumeCommand, SessionIDFlag: resolved.SessionIDFlag, }, - Hints: workerSessionCreateHints(resolved), + Hints: func() runtime.Config { + hints := workerSessionCreateHints(resolved) + hints.MCPServers = mcpServers + return hints + }(), }, }) } @@ -344,6 +389,15 @@ func resolvedWorkerRuntimeWithConfig(cityPath string, cfg *config.City, info ses if workDir == "" { workDir = cityPath } + mcpServers, _ := resolvedRuntimeMCPServersWithConfig( + cityPath, + cfg, + info.Alias, + info.Template, + firstNonEmptyGCString(info.Provider, resolved.Name, info.Template), + workDir, + nil, + ) return &worker.ResolvedRuntime{ Command: command, WorkDir: workDir, @@ -355,6 +409,7 @@ func resolvedWorkerRuntimeWithConfig(cityPath string, cfg *config.City, info ses ReadyDelayMs: resolved.ReadyDelayMs, ProcessNames: resolved.ProcessNames, EmitsPermissionWarning: resolved.EmitsPermissionWarning, + MCPServers: mcpServers, }, Resume: session.ProviderResume{ ResumeFlag: firstNonEmptyGCString(resolved.ResumeFlag, info.ResumeFlag), diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index 2ab8dbc0f8..8a9f86b95b 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -217,6 +217,14 @@ command = "/bin/echo" supports_acp = true acp_command = "/bin/echo" acp_args = ["acp"] +`) + writeCatalogFile(t, cityDir, "mcp/filesystem.toml", ` +name = "filesystem" +command = "/bin/mcp" +args = ["--stdio"] + +[env] +TOKEN = "abc" `) cfg, err := loadCityConfig(cityDir) @@ -236,6 +244,12 @@ acp_args = ["acp"] if got, want := resolved.Command, "/bin/echo acp"; got != want { t.Fatalf("Command = %q, want %q", got, want) } + if len(resolved.Hints.MCPServers) != 1 { + t.Fatalf("Hints.MCPServers len = %d, want 1", len(resolved.Hints.MCPServers)) + } + if got, want := resolved.Hints.MCPServers[0].Name, "filesystem"; got != want { + t.Fatalf("Hints.MCPServers[0].Name = %q, want %q", got, want) + } } func TestResolvedWorkerRuntimeWithConfigKeepsDefaultTransportWithoutStoredTemplateACPMetadata(t *testing.T) { @@ -625,6 +639,7 @@ func TestResolvedWorkerSessionConfigWithConfigFallsBackToResolvedProviderNameFor Name: "custom-provider", }, map[string]string{"session_origin": "test"}, + nil, ) if err != nil { t.Fatalf("resolvedWorkerSessionConfigWithConfig: %v", err) @@ -649,6 +664,7 @@ func TestResolvedWorkerSessionConfigWithConfigFallsBackToProviderArgForCommand(t "", &config.ResolvedProvider{}, nil, + nil, ) if err != nil { t.Fatalf("resolvedWorkerSessionConfigWithConfig: %v", err) diff --git a/internal/api/handler_session_create.go b/internal/api/handler_session_create.go index 9b71d10f4a..a2adad4f4d 100644 --- a/internal/api/handler_session_create.go +++ b/internal/api/handler_session_create.go @@ -143,7 +143,7 @@ func (s *Server) handleSessionCreate(w http.ResponseWriter, r *http.Request) { // starts the agent process on the next tick. This avoids blocking the // HTTP response for 10-30s while the agent boots in tmux, and lets MC // show the session in the sidebar immediately via optimistic UI. - resolvedCfg, err := resolvedSessionConfigForProvider(alias, "", template, title, transport, extraMeta, resolved, command, workDir) + resolvedCfg, err := resolvedSessionConfigForProvider(alias, "", template, title, transport, extraMeta, resolved, command, workDir, nil) if err != nil { s.idem.unreserve(idemKey) writeSessionManagerError(w, err) @@ -286,10 +286,16 @@ func (s *Server) createProviderSession(w http.ResponseWriter, r *http.Request, s return } command := launchCommand.Command + mcpServers, err := s.providerSessionMCPServers(providerName, workDir) + if err != nil { + s.idem.unreserve(idemKey) + writeError(w, http.StatusInternalServerError, "internal", err.Error()) + return + } resolvedCfg, err := resolvedSessionConfigForProvider(alias, "", template, title, transport, map[string]string{ "session_origin": "manual", - }, resolved, command, workDir) + }, resolved, command, workDir, mcpServers) if err != nil { s.idem.unreserve(idemKey) writeSessionManagerError(w, err) diff --git a/internal/api/huma_handlers_sessions_command.go b/internal/api/huma_handlers_sessions_command.go index 7d506980c6..db1c436d7e 100644 --- a/internal/api/huma_handlers_sessions_command.go +++ b/internal/api/huma_handlers_sessions_command.go @@ -240,9 +240,13 @@ func (s *Server) humaCreateProviderSession(ctx context.Context, store beads.Stor return nil, huma.Error400BadRequest(err.Error()) } command := launchCommand.Command + mcpServers, err := s.providerSessionMCPServers(resolved.Name, workDir) + if err != nil { + return nil, huma.Error500InternalServerError(err.Error()) + } mgr := s.sessionManager(store) - hints := sessionCreateHints(resolved) + hints := sessionCreateHints(resolved, mcpServers) var info session.Info err = session.WithCitySessionAliasLock(s.state.CityPath(), alias, func() error { if aliasErr := session.EnsureAliasAvailableWithConfig(store, s.state.Config(), alias, ""); aliasErr != nil { diff --git a/internal/api/session_resolution.go b/internal/api/session_resolution.go index 7a79941c50..8b524ffcbe 100644 --- a/internal/api/session_resolution.go +++ b/internal/api/session_resolution.go @@ -308,7 +308,11 @@ func (s *Server) materializeNamedSessionWithContext(ctx context.Context, store b if resolved.BuiltinAncestor != "" && resolved.BuiltinAncestor != resolved.Name { extraMeta["builtin_ancestor"] = resolved.BuiltinAncestor } - hints := sessionCreateHints(resolved) + mcpServers, err := s.sessionMCPServers(qualifiedTemplate, resolved.Name, spec.Identity, workDir, "") + if err != nil { + return "", err + } + hints := sessionCreateHints(resolved, mcpServers) var info session.Info err = session.WithCitySessionIdentifierLocks(s.state.CityPath(), []string{spec.Identity, spec.SessionName}, func() error { if err := session.EnsureAliasAvailableWithConfigForOwner(store, s.state.Config(), spec.Identity, "", spec.Identity); err != nil { diff --git a/internal/api/session_resolved_config.go b/internal/api/session_resolved_config.go index dfb712a2c2..3687dad19d 100644 --- a/internal/api/session_resolved_config.go +++ b/internal/api/session_resolved_config.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/gastownhall/gascity/internal/config" + "github.com/gastownhall/gascity/internal/runtime" "github.com/gastownhall/gascity/internal/session" "github.com/gastownhall/gascity/internal/worker" ) @@ -13,6 +14,7 @@ func resolvedSessionConfigForProvider( metadata map[string]string, resolved *config.ResolvedProvider, command, workDir string, + mcpServers []runtime.MCPServerConfig, ) (worker.ResolvedSessionConfig, error) { if resolved == nil { return worker.ResolvedSessionConfig{}, fmt.Errorf("%w: resolved provider is required", worker.ErrHandleConfig) @@ -41,7 +43,7 @@ func resolvedSessionConfigForProvider( ResumeCommand: resolved.ResumeCommand, SessionIDFlag: resolved.SessionIDFlag, }, - Hints: sessionCreateHints(resolved), + Hints: sessionCreateHints(resolved, mcpServers), }, }) } diff --git a/internal/api/session_resolved_config_test.go b/internal/api/session_resolved_config_test.go index f1a84578d2..42a898b362 100644 --- a/internal/api/session_resolved_config_test.go +++ b/internal/api/session_resolved_config_test.go @@ -33,6 +33,7 @@ func TestResolvedSessionConfigForProviderBuildsNormalizedConfig(t *testing.T) { resolved, "", "/tmp/workdir", + nil, ) if err != nil { t.Fatalf("resolvedSessionConfigForProvider: %v", err) @@ -78,6 +79,7 @@ func TestResolvedSessionConfigForProviderRejectsNilProvider(t *testing.T) { nil, "", "/tmp/workdir", + nil, ); err == nil { t.Fatal("resolvedSessionConfigForProvider() error = nil, want error") } diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index b6a71ed078..4d0d74949a 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/gastownhall/gascity/internal/config" + "github.com/gastownhall/gascity/internal/materialize" "github.com/gastownhall/gascity/internal/runtime" "github.com/gastownhall/gascity/internal/session" workdirutil "github.com/gastownhall/gascity/internal/workdir" @@ -24,16 +25,17 @@ func (s *Server) sessionLogPaths() []string { return worker.MergeSearchPaths(cfg.Daemon.ObservePaths) } -func sessionCreateHints(resolved *config.ResolvedProvider) runtime.Config { +func sessionCreateHints(resolved *config.ResolvedProvider, mcpServers []runtime.MCPServerConfig) runtime.Config { return runtime.Config{ ReadyPromptPrefix: resolved.ReadyPromptPrefix, ReadyDelayMs: resolved.ReadyDelayMs, ProcessNames: resolved.ProcessNames, EmitsPermissionWarning: resolved.EmitsPermissionWarning, + MCPServers: mcpServers, } } -func sessionResumeHints(resolved *config.ResolvedProvider, workDir string) runtime.Config { +func sessionResumeHints(resolved *config.ResolvedProvider, workDir string, mcpServers []runtime.MCPServerConfig) runtime.Config { return runtime.Config{ WorkDir: workDir, ReadyPromptPrefix: resolved.ReadyPromptPrefix, @@ -41,9 +43,46 @@ func sessionResumeHints(resolved *config.ResolvedProvider, workDir string) runti ProcessNames: resolved.ProcessNames, EmitsPermissionWarning: resolved.EmitsPermissionWarning, Env: resolved.Env, + MCPServers: mcpServers, } } +func (s *Server) providerSessionMCPServers(providerName, workDir string) ([]runtime.MCPServerConfig, error) { + cfg := s.state.Config() + if cfg == nil || strings.TrimSpace(workDir) == "" { + return nil, nil + } + synthetic := &config.Agent{Provider: providerName} + catalog, err := materialize.EffectiveMCPForSession(cfg, s.state.CityPath(), synthetic, providerName, workDir) + if err != nil { + return nil, fmt.Errorf("loading effective MCP: %w", err) + } + return materialize.RuntimeMCPServers(catalog.Servers), nil +} + +func (s *Server) sessionMCPServers(template, providerName, identity, workDir, sessionKind string) ([]runtime.MCPServerConfig, error) { + cfg := s.state.Config() + if cfg == nil || strings.TrimSpace(workDir) == "" { + return nil, nil + } + if sessionKind != "provider" { + if agentCfg, ok := resolveSessionTemplateAgent(cfg, template); ok { + catalog, err := materialize.EffectiveMCPForSession( + cfg, + s.state.CityPath(), + &agentCfg, + firstNonEmptyString(identity, template), + workDir, + ) + if err != nil { + return nil, fmt.Errorf("loading effective MCP: %w", err) + } + return materialize.RuntimeMCPServers(catalog.Servers), nil + } + } + return s.providerSessionMCPServers(firstNonEmptyString(providerName, template), workDir) +} + func sessionExplicitNameForCreate(agentCfg config.Agent, alias string) (string, error) { if !agentCfg.SupportsMultipleSessions() || strings.TrimSpace(alias) != "" { return "", nil @@ -117,6 +156,13 @@ func (s *Server) buildSessionResume(info session.Info) (string, runtime.Config) if resolved == nil { return cmd, runtime.Config{WorkDir: info.WorkDir} } + mcpServers, _ := s.sessionMCPServers( + info.Template, + firstNonEmptyString(info.Provider, resolved.Name), + info.Alias, + firstNonEmptyString(workDir, info.WorkDir), + s.sessionKind(info.ID), + ) resolvedInfo := info if command, err := s.resolvedSessionRuntimeCommand(resolved, transport, info.Command); err == nil { resolvedInfo.Command = command @@ -132,7 +178,7 @@ func (s *Server) buildSessionResume(info session.Info) (string, runtime.Config) resolvedInfo.ResumeFlag = resolved.ResumeFlag resolvedInfo.ResumeStyle = resolved.ResumeStyle resolvedInfo.ResumeCommand = resolved.ResumeCommand - return session.BuildResumeCommand(resolvedInfo), sessionResumeHints(resolved, workDir) + return session.BuildResumeCommand(resolvedInfo), sessionResumeHints(resolved, workDir, mcpServers) } func (s *Server) resolvedSessionRuntimeCommand(resolved *config.ResolvedProvider, transport, storedCommand string) (string, error) { @@ -176,6 +222,16 @@ func (s *Server) resolveWorkerSessionRuntime(info session.Info, _ string) (*work if resolved == nil { return nil, nil } + mcpServers, err := s.sessionMCPServers( + info.Template, + firstNonEmptyString(info.Provider, resolved.Name), + info.Alias, + firstNonEmptyString(workDir, info.WorkDir), + s.sessionKind(info.ID), + ) + if err != nil { + return nil, err + } command, err := s.resolvedSessionRuntimeCommand(resolved, transport, info.Command) if err != nil { return nil, err @@ -185,7 +241,7 @@ func (s *Server) resolveWorkerSessionRuntime(info session.Info, _ string) (*work WorkDir: firstNonEmptyString(info.WorkDir, workDir), Provider: firstNonEmptyString(info.Provider, resolved.Name), SessionEnv: resolved.Env, - Hints: sessionResumeHints(resolved, firstNonEmptyString(workDir, info.WorkDir)), + Hints: sessionResumeHints(resolved, firstNonEmptyString(workDir, info.WorkDir), mcpServers), Resume: session.ProviderResume{ ResumeFlag: firstNonEmptyString(resolved.ResumeFlag, info.ResumeFlag), ResumeStyle: firstNonEmptyString(resolved.ResumeStyle, info.ResumeStyle), diff --git a/internal/api/worker_factory_test.go b/internal/api/worker_factory_test.go index 12d9d3c9d0..7d77d5f94c 100644 --- a/internal/api/worker_factory_test.go +++ b/internal/api/worker_factory_test.go @@ -2,6 +2,8 @@ package api import ( "context" + "os" + "path/filepath" "testing" "github.com/gastownhall/gascity/internal/config" @@ -135,6 +137,53 @@ func TestResolveWorkerSessionRuntimeUsesResolvedCommandWhenPersistedCommandIsSta } } +func TestResolveWorkerSessionRuntimeIncludesEffectiveMCPServers(t *testing.T) { + fs := newSessionFakeState(t) + fs.cfg.Agents[0].Provider = "resolved-worker" + fs.cfg.Agents[0].Session = "acp" + supportsACP := true + fs.cfg.Providers["resolved-worker"] = config.ProviderSpec{ + DisplayName: "Resolved Worker", + Command: "/bin/echo", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + fs.cfg.PackMCPDir = filepath.Join(fs.cityPath, "mcp") + if err := os.MkdirAll(fs.cfg.PackMCPDir, 0o755); err != nil { + t.Fatalf("MkdirAll(mcp): %v", err) + } + if err := os.WriteFile(filepath.Join(fs.cfg.PackMCPDir, "filesystem.toml"), []byte(` +name = "filesystem" +command = "/bin/mcp" +args = ["--stdio"] +`), 0o644); err != nil { + t.Fatalf("WriteFile(mcp): %v", err) + } + + srv := New(fs) + info := session.Info{ + ID: "sess-1", + Template: "myrig/worker", + Transport: "acp", + WorkDir: t.TempDir(), + } + + runtimeCfg, err := srv.resolveWorkerSessionRuntime(info, "") + if err != nil { + t.Fatalf("resolveWorkerSessionRuntime: %v", err) + } + if runtimeCfg == nil { + t.Fatal("resolveWorkerSessionRuntime() = nil") + } + if len(runtimeCfg.Hints.MCPServers) != 1 { + t.Fatalf("Hints.MCPServers len = %d, want 1", len(runtimeCfg.Hints.MCPServers)) + } + if got, want := runtimeCfg.Hints.MCPServers[0].Name, "filesystem"; got != want { + t.Fatalf("Hints.MCPServers[0].Name = %q, want %q", got, want) + } +} + func TestWorkerFactorySessionByIDUsesResolvedTemplateRuntime(t *testing.T) { fs := newSessionFakeState(t) fs.cfg.Agents[0].Provider = "resolved-worker" diff --git a/internal/materialize/mcp_runtime.go b/internal/materialize/mcp_runtime.go new file mode 100644 index 0000000000..7ba855c82c --- /dev/null +++ b/internal/materialize/mcp_runtime.go @@ -0,0 +1,127 @@ +package materialize + +import ( + "os" + "path/filepath" + + "github.com/gastownhall/gascity/internal/config" + "github.com/gastownhall/gascity/internal/git" + "github.com/gastownhall/gascity/internal/runtime" + workdirutil "github.com/gastownhall/gascity/internal/workdir" +) + +// EffectiveMCPForSession loads, expands, and resolves the effective MCP +// catalog for one concrete session context. +func EffectiveMCPForSession( + cfg *config.City, + cityPath string, + agent *config.Agent, + identity string, + workDir string, +) (MCPCatalog, error) { + cfgForMCP := cfg + if cfg != nil && cfg.PackMCPDir == "" { + cityMCPDir := filepath.Join(cityPath, "mcp") + if info, err := os.Stat(cityMCPDir); err == nil && info.IsDir() { + clone := *cfg + clone.PackMCPDir = cityMCPDir + cfgForMCP = &clone + } + } + return EffectiveMCPForAgent(cfgForMCP, agent, MCPTemplateData(cfgForMCP, cityPath, agent, identity, workDir)) +} + +// MCPTemplateData builds the template expansion surface used by MCP catalogs. +func MCPTemplateData( + cfg *config.City, + cityPath string, + agent *config.Agent, + identity string, + workDir string, +) map[string]string { + if agent == nil { + return map[string]string{ + "CityRoot": cityPath, + "AgentName": identity, + "TemplateName": identity, + "WorkDir": workDir, + "DefaultBranch": defaultMCPBranch(workDir), + } + } + var rigs []config.Rig + if cfg != nil { + rigs = cfg.Rigs + } + rigName := workdirutil.ConfiguredRigName(cityPath, *agent, rigs) + rigRoot := workdirutil.RigRootForName(rigName, rigs) + templateName := identity + if agent.PoolName != "" { + templateName = agent.PoolName + } + if templateName == "" { + templateName = agent.QualifiedName() + } + data := make(map[string]string, len(agent.Env)+11) + for key, value := range agent.Env { + data[key] = value + } + data["CityRoot"] = cityPath + data["AgentName"] = identity + data["TemplateName"] = templateName + data["RigName"] = rigName + data["RigRoot"] = rigRoot + data["WorkDir"] = workDir + data["IssuePrefix"] = mcpRigPrefix(rigName, rigs) + data["DefaultBranch"] = defaultMCPBranch(workDir) + data["WorkQuery"] = agent.EffectiveWorkQuery() + data["SlingQuery"] = agent.EffectiveSlingQuery() + return data +} + +// RuntimeMCPServers converts neutral MCP servers into runtime-owned ACP +// session/new server definitions. +func RuntimeMCPServers(servers []MCPServer) []runtime.MCPServerConfig { + if len(servers) == 0 { + return nil + } + out := make([]runtime.MCPServerConfig, 0, len(servers)) + for _, server := range servers { + entry := runtime.MCPServerConfig{ + Name: server.Name, + Command: server.Command, + Args: append([]string(nil), server.Args...), + Env: cloneStringMap(server.Env), + URL: server.URL, + Headers: cloneStringMap(server.Headers), + } + switch server.Transport { + case MCPTransportHTTP: + entry.Transport = runtime.MCPTransportHTTP + default: + entry.Transport = runtime.MCPTransportStdio + } + out = append(out, entry) + } + return runtime.NormalizeMCPServerConfigs(out) +} + +func mcpRigPrefix(rigName string, rigs []config.Rig) string { + for i := range rigs { + if rigs[i].Name == rigName { + return rigs[i].EffectivePrefix() + } + } + return "" +} + +func defaultMCPBranch(dir string) string { + if dir == "" { + return "main" + } + g := git.New(filepath.Clean(dir)) + branch, _ := g.DefaultBranch() + if branch == "" { + return "main" + } + return branch +} diff --git a/internal/runtime/acp/acp.go b/internal/runtime/acp/acp.go index c7492044fa..3299ea8dca 100644 --- a/internal/runtime/acp/acp.go +++ b/internal/runtime/acp/acp.go @@ -263,7 +263,7 @@ func (p *Provider) Start(ctx context.Context, name string, cfg runtime.Config) e hsTimeoutCtx, hsTimeoutCancel := context.WithTimeout(hsCtx, p.cfg.handshakeTimeout()) defer hsTimeoutCancel() - if err := p.handshake(hsTimeoutCtx, sc, cfg.WorkDir); err != nil { + if err := p.handshake(hsTimeoutCtx, sc, cfg.WorkDir, cfg.MCPServers); err != nil { // Handshake failed — kill the process. The monitor goroutine // handles listener/socket cleanup when the process exits. _ = stdinPipe.Close() @@ -301,7 +301,7 @@ func (p *Provider) Start(ctx context.Context, name string, cfg runtime.Config) e } // handshake performs the ACP initialize → initialized → session/new sequence. -func (p *Provider) handshake(ctx context.Context, sc *sessionConn, workDir string) error { +func (p *Provider) handshake(ctx context.Context, sc *sessionConn, workDir string, mcpServers []runtime.MCPServerConfig) error { // Step 1: Send "initialize" request. initReq, _ := newInitializeRequest() ch, err := sc.sendRequest(initReq) @@ -328,7 +328,7 @@ func (p *Provider) handshake(ctx context.Context, sc *sessionConn, workDir strin } // Step 3: Send "session/new" request. - newReq, _ := newSessionNewRequest(workDir) + newReq, _ := newSessionNewRequest(workDir, mcpServers) ch, err = sc.sendRequest(newReq) if err != nil { return fmt.Errorf("sending session/new: %w", err) diff --git a/internal/runtime/acp/protocol.go b/internal/runtime/acp/protocol.go index ae77e3b670..3ebefd3e49 100644 --- a/internal/runtime/acp/protocol.go +++ b/internal/runtime/acp/protocol.go @@ -16,6 +16,7 @@ import ( "fmt" "os" "path/filepath" + "sort" "sync/atomic" "github.com/gastownhall/gascity/internal/runtime" @@ -77,8 +78,62 @@ type InitializeResult struct { // SessionNewParams is the params for the "session/new" request. type SessionNewParams struct { - Cwd string `json:"cwd"` - McpServers []any `json:"mcpServers"` + Cwd string `json:"cwd"` + McpServers []SessionNewMCPServer `json:"mcpServers"` +} + +// SessionNewMCPServer is the ACP wire representation of one MCP server +// attached to session/new. +type SessionNewMCPServer struct { + Name string + Transport runtime.MCPTransport + Command string + Args []string + Env []runtime.MCPKeyValue + URL string + Headers []runtime.MCPKeyValue +} + +type sessionNewMCPServerStdio struct { + Name string `json:"name"` + Command string `json:"command"` + Args []string `json:"args"` + Env []runtime.MCPKeyValue `json:"env"` +} + +type sessionNewMCPServerHTTP struct { + Type string `json:"type"` + Name string `json:"name"` + URL string `json:"url"` + Headers []runtime.MCPKeyValue `json:"headers"` +} + +// MarshalJSON emits the transport-specific ACP schema shape for one MCP +// server. Stdio omits the type discriminator per spec. +func (s SessionNewMCPServer) MarshalJSON() ([]byte, error) { + switch s.Transport { + case runtime.MCPTransportHTTP: + return json.Marshal(sessionNewMCPServerHTTP{ + Type: string(runtime.MCPTransportHTTP), + Name: s.Name, + URL: s.URL, + Headers: nonNilMCPKeyValues(s.Headers), + }) + case runtime.MCPTransportSSE: + return json.Marshal(sessionNewMCPServerHTTP{ + Type: string(runtime.MCPTransportSSE), + Name: s.Name, + URL: s.URL, + Headers: nonNilMCPKeyValues(s.Headers), + }) + default: + return json.Marshal(sessionNewMCPServerStdio{ + Name: s.Name, + Command: s.Command, + Args: nonNilStrings(s.Args), + Env: nonNilMCPKeyValues(s.Env), + }) + } } // SessionNewResult is the result of the "session/new" request. @@ -135,13 +190,63 @@ func newInitializedNotification() JSONRPCMessage { } // newSessionNewRequest creates a "session/new" request. -func newSessionNewRequest(workDir string) (JSONRPCMessage, int64) { +func newSessionNewRequest(workDir string, mcpServers []runtime.MCPServerConfig) (JSONRPCMessage, int64) { return newRequest("session/new", SessionNewParams{ Cwd: workDir, - McpServers: []any{}, + McpServers: sessionNewMCPServers(mcpServers), }) } +func sessionNewMCPServers(servers []runtime.MCPServerConfig) []SessionNewMCPServer { + if len(servers) == 0 { + return []SessionNewMCPServer{} + } + normalized := runtime.NormalizeMCPServerConfigs(servers) + out := make([]SessionNewMCPServer, 0, len(normalized)) + for _, server := range normalized { + out = append(out, SessionNewMCPServer{ + Name: server.Name, + Transport: server.Transport, + Command: server.Command, + Args: append([]string(nil), server.Args...), + Env: sortedMCPKeyValues(server.Env), + URL: server.URL, + Headers: sortedMCPKeyValues(server.Headers), + }) + } + return out +} + +func sortedMCPKeyValues(values map[string]string) []runtime.MCPKeyValue { + if len(values) == 0 { + return nil + } + keys := make([]string, 0, len(values)) + for key := range values { + keys = append(keys, key) + } + sort.Strings(keys) + out := make([]runtime.MCPKeyValue, 0, len(keys)) + for _, key := range keys { + out = append(out, runtime.MCPKeyValue{Name: key, Value: values[key]}) + } + return out +} + +func nonNilStrings(values []string) []string { + if values == nil { + return []string{} + } + return values +} + +func nonNilMCPKeyValues(values []runtime.MCPKeyValue) []runtime.MCPKeyValue { + if values == nil { + return []runtime.MCPKeyValue{} + } + return values +} + // newSessionPromptRequest creates a "session/prompt" request from // structured content blocks. File_path blocks are inlined as text // with a preamble (ACP agents receive file content inline). diff --git a/internal/runtime/acp/protocol_test.go b/internal/runtime/acp/protocol_test.go index 1534aeb9e3..9e4cb23194 100644 --- a/internal/runtime/acp/protocol_test.go +++ b/internal/runtime/acp/protocol_test.go @@ -271,7 +271,7 @@ func TestInitializeRequest_IncludesProtocolVersion(t *testing.T) { } func TestSessionNewRequest_IncludesCwdAndMcpServers(t *testing.T) { - msg, _ := newSessionNewRequest("/home/user/project") + msg, _ := newSessionNewRequest("/home/user/project", nil) data, err := json.Marshal(msg) if err != nil { t.Fatalf("Marshal: %v", err) @@ -305,6 +305,88 @@ func TestSessionNewRequest_IncludesCwdAndMcpServers(t *testing.T) { } } +func TestSessionNewRequest_SerializesMCPServersByTransport(t *testing.T) { + msg, _ := newSessionNewRequest("/home/user/project", []runtime.MCPServerConfig{ + { + Name: "filesystem", + Transport: runtime.MCPTransportStdio, + Command: "/bin/mcp-fs", + Args: []string{"--stdio"}, + Env: map[string]string{ + "HOME": "/tmp/home", + "TOKEN": "secret", + }, + }, + { + Name: "remote", + Transport: runtime.MCPTransportHTTP, + URL: "https://mcp.example.test", + Headers: map[string]string{ + "Authorization": "Bearer token", + }, + }, + }) + data, err := json.Marshal(msg) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + var decoded JSONRPCMessage + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + + var params struct { + Cwd string `json:"cwd"` + McpServers []json.RawMessage `json:"mcpServers"` + } + if err := json.Unmarshal(decoded.Params, ¶ms); err != nil { + t.Fatalf("Unmarshal params: %v", err) + } + if len(params.McpServers) != 2 { + t.Fatalf("mcpServers len = %d, want 2", len(params.McpServers)) + } + + var stdio struct { + Type string `json:"type"` + Name string `json:"name"` + Command string `json:"command"` + Args []string `json:"args"` + Env []runtime.MCPKeyValue `json:"env"` + } + if err := json.Unmarshal(params.McpServers[0], &stdio); err != nil { + t.Fatalf("Unmarshal stdio server: %v", err) + } + if stdio.Type != "" { + t.Fatalf("stdio type = %q, want omitted", stdio.Type) + } + if stdio.Command != "/bin/mcp-fs" { + t.Fatalf("stdio command = %q, want /bin/mcp-fs", stdio.Command) + } + if len(stdio.Env) != 2 || stdio.Env[0].Name != "HOME" || stdio.Env[1].Name != "TOKEN" { + t.Fatalf("stdio env = %#v, want sorted HOME/TOKEN", stdio.Env) + } + + var http struct { + Type string `json:"type"` + Name string `json:"name"` + URL string `json:"url"` + Headers []runtime.MCPKeyValue `json:"headers"` + } + if err := json.Unmarshal(params.McpServers[1], &http); err != nil { + t.Fatalf("Unmarshal http server: %v", err) + } + if http.Type != "http" { + t.Fatalf("http type = %q, want http", http.Type) + } + if http.URL != "https://mcp.example.test" { + t.Fatalf("http url = %q, want https://mcp.example.test", http.URL) + } + if len(http.Headers) != 1 || http.Headers[0].Name != "Authorization" { + t.Fatalf("http headers = %#v, want Authorization", http.Headers) + } +} + func TestSessionPromptRequest_UsesPromptFieldNotMessages(t *testing.T) { msg, _ := newSessionPromptRequest("sess-1", runtime.TextContent("test")) data, err := json.Marshal(msg) diff --git a/internal/runtime/fingerprint.go b/internal/runtime/fingerprint.go index 9b8030d659..095adb0e4d 100644 --- a/internal/runtime/fingerprint.go +++ b/internal/runtime/fingerprint.go @@ -123,6 +123,7 @@ func hashCoreFields(h hash.Hash, cfg Config) { h.Write([]byte{0}) //nolint:errcheck // hash.Write never errors hashSortedMapIncluded(h, cfg.Env, envFingerprintInclude) + hashMCPServers(h, cfg.MCPServers) // FingerprintExtra carries additional identity fields (pool config, etc.) // that aren't part of the session command but should @@ -220,6 +221,28 @@ func hashSortedMap(h hash.Hash, m map[string]string) { } } +func hashMCPServers(h hash.Hash, servers []MCPServerConfig) { + for _, server := range NormalizeMCPServerConfigs(servers) { + h.Write([]byte(server.Name)) //nolint:errcheck // hash.Write never errors + h.Write([]byte{0}) //nolint:errcheck // hash.Write never errors + h.Write([]byte(server.Transport)) //nolint:errcheck // hash.Write never errors + h.Write([]byte{0}) //nolint:errcheck // hash.Write never errors + h.Write([]byte(server.Command)) //nolint:errcheck // hash.Write never errors + h.Write([]byte{0}) //nolint:errcheck // hash.Write never errors + for _, arg := range server.Args { + h.Write([]byte(arg)) //nolint:errcheck // hash.Write never errors + h.Write([]byte{0}) //nolint:errcheck // hash.Write never errors + } + h.Write([]byte{1}) //nolint:errcheck // sentinel between args/env + hashSortedMap(h, server.Env) + h.Write([]byte{1}) //nolint:errcheck // sentinel between env/url + h.Write([]byte(server.URL)) //nolint:errcheck // hash.Write never errors + h.Write([]byte{0}) //nolint:errcheck // hash.Write never errors + hashSortedMap(h, server.Headers) + h.Write([]byte{2}) //nolint:errcheck // sentinel between servers + } +} + // CoreFingerprintBreakdown returns per-field hash components of the core // fingerprint. Used to diagnose config-drift by comparing breakdowns // from session start vs reconcile time. @@ -236,6 +259,9 @@ func CoreFingerprintBreakdown(cfg Config) map[string]string { "Env": fieldHash(func(h hash.Hash) { hashSortedMapIncluded(h, cfg.Env, envFingerprintInclude) }), + "MCPServers": fieldHash(func(h hash.Hash) { + hashMCPServers(h, cfg.MCPServers) + }), "FPExtra": fieldHash(func(h hash.Hash) { if len(cfg.FingerprintExtra) > 0 { h.Write([]byte("fp")) @@ -314,6 +340,8 @@ func LogCoreFingerprintDrift(w io.Writer, name string, storedBreakdown map[strin fmt.Fprintf(w, " Command: %q\n", current.Command) //nolint:errcheck // best-effort diag case "Env": fmt.Fprintf(w, " Env: %v\n", filteredEnv(current.Env)) //nolint:errcheck // best-effort diag + case "MCPServers": + fmt.Fprintf(w, " MCPServers: %+v\n", NormalizeMCPServerConfigs(current.MCPServers)) //nolint:errcheck // best-effort diag case "FPExtra": fmt.Fprintf(w, " FPExtra: %v (len=%d)\n", current.FingerprintExtra, len(current.FingerprintExtra)) //nolint:errcheck // best-effort diag case "PreStart": diff --git a/internal/runtime/fingerprint_test.go b/internal/runtime/fingerprint_test.go index 088e5d70b0..59e266a19b 100644 --- a/internal/runtime/fingerprint_test.go +++ b/internal/runtime/fingerprint_test.go @@ -201,6 +201,42 @@ func TestConfigFingerprintExtraDifferentValues(t *testing.T) { } } +func TestConfigFingerprintIncludesMCPServers(t *testing.T) { + a := Config{Command: "claude"} + b := Config{ + Command: "claude", + MCPServers: []MCPServerConfig{{ + Name: "filesystem", + Transport: MCPTransportStdio, + Command: "/bin/mcp", + Args: []string{"--stdio"}, + }}, + } + if ConfigFingerprint(a) == ConfigFingerprint(b) { + t.Error("MCPServers should change the config fingerprint") + } +} + +func TestConfigFingerprintMCPServersOrderIndependent(t *testing.T) { + a := Config{ + Command: "claude", + MCPServers: []MCPServerConfig{ + {Name: "remote", Transport: MCPTransportHTTP, URL: "https://mcp.example", Headers: map[string]string{"Authorization": "token"}}, + {Name: "filesystem", Transport: MCPTransportStdio, Command: "/bin/mcp", Args: []string{"--stdio"}, Env: map[string]string{"TOKEN": "abc"}}, + }, + } + b := Config{ + Command: "claude", + MCPServers: []MCPServerConfig{ + {Name: "filesystem", Transport: MCPTransportStdio, Command: "/bin/mcp", Args: []string{"--stdio"}, Env: map[string]string{"TOKEN": "abc"}}, + {Name: "remote", Transport: MCPTransportHTTP, URL: "https://mcp.example", Headers: map[string]string{"Authorization": "token"}}, + }, + } + if ConfigFingerprint(a) != ConfigFingerprint(b) { + t.Error("MCPServers order should not affect hash") + } +} + func TestConfigFingerprintNilVsEmptyExtra(t *testing.T) { a := Config{Command: "claude", FingerprintExtra: nil} b := Config{Command: "claude", FingerprintExtra: map[string]string{}} diff --git a/internal/runtime/mcp.go b/internal/runtime/mcp.go new file mode 100644 index 0000000000..c6db27eb6b --- /dev/null +++ b/internal/runtime/mcp.go @@ -0,0 +1,77 @@ +package runtime + +import "sort" + +// MCPTransport identifies the ACP session/new transport type for an MCP server. +type MCPTransport string + +const ( + // MCPTransportStdio launches the MCP server over stdio. + MCPTransportStdio MCPTransport = "stdio" + // MCPTransportHTTP connects to the MCP server over streamable HTTP. + MCPTransportHTTP MCPTransport = "http" + // MCPTransportSSE connects to the MCP server over SSE. + MCPTransportSSE MCPTransport = "sse" +) + +// MCPKeyValue is a name/value pair used for MCP env vars and HTTP headers. +type MCPKeyValue struct { + Name string `json:"name"` + Value string `json:"value"` +} + +// MCPServerConfig is the runtime-owned ACP session/new representation of one +// MCP server. Providers that do not speak ACP ignore this field. +type MCPServerConfig struct { + Name string + Transport MCPTransport + Command string + Args []string + Env map[string]string + URL string + Headers map[string]string +} + +// NormalizeMCPServerConfigs clones and deterministically sorts MCP server +// definitions so runtime configs are safe to retain and compare. +func NormalizeMCPServerConfigs(in []MCPServerConfig) []MCPServerConfig { + if len(in) == 0 { + return nil + } + out := make([]MCPServerConfig, len(in)) + for i, server := range in { + out[i] = MCPServerConfig{ + Name: server.Name, + Transport: server.Transport, + Command: server.Command, + Args: append([]string(nil), server.Args...), + Env: cloneRuntimeStringMap(server.Env), + URL: server.URL, + Headers: cloneRuntimeStringMap(server.Headers), + } + } + sort.Slice(out, func(i, j int) bool { + if out[i].Name != out[j].Name { + return out[i].Name < out[j].Name + } + if out[i].Transport != out[j].Transport { + return out[i].Transport < out[j].Transport + } + if out[i].Command != out[j].Command { + return out[i].Command < out[j].Command + } + return out[i].URL < out[j].URL + }) + return out +} + +func cloneRuntimeStringMap(in map[string]string) map[string]string { + if len(in) == 0 { + return nil + } + out := make(map[string]string, len(in)) + for key, value := range in { + out[key] = value + } + return out +} diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go index 23414f2097..3090607e33 100644 --- a/internal/runtime/runtime.go +++ b/internal/runtime/runtime.go @@ -352,6 +352,10 @@ type Config struct { // Env is additional environment variables set in the session. Env map[string]string + // MCPServers is the effective ACP session/new MCP server list for this + // session. Non-ACP providers ignore it. + MCPServers []MCPServerConfig + // Startup reliability hints (all optional — zero values skip). // ReadyPromptPrefix is the prompt prefix for readiness detection (e.g. "> "). diff --git a/internal/worker/handle_clone.go b/internal/worker/handle_clone.go index 9a025c2a8e..4bbf15c13f 100644 --- a/internal/worker/handle_clone.go +++ b/internal/worker/handle_clone.go @@ -44,6 +44,7 @@ func mergeStringMaps(base, extra map[string]string) map[string]string { func cloneRuntimeConfig(cfg runtime.Config) runtime.Config { cfg.Env = cloneStringMap(cfg.Env) + cfg.MCPServers = runtime.NormalizeMCPServerConfigs(cfg.MCPServers) cfg.ProcessNames = append([]string(nil), cfg.ProcessNames...) cfg.PreStart = append([]string(nil), cfg.PreStart...) cfg.SessionSetup = append([]string(nil), cfg.SessionSetup...) From 19cc652ebac58f5498e16140686e41547bdf29f8 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 01:05:42 +0000 Subject: [PATCH 48/85] fix: fail fast on ACP routing drift --- cmd/gc/worker_handle.go | 18 +++-- cmd/gc/worker_handle_test.go | 68 +++++++++++++++++-- internal/api/handler_session_create.go | 7 +- internal/api/handler_sessions_test.go | 43 ++++++------ .../api/huma_handlers_sessions_command.go | 5 +- internal/api/session_transport.go | 10 +-- 6 files changed, 114 insertions(+), 37 deletions(-) diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index 25ae68b6a8..77d8590abb 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -54,7 +54,10 @@ func workerSessionRuntimeResolverWithConfig(cityPath string, cfg *config.City) w return nil } return func(info session.Info, sessionKind string) (*worker.ResolvedRuntime, error) { - runtimeCfg := resolvedWorkerRuntimeWithConfig(cityPath, cfg, info, sessionKind) + runtimeCfg, err := resolvedWorkerRuntimeWithConfig(cityPath, cfg, info, sessionKind) + if err != nil { + return nil, err + } if runtimeCfg == nil { return nil, nil } @@ -362,13 +365,13 @@ func workerRespondSessionTargetWithConfig(cityPath string, store beads.Store, sp return handle.Respond(context.Background(), response) } -func resolvedWorkerRuntimeWithConfig(cityPath string, cfg *config.City, info session.Info, sessionKind string) *worker.ResolvedRuntime { +func resolvedWorkerRuntimeWithConfig(cityPath string, cfg *config.City, info session.Info, sessionKind string) (*worker.ResolvedRuntime, error) { if cfg == nil { - return nil + return nil, nil } resolved, transport := resolveWorkerRuntimeProviderWithConfig(cfg, info, sessionKind) if resolved == nil { - return nil + return nil, nil } command := strings.TrimSpace(info.Command) @@ -389,7 +392,7 @@ func resolvedWorkerRuntimeWithConfig(cityPath string, cfg *config.City, info ses if workDir == "" { workDir = cityPath } - mcpServers, _ := resolvedRuntimeMCPServersWithConfig( + mcpServers, err := resolvedRuntimeMCPServersWithConfig( cityPath, cfg, info.Alias, @@ -398,6 +401,9 @@ func resolvedWorkerRuntimeWithConfig(cityPath string, cfg *config.City, info ses workDir, nil, ) + if err != nil { + return nil, err + } return &worker.ResolvedRuntime{ Command: command, WorkDir: workDir, @@ -417,7 +423,7 @@ func resolvedWorkerRuntimeWithConfig(cityPath string, cfg *config.City, info ses ResumeCommand: firstNonEmptyGCString(resolved.ResumeCommand, info.ResumeCommand), SessionIDFlag: resolved.SessionIDFlag, }, - } + }, nil } func shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand string) bool { diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index 8a9f86b95b..1e44d7eadb 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -125,10 +125,13 @@ func TestResolvedWorkerRuntimeWithConfigUsesProviderLaunchCommand(t *testing.T) }, } - resolved := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + resolved, err := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ Template: "worker", WorkDir: cityDir, }, "") + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfig: %v", err) + } if resolved == nil { t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") } @@ -232,12 +235,15 @@ TOKEN = "abc" t.Fatalf("loadCityConfig: %v", err) } - resolved := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + resolved, err := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ Template: "worker", Command: "/bin/echo", Transport: "acp", WorkDir: cityDir, }, "") + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfig: %v", err) + } if resolved == nil { t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") } @@ -277,11 +283,14 @@ acp_args = ["acp"] t.Fatalf("loadCityConfig: %v", err) } - resolved := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + resolved, err := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ Template: "worker", Command: "/bin/echo", WorkDir: cityDir, }, "") + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfig: %v", err) + } if resolved == nil { t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") } @@ -311,12 +320,15 @@ acp_args = ["acp"] t.Fatalf("loadCityConfig: %v", err) } - resolved := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + resolved, err := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ Template: "opencode", Command: "/bin/echo", Transport: "acp", WorkDir: cityDir, }, "provider") + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfig: %v", err) + } if resolved == nil { t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") } @@ -346,11 +358,14 @@ acp_args = ["acp"] t.Fatalf("loadCityConfig: %v", err) } - resolved := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + resolved, err := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ Template: "opencode", Command: "/bin/echo", WorkDir: cityDir, }, "provider") + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfig: %v", err) + } if resolved == nil { t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") } @@ -700,9 +715,12 @@ ready_delay_ms = 250 t.Fatalf("loadCityConfig: %v", err) } - runtimeCfg := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + runtimeCfg, err := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ Template: "worker", }, "") + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfig: %v", err) + } if runtimeCfg == nil { t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") } @@ -720,6 +738,44 @@ ready_delay_ms = 250 } } +func TestResolvedWorkerRuntimeWithConfigPropagatesMCPResolutionError(t *testing.T) { + cityDir := t.TempDir() + writePhase0InterfaceCity(t, cityDir, `[workspace] +name = "test-city" + +[beads] +provider = "file" + +[[agent]] +name = "worker" +provider = "stub" +session = "acp" + +[providers.stub] +command = "/bin/echo" +supports_acp = true +acp_command = "/bin/echo" +acp_args = ["acp"] +`) + writeCatalogFile(t, cityDir, "mcp/filesystem.toml", ` +name = "filesystem" +command = [broken +`) + + cfg, err := loadCityConfig(cityDir) + if err != nil { + t.Fatalf("loadCityConfig: %v", err) + } + + if _, err := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + Template: "worker", + Transport: "acp", + WorkDir: cityDir, + }, ""); err == nil { + t.Fatal("resolvedWorkerRuntimeWithConfig() error = nil, want MCP resolution error") + } +} + func TestWorkerSessionRuntimeResolverWithConfigFallsBackToProviderNameWhenResolvedCommandMissing(t *testing.T) { cfg := &config.City{ Workspace: config.Workspace{Name: "test-city"}, diff --git a/internal/api/handler_session_create.go b/internal/api/handler_session_create.go index a2adad4f4d..d5b3dfba82 100644 --- a/internal/api/handler_session_create.go +++ b/internal/api/handler_session_create.go @@ -274,7 +274,12 @@ func (s *Server) createProviderSession(w http.ResponseWriter, r *http.Request, s return } - transport := providerSessionTransport(resolved, s.state.SessionProvider()) + transport, err := providerSessionTransport(resolved, s.state.SessionProvider()) + if err != nil { + s.idem.unreserve(idemKey) + writeError(w, http.StatusServiceUnavailable, "provider_unavailable", err.Error()) + return + } launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, body.Options, transport) if err != nil { s.idem.unreserve(idemKey) diff --git a/internal/api/handler_sessions_test.go b/internal/api/handler_sessions_test.go index 7c5dbc2102..2e6a057667 100644 --- a/internal/api/handler_sessions_test.go +++ b/internal/api/handler_sessions_test.go @@ -1531,7 +1531,7 @@ func TestHumaCreateProviderSessionUsesACPTransportCommand(t *testing.T) { } } -func TestHandleProviderSessionCreateKeepsDefaultTransportWithoutACPProvider(t *testing.T) { +func TestHandleProviderSessionCreateRejectsACPProviderWithoutACPRouting(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) fs.cfg.Providers["opencode"] = config.ProviderSpec{ @@ -1549,27 +1549,32 @@ func TestHandleProviderSessionCreateKeepsDefaultTransportWithoutACPProvider(t *t rec := httptest.NewRecorder() h.ServeHTTP(rec, req) - if rec.Code != http.StatusCreated { - t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusCreated, rec.Body.String()) + if rec.Code != http.StatusServiceUnavailable { + t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusServiceUnavailable, rec.Body.String()) } - - var resp sessionResponse - if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { - t.Fatalf("decode: %v", err) + if !strings.Contains(rec.Body.String(), "requires ACP transport") { + t.Fatalf("body = %q, want ACP transport error", rec.Body.String()) } - start := fs.sp.LastStartConfig(resp.SessionName) - if start == nil { - t.Fatalf("LastStartConfig(%q) = nil", resp.SessionName) - } - if got, want := start.Command, "/bin/echo"; got != want { - t.Fatalf("start command = %q, want %q", got, want) - } - bead, err := fs.cityBeadStore.Get(resp.ID) - if err != nil { - t.Fatalf("Get(%s): %v", resp.ID, err) +} + +func TestHumaCreateProviderSessionRejectsACPProviderWithoutACPRouting(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg.Providers["opencode"] = config.ProviderSpec{ + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, } - if got := bead.Metadata["transport"]; got != "" { - t.Fatalf("transport metadata = %q, want empty", got) + srv := New(fs) + + if _, err := srv.humaCreateProviderSession(context.Background(), fs.cityBeadStore, sessionCreateBody{ + Kind: "provider", + Name: "opencode", + }, "opencode"); err == nil { + t.Fatal("humaCreateProviderSession() error = nil, want ACP routing error") } } diff --git a/internal/api/huma_handlers_sessions_command.go b/internal/api/huma_handlers_sessions_command.go index db1c436d7e..7f81cd54ec 100644 --- a/internal/api/huma_handlers_sessions_command.go +++ b/internal/api/huma_handlers_sessions_command.go @@ -234,7 +234,10 @@ func (s *Server) humaCreateProviderSession(ctx context.Context, store beads.Stor return nil, humaSessionManagerError(err) } - transport := providerSessionTransport(resolved, s.state.SessionProvider()) + transport, err := providerSessionTransport(resolved, s.state.SessionProvider()) + if err != nil { + return nil, huma.Error503ServiceUnavailable(err.Error()) + } launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, body.Options, transport) if err != nil { return nil, huma.Error400BadRequest(err.Error()) diff --git a/internal/api/session_transport.go b/internal/api/session_transport.go index 4902b7b609..33c1e04f8b 100644 --- a/internal/api/session_transport.go +++ b/internal/api/session_transport.go @@ -1,6 +1,8 @@ package api import ( + "fmt" + "github.com/gastownhall/gascity/internal/config" "github.com/gastownhall/gascity/internal/runtime" sessionacp "github.com/gastownhall/gascity/internal/runtime/acp" @@ -10,14 +12,14 @@ type acpRoutingProvider interface { RouteACP(name string) } -func providerSessionTransport(resolved *config.ResolvedProvider, sp runtime.Provider) string { +func providerSessionTransport(resolved *config.ResolvedProvider, sp runtime.Provider) (string, error) { if resolved == nil || resolved.DefaultSessionTransport() != "acp" { - return "" + return "", nil } if transportSupportsACP(sp) { - return "acp" + return "acp", nil } - return "" + return "", fmt.Errorf("provider %q requires ACP transport but the session provider cannot route ACP sessions", resolved.Name) } func transportSupportsACP(sp runtime.Provider) bool { From 214f92054eb8be609f37f5e3fbe07cb4d040ae3d Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 01:13:06 +0000 Subject: [PATCH 49/85] fix: tighten ACP wrapper and resume errors --- cmd/gc/providers.go | 21 +++--- cmd/gc/providers_test.go | 70 +++++++++++++++++++ internal/api/handler_session_chat_test.go | 84 ++++++++++++++++++++--- internal/api/session_runtime.go | 11 +-- 4 files changed, 165 insertions(+), 21 deletions(-) diff --git a/cmd/gc/providers.go b/cmd/gc/providers.go index 5d9bb161a3..b0db445c3c 100644 --- a/cmd/gc/providers.go +++ b/cmd/gc/providers.go @@ -71,6 +71,7 @@ func sessionProviderContextForCity(cfg *config.City, cityPath, providerOverride } var openSessionProviderStore = openCityStoreAt +var buildSessionProviderByName = newSessionProviderByName // tmuxConfigFromSession converts a config.SessionConfig into a // sessiontmux.Config with resolved durations and defaults. If the @@ -193,10 +194,14 @@ func newSessionProviderFromContextWithError(ctx sessionProviderContext, sessionB // wrap in an auto provider that routes per-session. // NOTE: agents comes from loadCityConfig which applies pack overrides, // so the Session field from overrides is already resolved here. + requireACPWrapper := requiresACPProviderWrapper(sessionBeads, ctx.cfg) if ctx.providerName != "acp" && needsACPProviderWrapper(sessionBeads, ctx.cfg) { - acpSP, acpErr := newSessionProviderByName("acp", ctx.sc, ctx.cityName, ctx.cityPath) + acpSP, acpErr := buildSessionProviderByName("acp", ctx.sc, ctx.cityName, ctx.cityPath) if acpErr != nil { - return nil, fmt.Errorf("acp provider: %w", acpErr) + if requireACPWrapper { + return nil, fmt.Errorf("acp provider: %w", acpErr) + } + return sp, nil } autoSP := sessionauto.New(sp, acpSP) for _, sessName := range configuredACPRouteNames(sessionBeads, ctx.cityName, ctx.cfg) { @@ -238,13 +243,11 @@ func configuredACPSessionNames(snapshot *sessionBeadSnapshot, cityName, sessionT } func needsACPProviderWrapper(snapshot *sessionBeadSnapshot, cfg *config.City) bool { - if len(observedACPSessionNames(snapshot)) > 0 { - return true - } - if cfg == nil { - return false - } - return hasACPAgents(cfg.Agents) || hasACPProviderTargets(cfg) + return requiresACPProviderWrapper(snapshot, cfg) || (cfg != nil && hasACPProviderTargets(cfg)) +} + +func requiresACPProviderWrapper(snapshot *sessionBeadSnapshot, cfg *config.City) bool { + return len(observedACPSessionNames(snapshot)) > 0 || (cfg != nil && hasACPAgents(cfg.Agents)) } func hasACPProviderTargets(cfg *config.City) bool { diff --git a/cmd/gc/providers_test.go b/cmd/gc/providers_test.go index a35a99284c..0ceba90d4d 100644 --- a/cmd/gc/providers_test.go +++ b/cmd/gc/providers_test.go @@ -1,6 +1,7 @@ package main import ( + "errors" "os" "path/filepath" "strings" @@ -9,6 +10,7 @@ import ( "github.com/gastownhall/gascity/internal/agent" "github.com/gastownhall/gascity/internal/beads" "github.com/gastownhall/gascity/internal/config" + "github.com/gastownhall/gascity/internal/runtime" ) func TestTmuxConfigFromSessionDefaultsSocketToCityName(t *testing.T) { @@ -407,6 +409,74 @@ func TestNewSessionProviderWrapsACPProvidersWithoutACPAgents(t *testing.T) { } } +func TestNewSessionProviderIgnoresACPInitFailureForUnusedACPProviders(t *testing.T) { + oldBuild := buildSessionProviderByName + t.Cleanup(func() { buildSessionProviderByName = oldBuild }) + buildSessionProviderByName = func(name string, sc config.SessionConfig, cityName, cityPath string) (runtime.Provider, error) { + if name == "acp" { + return nil, errors.New("acp unavailable") + } + return oldBuild(name, sc, cityName, cityPath) + } + + ctx := sessionProviderContextForCity(&config.City{ + Workspace: config.Workspace{ + Name: "test-city", + Provider: "opencode", + }, + Providers: map[string]config.ProviderSpec{ + "opencode": { + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: boolPtr(true), + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + }, t.TempDir(), "fake") + + sp, err := newSessionProviderFromContextWithError(ctx, nil) + if err != nil { + t.Fatalf("newSessionProviderFromContextWithError: %v", err) + } + if _, ok := sp.(interface{ RouteACP(string) }); ok { + t.Fatalf("provider = %T, want plain provider fallback when ACP is unavailable", sp) + } +} + +func TestNewSessionProviderRequiresACPInitForACPAgents(t *testing.T) { + oldBuild := buildSessionProviderByName + t.Cleanup(func() { buildSessionProviderByName = oldBuild }) + buildSessionProviderByName = func(name string, sc config.SessionConfig, cityName, cityPath string) (runtime.Provider, error) { + if name == "acp" { + return nil, errors.New("acp unavailable") + } + return oldBuild(name, sc, cityName, cityPath) + } + + ctx := sessionProviderContextForCity(&config.City{ + Workspace: config.Workspace{ + Name: "test-city", + }, + Agents: []config.Agent{ + {Name: "worker", Provider: "opencode", Session: "acp"}, + }, + Providers: map[string]config.ProviderSpec{ + "opencode": { + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: boolPtr(true), + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + }, t.TempDir(), "fake") + + if _, err := newSessionProviderFromContextWithError(ctx, nil); err == nil { + t.Fatal("newSessionProviderFromContextWithError() error = nil, want ACP init failure") + } +} + func TestNewSessionProviderRoutesObservedACPProviderSessionsWithoutACPAgents(t *testing.T) { t.Setenv("GC_BEADS", "file") t.Setenv("GC_SESSION", "fake") diff --git a/internal/api/handler_session_chat_test.go b/internal/api/handler_session_chat_test.go index fdea344390..dc98c26abc 100644 --- a/internal/api/handler_session_chat_test.go +++ b/internal/api/handler_session_chat_test.go @@ -71,7 +71,10 @@ func TestBuildSessionResumeUsesResolvedProviderCommand(t *testing.T) { WorkDir: "/tmp/workdir", } - cmd, hints := srv.buildSessionResume(info) + cmd, hints, err := srv.buildSessionResume(info) + if err != nil { + t.Fatalf("buildSessionResume: %v", err) + } if got, want := cmd, "aimux run gemini -- --approval-mode yolo"; got != want { t.Fatalf("resume command = %q, want %q", got, want) } @@ -111,7 +114,10 @@ func TestBuildSessionResumePreservesStoredResolvedCommand(t *testing.T) { WorkDir: "/tmp/workdir", } - cmd, _ := srv.buildSessionResume(info) + cmd, _, err := srv.buildSessionResume(info) + if err != nil { + t.Fatalf("buildSessionResume: %v", err) + } if got, want := cmd, "claude --dangerously-skip-permissions --settings /tmp/settings.json"; got != want { t.Fatalf("resume command = %q, want %q", got, want) } @@ -161,7 +167,10 @@ func TestBuildSessionResumeRebuildsBareStoredCommandForPoolClaudeAgent(t *testin ResumeFlag: "--resume", } - cmd, _ := srv.buildSessionResume(info) + cmd, _, err := srv.buildSessionResume(info) + if err != nil { + t.Fatalf("buildSessionResume: %v", err) + } if !strings.Contains(cmd, "--dangerously-skip-permissions") { t.Fatalf("resume command missing default args:\n got: %s", cmd) } @@ -204,7 +213,10 @@ func TestBuildSessionResumeUsesStoredACPCommandForProviderSession(t *testing.T) WorkDir: "/tmp/workdir", } - cmd, _ := srv.buildSessionResume(info) + cmd, _, err := srv.buildSessionResume(info) + if err != nil { + t.Fatalf("buildSessionResume: %v", err) + } if got, want := cmd, "/bin/echo acp"; got != want { t.Fatalf("resume command = %q, want %q", got, want) } @@ -236,7 +248,10 @@ func TestBuildSessionResumeKeepsDefaultCommandWithoutACPTransportProvider(t *tes WorkDir: "/tmp/workdir", } - cmd, _ := srv.buildSessionResume(info) + cmd, _, err := srv.buildSessionResume(info) + if err != nil { + t.Fatalf("buildSessionResume: %v", err) + } if got, want := cmd, "/bin/echo"; got != want { t.Fatalf("resume command = %q, want %q", got, want) } @@ -272,7 +287,10 @@ func TestBuildSessionResumeKeepsDefaultCommandForLegacyProviderSessionWithoutTra WorkDir: "/tmp/workdir", } - cmd, _ := srv.buildSessionResume(info) + cmd, _, err := srv.buildSessionResume(info) + if err != nil { + t.Fatalf("buildSessionResume: %v", err) + } if got, want := cmd, "/bin/echo"; got != want { t.Fatalf("resume command = %q, want %q", got, want) } @@ -308,7 +326,10 @@ func TestBuildSessionResumeUsesStoredACPTransportForTemplateSession(t *testing.T WorkDir: "/tmp/workdir", } - cmd, _ := srv.buildSessionResume(info) + cmd, _, err := srv.buildSessionResume(info) + if err != nil { + t.Fatalf("buildSessionResume: %v", err) + } if got, want := cmd, "/bin/echo acp"; got != want { t.Fatalf("resume command = %q, want %q", got, want) } @@ -343,8 +364,55 @@ func TestBuildSessionResumeKeepsDefaultCommandForLegacyTemplateSessionWithoutTra WorkDir: "/tmp/workdir", } - cmd, _ := srv.buildSessionResume(info) + cmd, _, err := srv.buildSessionResume(info) + if err != nil { + t.Fatalf("buildSessionResume: %v", err) + } if got, want := cmd, "/bin/echo"; got != want { t.Fatalf("resume command = %q, want %q", got, want) } } + +func TestBuildSessionResumePropagatesMCPResolutionError(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg = &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Agents: []config.Agent{ + {Name: "worker", Provider: "opencode", Session: "acp"}, + }, + Providers: map[string]config.ProviderSpec{ + "opencode": { + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + } + fs.cfg.PackMCPDir = filepath.Join(fs.cityPath, "mcp") + if err := os.MkdirAll(fs.cfg.PackMCPDir, 0o755); err != nil { + t.Fatalf("MkdirAll(mcp): %v", err) + } + if err := os.WriteFile(filepath.Join(fs.cfg.PackMCPDir, "filesystem.toml"), []byte(` +name = "filesystem" +command = [broken +`), 0o644); err != nil { + t.Fatalf("WriteFile(mcp): %v", err) + } + + srv := New(fs) + info := session.Info{ + ID: "gc-1", + Template: "worker", + Provider: "opencode", + Transport: "acp", + WorkDir: fs.cityPath, + } + + if _, _, err := srv.buildSessionResume(info); err == nil { + t.Fatal("buildSessionResume() error = nil, want MCP resolution error") + } +} diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index 4d0d74949a..2b545bba60 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -150,19 +150,22 @@ func (s *Server) resolveSessionTemplate(template string) (*config.ResolvedProvid return resolved, workDir, agentCfg.Session, agentCfg.QualifiedName(), nil } -func (s *Server) buildSessionResume(info session.Info) (string, runtime.Config) { +func (s *Server) buildSessionResume(info session.Info) (string, runtime.Config, error) { cmd := session.BuildResumeCommand(info) resolved, workDir, transport := s.resolveSessionRuntime(info) if resolved == nil { - return cmd, runtime.Config{WorkDir: info.WorkDir} + return cmd, runtime.Config{WorkDir: info.WorkDir}, nil } - mcpServers, _ := s.sessionMCPServers( + mcpServers, err := s.sessionMCPServers( info.Template, firstNonEmptyString(info.Provider, resolved.Name), info.Alias, firstNonEmptyString(workDir, info.WorkDir), s.sessionKind(info.ID), ) + if err != nil { + return "", runtime.Config{}, err + } resolvedInfo := info if command, err := s.resolvedSessionRuntimeCommand(resolved, transport, info.Command); err == nil { resolvedInfo.Command = command @@ -178,7 +181,7 @@ func (s *Server) buildSessionResume(info session.Info) (string, runtime.Config) resolvedInfo.ResumeFlag = resolved.ResumeFlag resolvedInfo.ResumeStyle = resolved.ResumeStyle resolvedInfo.ResumeCommand = resolved.ResumeCommand - return session.BuildResumeCommand(resolvedInfo), sessionResumeHints(resolved, workDir, mcpServers) + return session.BuildResumeCommand(resolvedInfo), sessionResumeHints(resolved, workDir, mcpServers), nil } func (s *Server) resolvedSessionRuntimeCommand(resolved *config.ResolvedProvider, transport, storedCommand string) (string, error) { From 1a6ed739c84c257f949b390afd8fc31b6fe2bb3c Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 01:23:04 +0000 Subject: [PATCH 50/85] fix: tighten ACP transport capability checks --- internal/api/handler_session_create.go | 9 ++- internal/api/handler_sessions_test.go | 59 +++++++++++++++++++- internal/api/session_resolved_config_test.go | 14 ++++- internal/api/session_transport.go | 7 ++- internal/runtime/acp/acp.go | 11 +++- internal/runtime/auto/auto.go | 13 +++++ internal/runtime/runtime.go | 9 +++ 7 files changed, 113 insertions(+), 9 deletions(-) diff --git a/internal/api/handler_session_create.go b/internal/api/handler_session_create.go index d5b3dfba82..f22fa682a4 100644 --- a/internal/api/handler_session_create.go +++ b/internal/api/handler_session_create.go @@ -127,6 +127,13 @@ func (s *Server) handleSessionCreate(w http.ResponseWriter, r *http.Request) { return } + mcpServers, err := s.sessionMCPServers(template, resolved.Name, firstNonEmptyString(alias, template), workDir, kind) + if err != nil { + s.idem.unreserve(idemKey) + writeError(w, http.StatusInternalServerError, "internal", err.Error()) + return + } + command := sessionCreateAgentCommand(resolved) // Build template_overrides metadata. Includes schema overrides AND @@ -143,7 +150,7 @@ func (s *Server) handleSessionCreate(w http.ResponseWriter, r *http.Request) { // starts the agent process on the next tick. This avoids blocking the // HTTP response for 10-30s while the agent boots in tmux, and lets MC // show the session in the sidebar immediately via optimistic UI. - resolvedCfg, err := resolvedSessionConfigForProvider(alias, "", template, title, transport, extraMeta, resolved, command, workDir, nil) + resolvedCfg, err := resolvedSessionConfigForProvider(alias, "", template, title, transport, extraMeta, resolved, command, workDir, mcpServers) if err != nil { s.idem.unreserve(idemKey) writeSessionManagerError(w, err) diff --git a/internal/api/handler_sessions_test.go b/internal/api/handler_sessions_test.go index 2e6a057667..ff707c8da4 100644 --- a/internal/api/handler_sessions_test.go +++ b/internal/api/handler_sessions_test.go @@ -77,6 +77,14 @@ func (p *failNudgeProvider) Nudge(name string, content []runtime.ContentBlock) e return nil } +type transportCapableProvider struct { + *runtime.Fake +} + +func (p *transportCapableProvider) SupportsTransport(transport string) bool { + return transport == "acp" +} + type stateWithSessionProvider struct { *fakeState provider runtime.Provider @@ -1444,7 +1452,7 @@ func TestHandleProviderSessionCreateUsesACPTransportCommand(t *testing.T) { ACPArgs: []string{"acp"}, } defaultSP := runtime.NewFake() - acpSP := runtime.NewFake() + acpSP := &transportCapableProvider{Fake: runtime.NewFake()} state := &stateWithSessionProvider{ fakeState: fs, provider: sessionauto.New(defaultSP, acpSP), @@ -1495,7 +1503,7 @@ func TestHumaCreateProviderSessionUsesACPTransportCommand(t *testing.T) { ACPArgs: []string{"acp"}, } defaultSP := runtime.NewFake() - acpSP := runtime.NewFake() + acpSP := &transportCapableProvider{Fake: runtime.NewFake()} state := &stateWithSessionProvider{ fakeState: fs, provider: sessionauto.New(defaultSP, acpSP), @@ -1531,6 +1539,53 @@ func TestHumaCreateProviderSessionUsesACPTransportCommand(t *testing.T) { } } +func TestHandleProviderSessionCreateUsesACPTransportCapabilityProvider(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg.Providers["opencode"] = config.ProviderSpec{ + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + provider := &transportCapableProvider{Fake: runtime.NewFake()} + state := &stateWithSessionProvider{ + fakeState: fs, + provider: provider, + } + srv := New(state) + h := newTestCityHandlerWith(t, state, srv) + + req := newPostRequest(cityURL(fs, "/sessions"), strings.NewReader(`{"kind":"provider","name":"opencode"}`)) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + + if rec.Code != http.StatusCreated { + t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusCreated, rec.Body.String()) + } + + var resp sessionResponse + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode: %v", err) + } + start := provider.LastStartConfig(resp.SessionName) + if start == nil { + t.Fatalf("LastStartConfig(%q) = nil", resp.SessionName) + } + if got, want := start.Command, "/bin/echo acp"; got != want { + t.Fatalf("start command = %q, want %q", got, want) + } + bead, err := fs.cityBeadStore.Get(resp.ID) + if err != nil { + t.Fatalf("Get(%s): %v", resp.ID, err) + } + if got, want := bead.Metadata["transport"], "acp"; got != want { + t.Fatalf("transport metadata = %q, want %q", got, want) + } +} + func TestHandleProviderSessionCreateRejectsACPProviderWithoutACPRouting(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) diff --git a/internal/api/session_resolved_config_test.go b/internal/api/session_resolved_config_test.go index 42a898b362..c70b224414 100644 --- a/internal/api/session_resolved_config_test.go +++ b/internal/api/session_resolved_config_test.go @@ -4,11 +4,17 @@ import ( "testing" "github.com/gastownhall/gascity/internal/config" + "github.com/gastownhall/gascity/internal/runtime" ) func TestResolvedSessionConfigForProviderBuildsNormalizedConfig(t *testing.T) { metadata := map[string]string{"session_origin": "named"} env := map[string]string{"API_TOKEN": "present"} + mcpServers := []runtime.MCPServerConfig{{ + Name: "filesystem", + Command: "/bin/mcp", + Args: []string{"--stdio"}, + }} resolved := &config.ResolvedProvider{ Name: "stub", Command: "/bin/echo", @@ -33,7 +39,7 @@ func TestResolvedSessionConfigForProviderBuildsNormalizedConfig(t *testing.T) { resolved, "", "/tmp/workdir", - nil, + mcpServers, ) if err != nil { t.Fatalf("resolvedSessionConfigForProvider: %v", err) @@ -54,6 +60,12 @@ func TestResolvedSessionConfigForProviderBuildsNormalizedConfig(t *testing.T) { if got, want := cfg.Runtime.Hints.ReadyPromptPrefix, "stub-ready>"; got != want { t.Fatalf("Runtime.Hints.ReadyPromptPrefix = %q, want %q", got, want) } + if len(cfg.Runtime.Hints.MCPServers) != 1 { + t.Fatalf("Runtime.Hints.MCPServers len = %d, want 1", len(cfg.Runtime.Hints.MCPServers)) + } + if got, want := cfg.Runtime.Hints.MCPServers[0].Name, "filesystem"; got != want { + t.Fatalf("Runtime.Hints.MCPServers[0].Name = %q, want %q", got, want) + } if got, want := cfg.Runtime.Resume.SessionIDFlag, "--session-id"; got != want { t.Fatalf("Runtime.Resume.SessionIDFlag = %q, want %q", got, want) } diff --git a/internal/api/session_transport.go b/internal/api/session_transport.go index 33c1e04f8b..9b88a2bbae 100644 --- a/internal/api/session_transport.go +++ b/internal/api/session_transport.go @@ -5,7 +5,6 @@ import ( "github.com/gastownhall/gascity/internal/config" "github.com/gastownhall/gascity/internal/runtime" - sessionacp "github.com/gastownhall/gascity/internal/runtime/acp" ) type acpRoutingProvider interface { @@ -26,9 +25,11 @@ func transportSupportsACP(sp runtime.Provider) bool { if sp == nil { return false } + if provider, ok := sp.(runtime.TransportCapabilityProvider); ok { + return provider.SupportsTransport("acp") + } if _, ok := sp.(acpRoutingProvider); ok { return true } - _, ok := sp.(*sessionacp.Provider) - return ok + return false } diff --git a/internal/runtime/acp/acp.go b/internal/runtime/acp/acp.go index 3299ea8dca..f3a851fd2a 100644 --- a/internal/runtime/acp/acp.go +++ b/internal/runtime/acp/acp.go @@ -67,8 +67,9 @@ type Provider struct { // Compile-time check. var ( - _ runtime.Provider = (*Provider)(nil) - _ runtime.InteractionProvider = (*Provider)(nil) + _ runtime.Provider = (*Provider)(nil) + _ runtime.InteractionProvider = (*Provider)(nil) + _ runtime.TransportCapabilityProvider = (*Provider)(nil) ) // NewProvider returns an ACP [Provider] that stores socket files in @@ -96,6 +97,12 @@ func NewProviderWithDir(dir string, cfg Config) *Provider { } } +// SupportsTransport reports whether this provider can host the requested +// session transport. +func (p *Provider) SupportsTransport(transport string) bool { + return transport == "acp" +} + // Start spawns an ACP agent process, performs the JSON-RPC handshake, and // optionally sends the initial nudge. Returns an error if a session with // that name already exists or the handshake fails. diff --git a/internal/runtime/auto/auto.go b/internal/runtime/auto/auto.go index 85456fe525..fb3bffbd2c 100644 --- a/internal/runtime/auto/auto.go +++ b/internal/runtime/auto/auto.go @@ -29,6 +29,7 @@ var ( _ runtime.InteractionProvider = (*Provider)(nil) _ runtime.InterruptBoundaryWaitProvider = (*Provider)(nil) _ runtime.InterruptedTurnResetProvider = (*Provider)(nil) + _ runtime.TransportCapabilityProvider = (*Provider)(nil) ) // New creates a composite provider. defaultSP handles sessions not @@ -67,6 +68,18 @@ func (p *Provider) route(name string) runtime.Provider { return p.defaultSP } +// SupportsTransport reports whether this provider can route the requested +// session transport. +func (p *Provider) SupportsTransport(transport string) bool { + if transport != "acp" { + return true + } + if provider, ok := p.acpSP.(runtime.TransportCapabilityProvider); ok { + return provider.SupportsTransport(transport) + } + return false +} + // DetectTransport reports the backend currently hosting the named session. // It returns "acp" for ACP-backed sessions and "" for default or unknown. func (p *Provider) DetectTransport(name string) string { diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go index 3090607e33..bd1dc8ca69 100644 --- a/internal/runtime/runtime.go +++ b/internal/runtime/runtime.go @@ -236,6 +236,15 @@ type DialogProvider interface { DismissKnownDialogs(ctx context.Context, name string, timeout time.Duration) error } +// TransportCapabilityProvider is an optional extension for providers that can +// report whether they support starting sessions with a specific transport. +// +// Callers use this to fail fast when a requested transport cannot be routed by +// the active session provider before session creation starts mutating state. +type TransportCapabilityProvider interface { + SupportsTransport(transport string) bool +} + // ImmediateNudgeProvider is an optional extension for runtimes that can inject // input immediately without performing their own wait-idle heuristic first. type ImmediateNudgeProvider interface { From b3f1f1f775f9995e20177454226fc505031cdf79 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 01:31:01 +0000 Subject: [PATCH 51/85] fix: align ACP session startup with transport --- cmd/gc/worker_handle.go | 6 +- cmd/gc/worker_handle_test.go | 43 ++++++++ internal/api/handler_session_chat_test.go | 42 +++++++ internal/api/handler_session_create.go | 11 +- internal/api/handler_sessions_test.go | 104 ++++++++++++++++++ .../api/huma_handlers_sessions_command.go | 4 +- internal/api/session_resolution.go | 2 +- internal/api/session_runtime.go | 12 +- 8 files changed, 210 insertions(+), 14 deletions(-) diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index 77d8590abb..afd1ad1ca7 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -85,9 +85,10 @@ func resolvedRuntimeMCPServersWithConfig( cityPath string, cfg *config.City, alias, template, provider, workDir string, + transport string, metadata map[string]string, ) ([]runtime.MCPServerConfig, error) { - if cfg == nil || strings.TrimSpace(workDir) == "" { + if cfg == nil || strings.TrimSpace(workDir) == "" || strings.TrimSpace(transport) != "acp" { return nil, nil } identity := strings.TrimSpace(metadata["agent_name"]) @@ -128,7 +129,7 @@ func newWorkerSessionHandleForResolvedRuntimeWithConfig( if err != nil { return nil, err } - mcpServers, err := resolvedRuntimeMCPServersWithConfig(cityPath, cfg, alias, template, provider, workDir, metadata) + mcpServers, err := resolvedRuntimeMCPServersWithConfig(cityPath, cfg, alias, template, provider, workDir, transport, metadata) if err != nil { return nil, err } @@ -399,6 +400,7 @@ func resolvedWorkerRuntimeWithConfig(cityPath string, cfg *config.City, info ses info.Template, firstNonEmptyGCString(info.Provider, resolved.Name, info.Template), workDir, + transport, nil, ) if err != nil { diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index 1e44d7eadb..3d7bfee7cf 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -776,6 +776,49 @@ command = [broken } } +func TestResolvedWorkerRuntimeWithConfigIgnoresMCPResolutionErrorWithoutACPTransport(t *testing.T) { + cityDir := t.TempDir() + writePhase0InterfaceCity(t, cityDir, `[workspace] +name = "test-city" + +[beads] +provider = "file" + +[[agent]] +name = "worker" +provider = "stub" + +[providers.stub] +command = "/bin/echo" +`) + writeCatalogFile(t, cityDir, "mcp/filesystem.toml", ` +name = "filesystem" +command = [broken +`) + + cfg, err := loadCityConfig(cityDir) + if err != nil { + t.Fatalf("loadCityConfig: %v", err) + } + + resolved, err := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + Template: "worker", + WorkDir: cityDir, + }, "") + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfig: %v", err) + } + if resolved == nil { + t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") + } + if got, want := resolved.Command, "/bin/echo"; got != want { + t.Fatalf("Command = %q, want %q", got, want) + } + if len(resolved.Hints.MCPServers) != 0 { + t.Fatalf("Hints.MCPServers len = %d, want 0", len(resolved.Hints.MCPServers)) + } +} + func TestWorkerSessionRuntimeResolverWithConfigFallsBackToProviderNameWhenResolvedCommandMissing(t *testing.T) { cfg := &config.City{ Workspace: config.Workspace{Name: "test-city"}, diff --git a/internal/api/handler_session_chat_test.go b/internal/api/handler_session_chat_test.go index dc98c26abc..4805d51f7c 100644 --- a/internal/api/handler_session_chat_test.go +++ b/internal/api/handler_session_chat_test.go @@ -416,3 +416,45 @@ command = [broken t.Fatal("buildSessionResume() error = nil, want MCP resolution error") } } + +func TestBuildSessionResumeIgnoresMCPResolutionErrorWithoutACPTransport(t *testing.T) { + fs := newSessionFakeState(t) + fs.cfg = &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Agents: []config.Agent{ + {Name: "worker", Provider: "stub"}, + }, + Providers: map[string]config.ProviderSpec{ + "stub": { + DisplayName: "Stub", + Command: "/bin/echo", + }, + }, + } + fs.cfg.PackMCPDir = filepath.Join(fs.cityPath, "mcp") + if err := os.MkdirAll(fs.cfg.PackMCPDir, 0o755); err != nil { + t.Fatalf("MkdirAll(mcp): %v", err) + } + if err := os.WriteFile(filepath.Join(fs.cfg.PackMCPDir, "filesystem.toml"), []byte(` +name = "filesystem" +command = [broken +`), 0o644); err != nil { + t.Fatalf("WriteFile(mcp): %v", err) + } + + srv := New(fs) + info := session.Info{ + ID: "gc-1", + Template: "worker", + Provider: "stub", + WorkDir: fs.cityPath, + } + + cmd, _, err := srv.buildSessionResume(info) + if err != nil { + t.Fatalf("buildSessionResume: %v", err) + } + if got, want := cmd, "/bin/echo"; got != want { + t.Fatalf("resume command = %q, want %q", got, want) + } +} diff --git a/internal/api/handler_session_create.go b/internal/api/handler_session_create.go index f22fa682a4..f2e66d7dc8 100644 --- a/internal/api/handler_session_create.go +++ b/internal/api/handler_session_create.go @@ -127,14 +127,14 @@ func (s *Server) handleSessionCreate(w http.ResponseWriter, r *http.Request) { return } - mcpServers, err := s.sessionMCPServers(template, resolved.Name, firstNonEmptyString(alias, template), workDir, kind) + mcpServers, err := s.sessionMCPServers(template, resolved.Name, firstNonEmptyString(alias, template), workDir, transport, kind) if err != nil { s.idem.unreserve(idemKey) writeError(w, http.StatusInternalServerError, "internal", err.Error()) return } - command := sessionCreateAgentCommand(resolved) + command := sessionCreateAgentCommand(resolved, transport) // Build template_overrides metadata. Includes schema overrides AND // the initial message (as "initial_message" key). The reconciler @@ -298,7 +298,7 @@ func (s *Server) createProviderSession(w http.ResponseWriter, r *http.Request, s return } command := launchCommand.Command - mcpServers, err := s.providerSessionMCPServers(providerName, workDir) + mcpServers, err := s.providerSessionMCPServers(providerName, workDir, transport) if err != nil { s.idem.unreserve(idemKey) writeError(w, http.StatusInternalServerError, "internal", err.Error()) @@ -378,7 +378,10 @@ func (s *Server) createProviderSession(w http.ResponseWriter, r *http.Request, s writeJSON(w, statusCode, resp) } -func sessionCreateAgentCommand(resolved *config.ResolvedProvider) string { +func sessionCreateAgentCommand(resolved *config.ResolvedProvider, transport string) string { + if strings.TrimSpace(transport) == "acp" { + return firstNonEmptyString(resolved.ACPCommandString(), resolved.Name) + } return firstNonEmptyString(resolved.CommandString(), resolved.Name) } diff --git a/internal/api/handler_sessions_test.go b/internal/api/handler_sessions_test.go index ff707c8da4..8fb9c512bc 100644 --- a/internal/api/handler_sessions_test.go +++ b/internal/api/handler_sessions_test.go @@ -1144,6 +1144,110 @@ func TestHandleSessionCreate(t *testing.T) { } } +func TestHandleSessionCreateUsesACPTransportCommandForAgentTemplate(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg.Agents[0].Provider = "opencode" + fs.cfg.Agents[0].Session = "acp" + fs.cfg.Providers["opencode"] = config.ProviderSpec{ + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + srv := New(fs) + h := newTestCityHandlerWith(t, fs, srv) + + req := newPostRequest(cityURL(fs, "/sessions"), strings.NewReader(`{"kind":"agent","name":"myrig/worker"}`)) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + + if rec.Code != http.StatusAccepted { + t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusAccepted, rec.Body.String()) + } + + var resp sessionResponse + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode: %v", err) + } + bead, err := fs.cityBeadStore.Get(resp.ID) + if err != nil { + t.Fatalf("Get(%s): %v", resp.ID, err) + } + if got, want := bead.Metadata["command"], "/bin/echo acp"; got != want { + t.Fatalf("command metadata = %q, want %q", got, want) + } + if got, want := bead.Metadata["transport"], "acp"; got != want { + t.Fatalf("transport metadata = %q, want %q", got, want) + } +} + +func TestHumaHandleSessionCreateUsesACPTransportCommandForAgentTemplate(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg.Agents[0].Provider = "opencode" + fs.cfg.Agents[0].Session = "acp" + fs.cfg.Providers["opencode"] = config.ProviderSpec{ + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + srv := New(fs) + + out, err := srv.humaHandleSessionCreate(context.Background(), &SessionCreateInput{ + Body: sessionCreateBody{ + Kind: "agent", + Name: "myrig/worker", + }, + }) + if err != nil { + t.Fatalf("humaHandleSessionCreate: %v", err) + } + if got, want := out.Status, http.StatusAccepted; got != want { + t.Fatalf("status = %d, want %d", got, want) + } + bead, err := fs.cityBeadStore.Get(out.Body.ID) + if err != nil { + t.Fatalf("Get(%s): %v", out.Body.ID, err) + } + if got, want := bead.Metadata["command"], "/bin/echo acp"; got != want { + t.Fatalf("command metadata = %q, want %q", got, want) + } + if got, want := bead.Metadata["transport"], "acp"; got != want { + t.Fatalf("transport metadata = %q, want %q", got, want) + } +} + +func TestHandleSessionCreateIgnoresBrokenMCPWithoutACPTransport(t *testing.T) { + fs := newSessionFakeState(t) + fs.cfg.PackMCPDir = filepath.Join(fs.cityPath, "mcp") + if err := os.MkdirAll(fs.cfg.PackMCPDir, 0o755); err != nil { + t.Fatalf("MkdirAll(mcp): %v", err) + } + if err := os.WriteFile(filepath.Join(fs.cfg.PackMCPDir, "filesystem.toml"), []byte(` +name = "filesystem" +command = [broken +`), 0o644); err != nil { + t.Fatalf("WriteFile(mcp): %v", err) + } + + srv := New(fs) + h := newTestCityHandlerWith(t, fs, srv) + + req := newPostRequest(cityURL(fs, "/sessions"), strings.NewReader(`{"kind":"agent","name":"myrig/worker"}`)) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + + if rec.Code != http.StatusAccepted { + t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusAccepted, rec.Body.String()) + } +} + func TestHandleSessionCreateAsync(t *testing.T) { fs := newSessionFakeState(t) srv := New(fs) diff --git a/internal/api/huma_handlers_sessions_command.go b/internal/api/huma_handlers_sessions_command.go index 7f81cd54ec..2094575e30 100644 --- a/internal/api/huma_handlers_sessions_command.go +++ b/internal/api/huma_handlers_sessions_command.go @@ -105,7 +105,7 @@ func (s *Server) humaHandleSessionCreate(ctx context.Context, input *SessionCrea return nil, huma.Error500InternalServerError(err.Error()) } - command := sessionCreateAgentCommand(resolved) + command := sessionCreateAgentCommand(resolved, transport) extraMeta := sessionTemplateOverridesMetadata(body.Options, body.Message) mgr := s.sessionManager(store) @@ -243,7 +243,7 @@ func (s *Server) humaCreateProviderSession(ctx context.Context, store beads.Stor return nil, huma.Error400BadRequest(err.Error()) } command := launchCommand.Command - mcpServers, err := s.providerSessionMCPServers(resolved.Name, workDir) + mcpServers, err := s.providerSessionMCPServers(resolved.Name, workDir, transport) if err != nil { return nil, huma.Error500InternalServerError(err.Error()) } diff --git a/internal/api/session_resolution.go b/internal/api/session_resolution.go index 8b524ffcbe..b3e41341af 100644 --- a/internal/api/session_resolution.go +++ b/internal/api/session_resolution.go @@ -308,7 +308,7 @@ func (s *Server) materializeNamedSessionWithContext(ctx context.Context, store b if resolved.BuiltinAncestor != "" && resolved.BuiltinAncestor != resolved.Name { extraMeta["builtin_ancestor"] = resolved.BuiltinAncestor } - mcpServers, err := s.sessionMCPServers(qualifiedTemplate, resolved.Name, spec.Identity, workDir, "") + mcpServers, err := s.sessionMCPServers(qualifiedTemplate, resolved.Name, spec.Identity, workDir, transport, "") if err != nil { return "", err } diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index 2b545bba60..e937f396e7 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -47,9 +47,9 @@ func sessionResumeHints(resolved *config.ResolvedProvider, workDir string, mcpSe } } -func (s *Server) providerSessionMCPServers(providerName, workDir string) ([]runtime.MCPServerConfig, error) { +func (s *Server) providerSessionMCPServers(providerName, workDir, transport string) ([]runtime.MCPServerConfig, error) { cfg := s.state.Config() - if cfg == nil || strings.TrimSpace(workDir) == "" { + if cfg == nil || strings.TrimSpace(workDir) == "" || strings.TrimSpace(transport) != "acp" { return nil, nil } synthetic := &config.Agent{Provider: providerName} @@ -60,9 +60,9 @@ func (s *Server) providerSessionMCPServers(providerName, workDir string) ([]runt return materialize.RuntimeMCPServers(catalog.Servers), nil } -func (s *Server) sessionMCPServers(template, providerName, identity, workDir, sessionKind string) ([]runtime.MCPServerConfig, error) { +func (s *Server) sessionMCPServers(template, providerName, identity, workDir, transport, sessionKind string) ([]runtime.MCPServerConfig, error) { cfg := s.state.Config() - if cfg == nil || strings.TrimSpace(workDir) == "" { + if cfg == nil || strings.TrimSpace(workDir) == "" || strings.TrimSpace(transport) != "acp" { return nil, nil } if sessionKind != "provider" { @@ -80,7 +80,7 @@ func (s *Server) sessionMCPServers(template, providerName, identity, workDir, se return materialize.RuntimeMCPServers(catalog.Servers), nil } } - return s.providerSessionMCPServers(firstNonEmptyString(providerName, template), workDir) + return s.providerSessionMCPServers(firstNonEmptyString(providerName, template), workDir, transport) } func sessionExplicitNameForCreate(agentCfg config.Agent, alias string) (string, error) { @@ -161,6 +161,7 @@ func (s *Server) buildSessionResume(info session.Info) (string, runtime.Config, firstNonEmptyString(info.Provider, resolved.Name), info.Alias, firstNonEmptyString(workDir, info.WorkDir), + transport, s.sessionKind(info.ID), ) if err != nil { @@ -230,6 +231,7 @@ func (s *Server) resolveWorkerSessionRuntime(info session.Info, _ string) (*work firstNonEmptyString(info.Provider, resolved.Name), info.Alias, firstNonEmptyString(workDir, info.WorkDir), + transport, s.sessionKind(info.ID), ) if err != nil { From 978951d1e5ad347219e02f60e9ce6d3b81629757 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 01:40:05 +0000 Subject: [PATCH 52/85] fix: fail fast for ACP session routing --- internal/api/handler_session_create.go | 25 ++-- internal/api/handler_sessions_test.go | 109 +++++++++++++++++- .../api/huma_handlers_sessions_command.go | 10 +- internal/api/session_resolution.go | 4 + internal/api/session_transport.go | 26 ++++- internal/config/launch_command.go | 36 +++++- internal/config/launch_command_test.go | 30 +++++ 7 files changed, 213 insertions(+), 27 deletions(-) diff --git a/internal/api/handler_session_create.go b/internal/api/handler_session_create.go index f2e66d7dc8..71cf2abb3e 100644 --- a/internal/api/handler_session_create.go +++ b/internal/api/handler_session_create.go @@ -88,8 +88,14 @@ func (s *Server) handleSessionCreate(w http.ResponseWriter, r *http.Request) { writeError(w, http.StatusInternalServerError, "internal", err.Error()) return } - // Agent track: command comes from the agent config as-is. - // Do NOT inject OptionsSchema defaults — agents encode their own CLI flags. + transport, err = validateSessionTransport(resolved, transport, s.state.SessionProvider()) + if err != nil { + s.idem.unreserve(idemKey) + writeError(w, http.StatusServiceUnavailable, "provider_unavailable", err.Error()) + return + } + // Agent track stores a transport-aligned base command only. + // Do NOT inject OptionsSchema defaults or explicit overrides here. // Options are stored as template_overrides and applied at start time // by the session lifecycle via ResolveExplicitOptions. if len(body.Options) > 0 { @@ -134,7 +140,13 @@ func (s *Server) handleSessionCreate(w http.ResponseWriter, r *http.Request) { return } - command := sessionCreateAgentCommand(resolved, transport) + launchCommand, err := config.BuildProviderLaunchCommandWithoutOptions(s.state.CityPath(), resolved, transport) + if err != nil { + s.idem.unreserve(idemKey) + writeError(w, http.StatusInternalServerError, "internal", err.Error()) + return + } + command := launchCommand.Command // Build template_overrides metadata. Includes schema overrides AND // the initial message (as "initial_message" key). The reconciler @@ -378,13 +390,6 @@ func (s *Server) createProviderSession(w http.ResponseWriter, r *http.Request, s writeJSON(w, statusCode, resp) } -func sessionCreateAgentCommand(resolved *config.ResolvedProvider, transport string) string { - if strings.TrimSpace(transport) == "acp" { - return firstNonEmptyString(resolved.ACPCommandString(), resolved.Name) - } - return firstNonEmptyString(resolved.CommandString(), resolved.Name) -} - func sessionTemplateOverridesMetadata(options map[string]string, message string) map[string]string { allOverrides := make(map[string]string, len(options)+1) for k, v := range options { diff --git a/internal/api/handler_sessions_test.go b/internal/api/handler_sessions_test.go index 8fb9c512bc..f67c329599 100644 --- a/internal/api/handler_sessions_test.go +++ b/internal/api/handler_sessions_test.go @@ -1157,8 +1157,12 @@ func TestHandleSessionCreateUsesACPTransportCommandForAgentTemplate(t *testing.T ACPCommand: "/bin/echo", ACPArgs: []string{"acp"}, } - srv := New(fs) - h := newTestCityHandlerWith(t, fs, srv) + state := &stateWithSessionProvider{ + fakeState: fs, + provider: &transportCapableProvider{Fake: runtime.NewFake()}, + } + srv := New(state) + h := newTestCityHandlerWith(t, state, srv) req := newPostRequest(cityURL(fs, "/sessions"), strings.NewReader(`{"kind":"agent","name":"myrig/worker"}`)) rec := httptest.NewRecorder() @@ -1172,7 +1176,7 @@ func TestHandleSessionCreateUsesACPTransportCommandForAgentTemplate(t *testing.T if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { t.Fatalf("decode: %v", err) } - bead, err := fs.cityBeadStore.Get(resp.ID) + bead, err := state.cityBeadStore.Get(resp.ID) if err != nil { t.Fatalf("Get(%s): %v", resp.ID, err) } @@ -1197,7 +1201,11 @@ func TestHumaHandleSessionCreateUsesACPTransportCommandForAgentTemplate(t *testi ACPCommand: "/bin/echo", ACPArgs: []string{"acp"}, } - srv := New(fs) + state := &stateWithSessionProvider{ + fakeState: fs, + provider: &transportCapableProvider{Fake: runtime.NewFake()}, + } + srv := New(state) out, err := srv.humaHandleSessionCreate(context.Background(), &SessionCreateInput{ Body: sessionCreateBody{ @@ -1211,7 +1219,7 @@ func TestHumaHandleSessionCreateUsesACPTransportCommandForAgentTemplate(t *testi if got, want := out.Status, http.StatusAccepted; got != want { t.Fatalf("status = %d, want %d", got, want) } - bead, err := fs.cityBeadStore.Get(out.Body.ID) + bead, err := state.cityBeadStore.Get(out.Body.ID) if err != nil { t.Fatalf("Get(%s): %v", out.Body.ID, err) } @@ -1223,6 +1231,61 @@ func TestHumaHandleSessionCreateUsesACPTransportCommandForAgentTemplate(t *testi } } +func TestHandleSessionCreateRejectsACPAgentWithoutACPRouting(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg.Agents[0].Provider = "opencode" + fs.cfg.Agents[0].Session = "acp" + fs.cfg.Providers["opencode"] = config.ProviderSpec{ + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + srv := New(fs) + h := newTestCityHandlerWith(t, fs, srv) + + req := newPostRequest(cityURL(fs, "/sessions"), strings.NewReader(`{"kind":"agent","name":"myrig/worker"}`)) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + + if rec.Code != http.StatusServiceUnavailable { + t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusServiceUnavailable, rec.Body.String()) + } + if !strings.Contains(rec.Body.String(), "requires ACP transport") { + t.Fatalf("body = %q, want ACP transport error", rec.Body.String()) + } +} + +func TestHumaHandleSessionCreateRejectsACPAgentWithoutACPRouting(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg.Agents[0].Provider = "opencode" + fs.cfg.Agents[0].Session = "acp" + fs.cfg.Providers["opencode"] = config.ProviderSpec{ + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + srv := New(fs) + + if _, err := srv.humaHandleSessionCreate(context.Background(), &SessionCreateInput{ + Body: sessionCreateBody{ + Kind: "agent", + Name: "myrig/worker", + }, + }); err == nil { + t.Fatal("humaHandleSessionCreate() error = nil, want ACP routing error") + } else if !strings.Contains(err.Error(), "requires ACP transport") { + t.Fatalf("humaHandleSessionCreate() error = %v, want ACP transport error", err) + } +} + func TestHandleSessionCreateIgnoresBrokenMCPWithoutACPTransport(t *testing.T) { fs := newSessionFakeState(t) fs.cfg.PackMCPDir = filepath.Join(fs.cityPath, "mcp") @@ -1504,6 +1567,42 @@ func TestMaterializeNamedSessionStampsProviderFamilyMetadata(t *testing.T) { } } +func TestMaterializeNamedSessionRejectsACPTemplateWithoutACPRouting(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg.Agents[0].Provider = "opencode" + fs.cfg.Agents[0].Session = "acp" + fs.cfg.Providers["opencode"] = config.ProviderSpec{ + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + srv := New(fs) + + spec, ok, err := srv.findNamedSessionSpecForTarget(fs.cityBeadStore, "worker") + if err != nil { + t.Fatalf("findNamedSessionSpecForTarget: %v", err) + } + if !ok { + t.Fatal("expected named session spec") + } + if _, err := srv.materializeNamedSession(fs.cityBeadStore, spec); err == nil { + t.Fatal("materializeNamedSession() error = nil, want ACP routing error") + } else if !strings.Contains(err.Error(), "requires ACP transport") { + t.Fatalf("materializeNamedSession() error = %v, want ACP transport error", err) + } + items, err := fs.cityBeadStore.ListByLabel(session.LabelSession, 0) + if err != nil { + t.Fatalf("ListByLabel: %v", err) + } + if len(items) != 0 { + t.Fatalf("session bead count = %d, want 0", len(items)) + } +} + func TestHandleProviderSessionCreateWithMessageUsesProviderDefaultNudge(t *testing.T) { fs := newSessionFakeState(t) srv := New(fs) diff --git a/internal/api/huma_handlers_sessions_command.go b/internal/api/huma_handlers_sessions_command.go index 2094575e30..777ec876e0 100644 --- a/internal/api/huma_handlers_sessions_command.go +++ b/internal/api/huma_handlers_sessions_command.go @@ -56,6 +56,10 @@ func (s *Server) humaHandleSessionCreate(ctx context.Context, input *SessionCrea } return nil, huma.Error500InternalServerError(err.Error()) } + transport, err = validateSessionTransport(resolved, transport, s.state.SessionProvider()) + if err != nil { + return nil, huma.Error503ServiceUnavailable(err.Error()) + } if len(body.Options) > 0 { if len(resolved.OptionsSchema) == 0 { @@ -105,7 +109,11 @@ func (s *Server) humaHandleSessionCreate(ctx context.Context, input *SessionCrea return nil, huma.Error500InternalServerError(err.Error()) } - command := sessionCreateAgentCommand(resolved, transport) + launchCommand, err := config.BuildProviderLaunchCommandWithoutOptions(s.state.CityPath(), resolved, transport) + if err != nil { + return nil, huma.Error500InternalServerError(err.Error()) + } + command := launchCommand.Command extraMeta := sessionTemplateOverridesMetadata(body.Options, body.Message) mgr := s.sessionManager(store) diff --git a/internal/api/session_resolution.go b/internal/api/session_resolution.go index b3e41341af..5b90b2699a 100644 --- a/internal/api/session_resolution.go +++ b/internal/api/session_resolution.go @@ -279,6 +279,10 @@ func (s *Server) materializeNamedSessionWithContext(ctx context.Context, store b if err != nil { return "", err } + transport, err = validateSessionTransport(resolved, transport, s.state.SessionProvider()) + if err != nil { + return "", err + } var workDir string workDirQualifiedName := workdirutil.SessionQualifiedName(s.state.CityPath(), *spec.Agent, s.state.Config().Rigs, spec.Identity, "") workDir, err = s.resolveSessionWorkDir(*spec.Agent, workDirQualifiedName) diff --git a/internal/api/session_transport.go b/internal/api/session_transport.go index 9b88a2bbae..187c64433a 100644 --- a/internal/api/session_transport.go +++ b/internal/api/session_transport.go @@ -2,6 +2,7 @@ package api import ( "fmt" + "strings" "github.com/gastownhall/gascity/internal/config" "github.com/gastownhall/gascity/internal/runtime" @@ -11,14 +12,29 @@ type acpRoutingProvider interface { RouteACP(name string) } -func providerSessionTransport(resolved *config.ResolvedProvider, sp runtime.Provider) (string, error) { - if resolved == nil || resolved.DefaultSessionTransport() != "acp" { - return "", nil +func validateSessionTransport(resolved *config.ResolvedProvider, transport string, sp runtime.Provider) (string, error) { + transport = strings.TrimSpace(transport) + if transport != "acp" { + return transport, nil } if transportSupportsACP(sp) { - return "acp", nil + return transport, nil + } + providerName := "" + if resolved != nil { + providerName = resolved.Name + } + if providerName == "" { + providerName = transport + } + return "", fmt.Errorf("provider %q requires ACP transport but the session provider cannot route ACP sessions", providerName) +} + +func providerSessionTransport(resolved *config.ResolvedProvider, sp runtime.Provider) (string, error) { + if resolved == nil { + return "", nil } - return "", fmt.Errorf("provider %q requires ACP transport but the session provider cannot route ACP sessions", resolved.Name) + return validateSessionTransport(resolved, resolved.DefaultSessionTransport(), sp) } func transportSupportsACP(sp runtime.Provider) bool { diff --git a/internal/config/launch_command.go b/internal/config/launch_command.go index 78d43161eb..c9444d6a6d 100644 --- a/internal/config/launch_command.go +++ b/internal/config/launch_command.go @@ -31,10 +31,7 @@ func BuildProviderLaunchCommand(cityPath string, resolved *ResolvedProvider, opt return ProviderLaunchCommand{}, fmt.Errorf("resolved provider is nil") } - command := resolved.CommandString() - if transport == "acp" { - command = resolved.ACPCommandString() - } + command := providerLaunchBaseCommand(resolved, transport) if len(resolved.OptionsSchema) > 0 { mergedOptions := make(map[string]string, len(resolved.EffectiveDefaults)+len(optionOverrides)) for key, value := range resolved.EffectiveDefaults { @@ -55,7 +52,34 @@ func BuildProviderLaunchCommand(cityPath string, resolved *ResolvedProvider, opt } } - settingsPath, settingsRel := ProviderSettingsSource(cityPath, resolved.Name) + return appendProviderSettings(cityPath, resolved.Name, command), nil +} + +// BuildProviderLaunchCommandWithoutOptions composes the transport-specific +// provider command plus any provider-owned settings file without applying +// schema-managed defaults or explicit option overrides. +// +// Deferred agent-session creation uses this helper because option state is +// stored separately in template_overrides and applied later at actual start +// time, but the stored base command must still match the selected transport +// and provider-owned settings semantics. +func BuildProviderLaunchCommandWithoutOptions(cityPath string, resolved *ResolvedProvider, transport string) (ProviderLaunchCommand, error) { + if resolved == nil { + return ProviderLaunchCommand{}, fmt.Errorf("resolved provider is nil") + } + return appendProviderSettings(cityPath, resolved.Name, providerLaunchBaseCommand(resolved, transport)), nil +} + +func providerLaunchBaseCommand(resolved *ResolvedProvider, transport string) string { + command := resolved.CommandString() + if transport == "acp" { + command = resolved.ACPCommandString() + } + return command +} + +func appendProviderSettings(cityPath, providerName, command string) ProviderLaunchCommand { + settingsPath, settingsRel := ProviderSettingsSource(cityPath, providerName) if settingsPath != "" { command = command + " " + fmt.Sprintf("--settings %q", settingsPath) } @@ -64,7 +88,7 @@ func BuildProviderLaunchCommand(cityPath string, resolved *ResolvedProvider, opt Command: command, SettingsPath: settingsPath, SettingsRel: settingsRel, - }, nil + } } // ProviderSettingsSource returns the provider-owned settings file that should diff --git a/internal/config/launch_command_test.go b/internal/config/launch_command_test.go index befc7e73ca..93fe87f260 100644 --- a/internal/config/launch_command_test.go +++ b/internal/config/launch_command_test.go @@ -104,3 +104,33 @@ func TestBuildProviderLaunchCommandUsesACPCommand(t *testing.T) { } }) } + +func TestBuildProviderLaunchCommandWithoutOptionsSkipsDefaultsButKeepsSettings(t *testing.T) { + dir := t.TempDir() + runtimeDir := filepath.Join(dir, ".gc") + if err := os.MkdirAll(runtimeDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(runtimeDir, "settings.json"), []byte(`{}`), 0o644); err != nil { + t.Fatal(err) + } + + spec := BuiltinProviders()["claude"] + rp := specToResolved("claude", &spec) + + got, err := BuildProviderLaunchCommandWithoutOptions(dir, rp, "") + if err != nil { + t.Fatalf("BuildProviderLaunchCommandWithoutOptions: %v", err) + } + + wantCommand := fmt.Sprintf("claude --settings %q", filepath.Join(dir, ".gc", "settings.json")) + if got.Command != wantCommand { + t.Fatalf("Command = %q, want %q", got.Command, wantCommand) + } + if got.SettingsPath != filepath.Join(dir, ".gc", "settings.json") { + t.Fatalf("SettingsPath = %q, want %q", got.SettingsPath, filepath.Join(dir, ".gc", "settings.json")) + } + if got.SettingsRel != filepath.Join(".gc", "settings.json") { + t.Fatalf("SettingsRel = %q, want %q", got.SettingsRel, filepath.Join(".gc", "settings.json")) + } +} From 48800d5260f1074a92a69a79e877a23eedcf8845 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 01:51:53 +0000 Subject: [PATCH 53/85] fix: validate ACP support across entrypoints --- cmd/gc/cmd_session.go | 35 ++++++++++ cmd/gc/cmd_session_test.go | 36 ++++++++++ cmd/gc/session_template_start.go | 10 ++- internal/api/handler_sessions_test.go | 69 +++++++++++++++++++ .../api/huma_handlers_sessions_command.go | 32 +++++---- internal/api/session_transport.go | 12 +++- 6 files changed, 176 insertions(+), 18 deletions(-) diff --git a/cmd/gc/cmd_session.go b/cmd/gc/cmd_session.go index 82e91b48f1..923991c793 100644 --- a/cmd/gc/cmd_session.go +++ b/cmd/gc/cmd_session.go @@ -192,6 +192,10 @@ func cmdSessionNew(args []string, alias, title, titleHint string, noAttach bool, } sp := newSessionProvider() + if err := validateResolvedSessionTransport(resolved, found.Session, sp); err != nil { + fmt.Fprintf(stderr, "gc session new: %v\n", err) //nolint:errcheck // best-effort stderr + return 1 + } // Build the work directory. sessionQualifiedName := workdirutil.SessionQualifiedName(cityPath, found, cfg.Rigs, requestedAlias, explicitName) @@ -401,6 +405,37 @@ func maybeAutoTitle(store beads.Store, beadID, userTitle, titleHint string, prov }) } +type acpRouteRegistrar interface { + RouteACP(name string) +} + +func validateResolvedSessionTransport(resolved *config.ResolvedProvider, transport string, sp runtime.Provider) error { + transport = strings.TrimSpace(transport) + if transport != "acp" { + return nil + } + providerName := "" + if resolved != nil { + providerName = resolved.Name + if !resolved.SupportsACP { + if providerName == "" { + providerName = transport + } + return fmt.Errorf("provider %q does not support ACP transport", providerName) + } + } + if provider, ok := sp.(runtime.TransportCapabilityProvider); ok && provider.SupportsTransport("acp") { + return nil + } + if _, ok := sp.(acpRouteRegistrar); ok { + return nil + } + if providerName == "" { + providerName = transport + } + return fmt.Errorf("provider %q requires ACP transport but the session provider cannot route ACP sessions", providerName) +} + func resolvedSessionCommand(cityPath string, resolved *config.ResolvedProvider, optionOverrides map[string]string, transport string) (string, error) { if resolved == nil { return "", fmt.Errorf("resolved provider is nil") diff --git a/cmd/gc/cmd_session_test.go b/cmd/gc/cmd_session_test.go index 9bbd25fe23..15d887d751 100644 --- a/cmd/gc/cmd_session_test.go +++ b/cmd/gc/cmd_session_test.go @@ -53,6 +53,14 @@ func (p *attachmentAwareProvider) Respond(_ string, response runtime.Interaction return nil } +type transportCapableSessionProvider struct { + *runtime.Fake +} + +func (p *transportCapableSessionProvider) SupportsTransport(transport string) bool { + return transport == "acp" +} + func TestFormatDuration(t *testing.T) { tests := []struct { d time.Duration @@ -1357,3 +1365,31 @@ func TestResolvedSessionCommandUsesACPTransportCommand(t *testing.T) { t.Fatalf("command = %q, want %q", got, "/bin/echo acp") } } + +func TestValidateResolvedSessionTransportRejectsUnsupportedACPProvider(t *testing.T) { + err := validateResolvedSessionTransport(&config.ResolvedProvider{ + Name: "opencode", + }, "acp", &transportCapableSessionProvider{Fake: runtime.NewFake()}) + if err == nil || !strings.Contains(err.Error(), "does not support ACP transport") { + t.Fatalf("validateResolvedSessionTransport() error = %v, want provider ACP support error", err) + } +} + +func TestValidateResolvedSessionTransportRejectsUnroutableACPProvider(t *testing.T) { + err := validateResolvedSessionTransport(&config.ResolvedProvider{ + Name: "opencode", + SupportsACP: true, + }, "acp", runtime.NewFake()) + if err == nil || !strings.Contains(err.Error(), "requires ACP transport") { + t.Fatalf("validateResolvedSessionTransport() error = %v, want ACP routing error", err) + } +} + +func TestValidateResolvedSessionTransportAcceptsRoutedACPProvider(t *testing.T) { + if err := validateResolvedSessionTransport(&config.ResolvedProvider{ + Name: "opencode", + SupportsACP: true, + }, "acp", &transportCapableSessionProvider{Fake: runtime.NewFake()}); err != nil { + t.Fatalf("validateResolvedSessionTransport() = %v, want nil", err) + } +} diff --git a/cmd/gc/session_template_start.go b/cmd/gc/session_template_start.go index 538882f5fc..6d0dcfdc5b 100644 --- a/cmd/gc/session_template_start.go +++ b/cmd/gc/session_template_start.go @@ -119,6 +119,10 @@ func materializeSessionForTemplateWithOptions( if err != nil { return "", err } + sp := newSessionProvider() + if err := validateResolvedSessionTransport(resolved, spec.Agent.Session, sp); err != nil { + return "", err + } sessionCommand, err := resolvedSessionCommand(cityPath, resolved, nil, spec.Agent.Session) if err != nil { return "", err @@ -129,7 +133,6 @@ func materializeSessionForTemplateWithOptions( return "", err } - sp := newSessionProvider() title := spec.Identity templateIdentity := namedSessionBackingTemplate(spec) extraMeta := map[string]string{ @@ -272,6 +275,10 @@ func materializeSessionForAgentConfig(cityPath string, cfg *config.City, store b if err != nil { return "", err } + sp := newSessionProvider() + if err := validateResolvedSessionTransport(resolved, agentCfg.Session, sp); err != nil { + return "", err + } sessionCommand, err := resolvedSessionCommand(cityPath, resolved, nil, agentCfg.Session) if err != nil { return "", err @@ -291,7 +298,6 @@ func materializeSessionForAgentConfig(cityPath string, cfg *config.City, store b return "", err } - sp := newSessionProvider() title := agentCfg.QualifiedName() extraMeta := map[string]string{ "agent_name": sessionQualifiedName, diff --git a/internal/api/handler_sessions_test.go b/internal/api/handler_sessions_test.go index f67c329599..ce13e8a212 100644 --- a/internal/api/handler_sessions_test.go +++ b/internal/api/handler_sessions_test.go @@ -1286,6 +1286,75 @@ func TestHumaHandleSessionCreateRejectsACPAgentWithoutACPRouting(t *testing.T) { } } +func TestHandleSessionCreateRejectsACPAgentWhenProviderLacksACP(t *testing.T) { + fs := newSessionFakeState(t) + fs.cfg.Agents[0].Provider = "custom" + fs.cfg.Agents[0].Session = "acp" + fs.cfg.Providers["custom"] = config.ProviderSpec{ + DisplayName: "Custom", + Command: "/bin/echo", + PathCheck: "true", + } + state := &stateWithSessionProvider{ + fakeState: fs, + provider: &transportCapableProvider{Fake: runtime.NewFake()}, + } + srv := New(state) + h := newTestCityHandlerWith(t, state, srv) + + req := newPostRequest(cityURL(fs, "/sessions"), strings.NewReader(`{"kind":"agent","name":"myrig/worker"}`)) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + + if rec.Code != http.StatusServiceUnavailable { + t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusServiceUnavailable, rec.Body.String()) + } + if !strings.Contains(rec.Body.String(), "does not support ACP transport") { + t.Fatalf("body = %q, want provider ACP support error", rec.Body.String()) + } +} + +func TestHumaHandleSessionCreatePropagatesMCPResolutionErrorForACPAgent(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg.Agents[0].Provider = "opencode" + fs.cfg.Agents[0].Session = "acp" + fs.cfg.Providers["opencode"] = config.ProviderSpec{ + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + fs.cfg.PackMCPDir = filepath.Join(fs.cityPath, "mcp") + if err := os.MkdirAll(fs.cfg.PackMCPDir, 0o755); err != nil { + t.Fatalf("MkdirAll(mcp): %v", err) + } + if err := os.WriteFile(filepath.Join(fs.cfg.PackMCPDir, "filesystem.toml"), []byte(` +name = "filesystem" +command = [broken +`), 0o644); err != nil { + t.Fatalf("WriteFile(mcp): %v", err) + } + state := &stateWithSessionProvider{ + fakeState: fs, + provider: &transportCapableProvider{Fake: runtime.NewFake()}, + } + srv := New(state) + + if _, err := srv.humaHandleSessionCreate(context.Background(), &SessionCreateInput{ + Body: sessionCreateBody{ + Kind: "agent", + Name: "myrig/worker", + }, + }); err == nil { + t.Fatal("humaHandleSessionCreate() error = nil, want MCP resolution error") + } else if !strings.Contains(err.Error(), "loading effective MCP") { + t.Fatalf("humaHandleSessionCreate() error = %v, want MCP resolution error", err) + } +} + func TestHandleSessionCreateIgnoresBrokenMCPWithoutACPTransport(t *testing.T) { fs := newSessionFakeState(t) fs.cfg.PackMCPDir = filepath.Join(fs.cityPath, "mcp") diff --git a/internal/api/huma_handlers_sessions_command.go b/internal/api/huma_handlers_sessions_command.go index 777ec876e0..00d19747b4 100644 --- a/internal/api/huma_handlers_sessions_command.go +++ b/internal/api/huma_handlers_sessions_command.go @@ -18,6 +18,7 @@ import ( "github.com/gastownhall/gascity/internal/session" "github.com/gastownhall/gascity/internal/sessionlog" workdirutil "github.com/gastownhall/gascity/internal/workdir" + "github.com/gastownhall/gascity/internal/worker" ) // Command-side session handlers (create, patch, submit, message, stop, kill, @@ -78,12 +79,6 @@ func (s *Server) humaHandleSessionCreate(ctx context.Context, input *SessionCrea title = template } - resume := session.ProviderResume{ - ResumeFlag: resolved.ResumeFlag, - ResumeStyle: resolved.ResumeStyle, - ResumeCommand: resolved.ResumeCommand, - SessionIDFlag: resolved.SessionIDFlag, - } alias, err := session.ValidateAlias(body.Alias) if err != nil { return nil, humaSessionManagerError(err) @@ -115,8 +110,11 @@ func (s *Server) humaHandleSessionCreate(ctx context.Context, input *SessionCrea } command := launchCommand.Command extraMeta := sessionTemplateOverridesMetadata(body.Options, body.Message) + mcpServers, err := s.sessionMCPServers(template, resolved.Name, firstNonEmptyString(alias, template), workDir, transport, kind) + if err != nil { + return nil, huma.Error500InternalServerError(err.Error()) + } - mgr := s.sessionManager(store) var info session.Info reservationIDs := []string{alias, explicitName} reserveConcreteIdentity := agentCfg.SupportsMultipleSessions() && strings.TrimSpace(workDirQualifiedName) != "" @@ -140,19 +138,27 @@ func (s *Server) humaHandleSessionCreate(ctx context.Context, input *SessionCrea } extraMeta["agent_name"] = workDirQualifiedName extraMeta["session_origin"] = "manual" - var createErr error - info, createErr = mgr.CreateAliasedBeadOnlyNamedWithMetadata( + resolvedCfg, cfgErr := resolvedSessionConfigForProvider( alias, explicitName, template, title, - command, - workDir, - resolved.Name, transport, - resume, extraMeta, + resolved, + command, + workDir, + mcpServers, ) + if cfgErr != nil { + return cfgErr + } + handle, handleErr := s.newResolvedWorkerSessionHandle(store, resolvedCfg) + if handleErr != nil { + return handleErr + } + var createErr error + info, createErr = handle.Create(ctx, worker.CreateModeDeferred) return createErr }) if err != nil { diff --git a/internal/api/session_transport.go b/internal/api/session_transport.go index 187c64433a..f076306c4d 100644 --- a/internal/api/session_transport.go +++ b/internal/api/session_transport.go @@ -17,12 +17,18 @@ func validateSessionTransport(resolved *config.ResolvedProvider, transport strin if transport != "acp" { return transport, nil } - if transportSupportsACP(sp) { - return transport, nil - } providerName := "" if resolved != nil { providerName = resolved.Name + if !resolved.SupportsACP { + if providerName == "" { + providerName = transport + } + return "", fmt.Errorf("provider %q does not support ACP transport", providerName) + } + } + if transportSupportsACP(sp) { + return transport, nil } if providerName == "" { providerName = transport From c210ad802c3fa17d220c04bdb71597f7d71a9af8 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 01:59:54 +0000 Subject: [PATCH 54/85] fix: preserve acp transport semantics --- cmd/gc/cmd_session.go | 18 ++++++++++++---- cmd/gc/cmd_session_test.go | 20 +++++++++++++++++ internal/materialize/mcp.go | 2 ++ internal/materialize/mcp_runtime.go | 2 ++ internal/materialize/mcp_test.go | 19 ++++++++++++++++ internal/runtime/acp/protocol_test.go | 31 +++++++++++++++++++++++++-- 6 files changed, 86 insertions(+), 6 deletions(-) diff --git a/cmd/gc/cmd_session.go b/cmd/gc/cmd_session.go index 923991c793..603e788488 100644 --- a/cmd/gc/cmd_session.go +++ b/cmd/gc/cmd_session.go @@ -424,10 +424,7 @@ func validateResolvedSessionTransport(resolved *config.ResolvedProvider, transpo return fmt.Errorf("provider %q does not support ACP transport", providerName) } } - if provider, ok := sp.(runtime.TransportCapabilityProvider); ok && provider.SupportsTransport("acp") { - return nil - } - if _, ok := sp.(acpRouteRegistrar); ok { + if sessionProviderSupportsACP(sp) { return nil } if providerName == "" { @@ -436,6 +433,19 @@ func validateResolvedSessionTransport(resolved *config.ResolvedProvider, transpo return fmt.Errorf("provider %q requires ACP transport but the session provider cannot route ACP sessions", providerName) } +func sessionProviderSupportsACP(sp runtime.Provider) bool { + if sp == nil { + return false + } + if provider, ok := sp.(runtime.TransportCapabilityProvider); ok { + return provider.SupportsTransport("acp") + } + if _, ok := sp.(acpRouteRegistrar); ok { + return true + } + return false +} + func resolvedSessionCommand(cityPath string, resolved *config.ResolvedProvider, optionOverrides map[string]string, transport string) (string, error) { if resolved == nil { return "", fmt.Errorf("resolved provider is nil") diff --git a/cmd/gc/cmd_session_test.go b/cmd/gc/cmd_session_test.go index 15d887d751..309f9f9b8b 100644 --- a/cmd/gc/cmd_session_test.go +++ b/cmd/gc/cmd_session_test.go @@ -61,6 +61,16 @@ func (p *transportCapableSessionProvider) SupportsTransport(transport string) bo return transport == "acp" } +type routedRejectingSessionProvider struct { + *runtime.Fake +} + +func (p *routedRejectingSessionProvider) SupportsTransport(string) bool { + return false +} + +func (p *routedRejectingSessionProvider) RouteACP(string) {} + func TestFormatDuration(t *testing.T) { tests := []struct { d time.Duration @@ -1393,3 +1403,13 @@ func TestValidateResolvedSessionTransportAcceptsRoutedACPProvider(t *testing.T) t.Fatalf("validateResolvedSessionTransport() = %v, want nil", err) } } + +func TestValidateResolvedSessionTransportRejectsRoutedProviderWhenTransportCapabilityDisablesACP(t *testing.T) { + err := validateResolvedSessionTransport(&config.ResolvedProvider{ + Name: "opencode", + SupportsACP: true, + }, "acp", &routedRejectingSessionProvider{Fake: runtime.NewFake()}) + if err == nil || !strings.Contains(err.Error(), "requires ACP transport") { + t.Fatalf("validateResolvedSessionTransport() error = %v, want ACP routing error", err) + } +} diff --git a/internal/materialize/mcp.go b/internal/materialize/mcp.go index aba778af02..3a8d25b320 100644 --- a/internal/materialize/mcp.go +++ b/internal/materialize/mcp.go @@ -24,6 +24,8 @@ const ( MCPTransportStdio MCPTransport = "stdio" // MCPTransportHTTP is a streamable HTTP MCP server. MCPTransportHTTP MCPTransport = "http" + // MCPTransportSSE is an SSE-connected MCP server. + MCPTransportSSE MCPTransport = "sse" ) // MCPServer is the canonical neutral MCP model after parsing, diff --git a/internal/materialize/mcp_runtime.go b/internal/materialize/mcp_runtime.go index 7ba855c82c..6e037eea16 100644 --- a/internal/materialize/mcp_runtime.go +++ b/internal/materialize/mcp_runtime.go @@ -97,6 +97,8 @@ func RuntimeMCPServers(servers []MCPServer) []runtime.MCPServerConfig { switch server.Transport { case MCPTransportHTTP: entry.Transport = runtime.MCPTransportHTTP + case MCPTransportSSE: + entry.Transport = runtime.MCPTransportSSE default: entry.Transport = runtime.MCPTransportStdio } diff --git a/internal/materialize/mcp_test.go b/internal/materialize/mcp_test.go index 248483bcf9..ed199669da 100644 --- a/internal/materialize/mcp_test.go +++ b/internal/materialize/mcp_test.go @@ -9,6 +9,7 @@ import ( "github.com/gastownhall/gascity/internal/config" "github.com/gastownhall/gascity/internal/fsys" + "github.com/gastownhall/gascity/internal/runtime" ) func TestMCPIdentityForFilename(t *testing.T) { @@ -196,6 +197,24 @@ func TestNormalizeMCPServerStableMapOrder(t *testing.T) { } } +func TestRuntimeMCPServersPreservesTransport(t *testing.T) { + t.Parallel() + + got := RuntimeMCPServers([]MCPServer{ + {Name: "stdio", Transport: MCPTransportStdio, Command: "uvx"}, + {Name: "http", Transport: MCPTransportHTTP, URL: "https://example.test/http"}, + {Name: "sse", Transport: MCPTransportSSE, URL: "https://example.test/sse"}, + }) + want := []runtime.MCPServerConfig{ + {Name: "http", Transport: runtime.MCPTransportHTTP, URL: "https://example.test/http"}, + {Name: "sse", Transport: runtime.MCPTransportSSE, URL: "https://example.test/sse"}, + {Name: "stdio", Transport: runtime.MCPTransportStdio, Command: "uvx"}, + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("RuntimeMCPServers()=%#v, want %#v", got, want) + } +} + func TestMCPPackSourcesForAgentOrdersAndDedupes(t *testing.T) { t.Parallel() diff --git a/internal/runtime/acp/protocol_test.go b/internal/runtime/acp/protocol_test.go index 9e4cb23194..1fc03f5df4 100644 --- a/internal/runtime/acp/protocol_test.go +++ b/internal/runtime/acp/protocol_test.go @@ -325,6 +325,14 @@ func TestSessionNewRequest_SerializesMCPServersByTransport(t *testing.T) { "Authorization": "Bearer token", }, }, + { + Name: "stream", + Transport: runtime.MCPTransportSSE, + URL: "https://mcp.example.test/sse", + Headers: map[string]string{ + "X-Test": "1", + }, + }, }) data, err := json.Marshal(msg) if err != nil { @@ -343,8 +351,8 @@ func TestSessionNewRequest_SerializesMCPServersByTransport(t *testing.T) { if err := json.Unmarshal(decoded.Params, ¶ms); err != nil { t.Fatalf("Unmarshal params: %v", err) } - if len(params.McpServers) != 2 { - t.Fatalf("mcpServers len = %d, want 2", len(params.McpServers)) + if len(params.McpServers) != 3 { + t.Fatalf("mcpServers len = %d, want 3", len(params.McpServers)) } var stdio struct { @@ -385,6 +393,25 @@ func TestSessionNewRequest_SerializesMCPServersByTransport(t *testing.T) { if len(http.Headers) != 1 || http.Headers[0].Name != "Authorization" { t.Fatalf("http headers = %#v, want Authorization", http.Headers) } + + var sse struct { + Type string `json:"type"` + Name string `json:"name"` + URL string `json:"url"` + Headers []runtime.MCPKeyValue `json:"headers"` + } + if err := json.Unmarshal(params.McpServers[2], &sse); err != nil { + t.Fatalf("Unmarshal sse server: %v", err) + } + if sse.Type != "sse" { + t.Fatalf("sse type = %q, want sse", sse.Type) + } + if sse.URL != "https://mcp.example.test/sse" { + t.Fatalf("sse url = %q, want https://mcp.example.test/sse", sse.URL) + } + if len(sse.Headers) != 1 || sse.Headers[0].Name != "X-Test" { + t.Fatalf("sse headers = %#v, want X-Test", sse.Headers) + } } func TestSessionPromptRequest_UsesPromptFieldNotMessages(t *testing.T) { From 0b49dfc0cec8fa8d72a73d074edd64b04a7dca56 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 02:14:35 +0000 Subject: [PATCH 55/85] fix: restore legacy acp runtime inference --- cmd/gc/session_manager_test.go | 24 +++++++++++-- cmd/gc/worker_handle.go | 17 +++++++-- cmd/gc/worker_handle_test.go | 8 ++--- internal/api/handler_session_chat_test.go | 8 ++--- internal/api/session_manager.go | 44 +++++++++++++++++++---- internal/api/session_runtime.go | 10 ++++-- internal/materialize/mcp_runtime.go | 4 +-- internal/materialize/mcp_test.go | 33 +++++++++++++++++ internal/session/manager.go | 30 ++++++++++------ internal/session/manager_test.go | 18 +++++----- internal/worker/factory.go | 9 ++++- 11 files changed, 160 insertions(+), 45 deletions(-) diff --git a/cmd/gc/session_manager_test.go b/cmd/gc/session_manager_test.go index 5624d9b721..00dbcafc85 100644 --- a/cmd/gc/session_manager_test.go +++ b/cmd/gc/session_manager_test.go @@ -1,6 +1,8 @@ package main import ( + "strings" + "github.com/gastownhall/gascity/internal/beads" "github.com/gastownhall/gascity/internal/config" "github.com/gastownhall/gascity/internal/runtime" @@ -12,11 +14,27 @@ func newSessionManagerWithConfig(cityPath string, store beads.Store, sp runtime. return session.NewManagerWithCityPath(store, sp, cityPath) } rigContext := currentRigContext(cfg) - return session.NewManagerWithTransportResolverAndCityPath(store, sp, cityPath, func(template string) string { + return session.NewManagerWithTransportResolverAndCityPath(store, sp, cityPath, func(template, provider string) string { agentCfg, ok := resolveAgentIdentity(cfg, template, rigContext) - if !ok { + if ok { + return agentCfg.Session + } + provider = strings.TrimSpace(provider) + if provider == "" { + provider = strings.TrimSpace(template) + } + if provider == "" { + return "" + } + resolved, err := config.ResolveProvider( + &config.Agent{Provider: provider}, + &cfg.Workspace, + cfg.Providers, + func(name string) (string, error) { return name, nil }, + ) + if err != nil { return "" } - return agentCfg.Session + return strings.TrimSpace(resolved.DefaultSessionTransport()) }) } diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index afd1ad1ca7..e4caac1b87 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -449,6 +449,15 @@ func shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand string) b return strings.HasPrefix(storedCommand, resolvedCommand+" ") } +func firstNonEmptyWorkerString(values ...string) string { + for _, value := range values { + if trimmed := strings.TrimSpace(value); trimmed != "" { + return trimmed + } + } + return "" +} + func resolveWorkerRuntimeProviderWithConfig(cfg *config.City, info session.Info, sessionKind string) (*config.ResolvedProvider, string) { if cfg == nil { return nil, "" @@ -456,7 +465,11 @@ func resolveWorkerRuntimeProviderWithConfig(cfg *config.City, info session.Info, if sessionKind != "provider" { if found, ok := resolveAgentIdentity(cfg, info.Template, ""); ok { if resolved, err := config.ResolveProvider(&found, &cfg.Workspace, cfg.Providers, exec.LookPath); err == nil { - return resolved, strings.TrimSpace(info.Transport) + return resolved, firstNonEmptyWorkerString( + strings.TrimSpace(info.Transport), + strings.TrimSpace(found.Session), + strings.TrimSpace(resolved.DefaultSessionTransport()), + ) } } } @@ -464,7 +477,7 @@ func resolveWorkerRuntimeProviderWithConfig(cfg *config.City, info session.Info, if err != nil { return nil, "" } - return resolved, strings.TrimSpace(info.Transport) + return resolved, firstNonEmptyWorkerString(strings.TrimSpace(info.Transport), strings.TrimSpace(resolved.DefaultSessionTransport())) } func workerDeliveryIntentForSubmitIntent(intent session.SubmitIntent) worker.DeliveryIntent { diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index 3d7bfee7cf..ae9211c049 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -258,7 +258,7 @@ TOKEN = "abc" } } -func TestResolvedWorkerRuntimeWithConfigKeepsDefaultTransportWithoutStoredTemplateACPMetadata(t *testing.T) { +func TestResolvedWorkerRuntimeWithConfigUsesConfiguredTransportWithoutStoredTemplateACPMetadata(t *testing.T) { cityDir := t.TempDir() writePhase0InterfaceCity(t, cityDir, `[workspace] name = "test-city" @@ -294,7 +294,7 @@ acp_args = ["acp"] if resolved == nil { t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") } - if got, want := resolved.Command, "/bin/echo"; got != want { + if got, want := resolved.Command, "/bin/echo acp"; got != want { t.Fatalf("Command = %q, want %q", got, want) } } @@ -337,7 +337,7 @@ acp_args = ["acp"] } } -func TestResolvedWorkerRuntimeWithConfigKeepsDefaultTransportForLegacyProviderSessionWithoutMetadata(t *testing.T) { +func TestResolvedWorkerRuntimeWithConfigUsesConfiguredTransportForLegacyProviderSessionWithoutMetadata(t *testing.T) { cityDir := t.TempDir() writePhase0InterfaceCity(t, cityDir, `[workspace] name = "test-city" @@ -369,7 +369,7 @@ acp_args = ["acp"] if resolved == nil { t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") } - if got, want := resolved.Command, "/bin/echo"; got != want { + if got, want := resolved.Command, "/bin/echo acp"; got != want { t.Fatalf("Command = %q, want %q", got, want) } } diff --git a/internal/api/handler_session_chat_test.go b/internal/api/handler_session_chat_test.go index 4805d51f7c..1b43ef2dc6 100644 --- a/internal/api/handler_session_chat_test.go +++ b/internal/api/handler_session_chat_test.go @@ -257,7 +257,7 @@ func TestBuildSessionResumeKeepsDefaultCommandWithoutACPTransportProvider(t *tes } } -func TestBuildSessionResumeKeepsDefaultCommandForLegacyProviderSessionWithoutTransportMetadata(t *testing.T) { +func TestBuildSessionResumeUsesConfiguredACPCommandForLegacyProviderSessionWithoutTransportMetadata(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) fs.cfg = &config.City{ @@ -291,7 +291,7 @@ func TestBuildSessionResumeKeepsDefaultCommandForLegacyProviderSessionWithoutTra if err != nil { t.Fatalf("buildSessionResume: %v", err) } - if got, want := cmd, "/bin/echo"; got != want { + if got, want := cmd, "/bin/echo acp"; got != want { t.Fatalf("resume command = %q, want %q", got, want) } } @@ -335,7 +335,7 @@ func TestBuildSessionResumeUsesStoredACPTransportForTemplateSession(t *testing.T } } -func TestBuildSessionResumeKeepsDefaultCommandForLegacyTemplateSessionWithoutTransportMetadata(t *testing.T) { +func TestBuildSessionResumeUsesConfiguredACPCommandForLegacyTemplateSessionWithoutTransportMetadata(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) fs.cfg = &config.City{ @@ -368,7 +368,7 @@ func TestBuildSessionResumeKeepsDefaultCommandForLegacyTemplateSessionWithoutTra if err != nil { t.Fatalf("buildSessionResume: %v", err) } - if got, want := cmd, "/bin/echo"; got != want { + if got, want := cmd, "/bin/echo acp"; got != want { t.Fatalf("resume command = %q, want %q", got, want) } } diff --git a/internal/api/session_manager.go b/internal/api/session_manager.go index c7aa802312..3e16f70fd3 100644 --- a/internal/api/session_manager.go +++ b/internal/api/session_manager.go @@ -1,7 +1,10 @@ package api import ( + "strings" + "github.com/gastownhall/gascity/internal/beads" + "github.com/gastownhall/gascity/internal/config" "github.com/gastownhall/gascity/internal/session" ) @@ -10,11 +13,38 @@ func (s *Server) sessionManager(store beads.Store) *session.Manager { if cfg == nil { return session.NewManagerWithCityPath(store, s.state.SessionProvider(), s.state.CityPath()) } - return session.NewManagerWithTransportResolverAndCityPath(store, s.state.SessionProvider(), s.state.CityPath(), func(template string) string { - agentCfg, ok := resolveSessionTemplateAgent(cfg, template) - if !ok { - return "" - } - return agentCfg.Session - }) + return session.NewManagerWithTransportResolverAndCityPath( + store, + s.state.SessionProvider(), + s.state.CityPath(), + func(template, provider string) string { + return configuredSessionTransport(cfg, template, provider) + }, + ) +} + +func configuredSessionTransport(cfg *config.City, template, provider string) string { + if cfg == nil { + return "" + } + if agentCfg, ok := resolveSessionTemplateAgent(cfg, template); ok { + return strings.TrimSpace(agentCfg.Session) + } + provider = strings.TrimSpace(provider) + if provider == "" { + provider = strings.TrimSpace(template) + } + if provider == "" { + return "" + } + resolved, err := config.ResolveProvider( + &config.Agent{Provider: provider}, + &cfg.Workspace, + cfg.Providers, + func(name string) (string, error) { return name, nil }, + ) + if err != nil { + return "" + } + return strings.TrimSpace(resolved.DefaultSessionTransport()) } diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index e937f396e7..254cff8cf4 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -263,12 +263,16 @@ func (s *Server) resolveWorkerSessionRuntime(info session.Info, _ string) (*work func (s *Server) resolveSessionRuntime(info session.Info) (*config.ResolvedProvider, string, string) { kind := s.sessionKind(info.ID) if kind != "provider" { - resolved, workDir, _, _, err := s.resolveSessionTemplate(info.Template) + resolved, workDir, transport, _, err := s.resolveSessionTemplate(info.Template) if err == nil { if info.WorkDir != "" { workDir = info.WorkDir } - return resolved, workDir, strings.TrimSpace(info.Transport) + return resolved, workDir, firstNonEmptyString( + strings.TrimSpace(info.Transport), + strings.TrimSpace(transport), + strings.TrimSpace(resolved.DefaultSessionTransport()), + ) } } @@ -280,7 +284,7 @@ func (s *Server) resolveSessionRuntime(info session.Info) (*config.ResolvedProvi if workDir == "" { workDir = s.state.CityPath() } - transport := strings.TrimSpace(info.Transport) + transport := firstNonEmptyString(strings.TrimSpace(info.Transport), strings.TrimSpace(resolved.DefaultSessionTransport())) return resolved, workDir, transport } diff --git a/internal/materialize/mcp_runtime.go b/internal/materialize/mcp_runtime.go index 6e037eea16..c59072e95f 100644 --- a/internal/materialize/mcp_runtime.go +++ b/internal/materialize/mcp_runtime.go @@ -54,12 +54,12 @@ func MCPTemplateData( } rigName := workdirutil.ConfiguredRigName(cityPath, *agent, rigs) rigRoot := workdirutil.RigRootForName(rigName, rigs) - templateName := identity + templateName := agent.QualifiedName() if agent.PoolName != "" { templateName = agent.PoolName } if templateName == "" { - templateName = agent.QualifiedName() + templateName = identity } data := make(map[string]string, len(agent.Env)+11) for key, value := range agent.Env { diff --git a/internal/materialize/mcp_test.go b/internal/materialize/mcp_test.go index ed199669da..8acac8e29f 100644 --- a/internal/materialize/mcp_test.go +++ b/internal/materialize/mcp_test.go @@ -215,6 +215,39 @@ func TestRuntimeMCPServersPreservesTransport(t *testing.T) { } } +func TestMCPTemplateDataUsesBackingTemplateName(t *testing.T) { + t.Parallel() + + agent := &config.Agent{ + Name: "worker", + Dir: "rig-a", + Env: map[string]string{"TOKEN": "abc"}, + } + got := MCPTemplateData(&config.City{}, "/tmp/city", agent, "rig-a/worker-7", "/tmp/work") + if got["AgentName"] != "rig-a/worker-7" { + t.Fatalf("AgentName = %q, want %q", got["AgentName"], "rig-a/worker-7") + } + if got["TemplateName"] != "rig-a/worker" { + t.Fatalf("TemplateName = %q, want %q", got["TemplateName"], "rig-a/worker") + } + if got["TOKEN"] != "abc" { + t.Fatalf("TOKEN = %q, want abc", got["TOKEN"]) + } +} + +func TestMCPTemplateDataUsesPoolNameForPoolInstances(t *testing.T) { + t.Parallel() + + agent := &config.Agent{ + Name: "worker-3", + PoolName: "worker", + } + got := MCPTemplateData(&config.City{}, "/tmp/city", agent, "worker-3", "/tmp/work") + if got["TemplateName"] != "worker" { + t.Fatalf("TemplateName = %q, want %q", got["TemplateName"], "worker") + } +} + func TestMCPPackSourcesForAgentOrdersAndDedupes(t *testing.T) { t.Parallel() diff --git a/internal/session/manager.go b/internal/session/manager.go index 7fd835c6c6..022a8ae945 100644 --- a/internal/session/manager.go +++ b/internal/session/manager.go @@ -120,7 +120,7 @@ type Manager struct { store beads.Store sp runtime.Provider cityPath string - transportResolver func(template string) string + transportResolver func(template, provider string) string } // PruneResult reports which sessions were pruned and which queued wait nudges @@ -161,7 +161,10 @@ func (m *Manager) transportForBead(b beads.Bead, sessName string) (string, bool) } if strings.TrimSpace(b.Metadata["pending_create_claim"]) == "true" { if m.transportResolver != nil { - transport = normalizeTransport(b.Metadata["provider"], m.transportResolver(strings.TrimSpace(b.Metadata["template"]))) + transport = normalizeTransport( + b.Metadata["provider"], + m.transportResolver(strings.TrimSpace(b.Metadata["template"]), strings.TrimSpace(b.Metadata["provider"])), + ) if transport != "" { return transport, true } @@ -177,9 +180,15 @@ func (m *Manager) transportForBead(b beads.Bead, sessName string) (string, bool) if m.sp != nil && m.sp.IsRunning(sessName) { return "", false } - // Stopped legacy sessions without persisted transport metadata must keep - // their stored runtime semantics. Only pending-create beads use config - // inference because they have not materialized yet. + if m.transportResolver != nil { + transport = normalizeTransport( + b.Metadata["provider"], + m.transportResolver(strings.TrimSpace(b.Metadata["template"]), strings.TrimSpace(b.Metadata["provider"])), + ) + if transport != "" { + return transport, true + } + } return "", false } @@ -209,8 +218,9 @@ func NewManager(store beads.Store, sp runtime.Provider) *Manager { } // NewManagerWithTransportResolver creates a Manager that can infer session -// transport from template config when older beads do not have transport metadata. -func NewManagerWithTransportResolver(store beads.Store, sp runtime.Provider, resolver func(template string) string) *Manager { +// transport from template or provider config when older beads do not have +// transport metadata. +func NewManagerWithTransportResolver(store beads.Store, sp runtime.Provider, resolver func(template, provider string) string) *Manager { return &Manager{store: store, sp: sp, transportResolver: resolver} } @@ -221,9 +231,9 @@ func NewManagerWithCityPath(store beads.Store, sp runtime.Provider, cityPath str } // NewManagerWithTransportResolverAndCityPath creates a Manager that can infer -// session transport from template config and persist deferred submits into the -// city's nudge queue. -func NewManagerWithTransportResolverAndCityPath(store beads.Store, sp runtime.Provider, cityPath string, resolver func(template string) string) *Manager { +// session transport from template or provider config and persist deferred +// submits into the city's nudge queue. +func NewManagerWithTransportResolverAndCityPath(store beads.Store, sp runtime.Provider, cityPath string, resolver func(template, provider string) string) *Manager { return &Manager{store: store, sp: sp, cityPath: cityPath, transportResolver: resolver} } diff --git a/internal/session/manager_test.go b/internal/session/manager_test.go index 96ea3f1074..48207f61fa 100644 --- a/internal/session/manager_test.go +++ b/internal/session/manager_test.go @@ -2089,7 +2089,7 @@ func TestSendBackfillsTransportForLegacyACPSession(t *testing.T) { t.Fatalf("Start ACP session: %v", err) } - mgr := NewManagerWithTransportResolver(store, autoSP, func(template string) string { + mgr := NewManagerWithTransportResolver(store, autoSP, func(template, provider string) string { if template == "helper" { return "acp" } @@ -2147,7 +2147,7 @@ func TestGetDoesNotPersistGuessedTransportForLegacySession(t *testing.T) { t.Fatalf("Create legacy bead: %v", err) } - mgr := NewManagerWithTransportResolver(store, autoSP, func(template string) string { + mgr := NewManagerWithTransportResolver(store, autoSP, func(template, provider string) string { if template == "helper" { return "acp" } @@ -2190,7 +2190,7 @@ func TestGetUsesConfiguredTransportForPendingCreateWithoutRuntimeProbe(t *testin t.Fatalf("Create deferred bead: %v", err) } - mgr := NewManagerWithTransportResolver(store, sp, func(template string) string { + mgr := NewManagerWithTransportResolver(store, sp, func(template, provider string) string { if template == "helper" { return "acp" } @@ -2241,7 +2241,7 @@ func TestGetPrefersLiveTransportDetectionOverConfiguredTransportInference(t *tes t.Fatalf("Start default session: %v", err) } - mgr := NewManagerWithTransportResolver(store, autoSP, func(template string) string { + mgr := NewManagerWithTransportResolver(store, autoSP, func(template, provider string) string { if template == "helper" { return "acp" } @@ -2265,7 +2265,7 @@ func TestGetPrefersLiveTransportDetectionOverConfiguredTransportInference(t *tes } } -func TestGetDoesNotInferConfiguredTransportForStoppedLegacySession(t *testing.T) { +func TestGetInfersConfiguredTransportForStoppedLegacySession(t *testing.T) { store := beads.NewMemStore() defaultSP := runtime.NewFake() acpSP := runtime.NewFake() @@ -2294,7 +2294,7 @@ func TestGetDoesNotInferConfiguredTransportForStoppedLegacySession(t *testing.T) t.Fatalf("SetMetadata(session_name): %v", err) } - mgr := NewManagerWithTransportResolver(store, autoSP, func(template string) string { + mgr := NewManagerWithTransportResolver(store, autoSP, func(template, provider string) string { if template == "helper" { return "acp" } @@ -2305,8 +2305,8 @@ func TestGetDoesNotInferConfiguredTransportForStoppedLegacySession(t *testing.T) if err != nil { t.Fatalf("Get: %v", err) } - if got := info.Transport; got != "" { - t.Fatalf("Transport = %q, want empty for stopped legacy tmux session", got) + if got := info.Transport; got != "acp" { + t.Fatalf("Transport = %q, want acp for stopped legacy session under current config", got) } updated, err := store.Get(legacy.ID) @@ -2314,7 +2314,7 @@ func TestGetDoesNotInferConfiguredTransportForStoppedLegacySession(t *testing.T) t.Fatalf("Get updated bead: %v", err) } if got := updated.Metadata["transport"]; got != "" { - t.Fatalf("transport metadata = %q, want empty for stopped legacy tmux session", got) + t.Fatalf("transport metadata = %q, want empty for read-only lookup", got) } } diff --git a/internal/worker/factory.go b/internal/worker/factory.go index 6d229603bb..0c4903aa5b 100644 --- a/internal/worker/factory.go +++ b/internal/worker/factory.go @@ -44,7 +44,14 @@ func NewFactory(cfg FactoryConfig) (*Factory, error) { var manager *sessionpkg.Manager switch { case cfg.ResolveTransport != nil: - manager = sessionpkg.NewManagerWithTransportResolverAndCityPath(cfg.Store, cfg.Provider, cfg.CityPath, cfg.ResolveTransport) + manager = sessionpkg.NewManagerWithTransportResolverAndCityPath( + cfg.Store, + cfg.Provider, + cfg.CityPath, + func(template, provider string) string { + return cfg.ResolveTransport(template) + }, + ) case cfg.CityPath != "": manager = sessionpkg.NewManagerWithCityPath(cfg.Store, cfg.Provider, cfg.CityPath) default: From 3f6093be37fe369a368c740d72185030ffccb32f Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 02:24:48 +0000 Subject: [PATCH 56/85] fix: align agent create identity with acp fallback --- cmd/gc/worker_handle.go | 6 +- cmd/gc/worker_handle_test.go | 40 +++++++++++++ internal/api/handler_session_chat_test.go | 38 +++++++++++++ internal/api/handler_session_create.go | 28 +++++++++- internal/api/handler_sessions_test.go | 56 +++++++++++++++++++ .../api/huma_handlers_sessions_command.go | 22 +++----- internal/api/session_create_agent.go | 47 ++++++++++++++++ internal/api/session_runtime.go | 6 +- 8 files changed, 215 insertions(+), 28 deletions(-) create mode 100644 internal/api/session_create_agent.go diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index e4caac1b87..fdc5931857 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -465,11 +465,7 @@ func resolveWorkerRuntimeProviderWithConfig(cfg *config.City, info session.Info, if sessionKind != "provider" { if found, ok := resolveAgentIdentity(cfg, info.Template, ""); ok { if resolved, err := config.ResolveProvider(&found, &cfg.Workspace, cfg.Providers, exec.LookPath); err == nil { - return resolved, firstNonEmptyWorkerString( - strings.TrimSpace(info.Transport), - strings.TrimSpace(found.Session), - strings.TrimSpace(resolved.DefaultSessionTransport()), - ) + return resolved, firstNonEmptyWorkerString(strings.TrimSpace(info.Transport), strings.TrimSpace(found.Session)) } } } diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index ae9211c049..919ca5943a 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -299,6 +299,46 @@ acp_args = ["acp"] } } +func TestResolvedWorkerRuntimeWithConfigKeepsDefaultTransportWithoutExplicitACPTemplate(t *testing.T) { + cityDir := t.TempDir() + writePhase0InterfaceCity(t, cityDir, `[workspace] +name = "test-city" + +[beads] +provider = "file" + +[[agent]] +name = "worker" +provider = "stub" + +[providers.stub] +command = "/bin/echo" +supports_acp = true +acp_command = "/bin/echo" +acp_args = ["acp"] +`) + + cfg, err := loadCityConfig(cityDir) + if err != nil { + t.Fatalf("loadCityConfig: %v", err) + } + + resolved, err := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + Template: "worker", + Command: "/bin/echo", + WorkDir: cityDir, + }, "") + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfig: %v", err) + } + if resolved == nil { + t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") + } + if got, want := resolved.Command, "/bin/echo"; got != want { + t.Fatalf("Command = %q, want %q", got, want) + } +} + func TestResolvedWorkerRuntimeWithConfigUsesStoredACPTransportForProviderSession(t *testing.T) { cityDir := t.TempDir() writePhase0InterfaceCity(t, cityDir, `[workspace] diff --git a/internal/api/handler_session_chat_test.go b/internal/api/handler_session_chat_test.go index 1b43ef2dc6..067cb25a35 100644 --- a/internal/api/handler_session_chat_test.go +++ b/internal/api/handler_session_chat_test.go @@ -373,6 +373,44 @@ func TestBuildSessionResumeUsesConfiguredACPCommandForLegacyTemplateSessionWitho } } +func TestBuildSessionResumeKeepsDefaultCommandForLegacyTemplateWithoutExplicitACPTransport(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg = &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Agents: []config.Agent{ + {Name: "worker", Provider: "opencode"}, + }, + Providers: map[string]config.ProviderSpec{ + "opencode": { + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + } + + srv := New(fs) + info := session.Info{ + ID: "gc-1", + Template: "worker", + Command: "/bin/echo", + Provider: "opencode", + WorkDir: "/tmp/workdir", + } + + cmd, _, err := srv.buildSessionResume(info) + if err != nil { + t.Fatalf("buildSessionResume: %v", err) + } + if got, want := cmd, "/bin/echo"; got != want { + t.Fatalf("resume command = %q, want %q", got, want) + } +} + func TestBuildSessionResumePropagatesMCPResolutionError(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) diff --git a/internal/api/handler_session_create.go b/internal/api/handler_session_create.go index 71cf2abb3e..1cb4d80a4e 100644 --- a/internal/api/handler_session_create.go +++ b/internal/api/handler_session_create.go @@ -132,8 +132,16 @@ func (s *Server) handleSessionCreate(w http.ResponseWriter, r *http.Request) { writeSessionManagerError(w, err) return } + createCtx, err := s.resolveAgentCreateContext(template, alias) + if err != nil { + s.idem.unreserve(idemKey) + writeError(w, http.StatusInternalServerError, "internal", err.Error()) + return + } + alias = createCtx.Alias + workDir = createCtx.WorkDir - mcpServers, err := s.sessionMCPServers(template, resolved.Name, firstNonEmptyString(alias, template), workDir, transport, kind) + mcpServers, err := s.sessionMCPServers(template, resolved.Name, createCtx.Identity, workDir, transport, kind) if err != nil { s.idem.unreserve(idemKey) writeError(w, http.StatusInternalServerError, "internal", err.Error()) @@ -156,13 +164,14 @@ func (s *Server) handleSessionCreate(w http.ResponseWriter, r *http.Request) { if extraMeta == nil { extraMeta = make(map[string]string) } + extraMeta["agent_name"] = createCtx.Identity extraMeta["session_origin"] = "ephemeral" // Agent sessions always use async (bead-only) creation. The reconciler // starts the agent process on the next tick. This avoids blocking the // HTTP response for 10-30s while the agent boots in tmux, and lets MC // show the session in the sidebar immediately via optimistic UI. - resolvedCfg, err := resolvedSessionConfigForProvider(alias, "", template, title, transport, extraMeta, resolved, command, workDir, mcpServers) + resolvedCfg, err := resolvedSessionConfigForProvider(alias, createCtx.ExplicitName, template, title, transport, extraMeta, resolved, command, workDir, mcpServers) if err != nil { s.idem.unreserve(idemKey) writeSessionManagerError(w, err) @@ -175,10 +184,23 @@ func (s *Server) handleSessionCreate(w http.ResponseWriter, r *http.Request) { return } var info session.Info - err = session.WithCitySessionAliasLock(s.state.CityPath(), alias, func() error { + reservationIDs := []string{alias, createCtx.ExplicitName} + reserveConcreteIdentity := createCtx.Agent.SupportsMultipleSessions() && strings.TrimSpace(createCtx.Identity) != "" + if reserveConcreteIdentity { + reservationIDs = append(reservationIDs, createCtx.Identity) + } + err = session.WithCitySessionIdentifierLocks(s.state.CityPath(), reservationIDs, func() error { if err := session.EnsureAliasAvailableWithConfig(store, s.state.Config(), alias, ""); err != nil { return err } + if reserveConcreteIdentity && createCtx.Identity != alias { + if err := session.EnsureAliasAvailableWithConfig(store, s.state.Config(), createCtx.Identity, ""); err != nil { + return err + } + } + if err := session.EnsureSessionNameAvailableWithConfig(store, s.state.Config(), createCtx.ExplicitName, ""); err != nil { + return err + } var createErr error info, createErr = handle.Create(r.Context(), worker.CreateModeDeferred) return createErr diff --git a/internal/api/handler_sessions_test.go b/internal/api/handler_sessions_test.go index ce13e8a212..25cb2a4435 100644 --- a/internal/api/handler_sessions_test.go +++ b/internal/api/handler_sessions_test.go @@ -1492,6 +1492,62 @@ func TestHandleSessionCreateAsync_PoolTemplateWithoutAliasUsesGeneratedWorkDirId } } +func TestResolveAgentCreateContextUsesConcreteIdentityForMCPMaterialization(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg.Agents = []config.Agent{{ + Name: "ant", + Dir: "myrig", + Provider: "opencode", + Session: "acp", + WorkDir: ".gc/worktrees/{{.Rig}}/ants/{{.AgentBase}}", + MinActiveSessions: intPtr(0), + MaxActiveSessions: intPtr(4), + }} + fs.cfg.NamedSessions = nil + fs.cfg.Providers["opencode"] = config.ProviderSpec{ + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + fs.cfg.PackMCPDir = filepath.Join(fs.cityPath, "mcp") + if err := os.MkdirAll(fs.cfg.PackMCPDir, 0o755); err != nil { + t.Fatalf("MkdirAll(mcp): %v", err) + } + if err := os.WriteFile(filepath.Join(fs.cfg.PackMCPDir, "identity.template.toml"), []byte(` +name = "identity" +command = "/bin/mcp" +args = ["{{.AgentName}}", "{{.WorkDir}}", "{{.TemplateName}}"] +`), 0o644); err != nil { + t.Fatalf("WriteFile(mcp): %v", err) + } + + srv := New(fs) + createCtx, err := srv.resolveAgentCreateContext("myrig/ant", "") + if err != nil { + t.Fatalf("resolveAgentCreateContext: %v", err) + } + mcpServers, err := srv.sessionMCPServers("myrig/ant", "opencode", createCtx.Identity, createCtx.WorkDir, "acp", "agent") + if err != nil { + t.Fatalf("sessionMCPServers: %v", err) + } + if len(mcpServers) != 1 { + t.Fatalf("len(mcpServers) = %d, want 1", len(mcpServers)) + } + if got, want := mcpServers[0].Args[0], createCtx.Identity; got != want { + t.Fatalf("Args[0] = %q, want %q", got, want) + } + if got, want := mcpServers[0].Args[1], createCtx.WorkDir; got != want { + t.Fatalf("Args[1] = %q, want %q", got, want) + } + if got, want := mcpServers[0].Args[2], "myrig/ant"; got != want { + t.Fatalf("Args[2] = %q, want %q", got, want) + } +} + func TestHandleSessionCreateAsync_PoolTemplateCanonicalizesAliasCollisions(t *testing.T) { fs := newSessionFakeState(t) fs.cfg.Agents = []config.Agent{{ diff --git a/internal/api/huma_handlers_sessions_command.go b/internal/api/huma_handlers_sessions_command.go index 00d19747b4..a6f1adbb99 100644 --- a/internal/api/huma_handlers_sessions_command.go +++ b/internal/api/huma_handlers_sessions_command.go @@ -17,7 +17,6 @@ import ( "github.com/gastownhall/gascity/internal/runtime" "github.com/gastownhall/gascity/internal/session" "github.com/gastownhall/gascity/internal/sessionlog" - workdirutil "github.com/gastownhall/gascity/internal/workdir" "github.com/gastownhall/gascity/internal/worker" ) @@ -87,22 +86,15 @@ func (s *Server) humaHandleSessionCreate(ctx context.Context, input *SessionCrea if cfg == nil { return nil, huma.Error500InternalServerError("no city config loaded") } - agentCfg, ok := resolveSessionTemplateAgent(cfg, template) - if !ok { - return nil, huma.Error500InternalServerError("resolved agent template disappeared: " + template) - } - if alias != "" && agentCfg.SupportsMultipleSessions() { - alias = workdirutil.SessionQualifiedName(s.state.CityPath(), agentCfg, cfg.Rigs, alias, "") - } - explicitName, err := sessionExplicitNameForCreate(agentCfg, alias) - if err != nil { - return nil, humaSessionManagerError(err) - } - workDirQualifiedName := workdirutil.SessionQualifiedName(s.state.CityPath(), agentCfg, cfg.Rigs, alias, explicitName) - workDir, err = s.resolveSessionWorkDir(agentCfg, workDirQualifiedName) + createCtx, err := s.resolveAgentCreateContext(template, alias) if err != nil { return nil, huma.Error500InternalServerError(err.Error()) } + agentCfg := createCtx.Agent + alias = createCtx.Alias + explicitName := createCtx.ExplicitName + workDirQualifiedName := createCtx.Identity + workDir = createCtx.WorkDir launchCommand, err := config.BuildProviderLaunchCommandWithoutOptions(s.state.CityPath(), resolved, transport) if err != nil { @@ -110,7 +102,7 @@ func (s *Server) humaHandleSessionCreate(ctx context.Context, input *SessionCrea } command := launchCommand.Command extraMeta := sessionTemplateOverridesMetadata(body.Options, body.Message) - mcpServers, err := s.sessionMCPServers(template, resolved.Name, firstNonEmptyString(alias, template), workDir, transport, kind) + mcpServers, err := s.sessionMCPServers(template, resolved.Name, workDirQualifiedName, workDir, transport, kind) if err != nil { return nil, huma.Error500InternalServerError(err.Error()) } diff --git a/internal/api/session_create_agent.go b/internal/api/session_create_agent.go new file mode 100644 index 0000000000..8712afd8b5 --- /dev/null +++ b/internal/api/session_create_agent.go @@ -0,0 +1,47 @@ +package api + +import ( + "fmt" + "strings" + + "github.com/gastownhall/gascity/internal/config" + workdirutil "github.com/gastownhall/gascity/internal/workdir" +) + +type agentCreateContext struct { + Agent config.Agent + Alias string + ExplicitName string + Identity string + WorkDir string +} + +func (s *Server) resolveAgentCreateContext(template, alias string) (agentCreateContext, error) { + cfg := s.state.Config() + if cfg == nil { + return agentCreateContext{}, fmt.Errorf("no city config loaded") + } + agentCfg, ok := resolveSessionTemplateAgent(cfg, template) + if !ok { + return agentCreateContext{}, fmt.Errorf("resolved agent template disappeared: %s", template) + } + if alias != "" && agentCfg.SupportsMultipleSessions() { + alias = workdirutil.SessionQualifiedName(s.state.CityPath(), agentCfg, cfg.Rigs, alias, "") + } + explicitName, err := sessionExplicitNameForCreate(agentCfg, alias) + if err != nil { + return agentCreateContext{}, err + } + identity := workdirutil.SessionQualifiedName(s.state.CityPath(), agentCfg, cfg.Rigs, alias, explicitName) + workDir, err := s.resolveSessionWorkDir(agentCfg, identity) + if err != nil { + return agentCreateContext{}, err + } + return agentCreateContext{ + Agent: agentCfg, + Alias: strings.TrimSpace(alias), + ExplicitName: explicitName, + Identity: identity, + WorkDir: workDir, + }, nil +} diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index 254cff8cf4..839946dbac 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -268,11 +268,7 @@ func (s *Server) resolveSessionRuntime(info session.Info) (*config.ResolvedProvi if info.WorkDir != "" { workDir = info.WorkDir } - return resolved, workDir, firstNonEmptyString( - strings.TrimSpace(info.Transport), - strings.TrimSpace(transport), - strings.TrimSpace(resolved.DefaultSessionTransport()), - ) + return resolved, workDir, firstNonEmptyString(strings.TrimSpace(info.Transport), strings.TrimSpace(transport)) } } From 8e0d770c68a9cd91951f3d685d7005bd3d4708b0 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 02:37:49 +0000 Subject: [PATCH 57/85] fix: preserve resume identity and mcp resilience --- cmd/gc/worker_handle.go | 51 +++++++++---- cmd/gc/worker_handle_test.go | 80 +++++++++++++++++++-- internal/api/handler_session_chat_test.go | 87 +++++++++++++++++++++-- internal/api/session_runtime.go | 49 +++++++------ internal/api/worker_factory_test.go | 63 ++++++++++++++++ internal/session/manager.go | 2 + internal/session/manager_test.go | 22 ++++++ 7 files changed, 310 insertions(+), 44 deletions(-) diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index fdc5931857..8ab73ac466 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -116,6 +116,43 @@ func resolvedRuntimeMCPServersWithConfig( return materialize.RuntimeMCPServers(catalog.Servers), nil } +func resumeRuntimeMCPServersWithConfig( + cityPath string, + cfg *config.City, + info session.Info, + resolved *config.ResolvedProvider, + transport string, +) []runtime.MCPServerConfig { + if cfg == nil || resolved == nil { + return nil + } + workDir := strings.TrimSpace(info.WorkDir) + if workDir == "" { + workDir = cityPath + } + var metadata map[string]string + if agentName := strings.TrimSpace(info.AgentName); agentName != "" { + metadata = map[string]string{"agent_name": agentName} + } + // Existing ACP sessions resume from stored provider state. Current MCP + // catalog materialization only seeds session/new and should not block + // resume if the catalog on disk is currently broken. + mcpServers, err := resolvedRuntimeMCPServersWithConfig( + cityPath, + cfg, + info.Alias, + info.Template, + firstNonEmptyGCString(info.Provider, resolved.Name, info.Template), + workDir, + transport, + metadata, + ) + if err != nil { + return nil + } + return mcpServers +} + func newWorkerSessionHandleForResolvedRuntimeWithConfig( cityPath string, store beads.Store, @@ -393,19 +430,7 @@ func resolvedWorkerRuntimeWithConfig(cityPath string, cfg *config.City, info ses if workDir == "" { workDir = cityPath } - mcpServers, err := resolvedRuntimeMCPServersWithConfig( - cityPath, - cfg, - info.Alias, - info.Template, - firstNonEmptyGCString(info.Provider, resolved.Name, info.Template), - workDir, - transport, - nil, - ) - if err != nil { - return nil, err - } + mcpServers := resumeRuntimeMCPServersWithConfig(cityPath, cfg, info, resolved, transport) return &worker.ResolvedRuntime{ Command: command, WorkDir: workDir, diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index 919ca5943a..07486929ae 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -778,7 +778,7 @@ ready_delay_ms = 250 } } -func TestResolvedWorkerRuntimeWithConfigPropagatesMCPResolutionError(t *testing.T) { +func TestResolvedWorkerRuntimeWithConfigIgnoresMCPResolutionErrorForACPResume(t *testing.T) { cityDir := t.TempDir() writePhase0InterfaceCity(t, cityDir, `[workspace] name = "test-city" @@ -807,12 +807,22 @@ command = [broken t.Fatalf("loadCityConfig: %v", err) } - if _, err := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + resolved, err := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ Template: "worker", Transport: "acp", WorkDir: cityDir, - }, ""); err == nil { - t.Fatal("resolvedWorkerRuntimeWithConfig() error = nil, want MCP resolution error") + }, "") + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfig: %v", err) + } + if resolved == nil { + t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") + } + if got, want := resolved.Command, "/bin/echo acp"; got != want { + t.Fatalf("Command = %q, want %q", got, want) + } + if len(resolved.Hints.MCPServers) != 0 { + t.Fatalf("Hints.MCPServers len = %d, want 0", len(resolved.Hints.MCPServers)) } } @@ -859,6 +869,68 @@ command = [broken } } +func TestResolvedWorkerRuntimeWithConfigUsesStoredAgentNameForResumeMCPMaterialization(t *testing.T) { + cityDir := t.TempDir() + writePhase0InterfaceCity(t, cityDir, `[workspace] +name = "test-city" + +[beads] +provider = "file" + +[[agent]] +name = "ant" +dir = "myrig" +provider = "stub" +session = "acp" +work_dir = ".gc/worktrees/{{.Rig}}/ants/{{.AgentBase}}" +min_active_sessions = 0 +max_active_sessions = 4 + +[providers.stub] +command = "/bin/echo" +supports_acp = true +acp_command = "/bin/echo" +acp_args = ["acp"] +`) + writeCatalogFile(t, cityDir, "mcp/identity.template.toml", ` +name = "identity" +command = "/bin/mcp" +args = ["{{.AgentName}}", "{{.WorkDir}}", "{{.TemplateName}}"] +`) + + cfg, err := loadCityConfig(cityDir) + if err != nil { + t.Fatalf("loadCityConfig: %v", err) + } + + workDir := filepath.Join(cityDir, ".gc", "worktrees", "myrig", "ants", "ant") + resolved, err := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + Template: "myrig/ant", + Alias: "ant", + AgentName: "myrig/ant-adhoc-123", + Transport: "acp", + WorkDir: workDir, + }, "") + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfig: %v", err) + } + if resolved == nil { + t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") + } + if len(resolved.Hints.MCPServers) != 1 { + t.Fatalf("Hints.MCPServers len = %d, want 1", len(resolved.Hints.MCPServers)) + } + if got, want := resolved.Hints.MCPServers[0].Args[0], "myrig/ant-adhoc-123"; got != want { + t.Fatalf("Args[0] = %q, want %q", got, want) + } + if got, want := resolved.Hints.MCPServers[0].Args[1], workDir; got != want { + t.Fatalf("Args[1] = %q, want %q", got, want) + } + if got, want := resolved.Hints.MCPServers[0].Args[2], "myrig/ant"; got != want { + t.Fatalf("Args[2] = %q, want %q", got, want) + } +} + func TestWorkerSessionRuntimeResolverWithConfigFallsBackToProviderNameWhenResolvedCommandMissing(t *testing.T) { cfg := &config.City{ Workspace: config.Workspace{Name: "test-city"}, diff --git a/internal/api/handler_session_chat_test.go b/internal/api/handler_session_chat_test.go index 067cb25a35..87a1a5ae4b 100644 --- a/internal/api/handler_session_chat_test.go +++ b/internal/api/handler_session_chat_test.go @@ -222,7 +222,7 @@ func TestBuildSessionResumeUsesStoredACPCommandForProviderSession(t *testing.T) } } -func TestBuildSessionResumeKeepsDefaultCommandWithoutACPTransportProvider(t *testing.T) { +func TestBuildSessionResumeUsesConfiguredACPCommandForLegacyProviderSessionWithoutTransportMetadataWithoutSessionAutoProvider(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) fs.cfg = &config.City{ @@ -252,7 +252,7 @@ func TestBuildSessionResumeKeepsDefaultCommandWithoutACPTransportProvider(t *tes if err != nil { t.Fatalf("buildSessionResume: %v", err) } - if got, want := cmd, "/bin/echo"; got != want { + if got, want := cmd, "/bin/echo acp"; got != want { t.Fatalf("resume command = %q, want %q", got, want) } } @@ -411,7 +411,7 @@ func TestBuildSessionResumeKeepsDefaultCommandForLegacyTemplateWithoutExplicitAC } } -func TestBuildSessionResumePropagatesMCPResolutionError(t *testing.T) { +func TestBuildSessionResumeIgnoresMCPResolutionErrorForACPResume(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) fs.cfg = &config.City{ @@ -450,8 +450,15 @@ command = [broken WorkDir: fs.cityPath, } - if _, _, err := srv.buildSessionResume(info); err == nil { - t.Fatal("buildSessionResume() error = nil, want MCP resolution error") + cmd, hints, err := srv.buildSessionResume(info) + if err != nil { + t.Fatalf("buildSessionResume: %v", err) + } + if got, want := cmd, "/bin/echo acp"; got != want { + t.Fatalf("resume command = %q, want %q", got, want) + } + if len(hints.MCPServers) != 0 { + t.Fatalf("Hints.MCPServers len = %d, want 0", len(hints.MCPServers)) } } @@ -496,3 +503,73 @@ command = [broken t.Fatalf("resume command = %q, want %q", got, want) } } + +func TestBuildSessionResumeUsesStoredAgentNameForResumeMCPMaterialization(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg = &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Agents: []config.Agent{{ + Name: "ant", + Dir: "myrig", + Provider: "opencode", + Session: "acp", + WorkDir: ".gc/worktrees/{{.Rig}}/ants/{{.AgentBase}}", + MinActiveSessions: intPtr(0), + MaxActiveSessions: intPtr(4), + }}, + Providers: map[string]config.ProviderSpec{ + "opencode": { + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + } + fs.cfg.PackMCPDir = filepath.Join(fs.cityPath, "mcp") + if err := os.MkdirAll(fs.cfg.PackMCPDir, 0o755); err != nil { + t.Fatalf("MkdirAll(mcp): %v", err) + } + if err := os.WriteFile(filepath.Join(fs.cfg.PackMCPDir, "identity.template.toml"), []byte(` +name = "identity" +command = "/bin/mcp" +args = ["{{.AgentName}}", "{{.WorkDir}}", "{{.TemplateName}}"] +`), 0o644); err != nil { + t.Fatalf("WriteFile(mcp): %v", err) + } + + workDir := filepath.Join(fs.cityPath, ".gc", "worktrees", "myrig", "ants", "ant") + srv := New(fs) + info := session.Info{ + ID: "gc-1", + Template: "myrig/ant", + Alias: "ant", + AgentName: "myrig/ant-adhoc-123", + Provider: "opencode", + Transport: "acp", + WorkDir: workDir, + } + + cmd, hints, err := srv.buildSessionResume(info) + if err != nil { + t.Fatalf("buildSessionResume: %v", err) + } + if got, want := cmd, "/bin/echo acp"; got != want { + t.Fatalf("resume command = %q, want %q", got, want) + } + if len(hints.MCPServers) != 1 { + t.Fatalf("Hints.MCPServers len = %d, want 1", len(hints.MCPServers)) + } + if got, want := hints.MCPServers[0].Args[0], info.AgentName; got != want { + t.Fatalf("Args[0] = %q, want %q", got, want) + } + if got, want := hints.MCPServers[0].Args[1], workDir; got != want { + t.Fatalf("Args[1] = %q, want %q", got, want) + } + if got, want := hints.MCPServers[0].Args[2], "myrig/ant"; got != want { + t.Fatalf("Args[2] = %q, want %q", got, want) + } +} diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index 839946dbac..601ff9c03b 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -47,6 +47,31 @@ func sessionResumeHints(resolved *config.ResolvedProvider, workDir string, mcpSe } } +func resumeSessionIdentity(info session.Info) string { + return firstNonEmptyString(info.AgentName, info.Alias, info.Template, info.Provider) +} + +func (s *Server) resumeSessionMCPServers(info session.Info, resolved *config.ResolvedProvider, workDir, transport string) []runtime.MCPServerConfig { + if resolved == nil { + return nil + } + // Existing ACP sessions resume from their stored session state. Current + // MCP catalog materialization only seeds session/new and should not strand + // already-created sessions if the catalog on disk is currently broken. + mcpServers, err := s.sessionMCPServers( + info.Template, + firstNonEmptyString(info.Provider, resolved.Name), + resumeSessionIdentity(info), + workDir, + transport, + s.sessionKind(info.ID), + ) + if err != nil { + return nil + } + return mcpServers +} + func (s *Server) providerSessionMCPServers(providerName, workDir, transport string) ([]runtime.MCPServerConfig, error) { cfg := s.state.Config() if cfg == nil || strings.TrimSpace(workDir) == "" || strings.TrimSpace(transport) != "acp" { @@ -156,17 +181,7 @@ func (s *Server) buildSessionResume(info session.Info) (string, runtime.Config, if resolved == nil { return cmd, runtime.Config{WorkDir: info.WorkDir}, nil } - mcpServers, err := s.sessionMCPServers( - info.Template, - firstNonEmptyString(info.Provider, resolved.Name), - info.Alias, - firstNonEmptyString(workDir, info.WorkDir), - transport, - s.sessionKind(info.ID), - ) - if err != nil { - return "", runtime.Config{}, err - } + mcpServers := s.resumeSessionMCPServers(info, resolved, firstNonEmptyString(workDir, info.WorkDir), transport) resolvedInfo := info if command, err := s.resolvedSessionRuntimeCommand(resolved, transport, info.Command); err == nil { resolvedInfo.Command = command @@ -226,17 +241,7 @@ func (s *Server) resolveWorkerSessionRuntime(info session.Info, _ string) (*work if resolved == nil { return nil, nil } - mcpServers, err := s.sessionMCPServers( - info.Template, - firstNonEmptyString(info.Provider, resolved.Name), - info.Alias, - firstNonEmptyString(workDir, info.WorkDir), - transport, - s.sessionKind(info.ID), - ) - if err != nil { - return nil, err - } + mcpServers := s.resumeSessionMCPServers(info, resolved, firstNonEmptyString(workDir, info.WorkDir), transport) command, err := s.resolvedSessionRuntimeCommand(resolved, transport, info.Command) if err != nil { return nil, err diff --git a/internal/api/worker_factory_test.go b/internal/api/worker_factory_test.go index 7d77d5f94c..a81f1a030c 100644 --- a/internal/api/worker_factory_test.go +++ b/internal/api/worker_factory_test.go @@ -184,6 +184,69 @@ args = ["--stdio"] } } +func TestResolveWorkerSessionRuntimeUsesStoredAgentNameForResumeMCPMaterialization(t *testing.T) { + fs := newSessionFakeState(t) + fs.cfg.Agents = []config.Agent{{ + Name: "ant", + Dir: "myrig", + Provider: "resolved-worker", + Session: "acp", + WorkDir: ".gc/worktrees/{{.Rig}}/ants/{{.AgentBase}}", + MinActiveSessions: intPtr(0), + MaxActiveSessions: intPtr(4), + }} + supportsACP := true + fs.cfg.Providers["resolved-worker"] = config.ProviderSpec{ + DisplayName: "Resolved Worker", + Command: "/bin/echo", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + fs.cfg.PackMCPDir = filepath.Join(fs.cityPath, "mcp") + if err := os.MkdirAll(fs.cfg.PackMCPDir, 0o755); err != nil { + t.Fatalf("MkdirAll(mcp): %v", err) + } + if err := os.WriteFile(filepath.Join(fs.cfg.PackMCPDir, "identity.template.toml"), []byte(` +name = "identity" +command = "/bin/mcp" +args = ["{{.AgentName}}", "{{.WorkDir}}", "{{.TemplateName}}"] +`), 0o644); err != nil { + t.Fatalf("WriteFile(mcp): %v", err) + } + + workDir := filepath.Join(fs.cityPath, ".gc", "worktrees", "myrig", "ants", "ant") + srv := New(fs) + info := session.Info{ + ID: "sess-1", + Template: "myrig/ant", + Alias: "ant", + AgentName: "myrig/ant-adhoc-123", + Transport: "acp", + WorkDir: workDir, + } + + runtimeCfg, err := srv.resolveWorkerSessionRuntime(info, "") + if err != nil { + t.Fatalf("resolveWorkerSessionRuntime: %v", err) + } + if runtimeCfg == nil { + t.Fatal("resolveWorkerSessionRuntime() = nil") + } + if len(runtimeCfg.Hints.MCPServers) != 1 { + t.Fatalf("Hints.MCPServers len = %d, want 1", len(runtimeCfg.Hints.MCPServers)) + } + if got, want := runtimeCfg.Hints.MCPServers[0].Args[0], info.AgentName; got != want { + t.Fatalf("Args[0] = %q, want %q", got, want) + } + if got, want := runtimeCfg.Hints.MCPServers[0].Args[1], workDir; got != want { + t.Fatalf("Args[1] = %q, want %q", got, want) + } + if got, want := runtimeCfg.Hints.MCPServers[0].Args[2], "myrig/ant"; got != want { + t.Fatalf("Args[2] = %q, want %q", got, want) + } +} + func TestWorkerFactorySessionByIDUsesResolvedTemplateRuntime(t *testing.T) { fs := newSessionFakeState(t) fs.cfg.Agents[0].Provider = "resolved-worker" diff --git a/internal/session/manager.go b/internal/session/manager.go index 022a8ae945..4cb5133e69 100644 --- a/internal/session/manager.go +++ b/internal/session/manager.go @@ -64,6 +64,7 @@ type Info struct { Closed bool Title string Alias string + AgentName string // persisted concrete identity for MCP materialization Provider string Transport string Command string // resolved command stored at creation @@ -1186,6 +1187,7 @@ func (m *Manager) infoFromBead(b beads.Bead) Info { Closed: closed, Title: b.Title, Alias: b.Metadata["alias"], + AgentName: b.Metadata["agent_name"], Provider: b.Metadata["provider"], Transport: transport, Command: b.Metadata["command"], diff --git a/internal/session/manager_test.go b/internal/session/manager_test.go index 48207f61fa..1cb6279e2e 100644 --- a/internal/session/manager_test.go +++ b/internal/session/manager_test.go @@ -328,6 +328,28 @@ func TestCreateBeadOnly(t *testing.T) { } } +func TestGetSurfacesAgentNameMetadata(t *testing.T) { + store := beads.NewMemStore() + sp := runtime.NewFake() + mgr := NewManager(store, sp) + + info, err := mgr.CreateBeadOnly("helper", "my chat", "claude", "/tmp", "claude", "", nil, ProviderResume{}) + if err != nil { + t.Fatalf("CreateBeadOnly: %v", err) + } + if err := store.SetMetadata(info.ID, "agent_name", "myrig/helper-adhoc-123"); err != nil { + t.Fatalf("SetMetadata(agent_name): %v", err) + } + + got, err := mgr.Get(info.ID) + if err != nil { + t.Fatalf("Get: %v", err) + } + if got.AgentName != "myrig/helper-adhoc-123" { + t.Fatalf("AgentName = %q, want %q", got.AgentName, "myrig/helper-adhoc-123") + } +} + func TestCreateNamedWithTransport_UsesExplicitSessionName(t *testing.T) { store := beads.NewMemStore() sp := runtime.NewFake() From 4a034fa4f748bb04798daa09be66a737e7da449e Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 02:46:32 +0000 Subject: [PATCH 58/85] fix: scope acp defaults and mcp hints --- cmd/gc/template_resolve.go | 20 ++++++--- cmd/gc/template_resolve_mcp_test.go | 15 +++++++ cmd/gc/worker_handle_test.go | 37 +++++++++++++++++ internal/api/handler_session_chat_test.go | 35 ++++++++++++++++ internal/config/provider.go | 14 ++++++- internal/config/provider_test.go | 49 +++++++++++++++++++++++ 6 files changed, 163 insertions(+), 7 deletions(-) diff --git a/cmd/gc/template_resolve.go b/cmd/gc/template_resolve.go index 94dc2c5320..7cd4835deb 100644 --- a/cmd/gc/template_resolve.go +++ b/cmd/gc/template_resolve.go @@ -476,7 +476,10 @@ func resolveTemplate(p *agentBuildParams, cfgAgent *config.Agent, qualifiedName ) } } - mcpServers := materialize.RuntimeMCPServers(mcpCatalog.Servers) + var mcpServers []runtime.MCPServerConfig + if cfgAgent.Session == "acp" { + mcpServers = materialize.RuntimeMCPServers(mcpCatalog.Servers) + } // Step 12: Build startup hints. hints := agent.StartupHints{ @@ -584,11 +587,16 @@ func templateParamsToConfig(tp TemplateParams) runtime.Config { env[startupPromptDeliveredEnv] = "1" } return runtime.Config{ - Command: tp.Command, - PromptSuffix: promptSuffix, - PromptFlag: promptFlag, - Env: env, - MCPServers: tp.MCPServers, + Command: tp.Command, + PromptSuffix: promptSuffix, + PromptFlag: promptFlag, + Env: env, + MCPServers: func() []runtime.MCPServerConfig { + if tp.IsACP { + return tp.MCPServers + } + return nil + }(), WorkDir: tp.WorkDir, ReadyPromptPrefix: tp.Hints.ReadyPromptPrefix, ReadyDelayMs: tp.Hints.ReadyDelayMs, diff --git a/cmd/gc/template_resolve_mcp_test.go b/cmd/gc/template_resolve_mcp_test.go index 6f15417a6f..1d3c41ad79 100644 --- a/cmd/gc/template_resolve_mcp_test.go +++ b/cmd/gc/template_resolve_mcp_test.go @@ -90,6 +90,21 @@ args = ["notes-mcp"] } }) + t.Run("non acp runtime excludes mcp servers", func(t *testing.T) { + agent := &config.Agent{Name: "mayor", Scope: "city", Provider: "gemini"} + tp, err := resolveTemplate(buildParams("tmux"), agent, agent.QualifiedName(), nil) + if err != nil { + t.Fatalf("resolveTemplate: %v", err) + } + if len(tp.MCPServers) != 0 { + t.Fatalf("TemplateParams.MCPServers len = %d, want 0", len(tp.MCPServers)) + } + cfg := templateParamsToConfig(tp) + if len(cfg.MCPServers) != 0 { + t.Fatalf("runtime.Config.MCPServers len = %d, want 0", len(cfg.MCPServers)) + } + }) + t.Run("undeliverable runtime hard errors", func(t *testing.T) { agent := &config.Agent{ Name: "worker", diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index 07486929ae..559def272a 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -414,6 +414,43 @@ acp_args = ["acp"] } } +func TestResolvedWorkerRuntimeWithConfigKeepsDefaultTransportForLegacyProviderSessionOnACPEnabledCustomProvider(t *testing.T) { + cityDir := t.TempDir() + writePhase0InterfaceCity(t, cityDir, `[workspace] +name = "test-city" + +[beads] +provider = "file" + +[providers.custom-acp] +command = "/bin/echo" +path_check = "true" +supports_acp = true +acp_command = "/bin/echo" +acp_args = ["acp"] +`) + + cfg, err := loadCityConfig(cityDir) + if err != nil { + t.Fatalf("loadCityConfig: %v", err) + } + + resolved, err := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + Template: "custom-acp", + Command: "/bin/echo", + WorkDir: cityDir, + }, "provider") + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfig: %v", err) + } + if resolved == nil { + t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") + } + if got, want := resolved.Command, "/bin/echo"; got != want { + t.Fatalf("Command = %q, want %q", got, want) + } +} + func TestWorkerHandleForSessionWithConfigUsesResolvedProviderOnResume(t *testing.T) { skipSlowCmdGCTest(t, "waits through stale session-key detection; run make test-cmd-gc-process for full coverage") cityDir := t.TempDir() diff --git a/internal/api/handler_session_chat_test.go b/internal/api/handler_session_chat_test.go index 87a1a5ae4b..b093f90b30 100644 --- a/internal/api/handler_session_chat_test.go +++ b/internal/api/handler_session_chat_test.go @@ -296,6 +296,41 @@ func TestBuildSessionResumeUsesConfiguredACPCommandForLegacyProviderSessionWitho } } +func TestBuildSessionResumeKeepsDefaultCommandForLegacyProviderSessionOnACPEnabledCustomProvider(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg = &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Providers: map[string]config.ProviderSpec{ + "custom-acp": { + DisplayName: "Custom ACP", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + } + + srv := New(fs) + info := session.Info{ + ID: "gc-1", + Template: "custom-acp", + Command: "/bin/echo", + Provider: "custom-acp", + WorkDir: "/tmp/workdir", + } + + cmd, _, err := srv.buildSessionResume(info) + if err != nil { + t.Fatalf("buildSessionResume: %v", err) + } + if got, want := cmd, "/bin/echo"; got != want { + t.Fatalf("resume command = %q, want %q", got, want) + } +} + func TestBuildSessionResumeUsesStoredACPTransportForTemplateSession(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) diff --git a/internal/config/provider.go b/internal/config/provider.go index 60b3bc1d9e..c6166ae25c 100644 --- a/internal/config/provider.go +++ b/internal/config/provider.go @@ -1,6 +1,8 @@ package config import ( + "strings" + "github.com/gastownhall/gascity/internal/shellquote" workerbuiltin "github.com/gastownhall/gascity/internal/worker/builtin" ) @@ -231,7 +233,17 @@ func (rp *ResolvedProvider) ACPCommandString() string { // DefaultSessionTransport returns the transport used for provider-backed // sessions when no template-level session override exists. func (rp *ResolvedProvider) DefaultSessionTransport() string { - if rp != nil && rp.SupportsACP { + if rp == nil || !rp.SupportsACP { + return "" + } + family := strings.TrimSpace(rp.BuiltinAncestor) + if family == "" { + family = strings.TrimSpace(rp.Kind) + } + if family == "" { + family = strings.TrimSpace(rp.Name) + } + if family == "opencode" { return "acp" } return "" diff --git a/internal/config/provider_test.go b/internal/config/provider_test.go index e6585e4a24..62c73cd1b3 100644 --- a/internal/config/provider_test.go +++ b/internal/config/provider_test.go @@ -347,3 +347,52 @@ func TestACPCommandString(t *testing.T) { } }) } + +func TestDefaultSessionTransportOpenCodeFamilyDefaultsToACP(t *testing.T) { + tests := []struct { + name string + rp ResolvedProvider + }{ + { + name: "direct builtin name", + rp: ResolvedProvider{ + Name: "opencode", + SupportsACP: true, + }, + }, + { + name: "builtin ancestor", + rp: ResolvedProvider{ + Name: "custom-opencode", + BuiltinAncestor: "opencode", + SupportsACP: true, + }, + }, + { + name: "deprecated kind fallback", + rp: ResolvedProvider{ + Name: "custom-opencode", + Kind: "opencode", + SupportsACP: true, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.rp.DefaultSessionTransport(); got != "acp" { + t.Fatalf("DefaultSessionTransport() = %q, want %q", got, "acp") + } + }) + } +} + +func TestDefaultSessionTransportSupportsACPDoesNotImplyACPDefault(t *testing.T) { + rp := &ResolvedProvider{ + Name: "custom-acp", + SupportsACP: true, + } + if got := rp.DefaultSessionTransport(); got != "" { + t.Fatalf("DefaultSessionTransport() = %q, want empty default transport", got) + } +} From 19668930621adf9473bb2f2a6e835ef3c3f4c3da Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 02:50:56 +0000 Subject: [PATCH 59/85] fix: propagate provider-aware transport resolver --- cmd/gc/worker_handle.go | 24 +++++++++++--- internal/api/worker_factory.go | 10 ++---- internal/worker/factory.go | 6 ++-- internal/worker/factory_test.go | 58 +++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 15 deletions(-) diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index 8ab73ac466..e9873d9a61 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -25,17 +25,33 @@ func workerSessionCatalogWithConfig(cityPath string, store beads.Store, sp runti func workerFactoryWithConfig(cityPath string, store beads.Store, sp runtime.Provider, cfg *config.City) (*worker.Factory, error) { var ( - resolveTransport func(template string) string + resolveTransport func(template, provider string) string searchPaths []string ) if cfg != nil { rigContext := currentRigContext(cfg) - resolveTransport = func(template string) string { + resolveTransport = func(template, provider string) string { agentCfg, ok := resolveAgentIdentity(cfg, template, rigContext) - if !ok { + if ok { + return agentCfg.Session + } + provider = strings.TrimSpace(provider) + if provider == "" { + provider = strings.TrimSpace(template) + } + if provider == "" { + return "" + } + resolved, err := config.ResolveProvider( + &config.Agent{Provider: provider}, + &cfg.Workspace, + cfg.Providers, + func(name string) (string, error) { return name, nil }, + ) + if err != nil { return "" } - return agentCfg.Session + return strings.TrimSpace(resolved.DefaultSessionTransport()) } searchPaths = worker.MergeSearchPaths(cfg.Daemon.ObservePaths) } diff --git a/internal/api/worker_factory.go b/internal/api/worker_factory.go index ab4f152250..a4e45f25ab 100644 --- a/internal/api/worker_factory.go +++ b/internal/api/worker_factory.go @@ -7,14 +7,10 @@ import ( func (s *Server) workerFactory(store beads.Store) (*worker.Factory, error) { cfg := s.state.Config() - var resolveTransport func(template string) string + var resolveTransport func(template, provider string) string if cfg != nil { - resolveTransport = func(template string) string { - agentCfg, ok := resolveSessionTemplateAgent(cfg, template) - if !ok { - return "" - } - return agentCfg.Session + resolveTransport = func(template, provider string) string { + return configuredSessionTransport(cfg, template, provider) } } return worker.NewFactory(worker.FactoryConfig{ diff --git a/internal/worker/factory.go b/internal/worker/factory.go index 0c4903aa5b..e2e7ecd364 100644 --- a/internal/worker/factory.go +++ b/internal/worker/factory.go @@ -23,7 +23,7 @@ type FactoryConfig struct { CityPath string SearchPaths []string Recorder events.Recorder - ResolveTransport func(template string) string + ResolveTransport func(template, provider string) string ResolveSessionRuntime SessionRuntimeResolver } @@ -48,9 +48,7 @@ func NewFactory(cfg FactoryConfig) (*Factory, error) { cfg.Store, cfg.Provider, cfg.CityPath, - func(template, provider string) string { - return cfg.ResolveTransport(template) - }, + cfg.ResolveTransport, ) case cfg.CityPath != "": manager = sessionpkg.NewManagerWithCityPath(cfg.Store, cfg.Provider, cfg.CityPath) diff --git a/internal/worker/factory_test.go b/internal/worker/factory_test.go index d1baf55d8b..5cf428e194 100644 --- a/internal/worker/factory_test.go +++ b/internal/worker/factory_test.go @@ -205,6 +205,64 @@ func TestFactorySessionByIDResolvesSessionRuntime(t *testing.T) { } } +func TestFactoryTransportResolverReceivesProviderForLegacyProviderSession(t *testing.T) { + store := beads.NewMemStore() + sp := runtime.NewFake() + manager := sessionpkg.NewManager(store, sp) + + info, err := manager.CreateBeadOnly( + "opencode", + "Probe", + "", + t.TempDir(), + "opencode", + "", + nil, + sessionpkg.ProviderResume{}, + ) + if err != nil { + t.Fatalf("CreateBeadOnly: %v", err) + } + if err := store.SetMetadata(info.ID, "mc_session_kind", "provider"); err != nil { + t.Fatalf("SetMetadata(mc_session_kind): %v", err) + } + + var gotTemplate, gotProvider string + factory, err := NewFactory(FactoryConfig{ + Store: store, + Provider: sp, + ResolveTransport: func(template, provider string) string { + gotTemplate = template + gotProvider = provider + if provider == "opencode" { + return "acp" + } + return "" + }, + }) + if err != nil { + t.Fatalf("NewFactory: %v", err) + } + + catalog, err := factory.Catalog() + if err != nil { + t.Fatalf("Catalog: %v", err) + } + got, err := catalog.Get(info.ID) + if err != nil { + t.Fatalf("catalog.Get(%q): %v", info.ID, err) + } + if gotTemplate != "opencode" { + t.Fatalf("ResolveTransport template = %q, want %q", gotTemplate, "opencode") + } + if gotProvider != "opencode" { + t.Fatalf("ResolveTransport provider = %q, want %q", gotProvider, "opencode") + } + if got.Transport != "acp" { + t.Fatalf("catalog.Get(%q).Transport = %q, want %q", info.ID, got.Transport, "acp") + } +} + func TestFactorySessionByIDPropagatesResolvedRuntimeError(t *testing.T) { store := beads.NewMemStore() sp := runtime.NewFake() From 5a2b671b943e2479fd90fbde62e70a474637fa6f Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 03:26:47 +0000 Subject: [PATCH 60/85] fix: persist acp session mcp state --- cmd/gc/session_lifecycle_parallel.go | 9 +++ cmd/gc/worker_handle.go | 45 +++++++---- cmd/gc/worker_handle_test.go | 77 ++++++++++++++++++- internal/api/handler_session_create.go | 26 ++++++- internal/api/handler_sessions_test.go | 70 +++++++++++++++++ .../api/huma_handlers_sessions_command.go | 20 ++++- internal/api/session_runtime.go | 67 ++++++++++++---- internal/api/worker_factory.go | 2 +- internal/api/worker_factory_test.go | 72 +++++++++++++++++ internal/session/chat.go | 3 + internal/session/manager.go | 4 + internal/session/mcp_metadata.go | 69 +++++++++++++++++ internal/session/mcp_state.go | 33 ++++++++ internal/session/names.go | 19 +++++ internal/worker/factory.go | 6 +- internal/worker/factory_test.go | 4 +- 16 files changed, 481 insertions(+), 45 deletions(-) create mode 100644 internal/session/mcp_metadata.go create mode 100644 internal/session/mcp_state.go diff --git a/cmd/gc/session_lifecycle_parallel.go b/cmd/gc/session_lifecycle_parallel.go index 923d999d20..89024ac4ee 100644 --- a/cmd/gc/session_lifecycle_parallel.go +++ b/cmd/gc/session_lifecycle_parallel.go @@ -690,6 +690,15 @@ func commitStartResultTraced( ClearPendingCreateClaim: shouldRollbackPendingCreate(session), Now: clk.Now(), }) + storedMCPSnapshot, err := sessionpkg.EncodeMCPServersSnapshot(result.prepared.cfg.MCPServers) + if err != nil { + fmt.Fprintf(stderr, "session reconciler: encoding MCP snapshot for %s: %v\n", name, err) //nolint:errcheck + logLifecycleOutcome(stderr, "start", wave, name, tp.TemplateName, "metadata_encode_failed", result.started, result.finished, err) + return false + } + if storedMCPSnapshot != "" || session.Metadata[sessionpkg.MCPServersSnapshotMetadataKey] != "" { + metadata[sessionpkg.MCPServersSnapshotMetadataKey] = storedMCPSnapshot + } if err := store.SetMetadataBatch(session.ID, metadata); err != nil { fmt.Fprintf(stderr, "session reconciler: storing hashes for %s: %v\n", name, err) //nolint:errcheck if trace != nil { diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index e9873d9a61..2472f4cf99 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -69,8 +69,8 @@ func workerSessionRuntimeResolverWithConfig(cityPath string, cfg *config.City) w if cfg == nil { return nil } - return func(info session.Info, sessionKind string) (*worker.ResolvedRuntime, error) { - runtimeCfg, err := resolvedWorkerRuntimeWithConfig(cityPath, cfg, info, sessionKind) + return func(info session.Info, sessionKind string, metadata map[string]string) (*worker.ResolvedRuntime, error) { + runtimeCfg, err := resolvedWorkerRuntimeWithConfigAndMetadata(cityPath, cfg, info, sessionKind, metadata) if err != nil { return nil, err } @@ -107,7 +107,10 @@ func resolvedRuntimeMCPServersWithConfig( if cfg == nil || strings.TrimSpace(workDir) == "" || strings.TrimSpace(transport) != "acp" { return nil, nil } - identity := strings.TrimSpace(metadata["agent_name"]) + identity := strings.TrimSpace(metadata[session.MCPIdentityMetadataKey]) + if identity == "" { + identity = strings.TrimSpace(metadata["agent_name"]) + } if identity == "" { identity = strings.TrimSpace(alias) } @@ -138,21 +141,22 @@ func resumeRuntimeMCPServersWithConfig( info session.Info, resolved *config.ResolvedProvider, transport string, -) []runtime.MCPServerConfig { + metadata map[string]string, +) ([]runtime.MCPServerConfig, error) { if cfg == nil || resolved == nil { - return nil + return nil, nil } workDir := strings.TrimSpace(info.WorkDir) if workDir == "" { workDir = cityPath } - var metadata map[string]string + resumeMeta := make(map[string]string) + for key, value := range metadata { + resumeMeta[key] = value + } if agentName := strings.TrimSpace(info.AgentName); agentName != "" { - metadata = map[string]string{"agent_name": agentName} + resumeMeta["agent_name"] = agentName } - // Existing ACP sessions resume from stored provider state. Current MCP - // catalog materialization only seeds session/new and should not block - // resume if the catalog on disk is currently broken. mcpServers, err := resolvedRuntimeMCPServersWithConfig( cityPath, cfg, @@ -161,12 +165,16 @@ func resumeRuntimeMCPServersWithConfig( firstNonEmptyGCString(info.Provider, resolved.Name, info.Template), workDir, transport, - metadata, + resumeMeta, ) - if err != nil { - return nil + if err == nil { + return mcpServers, nil } - return mcpServers + stored, decodeErr := session.DecodeMCPServersSnapshot(resumeMeta[session.MCPServersSnapshotMetadataKey]) + if decodeErr != nil { + return nil, fmt.Errorf("decoding stored MCP snapshot: %w", decodeErr) + } + return stored, nil } func newWorkerSessionHandleForResolvedRuntimeWithConfig( @@ -420,6 +428,10 @@ func workerRespondSessionTargetWithConfig(cityPath string, store beads.Store, sp } func resolvedWorkerRuntimeWithConfig(cityPath string, cfg *config.City, info session.Info, sessionKind string) (*worker.ResolvedRuntime, error) { + return resolvedWorkerRuntimeWithConfigAndMetadata(cityPath, cfg, info, sessionKind, nil) +} + +func resolvedWorkerRuntimeWithConfigAndMetadata(cityPath string, cfg *config.City, info session.Info, sessionKind string, metadata map[string]string) (*worker.ResolvedRuntime, error) { if cfg == nil { return nil, nil } @@ -446,7 +458,10 @@ func resolvedWorkerRuntimeWithConfig(cityPath string, cfg *config.City, info ses if workDir == "" { workDir = cityPath } - mcpServers := resumeRuntimeMCPServersWithConfig(cityPath, cfg, info, resolved, transport) + mcpServers, err := resumeRuntimeMCPServersWithConfig(cityPath, cfg, info, resolved, transport, metadata) + if err != nil { + return nil, err + } return &worker.ResolvedRuntime{ Command: command, WorkDir: workDir, diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index 559def272a..1c79c7bfad 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -968,6 +968,77 @@ args = ["{{.AgentName}}", "{{.WorkDir}}", "{{.TemplateName}}"] } } +func TestResolvedWorkerRuntimeWithConfigFallsBackToStoredMCPServersWhenCatalogBreaks(t *testing.T) { + cityDir := t.TempDir() + writePhase0InterfaceCity(t, cityDir, `[workspace] +name = "test-city" + +[beads] +provider = "file" + +[[agent]] +name = "ant" +dir = "myrig" +provider = "stub" +session = "acp" +work_dir = ".gc/worktrees/{{.Rig}}/ants/{{.AgentBase}}" +min_active_sessions = 0 +max_active_sessions = 4 + +[providers.stub] +command = "/bin/echo" +supports_acp = true +acp_command = "/bin/echo" +acp_args = ["acp"] +`) + writeCatalogFile(t, cityDir, "mcp/identity.template.toml", ` +name = "identity" +command = [broken +`) + + cfg, err := loadCityConfig(cityDir) + if err != nil { + t.Fatalf("loadCityConfig: %v", err) + } + + workDir := filepath.Join(cityDir, ".gc", "worktrees", "myrig", "ants", "ant") + metadata, err := session.WithStoredMCPMetadata(nil, "myrig/ant-adhoc-123", []runtime.MCPServerConfig{{ + Name: "identity", + Transport: runtime.MCPTransportStdio, + Command: "/bin/mcp", + Args: []string{"myrig/ant-adhoc-123", workDir, "myrig/ant"}, + }}) + if err != nil { + t.Fatalf("WithStoredMCPMetadata: %v", err) + } + + resolved, err := resolvedWorkerRuntimeWithConfigAndMetadata(cityDir, cfg, session.Info{ + Template: "myrig/ant", + Alias: "ant", + AgentName: "myrig/ant-adhoc-123", + Transport: "acp", + WorkDir: workDir, + }, "", metadata) + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfigAndMetadata: %v", err) + } + if resolved == nil { + t.Fatal("resolvedWorkerRuntimeWithConfigAndMetadata() = nil") + } + if len(resolved.Hints.MCPServers) != 1 { + t.Fatalf("Hints.MCPServers len = %d, want 1", len(resolved.Hints.MCPServers)) + } + if got, want := resolved.Hints.MCPServers[0].Args[0], "myrig/ant-adhoc-123"; got != want { + t.Fatalf("Args[0] = %q, want %q", got, want) + } + if got, want := resolved.Hints.MCPServers[0].Args[1], workDir; got != want { + t.Fatalf("Args[1] = %q, want %q", got, want) + } + if got, want := resolved.Hints.MCPServers[0].Args[2], "myrig/ant"; got != want { + t.Fatalf("Args[2] = %q, want %q", got, want) + } +} + func TestWorkerSessionRuntimeResolverWithConfigFallsBackToProviderNameWhenResolvedCommandMissing(t *testing.T) { cfg := &config.City{ Workspace: config.Workspace{Name: "test-city"}, @@ -985,7 +1056,7 @@ func TestWorkerSessionRuntimeResolverWithConfigFallsBackToProviderNameWhenResolv t.Fatal("workerSessionRuntimeResolverWithConfig() = nil") } - runtimeCfg, err := resolver(session.Info{Template: "worker"}, "") + runtimeCfg, err := resolver(session.Info{Template: "worker"}, "", nil) if err != nil { t.Fatalf("resolver: %v", err) } @@ -1030,7 +1101,7 @@ func TestWorkerSessionRuntimeResolverWithConfigFallsBackToPersistedRuntimeOnInco ResumeCommand: "persisted resume {{.SessionKey}}", } - runtimeCfg, err := resolver(info, "") + runtimeCfg, err := resolver(info, "", nil) if err != nil { t.Fatalf("resolver: %v", err) } @@ -1090,7 +1161,7 @@ func TestWorkerSessionRuntimeResolverWithConfigFallsBackToPersistedProviderWhenC Provider: "persisted-provider", } - runtimeCfg, err := resolver(info, "") + runtimeCfg, err := resolver(info, "", nil) if err != nil { t.Fatalf("resolver: %v", err) } diff --git a/internal/api/handler_session_create.go b/internal/api/handler_session_create.go index 1cb4d80a4e..a626e0a2ff 100644 --- a/internal/api/handler_session_create.go +++ b/internal/api/handler_session_create.go @@ -166,6 +166,12 @@ func (s *Server) handleSessionCreate(w http.ResponseWriter, r *http.Request) { } extraMeta["agent_name"] = createCtx.Identity extraMeta["session_origin"] = "ephemeral" + extraMeta, err = session.WithStoredMCPMetadata(extraMeta, createCtx.Identity, mcpServers) + if err != nil { + s.idem.unreserve(idemKey) + writeError(w, http.StatusInternalServerError, "internal", err.Error()) + return + } // Agent sessions always use async (bead-only) creation. The reconciler // starts the agent process on the next tick. This avoids blocking the @@ -314,6 +320,12 @@ func (s *Server) createProviderSession(w http.ResponseWriter, r *http.Request, s writeSessionManagerError(w, err) return } + mcpIdentity, err := providerSessionMCPIdentity(providerName, alias) + if err != nil { + s.idem.unreserve(idemKey) + writeError(w, http.StatusInternalServerError, "internal", err.Error()) + return + } transport, err := providerSessionTransport(resolved, s.state.SessionProvider()) if err != nil { @@ -332,16 +344,22 @@ func (s *Server) createProviderSession(w http.ResponseWriter, r *http.Request, s return } command := launchCommand.Command - mcpServers, err := s.providerSessionMCPServers(providerName, workDir, transport) + mcpServers, err := s.providerSessionMCPServers(providerName, mcpIdentity, workDir, transport) if err != nil { s.idem.unreserve(idemKey) writeError(w, http.StatusInternalServerError, "internal", err.Error()) return } - - resolvedCfg, err := resolvedSessionConfigForProvider(alias, "", template, title, transport, map[string]string{ + extraMeta, err := session.WithStoredMCPMetadata(map[string]string{ "session_origin": "manual", - }, resolved, command, workDir, mcpServers) + }, mcpIdentity, mcpServers) + if err != nil { + s.idem.unreserve(idemKey) + writeError(w, http.StatusInternalServerError, "internal", err.Error()) + return + } + + resolvedCfg, err := resolvedSessionConfigForProvider(alias, "", template, title, transport, extraMeta, resolved, command, workDir, mcpServers) if err != nil { s.idem.unreserve(idemKey) writeSessionManagerError(w, err) diff --git a/internal/api/handler_sessions_test.go b/internal/api/handler_sessions_test.go index 25cb2a4435..f22820ce9d 100644 --- a/internal/api/handler_sessions_test.go +++ b/internal/api/handler_sessions_test.go @@ -1914,6 +1914,76 @@ func TestHandleProviderSessionCreateUsesACPTransportCapabilityProvider(t *testin } } +func TestHandleProviderSessionCreateUsesPerSessionMCPIdentity(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg.Providers["opencode"] = config.ProviderSpec{ + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + fs.cfg.PackMCPDir = filepath.Join(fs.cityPath, "mcp") + if err := os.MkdirAll(fs.cfg.PackMCPDir, 0o755); err != nil { + t.Fatalf("MkdirAll(mcp): %v", err) + } + if err := os.WriteFile(filepath.Join(fs.cfg.PackMCPDir, "identity.template.toml"), []byte(` +name = "identity" +command = "/bin/mcp" +args = ["{{.AgentName}}", "{{.WorkDir}}", "{{.TemplateName}}"] +`), 0o644); err != nil { + t.Fatalf("WriteFile(mcp): %v", err) + } + provider := &transportCapableProvider{Fake: runtime.NewFake()} + state := &stateWithSessionProvider{ + fakeState: fs, + provider: provider, + } + srv := New(state) + h := newTestCityHandlerWith(t, state, srv) + + req := newPostRequest(cityURL(fs, "/sessions"), strings.NewReader(`{"kind":"provider","name":"opencode"}`)) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + + if rec.Code != http.StatusCreated { + t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusCreated, rec.Body.String()) + } + + var resp sessionResponse + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode: %v", err) + } + start := provider.LastStartConfig(resp.SessionName) + if start == nil { + t.Fatalf("LastStartConfig(%q) = nil", resp.SessionName) + } + if len(start.MCPServers) != 1 { + t.Fatalf("Start MCPServers len = %d, want 1", len(start.MCPServers)) + } + bead, err := fs.cityBeadStore.Get(resp.ID) + if err != nil { + t.Fatalf("Get(%s): %v", resp.ID, err) + } + if got := bead.Metadata[session.MCPIdentityMetadataKey]; got == "" { + t.Fatal("mcp_identity metadata = empty, want per-session identity") + } + if got, want := start.MCPServers[0].Args[0], bead.Metadata[session.MCPIdentityMetadataKey]; got != want { + t.Fatalf("Start MCP identity = %q, want %q", got, want) + } + if got := bead.Metadata[session.MCPIdentityMetadataKey]; got == "opencode" { + t.Fatalf("mcp_identity metadata = %q, want unique per-session identity", got) + } + if got, want := start.MCPServers[0].Args[1], fs.cityPath; got != want { + t.Fatalf("Start workdir arg = %q, want %q", got, want) + } + if got, want := start.MCPServers[0].Args[2], bead.Metadata[session.MCPIdentityMetadataKey]; got != want { + t.Fatalf("Start template arg = %q, want %q", got, want) + } +} + func TestHandleProviderSessionCreateRejectsACPProviderWithoutACPRouting(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) diff --git a/internal/api/huma_handlers_sessions_command.go b/internal/api/huma_handlers_sessions_command.go index a6f1adbb99..6151dedaeb 100644 --- a/internal/api/huma_handlers_sessions_command.go +++ b/internal/api/huma_handlers_sessions_command.go @@ -130,6 +130,11 @@ func (s *Server) humaHandleSessionCreate(ctx context.Context, input *SessionCrea } extraMeta["agent_name"] = workDirQualifiedName extraMeta["session_origin"] = "manual" + var mcpMetaErr error + extraMeta, mcpMetaErr = session.WithStoredMCPMetadata(extraMeta, workDirQualifiedName, mcpServers) + if mcpMetaErr != nil { + return mcpMetaErr + } resolvedCfg, cfgErr := resolvedSessionConfigForProvider( alias, explicitName, @@ -239,6 +244,10 @@ func (s *Server) humaCreateProviderSession(ctx context.Context, store beads.Stor if err != nil { return nil, humaSessionManagerError(err) } + mcpIdentity, err := providerSessionMCPIdentity(resolved.Name, alias) + if err != nil { + return nil, huma.Error500InternalServerError(err.Error()) + } transport, err := providerSessionTransport(resolved, s.state.SessionProvider()) if err != nil { @@ -249,7 +258,13 @@ func (s *Server) humaCreateProviderSession(ctx context.Context, store beads.Stor return nil, huma.Error400BadRequest(err.Error()) } command := launchCommand.Command - mcpServers, err := s.providerSessionMCPServers(resolved.Name, workDir, transport) + mcpServers, err := s.providerSessionMCPServers(resolved.Name, mcpIdentity, workDir, transport) + if err != nil { + return nil, huma.Error500InternalServerError(err.Error()) + } + extraMeta, err := session.WithStoredMCPMetadata(map[string]string{ + "session_origin": "manual", + }, mcpIdentity, mcpServers) if err != nil { return nil, huma.Error500InternalServerError(err.Error()) } @@ -262,7 +277,7 @@ func (s *Server) humaCreateProviderSession(ctx context.Context, store beads.Stor return aliasErr } var createErr error - info, createErr = mgr.CreateAliasedNamedWithTransport( + info, createErr = mgr.CreateAliasedNamedWithTransportAndMetadata( ctx, alias, "", @@ -275,6 +290,7 @@ func (s *Server) humaCreateProviderSession(ctx context.Context, store beads.Stor resolved.Env, resume, hints, + extraMeta, ) return createErr }) diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index 601ff9c03b..b42a7de553 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -47,38 +47,44 @@ func sessionResumeHints(resolved *config.ResolvedProvider, workDir string, mcpSe } } -func resumeSessionIdentity(info session.Info) string { +func resumeSessionIdentity(info session.Info, metadata map[string]string) string { + if metadata != nil { + if identity := strings.TrimSpace(metadata[session.MCPIdentityMetadataKey]); identity != "" { + return identity + } + } return firstNonEmptyString(info.AgentName, info.Alias, info.Template, info.Provider) } -func (s *Server) resumeSessionMCPServers(info session.Info, resolved *config.ResolvedProvider, workDir, transport string) []runtime.MCPServerConfig { +func (s *Server) resumeSessionMCPServers(info session.Info, metadata map[string]string, resolved *config.ResolvedProvider, workDir, transport string) ([]runtime.MCPServerConfig, error) { if resolved == nil { - return nil + return nil, nil } - // Existing ACP sessions resume from their stored session state. Current - // MCP catalog materialization only seeds session/new and should not strand - // already-created sessions if the catalog on disk is currently broken. mcpServers, err := s.sessionMCPServers( info.Template, firstNonEmptyString(info.Provider, resolved.Name), - resumeSessionIdentity(info), + resumeSessionIdentity(info, metadata), workDir, transport, s.sessionKind(info.ID), ) - if err != nil { - return nil + if err == nil { + return mcpServers, nil } - return mcpServers + stored, decodeErr := session.DecodeMCPServersSnapshot(metadata[session.MCPServersSnapshotMetadataKey]) + if decodeErr != nil { + return nil, fmt.Errorf("decoding stored MCP snapshot: %w", decodeErr) + } + return stored, nil } -func (s *Server) providerSessionMCPServers(providerName, workDir, transport string) ([]runtime.MCPServerConfig, error) { +func (s *Server) providerSessionMCPServers(providerName, identity, workDir, transport string) ([]runtime.MCPServerConfig, error) { cfg := s.state.Config() if cfg == nil || strings.TrimSpace(workDir) == "" || strings.TrimSpace(transport) != "acp" { return nil, nil } synthetic := &config.Agent{Provider: providerName} - catalog, err := materialize.EffectiveMCPForSession(cfg, s.state.CityPath(), synthetic, providerName, workDir) + catalog, err := materialize.EffectiveMCPForSession(cfg, s.state.CityPath(), synthetic, firstNonEmptyString(identity, providerName), workDir) if err != nil { return nil, fmt.Errorf("loading effective MCP: %w", err) } @@ -105,7 +111,26 @@ func (s *Server) sessionMCPServers(template, providerName, identity, workDir, tr return materialize.RuntimeMCPServers(catalog.Servers), nil } } - return s.providerSessionMCPServers(firstNonEmptyString(providerName, template), workDir, transport) + return s.providerSessionMCPServers(firstNonEmptyString(providerName, template), identity, workDir, transport) +} + +func (s *Server) sessionMetadata(sessionID string) map[string]string { + store := s.state.CityBeadStore() + if store == nil || strings.TrimSpace(sessionID) == "" { + return nil + } + bead, err := store.Get(sessionID) + if err != nil { + return nil + } + return bead.Metadata +} + +func providerSessionMCPIdentity(providerName, alias string) (string, error) { + if alias = strings.TrimSpace(alias); alias != "" { + return alias, nil + } + return session.GenerateAdhocIdentity(providerName) } func sessionExplicitNameForCreate(agentCfg config.Agent, alias string) (string, error) { @@ -181,7 +206,10 @@ func (s *Server) buildSessionResume(info session.Info) (string, runtime.Config, if resolved == nil { return cmd, runtime.Config{WorkDir: info.WorkDir}, nil } - mcpServers := s.resumeSessionMCPServers(info, resolved, firstNonEmptyString(workDir, info.WorkDir), transport) + mcpServers, err := s.resumeSessionMCPServers(info, s.sessionMetadata(info.ID), resolved, firstNonEmptyString(workDir, info.WorkDir), transport) + if err != nil { + return "", runtime.Config{}, err + } resolvedInfo := info if command, err := s.resolvedSessionRuntimeCommand(resolved, transport, info.Command); err == nil { resolvedInfo.Command = command @@ -236,12 +264,19 @@ func shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand string) b return strings.HasPrefix(storedCommand, resolvedCommand+" ") } -func (s *Server) resolveWorkerSessionRuntime(info session.Info, _ string) (*worker.ResolvedRuntime, error) { +func (s *Server) resolveWorkerSessionRuntime(info session.Info, sessionKind string) (*worker.ResolvedRuntime, error) { + return s.resolveWorkerSessionRuntimeWithMetadata(info, sessionKind, nil) +} + +func (s *Server) resolveWorkerSessionRuntimeWithMetadata(info session.Info, _ string, metadata map[string]string) (*worker.ResolvedRuntime, error) { resolved, workDir, transport := s.resolveSessionRuntime(info) if resolved == nil { return nil, nil } - mcpServers := s.resumeSessionMCPServers(info, resolved, firstNonEmptyString(workDir, info.WorkDir), transport) + mcpServers, err := s.resumeSessionMCPServers(info, metadata, resolved, firstNonEmptyString(workDir, info.WorkDir), transport) + if err != nil { + return nil, err + } command, err := s.resolvedSessionRuntimeCommand(resolved, transport, info.Command) if err != nil { return nil, err diff --git a/internal/api/worker_factory.go b/internal/api/worker_factory.go index a4e45f25ab..04318cd5a3 100644 --- a/internal/api/worker_factory.go +++ b/internal/api/worker_factory.go @@ -20,7 +20,7 @@ func (s *Server) workerFactory(store beads.Store) (*worker.Factory, error) { SearchPaths: s.sessionLogPaths(), Recorder: s.state.EventProvider(), ResolveTransport: resolveTransport, - ResolveSessionRuntime: s.resolveWorkerSessionRuntime, + ResolveSessionRuntime: s.resolveWorkerSessionRuntimeWithMetadata, }) } diff --git a/internal/api/worker_factory_test.go b/internal/api/worker_factory_test.go index a81f1a030c..f5cec4ec40 100644 --- a/internal/api/worker_factory_test.go +++ b/internal/api/worker_factory_test.go @@ -247,6 +247,78 @@ args = ["{{.AgentName}}", "{{.WorkDir}}", "{{.TemplateName}}"] } } +func TestResolveWorkerSessionRuntimeFallsBackToStoredMCPServersWhenCatalogBreaks(t *testing.T) { + fs := newSessionFakeState(t) + fs.cfg.Agents = []config.Agent{{ + Name: "ant", + Dir: "myrig", + Provider: "resolved-worker", + Session: "acp", + WorkDir: ".gc/worktrees/{{.Rig}}/ants/{{.AgentBase}}", + MinActiveSessions: intPtr(0), + MaxActiveSessions: intPtr(4), + }} + supportsACP := true + fs.cfg.Providers["resolved-worker"] = config.ProviderSpec{ + DisplayName: "Resolved Worker", + Command: "/bin/echo", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + fs.cfg.PackMCPDir = filepath.Join(fs.cityPath, "mcp") + if err := os.MkdirAll(fs.cfg.PackMCPDir, 0o755); err != nil { + t.Fatalf("MkdirAll(mcp): %v", err) + } + if err := os.WriteFile(filepath.Join(fs.cfg.PackMCPDir, "identity.template.toml"), []byte(` +name = "identity" +command = [broken +`), 0o644); err != nil { + t.Fatalf("WriteFile(mcp): %v", err) + } + + workDir := filepath.Join(fs.cityPath, ".gc", "worktrees", "myrig", "ants", "ant") + metadata, err := session.WithStoredMCPMetadata(nil, "myrig/ant-adhoc-123", []runtime.MCPServerConfig{{ + Name: "identity", + Transport: runtime.MCPTransportStdio, + Command: "/bin/mcp", + Args: []string{"myrig/ant-adhoc-123", workDir, "myrig/ant"}, + }}) + if err != nil { + t.Fatalf("WithStoredMCPMetadata: %v", err) + } + + srv := New(fs) + info := session.Info{ + ID: "sess-1", + Template: "myrig/ant", + Alias: "ant", + AgentName: "myrig/ant-adhoc-123", + Transport: "acp", + WorkDir: workDir, + } + + runtimeCfg, err := srv.resolveWorkerSessionRuntimeWithMetadata(info, "", metadata) + if err != nil { + t.Fatalf("resolveWorkerSessionRuntimeWithMetadata: %v", err) + } + if runtimeCfg == nil { + t.Fatal("resolveWorkerSessionRuntimeWithMetadata() = nil") + } + if len(runtimeCfg.Hints.MCPServers) != 1 { + t.Fatalf("Hints.MCPServers len = %d, want 1", len(runtimeCfg.Hints.MCPServers)) + } + if got, want := runtimeCfg.Hints.MCPServers[0].Args[0], "myrig/ant-adhoc-123"; got != want { + t.Fatalf("Args[0] = %q, want %q", got, want) + } + if got, want := runtimeCfg.Hints.MCPServers[0].Args[1], workDir; got != want { + t.Fatalf("Args[1] = %q, want %q", got, want) + } + if got, want := runtimeCfg.Hints.MCPServers[0].Args[2], "myrig/ant"; got != want { + t.Fatalf("Args[2] = %q, want %q", got, want) + } +} + func TestWorkerFactorySessionByIDUsesResolvedTemplateRuntime(t *testing.T) { fs := newSessionFakeState(t) fs.cfg.Agents[0].Provider = "resolved-worker" diff --git a/internal/session/chat.go b/internal/session/chat.go index 91f8e767bf..4884eae018 100644 --- a/internal/session/chat.go +++ b/internal/session/chat.go @@ -295,6 +295,9 @@ func (m *Manager) ensureRunning(ctx context.Context, id string, b beads.Bead, se if b.Metadata["transport"] == "" && (started || transportVerified) { m.persistTransport(id, b.Metadata["provider"], transport) } + if err := m.syncStoredMCPServers(id, &b, cfg.MCPServers); err != nil { + return fmt.Errorf("%w: %w", ErrStateSync, err) + } if err := m.confirmLiveSessionState(id, &b); err != nil { if started && !errors.Is(err, ErrStateSync) { _ = m.sp.Stop(sessName) diff --git a/internal/session/manager.go b/internal/session/manager.go index 4cb5133e69..96d5c2f1bd 100644 --- a/internal/session/manager.go +++ b/internal/session/manager.go @@ -373,6 +373,10 @@ func (m *Manager) createAliasedNamedWithTransport(ctx context.Context, alias, ex if explicitName != "" { b.Metadata["session_name_explicit"] = "true" } + if err := m.syncStoredMCPServers(b.ID, &b, hints.MCPServers); err != nil { + _ = m.store.Close(b.ID) + return err + } unroute := m.routeACPIfNeeded(provider, transport, sessName) rollbackFailedCreate := func() error { diff --git a/internal/session/mcp_metadata.go b/internal/session/mcp_metadata.go new file mode 100644 index 0000000000..9195260015 --- /dev/null +++ b/internal/session/mcp_metadata.go @@ -0,0 +1,69 @@ +package session + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/gastownhall/gascity/internal/runtime" +) + +const ( + // MCPIdentityMetadataKey stores the stable identity used to materialize + // MCP templates for a session. + MCPIdentityMetadataKey = "mcp_identity" + // MCPServersSnapshotMetadataKey stores the normalized ACP session/new MCP + // server snapshot used to resume sessions when the current catalog cannot + // be materialized. + MCPServersSnapshotMetadataKey = "mcp_servers_snapshot" +) + +// EncodeMCPServersSnapshot returns the normalized metadata value for a +// session's persisted ACP session/new MCP server snapshot. +func EncodeMCPServersSnapshot(servers []runtime.MCPServerConfig) (string, error) { + normalized := runtime.NormalizeMCPServerConfigs(servers) + if len(normalized) == 0 { + return "", nil + } + data, err := json.Marshal(normalized) + if err != nil { + return "", fmt.Errorf("marshal MCP server snapshot: %w", err) + } + return string(data), nil +} + +// DecodeMCPServersSnapshot parses a persisted ACP session/new MCP server +// snapshot from session metadata. +func DecodeMCPServersSnapshot(raw string) ([]runtime.MCPServerConfig, error) { + raw = strings.TrimSpace(raw) + if raw == "" { + return nil, nil + } + var servers []runtime.MCPServerConfig + if err := json.Unmarshal([]byte(raw), &servers); err != nil { + return nil, fmt.Errorf("unmarshal MCP server snapshot: %w", err) + } + return runtime.NormalizeMCPServerConfigs(servers), nil +} + +// WithStoredMCPMetadata returns a metadata map augmented with the stable MCP +// identity and normalized ACP session/new snapshot for the session. +func WithStoredMCPMetadata(meta map[string]string, identity string, servers []runtime.MCPServerConfig) (map[string]string, error) { + if meta == nil { + meta = make(map[string]string) + } + identity = strings.TrimSpace(identity) + if identity != "" { + meta[MCPIdentityMetadataKey] = identity + } + snapshot, err := EncodeMCPServersSnapshot(servers) + if err != nil { + return nil, err + } + if snapshot != "" { + meta[MCPServersSnapshotMetadataKey] = snapshot + } else if _, ok := meta[MCPServersSnapshotMetadataKey]; ok { + meta[MCPServersSnapshotMetadataKey] = "" + } + return meta, nil +} diff --git a/internal/session/mcp_state.go b/internal/session/mcp_state.go new file mode 100644 index 0000000000..9bb6d502f6 --- /dev/null +++ b/internal/session/mcp_state.go @@ -0,0 +1,33 @@ +package session + +import ( + "fmt" + "strings" + + "github.com/gastownhall/gascity/internal/beads" + "github.com/gastownhall/gascity/internal/runtime" +) + +func (m *Manager) syncStoredMCPServers(id string, b *beads.Bead, servers []runtime.MCPServerConfig) error { + snapshot, err := EncodeMCPServersSnapshot(servers) + if err != nil { + return err + } + current := "" + if b != nil && b.Metadata != nil { + current = strings.TrimSpace(b.Metadata[MCPServersSnapshotMetadataKey]) + } + if current == snapshot { + return nil + } + if err := m.store.SetMetadata(id, MCPServersSnapshotMetadataKey, snapshot); err != nil { + return fmt.Errorf("storing MCP server snapshot: %w", err) + } + if b != nil { + if b.Metadata == nil { + b.Metadata = make(map[string]string) + } + b.Metadata[MCPServersSnapshotMetadataKey] = snapshot + } + return nil +} diff --git a/internal/session/names.go b/internal/session/names.go index d3a24c7283..330fff8513 100644 --- a/internal/session/names.go +++ b/internal/session/names.go @@ -13,6 +13,7 @@ import ( "sync" "syscall" + "github.com/gastownhall/gascity/internal/agent" "github.com/gastownhall/gascity/internal/beads" "github.com/gastownhall/gascity/internal/citylayout" "github.com/gastownhall/gascity/internal/config" @@ -106,6 +107,24 @@ func GenerateAdhocExplicitName(base string) (string, error) { return ValidateExplicitName(base + suffix) } +// GenerateAdhocIdentity produces a stable, MCP-safe per-session identity for +// aliasless sessions that still need a concrete unique name for templating. +func GenerateAdhocIdentity(base string) (string, error) { + token, err := GenerateSessionKey() + if err != nil { + return "", fmt.Errorf("generate adhoc identity: %w", err) + } + compact := strings.ReplaceAll(token, "-", "") + if len(compact) > 10 { + compact = compact[:10] + } + base = agent.SanitizeQualifiedNameForSession(strings.TrimSpace(base)) + if base == "" { + base = "session" + } + return base + "-adhoc-" + compact, nil +} + // ValidateAlias validates a human-chosen session alias. Empty means // "no alias". func ValidateAlias(alias string) (string, error) { diff --git a/internal/worker/factory.go b/internal/worker/factory.go index e2e7ecd364..26ff7afc39 100644 --- a/internal/worker/factory.go +++ b/internal/worker/factory.go @@ -13,7 +13,7 @@ import ( // SessionRuntimeResolver resolves provider/runtime details for an existing // session-backed worker without exposing SessionSpec mutation to callers. -type SessionRuntimeResolver func(info sessionpkg.Info, sessionKind string) (*ResolvedRuntime, error) +type SessionRuntimeResolver func(info sessionpkg.Info, sessionKind string, metadata map[string]string) (*ResolvedRuntime, error) // FactoryConfig constructs worker-owned session handles and catalogs without // leaking session.Manager setup into higher layers. @@ -118,16 +118,18 @@ func (f *Factory) SessionByID(id string) (Handle, error) { }, } sessionKind := "" + var metadata map[string]string if f.store != nil { if bead, beadErr := f.store.Get(id); beadErr == nil { sessionKind = strings.TrimSpace(bead.Metadata["mc_session_kind"]) if profile := strings.TrimSpace(bead.Metadata["worker_profile"]); profile != "" { spec.Profile = Profile(profile) } + metadata = cloneStringMap(bead.Metadata) } } if f.resolveSessionRuntime != nil { - resolved, err := f.resolveSessionRuntime(info, sessionKind) + resolved, err := f.resolveSessionRuntime(info, sessionKind, metadata) if err != nil { return nil, err } diff --git a/internal/worker/factory_test.go b/internal/worker/factory_test.go index 5cf428e194..d58b8523a3 100644 --- a/internal/worker/factory_test.go +++ b/internal/worker/factory_test.go @@ -148,7 +148,7 @@ func TestFactorySessionByIDResolvesSessionRuntime(t *testing.T) { factory, err := NewFactory(FactoryConfig{ Store: store, Provider: sp, - ResolveSessionRuntime: func(_ sessionpkg.Info, sessionKind string) (*ResolvedRuntime, error) { + ResolveSessionRuntime: func(_ sessionpkg.Info, sessionKind string, _ map[string]string) (*ResolvedRuntime, error) { gotSessionKind = sessionKind return &ResolvedRuntime{ Command: "/bin/echo", @@ -286,7 +286,7 @@ func TestFactorySessionByIDPropagatesResolvedRuntimeError(t *testing.T) { factory, err := NewFactory(FactoryConfig{ Store: store, Provider: sp, - ResolveSessionRuntime: func(sessionpkg.Info, string) (*ResolvedRuntime, error) { + ResolveSessionRuntime: func(sessionpkg.Info, string, map[string]string) (*ResolvedRuntime, error) { return nil, wantErr }, }) From 5ba8802065820c5b3f6ab8b7033e3fd5b18e714b Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 03:32:29 +0000 Subject: [PATCH 61/85] fix: seed mcp snapshots for deferred session create --- cmd/gc/worker_handle.go | 9 +++++ cmd/gc/worker_handle_test.go | 35 ++++++++++++++++++++ internal/api/session_resolved_config.go | 9 +++++ internal/api/session_resolved_config_test.go | 12 ++++++- 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index 2472f4cf99..df7852af45 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -229,6 +229,15 @@ func resolvedWorkerSessionConfigWithConfig( if resolved == nil { return worker.ResolvedSessionConfig{}, fmt.Errorf("resolved provider is required") } + var err error + metadata, err = session.WithStoredMCPMetadata( + metadata, + firstNonEmptyGCString(metadata[session.MCPIdentityMetadataKey], metadata["agent_name"]), + mcpServers, + ) + if err != nil { + return worker.ResolvedSessionConfig{}, err + } command = strings.TrimSpace(command) if command == "" { if transport == "acp" { diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index 1c79c7bfad..b498c1f0ab 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -769,6 +769,41 @@ func TestResolvedWorkerSessionConfigWithConfigFallsBackToProviderArgForCommand(t } } +func TestResolvedWorkerSessionConfigWithConfigPersistsStoredMCPMetadata(t *testing.T) { + cfg, err := resolvedWorkerSessionConfigWithConfig( + "", + "legacy-provider", + "/tmp/work", + "worker", + "", + "worker", + "Worker", + "acp", + &config.ResolvedProvider{ + Name: "custom-provider", + }, + map[string]string{ + "session_origin": "test", + "agent_name": "myrig/worker-adhoc-123", + }, + []runtime.MCPServerConfig{{ + Name: "filesystem", + Transport: runtime.MCPTransportStdio, + Command: "/bin/mcp", + Args: []string{"--stdio"}, + }}, + ) + if err != nil { + t.Fatalf("resolvedWorkerSessionConfigWithConfig: %v", err) + } + if got, want := cfg.Metadata[session.MCPIdentityMetadataKey], "myrig/worker-adhoc-123"; got != want { + t.Fatalf("Metadata[mcp_identity] = %q, want %q", got, want) + } + if got := cfg.Metadata[session.MCPServersSnapshotMetadataKey]; got == "" { + t.Fatal("Metadata[mcp_servers_snapshot] = empty, want persisted snapshot") + } +} + func TestResolvedWorkerRuntimeWithConfigFallsBackToCityPathAndSyncsHintsWorkDir(t *testing.T) { cityDir := t.TempDir() writePhase0InterfaceCity(t, cityDir, `[workspace] diff --git a/internal/api/session_resolved_config.go b/internal/api/session_resolved_config.go index 3687dad19d..288d6539f3 100644 --- a/internal/api/session_resolved_config.go +++ b/internal/api/session_resolved_config.go @@ -19,6 +19,15 @@ func resolvedSessionConfigForProvider( if resolved == nil { return worker.ResolvedSessionConfig{}, fmt.Errorf("%w: resolved provider is required", worker.ErrHandleConfig) } + var err error + metadata, err = session.WithStoredMCPMetadata( + metadata, + firstNonEmptyString(metadata[session.MCPIdentityMetadataKey], metadata["agent_name"]), + mcpServers, + ) + if err != nil { + return worker.ResolvedSessionConfig{}, err + } // Use the ACP-specific command when the session uses ACP transport, // falling back to the default command for tmux sessions. resolvedCommand := resolved.CommandString() diff --git a/internal/api/session_resolved_config_test.go b/internal/api/session_resolved_config_test.go index c70b224414..daa9458716 100644 --- a/internal/api/session_resolved_config_test.go +++ b/internal/api/session_resolved_config_test.go @@ -5,10 +5,14 @@ import ( "github.com/gastownhall/gascity/internal/config" "github.com/gastownhall/gascity/internal/runtime" + "github.com/gastownhall/gascity/internal/session" ) func TestResolvedSessionConfigForProviderBuildsNormalizedConfig(t *testing.T) { - metadata := map[string]string{"session_origin": "named"} + metadata := map[string]string{ + "session_origin": "named", + "agent_name": "myrig/worker-adhoc-123", + } env := map[string]string{"API_TOKEN": "present"} mcpServers := []runtime.MCPServerConfig{{ Name: "filesystem", @@ -69,6 +73,12 @@ func TestResolvedSessionConfigForProviderBuildsNormalizedConfig(t *testing.T) { if got, want := cfg.Runtime.Resume.SessionIDFlag, "--session-id"; got != want { t.Fatalf("Runtime.Resume.SessionIDFlag = %q, want %q", got, want) } + if got, want := cfg.Metadata[session.MCPIdentityMetadataKey], "myrig/worker-adhoc-123"; got != want { + t.Fatalf("Metadata[mcp_identity] = %q, want %q", got, want) + } + if got := cfg.Metadata[session.MCPServersSnapshotMetadataKey]; got == "" { + t.Fatal("Metadata[mcp_servers_snapshot] = empty, want persisted snapshot") + } metadata["session_origin"] = "mutated" env["API_TOKEN"] = "mutated" From d0dada5b8c61d912ab0e51de48e515ec7108390d Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 03:43:20 +0000 Subject: [PATCH 62/85] fix: infer legacy acp provider routes --- cmd/gc/providers.go | 31 +++++++++++++++++---- cmd/gc/providers_test.go | 59 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/cmd/gc/providers.go b/cmd/gc/providers.go index b0db445c3c..df7a699b4f 100644 --- a/cmd/gc/providers.go +++ b/cmd/gc/providers.go @@ -247,7 +247,7 @@ func needsACPProviderWrapper(snapshot *sessionBeadSnapshot, cfg *config.City) bo } func requiresACPProviderWrapper(snapshot *sessionBeadSnapshot, cfg *config.City) bool { - return len(observedACPSessionNames(snapshot)) > 0 || (cfg != nil && hasACPAgents(cfg.Agents)) + return len(observedACPSessionNames(snapshot, cfg)) > 0 || (cfg != nil && hasACPAgents(cfg.Agents)) } func hasACPProviderTargets(cfg *config.City) bool { @@ -292,14 +292,14 @@ func providerSupportsACP(cfg *config.City, providerName string) bool { return resolved.DefaultSessionTransport() == "acp" } -func observedACPSessionNames(snapshot *sessionBeadSnapshot) []string { +func observedACPSessionNames(snapshot *sessionBeadSnapshot, cfg *config.City) []string { if snapshot == nil { return nil } names := make([]string, 0, len(snapshot.open)) seen := make(map[string]bool, len(snapshot.open)) for _, bead := range snapshot.Open() { - if !beadUsesACPTransport(bead) { + if !beadUsesACPTransport(bead, cfg) { continue } sessionName := strings.TrimSpace(bead.Metadata["session_name"]) @@ -312,16 +312,35 @@ func observedACPSessionNames(snapshot *sessionBeadSnapshot) []string { return names } -func beadUsesACPTransport(bead beads.Bead) bool { +func beadUsesACPTransport(bead beads.Bead, cfg *config.City) bool { transport := strings.TrimSpace(bead.Metadata["transport"]) if transport != "" { return transport == "acp" } - return strings.TrimSpace(bead.Metadata["provider"]) == "acp" + providerName := strings.TrimSpace(bead.Metadata["provider"]) + if providerName == "acp" { + return true + } + templateName := strings.TrimSpace(bead.Metadata["template"]) + if cfg != nil { + if agentCfg, ok := resolveAgentIdentity(cfg, templateName, currentRigContext(cfg)); ok { + if strings.TrimSpace(agentCfg.Session) == "acp" { + return true + } + if providerName == "" { + providerName = strings.TrimSpace(agentCfg.Provider) + } + } + if providerName == "" { + providerName = templateName + } + return providerSupportsACP(cfg, providerName) + } + return false } func configuredACPRouteNames(snapshot *sessionBeadSnapshot, cityName string, cfg *config.City) []string { - names := observedACPSessionNames(snapshot) + names := observedACPSessionNames(snapshot, cfg) seen := make(map[string]bool, len(names)) for _, name := range names { seen[name] = true diff --git a/cmd/gc/providers_test.go b/cmd/gc/providers_test.go index 0ceba90d4d..ea3e0f30bb 100644 --- a/cmd/gc/providers_test.go +++ b/cmd/gc/providers_test.go @@ -330,6 +330,35 @@ func TestConfiguredACPRouteNames_IncludeObservedACPProviderSessions(t *testing.T } } +func TestConfiguredACPRouteNames_IncludeLegacyObservedACPProviderSessionsWithoutTransportMetadata(t *testing.T) { + cfg := &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Providers: map[string]config.ProviderSpec{ + "opencode": { + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: boolPtr(true), + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + } + snapshot := newSessionBeadSnapshot([]beads.Bead{{ + Type: sessionBeadType, + Labels: []string{sessionBeadLabel}, + Metadata: map[string]string{ + "template": "opencode", + "provider": "opencode", + "session_name": "provider-session", + }, + }}) + + got := configuredACPRouteNames(snapshot, "test-city", cfg) + if len(got) != 1 || got[0] != "provider-session" { + t.Fatalf("configuredACPRouteNames() = %v, want [provider-session]", got) + } +} + func TestNewSessionProvider_PreregistersACPBeadAndLegacyNames(t *testing.T) { t.Setenv("GC_BEADS", "file") t.Setenv("GC_SESSION", "fake") @@ -508,6 +537,36 @@ func TestNewSessionProviderRoutesObservedACPProviderSessionsWithoutACPAgents(t * } } +func TestNewSessionProviderRoutesLegacyObservedACPProviderSessionsWithoutTransportMetadata(t *testing.T) { + t.Setenv("GC_BEADS", "file") + t.Setenv("GC_SESSION", "fake") + + cityDir := t.TempDir() + t.Setenv("GC_CITY", cityDir) + writeACPProviderRouteCityTOML(t, cityDir, "test-city") + + store, err := openCityStoreAt(cityDir) + if err != nil { + t.Fatalf("openCityStoreAt: %v", err) + } + if _, err := store.Create(beads.Bead{ + Type: sessionBeadType, + Labels: []string{sessionBeadLabel}, + Metadata: map[string]string{ + "template": "opencode", + "provider": "opencode", + "session_name": "provider-session", + }, + }); err != nil { + t.Fatalf("Create(provider session bead): %v", err) + } + + sp := newSessionProvider() + if err := sp.Attach("provider-session"); err == nil || !strings.Contains(err.Error(), "ACP transport") { + t.Fatalf("Attach(provider-session) error = %v, want ACP transport error", err) + } +} + func TestLoadProviderSessionSnapshotLoadsStoreWithoutACPAgents(t *testing.T) { oldOpen := openSessionProviderStore t.Cleanup(func() { openSessionProviderStore = oldOpen }) From 68c1f246ce6bbf878d37ca0812522b12ef61977a Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 03:53:42 +0000 Subject: [PATCH 63/85] fix: persist cli acp mcp metadata --- cmd/gc/cmd_session.go | 57 ++++++++++++++ cmd/gc/cmd_session_test.go | 147 +++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) diff --git a/cmd/gc/cmd_session.go b/cmd/gc/cmd_session.go index 603e788488..27cffa7fa7 100644 --- a/cmd/gc/cmd_session.go +++ b/cmd/gc/cmd_session.go @@ -249,6 +249,20 @@ func cmdSessionNew(args []string, alias, title, titleHint string, noAttach bool, if resolved.BuiltinAncestor != "" && resolved.BuiltinAncestor != resolved.Name { kindMeta["builtin_ancestor"] = resolved.BuiltinAncestor } + kindMeta, err = newSessionStoredMCPMetadata( + cityPath, + cfg, + alias, + canonicalTemplate, + resolved.Name, + workDir, + found.Session, + kindMeta, + ) + if err != nil { + fmt.Fprintf(stderr, "gc session new: %v\n", err) //nolint:errcheck // best-effort stderr + return 1 + } handle, err := newWorkerSessionHandleForResolvedRuntimeWithConfig( cityPath, store, @@ -332,6 +346,20 @@ func cmdSessionNew(args []string, alias, title, titleHint string, noAttach bool, if resolved.BuiltinAncestor != "" && resolved.BuiltinAncestor != resolved.Name { kindMeta["builtin_ancestor"] = resolved.BuiltinAncestor } + kindMeta, err = newSessionStoredMCPMetadata( + cityPath, + cfg, + alias, + canonicalTemplate, + resolved.Name, + workDir, + found.Session, + kindMeta, + ) + if err != nil { + fmt.Fprintf(stderr, "gc session new: %v\n", err) //nolint:errcheck // best-effort stderr + return 1 + } handle, err := newWorkerSessionHandleForResolvedRuntimeWithConfig( cityPath, store, @@ -394,6 +422,35 @@ func cmdSessionNew(args []string, alias, title, titleHint string, noAttach bool, return 0 } +func newSessionStoredMCPMetadata( + cityPath string, + cfg *config.City, + alias, template, provider, workDir, transport string, + metadata map[string]string, +) (map[string]string, error) { + if strings.TrimSpace(transport) != "acp" { + return metadata, nil + } + mcpServers, err := resolvedRuntimeMCPServersWithConfig( + cityPath, + cfg, + alias, + template, + provider, + workDir, + transport, + metadata, + ) + if err != nil { + return nil, err + } + return session.WithStoredMCPMetadata( + metadata, + firstNonEmptyGCString(metadata[session.MCPIdentityMetadataKey], metadata["agent_name"]), + mcpServers, + ) +} + // maybeAutoTitle runs the auto-title flow for a newly created session. // The provider should already be resolved by the caller. It returns a // channel that is closed when background title generation completes. diff --git a/cmd/gc/cmd_session_test.go b/cmd/gc/cmd_session_test.go index 309f9f9b8b..fe8fd7091f 100644 --- a/cmd/gc/cmd_session_test.go +++ b/cmd/gc/cmd_session_test.go @@ -390,6 +390,113 @@ func TestCmdSessionNew_PoolTemplateWithoutAliasUsesGeneratedWorkDirIdentity(t *t } } +func TestCmdSessionNew_ACPTemplatePersistsStoredMCPMetadata(t *testing.T) { + t.Setenv("GC_BEADS", "file") + t.Setenv("GC_SESSION", "fake") + + cityDir := t.TempDir() + t.Setenv("GC_CITY", cityDir) + writePoolACPSessionCityTOML(t, cityDir) + writeCatalogFile(t, cityDir, "mcp/identity.template.toml", ` +name = "identity" +command = "/bin/mcp" +args = ["{{.AgentName}}", "{{.WorkDir}}", "{{.TemplateName}}"] +`) + + sockPath := filepath.Join(cityDir, ".gc", "controller.sock") + lis, err := net.Listen("unix", sockPath) + if err != nil { + t.Fatalf("Listen(%q): %v", sockPath, err) + } + defer lis.Close() //nolint:errcheck + + commands := make(chan string, 3) + errCh := make(chan error, 1) + go func() { + defer close(commands) + for i := 0; i < 3; i++ { + conn, err := lis.Accept() + if err != nil { + errCh <- err + return + } + buf := make([]byte, 64) + n, err := conn.Read(buf) + if err != nil { + conn.Close() //nolint:errcheck + errCh <- err + return + } + cmd := string(buf[:n]) + commands <- cmd + reply := "ok\n" + if cmd == "ping\n" { + reply = "123\n" + } + if _, err := conn.Write([]byte(reply)); err != nil { + conn.Close() //nolint:errcheck + errCh <- err + return + } + conn.Close() //nolint:errcheck + } + }() + + var stdout, stderr bytes.Buffer + if code := cmdSessionNew([]string{"demo/ant"}, "", "", "", true, &stdout, &stderr); code != 0 { + t.Fatalf("cmdSessionNew(acp) = %d, want 0; stderr=%s", code, stderr.String()) + } + + gotCommands := make([]string, 0, 3) + deadline := time.After(2 * time.Second) + for len(gotCommands) < 3 { + select { + case err := <-errCh: + if err != nil { + t.Fatalf("controller socket: %v", err) + } + case cmd, ok := <-commands: + if !ok { + if len(gotCommands) != 3 { + t.Fatalf("controller commands = %v, want ping plus 2 pokes", gotCommands) + } + break + } + gotCommands = append(gotCommands, cmd) + case <-deadline: + t.Fatalf("timed out waiting for controller pokes, got %v", gotCommands) + } + } + + bead := onlySessionBead(t, cityDir) + if got := bead.Metadata[session.MCPIdentityMetadataKey]; got == "" { + t.Fatal("mcp_identity metadata = empty, want persisted identity") + } + if got, want := bead.Metadata[session.MCPIdentityMetadataKey], bead.Metadata["agent_name"]; got != want { + t.Fatalf("mcp_identity = %q, want agent_name %q", got, want) + } + if got := bead.Metadata[session.MCPServersSnapshotMetadataKey]; got == "" { + t.Fatal("mcp_servers_snapshot metadata = empty, want persisted snapshot") + } + + servers, err := session.DecodeMCPServersSnapshot(bead.Metadata[session.MCPServersSnapshotMetadataKey]) + if err != nil { + t.Fatalf("DecodeMCPServersSnapshot: %v", err) + } + if len(servers) != 1 { + t.Fatalf("len(snapshot) = %d, want 1", len(servers)) + } + if got, want := servers[0].Args[0], bead.Metadata[session.MCPIdentityMetadataKey]; got != want { + t.Fatalf("snapshot Args[0] = %q, want %q", got, want) + } + if got, want := servers[0].Args[1], bead.Metadata["work_dir"]; got != want { + t.Fatalf("snapshot Args[1] = %q, want %q", got, want) + } + if got, want := servers[0].Args[2], "demo/ant"; got != want { + t.Fatalf("snapshot Args[2] = %q, want %q", got, want) + } +} + func TestCmdSessionNew_PoolTemplateRejectsAliasMatchingConcreteIdentity(t *testing.T) { t.Setenv("GC_BEADS", "file") t.Setenv("GC_SESSION", "fake") @@ -1139,6 +1246,46 @@ max_active_sessions = 4 } } +func writePoolACPSessionCityTOML(t *testing.T, dir string) { + t.Helper() + if err := os.MkdirAll(filepath.Join(dir, ".gc"), 0o755); err != nil { + t.Fatalf("MkdirAll(.gc): %v", err) + } + rigRoot := filepath.Join(dir, "repos", "demo") + if err := os.MkdirAll(rigRoot, 0o755); err != nil { + t.Fatalf("MkdirAll(rig root): %v", err) + } + data := []byte(fmt.Sprintf(`[workspace] +name = "test-city" + +[beads] +provider = "file" + +[[rigs]] +name = "demo" +path = %q + +[[agent]] +name = "ant" +dir = "demo" +provider = "stub" +session = "acp" +work_dir = ".gc/worktrees/{{.Rig}}/ants/{{.AgentBase}}" +min_active_sessions = 0 +max_active_sessions = 4 + +[providers.stub] +command = "/bin/echo" +path_check = "true" +supports_acp = true +acp_command = "/bin/echo" +acp_args = ["acp"] +`, rigRoot)) + if err := os.WriteFile(filepath.Join(dir, "city.toml"), data, 0o644); err != nil { + t.Fatalf("WriteFile(city.toml): %v", err) + } +} + func sessionBeads(t *testing.T, cityDir string) []beads.Bead { t.Helper() store, err := openCityStoreAt(cityDir) From 3318ef4d394589a286c64b1e86368a2a064b2c3d Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 04:05:19 +0000 Subject: [PATCH 64/85] fix: default custom provider sessions to acp --- cmd/gc/providers.go | 22 ++++++++--- cmd/gc/providers_test.go | 52 ++++++++++++++++++++++++++ internal/api/session_transport.go | 2 +- internal/api/session_transport_test.go | 43 +++++++++++++++++++++ internal/config/provider.go | 15 ++++++++ internal/config/provider_test.go | 50 +++++++++++++++++++++++++ 6 files changed, 177 insertions(+), 7 deletions(-) create mode 100644 internal/api/session_transport_test.go diff --git a/cmd/gc/providers.go b/cmd/gc/providers.go index df7a699b4f..545c201243 100644 --- a/cmd/gc/providers.go +++ b/cmd/gc/providers.go @@ -269,16 +269,16 @@ func hasACPProviderTargets(cfg *config.City) bool { add(agentCfg.Provider) } for name := range candidates { - if providerSupportsACP(cfg, name) { + if providerSessionCreateUsesACP(cfg, name) { return true } } return false } -func providerSupportsACP(cfg *config.City, providerName string) bool { +func resolveProviderForACPTransport(cfg *config.City, providerName string) *config.ResolvedProvider { if cfg == nil || strings.TrimSpace(providerName) == "" { - return false + return nil } resolved, err := config.ResolveProvider( &config.Agent{Provider: providerName}, @@ -287,9 +287,19 @@ func providerSupportsACP(cfg *config.City, providerName string) bool { func(name string) (string, error) { return name, nil }, ) if err != nil { - return false + return nil } - return resolved.DefaultSessionTransport() == "acp" + return resolved +} + +func providerSessionCreateUsesACP(cfg *config.City, providerName string) bool { + resolved := resolveProviderForACPTransport(cfg, providerName) + return resolved != nil && resolved.ProviderSessionCreateTransport() == "acp" +} + +func providerLegacyDefaultsToACP(cfg *config.City, providerName string) bool { + resolved := resolveProviderForACPTransport(cfg, providerName) + return resolved != nil && resolved.DefaultSessionTransport() == "acp" } func observedACPSessionNames(snapshot *sessionBeadSnapshot, cfg *config.City) []string { @@ -334,7 +344,7 @@ func beadUsesACPTransport(bead beads.Bead, cfg *config.City) bool { if providerName == "" { providerName = templateName } - return providerSupportsACP(cfg, providerName) + return providerLegacyDefaultsToACP(cfg, providerName) } return false } diff --git a/cmd/gc/providers_test.go b/cmd/gc/providers_test.go index ea3e0f30bb..7821d061ea 100644 --- a/cmd/gc/providers_test.go +++ b/cmd/gc/providers_test.go @@ -359,6 +359,35 @@ func TestConfiguredACPRouteNames_IncludeLegacyObservedACPProviderSessionsWithout } } +func TestConfiguredACPRouteNames_ExcludeLegacyObservedCustomACPProviderSessionsWithoutTransportMetadata(t *testing.T) { + cfg := &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Providers: map[string]config.ProviderSpec{ + "custom-acp": { + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: boolPtr(true), + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + } + snapshot := newSessionBeadSnapshot([]beads.Bead{{ + Type: sessionBeadType, + Labels: []string{sessionBeadLabel}, + Metadata: map[string]string{ + "template": "custom-acp", + "provider": "custom-acp", + "session_name": "provider-session", + }, + }}) + + got := configuredACPRouteNames(snapshot, "test-city", cfg) + if len(got) != 0 { + t.Fatalf("configuredACPRouteNames() = %v, want no legacy ACP inference for custom provider", got) + } +} + func TestNewSessionProvider_PreregistersACPBeadAndLegacyNames(t *testing.T) { t.Setenv("GC_BEADS", "file") t.Setenv("GC_SESSION", "fake") @@ -438,6 +467,29 @@ func TestNewSessionProviderWrapsACPProvidersWithoutACPAgents(t *testing.T) { } } +func TestNewSessionProviderWrapsCustomACPProvidersWithExplicitACPConfig(t *testing.T) { + ctx := sessionProviderContextForCity(&config.City{ + Workspace: config.Workspace{ + Name: "test-city", + Provider: "custom-acp", + }, + Providers: map[string]config.ProviderSpec{ + "custom-acp": { + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: boolPtr(true), + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + }, t.TempDir(), "fake") + + sp := newSessionProviderFromContext(ctx, nil) + if _, ok := sp.(interface{ RouteACP(string) }); !ok { + t.Fatalf("provider = %T, want ACP-routing wrapper", sp) + } +} + func TestNewSessionProviderIgnoresACPInitFailureForUnusedACPProviders(t *testing.T) { oldBuild := buildSessionProviderByName t.Cleanup(func() { buildSessionProviderByName = oldBuild }) diff --git a/internal/api/session_transport.go b/internal/api/session_transport.go index f076306c4d..301b73e2f5 100644 --- a/internal/api/session_transport.go +++ b/internal/api/session_transport.go @@ -40,7 +40,7 @@ func providerSessionTransport(resolved *config.ResolvedProvider, sp runtime.Prov if resolved == nil { return "", nil } - return validateSessionTransport(resolved, resolved.DefaultSessionTransport(), sp) + return validateSessionTransport(resolved, resolved.ProviderSessionCreateTransport(), sp) } func transportSupportsACP(sp runtime.Provider) bool { diff --git a/internal/api/session_transport_test.go b/internal/api/session_transport_test.go new file mode 100644 index 0000000000..97866b7500 --- /dev/null +++ b/internal/api/session_transport_test.go @@ -0,0 +1,43 @@ +package api + +import ( + "testing" + + "github.com/gastownhall/gascity/internal/config" + "github.com/gastownhall/gascity/internal/runtime" +) + +type createTransportCapableProvider struct { + *runtime.Fake +} + +func (p *createTransportCapableProvider) SupportsTransport(transport string) bool { + return transport == "acp" +} + +func TestProviderSessionTransportUsesExplicitACPConfigOnCustomProvider(t *testing.T) { + transport, err := providerSessionTransport(&config.ResolvedProvider{ + Name: "custom-acp", + SupportsACP: true, + ACPCommand: "/bin/echo", + }, &createTransportCapableProvider{Fake: runtime.NewFake()}) + if err != nil { + t.Fatalf("providerSessionTransport: %v", err) + } + if transport != "acp" { + t.Fatalf("providerSessionTransport() = %q, want %q", transport, "acp") + } +} + +func TestProviderSessionTransportSupportsACPAloneStaysDefault(t *testing.T) { + transport, err := providerSessionTransport(&config.ResolvedProvider{ + Name: "custom-acp", + SupportsACP: true, + }, &createTransportCapableProvider{Fake: runtime.NewFake()}) + if err != nil { + t.Fatalf("providerSessionTransport: %v", err) + } + if transport != "" { + t.Fatalf("providerSessionTransport() = %q, want empty transport", transport) + } +} diff --git a/internal/config/provider.go b/internal/config/provider.go index c6166ae25c..b66581bc06 100644 --- a/internal/config/provider.go +++ b/internal/config/provider.go @@ -249,6 +249,21 @@ func (rp *ResolvedProvider) DefaultSessionTransport() string { return "" } +// ProviderSessionCreateTransport returns the transport to use when creating a +// provider-backed session without any template-level session override. +func (rp *ResolvedProvider) ProviderSessionCreateTransport() string { + if rp == nil || !rp.SupportsACP { + return "" + } + if transport := rp.DefaultSessionTransport(); transport != "" { + return transport + } + if strings.TrimSpace(rp.ACPCommand) != "" || rp.ACPArgs != nil { + return "acp" + } + return "" +} + // TitleModelFlagArgs resolves the TitleModel key against the "model" // OptionsSchema entry. Returns the CLI flag args for the title model, // or nil if TitleModel is empty or not found in the schema. diff --git a/internal/config/provider_test.go b/internal/config/provider_test.go index 62c73cd1b3..79082f1756 100644 --- a/internal/config/provider_test.go +++ b/internal/config/provider_test.go @@ -396,3 +396,53 @@ func TestDefaultSessionTransportSupportsACPDoesNotImplyACPDefault(t *testing.T) t.Fatalf("DefaultSessionTransport() = %q, want empty default transport", got) } } + +func TestProviderSessionCreateTransportUsesExplicitACPOverrides(t *testing.T) { + tests := []struct { + name string + rp ResolvedProvider + }{ + { + name: "explicit acp command", + rp: ResolvedProvider{ + Name: "custom-acp", + SupportsACP: true, + ACPCommand: "/bin/custom-acp", + }, + }, + { + name: "explicit acp args", + rp: ResolvedProvider{ + Name: "custom-acp", + SupportsACP: true, + ACPArgs: []string{"acp"}, + }, + }, + { + name: "opencode family remains acp", + rp: ResolvedProvider{ + Name: "custom-opencode", + BuiltinAncestor: "opencode", + SupportsACP: true, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.rp.ProviderSessionCreateTransport(); got != "acp" { + t.Fatalf("ProviderSessionCreateTransport() = %q, want %q", got, "acp") + } + }) + } +} + +func TestProviderSessionCreateTransportSupportsACPAloneStaysDefault(t *testing.T) { + rp := &ResolvedProvider{ + Name: "custom-acp", + SupportsACP: true, + } + if got := rp.ProviderSessionCreateTransport(); got != "" { + t.Fatalf("ProviderSessionCreateTransport() = %q, want empty transport", got) + } +} From cd2c9d8b28bb5bb4677a355d060f8b4b4d9a7ae1 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 04:18:07 +0000 Subject: [PATCH 65/85] fix: default agent sessions to provider acp --- cmd/gc/cmd_session.go | 21 ++++---- cmd/gc/cmd_session_test.go | 75 ++++++++++++++++++++++++++ cmd/gc/session_template_start.go | 14 ++--- cmd/gc/template_resolve.go | 9 ++-- internal/api/handler_session_create.go | 2 +- internal/api/session_resolution.go | 2 +- internal/api/session_runtime.go | 24 ++++++++- internal/api/session_transport_test.go | 62 +++++++++++++++++++++ internal/config/provider.go | 13 +++++ internal/config/provider_test.go | 21 ++++++++ 10 files changed, 219 insertions(+), 24 deletions(-) diff --git a/cmd/gc/cmd_session.go b/cmd/gc/cmd_session.go index 27cffa7fa7..fc49ade40a 100644 --- a/cmd/gc/cmd_session.go +++ b/cmd/gc/cmd_session.go @@ -170,6 +170,7 @@ func cmdSessionNew(args []string, alias, title, titleHint string, noAttach bool, fmt.Fprintf(stderr, "gc session new: %v\n", err) //nolint:errcheck // best-effort stderr return 1 } + sessionTransport := config.ResolveSessionCreateTransport(found.Session, resolved) requestedAlias, err := session.ValidateAlias(alias) if err != nil { fmt.Fprintf(stderr, "gc session new: %v\n", err) //nolint:errcheck // best-effort stderr @@ -192,7 +193,7 @@ func cmdSessionNew(args []string, alias, title, titleHint string, noAttach bool, } sp := newSessionProvider() - if err := validateResolvedSessionTransport(resolved, found.Session, sp); err != nil { + if err := validateResolvedSessionTransport(resolved, sessionTransport, sp); err != nil { fmt.Fprintf(stderr, "gc session new: %v\n", err) //nolint:errcheck // best-effort stderr return 1 } @@ -227,7 +228,7 @@ func cmdSessionNew(args []string, alias, title, titleHint string, noAttach bool, if err != nil { titleProvider = nil } - sessionCommand, err := resolvedSessionCommand(cityPath, resolved, nil, found.Session) + sessionCommand, err := resolvedSessionCommand(cityPath, resolved, nil, sessionTransport) if err != nil { fmt.Fprintf(stderr, "gc session new: %v\n", err) //nolint:errcheck // best-effort stderr return 1 @@ -256,7 +257,7 @@ func cmdSessionNew(args []string, alias, title, titleHint string, noAttach bool, canonicalTemplate, resolved.Name, workDir, - found.Session, + sessionTransport, kindMeta, ) if err != nil { @@ -275,7 +276,7 @@ func cmdSessionNew(args []string, alias, title, titleHint string, noAttach bool, sessionCommand, found.Provider, workDir, - found.Session, + sessionTransport, resolved, kindMeta, ) @@ -313,8 +314,8 @@ func cmdSessionNew(args []string, alias, title, titleHint string, noAttach bool, fmt.Fprintf(stdout, "Session %s created from template %q (reconciler will start it).\n", info.ID, canonicalTemplate) //nolint:errcheck // best-effort stdout - if !shouldAttachNewSession(noAttach, found.Session) { - if found.Session == "acp" && !noAttach { + if !shouldAttachNewSession(noAttach, sessionTransport) { + if sessionTransport == "acp" && !noAttach { fmt.Fprintln(stdout, "Session uses ACP transport; not attaching.") //nolint:errcheck // best-effort stdout } return 0 @@ -353,7 +354,7 @@ func cmdSessionNew(args []string, alias, title, titleHint string, noAttach bool, canonicalTemplate, resolved.Name, workDir, - found.Session, + sessionTransport, kindMeta, ) if err != nil { @@ -372,7 +373,7 @@ func cmdSessionNew(args []string, alias, title, titleHint string, noAttach bool, sessionCommand, found.Provider, workDir, - found.Session, + sessionTransport, resolved, kindMeta, ) @@ -407,8 +408,8 @@ func cmdSessionNew(args []string, alias, title, titleHint string, noAttach bool, fmt.Fprintf(stdout, "Session %s created from template %q.\n", info.ID, canonicalTemplate) //nolint:errcheck // best-effort stdout - if !shouldAttachNewSession(noAttach, found.Session) { - if found.Session == "acp" && !noAttach { + if !shouldAttachNewSession(noAttach, sessionTransport) { + if sessionTransport == "acp" && !noAttach { fmt.Fprintln(stdout, "Session uses ACP transport; not attaching.") //nolint:errcheck // best-effort stdout } return 0 diff --git a/cmd/gc/cmd_session_test.go b/cmd/gc/cmd_session_test.go index fe8fd7091f..e7be2b480f 100644 --- a/cmd/gc/cmd_session_test.go +++ b/cmd/gc/cmd_session_test.go @@ -497,6 +497,42 @@ args = ["{{.AgentName}}", "{{.WorkDir}}", "{{.TemplateName}}"] } } +func TestCmdSessionNew_CustomACPProviderDefaultsAgentSessionToACP(t *testing.T) { + t.Setenv("GC_BEADS", "file") + t.Setenv("GC_SESSION", "fake") + + oldBuild := buildSessionProviderByName + t.Cleanup(func() { buildSessionProviderByName = oldBuild }) + buildSessionProviderByName = func(name string, sc config.SessionConfig, cityName, cityPath string) (runtime.Provider, error) { + if name == "acp" { + return &transportCapableSessionProvider{Fake: runtime.NewFake()}, nil + } + return oldBuild(name, sc, cityName, cityPath) + } + + cityDir := t.TempDir() + t.Setenv("GC_CITY", cityDir) + writePoolProviderDefaultACPSessionCityTOML(t, cityDir) + writeCatalogFile(t, cityDir, "mcp/identity.template.toml", ` +name = "identity" +command = "/bin/mcp" +args = ["{{.AgentName}}", "{{.WorkDir}}", "{{.TemplateName}}"] +`) + + var stdout, stderr bytes.Buffer + if code := cmdSessionNew([]string{"demo/ant"}, "", "", "", true, &stdout, &stderr); code != 0 { + t.Fatalf("cmdSessionNew(custom provider acp default) = %d, want 0; stderr=%s", code, stderr.String()) + } + + bead := onlySessionBead(t, cityDir) + if got := bead.Metadata["transport"]; got != "acp" { + t.Fatalf("transport = %q, want %q", got, "acp") + } + if got := bead.Metadata[session.MCPServersSnapshotMetadataKey]; got == "" { + t.Fatal("mcp_servers_snapshot metadata = empty, want persisted snapshot") + } +} + func TestCmdSessionNew_PoolTemplateRejectsAliasMatchingConcreteIdentity(t *testing.T) { t.Setenv("GC_BEADS", "file") t.Setenv("GC_SESSION", "fake") @@ -1286,6 +1322,45 @@ acp_args = ["acp"] } } +func writePoolProviderDefaultACPSessionCityTOML(t *testing.T, dir string) { + t.Helper() + if err := os.MkdirAll(filepath.Join(dir, ".gc"), 0o755); err != nil { + t.Fatalf("MkdirAll(.gc): %v", err) + } + rigRoot := filepath.Join(dir, "repos", "demo") + if err := os.MkdirAll(rigRoot, 0o755); err != nil { + t.Fatalf("MkdirAll(rig root): %v", err) + } + data := []byte(fmt.Sprintf(`[workspace] +name = "test-city" + +[beads] +provider = "file" + +[[rigs]] +name = "demo" +path = %q + +[[agent]] +name = "ant" +dir = "demo" +provider = "custom-acp" +work_dir = ".gc/worktrees/{{.Rig}}/ants/{{.AgentBase}}" +min_active_sessions = 0 +max_active_sessions = 4 + +[providers.custom-acp] +command = "/bin/echo" +path_check = "true" +supports_acp = true +acp_command = "/bin/echo" +acp_args = ["acp"] +`, rigRoot)) + if err := os.WriteFile(filepath.Join(dir, "city.toml"), data, 0o644); err != nil { + t.Fatalf("WriteFile(city.toml): %v", err) + } +} + func sessionBeads(t *testing.T, cityDir string) []beads.Bead { t.Helper() store, err := openCityStoreAt(cityDir) diff --git a/cmd/gc/session_template_start.go b/cmd/gc/session_template_start.go index 6d0dcfdc5b..98276d30c4 100644 --- a/cmd/gc/session_template_start.go +++ b/cmd/gc/session_template_start.go @@ -119,11 +119,12 @@ func materializeSessionForTemplateWithOptions( if err != nil { return "", err } + sessionTransport := config.ResolveSessionCreateTransport(spec.Agent.Session, resolved) sp := newSessionProvider() - if err := validateResolvedSessionTransport(resolved, spec.Agent.Session, sp); err != nil { + if err := validateResolvedSessionTransport(resolved, sessionTransport, sp); err != nil { return "", err } - sessionCommand, err := resolvedSessionCommand(cityPath, resolved, nil, spec.Agent.Session) + sessionCommand, err := resolvedSessionCommand(cityPath, resolved, nil, sessionTransport) if err != nil { return "", err } @@ -172,7 +173,7 @@ func materializeSessionForTemplateWithOptions( sessionCommand, providerName, workDir, - spec.Agent.Session, + sessionTransport, resolved, extraMeta, ) @@ -275,11 +276,12 @@ func materializeSessionForAgentConfig(cityPath string, cfg *config.City, store b if err != nil { return "", err } + sessionTransport := config.ResolveSessionCreateTransport(agentCfg.Session, resolved) sp := newSessionProvider() - if err := validateResolvedSessionTransport(resolved, agentCfg.Session, sp); err != nil { + if err := validateResolvedSessionTransport(resolved, sessionTransport, sp); err != nil { return "", err } - sessionCommand, err := resolvedSessionCommand(cityPath, resolved, nil, agentCfg.Session) + sessionCommand, err := resolvedSessionCommand(cityPath, resolved, nil, sessionTransport) if err != nil { return "", err } @@ -321,7 +323,7 @@ func materializeSessionForAgentConfig(cityPath string, cfg *config.City, store b sessionCommand, agentCfg.Provider, workDir, - agentCfg.Session, + sessionTransport, resolved, extraMeta, ) diff --git a/cmd/gc/template_resolve.go b/cmd/gc/template_resolve.go index 7cd4835deb..836d7e0f47 100644 --- a/cmd/gc/template_resolve.go +++ b/cmd/gc/template_resolve.go @@ -130,8 +130,9 @@ func resolveTemplate(p *agentBuildParams, cfgAgent *config.Agent, qualifiedName if err != nil { return TemplateParams{}, fmt.Errorf("agent %q: %w", qualifiedName, err) } + sessionTransport := config.ResolveSessionCreateTransport(cfgAgent.Session, resolved) // Step 2: Validate session vs provider compatibility. - if cfgAgent.Session == "acp" && !resolved.SupportsACP { + if sessionTransport == "acp" && !resolved.SupportsACP { return TemplateParams{}, fmt.Errorf("agent %q: session = \"acp\" but provider %q does not support ACP (set supports_acp = true on the provider)", qualifiedName, resolved.Name) } @@ -151,7 +152,7 @@ func resolveTemplate(p *agentBuildParams, cfgAgent *config.Agent, qualifiedName // Step 5: Build copy_files and command with settings args + schema defaults. var copyFiles []runtime.CopyEntry var command string - if cfgAgent.Session == "acp" { + if sessionTransport == "acp" { command = resolved.ACPCommandString() } else { command = resolved.CommandString() @@ -477,7 +478,7 @@ func resolveTemplate(p *agentBuildParams, cfgAgent *config.Agent, qualifiedName } } var mcpServers []runtime.MCPServerConfig - if cfgAgent.Session == "acp" { + if sessionTransport == "acp" { mcpServers = materialize.RuntimeMCPServers(mcpCatalog.Servers) } @@ -514,7 +515,7 @@ func resolveTemplate(p *agentBuildParams, cfgAgent *config.Agent, qualifiedName RigName: rigName, RigRoot: rigRoot, WakeMode: cfgAgent.WakeMode, - IsACP: cfgAgent.Session == "acp", + IsACP: sessionTransport == "acp", HookEnabled: hasHooks, MCPServers: mcpServers, }, nil diff --git a/internal/api/handler_session_create.go b/internal/api/handler_session_create.go index a626e0a2ff..2c5755b0d4 100644 --- a/internal/api/handler_session_create.go +++ b/internal/api/handler_session_create.go @@ -77,7 +77,7 @@ func (s *Server) handleSessionCreate(w http.ResponseWriter, r *http.Request) { switch kind { case "agent": var err error - resolved, workDir, transport, template, err = s.resolveSessionTemplate(name) + resolved, workDir, transport, template, err = s.resolveSessionTemplateForCreate(name) if err != nil { if errors.Is(err, errSessionTemplateNotFound) { s.idem.unreserve(idemKey) diff --git a/internal/api/session_resolution.go b/internal/api/session_resolution.go index 5b90b2699a..a77b4b9f2a 100644 --- a/internal/api/session_resolution.go +++ b/internal/api/session_resolution.go @@ -275,7 +275,7 @@ func (s *Server) materializeNamedSessionWithContext(ctx context.Context, store b return "", err } - resolved, _, transport, qualifiedTemplate, err := s.resolveSessionTemplate(spec.Agent.QualifiedName()) + resolved, _, transport, qualifiedTemplate, err := s.resolveSessionTemplateForCreate(spec.Agent.QualifiedName()) if err != nil { return "", err } diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index b42a7de553..b5a4ac4c74 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -166,7 +166,7 @@ func (s *Server) resolveSessionWorkDir(agentCfg config.Agent, qualifiedName stri // agent name that matches exactly one configured agent. Keeps the // two-phase lookup out of the handler. func (s *Server) resolveSessionTemplateWithBareNameFallback(name string) (*config.ResolvedProvider, string, string, string, error) { - resolved, workDir, transport, template, err := s.resolveSessionTemplate(name) + resolved, workDir, transport, template, err := s.resolveSessionTemplateForCreate(name) if err == nil { return resolved, workDir, transport, template, nil } @@ -177,7 +177,27 @@ func (s *Server) resolveSessionTemplateWithBareNameFallback(name string) (*confi if !ok { return nil, "", "", "", err } - return s.resolveSessionTemplate(agentCfg.QualifiedName()) + return s.resolveSessionTemplateForCreate(agentCfg.QualifiedName()) +} + +func (s *Server) resolveSessionTemplateForCreate(template string) (*config.ResolvedProvider, string, string, string, error) { + cfg := s.state.Config() + if cfg == nil { + return nil, "", "", "", errors.New("no city config loaded") + } + agentCfg, ok := resolveSessionTemplateAgent(cfg, template) + if !ok { + return nil, "", "", "", errSessionTemplateNotFound + } + resolved, err := config.ResolveProvider(&agentCfg, &cfg.Workspace, cfg.Providers, exec.LookPath) + if err != nil { + return nil, "", "", "", err + } + workDir, err := s.resolveSessionWorkDir(agentCfg, agentCfg.QualifiedName()) + if err != nil { + return nil, "", "", "", err + } + return resolved, workDir, config.ResolveSessionCreateTransport(agentCfg.Session, resolved), agentCfg.QualifiedName(), nil } func (s *Server) resolveSessionTemplate(template string) (*config.ResolvedProvider, string, string, string, error) { diff --git a/internal/api/session_transport_test.go b/internal/api/session_transport_test.go index 97866b7500..56bfddbba4 100644 --- a/internal/api/session_transport_test.go +++ b/internal/api/session_transport_test.go @@ -41,3 +41,65 @@ func TestProviderSessionTransportSupportsACPAloneStaysDefault(t *testing.T) { t.Fatalf("providerSessionTransport() = %q, want empty transport", transport) } } + +func TestResolveSessionTemplateForCreateUsesProviderACPDefault(t *testing.T) { + fs := newSessionFakeState(t) + supportsACP := true + fs.cfg = &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Agents: []config.Agent{{ + Name: "worker", + Dir: "myrig", + Provider: "custom-acp", + }}, + Providers: map[string]config.ProviderSpec{ + "custom-acp": { + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + } + + srv := New(fs) + _, _, transport, _, err := srv.resolveSessionTemplateForCreate("myrig/worker") + if err != nil { + t.Fatalf("resolveSessionTemplateForCreate: %v", err) + } + if transport != "acp" { + t.Fatalf("transport = %q, want %q", transport, "acp") + } +} + +func TestResolveSessionTemplateKeepsLegacyRuntimeTransportDefault(t *testing.T) { + fs := newSessionFakeState(t) + supportsACP := true + fs.cfg = &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Agents: []config.Agent{{ + Name: "worker", + Dir: "myrig", + Provider: "custom-acp", + }}, + Providers: map[string]config.ProviderSpec{ + "custom-acp": { + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + } + + srv := New(fs) + _, _, transport, _, err := srv.resolveSessionTemplate("myrig/worker") + if err != nil { + t.Fatalf("resolveSessionTemplate: %v", err) + } + if transport != "" { + t.Fatalf("transport = %q, want empty runtime default", transport) + } +} diff --git a/internal/config/provider.go b/internal/config/provider.go index b66581bc06..e4e8521e7a 100644 --- a/internal/config/provider.go +++ b/internal/config/provider.go @@ -264,6 +264,19 @@ func (rp *ResolvedProvider) ProviderSessionCreateTransport() string { return "" } +// ResolveSessionCreateTransport returns the transport to use when creating a +// fresh session from an agent/template configuration. +func ResolveSessionCreateTransport(agentSession string, resolved *ResolvedProvider) string { + agentSession = strings.TrimSpace(agentSession) + if agentSession != "" { + return agentSession + } + if resolved == nil { + return "" + } + return strings.TrimSpace(resolved.ProviderSessionCreateTransport()) +} + // TitleModelFlagArgs resolves the TitleModel key against the "model" // OptionsSchema entry. Returns the CLI flag args for the title model, // or nil if TitleModel is empty or not found in the schema. diff --git a/internal/config/provider_test.go b/internal/config/provider_test.go index 79082f1756..8fb9b48fd3 100644 --- a/internal/config/provider_test.go +++ b/internal/config/provider_test.go @@ -446,3 +446,24 @@ func TestProviderSessionCreateTransportSupportsACPAloneStaysDefault(t *testing.T t.Fatalf("ProviderSessionCreateTransport() = %q, want empty transport", got) } } + +func TestResolveSessionCreateTransportPrefersAgentSessionOverride(t *testing.T) { + got := ResolveSessionCreateTransport("acp", &ResolvedProvider{ + Name: "custom-acp", + SupportsACP: true, + }) + if got != "acp" { + t.Fatalf("ResolveSessionCreateTransport() = %q, want %q", got, "acp") + } +} + +func TestResolveSessionCreateTransportFallsBackToProviderCreateTransport(t *testing.T) { + got := ResolveSessionCreateTransport("", &ResolvedProvider{ + Name: "custom-acp", + SupportsACP: true, + ACPCommand: "/bin/echo", + }) + if got != "acp" { + t.Fatalf("ResolveSessionCreateTransport() = %q, want %q", got, "acp") + } +} From 48410dcce8d5962618ac8ee58e01238dc2af023d Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 04:26:27 +0000 Subject: [PATCH 66/85] fix: infer provider acp defaults for template sessions --- cmd/gc/providers.go | 26 +++++++++++--- cmd/gc/providers_test.go | 47 +++++++++++++++++++++++++- cmd/gc/session_manager_test.go | 11 +++++- cmd/gc/worker_handle.go | 13 +++++-- cmd/gc/worker_handle_test.go | 41 ++++++++++++++++++++++ internal/api/session_manager.go | 11 +++++- internal/api/session_transport_test.go | 26 ++++++++++++++ 7 files changed, 165 insertions(+), 10 deletions(-) diff --git a/cmd/gc/providers.go b/cmd/gc/providers.go index 545c201243..35d5a6aab3 100644 --- a/cmd/gc/providers.go +++ b/cmd/gc/providers.go @@ -215,20 +215,36 @@ func newSessionProviderFromContextWithError(ctx sessionProviderContext, sessionB // hasACPAgents reports whether any agent in the config uses session = "acp". func hasACPAgents(agents []config.Agent) bool { for _, a := range agents { - if a.Session == "acp" { + if strings.TrimSpace(a.Session) == "acp" { return true } } return false } +func agentSessionCreateTransport(cfg *config.City, agentCfg config.Agent) string { + if cfg == nil { + return strings.TrimSpace(agentCfg.Session) + } + resolved, err := config.ResolveProvider( + &agentCfg, + &cfg.Workspace, + cfg.Providers, + func(name string) (string, error) { return name, nil }, + ) + if err != nil { + return strings.TrimSpace(agentCfg.Session) + } + return config.ResolveSessionCreateTransport(agentCfg.Session, resolved) +} + // configuredACPSessionNames resolves the runtime session names for ACP-backed // agents using a single session-bead snapshot. When the snapshot is unavailable // or bead lookup fails, it falls back to the legacy deterministic name. -func configuredACPSessionNames(snapshot *sessionBeadSnapshot, cityName, sessionTemplate string, agents []config.Agent) []string { +func configuredACPSessionNames(snapshot *sessionBeadSnapshot, cityName, sessionTemplate string, cfg *config.City, agents []config.Agent) []string { names := make([]string, 0, len(agents)) for _, a := range agents { - if a.Session != "acp" { + if agentSessionCreateTransport(cfg, a) != "acp" { continue } sessName := agent.SessionNameFor(cityName, a.QualifiedName(), sessionTemplate) @@ -358,7 +374,7 @@ func configuredACPRouteNames(snapshot *sessionBeadSnapshot, cityName string, cfg if cfg == nil { return names } - for _, name := range configuredACPSessionNames(snapshot, cityName, cfg.Workspace.SessionTemplate, cfg.Agents) { + for _, name := range configuredACPSessionNames(snapshot, cityName, cfg.Workspace.SessionTemplate, cfg, cfg.Agents) { if name == "" || seen[name] { continue } @@ -367,7 +383,7 @@ func configuredACPRouteNames(snapshot *sessionBeadSnapshot, cityName string, cfg } for _, named := range cfg.NamedSessions { agentCfg := config.FindAgent(cfg, named.TemplateQualifiedName()) - if agentCfg == nil || agentCfg.Session != "acp" { + if agentCfg == nil || agentSessionCreateTransport(cfg, *agentCfg) != "acp" { continue } sessionName := config.NamedSessionRuntimeName(cityName, cfg.Workspace, named.QualifiedName()) diff --git a/cmd/gc/providers_test.go b/cmd/gc/providers_test.go index 7821d061ea..43a0ed1d9b 100644 --- a/cmd/gc/providers_test.go +++ b/cmd/gc/providers_test.go @@ -227,7 +227,7 @@ func TestConfiguredACPSessionNames_UsesProvidedSnapshot(t *testing.T) { {Name: "mayor"}, } - got := configuredACPSessionNames(snapshot, "city", "", agents) + got := configuredACPSessionNames(snapshot, "city", "", nil, agents) want := []string{ "custom-reviewer", agent.SessionNameFor("city", "witness", ""), @@ -444,6 +444,21 @@ func TestNewSessionProvider_PreregistersACPNamedSessionRuntimeName(t *testing.T) } } +func TestNewSessionProvider_PreregistersProviderDefaultACPNamedSessionRuntimeName(t *testing.T) { + t.Setenv("GC_BEADS", "file") + t.Setenv("GC_SESSION", "fake") + + cityDir := t.TempDir() + t.Setenv("GC_CITY", cityDir) + writeProviderDefaultACPNamedSessionRouteCityTOML(t, cityDir, "test-city") + + sp := newSessionProvider() + namedRuntime := config.NamedSessionRuntimeName("test-city", config.Workspace{}, "reviewer") + if err := sp.Attach(namedRuntime); err == nil || !strings.Contains(err.Error(), "ACP transport") { + t.Fatalf("Attach(%q) error = %v, want ACP transport error", namedRuntime, err) + } +} + func TestNewSessionProviderWrapsACPProvidersWithoutACPAgents(t *testing.T) { ctx := sessionProviderContextForCity(&config.City{ Workspace: config.Workspace{ @@ -749,6 +764,36 @@ start_command = "echo" } } +func writeProviderDefaultACPNamedSessionRouteCityTOML(t *testing.T, dir, cityName string) { + t.Helper() + if err := os.MkdirAll(filepath.Join(dir, ".gc"), 0o755); err != nil { + t.Fatalf("MkdirAll(.gc): %v", err) + } + data := []byte(`[workspace] +name = "` + cityName + `" + +[beads] +provider = "file" + +[[agent]] +name = "reviewer" +provider = "custom-acp" + +[[named_session]] +template = "reviewer" + +[providers.custom-acp] +command = "/bin/echo" +path_check = "true" +supports_acp = true +acp_command = "/bin/echo" +acp_args = ["acp"] +`) + if err := os.WriteFile(filepath.Join(dir, "city.toml"), data, 0o644); err != nil { + t.Fatalf("WriteFile(city.toml): %v", err) + } +} + func writeACPProviderRouteCityTOML(t *testing.T, dir, cityName string) { t.Helper() if err := os.MkdirAll(filepath.Join(dir, ".gc"), 0o755); err != nil { diff --git a/cmd/gc/session_manager_test.go b/cmd/gc/session_manager_test.go index 00dbcafc85..d53586e781 100644 --- a/cmd/gc/session_manager_test.go +++ b/cmd/gc/session_manager_test.go @@ -17,7 +17,16 @@ func newSessionManagerWithConfig(cityPath string, store beads.Store, sp runtime. return session.NewManagerWithTransportResolverAndCityPath(store, sp, cityPath, func(template, provider string) string { agentCfg, ok := resolveAgentIdentity(cfg, template, rigContext) if ok { - return agentCfg.Session + resolved, err := config.ResolveProvider( + &agentCfg, + &cfg.Workspace, + cfg.Providers, + func(name string) (string, error) { return name, nil }, + ) + if err != nil { + return agentCfg.Session + } + return config.ResolveSessionCreateTransport(agentCfg.Session, resolved) } provider = strings.TrimSpace(provider) if provider == "" { diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index df7852af45..0c7650a03d 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -33,7 +33,16 @@ func workerFactoryWithConfig(cityPath string, store beads.Store, sp runtime.Prov resolveTransport = func(template, provider string) string { agentCfg, ok := resolveAgentIdentity(cfg, template, rigContext) if ok { - return agentCfg.Session + resolved, err := config.ResolveProvider( + &agentCfg, + &cfg.Workspace, + cfg.Providers, + func(name string) (string, error) { return name, nil }, + ) + if err != nil { + return agentCfg.Session + } + return config.ResolveSessionCreateTransport(agentCfg.Session, resolved) } provider = strings.TrimSpace(provider) if provider == "" { @@ -530,7 +539,7 @@ func resolveWorkerRuntimeProviderWithConfig(cfg *config.City, info session.Info, if sessionKind != "provider" { if found, ok := resolveAgentIdentity(cfg, info.Template, ""); ok { if resolved, err := config.ResolveProvider(&found, &cfg.Workspace, cfg.Providers, exec.LookPath); err == nil { - return resolved, firstNonEmptyWorkerString(strings.TrimSpace(info.Transport), strings.TrimSpace(found.Session)) + return resolved, firstNonEmptyWorkerString(strings.TrimSpace(info.Transport), config.ResolveSessionCreateTransport(found.Session, resolved)) } } } diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index b498c1f0ab..4c27bc2784 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -451,6 +451,47 @@ acp_args = ["acp"] } } +func TestResolvedWorkerRuntimeWithConfigUsesProviderACPDefaultForAgentTemplateWithoutSessionOverride(t *testing.T) { + cityDir := t.TempDir() + writePhase0InterfaceCity(t, cityDir, `[workspace] +name = "test-city" + +[beads] +provider = "file" + +[[agent]] +name = "worker" +dir = "myrig" +provider = "custom-acp" + +[providers.custom-acp] +command = "/bin/echo" +path_check = "true" +supports_acp = true +acp_command = "/bin/echo" +acp_args = ["acp"] +`) + + cfg, err := loadCityConfig(cityDir) + if err != nil { + t.Fatalf("loadCityConfig: %v", err) + } + + resolved, err := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + Template: "myrig/worker", + WorkDir: cityDir, + }, "") + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfig: %v", err) + } + if resolved == nil { + t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") + } + if got, want := resolved.Command, "/bin/echo acp"; got != want { + t.Fatalf("Command = %q, want %q", got, want) + } +} + func TestWorkerHandleForSessionWithConfigUsesResolvedProviderOnResume(t *testing.T) { skipSlowCmdGCTest(t, "waits through stale session-key detection; run make test-cmd-gc-process for full coverage") cityDir := t.TempDir() diff --git a/internal/api/session_manager.go b/internal/api/session_manager.go index 3e16f70fd3..f160844d34 100644 --- a/internal/api/session_manager.go +++ b/internal/api/session_manager.go @@ -28,7 +28,16 @@ func configuredSessionTransport(cfg *config.City, template, provider string) str return "" } if agentCfg, ok := resolveSessionTemplateAgent(cfg, template); ok { - return strings.TrimSpace(agentCfg.Session) + resolved, err := config.ResolveProvider( + &agentCfg, + &cfg.Workspace, + cfg.Providers, + func(name string) (string, error) { return name, nil }, + ) + if err != nil { + return strings.TrimSpace(agentCfg.Session) + } + return config.ResolveSessionCreateTransport(agentCfg.Session, resolved) } provider = strings.TrimSpace(provider) if provider == "" { diff --git a/internal/api/session_transport_test.go b/internal/api/session_transport_test.go index 56bfddbba4..e9bcd864cd 100644 --- a/internal/api/session_transport_test.go +++ b/internal/api/session_transport_test.go @@ -103,3 +103,29 @@ func TestResolveSessionTemplateKeepsLegacyRuntimeTransportDefault(t *testing.T) t.Fatalf("transport = %q, want empty runtime default", transport) } } + +func TestConfiguredSessionTransportUsesProviderACPDefaultForAgentTemplates(t *testing.T) { + supportsACP := true + cfg := &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Agents: []config.Agent{{ + Name: "worker", + Dir: "myrig", + Provider: "custom-acp", + }}, + Providers: map[string]config.ProviderSpec{ + "custom-acp": { + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + } + + transport := configuredSessionTransport(cfg, "myrig/worker", "") + if transport != "acp" { + t.Fatalf("configuredSessionTransport() = %q, want %q", transport, "acp") + } +} From 7366b4725407ed067835ede9a7d72ebeb33896b3 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 04:34:57 +0000 Subject: [PATCH 67/85] fix: align api resume and dashboard schema --- .../dashboard/web/src/generated/client.gen.ts | 16 + .../web/src/generated/client/client.gen.ts | 298 + .../web/src/generated/client/index.ts | 25 + .../web/src/generated/client/types.gen.ts | 214 + .../web/src/generated/client/utils.gen.ts | 316 + .../web/src/generated/core/auth.gen.ts | 41 + .../src/generated/core/bodySerializer.gen.ts | 82 + .../web/src/generated/core/params.gen.ts | 169 + .../src/generated/core/pathSerializer.gen.ts | 171 + .../generated/core/queryKeySerializer.gen.ts | 117 + .../generated/core/serverSentEvents.gen.ts | 242 + .../web/src/generated/core/types.gen.ts | 104 + .../web/src/generated/core/utils.gen.ts | 140 + cmd/gc/dashboard/web/src/generated/index.ts | 4 + .../dashboard/web/src/generated/schema.d.ts | 9419 +++++++++++++++++ cmd/gc/dashboard/web/src/generated/sdk.gen.ts | 1017 ++ .../dashboard/web/src/generated/types.gen.ts | 8115 ++++++++++++++ internal/api/session_runtime.go | 2 +- internal/api/session_transport_test.go | 43 +- 19 files changed, 20531 insertions(+), 4 deletions(-) create mode 100644 cmd/gc/dashboard/web/src/generated/client.gen.ts create mode 100644 cmd/gc/dashboard/web/src/generated/client/client.gen.ts create mode 100644 cmd/gc/dashboard/web/src/generated/client/index.ts create mode 100644 cmd/gc/dashboard/web/src/generated/client/types.gen.ts create mode 100644 cmd/gc/dashboard/web/src/generated/client/utils.gen.ts create mode 100644 cmd/gc/dashboard/web/src/generated/core/auth.gen.ts create mode 100644 cmd/gc/dashboard/web/src/generated/core/bodySerializer.gen.ts create mode 100644 cmd/gc/dashboard/web/src/generated/core/params.gen.ts create mode 100644 cmd/gc/dashboard/web/src/generated/core/pathSerializer.gen.ts create mode 100644 cmd/gc/dashboard/web/src/generated/core/queryKeySerializer.gen.ts create mode 100644 cmd/gc/dashboard/web/src/generated/core/serverSentEvents.gen.ts create mode 100644 cmd/gc/dashboard/web/src/generated/core/types.gen.ts create mode 100644 cmd/gc/dashboard/web/src/generated/core/utils.gen.ts create mode 100644 cmd/gc/dashboard/web/src/generated/index.ts create mode 100644 cmd/gc/dashboard/web/src/generated/schema.d.ts create mode 100644 cmd/gc/dashboard/web/src/generated/sdk.gen.ts create mode 100644 cmd/gc/dashboard/web/src/generated/types.gen.ts diff --git a/cmd/gc/dashboard/web/src/generated/client.gen.ts b/cmd/gc/dashboard/web/src/generated/client.gen.ts new file mode 100644 index 0000000000..cab3c70195 --- /dev/null +++ b/cmd/gc/dashboard/web/src/generated/client.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { type ClientOptions, type Config, createClient, createConfig } from './client'; +import type { ClientOptions as ClientOptions2 } from './types.gen'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig()); diff --git a/cmd/gc/dashboard/web/src/generated/client/client.gen.ts b/cmd/gc/dashboard/web/src/generated/client/client.gen.ts new file mode 100644 index 0000000000..9ec9ad887c --- /dev/null +++ b/cmd/gc/dashboard/web/src/generated/client/client.gen.ts @@ -0,0 +1,298 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createSseClient } from '../core/serverSentEvents.gen'; +import type { HttpMethod } from '../core/types.gen'; +import { getValidRequestBody } from '../core/utils.gen'; +import type { Client, Config, RequestOptions, ResolvedRequestOptions } from './types.gen'; +import { + buildUrl, + createConfig, + createInterceptors, + getParseAs, + mergeConfigs, + mergeHeaders, + setAuthParams, +} from './utils.gen'; + +type ReqInit = Omit & { + body?: any; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors(); + + const beforeRequest = async < + TData = unknown, + TResponseStyle extends 'data' | 'fields' = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, + >( + options: RequestOptions, + ) => { + const opts = { + ..._config, + ...options, + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined as string | undefined, + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body) as string | undefined; + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + const resolvedOpts = opts as typeof opts & + ResolvedRequestOptions; + const url = buildUrl(resolvedOpts); + + return { opts: resolvedOpts, url }; + }; + + const request: Client['request'] = async (options) => { + const { opts, url } = await beforeRequest(options); + const requestInit: ReqInit = { + redirect: 'follow', + ...opts, + body: getValidRequestBody(opts), + }; + + let request = new Request(url, requestInit); + + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = opts.fetch!; + let response: Response; + + try { + response = await _fetch(request); + } catch (error) { + // Handle fetch exceptions (AbortError, network errors, etc.) + let finalError = error; + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = (await fn(error, undefined as any, request, opts)) as unknown; + } + } + + finalError = finalError || ({} as unknown); + + if (opts.throwOnError) { + throw finalError; + } + + // Return error response + return opts.responseStyle === 'data' + ? undefined + : { + error: finalError, + request, + response: undefined as any, + }; + } + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { + request, + response, + }; + + if (response.ok) { + const parseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + if (response.status === 204 || response.headers.get('Content-Length') === '0') { + let emptyData: any; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + emptyData = await response[parseAs](); + break; + case 'formData': + emptyData = new FormData(); + break; + case 'stream': + emptyData = response.body; + break; + case 'json': + default: + emptyData = {}; + break; + } + return opts.responseStyle === 'data' + ? emptyData + : { + data: emptyData, + ...result, + }; + } + + let data: any; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'text': + data = await response[parseAs](); + break; + case 'json': { + // Some servers return 200 with no Content-Length and empty body. + // response.json() would throw; read as text and parse if non-empty. + const text = await response.text(); + data = text ? JSON.parse(text) : {}; + break; + } + case 'stream': + return opts.responseStyle === 'data' + ? response.body + : { + data: response.body, + ...result, + }; + } + + if (parseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return opts.responseStyle === 'data' + ? data + : { + data, + ...result, + }; + } + + const textError = await response.text(); + let jsonError: unknown; + + try { + jsonError = JSON.parse(textError); + } catch { + // noop + } + + const error = jsonError ?? textError; + let finalError = error; + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = (await fn(error, response, request, opts)) as string; + } + } + + finalError = finalError || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + // TODO: we probably want to return error and improve types + return opts.responseStyle === 'data' + ? undefined + : { + error: finalError, + ...result, + }; + }; + + const makeMethodFn = (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method }); + + const makeSseFn = (method: Uppercase) => async (options: RequestOptions) => { + const { opts, url } = await beforeRequest(options); + return createSseClient({ + ...opts, + body: opts.body as BodyInit | null | undefined, + headers: opts.headers as unknown as Record, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + return request; + }, + serializedBody: getValidRequestBody(opts) as BodyInit | null | undefined, + url, + }); + }; + + const _buildUrl: Client['buildUrl'] = (options) => buildUrl({ ..._config, ...options }); + + return { + buildUrl: _buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/cmd/gc/dashboard/web/src/generated/client/index.ts b/cmd/gc/dashboard/web/src/generated/client/index.ts new file mode 100644 index 0000000000..b295edeca0 --- /dev/null +++ b/cmd/gc/dashboard/web/src/generated/client/index.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from '../core/auth.gen'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer.gen'; +export { buildClientParams } from '../core/params.gen'; +export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen'; +export { createClient } from './client.gen'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen'; +export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/cmd/gc/dashboard/web/src/generated/client/types.gen.ts b/cmd/gc/dashboard/web/src/generated/client/types.gen.ts new file mode 100644 index 0000000000..9813eeaba6 --- /dev/null +++ b/cmd/gc/dashboard/web/src/generated/client/types.gen.ts @@ -0,0 +1,214 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth } from '../core/auth.gen'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../core/serverSentEvents.gen'; +import type { Client as CoreClient, Config as CoreConfig } from '../core/types.gen'; +import type { Middleware } from './utils.gen'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, CoreConfig { + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: 'arrayBuffer' | 'auto' | 'blob' | 'formData' | 'json' | 'stream' | 'text'; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> + extends + Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onRequest' + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record ? TData[keyof TData] : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? (TData extends Record ? TData[keyof TData] : TData) | undefined + : ( + | { + data: TData extends Record ? TData[keyof TData] : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record ? TError[keyof TError] : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick>, 'method'>, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: TData & Options, +) => string; + +export type Client = CoreClient & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + ([TData] extends [never] ? unknown : Omit); diff --git a/cmd/gc/dashboard/web/src/generated/client/utils.gen.ts b/cmd/gc/dashboard/web/src/generated/client/utils.gen.ts new file mode 100644 index 0000000000..5162192d8a --- /dev/null +++ b/cmd/gc/dashboard/web/src/generated/client/utils.gen.ts @@ -0,0 +1,316 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { getAuthToken } from '../core/auth.gen'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer.gen'; +import { getUrl } from '../core/utils.gen'; +import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'; + +export const createQuerySerializer = ({ + parameters = {}, + ...args +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + const options = parameters[name] || args; + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved: options.allowReserved, + explode: true, + name, + style: 'form', + value, + ...options.array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved: options.allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...options.object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved: options.allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = (contentType: string | null): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if (cleanContent.startsWith('application/json') || cleanContent.endsWith('+json')) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => cleanContent.startsWith(type)) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e., their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = (request: Req, options: Options) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update(id: number | Interceptor, fn: Interceptor): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/cmd/gc/dashboard/web/src/generated/core/auth.gen.ts b/cmd/gc/dashboard/web/src/generated/core/auth.gen.ts new file mode 100644 index 0000000000..3ebf994788 --- /dev/null +++ b/cmd/gc/dashboard/web/src/generated/core/auth.gen.ts @@ -0,0 +1,41 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/cmd/gc/dashboard/web/src/generated/core/bodySerializer.gen.ts b/cmd/gc/dashboard/web/src/generated/core/bodySerializer.gen.ts new file mode 100644 index 0000000000..67daca60f8 --- /dev/null +++ b/cmd/gc/dashboard/web/src/generated/core/bodySerializer.gen.ts @@ -0,0 +1,82 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ArrayStyle, ObjectStyle, SerializerOptions } from './pathSerializer.gen'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: unknown) => unknown; + +type QuerySerializerOptionsObject = { + allowReserved?: boolean; + array?: Partial>; + object?: Partial>; +}; + +export type QuerySerializerOptions = QuerySerializerOptionsObject & { + /** + * Per-parameter serialization overrides. When provided, these settings + * override the global array/object settings for specific parameter names. + */ + parameters?: Record; +}; + +const serializeFormDataPair = (data: FormData, key: string, value: unknown): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = (data: URLSearchParams, key: string, value: unknown): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: (body: unknown): FormData => { + const data = new FormData(); + + Object.entries(body as Record).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: unknown): string => + JSON.stringify(body, (_key, value) => (typeof value === 'bigint' ? value.toString() : value)), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: (body: unknown): string => { + const data = new URLSearchParams(); + + Object.entries(body as Record).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/cmd/gc/dashboard/web/src/generated/core/params.gen.ts b/cmd/gc/dashboard/web/src/generated/core/params.gen.ts new file mode 100644 index 0000000000..7955601a5c --- /dev/null +++ b/cmd/gc/dashboard/web/src/generated/core/params.gen.ts @@ -0,0 +1,169 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + } + | { + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If `in` is omitted, `map` aliases `key` to the transport layer. + */ + map: Slot; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + | { + in: Slot; + map?: string; + } + | { + in?: never; + map: Slot; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if ('key' in config) { + map.set(config.key, { + map: config.map, + }); + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Array.isArray(value) && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = (args: ReadonlyArray, fields: FieldsConfig) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + if (field.in) { + (params[field.in] as Record)[name] = arg; + } + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + if (field.in) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + params[field.map] = value; + } + } else { + const extra = extraPrefixes.find(([prefix]) => key.startsWith(prefix)); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[key.slice(prefix.length)] = value; + } else if ('allowExtra' in config && config.allowExtra) { + for (const [slot, allowed] of Object.entries(config.allowExtra)) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/cmd/gc/dashboard/web/src/generated/core/pathSerializer.gen.ts b/cmd/gc/dashboard/web/src/generated/core/pathSerializer.gen.ts new file mode 100644 index 0000000000..994b2848c6 --- /dev/null +++ b/cmd/gc/dashboard/web/src/generated/core/pathSerializer.gen.ts @@ -0,0 +1,171 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions extends SerializePrimitiveOptions, SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [...values, key, allowReserved ? (v as string) : encodeURIComponent(v as string)]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues; +}; diff --git a/cmd/gc/dashboard/web/src/generated/core/queryKeySerializer.gen.ts b/cmd/gc/dashboard/web/src/generated/core/queryKeySerializer.gen.ts new file mode 100644 index 0000000000..5000df606f --- /dev/null +++ b/cmd/gc/dashboard/web/src/generated/core/queryKeySerializer.gen.ts @@ -0,0 +1,117 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * JSON-friendly union that mirrors what Pinia Colada can hash. + */ +export type JsonValue = + | null + | string + | number + | boolean + | JsonValue[] + | { [key: string]: JsonValue }; + +/** + * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes. + */ +export const queryKeyJsonReplacer = (_key: string, value: unknown) => { + if (value === undefined || typeof value === 'function' || typeof value === 'symbol') { + return undefined; + } + if (typeof value === 'bigint') { + return value.toString(); + } + if (value instanceof Date) { + return value.toISOString(); + } + return value; +}; + +/** + * Safely stringifies a value and parses it back into a JsonValue. + */ +export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => { + try { + const json = JSON.stringify(input, queryKeyJsonReplacer); + if (json === undefined) { + return undefined; + } + return JSON.parse(json) as JsonValue; + } catch { + return undefined; + } +}; + +/** + * Detects plain objects (including objects with a null prototype). + */ +const isPlainObject = (value: unknown): value is Record => { + if (value === null || typeof value !== 'object') { + return false; + } + const prototype = Object.getPrototypeOf(value as object); + return prototype === Object.prototype || prototype === null; +}; + +/** + * Turns URLSearchParams into a sorted JSON object for deterministic keys. + */ +const serializeSearchParams = (params: URLSearchParams): JsonValue => { + const entries = Array.from(params.entries()).sort(([a], [b]) => a.localeCompare(b)); + const result: Record = {}; + + for (const [key, value] of entries) { + const existing = result[key]; + if (existing === undefined) { + result[key] = value; + continue; + } + + if (Array.isArray(existing)) { + (existing as string[]).push(value); + } else { + result[key] = [existing, value]; + } + } + + return result; +}; + +/** + * Normalizes any accepted value into a JSON-friendly shape for query keys. + */ +export const serializeQueryKeyValue = (value: unknown): JsonValue | undefined => { + if (value === null) { + return null; + } + + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + return value; + } + + if (value === undefined || typeof value === 'function' || typeof value === 'symbol') { + return undefined; + } + + if (typeof value === 'bigint') { + return value.toString(); + } + + if (value instanceof Date) { + return value.toISOString(); + } + + if (Array.isArray(value)) { + return stringifyToJsonValue(value); + } + + if (typeof URLSearchParams !== 'undefined' && value instanceof URLSearchParams) { + return serializeSearchParams(value); + } + + if (isPlainObject(value)) { + return stringifyToJsonValue(value); + } + + return undefined; +}; diff --git a/cmd/gc/dashboard/web/src/generated/core/serverSentEvents.gen.ts b/cmd/gc/dashboard/web/src/generated/core/serverSentEvents.gen.ts new file mode 100644 index 0000000000..ddf3c4d13a --- /dev/null +++ b/cmd/gc/dashboard/web/src/generated/core/serverSentEvents.gen.ts @@ -0,0 +1,242 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen'; + +export type ServerSentEventsOptions = Omit & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult = { + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; +}; + +export function createSseClient({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult { + let lastEventId: string | undefined; + + const sleep = sseSleepFn ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + buffer = buffer.replace(/\r\n?/g, '\n'); // normalize line endings + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt(line.replace(/^retry:\s*/, ''), 10); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if (sseMaxRetryAttempts !== undefined && attempt >= sseMaxRetryAttempts) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 30000); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +} diff --git a/cmd/gc/dashboard/web/src/generated/core/types.gen.ts b/cmd/gc/dashboard/web/src/generated/core/types.gen.ts new file mode 100644 index 0000000000..9efe71d4c1 --- /dev/null +++ b/cmd/gc/dashboard/web/src/generated/core/types.gen.ts @@ -0,0 +1,104 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen'; +import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from './bodySerializer.gen'; + +export type HttpMethod = + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; + +export type Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, +> = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] ? { sse?: never } : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + string | number | boolean | (string | number | boolean)[] | null | undefined | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g., converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true ? never : K]: T[K]; +}; diff --git a/cmd/gc/dashboard/web/src/generated/core/utils.gen.ts b/cmd/gc/dashboard/web/src/generated/core/utils.gen.ts new file mode 100644 index 0000000000..9a4fec7830 --- /dev/null +++ b/cmd/gc/dashboard/web/src/generated/core/utils.gen.ts @@ -0,0 +1,140 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace(match, serializeArrayParam({ explode, name, style, value })); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}) { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e., client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/cmd/gc/dashboard/web/src/generated/index.ts b/cmd/gc/dashboard/web/src/generated/index.ts new file mode 100644 index 0000000000..87629493cb --- /dev/null +++ b/cmd/gc/dashboard/web/src/generated/index.ts @@ -0,0 +1,4 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export { createAgent, createBead, createConvoy, createProvider, createRig, createSession, deleteV0CityByCityNameAgentByBase, deleteV0CityByCityNameAgentByDirByBase, deleteV0CityByCityNameBeadById, deleteV0CityByCityNameConvoyById, deleteV0CityByCityNameExtmsgAdapters, deleteV0CityByCityNameExtmsgParticipants, deleteV0CityByCityNameMailById, deleteV0CityByCityNamePatchesAgentByBase, deleteV0CityByCityNamePatchesAgentByDirByBase, deleteV0CityByCityNamePatchesProviderByName, deleteV0CityByCityNamePatchesRigByName, deleteV0CityByCityNameProviderByName, deleteV0CityByCityNameRigByName, deleteV0CityByCityNameWorkflowByWorkflowId, emitEvent, ensureExtmsgGroup, getHealth, getV0Cities, getV0CityByCityName, getV0CityByCityNameAgentByBase, getV0CityByCityNameAgentByBaseOutput, getV0CityByCityNameAgentByDirByBase, getV0CityByCityNameAgentByDirByBaseOutput, getV0CityByCityNameAgents, getV0CityByCityNameBeadById, getV0CityByCityNameBeadByIdDeps, getV0CityByCityNameBeads, getV0CityByCityNameBeadsGraphByRootId, getV0CityByCityNameBeadsReady, getV0CityByCityNameConfig, getV0CityByCityNameConfigExplain, getV0CityByCityNameConfigValidate, getV0CityByCityNameConvoyById, getV0CityByCityNameConvoyByIdCheck, getV0CityByCityNameConvoys, getV0CityByCityNameEvents, getV0CityByCityNameExtmsgAdapters, getV0CityByCityNameExtmsgBindings, getV0CityByCityNameExtmsgGroups, getV0CityByCityNameExtmsgTranscript, getV0CityByCityNameFormulaByName, getV0CityByCityNameFormulas, getV0CityByCityNameFormulasByName, getV0CityByCityNameFormulasByNameRuns, getV0CityByCityNameFormulasFeed, getV0CityByCityNameHealth, getV0CityByCityNameMail, getV0CityByCityNameMailById, getV0CityByCityNameMailCount, getV0CityByCityNameMailThreadById, getV0CityByCityNameOrderByName, getV0CityByCityNameOrderHistoryByBeadId, getV0CityByCityNameOrders, getV0CityByCityNameOrdersCheck, getV0CityByCityNameOrdersFeed, getV0CityByCityNameOrdersHistory, getV0CityByCityNamePacks, getV0CityByCityNamePatchesAgentByBase, getV0CityByCityNamePatchesAgentByDirByBase, getV0CityByCityNamePatchesAgents, getV0CityByCityNamePatchesProviderByName, getV0CityByCityNamePatchesProviders, getV0CityByCityNamePatchesRigByName, getV0CityByCityNamePatchesRigs, getV0CityByCityNameProviderByName, getV0CityByCityNameProviderReadiness, getV0CityByCityNameProviders, getV0CityByCityNameProvidersPublic, getV0CityByCityNameReadiness, getV0CityByCityNameRigByName, getV0CityByCityNameRigs, getV0CityByCityNameServiceByName, getV0CityByCityNameServices, getV0CityByCityNameSessionById, getV0CityByCityNameSessionByIdAgents, getV0CityByCityNameSessionByIdAgentsByAgentId, getV0CityByCityNameSessionByIdPending, getV0CityByCityNameSessionByIdTranscript, getV0CityByCityNameSessions, getV0CityByCityNameStatus, getV0CityByCityNameWorkflowByWorkflowId, getV0Events, getV0ProviderReadiness, getV0Readiness, type Options, patchV0CityByCityName, patchV0CityByCityNameAgentByBase, patchV0CityByCityNameAgentByDirByBase, patchV0CityByCityNameBeadById, patchV0CityByCityNameProviderByName, patchV0CityByCityNameRigByName, patchV0CityByCityNameSessionById, postV0City, postV0CityByCityNameAgentByBaseByAction, postV0CityByCityNameAgentByDirByBaseByAction, postV0CityByCityNameBeadByIdAssign, postV0CityByCityNameBeadByIdClose, postV0CityByCityNameBeadByIdReopen, postV0CityByCityNameBeadByIdUpdate, postV0CityByCityNameConvoyByIdAdd, postV0CityByCityNameConvoyByIdClose, postV0CityByCityNameConvoyByIdRemove, postV0CityByCityNameExtmsgBind, postV0CityByCityNameExtmsgInbound, postV0CityByCityNameExtmsgOutbound, postV0CityByCityNameExtmsgParticipants, postV0CityByCityNameExtmsgTranscriptAck, postV0CityByCityNameExtmsgUnbind, postV0CityByCityNameFormulasByNamePreview, postV0CityByCityNameMailByIdArchive, postV0CityByCityNameMailByIdMarkUnread, postV0CityByCityNameMailByIdRead, postV0CityByCityNameOrderByNameDisable, postV0CityByCityNameOrderByNameEnable, postV0CityByCityNameRigByNameByAction, postV0CityByCityNameServiceByNameRestart, postV0CityByCityNameSessionByIdClose, postV0CityByCityNameSessionByIdKill, postV0CityByCityNameSessionByIdRename, postV0CityByCityNameSessionByIdStop, postV0CityByCityNameSessionByIdSuspend, postV0CityByCityNameSessionByIdWake, postV0CityByCityNameSling, putV0CityByCityNamePatchesAgents, putV0CityByCityNamePatchesProviders, putV0CityByCityNamePatchesRigs, registerExtmsgAdapter, replyMail, respondSession, sendMail, sendSessionMessage, streamAgentOutput, streamAgentOutputQualified, streamEvents, streamSession, streamSupervisorEvents, submitSession } from './sdk.gen'; +export type { AdapterCapabilities, AdapterEventPayload, AgentCreatedOutputBody, AgentCreateInputBody, AgentMapping, AgentOutputResponse, AgentPatch, AgentPatchSetInputBody, AgentResponse, AgentUpdateInputBody, AgentUpdateQualifiedInputBody, AnnotatedAgentResponse, AnnotatedProviderResponse, Bead, BeadAssignInputBody, BeadCreateInputBody, BeadDepsResponse, BeadEventPayload, BeadGraphResponse, BeadUpdateBody, BindingStatus, BoundEventPayload, CityCreateRequest, CityCreateResponse, CityGetResponse, CityInfo, CityPatchInputBody, ClientOptions, ConfigAgentResponse, ConfigExplainPatches, ConfigExplainResponse, ConfigPatchesResponse, ConfigResponse, ConfigRigResponse, ConfigValidateOutputBody, ConversationGroupParticipant, ConversationGroupRecord, ConversationKind, ConversationRef, ConversationTranscriptRecord, ConvoyAddInputBody, ConvoyCheckResponse, ConvoyCreateInputBody, ConvoyGetResponse, ConvoyProgress, ConvoyRemoveInputBody, CreateAgentData, CreateAgentError, CreateAgentErrors, CreateAgentResponse, CreateAgentResponses, CreateBeadData, CreateBeadError, CreateBeadErrors, CreateBeadResponse, CreateBeadResponses, CreateConvoyData, CreateConvoyError, CreateConvoyErrors, CreateConvoyResponse, CreateConvoyResponses, CreateProviderData, CreateProviderError, CreateProviderErrors, CreateProviderResponse, CreateProviderResponses, CreateRigData, CreateRigError, CreateRigErrors, CreateRigResponse, CreateRigResponses, CreateSessionData, CreateSessionError, CreateSessionErrors, CreateSessionResponse, CreateSessionResponses, DeleteV0CityByCityNameAgentByBaseData, DeleteV0CityByCityNameAgentByBaseError, DeleteV0CityByCityNameAgentByBaseErrors, DeleteV0CityByCityNameAgentByBaseResponse, DeleteV0CityByCityNameAgentByBaseResponses, DeleteV0CityByCityNameAgentByDirByBaseData, DeleteV0CityByCityNameAgentByDirByBaseError, DeleteV0CityByCityNameAgentByDirByBaseErrors, DeleteV0CityByCityNameAgentByDirByBaseResponse, DeleteV0CityByCityNameAgentByDirByBaseResponses, DeleteV0CityByCityNameBeadByIdData, DeleteV0CityByCityNameBeadByIdError, DeleteV0CityByCityNameBeadByIdErrors, DeleteV0CityByCityNameBeadByIdResponse, DeleteV0CityByCityNameBeadByIdResponses, DeleteV0CityByCityNameConvoyByIdData, DeleteV0CityByCityNameConvoyByIdError, DeleteV0CityByCityNameConvoyByIdErrors, DeleteV0CityByCityNameConvoyByIdResponse, DeleteV0CityByCityNameConvoyByIdResponses, DeleteV0CityByCityNameExtmsgAdaptersData, DeleteV0CityByCityNameExtmsgAdaptersError, DeleteV0CityByCityNameExtmsgAdaptersErrors, DeleteV0CityByCityNameExtmsgAdaptersResponse, DeleteV0CityByCityNameExtmsgAdaptersResponses, DeleteV0CityByCityNameExtmsgParticipantsData, DeleteV0CityByCityNameExtmsgParticipantsError, DeleteV0CityByCityNameExtmsgParticipantsErrors, DeleteV0CityByCityNameExtmsgParticipantsResponse, DeleteV0CityByCityNameExtmsgParticipantsResponses, DeleteV0CityByCityNameMailByIdData, DeleteV0CityByCityNameMailByIdError, DeleteV0CityByCityNameMailByIdErrors, DeleteV0CityByCityNameMailByIdResponse, DeleteV0CityByCityNameMailByIdResponses, DeleteV0CityByCityNamePatchesAgentByBaseData, DeleteV0CityByCityNamePatchesAgentByBaseError, DeleteV0CityByCityNamePatchesAgentByBaseErrors, DeleteV0CityByCityNamePatchesAgentByBaseResponse, DeleteV0CityByCityNamePatchesAgentByBaseResponses, DeleteV0CityByCityNamePatchesAgentByDirByBaseData, DeleteV0CityByCityNamePatchesAgentByDirByBaseError, DeleteV0CityByCityNamePatchesAgentByDirByBaseErrors, DeleteV0CityByCityNamePatchesAgentByDirByBaseResponse, DeleteV0CityByCityNamePatchesAgentByDirByBaseResponses, DeleteV0CityByCityNamePatchesProviderByNameData, DeleteV0CityByCityNamePatchesProviderByNameError, DeleteV0CityByCityNamePatchesProviderByNameErrors, DeleteV0CityByCityNamePatchesProviderByNameResponse, DeleteV0CityByCityNamePatchesProviderByNameResponses, DeleteV0CityByCityNamePatchesRigByNameData, DeleteV0CityByCityNamePatchesRigByNameError, DeleteV0CityByCityNamePatchesRigByNameErrors, DeleteV0CityByCityNamePatchesRigByNameResponse, DeleteV0CityByCityNamePatchesRigByNameResponses, DeleteV0CityByCityNameProviderByNameData, DeleteV0CityByCityNameProviderByNameError, DeleteV0CityByCityNameProviderByNameErrors, DeleteV0CityByCityNameProviderByNameResponse, DeleteV0CityByCityNameProviderByNameResponses, DeleteV0CityByCityNameRigByNameData, DeleteV0CityByCityNameRigByNameError, DeleteV0CityByCityNameRigByNameErrors, DeleteV0CityByCityNameRigByNameResponse, DeleteV0CityByCityNameRigByNameResponses, DeleteV0CityByCityNameWorkflowByWorkflowIdData, DeleteV0CityByCityNameWorkflowByWorkflowIdError, DeleteV0CityByCityNameWorkflowByWorkflowIdErrors, DeleteV0CityByCityNameWorkflowByWorkflowIdResponse, DeleteV0CityByCityNameWorkflowByWorkflowIdResponses, DeliveryContextRecord, Dep, EmitEventData, EmitEventError, EmitEventErrors, EmitEventResponse, EmitEventResponses, EnsureExtmsgGroupData, EnsureExtmsgGroupError, EnsureExtmsgGroupErrors, EnsureExtmsgGroupResponse, EnsureExtmsgGroupResponses, ErrorDetail, ErrorModel, EventEmitOutputBody, EventEmitRequest, EventPayload, EventStreamEnvelope, ExternalActor, ExternalAttachment, ExternalInboundMessage, ExtmsgAdapterInfo, ExtMsgAdapterRegisterInputBody, ExtMsgAdapterRegisterOutputBody, ExtMsgAdapterUnregisterInputBody, ExtMsgBindInputBody, ExtMsgGroupEnsureInputBody, ExtMsgInboundInputBody, ExtMsgOutboundInputBody, ExtMsgParticipantRemoveInputBody, ExtMsgParticipantUpsertInputBody, ExtMsgTranscriptAckInputBody, ExtMsgUnbindBody, ExtMsgUnbindInputBody, FanoutPolicy, FormulaDetailResponse, FormulaFeedBody, FormulaListBody, FormulaPreviewBody, FormulaPreviewEdgeResponse, FormulaPreviewNodeResponse, FormulaPreviewResponse, FormulaRecentRunResponse, FormulaRunsResponse, FormulaStepResponse, FormulaSummaryResponse, FormulaVarDefResponse, GetHealthData, GetHealthError, GetHealthErrors, GetHealthResponse, GetHealthResponses, GetV0CitiesData, GetV0CitiesError, GetV0CitiesErrors, GetV0CitiesResponse, GetV0CitiesResponses, GetV0CityByCityNameAgentByBaseData, GetV0CityByCityNameAgentByBaseError, GetV0CityByCityNameAgentByBaseErrors, GetV0CityByCityNameAgentByBaseOutputData, GetV0CityByCityNameAgentByBaseOutputError, GetV0CityByCityNameAgentByBaseOutputErrors, GetV0CityByCityNameAgentByBaseOutputResponse, GetV0CityByCityNameAgentByBaseOutputResponses, GetV0CityByCityNameAgentByBaseResponse, GetV0CityByCityNameAgentByBaseResponses, GetV0CityByCityNameAgentByDirByBaseData, GetV0CityByCityNameAgentByDirByBaseError, GetV0CityByCityNameAgentByDirByBaseErrors, GetV0CityByCityNameAgentByDirByBaseOutputData, GetV0CityByCityNameAgentByDirByBaseOutputError, GetV0CityByCityNameAgentByDirByBaseOutputErrors, GetV0CityByCityNameAgentByDirByBaseOutputResponse, GetV0CityByCityNameAgentByDirByBaseOutputResponses, GetV0CityByCityNameAgentByDirByBaseResponse, GetV0CityByCityNameAgentByDirByBaseResponses, GetV0CityByCityNameAgentsData, GetV0CityByCityNameAgentsError, GetV0CityByCityNameAgentsErrors, GetV0CityByCityNameAgentsResponse, GetV0CityByCityNameAgentsResponses, GetV0CityByCityNameBeadByIdData, GetV0CityByCityNameBeadByIdDepsData, GetV0CityByCityNameBeadByIdDepsError, GetV0CityByCityNameBeadByIdDepsErrors, GetV0CityByCityNameBeadByIdDepsResponse, GetV0CityByCityNameBeadByIdDepsResponses, GetV0CityByCityNameBeadByIdError, GetV0CityByCityNameBeadByIdErrors, GetV0CityByCityNameBeadByIdResponse, GetV0CityByCityNameBeadByIdResponses, GetV0CityByCityNameBeadsData, GetV0CityByCityNameBeadsError, GetV0CityByCityNameBeadsErrors, GetV0CityByCityNameBeadsGraphByRootIdData, GetV0CityByCityNameBeadsGraphByRootIdError, GetV0CityByCityNameBeadsGraphByRootIdErrors, GetV0CityByCityNameBeadsGraphByRootIdResponse, GetV0CityByCityNameBeadsGraphByRootIdResponses, GetV0CityByCityNameBeadsReadyData, GetV0CityByCityNameBeadsReadyError, GetV0CityByCityNameBeadsReadyErrors, GetV0CityByCityNameBeadsReadyResponse, GetV0CityByCityNameBeadsReadyResponses, GetV0CityByCityNameBeadsResponse, GetV0CityByCityNameBeadsResponses, GetV0CityByCityNameConfigData, GetV0CityByCityNameConfigError, GetV0CityByCityNameConfigErrors, GetV0CityByCityNameConfigExplainData, GetV0CityByCityNameConfigExplainError, GetV0CityByCityNameConfigExplainErrors, GetV0CityByCityNameConfigExplainResponse, GetV0CityByCityNameConfigExplainResponses, GetV0CityByCityNameConfigResponse, GetV0CityByCityNameConfigResponses, GetV0CityByCityNameConfigValidateData, GetV0CityByCityNameConfigValidateError, GetV0CityByCityNameConfigValidateErrors, GetV0CityByCityNameConfigValidateResponse, GetV0CityByCityNameConfigValidateResponses, GetV0CityByCityNameConvoyByIdCheckData, GetV0CityByCityNameConvoyByIdCheckError, GetV0CityByCityNameConvoyByIdCheckErrors, GetV0CityByCityNameConvoyByIdCheckResponse, GetV0CityByCityNameConvoyByIdCheckResponses, GetV0CityByCityNameConvoyByIdData, GetV0CityByCityNameConvoyByIdError, GetV0CityByCityNameConvoyByIdErrors, GetV0CityByCityNameConvoyByIdResponse, GetV0CityByCityNameConvoyByIdResponses, GetV0CityByCityNameConvoysData, GetV0CityByCityNameConvoysError, GetV0CityByCityNameConvoysErrors, GetV0CityByCityNameConvoysResponse, GetV0CityByCityNameConvoysResponses, GetV0CityByCityNameData, GetV0CityByCityNameError, GetV0CityByCityNameErrors, GetV0CityByCityNameEventsData, GetV0CityByCityNameEventsError, GetV0CityByCityNameEventsErrors, GetV0CityByCityNameEventsResponse, GetV0CityByCityNameEventsResponses, GetV0CityByCityNameExtmsgAdaptersData, GetV0CityByCityNameExtmsgAdaptersError, GetV0CityByCityNameExtmsgAdaptersErrors, GetV0CityByCityNameExtmsgAdaptersResponse, GetV0CityByCityNameExtmsgAdaptersResponses, GetV0CityByCityNameExtmsgBindingsData, GetV0CityByCityNameExtmsgBindingsError, GetV0CityByCityNameExtmsgBindingsErrors, GetV0CityByCityNameExtmsgBindingsResponse, GetV0CityByCityNameExtmsgBindingsResponses, GetV0CityByCityNameExtmsgGroupsData, GetV0CityByCityNameExtmsgGroupsError, GetV0CityByCityNameExtmsgGroupsErrors, GetV0CityByCityNameExtmsgGroupsResponse, GetV0CityByCityNameExtmsgGroupsResponses, GetV0CityByCityNameExtmsgTranscriptData, GetV0CityByCityNameExtmsgTranscriptError, GetV0CityByCityNameExtmsgTranscriptErrors, GetV0CityByCityNameExtmsgTranscriptResponse, GetV0CityByCityNameExtmsgTranscriptResponses, GetV0CityByCityNameFormulaByNameData, GetV0CityByCityNameFormulaByNameError, GetV0CityByCityNameFormulaByNameErrors, GetV0CityByCityNameFormulaByNameResponse, GetV0CityByCityNameFormulaByNameResponses, GetV0CityByCityNameFormulasByNameData, GetV0CityByCityNameFormulasByNameError, GetV0CityByCityNameFormulasByNameErrors, GetV0CityByCityNameFormulasByNameResponse, GetV0CityByCityNameFormulasByNameResponses, GetV0CityByCityNameFormulasByNameRunsData, GetV0CityByCityNameFormulasByNameRunsError, GetV0CityByCityNameFormulasByNameRunsErrors, GetV0CityByCityNameFormulasByNameRunsResponse, GetV0CityByCityNameFormulasByNameRunsResponses, GetV0CityByCityNameFormulasData, GetV0CityByCityNameFormulasError, GetV0CityByCityNameFormulasErrors, GetV0CityByCityNameFormulasFeedData, GetV0CityByCityNameFormulasFeedError, GetV0CityByCityNameFormulasFeedErrors, GetV0CityByCityNameFormulasFeedResponse, GetV0CityByCityNameFormulasFeedResponses, GetV0CityByCityNameFormulasResponse, GetV0CityByCityNameFormulasResponses, GetV0CityByCityNameHealthData, GetV0CityByCityNameHealthError, GetV0CityByCityNameHealthErrors, GetV0CityByCityNameHealthResponse, GetV0CityByCityNameHealthResponses, GetV0CityByCityNameMailByIdData, GetV0CityByCityNameMailByIdError, GetV0CityByCityNameMailByIdErrors, GetV0CityByCityNameMailByIdResponse, GetV0CityByCityNameMailByIdResponses, GetV0CityByCityNameMailCountData, GetV0CityByCityNameMailCountError, GetV0CityByCityNameMailCountErrors, GetV0CityByCityNameMailCountResponse, GetV0CityByCityNameMailCountResponses, GetV0CityByCityNameMailData, GetV0CityByCityNameMailError, GetV0CityByCityNameMailErrors, GetV0CityByCityNameMailResponse, GetV0CityByCityNameMailResponses, GetV0CityByCityNameMailThreadByIdData, GetV0CityByCityNameMailThreadByIdError, GetV0CityByCityNameMailThreadByIdErrors, GetV0CityByCityNameMailThreadByIdResponse, GetV0CityByCityNameMailThreadByIdResponses, GetV0CityByCityNameOrderByNameData, GetV0CityByCityNameOrderByNameError, GetV0CityByCityNameOrderByNameErrors, GetV0CityByCityNameOrderByNameResponse, GetV0CityByCityNameOrderByNameResponses, GetV0CityByCityNameOrderHistoryByBeadIdData, GetV0CityByCityNameOrderHistoryByBeadIdError, GetV0CityByCityNameOrderHistoryByBeadIdErrors, GetV0CityByCityNameOrderHistoryByBeadIdResponse, GetV0CityByCityNameOrderHistoryByBeadIdResponses, GetV0CityByCityNameOrdersCheckData, GetV0CityByCityNameOrdersCheckError, GetV0CityByCityNameOrdersCheckErrors, GetV0CityByCityNameOrdersCheckResponse, GetV0CityByCityNameOrdersCheckResponses, GetV0CityByCityNameOrdersData, GetV0CityByCityNameOrdersError, GetV0CityByCityNameOrdersErrors, GetV0CityByCityNameOrdersFeedData, GetV0CityByCityNameOrdersFeedError, GetV0CityByCityNameOrdersFeedErrors, GetV0CityByCityNameOrdersFeedResponse, GetV0CityByCityNameOrdersFeedResponses, GetV0CityByCityNameOrdersHistoryData, GetV0CityByCityNameOrdersHistoryError, GetV0CityByCityNameOrdersHistoryErrors, GetV0CityByCityNameOrdersHistoryResponse, GetV0CityByCityNameOrdersHistoryResponses, GetV0CityByCityNameOrdersResponse, GetV0CityByCityNameOrdersResponses, GetV0CityByCityNamePacksData, GetV0CityByCityNamePacksError, GetV0CityByCityNamePacksErrors, GetV0CityByCityNamePacksResponse, GetV0CityByCityNamePacksResponses, GetV0CityByCityNamePatchesAgentByBaseData, GetV0CityByCityNamePatchesAgentByBaseError, GetV0CityByCityNamePatchesAgentByBaseErrors, GetV0CityByCityNamePatchesAgentByBaseResponse, GetV0CityByCityNamePatchesAgentByBaseResponses, GetV0CityByCityNamePatchesAgentByDirByBaseData, GetV0CityByCityNamePatchesAgentByDirByBaseError, GetV0CityByCityNamePatchesAgentByDirByBaseErrors, GetV0CityByCityNamePatchesAgentByDirByBaseResponse, GetV0CityByCityNamePatchesAgentByDirByBaseResponses, GetV0CityByCityNamePatchesAgentsData, GetV0CityByCityNamePatchesAgentsError, GetV0CityByCityNamePatchesAgentsErrors, GetV0CityByCityNamePatchesAgentsResponse, GetV0CityByCityNamePatchesAgentsResponses, GetV0CityByCityNamePatchesProviderByNameData, GetV0CityByCityNamePatchesProviderByNameError, GetV0CityByCityNamePatchesProviderByNameErrors, GetV0CityByCityNamePatchesProviderByNameResponse, GetV0CityByCityNamePatchesProviderByNameResponses, GetV0CityByCityNamePatchesProvidersData, GetV0CityByCityNamePatchesProvidersError, GetV0CityByCityNamePatchesProvidersErrors, GetV0CityByCityNamePatchesProvidersResponse, GetV0CityByCityNamePatchesProvidersResponses, GetV0CityByCityNamePatchesRigByNameData, GetV0CityByCityNamePatchesRigByNameError, GetV0CityByCityNamePatchesRigByNameErrors, GetV0CityByCityNamePatchesRigByNameResponse, GetV0CityByCityNamePatchesRigByNameResponses, GetV0CityByCityNamePatchesRigsData, GetV0CityByCityNamePatchesRigsError, GetV0CityByCityNamePatchesRigsErrors, GetV0CityByCityNamePatchesRigsResponse, GetV0CityByCityNamePatchesRigsResponses, GetV0CityByCityNameProviderByNameData, GetV0CityByCityNameProviderByNameError, GetV0CityByCityNameProviderByNameErrors, GetV0CityByCityNameProviderByNameResponse, GetV0CityByCityNameProviderByNameResponses, GetV0CityByCityNameProviderReadinessData, GetV0CityByCityNameProviderReadinessError, GetV0CityByCityNameProviderReadinessErrors, GetV0CityByCityNameProviderReadinessResponse, GetV0CityByCityNameProviderReadinessResponses, GetV0CityByCityNameProvidersData, GetV0CityByCityNameProvidersError, GetV0CityByCityNameProvidersErrors, GetV0CityByCityNameProvidersPublicData, GetV0CityByCityNameProvidersPublicError, GetV0CityByCityNameProvidersPublicErrors, GetV0CityByCityNameProvidersPublicResponse, GetV0CityByCityNameProvidersPublicResponses, GetV0CityByCityNameProvidersResponse, GetV0CityByCityNameProvidersResponses, GetV0CityByCityNameReadinessData, GetV0CityByCityNameReadinessError, GetV0CityByCityNameReadinessErrors, GetV0CityByCityNameReadinessResponse, GetV0CityByCityNameReadinessResponses, GetV0CityByCityNameResponse, GetV0CityByCityNameResponses, GetV0CityByCityNameRigByNameData, GetV0CityByCityNameRigByNameError, GetV0CityByCityNameRigByNameErrors, GetV0CityByCityNameRigByNameResponse, GetV0CityByCityNameRigByNameResponses, GetV0CityByCityNameRigsData, GetV0CityByCityNameRigsError, GetV0CityByCityNameRigsErrors, GetV0CityByCityNameRigsResponse, GetV0CityByCityNameRigsResponses, GetV0CityByCityNameServiceByNameData, GetV0CityByCityNameServiceByNameError, GetV0CityByCityNameServiceByNameErrors, GetV0CityByCityNameServiceByNameResponse, GetV0CityByCityNameServiceByNameResponses, GetV0CityByCityNameServicesData, GetV0CityByCityNameServicesError, GetV0CityByCityNameServicesErrors, GetV0CityByCityNameServicesResponse, GetV0CityByCityNameServicesResponses, GetV0CityByCityNameSessionByIdAgentsByAgentIdData, GetV0CityByCityNameSessionByIdAgentsByAgentIdError, GetV0CityByCityNameSessionByIdAgentsByAgentIdErrors, GetV0CityByCityNameSessionByIdAgentsByAgentIdResponse, GetV0CityByCityNameSessionByIdAgentsByAgentIdResponses, GetV0CityByCityNameSessionByIdAgentsData, GetV0CityByCityNameSessionByIdAgentsError, GetV0CityByCityNameSessionByIdAgentsErrors, GetV0CityByCityNameSessionByIdAgentsResponse, GetV0CityByCityNameSessionByIdAgentsResponses, GetV0CityByCityNameSessionByIdData, GetV0CityByCityNameSessionByIdError, GetV0CityByCityNameSessionByIdErrors, GetV0CityByCityNameSessionByIdPendingData, GetV0CityByCityNameSessionByIdPendingError, GetV0CityByCityNameSessionByIdPendingErrors, GetV0CityByCityNameSessionByIdPendingResponse, GetV0CityByCityNameSessionByIdPendingResponses, GetV0CityByCityNameSessionByIdResponse, GetV0CityByCityNameSessionByIdResponses, GetV0CityByCityNameSessionByIdTranscriptData, GetV0CityByCityNameSessionByIdTranscriptError, GetV0CityByCityNameSessionByIdTranscriptErrors, GetV0CityByCityNameSessionByIdTranscriptResponse, GetV0CityByCityNameSessionByIdTranscriptResponses, GetV0CityByCityNameSessionsData, GetV0CityByCityNameSessionsError, GetV0CityByCityNameSessionsErrors, GetV0CityByCityNameSessionsResponse, GetV0CityByCityNameSessionsResponses, GetV0CityByCityNameStatusData, GetV0CityByCityNameStatusError, GetV0CityByCityNameStatusErrors, GetV0CityByCityNameStatusResponse, GetV0CityByCityNameStatusResponses, GetV0CityByCityNameWorkflowByWorkflowIdData, GetV0CityByCityNameWorkflowByWorkflowIdError, GetV0CityByCityNameWorkflowByWorkflowIdErrors, GetV0CityByCityNameWorkflowByWorkflowIdResponse, GetV0CityByCityNameWorkflowByWorkflowIdResponses, GetV0EventsData, GetV0EventsError, GetV0EventsErrors, GetV0EventsResponse, GetV0EventsResponses, GetV0ProviderReadinessData, GetV0ProviderReadinessError, GetV0ProviderReadinessErrors, GetV0ProviderReadinessResponse, GetV0ProviderReadinessResponses, GetV0ReadinessData, GetV0ReadinessError, GetV0ReadinessErrors, GetV0ReadinessResponse, GetV0ReadinessResponses, GitStatus, GroupCreatedEventPayload, GroupRouteDecision, HealthOutputBody, HeartbeatEvent, InboundEventPayload, InboundResult, ListBodyAgentPatch, ListBodyAgentResponse, ListBodyBead, ListBodyConversationTranscriptRecord, ListBodyExtmsgAdapterInfo, ListBodyProviderPatch, ListBodyProviderResponse, ListBodyRigPatch, ListBodyRigResponse, ListBodySessionBindingRecord, ListBodySessionResponse, ListBodyStatus, ListBodyWireEvent, LogicalNode, MailCountOutputBody, MailEventPayload, MailListBody, MailReplyInputBody, MailSendInputBody, Message, MonitorFeedItemResponse, NoPayload, OkResponseBody, OkWithIdResponseBody, OptionChoiceDto, OrderCheckListBody, OrderCheckResponse, OrderHistoryDetailResponse, OrderHistoryEntry, OrderHistoryListBody, OrderListBody, OrderResponse, OrdersFeedBody, OutboundEventPayload, OutboundResult, OutputTurn, PackListBody, PackResponse, PaginationInfo, PatchDeletedResponseBody, PatchOkResponseBody, PatchV0CityByCityNameAgentByBaseData, PatchV0CityByCityNameAgentByBaseError, PatchV0CityByCityNameAgentByBaseErrors, PatchV0CityByCityNameAgentByBaseResponse, PatchV0CityByCityNameAgentByBaseResponses, PatchV0CityByCityNameAgentByDirByBaseData, PatchV0CityByCityNameAgentByDirByBaseError, PatchV0CityByCityNameAgentByDirByBaseErrors, PatchV0CityByCityNameAgentByDirByBaseResponse, PatchV0CityByCityNameAgentByDirByBaseResponses, PatchV0CityByCityNameBeadByIdData, PatchV0CityByCityNameBeadByIdError, PatchV0CityByCityNameBeadByIdErrors, PatchV0CityByCityNameBeadByIdResponse, PatchV0CityByCityNameBeadByIdResponses, PatchV0CityByCityNameData, PatchV0CityByCityNameError, PatchV0CityByCityNameErrors, PatchV0CityByCityNameProviderByNameData, PatchV0CityByCityNameProviderByNameError, PatchV0CityByCityNameProviderByNameErrors, PatchV0CityByCityNameProviderByNameResponse, PatchV0CityByCityNameProviderByNameResponses, PatchV0CityByCityNameResponse, PatchV0CityByCityNameResponses, PatchV0CityByCityNameRigByNameData, PatchV0CityByCityNameRigByNameError, PatchV0CityByCityNameRigByNameErrors, PatchV0CityByCityNameRigByNameResponse, PatchV0CityByCityNameRigByNameResponses, PatchV0CityByCityNameSessionByIdData, PatchV0CityByCityNameSessionByIdError, PatchV0CityByCityNameSessionByIdErrors, PatchV0CityByCityNameSessionByIdResponse, PatchV0CityByCityNameSessionByIdResponses, PendingInteraction, PoolOverride, PostV0CityByCityNameAgentByBaseByActionData, PostV0CityByCityNameAgentByBaseByActionError, PostV0CityByCityNameAgentByBaseByActionErrors, PostV0CityByCityNameAgentByBaseByActionResponse, PostV0CityByCityNameAgentByBaseByActionResponses, PostV0CityByCityNameAgentByDirByBaseByActionData, PostV0CityByCityNameAgentByDirByBaseByActionError, PostV0CityByCityNameAgentByDirByBaseByActionErrors, PostV0CityByCityNameAgentByDirByBaseByActionResponse, PostV0CityByCityNameAgentByDirByBaseByActionResponses, PostV0CityByCityNameBeadByIdAssignData, PostV0CityByCityNameBeadByIdAssignError, PostV0CityByCityNameBeadByIdAssignErrors, PostV0CityByCityNameBeadByIdAssignResponse, PostV0CityByCityNameBeadByIdAssignResponses, PostV0CityByCityNameBeadByIdCloseData, PostV0CityByCityNameBeadByIdCloseError, PostV0CityByCityNameBeadByIdCloseErrors, PostV0CityByCityNameBeadByIdCloseResponse, PostV0CityByCityNameBeadByIdCloseResponses, PostV0CityByCityNameBeadByIdReopenData, PostV0CityByCityNameBeadByIdReopenError, PostV0CityByCityNameBeadByIdReopenErrors, PostV0CityByCityNameBeadByIdReopenResponse, PostV0CityByCityNameBeadByIdReopenResponses, PostV0CityByCityNameBeadByIdUpdateData, PostV0CityByCityNameBeadByIdUpdateError, PostV0CityByCityNameBeadByIdUpdateErrors, PostV0CityByCityNameBeadByIdUpdateResponse, PostV0CityByCityNameBeadByIdUpdateResponses, PostV0CityByCityNameConvoyByIdAddData, PostV0CityByCityNameConvoyByIdAddError, PostV0CityByCityNameConvoyByIdAddErrors, PostV0CityByCityNameConvoyByIdAddResponse, PostV0CityByCityNameConvoyByIdAddResponses, PostV0CityByCityNameConvoyByIdCloseData, PostV0CityByCityNameConvoyByIdCloseError, PostV0CityByCityNameConvoyByIdCloseErrors, PostV0CityByCityNameConvoyByIdCloseResponse, PostV0CityByCityNameConvoyByIdCloseResponses, PostV0CityByCityNameConvoyByIdRemoveData, PostV0CityByCityNameConvoyByIdRemoveError, PostV0CityByCityNameConvoyByIdRemoveErrors, PostV0CityByCityNameConvoyByIdRemoveResponse, PostV0CityByCityNameConvoyByIdRemoveResponses, PostV0CityByCityNameExtmsgBindData, PostV0CityByCityNameExtmsgBindError, PostV0CityByCityNameExtmsgBindErrors, PostV0CityByCityNameExtmsgBindResponse, PostV0CityByCityNameExtmsgBindResponses, PostV0CityByCityNameExtmsgInboundData, PostV0CityByCityNameExtmsgInboundError, PostV0CityByCityNameExtmsgInboundErrors, PostV0CityByCityNameExtmsgInboundResponse, PostV0CityByCityNameExtmsgInboundResponses, PostV0CityByCityNameExtmsgOutboundData, PostV0CityByCityNameExtmsgOutboundError, PostV0CityByCityNameExtmsgOutboundErrors, PostV0CityByCityNameExtmsgOutboundResponse, PostV0CityByCityNameExtmsgOutboundResponses, PostV0CityByCityNameExtmsgParticipantsData, PostV0CityByCityNameExtmsgParticipantsError, PostV0CityByCityNameExtmsgParticipantsErrors, PostV0CityByCityNameExtmsgParticipantsResponse, PostV0CityByCityNameExtmsgParticipantsResponses, PostV0CityByCityNameExtmsgTranscriptAckData, PostV0CityByCityNameExtmsgTranscriptAckError, PostV0CityByCityNameExtmsgTranscriptAckErrors, PostV0CityByCityNameExtmsgTranscriptAckResponse, PostV0CityByCityNameExtmsgTranscriptAckResponses, PostV0CityByCityNameExtmsgUnbindData, PostV0CityByCityNameExtmsgUnbindError, PostV0CityByCityNameExtmsgUnbindErrors, PostV0CityByCityNameExtmsgUnbindResponse, PostV0CityByCityNameExtmsgUnbindResponses, PostV0CityByCityNameFormulasByNamePreviewData, PostV0CityByCityNameFormulasByNamePreviewError, PostV0CityByCityNameFormulasByNamePreviewErrors, PostV0CityByCityNameFormulasByNamePreviewResponse, PostV0CityByCityNameFormulasByNamePreviewResponses, PostV0CityByCityNameMailByIdArchiveData, PostV0CityByCityNameMailByIdArchiveError, PostV0CityByCityNameMailByIdArchiveErrors, PostV0CityByCityNameMailByIdArchiveResponse, PostV0CityByCityNameMailByIdArchiveResponses, PostV0CityByCityNameMailByIdMarkUnreadData, PostV0CityByCityNameMailByIdMarkUnreadError, PostV0CityByCityNameMailByIdMarkUnreadErrors, PostV0CityByCityNameMailByIdMarkUnreadResponse, PostV0CityByCityNameMailByIdMarkUnreadResponses, PostV0CityByCityNameMailByIdReadData, PostV0CityByCityNameMailByIdReadError, PostV0CityByCityNameMailByIdReadErrors, PostV0CityByCityNameMailByIdReadResponse, PostV0CityByCityNameMailByIdReadResponses, PostV0CityByCityNameOrderByNameDisableData, PostV0CityByCityNameOrderByNameDisableError, PostV0CityByCityNameOrderByNameDisableErrors, PostV0CityByCityNameOrderByNameDisableResponse, PostV0CityByCityNameOrderByNameDisableResponses, PostV0CityByCityNameOrderByNameEnableData, PostV0CityByCityNameOrderByNameEnableError, PostV0CityByCityNameOrderByNameEnableErrors, PostV0CityByCityNameOrderByNameEnableResponse, PostV0CityByCityNameOrderByNameEnableResponses, PostV0CityByCityNameRigByNameByActionData, PostV0CityByCityNameRigByNameByActionError, PostV0CityByCityNameRigByNameByActionErrors, PostV0CityByCityNameRigByNameByActionResponse, PostV0CityByCityNameRigByNameByActionResponses, PostV0CityByCityNameServiceByNameRestartData, PostV0CityByCityNameServiceByNameRestartError, PostV0CityByCityNameServiceByNameRestartErrors, PostV0CityByCityNameServiceByNameRestartResponse, PostV0CityByCityNameServiceByNameRestartResponses, PostV0CityByCityNameSessionByIdCloseData, PostV0CityByCityNameSessionByIdCloseError, PostV0CityByCityNameSessionByIdCloseErrors, PostV0CityByCityNameSessionByIdCloseResponse, PostV0CityByCityNameSessionByIdCloseResponses, PostV0CityByCityNameSessionByIdKillData, PostV0CityByCityNameSessionByIdKillError, PostV0CityByCityNameSessionByIdKillErrors, PostV0CityByCityNameSessionByIdKillResponse, PostV0CityByCityNameSessionByIdKillResponses, PostV0CityByCityNameSessionByIdRenameData, PostV0CityByCityNameSessionByIdRenameError, PostV0CityByCityNameSessionByIdRenameErrors, PostV0CityByCityNameSessionByIdRenameResponse, PostV0CityByCityNameSessionByIdRenameResponses, PostV0CityByCityNameSessionByIdStopData, PostV0CityByCityNameSessionByIdStopError, PostV0CityByCityNameSessionByIdStopErrors, PostV0CityByCityNameSessionByIdStopResponse, PostV0CityByCityNameSessionByIdStopResponses, PostV0CityByCityNameSessionByIdSuspendData, PostV0CityByCityNameSessionByIdSuspendError, PostV0CityByCityNameSessionByIdSuspendErrors, PostV0CityByCityNameSessionByIdSuspendResponse, PostV0CityByCityNameSessionByIdSuspendResponses, PostV0CityByCityNameSessionByIdWakeData, PostV0CityByCityNameSessionByIdWakeError, PostV0CityByCityNameSessionByIdWakeErrors, PostV0CityByCityNameSessionByIdWakeResponse, PostV0CityByCityNameSessionByIdWakeResponses, PostV0CityByCityNameSlingData, PostV0CityByCityNameSlingError, PostV0CityByCityNameSlingErrors, PostV0CityByCityNameSlingResponse, PostV0CityByCityNameSlingResponses, PostV0CityData, PostV0CityError, PostV0CityErrors, PostV0CityResponse, PostV0CityResponses, ProviderCreatedOutputBody, ProviderCreateInputBody, ProviderOptionDto, ProviderPatch, ProviderPatchSetInputBody, ProviderPublicListBody, ProviderPublicResponse, ProviderReadiness, ProviderReadinessResponse, ProviderResponse, ProviderSpecJson, ProviderUpdateInputBody, PublishReceipt, PutV0CityByCityNamePatchesAgentsData, PutV0CityByCityNamePatchesAgentsError, PutV0CityByCityNamePatchesAgentsErrors, PutV0CityByCityNamePatchesAgentsResponse, PutV0CityByCityNamePatchesAgentsResponses, PutV0CityByCityNamePatchesProvidersData, PutV0CityByCityNamePatchesProvidersError, PutV0CityByCityNamePatchesProvidersErrors, PutV0CityByCityNamePatchesProvidersResponse, PutV0CityByCityNamePatchesProvidersResponses, PutV0CityByCityNamePatchesRigsData, PutV0CityByCityNamePatchesRigsError, PutV0CityByCityNamePatchesRigsErrors, PutV0CityByCityNamePatchesRigsResponse, PutV0CityByCityNamePatchesRigsResponses, ReadinessItem, ReadinessResponse, RegisterExtmsgAdapterData, RegisterExtmsgAdapterError, RegisterExtmsgAdapterErrors, RegisterExtmsgAdapterResponse, RegisterExtmsgAdapterResponses, ReplyMailData, ReplyMailError, ReplyMailErrors, ReplyMailResponse, ReplyMailResponses, RespondSessionData, RespondSessionError, RespondSessionErrors, RespondSessionResponse, RespondSessionResponses, RigActionBody, RigCreatedOutputBody, RigCreateInputBody, RigPatch, RigPatchSetInputBody, RigResponse, RigUpdateInputBody, ScopeGroup, SendMailData, SendMailError, SendMailErrors, SendMailResponse, SendMailResponses, SendSessionMessageData, SendSessionMessageError, SendSessionMessageErrors, SendSessionMessageResponse, SendSessionMessageResponses, ServiceRestartOutputBody, SessionActivityEvent, SessionAgentGetResponse, SessionAgentListResponse, SessionBindingRecord, SessionCreateBody, SessionInfo, SessionMessageInputBody, SessionMessageOutputBody, SessionPatchBody, SessionPendingResponse, SessionRawMessageFrame, SessionRenameInputBody, SessionRespondInputBody, SessionRespondOutputBody, SessionResponse, SessionStreamCommonEvent, SessionStreamMessageEvent, SessionStreamRawMessageEvent, SessionSubmitInputBody, SessionSubmitOutputBody, SessionTranscriptGetResponse, SlingInputBody, SlingResponse, Status, StatusAgentCounts, StatusBody, StatusMailCounts, StatusRigCounts, StatusWorkCounts, StreamAgentOutputData, StreamAgentOutputError, StreamAgentOutputErrors, StreamAgentOutputQualifiedData, StreamAgentOutputQualifiedError, StreamAgentOutputQualifiedErrors, StreamAgentOutputQualifiedResponse, StreamAgentOutputQualifiedResponses, StreamAgentOutputResponse, StreamAgentOutputResponses, StreamEventsData, StreamEventsError, StreamEventsErrors, StreamEventsResponse, StreamEventsResponses, StreamSessionData, StreamSessionError, StreamSessionErrors, StreamSessionResponse, StreamSessionResponses, StreamSupervisorEventsData, StreamSupervisorEventsError, StreamSupervisorEventsErrors, StreamSupervisorEventsResponse, StreamSupervisorEventsResponses, SubmissionCapabilities, SubmitIntent, SubmitSessionData, SubmitSessionError, SubmitSessionErrors, SubmitSessionResponse, SubmitSessionResponses, SupervisorCitiesOutputBody, SupervisorEventListOutputBody, SupervisorHealthOutputBody, SupervisorStartup, TaggedEventStreamEnvelope, TranscriptMessageKind, TranscriptProvenance, UnboundEventPayload, WireEvent, WireTaggedEvent, WorkerOperationEventPayload, WorkflowAttemptSummary, WorkflowBeadResponse, WorkflowDeleteResponse, WorkflowDepResponse, WorkflowEventProjection, WorkflowSnapshotResponse, WorkspaceResponse } from './types.gen'; diff --git a/cmd/gc/dashboard/web/src/generated/schema.d.ts b/cmd/gc/dashboard/web/src/generated/schema.d.ts new file mode 100644 index 0000000000..589fdb08ea --- /dev/null +++ b/cmd/gc/dashboard/web/src/generated/schema.d.ts @@ -0,0 +1,9419 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get health */ + get: operations["get-health"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/cities": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 cities */ + get: operations["get-v0-cities"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city */ + post: operations["post-v0-city"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name */ + get: operations["get-v0-city-by-city-name"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** Patch v0 city by city name */ + patch: operations["patch-v0-city-by-city-name"]; + trace?: never; + }; + "/v0/city/{cityName}/agent/{base}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name agent by base */ + get: operations["get-v0-city-by-city-name-agent-by-base"]; + put?: never; + post?: never; + /** Delete v0 city by city name agent by base */ + delete: operations["delete-v0-city-by-city-name-agent-by-base"]; + options?: never; + head?: never; + /** Patch v0 city by city name agent by base */ + patch: operations["patch-v0-city-by-city-name-agent-by-base"]; + trace?: never; + }; + "/v0/city/{cityName}/agent/{base}/output": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name agent by base output */ + get: operations["get-v0-city-by-city-name-agent-by-base-output"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/agent/{base}/output/stream": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Stream agent output in real time + * @description Server-Sent Events stream of agent output (session log tail or tmux pane polling). + */ + get: operations["stream-agent-output"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/agent/{base}/{action}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name agent by base by action */ + post: operations["post-v0-city-by-city-name-agent-by-base-by-action"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/agent/{dir}/{base}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name agent by dir by base */ + get: operations["get-v0-city-by-city-name-agent-by-dir-by-base"]; + put?: never; + post?: never; + /** Delete v0 city by city name agent by dir by base */ + delete: operations["delete-v0-city-by-city-name-agent-by-dir-by-base"]; + options?: never; + head?: never; + /** Patch v0 city by city name agent by dir by base */ + patch: operations["patch-v0-city-by-city-name-agent-by-dir-by-base"]; + trace?: never; + }; + "/v0/city/{cityName}/agent/{dir}/{base}/output": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name agent by dir by base output */ + get: operations["get-v0-city-by-city-name-agent-by-dir-by-base-output"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/agent/{dir}/{base}/output/stream": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Stream agent output in real time (qualified name) + * @description Server-Sent Events stream of agent output for qualified (rig-prefixed) agent names. + */ + get: operations["stream-agent-output-qualified"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/agent/{dir}/{base}/{action}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name agent by dir by base by action */ + post: operations["post-v0-city-by-city-name-agent-by-dir-by-base-by-action"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/agents": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name agents */ + get: operations["get-v0-city-by-city-name-agents"]; + put?: never; + /** Create an agent */ + post: operations["create-agent"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/bead/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name bead by ID */ + get: operations["get-v0-city-by-city-name-bead-by-id"]; + put?: never; + post?: never; + /** Delete v0 city by city name bead by ID */ + delete: operations["delete-v0-city-by-city-name-bead-by-id"]; + options?: never; + head?: never; + /** Patch v0 city by city name bead by ID */ + patch: operations["patch-v0-city-by-city-name-bead-by-id"]; + trace?: never; + }; + "/v0/city/{cityName}/bead/{id}/assign": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name bead by ID assign */ + post: operations["post-v0-city-by-city-name-bead-by-id-assign"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/bead/{id}/close": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name bead by ID close */ + post: operations["post-v0-city-by-city-name-bead-by-id-close"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/bead/{id}/deps": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name bead by ID deps */ + get: operations["get-v0-city-by-city-name-bead-by-id-deps"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/bead/{id}/reopen": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name bead by ID reopen */ + post: operations["post-v0-city-by-city-name-bead-by-id-reopen"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/bead/{id}/update": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name bead by ID update */ + post: operations["post-v0-city-by-city-name-bead-by-id-update"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/beads": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name beads */ + get: operations["get-v0-city-by-city-name-beads"]; + put?: never; + /** Create a bead */ + post: operations["create-bead"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/beads/graph/{rootID}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name beads graph by root ID */ + get: operations["get-v0-city-by-city-name-beads-graph-by-root-id"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/beads/ready": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name beads ready */ + get: operations["get-v0-city-by-city-name-beads-ready"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/config": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name config */ + get: operations["get-v0-city-by-city-name-config"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/config/explain": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name config explain */ + get: operations["get-v0-city-by-city-name-config-explain"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/config/validate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name config validate */ + get: operations["get-v0-city-by-city-name-config-validate"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/convoy/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name convoy by ID */ + get: operations["get-v0-city-by-city-name-convoy-by-id"]; + put?: never; + post?: never; + /** Delete v0 city by city name convoy by ID */ + delete: operations["delete-v0-city-by-city-name-convoy-by-id"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/convoy/{id}/add": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name convoy by ID add */ + post: operations["post-v0-city-by-city-name-convoy-by-id-add"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/convoy/{id}/check": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name convoy by ID check */ + get: operations["get-v0-city-by-city-name-convoy-by-id-check"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/convoy/{id}/close": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name convoy by ID close */ + post: operations["post-v0-city-by-city-name-convoy-by-id-close"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/convoy/{id}/remove": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name convoy by ID remove */ + post: operations["post-v0-city-by-city-name-convoy-by-id-remove"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/convoys": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name convoys */ + get: operations["get-v0-city-by-city-name-convoys"]; + put?: never; + /** Create a convoy */ + post: operations["create-convoy"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/events": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name events */ + get: operations["get-v0-city-by-city-name-events"]; + put?: never; + /** Emit an event */ + post: operations["emit-event"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/events/stream": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Stream city events in real time + * @description Server-Sent Events stream of city events with optional workflow projections. Supports reconnection via Last-Event-ID header or after_seq query param. + */ + get: operations["stream-events"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/extmsg/adapters": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name extmsg adapters */ + get: operations["get-v0-city-by-city-name-extmsg-adapters"]; + put?: never; + /** Register an external messaging adapter */ + post: operations["register-extmsg-adapter"]; + /** Delete v0 city by city name extmsg adapters */ + delete: operations["delete-v0-city-by-city-name-extmsg-adapters"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/extmsg/bind": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name extmsg bind */ + post: operations["post-v0-city-by-city-name-extmsg-bind"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/extmsg/bindings": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name extmsg bindings */ + get: operations["get-v0-city-by-city-name-extmsg-bindings"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/extmsg/groups": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name extmsg groups */ + get: operations["get-v0-city-by-city-name-extmsg-groups"]; + put?: never; + /** Ensure an external messaging group exists */ + post: operations["ensure-extmsg-group"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/extmsg/inbound": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name extmsg inbound */ + post: operations["post-v0-city-by-city-name-extmsg-inbound"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/extmsg/outbound": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name extmsg outbound */ + post: operations["post-v0-city-by-city-name-extmsg-outbound"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/extmsg/participants": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name extmsg participants */ + post: operations["post-v0-city-by-city-name-extmsg-participants"]; + /** Delete v0 city by city name extmsg participants */ + delete: operations["delete-v0-city-by-city-name-extmsg-participants"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/extmsg/transcript": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name extmsg transcript */ + get: operations["get-v0-city-by-city-name-extmsg-transcript"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/extmsg/transcript/ack": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name extmsg transcript ack */ + post: operations["post-v0-city-by-city-name-extmsg-transcript-ack"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/extmsg/unbind": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name extmsg unbind */ + post: operations["post-v0-city-by-city-name-extmsg-unbind"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/formula/{name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name formula by name */ + get: operations["get-v0-city-by-city-name-formula-by-name"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/formulas": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name formulas */ + get: operations["get-v0-city-by-city-name-formulas"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/formulas/feed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name formulas feed */ + get: operations["get-v0-city-by-city-name-formulas-feed"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/formulas/{name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name formulas by name */ + get: operations["get-v0-city-by-city-name-formulas-by-name"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/formulas/{name}/preview": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name formulas by name preview */ + post: operations["post-v0-city-by-city-name-formulas-by-name-preview"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/formulas/{name}/runs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name formulas by name runs */ + get: operations["get-v0-city-by-city-name-formulas-by-name-runs"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name health */ + get: operations["get-v0-city-by-city-name-health"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/mail": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name mail */ + get: operations["get-v0-city-by-city-name-mail"]; + put?: never; + /** Send a mail message */ + post: operations["send-mail"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/mail/count": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name mail count */ + get: operations["get-v0-city-by-city-name-mail-count"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/mail/thread/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name mail thread by ID */ + get: operations["get-v0-city-by-city-name-mail-thread-by-id"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/mail/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name mail by ID */ + get: operations["get-v0-city-by-city-name-mail-by-id"]; + put?: never; + post?: never; + /** Delete v0 city by city name mail by ID */ + delete: operations["delete-v0-city-by-city-name-mail-by-id"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/mail/{id}/archive": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name mail by ID archive */ + post: operations["post-v0-city-by-city-name-mail-by-id-archive"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/mail/{id}/mark-unread": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name mail by ID mark unread */ + post: operations["post-v0-city-by-city-name-mail-by-id-mark-unread"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/mail/{id}/read": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name mail by ID read */ + post: operations["post-v0-city-by-city-name-mail-by-id-read"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/mail/{id}/reply": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Reply to a mail message */ + post: operations["reply-mail"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/order/history/{bead_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name order history by bead ID */ + get: operations["get-v0-city-by-city-name-order-history-by-bead-id"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/order/{name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name order by name */ + get: operations["get-v0-city-by-city-name-order-by-name"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/order/{name}/disable": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name order by name disable */ + post: operations["post-v0-city-by-city-name-order-by-name-disable"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/order/{name}/enable": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name order by name enable */ + post: operations["post-v0-city-by-city-name-order-by-name-enable"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/orders": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name orders */ + get: operations["get-v0-city-by-city-name-orders"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/orders/check": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name orders check */ + get: operations["get-v0-city-by-city-name-orders-check"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/orders/feed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name orders feed */ + get: operations["get-v0-city-by-city-name-orders-feed"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/orders/history": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name orders history */ + get: operations["get-v0-city-by-city-name-orders-history"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/packs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name packs */ + get: operations["get-v0-city-by-city-name-packs"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/patches/agent/{base}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name patches agent by base */ + get: operations["get-v0-city-by-city-name-patches-agent-by-base"]; + put?: never; + post?: never; + /** Delete v0 city by city name patches agent by base */ + delete: operations["delete-v0-city-by-city-name-patches-agent-by-base"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/patches/agent/{dir}/{base}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name patches agent by dir by base */ + get: operations["get-v0-city-by-city-name-patches-agent-by-dir-by-base"]; + put?: never; + post?: never; + /** Delete v0 city by city name patches agent by dir by base */ + delete: operations["delete-v0-city-by-city-name-patches-agent-by-dir-by-base"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/patches/agents": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name patches agents */ + get: operations["get-v0-city-by-city-name-patches-agents"]; + /** Put v0 city by city name patches agents */ + put: operations["put-v0-city-by-city-name-patches-agents"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/patches/provider/{name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name patches provider by name */ + get: operations["get-v0-city-by-city-name-patches-provider-by-name"]; + put?: never; + post?: never; + /** Delete v0 city by city name patches provider by name */ + delete: operations["delete-v0-city-by-city-name-patches-provider-by-name"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/patches/providers": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name patches providers */ + get: operations["get-v0-city-by-city-name-patches-providers"]; + /** Put v0 city by city name patches providers */ + put: operations["put-v0-city-by-city-name-patches-providers"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/patches/rig/{name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name patches rig by name */ + get: operations["get-v0-city-by-city-name-patches-rig-by-name"]; + put?: never; + post?: never; + /** Delete v0 city by city name patches rig by name */ + delete: operations["delete-v0-city-by-city-name-patches-rig-by-name"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/patches/rigs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name patches rigs */ + get: operations["get-v0-city-by-city-name-patches-rigs"]; + /** Put v0 city by city name patches rigs */ + put: operations["put-v0-city-by-city-name-patches-rigs"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/provider-readiness": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name provider readiness */ + get: operations["get-v0-city-by-city-name-provider-readiness"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/provider/{name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name provider by name */ + get: operations["get-v0-city-by-city-name-provider-by-name"]; + put?: never; + post?: never; + /** Delete v0 city by city name provider by name */ + delete: operations["delete-v0-city-by-city-name-provider-by-name"]; + options?: never; + head?: never; + /** Patch v0 city by city name provider by name */ + patch: operations["patch-v0-city-by-city-name-provider-by-name"]; + trace?: never; + }; + "/v0/city/{cityName}/providers": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name providers */ + get: operations["get-v0-city-by-city-name-providers"]; + put?: never; + /** Create a provider */ + post: operations["create-provider"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/providers/public": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name providers public */ + get: operations["get-v0-city-by-city-name-providers-public"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/readiness": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name readiness */ + get: operations["get-v0-city-by-city-name-readiness"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/rig/{name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name rig by name */ + get: operations["get-v0-city-by-city-name-rig-by-name"]; + put?: never; + post?: never; + /** Delete v0 city by city name rig by name */ + delete: operations["delete-v0-city-by-city-name-rig-by-name"]; + options?: never; + head?: never; + /** Patch v0 city by city name rig by name */ + patch: operations["patch-v0-city-by-city-name-rig-by-name"]; + trace?: never; + }; + "/v0/city/{cityName}/rig/{name}/{action}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name rig by name by action */ + post: operations["post-v0-city-by-city-name-rig-by-name-by-action"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/rigs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name rigs */ + get: operations["get-v0-city-by-city-name-rigs"]; + put?: never; + /** Create a rig */ + post: operations["create-rig"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/service/{name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name service by name */ + get: operations["get-v0-city-by-city-name-service-by-name"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/service/{name}/restart": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name service by name restart */ + post: operations["post-v0-city-by-city-name-service-by-name-restart"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/services": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name services */ + get: operations["get-v0-city-by-city-name-services"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/session/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name session by ID */ + get: operations["get-v0-city-by-city-name-session-by-id"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** Patch v0 city by city name session by ID */ + patch: operations["patch-v0-city-by-city-name-session-by-id"]; + trace?: never; + }; + "/v0/city/{cityName}/session/{id}/agents": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name session by ID agents */ + get: operations["get-v0-city-by-city-name-session-by-id-agents"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/session/{id}/agents/{agentId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name session by ID agents by agent ID */ + get: operations["get-v0-city-by-city-name-session-by-id-agents-by-agent-id"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/session/{id}/close": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name session by ID close */ + post: operations["post-v0-city-by-city-name-session-by-id-close"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/session/{id}/kill": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name session by ID kill */ + post: operations["post-v0-city-by-city-name-session-by-id-kill"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/session/{id}/messages": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Send a message to a session */ + post: operations["send-session-message"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/session/{id}/pending": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name session by ID pending */ + get: operations["get-v0-city-by-city-name-session-by-id-pending"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/session/{id}/rename": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name session by ID rename */ + post: operations["post-v0-city-by-city-name-session-by-id-rename"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/session/{id}/respond": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Respond to a pending interaction */ + post: operations["respond-session"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/session/{id}/stop": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name session by ID stop */ + post: operations["post-v0-city-by-city-name-session-by-id-stop"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/session/{id}/stream": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Stream session output in real time + * @description Server-Sent Events stream of session transcript updates. Streams turns (conversation format) or raw messages (JSONL format) based on the format query parameter. Emits activity and pending events for tool approval prompts. + */ + get: operations["stream-session"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/session/{id}/submit": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Submit a message to a session */ + post: operations["submit-session"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/session/{id}/suspend": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name session by ID suspend */ + post: operations["post-v0-city-by-city-name-session-by-id-suspend"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/session/{id}/transcript": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name session by ID transcript */ + get: operations["get-v0-city-by-city-name-session-by-id-transcript"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/session/{id}/wake": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name session by ID wake */ + post: operations["post-v0-city-by-city-name-session-by-id-wake"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/sessions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name sessions */ + get: operations["get-v0-city-by-city-name-sessions"]; + put?: never; + /** Create a session */ + post: operations["create-session"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/sling": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name sling */ + post: operations["post-v0-city-by-city-name-sling"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name status */ + get: operations["get-v0-city-by-city-name-status"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/city/{cityName}/workflow/{workflow_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 city by city name workflow by workflow ID */ + get: operations["get-v0-city-by-city-name-workflow-by-workflow-id"]; + put?: never; + post?: never; + /** Delete v0 city by city name workflow by workflow ID */ + delete: operations["delete-v0-city-by-city-name-workflow-by-workflow-id"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/events": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 events */ + get: operations["get-v0-events"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/events/stream": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Stream tagged events from all running cities. */ + get: operations["stream-supervisor-events"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/provider-readiness": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 provider readiness */ + get: operations["get-v0-provider-readiness"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/readiness": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get v0 readiness */ + get: operations["get-v0-readiness"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + AdapterCapabilities: { + /** Format: int64 */ + MaxMessageLength: number; + SupportsAttachments: boolean; + SupportsChildConversations: boolean; + }; + AdapterEventPayload: { + account_id: string; + provider: string; + }; + AgentCreateInputBody: { + /** @description Working directory (rig name). */ + dir?: string; + /** + * @description Agent name. + * @example deacon-1 + */ + name: string; + /** + * @description Provider name. + * @example claude + */ + provider: string; + /** @description Agent scope. */ + scope?: string; + }; + AgentCreatedOutputBody: { + /** @description Created agent name. */ + agent: string; + /** + * @description Operation result. + * @example created + */ + status: string; + }; + AgentMapping: { + agent_id: string; + parent_tool_use_id: string; + }; + AgentOutputResponse: { + agent: string; + format: string; + pagination?: components["schemas"]["PaginationInfo"]; + turns: components["schemas"]["OutputTurn"][] | null; + }; + AgentPatch: { + Attach: boolean | null; + DefaultSlingFormula: string | null; + DependsOn: string[] | null; + Dir: string; + Env: { + [key: string]: string; + }; + EnvRemove: string[] | null; + HooksInstalled: boolean | null; + IdleTimeout: string | null; + InjectAssignedSkills: boolean | null; + InjectFragments: string[] | null; + InjectFragmentsAppend: string[] | null; + InstallAgentHooks: string[] | null; + InstallAgentHooksAppend: string[] | null; + MCP: string[] | null; + MCPAppend: string[] | null; + /** Format: int64 */ + MaxActiveSessions: number | null; + /** Format: int64 */ + MinActiveSessions: number | null; + Name: string; + Nudge: string | null; + OptionDefaults: { + [key: string]: string; + }; + OverlayDir: string | null; + Pool: components["schemas"]["PoolOverride"]; + PreStart: string[] | null; + PreStartAppend: string[] | null; + PromptTemplate: string | null; + Provider: string | null; + ResumeCommand: string | null; + ScaleCheck: string | null; + Scope: string | null; + Session: string | null; + SessionLive: string[] | null; + SessionLiveAppend: string[] | null; + SessionSetup: string[] | null; + SessionSetupAppend: string[] | null; + SessionSetupScript: string | null; + Skills: string[] | null; + SkillsAppend: string[] | null; + SleepAfterIdle: string | null; + StartCommand: string | null; + Suspended: boolean | null; + WakeMode: string | null; + WorkDir: string | null; + }; + AgentPatchSetInputBody: { + /** @description Agent directory scope. */ + dir?: string; + /** @description Override environment variables. */ + env?: { + [key: string]: string; + }; + /** @description Agent name. */ + name?: string; + /** @description Override agent scope. */ + scope?: string; + /** @description Override suspended state. */ + suspended?: boolean; + /** @description Override session working directory. */ + work_dir?: string; + }; + AgentResponse: { + active_bead?: string; + activity?: string; + available: boolean; + /** Format: int64 */ + context_pct?: number; + /** Format: int64 */ + context_window?: number; + description?: string; + display_name?: string; + last_output?: string; + model?: string; + name: string; + pool?: string; + provider?: string; + rig?: string; + running: boolean; + session?: components["schemas"]["SessionInfo"]; + state: string; + suspended: boolean; + unavailable_reason?: string; + }; + AgentUpdateInputBody: { + /** @description Provider name. */ + provider?: string; + /** @description Agent scope. */ + scope?: string; + /** @description Whether agent is suspended. */ + suspended?: boolean; + }; + AgentUpdateQualifiedInputBody: { + /** @description Provider name. */ + provider?: string; + /** @description Agent scope. */ + scope?: string; + /** @description Whether agent is suspended. */ + suspended?: boolean; + }; + AnnotatedAgentResponse: { + dir?: string; + is_pool?: boolean; + name: string; + /** @description Agent origin: inline or pack-derived. */ + origin: string; + provider?: string; + scope?: string; + suspended: boolean; + }; + AnnotatedProviderResponse: { + args?: string[] | null; + command?: string; + display_name?: string; + env?: { + [key: string]: string; + }; + /** @description Provider origin: builtin, city, or builtin+city. */ + origin: string; + prompt_flag?: string; + prompt_mode?: string; + /** Format: int64 */ + ready_delay_ms?: number; + }; + Bead: { + assignee?: string; + /** Format: date-time */ + created_at: string; + dependencies?: components["schemas"]["Dep"][] | null; + description?: string; + from?: string; + id: string; + issue_type: string; + labels?: string[] | null; + metadata?: { + [key: string]: string; + }; + needs?: string[] | null; + parent?: string; + /** Format: int64 */ + priority?: number; + ref?: string; + status: string; + title: string; + }; + BeadAssignInputBody: { + /** @description Assignee name. */ + assignee?: string; + }; + BeadCreateInputBody: { + /** @description Assigned agent. */ + assignee?: string; + /** @description Bead description. */ + description?: string; + /** @description Bead labels. */ + labels?: string[] | null; + /** + * Format: int64 + * @description Bead priority. + */ + priority?: number; + /** @description Rig name. */ + rig?: string; + /** @description Bead title. */ + title: string; + /** @description Bead type. */ + type?: string; + }; + BeadDepsResponse: { + children: components["schemas"]["Bead"][] | null; + }; + BeadEventPayload: { + bead: components["schemas"]["Bead"]; + }; + BeadGraphResponse: { + beads: components["schemas"]["Bead"][] | null; + deps: components["schemas"]["WorkflowDepResponse"][] | null; + root: components["schemas"]["Bead"]; + }; + BeadUpdateBody: { + /** @description Assigned agent. */ + assignee?: string; + /** @description Bead description. */ + description?: string; + /** @description Bead labels. */ + labels?: string[] | null; + /** @description Metadata key-value pairs to set. */ + metadata?: { + [key: string]: string; + }; + /** + * Format: int64 + * @description Bead priority. + */ + priority?: number; + /** @description Labels to remove. */ + remove_labels?: string[] | null; + /** @description Bead status. */ + status?: string; + /** @description Bead title. */ + title?: string; + /** @description Bead type. */ + type?: string; + }; + /** + * @description Lifecycle state of a session binding. + * @enum {string} + */ + BindingStatus: "active" | "ended"; + BoundEventPayload: { + conversation_id: string; + provider: string; + session_id: string; + }; + CityCreateRequest: { + /** + * @description Optional bootstrap profile. + * @enum {string} + */ + bootstrap_profile?: "k8s-cell" | "kubernetes" | "kubernetes-cell" | "single-host-compat"; + /** @description Directory to create the city in. Absolute or relative to $HOME. */ + dir: string; + /** @description Provider name for the city's default session template. */ + provider: string; + }; + CityCreateResponse: { + /** @description True on success. */ + ok: boolean; + /** @description Resolved absolute path of the created city. */ + path: string; + }; + CityGetResponse: { + /** Format: int64 */ + agent_count: number; + name: string; + path: string; + provider?: string; + /** Format: int64 */ + rig_count: number; + session_template?: string; + suspended: boolean; + /** Format: int64 */ + uptime_sec: number; + version?: string; + }; + CityInfo: { + error?: string; + name: string; + path: string; + phases_completed?: string[] | null; + running: boolean; + status?: string; + }; + CityPatchInputBody: { + /** @description Whether the city is suspended. */ + suspended?: boolean; + }; + ConfigAgentResponse: { + dir?: string; + is_pool?: boolean; + name: string; + provider?: string; + scope?: string; + suspended: boolean; + }; + ConfigExplainPatches: { + /** Format: int64 */ + agents: number; + /** Format: int64 */ + providers: number; + /** Format: int64 */ + rigs: number; + }; + ConfigExplainResponse: { + agents: components["schemas"]["AnnotatedAgentResponse"][] | null; + patches: components["schemas"]["ConfigExplainPatches"]; + providers: { + [key: string]: components["schemas"]["AnnotatedProviderResponse"]; + }; + }; + ConfigPatchesResponse: { + /** Format: int64 */ + agent_count: number; + /** Format: int64 */ + provider_count: number; + /** Format: int64 */ + rig_count: number; + }; + ConfigResponse: { + agents: components["schemas"]["ConfigAgentResponse"][] | null; + patches?: components["schemas"]["ConfigPatchesResponse"]; + providers?: { + [key: string]: components["schemas"]["ProviderSpecJSON"]; + }; + rigs: components["schemas"]["ConfigRigResponse"][] | null; + workspace: components["schemas"]["WorkspaceResponse"]; + }; + ConfigRigResponse: { + name: string; + path: string; + prefix?: string; + suspended: boolean; + }; + ConfigValidateOutputBody: { + /** @description Validation errors. */ + errors: string[] | null; + /** @description Whether the configuration is valid. */ + valid: boolean; + /** @description Validation warnings. */ + warnings: string[] | null; + }; + ConversationGroupParticipant: { + GroupID: string; + Handle: string; + ID: string; + Metadata: { + [key: string]: string; + }; + Public: boolean; + SessionID: string; + }; + ConversationGroupRecord: { + DefaultHandle: string; + FanoutPolicy: components["schemas"]["FanoutPolicy"]; + ID: string; + LastAddressedHandle: string; + Metadata: { + [key: string]: string; + }; + Mode: string; + RootConversation: components["schemas"]["ConversationRef"]; + /** Format: int64 */ + SchemaVersion: number; + }; + /** + * @description Shape of a conversation. + * @enum {string} + */ + ConversationKind: "dm" | "room" | "thread"; + ConversationRef: { + account_id: string; + conversation_id: string; + kind: components["schemas"]["ConversationKind"]; + parent_conversation_id?: string; + provider: string; + scope_id: string; + }; + ConversationTranscriptRecord: { + Actor: components["schemas"]["ExternalActor"]; + Attachments: components["schemas"]["ExternalAttachment"][] | null; + Conversation: components["schemas"]["ConversationRef"]; + /** Format: date-time */ + CreatedAt: string; + ExplicitTarget: string; + ID: string; + Kind: components["schemas"]["TranscriptMessageKind"]; + Metadata: { + [key: string]: string; + }; + Provenance: components["schemas"]["TranscriptProvenance"]; + ProviderMessageID: string; + ReplyToMessageID: string; + /** Format: int64 */ + SchemaVersion: number; + /** Format: int64 */ + Sequence: number; + SourceSessionID: string; + Text: string; + }; + ConvoyAddInputBody: { + /** @description Bead IDs to add. */ + items?: string[] | null; + }; + ConvoyCheckResponse: { + /** + * Format: int64 + * @description Closed child bead count. + */ + closed: number; + /** @description True when all child beads are closed and total > 0. */ + complete: boolean; + /** @description Convoy ID. */ + convoy_id: string; + /** + * Format: int64 + * @description Total child bead count. + */ + total: number; + }; + ConvoyCreateInputBody: { + /** @description Bead IDs to include. */ + items?: string[] | null; + /** @description Rig name. */ + rig?: string; + /** @description Convoy title. */ + title: string; + }; + ConvoyGetResponse: { + /** @description Direct child beads (non-workflow case). */ + children?: components["schemas"]["Bead"][] | null; + /** @description Simple convoy bead (non-workflow case). */ + convoy?: components["schemas"]["Bead"]; + /** @description Child bead progress (non-workflow case). */ + progress?: components["schemas"]["ConvoyProgress"]; + }; + ConvoyProgress: { + /** + * Format: int64 + * @description Closed child bead count. + */ + closed: number; + /** + * Format: int64 + * @description Total child bead count. + */ + total: number; + }; + ConvoyRemoveInputBody: { + /** @description Bead IDs to remove. */ + items?: string[] | null; + }; + DeliveryContextRecord: { + /** Format: int64 */ + BindingGeneration: number; + Conversation: components["schemas"]["ConversationRef"]; + ID: string; + LastMessageID: string; + /** Format: date-time */ + LastPublishedAt: string; + Metadata: { + [key: string]: string; + }; + /** Format: int64 */ + SchemaVersion: number; + SessionID: string; + SourceSessionID: string; + }; + Dep: { + depends_on_id: string; + issue_id: string; + type: string; + }; + ErrorDetail: { + /** @description Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id' */ + location?: string; + /** @description Error message text */ + message?: string; + /** @description The value at the given location */ + value?: unknown; + }; + ErrorModel: { + /** + * @description A human-readable explanation specific to this occurrence of the problem. + * @example Property foo is required but is missing. + */ + detail?: string; + /** @description Optional list of individual error details */ + errors?: components["schemas"]["ErrorDetail"][] | null; + /** + * Format: uri + * @description A URI reference that identifies the specific occurrence of the problem. + * @example https://example.com/error-log/abc123 + */ + instance?: string; + /** + * Format: int64 + * @description HTTP status code + * @example 400 + */ + status?: number; + /** + * @description A short, human-readable summary of the problem type. This value should not change between occurrences of the error. + * @example Bad Request + */ + title?: string; + /** + * Format: uri + * @description A URI reference to human-readable documentation for the error. + * @default about:blank + * @example https://example.com/errors/example + */ + type: string; + }; + EventEmitOutputBody: { + /** + * @description Operation result. + * @example recorded + */ + status: string; + }; + EventEmitRequest: { + /** @description Actor that produced the event. */ + actor: string; + /** @description Event message. */ + message?: string; + /** @description Event subject. */ + subject?: string; + /** @description Event type. */ + type: string; + }; + EventPayload: components["schemas"]["AdapterEventPayload"] | components["schemas"]["BeadEventPayload"] | components["schemas"]["BoundEventPayload"] | components["schemas"]["GroupCreatedEventPayload"] | components["schemas"]["InboundEventPayload"] | components["schemas"]["MailEventPayload"] | components["schemas"]["NoPayload"] | components["schemas"]["OutboundEventPayload"] | components["schemas"]["UnboundEventPayload"] | components["schemas"]["WorkerOperationEventPayload"]; + EventStreamEnvelope: { + actor: string; + message?: string; + payload?: components["schemas"]["EventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + type: string; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + ExtMsgAdapterRegisterInputBody: { + /** @description Account ID. */ + account_id: string; + /** @description Callback URL for outbound messages. */ + callback_url?: string; + /** @description Adapter capabilities. */ + capabilities?: components["schemas"]["AdapterCapabilities"]; + /** @description Adapter display name. */ + name?: string; + /** @description Provider name. */ + provider: string; + }; + ExtMsgAdapterRegisterOutputBody: { + /** @description Account ID. */ + account_id: string; + /** @description Adapter name. */ + name: string; + /** @description Provider name. */ + provider: string; + /** + * @description Operation result. + * @example registered + */ + status: string; + }; + ExtMsgAdapterUnregisterInputBody: { + /** @description Account ID. */ + account_id: string; + /** @description Provider name. */ + provider: string; + }; + ExtMsgBindInputBody: { + /** @description Conversation to bind. */ + conversation?: components["schemas"]["ConversationRef"]; + /** @description Optional binding metadata. */ + metadata?: { + [key: string]: string; + }; + /** @description Session ID to bind. */ + session_id: string; + }; + ExtMsgGroupEnsureInputBody: { + /** @description Default handle for the group. */ + default_handle?: string; + /** @description Group metadata. */ + metadata?: { + [key: string]: string; + }; + /** @description Group mode (launcher, etc.). */ + mode?: string; + /** @description Root conversation reference. */ + root_conversation?: components["schemas"]["ConversationRef"]; + }; + ExtMsgInboundInputBody: { + /** @description Account ID for raw payloads (required when message is absent). */ + account_id?: string; + /** @description Pre-normalized inbound message. */ + message?: components["schemas"]["ExternalInboundMessage"]; + /** @description Raw payload bytes. */ + payload?: string; + /** @description Provider name for raw payloads (required when message is absent). */ + provider?: string; + }; + ExtMsgOutboundInputBody: { + /** @description Target conversation. */ + conversation?: components["schemas"]["ConversationRef"]; + /** @description Idempotency key. */ + idempotency_key?: string; + /** @description Message ID to reply to. */ + reply_to_message_id?: string; + /** @description Session ID. */ + session_id: string; + /** @description Message text. */ + text?: string; + }; + ExtMsgParticipantRemoveInputBody: { + /** @description Group ID. */ + group_id: string; + /** @description Participant handle. */ + handle: string; + }; + ExtMsgParticipantUpsertInputBody: { + /** @description Group ID. */ + group_id: string; + /** @description Participant handle. */ + handle: string; + /** @description Participant metadata. */ + metadata?: { + [key: string]: string; + }; + /** @description Whether participant is public. */ + public?: boolean; + /** @description Session ID. */ + session_id: string; + }; + ExtMsgTranscriptAckInputBody: { + /** @description Conversation to acknowledge. */ + conversation?: components["schemas"]["ConversationRef"]; + /** + * Format: int64 + * @description Sequence number to acknowledge up to. + */ + sequence?: number; + /** @description Session ID. */ + session_id: string; + }; + ExtMsgUnbindBody: { + /** @description Bindings that were removed. */ + unbound: components["schemas"]["SessionBindingRecord"][] | null; + }; + ExtMsgUnbindInputBody: { + /** @description Conversation to unbind (nil = all). */ + conversation?: components["schemas"]["ConversationRef"]; + /** @description Session ID to unbind. */ + session_id: string; + }; + ExternalActor: { + display_name: string; + id: string; + is_bot: boolean; + }; + ExternalAttachment: { + mime_type: string; + provider_id: string; + url: string; + }; + ExternalInboundMessage: { + actor: components["schemas"]["ExternalActor"]; + attachments?: components["schemas"]["ExternalAttachment"][] | null; + conversation: components["schemas"]["ConversationRef"]; + dedup_key?: string; + explicit_target?: string; + provider_message_id: string; + /** Format: date-time */ + received_at: string; + reply_to_message_id?: string; + text: string; + }; + ExtmsgAdapterInfo: { + /** @description Adapter account ID. */ + account_id: string; + /** @description Adapter display name. */ + name: string; + /** @description Adapter provider key. */ + provider: string; + }; + FanoutPolicy: { + AllowUntargetedPublication: boolean; + Enabled: boolean; + /** Format: int64 */ + MaxPeerTriggeredPublishes: number; + /** Format: int64 */ + MaxTotalPeerDeliveries: number; + }; + FormulaDetailResponse: { + deps: components["schemas"]["FormulaPreviewEdgeResponse"][] | null; + description: string; + name: string; + preview: components["schemas"]["FormulaPreviewResponse"]; + steps: components["schemas"]["FormulaStepResponse"][] | null; + var_defs: components["schemas"]["FormulaVarDefResponse"][] | null; + version: string; + }; + FormulaFeedBody: { + items: components["schemas"]["MonitorFeedItemResponse"][] | null; + partial: boolean; + partial_errors?: string[] | null; + }; + FormulaListBody: { + /** @description Formula summaries. */ + items: components["schemas"]["FormulaSummaryResponse"][] | null; + /** @description Whether the list is partial. */ + partial: boolean; + }; + FormulaPreviewBody: { + /** @description Scope kind (city or rig). */ + scope_kind?: string; + /** @description Scope reference. */ + scope_ref?: string; + /** @description Target agent for preview compilation. */ + target: string; + /** @description Variable name-to-value overrides applied to the compiled preview. */ + vars?: { + [key: string]: string; + }; + }; + FormulaPreviewEdgeResponse: { + from: string; + kind?: string; + to: string; + }; + FormulaPreviewNodeResponse: { + id: string; + kind: string; + scope_ref?: string; + title: string; + }; + FormulaPreviewResponse: { + edges: components["schemas"]["FormulaPreviewEdgeResponse"][] | null; + nodes: components["schemas"]["FormulaPreviewNodeResponse"][] | null; + }; + FormulaRecentRunResponse: { + started_at: string; + status: string; + target: string; + updated_at: string; + workflow_id: string; + }; + FormulaRunsResponse: { + formula: string; + partial: boolean; + partial_errors?: string[] | null; + recent_runs: components["schemas"]["FormulaRecentRunResponse"][] | null; + /** Format: int64 */ + run_count: number; + }; + FormulaStepResponse: { + assignee?: string; + id: string; + kind: string; + labels?: string[] | null; + metadata?: { + [key: string]: string; + }; + title: string; + type?: string; + }; + FormulaSummaryResponse: { + description: string; + name: string; + recent_runs: components["schemas"]["FormulaRecentRunResponse"][] | null; + /** Format: int64 */ + run_count: number; + var_defs: components["schemas"]["FormulaVarDefResponse"][] | null; + version: string; + }; + FormulaVarDefResponse: { + default?: unknown; + description?: string; + enum?: string[] | null; + name: string; + pattern?: string; + required?: boolean; + type: string; + }; + GitStatus: { + /** Format: int64 */ + ahead: number; + /** Format: int64 */ + behind: number; + branch: string; + /** Format: int64 */ + changed_files: number; + clean: boolean; + }; + GroupCreatedEventPayload: { + conversation_id: string; + mode: string; + provider: string; + }; + GroupRouteDecision: { + Match: string; + TargetSessionID: string; + UpdateCursor: boolean; + }; + HealthOutputBody: { + /** @description City name. */ + city?: string; + /** + * @description Health status. + * @example ok + */ + status: string; + /** + * Format: int64 + * @description Server uptime in seconds. + */ + uptime_sec: number; + /** @description Server version. */ + version?: string; + }; + HeartbeatEvent: { + /** @description ISO 8601 timestamp when the heartbeat was sent. */ + timestamp: string; + }; + InboundEventPayload: { + actor: string; + conversation_id: string; + provider: string; + target_session: string; + }; + InboundResult: { + Binding: components["schemas"]["SessionBindingRecord"]; + GroupRoute: components["schemas"]["GroupRouteDecision"]; + Message: components["schemas"]["ExternalInboundMessage"]; + TargetSessionID: string; + TranscriptEntry: components["schemas"]["ConversationTranscriptRecord"]; + }; + ListBodyAgentPatch: { + /** @description The list of items. */ + items: components["schemas"]["AgentPatch"][] | null; + /** @description Cursor for the next page of results. */ + next_cursor?: string; + /** @description True when one or more backends failed and the list is incomplete. */ + partial?: boolean; + /** @description Human-readable errors from backends that failed during aggregation. */ + partial_errors?: string[] | null; + /** + * Format: int64 + * @description Total number of items matching the query. + */ + total: number; + }; + ListBodyAgentResponse: { + /** @description The list of items. */ + items: components["schemas"]["AgentResponse"][] | null; + /** @description Cursor for the next page of results. */ + next_cursor?: string; + /** @description True when one or more backends failed and the list is incomplete. */ + partial?: boolean; + /** @description Human-readable errors from backends that failed during aggregation. */ + partial_errors?: string[] | null; + /** + * Format: int64 + * @description Total number of items matching the query. + */ + total: number; + }; + ListBodyBead: { + /** @description The list of items. */ + items: components["schemas"]["Bead"][] | null; + /** @description Cursor for the next page of results. */ + next_cursor?: string; + /** @description True when one or more backends failed and the list is incomplete. */ + partial?: boolean; + /** @description Human-readable errors from backends that failed during aggregation. */ + partial_errors?: string[] | null; + /** + * Format: int64 + * @description Total number of items matching the query. + */ + total: number; + }; + ListBodyConversationTranscriptRecord: { + /** @description The list of items. */ + items: components["schemas"]["ConversationTranscriptRecord"][] | null; + /** @description Cursor for the next page of results. */ + next_cursor?: string; + /** @description True when one or more backends failed and the list is incomplete. */ + partial?: boolean; + /** @description Human-readable errors from backends that failed during aggregation. */ + partial_errors?: string[] | null; + /** + * Format: int64 + * @description Total number of items matching the query. + */ + total: number; + }; + ListBodyExtmsgAdapterInfo: { + /** @description The list of items. */ + items: components["schemas"]["ExtmsgAdapterInfo"][] | null; + /** @description Cursor for the next page of results. */ + next_cursor?: string; + /** @description True when one or more backends failed and the list is incomplete. */ + partial?: boolean; + /** @description Human-readable errors from backends that failed during aggregation. */ + partial_errors?: string[] | null; + /** + * Format: int64 + * @description Total number of items matching the query. + */ + total: number; + }; + ListBodyProviderPatch: { + /** @description The list of items. */ + items: components["schemas"]["ProviderPatch"][] | null; + /** @description Cursor for the next page of results. */ + next_cursor?: string; + /** @description True when one or more backends failed and the list is incomplete. */ + partial?: boolean; + /** @description Human-readable errors from backends that failed during aggregation. */ + partial_errors?: string[] | null; + /** + * Format: int64 + * @description Total number of items matching the query. + */ + total: number; + }; + ListBodyProviderResponse: { + /** @description The list of items. */ + items: components["schemas"]["ProviderResponse"][] | null; + /** @description Cursor for the next page of results. */ + next_cursor?: string; + /** @description True when one or more backends failed and the list is incomplete. */ + partial?: boolean; + /** @description Human-readable errors from backends that failed during aggregation. */ + partial_errors?: string[] | null; + /** + * Format: int64 + * @description Total number of items matching the query. + */ + total: number; + }; + ListBodyRigPatch: { + /** @description The list of items. */ + items: components["schemas"]["RigPatch"][] | null; + /** @description Cursor for the next page of results. */ + next_cursor?: string; + /** @description True when one or more backends failed and the list is incomplete. */ + partial?: boolean; + /** @description Human-readable errors from backends that failed during aggregation. */ + partial_errors?: string[] | null; + /** + * Format: int64 + * @description Total number of items matching the query. + */ + total: number; + }; + ListBodyRigResponse: { + /** @description The list of items. */ + items: components["schemas"]["RigResponse"][] | null; + /** @description Cursor for the next page of results. */ + next_cursor?: string; + /** @description True when one or more backends failed and the list is incomplete. */ + partial?: boolean; + /** @description Human-readable errors from backends that failed during aggregation. */ + partial_errors?: string[] | null; + /** + * Format: int64 + * @description Total number of items matching the query. + */ + total: number; + }; + ListBodySessionBindingRecord: { + /** @description The list of items. */ + items: components["schemas"]["SessionBindingRecord"][] | null; + /** @description Cursor for the next page of results. */ + next_cursor?: string; + /** @description True when one or more backends failed and the list is incomplete. */ + partial?: boolean; + /** @description Human-readable errors from backends that failed during aggregation. */ + partial_errors?: string[] | null; + /** + * Format: int64 + * @description Total number of items matching the query. + */ + total: number; + }; + ListBodySessionResponse: { + /** @description The list of items. */ + items: components["schemas"]["SessionResponse"][] | null; + /** @description Cursor for the next page of results. */ + next_cursor?: string; + /** @description True when one or more backends failed and the list is incomplete. */ + partial?: boolean; + /** @description Human-readable errors from backends that failed during aggregation. */ + partial_errors?: string[] | null; + /** + * Format: int64 + * @description Total number of items matching the query. + */ + total: number; + }; + ListBodyStatus: { + /** @description The list of items. */ + items: components["schemas"]["Status"][] | null; + /** @description Cursor for the next page of results. */ + next_cursor?: string; + /** @description True when one or more backends failed and the list is incomplete. */ + partial?: boolean; + /** @description Human-readable errors from backends that failed during aggregation. */ + partial_errors?: string[] | null; + /** + * Format: int64 + * @description Total number of items matching the query. + */ + total: number; + }; + ListBodyWireEvent: { + /** @description The list of items. */ + items: components["schemas"]["WireEvent"][] | null; + /** @description Cursor for the next page of results. */ + next_cursor?: string; + /** @description True when one or more backends failed and the list is incomplete. */ + partial?: boolean; + /** @description Human-readable errors from backends that failed during aggregation. */ + partial_errors?: string[] | null; + /** + * Format: int64 + * @description Total number of items matching the query. + */ + total: number; + }; + LogicalNode: Record; + MailCountOutputBody: { + /** @description True when one or more rig providers failed and the counts are not authoritative. */ + partial?: boolean; + /** @description Per-provider errors when partial is true. */ + partial_errors?: string[] | null; + /** + * Format: int64 + * @description Total message count. + */ + total: number; + /** + * Format: int64 + * @description Unread message count. + */ + unread: number; + }; + MailEventPayload: { + message?: components["schemas"]["Message"]; + rig: string; + }; + MailListBody: { + /** @description The list of messages. */ + items: components["schemas"]["Message"][] | null; + /** @description Cursor for the next page of results. */ + next_cursor?: string; + /** @description True when one or more rig providers failed and the list is not authoritative. */ + partial?: boolean; + /** @description Per-provider errors when partial is true. */ + partial_errors?: string[] | null; + /** + * Format: int64 + * @description Total number of messages matching the query. + */ + total: number; + }; + MailReplyInputBody: { + /** @description Reply body. */ + body?: string; + /** @description Sender name. */ + from?: string; + /** @description Reply subject. */ + subject?: string; + }; + MailSendInputBody: { + /** @description Message body. */ + body?: string; + /** @description Sender name. */ + from?: string; + /** @description Rig name. */ + rig?: string; + /** @description Message subject. */ + subject: string; + /** @description Recipient name. */ + to: string; + }; + Message: { + body: string; + cc?: string[] | null; + /** Format: date-time */ + created_at: string; + from: string; + id: string; + /** Format: int64 */ + priority?: number; + read: boolean; + reply_to?: string; + rig?: string; + subject: string; + thread_id?: string; + to: string; + }; + MonitorFeedItemResponse: { + attached_bead_id?: string; + bead_id?: string; + detail_available?: boolean; + id: string; + logical_bead_id?: string; + root_bead_id?: string; + root_store_ref?: string; + run_detail_available?: boolean; + scope_kind: string; + scope_ref: string; + started_at: string; + status: string; + store_ref?: string; + target: string; + title: string; + type: string; + updated_at: string; + workflow_id?: string; + }; + NoPayload: Record; + OKResponseBody: { + /** + * @description Operation result. + * @example ok + */ + status: string; + }; + OKWithIDResponseBody: { + /** @description Resource ID. */ + id?: string; + /** + * @description Operation result. + * @example ok + */ + status: string; + }; + OptionChoiceDTO: { + label: string; + value: string; + }; + OrderCheckListBody: { + /** @description Order trigger evaluations. */ + checks: components["schemas"]["OrderCheckResponse"][] | null; + }; + OrderCheckResponse: { + due: boolean; + last_run?: string; + last_run_outcome?: string; + name: string; + reason: string; + rig?: string; + scoped_name: string; + }; + OrderHistoryDetailResponse: { + bead_id: string; + created_at: string; + labels: string[] | null; + output: string; + store_ref: string; + }; + OrderHistoryEntry: { + bead_id: string; + capture_output: boolean; + created_at: string; + duration_ms?: string; + error?: string; + exit_code?: string; + has_output: boolean; + labels: string[] | null; + name: string; + rig?: string; + scoped_name: string; + signal?: string; + store_ref: string; + wisp_root_id?: string; + }; + OrderHistoryListBody: { + /** @description Order history entries. */ + entries: components["schemas"]["OrderHistoryEntry"][] | null; + }; + OrderListBody: { + /** @description Registered orders. */ + orders: components["schemas"]["OrderResponse"][] | null; + }; + OrderResponse: { + capture_output: boolean; + check?: string; + description?: string; + enabled: boolean; + exec?: string; + formula?: string; + /** @deprecated */ + gate?: string; + interval?: string; + name: string; + on?: string; + pool?: string; + rig?: string; + schedule?: string; + scoped_name: string; + timeout?: string; + /** Format: int64 */ + timeout_ms: number; + trigger?: string; + type: string; + }; + OrdersFeedBody: { + items: components["schemas"]["MonitorFeedItemResponse"][] | null; + partial: boolean; + partial_errors?: string[] | null; + }; + OutboundEventPayload: { + conversation_id: string; + message_id: string; + provider: string; + session: string; + }; + OutboundResult: { + DeliveryContext: components["schemas"]["DeliveryContextRecord"]; + Receipt: components["schemas"]["PublishReceipt"]; + TranscriptEntry: components["schemas"]["ConversationTranscriptRecord"]; + }; + OutputTurn: { + role: string; + text: string; + timestamp?: string; + }; + PackListBody: { + /** @description Registered packs. */ + packs: components["schemas"]["PackResponse"][] | null; + }; + PackResponse: { + name: string; + path?: string; + ref?: string; + source?: string; + }; + PaginationInfo: { + has_older_messages: boolean; + /** Format: int64 */ + returned_message_count: number; + /** Format: int64 */ + total_compactions: number; + /** Format: int64 */ + total_message_count: number; + truncated_before_message?: string; + }; + PatchDeletedResponseBody: { + /** @description Agent patch qualified name. */ + agent_patch?: string; + /** @description Provider patch name. */ + provider_patch?: string; + /** @description Rig patch name. */ + rig_patch?: string; + /** + * @description Operation result. + * @example deleted + */ + status: string; + }; + PatchOKResponseBody: { + /** @description Agent patch qualified name. */ + agent_patch?: string; + /** @description Provider patch name. */ + provider_patch?: string; + /** @description Rig patch name. */ + rig_patch?: string; + /** + * @description Operation result. + * @example ok + */ + status: string; + }; + PendingInteraction: { + kind: string; + metadata?: { + [key: string]: string; + }; + options?: string[] | null; + prompt?: string; + request_id: string; + }; + PoolOverride: { + Check: string | null; + DrainTimeout: string | null; + /** Format: int64 */ + Max: number | null; + /** Format: int64 */ + Min: number | null; + OnBoot: string | null; + OnDeath: string | null; + }; + ProviderCreateInputBody: { + /** @description Command arguments. */ + args?: string[] | null; + /** @description Arguments appended after inherited/base args. */ + args_append?: string[] | null; + /** @description Optional provider base for inheritance. */ + base?: string; + /** @description Provider command binary. Omit for base-only descendants. */ + command?: string; + /** @description Human-readable display name. */ + display_name?: string; + /** @description Environment variables. */ + env?: { + [key: string]: string; + }; + /** @description Provider name. */ + name: string; + /** @description Options schema merge mode across inheritance chain. */ + options_schema_merge?: string; + /** @description Flag for prompt delivery. */ + prompt_flag?: string; + /** @description Prompt delivery mode. */ + prompt_mode?: string; + /** + * Format: int64 + * @description Milliseconds to wait before probing readiness. + */ + ready_delay_ms?: number; + }; + ProviderCreatedOutputBody: { + /** @description Created provider name. */ + provider: string; + /** + * @description Operation result. + * @example created + */ + status: string; + }; + ProviderOptionDTO: { + choices: components["schemas"]["OptionChoiceDTO"][] | null; + default: string; + key: string; + label: string; + type: string; + }; + ProviderPatch: { + ACPArgs: string[] | null; + ACPCommand: string | null; + Args: string[] | null; + ArgsAppend: string[] | null; + Base: string | null; + Command: string | null; + Env: { + [key: string]: string; + }; + EnvRemove: string[] | null; + Name: string; + OptionsSchemaMerge: string | null; + PromptFlag: string | null; + PromptMode: string | null; + /** Format: int64 */ + ReadyDelayMs: number | null; + Replace: boolean; + }; + ProviderPatchSetInputBody: { + /** @description Override command arguments. */ + args?: string[] | null; + /** @description Override command binary. */ + command?: string; + /** @description Override environment variables. */ + env?: { + [key: string]: string; + }; + /** @description Provider name. */ + name?: string; + /** @description Override prompt flag. */ + prompt_flag?: string; + /** @description Override prompt delivery mode. */ + prompt_mode?: string; + /** + * Format: int64 + * @description Override ready delay in milliseconds. + */ + ready_delay_ms?: number; + }; + ProviderPublicListBody: { + /** @description The list of browser-safe provider summaries. */ + items: components["schemas"]["ProviderPublicResponse"][] | null; + /** @description Cursor for the next page of results. */ + next_cursor?: string; + /** + * Format: int64 + * @description Total number of providers in the list. + */ + total: number; + }; + ProviderPublicResponse: { + builtin: boolean; + city_level: boolean; + display_name?: string; + effective_defaults?: { + [key: string]: string; + }; + name: string; + options_schema?: components["schemas"]["ProviderOptionDTO"][] | null; + }; + ProviderReadiness: { + detail?: string; + display_name: string; + status: string; + }; + ProviderReadinessResponse: { + providers: { + [key: string]: components["schemas"]["ProviderReadiness"]; + }; + }; + ProviderResponse: { + args?: string[] | null; + builtin: boolean; + city_level: boolean; + command?: string; + display_name?: string; + env?: { + [key: string]: string; + }; + name: string; + prompt_flag?: string; + prompt_mode?: string; + /** Format: int64 */ + ready_delay_ms?: number; + }; + ProviderSpecJSON: { + args?: string[] | null; + command?: string; + display_name?: string; + env?: { + [key: string]: string; + }; + prompt_flag?: string; + prompt_mode?: string; + /** Format: int64 */ + ready_delay_ms?: number; + }; + ProviderUpdateInputBody: { + /** @description Command arguments. */ + args?: string[] | null; + /** @description Arguments appended after inherited/base args. */ + args_append?: string[] | null; + /** @description Provider base for inheritance. */ + base?: string; + /** @description Provider command binary. */ + command?: string; + /** @description Human-readable display name. */ + display_name?: string; + /** @description Environment variables. */ + env?: { + [key: string]: string; + }; + /** @description Options schema merge mode across inheritance chain. */ + options_schema_merge?: string; + /** @description Flag for prompt delivery. */ + prompt_flag?: string; + /** @description Prompt delivery mode. */ + prompt_mode?: string; + /** + * Format: int64 + * @description Milliseconds to wait before probing readiness. + */ + ready_delay_ms?: number; + }; + PublishReceipt: { + Conversation: components["schemas"]["ConversationRef"]; + Delivered: boolean; + FailureKind: string; + MessageID: string; + Metadata: { + [key: string]: string; + }; + /** Format: int64 */ + RetryAfter: number; + }; + ReadinessItem: { + detail?: string; + display_name: string; + kind: string; + name: string; + status: string; + }; + ReadinessResponse: { + items: { + [key: string]: components["schemas"]["ReadinessItem"]; + }; + }; + RigActionBody: { + /** @description Action that was performed. */ + action: string; + /** @description Agents that failed to stop (restart only). */ + failed?: string[] | null; + /** @description Agents that were killed (restart only). */ + killed?: string[] | null; + /** @description Rig name. */ + rig: string; + /** + * @description Operation result (ok, partial, failed). + * @example ok + */ + status: string; + }; + RigCreateInputBody: { + /** @description Rig name. */ + name: string; + /** @description Filesystem path. */ + path: string; + /** @description Session name prefix. */ + prefix?: string; + }; + RigCreatedOutputBody: { + /** @description Created rig name. */ + rig: string; + /** + * @description Operation result. + * @example created + */ + status: string; + }; + RigPatch: { + Name: string; + Path: string | null; + Prefix: string | null; + Suspended: boolean | null; + }; + RigPatchSetInputBody: { + /** @description Rig name. */ + name?: string; + /** @description Override filesystem path. */ + path?: string; + /** @description Override bead ID prefix. */ + prefix?: string; + /** @description Override suspended state. */ + suspended?: boolean; + }; + RigResponse: { + /** Format: int64 */ + agent_count: number; + git?: components["schemas"]["GitStatus"]; + /** Format: date-time */ + last_activity?: string; + name: string; + path: string; + prefix?: string; + /** Format: int64 */ + running_count: number; + suspended: boolean; + }; + RigUpdateInputBody: { + /** @description Filesystem path. */ + path?: string; + /** @description Session name prefix. */ + prefix?: string; + /** @description Whether rig is suspended. */ + suspended?: boolean; + }; + ScopeGroup: Record; + ServiceRestartOutputBody: { + /** + * @description Action performed. + * @example restart + */ + action: string; + /** @description Service name. */ + service: string; + /** + * @description Operation result. + * @example ok + */ + status: string; + }; + SessionActivityEvent: { + /** + * @description Session activity state: 'idle' or 'in-turn'. + * @example idle + */ + activity: string; + }; + SessionAgentGetResponse: { + messages: unknown[] | null; + status?: string; + }; + SessionAgentListResponse: { + agents: components["schemas"]["AgentMapping"][] | null; + }; + SessionBindingRecord: { + /** Format: int64 */ + BindingGeneration: number; + /** Format: date-time */ + BoundAt: string; + Conversation: components["schemas"]["ConversationRef"]; + /** Format: date-time */ + ExpiresAt: string | null; + ID: string; + Metadata: { + [key: string]: string; + }; + /** Format: int64 */ + SchemaVersion: number; + SessionID: string; + Status: components["schemas"]["BindingStatus"]; + }; + SessionCreateBody: { + /** @description Optional session alias. */ + alias?: string; + /** @description Create session asynchronously (agent only). */ + async?: boolean; + /** @description Session target kind: agent or provider. */ + kind?: string; + /** @description Initial message to send to the session. */ + message?: string; + /** @description Agent or provider name. */ + name?: string; + /** @description Provider/agent option overrides. */ + options?: { + [key: string]: string; + }; + /** @description Opaque project context identifier. */ + project_id?: string; + /** @description Deprecated: use alias. */ + session_name?: string; + /** @description Session title. */ + title?: string; + }; + SessionInfo: { + attached: boolean; + /** Format: date-time */ + last_activity?: string; + name: string; + }; + SessionMessageInputBody: { + /** @description Message text to send. */ + message: string; + }; + SessionMessageOutputBody: { + /** @description Session ID. */ + id: string; + /** + * @description Operation result. + * @example accepted + */ + status: string; + }; + SessionPatchBody: { + /** @description Session alias. Empty string clears the alias. */ + alias?: string; + /** @description Session title. If provided, must be non-empty. */ + title?: string; + }; + SessionPendingResponse: { + pending?: components["schemas"]["PendingInteraction"]; + supported: boolean; + }; + /** + * Session raw transcript frame + * @description Provider-native transcript frame. Gas City forwards the exact JSON the provider wrote to its session log, so the shape is provider-specific and can be any JSON value. The producing provider is identified by the Provider field on the enclosing envelope; consumers dispatch per-provider frame parsing keyed by that identifier. + */ + SessionRawMessageFrame: unknown; + SessionRenameInputBody: { + /** @description New session title. */ + title: string; + }; + SessionRespondInputBody: { + /** @description Response action (e.g. allow, deny). */ + action: string; + /** @description Optional response metadata. */ + metadata?: { + [key: string]: string; + }; + /** @description Pending interaction request ID (optional). */ + request_id?: string; + /** @description Optional response text. */ + text?: string; + }; + SessionRespondOutputBody: { + /** @description Session ID. */ + id: string; + /** + * @description Operation result. + * @example accepted + */ + status: string; + }; + SessionResponse: { + active_bead?: string; + activity?: string; + alias?: string; + attached: boolean; + configured_named_session?: boolean; + /** Format: int64 */ + context_pct?: number; + /** Format: int64 */ + context_window?: number; + created_at: string; + display_name?: string; + id: string; + kind?: string; + last_active?: string; + last_output?: string; + metadata?: { + [key: string]: string; + }; + model?: string; + options?: { + [key: string]: string; + }; + pool?: string; + provider: string; + reason?: string; + rig?: string; + running: boolean; + session_name: string; + state: string; + submission_capabilities?: components["schemas"]["SubmissionCapabilities"]; + template: string; + title: string; + }; + /** + * Session stream lifecycle event + * @description Non-message events emitted on the session SSE stream: activity transitions, pending interactions, and keepalive heartbeats. The concrete variant is identified by the SSE event name. + */ + SessionStreamCommonEvent: components["schemas"]["SessionActivityEvent"] | components["schemas"]["PendingInteraction"] | components["schemas"]["HeartbeatEvent"]; + SessionStreamMessageEvent: { + format: string; + id: string; + pagination?: components["schemas"]["PaginationInfo"]; + /** @description Producing provider identifier (claude, codex, gemini, open-code, etc.). */ + provider: string; + template: string; + turns: components["schemas"]["OutputTurn"][] | null; + }; + SessionStreamRawMessageEvent: { + format: string; + id: string; + /** @description Provider-native transcript frames, emitted verbatim as the provider wrote them. */ + messages: components["schemas"]["SessionRawMessageFrame"][] | null; + pagination?: components["schemas"]["PaginationInfo"]; + /** @description Producing provider identifier (claude, codex, gemini, open-code, etc.). Consumers use this to dispatch per-provider frame parsing. */ + provider: string; + template: string; + }; + SessionSubmitInputBody: { + /** + * @description Submit intent; empty defaults to "default". + * @enum {unknown} + */ + intent?: components["schemas"]["SubmitIntent"]; + /** @description Message text to submit. */ + message: string; + }; + SessionSubmitOutputBody: { + /** @description Session ID. */ + id: string; + /** @description Resolved submit intent. */ + intent: string; + /** @description Whether the message was queued. */ + queued: boolean; + /** + * @description Operation result. + * @example accepted + */ + status: string; + }; + SessionTranscriptGetResponse: { + /** @description conversation, text, or raw. */ + format: string; + id: string; + /** @description Populated for raw format; provider-native frames emitted verbatim as the provider wrote them. */ + messages?: components["schemas"]["SessionRawMessageFrame"][] | null; + pagination?: components["schemas"]["PaginationInfo"]; + /** @description Producing provider identifier (claude, codex, gemini, open-code, etc.). Consumers use this to dispatch per-provider frame parsing. */ + provider: string; + template: string; + /** @description Populated for conversation/text formats. */ + turns?: components["schemas"]["OutputTurn"][] | null; + }; + SlingInputBody: { + /** @description Bead ID to attach a formula to. */ + attached_bead_id?: string; + /** @description Bead ID to sling. */ + bead?: string; + /** @description Formula name for workflow launch. */ + formula?: string; + /** @description Rig name. */ + rig?: string; + /** @description Scope kind (city or rig). */ + scope_kind?: string; + /** @description Scope reference. */ + scope_ref?: string; + /** @description Target agent or pool. */ + target: string; + /** @description Workflow title. */ + title?: string; + /** @description Formula variables. */ + vars?: { + [key: string]: string; + }; + }; + SlingResponse: { + attached_bead_id?: string; + bead?: string; + formula?: string; + mode?: string; + root_bead_id?: string; + status: string; + target: string; + warnings?: string[] | null; + workflow_id?: string; + }; + Status: { + allow_websockets?: boolean; + hostname?: string; + kind?: string; + local_state: string; + mount_path: string; + publication_state: string; + publish_mode: string; + reason?: string; + service_name: string; + state?: string; + state_root: string; + /** Format: date-time */ + updated_at: string; + url?: string; + visibility?: string; + workflow_contract?: string; + }; + StatusAgentCounts: { + /** + * Format: int64 + * @description Number of quarantined agents. + */ + quarantined: number; + /** + * Format: int64 + * @description Number of running agents. + */ + running: number; + /** + * Format: int64 + * @description Number of suspended agents. + */ + suspended: number; + /** + * Format: int64 + * @description Total number of agents. + */ + total: number; + }; + StatusBody: { + /** + * Format: int64 + * @description Total agent count (deprecated, use agents.total). + */ + agent_count: number; + /** @description Agent state counts. */ + agents: components["schemas"]["StatusAgentCounts"]; + /** @description Mail counts. */ + mail: components["schemas"]["StatusMailCounts"]; + /** @description City name. */ + name: string; + /** @description City directory path. */ + path: string; + /** + * Format: int64 + * @description Total rig count (deprecated, use rigs.total). + */ + rig_count: number; + /** @description Rig state counts. */ + rigs: components["schemas"]["StatusRigCounts"]; + /** + * Format: int64 + * @description Number of running agent processes. + */ + running: number; + /** @description Whether the city is suspended. */ + suspended: boolean; + /** + * Format: int64 + * @description Server uptime in seconds. + */ + uptime_sec: number; + /** @description Server version. */ + version?: string; + /** @description Work item counts. */ + work: components["schemas"]["StatusWorkCounts"]; + }; + StatusMailCounts: { + /** + * Format: int64 + * @description Total number of messages. + */ + total: number; + /** + * Format: int64 + * @description Number of unread messages. + */ + unread: number; + }; + StatusRigCounts: { + /** + * Format: int64 + * @description Number of suspended rigs. + */ + suspended: number; + /** + * Format: int64 + * @description Total number of rigs. + */ + total: number; + }; + StatusWorkCounts: { + /** + * Format: int64 + * @description Number of in-progress work items. + */ + in_progress: number; + /** + * Format: int64 + * @description Number of open work items. + */ + open: number; + /** + * Format: int64 + * @description Number of ready work items. + */ + ready: number; + }; + SubmissionCapabilities: { + supports_follow_up: boolean; + supports_interrupt_now: boolean; + }; + /** + * @description Semantic delivery choice for a user message on a session submit request. + * @enum {string} + */ + SubmitIntent: "default" | "follow_up" | "interrupt_now"; + SupervisorCitiesOutputBody: { + /** @description Managed cities with status info. */ + items: components["schemas"]["CityInfo"][] | null; + /** + * Format: int64 + * @description Total count. + */ + total: number; + }; + SupervisorEventListOutputBody: { + items: components["schemas"]["WireTaggedEvent"][] | null; + /** Format: int64 */ + total: number; + }; + SupervisorHealthOutputBody: { + /** + * Format: int64 + * @description Cities currently running. + */ + cities_running: number; + /** + * Format: int64 + * @description Total managed cities. + */ + cities_total: number; + /** @description First-city startup info for single-city deployments. */ + startup?: components["schemas"]["SupervisorStartup"]; + /** @description Health status ("ok"). */ + status: string; + /** + * Format: int64 + * @description Supervisor uptime in seconds. + */ + uptime_sec: number; + /** @description Supervisor version. */ + version: string; + }; + SupervisorStartup: { + /** @description Current phase (when not ready). */ + phase?: string; + /** @description Phases completed so far. */ + phases_completed?: string[] | null; + /** @description True when the city is running. */ + ready: boolean; + }; + TaggedEventStreamEnvelope: { + actor: string; + city: string; + message?: string; + payload?: components["schemas"]["EventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + type: string; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** + * @description Direction of a transcript entry. + * @enum {string} + */ + TranscriptMessageKind: "inbound" | "outbound"; + /** + * @description Provenance of a transcript entry (freshly observed vs. replayed from persisted history). + * @enum {string} + */ + TranscriptProvenance: "live" | "hydrated"; + UnboundEventPayload: { + /** Format: int64 */ + count: number; + session_id: string; + }; + WireEvent: { + actor: string; + message?: string; + payload?: components["schemas"]["EventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + type: string; + }; + WireTaggedEvent: { + actor: string; + city: string; + message?: string; + payload?: components["schemas"]["EventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + type: string; + }; + WorkerOperationEventPayload: { + delivered?: boolean; + /** Format: int64 */ + duration_ms: number; + error?: string; + /** Format: date-time */ + finished_at: string; + op_id: string; + operation: string; + provider?: string; + queued?: boolean; + result: string; + session_id?: string; + session_name?: string; + /** Format: date-time */ + started_at: string; + template?: string; + transport?: string; + }; + WorkflowAttemptSummary: { + /** Format: int64 */ + active_attempt: number; + /** Format: int64 */ + attempt_count: number; + /** Format: int64 */ + max_attempts?: number; + }; + WorkflowBeadResponse: { + assignee?: string; + /** Format: int64 */ + attempt?: number; + id: string; + kind: string; + logical_bead_id?: string; + metadata: { + [key: string]: string; + }; + scope_ref?: string; + status: string; + step_ref?: string; + title: string; + }; + WorkflowDeleteResponse: { + /** + * Format: int64 + * @description Number of beads closed. + */ + closed: number; + /** + * Format: int64 + * @description Number of beads deleted. + */ + deleted: number; + /** @description True when one or more teardown steps failed; Closed/Deleted still reflect what succeeded. */ + partial?: boolean; + /** @description Human-readable errors from failed teardown steps. */ + partial_errors?: string[] | null; + /** @description Workflow ID. */ + workflow_id: string; + }; + WorkflowDepResponse: { + from: string; + kind?: string; + to: string; + }; + WorkflowEventProjection: { + attempt_summary?: components["schemas"]["WorkflowAttemptSummary"]; + bead: components["schemas"]["WorkflowBeadResponse"]; + changed_fields: string[] | null; + /** Format: int64 */ + event_seq: number; + event_ts: string; + event_type: string; + logical_node_id: string; + requires_resync?: boolean; + root_bead_id: string; + root_store_ref: string; + scope_kind: string; + scope_ref: string; + type: string; + watch_generation: string; + workflow_id: string; + /** Format: int64 */ + workflow_seq: number; + }; + WorkflowSnapshotResponse: { + beads: components["schemas"]["WorkflowBeadResponse"][] | null; + deps: components["schemas"]["WorkflowDepResponse"][] | null; + logical_edges: components["schemas"]["WorkflowDepResponse"][] | null; + logical_nodes: components["schemas"]["LogicalNode"][] | null; + partial: boolean; + resolved_root_store: string; + root_bead_id: string; + root_store_ref: string; + scope_groups: components["schemas"]["ScopeGroup"][] | null; + scope_kind: string; + scope_ref: string; + /** Format: int64 */ + snapshot_event_seq?: number; + /** Format: int64 */ + snapshot_version: number; + stores_scanned: string[] | null; + workflow_id: string; + }; + WorkspaceResponse: { + declared_name?: string; + declared_prefix?: string; + name: string; + prefix?: string; + provider?: string; + session_template?: string; + suspended: boolean; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + "get-health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SupervisorHealthOutputBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-cities": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SupervisorCitiesOutputBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CityCreateRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CityCreateResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CityGetResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "patch-v0-city-by-city-name": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CityPatchInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-agent-by-base": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Agent name (unqualified, no rig). */ + base: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AgentResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "delete-v0-city-by-city-name-agent-by-base": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Agent name (unqualified). */ + base: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "patch-v0-city-by-city-name-agent-by-base": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Agent name (unqualified). */ + base: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AgentUpdateInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-agent-by-base-output": { + parameters: { + query?: { + /** @description Number of recent compaction segments to return. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. */ + tail?: string; + /** @description Message UUID cursor for loading older messages. */ + before?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Agent base name. */ + base: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AgentOutputResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "stream-agent-output": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Agent base name. */ + base: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/event-stream": ({ + data: components["schemas"]["HeartbeatEvent"]; + /** + * @description The event name. + * @constant + */ + event: "heartbeat"; + /** @description The event ID. */ + id?: number; + /** @description The retry time in milliseconds. */ + retry?: number; + } | { + data: components["schemas"]["AgentOutputResponse"]; + /** + * @description The event name. + * @constant + */ + event: "turn"; + /** @description The event ID. */ + id?: number; + /** @description The retry time in milliseconds. */ + retry?: number; + })[]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-agent-by-base-by-action": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Agent name (unqualified). */ + base: string; + /** @description Action to perform. */ + action: "suspend" | "resume"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-agent-by-dir-by-base": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Agent directory (rig name). */ + dir: string; + /** @description Agent base name. */ + base: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AgentResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "delete-v0-city-by-city-name-agent-by-dir-by-base": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Agent directory (rig name). */ + dir: string; + /** @description Agent base name. */ + base: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "patch-v0-city-by-city-name-agent-by-dir-by-base": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Agent directory (rig name). */ + dir: string; + /** @description Agent base name. */ + base: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AgentUpdateQualifiedInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-agent-by-dir-by-base-output": { + parameters: { + query?: { + /** @description Number of recent compaction segments to return. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. */ + tail?: string; + /** @description Message UUID cursor for loading older messages. */ + before?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Agent directory (rig name). */ + dir: string; + /** @description Agent base name. */ + base: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AgentOutputResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "stream-agent-output-qualified": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Agent directory (rig name). */ + dir: string; + /** @description Agent base name. */ + base: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/event-stream": ({ + data: components["schemas"]["HeartbeatEvent"]; + /** + * @description The event name. + * @constant + */ + event: "heartbeat"; + /** @description The event ID. */ + id?: number; + /** @description The retry time in milliseconds. */ + retry?: number; + } | { + data: components["schemas"]["AgentOutputResponse"]; + /** + * @description The event name. + * @constant + */ + event: "turn"; + /** @description The event ID. */ + id?: number; + /** @description The retry time in milliseconds. */ + retry?: number; + })[]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-agent-by-dir-by-base-by-action": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Agent directory (rig name). */ + dir: string; + /** @description Agent base name. */ + base: string; + /** @description Action to perform. */ + action: "suspend" | "resume"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-agents": { + parameters: { + query?: { + /** @description Event sequence number; when provided, blocks until a newer event arrives. */ + index?: string; + /** @description How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. */ + wait?: string; + /** @description Filter by pool name. */ + pool?: string; + /** @description Filter by rig name. */ + rig?: string; + /** @description Filter by running state. Omit to return all agents. */ + running?: "true" | "false"; + /** @description Include last output preview. */ + peek?: boolean; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListBodyAgentResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "create-agent": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AgentCreateInputBody"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AgentCreatedOutputBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-bead-by-id": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Bead ID. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Bead"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "delete-v0-city-by-city-name-bead-by-id": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Bead ID. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "patch-v0-city-by-city-name-bead-by-id": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Bead ID. */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BeadUpdateBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-bead-by-id-assign": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Bead ID. */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BeadAssignInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": { + [key: string]: string; + }; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-bead-by-id-close": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Bead ID. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-bead-by-id-deps": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Bead ID. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BeadDepsResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-bead-by-id-reopen": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Bead ID. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-bead-by-id-update": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Bead ID. */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BeadUpdateBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-beads": { + parameters: { + query?: { + /** @description Event sequence number; when provided, blocks until a newer event arrives. */ + index?: string; + /** @description How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. */ + wait?: string; + /** @description Pagination cursor from a previous response's next_cursor field. */ + cursor?: string; + /** @description Maximum number of results to return. 0 = server default. */ + limit?: number; + /** @description Filter by bead status. */ + status?: string; + /** @description Filter by bead type. */ + type?: string; + /** @description Filter by label. */ + label?: string; + /** @description Filter by assignee. */ + assignee?: string; + /** @description Filter by rig. */ + rig?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListBodyBead"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "create-bead": { + parameters: { + query?: never; + header?: { + /** @description Idempotency key for safe retries. */ + "Idempotency-Key"?: string; + }; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BeadCreateInputBody"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Bead"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-beads-graph-by-root-id": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Root bead ID for the graph. */ + rootID: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BeadGraphResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-beads-ready": { + parameters: { + query?: { + /** @description Event sequence number; when provided, blocks until a newer event arrives. */ + index?: string; + /** @description How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. */ + wait?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListBodyBead"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-config": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ConfigResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-config-explain": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ConfigExplainResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-config-validate": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ConfigValidateOutputBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-convoy-by-id": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Convoy ID. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ConvoyGetResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "delete-v0-city-by-city-name-convoy-by-id": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Convoy ID. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-convoy-by-id-add": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Convoy ID. */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ConvoyAddInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-convoy-by-id-check": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Convoy ID. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ConvoyCheckResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-convoy-by-id-close": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Convoy ID. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-convoy-by-id-remove": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Convoy ID. */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ConvoyRemoveInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-convoys": { + parameters: { + query?: { + /** @description Event sequence number; when provided, blocks until a newer event arrives. */ + index?: string; + /** @description How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. */ + wait?: string; + /** @description Pagination cursor from a previous response's next_cursor field. */ + cursor?: string; + /** @description Maximum number of results to return. 0 = server default. */ + limit?: number; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListBodyBead"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "create-convoy": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ConvoyCreateInputBody"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Bead"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-events": { + parameters: { + query?: { + /** @description Event sequence number; when provided, blocks until a newer event arrives. */ + index?: string; + /** @description How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. */ + wait?: string; + /** @description Pagination cursor from a previous response's next_cursor field. */ + cursor?: string; + /** @description Maximum number of results to return. 0 = server default. */ + limit?: number; + /** @description Filter by event type. */ + type?: string; + /** @description Filter by actor. */ + actor?: string; + /** @description Filter events since duration ago (Go duration string, e.g. 5m). */ + since?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListBodyWireEvent"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "emit-event": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["EventEmitRequest"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["EventEmitOutputBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "stream-events": { + parameters: { + query?: { + /** @description Reconnect position: only deliver events after this sequence number. */ + after_seq?: string; + }; + header?: { + /** @description SSE reconnect position from the last received event ID. */ + "Last-Event-ID"?: string; + }; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/event-stream": ({ + data: components["schemas"]["EventStreamEnvelope"]; + /** + * @description The event name. + * @constant + */ + event: "event"; + /** @description The event ID. */ + id?: number; + /** @description The retry time in milliseconds. */ + retry?: number; + } | { + data: components["schemas"]["HeartbeatEvent"]; + /** + * @description The event name. + * @constant + */ + event: "heartbeat"; + /** @description The event ID. */ + id?: number; + /** @description The retry time in milliseconds. */ + retry?: number; + })[]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-extmsg-adapters": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListBodyExtmsgAdapterInfo"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "register-extmsg-adapter": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExtMsgAdapterRegisterInputBody"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExtMsgAdapterRegisterOutputBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "delete-v0-city-by-city-name-extmsg-adapters": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExtMsgAdapterUnregisterInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-extmsg-bind": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExtMsgBindInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SessionBindingRecord"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-extmsg-bindings": { + parameters: { + query?: { + /** @description Session ID to list bindings for. */ + session_id?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListBodySessionBindingRecord"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-extmsg-groups": { + parameters: { + query?: { + /** @description Scope ID. */ + scope_id?: string; + /** @description Provider name. */ + provider?: string; + /** @description Account ID. */ + account_id?: string; + /** @description Conversation ID. */ + conversation_id?: string; + /** @description Conversation kind. */ + kind?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ConversationGroupRecord"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "ensure-extmsg-group": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExtMsgGroupEnsureInputBody"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ConversationGroupRecord"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-extmsg-inbound": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExtMsgInboundInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["InboundResult"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-extmsg-outbound": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExtMsgOutboundInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OutboundResult"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-extmsg-participants": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExtMsgParticipantUpsertInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ConversationGroupParticipant"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "delete-v0-city-by-city-name-extmsg-participants": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExtMsgParticipantRemoveInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-extmsg-transcript": { + parameters: { + query?: { + /** @description Scope ID. */ + scope_id?: string; + /** @description Provider name. */ + provider?: string; + /** @description Account ID. */ + account_id?: string; + /** @description Conversation ID. */ + conversation_id?: string; + /** @description Parent conversation ID. */ + parent_conversation_id?: string; + /** @description Conversation kind. */ + kind?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListBodyConversationTranscriptRecord"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-extmsg-transcript-ack": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExtMsgTranscriptAckInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-extmsg-unbind": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExtMsgUnbindInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExtMsgUnbindBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-formula-by-name": { + parameters: { + query: { + /** @description Scope kind (city or rig). */ + scope_kind?: string; + /** @description Scope reference. */ + scope_ref?: string; + /** @description Target agent for preview compilation. */ + target: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Formula name. */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FormulaDetailResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-formulas": { + parameters: { + query?: { + /** @description Scope kind (city or rig). */ + scope_kind?: string; + /** @description Scope reference. */ + scope_ref?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FormulaListBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-formulas-feed": { + parameters: { + query?: { + /** @description Scope kind (city or rig). */ + scope_kind?: string; + /** @description Scope reference. */ + scope_ref?: string; + /** @description Maximum number of feed items to return. 0 = default. */ + limit?: number; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FormulaFeedBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-formulas-by-name": { + parameters: { + query: { + /** @description Scope kind (city or rig). */ + scope_kind?: string; + /** @description Scope reference. */ + scope_ref?: string; + /** @description Target agent for preview compilation. */ + target: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Formula name. */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FormulaDetailResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-formulas-by-name-preview": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Formula name. */ + name: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FormulaPreviewBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FormulaDetailResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-formulas-by-name-runs": { + parameters: { + query?: { + /** @description Scope kind (city or rig). */ + scope_kind?: string; + /** @description Scope reference. */ + scope_ref?: string; + /** @description Maximum number of recent runs to return. 0 = default. */ + limit?: number; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Formula name. */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FormulaRunsResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-health": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HealthOutputBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-mail": { + parameters: { + query?: { + /** @description Event sequence number; when provided, blocks until a newer event arrives. */ + index?: string; + /** @description How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. */ + wait?: string; + /** @description Pagination cursor from a previous response's next_cursor field. */ + cursor?: string; + /** @description Maximum number of results to return. 0 = server default. */ + limit?: number; + /** @description Filter by agent name. */ + agent?: string; + /** @description Filter by status (unread, all). */ + status?: string; + /** @description Filter by rig name. */ + rig?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MailListBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "send-mail": { + parameters: { + query?: never; + header?: { + /** @description Idempotency key for safe retries. */ + "Idempotency-Key"?: string; + }; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MailSendInputBody"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Message"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-mail-count": { + parameters: { + query?: { + /** @description Filter by agent name. */ + agent?: string; + /** @description Filter by rig name. */ + rig?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MailCountOutputBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-mail-thread-by-id": { + parameters: { + query?: { + /** @description Filter by rig. */ + rig?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Thread ID. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MailListBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-mail-by-id": { + parameters: { + query?: { + /** @description Rig hint for O(1) lookup. */ + rig?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Message ID. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Message"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "delete-v0-city-by-city-name-mail-by-id": { + parameters: { + query?: { + /** @description Rig hint. */ + rig?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Message ID. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-mail-by-id-archive": { + parameters: { + query?: { + /** @description Rig hint. */ + rig?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Message ID. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-mail-by-id-mark-unread": { + parameters: { + query?: { + /** @description Rig hint. */ + rig?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Message ID. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-mail-by-id-read": { + parameters: { + query?: { + /** @description Rig hint. */ + rig?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Message ID. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "reply-mail": { + parameters: { + query?: { + /** @description Rig hint. */ + rig?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Message ID. */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MailReplyInputBody"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Message"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-order-history-by-bead-id": { + parameters: { + query?: { + /** @description Store reference for disambiguating store-local bead IDs. */ + store_ref?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Bead ID for the order run. */ + bead_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OrderHistoryDetailResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-order-by-name": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Order name or scoped name. */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OrderResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-order-by-name-disable": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Order name or scoped name. */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-order-by-name-enable": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Order name or scoped name. */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-orders": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OrderListBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-orders-check": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OrderCheckListBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-orders-feed": { + parameters: { + query?: { + /** @description Scope kind (city or rig). */ + scope_kind?: string; + /** @description Scope reference. */ + scope_ref?: string; + /** @description Maximum number of feed items to return. */ + limit?: number; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OrdersFeedBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-orders-history": { + parameters: { + query: { + /** @description Scoped order name. */ + scoped_name: string; + /** @description Maximum number of history entries. 0 = default. */ + limit?: number; + /** @description Return entries before this RFC3339 timestamp. */ + before?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OrderHistoryListBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-packs": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PackListBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-patches-agent-by-base": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Agent patch name (unqualified). */ + base: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AgentPatch"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "delete-v0-city-by-city-name-patches-agent-by-base": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Agent patch name (unqualified). */ + base: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PatchDeletedResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-patches-agent-by-dir-by-base": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Agent directory (rig name). */ + dir: string; + /** @description Agent base name. */ + base: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AgentPatch"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "delete-v0-city-by-city-name-patches-agent-by-dir-by-base": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Agent directory (rig name). */ + dir: string; + /** @description Agent base name. */ + base: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PatchDeletedResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-patches-agents": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListBodyAgentPatch"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "put-v0-city-by-city-name-patches-agents": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AgentPatchSetInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PatchOKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-patches-provider-by-name": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Provider patch name. */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProviderPatch"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "delete-v0-city-by-city-name-patches-provider-by-name": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Provider patch name. */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PatchDeletedResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-patches-providers": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListBodyProviderPatch"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "put-v0-city-by-city-name-patches-providers": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ProviderPatchSetInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PatchOKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-patches-rig-by-name": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Rig patch name. */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RigPatch"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "delete-v0-city-by-city-name-patches-rig-by-name": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Rig patch name. */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PatchDeletedResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-patches-rigs": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListBodyRigPatch"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "put-v0-city-by-city-name-patches-rigs": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RigPatchSetInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PatchOKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-provider-readiness": { + parameters: { + query?: { + /** @description Comma-separated provider names to check (default: claude,codex,gemini). */ + providers?: string; + /** @description Force fresh probe, bypassing cache. */ + fresh?: boolean; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProviderReadinessResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-provider-by-name": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Provider name. */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProviderResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "delete-v0-city-by-city-name-provider-by-name": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Provider name. */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "patch-v0-city-by-city-name-provider-by-name": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Provider name. */ + name: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ProviderUpdateInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-providers": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListBodyProviderResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "create-provider": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ProviderCreateInputBody"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProviderCreatedOutputBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-providers-public": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProviderPublicListBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-readiness": { + parameters: { + query?: { + /** @description Comma-separated readiness items to check (default: claude,codex,gemini,github_cli). */ + items?: string; + /** @description Force fresh probe, bypassing cache. */ + fresh?: boolean; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReadinessResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-rig-by-name": { + parameters: { + query?: { + /** @description Include git status. */ + git?: boolean; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Rig name. */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RigResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "delete-v0-city-by-city-name-rig-by-name": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Rig name. */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "patch-v0-city-by-city-name-rig-by-name": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Rig name. */ + name: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RigUpdateInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-rig-by-name-by-action": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Rig name. */ + name: string; + /** @description Action to perform (suspend, resume, restart). */ + action: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RigActionBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-rigs": { + parameters: { + query?: { + /** @description Event sequence number; when provided, blocks until a newer event arrives. */ + index?: string; + /** @description How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. */ + wait?: string; + /** @description Include git status. */ + git?: boolean; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListBodyRigResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "create-rig": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RigCreateInputBody"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RigCreatedOutputBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-service-by-name": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Service name. */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-service-by-name-restart": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Service name. */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ServiceRestartOutputBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-services": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListBodyStatus"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-session-by-id": { + parameters: { + query?: { + /** @description Include last output preview. */ + peek?: boolean; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Session ID, alias, or runtime session_name. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SessionResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "patch-v0-city-by-city-name-session-by-id": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Session ID, alias, or runtime session_name. */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SessionPatchBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SessionResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-session-by-id-agents": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Session ID, alias, or runtime session_name. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SessionAgentListResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-session-by-id-agents-by-agent-id": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Session ID, alias, or runtime session_name. */ + id: string; + /** @description Subagent ID within the session. */ + agentId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SessionAgentGetResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-session-by-id-close": { + parameters: { + query?: { + /** @description Permanently delete bead after closing. */ + delete?: boolean; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Session ID, alias, or runtime session_name. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-session-by-id-kill": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Session ID, alias, or runtime session_name. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKWithIDResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "send-session-message": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Session ID, alias, or runtime session_name. */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SessionMessageInputBody"]; + }; + }; + responses: { + /** @description Accepted */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SessionMessageOutputBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-session-by-id-pending": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Session ID, alias, or runtime session_name. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SessionPendingResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-session-by-id-rename": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Session ID, alias, or runtime session_name. */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SessionRenameInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SessionResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "respond-session": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Session ID, alias, or runtime session_name. */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SessionRespondInputBody"]; + }; + }; + responses: { + /** @description Accepted */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SessionRespondOutputBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-session-by-id-stop": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Session ID, alias, or runtime session_name. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKWithIDResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "stream-session": { + parameters: { + query?: { + /** @description Transcript format: conversation (default) or raw. */ + format?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Session ID, alias, or runtime session_name. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/event-stream": ({ + data: components["schemas"]["SessionActivityEvent"]; + /** + * @description The event name. + * @constant + */ + event: "activity"; + /** @description The event ID. */ + id?: number; + /** @description The retry time in milliseconds. */ + retry?: number; + } | { + data: components["schemas"]["HeartbeatEvent"]; + /** + * @description The event name. + * @constant + */ + event: "heartbeat"; + /** @description The event ID. */ + id?: number; + /** @description The retry time in milliseconds. */ + retry?: number; + } | { + data: components["schemas"]["SessionStreamRawMessageEvent"]; + /** + * @description The event name. + * @constant + */ + event?: "message"; + /** @description The event ID. */ + id?: number; + /** @description The retry time in milliseconds. */ + retry?: number; + } | { + data: components["schemas"]["PendingInteraction"]; + /** + * @description The event name. + * @constant + */ + event: "pending"; + /** @description The event ID. */ + id?: number; + /** @description The retry time in milliseconds. */ + retry?: number; + } | { + data: components["schemas"]["SessionStreamMessageEvent"]; + /** + * @description The event name. + * @constant + */ + event: "turn"; + /** @description The event ID. */ + id?: number; + /** @description The retry time in milliseconds. */ + retry?: number; + })[]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "submit-session": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Session ID, alias, or runtime session_name. */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SessionSubmitInputBody"]; + }; + }; + responses: { + /** @description Accepted */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SessionSubmitOutputBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-session-by-id-suspend": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Session ID, alias, or runtime session_name. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-session-by-id-transcript": { + parameters: { + query?: { + /** @description Number of recent compaction segments to return. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. */ + tail?: string; + /** @description Transcript format: conversation (default) or raw. */ + format?: string; + /** @description Pagination cursor: return entries before this UUID. */ + before?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Session ID, alias, or runtime session_name. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SessionTranscriptGetResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-session-by-id-wake": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Session ID, alias, or runtime session_name. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OKWithIDResponseBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-sessions": { + parameters: { + query?: { + /** @description Pagination cursor from a previous response's next_cursor field. */ + cursor?: string; + /** @description Maximum number of results to return. 0 = server default. */ + limit?: number; + /** @description Filter by session state (e.g. active, closed). */ + state?: string; + /** @description Filter by session template (agent qualified name). */ + template?: string; + /** @description Include last output preview. */ + peek?: boolean; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListBodySessionResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "create-session": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SessionCreateBody"]; + }; + }; + responses: { + /** @description Accepted */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SessionResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-sling": { + parameters: { + query?: never; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SlingInputBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SlingResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-status": { + parameters: { + query?: { + /** @description Event sequence number; when provided, blocks until a newer event arrives. */ + index?: string; + /** @description How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. */ + wait?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StatusBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-city-by-city-name-workflow-by-workflow-id": { + parameters: { + query?: { + /** @description Scope kind (city or rig). */ + scope_kind?: string; + /** @description Scope reference. */ + scope_ref?: string; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Workflow (convoy) ID. */ + workflow_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "X-GC-Index"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WorkflowSnapshotResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "delete-v0-city-by-city-name-workflow-by-workflow-id": { + parameters: { + query?: { + /** @description Scope kind (city or rig). */ + scope_kind?: string; + /** @description Scope reference. */ + scope_ref?: string; + /** @description Permanently delete beads from store. */ + delete?: boolean; + }; + header?: never; + path: { + /** @description City name. */ + cityName: string; + /** @description Workflow (convoy) ID. */ + workflow_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WorkflowDeleteResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-events": { + parameters: { + query?: { + /** @description Filter by event type. */ + type?: string; + /** @description Filter by actor. */ + actor?: string; + /** @description Filter to events within the last Go duration (e.g. "5m"). */ + since?: string; + /** @description Maximum number of trailing events to return. 0 = no limit. Used by 'gc events --seq' to compute the head cursor cheaply. */ + limit?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SupervisorEventListOutputBody"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "stream-supervisor-events": { + parameters: { + query?: { + /** @description Alternative to Last-Event-ID for browsers that can't set custom headers. */ + after_cursor?: string; + }; + header?: { + /** @description Reconnect cursor (composite per-city cursor). */ + "Last-Event-ID"?: string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/event-stream": ({ + data: components["schemas"]["HeartbeatEvent"]; + /** + * @description The event name. + * @constant + */ + event: "heartbeat"; + /** @description The event ID (composite cursor). */ + id?: string; + /** @description The retry time in milliseconds. */ + retry?: number; + } | { + data: components["schemas"]["TaggedEventStreamEnvelope"]; + /** + * @description The event name. + * @constant + */ + event: "tagged_event"; + /** @description The event ID (composite cursor). */ + id?: string; + /** @description The retry time in milliseconds. */ + retry?: number; + })[]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-provider-readiness": { + parameters: { + query?: { + /** @description Comma-separated list of providers to probe. */ + providers?: string; + /** @description Force fresh probe, bypassing cache. */ + fresh?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProviderReadinessResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "get-v0-readiness": { + parameters: { + query?: { + /** @description Comma-separated list of readiness items to check. */ + items?: string; + /** @description Force fresh probe, bypassing cache. */ + fresh?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReadinessResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; +} diff --git a/cmd/gc/dashboard/web/src/generated/sdk.gen.ts b/cmd/gc/dashboard/web/src/generated/sdk.gen.ts new file mode 100644 index 0000000000..97eb4dced9 --- /dev/null +++ b/cmd/gc/dashboard/web/src/generated/sdk.gen.ts @@ -0,0 +1,1017 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Client, Options as Options2, TDataShape } from './client'; +import { client } from './client.gen'; +import type { CreateAgentData, CreateAgentErrors, CreateAgentResponses, CreateBeadData, CreateBeadErrors, CreateBeadResponses, CreateConvoyData, CreateConvoyErrors, CreateConvoyResponses, CreateProviderData, CreateProviderErrors, CreateProviderResponses, CreateRigData, CreateRigErrors, CreateRigResponses, CreateSessionData, CreateSessionErrors, CreateSessionResponses, DeleteV0CityByCityNameAgentByBaseData, DeleteV0CityByCityNameAgentByBaseErrors, DeleteV0CityByCityNameAgentByBaseResponses, DeleteV0CityByCityNameAgentByDirByBaseData, DeleteV0CityByCityNameAgentByDirByBaseErrors, DeleteV0CityByCityNameAgentByDirByBaseResponses, DeleteV0CityByCityNameBeadByIdData, DeleteV0CityByCityNameBeadByIdErrors, DeleteV0CityByCityNameBeadByIdResponses, DeleteV0CityByCityNameConvoyByIdData, DeleteV0CityByCityNameConvoyByIdErrors, DeleteV0CityByCityNameConvoyByIdResponses, DeleteV0CityByCityNameExtmsgAdaptersData, DeleteV0CityByCityNameExtmsgAdaptersErrors, DeleteV0CityByCityNameExtmsgAdaptersResponses, DeleteV0CityByCityNameExtmsgParticipantsData, DeleteV0CityByCityNameExtmsgParticipantsErrors, DeleteV0CityByCityNameExtmsgParticipantsResponses, DeleteV0CityByCityNameMailByIdData, DeleteV0CityByCityNameMailByIdErrors, DeleteV0CityByCityNameMailByIdResponses, DeleteV0CityByCityNamePatchesAgentByBaseData, DeleteV0CityByCityNamePatchesAgentByBaseErrors, DeleteV0CityByCityNamePatchesAgentByBaseResponses, DeleteV0CityByCityNamePatchesAgentByDirByBaseData, DeleteV0CityByCityNamePatchesAgentByDirByBaseErrors, DeleteV0CityByCityNamePatchesAgentByDirByBaseResponses, DeleteV0CityByCityNamePatchesProviderByNameData, DeleteV0CityByCityNamePatchesProviderByNameErrors, DeleteV0CityByCityNamePatchesProviderByNameResponses, DeleteV0CityByCityNamePatchesRigByNameData, DeleteV0CityByCityNamePatchesRigByNameErrors, DeleteV0CityByCityNamePatchesRigByNameResponses, DeleteV0CityByCityNameProviderByNameData, DeleteV0CityByCityNameProviderByNameErrors, DeleteV0CityByCityNameProviderByNameResponses, DeleteV0CityByCityNameRigByNameData, DeleteV0CityByCityNameRigByNameErrors, DeleteV0CityByCityNameRigByNameResponses, DeleteV0CityByCityNameWorkflowByWorkflowIdData, DeleteV0CityByCityNameWorkflowByWorkflowIdErrors, DeleteV0CityByCityNameWorkflowByWorkflowIdResponses, EmitEventData, EmitEventErrors, EmitEventResponses, EnsureExtmsgGroupData, EnsureExtmsgGroupErrors, EnsureExtmsgGroupResponses, GetHealthData, GetHealthErrors, GetHealthResponses, GetV0CitiesData, GetV0CitiesErrors, GetV0CitiesResponses, GetV0CityByCityNameAgentByBaseData, GetV0CityByCityNameAgentByBaseErrors, GetV0CityByCityNameAgentByBaseOutputData, GetV0CityByCityNameAgentByBaseOutputErrors, GetV0CityByCityNameAgentByBaseOutputResponses, GetV0CityByCityNameAgentByBaseResponses, GetV0CityByCityNameAgentByDirByBaseData, GetV0CityByCityNameAgentByDirByBaseErrors, GetV0CityByCityNameAgentByDirByBaseOutputData, GetV0CityByCityNameAgentByDirByBaseOutputErrors, GetV0CityByCityNameAgentByDirByBaseOutputResponses, GetV0CityByCityNameAgentByDirByBaseResponses, GetV0CityByCityNameAgentsData, GetV0CityByCityNameAgentsErrors, GetV0CityByCityNameAgentsResponses, GetV0CityByCityNameBeadByIdData, GetV0CityByCityNameBeadByIdDepsData, GetV0CityByCityNameBeadByIdDepsErrors, GetV0CityByCityNameBeadByIdDepsResponses, GetV0CityByCityNameBeadByIdErrors, GetV0CityByCityNameBeadByIdResponses, GetV0CityByCityNameBeadsData, GetV0CityByCityNameBeadsErrors, GetV0CityByCityNameBeadsGraphByRootIdData, GetV0CityByCityNameBeadsGraphByRootIdErrors, GetV0CityByCityNameBeadsGraphByRootIdResponses, GetV0CityByCityNameBeadsReadyData, GetV0CityByCityNameBeadsReadyErrors, GetV0CityByCityNameBeadsReadyResponses, GetV0CityByCityNameBeadsResponses, GetV0CityByCityNameConfigData, GetV0CityByCityNameConfigErrors, GetV0CityByCityNameConfigExplainData, GetV0CityByCityNameConfigExplainErrors, GetV0CityByCityNameConfigExplainResponses, GetV0CityByCityNameConfigResponses, GetV0CityByCityNameConfigValidateData, GetV0CityByCityNameConfigValidateErrors, GetV0CityByCityNameConfigValidateResponses, GetV0CityByCityNameConvoyByIdCheckData, GetV0CityByCityNameConvoyByIdCheckErrors, GetV0CityByCityNameConvoyByIdCheckResponses, GetV0CityByCityNameConvoyByIdData, GetV0CityByCityNameConvoyByIdErrors, GetV0CityByCityNameConvoyByIdResponses, GetV0CityByCityNameConvoysData, GetV0CityByCityNameConvoysErrors, GetV0CityByCityNameConvoysResponses, GetV0CityByCityNameData, GetV0CityByCityNameErrors, GetV0CityByCityNameEventsData, GetV0CityByCityNameEventsErrors, GetV0CityByCityNameEventsResponses, GetV0CityByCityNameExtmsgAdaptersData, GetV0CityByCityNameExtmsgAdaptersErrors, GetV0CityByCityNameExtmsgAdaptersResponses, GetV0CityByCityNameExtmsgBindingsData, GetV0CityByCityNameExtmsgBindingsErrors, GetV0CityByCityNameExtmsgBindingsResponses, GetV0CityByCityNameExtmsgGroupsData, GetV0CityByCityNameExtmsgGroupsErrors, GetV0CityByCityNameExtmsgGroupsResponses, GetV0CityByCityNameExtmsgTranscriptData, GetV0CityByCityNameExtmsgTranscriptErrors, GetV0CityByCityNameExtmsgTranscriptResponses, GetV0CityByCityNameFormulaByNameData, GetV0CityByCityNameFormulaByNameErrors, GetV0CityByCityNameFormulaByNameResponses, GetV0CityByCityNameFormulasByNameData, GetV0CityByCityNameFormulasByNameErrors, GetV0CityByCityNameFormulasByNameResponses, GetV0CityByCityNameFormulasByNameRunsData, GetV0CityByCityNameFormulasByNameRunsErrors, GetV0CityByCityNameFormulasByNameRunsResponses, GetV0CityByCityNameFormulasData, GetV0CityByCityNameFormulasErrors, GetV0CityByCityNameFormulasFeedData, GetV0CityByCityNameFormulasFeedErrors, GetV0CityByCityNameFormulasFeedResponses, GetV0CityByCityNameFormulasResponses, GetV0CityByCityNameHealthData, GetV0CityByCityNameHealthErrors, GetV0CityByCityNameHealthResponses, GetV0CityByCityNameMailByIdData, GetV0CityByCityNameMailByIdErrors, GetV0CityByCityNameMailByIdResponses, GetV0CityByCityNameMailCountData, GetV0CityByCityNameMailCountErrors, GetV0CityByCityNameMailCountResponses, GetV0CityByCityNameMailData, GetV0CityByCityNameMailErrors, GetV0CityByCityNameMailResponses, GetV0CityByCityNameMailThreadByIdData, GetV0CityByCityNameMailThreadByIdErrors, GetV0CityByCityNameMailThreadByIdResponses, GetV0CityByCityNameOrderByNameData, GetV0CityByCityNameOrderByNameErrors, GetV0CityByCityNameOrderByNameResponses, GetV0CityByCityNameOrderHistoryByBeadIdData, GetV0CityByCityNameOrderHistoryByBeadIdErrors, GetV0CityByCityNameOrderHistoryByBeadIdResponses, GetV0CityByCityNameOrdersCheckData, GetV0CityByCityNameOrdersCheckErrors, GetV0CityByCityNameOrdersCheckResponses, GetV0CityByCityNameOrdersData, GetV0CityByCityNameOrdersErrors, GetV0CityByCityNameOrdersFeedData, GetV0CityByCityNameOrdersFeedErrors, GetV0CityByCityNameOrdersFeedResponses, GetV0CityByCityNameOrdersHistoryData, GetV0CityByCityNameOrdersHistoryErrors, GetV0CityByCityNameOrdersHistoryResponses, GetV0CityByCityNameOrdersResponses, GetV0CityByCityNamePacksData, GetV0CityByCityNamePacksErrors, GetV0CityByCityNamePacksResponses, GetV0CityByCityNamePatchesAgentByBaseData, GetV0CityByCityNamePatchesAgentByBaseErrors, GetV0CityByCityNamePatchesAgentByBaseResponses, GetV0CityByCityNamePatchesAgentByDirByBaseData, GetV0CityByCityNamePatchesAgentByDirByBaseErrors, GetV0CityByCityNamePatchesAgentByDirByBaseResponses, GetV0CityByCityNamePatchesAgentsData, GetV0CityByCityNamePatchesAgentsErrors, GetV0CityByCityNamePatchesAgentsResponses, GetV0CityByCityNamePatchesProviderByNameData, GetV0CityByCityNamePatchesProviderByNameErrors, GetV0CityByCityNamePatchesProviderByNameResponses, GetV0CityByCityNamePatchesProvidersData, GetV0CityByCityNamePatchesProvidersErrors, GetV0CityByCityNamePatchesProvidersResponses, GetV0CityByCityNamePatchesRigByNameData, GetV0CityByCityNamePatchesRigByNameErrors, GetV0CityByCityNamePatchesRigByNameResponses, GetV0CityByCityNamePatchesRigsData, GetV0CityByCityNamePatchesRigsErrors, GetV0CityByCityNamePatchesRigsResponses, GetV0CityByCityNameProviderByNameData, GetV0CityByCityNameProviderByNameErrors, GetV0CityByCityNameProviderByNameResponses, GetV0CityByCityNameProviderReadinessData, GetV0CityByCityNameProviderReadinessErrors, GetV0CityByCityNameProviderReadinessResponses, GetV0CityByCityNameProvidersData, GetV0CityByCityNameProvidersErrors, GetV0CityByCityNameProvidersPublicData, GetV0CityByCityNameProvidersPublicErrors, GetV0CityByCityNameProvidersPublicResponses, GetV0CityByCityNameProvidersResponses, GetV0CityByCityNameReadinessData, GetV0CityByCityNameReadinessErrors, GetV0CityByCityNameReadinessResponses, GetV0CityByCityNameResponses, GetV0CityByCityNameRigByNameData, GetV0CityByCityNameRigByNameErrors, GetV0CityByCityNameRigByNameResponses, GetV0CityByCityNameRigsData, GetV0CityByCityNameRigsErrors, GetV0CityByCityNameRigsResponses, GetV0CityByCityNameServiceByNameData, GetV0CityByCityNameServiceByNameErrors, GetV0CityByCityNameServiceByNameResponses, GetV0CityByCityNameServicesData, GetV0CityByCityNameServicesErrors, GetV0CityByCityNameServicesResponses, GetV0CityByCityNameSessionByIdAgentsByAgentIdData, GetV0CityByCityNameSessionByIdAgentsByAgentIdErrors, GetV0CityByCityNameSessionByIdAgentsByAgentIdResponses, GetV0CityByCityNameSessionByIdAgentsData, GetV0CityByCityNameSessionByIdAgentsErrors, GetV0CityByCityNameSessionByIdAgentsResponses, GetV0CityByCityNameSessionByIdData, GetV0CityByCityNameSessionByIdErrors, GetV0CityByCityNameSessionByIdPendingData, GetV0CityByCityNameSessionByIdPendingErrors, GetV0CityByCityNameSessionByIdPendingResponses, GetV0CityByCityNameSessionByIdResponses, GetV0CityByCityNameSessionByIdTranscriptData, GetV0CityByCityNameSessionByIdTranscriptErrors, GetV0CityByCityNameSessionByIdTranscriptResponses, GetV0CityByCityNameSessionsData, GetV0CityByCityNameSessionsErrors, GetV0CityByCityNameSessionsResponses, GetV0CityByCityNameStatusData, GetV0CityByCityNameStatusErrors, GetV0CityByCityNameStatusResponses, GetV0CityByCityNameWorkflowByWorkflowIdData, GetV0CityByCityNameWorkflowByWorkflowIdErrors, GetV0CityByCityNameWorkflowByWorkflowIdResponses, GetV0EventsData, GetV0EventsErrors, GetV0EventsResponses, GetV0ProviderReadinessData, GetV0ProviderReadinessErrors, GetV0ProviderReadinessResponses, GetV0ReadinessData, GetV0ReadinessErrors, GetV0ReadinessResponses, PatchV0CityByCityNameAgentByBaseData, PatchV0CityByCityNameAgentByBaseErrors, PatchV0CityByCityNameAgentByBaseResponses, PatchV0CityByCityNameAgentByDirByBaseData, PatchV0CityByCityNameAgentByDirByBaseErrors, PatchV0CityByCityNameAgentByDirByBaseResponses, PatchV0CityByCityNameBeadByIdData, PatchV0CityByCityNameBeadByIdErrors, PatchV0CityByCityNameBeadByIdResponses, PatchV0CityByCityNameData, PatchV0CityByCityNameErrors, PatchV0CityByCityNameProviderByNameData, PatchV0CityByCityNameProviderByNameErrors, PatchV0CityByCityNameProviderByNameResponses, PatchV0CityByCityNameResponses, PatchV0CityByCityNameRigByNameData, PatchV0CityByCityNameRigByNameErrors, PatchV0CityByCityNameRigByNameResponses, PatchV0CityByCityNameSessionByIdData, PatchV0CityByCityNameSessionByIdErrors, PatchV0CityByCityNameSessionByIdResponses, PostV0CityByCityNameAgentByBaseByActionData, PostV0CityByCityNameAgentByBaseByActionErrors, PostV0CityByCityNameAgentByBaseByActionResponses, PostV0CityByCityNameAgentByDirByBaseByActionData, PostV0CityByCityNameAgentByDirByBaseByActionErrors, PostV0CityByCityNameAgentByDirByBaseByActionResponses, PostV0CityByCityNameBeadByIdAssignData, PostV0CityByCityNameBeadByIdAssignErrors, PostV0CityByCityNameBeadByIdAssignResponses, PostV0CityByCityNameBeadByIdCloseData, PostV0CityByCityNameBeadByIdCloseErrors, PostV0CityByCityNameBeadByIdCloseResponses, PostV0CityByCityNameBeadByIdReopenData, PostV0CityByCityNameBeadByIdReopenErrors, PostV0CityByCityNameBeadByIdReopenResponses, PostV0CityByCityNameBeadByIdUpdateData, PostV0CityByCityNameBeadByIdUpdateErrors, PostV0CityByCityNameBeadByIdUpdateResponses, PostV0CityByCityNameConvoyByIdAddData, PostV0CityByCityNameConvoyByIdAddErrors, PostV0CityByCityNameConvoyByIdAddResponses, PostV0CityByCityNameConvoyByIdCloseData, PostV0CityByCityNameConvoyByIdCloseErrors, PostV0CityByCityNameConvoyByIdCloseResponses, PostV0CityByCityNameConvoyByIdRemoveData, PostV0CityByCityNameConvoyByIdRemoveErrors, PostV0CityByCityNameConvoyByIdRemoveResponses, PostV0CityByCityNameExtmsgBindData, PostV0CityByCityNameExtmsgBindErrors, PostV0CityByCityNameExtmsgBindResponses, PostV0CityByCityNameExtmsgInboundData, PostV0CityByCityNameExtmsgInboundErrors, PostV0CityByCityNameExtmsgInboundResponses, PostV0CityByCityNameExtmsgOutboundData, PostV0CityByCityNameExtmsgOutboundErrors, PostV0CityByCityNameExtmsgOutboundResponses, PostV0CityByCityNameExtmsgParticipantsData, PostV0CityByCityNameExtmsgParticipantsErrors, PostV0CityByCityNameExtmsgParticipantsResponses, PostV0CityByCityNameExtmsgTranscriptAckData, PostV0CityByCityNameExtmsgTranscriptAckErrors, PostV0CityByCityNameExtmsgTranscriptAckResponses, PostV0CityByCityNameExtmsgUnbindData, PostV0CityByCityNameExtmsgUnbindErrors, PostV0CityByCityNameExtmsgUnbindResponses, PostV0CityByCityNameFormulasByNamePreviewData, PostV0CityByCityNameFormulasByNamePreviewErrors, PostV0CityByCityNameFormulasByNamePreviewResponses, PostV0CityByCityNameMailByIdArchiveData, PostV0CityByCityNameMailByIdArchiveErrors, PostV0CityByCityNameMailByIdArchiveResponses, PostV0CityByCityNameMailByIdMarkUnreadData, PostV0CityByCityNameMailByIdMarkUnreadErrors, PostV0CityByCityNameMailByIdMarkUnreadResponses, PostV0CityByCityNameMailByIdReadData, PostV0CityByCityNameMailByIdReadErrors, PostV0CityByCityNameMailByIdReadResponses, PostV0CityByCityNameOrderByNameDisableData, PostV0CityByCityNameOrderByNameDisableErrors, PostV0CityByCityNameOrderByNameDisableResponses, PostV0CityByCityNameOrderByNameEnableData, PostV0CityByCityNameOrderByNameEnableErrors, PostV0CityByCityNameOrderByNameEnableResponses, PostV0CityByCityNameRigByNameByActionData, PostV0CityByCityNameRigByNameByActionErrors, PostV0CityByCityNameRigByNameByActionResponses, PostV0CityByCityNameServiceByNameRestartData, PostV0CityByCityNameServiceByNameRestartErrors, PostV0CityByCityNameServiceByNameRestartResponses, PostV0CityByCityNameSessionByIdCloseData, PostV0CityByCityNameSessionByIdCloseErrors, PostV0CityByCityNameSessionByIdCloseResponses, PostV0CityByCityNameSessionByIdKillData, PostV0CityByCityNameSessionByIdKillErrors, PostV0CityByCityNameSessionByIdKillResponses, PostV0CityByCityNameSessionByIdRenameData, PostV0CityByCityNameSessionByIdRenameErrors, PostV0CityByCityNameSessionByIdRenameResponses, PostV0CityByCityNameSessionByIdStopData, PostV0CityByCityNameSessionByIdStopErrors, PostV0CityByCityNameSessionByIdStopResponses, PostV0CityByCityNameSessionByIdSuspendData, PostV0CityByCityNameSessionByIdSuspendErrors, PostV0CityByCityNameSessionByIdSuspendResponses, PostV0CityByCityNameSessionByIdWakeData, PostV0CityByCityNameSessionByIdWakeErrors, PostV0CityByCityNameSessionByIdWakeResponses, PostV0CityByCityNameSlingData, PostV0CityByCityNameSlingErrors, PostV0CityByCityNameSlingResponses, PostV0CityData, PostV0CityErrors, PostV0CityResponses, PutV0CityByCityNamePatchesAgentsData, PutV0CityByCityNamePatchesAgentsErrors, PutV0CityByCityNamePatchesAgentsResponses, PutV0CityByCityNamePatchesProvidersData, PutV0CityByCityNamePatchesProvidersErrors, PutV0CityByCityNamePatchesProvidersResponses, PutV0CityByCityNamePatchesRigsData, PutV0CityByCityNamePatchesRigsErrors, PutV0CityByCityNamePatchesRigsResponses, RegisterExtmsgAdapterData, RegisterExtmsgAdapterErrors, RegisterExtmsgAdapterResponses, ReplyMailData, ReplyMailErrors, ReplyMailResponses, RespondSessionData, RespondSessionErrors, RespondSessionResponses, SendMailData, SendMailErrors, SendMailResponses, SendSessionMessageData, SendSessionMessageErrors, SendSessionMessageResponses, StreamAgentOutputData, StreamAgentOutputErrors, StreamAgentOutputQualifiedData, StreamAgentOutputQualifiedErrors, StreamAgentOutputQualifiedResponse, StreamAgentOutputQualifiedResponses, StreamAgentOutputResponse, StreamAgentOutputResponses, StreamEventsData, StreamEventsErrors, StreamEventsResponse, StreamEventsResponses, StreamSessionData, StreamSessionErrors, StreamSessionResponse, StreamSessionResponses, StreamSupervisorEventsData, StreamSupervisorEventsErrors, StreamSupervisorEventsResponse, StreamSupervisorEventsResponses, SubmitSessionData, SubmitSessionErrors, SubmitSessionResponses } from './types.gen'; + +export type Options = Options2 & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +/** + * Get health + */ +export const getHealth = (options?: Options) => (options?.client ?? client).get({ url: '/health', ...options }); + +/** + * Get v0 cities + */ +export const getV0Cities = (options?: Options) => (options?.client ?? client).get({ url: '/v0/cities', ...options }); + +/** + * Post v0 city + */ +export const postV0City = (options: Options) => (options.client ?? client).post({ + url: '/v0/city', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name + */ +export const getV0CityByCityName = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}', ...options }); + +/** + * Patch v0 city by city name + */ +export const patchV0CityByCityName = (options: Options) => (options.client ?? client).patch({ + url: '/v0/city/{cityName}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Delete v0 city by city name agent by base + */ +export const deleteV0CityByCityNameAgentByBase = (options: Options) => (options.client ?? client).delete({ url: '/v0/city/{cityName}/agent/{base}', ...options }); + +/** + * Get v0 city by city name agent by base + */ +export const getV0CityByCityNameAgentByBase = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/agent/{base}', ...options }); + +/** + * Patch v0 city by city name agent by base + */ +export const patchV0CityByCityNameAgentByBase = (options: Options) => (options.client ?? client).patch({ + url: '/v0/city/{cityName}/agent/{base}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name agent by base output + */ +export const getV0CityByCityNameAgentByBaseOutput = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/agent/{base}/output', ...options }); + +/** + * Stream agent output in real time + * + * Server-Sent Events stream of agent output (session log tail or tmux pane polling). + */ +export const streamAgentOutput = (options: Options) => (options.client ?? client).sse.get({ url: '/v0/city/{cityName}/agent/{base}/output/stream', ...options }); + +/** + * Post v0 city by city name agent by base by action + */ +export const postV0CityByCityNameAgentByBaseByAction = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/agent/{base}/{action}', ...options }); + +/** + * Delete v0 city by city name agent by dir by base + */ +export const deleteV0CityByCityNameAgentByDirByBase = (options: Options) => (options.client ?? client).delete({ url: '/v0/city/{cityName}/agent/{dir}/{base}', ...options }); + +/** + * Get v0 city by city name agent by dir by base + */ +export const getV0CityByCityNameAgentByDirByBase = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/agent/{dir}/{base}', ...options }); + +/** + * Patch v0 city by city name agent by dir by base + */ +export const patchV0CityByCityNameAgentByDirByBase = (options: Options) => (options.client ?? client).patch({ + url: '/v0/city/{cityName}/agent/{dir}/{base}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name agent by dir by base output + */ +export const getV0CityByCityNameAgentByDirByBaseOutput = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/agent/{dir}/{base}/output', ...options }); + +/** + * Stream agent output in real time (qualified name) + * + * Server-Sent Events stream of agent output for qualified (rig-prefixed) agent names. + */ +export const streamAgentOutputQualified = (options: Options) => (options.client ?? client).sse.get({ url: '/v0/city/{cityName}/agent/{dir}/{base}/output/stream', ...options }); + +/** + * Post v0 city by city name agent by dir by base by action + */ +export const postV0CityByCityNameAgentByDirByBaseByAction = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/agent/{dir}/{base}/{action}', ...options }); + +/** + * Get v0 city by city name agents + */ +export const getV0CityByCityNameAgents = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/agents', ...options }); + +/** + * Create an agent + */ +export const createAgent = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/agents', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Delete v0 city by city name bead by ID + */ +export const deleteV0CityByCityNameBeadById = (options: Options) => (options.client ?? client).delete({ url: '/v0/city/{cityName}/bead/{id}', ...options }); + +/** + * Get v0 city by city name bead by ID + */ +export const getV0CityByCityNameBeadById = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/bead/{id}', ...options }); + +/** + * Patch v0 city by city name bead by ID + */ +export const patchV0CityByCityNameBeadById = (options: Options) => (options.client ?? client).patch({ + url: '/v0/city/{cityName}/bead/{id}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Post v0 city by city name bead by ID assign + */ +export const postV0CityByCityNameBeadByIdAssign = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/bead/{id}/assign', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Post v0 city by city name bead by ID close + */ +export const postV0CityByCityNameBeadByIdClose = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/bead/{id}/close', ...options }); + +/** + * Get v0 city by city name bead by ID deps + */ +export const getV0CityByCityNameBeadByIdDeps = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/bead/{id}/deps', ...options }); + +/** + * Post v0 city by city name bead by ID reopen + */ +export const postV0CityByCityNameBeadByIdReopen = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/bead/{id}/reopen', ...options }); + +/** + * Post v0 city by city name bead by ID update + */ +export const postV0CityByCityNameBeadByIdUpdate = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/bead/{id}/update', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name beads + */ +export const getV0CityByCityNameBeads = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/beads', ...options }); + +/** + * Create a bead + */ +export const createBead = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/beads', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name beads graph by root ID + */ +export const getV0CityByCityNameBeadsGraphByRootId = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/beads/graph/{rootID}', ...options }); + +/** + * Get v0 city by city name beads ready + */ +export const getV0CityByCityNameBeadsReady = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/beads/ready', ...options }); + +/** + * Get v0 city by city name config + */ +export const getV0CityByCityNameConfig = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/config', ...options }); + +/** + * Get v0 city by city name config explain + */ +export const getV0CityByCityNameConfigExplain = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/config/explain', ...options }); + +/** + * Get v0 city by city name config validate + */ +export const getV0CityByCityNameConfigValidate = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/config/validate', ...options }); + +/** + * Delete v0 city by city name convoy by ID + */ +export const deleteV0CityByCityNameConvoyById = (options: Options) => (options.client ?? client).delete({ url: '/v0/city/{cityName}/convoy/{id}', ...options }); + +/** + * Get v0 city by city name convoy by ID + */ +export const getV0CityByCityNameConvoyById = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/convoy/{id}', ...options }); + +/** + * Post v0 city by city name convoy by ID add + */ +export const postV0CityByCityNameConvoyByIdAdd = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/convoy/{id}/add', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name convoy by ID check + */ +export const getV0CityByCityNameConvoyByIdCheck = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/convoy/{id}/check', ...options }); + +/** + * Post v0 city by city name convoy by ID close + */ +export const postV0CityByCityNameConvoyByIdClose = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/convoy/{id}/close', ...options }); + +/** + * Post v0 city by city name convoy by ID remove + */ +export const postV0CityByCityNameConvoyByIdRemove = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/convoy/{id}/remove', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name convoys + */ +export const getV0CityByCityNameConvoys = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/convoys', ...options }); + +/** + * Create a convoy + */ +export const createConvoy = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/convoys', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name events + */ +export const getV0CityByCityNameEvents = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/events', ...options }); + +/** + * Emit an event + */ +export const emitEvent = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/events', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Stream city events in real time + * + * Server-Sent Events stream of city events with optional workflow projections. Supports reconnection via Last-Event-ID header or after_seq query param. + */ +export const streamEvents = (options: Options) => (options.client ?? client).sse.get({ url: '/v0/city/{cityName}/events/stream', ...options }); + +/** + * Delete v0 city by city name extmsg adapters + */ +export const deleteV0CityByCityNameExtmsgAdapters = (options: Options) => (options.client ?? client).delete({ + url: '/v0/city/{cityName}/extmsg/adapters', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name extmsg adapters + */ +export const getV0CityByCityNameExtmsgAdapters = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/extmsg/adapters', ...options }); + +/** + * Register an external messaging adapter + */ +export const registerExtmsgAdapter = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/extmsg/adapters', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Post v0 city by city name extmsg bind + */ +export const postV0CityByCityNameExtmsgBind = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/extmsg/bind', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name extmsg bindings + */ +export const getV0CityByCityNameExtmsgBindings = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/extmsg/bindings', ...options }); + +/** + * Get v0 city by city name extmsg groups + */ +export const getV0CityByCityNameExtmsgGroups = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/extmsg/groups', ...options }); + +/** + * Ensure an external messaging group exists + */ +export const ensureExtmsgGroup = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/extmsg/groups', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Post v0 city by city name extmsg inbound + */ +export const postV0CityByCityNameExtmsgInbound = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/extmsg/inbound', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Post v0 city by city name extmsg outbound + */ +export const postV0CityByCityNameExtmsgOutbound = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/extmsg/outbound', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Delete v0 city by city name extmsg participants + */ +export const deleteV0CityByCityNameExtmsgParticipants = (options: Options) => (options.client ?? client).delete({ + url: '/v0/city/{cityName}/extmsg/participants', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Post v0 city by city name extmsg participants + */ +export const postV0CityByCityNameExtmsgParticipants = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/extmsg/participants', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name extmsg transcript + */ +export const getV0CityByCityNameExtmsgTranscript = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/extmsg/transcript', ...options }); + +/** + * Post v0 city by city name extmsg transcript ack + */ +export const postV0CityByCityNameExtmsgTranscriptAck = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/extmsg/transcript/ack', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Post v0 city by city name extmsg unbind + */ +export const postV0CityByCityNameExtmsgUnbind = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/extmsg/unbind', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name formula by name + */ +export const getV0CityByCityNameFormulaByName = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/formula/{name}', ...options }); + +/** + * Get v0 city by city name formulas + */ +export const getV0CityByCityNameFormulas = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/formulas', ...options }); + +/** + * Get v0 city by city name formulas feed + */ +export const getV0CityByCityNameFormulasFeed = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/formulas/feed', ...options }); + +/** + * Get v0 city by city name formulas by name + */ +export const getV0CityByCityNameFormulasByName = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/formulas/{name}', ...options }); + +/** + * Post v0 city by city name formulas by name preview + */ +export const postV0CityByCityNameFormulasByNamePreview = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/formulas/{name}/preview', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name formulas by name runs + */ +export const getV0CityByCityNameFormulasByNameRuns = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/formulas/{name}/runs', ...options }); + +/** + * Get v0 city by city name health + */ +export const getV0CityByCityNameHealth = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/health', ...options }); + +/** + * Get v0 city by city name mail + */ +export const getV0CityByCityNameMail = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/mail', ...options }); + +/** + * Send a mail message + */ +export const sendMail = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/mail', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name mail count + */ +export const getV0CityByCityNameMailCount = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/mail/count', ...options }); + +/** + * Get v0 city by city name mail thread by ID + */ +export const getV0CityByCityNameMailThreadById = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/mail/thread/{id}', ...options }); + +/** + * Delete v0 city by city name mail by ID + */ +export const deleteV0CityByCityNameMailById = (options: Options) => (options.client ?? client).delete({ url: '/v0/city/{cityName}/mail/{id}', ...options }); + +/** + * Get v0 city by city name mail by ID + */ +export const getV0CityByCityNameMailById = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/mail/{id}', ...options }); + +/** + * Post v0 city by city name mail by ID archive + */ +export const postV0CityByCityNameMailByIdArchive = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/mail/{id}/archive', ...options }); + +/** + * Post v0 city by city name mail by ID mark unread + */ +export const postV0CityByCityNameMailByIdMarkUnread = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/mail/{id}/mark-unread', ...options }); + +/** + * Post v0 city by city name mail by ID read + */ +export const postV0CityByCityNameMailByIdRead = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/mail/{id}/read', ...options }); + +/** + * Reply to a mail message + */ +export const replyMail = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/mail/{id}/reply', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name order history by bead ID + */ +export const getV0CityByCityNameOrderHistoryByBeadId = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/order/history/{bead_id}', ...options }); + +/** + * Get v0 city by city name order by name + */ +export const getV0CityByCityNameOrderByName = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/order/{name}', ...options }); + +/** + * Post v0 city by city name order by name disable + */ +export const postV0CityByCityNameOrderByNameDisable = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/order/{name}/disable', ...options }); + +/** + * Post v0 city by city name order by name enable + */ +export const postV0CityByCityNameOrderByNameEnable = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/order/{name}/enable', ...options }); + +/** + * Get v0 city by city name orders + */ +export const getV0CityByCityNameOrders = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/orders', ...options }); + +/** + * Get v0 city by city name orders check + */ +export const getV0CityByCityNameOrdersCheck = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/orders/check', ...options }); + +/** + * Get v0 city by city name orders feed + */ +export const getV0CityByCityNameOrdersFeed = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/orders/feed', ...options }); + +/** + * Get v0 city by city name orders history + */ +export const getV0CityByCityNameOrdersHistory = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/orders/history', ...options }); + +/** + * Get v0 city by city name packs + */ +export const getV0CityByCityNamePacks = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/packs', ...options }); + +/** + * Delete v0 city by city name patches agent by base + */ +export const deleteV0CityByCityNamePatchesAgentByBase = (options: Options) => (options.client ?? client).delete({ url: '/v0/city/{cityName}/patches/agent/{base}', ...options }); + +/** + * Get v0 city by city name patches agent by base + */ +export const getV0CityByCityNamePatchesAgentByBase = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/patches/agent/{base}', ...options }); + +/** + * Delete v0 city by city name patches agent by dir by base + */ +export const deleteV0CityByCityNamePatchesAgentByDirByBase = (options: Options) => (options.client ?? client).delete({ url: '/v0/city/{cityName}/patches/agent/{dir}/{base}', ...options }); + +/** + * Get v0 city by city name patches agent by dir by base + */ +export const getV0CityByCityNamePatchesAgentByDirByBase = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/patches/agent/{dir}/{base}', ...options }); + +/** + * Get v0 city by city name patches agents + */ +export const getV0CityByCityNamePatchesAgents = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/patches/agents', ...options }); + +/** + * Put v0 city by city name patches agents + */ +export const putV0CityByCityNamePatchesAgents = (options: Options) => (options.client ?? client).put({ + url: '/v0/city/{cityName}/patches/agents', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Delete v0 city by city name patches provider by name + */ +export const deleteV0CityByCityNamePatchesProviderByName = (options: Options) => (options.client ?? client).delete({ url: '/v0/city/{cityName}/patches/provider/{name}', ...options }); + +/** + * Get v0 city by city name patches provider by name + */ +export const getV0CityByCityNamePatchesProviderByName = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/patches/provider/{name}', ...options }); + +/** + * Get v0 city by city name patches providers + */ +export const getV0CityByCityNamePatchesProviders = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/patches/providers', ...options }); + +/** + * Put v0 city by city name patches providers + */ +export const putV0CityByCityNamePatchesProviders = (options: Options) => (options.client ?? client).put({ + url: '/v0/city/{cityName}/patches/providers', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Delete v0 city by city name patches rig by name + */ +export const deleteV0CityByCityNamePatchesRigByName = (options: Options) => (options.client ?? client).delete({ url: '/v0/city/{cityName}/patches/rig/{name}', ...options }); + +/** + * Get v0 city by city name patches rig by name + */ +export const getV0CityByCityNamePatchesRigByName = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/patches/rig/{name}', ...options }); + +/** + * Get v0 city by city name patches rigs + */ +export const getV0CityByCityNamePatchesRigs = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/patches/rigs', ...options }); + +/** + * Put v0 city by city name patches rigs + */ +export const putV0CityByCityNamePatchesRigs = (options: Options) => (options.client ?? client).put({ + url: '/v0/city/{cityName}/patches/rigs', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name provider readiness + */ +export const getV0CityByCityNameProviderReadiness = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/provider-readiness', ...options }); + +/** + * Delete v0 city by city name provider by name + */ +export const deleteV0CityByCityNameProviderByName = (options: Options) => (options.client ?? client).delete({ url: '/v0/city/{cityName}/provider/{name}', ...options }); + +/** + * Get v0 city by city name provider by name + */ +export const getV0CityByCityNameProviderByName = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/provider/{name}', ...options }); + +/** + * Patch v0 city by city name provider by name + */ +export const patchV0CityByCityNameProviderByName = (options: Options) => (options.client ?? client).patch({ + url: '/v0/city/{cityName}/provider/{name}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name providers + */ +export const getV0CityByCityNameProviders = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/providers', ...options }); + +/** + * Create a provider + */ +export const createProvider = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/providers', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name providers public + */ +export const getV0CityByCityNameProvidersPublic = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/providers/public', ...options }); + +/** + * Get v0 city by city name readiness + */ +export const getV0CityByCityNameReadiness = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/readiness', ...options }); + +/** + * Delete v0 city by city name rig by name + */ +export const deleteV0CityByCityNameRigByName = (options: Options) => (options.client ?? client).delete({ url: '/v0/city/{cityName}/rig/{name}', ...options }); + +/** + * Get v0 city by city name rig by name + */ +export const getV0CityByCityNameRigByName = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/rig/{name}', ...options }); + +/** + * Patch v0 city by city name rig by name + */ +export const patchV0CityByCityNameRigByName = (options: Options) => (options.client ?? client).patch({ + url: '/v0/city/{cityName}/rig/{name}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Post v0 city by city name rig by name by action + */ +export const postV0CityByCityNameRigByNameByAction = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/rig/{name}/{action}', ...options }); + +/** + * Get v0 city by city name rigs + */ +export const getV0CityByCityNameRigs = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/rigs', ...options }); + +/** + * Create a rig + */ +export const createRig = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/rigs', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name service by name + */ +export const getV0CityByCityNameServiceByName = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/service/{name}', ...options }); + +/** + * Post v0 city by city name service by name restart + */ +export const postV0CityByCityNameServiceByNameRestart = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/service/{name}/restart', ...options }); + +/** + * Get v0 city by city name services + */ +export const getV0CityByCityNameServices = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/services', ...options }); + +/** + * Get v0 city by city name session by ID + */ +export const getV0CityByCityNameSessionById = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/session/{id}', ...options }); + +/** + * Patch v0 city by city name session by ID + */ +export const patchV0CityByCityNameSessionById = (options: Options) => (options.client ?? client).patch({ + url: '/v0/city/{cityName}/session/{id}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name session by ID agents + */ +export const getV0CityByCityNameSessionByIdAgents = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/session/{id}/agents', ...options }); + +/** + * Get v0 city by city name session by ID agents by agent ID + */ +export const getV0CityByCityNameSessionByIdAgentsByAgentId = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/session/{id}/agents/{agentId}', ...options }); + +/** + * Post v0 city by city name session by ID close + */ +export const postV0CityByCityNameSessionByIdClose = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/session/{id}/close', ...options }); + +/** + * Post v0 city by city name session by ID kill + */ +export const postV0CityByCityNameSessionByIdKill = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/session/{id}/kill', ...options }); + +/** + * Send a message to a session + */ +export const sendSessionMessage = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/session/{id}/messages', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name session by ID pending + */ +export const getV0CityByCityNameSessionByIdPending = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/session/{id}/pending', ...options }); + +/** + * Post v0 city by city name session by ID rename + */ +export const postV0CityByCityNameSessionByIdRename = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/session/{id}/rename', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Respond to a pending interaction + */ +export const respondSession = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/session/{id}/respond', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Post v0 city by city name session by ID stop + */ +export const postV0CityByCityNameSessionByIdStop = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/session/{id}/stop', ...options }); + +/** + * Stream session output in real time + * + * Server-Sent Events stream of session transcript updates. Streams turns (conversation format) or raw messages (JSONL format) based on the format query parameter. Emits activity and pending events for tool approval prompts. + */ +export const streamSession = (options: Options) => (options.client ?? client).sse.get({ url: '/v0/city/{cityName}/session/{id}/stream', ...options }); + +/** + * Submit a message to a session + */ +export const submitSession = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/session/{id}/submit', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Post v0 city by city name session by ID suspend + */ +export const postV0CityByCityNameSessionByIdSuspend = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/session/{id}/suspend', ...options }); + +/** + * Get v0 city by city name session by ID transcript + */ +export const getV0CityByCityNameSessionByIdTranscript = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/session/{id}/transcript', ...options }); + +/** + * Post v0 city by city name session by ID wake + */ +export const postV0CityByCityNameSessionByIdWake = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/session/{id}/wake', ...options }); + +/** + * Get v0 city by city name sessions + */ +export const getV0CityByCityNameSessions = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/sessions', ...options }); + +/** + * Create a session + */ +export const createSession = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/sessions', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Post v0 city by city name sling + */ +export const postV0CityByCityNameSling = (options: Options) => (options.client ?? client).post({ + url: '/v0/city/{cityName}/sling', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Get v0 city by city name status + */ +export const getV0CityByCityNameStatus = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/status', ...options }); + +/** + * Delete v0 city by city name workflow by workflow ID + */ +export const deleteV0CityByCityNameWorkflowByWorkflowId = (options: Options) => (options.client ?? client).delete({ url: '/v0/city/{cityName}/workflow/{workflow_id}', ...options }); + +/** + * Get v0 city by city name workflow by workflow ID + */ +export const getV0CityByCityNameWorkflowByWorkflowId = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/workflow/{workflow_id}', ...options }); + +/** + * Get v0 events + */ +export const getV0Events = (options?: Options) => (options?.client ?? client).get({ url: '/v0/events', ...options }); + +/** + * Stream tagged events from all running cities. + */ +export const streamSupervisorEvents = (options?: Options) => (options?.client ?? client).sse.get({ url: '/v0/events/stream', ...options }); + +/** + * Get v0 provider readiness + */ +export const getV0ProviderReadiness = (options?: Options) => (options?.client ?? client).get({ url: '/v0/provider-readiness', ...options }); + +/** + * Get v0 readiness + */ +export const getV0Readiness = (options?: Options) => (options?.client ?? client).get({ url: '/v0/readiness', ...options }); diff --git a/cmd/gc/dashboard/web/src/generated/types.gen.ts b/cmd/gc/dashboard/web/src/generated/types.gen.ts new file mode 100644 index 0000000000..4053db6bdd --- /dev/null +++ b/cmd/gc/dashboard/web/src/generated/types.gen.ts @@ -0,0 +1,8115 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: `${string}://${string}` | (string & {}); +}; + +export type AdapterCapabilities = { + MaxMessageLength: number; + SupportsAttachments: boolean; + SupportsChildConversations: boolean; +}; + +export type AdapterEventPayload = { + account_id: string; + provider: string; +}; + +export type AgentCreateInputBody = { + /** + * Working directory (rig name). + */ + dir?: string; + /** + * Agent name. + */ + name: string; + /** + * Provider name. + */ + provider: string; + /** + * Agent scope. + */ + scope?: string; +}; + +export type AgentCreatedOutputBody = { + /** + * Created agent name. + */ + agent: string; + /** + * Operation result. + */ + status: string; +}; + +export type AgentMapping = { + agent_id: string; + parent_tool_use_id: string; +}; + +export type AgentOutputResponse = { + agent: string; + format: string; + pagination?: PaginationInfo; + turns: Array | null; +}; + +export type AgentPatch = { + Attach: boolean | null; + DefaultSlingFormula: string | null; + DependsOn: Array | null; + Dir: string; + Env: { + [key: string]: string; + }; + EnvRemove: Array | null; + HooksInstalled: boolean | null; + IdleTimeout: string | null; + InjectAssignedSkills: boolean | null; + InjectFragments: Array | null; + InjectFragmentsAppend: Array | null; + InstallAgentHooks: Array | null; + InstallAgentHooksAppend: Array | null; + MCP: Array | null; + MCPAppend: Array | null; + MaxActiveSessions: number | null; + MinActiveSessions: number | null; + Name: string; + Nudge: string | null; + OptionDefaults: { + [key: string]: string; + }; + OverlayDir: string | null; + Pool: PoolOverride; + PreStart: Array | null; + PreStartAppend: Array | null; + PromptTemplate: string | null; + Provider: string | null; + ResumeCommand: string | null; + ScaleCheck: string | null; + Scope: string | null; + Session: string | null; + SessionLive: Array | null; + SessionLiveAppend: Array | null; + SessionSetup: Array | null; + SessionSetupAppend: Array | null; + SessionSetupScript: string | null; + Skills: Array | null; + SkillsAppend: Array | null; + SleepAfterIdle: string | null; + StartCommand: string | null; + Suspended: boolean | null; + WakeMode: string | null; + WorkDir: string | null; +}; + +export type AgentPatchSetInputBody = { + /** + * Agent directory scope. + */ + dir?: string; + /** + * Override environment variables. + */ + env?: { + [key: string]: string; + }; + /** + * Agent name. + */ + name?: string; + /** + * Override agent scope. + */ + scope?: string; + /** + * Override suspended state. + */ + suspended?: boolean; + /** + * Override session working directory. + */ + work_dir?: string; +}; + +export type AgentResponse = { + active_bead?: string; + activity?: string; + available: boolean; + context_pct?: number; + context_window?: number; + description?: string; + display_name?: string; + last_output?: string; + model?: string; + name: string; + pool?: string; + provider?: string; + rig?: string; + running: boolean; + session?: SessionInfo; + state: string; + suspended: boolean; + unavailable_reason?: string; +}; + +export type AgentUpdateInputBody = { + /** + * Provider name. + */ + provider?: string; + /** + * Agent scope. + */ + scope?: string; + /** + * Whether agent is suspended. + */ + suspended?: boolean; +}; + +export type AgentUpdateQualifiedInputBody = { + /** + * Provider name. + */ + provider?: string; + /** + * Agent scope. + */ + scope?: string; + /** + * Whether agent is suspended. + */ + suspended?: boolean; +}; + +export type AnnotatedAgentResponse = { + dir?: string; + is_pool?: boolean; + name: string; + /** + * Agent origin: inline or pack-derived. + */ + origin: string; + provider?: string; + scope?: string; + suspended: boolean; +}; + +export type AnnotatedProviderResponse = { + args?: Array | null; + command?: string; + display_name?: string; + env?: { + [key: string]: string; + }; + /** + * Provider origin: builtin, city, or builtin+city. + */ + origin: string; + prompt_flag?: string; + prompt_mode?: string; + ready_delay_ms?: number; +}; + +export type Bead = { + assignee?: string; + created_at: string; + dependencies?: Array | null; + description?: string; + from?: string; + id: string; + issue_type: string; + labels?: Array | null; + metadata?: { + [key: string]: string; + }; + needs?: Array | null; + parent?: string; + priority?: number; + ref?: string; + status: string; + title: string; +}; + +export type BeadAssignInputBody = { + /** + * Assignee name. + */ + assignee?: string; +}; + +export type BeadCreateInputBody = { + /** + * Assigned agent. + */ + assignee?: string; + /** + * Bead description. + */ + description?: string; + /** + * Bead labels. + */ + labels?: Array | null; + /** + * Bead priority. + */ + priority?: number; + /** + * Rig name. + */ + rig?: string; + /** + * Bead title. + */ + title: string; + /** + * Bead type. + */ + type?: string; +}; + +export type BeadDepsResponse = { + children: Array | null; +}; + +export type BeadEventPayload = { + bead: Bead; +}; + +export type BeadGraphResponse = { + beads: Array | null; + deps: Array | null; + root: Bead; +}; + +export type BeadUpdateBody = { + /** + * Assigned agent. + */ + assignee?: string; + /** + * Bead description. + */ + description?: string; + /** + * Bead labels. + */ + labels?: Array | null; + /** + * Metadata key-value pairs to set. + */ + metadata?: { + [key: string]: string; + }; + /** + * Bead priority. + */ + priority?: number; + /** + * Labels to remove. + */ + remove_labels?: Array | null; + /** + * Bead status. + */ + status?: string; + /** + * Bead title. + */ + title?: string; + /** + * Bead type. + */ + type?: string; +}; + +/** + * Lifecycle state of a session binding. + */ +export type BindingStatus = 'active' | 'ended'; + +export type BoundEventPayload = { + conversation_id: string; + provider: string; + session_id: string; +}; + +export type CityCreateRequest = { + /** + * Optional bootstrap profile. + */ + bootstrap_profile?: 'k8s-cell' | 'kubernetes' | 'kubernetes-cell' | 'single-host-compat'; + /** + * Directory to create the city in. Absolute or relative to $HOME. + */ + dir: string; + /** + * Provider name for the city's default session template. + */ + provider: string; +}; + +export type CityCreateResponse = { + /** + * True on success. + */ + ok: boolean; + /** + * Resolved absolute path of the created city. + */ + path: string; +}; + +export type CityGetResponse = { + agent_count: number; + name: string; + path: string; + provider?: string; + rig_count: number; + session_template?: string; + suspended: boolean; + uptime_sec: number; + version?: string; +}; + +export type CityInfo = { + error?: string; + name: string; + path: string; + phases_completed?: Array | null; + running: boolean; + status?: string; +}; + +export type CityPatchInputBody = { + /** + * Whether the city is suspended. + */ + suspended?: boolean; +}; + +export type ConfigAgentResponse = { + dir?: string; + is_pool?: boolean; + name: string; + provider?: string; + scope?: string; + suspended: boolean; +}; + +export type ConfigExplainPatches = { + agents: number; + providers: number; + rigs: number; +}; + +export type ConfigExplainResponse = { + agents: Array | null; + patches: ConfigExplainPatches; + providers: { + [key: string]: AnnotatedProviderResponse; + }; +}; + +export type ConfigPatchesResponse = { + agent_count: number; + provider_count: number; + rig_count: number; +}; + +export type ConfigResponse = { + agents: Array | null; + patches?: ConfigPatchesResponse; + providers?: { + [key: string]: ProviderSpecJson; + }; + rigs: Array | null; + workspace: WorkspaceResponse; +}; + +export type ConfigRigResponse = { + name: string; + path: string; + prefix?: string; + suspended: boolean; +}; + +export type ConfigValidateOutputBody = { + /** + * Validation errors. + */ + errors: Array | null; + /** + * Whether the configuration is valid. + */ + valid: boolean; + /** + * Validation warnings. + */ + warnings: Array | null; +}; + +export type ConversationGroupParticipant = { + GroupID: string; + Handle: string; + ID: string; + Metadata: { + [key: string]: string; + }; + Public: boolean; + SessionID: string; +}; + +export type ConversationGroupRecord = { + DefaultHandle: string; + FanoutPolicy: FanoutPolicy; + ID: string; + LastAddressedHandle: string; + Metadata: { + [key: string]: string; + }; + Mode: string; + RootConversation: ConversationRef; + SchemaVersion: number; +}; + +/** + * Shape of a conversation. + */ +export type ConversationKind = 'dm' | 'room' | 'thread'; + +export type ConversationRef = { + account_id: string; + conversation_id: string; + kind: ConversationKind; + parent_conversation_id?: string; + provider: string; + scope_id: string; +}; + +export type ConversationTranscriptRecord = { + Actor: ExternalActor; + Attachments: Array | null; + Conversation: ConversationRef; + CreatedAt: string; + ExplicitTarget: string; + ID: string; + Kind: TranscriptMessageKind; + Metadata: { + [key: string]: string; + }; + Provenance: TranscriptProvenance; + ProviderMessageID: string; + ReplyToMessageID: string; + SchemaVersion: number; + Sequence: number; + SourceSessionID: string; + Text: string; +}; + +export type ConvoyAddInputBody = { + /** + * Bead IDs to add. + */ + items?: Array | null; +}; + +export type ConvoyCheckResponse = { + /** + * Closed child bead count. + */ + closed: number; + /** + * True when all child beads are closed and total > 0. + */ + complete: boolean; + /** + * Convoy ID. + */ + convoy_id: string; + /** + * Total child bead count. + */ + total: number; +}; + +export type ConvoyCreateInputBody = { + /** + * Bead IDs to include. + */ + items?: Array | null; + /** + * Rig name. + */ + rig?: string; + /** + * Convoy title. + */ + title: string; +}; + +export type ConvoyGetResponse = { + /** + * Direct child beads (non-workflow case). + */ + children?: Array | null; + /** + * Simple convoy bead (non-workflow case). + */ + convoy?: Bead; + /** + * Child bead progress (non-workflow case). + */ + progress?: ConvoyProgress; +}; + +export type ConvoyProgress = { + /** + * Closed child bead count. + */ + closed: number; + /** + * Total child bead count. + */ + total: number; +}; + +export type ConvoyRemoveInputBody = { + /** + * Bead IDs to remove. + */ + items?: Array | null; +}; + +export type DeliveryContextRecord = { + BindingGeneration: number; + Conversation: ConversationRef; + ID: string; + LastMessageID: string; + LastPublishedAt: string; + Metadata: { + [key: string]: string; + }; + SchemaVersion: number; + SessionID: string; + SourceSessionID: string; +}; + +export type Dep = { + depends_on_id: string; + issue_id: string; + type: string; +}; + +export type ErrorDetail = { + /** + * Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id' + */ + location?: string; + /** + * Error message text + */ + message?: string; + /** + * The value at the given location + */ + value?: unknown; +}; + +export type ErrorModel = { + /** + * A human-readable explanation specific to this occurrence of the problem. + */ + detail?: string; + /** + * Optional list of individual error details + */ + errors?: Array | null; + /** + * A URI reference that identifies the specific occurrence of the problem. + */ + instance?: string; + /** + * HTTP status code + */ + status?: number; + /** + * A short, human-readable summary of the problem type. This value should not change between occurrences of the error. + */ + title?: string; + /** + * A URI reference to human-readable documentation for the error. + */ + type?: string; +}; + +export type EventEmitOutputBody = { + /** + * Operation result. + */ + status: string; +}; + +export type EventEmitRequest = { + /** + * Actor that produced the event. + */ + actor: string; + /** + * Event message. + */ + message?: string; + /** + * Event subject. + */ + subject?: string; + /** + * Event type. + */ + type: string; +}; + +export type EventPayload = AdapterEventPayload | BeadEventPayload | BoundEventPayload | GroupCreatedEventPayload | InboundEventPayload | MailEventPayload | NoPayload | OutboundEventPayload | UnboundEventPayload | WorkerOperationEventPayload; + +export type EventStreamEnvelope = { + actor: string; + message?: string; + payload?: EventPayload; + seq: number; + subject?: string; + ts: string; + type: string; + workflow?: WorkflowEventProjection; +}; + +export type ExtMsgAdapterRegisterInputBody = { + /** + * Account ID. + */ + account_id: string; + /** + * Callback URL for outbound messages. + */ + callback_url?: string; + /** + * Adapter capabilities. + */ + capabilities?: AdapterCapabilities; + /** + * Adapter display name. + */ + name?: string; + /** + * Provider name. + */ + provider: string; +}; + +export type ExtMsgAdapterRegisterOutputBody = { + /** + * Account ID. + */ + account_id: string; + /** + * Adapter name. + */ + name: string; + /** + * Provider name. + */ + provider: string; + /** + * Operation result. + */ + status: string; +}; + +export type ExtMsgAdapterUnregisterInputBody = { + /** + * Account ID. + */ + account_id: string; + /** + * Provider name. + */ + provider: string; +}; + +export type ExtMsgBindInputBody = { + /** + * Conversation to bind. + */ + conversation?: ConversationRef; + /** + * Optional binding metadata. + */ + metadata?: { + [key: string]: string; + }; + /** + * Session ID to bind. + */ + session_id: string; +}; + +export type ExtMsgGroupEnsureInputBody = { + /** + * Default handle for the group. + */ + default_handle?: string; + /** + * Group metadata. + */ + metadata?: { + [key: string]: string; + }; + /** + * Group mode (launcher, etc.). + */ + mode?: string; + /** + * Root conversation reference. + */ + root_conversation?: ConversationRef; +}; + +export type ExtMsgInboundInputBody = { + /** + * Account ID for raw payloads (required when message is absent). + */ + account_id?: string; + /** + * Pre-normalized inbound message. + */ + message?: ExternalInboundMessage; + /** + * Raw payload bytes. + */ + payload?: string; + /** + * Provider name for raw payloads (required when message is absent). + */ + provider?: string; +}; + +export type ExtMsgOutboundInputBody = { + /** + * Target conversation. + */ + conversation?: ConversationRef; + /** + * Idempotency key. + */ + idempotency_key?: string; + /** + * Message ID to reply to. + */ + reply_to_message_id?: string; + /** + * Session ID. + */ + session_id: string; + /** + * Message text. + */ + text?: string; +}; + +export type ExtMsgParticipantRemoveInputBody = { + /** + * Group ID. + */ + group_id: string; + /** + * Participant handle. + */ + handle: string; +}; + +export type ExtMsgParticipantUpsertInputBody = { + /** + * Group ID. + */ + group_id: string; + /** + * Participant handle. + */ + handle: string; + /** + * Participant metadata. + */ + metadata?: { + [key: string]: string; + }; + /** + * Whether participant is public. + */ + public?: boolean; + /** + * Session ID. + */ + session_id: string; +}; + +export type ExtMsgTranscriptAckInputBody = { + /** + * Conversation to acknowledge. + */ + conversation?: ConversationRef; + /** + * Sequence number to acknowledge up to. + */ + sequence?: number; + /** + * Session ID. + */ + session_id: string; +}; + +export type ExtMsgUnbindBody = { + /** + * Bindings that were removed. + */ + unbound: Array | null; +}; + +export type ExtMsgUnbindInputBody = { + /** + * Conversation to unbind (nil = all). + */ + conversation?: ConversationRef; + /** + * Session ID to unbind. + */ + session_id: string; +}; + +export type ExternalActor = { + display_name: string; + id: string; + is_bot: boolean; +}; + +export type ExternalAttachment = { + mime_type: string; + provider_id: string; + url: string; +}; + +export type ExternalInboundMessage = { + actor: ExternalActor; + attachments?: Array | null; + conversation: ConversationRef; + dedup_key?: string; + explicit_target?: string; + provider_message_id: string; + received_at: string; + reply_to_message_id?: string; + text: string; +}; + +export type ExtmsgAdapterInfo = { + /** + * Adapter account ID. + */ + account_id: string; + /** + * Adapter display name. + */ + name: string; + /** + * Adapter provider key. + */ + provider: string; +}; + +export type FanoutPolicy = { + AllowUntargetedPublication: boolean; + Enabled: boolean; + MaxPeerTriggeredPublishes: number; + MaxTotalPeerDeliveries: number; +}; + +export type FormulaDetailResponse = { + deps: Array | null; + description: string; + name: string; + preview: FormulaPreviewResponse; + steps: Array | null; + var_defs: Array | null; + version: string; +}; + +export type FormulaFeedBody = { + items: Array | null; + partial: boolean; + partial_errors?: Array | null; +}; + +export type FormulaListBody = { + /** + * Formula summaries. + */ + items: Array | null; + /** + * Whether the list is partial. + */ + partial: boolean; +}; + +export type FormulaPreviewBody = { + /** + * Scope kind (city or rig). + */ + scope_kind?: string; + /** + * Scope reference. + */ + scope_ref?: string; + /** + * Target agent for preview compilation. + */ + target: string; + /** + * Variable name-to-value overrides applied to the compiled preview. + */ + vars?: { + [key: string]: string; + }; +}; + +export type FormulaPreviewEdgeResponse = { + from: string; + kind?: string; + to: string; +}; + +export type FormulaPreviewNodeResponse = { + id: string; + kind: string; + scope_ref?: string; + title: string; +}; + +export type FormulaPreviewResponse = { + edges: Array | null; + nodes: Array | null; +}; + +export type FormulaRecentRunResponse = { + started_at: string; + status: string; + target: string; + updated_at: string; + workflow_id: string; +}; + +export type FormulaRunsResponse = { + formula: string; + partial: boolean; + partial_errors?: Array | null; + recent_runs: Array | null; + run_count: number; +}; + +export type FormulaStepResponse = { + assignee?: string; + id: string; + kind: string; + labels?: Array | null; + metadata?: { + [key: string]: string; + }; + title: string; + type?: string; +}; + +export type FormulaSummaryResponse = { + description: string; + name: string; + recent_runs: Array | null; + run_count: number; + var_defs: Array | null; + version: string; +}; + +export type FormulaVarDefResponse = { + default?: unknown; + description?: string; + enum?: Array | null; + name: string; + pattern?: string; + required?: boolean; + type: string; +}; + +export type GitStatus = { + ahead: number; + behind: number; + branch: string; + changed_files: number; + clean: boolean; +}; + +export type GroupCreatedEventPayload = { + conversation_id: string; + mode: string; + provider: string; +}; + +export type GroupRouteDecision = { + Match: string; + TargetSessionID: string; + UpdateCursor: boolean; +}; + +export type HealthOutputBody = { + /** + * City name. + */ + city?: string; + /** + * Health status. + */ + status: string; + /** + * Server uptime in seconds. + */ + uptime_sec: number; + /** + * Server version. + */ + version?: string; +}; + +export type HeartbeatEvent = { + /** + * ISO 8601 timestamp when the heartbeat was sent. + */ + timestamp: string; +}; + +export type InboundEventPayload = { + actor: string; + conversation_id: string; + provider: string; + target_session: string; +}; + +export type InboundResult = { + Binding: SessionBindingRecord; + GroupRoute: GroupRouteDecision; + Message: ExternalInboundMessage; + TargetSessionID: string; + TranscriptEntry: ConversationTranscriptRecord; +}; + +export type ListBodyAgentPatch = { + /** + * The list of items. + */ + items: Array | null; + /** + * Cursor for the next page of results. + */ + next_cursor?: string; + /** + * True when one or more backends failed and the list is incomplete. + */ + partial?: boolean; + /** + * Human-readable errors from backends that failed during aggregation. + */ + partial_errors?: Array | null; + /** + * Total number of items matching the query. + */ + total: number; +}; + +export type ListBodyAgentResponse = { + /** + * The list of items. + */ + items: Array | null; + /** + * Cursor for the next page of results. + */ + next_cursor?: string; + /** + * True when one or more backends failed and the list is incomplete. + */ + partial?: boolean; + /** + * Human-readable errors from backends that failed during aggregation. + */ + partial_errors?: Array | null; + /** + * Total number of items matching the query. + */ + total: number; +}; + +export type ListBodyBead = { + /** + * The list of items. + */ + items: Array | null; + /** + * Cursor for the next page of results. + */ + next_cursor?: string; + /** + * True when one or more backends failed and the list is incomplete. + */ + partial?: boolean; + /** + * Human-readable errors from backends that failed during aggregation. + */ + partial_errors?: Array | null; + /** + * Total number of items matching the query. + */ + total: number; +}; + +export type ListBodyConversationTranscriptRecord = { + /** + * The list of items. + */ + items: Array | null; + /** + * Cursor for the next page of results. + */ + next_cursor?: string; + /** + * True when one or more backends failed and the list is incomplete. + */ + partial?: boolean; + /** + * Human-readable errors from backends that failed during aggregation. + */ + partial_errors?: Array | null; + /** + * Total number of items matching the query. + */ + total: number; +}; + +export type ListBodyExtmsgAdapterInfo = { + /** + * The list of items. + */ + items: Array | null; + /** + * Cursor for the next page of results. + */ + next_cursor?: string; + /** + * True when one or more backends failed and the list is incomplete. + */ + partial?: boolean; + /** + * Human-readable errors from backends that failed during aggregation. + */ + partial_errors?: Array | null; + /** + * Total number of items matching the query. + */ + total: number; +}; + +export type ListBodyProviderPatch = { + /** + * The list of items. + */ + items: Array | null; + /** + * Cursor for the next page of results. + */ + next_cursor?: string; + /** + * True when one or more backends failed and the list is incomplete. + */ + partial?: boolean; + /** + * Human-readable errors from backends that failed during aggregation. + */ + partial_errors?: Array | null; + /** + * Total number of items matching the query. + */ + total: number; +}; + +export type ListBodyProviderResponse = { + /** + * The list of items. + */ + items: Array | null; + /** + * Cursor for the next page of results. + */ + next_cursor?: string; + /** + * True when one or more backends failed and the list is incomplete. + */ + partial?: boolean; + /** + * Human-readable errors from backends that failed during aggregation. + */ + partial_errors?: Array | null; + /** + * Total number of items matching the query. + */ + total: number; +}; + +export type ListBodyRigPatch = { + /** + * The list of items. + */ + items: Array | null; + /** + * Cursor for the next page of results. + */ + next_cursor?: string; + /** + * True when one or more backends failed and the list is incomplete. + */ + partial?: boolean; + /** + * Human-readable errors from backends that failed during aggregation. + */ + partial_errors?: Array | null; + /** + * Total number of items matching the query. + */ + total: number; +}; + +export type ListBodyRigResponse = { + /** + * The list of items. + */ + items: Array | null; + /** + * Cursor for the next page of results. + */ + next_cursor?: string; + /** + * True when one or more backends failed and the list is incomplete. + */ + partial?: boolean; + /** + * Human-readable errors from backends that failed during aggregation. + */ + partial_errors?: Array | null; + /** + * Total number of items matching the query. + */ + total: number; +}; + +export type ListBodySessionBindingRecord = { + /** + * The list of items. + */ + items: Array | null; + /** + * Cursor for the next page of results. + */ + next_cursor?: string; + /** + * True when one or more backends failed and the list is incomplete. + */ + partial?: boolean; + /** + * Human-readable errors from backends that failed during aggregation. + */ + partial_errors?: Array | null; + /** + * Total number of items matching the query. + */ + total: number; +}; + +export type ListBodySessionResponse = { + /** + * The list of items. + */ + items: Array | null; + /** + * Cursor for the next page of results. + */ + next_cursor?: string; + /** + * True when one or more backends failed and the list is incomplete. + */ + partial?: boolean; + /** + * Human-readable errors from backends that failed during aggregation. + */ + partial_errors?: Array | null; + /** + * Total number of items matching the query. + */ + total: number; +}; + +export type ListBodyStatus = { + /** + * The list of items. + */ + items: Array | null; + /** + * Cursor for the next page of results. + */ + next_cursor?: string; + /** + * True when one or more backends failed and the list is incomplete. + */ + partial?: boolean; + /** + * Human-readable errors from backends that failed during aggregation. + */ + partial_errors?: Array | null; + /** + * Total number of items matching the query. + */ + total: number; +}; + +export type ListBodyWireEvent = { + /** + * The list of items. + */ + items: Array | null; + /** + * Cursor for the next page of results. + */ + next_cursor?: string; + /** + * True when one or more backends failed and the list is incomplete. + */ + partial?: boolean; + /** + * Human-readable errors from backends that failed during aggregation. + */ + partial_errors?: Array | null; + /** + * Total number of items matching the query. + */ + total: number; +}; + +export type LogicalNode = { + [key: string]: never; +}; + +export type MailCountOutputBody = { + /** + * True when one or more rig providers failed and the counts are not authoritative. + */ + partial?: boolean; + /** + * Per-provider errors when partial is true. + */ + partial_errors?: Array | null; + /** + * Total message count. + */ + total: number; + /** + * Unread message count. + */ + unread: number; +}; + +export type MailEventPayload = { + message?: Message; + rig: string; +}; + +export type MailListBody = { + /** + * The list of messages. + */ + items: Array | null; + /** + * Cursor for the next page of results. + */ + next_cursor?: string; + /** + * True when one or more rig providers failed and the list is not authoritative. + */ + partial?: boolean; + /** + * Per-provider errors when partial is true. + */ + partial_errors?: Array | null; + /** + * Total number of messages matching the query. + */ + total: number; +}; + +export type MailReplyInputBody = { + /** + * Reply body. + */ + body?: string; + /** + * Sender name. + */ + from?: string; + /** + * Reply subject. + */ + subject?: string; +}; + +export type MailSendInputBody = { + /** + * Message body. + */ + body?: string; + /** + * Sender name. + */ + from?: string; + /** + * Rig name. + */ + rig?: string; + /** + * Message subject. + */ + subject: string; + /** + * Recipient name. + */ + to: string; +}; + +export type Message = { + body: string; + cc?: Array | null; + created_at: string; + from: string; + id: string; + priority?: number; + read: boolean; + reply_to?: string; + rig?: string; + subject: string; + thread_id?: string; + to: string; +}; + +export type MonitorFeedItemResponse = { + attached_bead_id?: string; + bead_id?: string; + detail_available?: boolean; + id: string; + logical_bead_id?: string; + root_bead_id?: string; + root_store_ref?: string; + run_detail_available?: boolean; + scope_kind: string; + scope_ref: string; + started_at: string; + status: string; + store_ref?: string; + target: string; + title: string; + type: string; + updated_at: string; + workflow_id?: string; +}; + +export type NoPayload = { + [key: string]: never; +}; + +export type OkResponseBody = { + /** + * Operation result. + */ + status: string; +}; + +export type OkWithIdResponseBody = { + /** + * Resource ID. + */ + id?: string; + /** + * Operation result. + */ + status: string; +}; + +export type OptionChoiceDto = { + label: string; + value: string; +}; + +export type OrderCheckListBody = { + /** + * Order trigger evaluations. + */ + checks: Array | null; +}; + +export type OrderCheckResponse = { + due: boolean; + last_run?: string; + last_run_outcome?: string; + name: string; + reason: string; + rig?: string; + scoped_name: string; +}; + +export type OrderHistoryDetailResponse = { + bead_id: string; + created_at: string; + labels: Array | null; + output: string; + store_ref: string; +}; + +export type OrderHistoryEntry = { + bead_id: string; + capture_output: boolean; + created_at: string; + duration_ms?: string; + error?: string; + exit_code?: string; + has_output: boolean; + labels: Array | null; + name: string; + rig?: string; + scoped_name: string; + signal?: string; + store_ref: string; + wisp_root_id?: string; +}; + +export type OrderHistoryListBody = { + /** + * Order history entries. + */ + entries: Array | null; +}; + +export type OrderListBody = { + /** + * Registered orders. + */ + orders: Array | null; +}; + +export type OrderResponse = { + capture_output: boolean; + check?: string; + description?: string; + enabled: boolean; + exec?: string; + formula?: string; + /** + * @deprecated + */ + gate?: string; + interval?: string; + name: string; + on?: string; + pool?: string; + rig?: string; + schedule?: string; + scoped_name: string; + timeout?: string; + timeout_ms: number; + trigger?: string; + type: string; +}; + +export type OrdersFeedBody = { + items: Array | null; + partial: boolean; + partial_errors?: Array | null; +}; + +export type OutboundEventPayload = { + conversation_id: string; + message_id: string; + provider: string; + session: string; +}; + +export type OutboundResult = { + DeliveryContext: DeliveryContextRecord; + Receipt: PublishReceipt; + TranscriptEntry: ConversationTranscriptRecord; +}; + +export type OutputTurn = { + role: string; + text: string; + timestamp?: string; +}; + +export type PackListBody = { + /** + * Registered packs. + */ + packs: Array | null; +}; + +export type PackResponse = { + name: string; + path?: string; + ref?: string; + source?: string; +}; + +export type PaginationInfo = { + has_older_messages: boolean; + returned_message_count: number; + total_compactions: number; + total_message_count: number; + truncated_before_message?: string; +}; + +export type PatchDeletedResponseBody = { + /** + * Agent patch qualified name. + */ + agent_patch?: string; + /** + * Provider patch name. + */ + provider_patch?: string; + /** + * Rig patch name. + */ + rig_patch?: string; + /** + * Operation result. + */ + status: string; +}; + +export type PatchOkResponseBody = { + /** + * Agent patch qualified name. + */ + agent_patch?: string; + /** + * Provider patch name. + */ + provider_patch?: string; + /** + * Rig patch name. + */ + rig_patch?: string; + /** + * Operation result. + */ + status: string; +}; + +export type PendingInteraction = { + kind: string; + metadata?: { + [key: string]: string; + }; + options?: Array | null; + prompt?: string; + request_id: string; +}; + +export type PoolOverride = { + Check: string | null; + DrainTimeout: string | null; + Max: number | null; + Min: number | null; + OnBoot: string | null; + OnDeath: string | null; +}; + +export type ProviderCreateInputBody = { + /** + * Command arguments. + */ + args?: Array | null; + /** + * Arguments appended after inherited/base args. + */ + args_append?: Array | null; + /** + * Optional provider base for inheritance. + */ + base?: string; + /** + * Provider command binary. Omit for base-only descendants. + */ + command?: string; + /** + * Human-readable display name. + */ + display_name?: string; + /** + * Environment variables. + */ + env?: { + [key: string]: string; + }; + /** + * Provider name. + */ + name: string; + /** + * Options schema merge mode across inheritance chain. + */ + options_schema_merge?: string; + /** + * Flag for prompt delivery. + */ + prompt_flag?: string; + /** + * Prompt delivery mode. + */ + prompt_mode?: string; + /** + * Milliseconds to wait before probing readiness. + */ + ready_delay_ms?: number; +}; + +export type ProviderCreatedOutputBody = { + /** + * Created provider name. + */ + provider: string; + /** + * Operation result. + */ + status: string; +}; + +export type ProviderOptionDto = { + choices: Array | null; + default: string; + key: string; + label: string; + type: string; +}; + +export type ProviderPatch = { + ACPArgs: Array | null; + ACPCommand: string | null; + Args: Array | null; + ArgsAppend: Array | null; + Base: string | null; + Command: string | null; + Env: { + [key: string]: string; + }; + EnvRemove: Array | null; + Name: string; + OptionsSchemaMerge: string | null; + PromptFlag: string | null; + PromptMode: string | null; + ReadyDelayMs: number | null; + Replace: boolean; +}; + +export type ProviderPatchSetInputBody = { + /** + * Override command arguments. + */ + args?: Array | null; + /** + * Override command binary. + */ + command?: string; + /** + * Override environment variables. + */ + env?: { + [key: string]: string; + }; + /** + * Provider name. + */ + name?: string; + /** + * Override prompt flag. + */ + prompt_flag?: string; + /** + * Override prompt delivery mode. + */ + prompt_mode?: string; + /** + * Override ready delay in milliseconds. + */ + ready_delay_ms?: number; +}; + +export type ProviderPublicListBody = { + /** + * The list of browser-safe provider summaries. + */ + items: Array | null; + /** + * Cursor for the next page of results. + */ + next_cursor?: string; + /** + * Total number of providers in the list. + */ + total: number; +}; + +export type ProviderPublicResponse = { + builtin: boolean; + city_level: boolean; + display_name?: string; + effective_defaults?: { + [key: string]: string; + }; + name: string; + options_schema?: Array | null; +}; + +export type ProviderReadiness = { + detail?: string; + display_name: string; + status: string; +}; + +export type ProviderReadinessResponse = { + providers: { + [key: string]: ProviderReadiness; + }; +}; + +export type ProviderResponse = { + args?: Array | null; + builtin: boolean; + city_level: boolean; + command?: string; + display_name?: string; + env?: { + [key: string]: string; + }; + name: string; + prompt_flag?: string; + prompt_mode?: string; + ready_delay_ms?: number; +}; + +export type ProviderSpecJson = { + args?: Array | null; + command?: string; + display_name?: string; + env?: { + [key: string]: string; + }; + prompt_flag?: string; + prompt_mode?: string; + ready_delay_ms?: number; +}; + +export type ProviderUpdateInputBody = { + /** + * Command arguments. + */ + args?: Array | null; + /** + * Arguments appended after inherited/base args. + */ + args_append?: Array | null; + /** + * Provider base for inheritance. + */ + base?: string; + /** + * Provider command binary. + */ + command?: string; + /** + * Human-readable display name. + */ + display_name?: string; + /** + * Environment variables. + */ + env?: { + [key: string]: string; + }; + /** + * Options schema merge mode across inheritance chain. + */ + options_schema_merge?: string; + /** + * Flag for prompt delivery. + */ + prompt_flag?: string; + /** + * Prompt delivery mode. + */ + prompt_mode?: string; + /** + * Milliseconds to wait before probing readiness. + */ + ready_delay_ms?: number; +}; + +export type PublishReceipt = { + Conversation: ConversationRef; + Delivered: boolean; + FailureKind: string; + MessageID: string; + Metadata: { + [key: string]: string; + }; + RetryAfter: number; +}; + +export type ReadinessItem = { + detail?: string; + display_name: string; + kind: string; + name: string; + status: string; +}; + +export type ReadinessResponse = { + items: { + [key: string]: ReadinessItem; + }; +}; + +export type RigActionBody = { + /** + * Action that was performed. + */ + action: string; + /** + * Agents that failed to stop (restart only). + */ + failed?: Array | null; + /** + * Agents that were killed (restart only). + */ + killed?: Array | null; + /** + * Rig name. + */ + rig: string; + /** + * Operation result (ok, partial, failed). + */ + status: string; +}; + +export type RigCreateInputBody = { + /** + * Rig name. + */ + name: string; + /** + * Filesystem path. + */ + path: string; + /** + * Session name prefix. + */ + prefix?: string; +}; + +export type RigCreatedOutputBody = { + /** + * Created rig name. + */ + rig: string; + /** + * Operation result. + */ + status: string; +}; + +export type RigPatch = { + Name: string; + Path: string | null; + Prefix: string | null; + Suspended: boolean | null; +}; + +export type RigPatchSetInputBody = { + /** + * Rig name. + */ + name?: string; + /** + * Override filesystem path. + */ + path?: string; + /** + * Override bead ID prefix. + */ + prefix?: string; + /** + * Override suspended state. + */ + suspended?: boolean; +}; + +export type RigResponse = { + agent_count: number; + git?: GitStatus; + last_activity?: string; + name: string; + path: string; + prefix?: string; + running_count: number; + suspended: boolean; +}; + +export type RigUpdateInputBody = { + /** + * Filesystem path. + */ + path?: string; + /** + * Session name prefix. + */ + prefix?: string; + /** + * Whether rig is suspended. + */ + suspended?: boolean; +}; + +export type ScopeGroup = { + [key: string]: never; +}; + +export type ServiceRestartOutputBody = { + /** + * Action performed. + */ + action: string; + /** + * Service name. + */ + service: string; + /** + * Operation result. + */ + status: string; +}; + +export type SessionActivityEvent = { + /** + * Session activity state: 'idle' or 'in-turn'. + */ + activity: string; +}; + +export type SessionAgentGetResponse = { + messages: Array | null; + status?: string; +}; + +export type SessionAgentListResponse = { + agents: Array | null; +}; + +export type SessionBindingRecord = { + BindingGeneration: number; + BoundAt: string; + Conversation: ConversationRef; + ExpiresAt: string | null; + ID: string; + Metadata: { + [key: string]: string; + }; + SchemaVersion: number; + SessionID: string; + Status: BindingStatus; +}; + +export type SessionCreateBody = { + /** + * Optional session alias. + */ + alias?: string; + /** + * Create session asynchronously (agent only). + */ + async?: boolean; + /** + * Session target kind: agent or provider. + */ + kind?: string; + /** + * Initial message to send to the session. + */ + message?: string; + /** + * Agent or provider name. + */ + name?: string; + /** + * Provider/agent option overrides. + */ + options?: { + [key: string]: string; + }; + /** + * Opaque project context identifier. + */ + project_id?: string; + /** + * Deprecated: use alias. + */ + session_name?: string; + /** + * Session title. + */ + title?: string; +}; + +export type SessionInfo = { + attached: boolean; + last_activity?: string; + name: string; +}; + +export type SessionMessageInputBody = { + /** + * Message text to send. + */ + message: string; +}; + +export type SessionMessageOutputBody = { + /** + * Session ID. + */ + id: string; + /** + * Operation result. + */ + status: string; +}; + +export type SessionPatchBody = { + /** + * Session alias. Empty string clears the alias. + */ + alias?: string; + /** + * Session title. If provided, must be non-empty. + */ + title?: string; +}; + +export type SessionPendingResponse = { + pending?: PendingInteraction; + supported: boolean; +}; + +/** + * Session raw transcript frame + * + * Provider-native transcript frame. Gas City forwards the exact JSON the provider wrote to its session log, so the shape is provider-specific and can be any JSON value. The producing provider is identified by the Provider field on the enclosing envelope; consumers dispatch per-provider frame parsing keyed by that identifier. + */ +export type SessionRawMessageFrame = unknown; + +export type SessionRenameInputBody = { + /** + * New session title. + */ + title: string; +}; + +export type SessionRespondInputBody = { + /** + * Response action (e.g. allow, deny). + */ + action: string; + /** + * Optional response metadata. + */ + metadata?: { + [key: string]: string; + }; + /** + * Pending interaction request ID (optional). + */ + request_id?: string; + /** + * Optional response text. + */ + text?: string; +}; + +export type SessionRespondOutputBody = { + /** + * Session ID. + */ + id: string; + /** + * Operation result. + */ + status: string; +}; + +export type SessionResponse = { + active_bead?: string; + activity?: string; + alias?: string; + attached: boolean; + configured_named_session?: boolean; + context_pct?: number; + context_window?: number; + created_at: string; + display_name?: string; + id: string; + kind?: string; + last_active?: string; + last_output?: string; + metadata?: { + [key: string]: string; + }; + model?: string; + options?: { + [key: string]: string; + }; + pool?: string; + provider: string; + reason?: string; + rig?: string; + running: boolean; + session_name: string; + state: string; + submission_capabilities?: SubmissionCapabilities; + template: string; + title: string; +}; + +/** + * Session stream lifecycle event + * + * Non-message events emitted on the session SSE stream: activity transitions, pending interactions, and keepalive heartbeats. The concrete variant is identified by the SSE event name. + */ +export type SessionStreamCommonEvent = SessionActivityEvent | PendingInteraction | HeartbeatEvent; + +export type SessionStreamMessageEvent = { + format: string; + id: string; + pagination?: PaginationInfo; + /** + * Producing provider identifier (claude, codex, gemini, open-code, etc.). + */ + provider: string; + template: string; + turns: Array | null; +}; + +export type SessionStreamRawMessageEvent = { + format: string; + id: string; + /** + * Provider-native transcript frames, emitted verbatim as the provider wrote them. + */ + messages: Array | null; + pagination?: PaginationInfo; + /** + * Producing provider identifier (claude, codex, gemini, open-code, etc.). Consumers use this to dispatch per-provider frame parsing. + */ + provider: string; + template: string; +}; + +export type SessionSubmitInputBody = { + /** + * Submit intent; empty defaults to "default". + */ + intent?: SubmitIntent; + /** + * Message text to submit. + */ + message: string; +}; + +export type SessionSubmitOutputBody = { + /** + * Session ID. + */ + id: string; + /** + * Resolved submit intent. + */ + intent: string; + /** + * Whether the message was queued. + */ + queued: boolean; + /** + * Operation result. + */ + status: string; +}; + +export type SessionTranscriptGetResponse = { + /** + * conversation, text, or raw. + */ + format: string; + id: string; + /** + * Populated for raw format; provider-native frames emitted verbatim as the provider wrote them. + */ + messages?: Array | null; + pagination?: PaginationInfo; + /** + * Producing provider identifier (claude, codex, gemini, open-code, etc.). Consumers use this to dispatch per-provider frame parsing. + */ + provider: string; + template: string; + /** + * Populated for conversation/text formats. + */ + turns?: Array | null; +}; + +export type SlingInputBody = { + /** + * Bead ID to attach a formula to. + */ + attached_bead_id?: string; + /** + * Bead ID to sling. + */ + bead?: string; + /** + * Formula name for workflow launch. + */ + formula?: string; + /** + * Rig name. + */ + rig?: string; + /** + * Scope kind (city or rig). + */ + scope_kind?: string; + /** + * Scope reference. + */ + scope_ref?: string; + /** + * Target agent or pool. + */ + target: string; + /** + * Workflow title. + */ + title?: string; + /** + * Formula variables. + */ + vars?: { + [key: string]: string; + }; +}; + +export type SlingResponse = { + attached_bead_id?: string; + bead?: string; + formula?: string; + mode?: string; + root_bead_id?: string; + status: string; + target: string; + warnings?: Array | null; + workflow_id?: string; +}; + +export type Status = { + allow_websockets?: boolean; + hostname?: string; + kind?: string; + local_state: string; + mount_path: string; + publication_state: string; + publish_mode: string; + reason?: string; + service_name: string; + state?: string; + state_root: string; + updated_at: string; + url?: string; + visibility?: string; + workflow_contract?: string; +}; + +export type StatusAgentCounts = { + /** + * Number of quarantined agents. + */ + quarantined: number; + /** + * Number of running agents. + */ + running: number; + /** + * Number of suspended agents. + */ + suspended: number; + /** + * Total number of agents. + */ + total: number; +}; + +export type StatusBody = { + /** + * Total agent count (deprecated, use agents.total). + */ + agent_count: number; + /** + * Agent state counts. + */ + agents: StatusAgentCounts; + /** + * Mail counts. + */ + mail: StatusMailCounts; + /** + * City name. + */ + name: string; + /** + * City directory path. + */ + path: string; + /** + * Total rig count (deprecated, use rigs.total). + */ + rig_count: number; + /** + * Rig state counts. + */ + rigs: StatusRigCounts; + /** + * Number of running agent processes. + */ + running: number; + /** + * Whether the city is suspended. + */ + suspended: boolean; + /** + * Server uptime in seconds. + */ + uptime_sec: number; + /** + * Server version. + */ + version?: string; + /** + * Work item counts. + */ + work: StatusWorkCounts; +}; + +export type StatusMailCounts = { + /** + * Total number of messages. + */ + total: number; + /** + * Number of unread messages. + */ + unread: number; +}; + +export type StatusRigCounts = { + /** + * Number of suspended rigs. + */ + suspended: number; + /** + * Total number of rigs. + */ + total: number; +}; + +export type StatusWorkCounts = { + /** + * Number of in-progress work items. + */ + in_progress: number; + /** + * Number of open work items. + */ + open: number; + /** + * Number of ready work items. + */ + ready: number; +}; + +export type SubmissionCapabilities = { + supports_follow_up: boolean; + supports_interrupt_now: boolean; +}; + +/** + * Semantic delivery choice for a user message on a session submit request. + */ +export type SubmitIntent = 'default' | 'follow_up' | 'interrupt_now'; + +export type SupervisorCitiesOutputBody = { + /** + * Managed cities with status info. + */ + items: Array | null; + /** + * Total count. + */ + total: number; +}; + +export type SupervisorEventListOutputBody = { + items: Array | null; + total: number; +}; + +export type SupervisorHealthOutputBody = { + /** + * Cities currently running. + */ + cities_running: number; + /** + * Total managed cities. + */ + cities_total: number; + /** + * First-city startup info for single-city deployments. + */ + startup?: SupervisorStartup; + /** + * Health status ("ok"). + */ + status: string; + /** + * Supervisor uptime in seconds. + */ + uptime_sec: number; + /** + * Supervisor version. + */ + version: string; +}; + +export type SupervisorStartup = { + /** + * Current phase (when not ready). + */ + phase?: string; + /** + * Phases completed so far. + */ + phases_completed?: Array | null; + /** + * True when the city is running. + */ + ready: boolean; +}; + +export type TaggedEventStreamEnvelope = { + actor: string; + city: string; + message?: string; + payload?: EventPayload; + seq: number; + subject?: string; + ts: string; + type: string; + workflow?: WorkflowEventProjection; +}; + +/** + * Direction of a transcript entry. + */ +export type TranscriptMessageKind = 'inbound' | 'outbound'; + +/** + * Provenance of a transcript entry (freshly observed vs. replayed from persisted history). + */ +export type TranscriptProvenance = 'live' | 'hydrated'; + +export type UnboundEventPayload = { + count: number; + session_id: string; +}; + +export type WireEvent = { + actor: string; + message?: string; + payload?: EventPayload; + seq: number; + subject?: string; + ts: string; + type: string; +}; + +export type WireTaggedEvent = { + actor: string; + city: string; + message?: string; + payload?: EventPayload; + seq: number; + subject?: string; + ts: string; + type: string; +}; + +export type WorkerOperationEventPayload = { + delivered?: boolean; + duration_ms: number; + error?: string; + finished_at: string; + op_id: string; + operation: string; + provider?: string; + queued?: boolean; + result: string; + session_id?: string; + session_name?: string; + started_at: string; + template?: string; + transport?: string; +}; + +export type WorkflowAttemptSummary = { + active_attempt: number; + attempt_count: number; + max_attempts?: number; +}; + +export type WorkflowBeadResponse = { + assignee?: string; + attempt?: number; + id: string; + kind: string; + logical_bead_id?: string; + metadata: { + [key: string]: string; + }; + scope_ref?: string; + status: string; + step_ref?: string; + title: string; +}; + +export type WorkflowDeleteResponse = { + /** + * Number of beads closed. + */ + closed: number; + /** + * Number of beads deleted. + */ + deleted: number; + /** + * True when one or more teardown steps failed; Closed/Deleted still reflect what succeeded. + */ + partial?: boolean; + /** + * Human-readable errors from failed teardown steps. + */ + partial_errors?: Array | null; + /** + * Workflow ID. + */ + workflow_id: string; +}; + +export type WorkflowDepResponse = { + from: string; + kind?: string; + to: string; +}; + +export type WorkflowEventProjection = { + attempt_summary?: WorkflowAttemptSummary; + bead: WorkflowBeadResponse; + changed_fields: Array | null; + event_seq: number; + event_ts: string; + event_type: string; + logical_node_id: string; + requires_resync?: boolean; + root_bead_id: string; + root_store_ref: string; + scope_kind: string; + scope_ref: string; + type: string; + watch_generation: string; + workflow_id: string; + workflow_seq: number; +}; + +export type WorkflowSnapshotResponse = { + beads: Array | null; + deps: Array | null; + logical_edges: Array | null; + logical_nodes: Array | null; + partial: boolean; + resolved_root_store: string; + root_bead_id: string; + root_store_ref: string; + scope_groups: Array | null; + scope_kind: string; + scope_ref: string; + snapshot_event_seq?: number; + snapshot_version: number; + stores_scanned: Array | null; + workflow_id: string; +}; + +export type WorkspaceResponse = { + declared_name?: string; + declared_prefix?: string; + name: string; + prefix?: string; + provider?: string; + session_template?: string; + suspended: boolean; +}; + +export type GetHealthData = { + body?: never; + path?: never; + query?: never; + url: '/health'; +}; + +export type GetHealthErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetHealthError = GetHealthErrors[keyof GetHealthErrors]; + +export type GetHealthResponses = { + /** + * OK + */ + 200: SupervisorHealthOutputBody; +}; + +export type GetHealthResponse = GetHealthResponses[keyof GetHealthResponses]; + +export type GetV0CitiesData = { + body?: never; + path?: never; + query?: never; + url: '/v0/cities'; +}; + +export type GetV0CitiesErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CitiesError = GetV0CitiesErrors[keyof GetV0CitiesErrors]; + +export type GetV0CitiesResponses = { + /** + * OK + */ + 200: SupervisorCitiesOutputBody; +}; + +export type GetV0CitiesResponse = GetV0CitiesResponses[keyof GetV0CitiesResponses]; + +export type PostV0CityData = { + body: CityCreateRequest; + path?: never; + query?: never; + url: '/v0/city'; +}; + +export type PostV0CityErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityError = PostV0CityErrors[keyof PostV0CityErrors]; + +export type PostV0CityResponses = { + /** + * OK + */ + 200: CityCreateResponse; +}; + +export type PostV0CityResponse = PostV0CityResponses[keyof PostV0CityResponses]; + +export type GetV0CityByCityNameData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}'; +}; + +export type GetV0CityByCityNameErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameError = GetV0CityByCityNameErrors[keyof GetV0CityByCityNameErrors]; + +export type GetV0CityByCityNameResponses = { + /** + * OK + */ + 200: CityGetResponse; +}; + +export type GetV0CityByCityNameResponse = GetV0CityByCityNameResponses[keyof GetV0CityByCityNameResponses]; + +export type PatchV0CityByCityNameData = { + body: CityPatchInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}'; +}; + +export type PatchV0CityByCityNameErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PatchV0CityByCityNameError = PatchV0CityByCityNameErrors[keyof PatchV0CityByCityNameErrors]; + +export type PatchV0CityByCityNameResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PatchV0CityByCityNameResponse = PatchV0CityByCityNameResponses[keyof PatchV0CityByCityNameResponses]; + +export type DeleteV0CityByCityNameAgentByBaseData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Agent name (unqualified). + */ + base: string; + }; + query?: never; + url: '/v0/city/{cityName}/agent/{base}'; +}; + +export type DeleteV0CityByCityNameAgentByBaseErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type DeleteV0CityByCityNameAgentByBaseError = DeleteV0CityByCityNameAgentByBaseErrors[keyof DeleteV0CityByCityNameAgentByBaseErrors]; + +export type DeleteV0CityByCityNameAgentByBaseResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type DeleteV0CityByCityNameAgentByBaseResponse = DeleteV0CityByCityNameAgentByBaseResponses[keyof DeleteV0CityByCityNameAgentByBaseResponses]; + +export type GetV0CityByCityNameAgentByBaseData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Agent name (unqualified, no rig). + */ + base: string; + }; + query?: never; + url: '/v0/city/{cityName}/agent/{base}'; +}; + +export type GetV0CityByCityNameAgentByBaseErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameAgentByBaseError = GetV0CityByCityNameAgentByBaseErrors[keyof GetV0CityByCityNameAgentByBaseErrors]; + +export type GetV0CityByCityNameAgentByBaseResponses = { + /** + * OK + */ + 200: AgentResponse; +}; + +export type GetV0CityByCityNameAgentByBaseResponse = GetV0CityByCityNameAgentByBaseResponses[keyof GetV0CityByCityNameAgentByBaseResponses]; + +export type PatchV0CityByCityNameAgentByBaseData = { + body: AgentUpdateInputBody; + path: { + /** + * City name. + */ + cityName: string; + /** + * Agent name (unqualified). + */ + base: string; + }; + query?: never; + url: '/v0/city/{cityName}/agent/{base}'; +}; + +export type PatchV0CityByCityNameAgentByBaseErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PatchV0CityByCityNameAgentByBaseError = PatchV0CityByCityNameAgentByBaseErrors[keyof PatchV0CityByCityNameAgentByBaseErrors]; + +export type PatchV0CityByCityNameAgentByBaseResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PatchV0CityByCityNameAgentByBaseResponse = PatchV0CityByCityNameAgentByBaseResponses[keyof PatchV0CityByCityNameAgentByBaseResponses]; + +export type GetV0CityByCityNameAgentByBaseOutputData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Agent base name. + */ + base: string; + }; + query?: { + /** + * Number of recent compaction segments to return. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. + */ + tail?: string; + /** + * Message UUID cursor for loading older messages. + */ + before?: string; + }; + url: '/v0/city/{cityName}/agent/{base}/output'; +}; + +export type GetV0CityByCityNameAgentByBaseOutputErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameAgentByBaseOutputError = GetV0CityByCityNameAgentByBaseOutputErrors[keyof GetV0CityByCityNameAgentByBaseOutputErrors]; + +export type GetV0CityByCityNameAgentByBaseOutputResponses = { + /** + * OK + */ + 200: AgentOutputResponse; +}; + +export type GetV0CityByCityNameAgentByBaseOutputResponse = GetV0CityByCityNameAgentByBaseOutputResponses[keyof GetV0CityByCityNameAgentByBaseOutputResponses]; + +export type StreamAgentOutputData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Agent base name. + */ + base: string; + }; + query?: never; + url: '/v0/city/{cityName}/agent/{base}/output/stream'; +}; + +export type StreamAgentOutputErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type StreamAgentOutputError = StreamAgentOutputErrors[keyof StreamAgentOutputErrors]; + +export type StreamAgentOutputResponses = { + /** + * Server Sent Events + * + * Each oneOf object represents one possible SSE message. + */ + 200: Array<{ + data: HeartbeatEvent; + /** + * The event name. + */ + event: 'heartbeat'; + /** + * The event ID. + */ + id?: number; + /** + * The retry time in milliseconds. + */ + retry?: number; + } | { + data: AgentOutputResponse; + /** + * The event name. + */ + event: 'turn'; + /** + * The event ID. + */ + id?: number; + /** + * The retry time in milliseconds. + */ + retry?: number; + }>; +}; + +export type StreamAgentOutputResponse = StreamAgentOutputResponses[keyof StreamAgentOutputResponses]; + +export type PostV0CityByCityNameAgentByBaseByActionData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Agent name (unqualified). + */ + base: string; + /** + * Action to perform. + */ + action: 'suspend' | 'resume'; + }; + query?: never; + url: '/v0/city/{cityName}/agent/{base}/{action}'; +}; + +export type PostV0CityByCityNameAgentByBaseByActionErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameAgentByBaseByActionError = PostV0CityByCityNameAgentByBaseByActionErrors[keyof PostV0CityByCityNameAgentByBaseByActionErrors]; + +export type PostV0CityByCityNameAgentByBaseByActionResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PostV0CityByCityNameAgentByBaseByActionResponse = PostV0CityByCityNameAgentByBaseByActionResponses[keyof PostV0CityByCityNameAgentByBaseByActionResponses]; + +export type DeleteV0CityByCityNameAgentByDirByBaseData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Agent directory (rig name). + */ + dir: string; + /** + * Agent base name. + */ + base: string; + }; + query?: never; + url: '/v0/city/{cityName}/agent/{dir}/{base}'; +}; + +export type DeleteV0CityByCityNameAgentByDirByBaseErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type DeleteV0CityByCityNameAgentByDirByBaseError = DeleteV0CityByCityNameAgentByDirByBaseErrors[keyof DeleteV0CityByCityNameAgentByDirByBaseErrors]; + +export type DeleteV0CityByCityNameAgentByDirByBaseResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type DeleteV0CityByCityNameAgentByDirByBaseResponse = DeleteV0CityByCityNameAgentByDirByBaseResponses[keyof DeleteV0CityByCityNameAgentByDirByBaseResponses]; + +export type GetV0CityByCityNameAgentByDirByBaseData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Agent directory (rig name). + */ + dir: string; + /** + * Agent base name. + */ + base: string; + }; + query?: never; + url: '/v0/city/{cityName}/agent/{dir}/{base}'; +}; + +export type GetV0CityByCityNameAgentByDirByBaseErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameAgentByDirByBaseError = GetV0CityByCityNameAgentByDirByBaseErrors[keyof GetV0CityByCityNameAgentByDirByBaseErrors]; + +export type GetV0CityByCityNameAgentByDirByBaseResponses = { + /** + * OK + */ + 200: AgentResponse; +}; + +export type GetV0CityByCityNameAgentByDirByBaseResponse = GetV0CityByCityNameAgentByDirByBaseResponses[keyof GetV0CityByCityNameAgentByDirByBaseResponses]; + +export type PatchV0CityByCityNameAgentByDirByBaseData = { + body: AgentUpdateQualifiedInputBody; + path: { + /** + * City name. + */ + cityName: string; + /** + * Agent directory (rig name). + */ + dir: string; + /** + * Agent base name. + */ + base: string; + }; + query?: never; + url: '/v0/city/{cityName}/agent/{dir}/{base}'; +}; + +export type PatchV0CityByCityNameAgentByDirByBaseErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PatchV0CityByCityNameAgentByDirByBaseError = PatchV0CityByCityNameAgentByDirByBaseErrors[keyof PatchV0CityByCityNameAgentByDirByBaseErrors]; + +export type PatchV0CityByCityNameAgentByDirByBaseResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PatchV0CityByCityNameAgentByDirByBaseResponse = PatchV0CityByCityNameAgentByDirByBaseResponses[keyof PatchV0CityByCityNameAgentByDirByBaseResponses]; + +export type GetV0CityByCityNameAgentByDirByBaseOutputData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Agent directory (rig name). + */ + dir: string; + /** + * Agent base name. + */ + base: string; + }; + query?: { + /** + * Number of recent compaction segments to return. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. + */ + tail?: string; + /** + * Message UUID cursor for loading older messages. + */ + before?: string; + }; + url: '/v0/city/{cityName}/agent/{dir}/{base}/output'; +}; + +export type GetV0CityByCityNameAgentByDirByBaseOutputErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameAgentByDirByBaseOutputError = GetV0CityByCityNameAgentByDirByBaseOutputErrors[keyof GetV0CityByCityNameAgentByDirByBaseOutputErrors]; + +export type GetV0CityByCityNameAgentByDirByBaseOutputResponses = { + /** + * OK + */ + 200: AgentOutputResponse; +}; + +export type GetV0CityByCityNameAgentByDirByBaseOutputResponse = GetV0CityByCityNameAgentByDirByBaseOutputResponses[keyof GetV0CityByCityNameAgentByDirByBaseOutputResponses]; + +export type StreamAgentOutputQualifiedData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Agent directory (rig name). + */ + dir: string; + /** + * Agent base name. + */ + base: string; + }; + query?: never; + url: '/v0/city/{cityName}/agent/{dir}/{base}/output/stream'; +}; + +export type StreamAgentOutputQualifiedErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type StreamAgentOutputQualifiedError = StreamAgentOutputQualifiedErrors[keyof StreamAgentOutputQualifiedErrors]; + +export type StreamAgentOutputQualifiedResponses = { + /** + * Server Sent Events + * + * Each oneOf object represents one possible SSE message. + */ + 200: Array<{ + data: HeartbeatEvent; + /** + * The event name. + */ + event: 'heartbeat'; + /** + * The event ID. + */ + id?: number; + /** + * The retry time in milliseconds. + */ + retry?: number; + } | { + data: AgentOutputResponse; + /** + * The event name. + */ + event: 'turn'; + /** + * The event ID. + */ + id?: number; + /** + * The retry time in milliseconds. + */ + retry?: number; + }>; +}; + +export type StreamAgentOutputQualifiedResponse = StreamAgentOutputQualifiedResponses[keyof StreamAgentOutputQualifiedResponses]; + +export type PostV0CityByCityNameAgentByDirByBaseByActionData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Agent directory (rig name). + */ + dir: string; + /** + * Agent base name. + */ + base: string; + /** + * Action to perform. + */ + action: 'suspend' | 'resume'; + }; + query?: never; + url: '/v0/city/{cityName}/agent/{dir}/{base}/{action}'; +}; + +export type PostV0CityByCityNameAgentByDirByBaseByActionErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameAgentByDirByBaseByActionError = PostV0CityByCityNameAgentByDirByBaseByActionErrors[keyof PostV0CityByCityNameAgentByDirByBaseByActionErrors]; + +export type PostV0CityByCityNameAgentByDirByBaseByActionResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PostV0CityByCityNameAgentByDirByBaseByActionResponse = PostV0CityByCityNameAgentByDirByBaseByActionResponses[keyof PostV0CityByCityNameAgentByDirByBaseByActionResponses]; + +export type GetV0CityByCityNameAgentsData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Event sequence number; when provided, blocks until a newer event arrives. + */ + index?: string; + /** + * How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. + */ + wait?: string; + /** + * Filter by pool name. + */ + pool?: string; + /** + * Filter by rig name. + */ + rig?: string; + /** + * Filter by running state. Omit to return all agents. + */ + running?: 'true' | 'false'; + /** + * Include last output preview. + */ + peek?: boolean; + }; + url: '/v0/city/{cityName}/agents'; +}; + +export type GetV0CityByCityNameAgentsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameAgentsError = GetV0CityByCityNameAgentsErrors[keyof GetV0CityByCityNameAgentsErrors]; + +export type GetV0CityByCityNameAgentsResponses = { + /** + * OK + */ + 200: ListBodyAgentResponse; +}; + +export type GetV0CityByCityNameAgentsResponse = GetV0CityByCityNameAgentsResponses[keyof GetV0CityByCityNameAgentsResponses]; + +export type CreateAgentData = { + body: AgentCreateInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/agents'; +}; + +export type CreateAgentErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type CreateAgentError = CreateAgentErrors[keyof CreateAgentErrors]; + +export type CreateAgentResponses = { + /** + * Created + */ + 201: AgentCreatedOutputBody; +}; + +export type CreateAgentResponse = CreateAgentResponses[keyof CreateAgentResponses]; + +export type DeleteV0CityByCityNameBeadByIdData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Bead ID. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/bead/{id}'; +}; + +export type DeleteV0CityByCityNameBeadByIdErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type DeleteV0CityByCityNameBeadByIdError = DeleteV0CityByCityNameBeadByIdErrors[keyof DeleteV0CityByCityNameBeadByIdErrors]; + +export type DeleteV0CityByCityNameBeadByIdResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type DeleteV0CityByCityNameBeadByIdResponse = DeleteV0CityByCityNameBeadByIdResponses[keyof DeleteV0CityByCityNameBeadByIdResponses]; + +export type GetV0CityByCityNameBeadByIdData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Bead ID. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/bead/{id}'; +}; + +export type GetV0CityByCityNameBeadByIdErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameBeadByIdError = GetV0CityByCityNameBeadByIdErrors[keyof GetV0CityByCityNameBeadByIdErrors]; + +export type GetV0CityByCityNameBeadByIdResponses = { + /** + * OK + */ + 200: Bead; +}; + +export type GetV0CityByCityNameBeadByIdResponse = GetV0CityByCityNameBeadByIdResponses[keyof GetV0CityByCityNameBeadByIdResponses]; + +export type PatchV0CityByCityNameBeadByIdData = { + body: BeadUpdateBody; + path: { + /** + * City name. + */ + cityName: string; + /** + * Bead ID. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/bead/{id}'; +}; + +export type PatchV0CityByCityNameBeadByIdErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PatchV0CityByCityNameBeadByIdError = PatchV0CityByCityNameBeadByIdErrors[keyof PatchV0CityByCityNameBeadByIdErrors]; + +export type PatchV0CityByCityNameBeadByIdResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PatchV0CityByCityNameBeadByIdResponse = PatchV0CityByCityNameBeadByIdResponses[keyof PatchV0CityByCityNameBeadByIdResponses]; + +export type PostV0CityByCityNameBeadByIdAssignData = { + body: BeadAssignInputBody; + path: { + /** + * City name. + */ + cityName: string; + /** + * Bead ID. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/bead/{id}/assign'; +}; + +export type PostV0CityByCityNameBeadByIdAssignErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameBeadByIdAssignError = PostV0CityByCityNameBeadByIdAssignErrors[keyof PostV0CityByCityNameBeadByIdAssignErrors]; + +export type PostV0CityByCityNameBeadByIdAssignResponses = { + /** + * OK + */ + 200: { + [key: string]: string; + }; +}; + +export type PostV0CityByCityNameBeadByIdAssignResponse = PostV0CityByCityNameBeadByIdAssignResponses[keyof PostV0CityByCityNameBeadByIdAssignResponses]; + +export type PostV0CityByCityNameBeadByIdCloseData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Bead ID. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/bead/{id}/close'; +}; + +export type PostV0CityByCityNameBeadByIdCloseErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameBeadByIdCloseError = PostV0CityByCityNameBeadByIdCloseErrors[keyof PostV0CityByCityNameBeadByIdCloseErrors]; + +export type PostV0CityByCityNameBeadByIdCloseResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PostV0CityByCityNameBeadByIdCloseResponse = PostV0CityByCityNameBeadByIdCloseResponses[keyof PostV0CityByCityNameBeadByIdCloseResponses]; + +export type GetV0CityByCityNameBeadByIdDepsData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Bead ID. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/bead/{id}/deps'; +}; + +export type GetV0CityByCityNameBeadByIdDepsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameBeadByIdDepsError = GetV0CityByCityNameBeadByIdDepsErrors[keyof GetV0CityByCityNameBeadByIdDepsErrors]; + +export type GetV0CityByCityNameBeadByIdDepsResponses = { + /** + * OK + */ + 200: BeadDepsResponse; +}; + +export type GetV0CityByCityNameBeadByIdDepsResponse = GetV0CityByCityNameBeadByIdDepsResponses[keyof GetV0CityByCityNameBeadByIdDepsResponses]; + +export type PostV0CityByCityNameBeadByIdReopenData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Bead ID. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/bead/{id}/reopen'; +}; + +export type PostV0CityByCityNameBeadByIdReopenErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameBeadByIdReopenError = PostV0CityByCityNameBeadByIdReopenErrors[keyof PostV0CityByCityNameBeadByIdReopenErrors]; + +export type PostV0CityByCityNameBeadByIdReopenResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PostV0CityByCityNameBeadByIdReopenResponse = PostV0CityByCityNameBeadByIdReopenResponses[keyof PostV0CityByCityNameBeadByIdReopenResponses]; + +export type PostV0CityByCityNameBeadByIdUpdateData = { + body: BeadUpdateBody; + path: { + /** + * City name. + */ + cityName: string; + /** + * Bead ID. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/bead/{id}/update'; +}; + +export type PostV0CityByCityNameBeadByIdUpdateErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameBeadByIdUpdateError = PostV0CityByCityNameBeadByIdUpdateErrors[keyof PostV0CityByCityNameBeadByIdUpdateErrors]; + +export type PostV0CityByCityNameBeadByIdUpdateResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PostV0CityByCityNameBeadByIdUpdateResponse = PostV0CityByCityNameBeadByIdUpdateResponses[keyof PostV0CityByCityNameBeadByIdUpdateResponses]; + +export type GetV0CityByCityNameBeadsData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Event sequence number; when provided, blocks until a newer event arrives. + */ + index?: string; + /** + * How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. + */ + wait?: string; + /** + * Pagination cursor from a previous response's next_cursor field. + */ + cursor?: string; + /** + * Maximum number of results to return. 0 = server default. + */ + limit?: number; + /** + * Filter by bead status. + */ + status?: string; + /** + * Filter by bead type. + */ + type?: string; + /** + * Filter by label. + */ + label?: string; + /** + * Filter by assignee. + */ + assignee?: string; + /** + * Filter by rig. + */ + rig?: string; + }; + url: '/v0/city/{cityName}/beads'; +}; + +export type GetV0CityByCityNameBeadsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameBeadsError = GetV0CityByCityNameBeadsErrors[keyof GetV0CityByCityNameBeadsErrors]; + +export type GetV0CityByCityNameBeadsResponses = { + /** + * OK + */ + 200: ListBodyBead; +}; + +export type GetV0CityByCityNameBeadsResponse = GetV0CityByCityNameBeadsResponses[keyof GetV0CityByCityNameBeadsResponses]; + +export type CreateBeadData = { + body: BeadCreateInputBody; + headers?: { + /** + * Idempotency key for safe retries. + */ + 'Idempotency-Key'?: string; + }; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/beads'; +}; + +export type CreateBeadErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type CreateBeadError = CreateBeadErrors[keyof CreateBeadErrors]; + +export type CreateBeadResponses = { + /** + * Created + */ + 201: Bead; +}; + +export type CreateBeadResponse = CreateBeadResponses[keyof CreateBeadResponses]; + +export type GetV0CityByCityNameBeadsGraphByRootIdData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Root bead ID for the graph. + */ + rootID: string; + }; + query?: never; + url: '/v0/city/{cityName}/beads/graph/{rootID}'; +}; + +export type GetV0CityByCityNameBeadsGraphByRootIdErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameBeadsGraphByRootIdError = GetV0CityByCityNameBeadsGraphByRootIdErrors[keyof GetV0CityByCityNameBeadsGraphByRootIdErrors]; + +export type GetV0CityByCityNameBeadsGraphByRootIdResponses = { + /** + * OK + */ + 200: BeadGraphResponse; +}; + +export type GetV0CityByCityNameBeadsGraphByRootIdResponse = GetV0CityByCityNameBeadsGraphByRootIdResponses[keyof GetV0CityByCityNameBeadsGraphByRootIdResponses]; + +export type GetV0CityByCityNameBeadsReadyData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Event sequence number; when provided, blocks until a newer event arrives. + */ + index?: string; + /** + * How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. + */ + wait?: string; + }; + url: '/v0/city/{cityName}/beads/ready'; +}; + +export type GetV0CityByCityNameBeadsReadyErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameBeadsReadyError = GetV0CityByCityNameBeadsReadyErrors[keyof GetV0CityByCityNameBeadsReadyErrors]; + +export type GetV0CityByCityNameBeadsReadyResponses = { + /** + * OK + */ + 200: ListBodyBead; +}; + +export type GetV0CityByCityNameBeadsReadyResponse = GetV0CityByCityNameBeadsReadyResponses[keyof GetV0CityByCityNameBeadsReadyResponses]; + +export type GetV0CityByCityNameConfigData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/config'; +}; + +export type GetV0CityByCityNameConfigErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameConfigError = GetV0CityByCityNameConfigErrors[keyof GetV0CityByCityNameConfigErrors]; + +export type GetV0CityByCityNameConfigResponses = { + /** + * OK + */ + 200: ConfigResponse; +}; + +export type GetV0CityByCityNameConfigResponse = GetV0CityByCityNameConfigResponses[keyof GetV0CityByCityNameConfigResponses]; + +export type GetV0CityByCityNameConfigExplainData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/config/explain'; +}; + +export type GetV0CityByCityNameConfigExplainErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameConfigExplainError = GetV0CityByCityNameConfigExplainErrors[keyof GetV0CityByCityNameConfigExplainErrors]; + +export type GetV0CityByCityNameConfigExplainResponses = { + /** + * OK + */ + 200: ConfigExplainResponse; +}; + +export type GetV0CityByCityNameConfigExplainResponse = GetV0CityByCityNameConfigExplainResponses[keyof GetV0CityByCityNameConfigExplainResponses]; + +export type GetV0CityByCityNameConfigValidateData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/config/validate'; +}; + +export type GetV0CityByCityNameConfigValidateErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameConfigValidateError = GetV0CityByCityNameConfigValidateErrors[keyof GetV0CityByCityNameConfigValidateErrors]; + +export type GetV0CityByCityNameConfigValidateResponses = { + /** + * OK + */ + 200: ConfigValidateOutputBody; +}; + +export type GetV0CityByCityNameConfigValidateResponse = GetV0CityByCityNameConfigValidateResponses[keyof GetV0CityByCityNameConfigValidateResponses]; + +export type DeleteV0CityByCityNameConvoyByIdData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Convoy ID. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/convoy/{id}'; +}; + +export type DeleteV0CityByCityNameConvoyByIdErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type DeleteV0CityByCityNameConvoyByIdError = DeleteV0CityByCityNameConvoyByIdErrors[keyof DeleteV0CityByCityNameConvoyByIdErrors]; + +export type DeleteV0CityByCityNameConvoyByIdResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type DeleteV0CityByCityNameConvoyByIdResponse = DeleteV0CityByCityNameConvoyByIdResponses[keyof DeleteV0CityByCityNameConvoyByIdResponses]; + +export type GetV0CityByCityNameConvoyByIdData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Convoy ID. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/convoy/{id}'; +}; + +export type GetV0CityByCityNameConvoyByIdErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameConvoyByIdError = GetV0CityByCityNameConvoyByIdErrors[keyof GetV0CityByCityNameConvoyByIdErrors]; + +export type GetV0CityByCityNameConvoyByIdResponses = { + /** + * OK + */ + 200: ConvoyGetResponse; +}; + +export type GetV0CityByCityNameConvoyByIdResponse = GetV0CityByCityNameConvoyByIdResponses[keyof GetV0CityByCityNameConvoyByIdResponses]; + +export type PostV0CityByCityNameConvoyByIdAddData = { + body: ConvoyAddInputBody; + path: { + /** + * City name. + */ + cityName: string; + /** + * Convoy ID. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/convoy/{id}/add'; +}; + +export type PostV0CityByCityNameConvoyByIdAddErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameConvoyByIdAddError = PostV0CityByCityNameConvoyByIdAddErrors[keyof PostV0CityByCityNameConvoyByIdAddErrors]; + +export type PostV0CityByCityNameConvoyByIdAddResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PostV0CityByCityNameConvoyByIdAddResponse = PostV0CityByCityNameConvoyByIdAddResponses[keyof PostV0CityByCityNameConvoyByIdAddResponses]; + +export type GetV0CityByCityNameConvoyByIdCheckData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Convoy ID. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/convoy/{id}/check'; +}; + +export type GetV0CityByCityNameConvoyByIdCheckErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameConvoyByIdCheckError = GetV0CityByCityNameConvoyByIdCheckErrors[keyof GetV0CityByCityNameConvoyByIdCheckErrors]; + +export type GetV0CityByCityNameConvoyByIdCheckResponses = { + /** + * OK + */ + 200: ConvoyCheckResponse; +}; + +export type GetV0CityByCityNameConvoyByIdCheckResponse = GetV0CityByCityNameConvoyByIdCheckResponses[keyof GetV0CityByCityNameConvoyByIdCheckResponses]; + +export type PostV0CityByCityNameConvoyByIdCloseData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Convoy ID. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/convoy/{id}/close'; +}; + +export type PostV0CityByCityNameConvoyByIdCloseErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameConvoyByIdCloseError = PostV0CityByCityNameConvoyByIdCloseErrors[keyof PostV0CityByCityNameConvoyByIdCloseErrors]; + +export type PostV0CityByCityNameConvoyByIdCloseResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PostV0CityByCityNameConvoyByIdCloseResponse = PostV0CityByCityNameConvoyByIdCloseResponses[keyof PostV0CityByCityNameConvoyByIdCloseResponses]; + +export type PostV0CityByCityNameConvoyByIdRemoveData = { + body: ConvoyRemoveInputBody; + path: { + /** + * City name. + */ + cityName: string; + /** + * Convoy ID. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/convoy/{id}/remove'; +}; + +export type PostV0CityByCityNameConvoyByIdRemoveErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameConvoyByIdRemoveError = PostV0CityByCityNameConvoyByIdRemoveErrors[keyof PostV0CityByCityNameConvoyByIdRemoveErrors]; + +export type PostV0CityByCityNameConvoyByIdRemoveResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PostV0CityByCityNameConvoyByIdRemoveResponse = PostV0CityByCityNameConvoyByIdRemoveResponses[keyof PostV0CityByCityNameConvoyByIdRemoveResponses]; + +export type GetV0CityByCityNameConvoysData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Event sequence number; when provided, blocks until a newer event arrives. + */ + index?: string; + /** + * How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. + */ + wait?: string; + /** + * Pagination cursor from a previous response's next_cursor field. + */ + cursor?: string; + /** + * Maximum number of results to return. 0 = server default. + */ + limit?: number; + }; + url: '/v0/city/{cityName}/convoys'; +}; + +export type GetV0CityByCityNameConvoysErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameConvoysError = GetV0CityByCityNameConvoysErrors[keyof GetV0CityByCityNameConvoysErrors]; + +export type GetV0CityByCityNameConvoysResponses = { + /** + * OK + */ + 200: ListBodyBead; +}; + +export type GetV0CityByCityNameConvoysResponse = GetV0CityByCityNameConvoysResponses[keyof GetV0CityByCityNameConvoysResponses]; + +export type CreateConvoyData = { + body: ConvoyCreateInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/convoys'; +}; + +export type CreateConvoyErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type CreateConvoyError = CreateConvoyErrors[keyof CreateConvoyErrors]; + +export type CreateConvoyResponses = { + /** + * Created + */ + 201: Bead; +}; + +export type CreateConvoyResponse = CreateConvoyResponses[keyof CreateConvoyResponses]; + +export type GetV0CityByCityNameEventsData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Event sequence number; when provided, blocks until a newer event arrives. + */ + index?: string; + /** + * How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. + */ + wait?: string; + /** + * Pagination cursor from a previous response's next_cursor field. + */ + cursor?: string; + /** + * Maximum number of results to return. 0 = server default. + */ + limit?: number; + /** + * Filter by event type. + */ + type?: string; + /** + * Filter by actor. + */ + actor?: string; + /** + * Filter events since duration ago (Go duration string, e.g. 5m). + */ + since?: string; + }; + url: '/v0/city/{cityName}/events'; +}; + +export type GetV0CityByCityNameEventsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameEventsError = GetV0CityByCityNameEventsErrors[keyof GetV0CityByCityNameEventsErrors]; + +export type GetV0CityByCityNameEventsResponses = { + /** + * OK + */ + 200: ListBodyWireEvent; +}; + +export type GetV0CityByCityNameEventsResponse = GetV0CityByCityNameEventsResponses[keyof GetV0CityByCityNameEventsResponses]; + +export type EmitEventData = { + body: EventEmitRequest; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/events'; +}; + +export type EmitEventErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type EmitEventError = EmitEventErrors[keyof EmitEventErrors]; + +export type EmitEventResponses = { + /** + * Created + */ + 201: EventEmitOutputBody; +}; + +export type EmitEventResponse = EmitEventResponses[keyof EmitEventResponses]; + +export type StreamEventsData = { + body?: never; + headers?: { + /** + * SSE reconnect position from the last received event ID. + */ + 'Last-Event-ID'?: string; + }; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Reconnect position: only deliver events after this sequence number. + */ + after_seq?: string; + }; + url: '/v0/city/{cityName}/events/stream'; +}; + +export type StreamEventsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type StreamEventsError = StreamEventsErrors[keyof StreamEventsErrors]; + +export type StreamEventsResponses = { + /** + * Server Sent Events + * + * Each oneOf object represents one possible SSE message. + */ + 200: Array<{ + data: EventStreamEnvelope; + /** + * The event name. + */ + event: 'event'; + /** + * The event ID. + */ + id?: number; + /** + * The retry time in milliseconds. + */ + retry?: number; + } | { + data: HeartbeatEvent; + /** + * The event name. + */ + event: 'heartbeat'; + /** + * The event ID. + */ + id?: number; + /** + * The retry time in milliseconds. + */ + retry?: number; + }>; +}; + +export type StreamEventsResponse = StreamEventsResponses[keyof StreamEventsResponses]; + +export type DeleteV0CityByCityNameExtmsgAdaptersData = { + body: ExtMsgAdapterUnregisterInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/extmsg/adapters'; +}; + +export type DeleteV0CityByCityNameExtmsgAdaptersErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type DeleteV0CityByCityNameExtmsgAdaptersError = DeleteV0CityByCityNameExtmsgAdaptersErrors[keyof DeleteV0CityByCityNameExtmsgAdaptersErrors]; + +export type DeleteV0CityByCityNameExtmsgAdaptersResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type DeleteV0CityByCityNameExtmsgAdaptersResponse = DeleteV0CityByCityNameExtmsgAdaptersResponses[keyof DeleteV0CityByCityNameExtmsgAdaptersResponses]; + +export type GetV0CityByCityNameExtmsgAdaptersData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/extmsg/adapters'; +}; + +export type GetV0CityByCityNameExtmsgAdaptersErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameExtmsgAdaptersError = GetV0CityByCityNameExtmsgAdaptersErrors[keyof GetV0CityByCityNameExtmsgAdaptersErrors]; + +export type GetV0CityByCityNameExtmsgAdaptersResponses = { + /** + * OK + */ + 200: ListBodyExtmsgAdapterInfo; +}; + +export type GetV0CityByCityNameExtmsgAdaptersResponse = GetV0CityByCityNameExtmsgAdaptersResponses[keyof GetV0CityByCityNameExtmsgAdaptersResponses]; + +export type RegisterExtmsgAdapterData = { + body: ExtMsgAdapterRegisterInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/extmsg/adapters'; +}; + +export type RegisterExtmsgAdapterErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type RegisterExtmsgAdapterError = RegisterExtmsgAdapterErrors[keyof RegisterExtmsgAdapterErrors]; + +export type RegisterExtmsgAdapterResponses = { + /** + * Created + */ + 201: ExtMsgAdapterRegisterOutputBody; +}; + +export type RegisterExtmsgAdapterResponse = RegisterExtmsgAdapterResponses[keyof RegisterExtmsgAdapterResponses]; + +export type PostV0CityByCityNameExtmsgBindData = { + body: ExtMsgBindInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/extmsg/bind'; +}; + +export type PostV0CityByCityNameExtmsgBindErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameExtmsgBindError = PostV0CityByCityNameExtmsgBindErrors[keyof PostV0CityByCityNameExtmsgBindErrors]; + +export type PostV0CityByCityNameExtmsgBindResponses = { + /** + * OK + */ + 200: SessionBindingRecord; +}; + +export type PostV0CityByCityNameExtmsgBindResponse = PostV0CityByCityNameExtmsgBindResponses[keyof PostV0CityByCityNameExtmsgBindResponses]; + +export type GetV0CityByCityNameExtmsgBindingsData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Session ID to list bindings for. + */ + session_id?: string; + }; + url: '/v0/city/{cityName}/extmsg/bindings'; +}; + +export type GetV0CityByCityNameExtmsgBindingsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameExtmsgBindingsError = GetV0CityByCityNameExtmsgBindingsErrors[keyof GetV0CityByCityNameExtmsgBindingsErrors]; + +export type GetV0CityByCityNameExtmsgBindingsResponses = { + /** + * OK + */ + 200: ListBodySessionBindingRecord; +}; + +export type GetV0CityByCityNameExtmsgBindingsResponse = GetV0CityByCityNameExtmsgBindingsResponses[keyof GetV0CityByCityNameExtmsgBindingsResponses]; + +export type GetV0CityByCityNameExtmsgGroupsData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Scope ID. + */ + scope_id?: string; + /** + * Provider name. + */ + provider?: string; + /** + * Account ID. + */ + account_id?: string; + /** + * Conversation ID. + */ + conversation_id?: string; + /** + * Conversation kind. + */ + kind?: string; + }; + url: '/v0/city/{cityName}/extmsg/groups'; +}; + +export type GetV0CityByCityNameExtmsgGroupsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameExtmsgGroupsError = GetV0CityByCityNameExtmsgGroupsErrors[keyof GetV0CityByCityNameExtmsgGroupsErrors]; + +export type GetV0CityByCityNameExtmsgGroupsResponses = { + /** + * OK + */ + 200: ConversationGroupRecord; +}; + +export type GetV0CityByCityNameExtmsgGroupsResponse = GetV0CityByCityNameExtmsgGroupsResponses[keyof GetV0CityByCityNameExtmsgGroupsResponses]; + +export type EnsureExtmsgGroupData = { + body: ExtMsgGroupEnsureInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/extmsg/groups'; +}; + +export type EnsureExtmsgGroupErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type EnsureExtmsgGroupError = EnsureExtmsgGroupErrors[keyof EnsureExtmsgGroupErrors]; + +export type EnsureExtmsgGroupResponses = { + /** + * Created + */ + 201: ConversationGroupRecord; +}; + +export type EnsureExtmsgGroupResponse = EnsureExtmsgGroupResponses[keyof EnsureExtmsgGroupResponses]; + +export type PostV0CityByCityNameExtmsgInboundData = { + body: ExtMsgInboundInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/extmsg/inbound'; +}; + +export type PostV0CityByCityNameExtmsgInboundErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameExtmsgInboundError = PostV0CityByCityNameExtmsgInboundErrors[keyof PostV0CityByCityNameExtmsgInboundErrors]; + +export type PostV0CityByCityNameExtmsgInboundResponses = { + /** + * OK + */ + 200: InboundResult; +}; + +export type PostV0CityByCityNameExtmsgInboundResponse = PostV0CityByCityNameExtmsgInboundResponses[keyof PostV0CityByCityNameExtmsgInboundResponses]; + +export type PostV0CityByCityNameExtmsgOutboundData = { + body: ExtMsgOutboundInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/extmsg/outbound'; +}; + +export type PostV0CityByCityNameExtmsgOutboundErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameExtmsgOutboundError = PostV0CityByCityNameExtmsgOutboundErrors[keyof PostV0CityByCityNameExtmsgOutboundErrors]; + +export type PostV0CityByCityNameExtmsgOutboundResponses = { + /** + * OK + */ + 200: OutboundResult; +}; + +export type PostV0CityByCityNameExtmsgOutboundResponse = PostV0CityByCityNameExtmsgOutboundResponses[keyof PostV0CityByCityNameExtmsgOutboundResponses]; + +export type DeleteV0CityByCityNameExtmsgParticipantsData = { + body: ExtMsgParticipantRemoveInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/extmsg/participants'; +}; + +export type DeleteV0CityByCityNameExtmsgParticipantsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type DeleteV0CityByCityNameExtmsgParticipantsError = DeleteV0CityByCityNameExtmsgParticipantsErrors[keyof DeleteV0CityByCityNameExtmsgParticipantsErrors]; + +export type DeleteV0CityByCityNameExtmsgParticipantsResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type DeleteV0CityByCityNameExtmsgParticipantsResponse = DeleteV0CityByCityNameExtmsgParticipantsResponses[keyof DeleteV0CityByCityNameExtmsgParticipantsResponses]; + +export type PostV0CityByCityNameExtmsgParticipantsData = { + body: ExtMsgParticipantUpsertInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/extmsg/participants'; +}; + +export type PostV0CityByCityNameExtmsgParticipantsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameExtmsgParticipantsError = PostV0CityByCityNameExtmsgParticipantsErrors[keyof PostV0CityByCityNameExtmsgParticipantsErrors]; + +export type PostV0CityByCityNameExtmsgParticipantsResponses = { + /** + * OK + */ + 200: ConversationGroupParticipant; +}; + +export type PostV0CityByCityNameExtmsgParticipantsResponse = PostV0CityByCityNameExtmsgParticipantsResponses[keyof PostV0CityByCityNameExtmsgParticipantsResponses]; + +export type GetV0CityByCityNameExtmsgTranscriptData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Scope ID. + */ + scope_id?: string; + /** + * Provider name. + */ + provider?: string; + /** + * Account ID. + */ + account_id?: string; + /** + * Conversation ID. + */ + conversation_id?: string; + /** + * Parent conversation ID. + */ + parent_conversation_id?: string; + /** + * Conversation kind. + */ + kind?: string; + }; + url: '/v0/city/{cityName}/extmsg/transcript'; +}; + +export type GetV0CityByCityNameExtmsgTranscriptErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameExtmsgTranscriptError = GetV0CityByCityNameExtmsgTranscriptErrors[keyof GetV0CityByCityNameExtmsgTranscriptErrors]; + +export type GetV0CityByCityNameExtmsgTranscriptResponses = { + /** + * OK + */ + 200: ListBodyConversationTranscriptRecord; +}; + +export type GetV0CityByCityNameExtmsgTranscriptResponse = GetV0CityByCityNameExtmsgTranscriptResponses[keyof GetV0CityByCityNameExtmsgTranscriptResponses]; + +export type PostV0CityByCityNameExtmsgTranscriptAckData = { + body: ExtMsgTranscriptAckInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/extmsg/transcript/ack'; +}; + +export type PostV0CityByCityNameExtmsgTranscriptAckErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameExtmsgTranscriptAckError = PostV0CityByCityNameExtmsgTranscriptAckErrors[keyof PostV0CityByCityNameExtmsgTranscriptAckErrors]; + +export type PostV0CityByCityNameExtmsgTranscriptAckResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PostV0CityByCityNameExtmsgTranscriptAckResponse = PostV0CityByCityNameExtmsgTranscriptAckResponses[keyof PostV0CityByCityNameExtmsgTranscriptAckResponses]; + +export type PostV0CityByCityNameExtmsgUnbindData = { + body: ExtMsgUnbindInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/extmsg/unbind'; +}; + +export type PostV0CityByCityNameExtmsgUnbindErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameExtmsgUnbindError = PostV0CityByCityNameExtmsgUnbindErrors[keyof PostV0CityByCityNameExtmsgUnbindErrors]; + +export type PostV0CityByCityNameExtmsgUnbindResponses = { + /** + * OK + */ + 200: ExtMsgUnbindBody; +}; + +export type PostV0CityByCityNameExtmsgUnbindResponse = PostV0CityByCityNameExtmsgUnbindResponses[keyof PostV0CityByCityNameExtmsgUnbindResponses]; + +export type GetV0CityByCityNameFormulaByNameData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Formula name. + */ + name: string; + }; + query: { + /** + * Scope kind (city or rig). + */ + scope_kind?: string; + /** + * Scope reference. + */ + scope_ref?: string; + /** + * Target agent for preview compilation. + */ + target: string; + }; + url: '/v0/city/{cityName}/formula/{name}'; +}; + +export type GetV0CityByCityNameFormulaByNameErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameFormulaByNameError = GetV0CityByCityNameFormulaByNameErrors[keyof GetV0CityByCityNameFormulaByNameErrors]; + +export type GetV0CityByCityNameFormulaByNameResponses = { + /** + * OK + */ + 200: FormulaDetailResponse; +}; + +export type GetV0CityByCityNameFormulaByNameResponse = GetV0CityByCityNameFormulaByNameResponses[keyof GetV0CityByCityNameFormulaByNameResponses]; + +export type GetV0CityByCityNameFormulasData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Scope kind (city or rig). + */ + scope_kind?: string; + /** + * Scope reference. + */ + scope_ref?: string; + }; + url: '/v0/city/{cityName}/formulas'; +}; + +export type GetV0CityByCityNameFormulasErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameFormulasError = GetV0CityByCityNameFormulasErrors[keyof GetV0CityByCityNameFormulasErrors]; + +export type GetV0CityByCityNameFormulasResponses = { + /** + * OK + */ + 200: FormulaListBody; +}; + +export type GetV0CityByCityNameFormulasResponse = GetV0CityByCityNameFormulasResponses[keyof GetV0CityByCityNameFormulasResponses]; + +export type GetV0CityByCityNameFormulasFeedData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Scope kind (city or rig). + */ + scope_kind?: string; + /** + * Scope reference. + */ + scope_ref?: string; + /** + * Maximum number of feed items to return. 0 = default. + */ + limit?: number; + }; + url: '/v0/city/{cityName}/formulas/feed'; +}; + +export type GetV0CityByCityNameFormulasFeedErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameFormulasFeedError = GetV0CityByCityNameFormulasFeedErrors[keyof GetV0CityByCityNameFormulasFeedErrors]; + +export type GetV0CityByCityNameFormulasFeedResponses = { + /** + * OK + */ + 200: FormulaFeedBody; +}; + +export type GetV0CityByCityNameFormulasFeedResponse = GetV0CityByCityNameFormulasFeedResponses[keyof GetV0CityByCityNameFormulasFeedResponses]; + +export type GetV0CityByCityNameFormulasByNameData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Formula name. + */ + name: string; + }; + query: { + /** + * Scope kind (city or rig). + */ + scope_kind?: string; + /** + * Scope reference. + */ + scope_ref?: string; + /** + * Target agent for preview compilation. + */ + target: string; + }; + url: '/v0/city/{cityName}/formulas/{name}'; +}; + +export type GetV0CityByCityNameFormulasByNameErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameFormulasByNameError = GetV0CityByCityNameFormulasByNameErrors[keyof GetV0CityByCityNameFormulasByNameErrors]; + +export type GetV0CityByCityNameFormulasByNameResponses = { + /** + * OK + */ + 200: FormulaDetailResponse; +}; + +export type GetV0CityByCityNameFormulasByNameResponse = GetV0CityByCityNameFormulasByNameResponses[keyof GetV0CityByCityNameFormulasByNameResponses]; + +export type PostV0CityByCityNameFormulasByNamePreviewData = { + body: FormulaPreviewBody; + path: { + /** + * City name. + */ + cityName: string; + /** + * Formula name. + */ + name: string; + }; + query?: never; + url: '/v0/city/{cityName}/formulas/{name}/preview'; +}; + +export type PostV0CityByCityNameFormulasByNamePreviewErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameFormulasByNamePreviewError = PostV0CityByCityNameFormulasByNamePreviewErrors[keyof PostV0CityByCityNameFormulasByNamePreviewErrors]; + +export type PostV0CityByCityNameFormulasByNamePreviewResponses = { + /** + * OK + */ + 200: FormulaDetailResponse; +}; + +export type PostV0CityByCityNameFormulasByNamePreviewResponse = PostV0CityByCityNameFormulasByNamePreviewResponses[keyof PostV0CityByCityNameFormulasByNamePreviewResponses]; + +export type GetV0CityByCityNameFormulasByNameRunsData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Formula name. + */ + name: string; + }; + query?: { + /** + * Scope kind (city or rig). + */ + scope_kind?: string; + /** + * Scope reference. + */ + scope_ref?: string; + /** + * Maximum number of recent runs to return. 0 = default. + */ + limit?: number; + }; + url: '/v0/city/{cityName}/formulas/{name}/runs'; +}; + +export type GetV0CityByCityNameFormulasByNameRunsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameFormulasByNameRunsError = GetV0CityByCityNameFormulasByNameRunsErrors[keyof GetV0CityByCityNameFormulasByNameRunsErrors]; + +export type GetV0CityByCityNameFormulasByNameRunsResponses = { + /** + * OK + */ + 200: FormulaRunsResponse; +}; + +export type GetV0CityByCityNameFormulasByNameRunsResponse = GetV0CityByCityNameFormulasByNameRunsResponses[keyof GetV0CityByCityNameFormulasByNameRunsResponses]; + +export type GetV0CityByCityNameHealthData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/health'; +}; + +export type GetV0CityByCityNameHealthErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameHealthError = GetV0CityByCityNameHealthErrors[keyof GetV0CityByCityNameHealthErrors]; + +export type GetV0CityByCityNameHealthResponses = { + /** + * OK + */ + 200: HealthOutputBody; +}; + +export type GetV0CityByCityNameHealthResponse = GetV0CityByCityNameHealthResponses[keyof GetV0CityByCityNameHealthResponses]; + +export type GetV0CityByCityNameMailData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Event sequence number; when provided, blocks until a newer event arrives. + */ + index?: string; + /** + * How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. + */ + wait?: string; + /** + * Pagination cursor from a previous response's next_cursor field. + */ + cursor?: string; + /** + * Maximum number of results to return. 0 = server default. + */ + limit?: number; + /** + * Filter by agent name. + */ + agent?: string; + /** + * Filter by status (unread, all). + */ + status?: string; + /** + * Filter by rig name. + */ + rig?: string; + }; + url: '/v0/city/{cityName}/mail'; +}; + +export type GetV0CityByCityNameMailErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameMailError = GetV0CityByCityNameMailErrors[keyof GetV0CityByCityNameMailErrors]; + +export type GetV0CityByCityNameMailResponses = { + /** + * OK + */ + 200: MailListBody; +}; + +export type GetV0CityByCityNameMailResponse = GetV0CityByCityNameMailResponses[keyof GetV0CityByCityNameMailResponses]; + +export type SendMailData = { + body: MailSendInputBody; + headers?: { + /** + * Idempotency key for safe retries. + */ + 'Idempotency-Key'?: string; + }; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/mail'; +}; + +export type SendMailErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type SendMailError = SendMailErrors[keyof SendMailErrors]; + +export type SendMailResponses = { + /** + * Created + */ + 201: Message; +}; + +export type SendMailResponse = SendMailResponses[keyof SendMailResponses]; + +export type GetV0CityByCityNameMailCountData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Filter by agent name. + */ + agent?: string; + /** + * Filter by rig name. + */ + rig?: string; + }; + url: '/v0/city/{cityName}/mail/count'; +}; + +export type GetV0CityByCityNameMailCountErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameMailCountError = GetV0CityByCityNameMailCountErrors[keyof GetV0CityByCityNameMailCountErrors]; + +export type GetV0CityByCityNameMailCountResponses = { + /** + * OK + */ + 200: MailCountOutputBody; +}; + +export type GetV0CityByCityNameMailCountResponse = GetV0CityByCityNameMailCountResponses[keyof GetV0CityByCityNameMailCountResponses]; + +export type GetV0CityByCityNameMailThreadByIdData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Thread ID. + */ + id: string; + }; + query?: { + /** + * Filter by rig. + */ + rig?: string; + }; + url: '/v0/city/{cityName}/mail/thread/{id}'; +}; + +export type GetV0CityByCityNameMailThreadByIdErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameMailThreadByIdError = GetV0CityByCityNameMailThreadByIdErrors[keyof GetV0CityByCityNameMailThreadByIdErrors]; + +export type GetV0CityByCityNameMailThreadByIdResponses = { + /** + * OK + */ + 200: MailListBody; +}; + +export type GetV0CityByCityNameMailThreadByIdResponse = GetV0CityByCityNameMailThreadByIdResponses[keyof GetV0CityByCityNameMailThreadByIdResponses]; + +export type DeleteV0CityByCityNameMailByIdData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Message ID. + */ + id: string; + }; + query?: { + /** + * Rig hint. + */ + rig?: string; + }; + url: '/v0/city/{cityName}/mail/{id}'; +}; + +export type DeleteV0CityByCityNameMailByIdErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type DeleteV0CityByCityNameMailByIdError = DeleteV0CityByCityNameMailByIdErrors[keyof DeleteV0CityByCityNameMailByIdErrors]; + +export type DeleteV0CityByCityNameMailByIdResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type DeleteV0CityByCityNameMailByIdResponse = DeleteV0CityByCityNameMailByIdResponses[keyof DeleteV0CityByCityNameMailByIdResponses]; + +export type GetV0CityByCityNameMailByIdData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Message ID. + */ + id: string; + }; + query?: { + /** + * Rig hint for O(1) lookup. + */ + rig?: string; + }; + url: '/v0/city/{cityName}/mail/{id}'; +}; + +export type GetV0CityByCityNameMailByIdErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameMailByIdError = GetV0CityByCityNameMailByIdErrors[keyof GetV0CityByCityNameMailByIdErrors]; + +export type GetV0CityByCityNameMailByIdResponses = { + /** + * OK + */ + 200: Message; +}; + +export type GetV0CityByCityNameMailByIdResponse = GetV0CityByCityNameMailByIdResponses[keyof GetV0CityByCityNameMailByIdResponses]; + +export type PostV0CityByCityNameMailByIdArchiveData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Message ID. + */ + id: string; + }; + query?: { + /** + * Rig hint. + */ + rig?: string; + }; + url: '/v0/city/{cityName}/mail/{id}/archive'; +}; + +export type PostV0CityByCityNameMailByIdArchiveErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameMailByIdArchiveError = PostV0CityByCityNameMailByIdArchiveErrors[keyof PostV0CityByCityNameMailByIdArchiveErrors]; + +export type PostV0CityByCityNameMailByIdArchiveResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PostV0CityByCityNameMailByIdArchiveResponse = PostV0CityByCityNameMailByIdArchiveResponses[keyof PostV0CityByCityNameMailByIdArchiveResponses]; + +export type PostV0CityByCityNameMailByIdMarkUnreadData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Message ID. + */ + id: string; + }; + query?: { + /** + * Rig hint. + */ + rig?: string; + }; + url: '/v0/city/{cityName}/mail/{id}/mark-unread'; +}; + +export type PostV0CityByCityNameMailByIdMarkUnreadErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameMailByIdMarkUnreadError = PostV0CityByCityNameMailByIdMarkUnreadErrors[keyof PostV0CityByCityNameMailByIdMarkUnreadErrors]; + +export type PostV0CityByCityNameMailByIdMarkUnreadResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PostV0CityByCityNameMailByIdMarkUnreadResponse = PostV0CityByCityNameMailByIdMarkUnreadResponses[keyof PostV0CityByCityNameMailByIdMarkUnreadResponses]; + +export type PostV0CityByCityNameMailByIdReadData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Message ID. + */ + id: string; + }; + query?: { + /** + * Rig hint. + */ + rig?: string; + }; + url: '/v0/city/{cityName}/mail/{id}/read'; +}; + +export type PostV0CityByCityNameMailByIdReadErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameMailByIdReadError = PostV0CityByCityNameMailByIdReadErrors[keyof PostV0CityByCityNameMailByIdReadErrors]; + +export type PostV0CityByCityNameMailByIdReadResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PostV0CityByCityNameMailByIdReadResponse = PostV0CityByCityNameMailByIdReadResponses[keyof PostV0CityByCityNameMailByIdReadResponses]; + +export type ReplyMailData = { + body: MailReplyInputBody; + path: { + /** + * City name. + */ + cityName: string; + /** + * Message ID. + */ + id: string; + }; + query?: { + /** + * Rig hint. + */ + rig?: string; + }; + url: '/v0/city/{cityName}/mail/{id}/reply'; +}; + +export type ReplyMailErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type ReplyMailError = ReplyMailErrors[keyof ReplyMailErrors]; + +export type ReplyMailResponses = { + /** + * Created + */ + 201: Message; +}; + +export type ReplyMailResponse = ReplyMailResponses[keyof ReplyMailResponses]; + +export type GetV0CityByCityNameOrderHistoryByBeadIdData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Bead ID for the order run. + */ + bead_id: string; + }; + query?: { + /** + * Store reference for disambiguating store-local bead IDs. + */ + store_ref?: string; + }; + url: '/v0/city/{cityName}/order/history/{bead_id}'; +}; + +export type GetV0CityByCityNameOrderHistoryByBeadIdErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameOrderHistoryByBeadIdError = GetV0CityByCityNameOrderHistoryByBeadIdErrors[keyof GetV0CityByCityNameOrderHistoryByBeadIdErrors]; + +export type GetV0CityByCityNameOrderHistoryByBeadIdResponses = { + /** + * OK + */ + 200: OrderHistoryDetailResponse; +}; + +export type GetV0CityByCityNameOrderHistoryByBeadIdResponse = GetV0CityByCityNameOrderHistoryByBeadIdResponses[keyof GetV0CityByCityNameOrderHistoryByBeadIdResponses]; + +export type GetV0CityByCityNameOrderByNameData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Order name or scoped name. + */ + name: string; + }; + query?: never; + url: '/v0/city/{cityName}/order/{name}'; +}; + +export type GetV0CityByCityNameOrderByNameErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameOrderByNameError = GetV0CityByCityNameOrderByNameErrors[keyof GetV0CityByCityNameOrderByNameErrors]; + +export type GetV0CityByCityNameOrderByNameResponses = { + /** + * OK + */ + 200: OrderResponse; +}; + +export type GetV0CityByCityNameOrderByNameResponse = GetV0CityByCityNameOrderByNameResponses[keyof GetV0CityByCityNameOrderByNameResponses]; + +export type PostV0CityByCityNameOrderByNameDisableData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Order name or scoped name. + */ + name: string; + }; + query?: never; + url: '/v0/city/{cityName}/order/{name}/disable'; +}; + +export type PostV0CityByCityNameOrderByNameDisableErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameOrderByNameDisableError = PostV0CityByCityNameOrderByNameDisableErrors[keyof PostV0CityByCityNameOrderByNameDisableErrors]; + +export type PostV0CityByCityNameOrderByNameDisableResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PostV0CityByCityNameOrderByNameDisableResponse = PostV0CityByCityNameOrderByNameDisableResponses[keyof PostV0CityByCityNameOrderByNameDisableResponses]; + +export type PostV0CityByCityNameOrderByNameEnableData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Order name or scoped name. + */ + name: string; + }; + query?: never; + url: '/v0/city/{cityName}/order/{name}/enable'; +}; + +export type PostV0CityByCityNameOrderByNameEnableErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameOrderByNameEnableError = PostV0CityByCityNameOrderByNameEnableErrors[keyof PostV0CityByCityNameOrderByNameEnableErrors]; + +export type PostV0CityByCityNameOrderByNameEnableResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PostV0CityByCityNameOrderByNameEnableResponse = PostV0CityByCityNameOrderByNameEnableResponses[keyof PostV0CityByCityNameOrderByNameEnableResponses]; + +export type GetV0CityByCityNameOrdersData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/orders'; +}; + +export type GetV0CityByCityNameOrdersErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameOrdersError = GetV0CityByCityNameOrdersErrors[keyof GetV0CityByCityNameOrdersErrors]; + +export type GetV0CityByCityNameOrdersResponses = { + /** + * OK + */ + 200: OrderListBody; +}; + +export type GetV0CityByCityNameOrdersResponse = GetV0CityByCityNameOrdersResponses[keyof GetV0CityByCityNameOrdersResponses]; + +export type GetV0CityByCityNameOrdersCheckData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/orders/check'; +}; + +export type GetV0CityByCityNameOrdersCheckErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameOrdersCheckError = GetV0CityByCityNameOrdersCheckErrors[keyof GetV0CityByCityNameOrdersCheckErrors]; + +export type GetV0CityByCityNameOrdersCheckResponses = { + /** + * OK + */ + 200: OrderCheckListBody; +}; + +export type GetV0CityByCityNameOrdersCheckResponse = GetV0CityByCityNameOrdersCheckResponses[keyof GetV0CityByCityNameOrdersCheckResponses]; + +export type GetV0CityByCityNameOrdersFeedData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Scope kind (city or rig). + */ + scope_kind?: string; + /** + * Scope reference. + */ + scope_ref?: string; + /** + * Maximum number of feed items to return. + */ + limit?: number; + }; + url: '/v0/city/{cityName}/orders/feed'; +}; + +export type GetV0CityByCityNameOrdersFeedErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameOrdersFeedError = GetV0CityByCityNameOrdersFeedErrors[keyof GetV0CityByCityNameOrdersFeedErrors]; + +export type GetV0CityByCityNameOrdersFeedResponses = { + /** + * OK + */ + 200: OrdersFeedBody; +}; + +export type GetV0CityByCityNameOrdersFeedResponse = GetV0CityByCityNameOrdersFeedResponses[keyof GetV0CityByCityNameOrdersFeedResponses]; + +export type GetV0CityByCityNameOrdersHistoryData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query: { + /** + * Scoped order name. + */ + scoped_name: string; + /** + * Maximum number of history entries. 0 = default. + */ + limit?: number; + /** + * Return entries before this RFC3339 timestamp. + */ + before?: string; + }; + url: '/v0/city/{cityName}/orders/history'; +}; + +export type GetV0CityByCityNameOrdersHistoryErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameOrdersHistoryError = GetV0CityByCityNameOrdersHistoryErrors[keyof GetV0CityByCityNameOrdersHistoryErrors]; + +export type GetV0CityByCityNameOrdersHistoryResponses = { + /** + * OK + */ + 200: OrderHistoryListBody; +}; + +export type GetV0CityByCityNameOrdersHistoryResponse = GetV0CityByCityNameOrdersHistoryResponses[keyof GetV0CityByCityNameOrdersHistoryResponses]; + +export type GetV0CityByCityNamePacksData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/packs'; +}; + +export type GetV0CityByCityNamePacksErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNamePacksError = GetV0CityByCityNamePacksErrors[keyof GetV0CityByCityNamePacksErrors]; + +export type GetV0CityByCityNamePacksResponses = { + /** + * OK + */ + 200: PackListBody; +}; + +export type GetV0CityByCityNamePacksResponse = GetV0CityByCityNamePacksResponses[keyof GetV0CityByCityNamePacksResponses]; + +export type DeleteV0CityByCityNamePatchesAgentByBaseData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Agent patch name (unqualified). + */ + base: string; + }; + query?: never; + url: '/v0/city/{cityName}/patches/agent/{base}'; +}; + +export type DeleteV0CityByCityNamePatchesAgentByBaseErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type DeleteV0CityByCityNamePatchesAgentByBaseError = DeleteV0CityByCityNamePatchesAgentByBaseErrors[keyof DeleteV0CityByCityNamePatchesAgentByBaseErrors]; + +export type DeleteV0CityByCityNamePatchesAgentByBaseResponses = { + /** + * OK + */ + 200: PatchDeletedResponseBody; +}; + +export type DeleteV0CityByCityNamePatchesAgentByBaseResponse = DeleteV0CityByCityNamePatchesAgentByBaseResponses[keyof DeleteV0CityByCityNamePatchesAgentByBaseResponses]; + +export type GetV0CityByCityNamePatchesAgentByBaseData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Agent patch name (unqualified). + */ + base: string; + }; + query?: never; + url: '/v0/city/{cityName}/patches/agent/{base}'; +}; + +export type GetV0CityByCityNamePatchesAgentByBaseErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNamePatchesAgentByBaseError = GetV0CityByCityNamePatchesAgentByBaseErrors[keyof GetV0CityByCityNamePatchesAgentByBaseErrors]; + +export type GetV0CityByCityNamePatchesAgentByBaseResponses = { + /** + * OK + */ + 200: AgentPatch; +}; + +export type GetV0CityByCityNamePatchesAgentByBaseResponse = GetV0CityByCityNamePatchesAgentByBaseResponses[keyof GetV0CityByCityNamePatchesAgentByBaseResponses]; + +export type DeleteV0CityByCityNamePatchesAgentByDirByBaseData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Agent directory (rig name). + */ + dir: string; + /** + * Agent base name. + */ + base: string; + }; + query?: never; + url: '/v0/city/{cityName}/patches/agent/{dir}/{base}'; +}; + +export type DeleteV0CityByCityNamePatchesAgentByDirByBaseErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type DeleteV0CityByCityNamePatchesAgentByDirByBaseError = DeleteV0CityByCityNamePatchesAgentByDirByBaseErrors[keyof DeleteV0CityByCityNamePatchesAgentByDirByBaseErrors]; + +export type DeleteV0CityByCityNamePatchesAgentByDirByBaseResponses = { + /** + * OK + */ + 200: PatchDeletedResponseBody; +}; + +export type DeleteV0CityByCityNamePatchesAgentByDirByBaseResponse = DeleteV0CityByCityNamePatchesAgentByDirByBaseResponses[keyof DeleteV0CityByCityNamePatchesAgentByDirByBaseResponses]; + +export type GetV0CityByCityNamePatchesAgentByDirByBaseData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Agent directory (rig name). + */ + dir: string; + /** + * Agent base name. + */ + base: string; + }; + query?: never; + url: '/v0/city/{cityName}/patches/agent/{dir}/{base}'; +}; + +export type GetV0CityByCityNamePatchesAgentByDirByBaseErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNamePatchesAgentByDirByBaseError = GetV0CityByCityNamePatchesAgentByDirByBaseErrors[keyof GetV0CityByCityNamePatchesAgentByDirByBaseErrors]; + +export type GetV0CityByCityNamePatchesAgentByDirByBaseResponses = { + /** + * OK + */ + 200: AgentPatch; +}; + +export type GetV0CityByCityNamePatchesAgentByDirByBaseResponse = GetV0CityByCityNamePatchesAgentByDirByBaseResponses[keyof GetV0CityByCityNamePatchesAgentByDirByBaseResponses]; + +export type GetV0CityByCityNamePatchesAgentsData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/patches/agents'; +}; + +export type GetV0CityByCityNamePatchesAgentsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNamePatchesAgentsError = GetV0CityByCityNamePatchesAgentsErrors[keyof GetV0CityByCityNamePatchesAgentsErrors]; + +export type GetV0CityByCityNamePatchesAgentsResponses = { + /** + * OK + */ + 200: ListBodyAgentPatch; +}; + +export type GetV0CityByCityNamePatchesAgentsResponse = GetV0CityByCityNamePatchesAgentsResponses[keyof GetV0CityByCityNamePatchesAgentsResponses]; + +export type PutV0CityByCityNamePatchesAgentsData = { + body: AgentPatchSetInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/patches/agents'; +}; + +export type PutV0CityByCityNamePatchesAgentsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PutV0CityByCityNamePatchesAgentsError = PutV0CityByCityNamePatchesAgentsErrors[keyof PutV0CityByCityNamePatchesAgentsErrors]; + +export type PutV0CityByCityNamePatchesAgentsResponses = { + /** + * OK + */ + 200: PatchOkResponseBody; +}; + +export type PutV0CityByCityNamePatchesAgentsResponse = PutV0CityByCityNamePatchesAgentsResponses[keyof PutV0CityByCityNamePatchesAgentsResponses]; + +export type DeleteV0CityByCityNamePatchesProviderByNameData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Provider patch name. + */ + name: string; + }; + query?: never; + url: '/v0/city/{cityName}/patches/provider/{name}'; +}; + +export type DeleteV0CityByCityNamePatchesProviderByNameErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type DeleteV0CityByCityNamePatchesProviderByNameError = DeleteV0CityByCityNamePatchesProviderByNameErrors[keyof DeleteV0CityByCityNamePatchesProviderByNameErrors]; + +export type DeleteV0CityByCityNamePatchesProviderByNameResponses = { + /** + * OK + */ + 200: PatchDeletedResponseBody; +}; + +export type DeleteV0CityByCityNamePatchesProviderByNameResponse = DeleteV0CityByCityNamePatchesProviderByNameResponses[keyof DeleteV0CityByCityNamePatchesProviderByNameResponses]; + +export type GetV0CityByCityNamePatchesProviderByNameData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Provider patch name. + */ + name: string; + }; + query?: never; + url: '/v0/city/{cityName}/patches/provider/{name}'; +}; + +export type GetV0CityByCityNamePatchesProviderByNameErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNamePatchesProviderByNameError = GetV0CityByCityNamePatchesProviderByNameErrors[keyof GetV0CityByCityNamePatchesProviderByNameErrors]; + +export type GetV0CityByCityNamePatchesProviderByNameResponses = { + /** + * OK + */ + 200: ProviderPatch; +}; + +export type GetV0CityByCityNamePatchesProviderByNameResponse = GetV0CityByCityNamePatchesProviderByNameResponses[keyof GetV0CityByCityNamePatchesProviderByNameResponses]; + +export type GetV0CityByCityNamePatchesProvidersData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/patches/providers'; +}; + +export type GetV0CityByCityNamePatchesProvidersErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNamePatchesProvidersError = GetV0CityByCityNamePatchesProvidersErrors[keyof GetV0CityByCityNamePatchesProvidersErrors]; + +export type GetV0CityByCityNamePatchesProvidersResponses = { + /** + * OK + */ + 200: ListBodyProviderPatch; +}; + +export type GetV0CityByCityNamePatchesProvidersResponse = GetV0CityByCityNamePatchesProvidersResponses[keyof GetV0CityByCityNamePatchesProvidersResponses]; + +export type PutV0CityByCityNamePatchesProvidersData = { + body: ProviderPatchSetInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/patches/providers'; +}; + +export type PutV0CityByCityNamePatchesProvidersErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PutV0CityByCityNamePatchesProvidersError = PutV0CityByCityNamePatchesProvidersErrors[keyof PutV0CityByCityNamePatchesProvidersErrors]; + +export type PutV0CityByCityNamePatchesProvidersResponses = { + /** + * OK + */ + 200: PatchOkResponseBody; +}; + +export type PutV0CityByCityNamePatchesProvidersResponse = PutV0CityByCityNamePatchesProvidersResponses[keyof PutV0CityByCityNamePatchesProvidersResponses]; + +export type DeleteV0CityByCityNamePatchesRigByNameData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Rig patch name. + */ + name: string; + }; + query?: never; + url: '/v0/city/{cityName}/patches/rig/{name}'; +}; + +export type DeleteV0CityByCityNamePatchesRigByNameErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type DeleteV0CityByCityNamePatchesRigByNameError = DeleteV0CityByCityNamePatchesRigByNameErrors[keyof DeleteV0CityByCityNamePatchesRigByNameErrors]; + +export type DeleteV0CityByCityNamePatchesRigByNameResponses = { + /** + * OK + */ + 200: PatchDeletedResponseBody; +}; + +export type DeleteV0CityByCityNamePatchesRigByNameResponse = DeleteV0CityByCityNamePatchesRigByNameResponses[keyof DeleteV0CityByCityNamePatchesRigByNameResponses]; + +export type GetV0CityByCityNamePatchesRigByNameData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Rig patch name. + */ + name: string; + }; + query?: never; + url: '/v0/city/{cityName}/patches/rig/{name}'; +}; + +export type GetV0CityByCityNamePatchesRigByNameErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNamePatchesRigByNameError = GetV0CityByCityNamePatchesRigByNameErrors[keyof GetV0CityByCityNamePatchesRigByNameErrors]; + +export type GetV0CityByCityNamePatchesRigByNameResponses = { + /** + * OK + */ + 200: RigPatch; +}; + +export type GetV0CityByCityNamePatchesRigByNameResponse = GetV0CityByCityNamePatchesRigByNameResponses[keyof GetV0CityByCityNamePatchesRigByNameResponses]; + +export type GetV0CityByCityNamePatchesRigsData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/patches/rigs'; +}; + +export type GetV0CityByCityNamePatchesRigsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNamePatchesRigsError = GetV0CityByCityNamePatchesRigsErrors[keyof GetV0CityByCityNamePatchesRigsErrors]; + +export type GetV0CityByCityNamePatchesRigsResponses = { + /** + * OK + */ + 200: ListBodyRigPatch; +}; + +export type GetV0CityByCityNamePatchesRigsResponse = GetV0CityByCityNamePatchesRigsResponses[keyof GetV0CityByCityNamePatchesRigsResponses]; + +export type PutV0CityByCityNamePatchesRigsData = { + body: RigPatchSetInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/patches/rigs'; +}; + +export type PutV0CityByCityNamePatchesRigsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PutV0CityByCityNamePatchesRigsError = PutV0CityByCityNamePatchesRigsErrors[keyof PutV0CityByCityNamePatchesRigsErrors]; + +export type PutV0CityByCityNamePatchesRigsResponses = { + /** + * OK + */ + 200: PatchOkResponseBody; +}; + +export type PutV0CityByCityNamePatchesRigsResponse = PutV0CityByCityNamePatchesRigsResponses[keyof PutV0CityByCityNamePatchesRigsResponses]; + +export type GetV0CityByCityNameProviderReadinessData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Comma-separated provider names to check (default: claude,codex,gemini). + */ + providers?: string; + /** + * Force fresh probe, bypassing cache. + */ + fresh?: boolean; + }; + url: '/v0/city/{cityName}/provider-readiness'; +}; + +export type GetV0CityByCityNameProviderReadinessErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameProviderReadinessError = GetV0CityByCityNameProviderReadinessErrors[keyof GetV0CityByCityNameProviderReadinessErrors]; + +export type GetV0CityByCityNameProviderReadinessResponses = { + /** + * OK + */ + 200: ProviderReadinessResponse; +}; + +export type GetV0CityByCityNameProviderReadinessResponse = GetV0CityByCityNameProviderReadinessResponses[keyof GetV0CityByCityNameProviderReadinessResponses]; + +export type DeleteV0CityByCityNameProviderByNameData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Provider name. + */ + name: string; + }; + query?: never; + url: '/v0/city/{cityName}/provider/{name}'; +}; + +export type DeleteV0CityByCityNameProviderByNameErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type DeleteV0CityByCityNameProviderByNameError = DeleteV0CityByCityNameProviderByNameErrors[keyof DeleteV0CityByCityNameProviderByNameErrors]; + +export type DeleteV0CityByCityNameProviderByNameResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type DeleteV0CityByCityNameProviderByNameResponse = DeleteV0CityByCityNameProviderByNameResponses[keyof DeleteV0CityByCityNameProviderByNameResponses]; + +export type GetV0CityByCityNameProviderByNameData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Provider name. + */ + name: string; + }; + query?: never; + url: '/v0/city/{cityName}/provider/{name}'; +}; + +export type GetV0CityByCityNameProviderByNameErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameProviderByNameError = GetV0CityByCityNameProviderByNameErrors[keyof GetV0CityByCityNameProviderByNameErrors]; + +export type GetV0CityByCityNameProviderByNameResponses = { + /** + * OK + */ + 200: ProviderResponse; +}; + +export type GetV0CityByCityNameProviderByNameResponse = GetV0CityByCityNameProviderByNameResponses[keyof GetV0CityByCityNameProviderByNameResponses]; + +export type PatchV0CityByCityNameProviderByNameData = { + body: ProviderUpdateInputBody; + path: { + /** + * City name. + */ + cityName: string; + /** + * Provider name. + */ + name: string; + }; + query?: never; + url: '/v0/city/{cityName}/provider/{name}'; +}; + +export type PatchV0CityByCityNameProviderByNameErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PatchV0CityByCityNameProviderByNameError = PatchV0CityByCityNameProviderByNameErrors[keyof PatchV0CityByCityNameProviderByNameErrors]; + +export type PatchV0CityByCityNameProviderByNameResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PatchV0CityByCityNameProviderByNameResponse = PatchV0CityByCityNameProviderByNameResponses[keyof PatchV0CityByCityNameProviderByNameResponses]; + +export type GetV0CityByCityNameProvidersData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/providers'; +}; + +export type GetV0CityByCityNameProvidersErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameProvidersError = GetV0CityByCityNameProvidersErrors[keyof GetV0CityByCityNameProvidersErrors]; + +export type GetV0CityByCityNameProvidersResponses = { + /** + * OK + */ + 200: ListBodyProviderResponse; +}; + +export type GetV0CityByCityNameProvidersResponse = GetV0CityByCityNameProvidersResponses[keyof GetV0CityByCityNameProvidersResponses]; + +export type CreateProviderData = { + body: ProviderCreateInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/providers'; +}; + +export type CreateProviderErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type CreateProviderError = CreateProviderErrors[keyof CreateProviderErrors]; + +export type CreateProviderResponses = { + /** + * Created + */ + 201: ProviderCreatedOutputBody; +}; + +export type CreateProviderResponse = CreateProviderResponses[keyof CreateProviderResponses]; + +export type GetV0CityByCityNameProvidersPublicData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/providers/public'; +}; + +export type GetV0CityByCityNameProvidersPublicErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameProvidersPublicError = GetV0CityByCityNameProvidersPublicErrors[keyof GetV0CityByCityNameProvidersPublicErrors]; + +export type GetV0CityByCityNameProvidersPublicResponses = { + /** + * OK + */ + 200: ProviderPublicListBody; +}; + +export type GetV0CityByCityNameProvidersPublicResponse = GetV0CityByCityNameProvidersPublicResponses[keyof GetV0CityByCityNameProvidersPublicResponses]; + +export type GetV0CityByCityNameReadinessData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Comma-separated readiness items to check (default: claude,codex,gemini,github_cli). + */ + items?: string; + /** + * Force fresh probe, bypassing cache. + */ + fresh?: boolean; + }; + url: '/v0/city/{cityName}/readiness'; +}; + +export type GetV0CityByCityNameReadinessErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameReadinessError = GetV0CityByCityNameReadinessErrors[keyof GetV0CityByCityNameReadinessErrors]; + +export type GetV0CityByCityNameReadinessResponses = { + /** + * OK + */ + 200: ReadinessResponse; +}; + +export type GetV0CityByCityNameReadinessResponse = GetV0CityByCityNameReadinessResponses[keyof GetV0CityByCityNameReadinessResponses]; + +export type DeleteV0CityByCityNameRigByNameData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Rig name. + */ + name: string; + }; + query?: never; + url: '/v0/city/{cityName}/rig/{name}'; +}; + +export type DeleteV0CityByCityNameRigByNameErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type DeleteV0CityByCityNameRigByNameError = DeleteV0CityByCityNameRigByNameErrors[keyof DeleteV0CityByCityNameRigByNameErrors]; + +export type DeleteV0CityByCityNameRigByNameResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type DeleteV0CityByCityNameRigByNameResponse = DeleteV0CityByCityNameRigByNameResponses[keyof DeleteV0CityByCityNameRigByNameResponses]; + +export type GetV0CityByCityNameRigByNameData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Rig name. + */ + name: string; + }; + query?: { + /** + * Include git status. + */ + git?: boolean; + }; + url: '/v0/city/{cityName}/rig/{name}'; +}; + +export type GetV0CityByCityNameRigByNameErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameRigByNameError = GetV0CityByCityNameRigByNameErrors[keyof GetV0CityByCityNameRigByNameErrors]; + +export type GetV0CityByCityNameRigByNameResponses = { + /** + * OK + */ + 200: RigResponse; +}; + +export type GetV0CityByCityNameRigByNameResponse = GetV0CityByCityNameRigByNameResponses[keyof GetV0CityByCityNameRigByNameResponses]; + +export type PatchV0CityByCityNameRigByNameData = { + body: RigUpdateInputBody; + path: { + /** + * City name. + */ + cityName: string; + /** + * Rig name. + */ + name: string; + }; + query?: never; + url: '/v0/city/{cityName}/rig/{name}'; +}; + +export type PatchV0CityByCityNameRigByNameErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PatchV0CityByCityNameRigByNameError = PatchV0CityByCityNameRigByNameErrors[keyof PatchV0CityByCityNameRigByNameErrors]; + +export type PatchV0CityByCityNameRigByNameResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PatchV0CityByCityNameRigByNameResponse = PatchV0CityByCityNameRigByNameResponses[keyof PatchV0CityByCityNameRigByNameResponses]; + +export type PostV0CityByCityNameRigByNameByActionData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Rig name. + */ + name: string; + /** + * Action to perform (suspend, resume, restart). + */ + action: string; + }; + query?: never; + url: '/v0/city/{cityName}/rig/{name}/{action}'; +}; + +export type PostV0CityByCityNameRigByNameByActionErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameRigByNameByActionError = PostV0CityByCityNameRigByNameByActionErrors[keyof PostV0CityByCityNameRigByNameByActionErrors]; + +export type PostV0CityByCityNameRigByNameByActionResponses = { + /** + * OK + */ + 200: RigActionBody; +}; + +export type PostV0CityByCityNameRigByNameByActionResponse = PostV0CityByCityNameRigByNameByActionResponses[keyof PostV0CityByCityNameRigByNameByActionResponses]; + +export type GetV0CityByCityNameRigsData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Event sequence number; when provided, blocks until a newer event arrives. + */ + index?: string; + /** + * How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. + */ + wait?: string; + /** + * Include git status. + */ + git?: boolean; + }; + url: '/v0/city/{cityName}/rigs'; +}; + +export type GetV0CityByCityNameRigsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameRigsError = GetV0CityByCityNameRigsErrors[keyof GetV0CityByCityNameRigsErrors]; + +export type GetV0CityByCityNameRigsResponses = { + /** + * OK + */ + 200: ListBodyRigResponse; +}; + +export type GetV0CityByCityNameRigsResponse = GetV0CityByCityNameRigsResponses[keyof GetV0CityByCityNameRigsResponses]; + +export type CreateRigData = { + body: RigCreateInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/rigs'; +}; + +export type CreateRigErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type CreateRigError = CreateRigErrors[keyof CreateRigErrors]; + +export type CreateRigResponses = { + /** + * Created + */ + 201: RigCreatedOutputBody; +}; + +export type CreateRigResponse = CreateRigResponses[keyof CreateRigResponses]; + +export type GetV0CityByCityNameServiceByNameData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Service name. + */ + name: string; + }; + query?: never; + url: '/v0/city/{cityName}/service/{name}'; +}; + +export type GetV0CityByCityNameServiceByNameErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameServiceByNameError = GetV0CityByCityNameServiceByNameErrors[keyof GetV0CityByCityNameServiceByNameErrors]; + +export type GetV0CityByCityNameServiceByNameResponses = { + /** + * OK + */ + 200: Status; +}; + +export type GetV0CityByCityNameServiceByNameResponse = GetV0CityByCityNameServiceByNameResponses[keyof GetV0CityByCityNameServiceByNameResponses]; + +export type PostV0CityByCityNameServiceByNameRestartData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Service name. + */ + name: string; + }; + query?: never; + url: '/v0/city/{cityName}/service/{name}/restart'; +}; + +export type PostV0CityByCityNameServiceByNameRestartErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameServiceByNameRestartError = PostV0CityByCityNameServiceByNameRestartErrors[keyof PostV0CityByCityNameServiceByNameRestartErrors]; + +export type PostV0CityByCityNameServiceByNameRestartResponses = { + /** + * OK + */ + 200: ServiceRestartOutputBody; +}; + +export type PostV0CityByCityNameServiceByNameRestartResponse = PostV0CityByCityNameServiceByNameRestartResponses[keyof PostV0CityByCityNameServiceByNameRestartResponses]; + +export type GetV0CityByCityNameServicesData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/services'; +}; + +export type GetV0CityByCityNameServicesErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameServicesError = GetV0CityByCityNameServicesErrors[keyof GetV0CityByCityNameServicesErrors]; + +export type GetV0CityByCityNameServicesResponses = { + /** + * OK + */ + 200: ListBodyStatus; +}; + +export type GetV0CityByCityNameServicesResponse = GetV0CityByCityNameServicesResponses[keyof GetV0CityByCityNameServicesResponses]; + +export type GetV0CityByCityNameSessionByIdData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Session ID, alias, or runtime session_name. + */ + id: string; + }; + query?: { + /** + * Include last output preview. + */ + peek?: boolean; + }; + url: '/v0/city/{cityName}/session/{id}'; +}; + +export type GetV0CityByCityNameSessionByIdErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameSessionByIdError = GetV0CityByCityNameSessionByIdErrors[keyof GetV0CityByCityNameSessionByIdErrors]; + +export type GetV0CityByCityNameSessionByIdResponses = { + /** + * OK + */ + 200: SessionResponse; +}; + +export type GetV0CityByCityNameSessionByIdResponse = GetV0CityByCityNameSessionByIdResponses[keyof GetV0CityByCityNameSessionByIdResponses]; + +export type PatchV0CityByCityNameSessionByIdData = { + body: SessionPatchBody; + path: { + /** + * City name. + */ + cityName: string; + /** + * Session ID, alias, or runtime session_name. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/session/{id}'; +}; + +export type PatchV0CityByCityNameSessionByIdErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PatchV0CityByCityNameSessionByIdError = PatchV0CityByCityNameSessionByIdErrors[keyof PatchV0CityByCityNameSessionByIdErrors]; + +export type PatchV0CityByCityNameSessionByIdResponses = { + /** + * OK + */ + 200: SessionResponse; +}; + +export type PatchV0CityByCityNameSessionByIdResponse = PatchV0CityByCityNameSessionByIdResponses[keyof PatchV0CityByCityNameSessionByIdResponses]; + +export type GetV0CityByCityNameSessionByIdAgentsData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Session ID, alias, or runtime session_name. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/session/{id}/agents'; +}; + +export type GetV0CityByCityNameSessionByIdAgentsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameSessionByIdAgentsError = GetV0CityByCityNameSessionByIdAgentsErrors[keyof GetV0CityByCityNameSessionByIdAgentsErrors]; + +export type GetV0CityByCityNameSessionByIdAgentsResponses = { + /** + * OK + */ + 200: SessionAgentListResponse; +}; + +export type GetV0CityByCityNameSessionByIdAgentsResponse = GetV0CityByCityNameSessionByIdAgentsResponses[keyof GetV0CityByCityNameSessionByIdAgentsResponses]; + +export type GetV0CityByCityNameSessionByIdAgentsByAgentIdData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Session ID, alias, or runtime session_name. + */ + id: string; + /** + * Subagent ID within the session. + */ + agentId: string; + }; + query?: never; + url: '/v0/city/{cityName}/session/{id}/agents/{agentId}'; +}; + +export type GetV0CityByCityNameSessionByIdAgentsByAgentIdErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameSessionByIdAgentsByAgentIdError = GetV0CityByCityNameSessionByIdAgentsByAgentIdErrors[keyof GetV0CityByCityNameSessionByIdAgentsByAgentIdErrors]; + +export type GetV0CityByCityNameSessionByIdAgentsByAgentIdResponses = { + /** + * OK + */ + 200: SessionAgentGetResponse; +}; + +export type GetV0CityByCityNameSessionByIdAgentsByAgentIdResponse = GetV0CityByCityNameSessionByIdAgentsByAgentIdResponses[keyof GetV0CityByCityNameSessionByIdAgentsByAgentIdResponses]; + +export type PostV0CityByCityNameSessionByIdCloseData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Session ID, alias, or runtime session_name. + */ + id: string; + }; + query?: { + /** + * Permanently delete bead after closing. + */ + delete?: boolean; + }; + url: '/v0/city/{cityName}/session/{id}/close'; +}; + +export type PostV0CityByCityNameSessionByIdCloseErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameSessionByIdCloseError = PostV0CityByCityNameSessionByIdCloseErrors[keyof PostV0CityByCityNameSessionByIdCloseErrors]; + +export type PostV0CityByCityNameSessionByIdCloseResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PostV0CityByCityNameSessionByIdCloseResponse = PostV0CityByCityNameSessionByIdCloseResponses[keyof PostV0CityByCityNameSessionByIdCloseResponses]; + +export type PostV0CityByCityNameSessionByIdKillData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Session ID, alias, or runtime session_name. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/session/{id}/kill'; +}; + +export type PostV0CityByCityNameSessionByIdKillErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameSessionByIdKillError = PostV0CityByCityNameSessionByIdKillErrors[keyof PostV0CityByCityNameSessionByIdKillErrors]; + +export type PostV0CityByCityNameSessionByIdKillResponses = { + /** + * OK + */ + 200: OkWithIdResponseBody; +}; + +export type PostV0CityByCityNameSessionByIdKillResponse = PostV0CityByCityNameSessionByIdKillResponses[keyof PostV0CityByCityNameSessionByIdKillResponses]; + +export type SendSessionMessageData = { + body: SessionMessageInputBody; + path: { + /** + * City name. + */ + cityName: string; + /** + * Session ID, alias, or runtime session_name. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/session/{id}/messages'; +}; + +export type SendSessionMessageErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type SendSessionMessageError = SendSessionMessageErrors[keyof SendSessionMessageErrors]; + +export type SendSessionMessageResponses = { + /** + * Accepted + */ + 202: SessionMessageOutputBody; +}; + +export type SendSessionMessageResponse = SendSessionMessageResponses[keyof SendSessionMessageResponses]; + +export type GetV0CityByCityNameSessionByIdPendingData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Session ID, alias, or runtime session_name. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/session/{id}/pending'; +}; + +export type GetV0CityByCityNameSessionByIdPendingErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameSessionByIdPendingError = GetV0CityByCityNameSessionByIdPendingErrors[keyof GetV0CityByCityNameSessionByIdPendingErrors]; + +export type GetV0CityByCityNameSessionByIdPendingResponses = { + /** + * OK + */ + 200: SessionPendingResponse; +}; + +export type GetV0CityByCityNameSessionByIdPendingResponse = GetV0CityByCityNameSessionByIdPendingResponses[keyof GetV0CityByCityNameSessionByIdPendingResponses]; + +export type PostV0CityByCityNameSessionByIdRenameData = { + body: SessionRenameInputBody; + path: { + /** + * City name. + */ + cityName: string; + /** + * Session ID, alias, or runtime session_name. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/session/{id}/rename'; +}; + +export type PostV0CityByCityNameSessionByIdRenameErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameSessionByIdRenameError = PostV0CityByCityNameSessionByIdRenameErrors[keyof PostV0CityByCityNameSessionByIdRenameErrors]; + +export type PostV0CityByCityNameSessionByIdRenameResponses = { + /** + * OK + */ + 200: SessionResponse; +}; + +export type PostV0CityByCityNameSessionByIdRenameResponse = PostV0CityByCityNameSessionByIdRenameResponses[keyof PostV0CityByCityNameSessionByIdRenameResponses]; + +export type RespondSessionData = { + body: SessionRespondInputBody; + path: { + /** + * City name. + */ + cityName: string; + /** + * Session ID, alias, or runtime session_name. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/session/{id}/respond'; +}; + +export type RespondSessionErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type RespondSessionError = RespondSessionErrors[keyof RespondSessionErrors]; + +export type RespondSessionResponses = { + /** + * Accepted + */ + 202: SessionRespondOutputBody; +}; + +export type RespondSessionResponse = RespondSessionResponses[keyof RespondSessionResponses]; + +export type PostV0CityByCityNameSessionByIdStopData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Session ID, alias, or runtime session_name. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/session/{id}/stop'; +}; + +export type PostV0CityByCityNameSessionByIdStopErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameSessionByIdStopError = PostV0CityByCityNameSessionByIdStopErrors[keyof PostV0CityByCityNameSessionByIdStopErrors]; + +export type PostV0CityByCityNameSessionByIdStopResponses = { + /** + * OK + */ + 200: OkWithIdResponseBody; +}; + +export type PostV0CityByCityNameSessionByIdStopResponse = PostV0CityByCityNameSessionByIdStopResponses[keyof PostV0CityByCityNameSessionByIdStopResponses]; + +export type StreamSessionData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Session ID, alias, or runtime session_name. + */ + id: string; + }; + query?: { + /** + * Transcript format: conversation (default) or raw. + */ + format?: string; + }; + url: '/v0/city/{cityName}/session/{id}/stream'; +}; + +export type StreamSessionErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type StreamSessionError = StreamSessionErrors[keyof StreamSessionErrors]; + +export type StreamSessionResponses = { + /** + * Server Sent Events + * + * Each oneOf object represents one possible SSE message. + */ + 200: Array<{ + data: SessionActivityEvent; + /** + * The event name. + */ + event: 'activity'; + /** + * The event ID. + */ + id?: number; + /** + * The retry time in milliseconds. + */ + retry?: number; + } | { + data: HeartbeatEvent; + /** + * The event name. + */ + event: 'heartbeat'; + /** + * The event ID. + */ + id?: number; + /** + * The retry time in milliseconds. + */ + retry?: number; + } | { + data: SessionStreamRawMessageEvent; + /** + * The event name. + */ + event?: 'message'; + /** + * The event ID. + */ + id?: number; + /** + * The retry time in milliseconds. + */ + retry?: number; + } | { + data: PendingInteraction; + /** + * The event name. + */ + event: 'pending'; + /** + * The event ID. + */ + id?: number; + /** + * The retry time in milliseconds. + */ + retry?: number; + } | { + data: SessionStreamMessageEvent; + /** + * The event name. + */ + event: 'turn'; + /** + * The event ID. + */ + id?: number; + /** + * The retry time in milliseconds. + */ + retry?: number; + }>; +}; + +export type StreamSessionResponse = StreamSessionResponses[keyof StreamSessionResponses]; + +export type SubmitSessionData = { + body: SessionSubmitInputBody; + path: { + /** + * City name. + */ + cityName: string; + /** + * Session ID, alias, or runtime session_name. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/session/{id}/submit'; +}; + +export type SubmitSessionErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type SubmitSessionError = SubmitSessionErrors[keyof SubmitSessionErrors]; + +export type SubmitSessionResponses = { + /** + * Accepted + */ + 202: SessionSubmitOutputBody; +}; + +export type SubmitSessionResponse = SubmitSessionResponses[keyof SubmitSessionResponses]; + +export type PostV0CityByCityNameSessionByIdSuspendData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Session ID, alias, or runtime session_name. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/session/{id}/suspend'; +}; + +export type PostV0CityByCityNameSessionByIdSuspendErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameSessionByIdSuspendError = PostV0CityByCityNameSessionByIdSuspendErrors[keyof PostV0CityByCityNameSessionByIdSuspendErrors]; + +export type PostV0CityByCityNameSessionByIdSuspendResponses = { + /** + * OK + */ + 200: OkResponseBody; +}; + +export type PostV0CityByCityNameSessionByIdSuspendResponse = PostV0CityByCityNameSessionByIdSuspendResponses[keyof PostV0CityByCityNameSessionByIdSuspendResponses]; + +export type GetV0CityByCityNameSessionByIdTranscriptData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Session ID, alias, or runtime session_name. + */ + id: string; + }; + query?: { + /** + * Number of recent compaction segments to return. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. + */ + tail?: string; + /** + * Transcript format: conversation (default) or raw. + */ + format?: string; + /** + * Pagination cursor: return entries before this UUID. + */ + before?: string; + }; + url: '/v0/city/{cityName}/session/{id}/transcript'; +}; + +export type GetV0CityByCityNameSessionByIdTranscriptErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameSessionByIdTranscriptError = GetV0CityByCityNameSessionByIdTranscriptErrors[keyof GetV0CityByCityNameSessionByIdTranscriptErrors]; + +export type GetV0CityByCityNameSessionByIdTranscriptResponses = { + /** + * OK + */ + 200: SessionTranscriptGetResponse; +}; + +export type GetV0CityByCityNameSessionByIdTranscriptResponse = GetV0CityByCityNameSessionByIdTranscriptResponses[keyof GetV0CityByCityNameSessionByIdTranscriptResponses]; + +export type PostV0CityByCityNameSessionByIdWakeData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Session ID, alias, or runtime session_name. + */ + id: string; + }; + query?: never; + url: '/v0/city/{cityName}/session/{id}/wake'; +}; + +export type PostV0CityByCityNameSessionByIdWakeErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameSessionByIdWakeError = PostV0CityByCityNameSessionByIdWakeErrors[keyof PostV0CityByCityNameSessionByIdWakeErrors]; + +export type PostV0CityByCityNameSessionByIdWakeResponses = { + /** + * OK + */ + 200: OkWithIdResponseBody; +}; + +export type PostV0CityByCityNameSessionByIdWakeResponse = PostV0CityByCityNameSessionByIdWakeResponses[keyof PostV0CityByCityNameSessionByIdWakeResponses]; + +export type GetV0CityByCityNameSessionsData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Pagination cursor from a previous response's next_cursor field. + */ + cursor?: string; + /** + * Maximum number of results to return. 0 = server default. + */ + limit?: number; + /** + * Filter by session state (e.g. active, closed). + */ + state?: string; + /** + * Filter by session template (agent qualified name). + */ + template?: string; + /** + * Include last output preview. + */ + peek?: boolean; + }; + url: '/v0/city/{cityName}/sessions'; +}; + +export type GetV0CityByCityNameSessionsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameSessionsError = GetV0CityByCityNameSessionsErrors[keyof GetV0CityByCityNameSessionsErrors]; + +export type GetV0CityByCityNameSessionsResponses = { + /** + * OK + */ + 200: ListBodySessionResponse; +}; + +export type GetV0CityByCityNameSessionsResponse = GetV0CityByCityNameSessionsResponses[keyof GetV0CityByCityNameSessionsResponses]; + +export type CreateSessionData = { + body: SessionCreateBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/sessions'; +}; + +export type CreateSessionErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type CreateSessionError = CreateSessionErrors[keyof CreateSessionErrors]; + +export type CreateSessionResponses = { + /** + * Accepted + */ + 202: SessionResponse; +}; + +export type CreateSessionResponse = CreateSessionResponses[keyof CreateSessionResponses]; + +export type PostV0CityByCityNameSlingData = { + body: SlingInputBody; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/sling'; +}; + +export type PostV0CityByCityNameSlingErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameSlingError = PostV0CityByCityNameSlingErrors[keyof PostV0CityByCityNameSlingErrors]; + +export type PostV0CityByCityNameSlingResponses = { + /** + * OK + */ + 200: SlingResponse; +}; + +export type PostV0CityByCityNameSlingResponse = PostV0CityByCityNameSlingResponses[keyof PostV0CityByCityNameSlingResponses]; + +export type GetV0CityByCityNameStatusData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + }; + query?: { + /** + * Event sequence number; when provided, blocks until a newer event arrives. + */ + index?: string; + /** + * How long to block waiting for changes (Go duration string, e.g. 30s). Default 30s, max 2m. + */ + wait?: string; + }; + url: '/v0/city/{cityName}/status'; +}; + +export type GetV0CityByCityNameStatusErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameStatusError = GetV0CityByCityNameStatusErrors[keyof GetV0CityByCityNameStatusErrors]; + +export type GetV0CityByCityNameStatusResponses = { + /** + * OK + */ + 200: StatusBody; +}; + +export type GetV0CityByCityNameStatusResponse = GetV0CityByCityNameStatusResponses[keyof GetV0CityByCityNameStatusResponses]; + +export type DeleteV0CityByCityNameWorkflowByWorkflowIdData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Workflow (convoy) ID. + */ + workflow_id: string; + }; + query?: { + /** + * Scope kind (city or rig). + */ + scope_kind?: string; + /** + * Scope reference. + */ + scope_ref?: string; + /** + * Permanently delete beads from store. + */ + delete?: boolean; + }; + url: '/v0/city/{cityName}/workflow/{workflow_id}'; +}; + +export type DeleteV0CityByCityNameWorkflowByWorkflowIdErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type DeleteV0CityByCityNameWorkflowByWorkflowIdError = DeleteV0CityByCityNameWorkflowByWorkflowIdErrors[keyof DeleteV0CityByCityNameWorkflowByWorkflowIdErrors]; + +export type DeleteV0CityByCityNameWorkflowByWorkflowIdResponses = { + /** + * OK + */ + 200: WorkflowDeleteResponse; +}; + +export type DeleteV0CityByCityNameWorkflowByWorkflowIdResponse = DeleteV0CityByCityNameWorkflowByWorkflowIdResponses[keyof DeleteV0CityByCityNameWorkflowByWorkflowIdResponses]; + +export type GetV0CityByCityNameWorkflowByWorkflowIdData = { + body?: never; + path: { + /** + * City name. + */ + cityName: string; + /** + * Workflow (convoy) ID. + */ + workflow_id: string; + }; + query?: { + /** + * Scope kind (city or rig). + */ + scope_kind?: string; + /** + * Scope reference. + */ + scope_ref?: string; + }; + url: '/v0/city/{cityName}/workflow/{workflow_id}'; +}; + +export type GetV0CityByCityNameWorkflowByWorkflowIdErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0CityByCityNameWorkflowByWorkflowIdError = GetV0CityByCityNameWorkflowByWorkflowIdErrors[keyof GetV0CityByCityNameWorkflowByWorkflowIdErrors]; + +export type GetV0CityByCityNameWorkflowByWorkflowIdResponses = { + /** + * OK + */ + 200: WorkflowSnapshotResponse; +}; + +export type GetV0CityByCityNameWorkflowByWorkflowIdResponse = GetV0CityByCityNameWorkflowByWorkflowIdResponses[keyof GetV0CityByCityNameWorkflowByWorkflowIdResponses]; + +export type GetV0EventsData = { + body?: never; + path?: never; + query?: { + /** + * Filter by event type. + */ + type?: string; + /** + * Filter by actor. + */ + actor?: string; + /** + * Filter to events within the last Go duration (e.g. "5m"). + */ + since?: string; + /** + * Maximum number of trailing events to return. 0 = no limit. Used by 'gc events --seq' to compute the head cursor cheaply. + */ + limit?: number; + }; + url: '/v0/events'; +}; + +export type GetV0EventsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0EventsError = GetV0EventsErrors[keyof GetV0EventsErrors]; + +export type GetV0EventsResponses = { + /** + * OK + */ + 200: SupervisorEventListOutputBody; +}; + +export type GetV0EventsResponse = GetV0EventsResponses[keyof GetV0EventsResponses]; + +export type StreamSupervisorEventsData = { + body?: never; + headers?: { + /** + * Reconnect cursor (composite per-city cursor). + */ + 'Last-Event-ID'?: string; + }; + path?: never; + query?: { + /** + * Alternative to Last-Event-ID for browsers that can't set custom headers. + */ + after_cursor?: string; + }; + url: '/v0/events/stream'; +}; + +export type StreamSupervisorEventsErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type StreamSupervisorEventsError = StreamSupervisorEventsErrors[keyof StreamSupervisorEventsErrors]; + +export type StreamSupervisorEventsResponses = { + /** + * Server Sent Events + * + * Each oneOf object represents one possible SSE message. + */ + 200: Array<{ + data: HeartbeatEvent; + /** + * The event name. + */ + event: 'heartbeat'; + /** + * The event ID (composite cursor). + */ + id?: string; + /** + * The retry time in milliseconds. + */ + retry?: number; + } | { + data: TaggedEventStreamEnvelope; + /** + * The event name. + */ + event: 'tagged_event'; + /** + * The event ID (composite cursor). + */ + id?: string; + /** + * The retry time in milliseconds. + */ + retry?: number; + }>; +}; + +export type StreamSupervisorEventsResponse = StreamSupervisorEventsResponses[keyof StreamSupervisorEventsResponses]; + +export type GetV0ProviderReadinessData = { + body?: never; + path?: never; + query?: { + /** + * Comma-separated list of providers to probe. + */ + providers?: string; + /** + * Force fresh probe, bypassing cache. + */ + fresh?: boolean; + }; + url: '/v0/provider-readiness'; +}; + +export type GetV0ProviderReadinessErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0ProviderReadinessError = GetV0ProviderReadinessErrors[keyof GetV0ProviderReadinessErrors]; + +export type GetV0ProviderReadinessResponses = { + /** + * OK + */ + 200: ProviderReadinessResponse; +}; + +export type GetV0ProviderReadinessResponse = GetV0ProviderReadinessResponses[keyof GetV0ProviderReadinessResponses]; + +export type GetV0ReadinessData = { + body?: never; + path?: never; + query?: { + /** + * Comma-separated list of readiness items to check. + */ + items?: string; + /** + * Force fresh probe, bypassing cache. + */ + fresh?: boolean; + }; + url: '/v0/readiness'; +}; + +export type GetV0ReadinessErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type GetV0ReadinessError = GetV0ReadinessErrors[keyof GetV0ReadinessErrors]; + +export type GetV0ReadinessResponses = { + /** + * OK + */ + 200: ReadinessResponse; +}; + +export type GetV0ReadinessResponse = GetV0ReadinessResponses[keyof GetV0ReadinessResponses]; diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index b5a4ac4c74..08cdfbdbc9 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -217,7 +217,7 @@ func (s *Server) resolveSessionTemplate(template string) (*config.ResolvedProvid if err != nil { return nil, "", "", "", err } - return resolved, workDir, agentCfg.Session, agentCfg.QualifiedName(), nil + return resolved, workDir, config.ResolveSessionCreateTransport(agentCfg.Session, resolved), agentCfg.QualifiedName(), nil } func (s *Server) buildSessionResume(info session.Info) (string, runtime.Config, error) { diff --git a/internal/api/session_transport_test.go b/internal/api/session_transport_test.go index e9bcd864cd..163768559b 100644 --- a/internal/api/session_transport_test.go +++ b/internal/api/session_transport_test.go @@ -5,6 +5,7 @@ import ( "github.com/gastownhall/gascity/internal/config" "github.com/gastownhall/gascity/internal/runtime" + "github.com/gastownhall/gascity/internal/session" ) type createTransportCapableProvider struct { @@ -73,7 +74,7 @@ func TestResolveSessionTemplateForCreateUsesProviderACPDefault(t *testing.T) { } } -func TestResolveSessionTemplateKeepsLegacyRuntimeTransportDefault(t *testing.T) { +func TestResolveSessionTemplateUsesProviderACPDefaultForLegacyRuntimeTransport(t *testing.T) { fs := newSessionFakeState(t) supportsACP := true fs.cfg = &config.City{ @@ -99,8 +100,8 @@ func TestResolveSessionTemplateKeepsLegacyRuntimeTransportDefault(t *testing.T) if err != nil { t.Fatalf("resolveSessionTemplate: %v", err) } - if transport != "" { - t.Fatalf("transport = %q, want empty runtime default", transport) + if transport != "acp" { + t.Fatalf("transport = %q, want %q", transport, "acp") } } @@ -129,3 +130,39 @@ func TestConfiguredSessionTransportUsesProviderACPDefaultForAgentTemplates(t *te t.Fatalf("configuredSessionTransport() = %q, want %q", transport, "acp") } } + +func TestBuildSessionResumeUsesProviderACPDefaultForLegacyTemplateSession(t *testing.T) { + fs := newSessionFakeState(t) + supportsACP := true + fs.cfg = &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Agents: []config.Agent{{ + Name: "worker", + Dir: "myrig", + Provider: "custom-acp", + }}, + Providers: map[string]config.ProviderSpec{ + "custom-acp": { + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + } + + srv := New(fs) + cmd, _, err := srv.buildSessionResume(session.Info{ + ID: "gc-1", + Template: "myrig/worker", + Command: "/bin/echo", + WorkDir: "/tmp/workdir", + }) + if err != nil { + t.Fatalf("buildSessionResume: %v", err) + } + if cmd != "/bin/echo acp" { + t.Fatalf("resume command = %q, want %q", cmd, "/bin/echo acp") + } +} From 9e280d0c33bc380d3cfc9ff5744368c625e93f51 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 04:42:19 +0000 Subject: [PATCH 68/85] fix: replay template overrides on resume --- cmd/gc/providers.go | 2 +- cmd/gc/providers_test.go | 6 +-- cmd/gc/session_manager_test.go | 2 +- cmd/gc/worker_handle.go | 21 ++++++---- cmd/gc/worker_handle_test.go | 47 ++++++++++++++++++++++- internal/api/handler_session_chat_test.go | 4 +- internal/api/session_manager.go | 2 +- internal/api/session_runtime.go | 28 ++++++++------ internal/api/session_transport_test.go | 30 +++++++++++++++ internal/session/template_overrides.go | 26 +++++++++++++ 10 files changed, 139 insertions(+), 29 deletions(-) create mode 100644 internal/session/template_overrides.go diff --git a/cmd/gc/providers.go b/cmd/gc/providers.go index 35d5a6aab3..088cb78927 100644 --- a/cmd/gc/providers.go +++ b/cmd/gc/providers.go @@ -315,7 +315,7 @@ func providerSessionCreateUsesACP(cfg *config.City, providerName string) bool { func providerLegacyDefaultsToACP(cfg *config.City, providerName string) bool { resolved := resolveProviderForACPTransport(cfg, providerName) - return resolved != nil && resolved.DefaultSessionTransport() == "acp" + return resolved != nil && resolved.ProviderSessionCreateTransport() == "acp" } func observedACPSessionNames(snapshot *sessionBeadSnapshot, cfg *config.City) []string { diff --git a/cmd/gc/providers_test.go b/cmd/gc/providers_test.go index 43a0ed1d9b..6f8323f297 100644 --- a/cmd/gc/providers_test.go +++ b/cmd/gc/providers_test.go @@ -359,7 +359,7 @@ func TestConfiguredACPRouteNames_IncludeLegacyObservedACPProviderSessionsWithout } } -func TestConfiguredACPRouteNames_ExcludeLegacyObservedCustomACPProviderSessionsWithoutTransportMetadata(t *testing.T) { +func TestConfiguredACPRouteNames_IncludeLegacyObservedCustomACPProviderSessionsWithoutTransportMetadata(t *testing.T) { cfg := &config.City{ Workspace: config.Workspace{Name: "test-city"}, Providers: map[string]config.ProviderSpec{ @@ -383,8 +383,8 @@ func TestConfiguredACPRouteNames_ExcludeLegacyObservedCustomACPProviderSessionsW }}) got := configuredACPRouteNames(snapshot, "test-city", cfg) - if len(got) != 0 { - t.Fatalf("configuredACPRouteNames() = %v, want no legacy ACP inference for custom provider", got) + if len(got) != 1 || got[0] != "provider-session" { + t.Fatalf("configuredACPRouteNames() = %v, want [provider-session]", got) } } diff --git a/cmd/gc/session_manager_test.go b/cmd/gc/session_manager_test.go index d53586e781..507c7718f6 100644 --- a/cmd/gc/session_manager_test.go +++ b/cmd/gc/session_manager_test.go @@ -44,6 +44,6 @@ func newSessionManagerWithConfig(cityPath string, store beads.Store, sp runtime. if err != nil { return "" } - return strings.TrimSpace(resolved.DefaultSessionTransport()) + return strings.TrimSpace(resolved.ProviderSessionCreateTransport()) }) } diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index 0c7650a03d..e9853e12fd 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -60,7 +60,7 @@ func workerFactoryWithConfig(cityPath string, store beads.Store, sp runtime.Prov if err != nil { return "" } - return strings.TrimSpace(resolved.DefaultSessionTransport()) + return strings.TrimSpace(resolved.ProviderSessionCreateTransport()) } searchPaths = worker.MergeSearchPaths(cfg.Daemon.ObservePaths) } @@ -459,16 +459,21 @@ func resolvedWorkerRuntimeWithConfigAndMetadata(cityPath string, cfg *config.Cit } command := strings.TrimSpace(info.Command) + optionOverrides, err := session.ParseTemplateOverrides(metadata) + if err != nil { + return nil, fmt.Errorf("parsing template overrides: %w", err) + } + launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, optionOverrides, transport) + if err != nil { + return nil, fmt.Errorf("building provider launch command: %w", err) + } resolvedCommand := resolved.CommandString() if transport == "acp" { resolvedCommand = resolved.ACPCommandString() } - if !shouldPreserveStoredRuntimeCommand(command, resolvedCommand) { - launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, nil, transport) - command = resolvedCommand - if err == nil { - command = launchCommand.Command - } + desiredCommand := firstNonEmptyGCString(launchCommand.Command, resolvedCommand, resolved.Name) + if !shouldPreserveStoredRuntimeCommand(command, desiredCommand) { + command = desiredCommand } command = firstNonEmptyGCString(command, info.Provider, resolved.Name) @@ -547,7 +552,7 @@ func resolveWorkerRuntimeProviderWithConfig(cfg *config.City, info session.Info, if err != nil { return nil, "" } - return resolved, firstNonEmptyWorkerString(strings.TrimSpace(info.Transport), strings.TrimSpace(resolved.DefaultSessionTransport())) + return resolved, firstNonEmptyWorkerString(strings.TrimSpace(info.Transport), strings.TrimSpace(resolved.ProviderSessionCreateTransport())) } func workerDeliveryIntentForSubmitIntent(intent session.SubmitIntent) worker.DeliveryIntent { diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index 4c27bc2784..4adec089fe 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -414,7 +414,7 @@ acp_args = ["acp"] } } -func TestResolvedWorkerRuntimeWithConfigKeepsDefaultTransportForLegacyProviderSessionOnACPEnabledCustomProvider(t *testing.T) { +func TestResolvedWorkerRuntimeWithConfigUsesACPTransportForLegacyProviderSessionOnACPEnabledCustomProvider(t *testing.T) { cityDir := t.TempDir() writePhase0InterfaceCity(t, cityDir, `[workspace] name = "test-city" @@ -446,7 +446,7 @@ acp_args = ["acp"] if resolved == nil { t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") } - if got, want := resolved.Command, "/bin/echo"; got != want { + if got, want := resolved.Command, "/bin/echo acp"; got != want { t.Fatalf("Command = %q, want %q", got, want) } } @@ -492,6 +492,49 @@ acp_args = ["acp"] } } +func TestResolvedWorkerRuntimeWithConfigReplaysTemplateOverridesOnResume(t *testing.T) { + cityDir := t.TempDir() + cfg := &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Agents: []config.Agent{{ + Name: "worker", + Dir: "myrig", + Provider: "custom", + }}, + Providers: map[string]config.ProviderSpec{ + "custom": { + Command: "/bin/echo", + PathCheck: "true", + OptionsSchema: []config.ProviderOption{{ + Key: "effort", + Type: "select", + Choices: []config.OptionChoice{{ + Value: "high", + FlagArgs: []string{"--effort", "high"}, + }}, + }}, + }, + }, + } + + resolved, err := resolvedWorkerRuntimeWithConfigAndMetadata(cityDir, cfg, session.Info{ + Template: "myrig/worker", + Command: "/bin/echo", + WorkDir: cityDir, + }, "", map[string]string{ + "template_overrides": `{"effort":"high","initial_message":"hello"}`, + }) + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfigAndMetadata: %v", err) + } + if resolved == nil { + t.Fatal("resolvedWorkerRuntimeWithConfigAndMetadata() = nil") + } + if got, want := resolved.Command, "/bin/echo --effort high"; got != want { + t.Fatalf("Command = %q, want %q", got, want) + } +} + func TestWorkerHandleForSessionWithConfigUsesResolvedProviderOnResume(t *testing.T) { skipSlowCmdGCTest(t, "waits through stale session-key detection; run make test-cmd-gc-process for full coverage") cityDir := t.TempDir() diff --git a/internal/api/handler_session_chat_test.go b/internal/api/handler_session_chat_test.go index b093f90b30..5c25aa04ab 100644 --- a/internal/api/handler_session_chat_test.go +++ b/internal/api/handler_session_chat_test.go @@ -296,7 +296,7 @@ func TestBuildSessionResumeUsesConfiguredACPCommandForLegacyProviderSessionWitho } } -func TestBuildSessionResumeKeepsDefaultCommandForLegacyProviderSessionOnACPEnabledCustomProvider(t *testing.T) { +func TestBuildSessionResumeUsesACPCommandForLegacyProviderSessionOnACPEnabledCustomProvider(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) fs.cfg = &config.City{ @@ -326,7 +326,7 @@ func TestBuildSessionResumeKeepsDefaultCommandForLegacyProviderSessionOnACPEnabl if err != nil { t.Fatalf("buildSessionResume: %v", err) } - if got, want := cmd, "/bin/echo"; got != want { + if got, want := cmd, "/bin/echo acp"; got != want { t.Fatalf("resume command = %q, want %q", got, want) } } diff --git a/internal/api/session_manager.go b/internal/api/session_manager.go index f160844d34..1ea71564ae 100644 --- a/internal/api/session_manager.go +++ b/internal/api/session_manager.go @@ -55,5 +55,5 @@ func configuredSessionTransport(cfg *config.City, template, provider string) str if err != nil { return "" } - return strings.TrimSpace(resolved.DefaultSessionTransport()) + return strings.TrimSpace(resolved.ProviderSessionCreateTransport()) } diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index 08cdfbdbc9..a82aaef23b 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -226,12 +226,13 @@ func (s *Server) buildSessionResume(info session.Info) (string, runtime.Config, if resolved == nil { return cmd, runtime.Config{WorkDir: info.WorkDir}, nil } - mcpServers, err := s.resumeSessionMCPServers(info, s.sessionMetadata(info.ID), resolved, firstNonEmptyString(workDir, info.WorkDir), transport) + metadata := s.sessionMetadata(info.ID) + mcpServers, err := s.resumeSessionMCPServers(info, metadata, resolved, firstNonEmptyString(workDir, info.WorkDir), transport) if err != nil { return "", runtime.Config{}, err } resolvedInfo := info - if command, err := s.resolvedSessionRuntimeCommand(resolved, transport, info.Command); err == nil { + if command, err := s.resolvedSessionRuntimeCommand(resolved, transport, info.Command, metadata); err == nil { resolvedInfo.Command = command } else { resolvedCommand := resolved.CommandString() @@ -248,19 +249,24 @@ func (s *Server) buildSessionResume(info session.Info) (string, runtime.Config, return session.BuildResumeCommand(resolvedInfo), sessionResumeHints(resolved, workDir, mcpServers), nil } -func (s *Server) resolvedSessionRuntimeCommand(resolved *config.ResolvedProvider, transport, storedCommand string) (string, error) { +func (s *Server) resolvedSessionRuntimeCommand(resolved *config.ResolvedProvider, transport, storedCommand string, metadata map[string]string) (string, error) { + optionOverrides, err := session.ParseTemplateOverrides(metadata) + if err != nil { + return "", fmt.Errorf("parsing template overrides: %w", err) + } + launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, optionOverrides, transport) + if err != nil { + return "", fmt.Errorf("building provider launch command: %w", err) + } resolvedCommand := resolved.CommandString() if transport == "acp" { resolvedCommand = resolved.ACPCommandString() } - if command := strings.TrimSpace(storedCommand); shouldPreserveStoredRuntimeCommand(command, resolvedCommand) { + desiredCommand := firstNonEmptyString(launchCommand.Command, resolvedCommand, resolved.Name) + if command := strings.TrimSpace(storedCommand); shouldPreserveStoredRuntimeCommand(command, desiredCommand) { return command, nil } - launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, nil, transport) - if err != nil { - return "", fmt.Errorf("building provider launch command: %w", err) - } - return firstNonEmptyString(launchCommand.Command, resolvedCommand, resolved.Name), nil + return desiredCommand, nil } func shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand string) bool { @@ -297,7 +303,7 @@ func (s *Server) resolveWorkerSessionRuntimeWithMetadata(info session.Info, _ st if err != nil { return nil, err } - command, err := s.resolvedSessionRuntimeCommand(resolved, transport, info.Command) + command, err := s.resolvedSessionRuntimeCommand(resolved, transport, info.Command, metadata) if err != nil { return nil, err } @@ -340,7 +346,7 @@ func (s *Server) resolveSessionRuntime(info session.Info) (*config.ResolvedProvi if workDir == "" { workDir = s.state.CityPath() } - transport := firstNonEmptyString(strings.TrimSpace(info.Transport), strings.TrimSpace(resolved.DefaultSessionTransport())) + transport := firstNonEmptyString(strings.TrimSpace(info.Transport), strings.TrimSpace(resolved.ProviderSessionCreateTransport())) return resolved, workDir, transport } diff --git a/internal/api/session_transport_test.go b/internal/api/session_transport_test.go index 163768559b..fd669d9966 100644 --- a/internal/api/session_transport_test.go +++ b/internal/api/session_transport_test.go @@ -166,3 +166,33 @@ func TestBuildSessionResumeUsesProviderACPDefaultForLegacyTemplateSession(t *tes t.Fatalf("resume command = %q, want %q", cmd, "/bin/echo acp") } } + +func TestResolvedSessionRuntimeCommandReplaysTemplateOverrides(t *testing.T) { + fs := newSessionFakeState(t) + srv := New(fs) + resolved := &config.ResolvedProvider{ + Name: "custom", + Command: "/bin/echo", + OptionsSchema: []config.ProviderOption{{ + Key: "effort", + Type: "select", + Choices: []config.OptionChoice{{ + Value: "high", + FlagArgs: []string{"--effort", "high"}, + }}, + }}, + } + + command, err := srv.resolvedSessionRuntimeCommand( + resolved, + "", + "/bin/echo", + map[string]string{"template_overrides": `{"effort":"high","initial_message":"hello"}`}, + ) + if err != nil { + t.Fatalf("resolvedSessionRuntimeCommand: %v", err) + } + if command != "/bin/echo --effort high" { + t.Fatalf("command = %q, want %q", command, "/bin/echo --effort high") + } +} diff --git a/internal/session/template_overrides.go b/internal/session/template_overrides.go new file mode 100644 index 0000000000..a5293f422d --- /dev/null +++ b/internal/session/template_overrides.go @@ -0,0 +1,26 @@ +package session + +import ( + "encoding/json" + "fmt" + "strings" +) + +// ParseTemplateOverrides decodes persisted session template_overrides metadata. +func ParseTemplateOverrides(metadata map[string]string) (map[string]string, error) { + if metadata == nil { + return nil, nil + } + raw := strings.TrimSpace(metadata["template_overrides"]) + if raw == "" { + return nil, nil + } + var overrides map[string]string + if err := json.Unmarshal([]byte(raw), &overrides); err != nil { + return nil, fmt.Errorf("unmarshal template_overrides: %w", err) + } + if len(overrides) == 0 { + return nil, nil + } + return overrides, nil +} From 8c74c50cf5e58b614d7c4de0fb03817998b29b8b Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 05:06:11 +0000 Subject: [PATCH 69/85] fix: recover provider resume contracts --- cmd/gc/api_state.go | 2 + .../dashboard/web/src/generated/schema.d.ts | 14 ++++ .../dashboard/web/src/generated/types.gen.ts | 22 +++++ cmd/gc/worker_handle.go | 54 +++++++++--- cmd/gc/worker_handle_test.go | 35 ++++++++ docs/schema/openapi.json | 64 ++++++++++++++ docs/schema/openapi.txt | 64 ++++++++++++++ internal/api/fake_state_test.go | 7 ++ internal/api/genclient/client_gen.go | 20 +++++ internal/api/handler_config.go | 2 + internal/api/handler_config_test.go | 27 +++++- internal/api/handler_provider_crud_test.go | 83 +++++++++++++++++++ internal/api/handler_providers.go | 4 + internal/api/handler_session_chat_test.go | 28 ++++++- internal/api/huma_handlers_config.go | 8 ++ internal/api/huma_handlers_providers.go | 4 + internal/api/huma_types_providers.go | 4 + internal/api/openapi.json | 64 ++++++++++++++ internal/api/session_runtime.go | 40 +++++++-- internal/api/state.go | 2 + internal/api/worker_factory_test.go | 29 +++++++ internal/configedit/configedit.go | 9 ++ internal/configedit/configedit_test.go | 9 ++ 23 files changed, 572 insertions(+), 23 deletions(-) diff --git a/cmd/gc/api_state.go b/cmd/gc/api_state.go index dcd3d9fe7c..db26edae92 100644 --- a/cmd/gc/api_state.go +++ b/cmd/gc/api_state.go @@ -587,7 +587,9 @@ func (cs *controllerState) UpdateProvider(name string, patch api.ProviderUpdate) DisplayName: patch.DisplayName, Base: patch.Base, Command: patch.Command, + ACPCommand: patch.ACPCommand, Args: patch.Args, + ACPArgs: patch.ACPArgs, ArgsAppend: patch.ArgsAppend, PromptMode: patch.PromptMode, PromptFlag: patch.PromptFlag, diff --git a/cmd/gc/dashboard/web/src/generated/schema.d.ts b/cmd/gc/dashboard/web/src/generated/schema.d.ts index 589fdb08ea..be0bee478e 100644 --- a/cmd/gc/dashboard/web/src/generated/schema.d.ts +++ b/cmd/gc/dashboard/web/src/generated/schema.d.ts @@ -2050,6 +2050,8 @@ export interface components { suspended: boolean; }; AnnotatedProviderResponse: { + acp_args?: string[] | null; + acp_command?: string; args?: string[] | null; command?: string; display_name?: string; @@ -3211,6 +3213,10 @@ export interface components { OnDeath: string | null; }; ProviderCreateInputBody: { + /** @description ACP transport command arguments override. */ + acp_args?: string[] | null; + /** @description ACP transport command binary override. */ + acp_command?: string; /** @description Command arguments. */ args?: string[] | null; /** @description Arguments appended after inherited/base args. */ @@ -3327,6 +3333,8 @@ export interface components { }; }; ProviderResponse: { + acp_args?: string[] | null; + acp_command?: string; args?: string[] | null; builtin: boolean; city_level: boolean; @@ -3342,6 +3350,8 @@ export interface components { ready_delay_ms?: number; }; ProviderSpecJSON: { + acp_args?: string[] | null; + acp_command?: string; args?: string[] | null; command?: string; display_name?: string; @@ -3354,6 +3364,10 @@ export interface components { ready_delay_ms?: number; }; ProviderUpdateInputBody: { + /** @description ACP transport command arguments override. */ + acp_args?: string[] | null; + /** @description ACP transport command binary override. */ + acp_command?: string; /** @description Command arguments. */ args?: string[] | null; /** @description Arguments appended after inherited/base args. */ diff --git a/cmd/gc/dashboard/web/src/generated/types.gen.ts b/cmd/gc/dashboard/web/src/generated/types.gen.ts index 4053db6bdd..caaf7a7e7b 100644 --- a/cmd/gc/dashboard/web/src/generated/types.gen.ts +++ b/cmd/gc/dashboard/web/src/generated/types.gen.ts @@ -200,6 +200,8 @@ export type AnnotatedAgentResponse = { }; export type AnnotatedProviderResponse = { + acp_args?: Array | null; + acp_command?: string; args?: Array | null; command?: string; display_name?: string; @@ -1745,6 +1747,14 @@ export type PoolOverride = { }; export type ProviderCreateInputBody = { + /** + * ACP transport command arguments override. + */ + acp_args?: Array | null; + /** + * ACP transport command binary override. + */ + acp_command?: string; /** * Command arguments. */ @@ -1903,6 +1913,8 @@ export type ProviderReadinessResponse = { }; export type ProviderResponse = { + acp_args?: Array | null; + acp_command?: string; args?: Array | null; builtin: boolean; city_level: boolean; @@ -1918,6 +1930,8 @@ export type ProviderResponse = { }; export type ProviderSpecJson = { + acp_args?: Array | null; + acp_command?: string; args?: Array | null; command?: string; display_name?: string; @@ -1930,6 +1944,14 @@ export type ProviderSpecJson = { }; export type ProviderUpdateInputBody = { + /** + * ACP transport command arguments override. + */ + acp_args?: Array | null; + /** + * ACP transport command binary override. + */ + acp_command?: string; /** * Command arguments. */ diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index e9853e12fd..137a7b3d10 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -459,19 +459,19 @@ func resolvedWorkerRuntimeWithConfigAndMetadata(cityPath string, cfg *config.Cit } command := strings.TrimSpace(info.Command) - optionOverrides, err := session.ParseTemplateOverrides(metadata) - if err != nil { - return nil, fmt.Errorf("parsing template overrides: %w", err) - } - launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, optionOverrides, transport) - if err != nil { - return nil, fmt.Errorf("building provider launch command: %w", err) - } - resolvedCommand := resolved.CommandString() - if transport == "acp" { - resolvedCommand = resolved.ACPCommandString() + desiredCommand := fallbackResolvedWorkerRuntimeCommand(resolved, transport, command) + if optionOverrides, err := session.ParseTemplateOverrides(metadata); err == nil { + if launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, optionOverrides, transport); err == nil { + resolvedCommand := resolved.CommandString() + if transport == "acp" { + resolvedCommand = resolved.ACPCommandString() + } + desiredCommand = firstNonEmptyGCString(launchCommand.Command, resolvedCommand, resolved.Name) + if shouldPreserveStoredRuntimeCommandForTransport(command, desiredCommand, transport, optionOverrides) { + desiredCommand = command + } + } } - desiredCommand := firstNonEmptyGCString(launchCommand.Command, resolvedCommand, resolved.Name) if !shouldPreserveStoredRuntimeCommand(command, desiredCommand) { command = desiredCommand } @@ -528,6 +528,36 @@ func shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand string) b return strings.HasPrefix(storedCommand, resolvedCommand+" ") } +func shouldPreserveStoredRuntimeCommandForTransport(storedCommand, resolvedCommand, transport string, optionOverrides map[string]string) bool { + if shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand) { + return true + } + if transport == "acp" || len(optionOverrides) != 0 { + return false + } + return sameRuntimeCommandExecutable(storedCommand, resolvedCommand) +} + +func sameRuntimeCommandExecutable(storedCommand, resolvedCommand string) bool { + storedFields := strings.Fields(strings.TrimSpace(storedCommand)) + resolvedFields := strings.Fields(strings.TrimSpace(resolvedCommand)) + if len(storedFields) == 0 || len(resolvedFields) == 0 { + return false + } + return storedFields[0] == resolvedFields[0] +} + +func fallbackResolvedWorkerRuntimeCommand(resolved *config.ResolvedProvider, transport, storedCommand string) string { + resolvedCommand := "" + if resolved != nil { + resolvedCommand = resolved.CommandString() + if transport == "acp" { + resolvedCommand = resolved.ACPCommandString() + } + } + return firstNonEmptyGCString(storedCommand, resolvedCommand, resolved.Name) +} + func firstNonEmptyWorkerString(values ...string) string { for _, value := range values { if trimmed := strings.TrimSpace(value); trimmed != "" { diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index 4adec089fe..cbaa0f4a69 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -535,6 +535,41 @@ func TestResolvedWorkerRuntimeWithConfigReplaysTemplateOverridesOnResume(t *test } } +func TestResolvedWorkerRuntimeWithConfigFallsBackToStoredCommandWhenTemplateOverridesInvalid(t *testing.T) { + cityDir := t.TempDir() + cfg := &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Agents: []config.Agent{{ + Name: "worker", + Dir: "myrig", + Provider: "custom", + }}, + Providers: map[string]config.ProviderSpec{ + "custom": { + Command: "/bin/echo", + PathCheck: "true", + }, + }, + } + + resolved, err := resolvedWorkerRuntimeWithConfigAndMetadata(cityDir, cfg, session.Info{ + Template: "myrig/worker", + Command: "/bin/echo --stored", + WorkDir: cityDir, + }, "", map[string]string{ + "template_overrides": `{`, + }) + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfigAndMetadata: %v", err) + } + if resolved == nil { + t.Fatal("resolvedWorkerRuntimeWithConfigAndMetadata() = nil") + } + if got, want := resolved.Command, "/bin/echo --stored"; got != want { + t.Fatalf("Command = %q, want %q", got, want) + } +} + func TestWorkerHandleForSessionWithConfigUsesResolvedProviderOnResume(t *testing.T) { skipSlowCmdGCTest(t, "waits through stale session-key detection; run make test-cmd-gc-process for full coverage") cityDir := t.TempDir() diff --git a/docs/schema/openapi.json b/docs/schema/openapi.json index 771b40da87..1c603f51a9 100644 --- a/docs/schema/openapi.json +++ b/docs/schema/openapi.json @@ -674,6 +674,18 @@ "AnnotatedProviderResponse": { "additionalProperties": false, "properties": { + "acp_args": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "type": "string" + }, "args": { "items": { "type": "string" @@ -4473,6 +4485,20 @@ "ProviderCreateInputBody": { "additionalProperties": false, "properties": { + "acp_args": { + "description": "ACP transport command arguments override.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "description": "ACP transport command binary override.", + "type": "string" + }, "args": { "description": "Command arguments.", "items": { @@ -4856,6 +4882,18 @@ "ProviderResponse": { "additionalProperties": false, "properties": { + "acp_args": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "type": "string" + }, "args": { "items": { "type": "string" @@ -4907,6 +4945,18 @@ "ProviderSpecJSON": { "additionalProperties": false, "properties": { + "acp_args": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "type": "string" + }, "args": { "items": { "type": "string" @@ -4944,6 +4994,20 @@ "ProviderUpdateInputBody": { "additionalProperties": false, "properties": { + "acp_args": { + "description": "ACP transport command arguments override.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "description": "ACP transport command binary override.", + "type": "string" + }, "args": { "description": "Command arguments.", "items": { diff --git a/docs/schema/openapi.txt b/docs/schema/openapi.txt index 771b40da87..1c603f51a9 100644 --- a/docs/schema/openapi.txt +++ b/docs/schema/openapi.txt @@ -674,6 +674,18 @@ "AnnotatedProviderResponse": { "additionalProperties": false, "properties": { + "acp_args": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "type": "string" + }, "args": { "items": { "type": "string" @@ -4473,6 +4485,20 @@ "ProviderCreateInputBody": { "additionalProperties": false, "properties": { + "acp_args": { + "description": "ACP transport command arguments override.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "description": "ACP transport command binary override.", + "type": "string" + }, "args": { "description": "Command arguments.", "items": { @@ -4856,6 +4882,18 @@ "ProviderResponse": { "additionalProperties": false, "properties": { + "acp_args": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "type": "string" + }, "args": { "items": { "type": "string" @@ -4907,6 +4945,18 @@ "ProviderSpecJSON": { "additionalProperties": false, "properties": { + "acp_args": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "type": "string" + }, "args": { "items": { "type": "string" @@ -4944,6 +4994,20 @@ "ProviderUpdateInputBody": { "additionalProperties": false, "properties": { + "acp_args": { + "description": "ACP transport command arguments override.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "description": "ACP transport command binary override.", + "type": "string" + }, "args": { "description": "Command arguments.", "items": { diff --git a/internal/api/fake_state_test.go b/internal/api/fake_state_test.go index 431d817f59..47d01c2bc7 100644 --- a/internal/api/fake_state_test.go +++ b/internal/api/fake_state_test.go @@ -302,10 +302,17 @@ func (f *fakeMutatorState) UpdateProvider(name string, patch ProviderUpdate) err if patch.Command != nil { spec.Command = *patch.Command } + if patch.ACPCommand != nil { + spec.ACPCommand = *patch.ACPCommand + } if patch.Args != nil { spec.Args = make([]string, len(patch.Args)) copy(spec.Args, patch.Args) } + if patch.ACPArgs != nil { + spec.ACPArgs = make([]string, len(patch.ACPArgs)) + copy(spec.ACPArgs, patch.ACPArgs) + } if patch.ArgsAppend != nil { spec.ArgsAppend = make([]string, len(patch.ArgsAppend)) copy(spec.ArgsAppend, patch.ArgsAppend) diff --git a/internal/api/genclient/client_gen.go b/internal/api/genclient/client_gen.go index 2f98ea2935..28c8006b4f 100644 --- a/internal/api/genclient/client_gen.go +++ b/internal/api/genclient/client_gen.go @@ -372,6 +372,8 @@ type AnnotatedAgentResponse struct { // AnnotatedProviderResponse defines model for AnnotatedProviderResponse. type AnnotatedProviderResponse struct { + AcpArgs *[]string `json:"acp_args,omitempty"` + AcpCommand *string `json:"acp_command,omitempty"` Args *[]string `json:"args,omitempty"` Command *string `json:"command,omitempty"` DisplayName *string `json:"display_name,omitempty"` @@ -1759,6 +1761,12 @@ type PoolOverride struct { // ProviderCreateInputBody defines model for ProviderCreateInputBody. type ProviderCreateInputBody struct { + // AcpArgs ACP transport command arguments override. + AcpArgs *[]string `json:"acp_args,omitempty"` + + // AcpCommand ACP transport command binary override. + AcpCommand *string `json:"acp_command,omitempty"` + // Args Command arguments. Args *[]string `json:"args,omitempty"` @@ -1813,6 +1821,8 @@ type ProviderOptionDTO struct { // ProviderPatch defines model for ProviderPatch. type ProviderPatch struct { + ACPArgs *[]string `json:"ACPArgs"` + ACPCommand *string `json:"ACPCommand"` Args *[]string `json:"Args"` ArgsAppend *[]string `json:"ArgsAppend"` Base *string `json:"Base"` @@ -1887,6 +1897,8 @@ type ProviderReadinessResponse struct { // ProviderResponse defines model for ProviderResponse. type ProviderResponse struct { + AcpArgs *[]string `json:"acp_args,omitempty"` + AcpCommand *string `json:"acp_command,omitempty"` Args *[]string `json:"args,omitempty"` Builtin bool `json:"builtin"` CityLevel bool `json:"city_level"` @@ -1901,6 +1913,8 @@ type ProviderResponse struct { // ProviderSpecJSON defines model for ProviderSpecJSON. type ProviderSpecJSON struct { + AcpArgs *[]string `json:"acp_args,omitempty"` + AcpCommand *string `json:"acp_command,omitempty"` Args *[]string `json:"args,omitempty"` Command *string `json:"command,omitempty"` DisplayName *string `json:"display_name,omitempty"` @@ -1912,6 +1926,12 @@ type ProviderSpecJSON struct { // ProviderUpdateInputBody defines model for ProviderUpdateInputBody. type ProviderUpdateInputBody struct { + // AcpArgs ACP transport command arguments override. + AcpArgs *[]string `json:"acp_args,omitempty"` + + // AcpCommand ACP transport command binary override. + AcpCommand *string `json:"acp_command,omitempty"` + // Args Command arguments. Args *[]string `json:"args,omitempty"` diff --git a/internal/api/handler_config.go b/internal/api/handler_config.go index eab75db547..9e078a98dd 100644 --- a/internal/api/handler_config.go +++ b/internal/api/handler_config.go @@ -45,7 +45,9 @@ type configRigResponse struct { type providerSpecJSON struct { DisplayName string `json:"display_name,omitempty"` Command string `json:"command,omitempty"` + ACPCommand string `json:"acp_command,omitempty"` Args []string `json:"args,omitempty"` + ACPArgs []string `json:"acp_args,omitempty"` PromptMode string `json:"prompt_mode,omitempty"` PromptFlag string `json:"prompt_flag,omitempty"` ReadyDelayMs int `json:"ready_delay_ms,omitempty"` diff --git a/internal/api/handler_config_test.go b/internal/api/handler_config_test.go index 1b6e8f66ec..dab90c88e1 100644 --- a/internal/api/handler_config_test.go +++ b/internal/api/handler_config_test.go @@ -16,7 +16,12 @@ func TestHandleConfigGet(t *testing.T) { fs.cfg.Agents[0].MinActiveSessions = intPtr(0) fs.cfg.Agents[0].MaxActiveSessions = intPtr(3) fs.cfg.Providers = map[string]config.ProviderSpec{ - "custom": {DisplayName: "Custom", Command: "custom-cli"}, + "custom": { + DisplayName: "Custom", + Command: "custom-cli", + ACPCommand: "custom-cli-acp", + ACPArgs: []string{"rpc", "--stdio"}, + }, } h := newTestCityHandler(t, fs) @@ -52,6 +57,12 @@ func TestHandleConfigGet(t *testing.T) { if _, ok := resp.Providers["custom"]; !ok { t.Error("expected 'custom' in providers") } + if resp.Providers["custom"].ACPCommand != "custom-cli-acp" { + t.Errorf("providers.custom.acp_command = %q, want %q", resp.Providers["custom"].ACPCommand, "custom-cli-acp") + } + if len(resp.Providers["custom"].ACPArgs) != 2 || resp.Providers["custom"].ACPArgs[0] != "rpc" || resp.Providers["custom"].ACPArgs[1] != "--stdio" { + t.Errorf("providers.custom.acp_args = %#v, want [rpc --stdio]", resp.Providers["custom"].ACPArgs) + } } func TestHandleConfigGet_UsesEffectiveWorkspaceIdentity(t *testing.T) { @@ -166,7 +177,12 @@ func TestHandleConfigExplain(t *testing.T) { fs.cfg.Agents[0].MinActiveSessions = intPtr(0) fs.cfg.Agents[0].MaxActiveSessions = intPtr(3) fs.cfg.Providers = map[string]config.ProviderSpec{ - "claude": {DisplayName: "My Claude", Command: "my-claude"}, + "claude": { + DisplayName: "My Claude", + Command: "my-claude", + ACPCommand: "my-claude-acp", + ACPArgs: []string{"rpc"}, + }, } h := newTestCityHandler(t, fs) @@ -206,6 +222,13 @@ func TestHandleConfigExplain(t *testing.T) { if claude["origin"] != "builtin+city" { t.Errorf("claude origin = %q, want %q", claude["origin"], "builtin+city") } + if claude["acp_command"] != "my-claude-acp" { + t.Errorf("claude acp_command = %q, want %q", claude["acp_command"], "my-claude-acp") + } + acpArgs, ok := claude["acp_args"].([]any) + if !ok || len(acpArgs) != 1 || acpArgs[0] != "rpc" { + t.Errorf("claude acp_args = %#v, want [rpc]", claude["acp_args"]) + } // A builtin-only provider should have origin "builtin". codex := providers["codex"].(map[string]any) if codex["origin"] != "builtin" { diff --git a/internal/api/handler_provider_crud_test.go b/internal/api/handler_provider_crud_test.go index 04ce338d08..64ffc706d6 100644 --- a/internal/api/handler_provider_crud_test.go +++ b/internal/api/handler_provider_crud_test.go @@ -1,10 +1,13 @@ package api import ( + "encoding/json" "net/http" "net/http/httptest" "strings" "testing" + + "github.com/gastownhall/gascity/internal/config" ) func TestHandleProviderCreate_AllowsBaseOnlyDescendant(t *testing.T) { @@ -32,6 +35,32 @@ func TestHandleProviderCreate_AllowsBaseOnlyDescendant(t *testing.T) { } } +func TestHandleProviderCreate_PersistsACPTransportOverrides(t *testing.T) { + fs := newFakeMutatorState(t) + srv := New(fs) + h := newTestCityHandlerWith(t, fs, srv) + + req := newPostRequest(cityURL(fs, "/providers"), strings.NewReader( + `{"name":"custom-acp","command":"custom","acp_command":"custom-acp","acp_args":["rpc","--stdio"]}`)) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + + if rec.Code != http.StatusCreated { + t.Fatalf("status = %d, want %d; body=%s", rec.Code, http.StatusCreated, rec.Body.String()) + } + + spec, ok := fs.cfg.Providers["custom-acp"] + if !ok { + t.Fatal("provider custom-acp not created") + } + if spec.ACPCommand != "custom-acp" { + t.Fatalf("ACPCommand = %q, want %q", spec.ACPCommand, "custom-acp") + } + if len(spec.ACPArgs) != 2 || spec.ACPArgs[0] != "rpc" || spec.ACPArgs[1] != "--stdio" { + t.Fatalf("ACPArgs = %#v, want [rpc --stdio]", spec.ACPArgs) + } +} + func TestHandleProviderUpdate_UpdatesInheritanceFields(t *testing.T) { fs := newFakeMutatorState(t) fs.cfg.Providers["custom"] = fs.cfg.Providers["test-agent"] @@ -58,3 +87,57 @@ func TestHandleProviderUpdate_UpdatesInheritanceFields(t *testing.T) { t.Fatalf("OptionsSchemaMerge = %q, want by_key", spec.OptionsSchemaMerge) } } + +func TestHandleProviderUpdate_UpdatesACPTransportOverrides(t *testing.T) { + fs := newFakeMutatorState(t) + fs.cfg.Providers["custom"] = fs.cfg.Providers["test-agent"] + srv := New(fs) + h := newTestCityHandlerWith(t, fs, srv) + + req := httptest.NewRequest(http.MethodPatch, cityURL(fs, "/provider/custom"), strings.NewReader( + `{"acp_command":"custom-acp","acp_args":["rpc","--stdio"]}`)) + req.Header.Set("X-GC-Request", "true") + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String()) + } + + spec := fs.cfg.Providers["custom"] + if spec.ACPCommand != "custom-acp" { + t.Fatalf("ACPCommand = %q, want %q", spec.ACPCommand, "custom-acp") + } + if len(spec.ACPArgs) != 2 || spec.ACPArgs[0] != "rpc" || spec.ACPArgs[1] != "--stdio" { + t.Fatalf("ACPArgs = %#v, want [rpc --stdio]", spec.ACPArgs) + } +} + +func TestHandleProviderGet_IncludesACPTransportOverrides(t *testing.T) { + fs := newFakeState(t) + fs.cfg.Providers["custom"] = config.ProviderSpec{ + Command: "custom", + ACPCommand: "custom-acp", + ACPArgs: []string{"rpc", "--stdio"}, + } + h := newTestCityHandler(t, fs) + + req := httptest.NewRequest(http.MethodGet, cityURL(fs, "/provider/custom"), nil) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String()) + } + + var resp providerResponse + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode response: %v", err) + } + if resp.ACPCommand != "custom-acp" { + t.Fatalf("ACPCommand = %q, want %q", resp.ACPCommand, "custom-acp") + } + if len(resp.ACPArgs) != 2 || resp.ACPArgs[0] != "rpc" || resp.ACPArgs[1] != "--stdio" { + t.Fatalf("ACPArgs = %#v, want [rpc --stdio]", resp.ACPArgs) + } +} diff --git a/internal/api/handler_providers.go b/internal/api/handler_providers.go index 6802256b5b..3f41236d35 100644 --- a/internal/api/handler_providers.go +++ b/internal/api/handler_providers.go @@ -13,7 +13,9 @@ type providerResponse struct { Name string `json:"name"` DisplayName string `json:"display_name,omitempty"` Command string `json:"command,omitempty"` + ACPCommand string `json:"acp_command,omitempty"` Args []string `json:"args,omitempty"` + ACPArgs []string `json:"acp_args,omitempty"` PromptMode string `json:"prompt_mode,omitempty"` PromptFlag string `json:"prompt_flag,omitempty"` ReadyDelayMs int `json:"ready_delay_ms,omitempty"` @@ -40,7 +42,9 @@ func providerFromSpec(name string, spec config.ProviderSpec, builtin, cityLevel Name: name, DisplayName: spec.DisplayName, Command: spec.Command, + ACPCommand: spec.ACPCommand, Args: spec.Args, + ACPArgs: spec.ACPArgs, PromptMode: spec.PromptMode, PromptFlag: spec.PromptFlag, ReadyDelayMs: spec.ReadyDelayMs, diff --git a/internal/api/handler_session_chat_test.go b/internal/api/handler_session_chat_test.go index 5c25aa04ab..55daa92a92 100644 --- a/internal/api/handler_session_chat_test.go +++ b/internal/api/handler_session_chat_test.go @@ -222,6 +222,30 @@ func TestBuildSessionResumeUsesStoredACPCommandForProviderSession(t *testing.T) } } +func TestBuildSessionResumeFallsBackToStoredCommandWhenTemplateOverridesInvalid(t *testing.T) { + fs := newSessionFakeState(t) + fs.cfg.Providers["test-agent"] = config.ProviderSpec{ + Command: "/bin/echo", + PathCheck: "true", + } + + info := createTestSession(t, fs.cityBeadStore, fs.sp, "Chat") + info.Template = "myrig/worker" + info.Command = "/bin/echo --stored" + if err := fs.cityBeadStore.SetMetadata(info.ID, "template_overrides", "{"); err != nil { + t.Fatalf("SetMetadata(template_overrides): %v", err) + } + + srv := New(fs) + cmd, _, err := srv.buildSessionResume(info) + if err != nil { + t.Fatalf("buildSessionResume: %v", err) + } + if got, want := cmd, "/bin/echo --stored"; got != want { + t.Fatalf("resume command = %q, want %q", got, want) + } +} + func TestBuildSessionResumeUsesConfiguredACPCommandForLegacyProviderSessionWithoutTransportMetadataWithoutSessionAutoProvider(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) @@ -408,7 +432,7 @@ func TestBuildSessionResumeUsesConfiguredACPCommandForLegacyTemplateSessionWitho } } -func TestBuildSessionResumeKeepsDefaultCommandForLegacyTemplateWithoutExplicitACPTransport(t *testing.T) { +func TestBuildSessionResumeUsesACPCommandForLegacyTemplateWithoutExplicitTransport(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) fs.cfg = &config.City{ @@ -441,7 +465,7 @@ func TestBuildSessionResumeKeepsDefaultCommandForLegacyTemplateWithoutExplicitAC if err != nil { t.Fatalf("buildSessionResume: %v", err) } - if got, want := cmd, "/bin/echo"; got != want { + if got, want := cmd, "/bin/echo acp"; got != want { t.Fatalf("resume command = %q, want %q", got, want) } } diff --git a/internal/api/huma_handlers_config.go b/internal/api/huma_handlers_config.go index dbec8d8ed5..6aeb27fe00 100644 --- a/internal/api/huma_handlers_config.go +++ b/internal/api/huma_handlers_config.go @@ -44,7 +44,9 @@ func (s *Server) humaHandleConfigGet(_ context.Context, _ *ConfigGetInput) (*Ind providers[name] = providerSpecJSON{ DisplayName: spec.DisplayName, Command: spec.Command, + ACPCommand: spec.ACPCommand, Args: spec.Args, + ACPArgs: spec.ACPArgs, PromptMode: spec.PromptMode, PromptFlag: spec.PromptFlag, ReadyDelayMs: spec.ReadyDelayMs, @@ -129,7 +131,9 @@ func (s *Server) humaHandleConfigExplain(_ context.Context, _ *ConfigExplainInpu provMap[name] = annotatedProviderResponse{ DisplayName: spec.DisplayName, Command: spec.Command, + ACPCommand: spec.ACPCommand, Args: spec.Args, + ACPArgs: spec.ACPArgs, PromptMode: spec.PromptMode, PromptFlag: spec.PromptFlag, ReadyDelayMs: spec.ReadyDelayMs, @@ -143,7 +147,9 @@ func (s *Server) humaHandleConfigExplain(_ context.Context, _ *ConfigExplainInpu provMap[name] = annotatedProviderResponse{ DisplayName: spec.DisplayName, Command: spec.Command, + ACPCommand: spec.ACPCommand, Args: spec.Args, + ACPArgs: spec.ACPArgs, PromptMode: spec.PromptMode, PromptFlag: spec.PromptFlag, ReadyDelayMs: spec.ReadyDelayMs, @@ -220,7 +226,9 @@ type annotatedAgentResponse struct { type annotatedProviderResponse struct { DisplayName string `json:"display_name,omitempty"` Command string `json:"command,omitempty"` + ACPCommand string `json:"acp_command,omitempty"` Args []string `json:"args,omitempty"` + ACPArgs []string `json:"acp_args,omitempty"` PromptMode string `json:"prompt_mode,omitempty"` PromptFlag string `json:"prompt_flag,omitempty"` ReadyDelayMs int `json:"ready_delay_ms,omitempty"` diff --git a/internal/api/huma_handlers_providers.go b/internal/api/huma_handlers_providers.go index d5a9a82e38..9fd3132b3a 100644 --- a/internal/api/huma_handlers_providers.go +++ b/internal/api/huma_handlers_providers.go @@ -140,7 +140,9 @@ func (s *Server) humaHandleProviderCreate(_ context.Context, input *ProviderCrea DisplayName: input.Body.DisplayName, Base: input.Body.Base, Command: input.Body.Command, + ACPCommand: input.Body.ACPCommand, Args: input.Body.Args, + ACPArgs: input.Body.ACPArgs, ArgsAppend: input.Body.ArgsAppend, PromptMode: input.Body.PromptMode, PromptFlag: input.Body.PromptFlag, @@ -172,7 +174,9 @@ func (s *Server) humaHandleProviderUpdate(_ context.Context, input *ProviderUpda patch := ProviderUpdate{ DisplayName: input.Body.DisplayName, Command: input.Body.Command, + ACPCommand: input.Body.ACPCommand, Args: input.Body.Args, + ACPArgs: input.Body.ACPArgs, ArgsAppend: input.Body.ArgsAppend, PromptMode: input.Body.PromptMode, PromptFlag: input.Body.PromptFlag, diff --git a/internal/api/huma_types_providers.go b/internal/api/huma_types_providers.go index 49d8028b26..6b62b235b7 100644 --- a/internal/api/huma_types_providers.go +++ b/internal/api/huma_types_providers.go @@ -61,7 +61,9 @@ type ProviderCreateInput struct { DisplayName string `json:"display_name,omitempty" doc:"Human-readable display name."` Base *string `json:"base,omitempty" doc:"Optional provider base for inheritance."` Command string `json:"command,omitempty" doc:"Provider command binary. Omit for base-only descendants."` + ACPCommand string `json:"acp_command,omitempty" doc:"ACP transport command binary override."` Args []string `json:"args,omitempty" doc:"Command arguments."` + ACPArgs []string `json:"acp_args,omitempty" doc:"ACP transport command arguments override."` ArgsAppend []string `json:"args_append,omitempty" doc:"Arguments appended after inherited/base args."` PromptMode string `json:"prompt_mode,omitempty" doc:"Prompt delivery mode."` PromptFlag string `json:"prompt_flag,omitempty" doc:"Flag for prompt delivery."` @@ -79,7 +81,9 @@ type ProviderUpdateInput struct { DisplayName *string `json:"display_name,omitempty" doc:"Human-readable display name."` Base *string `json:"base,omitempty" doc:"Provider base for inheritance."` Command *string `json:"command,omitempty" doc:"Provider command binary."` + ACPCommand *string `json:"acp_command,omitempty" doc:"ACP transport command binary override."` Args []string `json:"args,omitempty" doc:"Command arguments."` + ACPArgs []string `json:"acp_args,omitempty" doc:"ACP transport command arguments override."` ArgsAppend []string `json:"args_append,omitempty" doc:"Arguments appended after inherited/base args."` PromptMode *string `json:"prompt_mode,omitempty" doc:"Prompt delivery mode."` PromptFlag *string `json:"prompt_flag,omitempty" doc:"Flag for prompt delivery."` diff --git a/internal/api/openapi.json b/internal/api/openapi.json index 771b40da87..1c603f51a9 100644 --- a/internal/api/openapi.json +++ b/internal/api/openapi.json @@ -674,6 +674,18 @@ "AnnotatedProviderResponse": { "additionalProperties": false, "properties": { + "acp_args": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "type": "string" + }, "args": { "items": { "type": "string" @@ -4473,6 +4485,20 @@ "ProviderCreateInputBody": { "additionalProperties": false, "properties": { + "acp_args": { + "description": "ACP transport command arguments override.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "description": "ACP transport command binary override.", + "type": "string" + }, "args": { "description": "Command arguments.", "items": { @@ -4856,6 +4882,18 @@ "ProviderResponse": { "additionalProperties": false, "properties": { + "acp_args": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "type": "string" + }, "args": { "items": { "type": "string" @@ -4907,6 +4945,18 @@ "ProviderSpecJSON": { "additionalProperties": false, "properties": { + "acp_args": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "type": "string" + }, "args": { "items": { "type": "string" @@ -4944,6 +4994,20 @@ "ProviderUpdateInputBody": { "additionalProperties": false, "properties": { + "acp_args": { + "description": "ACP transport command arguments override.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "description": "ACP transport command binary override.", + "type": "string" + }, "args": { "description": "Command arguments.", "items": { diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index a82aaef23b..80c700af22 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -235,11 +235,7 @@ func (s *Server) buildSessionResume(info session.Info) (string, runtime.Config, if command, err := s.resolvedSessionRuntimeCommand(resolved, transport, info.Command, metadata); err == nil { resolvedInfo.Command = command } else { - resolvedCommand := resolved.CommandString() - if transport == "acp" { - resolvedCommand = resolved.ACPCommandString() - } - resolvedInfo.Command = firstNonEmptyString(info.Command, resolvedCommand, resolved.Name) + resolvedInfo.Command = fallbackSessionRuntimeCommand(resolved, transport, info.Command) } resolvedInfo.Provider = resolved.Name resolvedInfo.Transport = transport @@ -263,12 +259,23 @@ func (s *Server) resolvedSessionRuntimeCommand(resolved *config.ResolvedProvider resolvedCommand = resolved.ACPCommandString() } desiredCommand := firstNonEmptyString(launchCommand.Command, resolvedCommand, resolved.Name) - if command := strings.TrimSpace(storedCommand); shouldPreserveStoredRuntimeCommand(command, desiredCommand) { + if command := strings.TrimSpace(storedCommand); shouldPreserveStoredRuntimeCommandForTransport(command, desiredCommand, transport, optionOverrides) { return command, nil } return desiredCommand, nil } +func fallbackSessionRuntimeCommand(resolved *config.ResolvedProvider, transport, storedCommand string) string { + resolvedCommand := "" + if resolved != nil { + resolvedCommand = resolved.CommandString() + if transport == "acp" { + resolvedCommand = resolved.ACPCommandString() + } + } + return firstNonEmptyString(storedCommand, resolvedCommand, resolved.Name) +} + func shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand string) bool { storedCommand = strings.TrimSpace(storedCommand) if storedCommand == "" { @@ -290,6 +297,25 @@ func shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand string) b return strings.HasPrefix(storedCommand, resolvedCommand+" ") } +func shouldPreserveStoredRuntimeCommandForTransport(storedCommand, resolvedCommand, transport string, optionOverrides map[string]string) bool { + if shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand) { + return true + } + if transport == "acp" || len(optionOverrides) != 0 { + return false + } + return sameRuntimeCommandExecutable(storedCommand, resolvedCommand) +} + +func sameRuntimeCommandExecutable(storedCommand, resolvedCommand string) bool { + storedFields := strings.Fields(strings.TrimSpace(storedCommand)) + resolvedFields := strings.Fields(strings.TrimSpace(resolvedCommand)) + if len(storedFields) == 0 || len(resolvedFields) == 0 { + return false + } + return storedFields[0] == resolvedFields[0] +} + func (s *Server) resolveWorkerSessionRuntime(info session.Info, sessionKind string) (*worker.ResolvedRuntime, error) { return s.resolveWorkerSessionRuntimeWithMetadata(info, sessionKind, nil) } @@ -305,7 +331,7 @@ func (s *Server) resolveWorkerSessionRuntimeWithMetadata(info session.Info, _ st } command, err := s.resolvedSessionRuntimeCommand(resolved, transport, info.Command, metadata) if err != nil { - return nil, err + command = fallbackSessionRuntimeCommand(resolved, transport, info.Command) } runtimeCfg, err := worker.NormalizeResolvedRuntime(worker.ResolvedRuntime{ Command: command, diff --git a/internal/api/state.go b/internal/api/state.go index e3d6c647a2..9646ac2ce2 100644 --- a/internal/api/state.go +++ b/internal/api/state.go @@ -121,7 +121,9 @@ type ProviderUpdate struct { DisplayName *string Base **string Command *string + ACPCommand *string Args []string // nil = not set, non-nil = replace + ACPArgs []string // nil = not set, non-nil = replace ArgsAppend []string // nil = not set, non-nil = replace PromptMode *string PromptFlag *string diff --git a/internal/api/worker_factory_test.go b/internal/api/worker_factory_test.go index f5cec4ec40..8ef4c78aa8 100644 --- a/internal/api/worker_factory_test.go +++ b/internal/api/worker_factory_test.go @@ -319,6 +319,35 @@ command = [broken } } +func TestResolveWorkerSessionRuntimeFallsBackToStoredCommandWhenTemplateOverridesInvalid(t *testing.T) { + fs := newSessionFakeState(t) + fs.cfg.Providers["test-agent"] = config.ProviderSpec{ + Command: "/bin/echo", + PathCheck: "true", + } + + srv := New(fs) + info := session.Info{ + ID: "sess-1", + Template: "myrig/worker", + Command: "/bin/echo --stored", + WorkDir: t.TempDir(), + } + + runtimeCfg, err := srv.resolveWorkerSessionRuntimeWithMetadata(info, "", map[string]string{ + "template_overrides": `{`, + }) + if err != nil { + t.Fatalf("resolveWorkerSessionRuntimeWithMetadata: %v", err) + } + if runtimeCfg == nil { + t.Fatal("resolveWorkerSessionRuntimeWithMetadata() = nil") + } + if got, want := runtimeCfg.Command, "/bin/echo --stored"; got != want { + t.Fatalf("Command = %q, want %q", got, want) + } +} + func TestWorkerFactorySessionByIDUsesResolvedTemplateRuntime(t *testing.T) { fs := newSessionFakeState(t) fs.cfg.Agents[0].Provider = "resolved-worker" diff --git a/internal/configedit/configedit.go b/internal/configedit/configedit.go index 33bf295af1..57f9bad728 100644 --- a/internal/configedit/configedit.go +++ b/internal/configedit/configedit.go @@ -711,7 +711,9 @@ type ProviderUpdate struct { DisplayName *string Base **string Command *string + ACPCommand *string Args []string // nil = not set, non-nil = replace + ACPArgs []string // nil = not set, non-nil = replace ArgsAppend []string // nil = not set, non-nil = replace PromptMode *string PromptFlag *string @@ -760,10 +762,17 @@ func (e *Editor) UpdateProvider(name string, patch ProviderUpdate) error { if patch.Command != nil { spec.Command = *patch.Command } + if patch.ACPCommand != nil { + spec.ACPCommand = *patch.ACPCommand + } if patch.Args != nil { spec.Args = make([]string, len(patch.Args)) copy(spec.Args, patch.Args) } + if patch.ACPArgs != nil { + spec.ACPArgs = make([]string, len(patch.ACPArgs)) + copy(spec.ACPArgs, patch.ACPArgs) + } if patch.ArgsAppend != nil { spec.ArgsAppend = make([]string, len(patch.ArgsAppend)) copy(spec.ArgsAppend, patch.ArgsAppend) diff --git a/internal/configedit/configedit_test.go b/internal/configedit/configedit_test.go index afcec12acb..cd91011859 100644 --- a/internal/configedit/configedit_test.go +++ b/internal/configedit/configedit_test.go @@ -1198,9 +1198,12 @@ func TestUpdateProvider(t *testing.T) { ed := configedit.NewEditor(fsys.OSFS{}, path) newCmd := "updated-cli" + newACPCmd := "updated-cli-acp" newName := "Updated Agent" err := ed.UpdateProvider("custom", configedit.ProviderUpdate{ Command: &newCmd, + ACPCommand: &newACPCmd, + ACPArgs: []string{"rpc", "--stdio"}, DisplayName: &newName, }) if err != nil { @@ -1212,6 +1215,12 @@ func TestUpdateProvider(t *testing.T) { if got.Command != "updated-cli" { t.Errorf("command = %q, want %q", got.Command, "updated-cli") } + if got.ACPCommand != "updated-cli-acp" { + t.Errorf("acp_command = %q, want %q", got.ACPCommand, "updated-cli-acp") + } + if len(got.ACPArgs) != 2 || got.ACPArgs[0] != "rpc" || got.ACPArgs[1] != "--stdio" { + t.Errorf("acp_args = %#v, want [rpc --stdio]", got.ACPArgs) + } if got.DisplayName != "Updated Agent" { t.Errorf("display_name = %q, want %q", got.DisplayName, "Updated Agent") } From ce4031db75d1d9a3bf24e31ff84f64ba346275f1 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 05:16:27 +0000 Subject: [PATCH 70/85] fix: persist named session mcp metadata --- .../dashboard/web/src/generated/schema.d.ts | 6 +-- .../dashboard/web/src/generated/types.gen.ts | 6 +-- docs/schema/openapi.json | 15 ++---- docs/schema/openapi.txt | 15 ++---- internal/api/handler_config.go | 2 +- internal/api/handler_config_test.go | 42 ++++++++++++++- internal/api/handler_provider_crud_test.go | 32 ++++++++++- internal/api/handler_providers.go | 13 ++++- internal/api/handler_sessions_test.go | 53 +++++++++++++++++++ internal/api/huma_handlers_config.go | 8 +-- internal/api/openapi.json | 15 ++---- internal/api/session_resolution.go | 4 ++ 12 files changed, 160 insertions(+), 51 deletions(-) diff --git a/cmd/gc/dashboard/web/src/generated/schema.d.ts b/cmd/gc/dashboard/web/src/generated/schema.d.ts index be0bee478e..df28159378 100644 --- a/cmd/gc/dashboard/web/src/generated/schema.d.ts +++ b/cmd/gc/dashboard/web/src/generated/schema.d.ts @@ -2050,7 +2050,7 @@ export interface components { suspended: boolean; }; AnnotatedProviderResponse: { - acp_args?: string[] | null; + acp_args?: string[]; acp_command?: string; args?: string[] | null; command?: string; @@ -3333,7 +3333,7 @@ export interface components { }; }; ProviderResponse: { - acp_args?: string[] | null; + acp_args?: string[]; acp_command?: string; args?: string[] | null; builtin: boolean; @@ -3350,7 +3350,7 @@ export interface components { ready_delay_ms?: number; }; ProviderSpecJSON: { - acp_args?: string[] | null; + acp_args?: string[]; acp_command?: string; args?: string[] | null; command?: string; diff --git a/cmd/gc/dashboard/web/src/generated/types.gen.ts b/cmd/gc/dashboard/web/src/generated/types.gen.ts index caaf7a7e7b..3bb6a5eca4 100644 --- a/cmd/gc/dashboard/web/src/generated/types.gen.ts +++ b/cmd/gc/dashboard/web/src/generated/types.gen.ts @@ -200,7 +200,7 @@ export type AnnotatedAgentResponse = { }; export type AnnotatedProviderResponse = { - acp_args?: Array | null; + acp_args?: Array; acp_command?: string; args?: Array | null; command?: string; @@ -1913,7 +1913,7 @@ export type ProviderReadinessResponse = { }; export type ProviderResponse = { - acp_args?: Array | null; + acp_args?: Array; acp_command?: string; args?: Array | null; builtin: boolean; @@ -1930,7 +1930,7 @@ export type ProviderResponse = { }; export type ProviderSpecJson = { - acp_args?: Array | null; + acp_args?: Array; acp_command?: string; args?: Array | null; command?: string; diff --git a/docs/schema/openapi.json b/docs/schema/openapi.json index 1c603f51a9..af09cc51fe 100644 --- a/docs/schema/openapi.json +++ b/docs/schema/openapi.json @@ -678,10 +678,7 @@ "items": { "type": "string" }, - "type": [ - "array", - "null" - ] + "type": "array" }, "acp_command": { "type": "string" @@ -4886,10 +4883,7 @@ "items": { "type": "string" }, - "type": [ - "array", - "null" - ] + "type": "array" }, "acp_command": { "type": "string" @@ -4949,10 +4943,7 @@ "items": { "type": "string" }, - "type": [ - "array", - "null" - ] + "type": "array" }, "acp_command": { "type": "string" diff --git a/docs/schema/openapi.txt b/docs/schema/openapi.txt index 1c603f51a9..af09cc51fe 100644 --- a/docs/schema/openapi.txt +++ b/docs/schema/openapi.txt @@ -678,10 +678,7 @@ "items": { "type": "string" }, - "type": [ - "array", - "null" - ] + "type": "array" }, "acp_command": { "type": "string" @@ -4886,10 +4883,7 @@ "items": { "type": "string" }, - "type": [ - "array", - "null" - ] + "type": "array" }, "acp_command": { "type": "string" @@ -4949,10 +4943,7 @@ "items": { "type": "string" }, - "type": [ - "array", - "null" - ] + "type": "array" }, "acp_command": { "type": "string" diff --git a/internal/api/handler_config.go b/internal/api/handler_config.go index 9e078a98dd..21d6fb4787 100644 --- a/internal/api/handler_config.go +++ b/internal/api/handler_config.go @@ -47,7 +47,7 @@ type providerSpecJSON struct { Command string `json:"command,omitempty"` ACPCommand string `json:"acp_command,omitempty"` Args []string `json:"args,omitempty"` - ACPArgs []string `json:"acp_args,omitempty"` + ACPArgs *[]string `json:"acp_args,omitempty"` PromptMode string `json:"prompt_mode,omitempty"` PromptFlag string `json:"prompt_flag,omitempty"` ReadyDelayMs int `json:"ready_delay_ms,omitempty"` diff --git a/internal/api/handler_config_test.go b/internal/api/handler_config_test.go index dab90c88e1..060d9a7d77 100644 --- a/internal/api/handler_config_test.go +++ b/internal/api/handler_config_test.go @@ -60,7 +60,7 @@ func TestHandleConfigGet(t *testing.T) { if resp.Providers["custom"].ACPCommand != "custom-cli-acp" { t.Errorf("providers.custom.acp_command = %q, want %q", resp.Providers["custom"].ACPCommand, "custom-cli-acp") } - if len(resp.Providers["custom"].ACPArgs) != 2 || resp.Providers["custom"].ACPArgs[0] != "rpc" || resp.Providers["custom"].ACPArgs[1] != "--stdio" { + if resp.Providers["custom"].ACPArgs == nil || len(*resp.Providers["custom"].ACPArgs) != 2 || (*resp.Providers["custom"].ACPArgs)[0] != "rpc" || (*resp.Providers["custom"].ACPArgs)[1] != "--stdio" { t.Errorf("providers.custom.acp_args = %#v, want [rpc --stdio]", resp.Providers["custom"].ACPArgs) } } @@ -97,6 +97,46 @@ func TestHandleConfigGet_UsesEffectiveWorkspaceIdentity(t *testing.T) { } } +func TestHandleConfigGetPreservesExplicitEmptyACPArgs(t *testing.T) { + fs := newFakeState(t) + fs.cfg.Providers = map[string]config.ProviderSpec{ + "custom": { + Command: "custom-cli", + ACPCommand: "custom-cli-acp", + ACPArgs: []string{}, + }, + } + h := newTestCityHandler(t, fs) + + req := httptest.NewRequest("GET", cityURL(fs, "/config"), nil) + w := httptest.NewRecorder() + h.ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("status = %d, want %d; body = %s", w.Code, http.StatusOK, w.Body.String()) + } + + var resp map[string]any + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatalf("decode response: %v", err) + } + providers, ok := resp["providers"].(map[string]any) + if !ok { + t.Fatal("expected providers map") + } + custom, ok := providers["custom"].(map[string]any) + if !ok { + t.Fatal("expected custom provider") + } + acpArgs, ok := custom["acp_args"].([]any) + if !ok { + t.Fatalf("acp_args = %#v, want empty array field", custom["acp_args"]) + } + if len(acpArgs) != 0 { + t.Fatalf("acp_args len = %d, want 0", len(acpArgs)) + } +} + func TestHandleConfigGet_DerivesPrefixFromRuntimeAliasWhenNoExplicitPrefix(t *testing.T) { fs := newFakeState(t) fs.cityName = "machine-alias" diff --git a/internal/api/handler_provider_crud_test.go b/internal/api/handler_provider_crud_test.go index 64ffc706d6..ace9d75aec 100644 --- a/internal/api/handler_provider_crud_test.go +++ b/internal/api/handler_provider_crud_test.go @@ -137,7 +137,37 @@ func TestHandleProviderGet_IncludesACPTransportOverrides(t *testing.T) { if resp.ACPCommand != "custom-acp" { t.Fatalf("ACPCommand = %q, want %q", resp.ACPCommand, "custom-acp") } - if len(resp.ACPArgs) != 2 || resp.ACPArgs[0] != "rpc" || resp.ACPArgs[1] != "--stdio" { + if resp.ACPArgs == nil || len(*resp.ACPArgs) != 2 || (*resp.ACPArgs)[0] != "rpc" || (*resp.ACPArgs)[1] != "--stdio" { t.Fatalf("ACPArgs = %#v, want [rpc --stdio]", resp.ACPArgs) } } + +func TestHandleProviderGetPreservesExplicitEmptyACPArgs(t *testing.T) { + fs := newFakeState(t) + fs.cfg.Providers["custom"] = config.ProviderSpec{ + Command: "custom", + ACPCommand: "custom-acp", + ACPArgs: []string{}, + } + h := newTestCityHandler(t, fs) + + req := httptest.NewRequest(http.MethodGet, cityURL(fs, "/provider/custom"), nil) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String()) + } + + var resp map[string]any + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode response: %v", err) + } + acpArgs, ok := resp["acp_args"].([]any) + if !ok { + t.Fatalf("acp_args = %#v, want empty array field", resp["acp_args"]) + } + if len(acpArgs) != 0 { + t.Fatalf("acp_args len = %d, want 0", len(acpArgs)) + } +} diff --git a/internal/api/handler_providers.go b/internal/api/handler_providers.go index 3f41236d35..1891ada8fb 100644 --- a/internal/api/handler_providers.go +++ b/internal/api/handler_providers.go @@ -15,7 +15,7 @@ type providerResponse struct { Command string `json:"command,omitempty"` ACPCommand string `json:"acp_command,omitempty"` Args []string `json:"args,omitempty"` - ACPArgs []string `json:"acp_args,omitempty"` + ACPArgs *[]string `json:"acp_args,omitempty"` PromptMode string `json:"prompt_mode,omitempty"` PromptFlag string `json:"prompt_flag,omitempty"` ReadyDelayMs int `json:"ready_delay_ms,omitempty"` @@ -44,7 +44,7 @@ func providerFromSpec(name string, spec config.ProviderSpec, builtin, cityLevel Command: spec.Command, ACPCommand: spec.ACPCommand, Args: spec.Args, - ACPArgs: spec.ACPArgs, + ACPArgs: optionalStringSlice(spec.ACPArgs), PromptMode: spec.PromptMode, PromptFlag: spec.PromptFlag, ReadyDelayMs: spec.ReadyDelayMs, @@ -54,6 +54,15 @@ func providerFromSpec(name string, spec config.ProviderSpec, builtin, cityLevel } } +func optionalStringSlice(values []string) *[]string { + if values == nil { + return nil + } + cloned := make([]string, len(values)) + copy(cloned, values) + return &cloned +} + // toProviderPublicResponse builds the browser-safe DTO from a MERGED // provider spec. The spec must already be the result of // MergeProviderOverBuiltin so it carries the correct OptionsSchema and diff --git a/internal/api/handler_sessions_test.go b/internal/api/handler_sessions_test.go index f22820ce9d..2d759486f6 100644 --- a/internal/api/handler_sessions_test.go +++ b/internal/api/handler_sessions_test.go @@ -1728,6 +1728,59 @@ func TestMaterializeNamedSessionRejectsACPTemplateWithoutACPRouting(t *testing.T } } +func TestMaterializeNamedSessionPersistsStoredMCPMetadata(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg.Agents[0].Provider = "opencode" + fs.cfg.Agents[0].Session = "acp" + fs.cfg.Providers["opencode"] = config.ProviderSpec{ + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + fs.cfg.PackMCPDir = filepath.Join(fs.cityPath, "mcp") + if err := os.MkdirAll(fs.cfg.PackMCPDir, 0o755); err != nil { + t.Fatalf("MkdirAll(mcp): %v", err) + } + if err := os.WriteFile(filepath.Join(fs.cfg.PackMCPDir, "identity.template.toml"), []byte(` +name = "identity" +command = "/bin/mcp" +args = ["{{.AgentName}}", "{{.WorkDir}}", "{{.TemplateName}}"] +`), 0o644); err != nil { + t.Fatalf("WriteFile(mcp): %v", err) + } + state := &stateWithSessionProvider{ + fakeState: fs, + provider: &transportCapableProvider{Fake: runtime.NewFake()}, + } + srv := New(state) + + spec, ok, err := srv.findNamedSessionSpecForTarget(fs.cityBeadStore, "worker") + if err != nil { + t.Fatalf("findNamedSessionSpecForTarget: %v", err) + } + if !ok { + t.Fatal("expected named session spec") + } + id, err := srv.materializeNamedSession(fs.cityBeadStore, spec) + if err != nil { + t.Fatalf("materializeNamedSession: %v", err) + } + bead, err := fs.cityBeadStore.Get(id) + if err != nil { + t.Fatalf("Get(%s): %v", id, err) + } + if got, want := bead.Metadata[session.MCPIdentityMetadataKey], spec.Identity; got != want { + t.Fatalf("mcp_identity = %q, want %q", got, want) + } + if got := bead.Metadata[session.MCPServersSnapshotMetadataKey]; got == "" { + t.Fatal("mcp_servers_snapshot = empty, want persisted snapshot") + } +} + func TestHandleProviderSessionCreateWithMessageUsesProviderDefaultNudge(t *testing.T) { fs := newSessionFakeState(t) srv := New(fs) diff --git a/internal/api/huma_handlers_config.go b/internal/api/huma_handlers_config.go index 6aeb27fe00..19e951a0e3 100644 --- a/internal/api/huma_handlers_config.go +++ b/internal/api/huma_handlers_config.go @@ -46,7 +46,7 @@ func (s *Server) humaHandleConfigGet(_ context.Context, _ *ConfigGetInput) (*Ind Command: spec.Command, ACPCommand: spec.ACPCommand, Args: spec.Args, - ACPArgs: spec.ACPArgs, + ACPArgs: optionalStringSlice(spec.ACPArgs), PromptMode: spec.PromptMode, PromptFlag: spec.PromptFlag, ReadyDelayMs: spec.ReadyDelayMs, @@ -133,7 +133,7 @@ func (s *Server) humaHandleConfigExplain(_ context.Context, _ *ConfigExplainInpu Command: spec.Command, ACPCommand: spec.ACPCommand, Args: spec.Args, - ACPArgs: spec.ACPArgs, + ACPArgs: optionalStringSlice(spec.ACPArgs), PromptMode: spec.PromptMode, PromptFlag: spec.PromptFlag, ReadyDelayMs: spec.ReadyDelayMs, @@ -149,7 +149,7 @@ func (s *Server) humaHandleConfigExplain(_ context.Context, _ *ConfigExplainInpu Command: spec.Command, ACPCommand: spec.ACPCommand, Args: spec.Args, - ACPArgs: spec.ACPArgs, + ACPArgs: optionalStringSlice(spec.ACPArgs), PromptMode: spec.PromptMode, PromptFlag: spec.PromptFlag, ReadyDelayMs: spec.ReadyDelayMs, @@ -228,7 +228,7 @@ type annotatedProviderResponse struct { Command string `json:"command,omitempty"` ACPCommand string `json:"acp_command,omitempty"` Args []string `json:"args,omitempty"` - ACPArgs []string `json:"acp_args,omitempty"` + ACPArgs *[]string `json:"acp_args,omitempty"` PromptMode string `json:"prompt_mode,omitempty"` PromptFlag string `json:"prompt_flag,omitempty"` ReadyDelayMs int `json:"ready_delay_ms,omitempty"` diff --git a/internal/api/openapi.json b/internal/api/openapi.json index 1c603f51a9..af09cc51fe 100644 --- a/internal/api/openapi.json +++ b/internal/api/openapi.json @@ -678,10 +678,7 @@ "items": { "type": "string" }, - "type": [ - "array", - "null" - ] + "type": "array" }, "acp_command": { "type": "string" @@ -4886,10 +4883,7 @@ "items": { "type": "string" }, - "type": [ - "array", - "null" - ] + "type": "array" }, "acp_command": { "type": "string" @@ -4949,10 +4943,7 @@ "items": { "type": "string" }, - "type": [ - "array", - "null" - ] + "type": "array" }, "acp_command": { "type": "string" diff --git a/internal/api/session_resolution.go b/internal/api/session_resolution.go index a77b4b9f2a..02d30c25cd 100644 --- a/internal/api/session_resolution.go +++ b/internal/api/session_resolution.go @@ -316,6 +316,10 @@ func (s *Server) materializeNamedSessionWithContext(ctx context.Context, store b if err != nil { return "", err } + extraMeta, err = session.WithStoredMCPMetadata(extraMeta, spec.Identity, mcpServers) + if err != nil { + return "", err + } hints := sessionCreateHints(resolved, mcpServers) var info session.Info err = session.WithCitySessionIdentifierLocks(s.state.CityPath(), []string{spec.Identity, spec.SessionName}, func() error { From 419a0ecb9b8cb3978d0a48b258808975b4812e9e Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 05:31:28 +0000 Subject: [PATCH 71/85] fix: avoid legacy transport reinterpretation --- .../web/src/generated/client/utils.gen.ts | 4 +- cmd/gc/providers.go | 24 ++++++++- cmd/gc/providers_test.go | 3 ++ cmd/gc/worker_handle.go | 41 ++++++++++++++-- cmd/gc/worker_handle_test.go | 4 +- internal/api/handler_session_chat_test.go | 20 ++++---- internal/api/session_runtime.go | 49 ++++++++++++++++--- internal/api/session_transport_test.go | 6 +-- internal/session/manager.go | 9 ---- internal/session/manager_test.go | 6 +-- 10 files changed, 126 insertions(+), 40 deletions(-) diff --git a/cmd/gc/dashboard/web/src/generated/client/utils.gen.ts b/cmd/gc/dashboard/web/src/generated/client/utils.gen.ts index 5162192d8a..1f71eaf8ad 100644 --- a/cmd/gc/dashboard/web/src/generated/client/utils.gen.ts +++ b/cmd/gc/dashboard/web/src/generated/client/utils.gen.ts @@ -75,7 +75,7 @@ export const getParseAs = (contentType: string | null): Exclude Date: Wed, 22 Apr 2026 05:43:15 +0000 Subject: [PATCH 72/85] fix: restore legacy acp fallback boundaries --- cmd/gc/providers.go | 8 ++++ cmd/gc/providers_test.go | 3 -- cmd/gc/worker_handle.go | 18 ++++---- internal/api/handler_session_chat_test.go | 50 ++++++++++++++++++++--- internal/api/session_runtime.go | 25 ++++++++---- 5 files changed, 77 insertions(+), 27 deletions(-) diff --git a/cmd/gc/providers.go b/cmd/gc/providers.go index 4e2b516d5c..e6a0a48240 100644 --- a/cmd/gc/providers.go +++ b/cmd/gc/providers.go @@ -353,8 +353,13 @@ func beadUsesACPTransport(bead beads.Bead, cfg *config.City) bool { return true } templateName := strings.TrimSpace(bead.Metadata["template"]) + providerScoped := true if cfg != nil { if agentCfg, ok := resolveAgentIdentity(cfg, templateName, currentRigContext(cfg)); ok { + providerScoped = false + if strings.TrimSpace(agentCfg.Session) != "" && agentSessionCreateTransport(cfg, agentCfg) == "acp" { + return true + } if strings.TrimSpace(bead.Metadata["command"]) == "" && strings.TrimSpace(bead.Metadata["pending_create_claim"]) == "true" && agentSessionCreateTransport(cfg, agentCfg) == "acp" { @@ -377,6 +382,9 @@ func beadUsesACPTransport(bead beads.Bead, cfg *config.City) bool { return true } } + if providerScoped { + return providerLegacyDefaultsToACP(cfg, providerName) + } if strings.TrimSpace(bead.Metadata["command"]) == "" && strings.TrimSpace(bead.Metadata["pending_create_claim"]) == "true" { return providerLegacyDefaultsToACP(cfg, providerName) diff --git a/cmd/gc/providers_test.go b/cmd/gc/providers_test.go index 8278de5550..6f8323f297 100644 --- a/cmd/gc/providers_test.go +++ b/cmd/gc/providers_test.go @@ -349,7 +349,6 @@ func TestConfiguredACPRouteNames_IncludeLegacyObservedACPProviderSessionsWithout Metadata: map[string]string{ "template": "opencode", "provider": "opencode", - "command": "/bin/echo acp", "session_name": "provider-session", }, }}) @@ -379,7 +378,6 @@ func TestConfiguredACPRouteNames_IncludeLegacyObservedCustomACPProviderSessionsW Metadata: map[string]string{ "template": "custom-acp", "provider": "custom-acp", - "command": "/bin/echo acp", "session_name": "provider-session", }, }}) @@ -624,7 +622,6 @@ func TestNewSessionProviderRoutesLegacyObservedACPProviderSessionsWithoutTranspo Metadata: map[string]string{ "template": "opencode", "provider": "opencode", - "command": "/bin/echo acp", "session_name": "provider-session", }, }); err != nil { diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index 7ded170076..315277e7c0 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -453,11 +453,11 @@ func resolvedWorkerRuntimeWithConfigAndMetadata(cityPath string, cfg *config.Cit if cfg == nil { return nil, nil } - resolved, configuredTransport := resolveWorkerRuntimeProviderWithConfig(cfg, info, sessionKind) + resolved, configuredTransport, allowConfiguredTransportFallback := resolveWorkerRuntimeProviderWithConfig(cfg, info, sessionKind) if resolved == nil { return nil, nil } - transport := resolvedWorkerRuntimeTransport(info, resolved, configuredTransport, metadata) + transport := resolvedWorkerRuntimeTransport(info, resolved, configuredTransport, metadata, allowConfiguredTransportFallback) command := strings.TrimSpace(info.Command) desiredCommand := fallbackResolvedWorkerRuntimeCommand(resolved, transport, command) @@ -577,7 +577,7 @@ func storedWorkerSessionProvesACPTransport(resolved *config.ResolvedProvider, st return shouldPreserveStoredRuntimeCommand(storedCommand, acpCommand) } -func resolvedWorkerRuntimeTransport(info session.Info, resolved *config.ResolvedProvider, configuredTransport string, metadata map[string]string) string { +func resolvedWorkerRuntimeTransport(info session.Info, resolved *config.ResolvedProvider, configuredTransport string, metadata map[string]string, allowConfiguredTransportFallback bool) string { if transport := strings.TrimSpace(info.Transport); transport != "" { return transport } @@ -587,7 +587,7 @@ func resolvedWorkerRuntimeTransport(info session.Info, resolved *config.Resolved if storedWorkerSessionProvesACPTransport(resolved, info.Command, metadata) { return "acp" } - if strings.TrimSpace(info.Command) == "" { + if allowConfiguredTransportFallback || strings.TrimSpace(info.Command) == "" { return strings.TrimSpace(configuredTransport) } return "" @@ -602,22 +602,22 @@ func firstNonEmptyWorkerString(values ...string) string { return "" } -func resolveWorkerRuntimeProviderWithConfig(cfg *config.City, info session.Info, sessionKind string) (*config.ResolvedProvider, string) { +func resolveWorkerRuntimeProviderWithConfig(cfg *config.City, info session.Info, sessionKind string) (*config.ResolvedProvider, string, bool) { if cfg == nil { - return nil, "" + return nil, "", false } if sessionKind != "provider" { if found, ok := resolveAgentIdentity(cfg, info.Template, ""); ok { if resolved, err := config.ResolveProvider(&found, &cfg.Workspace, cfg.Providers, exec.LookPath); err == nil { - return resolved, config.ResolveSessionCreateTransport(found.Session, resolved) + return resolved, config.ResolveSessionCreateTransport(found.Session, resolved), strings.TrimSpace(found.Session) != "" } } } resolved, err := config.ResolveProvider(&config.Agent{Provider: info.Template}, &cfg.Workspace, cfg.Providers, exec.LookPath) if err != nil { - return nil, "" + return nil, "", false } - return resolved, strings.TrimSpace(resolved.ProviderSessionCreateTransport()) + return resolved, strings.TrimSpace(resolved.ProviderSessionCreateTransport()), true } func workerDeliveryIntentForSubmitIntent(intent session.SubmitIntent) worker.DeliveryIntent { diff --git a/internal/api/handler_session_chat_test.go b/internal/api/handler_session_chat_test.go index 3058f0a0ea..8bd5fc8692 100644 --- a/internal/api/handler_session_chat_test.go +++ b/internal/api/handler_session_chat_test.go @@ -246,7 +246,7 @@ func TestBuildSessionResumeFallsBackToStoredCommandWhenTemplateOverridesInvalid( } } -func TestBuildSessionResumeUsesStoredACPCommandForLegacyProviderSessionWithoutTransportMetadataWithoutSessionAutoProvider(t *testing.T) { +func TestBuildSessionResumeUsesConfiguredACPCommandForLegacyProviderSessionWithoutTransportMetadataWithoutSessionAutoProvider(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) fs.cfg = &config.City{ @@ -267,7 +267,7 @@ func TestBuildSessionResumeUsesStoredACPCommandForLegacyProviderSessionWithoutTr info := session.Info{ ID: "gc-1", Template: "opencode", - Command: "/bin/echo acp", + Command: "/bin/echo", Provider: "opencode", WorkDir: "/tmp/workdir", } @@ -281,7 +281,7 @@ func TestBuildSessionResumeUsesStoredACPCommandForLegacyProviderSessionWithoutTr } } -func TestBuildSessionResumeUsesStoredACPCommandForLegacyProviderSessionWithoutTransportMetadata(t *testing.T) { +func TestBuildSessionResumeUsesConfiguredACPCommandForLegacyProviderSessionWithoutTransportMetadata(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) fs.cfg = &config.City{ @@ -306,7 +306,7 @@ func TestBuildSessionResumeUsesStoredACPCommandForLegacyProviderSessionWithoutTr info := session.Info{ ID: "gc-1", Template: "opencode", - Command: "/bin/echo acp", + Command: "/bin/echo", Provider: "opencode", WorkDir: "/tmp/workdir", } @@ -320,7 +320,7 @@ func TestBuildSessionResumeUsesStoredACPCommandForLegacyProviderSessionWithoutTr } } -func TestBuildSessionResumeUsesStoredACPCommandForLegacyProviderSessionOnACPEnabledCustomProvider(t *testing.T) { +func TestBuildSessionResumeUsesACPCommandForLegacyProviderSessionOnACPEnabledCustomProvider(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) fs.cfg = &config.City{ @@ -341,7 +341,7 @@ func TestBuildSessionResumeUsesStoredACPCommandForLegacyProviderSessionOnACPEnab info := session.Info{ ID: "gc-1", Template: "custom-acp", - Command: "/bin/echo acp", + Command: "/bin/echo", Provider: "custom-acp", WorkDir: "/tmp/workdir", } @@ -394,6 +394,44 @@ func TestBuildSessionResumeUsesStoredACPTransportForTemplateSession(t *testing.T } } +func TestBuildSessionResumeUsesConfiguredACPTransportForTemplateSessionWithoutStoredMetadata(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg = &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Agents: []config.Agent{ + {Name: "worker", Provider: "opencode", Session: "acp"}, + }, + Providers: map[string]config.ProviderSpec{ + "opencode": { + DisplayName: "OpenCode", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + } + + srv := New(fs) + info := session.Info{ + ID: "gc-1", + Template: "worker", + Command: "/bin/echo", + Provider: "opencode", + WorkDir: "/tmp/workdir", + } + + cmd, _, err := srv.buildSessionResume(info) + if err != nil { + t.Fatalf("buildSessionResume: %v", err) + } + if got, want := cmd, "/bin/echo acp"; got != want { + t.Fatalf("resume command = %q, want %q", got, want) + } +} + func TestBuildSessionResumeUsesStoredACPCommandForLegacyTemplateSessionWithoutTransportMetadata(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index c98c6bf7d1..6264e435c2 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -373,7 +373,7 @@ func storedSessionProvesACPTransport(resolved *config.ResolvedProvider, storedCo return shouldPreserveStoredRuntimeCommand(storedCommand, acpCommand) } -func resolvedSessionTransport(info session.Info, resolved *config.ResolvedProvider, configuredTransport string, metadata map[string]string) string { +func resolvedSessionTransport(info session.Info, resolved *config.ResolvedProvider, configuredTransport string, metadata map[string]string, allowConfiguredTransportFallback bool) string { if transport := strings.TrimSpace(info.Transport); transport != "" { return transport } @@ -383,7 +383,7 @@ func resolvedSessionTransport(info session.Info, resolved *config.ResolvedProvid if storedSessionProvesACPTransport(resolved, info.Command, metadata) { return "acp" } - if strings.TrimSpace(info.Command) == "" { + if allowConfiguredTransportFallback || strings.TrimSpace(info.Command) == "" { return strings.TrimSpace(configuredTransport) } return "" @@ -391,13 +391,20 @@ func resolvedSessionTransport(info session.Info, resolved *config.ResolvedProvid func (s *Server) resolveSessionRuntimeWithMetadata(info session.Info, metadata map[string]string) (*config.ResolvedProvider, string, string) { kind := s.sessionKind(info.ID) - if kind != "provider" { - resolved, workDir, transport, _, err := s.resolveSessionTemplate(info.Template) - if err == nil { - if info.WorkDir != "" { - workDir = info.WorkDir + cfg := s.state.Config() + if kind != "provider" && cfg != nil { + if agentCfg, ok := resolveSessionTemplateAgent(cfg, info.Template); ok { + resolved, err := config.ResolveProvider(&agentCfg, &cfg.Workspace, cfg.Providers, exec.LookPath) + if err == nil { + workDir, workDirErr := s.resolveSessionWorkDir(agentCfg, agentCfg.QualifiedName()) + if workDirErr == nil { + if info.WorkDir != "" { + workDir = info.WorkDir + } + transport := config.ResolveSessionCreateTransport(agentCfg.Session, resolved) + return resolved, workDir, resolvedSessionTransport(info, resolved, transport, metadata, strings.TrimSpace(agentCfg.Session) != "") + } } - return resolved, workDir, resolvedSessionTransport(info, resolved, transport, metadata) } } @@ -409,7 +416,7 @@ func (s *Server) resolveSessionRuntimeWithMetadata(info session.Info, metadata m if workDir == "" { workDir = s.state.CityPath() } - transport := resolvedSessionTransport(info, resolved, resolved.ProviderSessionCreateTransport(), metadata) + transport := resolvedSessionTransport(info, resolved, resolved.ProviderSessionCreateTransport(), metadata, true) return resolved, workDir, transport } From 41bd5604c2a947eb71a9d96d1bb787022e405fe0 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 05:51:29 +0000 Subject: [PATCH 73/85] fix: scope mcp metadata to acp sessions --- cmd/gc/worker_handle.go | 18 ++++++----- cmd/gc/worker_handle_test.go | 30 +++++++++++++++++++ internal/api/handler_session_create.go | 27 ++++++++++------- internal/api/handler_sessions_test.go | 10 +++++++ .../api/huma_handlers_sessions_command.go | 21 ++++++++----- internal/api/session_resolution.go | 8 +++-- internal/api/session_resolved_config.go | 18 ++++++----- internal/api/session_resolved_config_test.go | 30 +++++++++++++++++++ 8 files changed, 124 insertions(+), 38 deletions(-) diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index 315277e7c0..89122e41ea 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -238,14 +238,16 @@ func resolvedWorkerSessionConfigWithConfig( if resolved == nil { return worker.ResolvedSessionConfig{}, fmt.Errorf("resolved provider is required") } - var err error - metadata, err = session.WithStoredMCPMetadata( - metadata, - firstNonEmptyGCString(metadata[session.MCPIdentityMetadataKey], metadata["agent_name"]), - mcpServers, - ) - if err != nil { - return worker.ResolvedSessionConfig{}, err + if transport == "acp" { + var err error + metadata, err = session.WithStoredMCPMetadata( + metadata, + firstNonEmptyGCString(metadata[session.MCPIdentityMetadataKey], metadata["agent_name"]), + mcpServers, + ) + if err != nil { + return worker.ResolvedSessionConfig{}, err + } } command = strings.TrimSpace(command) if command == "" { diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index 46496c275e..3d36419902 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -923,6 +923,36 @@ func TestResolvedWorkerSessionConfigWithConfigPersistsStoredMCPMetadata(t *testi } } +func TestResolvedWorkerSessionConfigWithConfigSkipsStoredMCPMetadataForTmuxTransport(t *testing.T) { + cfg, err := resolvedWorkerSessionConfigWithConfig( + "", + "legacy-provider", + "/tmp/work", + "worker", + "", + "worker", + "Worker", + "", + &config.ResolvedProvider{ + Name: "custom-provider", + }, + map[string]string{ + "session_origin": "test", + "agent_name": "myrig/worker-adhoc-123", + }, + nil, + ) + if err != nil { + t.Fatalf("resolvedWorkerSessionConfigWithConfig: %v", err) + } + if got := cfg.Metadata[session.MCPIdentityMetadataKey]; got != "" { + t.Fatalf("Metadata[mcp_identity] = %q, want empty for tmux transport", got) + } + if got := cfg.Metadata[session.MCPServersSnapshotMetadataKey]; got != "" { + t.Fatalf("Metadata[mcp_servers_snapshot] = %q, want empty for tmux transport", got) + } +} + func TestResolvedWorkerRuntimeWithConfigFallsBackToCityPathAndSyncsHintsWorkDir(t *testing.T) { cityDir := t.TempDir() writePhase0InterfaceCity(t, cityDir, `[workspace] diff --git a/internal/api/handler_session_create.go b/internal/api/handler_session_create.go index 2c5755b0d4..a86976f56a 100644 --- a/internal/api/handler_session_create.go +++ b/internal/api/handler_session_create.go @@ -166,11 +166,13 @@ func (s *Server) handleSessionCreate(w http.ResponseWriter, r *http.Request) { } extraMeta["agent_name"] = createCtx.Identity extraMeta["session_origin"] = "ephemeral" - extraMeta, err = session.WithStoredMCPMetadata(extraMeta, createCtx.Identity, mcpServers) - if err != nil { - s.idem.unreserve(idemKey) - writeError(w, http.StatusInternalServerError, "internal", err.Error()) - return + if transport == "acp" { + extraMeta, err = session.WithStoredMCPMetadata(extraMeta, createCtx.Identity, mcpServers) + if err != nil { + s.idem.unreserve(idemKey) + writeError(w, http.StatusInternalServerError, "internal", err.Error()) + return + } } // Agent sessions always use async (bead-only) creation. The reconciler @@ -350,13 +352,16 @@ func (s *Server) createProviderSession(w http.ResponseWriter, r *http.Request, s writeError(w, http.StatusInternalServerError, "internal", err.Error()) return } - extraMeta, err := session.WithStoredMCPMetadata(map[string]string{ + extraMeta := map[string]string{ "session_origin": "manual", - }, mcpIdentity, mcpServers) - if err != nil { - s.idem.unreserve(idemKey) - writeError(w, http.StatusInternalServerError, "internal", err.Error()) - return + } + if transport == "acp" { + extraMeta, err = session.WithStoredMCPMetadata(extraMeta, mcpIdentity, mcpServers) + if err != nil { + s.idem.unreserve(idemKey) + writeError(w, http.StatusInternalServerError, "internal", err.Error()) + return + } } resolvedCfg, err := resolvedSessionConfigForProvider(alias, "", template, title, transport, extraMeta, resolved, command, workDir, mcpServers) diff --git a/internal/api/handler_sessions_test.go b/internal/api/handler_sessions_test.go index 2d759486f6..1eb6ba7503 100644 --- a/internal/api/handler_sessions_test.go +++ b/internal/api/handler_sessions_test.go @@ -1134,6 +1134,16 @@ func TestHandleSessionCreate(t *testing.T) { if resp.Title != "myrig/worker" { t.Errorf("Title = %q, want default %q", resp.Title, "myrig/worker") } + bead, err := fs.cityBeadStore.Get(resp.ID) + if err != nil { + t.Fatalf("Get(%s): %v", resp.ID, err) + } + if got := bead.Metadata[session.MCPIdentityMetadataKey]; got != "" { + t.Fatalf("mcp_identity = %q, want empty for non-ACP agent session", got) + } + if got := bead.Metadata[session.MCPServersSnapshotMetadataKey]; got != "" { + t.Fatalf("mcp_servers_snapshot = %q, want empty for non-ACP agent session", got) + } // Agent sessions are always created async — not running until the // reconciler starts the process. if resp.Running { diff --git a/internal/api/huma_handlers_sessions_command.go b/internal/api/huma_handlers_sessions_command.go index 6151dedaeb..fb2ba5ec41 100644 --- a/internal/api/huma_handlers_sessions_command.go +++ b/internal/api/huma_handlers_sessions_command.go @@ -130,10 +130,12 @@ func (s *Server) humaHandleSessionCreate(ctx context.Context, input *SessionCrea } extraMeta["agent_name"] = workDirQualifiedName extraMeta["session_origin"] = "manual" - var mcpMetaErr error - extraMeta, mcpMetaErr = session.WithStoredMCPMetadata(extraMeta, workDirQualifiedName, mcpServers) - if mcpMetaErr != nil { - return mcpMetaErr + if transport == "acp" { + var mcpMetaErr error + extraMeta, mcpMetaErr = session.WithStoredMCPMetadata(extraMeta, workDirQualifiedName, mcpServers) + if mcpMetaErr != nil { + return mcpMetaErr + } } resolvedCfg, cfgErr := resolvedSessionConfigForProvider( alias, @@ -262,11 +264,14 @@ func (s *Server) humaCreateProviderSession(ctx context.Context, store beads.Stor if err != nil { return nil, huma.Error500InternalServerError(err.Error()) } - extraMeta, err := session.WithStoredMCPMetadata(map[string]string{ + extraMeta := map[string]string{ "session_origin": "manual", - }, mcpIdentity, mcpServers) - if err != nil { - return nil, huma.Error500InternalServerError(err.Error()) + } + if transport == "acp" { + extraMeta, err = session.WithStoredMCPMetadata(extraMeta, mcpIdentity, mcpServers) + if err != nil { + return nil, huma.Error500InternalServerError(err.Error()) + } } mgr := s.sessionManager(store) diff --git a/internal/api/session_resolution.go b/internal/api/session_resolution.go index 02d30c25cd..4540e6f639 100644 --- a/internal/api/session_resolution.go +++ b/internal/api/session_resolution.go @@ -316,9 +316,11 @@ func (s *Server) materializeNamedSessionWithContext(ctx context.Context, store b if err != nil { return "", err } - extraMeta, err = session.WithStoredMCPMetadata(extraMeta, spec.Identity, mcpServers) - if err != nil { - return "", err + if transport == "acp" { + extraMeta, err = session.WithStoredMCPMetadata(extraMeta, spec.Identity, mcpServers) + if err != nil { + return "", err + } } hints := sessionCreateHints(resolved, mcpServers) var info session.Info diff --git a/internal/api/session_resolved_config.go b/internal/api/session_resolved_config.go index 288d6539f3..8e746f111e 100644 --- a/internal/api/session_resolved_config.go +++ b/internal/api/session_resolved_config.go @@ -19,14 +19,16 @@ func resolvedSessionConfigForProvider( if resolved == nil { return worker.ResolvedSessionConfig{}, fmt.Errorf("%w: resolved provider is required", worker.ErrHandleConfig) } - var err error - metadata, err = session.WithStoredMCPMetadata( - metadata, - firstNonEmptyString(metadata[session.MCPIdentityMetadataKey], metadata["agent_name"]), - mcpServers, - ) - if err != nil { - return worker.ResolvedSessionConfig{}, err + if transport == "acp" { + var err error + metadata, err = session.WithStoredMCPMetadata( + metadata, + firstNonEmptyString(metadata[session.MCPIdentityMetadataKey], metadata["agent_name"]), + mcpServers, + ) + if err != nil { + return worker.ResolvedSessionConfig{}, err + } } // Use the ACP-specific command when the session uses ACP transport, // falling back to the default command for tmux sessions. diff --git a/internal/api/session_resolved_config_test.go b/internal/api/session_resolved_config_test.go index daa9458716..106e5d24f6 100644 --- a/internal/api/session_resolved_config_test.go +++ b/internal/api/session_resolved_config_test.go @@ -106,3 +106,33 @@ func TestResolvedSessionConfigForProviderRejectsNilProvider(t *testing.T) { t.Fatal("resolvedSessionConfigForProvider() error = nil, want error") } } + +func TestResolvedSessionConfigForProviderSkipsStoredMCPMetadataForTmuxTransport(t *testing.T) { + cfg, err := resolvedSessionConfigForProvider( + "worker", + "", + "myrig/worker", + "Worker", + "", + map[string]string{ + "session_origin": "manual", + "agent_name": "myrig/worker-adhoc-123", + }, + &config.ResolvedProvider{ + Name: "stub", + Command: "/bin/echo", + }, + "", + "/tmp/workdir", + nil, + ) + if err != nil { + t.Fatalf("resolvedSessionConfigForProvider: %v", err) + } + if got := cfg.Metadata[session.MCPIdentityMetadataKey]; got != "" { + t.Fatalf("Metadata[mcp_identity] = %q, want empty for tmux transport", got) + } + if got := cfg.Metadata[session.MCPServersSnapshotMetadataKey]; got != "" { + t.Fatalf("Metadata[mcp_servers_snapshot] = %q, want empty for tmux transport", got) + } +} From 5838810ebaa9cdbc00926f027ceee4ab598c79b2 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 06:03:46 +0000 Subject: [PATCH 74/85] fix: preserve legacy acp resume inference --- cmd/gc/session_lifecycle_parallel.go | 12 +++ cmd/gc/session_lifecycle_parallel_test.go | 58 +++++++++++++++ cmd/gc/session_manager_test.go | 12 +-- internal/api/session_manager.go | 23 +++--- internal/session/manager.go | 84 ++++++++++++++++++--- internal/session/manager_test.go | 89 +++++++++++++++++++++++ 6 files changed, 252 insertions(+), 26 deletions(-) diff --git a/cmd/gc/session_lifecycle_parallel.go b/cmd/gc/session_lifecycle_parallel.go index 89024ac4ee..ebf2be8e83 100644 --- a/cmd/gc/session_lifecycle_parallel.go +++ b/cmd/gc/session_lifecycle_parallel.go @@ -699,6 +699,18 @@ func commitStartResultTraced( if storedMCPSnapshot != "" || session.Metadata[sessionpkg.MCPServersSnapshotMetadataKey] != "" { metadata[sessionpkg.MCPServersSnapshotMetadataKey] = storedMCPSnapshot } + if result.prepared.candidate.tp.IsACP || + session.Metadata[sessionpkg.MCPIdentityMetadataKey] != "" || + session.Metadata[sessionpkg.MCPServersSnapshotMetadataKey] != "" { + storedMCPIdentity := firstNonEmptyGCString( + session.Metadata[sessionpkg.MCPIdentityMetadataKey], + session.Metadata[sessionpkg.NamedSessionIdentityMetadata], + session.Metadata["agent_name"], + ) + if storedMCPIdentity != "" || session.Metadata[sessionpkg.MCPIdentityMetadataKey] != "" { + metadata[sessionpkg.MCPIdentityMetadataKey] = storedMCPIdentity + } + } if err := store.SetMetadataBatch(session.ID, metadata); err != nil { fmt.Fprintf(stderr, "session reconciler: storing hashes for %s: %v\n", name, err) //nolint:errcheck if trace != nil { diff --git a/cmd/gc/session_lifecycle_parallel_test.go b/cmd/gc/session_lifecycle_parallel_test.go index da9e4c25b6..1c8eedeeb0 100644 --- a/cmd/gc/session_lifecycle_parallel_test.go +++ b/cmd/gc/session_lifecycle_parallel_test.go @@ -2592,3 +2592,61 @@ func TestCommitStartResult_TransitionsCreatingToActive(t *testing.T) { t.Errorf("started_config_hash = %q, want %q", got.Metadata["started_config_hash"], "core-abc") } } + +func TestCommitStartResult_PersistsMCPIdentityForACPStart(t *testing.T) { + store := beads.NewMemStore() + session, err := store.Create(beads.Bead{ + Title: "worker-session", + Type: sessionBeadType, + Labels: []string{sessionBeadLabel}, + Metadata: map[string]string{ + "template": "worker", + "agent_name": "myrig/worker-adhoc-123", + "session_name": "worker-1", + "state": "creating", + }, + }) + if err != nil { + t.Fatal(err) + } + candidate := startCandidate{ + session: &session, + tp: TemplateParams{ + TemplateName: "worker", + InstanceName: "worker-1", + IsACP: true, + }, + } + result := startResult{ + prepared: preparedStart{ + candidate: candidate, + cfg: runtime.Config{ + MCPServers: []runtime.MCPServerConfig{{ + Name: "filesystem", + Transport: runtime.MCPTransportStdio, + Command: "/bin/mcp", + }}, + }, + coreHash: "core-abc", + liveHash: "live-xyz", + }, + outcome: "success", + started: time.Unix(100, 0), + finished: time.Unix(101, 0), + } + rec := events.NewFake() + ok := commitStartResult(result, store, &clock.Fake{Time: time.Unix(102, 0)}, rec, 0, ioDiscard{}, ioDiscard{}) + if !ok { + t.Fatal("commitStartResult returned false for successful start") + } + got, err := store.Get(session.ID) + if err != nil { + t.Fatal(err) + } + if got.Metadata[sessionpkg.MCPIdentityMetadataKey] != "myrig/worker-adhoc-123" { + t.Fatalf("mcp_identity = %q, want %q", got.Metadata[sessionpkg.MCPIdentityMetadataKey], "myrig/worker-adhoc-123") + } + if got.Metadata[sessionpkg.MCPServersSnapshotMetadataKey] == "" { + t.Fatal("mcp_servers_snapshot = empty, want persisted snapshot") + } +} diff --git a/cmd/gc/session_manager_test.go b/cmd/gc/session_manager_test.go index 507c7718f6..5b5263725b 100644 --- a/cmd/gc/session_manager_test.go +++ b/cmd/gc/session_manager_test.go @@ -14,7 +14,7 @@ func newSessionManagerWithConfig(cityPath string, store beads.Store, sp runtime. return session.NewManagerWithCityPath(store, sp, cityPath) } rigContext := currentRigContext(cfg) - return session.NewManagerWithTransportResolverAndCityPath(store, sp, cityPath, func(template, provider string) string { + return session.NewManagerWithTransportPolicyResolverAndCityPath(store, sp, cityPath, func(template, provider string) (string, bool) { agentCfg, ok := resolveAgentIdentity(cfg, template, rigContext) if ok { resolved, err := config.ResolveProvider( @@ -24,16 +24,16 @@ func newSessionManagerWithConfig(cityPath string, store beads.Store, sp runtime. func(name string) (string, error) { return name, nil }, ) if err != nil { - return agentCfg.Session + return agentCfg.Session, strings.TrimSpace(agentCfg.Session) != "" } - return config.ResolveSessionCreateTransport(agentCfg.Session, resolved) + return config.ResolveSessionCreateTransport(agentCfg.Session, resolved), strings.TrimSpace(agentCfg.Session) != "" } provider = strings.TrimSpace(provider) if provider == "" { provider = strings.TrimSpace(template) } if provider == "" { - return "" + return "", false } resolved, err := config.ResolveProvider( &config.Agent{Provider: provider}, @@ -42,8 +42,8 @@ func newSessionManagerWithConfig(cityPath string, store beads.Store, sp runtime. func(name string) (string, error) { return name, nil }, ) if err != nil { - return "" + return "", false } - return strings.TrimSpace(resolved.ProviderSessionCreateTransport()) + return strings.TrimSpace(resolved.ProviderSessionCreateTransport()), true }) } diff --git a/internal/api/session_manager.go b/internal/api/session_manager.go index 1ea71564ae..c7c87ccebf 100644 --- a/internal/api/session_manager.go +++ b/internal/api/session_manager.go @@ -13,19 +13,24 @@ func (s *Server) sessionManager(store beads.Store) *session.Manager { if cfg == nil { return session.NewManagerWithCityPath(store, s.state.SessionProvider(), s.state.CityPath()) } - return session.NewManagerWithTransportResolverAndCityPath( + return session.NewManagerWithTransportPolicyResolverAndCityPath( store, s.state.SessionProvider(), s.state.CityPath(), - func(template, provider string) string { - return configuredSessionTransport(cfg, template, provider) + func(template, provider string) (string, bool) { + return configuredSessionTransportResolution(cfg, template, provider) }, ) } func configuredSessionTransport(cfg *config.City, template, provider string) string { + transport, _ := configuredSessionTransportResolution(cfg, template, provider) + return transport +} + +func configuredSessionTransportResolution(cfg *config.City, template, provider string) (string, bool) { if cfg == nil { - return "" + return "", false } if agentCfg, ok := resolveSessionTemplateAgent(cfg, template); ok { resolved, err := config.ResolveProvider( @@ -35,16 +40,16 @@ func configuredSessionTransport(cfg *config.City, template, provider string) str func(name string) (string, error) { return name, nil }, ) if err != nil { - return strings.TrimSpace(agentCfg.Session) + return strings.TrimSpace(agentCfg.Session), strings.TrimSpace(agentCfg.Session) != "" } - return config.ResolveSessionCreateTransport(agentCfg.Session, resolved) + return config.ResolveSessionCreateTransport(agentCfg.Session, resolved), strings.TrimSpace(agentCfg.Session) != "" } provider = strings.TrimSpace(provider) if provider == "" { provider = strings.TrimSpace(template) } if provider == "" { - return "" + return "", false } resolved, err := config.ResolveProvider( &config.Agent{Provider: provider}, @@ -53,7 +58,7 @@ func configuredSessionTransport(cfg *config.City, template, provider string) str func(name string) (string, error) { return name, nil }, ) if err != nil { - return "" + return "", false } - return strings.TrimSpace(resolved.ProviderSessionCreateTransport()) + return strings.TrimSpace(resolved.ProviderSessionCreateTransport()), true } diff --git a/internal/session/manager.go b/internal/session/manager.go index ddfdf7c60a..dc37f18549 100644 --- a/internal/session/manager.go +++ b/internal/session/manager.go @@ -121,7 +121,7 @@ type Manager struct { store beads.Store sp runtime.Provider cityPath string - transportResolver func(template, provider string) string + transportResolver func(template, provider string) transportResolution } // PruneResult reports which sessions were pruned and which queued wait nudges @@ -141,6 +141,11 @@ type transportDetector interface { DetectTransport(name string) string } +type transportResolution struct { + transport string + allowStoppedFallback bool +} + func normalizeTransport(provider, transport string) string { if transport != "" { return transport @@ -155,20 +160,27 @@ func transportFromMetadata(b beads.Bead) string { return normalizeTransport(b.Metadata["provider"], b.Metadata["transport"]) } +func (m *Manager) resolveConfiguredTransport(template, provider string) (string, bool) { + if m.transportResolver == nil { + return "", false + } + resolution := m.transportResolver(strings.TrimSpace(template), strings.TrimSpace(provider)) + return normalizeTransport(provider, resolution.transport), resolution.allowStoppedFallback +} + func (m *Manager) transportForBead(b beads.Bead, sessName string) (string, bool) { transport := transportFromMetadata(b) if transport != "" { return transport, false } + if strings.TrimSpace(b.Metadata[MCPIdentityMetadataKey]) != "" || + strings.TrimSpace(b.Metadata[MCPServersSnapshotMetadataKey]) != "" { + return "acp", false + } if strings.TrimSpace(b.Metadata["pending_create_claim"]) == "true" { - if m.transportResolver != nil { - transport = normalizeTransport( - b.Metadata["provider"], - m.transportResolver(strings.TrimSpace(b.Metadata["template"]), strings.TrimSpace(b.Metadata["provider"])), - ) - if transport != "" { - return transport, true - } + transport, _ = m.resolveConfiguredTransport(b.Metadata["template"], b.Metadata["provider"]) + if transport != "" { + return transport, true } return "", false } @@ -181,6 +193,10 @@ func (m *Manager) transportForBead(b beads.Bead, sessName string) (string, bool) if m.sp != nil && m.sp.IsRunning(sessName) { return "", false } + transport, allowStoppedFallback := m.resolveConfiguredTransport(b.Metadata["template"], b.Metadata["provider"]) + if transport != "" && allowStoppedFallback { + return transport, true + } return "", false } @@ -213,7 +229,16 @@ func NewManager(store beads.Store, sp runtime.Provider) *Manager { // transport from template or provider config when older beads do not have // transport metadata. func NewManagerWithTransportResolver(store beads.Store, sp runtime.Provider, resolver func(template, provider string) string) *Manager { - return &Manager{store: store, sp: sp, transportResolver: resolver} + return &Manager{ + store: store, + sp: sp, + transportResolver: func(template, provider string) transportResolution { + if resolver == nil { + return transportResolution{} + } + return transportResolution{transport: resolver(template, provider)} + }, + } } // NewManagerWithCityPath creates a Manager that can persist deferred submits @@ -226,7 +251,44 @@ func NewManagerWithCityPath(store beads.Store, sp runtime.Provider, cityPath str // session transport from template or provider config and persist deferred // submits into the city's nudge queue. func NewManagerWithTransportResolverAndCityPath(store beads.Store, sp runtime.Provider, cityPath string, resolver func(template, provider string) string) *Manager { - return &Manager{store: store, sp: sp, cityPath: cityPath, transportResolver: resolver} + return &Manager{ + store: store, + sp: sp, + cityPath: cityPath, + transportResolver: func(template, provider string) transportResolution { + if resolver == nil { + return transportResolution{} + } + return transportResolution{transport: resolver(template, provider)} + }, + } +} + +// NewManagerWithTransportPolicyResolverAndCityPath creates a Manager that can +// infer transport from config and, when the resolver marks it safe, continue +// using that transport for stopped legacy sessions without persisted +// transport metadata. +func NewManagerWithTransportPolicyResolverAndCityPath( + store beads.Store, + sp runtime.Provider, + cityPath string, + resolver func(template, provider string) (string, bool), +) *Manager { + return &Manager{ + store: store, + sp: sp, + cityPath: cityPath, + transportResolver: func(template, provider string) transportResolution { + if resolver == nil { + return transportResolution{} + } + transport, allowStoppedFallback := resolver(template, provider) + return transportResolution{ + transport: transport, + allowStoppedFallback: allowStoppedFallback, + } + }, + } } // Create creates a new chat session bead and starts the runtime session. diff --git a/internal/session/manager_test.go b/internal/session/manager_test.go index d5ffc157c2..d45e2ba09e 100644 --- a/internal/session/manager_test.go +++ b/internal/session/manager_test.go @@ -2340,6 +2340,95 @@ func TestGetDoesNotInferConfiguredTransportForStoppedLegacySession(t *testing.T) } } +func TestGetInfersConfiguredTransportForStoppedLegacySessionWithPolicyFallback(t *testing.T) { + store := beads.NewMemStore() + defaultSP := runtime.NewFake() + acpSP := runtime.NewFake() + autoSP := sessionauto.New(defaultSP, acpSP) + + legacy, err := store.Create(beads.Bead{ + Title: "legacy acp", + Type: BeadType, + Labels: []string{ + LabelSession, + "template:helper", + }, + Metadata: map[string]string{ + "template": "helper", + "state": string(StateAsleep), + "provider": "claude", + "work_dir": "/tmp", + "command": "claude", + }, + }) + if err != nil { + t.Fatalf("Create legacy bead: %v", err) + } + sessName := sessionNameFor(legacy.ID) + if err := store.SetMetadata(legacy.ID, "session_name", sessName); err != nil { + t.Fatalf("SetMetadata(session_name): %v", err) + } + + mgr := NewManagerWithTransportPolicyResolverAndCityPath(store, autoSP, "", func(template, provider string) (string, bool) { + if template == "helper" { + return "acp", true + } + return "", false + }) + + info, err := mgr.Get(legacy.ID) + if err != nil { + t.Fatalf("Get: %v", err) + } + if got := info.Transport; got != "acp" { + t.Fatalf("Transport = %q, want acp for stopped legacy session with policy fallback", got) + } + + updated, err := store.Get(legacy.ID) + if err != nil { + t.Fatalf("Get updated bead: %v", err) + } + if got := updated.Metadata["transport"]; got != "" { + t.Fatalf("transport metadata = %q, want empty for read-only lookup", got) + } +} + +func TestGetInfersACPTransportFromStoredMCPMetadata(t *testing.T) { + store := beads.NewMemStore() + defaultSP := runtime.NewFake() + acpSP := runtime.NewFake() + autoSP := sessionauto.New(defaultSP, acpSP) + + legacy, err := store.Create(beads.Bead{ + Title: "legacy acp", + Type: BeadType, + Labels: []string{ + LabelSession, + "template:helper", + }, + Metadata: map[string]string{ + "template": "helper", + "state": string(StateAsleep), + "provider": "claude", + "work_dir": "/tmp", + "command": "claude", + MCPServersSnapshotMetadataKey: `[{"name":"filesystem","transport":"stdio","command":"/bin/mcp"}]`, + }, + }) + if err != nil { + t.Fatalf("Create legacy bead: %v", err) + } + + mgr := NewManagerWithTransportResolver(store, autoSP, nil) + info, err := mgr.Get(legacy.ID) + if err != nil { + t.Fatalf("Get: %v", err) + } + if got := info.Transport; got != "acp" { + t.Fatalf("Transport = %q, want acp from stored MCP metadata", got) + } +} + func TestSendConvergesWhenSessionAlreadyResumed(t *testing.T) { store := beads.NewMemStore() sp := runtime.NewFake() From 930b8ac87ad420ee09664a653ae8a11a9f96c20d Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 06:12:57 +0000 Subject: [PATCH 75/85] fix: avoid provider default acp reinterpretation --- cmd/gc/providers.go | 5 ----- cmd/gc/providers_test.go | 3 +++ cmd/gc/session_manager_test.go | 2 +- cmd/gc/worker_handle.go | 2 +- cmd/gc/worker_handle_test.go | 4 ++-- internal/api/handler_session_chat_test.go | 12 ++++++------ internal/api/session_manager.go | 2 +- internal/api/session_runtime.go | 2 +- 8 files changed, 15 insertions(+), 17 deletions(-) diff --git a/cmd/gc/providers.go b/cmd/gc/providers.go index e6a0a48240..5969e06c38 100644 --- a/cmd/gc/providers.go +++ b/cmd/gc/providers.go @@ -353,10 +353,8 @@ func beadUsesACPTransport(bead beads.Bead, cfg *config.City) bool { return true } templateName := strings.TrimSpace(bead.Metadata["template"]) - providerScoped := true if cfg != nil { if agentCfg, ok := resolveAgentIdentity(cfg, templateName, currentRigContext(cfg)); ok { - providerScoped = false if strings.TrimSpace(agentCfg.Session) != "" && agentSessionCreateTransport(cfg, agentCfg) == "acp" { return true } @@ -382,9 +380,6 @@ func beadUsesACPTransport(bead beads.Bead, cfg *config.City) bool { return true } } - if providerScoped { - return providerLegacyDefaultsToACP(cfg, providerName) - } if strings.TrimSpace(bead.Metadata["command"]) == "" && strings.TrimSpace(bead.Metadata["pending_create_claim"]) == "true" { return providerLegacyDefaultsToACP(cfg, providerName) diff --git a/cmd/gc/providers_test.go b/cmd/gc/providers_test.go index 6f8323f297..8278de5550 100644 --- a/cmd/gc/providers_test.go +++ b/cmd/gc/providers_test.go @@ -349,6 +349,7 @@ func TestConfiguredACPRouteNames_IncludeLegacyObservedACPProviderSessionsWithout Metadata: map[string]string{ "template": "opencode", "provider": "opencode", + "command": "/bin/echo acp", "session_name": "provider-session", }, }}) @@ -378,6 +379,7 @@ func TestConfiguredACPRouteNames_IncludeLegacyObservedCustomACPProviderSessionsW Metadata: map[string]string{ "template": "custom-acp", "provider": "custom-acp", + "command": "/bin/echo acp", "session_name": "provider-session", }, }}) @@ -622,6 +624,7 @@ func TestNewSessionProviderRoutesLegacyObservedACPProviderSessionsWithoutTranspo Metadata: map[string]string{ "template": "opencode", "provider": "opencode", + "command": "/bin/echo acp", "session_name": "provider-session", }, }); err != nil { diff --git a/cmd/gc/session_manager_test.go b/cmd/gc/session_manager_test.go index 5b5263725b..d4f86a2819 100644 --- a/cmd/gc/session_manager_test.go +++ b/cmd/gc/session_manager_test.go @@ -44,6 +44,6 @@ func newSessionManagerWithConfig(cityPath string, store beads.Store, sp runtime. if err != nil { return "", false } - return strings.TrimSpace(resolved.ProviderSessionCreateTransport()), true + return strings.TrimSpace(resolved.ProviderSessionCreateTransport()), false }) } diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index 89122e41ea..793a1a9746 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -619,7 +619,7 @@ func resolveWorkerRuntimeProviderWithConfig(cfg *config.City, info session.Info, if err != nil { return nil, "", false } - return resolved, strings.TrimSpace(resolved.ProviderSessionCreateTransport()), true + return resolved, strings.TrimSpace(resolved.ProviderSessionCreateTransport()), false } func workerDeliveryIntentForSubmitIntent(intent session.SubmitIntent) worker.DeliveryIntent { diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index 3d36419902..b33d2bdafc 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -377,7 +377,7 @@ acp_args = ["acp"] } } -func TestResolvedWorkerRuntimeWithConfigUsesConfiguredTransportForLegacyProviderSessionWithoutMetadata(t *testing.T) { +func TestResolvedWorkerRuntimeWithConfigUsesStoredACPTransportForLegacyProviderSessionWithoutMetadata(t *testing.T) { cityDir := t.TempDir() writePhase0InterfaceCity(t, cityDir, `[workspace] name = "test-city" @@ -400,7 +400,7 @@ acp_args = ["acp"] resolved, err := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ Template: "opencode", - Command: "/bin/echo", + Command: "/bin/echo acp", WorkDir: cityDir, }, "provider") if err != nil { diff --git a/internal/api/handler_session_chat_test.go b/internal/api/handler_session_chat_test.go index 8bd5fc8692..15b2a7694a 100644 --- a/internal/api/handler_session_chat_test.go +++ b/internal/api/handler_session_chat_test.go @@ -246,7 +246,7 @@ func TestBuildSessionResumeFallsBackToStoredCommandWhenTemplateOverridesInvalid( } } -func TestBuildSessionResumeUsesConfiguredACPCommandForLegacyProviderSessionWithoutTransportMetadataWithoutSessionAutoProvider(t *testing.T) { +func TestBuildSessionResumeUsesStoredACPCommandForLegacyProviderSessionWithoutTransportMetadataWithoutSessionAutoProvider(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) fs.cfg = &config.City{ @@ -267,7 +267,7 @@ func TestBuildSessionResumeUsesConfiguredACPCommandForLegacyProviderSessionWitho info := session.Info{ ID: "gc-1", Template: "opencode", - Command: "/bin/echo", + Command: "/bin/echo acp", Provider: "opencode", WorkDir: "/tmp/workdir", } @@ -281,7 +281,7 @@ func TestBuildSessionResumeUsesConfiguredACPCommandForLegacyProviderSessionWitho } } -func TestBuildSessionResumeUsesConfiguredACPCommandForLegacyProviderSessionWithoutTransportMetadata(t *testing.T) { +func TestBuildSessionResumeUsesStoredACPCommandForLegacyProviderSessionWithoutTransportMetadata(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) fs.cfg = &config.City{ @@ -306,7 +306,7 @@ func TestBuildSessionResumeUsesConfiguredACPCommandForLegacyProviderSessionWitho info := session.Info{ ID: "gc-1", Template: "opencode", - Command: "/bin/echo", + Command: "/bin/echo acp", Provider: "opencode", WorkDir: "/tmp/workdir", } @@ -320,7 +320,7 @@ func TestBuildSessionResumeUsesConfiguredACPCommandForLegacyProviderSessionWitho } } -func TestBuildSessionResumeUsesACPCommandForLegacyProviderSessionOnACPEnabledCustomProvider(t *testing.T) { +func TestBuildSessionResumeUsesStoredACPCommandForLegacyProviderSessionOnACPEnabledCustomProvider(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) fs.cfg = &config.City{ @@ -341,7 +341,7 @@ func TestBuildSessionResumeUsesACPCommandForLegacyProviderSessionOnACPEnabledCus info := session.Info{ ID: "gc-1", Template: "custom-acp", - Command: "/bin/echo", + Command: "/bin/echo acp", Provider: "custom-acp", WorkDir: "/tmp/workdir", } diff --git a/internal/api/session_manager.go b/internal/api/session_manager.go index c7c87ccebf..4c86485e1a 100644 --- a/internal/api/session_manager.go +++ b/internal/api/session_manager.go @@ -60,5 +60,5 @@ func configuredSessionTransportResolution(cfg *config.City, template, provider s if err != nil { return "", false } - return strings.TrimSpace(resolved.ProviderSessionCreateTransport()), true + return strings.TrimSpace(resolved.ProviderSessionCreateTransport()), false } diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index 6264e435c2..e7b1338056 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -416,7 +416,7 @@ func (s *Server) resolveSessionRuntimeWithMetadata(info session.Info, metadata m if workDir == "" { workDir = s.state.CityPath() } - transport := resolvedSessionTransport(info, resolved, resolved.ProviderSessionCreateTransport(), metadata, true) + transport := resolvedSessionTransport(info, resolved, resolved.ProviderSessionCreateTransport(), metadata, false) return resolved, workDir, transport } From d89914b8981cd9d051ce639af6acaca2924a207e Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 06:21:13 +0000 Subject: [PATCH 76/85] fix: stop inferring acp for stopped sessions --- cmd/gc/worker_handle.go | 4 ++-- cmd/gc/worker_handle_test.go | 4 ++-- internal/api/handler_session_chat_test.go | 4 ++-- internal/api/session_manager.go | 4 ++-- internal/api/session_runtime.go | 4 ++-- internal/session/manager.go | 4 ---- internal/session/manager_test.go | 6 +++--- 7 files changed, 13 insertions(+), 17 deletions(-) diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index 793a1a9746..7111bce2f7 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -589,7 +589,7 @@ func resolvedWorkerRuntimeTransport(info session.Info, resolved *config.Resolved if storedWorkerSessionProvesACPTransport(resolved, info.Command, metadata) { return "acp" } - if allowConfiguredTransportFallback || strings.TrimSpace(info.Command) == "" { + if allowConfiguredTransportFallback { return strings.TrimSpace(configuredTransport) } return "" @@ -611,7 +611,7 @@ func resolveWorkerRuntimeProviderWithConfig(cfg *config.City, info session.Info, if sessionKind != "provider" { if found, ok := resolveAgentIdentity(cfg, info.Template, ""); ok { if resolved, err := config.ResolveProvider(&found, &cfg.Workspace, cfg.Providers, exec.LookPath); err == nil { - return resolved, config.ResolveSessionCreateTransport(found.Session, resolved), strings.TrimSpace(found.Session) != "" + return resolved, config.ResolveSessionCreateTransport(found.Session, resolved), false } } } diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index b33d2bdafc..1fb6369363 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -258,7 +258,7 @@ TOKEN = "abc" } } -func TestResolvedWorkerRuntimeWithConfigUsesConfiguredTransportWithoutStoredTemplateACPMetadata(t *testing.T) { +func TestResolvedWorkerRuntimeWithConfigDoesNotInferConfiguredTransportWithoutStoredTemplateACPMetadata(t *testing.T) { cityDir := t.TempDir() writePhase0InterfaceCity(t, cityDir, `[workspace] name = "test-city" @@ -294,7 +294,7 @@ acp_args = ["acp"] if resolved == nil { t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") } - if got, want := resolved.Command, "/bin/echo acp"; got != want { + if got, want := resolved.Command, "/bin/echo"; got != want { t.Fatalf("Command = %q, want %q", got, want) } } diff --git a/internal/api/handler_session_chat_test.go b/internal/api/handler_session_chat_test.go index 15b2a7694a..4d3400248b 100644 --- a/internal/api/handler_session_chat_test.go +++ b/internal/api/handler_session_chat_test.go @@ -394,7 +394,7 @@ func TestBuildSessionResumeUsesStoredACPTransportForTemplateSession(t *testing.T } } -func TestBuildSessionResumeUsesConfiguredACPTransportForTemplateSessionWithoutStoredMetadata(t *testing.T) { +func TestBuildSessionResumeDoesNotInferConfiguredACPTransportForTemplateSessionWithoutStoredMetadata(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) fs.cfg = &config.City{ @@ -427,7 +427,7 @@ func TestBuildSessionResumeUsesConfiguredACPTransportForTemplateSessionWithoutSt if err != nil { t.Fatalf("buildSessionResume: %v", err) } - if got, want := cmd, "/bin/echo acp"; got != want { + if got, want := cmd, "/bin/echo"; got != want { t.Fatalf("resume command = %q, want %q", got, want) } } diff --git a/internal/api/session_manager.go b/internal/api/session_manager.go index 4c86485e1a..afea441a87 100644 --- a/internal/api/session_manager.go +++ b/internal/api/session_manager.go @@ -40,9 +40,9 @@ func configuredSessionTransportResolution(cfg *config.City, template, provider s func(name string) (string, error) { return name, nil }, ) if err != nil { - return strings.TrimSpace(agentCfg.Session), strings.TrimSpace(agentCfg.Session) != "" + return strings.TrimSpace(agentCfg.Session), false } - return config.ResolveSessionCreateTransport(agentCfg.Session, resolved), strings.TrimSpace(agentCfg.Session) != "" + return config.ResolveSessionCreateTransport(agentCfg.Session, resolved), false } provider = strings.TrimSpace(provider) if provider == "" { diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index e7b1338056..5beb24c4a6 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -383,7 +383,7 @@ func resolvedSessionTransport(info session.Info, resolved *config.ResolvedProvid if storedSessionProvesACPTransport(resolved, info.Command, metadata) { return "acp" } - if allowConfiguredTransportFallback || strings.TrimSpace(info.Command) == "" { + if allowConfiguredTransportFallback { return strings.TrimSpace(configuredTransport) } return "" @@ -402,7 +402,7 @@ func (s *Server) resolveSessionRuntimeWithMetadata(info session.Info, metadata m workDir = info.WorkDir } transport := config.ResolveSessionCreateTransport(agentCfg.Session, resolved) - return resolved, workDir, resolvedSessionTransport(info, resolved, transport, metadata, strings.TrimSpace(agentCfg.Session) != "") + return resolved, workDir, resolvedSessionTransport(info, resolved, transport, metadata, false) } } } diff --git a/internal/session/manager.go b/internal/session/manager.go index dc37f18549..48411bfd62 100644 --- a/internal/session/manager.go +++ b/internal/session/manager.go @@ -193,10 +193,6 @@ func (m *Manager) transportForBead(b beads.Bead, sessName string) (string, bool) if m.sp != nil && m.sp.IsRunning(sessName) { return "", false } - transport, allowStoppedFallback := m.resolveConfiguredTransport(b.Metadata["template"], b.Metadata["provider"]) - if transport != "" && allowStoppedFallback { - return transport, true - } return "", false } diff --git a/internal/session/manager_test.go b/internal/session/manager_test.go index d45e2ba09e..34e26f848e 100644 --- a/internal/session/manager_test.go +++ b/internal/session/manager_test.go @@ -2340,7 +2340,7 @@ func TestGetDoesNotInferConfiguredTransportForStoppedLegacySession(t *testing.T) } } -func TestGetInfersConfiguredTransportForStoppedLegacySessionWithPolicyFallback(t *testing.T) { +func TestGetDoesNotInferConfiguredTransportForStoppedLegacySessionWithPolicyFallback(t *testing.T) { store := beads.NewMemStore() defaultSP := runtime.NewFake() acpSP := runtime.NewFake() @@ -2380,8 +2380,8 @@ func TestGetInfersConfiguredTransportForStoppedLegacySessionWithPolicyFallback(t if err != nil { t.Fatalf("Get: %v", err) } - if got := info.Transport; got != "acp" { - t.Fatalf("Transport = %q, want acp for stopped legacy session with policy fallback", got) + if got := info.Transport; got != "" { + t.Fatalf("Transport = %q, want empty for stopped legacy session without stored evidence", got) } updated, err := store.Get(legacy.ID) From 334edbc408058d993f446898a9e0dd673dbc44a0 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 06:37:00 +0000 Subject: [PATCH 77/85] fix: preserve acp proof without leaking mcp secrets --- cmd/gc/worker_handle.go | 36 +++++++++++++- cmd/gc/worker_handle_test.go | 51 ++++++++++++++++++++ internal/api/handler_session_chat_test.go | 28 +++++++++++ internal/api/session_runtime.go | 59 +++++++++++++++++++---- internal/session/mcp_metadata.go | 45 ++++++++++++++++- internal/session/mcp_metadata_test.go | 46 ++++++++++++++++++ 6 files changed, 252 insertions(+), 13 deletions(-) create mode 100644 internal/session/mcp_metadata_test.go diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index 7111bce2f7..697b2f448b 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -183,6 +183,9 @@ func resumeRuntimeMCPServersWithConfig( if decodeErr != nil { return nil, fmt.Errorf("decoding stored MCP snapshot: %w", decodeErr) } + if session.StoredMCPSnapshotContainsRedactions(stored) { + return nil, fmt.Errorf("loading effective MCP: %w; stored snapshot contains redacted secrets", err) + } return stored, nil } @@ -460,6 +463,9 @@ func resolvedWorkerRuntimeWithConfigAndMetadata(cityPath string, cfg *config.Cit return nil, nil } transport := resolvedWorkerRuntimeTransport(info, resolved, configuredTransport, metadata, allowConfiguredTransportFallback) + if transport == "" && legacyWorkerACPTransportAmbiguous(resolved, configuredTransport, info.Command, metadata) { + return nil, fmt.Errorf("legacy session transport is ambiguous: recreate the stopped session or resume it while ACP metadata can still be persisted") + } command := strings.TrimSpace(info.Command) desiredCommand := fallbackResolvedWorkerRuntimeCommand(resolved, transport, command) @@ -561,12 +567,15 @@ func fallbackResolvedWorkerRuntimeCommand(resolved *config.ResolvedProvider, tra return firstNonEmptyGCString(storedCommand, resolvedCommand, resolved.Name) } -func storedWorkerSessionProvesACPTransport(resolved *config.ResolvedProvider, storedCommand string, metadata map[string]string) bool { +func storedWorkerSessionProvesACPTransport(resolved *config.ResolvedProvider, configuredTransport, storedCommand string, metadata map[string]string) bool { if metadata != nil { if strings.TrimSpace(metadata[session.MCPIdentityMetadataKey]) != "" || strings.TrimSpace(metadata[session.MCPServersSnapshotMetadataKey]) != "" { return true } + if strings.TrimSpace(configuredTransport) == "acp" && legacyWorkerResumeMetadataProvesACPTransport(metadata) { + return true + } } if resolved == nil { return false @@ -579,6 +588,29 @@ func storedWorkerSessionProvesACPTransport(resolved *config.ResolvedProvider, st return shouldPreserveStoredRuntimeCommand(storedCommand, acpCommand) } +func legacyWorkerResumeMetadataProvesACPTransport(metadata map[string]string) bool { + if metadata == nil || strings.TrimSpace(metadata["session_key"]) == "" { + return false + } + return strings.TrimSpace(metadata["resume_command"]) != "" || strings.TrimSpace(metadata["resume_flag"]) != "" +} + +func legacyWorkerACPTransportAmbiguous(resolved *config.ResolvedProvider, configuredTransport, storedCommand string, metadata map[string]string) bool { + if strings.TrimSpace(configuredTransport) != "acp" || resolved == nil { + return false + } + if storedWorkerSessionProvesACPTransport(resolved, configuredTransport, storedCommand, metadata) { + return false + } + acpCommand := strings.TrimSpace(resolved.ACPCommandString()) + defaultCommand := strings.TrimSpace(resolved.CommandString()) + if acpCommand == "" || acpCommand != defaultCommand { + return false + } + storedCommand = strings.TrimSpace(storedCommand) + return storedCommand == "" || sameRuntimeCommandExecutable(storedCommand, defaultCommand) +} + func resolvedWorkerRuntimeTransport(info session.Info, resolved *config.ResolvedProvider, configuredTransport string, metadata map[string]string, allowConfiguredTransportFallback bool) string { if transport := strings.TrimSpace(info.Transport); transport != "" { return transport @@ -586,7 +618,7 @@ func resolvedWorkerRuntimeTransport(info session.Info, resolved *config.Resolved if strings.TrimSpace(info.Provider) == "acp" { return "acp" } - if storedWorkerSessionProvesACPTransport(resolved, info.Command, metadata) { + if storedWorkerSessionProvesACPTransport(resolved, configuredTransport, info.Command, metadata) { return "acp" } if allowConfiguredTransportFallback { diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index 1fb6369363..d54ef96d47 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -299,6 +299,57 @@ acp_args = ["acp"] } } +func TestResolvedWorkerRuntimeTransportUsesResumeMetadataForLegacyACPWithSameCommand(t *testing.T) { + resolved := &config.ResolvedProvider{ + Command: "/bin/echo", + ACPCommand: "/bin/echo", + } + + got := resolvedWorkerRuntimeTransport(session.Info{ + Command: "/bin/echo", + }, resolved, "acp", map[string]string{ + "session_key": "legacy-key", + "resume_flag": "--resume", + }, false) + if got != "acp" { + t.Fatalf("resolvedWorkerRuntimeTransport() = %q, want acp", got) + } +} + +func TestResolvedWorkerRuntimeWithConfigErrorsForAmbiguousLegacyACPTransportWithSameCommand(t *testing.T) { + cityDir := t.TempDir() + writePhase0InterfaceCity(t, cityDir, `[workspace] +name = "test-city" + +[beads] +provider = "file" + +[[agent]] +name = "worker" +provider = "stub" +session = "acp" + +[providers.stub] +command = "/bin/echo" +supports_acp = true +acp_command = "/bin/echo" +`) + + cfg, err := loadCityConfig(cityDir) + if err != nil { + t.Fatalf("loadCityConfig: %v", err) + } + + _, err = resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + Template: "worker", + Command: "/bin/echo", + WorkDir: cityDir, + }, "") + if err == nil || !strings.Contains(err.Error(), "legacy session transport is ambiguous") { + t.Fatalf("resolvedWorkerRuntimeWithConfig() error = %v, want ambiguous legacy ACP transport", err) + } +} + func TestResolvedWorkerRuntimeWithConfigKeepsDefaultTransportWithoutExplicitACPTemplate(t *testing.T) { cityDir := t.TempDir() writePhase0InterfaceCity(t, cityDir, `[workspace] diff --git a/internal/api/handler_session_chat_test.go b/internal/api/handler_session_chat_test.go index 4d3400248b..ec99ddf8c0 100644 --- a/internal/api/handler_session_chat_test.go +++ b/internal/api/handler_session_chat_test.go @@ -432,6 +432,34 @@ func TestBuildSessionResumeDoesNotInferConfiguredACPTransportForTemplateSessionW } } +func TestResolvedSessionTransportUsesResumeMetadataForLegacyACPWithSameCommand(t *testing.T) { + resolved := &config.ResolvedProvider{ + Command: "/bin/echo", + ACPCommand: "/bin/echo", + } + + got := resolvedSessionTransport(session.Info{ + Command: "/bin/echo", + }, resolved, "acp", map[string]string{ + "session_key": "legacy-key", + "resume_flag": "--resume", + }, false) + if got != "acp" { + t.Fatalf("resolvedSessionTransport() = %q, want acp", got) + } +} + +func TestLegacyACPTransportAmbiguousWithSameCommand(t *testing.T) { + resolved := &config.ResolvedProvider{ + Command: "/bin/echo", + ACPCommand: "/bin/echo", + } + + if !legacyACPTransportAmbiguous(resolved, "acp", "/bin/echo", nil) { + t.Fatal("legacyACPTransportAmbiguous() = false, want true") + } +} + func TestBuildSessionResumeUsesStoredACPCommandForLegacyTemplateSessionWithoutTransportMetadata(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index 5beb24c4a6..5aa43e9092 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -14,6 +14,8 @@ import ( "github.com/gastownhall/gascity/internal/worker" ) +var errAmbiguousLegacyACPTransport = errors.New("legacy session transport is ambiguous") + func (s *Server) sessionLogPaths() []string { if s.sessionLogSearchPaths != nil { return s.sessionLogSearchPaths @@ -75,6 +77,9 @@ func (s *Server) resumeSessionMCPServers(info session.Info, metadata map[string] if decodeErr != nil { return nil, fmt.Errorf("decoding stored MCP snapshot: %w", decodeErr) } + if session.StoredMCPSnapshotContainsRedactions(stored) { + return nil, fmt.Errorf("loading effective MCP: %w; stored snapshot contains redacted secrets", err) + } return stored, nil } @@ -223,10 +228,13 @@ func (s *Server) resolveSessionTemplate(template string) (*config.ResolvedProvid func (s *Server) buildSessionResume(info session.Info) (string, runtime.Config, error) { cmd := session.BuildResumeCommand(info) metadata := s.sessionMetadata(info.ID) - resolved, workDir, transport := s.resolveSessionRuntimeWithMetadata(info, metadata) + resolved, workDir, transport, ambiguous := s.resolveSessionRuntimeWithMetadata(info, metadata) if resolved == nil { return cmd, runtime.Config{WorkDir: info.WorkDir}, nil } + if ambiguous { + return "", runtime.Config{}, fmt.Errorf("%w: recreate the stopped session or resume it while ACP metadata can still be persisted", errAmbiguousLegacyACPTransport) + } mcpServers, err := s.resumeSessionMCPServers(info, metadata, resolved, firstNonEmptyString(workDir, info.WorkDir), transport) if err != nil { return "", runtime.Config{}, err @@ -324,10 +332,13 @@ func (s *Server) resolveWorkerSessionRuntimeWithMetadata(info session.Info, _ st if metadata == nil { metadata = s.sessionMetadata(info.ID) } - resolved, workDir, transport := s.resolveSessionRuntimeWithMetadata(info, metadata) + resolved, workDir, transport, ambiguous := s.resolveSessionRuntimeWithMetadata(info, metadata) if resolved == nil { return nil, nil } + if ambiguous { + return nil, fmt.Errorf("%w: recreate the stopped session or resume it while ACP metadata can still be persisted", errAmbiguousLegacyACPTransport) + } mcpServers, err := s.resumeSessionMCPServers(info, metadata, resolved, firstNonEmptyString(workDir, info.WorkDir), transport) if err != nil { return nil, err @@ -355,12 +366,15 @@ func (s *Server) resolveWorkerSessionRuntimeWithMetadata(info session.Info, _ st return &runtimeCfg, nil } -func storedSessionProvesACPTransport(resolved *config.ResolvedProvider, storedCommand string, metadata map[string]string) bool { +func storedSessionProvesACPTransport(resolved *config.ResolvedProvider, configuredTransport, storedCommand string, metadata map[string]string) bool { if metadata != nil { if strings.TrimSpace(metadata[session.MCPIdentityMetadataKey]) != "" || strings.TrimSpace(metadata[session.MCPServersSnapshotMetadataKey]) != "" { return true } + if strings.TrimSpace(configuredTransport) == "acp" && legacyResumeMetadataProvesACPTransport(metadata) { + return true + } } if resolved == nil { return false @@ -373,6 +387,29 @@ func storedSessionProvesACPTransport(resolved *config.ResolvedProvider, storedCo return shouldPreserveStoredRuntimeCommand(storedCommand, acpCommand) } +func legacyResumeMetadataProvesACPTransport(metadata map[string]string) bool { + if metadata == nil || strings.TrimSpace(metadata["session_key"]) == "" { + return false + } + return strings.TrimSpace(metadata["resume_command"]) != "" || strings.TrimSpace(metadata["resume_flag"]) != "" +} + +func legacyACPTransportAmbiguous(resolved *config.ResolvedProvider, configuredTransport, storedCommand string, metadata map[string]string) bool { + if strings.TrimSpace(configuredTransport) != "acp" || resolved == nil { + return false + } + if storedSessionProvesACPTransport(resolved, configuredTransport, storedCommand, metadata) { + return false + } + acpCommand := strings.TrimSpace(resolved.ACPCommandString()) + defaultCommand := strings.TrimSpace(resolved.CommandString()) + if acpCommand == "" || acpCommand != defaultCommand { + return false + } + storedCommand = strings.TrimSpace(storedCommand) + return storedCommand == "" || sameRuntimeCommandExecutable(storedCommand, defaultCommand) +} + func resolvedSessionTransport(info session.Info, resolved *config.ResolvedProvider, configuredTransport string, metadata map[string]string, allowConfiguredTransportFallback bool) string { if transport := strings.TrimSpace(info.Transport); transport != "" { return transport @@ -380,7 +417,7 @@ func resolvedSessionTransport(info session.Info, resolved *config.ResolvedProvid if strings.TrimSpace(info.Provider) == "acp" { return "acp" } - if storedSessionProvesACPTransport(resolved, info.Command, metadata) { + if storedSessionProvesACPTransport(resolved, configuredTransport, info.Command, metadata) { return "acp" } if allowConfiguredTransportFallback { @@ -389,7 +426,7 @@ func resolvedSessionTransport(info session.Info, resolved *config.ResolvedProvid return "" } -func (s *Server) resolveSessionRuntimeWithMetadata(info session.Info, metadata map[string]string) (*config.ResolvedProvider, string, string) { +func (s *Server) resolveSessionRuntimeWithMetadata(info session.Info, metadata map[string]string) (*config.ResolvedProvider, string, string, bool) { kind := s.sessionKind(info.ID) cfg := s.state.Config() if kind != "provider" && cfg != nil { @@ -401,8 +438,9 @@ func (s *Server) resolveSessionRuntimeWithMetadata(info session.Info, metadata m if info.WorkDir != "" { workDir = info.WorkDir } - transport := config.ResolveSessionCreateTransport(agentCfg.Session, resolved) - return resolved, workDir, resolvedSessionTransport(info, resolved, transport, metadata, false) + configuredTransport := config.ResolveSessionCreateTransport(agentCfg.Session, resolved) + transport := resolvedSessionTransport(info, resolved, configuredTransport, metadata, false) + return resolved, workDir, transport, transport == "" && legacyACPTransportAmbiguous(resolved, configuredTransport, info.Command, metadata) } } } @@ -410,14 +448,15 @@ func (s *Server) resolveSessionRuntimeWithMetadata(info session.Info, metadata m resolved, err := s.resolveBareProvider(info.Template) if err != nil { - return nil, "", "" + return nil, "", "", false } workDir := info.WorkDir if workDir == "" { workDir = s.state.CityPath() } - transport := resolvedSessionTransport(info, resolved, resolved.ProviderSessionCreateTransport(), metadata, false) - return resolved, workDir, transport + configuredTransport := resolved.ProviderSessionCreateTransport() + transport := resolvedSessionTransport(info, resolved, configuredTransport, metadata, false) + return resolved, workDir, transport, transport == "" && legacyACPTransportAmbiguous(resolved, configuredTransport, info.Command, metadata) } // sessionKind reads the persisted mc_session_kind from bead metadata. diff --git a/internal/session/mcp_metadata.go b/internal/session/mcp_metadata.go index 9195260015..6634d6d5eb 100644 --- a/internal/session/mcp_metadata.go +++ b/internal/session/mcp_metadata.go @@ -16,12 +16,14 @@ const ( // server snapshot used to resume sessions when the current catalog cannot // be materialized. MCPServersSnapshotMetadataKey = "mcp_servers_snapshot" + + redactedMCPSnapshotValue = "__redacted__" ) // EncodeMCPServersSnapshot returns the normalized metadata value for a // session's persisted ACP session/new MCP server snapshot. func EncodeMCPServersSnapshot(servers []runtime.MCPServerConfig) (string, error) { - normalized := runtime.NormalizeMCPServerConfigs(servers) + normalized := normalizeMCPServersSnapshotForMetadata(servers) if len(normalized) == 0 { return "", nil } @@ -46,6 +48,18 @@ func DecodeMCPServersSnapshot(raw string) ([]runtime.MCPServerConfig, error) { return runtime.NormalizeMCPServerConfigs(servers), nil } +// StoredMCPSnapshotContainsRedactions reports whether a decoded persisted MCP +// snapshot contains redacted secret values and therefore cannot be used as a +// complete runtime fallback. +func StoredMCPSnapshotContainsRedactions(servers []runtime.MCPServerConfig) bool { + for _, server := range servers { + if snapshotMapContainsRedactions(server.Env) || snapshotMapContainsRedactions(server.Headers) { + return true + } + } + return false +} + // WithStoredMCPMetadata returns a metadata map augmented with the stable MCP // identity and normalized ACP session/new snapshot for the session. func WithStoredMCPMetadata(meta map[string]string, identity string, servers []runtime.MCPServerConfig) (map[string]string, error) { @@ -67,3 +81,32 @@ func WithStoredMCPMetadata(meta map[string]string, identity string, servers []ru } return meta, nil } + +func normalizeMCPServersSnapshotForMetadata(servers []runtime.MCPServerConfig) []runtime.MCPServerConfig { + normalized := runtime.NormalizeMCPServerConfigs(servers) + for i := range normalized { + normalized[i].Env = redactMCPMetadataMap(normalized[i].Env) + normalized[i].Headers = redactMCPMetadataMap(normalized[i].Headers) + } + return normalized +} + +func redactMCPMetadataMap(in map[string]string) map[string]string { + if len(in) == 0 { + return nil + } + out := make(map[string]string, len(in)) + for key := range in { + out[key] = redactedMCPSnapshotValue + } + return out +} + +func snapshotMapContainsRedactions(in map[string]string) bool { + for _, value := range in { + if value == redactedMCPSnapshotValue { + return true + } + } + return false +} diff --git a/internal/session/mcp_metadata_test.go b/internal/session/mcp_metadata_test.go new file mode 100644 index 0000000000..1ba177180d --- /dev/null +++ b/internal/session/mcp_metadata_test.go @@ -0,0 +1,46 @@ +package session + +import ( + "testing" + + "github.com/gastownhall/gascity/internal/runtime" +) + +func TestEncodeMCPServersSnapshotRedactsSecrets(t *testing.T) { + raw, err := EncodeMCPServersSnapshot([]runtime.MCPServerConfig{{ + Name: "remote", + Transport: runtime.MCPTransportHTTP, + Command: "/bin/mcp", + Args: []string{"--serve"}, + Env: map[string]string{ + "API_TOKEN": "super-secret", + }, + URL: "https://example.invalid/mcp", + Headers: map[string]string{ + "Authorization": "Bearer secret", + }, + }}) + if err != nil { + t.Fatalf("EncodeMCPServersSnapshot: %v", err) + } + + servers, err := DecodeMCPServersSnapshot(raw) + if err != nil { + t.Fatalf("DecodeMCPServersSnapshot: %v", err) + } + if len(servers) != 1 { + t.Fatalf("len(servers) = %d, want 1", len(servers)) + } + if got, want := servers[0].Env["API_TOKEN"], redactedMCPSnapshotValue; got != want { + t.Fatalf("Env[API_TOKEN] = %q, want %q", got, want) + } + if got, want := servers[0].Headers["Authorization"], redactedMCPSnapshotValue; got != want { + t.Fatalf("Headers[Authorization] = %q, want %q", got, want) + } + if got, want := servers[0].Args[0], "--serve"; got != want { + t.Fatalf("Args[0] = %q, want %q", got, want) + } + if !StoredMCPSnapshotContainsRedactions(servers) { + t.Fatal("StoredMCPSnapshotContainsRedactions() = false, want true") + } +} From a286f22df58a8f6ae528f7f893e6224214a7eb04 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 06:44:56 +0000 Subject: [PATCH 78/85] fix: preserve mcp template branch alias --- internal/materialize/mcp_runtime.go | 8 ++++++-- internal/materialize/mcp_test.go | 13 +++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/internal/materialize/mcp_runtime.go b/internal/materialize/mcp_runtime.go index c59072e95f..c593ef0040 100644 --- a/internal/materialize/mcp_runtime.go +++ b/internal/materialize/mcp_runtime.go @@ -40,12 +40,14 @@ func MCPTemplateData( workDir string, ) map[string]string { if agent == nil { + branch := defaultMCPBranch(workDir) return map[string]string{ "CityRoot": cityPath, "AgentName": identity, "TemplateName": identity, "WorkDir": workDir, - "DefaultBranch": defaultMCPBranch(workDir), + "Branch": branch, + "DefaultBranch": branch, } } var rigs []config.Rig @@ -65,6 +67,7 @@ func MCPTemplateData( for key, value := range agent.Env { data[key] = value } + branch := defaultMCPBranch(workDir) data["CityRoot"] = cityPath data["AgentName"] = identity data["TemplateName"] = templateName @@ -72,7 +75,8 @@ func MCPTemplateData( data["RigRoot"] = rigRoot data["WorkDir"] = workDir data["IssuePrefix"] = mcpRigPrefix(rigName, rigs) - data["DefaultBranch"] = defaultMCPBranch(workDir) + data["Branch"] = branch + data["DefaultBranch"] = branch data["WorkQuery"] = agent.EffectiveWorkQuery() data["SlingQuery"] = agent.EffectiveSlingQuery() return data diff --git a/internal/materialize/mcp_test.go b/internal/materialize/mcp_test.go index 8acac8e29f..ac9b05ef30 100644 --- a/internal/materialize/mcp_test.go +++ b/internal/materialize/mcp_test.go @@ -248,6 +248,19 @@ func TestMCPTemplateDataUsesPoolNameForPoolInstances(t *testing.T) { } } +func TestMCPTemplateDataPreservesBranchAlias(t *testing.T) { + t.Parallel() + + agent := &config.Agent{Name: "worker"} + got := MCPTemplateData(&config.City{}, "/tmp/city", agent, "worker-1", "") + if got["Branch"] == "" { + t.Fatal("Branch = empty, want default branch alias") + } + if got["Branch"] != got["DefaultBranch"] { + t.Fatalf("Branch = %q, want %q", got["Branch"], got["DefaultBranch"]) + } +} + func TestMCPPackSourcesForAgentOrdersAndDedupes(t *testing.T) { t.Parallel() From 5d211b596094b32b606b3964a6678f506a82a045 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 06:51:21 +0000 Subject: [PATCH 79/85] fix: honor legacy acp resume metadata --- cmd/gc/worker_handle.go | 6 ++++-- cmd/gc/worker_handle_test.go | 1 - internal/api/handler_session_chat_test.go | 1 - internal/api/session_runtime.go | 6 ++++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index 697b2f448b..b3768e3bdd 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -589,10 +589,12 @@ func storedWorkerSessionProvesACPTransport(resolved *config.ResolvedProvider, co } func legacyWorkerResumeMetadataProvesACPTransport(metadata map[string]string) bool { - if metadata == nil || strings.TrimSpace(metadata["session_key"]) == "" { + if metadata == nil { return false } - return strings.TrimSpace(metadata["resume_command"]) != "" || strings.TrimSpace(metadata["resume_flag"]) != "" + return strings.TrimSpace(metadata["resume_command"]) != "" || + strings.TrimSpace(metadata["resume_flag"]) != "" || + strings.TrimSpace(metadata["session_key"]) != "" } func legacyWorkerACPTransportAmbiguous(resolved *config.ResolvedProvider, configuredTransport, storedCommand string, metadata map[string]string) bool { diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index d54ef96d47..b087e4019e 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -308,7 +308,6 @@ func TestResolvedWorkerRuntimeTransportUsesResumeMetadataForLegacyACPWithSameCom got := resolvedWorkerRuntimeTransport(session.Info{ Command: "/bin/echo", }, resolved, "acp", map[string]string{ - "session_key": "legacy-key", "resume_flag": "--resume", }, false) if got != "acp" { diff --git a/internal/api/handler_session_chat_test.go b/internal/api/handler_session_chat_test.go index ec99ddf8c0..765b1503dd 100644 --- a/internal/api/handler_session_chat_test.go +++ b/internal/api/handler_session_chat_test.go @@ -441,7 +441,6 @@ func TestResolvedSessionTransportUsesResumeMetadataForLegacyACPWithSameCommand(t got := resolvedSessionTransport(session.Info{ Command: "/bin/echo", }, resolved, "acp", map[string]string{ - "session_key": "legacy-key", "resume_flag": "--resume", }, false) if got != "acp" { diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index 5aa43e9092..a31a6c11d9 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -388,10 +388,12 @@ func storedSessionProvesACPTransport(resolved *config.ResolvedProvider, configur } func legacyResumeMetadataProvesACPTransport(metadata map[string]string) bool { - if metadata == nil || strings.TrimSpace(metadata["session_key"]) == "" { + if metadata == nil { return false } - return strings.TrimSpace(metadata["resume_command"]) != "" || strings.TrimSpace(metadata["resume_flag"]) != "" + return strings.TrimSpace(metadata["resume_command"]) != "" || + strings.TrimSpace(metadata["resume_flag"]) != "" || + strings.TrimSpace(metadata["session_key"]) != "" } func legacyACPTransportAmbiguous(resolved *config.ResolvedProvider, configuredTransport, storedCommand string, metadata map[string]string) bool { From 3a4bec3772f99848a663b238c7eaf96377e188f4 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 07:13:22 +0000 Subject: [PATCH 80/85] fix: preserve legacy acp route proofs --- cmd/gc/providers.go | 12 +-- cmd/gc/providers_test.go | 33 +++++++++ cmd/gc/worker_handle.go | 89 ++++++++++++++++++----- cmd/gc/worker_handle_test.go | 71 ++++++++++++++++++ internal/api/handler_session_chat_test.go | 68 +++++++++++++++++ internal/api/session_runtime.go | 87 ++++++++++++++++++---- 6 files changed, 322 insertions(+), 38 deletions(-) diff --git a/cmd/gc/providers.go b/cmd/gc/providers.go index 5969e06c38..84298ad6c6 100644 --- a/cmd/gc/providers.go +++ b/cmd/gc/providers.go @@ -195,8 +195,8 @@ func newSessionProviderFromContextWithError(ctx sessionProviderContext, sessionB // wrap in an auto provider that routes per-session. // NOTE: agents comes from loadCityConfig which applies pack overrides, // so the Session field from overrides is already resolved here. - requireACPWrapper := requiresACPProviderWrapper(sessionBeads, ctx.cfg) - if ctx.providerName != "acp" && needsACPProviderWrapper(sessionBeads, ctx.cfg) { + requireACPWrapper := requiresACPProviderWrapper(sessionBeads, ctx.cityName, ctx.cfg) + if ctx.providerName != "acp" && needsACPProviderWrapper(sessionBeads, ctx.cityName, ctx.cfg) { acpSP, acpErr := buildSessionProviderByName("acp", ctx.sc, ctx.cityName, ctx.cityPath) if acpErr != nil { if requireACPWrapper { @@ -259,12 +259,12 @@ func configuredACPSessionNames(snapshot *sessionBeadSnapshot, cityName, sessionT return names } -func needsACPProviderWrapper(snapshot *sessionBeadSnapshot, cfg *config.City) bool { - return requiresACPProviderWrapper(snapshot, cfg) || (cfg != nil && hasACPProviderTargets(cfg)) +func needsACPProviderWrapper(snapshot *sessionBeadSnapshot, cityName string, cfg *config.City) bool { + return requiresACPProviderWrapper(snapshot, cityName, cfg) || (cfg != nil && hasACPProviderTargets(cfg)) } -func requiresACPProviderWrapper(snapshot *sessionBeadSnapshot, cfg *config.City) bool { - return len(observedACPSessionNames(snapshot, cfg)) > 0 || (cfg != nil && hasACPAgents(cfg.Agents)) +func requiresACPProviderWrapper(snapshot *sessionBeadSnapshot, cityName string, cfg *config.City) bool { + return len(configuredACPRouteNames(snapshot, cityName, cfg)) > 0 } func hasACPProviderTargets(cfg *config.City) bool { diff --git a/cmd/gc/providers_test.go b/cmd/gc/providers_test.go index 8278de5550..e4dd0d17ff 100644 --- a/cmd/gc/providers_test.go +++ b/cmd/gc/providers_test.go @@ -575,6 +575,39 @@ func TestNewSessionProviderRequiresACPInitForACPAgents(t *testing.T) { } } +func TestNewSessionProviderRequiresACPInitForImplicitACPTemplates(t *testing.T) { + oldBuild := buildSessionProviderByName + t.Cleanup(func() { buildSessionProviderByName = oldBuild }) + buildSessionProviderByName = func(name string, sc config.SessionConfig, cityName, cityPath string) (runtime.Provider, error) { + if name == "acp" { + return nil, errors.New("acp unavailable") + } + return oldBuild(name, sc, cityName, cityPath) + } + + ctx := sessionProviderContextForCity(&config.City{ + Workspace: config.Workspace{ + Name: "test-city", + }, + Agents: []config.Agent{ + {Name: "worker", Provider: "custom-acp"}, + }, + Providers: map[string]config.ProviderSpec{ + "custom-acp": { + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: boolPtr(true), + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + }, + }, + }, t.TempDir(), "fake") + + if _, err := newSessionProviderFromContextWithError(ctx, nil); err == nil { + t.Fatal("newSessionProviderFromContextWithError() error = nil, want ACP init failure") + } +} + func TestNewSessionProviderRoutesObservedACPProviderSessionsWithoutACPAgents(t *testing.T) { t.Setenv("GC_BEADS", "file") t.Setenv("GC_SESSION", "fake") diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index b3768e3bdd..65fa226865 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -463,28 +463,14 @@ func resolvedWorkerRuntimeWithConfigAndMetadata(cityPath string, cfg *config.Cit return nil, nil } transport := resolvedWorkerRuntimeTransport(info, resolved, configuredTransport, metadata, allowConfiguredTransportFallback) + if transport == "" && startedConfigHashProvesWorkerACPTransport(cityPath, cfg, info, sessionKind, resolved, metadata, configuredTransport) { + transport = "acp" + } if transport == "" && legacyWorkerACPTransportAmbiguous(resolved, configuredTransport, info.Command, metadata) { return nil, fmt.Errorf("legacy session transport is ambiguous: recreate the stopped session or resume it while ACP metadata can still be persisted") } - command := strings.TrimSpace(info.Command) - desiredCommand := fallbackResolvedWorkerRuntimeCommand(resolved, transport, command) - if optionOverrides, err := session.ParseTemplateOverrides(metadata); err == nil { - if launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, optionOverrides, transport); err == nil { - resolvedCommand := resolved.CommandString() - if transport == "acp" { - resolvedCommand = resolved.ACPCommandString() - } - desiredCommand = firstNonEmptyGCString(launchCommand.Command, resolvedCommand, resolved.Name) - if shouldPreserveStoredRuntimeCommandForTransport(command, desiredCommand, transport, optionOverrides) { - desiredCommand = command - } - } - } - if !shouldPreserveStoredRuntimeCommand(command, desiredCommand) { - command = desiredCommand - } - command = firstNonEmptyGCString(command, info.Provider, resolved.Name) + command := resolvedWorkerRuntimeCommandForTransport(cityPath, resolved, transport, info.Command, info.Provider, metadata) workDir := strings.TrimSpace(info.WorkDir) if workDir == "" { @@ -516,6 +502,27 @@ func resolvedWorkerRuntimeWithConfigAndMetadata(cityPath string, cfg *config.Cit }, nil } +func resolvedWorkerRuntimeCommandForTransport(cityPath string, resolved *config.ResolvedProvider, transport, storedCommand, fallbackProvider string, metadata map[string]string) string { + command := strings.TrimSpace(storedCommand) + desiredCommand := fallbackResolvedWorkerRuntimeCommand(resolved, transport, command) + if optionOverrides, err := session.ParseTemplateOverrides(metadata); err == nil { + if launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, optionOverrides, transport); err == nil { + resolvedCommand := resolved.CommandString() + if transport == "acp" { + resolvedCommand = resolved.ACPCommandString() + } + desiredCommand = firstNonEmptyGCString(launchCommand.Command, resolvedCommand, resolved.Name) + if shouldPreserveStoredRuntimeCommandForTransport(command, desiredCommand, transport, optionOverrides) { + desiredCommand = command + } + } + } + if !shouldPreserveStoredRuntimeCommand(command, desiredCommand) { + command = desiredCommand + } + return firstNonEmptyGCString(command, fallbackProvider, resolved.Name) +} + func shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand string) bool { storedCommand = strings.TrimSpace(storedCommand) if storedCommand == "" { @@ -613,6 +620,52 @@ func legacyWorkerACPTransportAmbiguous(resolved *config.ResolvedProvider, config return storedCommand == "" || sameRuntimeCommandExecutable(storedCommand, defaultCommand) } +func startedConfigHashProvesWorkerACPTransport( + cityPath string, + cfg *config.City, + info session.Info, + sessionKind string, + resolved *config.ResolvedProvider, + metadata map[string]string, + configuredTransport string, +) bool { + if cfg == nil || resolved == nil || metadata == nil || strings.TrimSpace(configuredTransport) != "acp" { + return false + } + startedHash := strings.TrimSpace(metadata["started_config_hash"]) + if startedHash == "" { + return false + } + acpCommand := resolvedWorkerRuntimeCommandForTransport(cityPath, resolved, "acp", info.Command, info.Provider, metadata) + defaultCommand := resolvedWorkerRuntimeCommandForTransport(cityPath, resolved, "", info.Command, info.Provider, metadata) + mcpServers, err := resolvedRuntimeMCPServersWithConfig( + cityPath, + cfg, + info.Alias, + info.Template, + firstNonEmptyGCString(info.Provider, resolved.Name, info.Template), + firstNonEmptyGCString(info.WorkDir, cityPath), + "acp", + metadata, + ) + if err != nil { + return false + } + acpHash := runtime.CoreFingerprint(runtime.Config{ + Command: acpCommand, + Env: resolved.Env, + MCPServers: mcpServers, + }) + defaultHash := runtime.CoreFingerprint(runtime.Config{ + Command: defaultCommand, + Env: resolved.Env, + }) + if acpHash == defaultHash { + return false + } + return startedHash == acpHash +} + func resolvedWorkerRuntimeTransport(info session.Info, resolved *config.ResolvedProvider, configuredTransport string, metadata map[string]string, allowConfiguredTransportFallback bool) string { if transport := strings.TrimSpace(info.Transport); transport != "" { return transport diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index b087e4019e..bcbca44ec2 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -349,6 +349,77 @@ acp_command = "/bin/echo" } } +func TestResolvedWorkerRuntimeWithConfigUsesStartedConfigHashForLegacyProviderACPWithSameCommand(t *testing.T) { + cityDir := t.TempDir() + writePhase0InterfaceCity(t, cityDir, `[workspace] +name = "test-city" + +[beads] +provider = "file" + +[providers.custom-acp] +command = "/bin/echo" +path_check = "true" +supports_acp = true +acp_command = "/bin/echo" +`) + + cfg, err := loadCityConfig(cityDir) + if err != nil { + t.Fatalf("loadCityConfig: %v", err) + } + cfg.PackMCPDir = filepath.Join(cityDir, "mcp") + if err := os.MkdirAll(cfg.PackMCPDir, 0o755); err != nil { + t.Fatalf("MkdirAll(mcp): %v", err) + } + if err := os.WriteFile(filepath.Join(cfg.PackMCPDir, "identity.template.toml"), []byte(` +name = "identity" +command = "/bin/mcp" +args = ["{{.AgentName}}"] +`), 0o644); err != nil { + t.Fatalf("WriteFile(mcp): %v", err) + } + + info := session.Info{ + Template: "custom-acp", + Command: "/bin/echo", + Provider: "custom-acp", + WorkDir: cityDir, + } + resolved, _, _ := resolveWorkerRuntimeProviderWithConfig(cfg, info, "provider") + mcpServers, err := resolvedRuntimeMCPServersWithConfig( + cityDir, + cfg, + info.Alias, + info.Template, + info.Provider, + info.WorkDir, + "acp", + nil, + ) + if err != nil { + t.Fatalf("resolvedRuntimeMCPServersWithConfig: %v", err) + } + startedHash := runtime.CoreFingerprint(runtime.Config{ + Command: resolved.ACPCommandString(), + Env: resolved.Env, + MCPServers: mcpServers, + }) + + runtimeCfg, err := resolvedWorkerRuntimeWithConfigAndMetadata(cityDir, cfg, info, "provider", map[string]string{ + "started_config_hash": startedHash, + }) + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfigAndMetadata: %v", err) + } + if runtimeCfg == nil { + t.Fatal("resolvedWorkerRuntimeWithConfigAndMetadata() = nil") + } + if len(runtimeCfg.Hints.MCPServers) != 1 { + t.Fatalf("len(runtimeCfg.Hints.MCPServers) = %d, want 1", len(runtimeCfg.Hints.MCPServers)) + } +} + func TestResolvedWorkerRuntimeWithConfigKeepsDefaultTransportWithoutExplicitACPTemplate(t *testing.T) { cityDir := t.TempDir() writePhase0InterfaceCity(t, cityDir, `[workspace] diff --git a/internal/api/handler_session_chat_test.go b/internal/api/handler_session_chat_test.go index 765b1503dd..b429f0e6eb 100644 --- a/internal/api/handler_session_chat_test.go +++ b/internal/api/handler_session_chat_test.go @@ -6,6 +6,7 @@ import ( "strings" "testing" + "github.com/gastownhall/gascity/internal/beads" "github.com/gastownhall/gascity/internal/config" "github.com/gastownhall/gascity/internal/runtime" sessionauto "github.com/gastownhall/gascity/internal/runtime/auto" @@ -459,6 +460,73 @@ func TestLegacyACPTransportAmbiguousWithSameCommand(t *testing.T) { } } +func TestBuildSessionResumeUsesStartedConfigHashForLegacyProviderACPWithSameCommand(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg = &config.City{ + Workspace: config.Workspace{Name: "test-city"}, + Providers: map[string]config.ProviderSpec{ + "custom-acp": { + DisplayName: "Custom ACP", + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + }, + }, + } + fs.cfg.PackMCPDir = filepath.Join(fs.cityPath, "mcp") + if err := os.MkdirAll(fs.cfg.PackMCPDir, 0o755); err != nil { + t.Fatalf("MkdirAll(mcp): %v", err) + } + if err := os.WriteFile(filepath.Join(fs.cfg.PackMCPDir, "identity.template.toml"), []byte(` +name = "identity" +command = "/bin/mcp" +args = ["{{.AgentName}}"] +`), 0o644); err != nil { + t.Fatalf("WriteFile(mcp): %v", err) + } + + srv := New(fs) + resolved, err := srv.resolveBareProvider("custom-acp") + if err != nil { + t.Fatalf("resolveBareProvider: %v", err) + } + mcpServers, err := srv.sessionMCPServers("custom-acp", "custom-acp", "custom-acp", fs.cityPath, "acp", "provider") + if err != nil { + t.Fatalf("sessionMCPServers: %v", err) + } + startedHash := runtime.CoreFingerprint(runtime.Config{ + Command: resolved.ACPCommandString(), + Env: resolved.Env, + MCPServers: mcpServers, + }) + bead, err := fs.cityBeadStore.Create(beads.Bead{ + Type: "session", + Metadata: map[string]string{ + "mc_session_kind": "provider", + "started_config_hash": startedHash, + }, + }) + if err != nil { + t.Fatalf("Create(session bead): %v", err) + } + + _, hints, err := srv.buildSessionResume(session.Info{ + ID: bead.ID, + Template: "custom-acp", + Command: "/bin/echo", + Provider: "custom-acp", + WorkDir: fs.cityPath, + }) + if err != nil { + t.Fatalf("buildSessionResume: %v", err) + } + if len(hints.MCPServers) != 1 { + t.Fatalf("len(hints.MCPServers) = %d, want 1", len(hints.MCPServers)) + } +} + func TestBuildSessionResumeUsesStoredACPCommandForLegacyTemplateSessionWithoutTransportMetadata(t *testing.T) { supportsACP := true fs := newSessionFakeState(t) diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index a31a6c11d9..3d9fc75686 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -412,6 +412,55 @@ func legacyACPTransportAmbiguous(resolved *config.ResolvedProvider, configuredTr return storedCommand == "" || sameRuntimeCommandExecutable(storedCommand, defaultCommand) } +func (s *Server) startedConfigHashProvesACPTransport( + info session.Info, + metadata map[string]string, + resolved *config.ResolvedProvider, + workDir, + configuredTransport, + sessionKind string, +) bool { + if strings.TrimSpace(configuredTransport) != "acp" || resolved == nil || metadata == nil { + return false + } + startedHash := strings.TrimSpace(metadata["started_config_hash"]) + if startedHash == "" { + return false + } + acpCommand, err := s.resolvedSessionRuntimeCommand(resolved, "acp", info.Command, metadata) + if err != nil { + acpCommand = fallbackSessionRuntimeCommand(resolved, "acp", info.Command) + } + defaultCommand, err := s.resolvedSessionRuntimeCommand(resolved, "", info.Command, metadata) + if err != nil { + defaultCommand = fallbackSessionRuntimeCommand(resolved, "", info.Command) + } + mcpServers, err := s.sessionMCPServers( + info.Template, + firstNonEmptyString(info.Provider, resolved.Name), + resumeSessionIdentity(info, metadata), + firstNonEmptyString(workDir, info.WorkDir), + "acp", + sessionKind, + ) + if err != nil { + return false + } + acpHash := runtime.CoreFingerprint(runtime.Config{ + Command: acpCommand, + Env: resolved.Env, + MCPServers: mcpServers, + }) + defaultHash := runtime.CoreFingerprint(runtime.Config{ + Command: defaultCommand, + Env: resolved.Env, + }) + if acpHash == defaultHash { + return false + } + return startedHash == acpHash +} + func resolvedSessionTransport(info session.Info, resolved *config.ResolvedProvider, configuredTransport string, metadata map[string]string, allowConfiguredTransportFallback bool) string { if transport := strings.TrimSpace(info.Transport); transport != "" { return transport @@ -431,33 +480,43 @@ func resolvedSessionTransport(info session.Info, resolved *config.ResolvedProvid func (s *Server) resolveSessionRuntimeWithMetadata(info session.Info, metadata map[string]string) (*config.ResolvedProvider, string, string, bool) { kind := s.sessionKind(info.ID) cfg := s.state.Config() + var ( + resolved *config.ResolvedProvider + workDir string + configuredTransport string + ) if kind != "provider" && cfg != nil { if agentCfg, ok := resolveSessionTemplateAgent(cfg, info.Template); ok { - resolved, err := config.ResolveProvider(&agentCfg, &cfg.Workspace, cfg.Providers, exec.LookPath) + candidate, err := config.ResolveProvider(&agentCfg, &cfg.Workspace, cfg.Providers, exec.LookPath) if err == nil { - workDir, workDirErr := s.resolveSessionWorkDir(agentCfg, agentCfg.QualifiedName()) + candidateWorkDir, workDirErr := s.resolveSessionWorkDir(agentCfg, agentCfg.QualifiedName()) if workDirErr == nil { + resolved = candidate + workDir = candidateWorkDir if info.WorkDir != "" { workDir = info.WorkDir } - configuredTransport := config.ResolveSessionCreateTransport(agentCfg.Session, resolved) - transport := resolvedSessionTransport(info, resolved, configuredTransport, metadata, false) - return resolved, workDir, transport, transport == "" && legacyACPTransportAmbiguous(resolved, configuredTransport, info.Command, metadata) + configuredTransport = config.ResolveSessionCreateTransport(agentCfg.Session, resolved) } } } } - - resolved, err := s.resolveBareProvider(info.Template) - if err != nil { - return nil, "", "", false - } - workDir := info.WorkDir - if workDir == "" { - workDir = s.state.CityPath() + if resolved == nil { + candidate, err := s.resolveBareProvider(info.Template) + if err != nil { + return nil, "", "", false + } + resolved = candidate + workDir = info.WorkDir + if workDir == "" { + workDir = s.state.CityPath() + } + configuredTransport = resolved.ProviderSessionCreateTransport() } - configuredTransport := resolved.ProviderSessionCreateTransport() transport := resolvedSessionTransport(info, resolved, configuredTransport, metadata, false) + if transport == "" && s.startedConfigHashProvesACPTransport(info, metadata, resolved, workDir, configuredTransport, kind) { + transport = "acp" + } return resolved, workDir, transport, transport == "" && legacyACPTransportAmbiguous(resolved, configuredTransport, info.Command, metadata) } From 1433b18785b64821884db0eb24e763448ac5bf7d Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 07:24:23 +0000 Subject: [PATCH 81/85] fix: scrub stored mcp resume snapshots --- cmd/gc/worker_handle.go | 3 - cmd/gc/worker_handle_test.go | 78 ++++++++++++++++++ internal/api/session_runtime.go | 3 - internal/api/worker_factory_test.go | 78 ++++++++++++++++++ internal/session/mcp_metadata.go | 111 +++++++++++++++++++++++++- internal/session/mcp_metadata_test.go | 26 +++++- 6 files changed, 288 insertions(+), 11 deletions(-) diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index 65fa226865..0443880573 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -183,9 +183,6 @@ func resumeRuntimeMCPServersWithConfig( if decodeErr != nil { return nil, fmt.Errorf("decoding stored MCP snapshot: %w", decodeErr) } - if session.StoredMCPSnapshotContainsRedactions(stored) { - return nil, fmt.Errorf("loading effective MCP: %w; stored snapshot contains redacted secrets", err) - } return stored, nil } diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index bcbca44ec2..60d93be63b 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -1344,6 +1344,84 @@ command = [broken } } +func TestResolvedWorkerRuntimeWithConfigFallsBackToRedactedStoredMCPServersWhenCatalogBreaks(t *testing.T) { + cityDir := t.TempDir() + writePhase0InterfaceCity(t, cityDir, `[workspace] +name = "test-city" + +[beads] +provider = "file" + +[[agent]] +name = "ant" +dir = "myrig" +provider = "stub" +session = "acp" +work_dir = ".gc/worktrees/{{.Rig}}/ants/{{.AgentBase}}" +min_active_sessions = 0 +max_active_sessions = 4 + +[providers.stub] +command = "/bin/echo" +supports_acp = true +acp_command = "/bin/echo" +acp_args = ["acp"] +`) + writeCatalogFile(t, cityDir, "mcp/identity.template.toml", ` +name = "identity" +command = [broken +`) + + cfg, err := loadCityConfig(cityDir) + if err != nil { + t.Fatalf("loadCityConfig: %v", err) + } + + workDir := filepath.Join(cityDir, ".gc", "worktrees", "myrig", "ants", "ant") + metadata, err := session.WithStoredMCPMetadata(nil, "myrig/ant-adhoc-123", []runtime.MCPServerConfig{{ + Name: "identity", + Transport: runtime.MCPTransportHTTP, + Command: "/bin/mcp", + Args: []string{"--api-key", "super-secret"}, + Env: map[string]string{ + "API_TOKEN": "super-secret", + }, + URL: "https://user:pass@example.invalid/mcp?token=abc123", + Headers: map[string]string{ + "Authorization": "Bearer secret", + }, + }}) + if err != nil { + t.Fatalf("WithStoredMCPMetadata: %v", err) + } + + resolved, err := resolvedWorkerRuntimeWithConfigAndMetadata(cityDir, cfg, session.Info{ + Template: "myrig/ant", + Alias: "ant", + AgentName: "myrig/ant-adhoc-123", + Transport: "acp", + WorkDir: workDir, + }, "", metadata) + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfigAndMetadata: %v", err) + } + if resolved == nil { + t.Fatal("resolvedWorkerRuntimeWithConfigAndMetadata() = nil") + } + if len(resolved.Hints.MCPServers) != 1 { + t.Fatalf("Hints.MCPServers len = %d, want 1", len(resolved.Hints.MCPServers)) + } + if got, want := resolved.Hints.MCPServers[0].Args[1], "__redacted__"; got != want { + t.Fatalf("Args[1] = %q, want %q", got, want) + } + if got, want := resolved.Hints.MCPServers[0].Env["API_TOKEN"], "__redacted__"; got != want { + t.Fatalf("Env[API_TOKEN] = %q, want %q", got, want) + } + if got, want := resolved.Hints.MCPServers[0].Headers["Authorization"], "__redacted__"; got != want { + t.Fatalf("Headers[Authorization] = %q, want %q", got, want) + } +} + func TestWorkerSessionRuntimeResolverWithConfigFallsBackToProviderNameWhenResolvedCommandMissing(t *testing.T) { cfg := &config.City{ Workspace: config.Workspace{Name: "test-city"}, diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index 3d9fc75686..ca32fce3c6 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -77,9 +77,6 @@ func (s *Server) resumeSessionMCPServers(info session.Info, metadata map[string] if decodeErr != nil { return nil, fmt.Errorf("decoding stored MCP snapshot: %w", decodeErr) } - if session.StoredMCPSnapshotContainsRedactions(stored) { - return nil, fmt.Errorf("loading effective MCP: %w; stored snapshot contains redacted secrets", err) - } return stored, nil } diff --git a/internal/api/worker_factory_test.go b/internal/api/worker_factory_test.go index 8ef4c78aa8..d4a53ba96e 100644 --- a/internal/api/worker_factory_test.go +++ b/internal/api/worker_factory_test.go @@ -319,6 +319,84 @@ command = [broken } } +func TestResolveWorkerSessionRuntimeFallsBackToRedactedStoredMCPServersWhenCatalogBreaks(t *testing.T) { + fs := newSessionFakeState(t) + fs.cfg.Agents = []config.Agent{{ + Name: "ant", + Dir: "myrig", + Provider: "resolved-worker", + Session: "acp", + WorkDir: ".gc/worktrees/{{.Rig}}/ants/{{.AgentBase}}", + MinActiveSessions: intPtr(0), + MaxActiveSessions: intPtr(4), + }} + supportsACP := true + fs.cfg.Providers["resolved-worker"] = config.ProviderSpec{ + DisplayName: "Resolved Worker", + Command: "/bin/echo", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + fs.cfg.PackMCPDir = filepath.Join(fs.cityPath, "mcp") + if err := os.MkdirAll(fs.cfg.PackMCPDir, 0o755); err != nil { + t.Fatalf("MkdirAll(mcp): %v", err) + } + if err := os.WriteFile(filepath.Join(fs.cfg.PackMCPDir, "identity.template.toml"), []byte(` +name = "identity" +command = [broken +`), 0o644); err != nil { + t.Fatalf("WriteFile(mcp): %v", err) + } + + metadata, err := session.WithStoredMCPMetadata(nil, "myrig/ant-adhoc-123", []runtime.MCPServerConfig{{ + Name: "identity", + Transport: runtime.MCPTransportHTTP, + Command: "/bin/mcp", + Args: []string{"--api-key", "super-secret"}, + Env: map[string]string{ + "API_TOKEN": "super-secret", + }, + URL: "https://user:pass@example.invalid/mcp?token=abc123", + Headers: map[string]string{ + "Authorization": "Bearer secret", + }, + }}) + if err != nil { + t.Fatalf("WithStoredMCPMetadata: %v", err) + } + + srv := New(fs) + info := session.Info{ + ID: "sess-1", + Template: "myrig/ant", + Alias: "ant", + AgentName: "myrig/ant-adhoc-123", + Transport: "acp", + WorkDir: filepath.Join(fs.cityPath, ".gc", "worktrees", "myrig", "ants", "ant"), + } + + runtimeCfg, err := srv.resolveWorkerSessionRuntimeWithMetadata(info, "", metadata) + if err != nil { + t.Fatalf("resolveWorkerSessionRuntimeWithMetadata: %v", err) + } + if runtimeCfg == nil { + t.Fatal("resolveWorkerSessionRuntimeWithMetadata() = nil") + } + if len(runtimeCfg.Hints.MCPServers) != 1 { + t.Fatalf("Hints.MCPServers len = %d, want 1", len(runtimeCfg.Hints.MCPServers)) + } + if got, want := runtimeCfg.Hints.MCPServers[0].Args[1], "__redacted__"; got != want { + t.Fatalf("Args[1] = %q, want %q", got, want) + } + if got, want := runtimeCfg.Hints.MCPServers[0].Env["API_TOKEN"], "__redacted__"; got != want { + t.Fatalf("Env[API_TOKEN] = %q, want %q", got, want) + } + if got, want := runtimeCfg.Hints.MCPServers[0].Headers["Authorization"], "__redacted__"; got != want { + t.Fatalf("Headers[Authorization] = %q, want %q", got, want) + } +} + func TestResolveWorkerSessionRuntimeFallsBackToStoredCommandWhenTemplateOverridesInvalid(t *testing.T) { fs := newSessionFakeState(t) fs.cfg.Providers["test-agent"] = config.ProviderSpec{ diff --git a/internal/session/mcp_metadata.go b/internal/session/mcp_metadata.go index 6634d6d5eb..8a0ecb7af5 100644 --- a/internal/session/mcp_metadata.go +++ b/internal/session/mcp_metadata.go @@ -3,6 +3,7 @@ package session import ( "encoding/json" "fmt" + "net/url" "strings" "github.com/gastownhall/gascity/internal/runtime" @@ -49,11 +50,13 @@ func DecodeMCPServersSnapshot(raw string) ([]runtime.MCPServerConfig, error) { } // StoredMCPSnapshotContainsRedactions reports whether a decoded persisted MCP -// snapshot contains redacted secret values and therefore cannot be used as a -// complete runtime fallback. +// snapshot contains redacted secret placeholders. func StoredMCPSnapshotContainsRedactions(servers []runtime.MCPServerConfig) bool { for _, server := range servers { - if snapshotMapContainsRedactions(server.Env) || snapshotMapContainsRedactions(server.Headers) { + if snapshotMapContainsRedactions(server.Env) || + snapshotMapContainsRedactions(server.Headers) || + snapshotArgsContainRedactions(server.Args) || + strings.Contains(server.URL, redactedMCPSnapshotValue) { return true } } @@ -85,12 +88,52 @@ func WithStoredMCPMetadata(meta map[string]string, identity string, servers []ru func normalizeMCPServersSnapshotForMetadata(servers []runtime.MCPServerConfig) []runtime.MCPServerConfig { normalized := runtime.NormalizeMCPServerConfigs(servers) for i := range normalized { + normalized[i].Args = redactMCPMetadataArgs(normalized[i].Args) normalized[i].Env = redactMCPMetadataMap(normalized[i].Env) + normalized[i].URL = redactMCPMetadataURL(normalized[i].URL) normalized[i].Headers = redactMCPMetadataMap(normalized[i].Headers) } return normalized } +func redactMCPMetadataArgs(args []string) []string { + if len(args) == 0 { + return nil + } + out := make([]string, 0, len(args)) + redactNext := false + for _, arg := range args { + if redactNext { + out = append(out, redactedMCPSnapshotValue) + redactNext = false + continue + } + if isSensitiveMCPMetadataValue(arg) { + out = append(out, redactedMCPSnapshotValue) + continue + } + if redactedURL := redactMCPMetadataURL(arg); redactedURL != arg { + out = append(out, redactedURL) + continue + } + if key, value, ok := strings.Cut(arg, "="); ok && isSensitiveMCPMetadataToken(key) { + if strings.TrimSpace(value) == "" { + out = append(out, key+"=") + } else { + out = append(out, key+"="+redactedMCPSnapshotValue) + } + continue + } + if isSensitiveMCPMetadataToken(arg) && strings.HasPrefix(strings.TrimSpace(arg), "-") { + out = append(out, arg) + redactNext = true + continue + } + out = append(out, arg) + } + return out +} + func redactMCPMetadataMap(in map[string]string) map[string]string { if len(in) == 0 { return nil @@ -102,6 +145,37 @@ func redactMCPMetadataMap(in map[string]string) map[string]string { return out } +func redactMCPMetadataURL(raw string) string { + raw = strings.TrimSpace(raw) + if raw == "" { + return "" + } + parsed, err := url.Parse(raw) + if err != nil { + return raw + } + changed := false + if parsed.User != nil { + if _, hasPassword := parsed.User.Password(); hasPassword { + parsed.User = url.UserPassword(redactedMCPSnapshotValue, redactedMCPSnapshotValue) + } else { + parsed.User = url.User(redactedMCPSnapshotValue) + } + changed = true + } + if query := parsed.Query(); len(query) > 0 { + for key := range query { + query.Set(key, redactedMCPSnapshotValue) + } + parsed.RawQuery = query.Encode() + changed = true + } + if !changed { + return raw + } + return parsed.String() +} + func snapshotMapContainsRedactions(in map[string]string) bool { for _, value := range in { if value == redactedMCPSnapshotValue { @@ -110,3 +184,34 @@ func snapshotMapContainsRedactions(in map[string]string) bool { } return false } + +func snapshotArgsContainRedactions(args []string) bool { + for _, arg := range args { + if strings.Contains(arg, redactedMCPSnapshotValue) { + return true + } + } + return false +} + +func isSensitiveMCPMetadataToken(value string) bool { + value = strings.ToLower(strings.TrimSpace(value)) + return strings.Contains(value, "token") || + strings.Contains(value, "secret") || + strings.Contains(value, "password") || + strings.Contains(value, "passwd") || + strings.Contains(value, "authorization") || + strings.Contains(value, "auth") || + strings.Contains(value, "bearer") || + strings.Contains(value, "cookie") || + strings.Contains(value, "api-key") || + strings.Contains(value, "apikey") +} + +func isSensitiveMCPMetadataValue(value string) bool { + value = strings.ToLower(strings.TrimSpace(value)) + return strings.HasPrefix(value, "authorization:") || + strings.HasPrefix(value, "bearer ") || + strings.HasPrefix(value, "basic ") || + strings.HasPrefix(value, "token ") +} diff --git a/internal/session/mcp_metadata_test.go b/internal/session/mcp_metadata_test.go index 1ba177180d..c9547a3e7f 100644 --- a/internal/session/mcp_metadata_test.go +++ b/internal/session/mcp_metadata_test.go @@ -11,11 +11,18 @@ func TestEncodeMCPServersSnapshotRedactsSecrets(t *testing.T) { Name: "remote", Transport: runtime.MCPTransportHTTP, Command: "/bin/mcp", - Args: []string{"--serve"}, + Args: []string{ + "--serve", + "--api-key", + "super-secret", + "--token=abc123", + "Authorization: Bearer secret", + "https://user:pass@example.invalid/mcp?token=abc123", + }, Env: map[string]string{ "API_TOKEN": "super-secret", }, - URL: "https://example.invalid/mcp", + URL: "https://user:pass@example.invalid/mcp?token=abc123", Headers: map[string]string{ "Authorization": "Bearer secret", }, @@ -40,6 +47,21 @@ func TestEncodeMCPServersSnapshotRedactsSecrets(t *testing.T) { if got, want := servers[0].Args[0], "--serve"; got != want { t.Fatalf("Args[0] = %q, want %q", got, want) } + if got, want := servers[0].Args[2], redactedMCPSnapshotValue; got != want { + t.Fatalf("Args[2] = %q, want %q", got, want) + } + if got, want := servers[0].Args[3], "--token="+redactedMCPSnapshotValue; got != want { + t.Fatalf("Args[3] = %q, want %q", got, want) + } + if got, want := servers[0].Args[4], redactedMCPSnapshotValue; got != want { + t.Fatalf("Args[4] = %q, want %q", got, want) + } + if got, want := servers[0].Args[5], "https://__redacted__:__redacted__@example.invalid/mcp?token="+redactedMCPSnapshotValue; got != want { + t.Fatalf("Args[5] = %q, want %q", got, want) + } + if got, want := servers[0].URL, "https://__redacted__:__redacted__@example.invalid/mcp?token="+redactedMCPSnapshotValue; got != want { + t.Fatalf("URL = %q, want %q", got, want) + } if !StoredMCPSnapshotContainsRedactions(servers) { t.Fatal("StoredMCPSnapshotContainsRedactions() = false, want true") } From 468ebc02dd7c6552c5fda331132a1fcfd543ef3a Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 07:34:00 +0000 Subject: [PATCH 82/85] fix: cache runtime mcp snapshots for resume --- cmd/gc/session_lifecycle_parallel.go | 5 ++ cmd/gc/worker_handle.go | 10 +++ cmd/gc/worker_handle_test.go | 17 +++-- internal/api/session_runtime.go | 10 +++ internal/api/worker_factory_test.go | 16 +++-- internal/session/mcp_metadata_test.go | 37 ++++++++++ internal/session/mcp_state.go | 98 ++++++++++++++++++++++++--- 7 files changed, 173 insertions(+), 20 deletions(-) diff --git a/cmd/gc/session_lifecycle_parallel.go b/cmd/gc/session_lifecycle_parallel.go index ebf2be8e83..6ae7405544 100644 --- a/cmd/gc/session_lifecycle_parallel.go +++ b/cmd/gc/session_lifecycle_parallel.go @@ -699,6 +699,11 @@ func commitStartResultTraced( if storedMCPSnapshot != "" || session.Metadata[sessionpkg.MCPServersSnapshotMetadataKey] != "" { metadata[sessionpkg.MCPServersSnapshotMetadataKey] = storedMCPSnapshot } + if err := sessionpkg.PersistRuntimeMCPServersSnapshot(result.prepared.cfg.Env["GC_CITY_PATH"], session.ID, result.prepared.cfg.MCPServers); err != nil { + fmt.Fprintf(stderr, "session reconciler: storing runtime MCP snapshot for %s: %v\n", name, err) //nolint:errcheck + logLifecycleOutcome(stderr, "start", wave, name, tp.TemplateName, "runtime_mcp_snapshot_failed", result.started, result.finished, err) + return false + } if result.prepared.candidate.tp.IsACP || session.Metadata[sessionpkg.MCPIdentityMetadataKey] != "" || session.Metadata[sessionpkg.MCPServersSnapshotMetadataKey] != "" { diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index 0443880573..e9a352fce2 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -179,10 +179,20 @@ func resumeRuntimeMCPServersWithConfig( if err == nil { return mcpServers, nil } + runtimeSnapshot, loadErr := session.LoadRuntimeMCPServersSnapshot(cityPath, info.ID) + if loadErr != nil { + return nil, loadErr + } + if len(runtimeSnapshot) > 0 { + return runtimeSnapshot, nil + } stored, decodeErr := session.DecodeMCPServersSnapshot(resumeMeta[session.MCPServersSnapshotMetadataKey]) if decodeErr != nil { return nil, fmt.Errorf("decoding stored MCP snapshot: %w", decodeErr) } + if session.StoredMCPSnapshotContainsRedactions(stored) { + return nil, nil + } return stored, nil } diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index 60d93be63b..5f56bd6130 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -1344,7 +1344,7 @@ command = [broken } } -func TestResolvedWorkerRuntimeWithConfigFallsBackToRedactedStoredMCPServersWhenCatalogBreaks(t *testing.T) { +func TestResolvedWorkerRuntimeWithConfigFallsBackToRuntimeMCPServersSnapshotWhenCatalogBreaks(t *testing.T) { cityDir := t.TempDir() writePhase0InterfaceCity(t, cityDir, `[workspace] name = "test-city" @@ -1378,7 +1378,7 @@ command = [broken } workDir := filepath.Join(cityDir, ".gc", "worktrees", "myrig", "ants", "ant") - metadata, err := session.WithStoredMCPMetadata(nil, "myrig/ant-adhoc-123", []runtime.MCPServerConfig{{ + servers := []runtime.MCPServerConfig{{ Name: "identity", Transport: runtime.MCPTransportHTTP, Command: "/bin/mcp", @@ -1390,12 +1390,17 @@ command = [broken Headers: map[string]string{ "Authorization": "Bearer secret", }, - }}) + }} + metadata, err := session.WithStoredMCPMetadata(nil, "myrig/ant-adhoc-123", servers) if err != nil { t.Fatalf("WithStoredMCPMetadata: %v", err) } + if err := session.PersistRuntimeMCPServersSnapshot(cityDir, "sess-1", servers); err != nil { + t.Fatalf("PersistRuntimeMCPServersSnapshot: %v", err) + } resolved, err := resolvedWorkerRuntimeWithConfigAndMetadata(cityDir, cfg, session.Info{ + ID: "sess-1", Template: "myrig/ant", Alias: "ant", AgentName: "myrig/ant-adhoc-123", @@ -1411,13 +1416,13 @@ command = [broken if len(resolved.Hints.MCPServers) != 1 { t.Fatalf("Hints.MCPServers len = %d, want 1", len(resolved.Hints.MCPServers)) } - if got, want := resolved.Hints.MCPServers[0].Args[1], "__redacted__"; got != want { + if got, want := resolved.Hints.MCPServers[0].Args[1], "super-secret"; got != want { t.Fatalf("Args[1] = %q, want %q", got, want) } - if got, want := resolved.Hints.MCPServers[0].Env["API_TOKEN"], "__redacted__"; got != want { + if got, want := resolved.Hints.MCPServers[0].Env["API_TOKEN"], "super-secret"; got != want { t.Fatalf("Env[API_TOKEN] = %q, want %q", got, want) } - if got, want := resolved.Hints.MCPServers[0].Headers["Authorization"], "__redacted__"; got != want { + if got, want := resolved.Hints.MCPServers[0].Headers["Authorization"], "Bearer secret"; got != want { t.Fatalf("Headers[Authorization] = %q, want %q", got, want) } } diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index ca32fce3c6..3f89f6c931 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -73,10 +73,20 @@ func (s *Server) resumeSessionMCPServers(info session.Info, metadata map[string] if err == nil { return mcpServers, nil } + runtimeSnapshot, loadErr := session.LoadRuntimeMCPServersSnapshot(s.state.CityPath(), info.ID) + if loadErr != nil { + return nil, loadErr + } + if len(runtimeSnapshot) > 0 { + return runtimeSnapshot, nil + } stored, decodeErr := session.DecodeMCPServersSnapshot(metadata[session.MCPServersSnapshotMetadataKey]) if decodeErr != nil { return nil, fmt.Errorf("decoding stored MCP snapshot: %w", decodeErr) } + if session.StoredMCPSnapshotContainsRedactions(stored) { + return nil, nil + } return stored, nil } diff --git a/internal/api/worker_factory_test.go b/internal/api/worker_factory_test.go index d4a53ba96e..f7d067c135 100644 --- a/internal/api/worker_factory_test.go +++ b/internal/api/worker_factory_test.go @@ -319,7 +319,7 @@ command = [broken } } -func TestResolveWorkerSessionRuntimeFallsBackToRedactedStoredMCPServersWhenCatalogBreaks(t *testing.T) { +func TestResolveWorkerSessionRuntimeFallsBackToRuntimeMCPServersSnapshotWhenCatalogBreaks(t *testing.T) { fs := newSessionFakeState(t) fs.cfg.Agents = []config.Agent{{ Name: "ant", @@ -349,7 +349,7 @@ command = [broken t.Fatalf("WriteFile(mcp): %v", err) } - metadata, err := session.WithStoredMCPMetadata(nil, "myrig/ant-adhoc-123", []runtime.MCPServerConfig{{ + servers := []runtime.MCPServerConfig{{ Name: "identity", Transport: runtime.MCPTransportHTTP, Command: "/bin/mcp", @@ -361,10 +361,14 @@ command = [broken Headers: map[string]string{ "Authorization": "Bearer secret", }, - }}) + }} + metadata, err := session.WithStoredMCPMetadata(nil, "myrig/ant-adhoc-123", servers) if err != nil { t.Fatalf("WithStoredMCPMetadata: %v", err) } + if err := session.PersistRuntimeMCPServersSnapshot(fs.cityPath, "sess-1", servers); err != nil { + t.Fatalf("PersistRuntimeMCPServersSnapshot: %v", err) + } srv := New(fs) info := session.Info{ @@ -386,13 +390,13 @@ command = [broken if len(runtimeCfg.Hints.MCPServers) != 1 { t.Fatalf("Hints.MCPServers len = %d, want 1", len(runtimeCfg.Hints.MCPServers)) } - if got, want := runtimeCfg.Hints.MCPServers[0].Args[1], "__redacted__"; got != want { + if got, want := runtimeCfg.Hints.MCPServers[0].Args[1], "super-secret"; got != want { t.Fatalf("Args[1] = %q, want %q", got, want) } - if got, want := runtimeCfg.Hints.MCPServers[0].Env["API_TOKEN"], "__redacted__"; got != want { + if got, want := runtimeCfg.Hints.MCPServers[0].Env["API_TOKEN"], "super-secret"; got != want { t.Fatalf("Env[API_TOKEN] = %q, want %q", got, want) } - if got, want := runtimeCfg.Hints.MCPServers[0].Headers["Authorization"], "__redacted__"; got != want { + if got, want := runtimeCfg.Hints.MCPServers[0].Headers["Authorization"], "Bearer secret"; got != want { t.Fatalf("Headers[Authorization] = %q, want %q", got, want) } } diff --git a/internal/session/mcp_metadata_test.go b/internal/session/mcp_metadata_test.go index c9547a3e7f..1a4cecb818 100644 --- a/internal/session/mcp_metadata_test.go +++ b/internal/session/mcp_metadata_test.go @@ -66,3 +66,40 @@ func TestEncodeMCPServersSnapshotRedactsSecrets(t *testing.T) { t.Fatal("StoredMCPSnapshotContainsRedactions() = false, want true") } } + +func TestRuntimeMCPServersSnapshotRoundTrip(t *testing.T) { + cityPath := t.TempDir() + servers := []runtime.MCPServerConfig{{ + Name: "remote", + Transport: runtime.MCPTransportHTTP, + Command: "/bin/mcp", + Args: []string{"--api-key", "super-secret"}, + Env: map[string]string{ + "API_TOKEN": "super-secret", + }, + URL: "https://user:pass@example.invalid/mcp?token=abc123", + Headers: map[string]string{ + "Authorization": "Bearer secret", + }, + }} + if err := PersistRuntimeMCPServersSnapshot(cityPath, "sess-1", servers); err != nil { + t.Fatalf("PersistRuntimeMCPServersSnapshot: %v", err) + } + + loaded, err := LoadRuntimeMCPServersSnapshot(cityPath, "sess-1") + if err != nil { + t.Fatalf("LoadRuntimeMCPServersSnapshot: %v", err) + } + if len(loaded) != 1 { + t.Fatalf("len(loaded) = %d, want 1", len(loaded)) + } + if got, want := loaded[0].Args[1], "super-secret"; got != want { + t.Fatalf("Args[1] = %q, want %q", got, want) + } + if got, want := loaded[0].Env["API_TOKEN"], "super-secret"; got != want { + t.Fatalf("Env[API_TOKEN] = %q, want %q", got, want) + } + if got, want := loaded[0].Headers["Authorization"], "Bearer secret"; got != want { + t.Fatalf("Headers[Authorization] = %q, want %q", got, want) + } +} diff --git a/internal/session/mcp_state.go b/internal/session/mcp_state.go index 9bb6d502f6..79cb4efe29 100644 --- a/internal/session/mcp_state.go +++ b/internal/session/mcp_state.go @@ -1,10 +1,14 @@ package session import ( + "encoding/json" "fmt" + "os" + "path/filepath" "strings" "github.com/gastownhall/gascity/internal/beads" + "github.com/gastownhall/gascity/internal/citylayout" "github.com/gastownhall/gascity/internal/runtime" ) @@ -17,17 +21,95 @@ func (m *Manager) syncStoredMCPServers(id string, b *beads.Bead, servers []runti if b != nil && b.Metadata != nil { current = strings.TrimSpace(b.Metadata[MCPServersSnapshotMetadataKey]) } - if current == snapshot { - return nil + if current != snapshot { + if err := m.store.SetMetadata(id, MCPServersSnapshotMetadataKey, snapshot); err != nil { + return fmt.Errorf("storing MCP server snapshot: %w", err) + } + if b != nil { + if b.Metadata == nil { + b.Metadata = make(map[string]string) + } + b.Metadata[MCPServersSnapshotMetadataKey] = snapshot + } + } + if err := PersistRuntimeMCPServersSnapshot(m.cityPath, id, servers); err != nil { + return fmt.Errorf("storing runtime MCP server snapshot: %w", err) } - if err := m.store.SetMetadata(id, MCPServersSnapshotMetadataKey, snapshot); err != nil { - return fmt.Errorf("storing MCP server snapshot: %w", err) + return nil +} + +// PersistRuntimeMCPServersSnapshot stores the full normalized MCP server +// snapshot for a session in the controller-local runtime cache. The cache is +// not exposed on the bead metadata wire and is used only as a degraded resume +// fallback when the live MCP catalog cannot be materialized. +func PersistRuntimeMCPServersSnapshot(cityPath, sessionID string, servers []runtime.MCPServerConfig) error { + path := runtimeMCPServersSnapshotPath(cityPath, sessionID) + if path == "" { + return nil } - if b != nil { - if b.Metadata == nil { - b.Metadata = make(map[string]string) + if len(servers) == 0 { + if err := os.Remove(path); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("remove runtime MCP snapshot: %w", err) } - b.Metadata[MCPServersSnapshotMetadataKey] = snapshot + return nil + } + data, err := json.Marshal(runtime.NormalizeMCPServerConfigs(servers)) + if err != nil { + return fmt.Errorf("marshal runtime MCP snapshot: %w", err) + } + if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { + return fmt.Errorf("mkdir runtime MCP snapshot dir: %w", err) + } + temp, err := os.CreateTemp(filepath.Dir(path), filepath.Base(path)+".tmp-*") + if err != nil { + return fmt.Errorf("create runtime MCP snapshot temp file: %w", err) + } + tempPath := temp.Name() + defer func() { _ = os.Remove(tempPath) }() + if err := temp.Chmod(0o600); err != nil { + _ = temp.Close() + return fmt.Errorf("chmod runtime MCP snapshot temp file: %w", err) + } + if _, err := temp.Write(data); err != nil { + _ = temp.Close() + return fmt.Errorf("write runtime MCP snapshot: %w", err) + } + if err := temp.Close(); err != nil { + return fmt.Errorf("close runtime MCP snapshot temp file: %w", err) + } + if err := os.Rename(tempPath, path); err != nil { + return fmt.Errorf("rename runtime MCP snapshot: %w", err) } return nil } + +// LoadRuntimeMCPServersSnapshot loads the full normalized MCP server snapshot +// for a session from the controller-local runtime cache. It returns nil, nil +// when no cache file exists. +func LoadRuntimeMCPServersSnapshot(cityPath, sessionID string) ([]runtime.MCPServerConfig, error) { + path := runtimeMCPServersSnapshotPath(cityPath, sessionID) + if path == "" { + return nil, nil + } + data, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, fmt.Errorf("read runtime MCP snapshot: %w", err) + } + var servers []runtime.MCPServerConfig + if err := json.Unmarshal(data, &servers); err != nil { + return nil, fmt.Errorf("unmarshal runtime MCP snapshot: %w", err) + } + return runtime.NormalizeMCPServerConfigs(servers), nil +} + +func runtimeMCPServersSnapshotPath(cityPath, sessionID string) string { + cityPath = strings.TrimSpace(cityPath) + sessionID = strings.TrimSpace(sessionID) + if cityPath == "" || sessionID == "" { + return "" + } + return citylayout.RuntimePath(cityPath, "session-mcp", sessionID+".json") +} From 50dc7e14c8a59d42db120c2afd4ae9d2a59750b7 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 07:48:16 +0000 Subject: [PATCH 83/85] fix: tighten runtime resume fallbacks --- cmd/gc/worker_handle.go | 5 +---- cmd/gc/worker_handle_test.go | 16 +++++++++++++- internal/api/session_runtime.go | 5 +---- internal/api/session_transport_test.go | 11 ++++++++++ internal/session/manager.go | 7 ++++++- internal/session/manager_test.go | 29 ++++++++++++++++++++++++++ internal/session/mcp_state.go | 16 ++++++++++---- 7 files changed, 75 insertions(+), 14 deletions(-) diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index e9a352fce2..f3681ffab8 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -555,10 +555,7 @@ func shouldPreserveStoredRuntimeCommandForTransport(storedCommand, resolvedComma if shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand) { return true } - if transport == "acp" || len(optionOverrides) != 0 { - return false - } - return sameRuntimeCommandExecutable(storedCommand, resolvedCommand) + return false } func sameRuntimeCommandExecutable(storedCommand, resolvedCommand string) bool { diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index 5f56bd6130..dc60f60e23 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -183,11 +183,14 @@ func TestResolvedWorkerRuntimeResumesPoolSessionPreservesLaunchFlags(t *testing. // worker-boundary refactor when the API created the bead with // sessionCreateAgentCommand(resolved) before the reconciler synced // the full tp.Command. - runtimeCfg := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ + runtimeCfg, err := resolvedWorkerRuntimeWithConfig(cityDir, cfg, session.Info{ Template: "perspective_planner", Command: "claude", WorkDir: cityDir, }, "") + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfig: %v", err) + } if runtimeCfg == nil { t.Fatal("resolvedWorkerRuntimeWithConfig() = nil") } @@ -202,6 +205,17 @@ func TestResolvedWorkerRuntimeResumesPoolSessionPreservesLaunchFlags(t *testing. } } +func TestShouldPreserveStoredRuntimeCommandForTransportRejectsExecutableOnlyMatch(t *testing.T) { + if shouldPreserveStoredRuntimeCommandForTransport( + "claude", + "claude --settings /tmp/settings.json", + "", + nil, + ) { + t.Fatal("shouldPreserveStoredRuntimeCommandForTransport() = true, want false") + } +} + func TestResolvedWorkerRuntimeWithConfigUsesStoredTemplateACPTransport(t *testing.T) { cityDir := t.TempDir() writePhase0InterfaceCity(t, cityDir, `[workspace] diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index 3f89f6c931..5de90bf077 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -316,10 +316,7 @@ func shouldPreserveStoredRuntimeCommandForTransport(storedCommand, resolvedComma if shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand) { return true } - if transport == "acp" || len(optionOverrides) != 0 { - return false - } - return sameRuntimeCommandExecutable(storedCommand, resolvedCommand) + return false } func sameRuntimeCommandExecutable(storedCommand, resolvedCommand string) bool { diff --git a/internal/api/session_transport_test.go b/internal/api/session_transport_test.go index 0d011606e9..0edce90b34 100644 --- a/internal/api/session_transport_test.go +++ b/internal/api/session_transport_test.go @@ -196,3 +196,14 @@ func TestResolvedSessionRuntimeCommandReplaysTemplateOverrides(t *testing.T) { t.Fatalf("command = %q, want %q", command, "/bin/echo --effort high") } } + +func TestShouldPreserveStoredRuntimeCommandForTransportRejectsExecutableOnlyMatch(t *testing.T) { + if shouldPreserveStoredRuntimeCommandForTransport( + "claude", + "claude --settings /tmp/settings.json", + "", + nil, + ) { + t.Fatal("shouldPreserveStoredRuntimeCommandForTransport() = true, want false") + } +} diff --git a/internal/session/manager.go b/internal/session/manager.go index 48411bfd62..8cb63548b2 100644 --- a/internal/session/manager.go +++ b/internal/session/manager.go @@ -761,6 +761,7 @@ func (m *Manager) Close(id string) error { return err } if b.Status == "closed" { + _ = clearRuntimeMCPServersSnapshot(m.cityPath, id) return nil // idempotent: already closed } // CmdClose is legal from any non-none state; this is effectively a @@ -790,7 +791,11 @@ func (m *Manager) Close(id string) error { return err } - return m.store.Close(id) + if err := m.store.Close(id); err != nil { + return err + } + _ = clearRuntimeMCPServersSnapshot(m.cityPath, id) + return nil }) } diff --git a/internal/session/manager_test.go b/internal/session/manager_test.go index 34e26f848e..6534ba562f 100644 --- a/internal/session/manager_test.go +++ b/internal/session/manager_test.go @@ -642,6 +642,35 @@ func TestClose(t *testing.T) { } } +func TestCloseRemovesRuntimeMCPSnapshot(t *testing.T) { + store := beads.NewMemStore() + sp := runtime.NewFake() + cityPath := t.TempDir() + mgr := NewManagerWithCityPath(store, sp, cityPath) + + info, err := mgr.Create(context.Background(), "helper", "", "claude", "/tmp", "claude", nil, ProviderResume{}, runtime.Config{}) + if err != nil { + t.Fatalf("Create: %v", err) + } + if err := PersistRuntimeMCPServersSnapshot(cityPath, info.ID, []runtime.MCPServerConfig{{ + Name: "identity", + Transport: runtime.MCPTransportHTTP, + URL: "https://example.invalid/mcp", + }}); err != nil { + t.Fatalf("PersistRuntimeMCPServersSnapshot: %v", err) + } + if _, err := os.Stat(runtimeMCPServersSnapshotPath(cityPath, info.ID)); err != nil { + t.Fatalf("Stat(runtime snapshot): %v", err) + } + + if err := mgr.Close(info.ID); err != nil { + t.Fatalf("Close: %v", err) + } + if _, err := os.Stat(runtimeMCPServersSnapshotPath(cityPath, info.ID)); !os.IsNotExist(err) { + t.Fatalf("runtime snapshot still exists after close, stat err = %v", err) + } +} + func TestClose_ConfiguredNamedSessionRetiresIdentifiers(t *testing.T) { store := beads.NewMemStore() sp := runtime.NewFake() diff --git a/internal/session/mcp_state.go b/internal/session/mcp_state.go index 79cb4efe29..1726752944 100644 --- a/internal/session/mcp_state.go +++ b/internal/session/mcp_state.go @@ -48,10 +48,7 @@ func PersistRuntimeMCPServersSnapshot(cityPath, sessionID string, servers []runt return nil } if len(servers) == 0 { - if err := os.Remove(path); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("remove runtime MCP snapshot: %w", err) - } - return nil + return clearRuntimeMCPServersSnapshot(cityPath, sessionID) } data, err := json.Marshal(runtime.NormalizeMCPServerConfigs(servers)) if err != nil { @@ -113,3 +110,14 @@ func runtimeMCPServersSnapshotPath(cityPath, sessionID string) string { } return citylayout.RuntimePath(cityPath, "session-mcp", sessionID+".json") } + +func clearRuntimeMCPServersSnapshot(cityPath, sessionID string) error { + path := runtimeMCPServersSnapshotPath(cityPath, sessionID) + if path == "" { + return nil + } + if err := os.Remove(path); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("remove runtime MCP snapshot: %w", err) + } + return nil +} From 60c49c61dbfcbcf756e6cb6a3189fe74e6fdeeec Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 22 Apr 2026 08:00:15 +0000 Subject: [PATCH 84/85] fix: complete provider patch and mcp fallback wiring --- .../web/src/generated/client/utils.gen.ts | 4 +- .../dashboard/web/src/generated/schema.d.ts | 4 + .../dashboard/web/src/generated/types.gen.ts | 8 ++ cmd/gc/worker_handle.go | 5 +- cmd/gc/worker_handle_test.go | 82 ++++++++++++++ docs/schema/openapi.json | 14 +++ docs/schema/openapi.txt | 14 +++ internal/api/genclient/client_gen.go | 6 ++ internal/api/handler_patches_test.go | 8 +- internal/api/huma_handlers_patches.go | 2 + internal/api/huma_types_patches.go | 2 + internal/api/openapi.json | 14 +++ internal/api/session_runtime.go | 5 +- internal/api/worker_factory_test.go | 81 ++++++++++++++ internal/session/mcp_metadata.go | 102 ++++++++++++++++++ internal/session/mcp_metadata_test.go | 46 ++++++++ 16 files changed, 386 insertions(+), 11 deletions(-) diff --git a/cmd/gc/dashboard/web/src/generated/client/utils.gen.ts b/cmd/gc/dashboard/web/src/generated/client/utils.gen.ts index 1f71eaf8ad..5162192d8a 100644 --- a/cmd/gc/dashboard/web/src/generated/client/utils.gen.ts +++ b/cmd/gc/dashboard/web/src/generated/client/utils.gen.ts @@ -75,7 +75,7 @@ export const getParseAs = (contentType: string | null): Exclude | null; + /** + * Override ACP transport command binary. + */ + acp_command?: string; /** * Override command arguments. */ diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index f3681ffab8..c608bd0641 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -190,10 +190,7 @@ func resumeRuntimeMCPServersWithConfig( if decodeErr != nil { return nil, fmt.Errorf("decoding stored MCP snapshot: %w", decodeErr) } - if session.StoredMCPSnapshotContainsRedactions(stored) { - return nil, nil - } - return stored, nil + return session.SanitizeStoredMCPSnapshotForResume(stored), nil } func newWorkerSessionHandleForResolvedRuntimeWithConfig( diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index dc60f60e23..9bd86b609d 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -1441,6 +1441,88 @@ command = [broken } } +func TestResolvedWorkerRuntimeWithConfigFallsBackToSanitizedStoredMCPServersWhenRuntimeSnapshotMissing(t *testing.T) { + cityDir := t.TempDir() + writePhase0InterfaceCity(t, cityDir, `[workspace] +name = "test-city" + +[beads] +provider = "file" + +[[agent]] +name = "ant" +dir = "myrig" +provider = "stub" +session = "acp" +work_dir = ".gc/worktrees/{{.Rig}}/ants/{{.AgentBase}}" +min_active_sessions = 0 +max_active_sessions = 4 + +[providers.stub] +command = "/bin/echo" +supports_acp = true +acp_command = "/bin/echo" +acp_args = ["acp"] +`) + writeCatalogFile(t, cityDir, "mcp/identity.template.toml", ` +name = "identity" +command = [broken +`) + + cfg, err := loadCityConfig(cityDir) + if err != nil { + t.Fatalf("loadCityConfig: %v", err) + } + + workDir := filepath.Join(cityDir, ".gc", "worktrees", "myrig", "ants", "ant") + metadata, err := session.WithStoredMCPMetadata(nil, "myrig/ant-adhoc-123", []runtime.MCPServerConfig{{ + Name: "identity", + Transport: runtime.MCPTransportHTTP, + Command: "/bin/mcp", + Args: []string{"--serve", "--api-key", "super-secret"}, + Env: map[string]string{ + "API_TOKEN": "super-secret", + }, + URL: "https://user:pass@example.invalid/mcp?token=abc123", + Headers: map[string]string{ + "Authorization": "Bearer secret", + }, + }}) + if err != nil { + t.Fatalf("WithStoredMCPMetadata: %v", err) + } + + resolved, err := resolvedWorkerRuntimeWithConfigAndMetadata(cityDir, cfg, session.Info{ + ID: "sess-1", + Template: "myrig/ant", + Alias: "ant", + AgentName: "myrig/ant-adhoc-123", + Transport: "acp", + WorkDir: workDir, + }, "", metadata) + if err != nil { + t.Fatalf("resolvedWorkerRuntimeWithConfigAndMetadata: %v", err) + } + if resolved == nil { + t.Fatal("resolvedWorkerRuntimeWithConfigAndMetadata() = nil") + } + if len(resolved.Hints.MCPServers) != 1 { + t.Fatalf("Hints.MCPServers len = %d, want 1", len(resolved.Hints.MCPServers)) + } + if got, want := resolved.Hints.MCPServers[0].Args, []string{"--serve"}; len(got) != len(want) || got[0] != want[0] { + t.Fatalf("Args = %#v, want %#v", got, want) + } + if len(resolved.Hints.MCPServers[0].Env) != 0 { + t.Fatalf("Env = %#v, want empty", resolved.Hints.MCPServers[0].Env) + } + if len(resolved.Hints.MCPServers[0].Headers) != 0 { + t.Fatalf("Headers = %#v, want empty", resolved.Hints.MCPServers[0].Headers) + } + if got, want := resolved.Hints.MCPServers[0].URL, "https://example.invalid/mcp"; got != want { + t.Fatalf("URL = %q, want %q", got, want) + } +} + func TestWorkerSessionRuntimeResolverWithConfigFallsBackToProviderNameWhenResolvedCommandMissing(t *testing.T) { cfg := &config.City{ Workspace: config.Workspace{Name: "test-city"}, diff --git a/docs/schema/openapi.json b/docs/schema/openapi.json index af09cc51fe..dcac755149 100644 --- a/docs/schema/openapi.json +++ b/docs/schema/openapi.json @@ -4734,6 +4734,20 @@ "ProviderPatchSetInputBody": { "additionalProperties": false, "properties": { + "acp_args": { + "description": "Override ACP transport command arguments.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "description": "Override ACP transport command binary.", + "type": "string" + }, "args": { "description": "Override command arguments.", "items": { diff --git a/docs/schema/openapi.txt b/docs/schema/openapi.txt index af09cc51fe..dcac755149 100644 --- a/docs/schema/openapi.txt +++ b/docs/schema/openapi.txt @@ -4734,6 +4734,20 @@ "ProviderPatchSetInputBody": { "additionalProperties": false, "properties": { + "acp_args": { + "description": "Override ACP transport command arguments.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "description": "Override ACP transport command binary.", + "type": "string" + }, "args": { "description": "Override command arguments.", "items": { diff --git a/internal/api/genclient/client_gen.go b/internal/api/genclient/client_gen.go index 28c8006b4f..2b1a71518a 100644 --- a/internal/api/genclient/client_gen.go +++ b/internal/api/genclient/client_gen.go @@ -1839,6 +1839,12 @@ type ProviderPatch struct { // ProviderPatchSetInputBody defines model for ProviderPatchSetInputBody. type ProviderPatchSetInputBody struct { + // AcpArgs Override ACP transport command arguments. + AcpArgs *[]string `json:"acp_args,omitempty"` + + // AcpCommand Override ACP transport command binary. + AcpCommand *string `json:"acp_command,omitempty"` + // Args Override command arguments. Args *[]string `json:"args,omitempty"` diff --git a/internal/api/handler_patches_test.go b/internal/api/handler_patches_test.go index 57cd216a17..fc91d91457 100644 --- a/internal/api/handler_patches_test.go +++ b/internal/api/handler_patches_test.go @@ -252,7 +252,7 @@ func TestHandleProviderPatchSet(t *testing.T) { fs := newFakeMutatorState(t) h := newTestCityHandler(t, fs) - body := `{"name":"claude","command":"my-claude"}` + body := `{"name":"claude","command":"my-claude","acp_command":"my-claude-acp","acp_args":["serve","--stdio"]}` req := httptest.NewRequest("PUT", cityURL(fs, "/patches/providers"), strings.NewReader(body)) req.Header.Set("X-GC-Request", "true") w := httptest.NewRecorder() @@ -265,6 +265,12 @@ func TestHandleProviderPatchSet(t *testing.T) { if len(fs.cfg.Patches.Providers) != 1 { t.Fatalf("patches.providers count = %d, want 1", len(fs.cfg.Patches.Providers)) } + if got := fs.cfg.Patches.Providers[0].ACPCommand; got == nil || *got != "my-claude-acp" { + t.Fatalf("ACPCommand = %v, want %q", got, "my-claude-acp") + } + if got := fs.cfg.Patches.Providers[0].ACPArgs; len(got) != 2 || got[0] != "serve" || got[1] != "--stdio" { + t.Fatalf("ACPArgs = %#v, want [\"serve\" \"--stdio\"]", got) + } } func TestHandleProviderPatchDelete(t *testing.T) { diff --git a/internal/api/huma_handlers_patches.go b/internal/api/huma_handlers_patches.go index ec215ab8ad..8ffb58248c 100644 --- a/internal/api/huma_handlers_patches.go +++ b/internal/api/huma_handlers_patches.go @@ -222,7 +222,9 @@ func (s *Server) humaHandleProviderPatchSet(_ context.Context, input *ProviderPa patch := config.ProviderPatch{ Name: input.Body.Name, Command: input.Body.Command, + ACPCommand: input.Body.ACPCommand, Args: input.Body.Args, + ACPArgs: input.Body.ACPArgs, PromptMode: input.Body.PromptMode, PromptFlag: input.Body.PromptFlag, ReadyDelayMs: input.Body.ReadyDelayMs, diff --git a/internal/api/huma_types_patches.go b/internal/api/huma_types_patches.go index a5d7bfcbf4..d4215cb132 100644 --- a/internal/api/huma_types_patches.go +++ b/internal/api/huma_types_patches.go @@ -109,7 +109,9 @@ type ProviderPatchSetInput struct { Body struct { Name string `json:"name,omitempty" doc:"Provider name."` Command *string `json:"command,omitempty" doc:"Override command binary."` + ACPCommand *string `json:"acp_command,omitempty" doc:"Override ACP transport command binary."` Args []string `json:"args,omitempty" doc:"Override command arguments."` + ACPArgs []string `json:"acp_args,omitempty" doc:"Override ACP transport command arguments."` PromptMode *string `json:"prompt_mode,omitempty" doc:"Override prompt delivery mode."` PromptFlag *string `json:"prompt_flag,omitempty" doc:"Override prompt flag."` ReadyDelayMs *int `json:"ready_delay_ms,omitempty" doc:"Override ready delay in milliseconds."` diff --git a/internal/api/openapi.json b/internal/api/openapi.json index af09cc51fe..dcac755149 100644 --- a/internal/api/openapi.json +++ b/internal/api/openapi.json @@ -4734,6 +4734,20 @@ "ProviderPatchSetInputBody": { "additionalProperties": false, "properties": { + "acp_args": { + "description": "Override ACP transport command arguments.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "acp_command": { + "description": "Override ACP transport command binary.", + "type": "string" + }, "args": { "description": "Override command arguments.", "items": { diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index 5de90bf077..2939add067 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -84,10 +84,7 @@ func (s *Server) resumeSessionMCPServers(info session.Info, metadata map[string] if decodeErr != nil { return nil, fmt.Errorf("decoding stored MCP snapshot: %w", decodeErr) } - if session.StoredMCPSnapshotContainsRedactions(stored) { - return nil, nil - } - return stored, nil + return session.SanitizeStoredMCPSnapshotForResume(stored), nil } func (s *Server) providerSessionMCPServers(providerName, identity, workDir, transport string) ([]runtime.MCPServerConfig, error) { diff --git a/internal/api/worker_factory_test.go b/internal/api/worker_factory_test.go index f7d067c135..55cdd5a1c3 100644 --- a/internal/api/worker_factory_test.go +++ b/internal/api/worker_factory_test.go @@ -401,6 +401,87 @@ command = [broken } } +func TestResolveWorkerSessionRuntimeFallsBackToSanitizedStoredMCPServersWhenRuntimeSnapshotMissing(t *testing.T) { + fs := newSessionFakeState(t) + fs.cfg.Agents = []config.Agent{{ + Name: "ant", + Dir: "myrig", + Provider: "resolved-worker", + Session: "acp", + WorkDir: ".gc/worktrees/{{.Rig}}/ants/{{.AgentBase}}", + MinActiveSessions: intPtr(0), + MaxActiveSessions: intPtr(4), + }} + supportsACP := true + fs.cfg.Providers["resolved-worker"] = config.ProviderSpec{ + DisplayName: "Resolved Worker", + Command: "/bin/echo", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + fs.cfg.PackMCPDir = filepath.Join(fs.cityPath, "mcp") + if err := os.MkdirAll(fs.cfg.PackMCPDir, 0o755); err != nil { + t.Fatalf("MkdirAll(mcp): %v", err) + } + if err := os.WriteFile(filepath.Join(fs.cfg.PackMCPDir, "identity.template.toml"), []byte(` +name = "identity" +command = [broken +`), 0o644); err != nil { + t.Fatalf("WriteFile(mcp): %v", err) + } + + metadata, err := session.WithStoredMCPMetadata(nil, "myrig/ant-adhoc-123", []runtime.MCPServerConfig{{ + Name: "identity", + Transport: runtime.MCPTransportHTTP, + Command: "/bin/mcp", + Args: []string{"--serve", "--api-key", "super-secret"}, + Env: map[string]string{ + "API_TOKEN": "super-secret", + }, + URL: "https://user:pass@example.invalid/mcp?token=abc123", + Headers: map[string]string{ + "Authorization": "Bearer secret", + }, + }}) + if err != nil { + t.Fatalf("WithStoredMCPMetadata: %v", err) + } + + srv := New(fs) + info := session.Info{ + ID: "sess-1", + Template: "myrig/ant", + Alias: "ant", + AgentName: "myrig/ant-adhoc-123", + Transport: "acp", + WorkDir: filepath.Join(fs.cityPath, ".gc", "worktrees", "myrig", "ants", "ant"), + } + + runtimeCfg, err := srv.resolveWorkerSessionRuntimeWithMetadata(info, "", metadata) + if err != nil { + t.Fatalf("resolveWorkerSessionRuntimeWithMetadata: %v", err) + } + if runtimeCfg == nil { + t.Fatal("resolveWorkerSessionRuntimeWithMetadata() = nil") + } + if len(runtimeCfg.Hints.MCPServers) != 1 { + t.Fatalf("Hints.MCPServers len = %d, want 1", len(runtimeCfg.Hints.MCPServers)) + } + if got, want := runtimeCfg.Hints.MCPServers[0].Args, []string{"--serve"}; len(got) != len(want) || got[0] != want[0] { + t.Fatalf("Args = %#v, want %#v", got, want) + } + if len(runtimeCfg.Hints.MCPServers[0].Env) != 0 { + t.Fatalf("Env = %#v, want empty", runtimeCfg.Hints.MCPServers[0].Env) + } + if len(runtimeCfg.Hints.MCPServers[0].Headers) != 0 { + t.Fatalf("Headers = %#v, want empty", runtimeCfg.Hints.MCPServers[0].Headers) + } + if got, want := runtimeCfg.Hints.MCPServers[0].URL, "https://example.invalid/mcp"; got != want { + t.Fatalf("URL = %q, want %q", got, want) + } +} + func TestResolveWorkerSessionRuntimeFallsBackToStoredCommandWhenTemplateOverridesInvalid(t *testing.T) { fs := newSessionFakeState(t) fs.cfg.Providers["test-agent"] = config.ProviderSpec{ diff --git a/internal/session/mcp_metadata.go b/internal/session/mcp_metadata.go index 8a0ecb7af5..a8b0812d72 100644 --- a/internal/session/mcp_metadata.go +++ b/internal/session/mcp_metadata.go @@ -63,6 +63,23 @@ func StoredMCPSnapshotContainsRedactions(servers []runtime.MCPServerConfig) bool return false } +// SanitizeStoredMCPSnapshotForResume strips redacted secret placeholders from +// a stored MCP snapshot while preserving any non-secret fields that can still +// help degraded resume reconstruct MCP hints. +func SanitizeStoredMCPSnapshotForResume(servers []runtime.MCPServerConfig) []runtime.MCPServerConfig { + if len(servers) == 0 { + return nil + } + normalized := runtime.NormalizeMCPServerConfigs(servers) + for i := range normalized { + normalized[i].Args = sanitizeStoredMCPMetadataArgs(normalized[i].Args) + normalized[i].Env = sanitizeStoredMCPMetadataMap(normalized[i].Env) + normalized[i].URL = sanitizeStoredMCPMetadataURL(normalized[i].URL) + normalized[i].Headers = sanitizeStoredMCPMetadataMap(normalized[i].Headers) + } + return runtime.NormalizeMCPServerConfigs(normalized) +} + // WithStoredMCPMetadata returns a metadata map augmented with the stable MCP // identity and normalized ACP session/new snapshot for the session. func WithStoredMCPMetadata(meta map[string]string, identity string, servers []runtime.MCPServerConfig) (map[string]string, error) { @@ -194,6 +211,91 @@ func snapshotArgsContainRedactions(args []string) bool { return false } +func sanitizeStoredMCPMetadataArgs(args []string) []string { + if len(args) == 0 { + return nil + } + out := make([]string, 0, len(args)) + for i := 0; i < len(args); i++ { + arg := args[i] + trimmed := strings.TrimSpace(arg) + if strings.HasPrefix(trimmed, "-") && + isSensitiveMCPMetadataToken(trimmed) && + i+1 < len(args) && + strings.Contains(args[i+1], redactedMCPSnapshotValue) { + i++ + continue + } + if !strings.Contains(arg, redactedMCPSnapshotValue) { + out = append(out, arg) + continue + } + if key, value, ok := strings.Cut(arg, "="); ok && + isSensitiveMCPMetadataToken(key) && + strings.Contains(value, redactedMCPSnapshotValue) { + continue + } + if sanitizedURL := sanitizeStoredMCPMetadataURL(arg); sanitizedURL != "" && sanitizedURL != arg { + out = append(out, sanitizedURL) + } + } + return out +} + +func sanitizeStoredMCPMetadataMap(in map[string]string) map[string]string { + if len(in) == 0 { + return nil + } + out := make(map[string]string) + for key, value := range in { + if strings.Contains(value, redactedMCPSnapshotValue) { + continue + } + out[key] = value + } + if len(out) == 0 { + return nil + } + return out +} + +func sanitizeStoredMCPMetadataURL(raw string) string { + raw = strings.TrimSpace(raw) + if raw == "" { + return "" + } + if !strings.Contains(raw, redactedMCPSnapshotValue) { + return raw + } + parsed, err := url.Parse(raw) + if err != nil { + return "" + } + if parsed.User != nil && strings.Contains(parsed.User.String(), redactedMCPSnapshotValue) { + parsed.User = nil + } + if query := parsed.Query(); len(query) > 0 { + for key, values := range query { + filtered := values[:0] + for _, value := range values { + if !strings.Contains(value, redactedMCPSnapshotValue) { + filtered = append(filtered, value) + } + } + if len(filtered) == 0 { + query.Del(key) + continue + } + query[key] = filtered + } + parsed.RawQuery = query.Encode() + } + if strings.Contains(parsed.String(), redactedMCPSnapshotValue) { + return "" + } + return parsed.String() +} + func isSensitiveMCPMetadataToken(value string) bool { value = strings.ToLower(strings.TrimSpace(value)) return strings.Contains(value, "token") || diff --git a/internal/session/mcp_metadata_test.go b/internal/session/mcp_metadata_test.go index 1a4cecb818..eecac2d0b7 100644 --- a/internal/session/mcp_metadata_test.go +++ b/internal/session/mcp_metadata_test.go @@ -103,3 +103,49 @@ func TestRuntimeMCPServersSnapshotRoundTrip(t *testing.T) { t.Fatalf("Headers[Authorization] = %q, want %q", got, want) } } + +func TestSanitizeStoredMCPSnapshotForResumePreservesNonSecretFields(t *testing.T) { + raw, err := EncodeMCPServersSnapshot([]runtime.MCPServerConfig{{ + Name: "remote", + Transport: runtime.MCPTransportHTTP, + Command: "/bin/mcp", + Args: []string{ + "--serve", + "--api-key", + "super-secret", + "--token=abc123", + "https://user:pass@example.invalid/mcp?token=abc123", + }, + Env: map[string]string{ + "API_TOKEN": "super-secret", + }, + URL: "https://user:pass@example.invalid/mcp?token=abc123", + Headers: map[string]string{ + "Authorization": "Bearer secret", + }, + }}) + if err != nil { + t.Fatalf("EncodeMCPServersSnapshot: %v", err) + } + stored, err := DecodeMCPServersSnapshot(raw) + if err != nil { + t.Fatalf("DecodeMCPServersSnapshot: %v", err) + } + + sanitized := SanitizeStoredMCPSnapshotForResume(stored) + if len(sanitized) != 1 { + t.Fatalf("len(sanitized) = %d, want 1", len(sanitized)) + } + if got, want := sanitized[0].Args, []string{"--serve"}; len(got) != len(want) || got[0] != want[0] { + t.Fatalf("Args = %#v, want %#v", got, want) + } + if len(sanitized[0].Env) != 0 { + t.Fatalf("Env = %#v, want empty", sanitized[0].Env) + } + if len(sanitized[0].Headers) != 0 { + t.Fatalf("Headers = %#v, want empty", sanitized[0].Headers) + } + if got, want := sanitized[0].URL, "https://example.invalid/mcp"; got != want { + t.Fatalf("URL = %q, want %q", got, want) + } +} From 0d2e18529bcc82e57335d269db71dabed8c46c76 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Sun, 26 Apr 2026 18:39:39 +0000 Subject: [PATCH 85/85] fix: preserve runtime fallback semantics --- cmd/gc/dashboard/web/src/generated/index.ts | 4 +- .../dashboard/web/src/generated/schema.d.ts | 2346 ++++++++++++++++- cmd/gc/dashboard/web/src/generated/sdk.gen.ts | 7 +- .../dashboard/web/src/generated/types.gen.ts | 1960 +++++++++++++- cmd/gc/providers.go | 16 +- cmd/gc/worker_handle.go | 70 +- cmd/gc/worker_handle_test.go | 4 +- docs/reference/config.md | 2 + docs/schema/city-schema.json | 11 + docs/schema/city-schema.txt | 11 + internal/api/handler_session_create.go | 2 +- internal/api/session_runtime.go | 61 +- internal/api/worker_factory_test.go | 116 +- internal/runtime/fingerprint.go | 2 +- internal/session/manager_test.go | 12 +- 15 files changed, 4398 insertions(+), 226 deletions(-) diff --git a/cmd/gc/dashboard/web/src/generated/index.ts b/cmd/gc/dashboard/web/src/generated/index.ts index 87629493cb..47eed4b632 100644 --- a/cmd/gc/dashboard/web/src/generated/index.ts +++ b/cmd/gc/dashboard/web/src/generated/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export { createAgent, createBead, createConvoy, createProvider, createRig, createSession, deleteV0CityByCityNameAgentByBase, deleteV0CityByCityNameAgentByDirByBase, deleteV0CityByCityNameBeadById, deleteV0CityByCityNameConvoyById, deleteV0CityByCityNameExtmsgAdapters, deleteV0CityByCityNameExtmsgParticipants, deleteV0CityByCityNameMailById, deleteV0CityByCityNamePatchesAgentByBase, deleteV0CityByCityNamePatchesAgentByDirByBase, deleteV0CityByCityNamePatchesProviderByName, deleteV0CityByCityNamePatchesRigByName, deleteV0CityByCityNameProviderByName, deleteV0CityByCityNameRigByName, deleteV0CityByCityNameWorkflowByWorkflowId, emitEvent, ensureExtmsgGroup, getHealth, getV0Cities, getV0CityByCityName, getV0CityByCityNameAgentByBase, getV0CityByCityNameAgentByBaseOutput, getV0CityByCityNameAgentByDirByBase, getV0CityByCityNameAgentByDirByBaseOutput, getV0CityByCityNameAgents, getV0CityByCityNameBeadById, getV0CityByCityNameBeadByIdDeps, getV0CityByCityNameBeads, getV0CityByCityNameBeadsGraphByRootId, getV0CityByCityNameBeadsReady, getV0CityByCityNameConfig, getV0CityByCityNameConfigExplain, getV0CityByCityNameConfigValidate, getV0CityByCityNameConvoyById, getV0CityByCityNameConvoyByIdCheck, getV0CityByCityNameConvoys, getV0CityByCityNameEvents, getV0CityByCityNameExtmsgAdapters, getV0CityByCityNameExtmsgBindings, getV0CityByCityNameExtmsgGroups, getV0CityByCityNameExtmsgTranscript, getV0CityByCityNameFormulaByName, getV0CityByCityNameFormulas, getV0CityByCityNameFormulasByName, getV0CityByCityNameFormulasByNameRuns, getV0CityByCityNameFormulasFeed, getV0CityByCityNameHealth, getV0CityByCityNameMail, getV0CityByCityNameMailById, getV0CityByCityNameMailCount, getV0CityByCityNameMailThreadById, getV0CityByCityNameOrderByName, getV0CityByCityNameOrderHistoryByBeadId, getV0CityByCityNameOrders, getV0CityByCityNameOrdersCheck, getV0CityByCityNameOrdersFeed, getV0CityByCityNameOrdersHistory, getV0CityByCityNamePacks, getV0CityByCityNamePatchesAgentByBase, getV0CityByCityNamePatchesAgentByDirByBase, getV0CityByCityNamePatchesAgents, getV0CityByCityNamePatchesProviderByName, getV0CityByCityNamePatchesProviders, getV0CityByCityNamePatchesRigByName, getV0CityByCityNamePatchesRigs, getV0CityByCityNameProviderByName, getV0CityByCityNameProviderReadiness, getV0CityByCityNameProviders, getV0CityByCityNameProvidersPublic, getV0CityByCityNameReadiness, getV0CityByCityNameRigByName, getV0CityByCityNameRigs, getV0CityByCityNameServiceByName, getV0CityByCityNameServices, getV0CityByCityNameSessionById, getV0CityByCityNameSessionByIdAgents, getV0CityByCityNameSessionByIdAgentsByAgentId, getV0CityByCityNameSessionByIdPending, getV0CityByCityNameSessionByIdTranscript, getV0CityByCityNameSessions, getV0CityByCityNameStatus, getV0CityByCityNameWorkflowByWorkflowId, getV0Events, getV0ProviderReadiness, getV0Readiness, type Options, patchV0CityByCityName, patchV0CityByCityNameAgentByBase, patchV0CityByCityNameAgentByDirByBase, patchV0CityByCityNameBeadById, patchV0CityByCityNameProviderByName, patchV0CityByCityNameRigByName, patchV0CityByCityNameSessionById, postV0City, postV0CityByCityNameAgentByBaseByAction, postV0CityByCityNameAgentByDirByBaseByAction, postV0CityByCityNameBeadByIdAssign, postV0CityByCityNameBeadByIdClose, postV0CityByCityNameBeadByIdReopen, postV0CityByCityNameBeadByIdUpdate, postV0CityByCityNameConvoyByIdAdd, postV0CityByCityNameConvoyByIdClose, postV0CityByCityNameConvoyByIdRemove, postV0CityByCityNameExtmsgBind, postV0CityByCityNameExtmsgInbound, postV0CityByCityNameExtmsgOutbound, postV0CityByCityNameExtmsgParticipants, postV0CityByCityNameExtmsgTranscriptAck, postV0CityByCityNameExtmsgUnbind, postV0CityByCityNameFormulasByNamePreview, postV0CityByCityNameMailByIdArchive, postV0CityByCityNameMailByIdMarkUnread, postV0CityByCityNameMailByIdRead, postV0CityByCityNameOrderByNameDisable, postV0CityByCityNameOrderByNameEnable, postV0CityByCityNameRigByNameByAction, postV0CityByCityNameServiceByNameRestart, postV0CityByCityNameSessionByIdClose, postV0CityByCityNameSessionByIdKill, postV0CityByCityNameSessionByIdRename, postV0CityByCityNameSessionByIdStop, postV0CityByCityNameSessionByIdSuspend, postV0CityByCityNameSessionByIdWake, postV0CityByCityNameSling, putV0CityByCityNamePatchesAgents, putV0CityByCityNamePatchesProviders, putV0CityByCityNamePatchesRigs, registerExtmsgAdapter, replyMail, respondSession, sendMail, sendSessionMessage, streamAgentOutput, streamAgentOutputQualified, streamEvents, streamSession, streamSupervisorEvents, submitSession } from './sdk.gen'; -export type { AdapterCapabilities, AdapterEventPayload, AgentCreatedOutputBody, AgentCreateInputBody, AgentMapping, AgentOutputResponse, AgentPatch, AgentPatchSetInputBody, AgentResponse, AgentUpdateInputBody, AgentUpdateQualifiedInputBody, AnnotatedAgentResponse, AnnotatedProviderResponse, Bead, BeadAssignInputBody, BeadCreateInputBody, BeadDepsResponse, BeadEventPayload, BeadGraphResponse, BeadUpdateBody, BindingStatus, BoundEventPayload, CityCreateRequest, CityCreateResponse, CityGetResponse, CityInfo, CityPatchInputBody, ClientOptions, ConfigAgentResponse, ConfigExplainPatches, ConfigExplainResponse, ConfigPatchesResponse, ConfigResponse, ConfigRigResponse, ConfigValidateOutputBody, ConversationGroupParticipant, ConversationGroupRecord, ConversationKind, ConversationRef, ConversationTranscriptRecord, ConvoyAddInputBody, ConvoyCheckResponse, ConvoyCreateInputBody, ConvoyGetResponse, ConvoyProgress, ConvoyRemoveInputBody, CreateAgentData, CreateAgentError, CreateAgentErrors, CreateAgentResponse, CreateAgentResponses, CreateBeadData, CreateBeadError, CreateBeadErrors, CreateBeadResponse, CreateBeadResponses, CreateConvoyData, CreateConvoyError, CreateConvoyErrors, CreateConvoyResponse, CreateConvoyResponses, CreateProviderData, CreateProviderError, CreateProviderErrors, CreateProviderResponse, CreateProviderResponses, CreateRigData, CreateRigError, CreateRigErrors, CreateRigResponse, CreateRigResponses, CreateSessionData, CreateSessionError, CreateSessionErrors, CreateSessionResponse, CreateSessionResponses, DeleteV0CityByCityNameAgentByBaseData, DeleteV0CityByCityNameAgentByBaseError, DeleteV0CityByCityNameAgentByBaseErrors, DeleteV0CityByCityNameAgentByBaseResponse, DeleteV0CityByCityNameAgentByBaseResponses, DeleteV0CityByCityNameAgentByDirByBaseData, DeleteV0CityByCityNameAgentByDirByBaseError, DeleteV0CityByCityNameAgentByDirByBaseErrors, DeleteV0CityByCityNameAgentByDirByBaseResponse, DeleteV0CityByCityNameAgentByDirByBaseResponses, DeleteV0CityByCityNameBeadByIdData, DeleteV0CityByCityNameBeadByIdError, DeleteV0CityByCityNameBeadByIdErrors, DeleteV0CityByCityNameBeadByIdResponse, DeleteV0CityByCityNameBeadByIdResponses, DeleteV0CityByCityNameConvoyByIdData, DeleteV0CityByCityNameConvoyByIdError, DeleteV0CityByCityNameConvoyByIdErrors, DeleteV0CityByCityNameConvoyByIdResponse, DeleteV0CityByCityNameConvoyByIdResponses, DeleteV0CityByCityNameExtmsgAdaptersData, DeleteV0CityByCityNameExtmsgAdaptersError, DeleteV0CityByCityNameExtmsgAdaptersErrors, DeleteV0CityByCityNameExtmsgAdaptersResponse, DeleteV0CityByCityNameExtmsgAdaptersResponses, DeleteV0CityByCityNameExtmsgParticipantsData, DeleteV0CityByCityNameExtmsgParticipantsError, DeleteV0CityByCityNameExtmsgParticipantsErrors, DeleteV0CityByCityNameExtmsgParticipantsResponse, DeleteV0CityByCityNameExtmsgParticipantsResponses, DeleteV0CityByCityNameMailByIdData, DeleteV0CityByCityNameMailByIdError, DeleteV0CityByCityNameMailByIdErrors, DeleteV0CityByCityNameMailByIdResponse, DeleteV0CityByCityNameMailByIdResponses, DeleteV0CityByCityNamePatchesAgentByBaseData, DeleteV0CityByCityNamePatchesAgentByBaseError, DeleteV0CityByCityNamePatchesAgentByBaseErrors, DeleteV0CityByCityNamePatchesAgentByBaseResponse, DeleteV0CityByCityNamePatchesAgentByBaseResponses, DeleteV0CityByCityNamePatchesAgentByDirByBaseData, DeleteV0CityByCityNamePatchesAgentByDirByBaseError, DeleteV0CityByCityNamePatchesAgentByDirByBaseErrors, DeleteV0CityByCityNamePatchesAgentByDirByBaseResponse, DeleteV0CityByCityNamePatchesAgentByDirByBaseResponses, DeleteV0CityByCityNamePatchesProviderByNameData, DeleteV0CityByCityNamePatchesProviderByNameError, DeleteV0CityByCityNamePatchesProviderByNameErrors, DeleteV0CityByCityNamePatchesProviderByNameResponse, DeleteV0CityByCityNamePatchesProviderByNameResponses, DeleteV0CityByCityNamePatchesRigByNameData, DeleteV0CityByCityNamePatchesRigByNameError, DeleteV0CityByCityNamePatchesRigByNameErrors, DeleteV0CityByCityNamePatchesRigByNameResponse, DeleteV0CityByCityNamePatchesRigByNameResponses, DeleteV0CityByCityNameProviderByNameData, DeleteV0CityByCityNameProviderByNameError, DeleteV0CityByCityNameProviderByNameErrors, DeleteV0CityByCityNameProviderByNameResponse, DeleteV0CityByCityNameProviderByNameResponses, DeleteV0CityByCityNameRigByNameData, DeleteV0CityByCityNameRigByNameError, DeleteV0CityByCityNameRigByNameErrors, DeleteV0CityByCityNameRigByNameResponse, DeleteV0CityByCityNameRigByNameResponses, DeleteV0CityByCityNameWorkflowByWorkflowIdData, DeleteV0CityByCityNameWorkflowByWorkflowIdError, DeleteV0CityByCityNameWorkflowByWorkflowIdErrors, DeleteV0CityByCityNameWorkflowByWorkflowIdResponse, DeleteV0CityByCityNameWorkflowByWorkflowIdResponses, DeliveryContextRecord, Dep, EmitEventData, EmitEventError, EmitEventErrors, EmitEventResponse, EmitEventResponses, EnsureExtmsgGroupData, EnsureExtmsgGroupError, EnsureExtmsgGroupErrors, EnsureExtmsgGroupResponse, EnsureExtmsgGroupResponses, ErrorDetail, ErrorModel, EventEmitOutputBody, EventEmitRequest, EventPayload, EventStreamEnvelope, ExternalActor, ExternalAttachment, ExternalInboundMessage, ExtmsgAdapterInfo, ExtMsgAdapterRegisterInputBody, ExtMsgAdapterRegisterOutputBody, ExtMsgAdapterUnregisterInputBody, ExtMsgBindInputBody, ExtMsgGroupEnsureInputBody, ExtMsgInboundInputBody, ExtMsgOutboundInputBody, ExtMsgParticipantRemoveInputBody, ExtMsgParticipantUpsertInputBody, ExtMsgTranscriptAckInputBody, ExtMsgUnbindBody, ExtMsgUnbindInputBody, FanoutPolicy, FormulaDetailResponse, FormulaFeedBody, FormulaListBody, FormulaPreviewBody, FormulaPreviewEdgeResponse, FormulaPreviewNodeResponse, FormulaPreviewResponse, FormulaRecentRunResponse, FormulaRunsResponse, FormulaStepResponse, FormulaSummaryResponse, FormulaVarDefResponse, GetHealthData, GetHealthError, GetHealthErrors, GetHealthResponse, GetHealthResponses, GetV0CitiesData, GetV0CitiesError, GetV0CitiesErrors, GetV0CitiesResponse, GetV0CitiesResponses, GetV0CityByCityNameAgentByBaseData, GetV0CityByCityNameAgentByBaseError, GetV0CityByCityNameAgentByBaseErrors, GetV0CityByCityNameAgentByBaseOutputData, GetV0CityByCityNameAgentByBaseOutputError, GetV0CityByCityNameAgentByBaseOutputErrors, GetV0CityByCityNameAgentByBaseOutputResponse, GetV0CityByCityNameAgentByBaseOutputResponses, GetV0CityByCityNameAgentByBaseResponse, GetV0CityByCityNameAgentByBaseResponses, GetV0CityByCityNameAgentByDirByBaseData, GetV0CityByCityNameAgentByDirByBaseError, GetV0CityByCityNameAgentByDirByBaseErrors, GetV0CityByCityNameAgentByDirByBaseOutputData, GetV0CityByCityNameAgentByDirByBaseOutputError, GetV0CityByCityNameAgentByDirByBaseOutputErrors, GetV0CityByCityNameAgentByDirByBaseOutputResponse, GetV0CityByCityNameAgentByDirByBaseOutputResponses, GetV0CityByCityNameAgentByDirByBaseResponse, GetV0CityByCityNameAgentByDirByBaseResponses, GetV0CityByCityNameAgentsData, GetV0CityByCityNameAgentsError, GetV0CityByCityNameAgentsErrors, GetV0CityByCityNameAgentsResponse, GetV0CityByCityNameAgentsResponses, GetV0CityByCityNameBeadByIdData, GetV0CityByCityNameBeadByIdDepsData, GetV0CityByCityNameBeadByIdDepsError, GetV0CityByCityNameBeadByIdDepsErrors, GetV0CityByCityNameBeadByIdDepsResponse, GetV0CityByCityNameBeadByIdDepsResponses, GetV0CityByCityNameBeadByIdError, GetV0CityByCityNameBeadByIdErrors, GetV0CityByCityNameBeadByIdResponse, GetV0CityByCityNameBeadByIdResponses, GetV0CityByCityNameBeadsData, GetV0CityByCityNameBeadsError, GetV0CityByCityNameBeadsErrors, GetV0CityByCityNameBeadsGraphByRootIdData, GetV0CityByCityNameBeadsGraphByRootIdError, GetV0CityByCityNameBeadsGraphByRootIdErrors, GetV0CityByCityNameBeadsGraphByRootIdResponse, GetV0CityByCityNameBeadsGraphByRootIdResponses, GetV0CityByCityNameBeadsReadyData, GetV0CityByCityNameBeadsReadyError, GetV0CityByCityNameBeadsReadyErrors, GetV0CityByCityNameBeadsReadyResponse, GetV0CityByCityNameBeadsReadyResponses, GetV0CityByCityNameBeadsResponse, GetV0CityByCityNameBeadsResponses, GetV0CityByCityNameConfigData, GetV0CityByCityNameConfigError, GetV0CityByCityNameConfigErrors, GetV0CityByCityNameConfigExplainData, GetV0CityByCityNameConfigExplainError, GetV0CityByCityNameConfigExplainErrors, GetV0CityByCityNameConfigExplainResponse, GetV0CityByCityNameConfigExplainResponses, GetV0CityByCityNameConfigResponse, GetV0CityByCityNameConfigResponses, GetV0CityByCityNameConfigValidateData, GetV0CityByCityNameConfigValidateError, GetV0CityByCityNameConfigValidateErrors, GetV0CityByCityNameConfigValidateResponse, GetV0CityByCityNameConfigValidateResponses, GetV0CityByCityNameConvoyByIdCheckData, GetV0CityByCityNameConvoyByIdCheckError, GetV0CityByCityNameConvoyByIdCheckErrors, GetV0CityByCityNameConvoyByIdCheckResponse, GetV0CityByCityNameConvoyByIdCheckResponses, GetV0CityByCityNameConvoyByIdData, GetV0CityByCityNameConvoyByIdError, GetV0CityByCityNameConvoyByIdErrors, GetV0CityByCityNameConvoyByIdResponse, GetV0CityByCityNameConvoyByIdResponses, GetV0CityByCityNameConvoysData, GetV0CityByCityNameConvoysError, GetV0CityByCityNameConvoysErrors, GetV0CityByCityNameConvoysResponse, GetV0CityByCityNameConvoysResponses, GetV0CityByCityNameData, GetV0CityByCityNameError, GetV0CityByCityNameErrors, GetV0CityByCityNameEventsData, GetV0CityByCityNameEventsError, GetV0CityByCityNameEventsErrors, GetV0CityByCityNameEventsResponse, GetV0CityByCityNameEventsResponses, GetV0CityByCityNameExtmsgAdaptersData, GetV0CityByCityNameExtmsgAdaptersError, GetV0CityByCityNameExtmsgAdaptersErrors, GetV0CityByCityNameExtmsgAdaptersResponse, GetV0CityByCityNameExtmsgAdaptersResponses, GetV0CityByCityNameExtmsgBindingsData, GetV0CityByCityNameExtmsgBindingsError, GetV0CityByCityNameExtmsgBindingsErrors, GetV0CityByCityNameExtmsgBindingsResponse, GetV0CityByCityNameExtmsgBindingsResponses, GetV0CityByCityNameExtmsgGroupsData, GetV0CityByCityNameExtmsgGroupsError, GetV0CityByCityNameExtmsgGroupsErrors, GetV0CityByCityNameExtmsgGroupsResponse, GetV0CityByCityNameExtmsgGroupsResponses, GetV0CityByCityNameExtmsgTranscriptData, GetV0CityByCityNameExtmsgTranscriptError, GetV0CityByCityNameExtmsgTranscriptErrors, GetV0CityByCityNameExtmsgTranscriptResponse, GetV0CityByCityNameExtmsgTranscriptResponses, GetV0CityByCityNameFormulaByNameData, GetV0CityByCityNameFormulaByNameError, GetV0CityByCityNameFormulaByNameErrors, GetV0CityByCityNameFormulaByNameResponse, GetV0CityByCityNameFormulaByNameResponses, GetV0CityByCityNameFormulasByNameData, GetV0CityByCityNameFormulasByNameError, GetV0CityByCityNameFormulasByNameErrors, GetV0CityByCityNameFormulasByNameResponse, GetV0CityByCityNameFormulasByNameResponses, GetV0CityByCityNameFormulasByNameRunsData, GetV0CityByCityNameFormulasByNameRunsError, GetV0CityByCityNameFormulasByNameRunsErrors, GetV0CityByCityNameFormulasByNameRunsResponse, GetV0CityByCityNameFormulasByNameRunsResponses, GetV0CityByCityNameFormulasData, GetV0CityByCityNameFormulasError, GetV0CityByCityNameFormulasErrors, GetV0CityByCityNameFormulasFeedData, GetV0CityByCityNameFormulasFeedError, GetV0CityByCityNameFormulasFeedErrors, GetV0CityByCityNameFormulasFeedResponse, GetV0CityByCityNameFormulasFeedResponses, GetV0CityByCityNameFormulasResponse, GetV0CityByCityNameFormulasResponses, GetV0CityByCityNameHealthData, GetV0CityByCityNameHealthError, GetV0CityByCityNameHealthErrors, GetV0CityByCityNameHealthResponse, GetV0CityByCityNameHealthResponses, GetV0CityByCityNameMailByIdData, GetV0CityByCityNameMailByIdError, GetV0CityByCityNameMailByIdErrors, GetV0CityByCityNameMailByIdResponse, GetV0CityByCityNameMailByIdResponses, GetV0CityByCityNameMailCountData, GetV0CityByCityNameMailCountError, GetV0CityByCityNameMailCountErrors, GetV0CityByCityNameMailCountResponse, GetV0CityByCityNameMailCountResponses, GetV0CityByCityNameMailData, GetV0CityByCityNameMailError, GetV0CityByCityNameMailErrors, GetV0CityByCityNameMailResponse, GetV0CityByCityNameMailResponses, GetV0CityByCityNameMailThreadByIdData, GetV0CityByCityNameMailThreadByIdError, GetV0CityByCityNameMailThreadByIdErrors, GetV0CityByCityNameMailThreadByIdResponse, GetV0CityByCityNameMailThreadByIdResponses, GetV0CityByCityNameOrderByNameData, GetV0CityByCityNameOrderByNameError, GetV0CityByCityNameOrderByNameErrors, GetV0CityByCityNameOrderByNameResponse, GetV0CityByCityNameOrderByNameResponses, GetV0CityByCityNameOrderHistoryByBeadIdData, GetV0CityByCityNameOrderHistoryByBeadIdError, GetV0CityByCityNameOrderHistoryByBeadIdErrors, GetV0CityByCityNameOrderHistoryByBeadIdResponse, GetV0CityByCityNameOrderHistoryByBeadIdResponses, GetV0CityByCityNameOrdersCheckData, GetV0CityByCityNameOrdersCheckError, GetV0CityByCityNameOrdersCheckErrors, GetV0CityByCityNameOrdersCheckResponse, GetV0CityByCityNameOrdersCheckResponses, GetV0CityByCityNameOrdersData, GetV0CityByCityNameOrdersError, GetV0CityByCityNameOrdersErrors, GetV0CityByCityNameOrdersFeedData, GetV0CityByCityNameOrdersFeedError, GetV0CityByCityNameOrdersFeedErrors, GetV0CityByCityNameOrdersFeedResponse, GetV0CityByCityNameOrdersFeedResponses, GetV0CityByCityNameOrdersHistoryData, GetV0CityByCityNameOrdersHistoryError, GetV0CityByCityNameOrdersHistoryErrors, GetV0CityByCityNameOrdersHistoryResponse, GetV0CityByCityNameOrdersHistoryResponses, GetV0CityByCityNameOrdersResponse, GetV0CityByCityNameOrdersResponses, GetV0CityByCityNamePacksData, GetV0CityByCityNamePacksError, GetV0CityByCityNamePacksErrors, GetV0CityByCityNamePacksResponse, GetV0CityByCityNamePacksResponses, GetV0CityByCityNamePatchesAgentByBaseData, GetV0CityByCityNamePatchesAgentByBaseError, GetV0CityByCityNamePatchesAgentByBaseErrors, GetV0CityByCityNamePatchesAgentByBaseResponse, GetV0CityByCityNamePatchesAgentByBaseResponses, GetV0CityByCityNamePatchesAgentByDirByBaseData, GetV0CityByCityNamePatchesAgentByDirByBaseError, GetV0CityByCityNamePatchesAgentByDirByBaseErrors, GetV0CityByCityNamePatchesAgentByDirByBaseResponse, GetV0CityByCityNamePatchesAgentByDirByBaseResponses, GetV0CityByCityNamePatchesAgentsData, GetV0CityByCityNamePatchesAgentsError, GetV0CityByCityNamePatchesAgentsErrors, GetV0CityByCityNamePatchesAgentsResponse, GetV0CityByCityNamePatchesAgentsResponses, GetV0CityByCityNamePatchesProviderByNameData, GetV0CityByCityNamePatchesProviderByNameError, GetV0CityByCityNamePatchesProviderByNameErrors, GetV0CityByCityNamePatchesProviderByNameResponse, GetV0CityByCityNamePatchesProviderByNameResponses, GetV0CityByCityNamePatchesProvidersData, GetV0CityByCityNamePatchesProvidersError, GetV0CityByCityNamePatchesProvidersErrors, GetV0CityByCityNamePatchesProvidersResponse, GetV0CityByCityNamePatchesProvidersResponses, GetV0CityByCityNamePatchesRigByNameData, GetV0CityByCityNamePatchesRigByNameError, GetV0CityByCityNamePatchesRigByNameErrors, GetV0CityByCityNamePatchesRigByNameResponse, GetV0CityByCityNamePatchesRigByNameResponses, GetV0CityByCityNamePatchesRigsData, GetV0CityByCityNamePatchesRigsError, GetV0CityByCityNamePatchesRigsErrors, GetV0CityByCityNamePatchesRigsResponse, GetV0CityByCityNamePatchesRigsResponses, GetV0CityByCityNameProviderByNameData, GetV0CityByCityNameProviderByNameError, GetV0CityByCityNameProviderByNameErrors, GetV0CityByCityNameProviderByNameResponse, GetV0CityByCityNameProviderByNameResponses, GetV0CityByCityNameProviderReadinessData, GetV0CityByCityNameProviderReadinessError, GetV0CityByCityNameProviderReadinessErrors, GetV0CityByCityNameProviderReadinessResponse, GetV0CityByCityNameProviderReadinessResponses, GetV0CityByCityNameProvidersData, GetV0CityByCityNameProvidersError, GetV0CityByCityNameProvidersErrors, GetV0CityByCityNameProvidersPublicData, GetV0CityByCityNameProvidersPublicError, GetV0CityByCityNameProvidersPublicErrors, GetV0CityByCityNameProvidersPublicResponse, GetV0CityByCityNameProvidersPublicResponses, GetV0CityByCityNameProvidersResponse, GetV0CityByCityNameProvidersResponses, GetV0CityByCityNameReadinessData, GetV0CityByCityNameReadinessError, GetV0CityByCityNameReadinessErrors, GetV0CityByCityNameReadinessResponse, GetV0CityByCityNameReadinessResponses, GetV0CityByCityNameResponse, GetV0CityByCityNameResponses, GetV0CityByCityNameRigByNameData, GetV0CityByCityNameRigByNameError, GetV0CityByCityNameRigByNameErrors, GetV0CityByCityNameRigByNameResponse, GetV0CityByCityNameRigByNameResponses, GetV0CityByCityNameRigsData, GetV0CityByCityNameRigsError, GetV0CityByCityNameRigsErrors, GetV0CityByCityNameRigsResponse, GetV0CityByCityNameRigsResponses, GetV0CityByCityNameServiceByNameData, GetV0CityByCityNameServiceByNameError, GetV0CityByCityNameServiceByNameErrors, GetV0CityByCityNameServiceByNameResponse, GetV0CityByCityNameServiceByNameResponses, GetV0CityByCityNameServicesData, GetV0CityByCityNameServicesError, GetV0CityByCityNameServicesErrors, GetV0CityByCityNameServicesResponse, GetV0CityByCityNameServicesResponses, GetV0CityByCityNameSessionByIdAgentsByAgentIdData, GetV0CityByCityNameSessionByIdAgentsByAgentIdError, GetV0CityByCityNameSessionByIdAgentsByAgentIdErrors, GetV0CityByCityNameSessionByIdAgentsByAgentIdResponse, GetV0CityByCityNameSessionByIdAgentsByAgentIdResponses, GetV0CityByCityNameSessionByIdAgentsData, GetV0CityByCityNameSessionByIdAgentsError, GetV0CityByCityNameSessionByIdAgentsErrors, GetV0CityByCityNameSessionByIdAgentsResponse, GetV0CityByCityNameSessionByIdAgentsResponses, GetV0CityByCityNameSessionByIdData, GetV0CityByCityNameSessionByIdError, GetV0CityByCityNameSessionByIdErrors, GetV0CityByCityNameSessionByIdPendingData, GetV0CityByCityNameSessionByIdPendingError, GetV0CityByCityNameSessionByIdPendingErrors, GetV0CityByCityNameSessionByIdPendingResponse, GetV0CityByCityNameSessionByIdPendingResponses, GetV0CityByCityNameSessionByIdResponse, GetV0CityByCityNameSessionByIdResponses, GetV0CityByCityNameSessionByIdTranscriptData, GetV0CityByCityNameSessionByIdTranscriptError, GetV0CityByCityNameSessionByIdTranscriptErrors, GetV0CityByCityNameSessionByIdTranscriptResponse, GetV0CityByCityNameSessionByIdTranscriptResponses, GetV0CityByCityNameSessionsData, GetV0CityByCityNameSessionsError, GetV0CityByCityNameSessionsErrors, GetV0CityByCityNameSessionsResponse, GetV0CityByCityNameSessionsResponses, GetV0CityByCityNameStatusData, GetV0CityByCityNameStatusError, GetV0CityByCityNameStatusErrors, GetV0CityByCityNameStatusResponse, GetV0CityByCityNameStatusResponses, GetV0CityByCityNameWorkflowByWorkflowIdData, GetV0CityByCityNameWorkflowByWorkflowIdError, GetV0CityByCityNameWorkflowByWorkflowIdErrors, GetV0CityByCityNameWorkflowByWorkflowIdResponse, GetV0CityByCityNameWorkflowByWorkflowIdResponses, GetV0EventsData, GetV0EventsError, GetV0EventsErrors, GetV0EventsResponse, GetV0EventsResponses, GetV0ProviderReadinessData, GetV0ProviderReadinessError, GetV0ProviderReadinessErrors, GetV0ProviderReadinessResponse, GetV0ProviderReadinessResponses, GetV0ReadinessData, GetV0ReadinessError, GetV0ReadinessErrors, GetV0ReadinessResponse, GetV0ReadinessResponses, GitStatus, GroupCreatedEventPayload, GroupRouteDecision, HealthOutputBody, HeartbeatEvent, InboundEventPayload, InboundResult, ListBodyAgentPatch, ListBodyAgentResponse, ListBodyBead, ListBodyConversationTranscriptRecord, ListBodyExtmsgAdapterInfo, ListBodyProviderPatch, ListBodyProviderResponse, ListBodyRigPatch, ListBodyRigResponse, ListBodySessionBindingRecord, ListBodySessionResponse, ListBodyStatus, ListBodyWireEvent, LogicalNode, MailCountOutputBody, MailEventPayload, MailListBody, MailReplyInputBody, MailSendInputBody, Message, MonitorFeedItemResponse, NoPayload, OkResponseBody, OkWithIdResponseBody, OptionChoiceDto, OrderCheckListBody, OrderCheckResponse, OrderHistoryDetailResponse, OrderHistoryEntry, OrderHistoryListBody, OrderListBody, OrderResponse, OrdersFeedBody, OutboundEventPayload, OutboundResult, OutputTurn, PackListBody, PackResponse, PaginationInfo, PatchDeletedResponseBody, PatchOkResponseBody, PatchV0CityByCityNameAgentByBaseData, PatchV0CityByCityNameAgentByBaseError, PatchV0CityByCityNameAgentByBaseErrors, PatchV0CityByCityNameAgentByBaseResponse, PatchV0CityByCityNameAgentByBaseResponses, PatchV0CityByCityNameAgentByDirByBaseData, PatchV0CityByCityNameAgentByDirByBaseError, PatchV0CityByCityNameAgentByDirByBaseErrors, PatchV0CityByCityNameAgentByDirByBaseResponse, PatchV0CityByCityNameAgentByDirByBaseResponses, PatchV0CityByCityNameBeadByIdData, PatchV0CityByCityNameBeadByIdError, PatchV0CityByCityNameBeadByIdErrors, PatchV0CityByCityNameBeadByIdResponse, PatchV0CityByCityNameBeadByIdResponses, PatchV0CityByCityNameData, PatchV0CityByCityNameError, PatchV0CityByCityNameErrors, PatchV0CityByCityNameProviderByNameData, PatchV0CityByCityNameProviderByNameError, PatchV0CityByCityNameProviderByNameErrors, PatchV0CityByCityNameProviderByNameResponse, PatchV0CityByCityNameProviderByNameResponses, PatchV0CityByCityNameResponse, PatchV0CityByCityNameResponses, PatchV0CityByCityNameRigByNameData, PatchV0CityByCityNameRigByNameError, PatchV0CityByCityNameRigByNameErrors, PatchV0CityByCityNameRigByNameResponse, PatchV0CityByCityNameRigByNameResponses, PatchV0CityByCityNameSessionByIdData, PatchV0CityByCityNameSessionByIdError, PatchV0CityByCityNameSessionByIdErrors, PatchV0CityByCityNameSessionByIdResponse, PatchV0CityByCityNameSessionByIdResponses, PendingInteraction, PoolOverride, PostV0CityByCityNameAgentByBaseByActionData, PostV0CityByCityNameAgentByBaseByActionError, PostV0CityByCityNameAgentByBaseByActionErrors, PostV0CityByCityNameAgentByBaseByActionResponse, PostV0CityByCityNameAgentByBaseByActionResponses, PostV0CityByCityNameAgentByDirByBaseByActionData, PostV0CityByCityNameAgentByDirByBaseByActionError, PostV0CityByCityNameAgentByDirByBaseByActionErrors, PostV0CityByCityNameAgentByDirByBaseByActionResponse, PostV0CityByCityNameAgentByDirByBaseByActionResponses, PostV0CityByCityNameBeadByIdAssignData, PostV0CityByCityNameBeadByIdAssignError, PostV0CityByCityNameBeadByIdAssignErrors, PostV0CityByCityNameBeadByIdAssignResponse, PostV0CityByCityNameBeadByIdAssignResponses, PostV0CityByCityNameBeadByIdCloseData, PostV0CityByCityNameBeadByIdCloseError, PostV0CityByCityNameBeadByIdCloseErrors, PostV0CityByCityNameBeadByIdCloseResponse, PostV0CityByCityNameBeadByIdCloseResponses, PostV0CityByCityNameBeadByIdReopenData, PostV0CityByCityNameBeadByIdReopenError, PostV0CityByCityNameBeadByIdReopenErrors, PostV0CityByCityNameBeadByIdReopenResponse, PostV0CityByCityNameBeadByIdReopenResponses, PostV0CityByCityNameBeadByIdUpdateData, PostV0CityByCityNameBeadByIdUpdateError, PostV0CityByCityNameBeadByIdUpdateErrors, PostV0CityByCityNameBeadByIdUpdateResponse, PostV0CityByCityNameBeadByIdUpdateResponses, PostV0CityByCityNameConvoyByIdAddData, PostV0CityByCityNameConvoyByIdAddError, PostV0CityByCityNameConvoyByIdAddErrors, PostV0CityByCityNameConvoyByIdAddResponse, PostV0CityByCityNameConvoyByIdAddResponses, PostV0CityByCityNameConvoyByIdCloseData, PostV0CityByCityNameConvoyByIdCloseError, PostV0CityByCityNameConvoyByIdCloseErrors, PostV0CityByCityNameConvoyByIdCloseResponse, PostV0CityByCityNameConvoyByIdCloseResponses, PostV0CityByCityNameConvoyByIdRemoveData, PostV0CityByCityNameConvoyByIdRemoveError, PostV0CityByCityNameConvoyByIdRemoveErrors, PostV0CityByCityNameConvoyByIdRemoveResponse, PostV0CityByCityNameConvoyByIdRemoveResponses, PostV0CityByCityNameExtmsgBindData, PostV0CityByCityNameExtmsgBindError, PostV0CityByCityNameExtmsgBindErrors, PostV0CityByCityNameExtmsgBindResponse, PostV0CityByCityNameExtmsgBindResponses, PostV0CityByCityNameExtmsgInboundData, PostV0CityByCityNameExtmsgInboundError, PostV0CityByCityNameExtmsgInboundErrors, PostV0CityByCityNameExtmsgInboundResponse, PostV0CityByCityNameExtmsgInboundResponses, PostV0CityByCityNameExtmsgOutboundData, PostV0CityByCityNameExtmsgOutboundError, PostV0CityByCityNameExtmsgOutboundErrors, PostV0CityByCityNameExtmsgOutboundResponse, PostV0CityByCityNameExtmsgOutboundResponses, PostV0CityByCityNameExtmsgParticipantsData, PostV0CityByCityNameExtmsgParticipantsError, PostV0CityByCityNameExtmsgParticipantsErrors, PostV0CityByCityNameExtmsgParticipantsResponse, PostV0CityByCityNameExtmsgParticipantsResponses, PostV0CityByCityNameExtmsgTranscriptAckData, PostV0CityByCityNameExtmsgTranscriptAckError, PostV0CityByCityNameExtmsgTranscriptAckErrors, PostV0CityByCityNameExtmsgTranscriptAckResponse, PostV0CityByCityNameExtmsgTranscriptAckResponses, PostV0CityByCityNameExtmsgUnbindData, PostV0CityByCityNameExtmsgUnbindError, PostV0CityByCityNameExtmsgUnbindErrors, PostV0CityByCityNameExtmsgUnbindResponse, PostV0CityByCityNameExtmsgUnbindResponses, PostV0CityByCityNameFormulasByNamePreviewData, PostV0CityByCityNameFormulasByNamePreviewError, PostV0CityByCityNameFormulasByNamePreviewErrors, PostV0CityByCityNameFormulasByNamePreviewResponse, PostV0CityByCityNameFormulasByNamePreviewResponses, PostV0CityByCityNameMailByIdArchiveData, PostV0CityByCityNameMailByIdArchiveError, PostV0CityByCityNameMailByIdArchiveErrors, PostV0CityByCityNameMailByIdArchiveResponse, PostV0CityByCityNameMailByIdArchiveResponses, PostV0CityByCityNameMailByIdMarkUnreadData, PostV0CityByCityNameMailByIdMarkUnreadError, PostV0CityByCityNameMailByIdMarkUnreadErrors, PostV0CityByCityNameMailByIdMarkUnreadResponse, PostV0CityByCityNameMailByIdMarkUnreadResponses, PostV0CityByCityNameMailByIdReadData, PostV0CityByCityNameMailByIdReadError, PostV0CityByCityNameMailByIdReadErrors, PostV0CityByCityNameMailByIdReadResponse, PostV0CityByCityNameMailByIdReadResponses, PostV0CityByCityNameOrderByNameDisableData, PostV0CityByCityNameOrderByNameDisableError, PostV0CityByCityNameOrderByNameDisableErrors, PostV0CityByCityNameOrderByNameDisableResponse, PostV0CityByCityNameOrderByNameDisableResponses, PostV0CityByCityNameOrderByNameEnableData, PostV0CityByCityNameOrderByNameEnableError, PostV0CityByCityNameOrderByNameEnableErrors, PostV0CityByCityNameOrderByNameEnableResponse, PostV0CityByCityNameOrderByNameEnableResponses, PostV0CityByCityNameRigByNameByActionData, PostV0CityByCityNameRigByNameByActionError, PostV0CityByCityNameRigByNameByActionErrors, PostV0CityByCityNameRigByNameByActionResponse, PostV0CityByCityNameRigByNameByActionResponses, PostV0CityByCityNameServiceByNameRestartData, PostV0CityByCityNameServiceByNameRestartError, PostV0CityByCityNameServiceByNameRestartErrors, PostV0CityByCityNameServiceByNameRestartResponse, PostV0CityByCityNameServiceByNameRestartResponses, PostV0CityByCityNameSessionByIdCloseData, PostV0CityByCityNameSessionByIdCloseError, PostV0CityByCityNameSessionByIdCloseErrors, PostV0CityByCityNameSessionByIdCloseResponse, PostV0CityByCityNameSessionByIdCloseResponses, PostV0CityByCityNameSessionByIdKillData, PostV0CityByCityNameSessionByIdKillError, PostV0CityByCityNameSessionByIdKillErrors, PostV0CityByCityNameSessionByIdKillResponse, PostV0CityByCityNameSessionByIdKillResponses, PostV0CityByCityNameSessionByIdRenameData, PostV0CityByCityNameSessionByIdRenameError, PostV0CityByCityNameSessionByIdRenameErrors, PostV0CityByCityNameSessionByIdRenameResponse, PostV0CityByCityNameSessionByIdRenameResponses, PostV0CityByCityNameSessionByIdStopData, PostV0CityByCityNameSessionByIdStopError, PostV0CityByCityNameSessionByIdStopErrors, PostV0CityByCityNameSessionByIdStopResponse, PostV0CityByCityNameSessionByIdStopResponses, PostV0CityByCityNameSessionByIdSuspendData, PostV0CityByCityNameSessionByIdSuspendError, PostV0CityByCityNameSessionByIdSuspendErrors, PostV0CityByCityNameSessionByIdSuspendResponse, PostV0CityByCityNameSessionByIdSuspendResponses, PostV0CityByCityNameSessionByIdWakeData, PostV0CityByCityNameSessionByIdWakeError, PostV0CityByCityNameSessionByIdWakeErrors, PostV0CityByCityNameSessionByIdWakeResponse, PostV0CityByCityNameSessionByIdWakeResponses, PostV0CityByCityNameSlingData, PostV0CityByCityNameSlingError, PostV0CityByCityNameSlingErrors, PostV0CityByCityNameSlingResponse, PostV0CityByCityNameSlingResponses, PostV0CityData, PostV0CityError, PostV0CityErrors, PostV0CityResponse, PostV0CityResponses, ProviderCreatedOutputBody, ProviderCreateInputBody, ProviderOptionDto, ProviderPatch, ProviderPatchSetInputBody, ProviderPublicListBody, ProviderPublicResponse, ProviderReadiness, ProviderReadinessResponse, ProviderResponse, ProviderSpecJson, ProviderUpdateInputBody, PublishReceipt, PutV0CityByCityNamePatchesAgentsData, PutV0CityByCityNamePatchesAgentsError, PutV0CityByCityNamePatchesAgentsErrors, PutV0CityByCityNamePatchesAgentsResponse, PutV0CityByCityNamePatchesAgentsResponses, PutV0CityByCityNamePatchesProvidersData, PutV0CityByCityNamePatchesProvidersError, PutV0CityByCityNamePatchesProvidersErrors, PutV0CityByCityNamePatchesProvidersResponse, PutV0CityByCityNamePatchesProvidersResponses, PutV0CityByCityNamePatchesRigsData, PutV0CityByCityNamePatchesRigsError, PutV0CityByCityNamePatchesRigsErrors, PutV0CityByCityNamePatchesRigsResponse, PutV0CityByCityNamePatchesRigsResponses, ReadinessItem, ReadinessResponse, RegisterExtmsgAdapterData, RegisterExtmsgAdapterError, RegisterExtmsgAdapterErrors, RegisterExtmsgAdapterResponse, RegisterExtmsgAdapterResponses, ReplyMailData, ReplyMailError, ReplyMailErrors, ReplyMailResponse, ReplyMailResponses, RespondSessionData, RespondSessionError, RespondSessionErrors, RespondSessionResponse, RespondSessionResponses, RigActionBody, RigCreatedOutputBody, RigCreateInputBody, RigPatch, RigPatchSetInputBody, RigResponse, RigUpdateInputBody, ScopeGroup, SendMailData, SendMailError, SendMailErrors, SendMailResponse, SendMailResponses, SendSessionMessageData, SendSessionMessageError, SendSessionMessageErrors, SendSessionMessageResponse, SendSessionMessageResponses, ServiceRestartOutputBody, SessionActivityEvent, SessionAgentGetResponse, SessionAgentListResponse, SessionBindingRecord, SessionCreateBody, SessionInfo, SessionMessageInputBody, SessionMessageOutputBody, SessionPatchBody, SessionPendingResponse, SessionRawMessageFrame, SessionRenameInputBody, SessionRespondInputBody, SessionRespondOutputBody, SessionResponse, SessionStreamCommonEvent, SessionStreamMessageEvent, SessionStreamRawMessageEvent, SessionSubmitInputBody, SessionSubmitOutputBody, SessionTranscriptGetResponse, SlingInputBody, SlingResponse, Status, StatusAgentCounts, StatusBody, StatusMailCounts, StatusRigCounts, StatusWorkCounts, StreamAgentOutputData, StreamAgentOutputError, StreamAgentOutputErrors, StreamAgentOutputQualifiedData, StreamAgentOutputQualifiedError, StreamAgentOutputQualifiedErrors, StreamAgentOutputQualifiedResponse, StreamAgentOutputQualifiedResponses, StreamAgentOutputResponse, StreamAgentOutputResponses, StreamEventsData, StreamEventsError, StreamEventsErrors, StreamEventsResponse, StreamEventsResponses, StreamSessionData, StreamSessionError, StreamSessionErrors, StreamSessionResponse, StreamSessionResponses, StreamSupervisorEventsData, StreamSupervisorEventsError, StreamSupervisorEventsErrors, StreamSupervisorEventsResponse, StreamSupervisorEventsResponses, SubmissionCapabilities, SubmitIntent, SubmitSessionData, SubmitSessionError, SubmitSessionErrors, SubmitSessionResponse, SubmitSessionResponses, SupervisorCitiesOutputBody, SupervisorEventListOutputBody, SupervisorHealthOutputBody, SupervisorStartup, TaggedEventStreamEnvelope, TranscriptMessageKind, TranscriptProvenance, UnboundEventPayload, WireEvent, WireTaggedEvent, WorkerOperationEventPayload, WorkflowAttemptSummary, WorkflowBeadResponse, WorkflowDeleteResponse, WorkflowDepResponse, WorkflowEventProjection, WorkflowSnapshotResponse, WorkspaceResponse } from './types.gen'; +export { createAgent, createBead, createConvoy, createProvider, createRig, createSession, deleteV0CityByCityNameAgentByBase, deleteV0CityByCityNameAgentByDirByBase, deleteV0CityByCityNameBeadById, deleteV0CityByCityNameConvoyById, deleteV0CityByCityNameExtmsgAdapters, deleteV0CityByCityNameExtmsgParticipants, deleteV0CityByCityNameMailById, deleteV0CityByCityNamePatchesAgentByBase, deleteV0CityByCityNamePatchesAgentByDirByBase, deleteV0CityByCityNamePatchesProviderByName, deleteV0CityByCityNamePatchesRigByName, deleteV0CityByCityNameProviderByName, deleteV0CityByCityNameRigByName, deleteV0CityByCityNameWorkflowByWorkflowId, emitEvent, ensureExtmsgGroup, getHealth, getV0Cities, getV0CityByCityName, getV0CityByCityNameAgentByBase, getV0CityByCityNameAgentByBaseOutput, getV0CityByCityNameAgentByDirByBase, getV0CityByCityNameAgentByDirByBaseOutput, getV0CityByCityNameAgents, getV0CityByCityNameBeadById, getV0CityByCityNameBeadByIdDeps, getV0CityByCityNameBeads, getV0CityByCityNameBeadsGraphByRootId, getV0CityByCityNameBeadsReady, getV0CityByCityNameConfig, getV0CityByCityNameConfigExplain, getV0CityByCityNameConfigValidate, getV0CityByCityNameConvoyById, getV0CityByCityNameConvoyByIdCheck, getV0CityByCityNameConvoys, getV0CityByCityNameEvents, getV0CityByCityNameExtmsgAdapters, getV0CityByCityNameExtmsgBindings, getV0CityByCityNameExtmsgGroups, getV0CityByCityNameExtmsgTranscript, getV0CityByCityNameFormulaByName, getV0CityByCityNameFormulas, getV0CityByCityNameFormulasByName, getV0CityByCityNameFormulasByNameRuns, getV0CityByCityNameFormulasFeed, getV0CityByCityNameHealth, getV0CityByCityNameMail, getV0CityByCityNameMailById, getV0CityByCityNameMailCount, getV0CityByCityNameMailThreadById, getV0CityByCityNameOrderByName, getV0CityByCityNameOrderHistoryByBeadId, getV0CityByCityNameOrders, getV0CityByCityNameOrdersCheck, getV0CityByCityNameOrdersFeed, getV0CityByCityNameOrdersHistory, getV0CityByCityNamePacks, getV0CityByCityNamePatchesAgentByBase, getV0CityByCityNamePatchesAgentByDirByBase, getV0CityByCityNamePatchesAgents, getV0CityByCityNamePatchesProviderByName, getV0CityByCityNamePatchesProviders, getV0CityByCityNamePatchesRigByName, getV0CityByCityNamePatchesRigs, getV0CityByCityNameProviderByName, getV0CityByCityNameProviderReadiness, getV0CityByCityNameProviders, getV0CityByCityNameProvidersPublic, getV0CityByCityNameReadiness, getV0CityByCityNameRigByName, getV0CityByCityNameRigs, getV0CityByCityNameServiceByName, getV0CityByCityNameServices, getV0CityByCityNameSessionById, getV0CityByCityNameSessionByIdAgents, getV0CityByCityNameSessionByIdAgentsByAgentId, getV0CityByCityNameSessionByIdPending, getV0CityByCityNameSessionByIdTranscript, getV0CityByCityNameSessions, getV0CityByCityNameStatus, getV0CityByCityNameWorkflowByWorkflowId, getV0Events, getV0ProviderReadiness, getV0Readiness, type Options, patchV0CityByCityName, patchV0CityByCityNameAgentByBase, patchV0CityByCityNameAgentByDirByBase, patchV0CityByCityNameBeadById, patchV0CityByCityNameProviderByName, patchV0CityByCityNameRigByName, patchV0CityByCityNameSessionById, postV0City, postV0CityByCityNameAgentByBaseByAction, postV0CityByCityNameAgentByDirByBaseByAction, postV0CityByCityNameBeadByIdAssign, postV0CityByCityNameBeadByIdClose, postV0CityByCityNameBeadByIdReopen, postV0CityByCityNameBeadByIdUpdate, postV0CityByCityNameConvoyByIdAdd, postV0CityByCityNameConvoyByIdClose, postV0CityByCityNameConvoyByIdRemove, postV0CityByCityNameExtmsgBind, postV0CityByCityNameExtmsgInbound, postV0CityByCityNameExtmsgOutbound, postV0CityByCityNameExtmsgParticipants, postV0CityByCityNameExtmsgTranscriptAck, postV0CityByCityNameExtmsgUnbind, postV0CityByCityNameFormulasByNamePreview, postV0CityByCityNameMailByIdArchive, postV0CityByCityNameMailByIdMarkUnread, postV0CityByCityNameMailByIdRead, postV0CityByCityNameOrderByNameDisable, postV0CityByCityNameOrderByNameEnable, postV0CityByCityNameRigByNameByAction, postV0CityByCityNameServiceByNameRestart, postV0CityByCityNameSessionByIdClose, postV0CityByCityNameSessionByIdKill, postV0CityByCityNameSessionByIdRename, postV0CityByCityNameSessionByIdStop, postV0CityByCityNameSessionByIdSuspend, postV0CityByCityNameSessionByIdWake, postV0CityByCityNameSling, postV0CityByCityNameUnregister, putV0CityByCityNamePatchesAgents, putV0CityByCityNamePatchesProviders, putV0CityByCityNamePatchesRigs, registerExtmsgAdapter, replyMail, respondSession, sendMail, sendSessionMessage, streamAgentOutput, streamAgentOutputQualified, streamEvents, streamSession, streamSupervisorEvents, submitSession } from './sdk.gen'; +export type { AdapterCapabilities, AdapterEventPayload, AgentCreatedOutputBody, AgentCreateInputBody, AgentMapping, AgentOutputResponse, AgentPatch, AgentPatchSetInputBody, AgentResponse, AgentUpdateInputBody, AgentUpdateQualifiedInputBody, AnnotatedAgentResponse, AnnotatedProviderResponse, Bead, BeadAssignInputBody, BeadCreateInputBody, BeadDepsResponse, BeadEventPayload, BeadGraphResponse, BeadUpdateBody, BindingStatus, BoundEventPayload, CityCreateRequest, CityCreateResponse, CityGetResponse, CityInfo, CityLifecyclePayload, CityPatchInputBody, CityUnregisterResponse, ClientOptions, ConfigAgentResponse, ConfigExplainPatches, ConfigExplainResponse, ConfigPatchesResponse, ConfigResponse, ConfigRigResponse, ConfigValidateOutputBody, ConversationGroupParticipant, ConversationGroupRecord, ConversationKind, ConversationRef, ConversationTranscriptRecord, ConvoyAddInputBody, ConvoyCheckResponse, ConvoyCreateInputBody, ConvoyGetResponse, ConvoyProgress, ConvoyRemoveInputBody, CreateAgentData, CreateAgentError, CreateAgentErrors, CreateAgentResponse, CreateAgentResponses, CreateBeadData, CreateBeadError, CreateBeadErrors, CreateBeadResponse, CreateBeadResponses, CreateConvoyData, CreateConvoyError, CreateConvoyErrors, CreateConvoyResponse, CreateConvoyResponses, CreateProviderData, CreateProviderError, CreateProviderErrors, CreateProviderResponse, CreateProviderResponses, CreateRigData, CreateRigError, CreateRigErrors, CreateRigResponse, CreateRigResponses, CreateSessionData, CreateSessionError, CreateSessionErrors, CreateSessionResponse, CreateSessionResponses, DeleteV0CityByCityNameAgentByBaseData, DeleteV0CityByCityNameAgentByBaseError, DeleteV0CityByCityNameAgentByBaseErrors, DeleteV0CityByCityNameAgentByBaseResponse, DeleteV0CityByCityNameAgentByBaseResponses, DeleteV0CityByCityNameAgentByDirByBaseData, DeleteV0CityByCityNameAgentByDirByBaseError, DeleteV0CityByCityNameAgentByDirByBaseErrors, DeleteV0CityByCityNameAgentByDirByBaseResponse, DeleteV0CityByCityNameAgentByDirByBaseResponses, DeleteV0CityByCityNameBeadByIdData, DeleteV0CityByCityNameBeadByIdError, DeleteV0CityByCityNameBeadByIdErrors, DeleteV0CityByCityNameBeadByIdResponse, DeleteV0CityByCityNameBeadByIdResponses, DeleteV0CityByCityNameConvoyByIdData, DeleteV0CityByCityNameConvoyByIdError, DeleteV0CityByCityNameConvoyByIdErrors, DeleteV0CityByCityNameConvoyByIdResponse, DeleteV0CityByCityNameConvoyByIdResponses, DeleteV0CityByCityNameExtmsgAdaptersData, DeleteV0CityByCityNameExtmsgAdaptersError, DeleteV0CityByCityNameExtmsgAdaptersErrors, DeleteV0CityByCityNameExtmsgAdaptersResponse, DeleteV0CityByCityNameExtmsgAdaptersResponses, DeleteV0CityByCityNameExtmsgParticipantsData, DeleteV0CityByCityNameExtmsgParticipantsError, DeleteV0CityByCityNameExtmsgParticipantsErrors, DeleteV0CityByCityNameExtmsgParticipantsResponse, DeleteV0CityByCityNameExtmsgParticipantsResponses, DeleteV0CityByCityNameMailByIdData, DeleteV0CityByCityNameMailByIdError, DeleteV0CityByCityNameMailByIdErrors, DeleteV0CityByCityNameMailByIdResponse, DeleteV0CityByCityNameMailByIdResponses, DeleteV0CityByCityNamePatchesAgentByBaseData, DeleteV0CityByCityNamePatchesAgentByBaseError, DeleteV0CityByCityNamePatchesAgentByBaseErrors, DeleteV0CityByCityNamePatchesAgentByBaseResponse, DeleteV0CityByCityNamePatchesAgentByBaseResponses, DeleteV0CityByCityNamePatchesAgentByDirByBaseData, DeleteV0CityByCityNamePatchesAgentByDirByBaseError, DeleteV0CityByCityNamePatchesAgentByDirByBaseErrors, DeleteV0CityByCityNamePatchesAgentByDirByBaseResponse, DeleteV0CityByCityNamePatchesAgentByDirByBaseResponses, DeleteV0CityByCityNamePatchesProviderByNameData, DeleteV0CityByCityNamePatchesProviderByNameError, DeleteV0CityByCityNamePatchesProviderByNameErrors, DeleteV0CityByCityNamePatchesProviderByNameResponse, DeleteV0CityByCityNamePatchesProviderByNameResponses, DeleteV0CityByCityNamePatchesRigByNameData, DeleteV0CityByCityNamePatchesRigByNameError, DeleteV0CityByCityNamePatchesRigByNameErrors, DeleteV0CityByCityNamePatchesRigByNameResponse, DeleteV0CityByCityNamePatchesRigByNameResponses, DeleteV0CityByCityNameProviderByNameData, DeleteV0CityByCityNameProviderByNameError, DeleteV0CityByCityNameProviderByNameErrors, DeleteV0CityByCityNameProviderByNameResponse, DeleteV0CityByCityNameProviderByNameResponses, DeleteV0CityByCityNameRigByNameData, DeleteV0CityByCityNameRigByNameError, DeleteV0CityByCityNameRigByNameErrors, DeleteV0CityByCityNameRigByNameResponse, DeleteV0CityByCityNameRigByNameResponses, DeleteV0CityByCityNameWorkflowByWorkflowIdData, DeleteV0CityByCityNameWorkflowByWorkflowIdError, DeleteV0CityByCityNameWorkflowByWorkflowIdErrors, DeleteV0CityByCityNameWorkflowByWorkflowIdResponse, DeleteV0CityByCityNameWorkflowByWorkflowIdResponses, DeliveryContextRecord, Dep, EmitEventData, EmitEventError, EmitEventErrors, EmitEventResponse, EmitEventResponses, EnsureExtmsgGroupData, EnsureExtmsgGroupError, EnsureExtmsgGroupErrors, EnsureExtmsgGroupResponse, EnsureExtmsgGroupResponses, ErrorDetail, ErrorModel, EventEmitOutputBody, EventEmitRequest, EventPayload, EventStreamEnvelope, ExternalActor, ExternalAttachment, ExternalInboundMessage, ExtmsgAdapterInfo, ExtMsgAdapterRegisterInputBody, ExtMsgAdapterRegisterOutputBody, ExtMsgAdapterUnregisterInputBody, ExtMsgBindInputBody, ExtMsgGroupEnsureInputBody, ExtMsgInboundInputBody, ExtMsgOutboundInputBody, ExtMsgParticipantRemoveInputBody, ExtMsgParticipantUpsertInputBody, ExtMsgTranscriptAckInputBody, ExtMsgUnbindBody, ExtMsgUnbindInputBody, FanoutPolicy, FormulaDetailResponse, FormulaFeedBody, FormulaListBody, FormulaPreviewBody, FormulaPreviewEdgeResponse, FormulaPreviewNodeResponse, FormulaPreviewResponse, FormulaRecentRunResponse, FormulaRunsResponse, FormulaStepResponse, FormulaSummaryResponse, FormulaVarDefResponse, GetHealthData, GetHealthError, GetHealthErrors, GetHealthResponse, GetHealthResponses, GetV0CitiesData, GetV0CitiesError, GetV0CitiesErrors, GetV0CitiesResponse, GetV0CitiesResponses, GetV0CityByCityNameAgentByBaseData, GetV0CityByCityNameAgentByBaseError, GetV0CityByCityNameAgentByBaseErrors, GetV0CityByCityNameAgentByBaseOutputData, GetV0CityByCityNameAgentByBaseOutputError, GetV0CityByCityNameAgentByBaseOutputErrors, GetV0CityByCityNameAgentByBaseOutputResponse, GetV0CityByCityNameAgentByBaseOutputResponses, GetV0CityByCityNameAgentByBaseResponse, GetV0CityByCityNameAgentByBaseResponses, GetV0CityByCityNameAgentByDirByBaseData, GetV0CityByCityNameAgentByDirByBaseError, GetV0CityByCityNameAgentByDirByBaseErrors, GetV0CityByCityNameAgentByDirByBaseOutputData, GetV0CityByCityNameAgentByDirByBaseOutputError, GetV0CityByCityNameAgentByDirByBaseOutputErrors, GetV0CityByCityNameAgentByDirByBaseOutputResponse, GetV0CityByCityNameAgentByDirByBaseOutputResponses, GetV0CityByCityNameAgentByDirByBaseResponse, GetV0CityByCityNameAgentByDirByBaseResponses, GetV0CityByCityNameAgentsData, GetV0CityByCityNameAgentsError, GetV0CityByCityNameAgentsErrors, GetV0CityByCityNameAgentsResponse, GetV0CityByCityNameAgentsResponses, GetV0CityByCityNameBeadByIdData, GetV0CityByCityNameBeadByIdDepsData, GetV0CityByCityNameBeadByIdDepsError, GetV0CityByCityNameBeadByIdDepsErrors, GetV0CityByCityNameBeadByIdDepsResponse, GetV0CityByCityNameBeadByIdDepsResponses, GetV0CityByCityNameBeadByIdError, GetV0CityByCityNameBeadByIdErrors, GetV0CityByCityNameBeadByIdResponse, GetV0CityByCityNameBeadByIdResponses, GetV0CityByCityNameBeadsData, GetV0CityByCityNameBeadsError, GetV0CityByCityNameBeadsErrors, GetV0CityByCityNameBeadsGraphByRootIdData, GetV0CityByCityNameBeadsGraphByRootIdError, GetV0CityByCityNameBeadsGraphByRootIdErrors, GetV0CityByCityNameBeadsGraphByRootIdResponse, GetV0CityByCityNameBeadsGraphByRootIdResponses, GetV0CityByCityNameBeadsReadyData, GetV0CityByCityNameBeadsReadyError, GetV0CityByCityNameBeadsReadyErrors, GetV0CityByCityNameBeadsReadyResponse, GetV0CityByCityNameBeadsReadyResponses, GetV0CityByCityNameBeadsResponse, GetV0CityByCityNameBeadsResponses, GetV0CityByCityNameConfigData, GetV0CityByCityNameConfigError, GetV0CityByCityNameConfigErrors, GetV0CityByCityNameConfigExplainData, GetV0CityByCityNameConfigExplainError, GetV0CityByCityNameConfigExplainErrors, GetV0CityByCityNameConfigExplainResponse, GetV0CityByCityNameConfigExplainResponses, GetV0CityByCityNameConfigResponse, GetV0CityByCityNameConfigResponses, GetV0CityByCityNameConfigValidateData, GetV0CityByCityNameConfigValidateError, GetV0CityByCityNameConfigValidateErrors, GetV0CityByCityNameConfigValidateResponse, GetV0CityByCityNameConfigValidateResponses, GetV0CityByCityNameConvoyByIdCheckData, GetV0CityByCityNameConvoyByIdCheckError, GetV0CityByCityNameConvoyByIdCheckErrors, GetV0CityByCityNameConvoyByIdCheckResponse, GetV0CityByCityNameConvoyByIdCheckResponses, GetV0CityByCityNameConvoyByIdData, GetV0CityByCityNameConvoyByIdError, GetV0CityByCityNameConvoyByIdErrors, GetV0CityByCityNameConvoyByIdResponse, GetV0CityByCityNameConvoyByIdResponses, GetV0CityByCityNameConvoysData, GetV0CityByCityNameConvoysError, GetV0CityByCityNameConvoysErrors, GetV0CityByCityNameConvoysResponse, GetV0CityByCityNameConvoysResponses, GetV0CityByCityNameData, GetV0CityByCityNameError, GetV0CityByCityNameErrors, GetV0CityByCityNameEventsData, GetV0CityByCityNameEventsError, GetV0CityByCityNameEventsErrors, GetV0CityByCityNameEventsResponse, GetV0CityByCityNameEventsResponses, GetV0CityByCityNameExtmsgAdaptersData, GetV0CityByCityNameExtmsgAdaptersError, GetV0CityByCityNameExtmsgAdaptersErrors, GetV0CityByCityNameExtmsgAdaptersResponse, GetV0CityByCityNameExtmsgAdaptersResponses, GetV0CityByCityNameExtmsgBindingsData, GetV0CityByCityNameExtmsgBindingsError, GetV0CityByCityNameExtmsgBindingsErrors, GetV0CityByCityNameExtmsgBindingsResponse, GetV0CityByCityNameExtmsgBindingsResponses, GetV0CityByCityNameExtmsgGroupsData, GetV0CityByCityNameExtmsgGroupsError, GetV0CityByCityNameExtmsgGroupsErrors, GetV0CityByCityNameExtmsgGroupsResponse, GetV0CityByCityNameExtmsgGroupsResponses, GetV0CityByCityNameExtmsgTranscriptData, GetV0CityByCityNameExtmsgTranscriptError, GetV0CityByCityNameExtmsgTranscriptErrors, GetV0CityByCityNameExtmsgTranscriptResponse, GetV0CityByCityNameExtmsgTranscriptResponses, GetV0CityByCityNameFormulaByNameData, GetV0CityByCityNameFormulaByNameError, GetV0CityByCityNameFormulaByNameErrors, GetV0CityByCityNameFormulaByNameResponse, GetV0CityByCityNameFormulaByNameResponses, GetV0CityByCityNameFormulasByNameData, GetV0CityByCityNameFormulasByNameError, GetV0CityByCityNameFormulasByNameErrors, GetV0CityByCityNameFormulasByNameResponse, GetV0CityByCityNameFormulasByNameResponses, GetV0CityByCityNameFormulasByNameRunsData, GetV0CityByCityNameFormulasByNameRunsError, GetV0CityByCityNameFormulasByNameRunsErrors, GetV0CityByCityNameFormulasByNameRunsResponse, GetV0CityByCityNameFormulasByNameRunsResponses, GetV0CityByCityNameFormulasData, GetV0CityByCityNameFormulasError, GetV0CityByCityNameFormulasErrors, GetV0CityByCityNameFormulasFeedData, GetV0CityByCityNameFormulasFeedError, GetV0CityByCityNameFormulasFeedErrors, GetV0CityByCityNameFormulasFeedResponse, GetV0CityByCityNameFormulasFeedResponses, GetV0CityByCityNameFormulasResponse, GetV0CityByCityNameFormulasResponses, GetV0CityByCityNameHealthData, GetV0CityByCityNameHealthError, GetV0CityByCityNameHealthErrors, GetV0CityByCityNameHealthResponse, GetV0CityByCityNameHealthResponses, GetV0CityByCityNameMailByIdData, GetV0CityByCityNameMailByIdError, GetV0CityByCityNameMailByIdErrors, GetV0CityByCityNameMailByIdResponse, GetV0CityByCityNameMailByIdResponses, GetV0CityByCityNameMailCountData, GetV0CityByCityNameMailCountError, GetV0CityByCityNameMailCountErrors, GetV0CityByCityNameMailCountResponse, GetV0CityByCityNameMailCountResponses, GetV0CityByCityNameMailData, GetV0CityByCityNameMailError, GetV0CityByCityNameMailErrors, GetV0CityByCityNameMailResponse, GetV0CityByCityNameMailResponses, GetV0CityByCityNameMailThreadByIdData, GetV0CityByCityNameMailThreadByIdError, GetV0CityByCityNameMailThreadByIdErrors, GetV0CityByCityNameMailThreadByIdResponse, GetV0CityByCityNameMailThreadByIdResponses, GetV0CityByCityNameOrderByNameData, GetV0CityByCityNameOrderByNameError, GetV0CityByCityNameOrderByNameErrors, GetV0CityByCityNameOrderByNameResponse, GetV0CityByCityNameOrderByNameResponses, GetV0CityByCityNameOrderHistoryByBeadIdData, GetV0CityByCityNameOrderHistoryByBeadIdError, GetV0CityByCityNameOrderHistoryByBeadIdErrors, GetV0CityByCityNameOrderHistoryByBeadIdResponse, GetV0CityByCityNameOrderHistoryByBeadIdResponses, GetV0CityByCityNameOrdersCheckData, GetV0CityByCityNameOrdersCheckError, GetV0CityByCityNameOrdersCheckErrors, GetV0CityByCityNameOrdersCheckResponse, GetV0CityByCityNameOrdersCheckResponses, GetV0CityByCityNameOrdersData, GetV0CityByCityNameOrdersError, GetV0CityByCityNameOrdersErrors, GetV0CityByCityNameOrdersFeedData, GetV0CityByCityNameOrdersFeedError, GetV0CityByCityNameOrdersFeedErrors, GetV0CityByCityNameOrdersFeedResponse, GetV0CityByCityNameOrdersFeedResponses, GetV0CityByCityNameOrdersHistoryData, GetV0CityByCityNameOrdersHistoryError, GetV0CityByCityNameOrdersHistoryErrors, GetV0CityByCityNameOrdersHistoryResponse, GetV0CityByCityNameOrdersHistoryResponses, GetV0CityByCityNameOrdersResponse, GetV0CityByCityNameOrdersResponses, GetV0CityByCityNamePacksData, GetV0CityByCityNamePacksError, GetV0CityByCityNamePacksErrors, GetV0CityByCityNamePacksResponse, GetV0CityByCityNamePacksResponses, GetV0CityByCityNamePatchesAgentByBaseData, GetV0CityByCityNamePatchesAgentByBaseError, GetV0CityByCityNamePatchesAgentByBaseErrors, GetV0CityByCityNamePatchesAgentByBaseResponse, GetV0CityByCityNamePatchesAgentByBaseResponses, GetV0CityByCityNamePatchesAgentByDirByBaseData, GetV0CityByCityNamePatchesAgentByDirByBaseError, GetV0CityByCityNamePatchesAgentByDirByBaseErrors, GetV0CityByCityNamePatchesAgentByDirByBaseResponse, GetV0CityByCityNamePatchesAgentByDirByBaseResponses, GetV0CityByCityNamePatchesAgentsData, GetV0CityByCityNamePatchesAgentsError, GetV0CityByCityNamePatchesAgentsErrors, GetV0CityByCityNamePatchesAgentsResponse, GetV0CityByCityNamePatchesAgentsResponses, GetV0CityByCityNamePatchesProviderByNameData, GetV0CityByCityNamePatchesProviderByNameError, GetV0CityByCityNamePatchesProviderByNameErrors, GetV0CityByCityNamePatchesProviderByNameResponse, GetV0CityByCityNamePatchesProviderByNameResponses, GetV0CityByCityNamePatchesProvidersData, GetV0CityByCityNamePatchesProvidersError, GetV0CityByCityNamePatchesProvidersErrors, GetV0CityByCityNamePatchesProvidersResponse, GetV0CityByCityNamePatchesProvidersResponses, GetV0CityByCityNamePatchesRigByNameData, GetV0CityByCityNamePatchesRigByNameError, GetV0CityByCityNamePatchesRigByNameErrors, GetV0CityByCityNamePatchesRigByNameResponse, GetV0CityByCityNamePatchesRigByNameResponses, GetV0CityByCityNamePatchesRigsData, GetV0CityByCityNamePatchesRigsError, GetV0CityByCityNamePatchesRigsErrors, GetV0CityByCityNamePatchesRigsResponse, GetV0CityByCityNamePatchesRigsResponses, GetV0CityByCityNameProviderByNameData, GetV0CityByCityNameProviderByNameError, GetV0CityByCityNameProviderByNameErrors, GetV0CityByCityNameProviderByNameResponse, GetV0CityByCityNameProviderByNameResponses, GetV0CityByCityNameProviderReadinessData, GetV0CityByCityNameProviderReadinessError, GetV0CityByCityNameProviderReadinessErrors, GetV0CityByCityNameProviderReadinessResponse, GetV0CityByCityNameProviderReadinessResponses, GetV0CityByCityNameProvidersData, GetV0CityByCityNameProvidersError, GetV0CityByCityNameProvidersErrors, GetV0CityByCityNameProvidersPublicData, GetV0CityByCityNameProvidersPublicError, GetV0CityByCityNameProvidersPublicErrors, GetV0CityByCityNameProvidersPublicResponse, GetV0CityByCityNameProvidersPublicResponses, GetV0CityByCityNameProvidersResponse, GetV0CityByCityNameProvidersResponses, GetV0CityByCityNameReadinessData, GetV0CityByCityNameReadinessError, GetV0CityByCityNameReadinessErrors, GetV0CityByCityNameReadinessResponse, GetV0CityByCityNameReadinessResponses, GetV0CityByCityNameResponse, GetV0CityByCityNameResponses, GetV0CityByCityNameRigByNameData, GetV0CityByCityNameRigByNameError, GetV0CityByCityNameRigByNameErrors, GetV0CityByCityNameRigByNameResponse, GetV0CityByCityNameRigByNameResponses, GetV0CityByCityNameRigsData, GetV0CityByCityNameRigsError, GetV0CityByCityNameRigsErrors, GetV0CityByCityNameRigsResponse, GetV0CityByCityNameRigsResponses, GetV0CityByCityNameServiceByNameData, GetV0CityByCityNameServiceByNameError, GetV0CityByCityNameServiceByNameErrors, GetV0CityByCityNameServiceByNameResponse, GetV0CityByCityNameServiceByNameResponses, GetV0CityByCityNameServicesData, GetV0CityByCityNameServicesError, GetV0CityByCityNameServicesErrors, GetV0CityByCityNameServicesResponse, GetV0CityByCityNameServicesResponses, GetV0CityByCityNameSessionByIdAgentsByAgentIdData, GetV0CityByCityNameSessionByIdAgentsByAgentIdError, GetV0CityByCityNameSessionByIdAgentsByAgentIdErrors, GetV0CityByCityNameSessionByIdAgentsByAgentIdResponse, GetV0CityByCityNameSessionByIdAgentsByAgentIdResponses, GetV0CityByCityNameSessionByIdAgentsData, GetV0CityByCityNameSessionByIdAgentsError, GetV0CityByCityNameSessionByIdAgentsErrors, GetV0CityByCityNameSessionByIdAgentsResponse, GetV0CityByCityNameSessionByIdAgentsResponses, GetV0CityByCityNameSessionByIdData, GetV0CityByCityNameSessionByIdError, GetV0CityByCityNameSessionByIdErrors, GetV0CityByCityNameSessionByIdPendingData, GetV0CityByCityNameSessionByIdPendingError, GetV0CityByCityNameSessionByIdPendingErrors, GetV0CityByCityNameSessionByIdPendingResponse, GetV0CityByCityNameSessionByIdPendingResponses, GetV0CityByCityNameSessionByIdResponse, GetV0CityByCityNameSessionByIdResponses, GetV0CityByCityNameSessionByIdTranscriptData, GetV0CityByCityNameSessionByIdTranscriptError, GetV0CityByCityNameSessionByIdTranscriptErrors, GetV0CityByCityNameSessionByIdTranscriptResponse, GetV0CityByCityNameSessionByIdTranscriptResponses, GetV0CityByCityNameSessionsData, GetV0CityByCityNameSessionsError, GetV0CityByCityNameSessionsErrors, GetV0CityByCityNameSessionsResponse, GetV0CityByCityNameSessionsResponses, GetV0CityByCityNameStatusData, GetV0CityByCityNameStatusError, GetV0CityByCityNameStatusErrors, GetV0CityByCityNameStatusResponse, GetV0CityByCityNameStatusResponses, GetV0CityByCityNameWorkflowByWorkflowIdData, GetV0CityByCityNameWorkflowByWorkflowIdError, GetV0CityByCityNameWorkflowByWorkflowIdErrors, GetV0CityByCityNameWorkflowByWorkflowIdResponse, GetV0CityByCityNameWorkflowByWorkflowIdResponses, GetV0EventsData, GetV0EventsError, GetV0EventsErrors, GetV0EventsResponse, GetV0EventsResponses, GetV0ProviderReadinessData, GetV0ProviderReadinessError, GetV0ProviderReadinessErrors, GetV0ProviderReadinessResponse, GetV0ProviderReadinessResponses, GetV0ReadinessData, GetV0ReadinessError, GetV0ReadinessErrors, GetV0ReadinessResponse, GetV0ReadinessResponses, GitStatus, GroupCreatedEventPayload, GroupRouteDecision, HealthOutputBody, HeartbeatEvent, InboundEventPayload, InboundResult, ListBodyAgentPatch, ListBodyAgentResponse, ListBodyBead, ListBodyConversationTranscriptRecord, ListBodyExtmsgAdapterInfo, ListBodyProviderPatch, ListBodyProviderResponse, ListBodyRigPatch, ListBodyRigResponse, ListBodySessionBindingRecord, ListBodySessionResponse, ListBodyStatus, ListBodyWireEvent, LogicalNode, MailCountOutputBody, MailEventPayload, MailListBody, MailReplyInputBody, MailSendInputBody, Message, MonitorFeedItemResponse, NoPayload, OkResponseBody, OkWithIdResponseBody, OptionChoiceDto, OrderCheckListBody, OrderCheckResponse, OrderHistoryDetailResponse, OrderHistoryEntry, OrderHistoryListBody, OrderListBody, OrderResponse, OrdersFeedBody, OutboundEventPayload, OutboundResult, OutputTurn, PackListBody, PackResponse, PaginationInfo, PatchDeletedResponseBody, PatchOkResponseBody, PatchV0CityByCityNameAgentByBaseData, PatchV0CityByCityNameAgentByBaseError, PatchV0CityByCityNameAgentByBaseErrors, PatchV0CityByCityNameAgentByBaseResponse, PatchV0CityByCityNameAgentByBaseResponses, PatchV0CityByCityNameAgentByDirByBaseData, PatchV0CityByCityNameAgentByDirByBaseError, PatchV0CityByCityNameAgentByDirByBaseErrors, PatchV0CityByCityNameAgentByDirByBaseResponse, PatchV0CityByCityNameAgentByDirByBaseResponses, PatchV0CityByCityNameBeadByIdData, PatchV0CityByCityNameBeadByIdError, PatchV0CityByCityNameBeadByIdErrors, PatchV0CityByCityNameBeadByIdResponse, PatchV0CityByCityNameBeadByIdResponses, PatchV0CityByCityNameData, PatchV0CityByCityNameError, PatchV0CityByCityNameErrors, PatchV0CityByCityNameProviderByNameData, PatchV0CityByCityNameProviderByNameError, PatchV0CityByCityNameProviderByNameErrors, PatchV0CityByCityNameProviderByNameResponse, PatchV0CityByCityNameProviderByNameResponses, PatchV0CityByCityNameResponse, PatchV0CityByCityNameResponses, PatchV0CityByCityNameRigByNameData, PatchV0CityByCityNameRigByNameError, PatchV0CityByCityNameRigByNameErrors, PatchV0CityByCityNameRigByNameResponse, PatchV0CityByCityNameRigByNameResponses, PatchV0CityByCityNameSessionByIdData, PatchV0CityByCityNameSessionByIdError, PatchV0CityByCityNameSessionByIdErrors, PatchV0CityByCityNameSessionByIdResponse, PatchV0CityByCityNameSessionByIdResponses, PendingInteraction, PoolOverride, PostV0CityByCityNameAgentByBaseByActionData, PostV0CityByCityNameAgentByBaseByActionError, PostV0CityByCityNameAgentByBaseByActionErrors, PostV0CityByCityNameAgentByBaseByActionResponse, PostV0CityByCityNameAgentByBaseByActionResponses, PostV0CityByCityNameAgentByDirByBaseByActionData, PostV0CityByCityNameAgentByDirByBaseByActionError, PostV0CityByCityNameAgentByDirByBaseByActionErrors, PostV0CityByCityNameAgentByDirByBaseByActionResponse, PostV0CityByCityNameAgentByDirByBaseByActionResponses, PostV0CityByCityNameBeadByIdAssignData, PostV0CityByCityNameBeadByIdAssignError, PostV0CityByCityNameBeadByIdAssignErrors, PostV0CityByCityNameBeadByIdAssignResponse, PostV0CityByCityNameBeadByIdAssignResponses, PostV0CityByCityNameBeadByIdCloseData, PostV0CityByCityNameBeadByIdCloseError, PostV0CityByCityNameBeadByIdCloseErrors, PostV0CityByCityNameBeadByIdCloseResponse, PostV0CityByCityNameBeadByIdCloseResponses, PostV0CityByCityNameBeadByIdReopenData, PostV0CityByCityNameBeadByIdReopenError, PostV0CityByCityNameBeadByIdReopenErrors, PostV0CityByCityNameBeadByIdReopenResponse, PostV0CityByCityNameBeadByIdReopenResponses, PostV0CityByCityNameBeadByIdUpdateData, PostV0CityByCityNameBeadByIdUpdateError, PostV0CityByCityNameBeadByIdUpdateErrors, PostV0CityByCityNameBeadByIdUpdateResponse, PostV0CityByCityNameBeadByIdUpdateResponses, PostV0CityByCityNameConvoyByIdAddData, PostV0CityByCityNameConvoyByIdAddError, PostV0CityByCityNameConvoyByIdAddErrors, PostV0CityByCityNameConvoyByIdAddResponse, PostV0CityByCityNameConvoyByIdAddResponses, PostV0CityByCityNameConvoyByIdCloseData, PostV0CityByCityNameConvoyByIdCloseError, PostV0CityByCityNameConvoyByIdCloseErrors, PostV0CityByCityNameConvoyByIdCloseResponse, PostV0CityByCityNameConvoyByIdCloseResponses, PostV0CityByCityNameConvoyByIdRemoveData, PostV0CityByCityNameConvoyByIdRemoveError, PostV0CityByCityNameConvoyByIdRemoveErrors, PostV0CityByCityNameConvoyByIdRemoveResponse, PostV0CityByCityNameConvoyByIdRemoveResponses, PostV0CityByCityNameExtmsgBindData, PostV0CityByCityNameExtmsgBindError, PostV0CityByCityNameExtmsgBindErrors, PostV0CityByCityNameExtmsgBindResponse, PostV0CityByCityNameExtmsgBindResponses, PostV0CityByCityNameExtmsgInboundData, PostV0CityByCityNameExtmsgInboundError, PostV0CityByCityNameExtmsgInboundErrors, PostV0CityByCityNameExtmsgInboundResponse, PostV0CityByCityNameExtmsgInboundResponses, PostV0CityByCityNameExtmsgOutboundData, PostV0CityByCityNameExtmsgOutboundError, PostV0CityByCityNameExtmsgOutboundErrors, PostV0CityByCityNameExtmsgOutboundResponse, PostV0CityByCityNameExtmsgOutboundResponses, PostV0CityByCityNameExtmsgParticipantsData, PostV0CityByCityNameExtmsgParticipantsError, PostV0CityByCityNameExtmsgParticipantsErrors, PostV0CityByCityNameExtmsgParticipantsResponse, PostV0CityByCityNameExtmsgParticipantsResponses, PostV0CityByCityNameExtmsgTranscriptAckData, PostV0CityByCityNameExtmsgTranscriptAckError, PostV0CityByCityNameExtmsgTranscriptAckErrors, PostV0CityByCityNameExtmsgTranscriptAckResponse, PostV0CityByCityNameExtmsgTranscriptAckResponses, PostV0CityByCityNameExtmsgUnbindData, PostV0CityByCityNameExtmsgUnbindError, PostV0CityByCityNameExtmsgUnbindErrors, PostV0CityByCityNameExtmsgUnbindResponse, PostV0CityByCityNameExtmsgUnbindResponses, PostV0CityByCityNameFormulasByNamePreviewData, PostV0CityByCityNameFormulasByNamePreviewError, PostV0CityByCityNameFormulasByNamePreviewErrors, PostV0CityByCityNameFormulasByNamePreviewResponse, PostV0CityByCityNameFormulasByNamePreviewResponses, PostV0CityByCityNameMailByIdArchiveData, PostV0CityByCityNameMailByIdArchiveError, PostV0CityByCityNameMailByIdArchiveErrors, PostV0CityByCityNameMailByIdArchiveResponse, PostV0CityByCityNameMailByIdArchiveResponses, PostV0CityByCityNameMailByIdMarkUnreadData, PostV0CityByCityNameMailByIdMarkUnreadError, PostV0CityByCityNameMailByIdMarkUnreadErrors, PostV0CityByCityNameMailByIdMarkUnreadResponse, PostV0CityByCityNameMailByIdMarkUnreadResponses, PostV0CityByCityNameMailByIdReadData, PostV0CityByCityNameMailByIdReadError, PostV0CityByCityNameMailByIdReadErrors, PostV0CityByCityNameMailByIdReadResponse, PostV0CityByCityNameMailByIdReadResponses, PostV0CityByCityNameOrderByNameDisableData, PostV0CityByCityNameOrderByNameDisableError, PostV0CityByCityNameOrderByNameDisableErrors, PostV0CityByCityNameOrderByNameDisableResponse, PostV0CityByCityNameOrderByNameDisableResponses, PostV0CityByCityNameOrderByNameEnableData, PostV0CityByCityNameOrderByNameEnableError, PostV0CityByCityNameOrderByNameEnableErrors, PostV0CityByCityNameOrderByNameEnableResponse, PostV0CityByCityNameOrderByNameEnableResponses, PostV0CityByCityNameRigByNameByActionData, PostV0CityByCityNameRigByNameByActionError, PostV0CityByCityNameRigByNameByActionErrors, PostV0CityByCityNameRigByNameByActionResponse, PostV0CityByCityNameRigByNameByActionResponses, PostV0CityByCityNameServiceByNameRestartData, PostV0CityByCityNameServiceByNameRestartError, PostV0CityByCityNameServiceByNameRestartErrors, PostV0CityByCityNameServiceByNameRestartResponse, PostV0CityByCityNameServiceByNameRestartResponses, PostV0CityByCityNameSessionByIdCloseData, PostV0CityByCityNameSessionByIdCloseError, PostV0CityByCityNameSessionByIdCloseErrors, PostV0CityByCityNameSessionByIdCloseResponse, PostV0CityByCityNameSessionByIdCloseResponses, PostV0CityByCityNameSessionByIdKillData, PostV0CityByCityNameSessionByIdKillError, PostV0CityByCityNameSessionByIdKillErrors, PostV0CityByCityNameSessionByIdKillResponse, PostV0CityByCityNameSessionByIdKillResponses, PostV0CityByCityNameSessionByIdRenameData, PostV0CityByCityNameSessionByIdRenameError, PostV0CityByCityNameSessionByIdRenameErrors, PostV0CityByCityNameSessionByIdRenameResponse, PostV0CityByCityNameSessionByIdRenameResponses, PostV0CityByCityNameSessionByIdStopData, PostV0CityByCityNameSessionByIdStopError, PostV0CityByCityNameSessionByIdStopErrors, PostV0CityByCityNameSessionByIdStopResponse, PostV0CityByCityNameSessionByIdStopResponses, PostV0CityByCityNameSessionByIdSuspendData, PostV0CityByCityNameSessionByIdSuspendError, PostV0CityByCityNameSessionByIdSuspendErrors, PostV0CityByCityNameSessionByIdSuspendResponse, PostV0CityByCityNameSessionByIdSuspendResponses, PostV0CityByCityNameSessionByIdWakeData, PostV0CityByCityNameSessionByIdWakeError, PostV0CityByCityNameSessionByIdWakeErrors, PostV0CityByCityNameSessionByIdWakeResponse, PostV0CityByCityNameSessionByIdWakeResponses, PostV0CityByCityNameSlingData, PostV0CityByCityNameSlingError, PostV0CityByCityNameSlingErrors, PostV0CityByCityNameSlingResponse, PostV0CityByCityNameSlingResponses, PostV0CityByCityNameUnregisterData, PostV0CityByCityNameUnregisterError, PostV0CityByCityNameUnregisterErrors, PostV0CityByCityNameUnregisterResponse, PostV0CityByCityNameUnregisterResponses, PostV0CityData, PostV0CityError, PostV0CityErrors, PostV0CityResponse, PostV0CityResponses, ProviderCreatedOutputBody, ProviderCreateInputBody, ProviderOptionDto, ProviderPatch, ProviderPatchSetInputBody, ProviderPublicListBody, ProviderPublicResponse, ProviderReadiness, ProviderReadinessResponse, ProviderResponse, ProviderSpecJson, ProviderUpdateInputBody, PublishReceipt, PutV0CityByCityNamePatchesAgentsData, PutV0CityByCityNamePatchesAgentsError, PutV0CityByCityNamePatchesAgentsErrors, PutV0CityByCityNamePatchesAgentsResponse, PutV0CityByCityNamePatchesAgentsResponses, PutV0CityByCityNamePatchesProvidersData, PutV0CityByCityNamePatchesProvidersError, PutV0CityByCityNamePatchesProvidersErrors, PutV0CityByCityNamePatchesProvidersResponse, PutV0CityByCityNamePatchesProvidersResponses, PutV0CityByCityNamePatchesRigsData, PutV0CityByCityNamePatchesRigsError, PutV0CityByCityNamePatchesRigsErrors, PutV0CityByCityNamePatchesRigsResponse, PutV0CityByCityNamePatchesRigsResponses, ReadinessItem, ReadinessResponse, RegisterExtmsgAdapterData, RegisterExtmsgAdapterError, RegisterExtmsgAdapterErrors, RegisterExtmsgAdapterResponse, RegisterExtmsgAdapterResponses, ReplyMailData, ReplyMailError, ReplyMailErrors, ReplyMailResponse, ReplyMailResponses, RespondSessionData, RespondSessionError, RespondSessionErrors, RespondSessionResponse, RespondSessionResponses, RigActionBody, RigCreatedOutputBody, RigCreateInputBody, RigPatch, RigPatchSetInputBody, RigResponse, RigUpdateInputBody, ScopeGroup, SendMailData, SendMailError, SendMailErrors, SendMailResponse, SendMailResponses, SendSessionMessageData, SendSessionMessageError, SendSessionMessageErrors, SendSessionMessageResponse, SendSessionMessageResponses, ServiceRestartOutputBody, SessionActivityEvent, SessionAgentGetResponse, SessionAgentListResponse, SessionBindingRecord, SessionCreateBody, SessionInfo, SessionMessageInputBody, SessionMessageOutputBody, SessionPatchBody, SessionPendingResponse, SessionRawMessageFrame, SessionRenameInputBody, SessionRespondInputBody, SessionRespondOutputBody, SessionResponse, SessionStreamCommonEvent, SessionStreamMessageEvent, SessionStreamRawMessageEvent, SessionSubmitInputBody, SessionSubmitOutputBody, SessionTranscriptGetResponse, SlingInputBody, SlingResponse, Status, StatusAgentCounts, StatusBody, StatusMailCounts, StatusRigCounts, StatusWorkCounts, StreamAgentOutputData, StreamAgentOutputError, StreamAgentOutputErrors, StreamAgentOutputQualifiedData, StreamAgentOutputQualifiedError, StreamAgentOutputQualifiedErrors, StreamAgentOutputQualifiedResponse, StreamAgentOutputQualifiedResponses, StreamAgentOutputResponse, StreamAgentOutputResponses, StreamEventsData, StreamEventsError, StreamEventsErrors, StreamEventsResponse, StreamEventsResponses, StreamSessionData, StreamSessionError, StreamSessionErrors, StreamSessionResponse, StreamSessionResponses, StreamSupervisorEventsData, StreamSupervisorEventsError, StreamSupervisorEventsErrors, StreamSupervisorEventsResponse, StreamSupervisorEventsResponses, SubmissionCapabilities, SubmitIntent, SubmitSessionData, SubmitSessionError, SubmitSessionErrors, SubmitSessionResponse, SubmitSessionResponses, SupervisorCitiesOutputBody, SupervisorEventListOutputBody, SupervisorHealthOutputBody, SupervisorStartup, TaggedEventStreamEnvelope, TranscriptMessageKind, TranscriptProvenance, TypedEventStreamEnvelope, TypedEventStreamEnvelopeBeadClosed, TypedEventStreamEnvelopeBeadCreated, TypedEventStreamEnvelopeBeadUpdated, TypedEventStreamEnvelopeCityCreated, TypedEventStreamEnvelopeCityInitFailed, TypedEventStreamEnvelopeCityReady, TypedEventStreamEnvelopeCityResumed, TypedEventStreamEnvelopeCitySuspended, TypedEventStreamEnvelopeCityUnregistered, TypedEventStreamEnvelopeCityUnregisterFailed, TypedEventStreamEnvelopeCityUnregisterRequested, TypedEventStreamEnvelopeControllerStarted, TypedEventStreamEnvelopeControllerStopped, TypedEventStreamEnvelopeConvoyClosed, TypedEventStreamEnvelopeConvoyCreated, TypedEventStreamEnvelopeExtmsgAdapterAdded, TypedEventStreamEnvelopeExtmsgAdapterRemoved, TypedEventStreamEnvelopeExtmsgBound, TypedEventStreamEnvelopeExtmsgGroupCreated, TypedEventStreamEnvelopeExtmsgInbound, TypedEventStreamEnvelopeExtmsgOutbound, TypedEventStreamEnvelopeExtmsgUnbound, TypedEventStreamEnvelopeMailArchived, TypedEventStreamEnvelopeMailDeleted, TypedEventStreamEnvelopeMailMarkedRead, TypedEventStreamEnvelopeMailMarkedUnread, TypedEventStreamEnvelopeMailRead, TypedEventStreamEnvelopeMailReplied, TypedEventStreamEnvelopeMailSent, TypedEventStreamEnvelopeOrderCompleted, TypedEventStreamEnvelopeOrderFailed, TypedEventStreamEnvelopeOrderFired, TypedEventStreamEnvelopeProviderSwapped, TypedEventStreamEnvelopeSessionCrashed, TypedEventStreamEnvelopeSessionDraining, TypedEventStreamEnvelopeSessionIdleKilled, TypedEventStreamEnvelopeSessionQuarantined, TypedEventStreamEnvelopeSessionStopped, TypedEventStreamEnvelopeSessionSuspended, TypedEventStreamEnvelopeSessionUndrained, TypedEventStreamEnvelopeSessionUpdated, TypedEventStreamEnvelopeSessionWoke, TypedEventStreamEnvelopeWorkerOperation, TypedTaggedEventStreamEnvelope, TypedTaggedEventStreamEnvelopeBeadClosed, TypedTaggedEventStreamEnvelopeBeadCreated, TypedTaggedEventStreamEnvelopeBeadUpdated, TypedTaggedEventStreamEnvelopeCityCreated, TypedTaggedEventStreamEnvelopeCityInitFailed, TypedTaggedEventStreamEnvelopeCityReady, TypedTaggedEventStreamEnvelopeCityResumed, TypedTaggedEventStreamEnvelopeCitySuspended, TypedTaggedEventStreamEnvelopeCityUnregistered, TypedTaggedEventStreamEnvelopeCityUnregisterFailed, TypedTaggedEventStreamEnvelopeCityUnregisterRequested, TypedTaggedEventStreamEnvelopeControllerStarted, TypedTaggedEventStreamEnvelopeControllerStopped, TypedTaggedEventStreamEnvelopeConvoyClosed, TypedTaggedEventStreamEnvelopeConvoyCreated, TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded, TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved, TypedTaggedEventStreamEnvelopeExtmsgBound, TypedTaggedEventStreamEnvelopeExtmsgGroupCreated, TypedTaggedEventStreamEnvelopeExtmsgInbound, TypedTaggedEventStreamEnvelopeExtmsgOutbound, TypedTaggedEventStreamEnvelopeExtmsgUnbound, TypedTaggedEventStreamEnvelopeMailArchived, TypedTaggedEventStreamEnvelopeMailDeleted, TypedTaggedEventStreamEnvelopeMailMarkedRead, TypedTaggedEventStreamEnvelopeMailMarkedUnread, TypedTaggedEventStreamEnvelopeMailRead, TypedTaggedEventStreamEnvelopeMailReplied, TypedTaggedEventStreamEnvelopeMailSent, TypedTaggedEventStreamEnvelopeOrderCompleted, TypedTaggedEventStreamEnvelopeOrderFailed, TypedTaggedEventStreamEnvelopeOrderFired, TypedTaggedEventStreamEnvelopeProviderSwapped, TypedTaggedEventStreamEnvelopeSessionCrashed, TypedTaggedEventStreamEnvelopeSessionDraining, TypedTaggedEventStreamEnvelopeSessionIdleKilled, TypedTaggedEventStreamEnvelopeSessionQuarantined, TypedTaggedEventStreamEnvelopeSessionStopped, TypedTaggedEventStreamEnvelopeSessionSuspended, TypedTaggedEventStreamEnvelopeSessionUndrained, TypedTaggedEventStreamEnvelopeSessionUpdated, TypedTaggedEventStreamEnvelopeSessionWoke, TypedTaggedEventStreamEnvelopeWorkerOperation, UnboundEventPayload, WireEvent, WireTaggedEvent, WorkerOperationEventPayload, WorkflowAttemptSummary, WorkflowBeadResponse, WorkflowDeleteResponse, WorkflowDepResponse, WorkflowEventProjection, WorkflowSnapshotResponse, WorkspaceResponse } from './types.gen'; diff --git a/cmd/gc/dashboard/web/src/generated/schema.d.ts b/cmd/gc/dashboard/web/src/generated/schema.d.ts index a90c609322..9adf65ac86 100644 --- a/cmd/gc/dashboard/web/src/generated/schema.d.ts +++ b/cmd/gc/dashboard/web/src/generated/schema.d.ts @@ -1800,6 +1800,23 @@ export interface paths { patch?: never; trace?: never; }; + "/v0/city/{cityName}/unregister": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post v0 city by city name unregister */ + post: operations["post-v0-city-by-city-name-unregister"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/v0/city/{cityName}/workflow/{workflow_id}": { parameters: { query?: never; @@ -1936,6 +1953,7 @@ export interface components { turns: components["schemas"]["OutputTurn"][] | null; }; AgentPatch: { + AppendFragments: string[] | null; Attach: boolean | null; DefaultSlingFormula: string | null; DependsOn: string[] | null; @@ -2097,6 +2115,12 @@ export interface components { description?: string; /** @description Bead labels. */ labels?: string[] | null; + /** @description Metadata key-value pairs to set at create time. */ + metadata?: { + [key: string]: string; + }; + /** @description Parent bead ID. */ + parent?: string; /** * Format: int64 * @description Bead priority. @@ -2131,6 +2155,8 @@ export interface components { metadata?: { [key: string]: string; }; + /** @description Parent bead ID. Use null or an empty string to clear. */ + parent?: string | null; /** * Format: int64 * @description Bead priority. @@ -2167,9 +2193,11 @@ export interface components { provider: string; }; CityCreateResponse: { - /** @description True on success. */ + /** @description Resolved city name as persisted in city.toml. Use this to filter the event stream for completion. */ + name: string; + /** @description True when scaffolding + registration succeeded. Does not imply the city is ready yet; watch /v0/events/stream for city.ready. */ ok: boolean; - /** @description Resolved absolute path of the created city. */ + /** @description Resolved absolute path of the created city directory. */ path: string; }; CityGetResponse: { @@ -2194,10 +2222,24 @@ export interface components { running: boolean; status?: string; }; + CityLifecyclePayload: { + error?: string; + name: string; + path: string; + phases_completed?: string[] | null; + }; CityPatchInputBody: { /** @description Whether the city is suspended. */ suspended?: boolean; }; + CityUnregisterResponse: { + /** @description Resolved registry name. Filter the event stream by this to observe completion. */ + name: string; + /** @description True when the registry entry was removed and the supervisor was signaled. Does not imply the city's controller has stopped yet; watch /v0/events/stream for city.unregistered. */ + ok: boolean; + /** @description Resolved absolute city directory. The directory itself is not modified; unregister only affects the supervisor's registry. */ + path: string; + }; ConfigAgentResponse: { dir?: string; is_pool?: boolean; @@ -2421,6 +2463,8 @@ export interface components { * @description A URI reference to human-readable documentation for the error. * @default about:blank * @example https://example.com/errors/example + * @example urn:gascity:error:sling-missing-bead + * @example urn:gascity:error:sling-cross-rig */ type: string; }; @@ -2441,7 +2485,7 @@ export interface components { /** @description Event type. */ type: string; }; - EventPayload: components["schemas"]["AdapterEventPayload"] | components["schemas"]["BeadEventPayload"] | components["schemas"]["BoundEventPayload"] | components["schemas"]["GroupCreatedEventPayload"] | components["schemas"]["InboundEventPayload"] | components["schemas"]["MailEventPayload"] | components["schemas"]["NoPayload"] | components["schemas"]["OutboundEventPayload"] | components["schemas"]["UnboundEventPayload"] | components["schemas"]["WorkerOperationEventPayload"]; + EventPayload: components["schemas"]["AdapterEventPayload"] | components["schemas"]["BeadEventPayload"] | components["schemas"]["BoundEventPayload"] | components["schemas"]["CityLifecyclePayload"] | components["schemas"]["GroupCreatedEventPayload"] | components["schemas"]["InboundEventPayload"] | components["schemas"]["MailEventPayload"] | components["schemas"]["NoPayload"] | components["schemas"]["OutboundEventPayload"] | components["schemas"]["UnboundEventPayload"] | components["schemas"]["WorkerOperationEventPayload"]; EventStreamEnvelope: { actor: string; message?: string; @@ -3715,6 +3759,8 @@ export interface components { attached_bead_id?: string; /** @description Bead ID to sling. */ bead?: string; + /** @description Bypass cross-rig guards; for direct bead routes, also bypass missing-bead validation. Formula-backed graph routes may replace existing live workflow roots but still require the source bead to exist. */ + force?: boolean; /** @description Formula name for workflow launch. */ formula?: string; /** @description Rig name. */ @@ -3875,70 +3921,1585 @@ export interface components { /** @description Managed cities with status info. */ items: components["schemas"]["CityInfo"][] | null; /** - * Format: int64 - * @description Total count. + * Format: int64 + * @description Total count. + */ + total: number; + }; + SupervisorEventListOutputBody: { + items: components["schemas"]["WireTaggedEvent"][] | null; + /** Format: int64 */ + total: number; + }; + SupervisorHealthOutputBody: { + /** + * Format: int64 + * @description Cities currently running. + */ + cities_running: number; + /** + * Format: int64 + * @description Total managed cities. + */ + cities_total: number; + /** @description First-city startup info for single-city deployments. */ + startup?: components["schemas"]["SupervisorStartup"]; + /** @description Health status ("ok"). */ + status: string; + /** + * Format: int64 + * @description Supervisor uptime in seconds. + */ + uptime_sec: number; + /** @description Supervisor version. */ + version: string; + }; + SupervisorStartup: { + /** @description Current phase (when not ready). */ + phase?: string; + /** @description Phases completed so far. */ + phases_completed?: string[] | null; + /** @description True when the city is running. */ + ready: boolean; + }; + TaggedEventStreamEnvelope: { + actor: string; + city: string; + message?: string; + payload?: components["schemas"]["EventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + type: string; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** + * @description Direction of a transcript entry. + * @enum {string} + */ + TranscriptMessageKind: "inbound" | "outbound"; + /** + * @description Provenance of a transcript entry (freshly observed vs. replayed from persisted history). + * @enum {string} + */ + TranscriptProvenance: "live" | "hydrated"; + /** + * Typed city event stream envelope + * @description Discriminated union of city event stream envelopes. Each variant constrains the envelope type and payload schema together. + */ + TypedEventStreamEnvelope: components["schemas"]["TypedEventStreamEnvelopeBeadClosed"] | components["schemas"]["TypedEventStreamEnvelopeBeadCreated"] | components["schemas"]["TypedEventStreamEnvelopeBeadUpdated"] | components["schemas"]["TypedEventStreamEnvelopeCityCreated"] | components["schemas"]["TypedEventStreamEnvelopeCityInitFailed"] | components["schemas"]["TypedEventStreamEnvelopeCityReady"] | components["schemas"]["TypedEventStreamEnvelopeCityResumed"] | components["schemas"]["TypedEventStreamEnvelopeCitySuspended"] | components["schemas"]["TypedEventStreamEnvelopeCityUnregisterFailed"] | components["schemas"]["TypedEventStreamEnvelopeCityUnregisterRequested"] | components["schemas"]["TypedEventStreamEnvelopeCityUnregistered"] | components["schemas"]["TypedEventStreamEnvelopeControllerStarted"] | components["schemas"]["TypedEventStreamEnvelopeControllerStopped"] | components["schemas"]["TypedEventStreamEnvelopeConvoyClosed"] | components["schemas"]["TypedEventStreamEnvelopeConvoyCreated"] | components["schemas"]["TypedEventStreamEnvelopeExtmsgAdapterAdded"] | components["schemas"]["TypedEventStreamEnvelopeExtmsgAdapterRemoved"] | components["schemas"]["TypedEventStreamEnvelopeExtmsgBound"] | components["schemas"]["TypedEventStreamEnvelopeExtmsgGroupCreated"] | components["schemas"]["TypedEventStreamEnvelopeExtmsgInbound"] | components["schemas"]["TypedEventStreamEnvelopeExtmsgOutbound"] | components["schemas"]["TypedEventStreamEnvelopeExtmsgUnbound"] | components["schemas"]["TypedEventStreamEnvelopeMailArchived"] | components["schemas"]["TypedEventStreamEnvelopeMailDeleted"] | components["schemas"]["TypedEventStreamEnvelopeMailMarkedRead"] | components["schemas"]["TypedEventStreamEnvelopeMailMarkedUnread"] | components["schemas"]["TypedEventStreamEnvelopeMailRead"] | components["schemas"]["TypedEventStreamEnvelopeMailReplied"] | components["schemas"]["TypedEventStreamEnvelopeMailSent"] | components["schemas"]["TypedEventStreamEnvelopeOrderCompleted"] | components["schemas"]["TypedEventStreamEnvelopeOrderFailed"] | components["schemas"]["TypedEventStreamEnvelopeOrderFired"] | components["schemas"]["TypedEventStreamEnvelopeProviderSwapped"] | components["schemas"]["TypedEventStreamEnvelopeSessionCrashed"] | components["schemas"]["TypedEventStreamEnvelopeSessionDraining"] | components["schemas"]["TypedEventStreamEnvelopeSessionIdleKilled"] | components["schemas"]["TypedEventStreamEnvelopeSessionQuarantined"] | components["schemas"]["TypedEventStreamEnvelopeSessionStopped"] | components["schemas"]["TypedEventStreamEnvelopeSessionSuspended"] | components["schemas"]["TypedEventStreamEnvelopeSessionUndrained"] | components["schemas"]["TypedEventStreamEnvelopeSessionUpdated"] | components["schemas"]["TypedEventStreamEnvelopeSessionWoke"] | components["schemas"]["TypedEventStreamEnvelopeWorkerOperation"]; + /** TypedEventStreamEnvelope bead.closed */ + TypedEventStreamEnvelopeBeadClosed: { + actor: string; + message?: string; + payload: components["schemas"]["BeadEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "bead.closed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope bead.created */ + TypedEventStreamEnvelopeBeadCreated: { + actor: string; + message?: string; + payload: components["schemas"]["BeadEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "bead.created"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope bead.updated */ + TypedEventStreamEnvelopeBeadUpdated: { + actor: string; + message?: string; + payload: components["schemas"]["BeadEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "bead.updated"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope city.created */ + TypedEventStreamEnvelopeCityCreated: { + actor: string; + message?: string; + payload: components["schemas"]["CityLifecyclePayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "city.created"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope city.init_failed */ + TypedEventStreamEnvelopeCityInitFailed: { + actor: string; + message?: string; + payload: components["schemas"]["CityLifecyclePayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "city.init_failed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope city.ready */ + TypedEventStreamEnvelopeCityReady: { + actor: string; + message?: string; + payload: components["schemas"]["CityLifecyclePayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "city.ready"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope city.resumed */ + TypedEventStreamEnvelopeCityResumed: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "city.resumed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope city.suspended */ + TypedEventStreamEnvelopeCitySuspended: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "city.suspended"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope city.unregister_failed */ + TypedEventStreamEnvelopeCityUnregisterFailed: { + actor: string; + message?: string; + payload: components["schemas"]["CityLifecyclePayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "city.unregister_failed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope city.unregister_requested */ + TypedEventStreamEnvelopeCityUnregisterRequested: { + actor: string; + message?: string; + payload: components["schemas"]["CityLifecyclePayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "city.unregister_requested"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope city.unregistered */ + TypedEventStreamEnvelopeCityUnregistered: { + actor: string; + message?: string; + payload: components["schemas"]["CityLifecyclePayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "city.unregistered"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope controller.started */ + TypedEventStreamEnvelopeControllerStarted: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "controller.started"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope controller.stopped */ + TypedEventStreamEnvelopeControllerStopped: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "controller.stopped"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope convoy.closed */ + TypedEventStreamEnvelopeConvoyClosed: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "convoy.closed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope convoy.created */ + TypedEventStreamEnvelopeConvoyCreated: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "convoy.created"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope extmsg.adapter_added */ + TypedEventStreamEnvelopeExtmsgAdapterAdded: { + actor: string; + message?: string; + payload: components["schemas"]["AdapterEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "extmsg.adapter_added"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope extmsg.adapter_removed */ + TypedEventStreamEnvelopeExtmsgAdapterRemoved: { + actor: string; + message?: string; + payload: components["schemas"]["AdapterEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "extmsg.adapter_removed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope extmsg.bound */ + TypedEventStreamEnvelopeExtmsgBound: { + actor: string; + message?: string; + payload: components["schemas"]["BoundEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "extmsg.bound"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope extmsg.group_created */ + TypedEventStreamEnvelopeExtmsgGroupCreated: { + actor: string; + message?: string; + payload: components["schemas"]["GroupCreatedEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "extmsg.group_created"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope extmsg.inbound */ + TypedEventStreamEnvelopeExtmsgInbound: { + actor: string; + message?: string; + payload: components["schemas"]["InboundEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "extmsg.inbound"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope extmsg.outbound */ + TypedEventStreamEnvelopeExtmsgOutbound: { + actor: string; + message?: string; + payload: components["schemas"]["OutboundEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "extmsg.outbound"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope extmsg.unbound */ + TypedEventStreamEnvelopeExtmsgUnbound: { + actor: string; + message?: string; + payload: components["schemas"]["UnboundEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "extmsg.unbound"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope mail.archived */ + TypedEventStreamEnvelopeMailArchived: { + actor: string; + message?: string; + payload: components["schemas"]["MailEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mail.archived"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope mail.deleted */ + TypedEventStreamEnvelopeMailDeleted: { + actor: string; + message?: string; + payload: components["schemas"]["MailEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mail.deleted"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope mail.marked_read */ + TypedEventStreamEnvelopeMailMarkedRead: { + actor: string; + message?: string; + payload: components["schemas"]["MailEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mail.marked_read"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope mail.marked_unread */ + TypedEventStreamEnvelopeMailMarkedUnread: { + actor: string; + message?: string; + payload: components["schemas"]["MailEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mail.marked_unread"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope mail.read */ + TypedEventStreamEnvelopeMailRead: { + actor: string; + message?: string; + payload: components["schemas"]["MailEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mail.read"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope mail.replied */ + TypedEventStreamEnvelopeMailReplied: { + actor: string; + message?: string; + payload: components["schemas"]["MailEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mail.replied"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope mail.sent */ + TypedEventStreamEnvelopeMailSent: { + actor: string; + message?: string; + payload: components["schemas"]["MailEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mail.sent"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope order.completed */ + TypedEventStreamEnvelopeOrderCompleted: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "order.completed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope order.failed */ + TypedEventStreamEnvelopeOrderFailed: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "order.failed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope order.fired */ + TypedEventStreamEnvelopeOrderFired: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "order.fired"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope provider.swapped */ + TypedEventStreamEnvelopeProviderSwapped: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "provider.swapped"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope session.crashed */ + TypedEventStreamEnvelopeSessionCrashed: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "session.crashed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope session.draining */ + TypedEventStreamEnvelopeSessionDraining: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "session.draining"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope session.idle_killed */ + TypedEventStreamEnvelopeSessionIdleKilled: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "session.idle_killed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope session.quarantined */ + TypedEventStreamEnvelopeSessionQuarantined: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "session.quarantined"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope session.stopped */ + TypedEventStreamEnvelopeSessionStopped: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "session.stopped"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope session.suspended */ + TypedEventStreamEnvelopeSessionSuspended: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "session.suspended"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope session.undrained */ + TypedEventStreamEnvelopeSessionUndrained: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "session.undrained"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope session.updated */ + TypedEventStreamEnvelopeSessionUpdated: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "session.updated"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope session.woke */ + TypedEventStreamEnvelopeSessionWoke: { + actor: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "session.woke"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedEventStreamEnvelope worker.operation */ + TypedEventStreamEnvelopeWorkerOperation: { + actor: string; + message?: string; + payload: components["schemas"]["WorkerOperationEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "worker.operation"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** + * Typed supervisor event stream envelope + * @description Discriminated union of supervisor event stream envelopes. Each variant constrains the envelope type and payload schema together and includes the source city. + */ + TypedTaggedEventStreamEnvelope: components["schemas"]["TypedTaggedEventStreamEnvelopeBeadClosed"] | components["schemas"]["TypedTaggedEventStreamEnvelopeBeadCreated"] | components["schemas"]["TypedTaggedEventStreamEnvelopeBeadUpdated"] | components["schemas"]["TypedTaggedEventStreamEnvelopeCityCreated"] | components["schemas"]["TypedTaggedEventStreamEnvelopeCityInitFailed"] | components["schemas"]["TypedTaggedEventStreamEnvelopeCityReady"] | components["schemas"]["TypedTaggedEventStreamEnvelopeCityResumed"] | components["schemas"]["TypedTaggedEventStreamEnvelopeCitySuspended"] | components["schemas"]["TypedTaggedEventStreamEnvelopeCityUnregisterFailed"] | components["schemas"]["TypedTaggedEventStreamEnvelopeCityUnregisterRequested"] | components["schemas"]["TypedTaggedEventStreamEnvelopeCityUnregistered"] | components["schemas"]["TypedTaggedEventStreamEnvelopeControllerStarted"] | components["schemas"]["TypedTaggedEventStreamEnvelopeControllerStopped"] | components["schemas"]["TypedTaggedEventStreamEnvelopeConvoyClosed"] | components["schemas"]["TypedTaggedEventStreamEnvelopeConvoyCreated"] | components["schemas"]["TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded"] | components["schemas"]["TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved"] | components["schemas"]["TypedTaggedEventStreamEnvelopeExtmsgBound"] | components["schemas"]["TypedTaggedEventStreamEnvelopeExtmsgGroupCreated"] | components["schemas"]["TypedTaggedEventStreamEnvelopeExtmsgInbound"] | components["schemas"]["TypedTaggedEventStreamEnvelopeExtmsgOutbound"] | components["schemas"]["TypedTaggedEventStreamEnvelopeExtmsgUnbound"] | components["schemas"]["TypedTaggedEventStreamEnvelopeMailArchived"] | components["schemas"]["TypedTaggedEventStreamEnvelopeMailDeleted"] | components["schemas"]["TypedTaggedEventStreamEnvelopeMailMarkedRead"] | components["schemas"]["TypedTaggedEventStreamEnvelopeMailMarkedUnread"] | components["schemas"]["TypedTaggedEventStreamEnvelopeMailRead"] | components["schemas"]["TypedTaggedEventStreamEnvelopeMailReplied"] | components["schemas"]["TypedTaggedEventStreamEnvelopeMailSent"] | components["schemas"]["TypedTaggedEventStreamEnvelopeOrderCompleted"] | components["schemas"]["TypedTaggedEventStreamEnvelopeOrderFailed"] | components["schemas"]["TypedTaggedEventStreamEnvelopeOrderFired"] | components["schemas"]["TypedTaggedEventStreamEnvelopeProviderSwapped"] | components["schemas"]["TypedTaggedEventStreamEnvelopeSessionCrashed"] | components["schemas"]["TypedTaggedEventStreamEnvelopeSessionDraining"] | components["schemas"]["TypedTaggedEventStreamEnvelopeSessionIdleKilled"] | components["schemas"]["TypedTaggedEventStreamEnvelopeSessionQuarantined"] | components["schemas"]["TypedTaggedEventStreamEnvelopeSessionStopped"] | components["schemas"]["TypedTaggedEventStreamEnvelopeSessionSuspended"] | components["schemas"]["TypedTaggedEventStreamEnvelopeSessionUndrained"] | components["schemas"]["TypedTaggedEventStreamEnvelopeSessionUpdated"] | components["schemas"]["TypedTaggedEventStreamEnvelopeSessionWoke"] | components["schemas"]["TypedTaggedEventStreamEnvelopeWorkerOperation"]; + /** TypedTaggedEventStreamEnvelope bead.closed */ + TypedTaggedEventStreamEnvelopeBeadClosed: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["BeadEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "bead.closed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope bead.created */ + TypedTaggedEventStreamEnvelopeBeadCreated: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["BeadEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "bead.created"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope bead.updated */ + TypedTaggedEventStreamEnvelopeBeadUpdated: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["BeadEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "bead.updated"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope city.created */ + TypedTaggedEventStreamEnvelopeCityCreated: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["CityLifecyclePayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "city.created"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope city.init_failed */ + TypedTaggedEventStreamEnvelopeCityInitFailed: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["CityLifecyclePayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "city.init_failed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope city.ready */ + TypedTaggedEventStreamEnvelopeCityReady: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["CityLifecyclePayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "city.ready"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope city.resumed */ + TypedTaggedEventStreamEnvelopeCityResumed: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "city.resumed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope city.suspended */ + TypedTaggedEventStreamEnvelopeCitySuspended: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "city.suspended"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope city.unregister_failed */ + TypedTaggedEventStreamEnvelopeCityUnregisterFailed: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["CityLifecyclePayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "city.unregister_failed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope city.unregister_requested */ + TypedTaggedEventStreamEnvelopeCityUnregisterRequested: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["CityLifecyclePayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "city.unregister_requested"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope city.unregistered */ + TypedTaggedEventStreamEnvelopeCityUnregistered: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["CityLifecyclePayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "city.unregistered"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope controller.started */ + TypedTaggedEventStreamEnvelopeControllerStarted: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "controller.started"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope controller.stopped */ + TypedTaggedEventStreamEnvelopeControllerStopped: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "controller.stopped"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope convoy.closed */ + TypedTaggedEventStreamEnvelopeConvoyClosed: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "convoy.closed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope convoy.created */ + TypedTaggedEventStreamEnvelopeConvoyCreated: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "convoy.created"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope extmsg.adapter_added */ + TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["AdapterEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "extmsg.adapter_added"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope extmsg.adapter_removed */ + TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["AdapterEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "extmsg.adapter_removed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope extmsg.bound */ + TypedTaggedEventStreamEnvelopeExtmsgBound: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["BoundEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "extmsg.bound"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope extmsg.group_created */ + TypedTaggedEventStreamEnvelopeExtmsgGroupCreated: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["GroupCreatedEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "extmsg.group_created"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope extmsg.inbound */ + TypedTaggedEventStreamEnvelopeExtmsgInbound: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["InboundEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "extmsg.inbound"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope extmsg.outbound */ + TypedTaggedEventStreamEnvelopeExtmsgOutbound: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["OutboundEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "extmsg.outbound"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope extmsg.unbound */ + TypedTaggedEventStreamEnvelopeExtmsgUnbound: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["UnboundEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "extmsg.unbound"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope mail.archived */ + TypedTaggedEventStreamEnvelopeMailArchived: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["MailEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mail.archived"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope mail.deleted */ + TypedTaggedEventStreamEnvelopeMailDeleted: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["MailEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mail.deleted"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope mail.marked_read */ + TypedTaggedEventStreamEnvelopeMailMarkedRead: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["MailEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mail.marked_read"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope mail.marked_unread */ + TypedTaggedEventStreamEnvelopeMailMarkedUnread: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["MailEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mail.marked_unread"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope mail.read */ + TypedTaggedEventStreamEnvelopeMailRead: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["MailEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mail.read"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope mail.replied */ + TypedTaggedEventStreamEnvelopeMailReplied: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["MailEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mail.replied"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope mail.sent */ + TypedTaggedEventStreamEnvelopeMailSent: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["MailEventPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mail.sent"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope order.completed */ + TypedTaggedEventStreamEnvelopeOrderCompleted: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "order.completed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope order.failed */ + TypedTaggedEventStreamEnvelopeOrderFailed: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "order.failed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope order.fired */ + TypedTaggedEventStreamEnvelopeOrderFired: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "order.fired"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope provider.swapped */ + TypedTaggedEventStreamEnvelopeProviderSwapped: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "provider.swapped"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope session.crashed */ + TypedTaggedEventStreamEnvelopeSessionCrashed: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "session.crashed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope session.draining */ + TypedTaggedEventStreamEnvelopeSessionDraining: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "session.draining"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope session.idle_killed */ + TypedTaggedEventStreamEnvelopeSessionIdleKilled: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "session.idle_killed"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope session.quarantined */ + TypedTaggedEventStreamEnvelopeSessionQuarantined: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "session.quarantined"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope session.stopped */ + TypedTaggedEventStreamEnvelopeSessionStopped: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "session.stopped"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope session.suspended */ + TypedTaggedEventStreamEnvelopeSessionSuspended: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} */ - total: number; + type: "session.suspended"; + workflow?: components["schemas"]["WorkflowEventProjection"]; }; - SupervisorEventListOutputBody: { - items: components["schemas"]["WireTaggedEvent"][] | null; + /** TypedTaggedEventStreamEnvelope session.undrained */ + TypedTaggedEventStreamEnvelopeSessionUndrained: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; /** Format: int64 */ - total: number; - }; - SupervisorHealthOutputBody: { + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; /** - * Format: int64 - * @description Cities currently running. + * @description discriminator enum property added by openapi-typescript + * @enum {string} */ - cities_running: number; + type: "session.undrained"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope session.updated */ + TypedTaggedEventStreamEnvelopeSessionUpdated: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; /** - * Format: int64 - * @description Total managed cities. + * @description discriminator enum property added by openapi-typescript + * @enum {string} */ - cities_total: number; - /** @description First-city startup info for single-city deployments. */ - startup?: components["schemas"]["SupervisorStartup"]; - /** @description Health status ("ok"). */ - status: string; + type: "session.updated"; + workflow?: components["schemas"]["WorkflowEventProjection"]; + }; + /** TypedTaggedEventStreamEnvelope session.woke */ + TypedTaggedEventStreamEnvelopeSessionWoke: { + actor: string; + city: string; + message?: string; + payload: components["schemas"]["NoPayload"]; + /** Format: int64 */ + seq: number; + subject?: string; + /** Format: date-time */ + ts: string; /** - * Format: int64 - * @description Supervisor uptime in seconds. + * @description discriminator enum property added by openapi-typescript + * @enum {string} */ - uptime_sec: number; - /** @description Supervisor version. */ - version: string; - }; - SupervisorStartup: { - /** @description Current phase (when not ready). */ - phase?: string; - /** @description Phases completed so far. */ - phases_completed?: string[] | null; - /** @description True when the city is running. */ - ready: boolean; + type: "session.woke"; + workflow?: components["schemas"]["WorkflowEventProjection"]; }; - TaggedEventStreamEnvelope: { + /** TypedTaggedEventStreamEnvelope worker.operation */ + TypedTaggedEventStreamEnvelopeWorkerOperation: { actor: string; city: string; message?: string; - payload?: components["schemas"]["EventPayload"]; + payload: components["schemas"]["WorkerOperationEventPayload"]; /** Format: int64 */ seq: number; subject?: string; /** Format: date-time */ ts: string; - type: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "worker.operation"; workflow?: components["schemas"]["WorkflowEventProjection"]; }; - /** - * @description Direction of a transcript entry. - * @enum {string} - */ - TranscriptMessageKind: "inbound" | "outbound"; - /** - * @description Provenance of a transcript entry (freshly observed vs. replayed from persisted history). - * @enum {string} - */ - TranscriptProvenance: "live" | "hydrated"; UnboundEventPayload: { /** Format: int64 */ count: number; @@ -4084,7 +5645,10 @@ export interface components { responses: never; parameters: never; requestBodies: never; - headers: never; + headers: { + /** @description Opaque per-response identifier assigned by the server for log correlation. Every response carries this header. */ + "X-GC-Request-Id": string; + }; pathItems: never; } export type $defs = Record; @@ -4101,6 +5665,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4110,6 +5675,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4130,6 +5696,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4139,6 +5706,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4150,7 +5718,10 @@ export interface operations { "post-v0-city": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path?: never; cookie?: never; }; @@ -4160,9 +5731,10 @@ export interface operations { }; }; responses: { - /** @description OK */ - 200: { + /** @description Accepted */ + 202: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4172,6 +5744,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4195,6 +5768,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4204,6 +5778,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4215,7 +5790,10 @@ export interface operations { "patch-v0-city-by-city-name": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -4231,6 +5809,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4240,6 +5819,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4266,6 +5846,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4275,6 +5856,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4286,7 +5868,10 @@ export interface operations { "delete-v0-city-by-city-name-agent-by-base": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -4300,6 +5885,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4309,6 +5895,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4320,7 +5907,10 @@ export interface operations { "patch-v0-city-by-city-name-agent-by-base": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -4338,6 +5928,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4347,6 +5938,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4358,7 +5950,7 @@ export interface operations { "get-v0-city-by-city-name-agent-by-base-output": { parameters: { query?: { - /** @description Number of recent compaction segments to return. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. */ + /** @description Number of recent compaction segments to return. This API parameter keeps compaction-segment semantics even though gc session logs --tail counts displayed transcript entries. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. */ tail?: string; /** @description Message UUID cursor for loading older messages. */ before?: string; @@ -4377,6 +5969,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4386,6 +5979,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4411,6 +6005,9 @@ export interface operations { /** @description OK */ 200: { headers: { + /** @description Agent runtime status at the time streaming began. Emitted as "stopped" when the agent is not running (the stream then serves replayed transcript from the session log). */ + "GC-Agent-Status"?: string; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4442,6 +6039,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4453,7 +6051,10 @@ export interface operations { "post-v0-city-by-city-name-agent-by-base-by-action": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -4469,6 +6070,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4478,6 +6080,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4506,6 +6109,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4515,6 +6119,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4526,7 +6131,10 @@ export interface operations { "delete-v0-city-by-city-name-agent-by-dir-by-base": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -4542,6 +6150,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4551,6 +6160,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4562,7 +6172,10 @@ export interface operations { "patch-v0-city-by-city-name-agent-by-dir-by-base": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -4582,6 +6195,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4591,6 +6205,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4602,7 +6217,7 @@ export interface operations { "get-v0-city-by-city-name-agent-by-dir-by-base-output": { parameters: { query?: { - /** @description Number of recent compaction segments to return. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. */ + /** @description Number of recent compaction segments to return. This API parameter keeps compaction-segment semantics even though gc session logs --tail counts displayed transcript entries. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. */ tail?: string; /** @description Message UUID cursor for loading older messages. */ before?: string; @@ -4623,6 +6238,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4632,6 +6248,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4659,6 +6276,9 @@ export interface operations { /** @description OK */ 200: { headers: { + /** @description Agent runtime status at the time streaming began. Emitted as "stopped" when the agent is not running (the stream then serves replayed transcript from the session log). */ + "GC-Agent-Status"?: string; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4690,6 +6310,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4701,7 +6322,10 @@ export interface operations { "post-v0-city-by-city-name-agent-by-dir-by-base-by-action": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -4719,6 +6343,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4728,6 +6353,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4765,6 +6391,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4774,6 +6401,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4785,7 +6413,10 @@ export interface operations { "create-agent": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -4801,6 +6432,7 @@ export interface operations { /** @description Created */ 201: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4810,6 +6442,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4836,6 +6469,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4845,6 +6479,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4856,7 +6491,10 @@ export interface operations { "delete-v0-city-by-city-name-bead-by-id": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -4870,6 +6508,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4879,6 +6518,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4890,7 +6530,10 @@ export interface operations { "patch-v0-city-by-city-name-bead-by-id": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -4908,6 +6551,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4917,6 +6561,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4928,7 +6573,10 @@ export interface operations { "post-v0-city-by-city-name-bead-by-id-assign": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -4947,6 +6595,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4958,6 +6607,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4969,7 +6619,10 @@ export interface operations { "post-v0-city-by-city-name-bead-by-id-close": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -4983,6 +6636,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -4992,6 +6646,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5018,6 +6673,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5027,6 +6683,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5038,7 +6695,10 @@ export interface operations { "post-v0-city-by-city-name-bead-by-id-reopen": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -5052,6 +6712,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5061,6 +6722,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5072,7 +6734,10 @@ export interface operations { "post-v0-city-by-city-name-bead-by-id-update": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -5090,6 +6755,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5099,6 +6765,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5142,6 +6809,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5151,6 +6819,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5162,7 +6831,9 @@ export interface operations { "create-bead": { parameters: { query?: never; - header?: { + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; /** @description Idempotency key for safe retries. */ "Idempotency-Key"?: string; }; @@ -5182,6 +6853,7 @@ export interface operations { 201: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5191,6 +6863,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5217,6 +6890,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5226,6 +6900,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5255,6 +6930,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5264,6 +6940,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5288,6 +6965,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5297,6 +6975,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5321,6 +7000,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5330,6 +7010,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5353,6 +7034,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5362,6 +7044,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5388,6 +7071,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5397,6 +7081,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5408,7 +7093,10 @@ export interface operations { "delete-v0-city-by-city-name-convoy-by-id": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -5422,6 +7110,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5431,6 +7120,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5442,7 +7132,10 @@ export interface operations { "post-v0-city-by-city-name-convoy-by-id-add": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -5460,6 +7153,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5469,6 +7163,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5495,6 +7190,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5504,6 +7200,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5515,7 +7212,10 @@ export interface operations { "post-v0-city-by-city-name-convoy-by-id-close": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -5529,6 +7229,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5538,6 +7239,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5549,7 +7251,10 @@ export interface operations { "post-v0-city-by-city-name-convoy-by-id-remove": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -5567,6 +7272,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5576,6 +7282,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5609,6 +7316,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5618,6 +7326,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5629,7 +7338,10 @@ export interface operations { "create-convoy": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -5646,6 +7358,7 @@ export interface operations { 201: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5655,6 +7368,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5694,6 +7408,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5703,6 +7418,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5714,7 +7430,10 @@ export interface operations { "emit-event": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -5730,6 +7449,7 @@ export interface operations { /** @description Created */ 201: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5739,6 +7459,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5768,11 +7489,12 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { "text/event-stream": ({ - data: components["schemas"]["EventStreamEnvelope"]; + data: components["schemas"]["TypedEventStreamEnvelope"]; /** * @description The event name. * @constant @@ -5799,6 +7521,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5823,6 +7546,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5832,6 +7556,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5843,7 +7568,10 @@ export interface operations { "register-extmsg-adapter": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -5859,6 +7587,7 @@ export interface operations { /** @description Created */ 201: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5868,6 +7597,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5879,7 +7609,10 @@ export interface operations { "delete-v0-city-by-city-name-extmsg-adapters": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -5895,6 +7628,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5904,6 +7638,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5915,7 +7650,10 @@ export interface operations { "post-v0-city-by-city-name-extmsg-bind": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -5931,6 +7669,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5940,6 +7679,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5967,6 +7707,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -5976,6 +7717,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6010,6 +7752,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6019,6 +7762,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6030,7 +7774,10 @@ export interface operations { "ensure-extmsg-group": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -6046,6 +7793,7 @@ export interface operations { /** @description Created */ 201: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6055,6 +7803,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6066,7 +7815,10 @@ export interface operations { "post-v0-city-by-city-name-extmsg-inbound": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -6082,6 +7834,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6091,6 +7844,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6102,7 +7856,10 @@ export interface operations { "post-v0-city-by-city-name-extmsg-outbound": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -6118,6 +7875,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6127,6 +7885,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6138,7 +7897,10 @@ export interface operations { "post-v0-city-by-city-name-extmsg-participants": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -6154,6 +7916,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6163,6 +7926,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6174,7 +7938,10 @@ export interface operations { "delete-v0-city-by-city-name-extmsg-participants": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -6190,6 +7957,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6199,6 +7967,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6236,6 +8005,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6245,6 +8015,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6256,7 +8027,10 @@ export interface operations { "post-v0-city-by-city-name-extmsg-transcript-ack": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -6272,6 +8046,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6281,6 +8056,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6292,7 +8068,10 @@ export interface operations { "post-v0-city-by-city-name-extmsg-unbind": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -6308,6 +8087,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6317,6 +8097,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6349,6 +8130,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6358,6 +8140,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6386,6 +8169,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6395,6 +8179,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6425,6 +8210,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6434,6 +8220,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6466,6 +8253,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6475,6 +8263,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6486,7 +8275,10 @@ export interface operations { "post-v0-city-by-city-name-formulas-by-name-preview": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -6504,6 +8296,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6513,6 +8306,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6545,6 +8339,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6554,6 +8349,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6577,6 +8373,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6586,6 +8383,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6625,6 +8423,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6634,6 +8433,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6645,7 +8445,9 @@ export interface operations { "send-mail": { parameters: { query?: never; - header?: { + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; /** @description Idempotency key for safe retries. */ "Idempotency-Key"?: string; }; @@ -6665,6 +8467,7 @@ export interface operations { 201: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6674,6 +8477,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6702,6 +8506,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6711,6 +8516,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6740,6 +8546,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6749,6 +8556,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6778,6 +8586,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6787,6 +8596,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6801,7 +8611,10 @@ export interface operations { /** @description Rig hint. */ rig?: string; }; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -6815,6 +8628,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6824,6 +8638,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6838,7 +8653,10 @@ export interface operations { /** @description Rig hint. */ rig?: string; }; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -6852,6 +8670,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6861,6 +8680,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6875,7 +8695,10 @@ export interface operations { /** @description Rig hint. */ rig?: string; }; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -6889,6 +8712,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6898,6 +8722,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6912,7 +8737,10 @@ export interface operations { /** @description Rig hint. */ rig?: string; }; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -6926,6 +8754,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6935,6 +8764,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6949,7 +8779,10 @@ export interface operations { /** @description Rig hint. */ rig?: string; }; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -6968,6 +8801,7 @@ export interface operations { 201: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -6977,6 +8811,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7005,6 +8840,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7014,6 +8850,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7039,6 +8876,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7048,6 +8886,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7059,7 +8898,10 @@ export interface operations { "post-v0-city-by-city-name-order-by-name-disable": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -7073,6 +8915,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7082,6 +8925,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7093,7 +8937,10 @@ export interface operations { "post-v0-city-by-city-name-order-by-name-enable": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -7107,6 +8954,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7116,6 +8964,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7139,6 +8988,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7148,6 +8998,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7171,6 +9022,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7180,6 +9032,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7210,6 +9063,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7219,6 +9073,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7249,6 +9104,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7258,6 +9114,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7281,6 +9138,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7290,6 +9148,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7316,6 +9175,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7325,6 +9185,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7336,7 +9197,10 @@ export interface operations { "delete-v0-city-by-city-name-patches-agent-by-base": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -7350,6 +9214,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7359,6 +9224,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7387,6 +9253,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7396,6 +9263,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7407,7 +9275,10 @@ export interface operations { "delete-v0-city-by-city-name-patches-agent-by-dir-by-base": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -7423,6 +9294,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7432,6 +9304,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7456,6 +9329,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7465,6 +9339,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7476,7 +9351,10 @@ export interface operations { "put-v0-city-by-city-name-patches-agents": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -7492,6 +9370,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7501,6 +9380,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7527,6 +9407,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7536,6 +9417,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7547,7 +9429,10 @@ export interface operations { "delete-v0-city-by-city-name-patches-provider-by-name": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -7561,6 +9446,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7570,6 +9456,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7594,6 +9481,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7603,6 +9491,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7614,7 +9503,10 @@ export interface operations { "put-v0-city-by-city-name-patches-providers": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -7630,6 +9522,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7639,6 +9532,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7665,6 +9559,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7674,6 +9569,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7685,7 +9581,10 @@ export interface operations { "delete-v0-city-by-city-name-patches-rig-by-name": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -7699,6 +9598,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7708,6 +9608,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7732,6 +9633,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7741,6 +9643,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7752,7 +9655,10 @@ export interface operations { "put-v0-city-by-city-name-patches-rigs": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -7768,6 +9674,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7777,6 +9684,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7805,6 +9713,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7814,6 +9723,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7840,6 +9750,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7849,6 +9760,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7860,7 +9772,10 @@ export interface operations { "delete-v0-city-by-city-name-provider-by-name": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -7874,6 +9789,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7883,6 +9799,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7894,7 +9811,10 @@ export interface operations { "patch-v0-city-by-city-name-provider-by-name": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -7912,6 +9832,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7921,6 +9842,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7945,6 +9867,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7954,6 +9877,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7965,7 +9889,10 @@ export interface operations { "create-provider": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -7981,6 +9908,7 @@ export interface operations { /** @description Created */ 201: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -7990,6 +9918,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8014,6 +9943,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8023,6 +9953,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8051,6 +9982,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8060,6 +9992,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8089,6 +10022,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8098,6 +10032,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8109,7 +10044,10 @@ export interface operations { "delete-v0-city-by-city-name-rig-by-name": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -8123,6 +10061,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8132,6 +10071,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8143,7 +10083,10 @@ export interface operations { "patch-v0-city-by-city-name-rig-by-name": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -8161,6 +10104,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8170,6 +10114,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8181,7 +10126,10 @@ export interface operations { "post-v0-city-by-city-name-rig-by-name-by-action": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -8197,6 +10145,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8206,6 +10155,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8237,6 +10187,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8246,6 +10197,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8257,7 +10209,10 @@ export interface operations { "create-rig": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -8273,6 +10228,7 @@ export interface operations { /** @description Created */ 201: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8282,6 +10238,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8308,6 +10265,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8317,6 +10275,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8328,7 +10287,10 @@ export interface operations { "post-v0-city-by-city-name-service-by-name-restart": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -8342,6 +10304,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8351,6 +10314,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8375,6 +10339,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8384,6 +10349,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8413,6 +10379,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8422,6 +10389,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8433,7 +10401,10 @@ export interface operations { "patch-v0-city-by-city-name-session-by-id": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -8452,6 +10423,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8461,6 +10433,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8487,6 +10460,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8496,6 +10470,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8524,6 +10499,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8533,6 +10509,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8547,7 +10524,10 @@ export interface operations { /** @description Permanently delete bead after closing. */ delete?: boolean; }; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -8561,6 +10541,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8570,6 +10551,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8581,7 +10563,10 @@ export interface operations { "post-v0-city-by-city-name-session-by-id-kill": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -8595,6 +10580,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8604,6 +10590,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8615,7 +10602,10 @@ export interface operations { "send-session-message": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -8633,6 +10623,7 @@ export interface operations { /** @description Accepted */ 202: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8642,6 +10633,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8668,6 +10660,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8677,6 +10670,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8688,7 +10682,10 @@ export interface operations { "post-v0-city-by-city-name-session-by-id-rename": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -8707,6 +10704,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8716,6 +10714,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8727,7 +10726,10 @@ export interface operations { "respond-session": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -8745,6 +10747,7 @@ export interface operations { /** @description Accepted */ 202: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8754,6 +10757,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8765,7 +10769,10 @@ export interface operations { "post-v0-city-by-city-name-session-by-id-stop": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -8779,6 +10786,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8788,6 +10796,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8816,6 +10825,11 @@ export interface operations { /** @description OK */ 200: { headers: { + /** @description Session state at the time streaming began (e.g. active, closed). */ + "GC-Session-State"?: string; + /** @description Runtime status at the time streaming began. Emitted as "stopped" when the session's underlying process is not running. */ + "GC-Session-Status"?: string; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8880,6 +10894,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8891,7 +10906,10 @@ export interface operations { "submit-session": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -8909,6 +10927,7 @@ export interface operations { /** @description Accepted */ 202: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8918,6 +10937,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8929,7 +10949,10 @@ export interface operations { "post-v0-city-by-city-name-session-by-id-suspend": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -8943,6 +10966,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8952,6 +10976,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8963,7 +10988,7 @@ export interface operations { "get-v0-city-by-city-name-session-by-id-transcript": { parameters: { query?: { - /** @description Number of recent compaction segments to return. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. */ + /** @description Number of recent compaction segments to return. This API parameter keeps compaction-segment semantics even though gc session logs --tail counts displayed transcript entries. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. */ tail?: string; /** @description Transcript format: conversation (default) or raw. */ format?: string; @@ -8985,6 +11010,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -8994,6 +11020,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9005,7 +11032,10 @@ export interface operations { "post-v0-city-by-city-name-session-by-id-wake": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -9019,6 +11049,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9028,6 +11059,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9063,6 +11095,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9072,6 +11105,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9083,7 +11117,10 @@ export interface operations { "create-session": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -9099,6 +11136,7 @@ export interface operations { /** @description Accepted */ 202: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9108,6 +11146,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9119,7 +11158,10 @@ export interface operations { "post-v0-city-by-city-name-sling": { parameters: { query?: never; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -9135,6 +11177,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9144,6 +11187,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9173,6 +11217,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9182,6 +11227,44 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ErrorModel"]; + }; + }; + }; + }; + "post-v0-city-by-city-name-unregister": { + parameters: { + query?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; + path: { + /** @description Supervisor-registered city name. */ + cityName: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Accepted */ + 202: { + headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CityUnregisterResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9213,6 +11296,7 @@ export interface operations { 200: { headers: { "X-GC-Index"?: number; + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9222,6 +11306,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9240,7 +11325,10 @@ export interface operations { /** @description Permanently delete beads from store. */ delete?: boolean; }; - header?: never; + header: { + /** @description Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. */ + "X-GC-Request": string; + }; path: { /** @description City name. */ cityName: string; @@ -9254,6 +11342,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9263,6 +11352,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9292,6 +11382,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9301,6 +11392,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9327,6 +11419,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9342,7 +11435,7 @@ export interface operations { /** @description The retry time in milliseconds. */ retry?: number; } | { - data: components["schemas"]["TaggedEventStreamEnvelope"]; + data: components["schemas"]["TypedTaggedEventStreamEnvelope"]; /** * @description The event name. * @constant @@ -9358,6 +11451,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9383,6 +11477,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9392,6 +11487,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9417,6 +11513,7 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { @@ -9426,6 +11523,7 @@ export interface operations { /** @description Error */ default: { headers: { + "X-GC-Request-Id": components["headers"]["X-GC-Request-Id"]; [name: string]: unknown; }; content: { diff --git a/cmd/gc/dashboard/web/src/generated/sdk.gen.ts b/cmd/gc/dashboard/web/src/generated/sdk.gen.ts index 97eb4dced9..be654f696f 100644 --- a/cmd/gc/dashboard/web/src/generated/sdk.gen.ts +++ b/cmd/gc/dashboard/web/src/generated/sdk.gen.ts @@ -2,7 +2,7 @@ import type { Client, Options as Options2, TDataShape } from './client'; import { client } from './client.gen'; -import type { CreateAgentData, CreateAgentErrors, CreateAgentResponses, CreateBeadData, CreateBeadErrors, CreateBeadResponses, CreateConvoyData, CreateConvoyErrors, CreateConvoyResponses, CreateProviderData, CreateProviderErrors, CreateProviderResponses, CreateRigData, CreateRigErrors, CreateRigResponses, CreateSessionData, CreateSessionErrors, CreateSessionResponses, DeleteV0CityByCityNameAgentByBaseData, DeleteV0CityByCityNameAgentByBaseErrors, DeleteV0CityByCityNameAgentByBaseResponses, DeleteV0CityByCityNameAgentByDirByBaseData, DeleteV0CityByCityNameAgentByDirByBaseErrors, DeleteV0CityByCityNameAgentByDirByBaseResponses, DeleteV0CityByCityNameBeadByIdData, DeleteV0CityByCityNameBeadByIdErrors, DeleteV0CityByCityNameBeadByIdResponses, DeleteV0CityByCityNameConvoyByIdData, DeleteV0CityByCityNameConvoyByIdErrors, DeleteV0CityByCityNameConvoyByIdResponses, DeleteV0CityByCityNameExtmsgAdaptersData, DeleteV0CityByCityNameExtmsgAdaptersErrors, DeleteV0CityByCityNameExtmsgAdaptersResponses, DeleteV0CityByCityNameExtmsgParticipantsData, DeleteV0CityByCityNameExtmsgParticipantsErrors, DeleteV0CityByCityNameExtmsgParticipantsResponses, DeleteV0CityByCityNameMailByIdData, DeleteV0CityByCityNameMailByIdErrors, DeleteV0CityByCityNameMailByIdResponses, DeleteV0CityByCityNamePatchesAgentByBaseData, DeleteV0CityByCityNamePatchesAgentByBaseErrors, DeleteV0CityByCityNamePatchesAgentByBaseResponses, DeleteV0CityByCityNamePatchesAgentByDirByBaseData, DeleteV0CityByCityNamePatchesAgentByDirByBaseErrors, DeleteV0CityByCityNamePatchesAgentByDirByBaseResponses, DeleteV0CityByCityNamePatchesProviderByNameData, DeleteV0CityByCityNamePatchesProviderByNameErrors, DeleteV0CityByCityNamePatchesProviderByNameResponses, DeleteV0CityByCityNamePatchesRigByNameData, DeleteV0CityByCityNamePatchesRigByNameErrors, DeleteV0CityByCityNamePatchesRigByNameResponses, DeleteV0CityByCityNameProviderByNameData, DeleteV0CityByCityNameProviderByNameErrors, DeleteV0CityByCityNameProviderByNameResponses, DeleteV0CityByCityNameRigByNameData, DeleteV0CityByCityNameRigByNameErrors, DeleteV0CityByCityNameRigByNameResponses, DeleteV0CityByCityNameWorkflowByWorkflowIdData, DeleteV0CityByCityNameWorkflowByWorkflowIdErrors, DeleteV0CityByCityNameWorkflowByWorkflowIdResponses, EmitEventData, EmitEventErrors, EmitEventResponses, EnsureExtmsgGroupData, EnsureExtmsgGroupErrors, EnsureExtmsgGroupResponses, GetHealthData, GetHealthErrors, GetHealthResponses, GetV0CitiesData, GetV0CitiesErrors, GetV0CitiesResponses, GetV0CityByCityNameAgentByBaseData, GetV0CityByCityNameAgentByBaseErrors, GetV0CityByCityNameAgentByBaseOutputData, GetV0CityByCityNameAgentByBaseOutputErrors, GetV0CityByCityNameAgentByBaseOutputResponses, GetV0CityByCityNameAgentByBaseResponses, GetV0CityByCityNameAgentByDirByBaseData, GetV0CityByCityNameAgentByDirByBaseErrors, GetV0CityByCityNameAgentByDirByBaseOutputData, GetV0CityByCityNameAgentByDirByBaseOutputErrors, GetV0CityByCityNameAgentByDirByBaseOutputResponses, GetV0CityByCityNameAgentByDirByBaseResponses, GetV0CityByCityNameAgentsData, GetV0CityByCityNameAgentsErrors, GetV0CityByCityNameAgentsResponses, GetV0CityByCityNameBeadByIdData, GetV0CityByCityNameBeadByIdDepsData, GetV0CityByCityNameBeadByIdDepsErrors, GetV0CityByCityNameBeadByIdDepsResponses, GetV0CityByCityNameBeadByIdErrors, GetV0CityByCityNameBeadByIdResponses, GetV0CityByCityNameBeadsData, GetV0CityByCityNameBeadsErrors, GetV0CityByCityNameBeadsGraphByRootIdData, GetV0CityByCityNameBeadsGraphByRootIdErrors, GetV0CityByCityNameBeadsGraphByRootIdResponses, GetV0CityByCityNameBeadsReadyData, GetV0CityByCityNameBeadsReadyErrors, GetV0CityByCityNameBeadsReadyResponses, GetV0CityByCityNameBeadsResponses, GetV0CityByCityNameConfigData, GetV0CityByCityNameConfigErrors, GetV0CityByCityNameConfigExplainData, GetV0CityByCityNameConfigExplainErrors, GetV0CityByCityNameConfigExplainResponses, GetV0CityByCityNameConfigResponses, GetV0CityByCityNameConfigValidateData, GetV0CityByCityNameConfigValidateErrors, GetV0CityByCityNameConfigValidateResponses, GetV0CityByCityNameConvoyByIdCheckData, GetV0CityByCityNameConvoyByIdCheckErrors, GetV0CityByCityNameConvoyByIdCheckResponses, GetV0CityByCityNameConvoyByIdData, GetV0CityByCityNameConvoyByIdErrors, GetV0CityByCityNameConvoyByIdResponses, GetV0CityByCityNameConvoysData, GetV0CityByCityNameConvoysErrors, GetV0CityByCityNameConvoysResponses, GetV0CityByCityNameData, GetV0CityByCityNameErrors, GetV0CityByCityNameEventsData, GetV0CityByCityNameEventsErrors, GetV0CityByCityNameEventsResponses, GetV0CityByCityNameExtmsgAdaptersData, GetV0CityByCityNameExtmsgAdaptersErrors, GetV0CityByCityNameExtmsgAdaptersResponses, GetV0CityByCityNameExtmsgBindingsData, GetV0CityByCityNameExtmsgBindingsErrors, GetV0CityByCityNameExtmsgBindingsResponses, GetV0CityByCityNameExtmsgGroupsData, GetV0CityByCityNameExtmsgGroupsErrors, GetV0CityByCityNameExtmsgGroupsResponses, GetV0CityByCityNameExtmsgTranscriptData, GetV0CityByCityNameExtmsgTranscriptErrors, GetV0CityByCityNameExtmsgTranscriptResponses, GetV0CityByCityNameFormulaByNameData, GetV0CityByCityNameFormulaByNameErrors, GetV0CityByCityNameFormulaByNameResponses, GetV0CityByCityNameFormulasByNameData, GetV0CityByCityNameFormulasByNameErrors, GetV0CityByCityNameFormulasByNameResponses, GetV0CityByCityNameFormulasByNameRunsData, GetV0CityByCityNameFormulasByNameRunsErrors, GetV0CityByCityNameFormulasByNameRunsResponses, GetV0CityByCityNameFormulasData, GetV0CityByCityNameFormulasErrors, GetV0CityByCityNameFormulasFeedData, GetV0CityByCityNameFormulasFeedErrors, GetV0CityByCityNameFormulasFeedResponses, GetV0CityByCityNameFormulasResponses, GetV0CityByCityNameHealthData, GetV0CityByCityNameHealthErrors, GetV0CityByCityNameHealthResponses, GetV0CityByCityNameMailByIdData, GetV0CityByCityNameMailByIdErrors, GetV0CityByCityNameMailByIdResponses, GetV0CityByCityNameMailCountData, GetV0CityByCityNameMailCountErrors, GetV0CityByCityNameMailCountResponses, GetV0CityByCityNameMailData, GetV0CityByCityNameMailErrors, GetV0CityByCityNameMailResponses, GetV0CityByCityNameMailThreadByIdData, GetV0CityByCityNameMailThreadByIdErrors, GetV0CityByCityNameMailThreadByIdResponses, GetV0CityByCityNameOrderByNameData, GetV0CityByCityNameOrderByNameErrors, GetV0CityByCityNameOrderByNameResponses, GetV0CityByCityNameOrderHistoryByBeadIdData, GetV0CityByCityNameOrderHistoryByBeadIdErrors, GetV0CityByCityNameOrderHistoryByBeadIdResponses, GetV0CityByCityNameOrdersCheckData, GetV0CityByCityNameOrdersCheckErrors, GetV0CityByCityNameOrdersCheckResponses, GetV0CityByCityNameOrdersData, GetV0CityByCityNameOrdersErrors, GetV0CityByCityNameOrdersFeedData, GetV0CityByCityNameOrdersFeedErrors, GetV0CityByCityNameOrdersFeedResponses, GetV0CityByCityNameOrdersHistoryData, GetV0CityByCityNameOrdersHistoryErrors, GetV0CityByCityNameOrdersHistoryResponses, GetV0CityByCityNameOrdersResponses, GetV0CityByCityNamePacksData, GetV0CityByCityNamePacksErrors, GetV0CityByCityNamePacksResponses, GetV0CityByCityNamePatchesAgentByBaseData, GetV0CityByCityNamePatchesAgentByBaseErrors, GetV0CityByCityNamePatchesAgentByBaseResponses, GetV0CityByCityNamePatchesAgentByDirByBaseData, GetV0CityByCityNamePatchesAgentByDirByBaseErrors, GetV0CityByCityNamePatchesAgentByDirByBaseResponses, GetV0CityByCityNamePatchesAgentsData, GetV0CityByCityNamePatchesAgentsErrors, GetV0CityByCityNamePatchesAgentsResponses, GetV0CityByCityNamePatchesProviderByNameData, GetV0CityByCityNamePatchesProviderByNameErrors, GetV0CityByCityNamePatchesProviderByNameResponses, GetV0CityByCityNamePatchesProvidersData, GetV0CityByCityNamePatchesProvidersErrors, GetV0CityByCityNamePatchesProvidersResponses, GetV0CityByCityNamePatchesRigByNameData, GetV0CityByCityNamePatchesRigByNameErrors, GetV0CityByCityNamePatchesRigByNameResponses, GetV0CityByCityNamePatchesRigsData, GetV0CityByCityNamePatchesRigsErrors, GetV0CityByCityNamePatchesRigsResponses, GetV0CityByCityNameProviderByNameData, GetV0CityByCityNameProviderByNameErrors, GetV0CityByCityNameProviderByNameResponses, GetV0CityByCityNameProviderReadinessData, GetV0CityByCityNameProviderReadinessErrors, GetV0CityByCityNameProviderReadinessResponses, GetV0CityByCityNameProvidersData, GetV0CityByCityNameProvidersErrors, GetV0CityByCityNameProvidersPublicData, GetV0CityByCityNameProvidersPublicErrors, GetV0CityByCityNameProvidersPublicResponses, GetV0CityByCityNameProvidersResponses, GetV0CityByCityNameReadinessData, GetV0CityByCityNameReadinessErrors, GetV0CityByCityNameReadinessResponses, GetV0CityByCityNameResponses, GetV0CityByCityNameRigByNameData, GetV0CityByCityNameRigByNameErrors, GetV0CityByCityNameRigByNameResponses, GetV0CityByCityNameRigsData, GetV0CityByCityNameRigsErrors, GetV0CityByCityNameRigsResponses, GetV0CityByCityNameServiceByNameData, GetV0CityByCityNameServiceByNameErrors, GetV0CityByCityNameServiceByNameResponses, GetV0CityByCityNameServicesData, GetV0CityByCityNameServicesErrors, GetV0CityByCityNameServicesResponses, GetV0CityByCityNameSessionByIdAgentsByAgentIdData, GetV0CityByCityNameSessionByIdAgentsByAgentIdErrors, GetV0CityByCityNameSessionByIdAgentsByAgentIdResponses, GetV0CityByCityNameSessionByIdAgentsData, GetV0CityByCityNameSessionByIdAgentsErrors, GetV0CityByCityNameSessionByIdAgentsResponses, GetV0CityByCityNameSessionByIdData, GetV0CityByCityNameSessionByIdErrors, GetV0CityByCityNameSessionByIdPendingData, GetV0CityByCityNameSessionByIdPendingErrors, GetV0CityByCityNameSessionByIdPendingResponses, GetV0CityByCityNameSessionByIdResponses, GetV0CityByCityNameSessionByIdTranscriptData, GetV0CityByCityNameSessionByIdTranscriptErrors, GetV0CityByCityNameSessionByIdTranscriptResponses, GetV0CityByCityNameSessionsData, GetV0CityByCityNameSessionsErrors, GetV0CityByCityNameSessionsResponses, GetV0CityByCityNameStatusData, GetV0CityByCityNameStatusErrors, GetV0CityByCityNameStatusResponses, GetV0CityByCityNameWorkflowByWorkflowIdData, GetV0CityByCityNameWorkflowByWorkflowIdErrors, GetV0CityByCityNameWorkflowByWorkflowIdResponses, GetV0EventsData, GetV0EventsErrors, GetV0EventsResponses, GetV0ProviderReadinessData, GetV0ProviderReadinessErrors, GetV0ProviderReadinessResponses, GetV0ReadinessData, GetV0ReadinessErrors, GetV0ReadinessResponses, PatchV0CityByCityNameAgentByBaseData, PatchV0CityByCityNameAgentByBaseErrors, PatchV0CityByCityNameAgentByBaseResponses, PatchV0CityByCityNameAgentByDirByBaseData, PatchV0CityByCityNameAgentByDirByBaseErrors, PatchV0CityByCityNameAgentByDirByBaseResponses, PatchV0CityByCityNameBeadByIdData, PatchV0CityByCityNameBeadByIdErrors, PatchV0CityByCityNameBeadByIdResponses, PatchV0CityByCityNameData, PatchV0CityByCityNameErrors, PatchV0CityByCityNameProviderByNameData, PatchV0CityByCityNameProviderByNameErrors, PatchV0CityByCityNameProviderByNameResponses, PatchV0CityByCityNameResponses, PatchV0CityByCityNameRigByNameData, PatchV0CityByCityNameRigByNameErrors, PatchV0CityByCityNameRigByNameResponses, PatchV0CityByCityNameSessionByIdData, PatchV0CityByCityNameSessionByIdErrors, PatchV0CityByCityNameSessionByIdResponses, PostV0CityByCityNameAgentByBaseByActionData, PostV0CityByCityNameAgentByBaseByActionErrors, PostV0CityByCityNameAgentByBaseByActionResponses, PostV0CityByCityNameAgentByDirByBaseByActionData, PostV0CityByCityNameAgentByDirByBaseByActionErrors, PostV0CityByCityNameAgentByDirByBaseByActionResponses, PostV0CityByCityNameBeadByIdAssignData, PostV0CityByCityNameBeadByIdAssignErrors, PostV0CityByCityNameBeadByIdAssignResponses, PostV0CityByCityNameBeadByIdCloseData, PostV0CityByCityNameBeadByIdCloseErrors, PostV0CityByCityNameBeadByIdCloseResponses, PostV0CityByCityNameBeadByIdReopenData, PostV0CityByCityNameBeadByIdReopenErrors, PostV0CityByCityNameBeadByIdReopenResponses, PostV0CityByCityNameBeadByIdUpdateData, PostV0CityByCityNameBeadByIdUpdateErrors, PostV0CityByCityNameBeadByIdUpdateResponses, PostV0CityByCityNameConvoyByIdAddData, PostV0CityByCityNameConvoyByIdAddErrors, PostV0CityByCityNameConvoyByIdAddResponses, PostV0CityByCityNameConvoyByIdCloseData, PostV0CityByCityNameConvoyByIdCloseErrors, PostV0CityByCityNameConvoyByIdCloseResponses, PostV0CityByCityNameConvoyByIdRemoveData, PostV0CityByCityNameConvoyByIdRemoveErrors, PostV0CityByCityNameConvoyByIdRemoveResponses, PostV0CityByCityNameExtmsgBindData, PostV0CityByCityNameExtmsgBindErrors, PostV0CityByCityNameExtmsgBindResponses, PostV0CityByCityNameExtmsgInboundData, PostV0CityByCityNameExtmsgInboundErrors, PostV0CityByCityNameExtmsgInboundResponses, PostV0CityByCityNameExtmsgOutboundData, PostV0CityByCityNameExtmsgOutboundErrors, PostV0CityByCityNameExtmsgOutboundResponses, PostV0CityByCityNameExtmsgParticipantsData, PostV0CityByCityNameExtmsgParticipantsErrors, PostV0CityByCityNameExtmsgParticipantsResponses, PostV0CityByCityNameExtmsgTranscriptAckData, PostV0CityByCityNameExtmsgTranscriptAckErrors, PostV0CityByCityNameExtmsgTranscriptAckResponses, PostV0CityByCityNameExtmsgUnbindData, PostV0CityByCityNameExtmsgUnbindErrors, PostV0CityByCityNameExtmsgUnbindResponses, PostV0CityByCityNameFormulasByNamePreviewData, PostV0CityByCityNameFormulasByNamePreviewErrors, PostV0CityByCityNameFormulasByNamePreviewResponses, PostV0CityByCityNameMailByIdArchiveData, PostV0CityByCityNameMailByIdArchiveErrors, PostV0CityByCityNameMailByIdArchiveResponses, PostV0CityByCityNameMailByIdMarkUnreadData, PostV0CityByCityNameMailByIdMarkUnreadErrors, PostV0CityByCityNameMailByIdMarkUnreadResponses, PostV0CityByCityNameMailByIdReadData, PostV0CityByCityNameMailByIdReadErrors, PostV0CityByCityNameMailByIdReadResponses, PostV0CityByCityNameOrderByNameDisableData, PostV0CityByCityNameOrderByNameDisableErrors, PostV0CityByCityNameOrderByNameDisableResponses, PostV0CityByCityNameOrderByNameEnableData, PostV0CityByCityNameOrderByNameEnableErrors, PostV0CityByCityNameOrderByNameEnableResponses, PostV0CityByCityNameRigByNameByActionData, PostV0CityByCityNameRigByNameByActionErrors, PostV0CityByCityNameRigByNameByActionResponses, PostV0CityByCityNameServiceByNameRestartData, PostV0CityByCityNameServiceByNameRestartErrors, PostV0CityByCityNameServiceByNameRestartResponses, PostV0CityByCityNameSessionByIdCloseData, PostV0CityByCityNameSessionByIdCloseErrors, PostV0CityByCityNameSessionByIdCloseResponses, PostV0CityByCityNameSessionByIdKillData, PostV0CityByCityNameSessionByIdKillErrors, PostV0CityByCityNameSessionByIdKillResponses, PostV0CityByCityNameSessionByIdRenameData, PostV0CityByCityNameSessionByIdRenameErrors, PostV0CityByCityNameSessionByIdRenameResponses, PostV0CityByCityNameSessionByIdStopData, PostV0CityByCityNameSessionByIdStopErrors, PostV0CityByCityNameSessionByIdStopResponses, PostV0CityByCityNameSessionByIdSuspendData, PostV0CityByCityNameSessionByIdSuspendErrors, PostV0CityByCityNameSessionByIdSuspendResponses, PostV0CityByCityNameSessionByIdWakeData, PostV0CityByCityNameSessionByIdWakeErrors, PostV0CityByCityNameSessionByIdWakeResponses, PostV0CityByCityNameSlingData, PostV0CityByCityNameSlingErrors, PostV0CityByCityNameSlingResponses, PostV0CityData, PostV0CityErrors, PostV0CityResponses, PutV0CityByCityNamePatchesAgentsData, PutV0CityByCityNamePatchesAgentsErrors, PutV0CityByCityNamePatchesAgentsResponses, PutV0CityByCityNamePatchesProvidersData, PutV0CityByCityNamePatchesProvidersErrors, PutV0CityByCityNamePatchesProvidersResponses, PutV0CityByCityNamePatchesRigsData, PutV0CityByCityNamePatchesRigsErrors, PutV0CityByCityNamePatchesRigsResponses, RegisterExtmsgAdapterData, RegisterExtmsgAdapterErrors, RegisterExtmsgAdapterResponses, ReplyMailData, ReplyMailErrors, ReplyMailResponses, RespondSessionData, RespondSessionErrors, RespondSessionResponses, SendMailData, SendMailErrors, SendMailResponses, SendSessionMessageData, SendSessionMessageErrors, SendSessionMessageResponses, StreamAgentOutputData, StreamAgentOutputErrors, StreamAgentOutputQualifiedData, StreamAgentOutputQualifiedErrors, StreamAgentOutputQualifiedResponse, StreamAgentOutputQualifiedResponses, StreamAgentOutputResponse, StreamAgentOutputResponses, StreamEventsData, StreamEventsErrors, StreamEventsResponse, StreamEventsResponses, StreamSessionData, StreamSessionErrors, StreamSessionResponse, StreamSessionResponses, StreamSupervisorEventsData, StreamSupervisorEventsErrors, StreamSupervisorEventsResponse, StreamSupervisorEventsResponses, SubmitSessionData, SubmitSessionErrors, SubmitSessionResponses } from './types.gen'; +import type { CreateAgentData, CreateAgentErrors, CreateAgentResponses, CreateBeadData, CreateBeadErrors, CreateBeadResponses, CreateConvoyData, CreateConvoyErrors, CreateConvoyResponses, CreateProviderData, CreateProviderErrors, CreateProviderResponses, CreateRigData, CreateRigErrors, CreateRigResponses, CreateSessionData, CreateSessionErrors, CreateSessionResponses, DeleteV0CityByCityNameAgentByBaseData, DeleteV0CityByCityNameAgentByBaseErrors, DeleteV0CityByCityNameAgentByBaseResponses, DeleteV0CityByCityNameAgentByDirByBaseData, DeleteV0CityByCityNameAgentByDirByBaseErrors, DeleteV0CityByCityNameAgentByDirByBaseResponses, DeleteV0CityByCityNameBeadByIdData, DeleteV0CityByCityNameBeadByIdErrors, DeleteV0CityByCityNameBeadByIdResponses, DeleteV0CityByCityNameConvoyByIdData, DeleteV0CityByCityNameConvoyByIdErrors, DeleteV0CityByCityNameConvoyByIdResponses, DeleteV0CityByCityNameExtmsgAdaptersData, DeleteV0CityByCityNameExtmsgAdaptersErrors, DeleteV0CityByCityNameExtmsgAdaptersResponses, DeleteV0CityByCityNameExtmsgParticipantsData, DeleteV0CityByCityNameExtmsgParticipantsErrors, DeleteV0CityByCityNameExtmsgParticipantsResponses, DeleteV0CityByCityNameMailByIdData, DeleteV0CityByCityNameMailByIdErrors, DeleteV0CityByCityNameMailByIdResponses, DeleteV0CityByCityNamePatchesAgentByBaseData, DeleteV0CityByCityNamePatchesAgentByBaseErrors, DeleteV0CityByCityNamePatchesAgentByBaseResponses, DeleteV0CityByCityNamePatchesAgentByDirByBaseData, DeleteV0CityByCityNamePatchesAgentByDirByBaseErrors, DeleteV0CityByCityNamePatchesAgentByDirByBaseResponses, DeleteV0CityByCityNamePatchesProviderByNameData, DeleteV0CityByCityNamePatchesProviderByNameErrors, DeleteV0CityByCityNamePatchesProviderByNameResponses, DeleteV0CityByCityNamePatchesRigByNameData, DeleteV0CityByCityNamePatchesRigByNameErrors, DeleteV0CityByCityNamePatchesRigByNameResponses, DeleteV0CityByCityNameProviderByNameData, DeleteV0CityByCityNameProviderByNameErrors, DeleteV0CityByCityNameProviderByNameResponses, DeleteV0CityByCityNameRigByNameData, DeleteV0CityByCityNameRigByNameErrors, DeleteV0CityByCityNameRigByNameResponses, DeleteV0CityByCityNameWorkflowByWorkflowIdData, DeleteV0CityByCityNameWorkflowByWorkflowIdErrors, DeleteV0CityByCityNameWorkflowByWorkflowIdResponses, EmitEventData, EmitEventErrors, EmitEventResponses, EnsureExtmsgGroupData, EnsureExtmsgGroupErrors, EnsureExtmsgGroupResponses, GetHealthData, GetHealthErrors, GetHealthResponses, GetV0CitiesData, GetV0CitiesErrors, GetV0CitiesResponses, GetV0CityByCityNameAgentByBaseData, GetV0CityByCityNameAgentByBaseErrors, GetV0CityByCityNameAgentByBaseOutputData, GetV0CityByCityNameAgentByBaseOutputErrors, GetV0CityByCityNameAgentByBaseOutputResponses, GetV0CityByCityNameAgentByBaseResponses, GetV0CityByCityNameAgentByDirByBaseData, GetV0CityByCityNameAgentByDirByBaseErrors, GetV0CityByCityNameAgentByDirByBaseOutputData, GetV0CityByCityNameAgentByDirByBaseOutputErrors, GetV0CityByCityNameAgentByDirByBaseOutputResponses, GetV0CityByCityNameAgentByDirByBaseResponses, GetV0CityByCityNameAgentsData, GetV0CityByCityNameAgentsErrors, GetV0CityByCityNameAgentsResponses, GetV0CityByCityNameBeadByIdData, GetV0CityByCityNameBeadByIdDepsData, GetV0CityByCityNameBeadByIdDepsErrors, GetV0CityByCityNameBeadByIdDepsResponses, GetV0CityByCityNameBeadByIdErrors, GetV0CityByCityNameBeadByIdResponses, GetV0CityByCityNameBeadsData, GetV0CityByCityNameBeadsErrors, GetV0CityByCityNameBeadsGraphByRootIdData, GetV0CityByCityNameBeadsGraphByRootIdErrors, GetV0CityByCityNameBeadsGraphByRootIdResponses, GetV0CityByCityNameBeadsReadyData, GetV0CityByCityNameBeadsReadyErrors, GetV0CityByCityNameBeadsReadyResponses, GetV0CityByCityNameBeadsResponses, GetV0CityByCityNameConfigData, GetV0CityByCityNameConfigErrors, GetV0CityByCityNameConfigExplainData, GetV0CityByCityNameConfigExplainErrors, GetV0CityByCityNameConfigExplainResponses, GetV0CityByCityNameConfigResponses, GetV0CityByCityNameConfigValidateData, GetV0CityByCityNameConfigValidateErrors, GetV0CityByCityNameConfigValidateResponses, GetV0CityByCityNameConvoyByIdCheckData, GetV0CityByCityNameConvoyByIdCheckErrors, GetV0CityByCityNameConvoyByIdCheckResponses, GetV0CityByCityNameConvoyByIdData, GetV0CityByCityNameConvoyByIdErrors, GetV0CityByCityNameConvoyByIdResponses, GetV0CityByCityNameConvoysData, GetV0CityByCityNameConvoysErrors, GetV0CityByCityNameConvoysResponses, GetV0CityByCityNameData, GetV0CityByCityNameErrors, GetV0CityByCityNameEventsData, GetV0CityByCityNameEventsErrors, GetV0CityByCityNameEventsResponses, GetV0CityByCityNameExtmsgAdaptersData, GetV0CityByCityNameExtmsgAdaptersErrors, GetV0CityByCityNameExtmsgAdaptersResponses, GetV0CityByCityNameExtmsgBindingsData, GetV0CityByCityNameExtmsgBindingsErrors, GetV0CityByCityNameExtmsgBindingsResponses, GetV0CityByCityNameExtmsgGroupsData, GetV0CityByCityNameExtmsgGroupsErrors, GetV0CityByCityNameExtmsgGroupsResponses, GetV0CityByCityNameExtmsgTranscriptData, GetV0CityByCityNameExtmsgTranscriptErrors, GetV0CityByCityNameExtmsgTranscriptResponses, GetV0CityByCityNameFormulaByNameData, GetV0CityByCityNameFormulaByNameErrors, GetV0CityByCityNameFormulaByNameResponses, GetV0CityByCityNameFormulasByNameData, GetV0CityByCityNameFormulasByNameErrors, GetV0CityByCityNameFormulasByNameResponses, GetV0CityByCityNameFormulasByNameRunsData, GetV0CityByCityNameFormulasByNameRunsErrors, GetV0CityByCityNameFormulasByNameRunsResponses, GetV0CityByCityNameFormulasData, GetV0CityByCityNameFormulasErrors, GetV0CityByCityNameFormulasFeedData, GetV0CityByCityNameFormulasFeedErrors, GetV0CityByCityNameFormulasFeedResponses, GetV0CityByCityNameFormulasResponses, GetV0CityByCityNameHealthData, GetV0CityByCityNameHealthErrors, GetV0CityByCityNameHealthResponses, GetV0CityByCityNameMailByIdData, GetV0CityByCityNameMailByIdErrors, GetV0CityByCityNameMailByIdResponses, GetV0CityByCityNameMailCountData, GetV0CityByCityNameMailCountErrors, GetV0CityByCityNameMailCountResponses, GetV0CityByCityNameMailData, GetV0CityByCityNameMailErrors, GetV0CityByCityNameMailResponses, GetV0CityByCityNameMailThreadByIdData, GetV0CityByCityNameMailThreadByIdErrors, GetV0CityByCityNameMailThreadByIdResponses, GetV0CityByCityNameOrderByNameData, GetV0CityByCityNameOrderByNameErrors, GetV0CityByCityNameOrderByNameResponses, GetV0CityByCityNameOrderHistoryByBeadIdData, GetV0CityByCityNameOrderHistoryByBeadIdErrors, GetV0CityByCityNameOrderHistoryByBeadIdResponses, GetV0CityByCityNameOrdersCheckData, GetV0CityByCityNameOrdersCheckErrors, GetV0CityByCityNameOrdersCheckResponses, GetV0CityByCityNameOrdersData, GetV0CityByCityNameOrdersErrors, GetV0CityByCityNameOrdersFeedData, GetV0CityByCityNameOrdersFeedErrors, GetV0CityByCityNameOrdersFeedResponses, GetV0CityByCityNameOrdersHistoryData, GetV0CityByCityNameOrdersHistoryErrors, GetV0CityByCityNameOrdersHistoryResponses, GetV0CityByCityNameOrdersResponses, GetV0CityByCityNamePacksData, GetV0CityByCityNamePacksErrors, GetV0CityByCityNamePacksResponses, GetV0CityByCityNamePatchesAgentByBaseData, GetV0CityByCityNamePatchesAgentByBaseErrors, GetV0CityByCityNamePatchesAgentByBaseResponses, GetV0CityByCityNamePatchesAgentByDirByBaseData, GetV0CityByCityNamePatchesAgentByDirByBaseErrors, GetV0CityByCityNamePatchesAgentByDirByBaseResponses, GetV0CityByCityNamePatchesAgentsData, GetV0CityByCityNamePatchesAgentsErrors, GetV0CityByCityNamePatchesAgentsResponses, GetV0CityByCityNamePatchesProviderByNameData, GetV0CityByCityNamePatchesProviderByNameErrors, GetV0CityByCityNamePatchesProviderByNameResponses, GetV0CityByCityNamePatchesProvidersData, GetV0CityByCityNamePatchesProvidersErrors, GetV0CityByCityNamePatchesProvidersResponses, GetV0CityByCityNamePatchesRigByNameData, GetV0CityByCityNamePatchesRigByNameErrors, GetV0CityByCityNamePatchesRigByNameResponses, GetV0CityByCityNamePatchesRigsData, GetV0CityByCityNamePatchesRigsErrors, GetV0CityByCityNamePatchesRigsResponses, GetV0CityByCityNameProviderByNameData, GetV0CityByCityNameProviderByNameErrors, GetV0CityByCityNameProviderByNameResponses, GetV0CityByCityNameProviderReadinessData, GetV0CityByCityNameProviderReadinessErrors, GetV0CityByCityNameProviderReadinessResponses, GetV0CityByCityNameProvidersData, GetV0CityByCityNameProvidersErrors, GetV0CityByCityNameProvidersPublicData, GetV0CityByCityNameProvidersPublicErrors, GetV0CityByCityNameProvidersPublicResponses, GetV0CityByCityNameProvidersResponses, GetV0CityByCityNameReadinessData, GetV0CityByCityNameReadinessErrors, GetV0CityByCityNameReadinessResponses, GetV0CityByCityNameResponses, GetV0CityByCityNameRigByNameData, GetV0CityByCityNameRigByNameErrors, GetV0CityByCityNameRigByNameResponses, GetV0CityByCityNameRigsData, GetV0CityByCityNameRigsErrors, GetV0CityByCityNameRigsResponses, GetV0CityByCityNameServiceByNameData, GetV0CityByCityNameServiceByNameErrors, GetV0CityByCityNameServiceByNameResponses, GetV0CityByCityNameServicesData, GetV0CityByCityNameServicesErrors, GetV0CityByCityNameServicesResponses, GetV0CityByCityNameSessionByIdAgentsByAgentIdData, GetV0CityByCityNameSessionByIdAgentsByAgentIdErrors, GetV0CityByCityNameSessionByIdAgentsByAgentIdResponses, GetV0CityByCityNameSessionByIdAgentsData, GetV0CityByCityNameSessionByIdAgentsErrors, GetV0CityByCityNameSessionByIdAgentsResponses, GetV0CityByCityNameSessionByIdData, GetV0CityByCityNameSessionByIdErrors, GetV0CityByCityNameSessionByIdPendingData, GetV0CityByCityNameSessionByIdPendingErrors, GetV0CityByCityNameSessionByIdPendingResponses, GetV0CityByCityNameSessionByIdResponses, GetV0CityByCityNameSessionByIdTranscriptData, GetV0CityByCityNameSessionByIdTranscriptErrors, GetV0CityByCityNameSessionByIdTranscriptResponses, GetV0CityByCityNameSessionsData, GetV0CityByCityNameSessionsErrors, GetV0CityByCityNameSessionsResponses, GetV0CityByCityNameStatusData, GetV0CityByCityNameStatusErrors, GetV0CityByCityNameStatusResponses, GetV0CityByCityNameWorkflowByWorkflowIdData, GetV0CityByCityNameWorkflowByWorkflowIdErrors, GetV0CityByCityNameWorkflowByWorkflowIdResponses, GetV0EventsData, GetV0EventsErrors, GetV0EventsResponses, GetV0ProviderReadinessData, GetV0ProviderReadinessErrors, GetV0ProviderReadinessResponses, GetV0ReadinessData, GetV0ReadinessErrors, GetV0ReadinessResponses, PatchV0CityByCityNameAgentByBaseData, PatchV0CityByCityNameAgentByBaseErrors, PatchV0CityByCityNameAgentByBaseResponses, PatchV0CityByCityNameAgentByDirByBaseData, PatchV0CityByCityNameAgentByDirByBaseErrors, PatchV0CityByCityNameAgentByDirByBaseResponses, PatchV0CityByCityNameBeadByIdData, PatchV0CityByCityNameBeadByIdErrors, PatchV0CityByCityNameBeadByIdResponses, PatchV0CityByCityNameData, PatchV0CityByCityNameErrors, PatchV0CityByCityNameProviderByNameData, PatchV0CityByCityNameProviderByNameErrors, PatchV0CityByCityNameProviderByNameResponses, PatchV0CityByCityNameResponses, PatchV0CityByCityNameRigByNameData, PatchV0CityByCityNameRigByNameErrors, PatchV0CityByCityNameRigByNameResponses, PatchV0CityByCityNameSessionByIdData, PatchV0CityByCityNameSessionByIdErrors, PatchV0CityByCityNameSessionByIdResponses, PostV0CityByCityNameAgentByBaseByActionData, PostV0CityByCityNameAgentByBaseByActionErrors, PostV0CityByCityNameAgentByBaseByActionResponses, PostV0CityByCityNameAgentByDirByBaseByActionData, PostV0CityByCityNameAgentByDirByBaseByActionErrors, PostV0CityByCityNameAgentByDirByBaseByActionResponses, PostV0CityByCityNameBeadByIdAssignData, PostV0CityByCityNameBeadByIdAssignErrors, PostV0CityByCityNameBeadByIdAssignResponses, PostV0CityByCityNameBeadByIdCloseData, PostV0CityByCityNameBeadByIdCloseErrors, PostV0CityByCityNameBeadByIdCloseResponses, PostV0CityByCityNameBeadByIdReopenData, PostV0CityByCityNameBeadByIdReopenErrors, PostV0CityByCityNameBeadByIdReopenResponses, PostV0CityByCityNameBeadByIdUpdateData, PostV0CityByCityNameBeadByIdUpdateErrors, PostV0CityByCityNameBeadByIdUpdateResponses, PostV0CityByCityNameConvoyByIdAddData, PostV0CityByCityNameConvoyByIdAddErrors, PostV0CityByCityNameConvoyByIdAddResponses, PostV0CityByCityNameConvoyByIdCloseData, PostV0CityByCityNameConvoyByIdCloseErrors, PostV0CityByCityNameConvoyByIdCloseResponses, PostV0CityByCityNameConvoyByIdRemoveData, PostV0CityByCityNameConvoyByIdRemoveErrors, PostV0CityByCityNameConvoyByIdRemoveResponses, PostV0CityByCityNameExtmsgBindData, PostV0CityByCityNameExtmsgBindErrors, PostV0CityByCityNameExtmsgBindResponses, PostV0CityByCityNameExtmsgInboundData, PostV0CityByCityNameExtmsgInboundErrors, PostV0CityByCityNameExtmsgInboundResponses, PostV0CityByCityNameExtmsgOutboundData, PostV0CityByCityNameExtmsgOutboundErrors, PostV0CityByCityNameExtmsgOutboundResponses, PostV0CityByCityNameExtmsgParticipantsData, PostV0CityByCityNameExtmsgParticipantsErrors, PostV0CityByCityNameExtmsgParticipantsResponses, PostV0CityByCityNameExtmsgTranscriptAckData, PostV0CityByCityNameExtmsgTranscriptAckErrors, PostV0CityByCityNameExtmsgTranscriptAckResponses, PostV0CityByCityNameExtmsgUnbindData, PostV0CityByCityNameExtmsgUnbindErrors, PostV0CityByCityNameExtmsgUnbindResponses, PostV0CityByCityNameFormulasByNamePreviewData, PostV0CityByCityNameFormulasByNamePreviewErrors, PostV0CityByCityNameFormulasByNamePreviewResponses, PostV0CityByCityNameMailByIdArchiveData, PostV0CityByCityNameMailByIdArchiveErrors, PostV0CityByCityNameMailByIdArchiveResponses, PostV0CityByCityNameMailByIdMarkUnreadData, PostV0CityByCityNameMailByIdMarkUnreadErrors, PostV0CityByCityNameMailByIdMarkUnreadResponses, PostV0CityByCityNameMailByIdReadData, PostV0CityByCityNameMailByIdReadErrors, PostV0CityByCityNameMailByIdReadResponses, PostV0CityByCityNameOrderByNameDisableData, PostV0CityByCityNameOrderByNameDisableErrors, PostV0CityByCityNameOrderByNameDisableResponses, PostV0CityByCityNameOrderByNameEnableData, PostV0CityByCityNameOrderByNameEnableErrors, PostV0CityByCityNameOrderByNameEnableResponses, PostV0CityByCityNameRigByNameByActionData, PostV0CityByCityNameRigByNameByActionErrors, PostV0CityByCityNameRigByNameByActionResponses, PostV0CityByCityNameServiceByNameRestartData, PostV0CityByCityNameServiceByNameRestartErrors, PostV0CityByCityNameServiceByNameRestartResponses, PostV0CityByCityNameSessionByIdCloseData, PostV0CityByCityNameSessionByIdCloseErrors, PostV0CityByCityNameSessionByIdCloseResponses, PostV0CityByCityNameSessionByIdKillData, PostV0CityByCityNameSessionByIdKillErrors, PostV0CityByCityNameSessionByIdKillResponses, PostV0CityByCityNameSessionByIdRenameData, PostV0CityByCityNameSessionByIdRenameErrors, PostV0CityByCityNameSessionByIdRenameResponses, PostV0CityByCityNameSessionByIdStopData, PostV0CityByCityNameSessionByIdStopErrors, PostV0CityByCityNameSessionByIdStopResponses, PostV0CityByCityNameSessionByIdSuspendData, PostV0CityByCityNameSessionByIdSuspendErrors, PostV0CityByCityNameSessionByIdSuspendResponses, PostV0CityByCityNameSessionByIdWakeData, PostV0CityByCityNameSessionByIdWakeErrors, PostV0CityByCityNameSessionByIdWakeResponses, PostV0CityByCityNameSlingData, PostV0CityByCityNameSlingErrors, PostV0CityByCityNameSlingResponses, PostV0CityByCityNameUnregisterData, PostV0CityByCityNameUnregisterErrors, PostV0CityByCityNameUnregisterResponses, PostV0CityData, PostV0CityErrors, PostV0CityResponses, PutV0CityByCityNamePatchesAgentsData, PutV0CityByCityNamePatchesAgentsErrors, PutV0CityByCityNamePatchesAgentsResponses, PutV0CityByCityNamePatchesProvidersData, PutV0CityByCityNamePatchesProvidersErrors, PutV0CityByCityNamePatchesProvidersResponses, PutV0CityByCityNamePatchesRigsData, PutV0CityByCityNamePatchesRigsErrors, PutV0CityByCityNamePatchesRigsResponses, RegisterExtmsgAdapterData, RegisterExtmsgAdapterErrors, RegisterExtmsgAdapterResponses, ReplyMailData, ReplyMailErrors, ReplyMailResponses, RespondSessionData, RespondSessionErrors, RespondSessionResponses, SendMailData, SendMailErrors, SendMailResponses, SendSessionMessageData, SendSessionMessageErrors, SendSessionMessageResponses, StreamAgentOutputData, StreamAgentOutputErrors, StreamAgentOutputQualifiedData, StreamAgentOutputQualifiedErrors, StreamAgentOutputQualifiedResponse, StreamAgentOutputQualifiedResponses, StreamAgentOutputResponse, StreamAgentOutputResponses, StreamEventsData, StreamEventsErrors, StreamEventsResponse, StreamEventsResponses, StreamSessionData, StreamSessionErrors, StreamSessionResponse, StreamSessionResponses, StreamSupervisorEventsData, StreamSupervisorEventsErrors, StreamSupervisorEventsResponse, StreamSupervisorEventsResponses, SubmitSessionData, SubmitSessionErrors, SubmitSessionResponses } from './types.gen'; export type Options = Options2 & { /** @@ -986,6 +986,11 @@ export const postV0CityByCityNameSling = ( */ export const getV0CityByCityNameStatus = (options: Options) => (options.client ?? client).get({ url: '/v0/city/{cityName}/status', ...options }); +/** + * Post v0 city by city name unregister + */ +export const postV0CityByCityNameUnregister = (options: Options) => (options.client ?? client).post({ url: '/v0/city/{cityName}/unregister', ...options }); + /** * Delete v0 city by city name workflow by workflow ID */ diff --git a/cmd/gc/dashboard/web/src/generated/types.gen.ts b/cmd/gc/dashboard/web/src/generated/types.gen.ts index 40bd0fce00..f516c7dcf0 100644 --- a/cmd/gc/dashboard/web/src/generated/types.gen.ts +++ b/cmd/gc/dashboard/web/src/generated/types.gen.ts @@ -58,6 +58,7 @@ export type AgentOutputResponse = { }; export type AgentPatch = { + AppendFragments: Array | null; Attach: boolean | null; DefaultSlingFormula: string | null; DependsOn: Array | null; @@ -257,6 +258,16 @@ export type BeadCreateInputBody = { * Bead labels. */ labels?: Array | null; + /** + * Metadata key-value pairs to set at create time. + */ + metadata?: { + [key: string]: string; + }; + /** + * Parent bead ID. + */ + parent?: string; /** * Bead priority. */ @@ -308,6 +319,10 @@ export type BeadUpdateBody = { metadata?: { [key: string]: string; }; + /** + * Parent bead ID. Use null or an empty string to clear. + */ + parent?: string | null; /** * Bead priority. */ @@ -358,11 +373,15 @@ export type CityCreateRequest = { export type CityCreateResponse = { /** - * True on success. + * Resolved city name as persisted in city.toml. Use this to filter the event stream for completion. + */ + name: string; + /** + * True when scaffolding + registration succeeded. Does not imply the city is ready yet; watch /v0/events/stream for city.ready. */ ok: boolean; /** - * Resolved absolute path of the created city. + * Resolved absolute path of the created city directory. */ path: string; }; @@ -388,6 +407,13 @@ export type CityInfo = { status?: string; }; +export type CityLifecyclePayload = { + error?: string; + name: string; + path: string; + phases_completed?: Array | null; +}; + export type CityPatchInputBody = { /** * Whether the city is suspended. @@ -395,6 +421,21 @@ export type CityPatchInputBody = { suspended?: boolean; }; +export type CityUnregisterResponse = { + /** + * Resolved registry name. Filter the event stream by this to observe completion. + */ + name: string; + /** + * True when the registry entry was removed and the supervisor was signaled. Does not imply the city's controller has stopped yet; watch /v0/events/stream for city.unregistered. + */ + ok: boolean; + /** + * Resolved absolute city directory. The directory itself is not modified; unregister only affects the supervisor's registry. + */ + path: string; +}; + export type ConfigAgentResponse = { dir?: string; is_pool?: boolean; @@ -676,7 +717,7 @@ export type EventEmitRequest = { type: string; }; -export type EventPayload = AdapterEventPayload | BeadEventPayload | BoundEventPayload | GroupCreatedEventPayload | InboundEventPayload | MailEventPayload | NoPayload | OutboundEventPayload | UnboundEventPayload | WorkerOperationEventPayload; +export type EventPayload = AdapterEventPayload | BeadEventPayload | BoundEventPayload | CityLifecyclePayload | GroupCreatedEventPayload | InboundEventPayload | MailEventPayload | NoPayload | OutboundEventPayload | UnboundEventPayload | WorkerOperationEventPayload; export type EventStreamEnvelope = { actor: string; @@ -2434,6 +2475,10 @@ export type SlingInputBody = { * Bead ID to sling. */ bead?: string; + /** + * Bypass cross-rig guards; for direct bead routes, also bypass missing-bead validation. Formula-backed graph routes may replace existing live workflow roots but still require the source bead to exist. + */ + force?: boolean; /** * Formula name for workflow launch. */ @@ -2675,23 +2720,1456 @@ export type TaggedEventStreamEnvelope = { actor: string; city: string; message?: string; - payload?: EventPayload; + payload?: EventPayload; + seq: number; + subject?: string; + ts: string; + type: string; + workflow?: WorkflowEventProjection; +}; + +/** + * Direction of a transcript entry. + */ +export type TranscriptMessageKind = 'inbound' | 'outbound'; + +/** + * Provenance of a transcript entry (freshly observed vs. replayed from persisted history). + */ +export type TranscriptProvenance = 'live' | 'hydrated'; + +/** + * Typed city event stream envelope + * + * Discriminated union of city event stream envelopes. Each variant constrains the envelope type and payload schema together. + */ +export type TypedEventStreamEnvelope = ({ + type: 'bead.closed'; +} & TypedEventStreamEnvelopeBeadClosed) | ({ + type: 'bead.created'; +} & TypedEventStreamEnvelopeBeadCreated) | ({ + type: 'bead.updated'; +} & TypedEventStreamEnvelopeBeadUpdated) | ({ + type: 'city.created'; +} & TypedEventStreamEnvelopeCityCreated) | ({ + type: 'city.init_failed'; +} & TypedEventStreamEnvelopeCityInitFailed) | ({ + type: 'city.ready'; +} & TypedEventStreamEnvelopeCityReady) | ({ + type: 'city.resumed'; +} & TypedEventStreamEnvelopeCityResumed) | ({ + type: 'city.suspended'; +} & TypedEventStreamEnvelopeCitySuspended) | ({ + type: 'city.unregister_failed'; +} & TypedEventStreamEnvelopeCityUnregisterFailed) | ({ + type: 'city.unregister_requested'; +} & TypedEventStreamEnvelopeCityUnregisterRequested) | ({ + type: 'city.unregistered'; +} & TypedEventStreamEnvelopeCityUnregistered) | ({ + type: 'controller.started'; +} & TypedEventStreamEnvelopeControllerStarted) | ({ + type: 'controller.stopped'; +} & TypedEventStreamEnvelopeControllerStopped) | ({ + type: 'convoy.closed'; +} & TypedEventStreamEnvelopeConvoyClosed) | ({ + type: 'convoy.created'; +} & TypedEventStreamEnvelopeConvoyCreated) | ({ + type: 'extmsg.adapter_added'; +} & TypedEventStreamEnvelopeExtmsgAdapterAdded) | ({ + type: 'extmsg.adapter_removed'; +} & TypedEventStreamEnvelopeExtmsgAdapterRemoved) | ({ + type: 'extmsg.bound'; +} & TypedEventStreamEnvelopeExtmsgBound) | ({ + type: 'extmsg.group_created'; +} & TypedEventStreamEnvelopeExtmsgGroupCreated) | ({ + type: 'extmsg.inbound'; +} & TypedEventStreamEnvelopeExtmsgInbound) | ({ + type: 'extmsg.outbound'; +} & TypedEventStreamEnvelopeExtmsgOutbound) | ({ + type: 'extmsg.unbound'; +} & TypedEventStreamEnvelopeExtmsgUnbound) | ({ + type: 'mail.archived'; +} & TypedEventStreamEnvelopeMailArchived) | ({ + type: 'mail.deleted'; +} & TypedEventStreamEnvelopeMailDeleted) | ({ + type: 'mail.marked_read'; +} & TypedEventStreamEnvelopeMailMarkedRead) | ({ + type: 'mail.marked_unread'; +} & TypedEventStreamEnvelopeMailMarkedUnread) | ({ + type: 'mail.read'; +} & TypedEventStreamEnvelopeMailRead) | ({ + type: 'mail.replied'; +} & TypedEventStreamEnvelopeMailReplied) | ({ + type: 'mail.sent'; +} & TypedEventStreamEnvelopeMailSent) | ({ + type: 'order.completed'; +} & TypedEventStreamEnvelopeOrderCompleted) | ({ + type: 'order.failed'; +} & TypedEventStreamEnvelopeOrderFailed) | ({ + type: 'order.fired'; +} & TypedEventStreamEnvelopeOrderFired) | ({ + type: 'provider.swapped'; +} & TypedEventStreamEnvelopeProviderSwapped) | ({ + type: 'session.crashed'; +} & TypedEventStreamEnvelopeSessionCrashed) | ({ + type: 'session.draining'; +} & TypedEventStreamEnvelopeSessionDraining) | ({ + type: 'session.idle_killed'; +} & TypedEventStreamEnvelopeSessionIdleKilled) | ({ + type: 'session.quarantined'; +} & TypedEventStreamEnvelopeSessionQuarantined) | ({ + type: 'session.stopped'; +} & TypedEventStreamEnvelopeSessionStopped) | ({ + type: 'session.suspended'; +} & TypedEventStreamEnvelopeSessionSuspended) | ({ + type: 'session.undrained'; +} & TypedEventStreamEnvelopeSessionUndrained) | ({ + type: 'session.updated'; +} & TypedEventStreamEnvelopeSessionUpdated) | ({ + type: 'session.woke'; +} & TypedEventStreamEnvelopeSessionWoke) | ({ + type: 'worker.operation'; +} & TypedEventStreamEnvelopeWorkerOperation); + +/** + * TypedEventStreamEnvelope bead.closed + */ +export type TypedEventStreamEnvelopeBeadClosed = { + actor: string; + message?: string; + payload: BeadEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'bead.closed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope bead.created + */ +export type TypedEventStreamEnvelopeBeadCreated = { + actor: string; + message?: string; + payload: BeadEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'bead.created'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope bead.updated + */ +export type TypedEventStreamEnvelopeBeadUpdated = { + actor: string; + message?: string; + payload: BeadEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'bead.updated'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope city.created + */ +export type TypedEventStreamEnvelopeCityCreated = { + actor: string; + message?: string; + payload: CityLifecyclePayload; + seq: number; + subject?: string; + ts: string; + type: 'city.created'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope city.init_failed + */ +export type TypedEventStreamEnvelopeCityInitFailed = { + actor: string; + message?: string; + payload: CityLifecyclePayload; + seq: number; + subject?: string; + ts: string; + type: 'city.init_failed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope city.ready + */ +export type TypedEventStreamEnvelopeCityReady = { + actor: string; + message?: string; + payload: CityLifecyclePayload; + seq: number; + subject?: string; + ts: string; + type: 'city.ready'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope city.resumed + */ +export type TypedEventStreamEnvelopeCityResumed = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'city.resumed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope city.suspended + */ +export type TypedEventStreamEnvelopeCitySuspended = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'city.suspended'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope city.unregister_failed + */ +export type TypedEventStreamEnvelopeCityUnregisterFailed = { + actor: string; + message?: string; + payload: CityLifecyclePayload; + seq: number; + subject?: string; + ts: string; + type: 'city.unregister_failed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope city.unregister_requested + */ +export type TypedEventStreamEnvelopeCityUnregisterRequested = { + actor: string; + message?: string; + payload: CityLifecyclePayload; + seq: number; + subject?: string; + ts: string; + type: 'city.unregister_requested'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope city.unregistered + */ +export type TypedEventStreamEnvelopeCityUnregistered = { + actor: string; + message?: string; + payload: CityLifecyclePayload; + seq: number; + subject?: string; + ts: string; + type: 'city.unregistered'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope controller.started + */ +export type TypedEventStreamEnvelopeControllerStarted = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'controller.started'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope controller.stopped + */ +export type TypedEventStreamEnvelopeControllerStopped = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'controller.stopped'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope convoy.closed + */ +export type TypedEventStreamEnvelopeConvoyClosed = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'convoy.closed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope convoy.created + */ +export type TypedEventStreamEnvelopeConvoyCreated = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'convoy.created'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope extmsg.adapter_added + */ +export type TypedEventStreamEnvelopeExtmsgAdapterAdded = { + actor: string; + message?: string; + payload: AdapterEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'extmsg.adapter_added'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope extmsg.adapter_removed + */ +export type TypedEventStreamEnvelopeExtmsgAdapterRemoved = { + actor: string; + message?: string; + payload: AdapterEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'extmsg.adapter_removed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope extmsg.bound + */ +export type TypedEventStreamEnvelopeExtmsgBound = { + actor: string; + message?: string; + payload: BoundEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'extmsg.bound'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope extmsg.group_created + */ +export type TypedEventStreamEnvelopeExtmsgGroupCreated = { + actor: string; + message?: string; + payload: GroupCreatedEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'extmsg.group_created'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope extmsg.inbound + */ +export type TypedEventStreamEnvelopeExtmsgInbound = { + actor: string; + message?: string; + payload: InboundEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'extmsg.inbound'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope extmsg.outbound + */ +export type TypedEventStreamEnvelopeExtmsgOutbound = { + actor: string; + message?: string; + payload: OutboundEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'extmsg.outbound'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope extmsg.unbound + */ +export type TypedEventStreamEnvelopeExtmsgUnbound = { + actor: string; + message?: string; + payload: UnboundEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'extmsg.unbound'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope mail.archived + */ +export type TypedEventStreamEnvelopeMailArchived = { + actor: string; + message?: string; + payload: MailEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'mail.archived'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope mail.deleted + */ +export type TypedEventStreamEnvelopeMailDeleted = { + actor: string; + message?: string; + payload: MailEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'mail.deleted'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope mail.marked_read + */ +export type TypedEventStreamEnvelopeMailMarkedRead = { + actor: string; + message?: string; + payload: MailEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'mail.marked_read'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope mail.marked_unread + */ +export type TypedEventStreamEnvelopeMailMarkedUnread = { + actor: string; + message?: string; + payload: MailEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'mail.marked_unread'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope mail.read + */ +export type TypedEventStreamEnvelopeMailRead = { + actor: string; + message?: string; + payload: MailEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'mail.read'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope mail.replied + */ +export type TypedEventStreamEnvelopeMailReplied = { + actor: string; + message?: string; + payload: MailEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'mail.replied'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope mail.sent + */ +export type TypedEventStreamEnvelopeMailSent = { + actor: string; + message?: string; + payload: MailEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'mail.sent'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope order.completed + */ +export type TypedEventStreamEnvelopeOrderCompleted = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'order.completed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope order.failed + */ +export type TypedEventStreamEnvelopeOrderFailed = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'order.failed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope order.fired + */ +export type TypedEventStreamEnvelopeOrderFired = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'order.fired'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope provider.swapped + */ +export type TypedEventStreamEnvelopeProviderSwapped = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'provider.swapped'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope session.crashed + */ +export type TypedEventStreamEnvelopeSessionCrashed = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'session.crashed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope session.draining + */ +export type TypedEventStreamEnvelopeSessionDraining = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'session.draining'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope session.idle_killed + */ +export type TypedEventStreamEnvelopeSessionIdleKilled = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'session.idle_killed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope session.quarantined + */ +export type TypedEventStreamEnvelopeSessionQuarantined = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'session.quarantined'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope session.stopped + */ +export type TypedEventStreamEnvelopeSessionStopped = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'session.stopped'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope session.suspended + */ +export type TypedEventStreamEnvelopeSessionSuspended = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'session.suspended'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope session.undrained + */ +export type TypedEventStreamEnvelopeSessionUndrained = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'session.undrained'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope session.updated + */ +export type TypedEventStreamEnvelopeSessionUpdated = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'session.updated'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope session.woke + */ +export type TypedEventStreamEnvelopeSessionWoke = { + actor: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'session.woke'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedEventStreamEnvelope worker.operation + */ +export type TypedEventStreamEnvelopeWorkerOperation = { + actor: string; + message?: string; + payload: WorkerOperationEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'worker.operation'; + workflow?: WorkflowEventProjection; +}; + +/** + * Typed supervisor event stream envelope + * + * Discriminated union of supervisor event stream envelopes. Each variant constrains the envelope type and payload schema together and includes the source city. + */ +export type TypedTaggedEventStreamEnvelope = ({ + type: 'bead.closed'; +} & TypedTaggedEventStreamEnvelopeBeadClosed) | ({ + type: 'bead.created'; +} & TypedTaggedEventStreamEnvelopeBeadCreated) | ({ + type: 'bead.updated'; +} & TypedTaggedEventStreamEnvelopeBeadUpdated) | ({ + type: 'city.created'; +} & TypedTaggedEventStreamEnvelopeCityCreated) | ({ + type: 'city.init_failed'; +} & TypedTaggedEventStreamEnvelopeCityInitFailed) | ({ + type: 'city.ready'; +} & TypedTaggedEventStreamEnvelopeCityReady) | ({ + type: 'city.resumed'; +} & TypedTaggedEventStreamEnvelopeCityResumed) | ({ + type: 'city.suspended'; +} & TypedTaggedEventStreamEnvelopeCitySuspended) | ({ + type: 'city.unregister_failed'; +} & TypedTaggedEventStreamEnvelopeCityUnregisterFailed) | ({ + type: 'city.unregister_requested'; +} & TypedTaggedEventStreamEnvelopeCityUnregisterRequested) | ({ + type: 'city.unregistered'; +} & TypedTaggedEventStreamEnvelopeCityUnregistered) | ({ + type: 'controller.started'; +} & TypedTaggedEventStreamEnvelopeControllerStarted) | ({ + type: 'controller.stopped'; +} & TypedTaggedEventStreamEnvelopeControllerStopped) | ({ + type: 'convoy.closed'; +} & TypedTaggedEventStreamEnvelopeConvoyClosed) | ({ + type: 'convoy.created'; +} & TypedTaggedEventStreamEnvelopeConvoyCreated) | ({ + type: 'extmsg.adapter_added'; +} & TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded) | ({ + type: 'extmsg.adapter_removed'; +} & TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved) | ({ + type: 'extmsg.bound'; +} & TypedTaggedEventStreamEnvelopeExtmsgBound) | ({ + type: 'extmsg.group_created'; +} & TypedTaggedEventStreamEnvelopeExtmsgGroupCreated) | ({ + type: 'extmsg.inbound'; +} & TypedTaggedEventStreamEnvelopeExtmsgInbound) | ({ + type: 'extmsg.outbound'; +} & TypedTaggedEventStreamEnvelopeExtmsgOutbound) | ({ + type: 'extmsg.unbound'; +} & TypedTaggedEventStreamEnvelopeExtmsgUnbound) | ({ + type: 'mail.archived'; +} & TypedTaggedEventStreamEnvelopeMailArchived) | ({ + type: 'mail.deleted'; +} & TypedTaggedEventStreamEnvelopeMailDeleted) | ({ + type: 'mail.marked_read'; +} & TypedTaggedEventStreamEnvelopeMailMarkedRead) | ({ + type: 'mail.marked_unread'; +} & TypedTaggedEventStreamEnvelopeMailMarkedUnread) | ({ + type: 'mail.read'; +} & TypedTaggedEventStreamEnvelopeMailRead) | ({ + type: 'mail.replied'; +} & TypedTaggedEventStreamEnvelopeMailReplied) | ({ + type: 'mail.sent'; +} & TypedTaggedEventStreamEnvelopeMailSent) | ({ + type: 'order.completed'; +} & TypedTaggedEventStreamEnvelopeOrderCompleted) | ({ + type: 'order.failed'; +} & TypedTaggedEventStreamEnvelopeOrderFailed) | ({ + type: 'order.fired'; +} & TypedTaggedEventStreamEnvelopeOrderFired) | ({ + type: 'provider.swapped'; +} & TypedTaggedEventStreamEnvelopeProviderSwapped) | ({ + type: 'session.crashed'; +} & TypedTaggedEventStreamEnvelopeSessionCrashed) | ({ + type: 'session.draining'; +} & TypedTaggedEventStreamEnvelopeSessionDraining) | ({ + type: 'session.idle_killed'; +} & TypedTaggedEventStreamEnvelopeSessionIdleKilled) | ({ + type: 'session.quarantined'; +} & TypedTaggedEventStreamEnvelopeSessionQuarantined) | ({ + type: 'session.stopped'; +} & TypedTaggedEventStreamEnvelopeSessionStopped) | ({ + type: 'session.suspended'; +} & TypedTaggedEventStreamEnvelopeSessionSuspended) | ({ + type: 'session.undrained'; +} & TypedTaggedEventStreamEnvelopeSessionUndrained) | ({ + type: 'session.updated'; +} & TypedTaggedEventStreamEnvelopeSessionUpdated) | ({ + type: 'session.woke'; +} & TypedTaggedEventStreamEnvelopeSessionWoke) | ({ + type: 'worker.operation'; +} & TypedTaggedEventStreamEnvelopeWorkerOperation); + +/** + * TypedTaggedEventStreamEnvelope bead.closed + */ +export type TypedTaggedEventStreamEnvelopeBeadClosed = { + actor: string; + city: string; + message?: string; + payload: BeadEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'bead.closed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope bead.created + */ +export type TypedTaggedEventStreamEnvelopeBeadCreated = { + actor: string; + city: string; + message?: string; + payload: BeadEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'bead.created'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope bead.updated + */ +export type TypedTaggedEventStreamEnvelopeBeadUpdated = { + actor: string; + city: string; + message?: string; + payload: BeadEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'bead.updated'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope city.created + */ +export type TypedTaggedEventStreamEnvelopeCityCreated = { + actor: string; + city: string; + message?: string; + payload: CityLifecyclePayload; + seq: number; + subject?: string; + ts: string; + type: 'city.created'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope city.init_failed + */ +export type TypedTaggedEventStreamEnvelopeCityInitFailed = { + actor: string; + city: string; + message?: string; + payload: CityLifecyclePayload; + seq: number; + subject?: string; + ts: string; + type: 'city.init_failed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope city.ready + */ +export type TypedTaggedEventStreamEnvelopeCityReady = { + actor: string; + city: string; + message?: string; + payload: CityLifecyclePayload; + seq: number; + subject?: string; + ts: string; + type: 'city.ready'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope city.resumed + */ +export type TypedTaggedEventStreamEnvelopeCityResumed = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'city.resumed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope city.suspended + */ +export type TypedTaggedEventStreamEnvelopeCitySuspended = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'city.suspended'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope city.unregister_failed + */ +export type TypedTaggedEventStreamEnvelopeCityUnregisterFailed = { + actor: string; + city: string; + message?: string; + payload: CityLifecyclePayload; + seq: number; + subject?: string; + ts: string; + type: 'city.unregister_failed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope city.unregister_requested + */ +export type TypedTaggedEventStreamEnvelopeCityUnregisterRequested = { + actor: string; + city: string; + message?: string; + payload: CityLifecyclePayload; + seq: number; + subject?: string; + ts: string; + type: 'city.unregister_requested'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope city.unregistered + */ +export type TypedTaggedEventStreamEnvelopeCityUnregistered = { + actor: string; + city: string; + message?: string; + payload: CityLifecyclePayload; + seq: number; + subject?: string; + ts: string; + type: 'city.unregistered'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope controller.started + */ +export type TypedTaggedEventStreamEnvelopeControllerStarted = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'controller.started'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope controller.stopped + */ +export type TypedTaggedEventStreamEnvelopeControllerStopped = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'controller.stopped'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope convoy.closed + */ +export type TypedTaggedEventStreamEnvelopeConvoyClosed = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'convoy.closed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope convoy.created + */ +export type TypedTaggedEventStreamEnvelopeConvoyCreated = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'convoy.created'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope extmsg.adapter_added + */ +export type TypedTaggedEventStreamEnvelopeExtmsgAdapterAdded = { + actor: string; + city: string; + message?: string; + payload: AdapterEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'extmsg.adapter_added'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope extmsg.adapter_removed + */ +export type TypedTaggedEventStreamEnvelopeExtmsgAdapterRemoved = { + actor: string; + city: string; + message?: string; + payload: AdapterEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'extmsg.adapter_removed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope extmsg.bound + */ +export type TypedTaggedEventStreamEnvelopeExtmsgBound = { + actor: string; + city: string; + message?: string; + payload: BoundEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'extmsg.bound'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope extmsg.group_created + */ +export type TypedTaggedEventStreamEnvelopeExtmsgGroupCreated = { + actor: string; + city: string; + message?: string; + payload: GroupCreatedEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'extmsg.group_created'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope extmsg.inbound + */ +export type TypedTaggedEventStreamEnvelopeExtmsgInbound = { + actor: string; + city: string; + message?: string; + payload: InboundEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'extmsg.inbound'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope extmsg.outbound + */ +export type TypedTaggedEventStreamEnvelopeExtmsgOutbound = { + actor: string; + city: string; + message?: string; + payload: OutboundEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'extmsg.outbound'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope extmsg.unbound + */ +export type TypedTaggedEventStreamEnvelopeExtmsgUnbound = { + actor: string; + city: string; + message?: string; + payload: UnboundEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'extmsg.unbound'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope mail.archived + */ +export type TypedTaggedEventStreamEnvelopeMailArchived = { + actor: string; + city: string; + message?: string; + payload: MailEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'mail.archived'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope mail.deleted + */ +export type TypedTaggedEventStreamEnvelopeMailDeleted = { + actor: string; + city: string; + message?: string; + payload: MailEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'mail.deleted'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope mail.marked_read + */ +export type TypedTaggedEventStreamEnvelopeMailMarkedRead = { + actor: string; + city: string; + message?: string; + payload: MailEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'mail.marked_read'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope mail.marked_unread + */ +export type TypedTaggedEventStreamEnvelopeMailMarkedUnread = { + actor: string; + city: string; + message?: string; + payload: MailEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'mail.marked_unread'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope mail.read + */ +export type TypedTaggedEventStreamEnvelopeMailRead = { + actor: string; + city: string; + message?: string; + payload: MailEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'mail.read'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope mail.replied + */ +export type TypedTaggedEventStreamEnvelopeMailReplied = { + actor: string; + city: string; + message?: string; + payload: MailEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'mail.replied'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope mail.sent + */ +export type TypedTaggedEventStreamEnvelopeMailSent = { + actor: string; + city: string; + message?: string; + payload: MailEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'mail.sent'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope order.completed + */ +export type TypedTaggedEventStreamEnvelopeOrderCompleted = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'order.completed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope order.failed + */ +export type TypedTaggedEventStreamEnvelopeOrderFailed = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'order.failed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope order.fired + */ +export type TypedTaggedEventStreamEnvelopeOrderFired = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'order.fired'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope provider.swapped + */ +export type TypedTaggedEventStreamEnvelopeProviderSwapped = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'provider.swapped'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope session.crashed + */ +export type TypedTaggedEventStreamEnvelopeSessionCrashed = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'session.crashed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope session.draining + */ +export type TypedTaggedEventStreamEnvelopeSessionDraining = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'session.draining'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope session.idle_killed + */ +export type TypedTaggedEventStreamEnvelopeSessionIdleKilled = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'session.idle_killed'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope session.quarantined + */ +export type TypedTaggedEventStreamEnvelopeSessionQuarantined = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'session.quarantined'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope session.stopped + */ +export type TypedTaggedEventStreamEnvelopeSessionStopped = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'session.stopped'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope session.suspended + */ +export type TypedTaggedEventStreamEnvelopeSessionSuspended = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'session.suspended'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope session.undrained + */ +export type TypedTaggedEventStreamEnvelopeSessionUndrained = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'session.undrained'; + workflow?: WorkflowEventProjection; +}; + +/** + * TypedTaggedEventStreamEnvelope session.updated + */ +export type TypedTaggedEventStreamEnvelopeSessionUpdated = { + actor: string; + city: string; + message?: string; + payload: NoPayload; seq: number; subject?: string; ts: string; - type: string; + type: 'session.updated'; workflow?: WorkflowEventProjection; }; /** - * Direction of a transcript entry. + * TypedTaggedEventStreamEnvelope session.woke */ -export type TranscriptMessageKind = 'inbound' | 'outbound'; +export type TypedTaggedEventStreamEnvelopeSessionWoke = { + actor: string; + city: string; + message?: string; + payload: NoPayload; + seq: number; + subject?: string; + ts: string; + type: 'session.woke'; + workflow?: WorkflowEventProjection; +}; /** - * Provenance of a transcript entry (freshly observed vs. replayed from persisted history). + * TypedTaggedEventStreamEnvelope worker.operation */ -export type TranscriptProvenance = 'live' | 'hydrated'; +export type TypedTaggedEventStreamEnvelopeWorkerOperation = { + actor: string; + city: string; + message?: string; + payload: WorkerOperationEventPayload; + seq: number; + subject?: string; + ts: string; + type: 'worker.operation'; + workflow?: WorkflowEventProjection; +}; export type UnboundEventPayload = { count: number; @@ -2885,6 +4363,12 @@ export type GetV0CitiesResponse = GetV0CitiesResponses[keyof GetV0CitiesResponse export type PostV0CityData = { body: CityCreateRequest; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path?: never; query?: never; url: '/v0/city'; @@ -2901,9 +4385,9 @@ export type PostV0CityError = PostV0CityErrors[keyof PostV0CityErrors]; export type PostV0CityResponses = { /** - * OK + * Accepted */ - 200: CityCreateResponse; + 202: CityCreateResponse; }; export type PostV0CityResponse = PostV0CityResponses[keyof PostV0CityResponses]; @@ -2940,6 +4424,12 @@ export type GetV0CityByCityNameResponse = GetV0CityByCityNameResponses[keyof Get export type PatchV0CityByCityNameData = { body: CityPatchInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -2970,6 +4460,12 @@ export type PatchV0CityByCityNameResponse = PatchV0CityByCityNameResponses[keyof export type DeleteV0CityByCityNameAgentByBaseData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -3038,6 +4534,12 @@ export type GetV0CityByCityNameAgentByBaseResponse = GetV0CityByCityNameAgentByB export type PatchV0CityByCityNameAgentByBaseData = { body: AgentUpdateInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -3084,7 +4586,7 @@ export type GetV0CityByCityNameAgentByBaseOutputData = { }; query?: { /** - * Number of recent compaction segments to return. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. + * Number of recent compaction segments to return. This API parameter keeps compaction-segment semantics even though gc session logs --tail counts displayed transcript entries. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. */ tail?: string; /** @@ -3179,6 +4681,12 @@ export type StreamAgentOutputResponse = StreamAgentOutputResponses[keyof StreamA export type PostV0CityByCityNameAgentByBaseByActionData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -3217,6 +4725,12 @@ export type PostV0CityByCityNameAgentByBaseByActionResponse = PostV0CityByCityNa export type DeleteV0CityByCityNameAgentByDirByBaseData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -3293,6 +4807,12 @@ export type GetV0CityByCityNameAgentByDirByBaseResponse = GetV0CityByCityNameAge export type PatchV0CityByCityNameAgentByDirByBaseData = { body: AgentUpdateQualifiedInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -3347,7 +4867,7 @@ export type GetV0CityByCityNameAgentByDirByBaseOutputData = { }; query?: { /** - * Number of recent compaction segments to return. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. + * Number of recent compaction segments to return. This API parameter keeps compaction-segment semantics even though gc session logs --tail counts displayed transcript entries. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. */ tail?: string; /** @@ -3446,6 +4966,12 @@ export type StreamAgentOutputQualifiedResponse = StreamAgentOutputQualifiedRespo export type PostV0CityByCityNameAgentByDirByBaseByActionData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -3543,6 +5069,12 @@ export type GetV0CityByCityNameAgentsResponse = GetV0CityByCityNameAgentsRespons export type CreateAgentData = { body: AgentCreateInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -3573,6 +5105,12 @@ export type CreateAgentResponse = CreateAgentResponses[keyof CreateAgentResponse export type DeleteV0CityByCityNameBeadByIdData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -3641,6 +5179,12 @@ export type GetV0CityByCityNameBeadByIdResponse = GetV0CityByCityNameBeadByIdRes export type PatchV0CityByCityNameBeadByIdData = { body: BeadUpdateBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -3675,6 +5219,12 @@ export type PatchV0CityByCityNameBeadByIdResponse = PatchV0CityByCityNameBeadByI export type PostV0CityByCityNameBeadByIdAssignData = { body: BeadAssignInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -3711,6 +5261,12 @@ export type PostV0CityByCityNameBeadByIdAssignResponse = PostV0CityByCityNameBea export type PostV0CityByCityNameBeadByIdCloseData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -3779,6 +5335,12 @@ export type GetV0CityByCityNameBeadByIdDepsResponse = GetV0CityByCityNameBeadByI export type PostV0CityByCityNameBeadByIdReopenData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -3813,6 +5375,12 @@ export type PostV0CityByCityNameBeadByIdReopenResponse = PostV0CityByCityNameBea export type PostV0CityByCityNameBeadByIdUpdateData = { body: BeadUpdateBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -3914,7 +5482,11 @@ export type GetV0CityByCityNameBeadsResponse = GetV0CityByCityNameBeadsResponses export type CreateBeadData = { body: BeadCreateInputBody; - headers?: { + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; /** * Idempotency key for safe retries. */ @@ -4113,6 +5685,12 @@ export type GetV0CityByCityNameConfigValidateResponse = GetV0CityByCityNameConfi export type DeleteV0CityByCityNameConvoyByIdData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -4181,6 +5759,12 @@ export type GetV0CityByCityNameConvoyByIdResponse = GetV0CityByCityNameConvoyByI export type PostV0CityByCityNameConvoyByIdAddData = { body: ConvoyAddInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -4249,6 +5833,12 @@ export type GetV0CityByCityNameConvoyByIdCheckResponse = GetV0CityByCityNameConv export type PostV0CityByCityNameConvoyByIdCloseData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -4283,6 +5873,12 @@ export type PostV0CityByCityNameConvoyByIdCloseResponse = PostV0CityByCityNameCo export type PostV0CityByCityNameConvoyByIdRemoveData = { body: ConvoyRemoveInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -4364,6 +5960,12 @@ export type GetV0CityByCityNameConvoysResponse = GetV0CityByCityNameConvoysRespo export type CreateConvoyData = { body: ConvoyCreateInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -4453,6 +6055,12 @@ export type GetV0CityByCityNameEventsResponse = GetV0CityByCityNameEventsRespons export type EmitEventData = { body: EventEmitRequest; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -4520,7 +6128,7 @@ export type StreamEventsResponses = { * Each oneOf object represents one possible SSE message. */ 200: Array<{ - data: EventStreamEnvelope; + data: TypedEventStreamEnvelope; /** * The event name. */ @@ -4554,6 +6162,12 @@ export type StreamEventsResponse = StreamEventsResponses[keyof StreamEventsRespo export type DeleteV0CityByCityNameExtmsgAdaptersData = { body: ExtMsgAdapterUnregisterInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -4614,6 +6228,12 @@ export type GetV0CityByCityNameExtmsgAdaptersResponse = GetV0CityByCityNameExtms export type RegisterExtmsgAdapterData = { body: ExtMsgAdapterRegisterInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -4644,6 +6264,12 @@ export type RegisterExtmsgAdapterResponse = RegisterExtmsgAdapterResponses[keyof export type PostV0CityByCityNameExtmsgBindData = { body: ExtMsgBindInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -4760,6 +6386,12 @@ export type GetV0CityByCityNameExtmsgGroupsResponse = GetV0CityByCityNameExtmsgG export type EnsureExtmsgGroupData = { body: ExtMsgGroupEnsureInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -4790,6 +6422,12 @@ export type EnsureExtmsgGroupResponse = EnsureExtmsgGroupResponses[keyof EnsureE export type PostV0CityByCityNameExtmsgInboundData = { body: ExtMsgInboundInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -4820,6 +6458,12 @@ export type PostV0CityByCityNameExtmsgInboundResponse = PostV0CityByCityNameExtm export type PostV0CityByCityNameExtmsgOutboundData = { body: ExtMsgOutboundInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -4850,6 +6494,12 @@ export type PostV0CityByCityNameExtmsgOutboundResponse = PostV0CityByCityNameExt export type DeleteV0CityByCityNameExtmsgParticipantsData = { body: ExtMsgParticipantRemoveInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -4880,6 +6530,12 @@ export type DeleteV0CityByCityNameExtmsgParticipantsResponse = DeleteV0CityByCit export type PostV0CityByCityNameExtmsgParticipantsData = { body: ExtMsgParticipantUpsertInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -4965,6 +6621,12 @@ export type GetV0CityByCityNameExtmsgTranscriptResponse = GetV0CityByCityNameExt export type PostV0CityByCityNameExtmsgTranscriptAckData = { body: ExtMsgTranscriptAckInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -4995,6 +6657,12 @@ export type PostV0CityByCityNameExtmsgTranscriptAckResponse = PostV0CityByCityNa export type PostV0CityByCityNameExtmsgUnbindData = { body: ExtMsgUnbindInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -5201,6 +6869,12 @@ export type GetV0CityByCityNameFormulasByNameResponse = GetV0CityByCityNameFormu export type PostV0CityByCityNameFormulasByNamePreviewData = { body: FormulaPreviewBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -5371,7 +7045,11 @@ export type GetV0CityByCityNameMailResponse = GetV0CityByCityNameMailResponses[k export type SendMailData = { body: MailSendInputBody; - headers?: { + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; /** * Idempotency key for safe retries. */ @@ -5485,6 +7163,12 @@ export type GetV0CityByCityNameMailThreadByIdResponse = GetV0CityByCityNameMailT export type DeleteV0CityByCityNameMailByIdData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -5563,6 +7247,12 @@ export type GetV0CityByCityNameMailByIdResponse = GetV0CityByCityNameMailByIdRes export type PostV0CityByCityNameMailByIdArchiveData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -5602,6 +7292,12 @@ export type PostV0CityByCityNameMailByIdArchiveResponse = PostV0CityByCityNameMa export type PostV0CityByCityNameMailByIdMarkUnreadData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -5641,6 +7337,12 @@ export type PostV0CityByCityNameMailByIdMarkUnreadResponse = PostV0CityByCityNam export type PostV0CityByCityNameMailByIdReadData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -5680,6 +7382,12 @@ export type PostV0CityByCityNameMailByIdReadResponse = PostV0CityByCityNameMailB export type ReplyMailData = { body: MailReplyInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -5792,6 +7500,12 @@ export type GetV0CityByCityNameOrderByNameResponse = GetV0CityByCityNameOrderByN export type PostV0CityByCityNameOrderByNameDisableData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -5826,6 +7540,12 @@ export type PostV0CityByCityNameOrderByNameDisableResponse = PostV0CityByCityNam export type PostV0CityByCityNameOrderByNameEnableData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -6036,6 +7756,12 @@ export type GetV0CityByCityNamePacksResponse = GetV0CityByCityNamePacksResponses export type DeleteV0CityByCityNamePatchesAgentByBaseData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -6104,6 +7830,12 @@ export type GetV0CityByCityNamePatchesAgentByBaseResponse = GetV0CityByCityNameP export type DeleteV0CityByCityNamePatchesAgentByDirByBaseData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -6210,6 +7942,12 @@ export type GetV0CityByCityNamePatchesAgentsResponse = GetV0CityByCityNamePatche export type PutV0CityByCityNamePatchesAgentsData = { body: AgentPatchSetInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -6240,6 +7978,12 @@ export type PutV0CityByCityNamePatchesAgentsResponse = PutV0CityByCityNamePatche export type DeleteV0CityByCityNamePatchesProviderByNameData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -6338,6 +8082,12 @@ export type GetV0CityByCityNamePatchesProvidersResponse = GetV0CityByCityNamePat export type PutV0CityByCityNamePatchesProvidersData = { body: ProviderPatchSetInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -6368,6 +8118,12 @@ export type PutV0CityByCityNamePatchesProvidersResponse = PutV0CityByCityNamePat export type DeleteV0CityByCityNamePatchesRigByNameData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -6466,6 +8222,12 @@ export type GetV0CityByCityNamePatchesRigsResponse = GetV0CityByCityNamePatchesR export type PutV0CityByCityNamePatchesRigsData = { body: RigPatchSetInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -6535,6 +8297,12 @@ export type GetV0CityByCityNameProviderReadinessResponse = GetV0CityByCityNamePr export type DeleteV0CityByCityNameProviderByNameData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -6603,6 +8371,12 @@ export type GetV0CityByCityNameProviderByNameResponse = GetV0CityByCityNameProvi export type PatchV0CityByCityNameProviderByNameData = { body: ProviderUpdateInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -6667,6 +8441,12 @@ export type GetV0CityByCityNameProvidersResponse = GetV0CityByCityNameProvidersR export type CreateProviderData = { body: ProviderCreateInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -6766,6 +8546,12 @@ export type GetV0CityByCityNameReadinessResponse = GetV0CityByCityNameReadinessR export type DeleteV0CityByCityNameRigByNameData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -6839,6 +8625,12 @@ export type GetV0CityByCityNameRigByNameResponse = GetV0CityByCityNameRigByNameR export type PatchV0CityByCityNameRigByNameData = { body: RigUpdateInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -6873,6 +8665,12 @@ export type PatchV0CityByCityNameRigByNameResponse = PatchV0CityByCityNameRigByN export type PostV0CityByCityNameRigByNameByActionData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -6954,6 +8752,12 @@ export type GetV0CityByCityNameRigsResponse = GetV0CityByCityNameRigsResponses[k export type CreateRigData = { body: RigCreateInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -7018,6 +8822,12 @@ export type GetV0CityByCityNameServiceByNameResponse = GetV0CityByCityNameServic export type PostV0CityByCityNameServiceByNameRestartData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -7121,6 +8931,12 @@ export type GetV0CityByCityNameSessionByIdResponse = GetV0CityByCityNameSessionB export type PatchV0CityByCityNameSessionByIdData = { body: SessionPatchBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -7227,6 +9043,12 @@ export type GetV0CityByCityNameSessionByIdAgentsByAgentIdResponse = GetV0CityByC export type PostV0CityByCityNameSessionByIdCloseData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -7266,6 +9088,12 @@ export type PostV0CityByCityNameSessionByIdCloseResponse = PostV0CityByCityNameS export type PostV0CityByCityNameSessionByIdKillData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -7300,6 +9128,12 @@ export type PostV0CityByCityNameSessionByIdKillResponse = PostV0CityByCityNameSe export type SendSessionMessageData = { body: SessionMessageInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -7368,6 +9202,12 @@ export type GetV0CityByCityNameSessionByIdPendingResponse = GetV0CityByCityNameS export type PostV0CityByCityNameSessionByIdRenameData = { body: SessionRenameInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -7402,6 +9242,12 @@ export type PostV0CityByCityNameSessionByIdRenameResponse = PostV0CityByCityName export type RespondSessionData = { body: SessionRespondInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -7436,6 +9282,12 @@ export type RespondSessionResponse = RespondSessionResponses[keyof RespondSessio export type PostV0CityByCityNameSessionByIdStopData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -7581,6 +9433,12 @@ export type StreamSessionResponse = StreamSessionResponses[keyof StreamSessionRe export type SubmitSessionData = { body: SessionSubmitInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -7615,6 +9473,12 @@ export type SubmitSessionResponse = SubmitSessionResponses[keyof SubmitSessionRe export type PostV0CityByCityNameSessionByIdSuspendData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -7661,7 +9525,7 @@ export type GetV0CityByCityNameSessionByIdTranscriptData = { }; query?: { /** - * Number of recent compaction segments to return. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. + * Number of recent compaction segments to return. This API parameter keeps compaction-segment semantics even though gc session logs --tail counts displayed transcript entries. Omit for the endpoint default (usually 1); 0 returns all segments; N>0 returns the last N. */ tail?: string; /** @@ -7696,6 +9560,12 @@ export type GetV0CityByCityNameSessionByIdTranscriptResponse = GetV0CityByCityNa export type PostV0CityByCityNameSessionByIdWakeData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -7781,6 +9651,12 @@ export type GetV0CityByCityNameSessionsResponse = GetV0CityByCityNameSessionsRes export type CreateSessionData = { body: SessionCreateBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -7811,6 +9687,12 @@ export type CreateSessionResponse = CreateSessionResponses[keyof CreateSessionRe export type PostV0CityByCityNameSlingData = { body: SlingInputBody; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -7878,8 +9760,50 @@ export type GetV0CityByCityNameStatusResponses = { export type GetV0CityByCityNameStatusResponse = GetV0CityByCityNameStatusResponses[keyof GetV0CityByCityNameStatusResponses]; +export type PostV0CityByCityNameUnregisterData = { + body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; + path: { + /** + * Supervisor-registered city name. + */ + cityName: string; + }; + query?: never; + url: '/v0/city/{cityName}/unregister'; +}; + +export type PostV0CityByCityNameUnregisterErrors = { + /** + * Error + */ + default: ErrorModel; +}; + +export type PostV0CityByCityNameUnregisterError = PostV0CityByCityNameUnregisterErrors[keyof PostV0CityByCityNameUnregisterErrors]; + +export type PostV0CityByCityNameUnregisterResponses = { + /** + * Accepted + */ + 202: CityUnregisterResponse; +}; + +export type PostV0CityByCityNameUnregisterResponse = PostV0CityByCityNameUnregisterResponses[keyof PostV0CityByCityNameUnregisterResponses]; + export type DeleteV0CityByCityNameWorkflowByWorkflowIdData = { body?: never; + headers: { + /** + * Anti-CSRF header required on mutation requests. Any non-empty value is accepted; the header's presence is what the server checks. + */ + 'X-GC-Request': string; + }; path: { /** * City name. @@ -8058,7 +9982,7 @@ export type StreamSupervisorEventsResponses = { */ retry?: number; } | { - data: TaggedEventStreamEnvelope; + data: TypedTaggedEventStreamEnvelope; /** * The event name. */ diff --git a/cmd/gc/providers.go b/cmd/gc/providers.go index 84298ad6c6..91320a27c4 100644 --- a/cmd/gc/providers.go +++ b/cmd/gc/providers.go @@ -71,8 +71,10 @@ func sessionProviderContextForCity(cfg *config.City, cityPath, providerOverride return ctx } -var openSessionProviderStore = openCityStoreAt -var buildSessionProviderByName = newSessionProviderByName +var ( + openSessionProviderStore = openCityStoreAt + buildSessionProviderByName = newSessionProviderByName +) // tmuxConfigFromSession converts a config.SessionConfig into a // sessiontmux.Config with resolved durations and defaults. If the @@ -213,16 +215,6 @@ func newSessionProviderFromContextWithError(ctx sessionProviderContext, sessionB return sp, nil } -// hasACPAgents reports whether any agent in the config uses session = "acp". -func hasACPAgents(agents []config.Agent) bool { - for _, a := range agents { - if strings.TrimSpace(a.Session) == "acp" { - return true - } - } - return false -} - func agentSessionCreateTransport(cfg *config.City, agentCfg config.Agent) string { if cfg == nil { return strings.TrimSpace(agentCfg.Session) diff --git a/cmd/gc/worker_handle.go b/cmd/gc/worker_handle.go index c608bd0641..400062926a 100644 --- a/cmd/gc/worker_handle.go +++ b/cmd/gc/worker_handle.go @@ -462,11 +462,11 @@ func resolvedWorkerRuntimeWithConfigAndMetadata(cityPath string, cfg *config.Cit if cfg == nil { return nil, nil } - resolved, configuredTransport, allowConfiguredTransportFallback := resolveWorkerRuntimeProviderWithConfig(cfg, info, sessionKind) + resolved, configuredTransport := resolveWorkerRuntimeProviderWithConfig(cfg, info, sessionKind) if resolved == nil { return nil, nil } - transport := resolvedWorkerRuntimeTransport(info, resolved, configuredTransport, metadata, allowConfiguredTransportFallback) + transport := resolvedWorkerRuntimeTransport(info, resolved, configuredTransport, metadata) if transport == "" && startedConfigHashProvesWorkerACPTransport(cityPath, cfg, info, sessionKind, resolved, metadata, configuredTransport) { transport = "acp" } @@ -508,14 +508,14 @@ func resolvedWorkerRuntimeWithConfigAndMetadata(cityPath string, cfg *config.Cit func resolvedWorkerRuntimeCommandForTransport(cityPath string, resolved *config.ResolvedProvider, transport, storedCommand, fallbackProvider string, metadata map[string]string) string { command := strings.TrimSpace(storedCommand) - desiredCommand := fallbackResolvedWorkerRuntimeCommand(resolved, transport, command) + configuredCommand := configuredWorkerRuntimeCommand(resolved, transport) + if configuredCommand == "" { + return firstNonEmptyGCString(command, fallbackProvider, resolved.Name) + } + desiredCommand := configuredCommand if optionOverrides, err := session.ParseTemplateOverrides(metadata); err == nil { if launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, optionOverrides, transport); err == nil { - resolvedCommand := resolved.CommandString() - if transport == "acp" { - resolvedCommand = resolved.ACPCommandString() - } - desiredCommand = firstNonEmptyGCString(launchCommand.Command, resolvedCommand, resolved.Name) + desiredCommand = firstNonEmptyGCString(launchCommand.Command, configuredCommand, resolved.Name) if shouldPreserveStoredRuntimeCommandForTransport(command, desiredCommand, transport, optionOverrides) { desiredCommand = command } @@ -527,6 +527,19 @@ func resolvedWorkerRuntimeCommandForTransport(cityPath string, resolved *config. return firstNonEmptyGCString(command, fallbackProvider, resolved.Name) } +func configuredWorkerRuntimeCommand(resolved *config.ResolvedProvider, transport string) string { + if resolved == nil { + return "" + } + if transport == "acp" && (strings.TrimSpace(resolved.ACPCommand) != "" || resolved.ACPArgs != nil) { + return strings.TrimSpace(resolved.ACPCommandString()) + } + if strings.TrimSpace(resolved.Command) != "" { + return strings.TrimSpace(resolved.CommandString()) + } + return "" +} + func shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand string) bool { storedCommand = strings.TrimSpace(storedCommand) if storedCommand == "" { @@ -548,10 +561,13 @@ func shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand string) b return strings.HasPrefix(storedCommand, resolvedCommand+" ") } -func shouldPreserveStoredRuntimeCommandForTransport(storedCommand, resolvedCommand, transport string, optionOverrides map[string]string) bool { +func shouldPreserveStoredRuntimeCommandForTransport(storedCommand, resolvedCommand, _ string, optionOverrides map[string]string) bool { if shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand) { return true } + if len(optionOverrides) == 0 && storedCommandHasSettingsArg(storedCommand) && sameRuntimeCommandExecutable(storedCommand, resolvedCommand) { + return true + } return false } @@ -564,15 +580,8 @@ func sameRuntimeCommandExecutable(storedCommand, resolvedCommand string) bool { return storedFields[0] == resolvedFields[0] } -func fallbackResolvedWorkerRuntimeCommand(resolved *config.ResolvedProvider, transport, storedCommand string) string { - resolvedCommand := "" - if resolved != nil { - resolvedCommand = resolved.CommandString() - if transport == "acp" { - resolvedCommand = resolved.ACPCommandString() - } - } - return firstNonEmptyGCString(storedCommand, resolvedCommand, resolved.Name) +func storedCommandHasSettingsArg(command string) bool { + return strings.Contains(" "+strings.TrimSpace(command)+" ", " --settings ") } func storedWorkerSessionProvesACPTransport(resolved *config.ResolvedProvider, configuredTransport, storedCommand string, metadata map[string]string) bool { @@ -625,7 +634,7 @@ func startedConfigHashProvesWorkerACPTransport( cityPath string, cfg *config.City, info session.Info, - sessionKind string, + _ string, resolved *config.ResolvedProvider, metadata map[string]string, configuredTransport string, @@ -667,7 +676,7 @@ func startedConfigHashProvesWorkerACPTransport( return startedHash == acpHash } -func resolvedWorkerRuntimeTransport(info session.Info, resolved *config.ResolvedProvider, configuredTransport string, metadata map[string]string, allowConfiguredTransportFallback bool) string { +func resolvedWorkerRuntimeTransport(info session.Info, resolved *config.ResolvedProvider, configuredTransport string, metadata map[string]string) string { if transport := strings.TrimSpace(info.Transport); transport != "" { return transport } @@ -677,37 +686,28 @@ func resolvedWorkerRuntimeTransport(info session.Info, resolved *config.Resolved if storedWorkerSessionProvesACPTransport(resolved, configuredTransport, info.Command, metadata) { return "acp" } - if allowConfiguredTransportFallback { + if strings.TrimSpace(info.Command) == "" { return strings.TrimSpace(configuredTransport) } return "" } -func firstNonEmptyWorkerString(values ...string) string { - for _, value := range values { - if trimmed := strings.TrimSpace(value); trimmed != "" { - return trimmed - } - } - return "" -} - -func resolveWorkerRuntimeProviderWithConfig(cfg *config.City, info session.Info, sessionKind string) (*config.ResolvedProvider, string, bool) { +func resolveWorkerRuntimeProviderWithConfig(cfg *config.City, info session.Info, sessionKind string) (*config.ResolvedProvider, string) { if cfg == nil { - return nil, "", false + return nil, "" } if sessionKind != "provider" { if found, ok := resolveAgentIdentity(cfg, info.Template, ""); ok { if resolved, err := config.ResolveProvider(&found, &cfg.Workspace, cfg.Providers, exec.LookPath); err == nil { - return resolved, config.ResolveSessionCreateTransport(found.Session, resolved), false + return resolved, config.ResolveSessionCreateTransport(found.Session, resolved) } } } resolved, err := config.ResolveProvider(&config.Agent{Provider: info.Template}, &cfg.Workspace, cfg.Providers, exec.LookPath) if err != nil { - return nil, "", false + return nil, "" } - return resolved, strings.TrimSpace(resolved.ProviderSessionCreateTransport()), false + return resolved, strings.TrimSpace(resolved.ProviderSessionCreateTransport()) } func workerDeliveryIntentForSubmitIntent(intent session.SubmitIntent) worker.DeliveryIntent { diff --git a/cmd/gc/worker_handle_test.go b/cmd/gc/worker_handle_test.go index 9bd86b609d..79b916c5e5 100644 --- a/cmd/gc/worker_handle_test.go +++ b/cmd/gc/worker_handle_test.go @@ -323,7 +323,7 @@ func TestResolvedWorkerRuntimeTransportUsesResumeMetadataForLegacyACPWithSameCom Command: "/bin/echo", }, resolved, "acp", map[string]string{ "resume_flag": "--resume", - }, false) + }) if got != "acp" { t.Fatalf("resolvedWorkerRuntimeTransport() = %q, want acp", got) } @@ -400,7 +400,7 @@ args = ["{{.AgentName}}"] Provider: "custom-acp", WorkDir: cityDir, } - resolved, _, _ := resolveWorkerRuntimeProviderWithConfig(cfg, info, "provider") + resolved, _ := resolveWorkerRuntimeProviderWithConfig(cfg, info, "provider") mcpServers, err := resolvedRuntimeMCPServersWithConfig( cityDir, cfg, diff --git a/docs/reference/config.md b/docs/reference/config.md index cc952139d0..f4b92800a6 100644 --- a/docs/reference/config.md +++ b/docs/reference/config.md @@ -434,7 +434,9 @@ ProviderPatch modifies an existing provider identified by Name. | `name` | string | **yes** | | Name is the targeting key (required). Must match an existing provider's name. | | `base` | string | | | Base overrides the provider's inheritance parent (presence-aware). Pointer to a pointer so the patch can distinguish "no change" (double-nil) from "clear to inherit default" (single-nil value in outer pointer) from "set to explicit empty opt-out" (value "" in inner pointer) from "set to <name>". Callers use: nil = patch does not touch Base &(*string)(nil) = patch clears Base to absent &(&"") = patch sets Base = "" (explicit opt-out) &(&"builtin:codex") = patch sets Base to that value | | `command` | string | | | Command overrides the provider command. | +| `acp_command` | string | | | ACPCommand overrides the provider command for ACP transport sessions. | | `args` | []string | | | Args overrides the provider args. | +| `acp_args` | []string | | | ACPArgs overrides the provider args for ACP transport sessions. | | `args_append` | []string | | | ArgsAppend overrides the provider args_append list. | | `options_schema_merge` | string | | | OptionsSchemaMerge overrides the options_schema merge mode. | | `prompt_mode` | string | | | PromptMode overrides prompt delivery mode. Enum: `arg`, `flag`, `none` | diff --git a/docs/schema/city-schema.json b/docs/schema/city-schema.json index 8a6e252173..cdb08ca4a9 100644 --- a/docs/schema/city-schema.json +++ b/docs/schema/city-schema.json @@ -1491,6 +1491,10 @@ "type": "string", "description": "Command overrides the provider command." }, + "acp_command": { + "type": "string", + "description": "ACPCommand overrides the provider command for ACP transport sessions." + }, "args": { "items": { "type": "string" @@ -1498,6 +1502,13 @@ "type": "array", "description": "Args overrides the provider args." }, + "acp_args": { + "items": { + "type": "string" + }, + "type": "array", + "description": "ACPArgs overrides the provider args for ACP transport sessions." + }, "args_append": { "items": { "type": "string" diff --git a/docs/schema/city-schema.txt b/docs/schema/city-schema.txt index 8a6e252173..cdb08ca4a9 100644 --- a/docs/schema/city-schema.txt +++ b/docs/schema/city-schema.txt @@ -1491,6 +1491,10 @@ "type": "string", "description": "Command overrides the provider command." }, + "acp_command": { + "type": "string", + "description": "ACPCommand overrides the provider command for ACP transport sessions." + }, "args": { "items": { "type": "string" @@ -1498,6 +1502,13 @@ "type": "array", "description": "Args overrides the provider args." }, + "acp_args": { + "items": { + "type": "string" + }, + "type": "array", + "description": "ACPArgs overrides the provider args for ACP transport sessions." + }, "args_append": { "items": { "type": "string" diff --git a/internal/api/handler_session_create.go b/internal/api/handler_session_create.go index a86976f56a..d71090d650 100644 --- a/internal/api/handler_session_create.go +++ b/internal/api/handler_session_create.go @@ -77,7 +77,7 @@ func (s *Server) handleSessionCreate(w http.ResponseWriter, r *http.Request) { switch kind { case "agent": var err error - resolved, workDir, transport, template, err = s.resolveSessionTemplateForCreate(name) + resolved, _, transport, template, err = s.resolveSessionTemplateForCreate(name) if err != nil { if errors.Is(err, errSessionTemplateNotFound) { s.idem.unreserve(idemKey) diff --git a/internal/api/session_runtime.go b/internal/api/session_runtime.go index 2939add067..d13088c191 100644 --- a/internal/api/session_runtime.go +++ b/internal/api/session_runtime.go @@ -209,6 +209,7 @@ func (s *Server) resolveSessionTemplateForCreate(template string) (*config.Resol return resolved, workDir, config.ResolveSessionCreateTransport(agentCfg.Session, resolved), agentCfg.QualifiedName(), nil } +//nolint:unparam // kept as a focused test helper even though current call sites use one template shape. func (s *Server) resolveSessionTemplate(template string) (*config.ResolvedProvider, string, string, string, error) { cfg := s.state.Config() if cfg == nil { @@ -247,7 +248,7 @@ func (s *Server) buildSessionResume(info session.Info) (string, runtime.Config, if command, err := s.resolvedSessionRuntimeCommand(resolved, transport, info.Command, metadata); err == nil { resolvedInfo.Command = command } else { - resolvedInfo.Command = fallbackSessionRuntimeCommand(resolved, transport, info.Command) + resolvedInfo.Command = fallbackSessionRuntimeCommand(resolved, transport, info.Command, info.Provider) } resolvedInfo.Provider = resolved.Name resolvedInfo.Transport = transport @@ -258,6 +259,13 @@ func (s *Server) buildSessionResume(info session.Info) (string, runtime.Config, } func (s *Server) resolvedSessionRuntimeCommand(resolved *config.ResolvedProvider, transport, storedCommand string, metadata map[string]string) (string, error) { + configuredCommand := configuredSessionRuntimeCommand(resolved, transport) + if configuredCommand == "" { + if command := strings.TrimSpace(storedCommand); command != "" { + return command, nil + } + return "", fmt.Errorf("resolved provider %q has no launch command", resolved.Name) + } optionOverrides, err := session.ParseTemplateOverrides(metadata) if err != nil { return "", fmt.Errorf("parsing template overrides: %w", err) @@ -266,26 +274,29 @@ func (s *Server) resolvedSessionRuntimeCommand(resolved *config.ResolvedProvider if err != nil { return "", fmt.Errorf("building provider launch command: %w", err) } - resolvedCommand := resolved.CommandString() - if transport == "acp" { - resolvedCommand = resolved.ACPCommandString() - } - desiredCommand := firstNonEmptyString(launchCommand.Command, resolvedCommand, resolved.Name) + desiredCommand := firstNonEmptyString(launchCommand.Command, configuredCommand, resolved.Name) if command := strings.TrimSpace(storedCommand); shouldPreserveStoredRuntimeCommandForTransport(command, desiredCommand, transport, optionOverrides) { return command, nil } return desiredCommand, nil } -func fallbackSessionRuntimeCommand(resolved *config.ResolvedProvider, transport, storedCommand string) string { - resolvedCommand := "" - if resolved != nil { - resolvedCommand = resolved.CommandString() - if transport == "acp" { - resolvedCommand = resolved.ACPCommandString() - } +func configuredSessionRuntimeCommand(resolved *config.ResolvedProvider, transport string) string { + if resolved == nil { + return "" + } + if transport == "acp" && (strings.TrimSpace(resolved.ACPCommand) != "" || resolved.ACPArgs != nil) { + return strings.TrimSpace(resolved.ACPCommandString()) } - return firstNonEmptyString(storedCommand, resolvedCommand, resolved.Name) + if strings.TrimSpace(resolved.Command) != "" { + return strings.TrimSpace(resolved.CommandString()) + } + return "" +} + +func fallbackSessionRuntimeCommand(resolved *config.ResolvedProvider, transport, storedCommand, fallbackProvider string) string { + resolvedCommand := configuredSessionRuntimeCommand(resolved, transport) + return firstNonEmptyString(storedCommand, resolvedCommand, fallbackProvider, resolved.Name) } func shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand string) bool { @@ -309,10 +320,13 @@ func shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand string) b return strings.HasPrefix(storedCommand, resolvedCommand+" ") } -func shouldPreserveStoredRuntimeCommandForTransport(storedCommand, resolvedCommand, transport string, optionOverrides map[string]string) bool { +func shouldPreserveStoredRuntimeCommandForTransport(storedCommand, resolvedCommand, _ string, optionOverrides map[string]string) bool { if shouldPreserveStoredRuntimeCommand(storedCommand, resolvedCommand) { return true } + if len(optionOverrides) == 0 && storedCommandHasSettingsArg(storedCommand) && sameRuntimeCommandExecutable(storedCommand, resolvedCommand) { + return true + } return false } @@ -325,8 +339,12 @@ func sameRuntimeCommandExecutable(storedCommand, resolvedCommand string) bool { return storedFields[0] == resolvedFields[0] } -func (s *Server) resolveWorkerSessionRuntime(info session.Info, sessionKind string) (*worker.ResolvedRuntime, error) { - return s.resolveWorkerSessionRuntimeWithMetadata(info, sessionKind, nil) +func storedCommandHasSettingsArg(command string) bool { + return strings.Contains(" "+strings.TrimSpace(command)+" ", " --settings ") +} + +func (s *Server) resolveWorkerSessionRuntime(info session.Info) (*worker.ResolvedRuntime, error) { + return s.resolveWorkerSessionRuntimeWithMetadata(info, "", nil) } func (s *Server) resolveWorkerSessionRuntimeWithMetadata(info session.Info, _ string, metadata map[string]string) (*worker.ResolvedRuntime, error) { @@ -346,7 +364,7 @@ func (s *Server) resolveWorkerSessionRuntimeWithMetadata(info session.Info, _ st } command, err := s.resolvedSessionRuntimeCommand(resolved, transport, info.Command, metadata) if err != nil { - command = fallbackSessionRuntimeCommand(resolved, transport, info.Command) + command = fallbackSessionRuntimeCommand(resolved, transport, info.Command, info.Provider) } runtimeCfg, err := worker.NormalizeResolvedRuntime(worker.ResolvedRuntime{ Command: command, @@ -430,11 +448,11 @@ func (s *Server) startedConfigHashProvesACPTransport( } acpCommand, err := s.resolvedSessionRuntimeCommand(resolved, "acp", info.Command, metadata) if err != nil { - acpCommand = fallbackSessionRuntimeCommand(resolved, "acp", info.Command) + acpCommand = fallbackSessionRuntimeCommand(resolved, "acp", info.Command, info.Provider) } defaultCommand, err := s.resolvedSessionRuntimeCommand(resolved, "", info.Command, metadata) if err != nil { - defaultCommand = fallbackSessionRuntimeCommand(resolved, "", info.Command) + defaultCommand = fallbackSessionRuntimeCommand(resolved, "", info.Command, info.Provider) } mcpServers, err := s.sessionMCPServers( info.Template, @@ -472,6 +490,9 @@ func resolvedSessionTransport(info session.Info, resolved *config.ResolvedProvid if storedSessionProvesACPTransport(resolved, configuredTransport, info.Command, metadata) { return "acp" } + if strings.TrimSpace(info.Command) == "" { + return strings.TrimSpace(configuredTransport) + } if allowConfiguredTransportFallback { return strings.TrimSpace(configuredTransport) } diff --git a/internal/api/worker_factory_test.go b/internal/api/worker_factory_test.go index 55cdd5a1c3..b05b3797ed 100644 --- a/internal/api/worker_factory_test.go +++ b/internal/api/worker_factory_test.go @@ -39,7 +39,7 @@ func TestResolveWorkerSessionRuntimePreservesStoredResolvedCommandAndBackfillsCu ResumeCommand: "persisted resume {{.SessionKey}}", } - runtimeCfg, err := srv.resolveWorkerSessionRuntime(info, "") + runtimeCfg, err := srv.resolveWorkerSessionRuntime(info) if err != nil { t.Fatalf("resolveWorkerSessionRuntime: %v", err) } @@ -101,7 +101,7 @@ func TestResolveWorkerSessionRuntimeUsesResolvedCommandWhenPersistedCommandIsSta ResumeCommand: "persisted resume {{.SessionKey}}", } - runtimeCfg, err := srv.resolveWorkerSessionRuntime(info, "") + runtimeCfg, err := srv.resolveWorkerSessionRuntime(info) if err != nil { t.Fatalf("resolveWorkerSessionRuntime: %v", err) } @@ -169,7 +169,7 @@ args = ["--stdio"] WorkDir: t.TempDir(), } - runtimeCfg, err := srv.resolveWorkerSessionRuntime(info, "") + runtimeCfg, err := srv.resolveWorkerSessionRuntime(info) if err != nil { t.Fatalf("resolveWorkerSessionRuntime: %v", err) } @@ -226,7 +226,7 @@ args = ["{{.AgentName}}", "{{.WorkDir}}", "{{.TemplateName}}"] WorkDir: workDir, } - runtimeCfg, err := srv.resolveWorkerSessionRuntime(info, "") + runtimeCfg, err := srv.resolveWorkerSessionRuntime(info) if err != nil { t.Fatalf("resolveWorkerSessionRuntime: %v", err) } @@ -511,6 +511,114 @@ func TestResolveWorkerSessionRuntimeFallsBackToStoredCommandWhenTemplateOverride } } +func TestResolveWorkerSessionRuntimeUsesProviderACPDefaultWithoutTemplateSessionOverride(t *testing.T) { + supportsACP := true + fs := newSessionFakeState(t) + fs.cfg.Providers["test-agent"] = config.ProviderSpec{ + Command: "/bin/echo", + PathCheck: "true", + SupportsACP: &supportsACP, + ACPCommand: "/bin/echo", + ACPArgs: []string{"acp"}, + } + + srv := New(fs) + runtimeCfg, err := srv.resolveWorkerSessionRuntimeWithMetadata(session.Info{ + Template: "myrig/worker", + WorkDir: t.TempDir(), + }, "", nil) + if err != nil { + t.Fatalf("resolveWorkerSessionRuntimeWithMetadata: %v", err) + } + if runtimeCfg == nil { + t.Fatal("resolveWorkerSessionRuntimeWithMetadata() = nil") + } + if got, want := runtimeCfg.Command, "/bin/echo acp"; got != want { + t.Fatalf("Command = %q, want %q", got, want) + } +} + +func TestResolveWorkerSessionRuntimeFallsBackToPersistedRuntimeOnIncompleteResolvedConfig(t *testing.T) { + fs := newSessionFakeState(t) + fs.cfg.Providers["test-agent"] = config.ProviderSpec{ + ReadyPromptPrefix: "resolved-ready>", + ReadyDelayMs: 321, + } + + srv := New(fs) + info := session.Info{ + Template: "myrig/worker", + Command: "persisted-worker --dangerously-skip-permissions", + Provider: "persisted-provider", + WorkDir: "/tmp/persisted-workdir", + ResumeFlag: "--resume-persisted", + ResumeStyle: "subcommand", + ResumeCommand: "persisted resume {{.SessionKey}}", + } + + runtimeCfg, err := srv.resolveWorkerSessionRuntimeWithMetadata(info, "", nil) + if err != nil { + t.Fatalf("resolveWorkerSessionRuntimeWithMetadata: %v", err) + } + if runtimeCfg == nil { + t.Fatal("resolveWorkerSessionRuntimeWithMetadata() = nil") + } + if got, want := runtimeCfg.Command, info.Command; got != want { + t.Fatalf("Command = %q, want %q", got, want) + } + if got, want := runtimeCfg.Provider, info.Provider; got != want { + t.Fatalf("Provider = %q, want %q", got, want) + } + if got, want := runtimeCfg.WorkDir, info.WorkDir; got != want { + t.Fatalf("WorkDir = %q, want %q", got, want) + } + if got, want := runtimeCfg.Resume.ResumeFlag, info.ResumeFlag; got != want { + t.Fatalf("Resume.ResumeFlag = %q, want %q", got, want) + } + if got, want := runtimeCfg.Resume.ResumeStyle, info.ResumeStyle; got != want { + t.Fatalf("Resume.ResumeStyle = %q, want %q", got, want) + } + if got, want := runtimeCfg.Resume.ResumeCommand, info.ResumeCommand; got != want { + t.Fatalf("Resume.ResumeCommand = %q, want %q", got, want) + } + if got, want := runtimeCfg.Hints.WorkDir, info.WorkDir; got != want { + t.Fatalf("Hints.WorkDir = %q, want %q", got, want) + } + if got, want := runtimeCfg.Hints.ReadyPromptPrefix, "resolved-ready>"; got != want { + t.Fatalf("Hints.ReadyPromptPrefix = %q, want %q", got, want) + } + if got, want := runtimeCfg.Hints.ReadyDelayMs, 321; got != want { + t.Fatalf("Hints.ReadyDelayMs = %d, want %d", got, want) + } +} + +func TestResolveWorkerSessionRuntimeFallsBackToPersistedProviderWhenCommandMissing(t *testing.T) { + fs := newSessionFakeState(t) + fs.cfg.Providers["test-agent"] = config.ProviderSpec{ + ReadyPromptPrefix: "resolved-ready>", + } + + srv := New(fs) + info := session.Info{ + Template: "myrig/worker", + Provider: "persisted-provider", + } + + runtimeCfg, err := srv.resolveWorkerSessionRuntimeWithMetadata(info, "", nil) + if err != nil { + t.Fatalf("resolveWorkerSessionRuntimeWithMetadata: %v", err) + } + if runtimeCfg == nil { + t.Fatal("resolveWorkerSessionRuntimeWithMetadata() = nil") + } + if got, want := runtimeCfg.Command, info.Provider; got != want { + t.Fatalf("Command = %q, want %q", got, want) + } + if got, want := runtimeCfg.Provider, info.Provider; got != want { + t.Fatalf("Provider = %q, want %q", got, want) + } +} + func TestWorkerFactorySessionByIDUsesResolvedTemplateRuntime(t *testing.T) { fs := newSessionFakeState(t) fs.cfg.Agents[0].Provider = "resolved-worker" diff --git a/internal/runtime/fingerprint.go b/internal/runtime/fingerprint.go index 095adb0e4d..370486273f 100644 --- a/internal/runtime/fingerprint.go +++ b/internal/runtime/fingerprint.go @@ -235,7 +235,7 @@ func hashMCPServers(h hash.Hash, servers []MCPServerConfig) { } h.Write([]byte{1}) //nolint:errcheck // sentinel between args/env hashSortedMap(h, server.Env) - h.Write([]byte{1}) //nolint:errcheck // sentinel between env/url + h.Write([]byte{1}) //nolint:errcheck // sentinel between env/url h.Write([]byte(server.URL)) //nolint:errcheck // hash.Write never errors h.Write([]byte{0}) //nolint:errcheck // hash.Write never errors hashSortedMap(h, server.Headers) diff --git a/internal/session/manager_test.go b/internal/session/manager_test.go index 6534ba562f..d88ea525ca 100644 --- a/internal/session/manager_test.go +++ b/internal/session/manager_test.go @@ -2140,7 +2140,7 @@ func TestSendBackfillsTransportForLegacyACPSession(t *testing.T) { t.Fatalf("Start ACP session: %v", err) } - mgr := NewManagerWithTransportResolver(store, autoSP, func(template, provider string) string { + mgr := NewManagerWithTransportResolver(store, autoSP, func(template, _ string) string { if template == "helper" { return "acp" } @@ -2198,7 +2198,7 @@ func TestGetDoesNotPersistGuessedTransportForLegacySession(t *testing.T) { t.Fatalf("Create legacy bead: %v", err) } - mgr := NewManagerWithTransportResolver(store, autoSP, func(template, provider string) string { + mgr := NewManagerWithTransportResolver(store, autoSP, func(template, _ string) string { if template == "helper" { return "acp" } @@ -2241,7 +2241,7 @@ func TestGetUsesConfiguredTransportForPendingCreateWithoutRuntimeProbe(t *testin t.Fatalf("Create deferred bead: %v", err) } - mgr := NewManagerWithTransportResolver(store, sp, func(template, provider string) string { + mgr := NewManagerWithTransportResolver(store, sp, func(template, _ string) string { if template == "helper" { return "acp" } @@ -2292,7 +2292,7 @@ func TestGetPrefersLiveTransportDetectionOverConfiguredTransportInference(t *tes t.Fatalf("Start default session: %v", err) } - mgr := NewManagerWithTransportResolver(store, autoSP, func(template, provider string) string { + mgr := NewManagerWithTransportResolver(store, autoSP, func(template, _ string) string { if template == "helper" { return "acp" } @@ -2345,7 +2345,7 @@ func TestGetDoesNotInferConfiguredTransportForStoppedLegacySession(t *testing.T) t.Fatalf("SetMetadata(session_name): %v", err) } - mgr := NewManagerWithTransportResolver(store, autoSP, func(template, provider string) string { + mgr := NewManagerWithTransportResolver(store, autoSP, func(template, _ string) string { if template == "helper" { return "acp" } @@ -2398,7 +2398,7 @@ func TestGetDoesNotInferConfiguredTransportForStoppedLegacySessionWithPolicyFall t.Fatalf("SetMetadata(session_name): %v", err) } - mgr := NewManagerWithTransportPolicyResolverAndCityPath(store, autoSP, "", func(template, provider string) (string, bool) { + mgr := NewManagerWithTransportPolicyResolverAndCityPath(store, autoSP, "", func(template, _ string) (string, bool) { if template == "helper" { return "acp", true }