diff --git a/ProwlCLI/Commands/AgentsSignalCommand.swift b/ProwlCLI/Commands/AgentsSignalCommand.swift index 642a3dea..293c8d70 100644 --- a/ProwlCLI/Commands/AgentsSignalCommand.swift +++ b/ProwlCLI/Commands/AgentsSignalCommand.swift @@ -22,7 +22,7 @@ struct AgentsSignalCommand: ParsableCommand { @Option(name: .customLong("session"), help: "Opaque agent session identifier.") var sessionID: String? - @Option(name: .long, help: "Short result or reason returned with the signal (maximum 4096 UTF-8 bytes).") + @Option(name: .long, help: "Short result or reason returned with the signal (maximum 32768 UTF-8 bytes).") var detail: String? @OptionGroup var options: GlobalOptions diff --git a/ProwlCLIContracts/Resources/cli-output-schema.json b/ProwlCLIContracts/Resources/cli-output-schema.json index 6063d514..5d2361fe 100644 --- a/ProwlCLIContracts/Resources/cli-output-schema.json +++ b/ProwlCLIContracts/Resources/cli-output-schema.json @@ -745,7 +745,7 @@ "detail": { "type": "string", "minLength": 1, - "maxLength": 4096 + "maxLength": 32768 }, "claimed_origin": { "type": "string", diff --git a/ProwlCLITests/AgentsCommandParsingTests.swift b/ProwlCLITests/AgentsCommandParsingTests.swift index e704ada8..fb4770ab 100644 --- a/ProwlCLITests/AgentsCommandParsingTests.swift +++ b/ProwlCLITests/AgentsCommandParsingTests.swift @@ -55,6 +55,17 @@ final class AgentsCommandParsingTests: XCTestCase { XCTAssertThrowsError(try AgentsSignalCommand.parse(["complete"])) XCTAssertThrowsError(try AgentsSignalCommand.parse(["turn-ended", "--progress", "1"])) XCTAssertThrowsError(try AgentsSignalCommand.parse(["progress", "--progress", "101"])) - XCTAssertThrowsError(try AgentsSignalCommand.parse(["needs-input", "--detail", String(repeating: "x", count: 4_097)])) + XCTAssertNoThrow( + try AgentsSignalCommand.parse(["needs-input", "--detail", String(repeating: "x", count: 32_768)]) + ) + XCTAssertNoThrow( + try AgentsSignalCommand.parse(["needs-input", "--detail", String(repeating: "界", count: 10_922)]) + ) + XCTAssertThrowsError( + try AgentsSignalCommand.parse(["needs-input", "--detail", String(repeating: "x", count: 32_769)]) + ) + XCTAssertThrowsError( + try AgentsSignalCommand.parse(["needs-input", "--detail", String(repeating: "界", count: 10_923)]) + ) } } diff --git a/docs-ai/013-prowl-cli/contracts/agents-signal.md b/docs-ai/013-prowl-cli/contracts/agents-signal.md index 73167d02..57205e9b 100644 --- a/docs-ai/013-prowl-cli/contracts/agents-signal.md +++ b/docs-ai/013-prowl-cli/contracts/agents-signal.md @@ -33,7 +33,7 @@ producer's business judgment authoritative. `--origin` is caller-authored metada cannot upgrade source/confidence or satisfy a future native-hook capability check. `--session` and `--origin` are non-empty, control-free UTF-8 up to 256 bytes. `--detail` -is non-empty, control-free UTF-8 up to 4096 bytes. Detail is a short result or reason returned +is non-empty, control-free UTF-8 up to 32768 bytes. Detail is a short result or reason returned with the signal; it is not logged and does not change confidence. Large results use `agents read`; workflow outputs use `workflow done -`. diff --git a/docs-ai/013-prowl-cli/contracts/input.md b/docs-ai/013-prowl-cli/contracts/input.md index 2b7fbba7..665d0ee6 100644 --- a/docs-ai/013-prowl-cli/contracts/input.md +++ b/docs-ai/013-prowl-cli/contracts/input.md @@ -68,7 +68,7 @@ prowl agents signal ``` `--progress` is valid only with `progress`; omitting it means indeterminate progress. -Session/origin are at most 256 UTF-8 bytes and detail is at most 4096. All are non-empty +Session/origin are at most 256 UTF-8 bytes and detail is at most 32768. All are non-empty and control-free when present. Parser and handler enforce the same shared validation. See [agents-signal.md](agents-signal.md). diff --git a/docs-ai/064-agent-completion-signals/000-plan.md b/docs-ai/064-agent-completion-signals/000-plan.md index 9b84f187..1ecc0ab5 100644 --- a/docs-ai/064-agent-completion-signals/000-plan.md +++ b/docs-ai/064-agent-completion-signals/000-plan.md @@ -232,6 +232,10 @@ opencode; partial for qodercli/qwen/amp; docs/bundle for the rest). Key conclusi ## Amendments +- Updated 2026-08-23 before merge: owner raised bounded signal `--detail` from 4 KiB to + 32 KiB (32768 UTF-8 bytes). The larger bound remains well below the 32 MiB socket frame and + macOS argument budget, accommodates useful completion summaries, and preserves the rule + that transcript/workflow-sized output uses its dedicated channels. - Updated 2026-08-22 before S1 implementation: owner review separated runtime `turn-ended` from S2's cooperative `dispatch-complete`; retained bounded `--detail`; made public origin claimed metadata only; required explicit overflow/resnapshot; and moved the complete diff --git a/docs-ai/064-agent-completion-signals/001-action.md b/docs-ai/064-agent-completion-signals/001-action.md index 5167c3fe..6983562a 100644 --- a/docs-ai/064-agent-completion-signals/001-action.md +++ b/docs-ai/064-agent-completion-signals/001-action.md @@ -20,7 +20,7 @@ S1 does not add `agents wait`, launch-scoped runtime hooks, workflow completion, - Continue the 064 path before the 063 workflow runner. `prowl workflow done` remains the only command that completes a workflow step; agent signals are observation/control-plane evidence. - Rename the runtime edge from ambiguous `turn-complete` to `turn-ended`. A runtime hook can prove that a turn ended, not that an assigned task completed. - Reserve `dispatch-complete` for S2's paired dispatch protocol. S2 must ship the entire path atomically: `create` returns a `dispatch_id`, the agent reports `dispatch-complete --detail`, a bounded in-memory receipt survives pane closure (but not app restart), and `agents wait --dispatch` consumes it without destructive read semantics. -- Keep bounded `--detail` in S1 so a cooperative producer can attach a short result/reason without forcing another CLI command. Detail is caller-authored metadata, never logged, never raises confidence, and is not a replacement for large transcript/workflow output channels. +- Keep bounded `--detail` in S1 so a cooperative producer can attach a short result/reason without forcing another CLI command. The owner raised its limit from 4 KiB to 32 KiB before merge: this remains small relative to the 32 MiB socket frame and 1 MiB macOS argument budget while accommodating useful completion summaries. Detail is caller-authored metadata, never logged, never raises confidence, and is not a replacement for large transcript/workflow output channels. - Public `--origin` is only a claimed origin. It cannot mark a channel as verified, raise confidence, or satisfy future hook self-checks. S3 may upgrade provenance only through a Prowl-configured launch-scoped capability; this is a correctness boundary, not a heavyweight security boundary. - Each observer has bounded buffering. State churn may be recovered by a newer snapshot. Signal/lifecycle loss is never silent: overflow terminates with an explicit internal error, and S2's waiter must re-subscribe/resnapshot before exposing a failure. - S1 signal state is in-memory and surface-scoped. S2 owns the separate bounded dispatch-receipt retention needed to survive surface closure. @@ -82,7 +82,9 @@ app xcresult (2423 tests), and `make build-app`. An isolated custom-socket Debug passed `list`/`agents` and returned `SOURCE_REQUIRED` for an outside `agents signal`; 11 focused tests re-verified real kernel peer-PID framing, app signal recording, and observer delivery. All final commands passed; only the documented concurrent-instance Ghostty UI -limitation remains. +limitation remains. A pre-merge owner follow-up then raised `--detail` to 32768 UTF-8 +bytes; exact ASCII and multibyte boundary tests, CLI integration, app tests, lint, and the +Debug build were repeated for that amendment. ## Review record diff --git a/docs-ai/064-agent-completion-signals/002-s1-work-note.md b/docs-ai/064-agent-completion-signals/002-s1-work-note.md index c50f5663..acf94f33 100644 --- a/docs-ai/064-agent-completion-signals/002-s1-work-note.md +++ b/docs-ai/064-agent-completion-signals/002-s1-work-note.md @@ -67,3 +67,4 @@ ad-hoc results continue through `agents read` or a future `agents wait --include - 2026-08-23 — Adversarial review round 1 at `99f0baa9`: no P0/P1; accepted four P3 hardening/docs findings (publisher liveness invariant, reentrancy-safe subscriber mutation, S2 `--until exit` reconciliation, encode-failure contract). - 2026-08-23 — Adversarial review round 2 at `a63ba4a4`: no P0/P1; accepted stricter published-agent close-all/prune ordering coverage plus precise dead-surface revision/terminated-continuation semantics. - 2026-08-23 — Adversarial review round 3 at `f26eb780`: no P0/P1/P2, merge-ready. Final validation repeated `make check`, CLI smoke/integration (87), app xcresult (2423), Debug build, isolated custom-socket discovery/rejection, and 11 focused socket/app-observer tests. +- 2026-08-23 — Owner follow-up raised `--detail` from 4 KiB to 32 KiB before merge. ASCII and multibyte exact-boundary tests went RED/GREEN; CLI smoke/integration (87), app xcresult (2423), `make check`, and Debug build passed. A 32768-byte argument crossed the real Debug Unix socket into the app, while 32769 bytes failed at CLI validation. diff --git a/docs/components/cli.md b/docs/components/cli.md index 9c73c58e..68637a29 100644 --- a/docs/components/cli.md +++ b/docs/components/cli.md @@ -241,7 +241,7 @@ Events are `turn-ended`, `needs-input`, `session-start`, `session-end`, and `pro `turn-ended` means one runtime interaction ended; it does not complete a workflow or prove an assigned task is done. `--progress` accepts 0–100 and is valid only for `progress`. Optional `--session` and claimed `--origin` are limited to 256 UTF-8 bytes; `--detail` -carries a short result/reason up to 4096 bytes. Values must be non-empty and control-free. +carries a short result/reason up to 32768 UTF-8 bytes. Values must be non-empty and control-free. The command accepts no target: Prowl attributes the kernel socket peer PID through process ancestry to a live pane. It never uses UI focus or `PROWL_PANE_ID`; external terminals, diff --git a/supacode/CLIService/Shared/InputModels.swift b/supacode/CLIService/Shared/InputModels.swift index 4ec4f2f1..b22160d1 100644 --- a/supacode/CLIService/Shared/InputModels.swift +++ b/supacode/CLIService/Shared/InputModels.swift @@ -41,7 +41,7 @@ public enum AgentSignalEvent: String, Codable, CaseIterable, Sendable { public struct AgentSignalInput: Codable, Sendable { public static let maximumSessionIDBytes = 256 public static let maximumOriginBytes = 256 - public static let maximumDetailBytes = 4 * 1_024 + public static let maximumDetailBytes = 32 * 1_024 public let event: AgentSignalEvent public let progress: Int? diff --git a/supacodeTests/CLIAgentSignalCommandHandlerTests.swift b/supacodeTests/CLIAgentSignalCommandHandlerTests.swift index d70efc13..3f885480 100644 --- a/supacodeTests/CLIAgentSignalCommandHandlerTests.swift +++ b/supacodeTests/CLIAgentSignalCommandHandlerTests.swift @@ -121,6 +121,12 @@ struct CLIAgentSignalCommandHandlerTests { }, recordSignal: { _, _ in true } ) + let maximumDetail = AgentSignalInput( + event: .needsInput, + detail: String(repeating: "x", count: 32_768) + ) + #expect(maximumDetail.validationErrorMessage == nil) + let invalidInputs = [ AgentSignalInput(event: .turnEnded, progress: 1), AgentSignalInput(event: .progress, progress: -1), @@ -128,7 +134,7 @@ struct CLIAgentSignalCommandHandlerTests { AgentSignalInput(event: .needsInput, sessionID: ""), AgentSignalInput(event: .needsInput, origin: "bad\norigin"), AgentSignalInput(event: .needsInput, detail: "bad\u{0}detail"), - AgentSignalInput(event: .needsInput, detail: String(repeating: "x", count: 4_097)), + AgentSignalInput(event: .needsInput, detail: String(repeating: "x", count: 32_769)), ] for input in invalidInputs {