Documentation
¶
Index ¶
- Variables
- func CmuxDegradedOwnershipKey(inventory TargetInventory, err error) (string, bool)
- type Activation
- type Adapter
- type Capability
- type CapabilityDeclarer
- type ClaudeDesktop
- func (ClaudeDesktop) Capability() Capability
- func (c ClaudeDesktop) Discover(ctx context.Context) (string, error)
- func (c ClaudeDesktop) Inject(ctx context.Context, target string, payload string) error
- func (ClaudeDesktop) Name() string
- func (ClaudeDesktop) NormalizeTarget(target string) (string, error)
- func (c ClaudeDesktop) Probe(ctx context.Context, target string) error
- type ClaudePrint
- func (ClaudePrint) Capability() Capability
- func (c ClaudePrint) CapabilityForTarget(target string) (Capability, error)
- func (c ClaudePrint) Inject(ctx context.Context, target string, payload string) error
- func (ClaudePrint) Name() string
- func (ClaudePrint) NormalizeTarget(target string) (string, error)
- func (c ClaudePrint) Probe(ctx context.Context, target string) error
- func (ClaudePrint) ReportsProviderAcceptance()
- type Cmux
- func (Cmux) Capability() Capability
- func (c Cmux) Discover(_ context.Context) (string, error)
- func (c Cmux) Inject(ctx context.Context, target string, payload string) error
- func (c Cmux) Inventory(ctx context.Context, _ OwnershipContext) (TargetInventory, error)
- func (Cmux) Name() string
- func (Cmux) NormalizeTarget(target string) (string, error)
- func (c Cmux) Probe(ctx context.Context, target string) error
- func (c Cmux) RememberOwnership(target, key string)
- func (c Cmux) WithOwnershipRecord() Cmux
- type CodexApp
- func (CodexApp) CapabilityForTarget(target string) (Capability, error)
- func (c CodexApp) Discover(ctx context.Context) (string, error)
- func (c CodexApp) Inject(ctx context.Context, target string, payload string) error
- func (CodexApp) Name() string
- func (CodexApp) NormalizeTarget(target string) (string, error)
- func (c CodexApp) Probe(ctx context.Context, target string) error
- type CodexQueue
- func (CodexQueue) Capability() Capability
- func (c CodexQueue) CapabilityForTarget(target string) (Capability, error)
- func (c CodexQueue) Inject(ctx context.Context, target string, payload string) error
- func (CodexQueue) Name() string
- func (CodexQueue) NormalizeTarget(target string) (string, error)
- func (c CodexQueue) Probe(ctx context.Context, target string) error
- func (CodexQueue) ReportsProviderAcceptance()
- type CommandRunner
- type Delivery
- type Discoverer
- type ExecRunner
- type File
- type Ghostty
- func (Ghostty) Capability() Capability
- func (g Ghostty) Discover(ctx context.Context) (string, error)
- func (g Ghostty) Inject(ctx context.Context, target string, payload string) error
- func (Ghostty) Name() string
- func (Ghostty) NormalizeTarget(target string) (string, error)
- func (g Ghostty) Probe(ctx context.Context, target string) error
- type InventoryProvider
- type OwnershipContext
- type ProviderAcceptanceReporter
- type Registry
- type SessionScope
- type TargetCapabilityDeclarer
- type TargetInventory
- type TargetNormalizer
- type WriterLockInspector
Constants ¶
This section is empty.
Variables ¶
var ( ErrTargetNotFound = errors.New("adapter target not found") // ErrTargetDegraded means an adapter could not safely establish physical // ownership. Supervisors must retry without mutating durable registry // bookkeeping because the uncertainty may be transient. ErrTargetDegraded = errors.New("adapter target ownership degraded") // ErrInjectUncertain means text already landed and Enter/submit failed. // App.Run prints this to stderr so inject-via can map it without a typed // error crossing the child process boundary. Never replay the payload. ErrInjectUncertain = errors.New("AMQ_INJECT_PROGRESS=uncertain") // ErrGUIAdapterNotReady keeps GUI seats out of the generic wake path until // their capability gate has live evidence. A refusal is not a weaker // delivery claim. ErrGUIAdapterNotReady = errors.New("GUI wake adapter is not ready") )
var ( ErrTargetNotRegular = errors.New("file adapter target must be a regular file") ErrTargetSymlink = errors.New("file adapter target must not be a symlink") )
Functions ¶
func CmuxDegradedOwnershipKey ¶ added in v0.59.1
func CmuxDegradedOwnershipKey(inventory TargetInventory, err error) (string, bool)
CmuxDegradedOwnershipKey returns a physical key only when the error came from the same concrete cmux inventory type used for the candidate lookup. This prevents generic or third-party TargetInventory implementations from turning ErrTargetDegraded into permission to skip an uncertain owner.
Types ¶
type Activation ¶ added in v0.70.0
type Activation int // none < launch < foreground
Activation is how strongly a seat can bring its surface to the user.
const ( ActivationNone Activation = iota ActivationLaunch ActivationForeground )
type Capability ¶ added in v0.70.0
type Capability struct {
Activation Activation
Delivery Delivery
Session SessionScope
RequiresHuman bool
}
Capability models the wake capability vector from docs/adr-wake-capability-vector.md. Each seat advertises the strongest delivery it can honestly claim; callers request a minimum. Weaker is refused, never substituted (ADR §"refusal over substitution").
func UnknownCapability ¶ added in v0.70.0
func UnknownCapability() Capability
UnknownCapability is the vector assumed for an adapter that does not implement CapabilityDeclarer: weakest on every ordered axis (Activation/Delivery/Session all None) and most-restrictive on the tolerance axis (RequiresHuman true). An undeclared adapter is therefore refused unless the caller explicitly tolerates a human-required seat, so an adapter that forgets to declare a Capability can never masquerade as an unattended full-strength seat. This is worst-case on the tolerance axis, not zero-value: the zero-value Capability has RequiresHuman=false, which would let an unknown adapter pass the default zero minimum as if it delivered unattended.
func (Capability) Satisfies ¶ added in v0.70.0
func (c Capability) Satisfies(min Capability) bool
Satisfies reports whether this seat is at least as strong as the caller's minimum on every axis. Weaker is REFUSED, never substituted (ADR §"refusal over substitution"). A caller that needs an unattended wake (min.RequiresHuman == false) must refuse a requires-human seat; a caller that accepts a human handoff (min.RequiresHuman == true) accepts either.
type CapabilityDeclarer ¶ added in v0.70.0
type CapabilityDeclarer interface {
Capability() Capability
}
CapabilityDeclarer lets an adapter publish its honest delivery vector. An adapter that does not implement it is treated as UnknownCapability() (weakest on every ordered axis and requires a human), so it is refused unless the caller explicitly tolerates a human-required seat. This keeps an adapter that forgets to declare a Capability from masquerading as an unattended full-strength seat.
type ClaudeDesktop ¶ added in v0.70.0
type ClaudeDesktop struct {
Runner CommandRunner
}
ClaudeDesktop implements the honest deep-link seat from the wake capability ADR: prefilled + new + requires_human. `claude://code/new?q=<prompt>` PREFILLS the composer on the Code surface and never auto-submits; a human send is always required (verified live on Claude Desktop 1.34493.1). This adapter only launches the deep-link; it does not and cannot submit.
func (ClaudeDesktop) Capability ¶ added in v0.70.0
func (ClaudeDesktop) Capability() Capability
Capability declares the proven seat vector: the deep-link launches the app (ActivationLaunch), prefills the composer without submitting (DeliveryPrefilled), always targets a new session (SessionNew), and a human must press send (RequiresHuman).
func (ClaudeDesktop) Discover ¶ added in v0.70.0
func (c ClaudeDesktop) Discover(ctx context.Context) (string, error)
Discover returns the single new-session seat. It is deliberately reachable so an explicit `attach --adapter claude-desktop --target claude-desktop:new` can name it; the human-handoff gate is enforced at registration (CapabilityDeclarer), not here. Discovery does not auto-open the app.
func (ClaudeDesktop) Inject ¶ added in v0.70.0
Inject launches the prefill-only deep-link. The payload becomes a properly query-escaped `q` parameter; the resulting URL is passed as a single argument to `open`. The prompt text never enters AppleScript/osascript or any script source — it exists only as the single query-escaped `open` argument. There is no System Events/keystroke/clipboard/activate, and crucially no submit — this seat never sends Enter.
Identity is revalidated before the write: `open` is scheme-bound, not identity-bound (it dispatches to whatever app owns claude:// at call time), so Probe runs here, not only at Discover/registration. On any identity mismatch or command failure Inject fails closed without emitting the open.
func (ClaudeDesktop) Name ¶ added in v0.70.0
func (ClaudeDesktop) Name() string
func (ClaudeDesktop) NormalizeTarget ¶ added in v0.70.0
func (ClaudeDesktop) NormalizeTarget(target string) (string, error)
func (ClaudeDesktop) Probe ¶ added in v0.70.0
func (c ClaudeDesktop) Probe(ctx context.Context, target string) error
Probe verifies the Claude Desktop app identity before blessing the target. It resolves the actual default handler of the `claude://` scheme and asserts it is the pinned bundle id com.anthropic.claudefordesktop — the same app that `open claude://...` will dispatch to at write time. A mismatch, a missing handler, a non-darwin platform, or any command failure fails closed.
type ClaudePrint ¶ added in v0.71.0
type ClaudePrint struct {
Runner CommandRunner
LookPath func(string) (string, error)
Spawner processSpawner
// Lock takes an exclusive non-blocking lock on path. Tests swap in an
// in-process locker because flock/fcntl are per-process. Production is
// flock LOCK_EX|LOCK_NB.
Lock func(path string) (unlock func(), err error)
// ConfigDir is CLAUDE_CONFIG_DIR (default ~/.claude). Ambient, not part of
// injector identity (docs/wake-lifecycle.md §9.4).
ConfigDir string
StateDir string
AckWait time.Duration
Now func() time.Time
}
ClaudePrint is the honest submitted seat into an existing Claude Code session. It spawns `claude -p --resume <uuid>` with stream-json replay ack, never raises a GUI (ActivationNone), and never claims the turn finished — the first matching isReplay user event is submitted.
Claude CLI does not enforce a one-owner mutex (a second -p --resume appends to the same jsonl). AMQ takes flock on inject.lock before the owner scan. Probe refuses unreadable/malformed/non-positive/pid-mismatched owner files, or a matching uuid whose filename pid is alive. Stale pid files are ignored, never deleted.
func (ClaudePrint) Capability ¶ added in v0.71.0
func (ClaudePrint) Capability() Capability
func (ClaudePrint) CapabilityForTarget ¶ added in v0.71.0
func (c ClaudePrint) CapabilityForTarget(target string) (Capability, error)
func (ClaudePrint) Name ¶ added in v0.71.0
func (ClaudePrint) Name() string
func (ClaudePrint) NormalizeTarget ¶ added in v0.71.0
func (ClaudePrint) NormalizeTarget(target string) (string, error)
func (ClaudePrint) Probe ¶ added in v0.71.0
func (c ClaudePrint) Probe(ctx context.Context, target string) error
func (ClaudePrint) ReportsProviderAcceptance ¶ added in v0.77.0
func (ClaudePrint) ReportsProviderAcceptance()
ReportsProviderAcceptance marks Claude Print as an application-level acceptance reporter. Inject waits for the matching isReplay user event in the Claude stream before returning nil.
type Cmux ¶
type Cmux struct {
Runner CommandRunner
Path string
Getenv func(string) string
LookPath func(string) (string, error)
UserHomeDir func() (string, error)
IsExecutable func(string) bool
Sleep func(context.Context, time.Duration) error
SettleDelay time.Duration
// LiveTTYOwnerCount reports how many live (non-zombie) processes hold the
// device at devPath as their controlling terminal. Defaults to the darwin
// sysctl implementation; tests inject a fake so they never run a real
// sysctl against fixture tty names.
LiveTTYOwnerCount func(devPath string) (int, error)
// Logf receives non-fatal diagnostics (evictions, degraded fail-closed
// ttys). Nil is a no-op.
Logf func(format string, args ...any)
// contains filtered or unexported fields
}
func (Cmux) Capability ¶ added in v0.70.0
func (Cmux) Capability() Capability
Capability declares the full-strength TTY seat on delivery and session: the adapter submits the payload (text + Enter) into an exact existing cmux surface with no human in the loop. Activation is None — Inject sends text and Enter via cmux RPC (surface.send_text / surface.send_key) WITHOUT raising/focusing the surface.
func (Cmux) Inventory ¶
func (c Cmux) Inventory(ctx context.Context, _ OwnershipContext) (TargetInventory, error)
func (Cmux) RememberOwnership ¶ added in v0.65.3
RememberOwnership seeds the last-known physical key for a surface. The supervisor loads keys persisted on registry entries so a restart still refuses UUID→TTY drift. Empty keys are ignored.
func (Cmux) WithOwnershipRecord ¶ added in v0.65.3
WithOwnershipRecord returns a copy whose last-key map survives Inventory rebuilds. Production DefaultRegistry already includes one; tests that exercise UUID→TTY drift across supervise ticks must use this so RememberOwnership can seed keys persisted on a registration.
type CodexApp ¶ added in v0.70.0
type CodexApp struct {
Runner CommandRunner
}
CodexApp implements the honest deep-link seat for the Codex app (com.openai.codex): `codex://threads/new?prompt=<text>` prefills a NEW thread, and `codex://threads/<uuid>?prompt=<text>` opens the EXACT EXISTING conversation <uuid> and prefills its composer. Neither auto-submits; a human send is always required (verified live on app 26.818.61809). This adapter only launches the deep-link; it does not and cannot submit.
Dispatch-vs-delivery caveat (live finding, issue #640): `open` exits 0 once the OS dispatches the URL to the registered handler — that proves DISPATCH only, not delivery. The app can refuse the deep-link with no adapter-visible signal: e.g. a thread with an ACTIVE WRITER shows an "Error creating chat — thread <uuid> already has an active writer" toast and leaves the composer empty, yet `open` still exits 0 and Inject reports success. Therefore the codex-app:thread:<uuid> target must name an IDLE conversation (no active writer), and callers must treat Inject success as "launched, not confirmed delivered". The adapter cannot observe the in-app refusal because the Codex app is AX-opaque (Chromium content is not exposed to System Events).
The native `execute javascript` Apple Events path is DEAD and stays dead: live smoke on app 26.818.61809 returned `Access not allowed (-1723)`, and writing browser.allow_javascript_apple_events=true into the profile Preferences did not unlock it (the pref survives quit but the runtime check is compiled out). Do not re-try that path; the finding is recorded at https://github.com/avivsinai/agent-message-queue/issues/640#issuecomment-5406484999. The task-0 probe script in scripts/probe-codex-app-execute-javascript.sh remains as historical evidence and is intentionally not invoked here.
func (CodexApp) CapabilityForTarget ¶ added in v0.70.0
func (CodexApp) CapabilityForTarget(target string) (Capability, error)
CapabilityForTarget declares the per-target vector this deep-link seat can honestly claim. Both targets are launch + prefilled + requires_human; they differ only on session scope: codex-app:new is SessionNew, and codex-app:thread:<uuid> is SessionExistingExact (it opens a specific existing conversation). A caller requesting an existing-exact session is therefore refused the new-only target rather than silently downgraded.
func (CodexApp) Discover ¶ added in v0.70.0
Discover returns the new-thread seat (the only target that does not require a pre-existing conversation id). It is deliberately reachable so an explicit `attach --adapter codex-app --target codex-app:new` can name it; the human-handoff gate is enforced at registration, not here. Discovery does not auto-open the app.
func (CodexApp) Inject ¶ added in v0.70.0
Inject launches the prefill-only deep-link. The payload becomes a properly query-escaped `prompt` parameter; the resulting URL is passed as a single argument to `open`. The prompt text never enters AppleScript/osascript or any script source — it exists only as the single query-escaped `open` argument. There is no System Events/keystroke/clipboard/activate, and crucially no submit — this seat never sends Enter.
Identity is revalidated before the write: `open` is scheme-bound, not identity-bound (it dispatches to whatever app owns codex:// at call time), so Probe runs here, not only at Discover/registration. On any identity mismatch or command failure Inject fails closed without emitting the open.
func (CodexApp) NormalizeTarget ¶ added in v0.70.0
func (CodexApp) Probe ¶ added in v0.70.0
Probe verifies the Codex app identity before blessing the target. It resolves the actual default handler of the `codex://` scheme and asserts it is the pinned bundle id com.openai.codex — the same app that `open codex://...` will dispatch to at write time. (The app also registers http/https; only the codex scheme is pinned.) A mismatch, a missing handler, a non-darwin platform, or any command failure fails closed.
type CodexQueue ¶ added in v0.71.0
type CodexQueue struct {
Runner CommandRunner
LookPath func(string) (string, error)
InspectWriterLock WriterLockInspector
Home string
}
CodexQueue implements the honest submitted seat into a live Codex GUI/TUI thread. `codex queue --thread <uuid> --message <text>` calls the app-server JSON-RPC `thread/queue/add`; the EXISTING writer (GUI app-server / TUI / codex-acp) drains it. The adapter never raises or focuses the app (ActivationNone) and never claims that the turn finished — exit 0 proves enqueue to the writer, not completion.
Queue cannot create a thread, so the only target is `codex-queue:thread:<uuid>`. An idle thread (no holder of thread-writer-locks/<uuid>.lock) is ErrTargetDegraded; deferred delivery for that case is out of scope.
func (CodexQueue) Capability ¶ added in v0.71.0
func (CodexQueue) Capability() Capability
Capability is the honest submitted vector. Queue enqueues to the thread's existing writer; it never raises/focuses the app.
func (CodexQueue) CapabilityForTarget ¶ added in v0.71.0
func (c CodexQueue) CapabilityForTarget(target string) (Capability, error)
func (CodexQueue) Name ¶ added in v0.71.0
func (CodexQueue) Name() string
func (CodexQueue) NormalizeTarget ¶ added in v0.71.0
func (CodexQueue) NormalizeTarget(target string) (string, error)
func (CodexQueue) Probe ¶ added in v0.71.0
func (c CodexQueue) Probe(ctx context.Context, target string) error
func (CodexQueue) ReportsProviderAcceptance ¶ added in v0.77.0
func (CodexQueue) ReportsProviderAcceptance()
ReportsProviderAcceptance marks Codex Queue as an application-level acceptance reporter. Inject returns nil only after the queue command accepts the message.
type CommandRunner ¶
type Delivery ¶ added in v0.70.0
type Delivery int // none < prefilled < submitted
Delivery is how much of the prompt reaches the composer.
type ExecRunner ¶
type ExecRunner struct{}
type File ¶
type File struct{}
func (File) Capability ¶ added in v0.70.0
func (File) Capability() Capability
Capability declares the file seat as full-strength on delivery and session: Inject appends the payload (with a trailing newline) to an exact, resolved target path with no human in the loop. Activation is None — appending to a file path does not foreground or raise any surface.
func (File) NormalizeTarget ¶
NormalizeTarget resolves a file target to its stable absolute pathname. Registration persists this value, so a later launchd invocation cannot reinterpret a relative target from a different working directory. Resolving existing symlinks also keeps lexical and symlink aliases from claiming the same destination independently.
type Ghostty ¶
type Ghostty struct {
Runner CommandRunner
}
func (Ghostty) Capability ¶ added in v0.70.0
func (Ghostty) Capability() Capability
Capability declares the full-strength TTY seat on delivery and session: the adapter submits the payload (text + Enter) into an exact existing terminal surface with no human in the loop. Activation is None because Inject types into a pinned terminal id WITHOUT raising/focusing it (`activate`/`AXRaise` are banned in adapter scripts); it does not claim to bring the surface to the foreground.
type InventoryProvider ¶
type InventoryProvider interface {
Inventory(ctx context.Context, own OwnershipContext) (TargetInventory, error)
}
InventoryProvider lets the supervisor inventory an adapter once per pass instead of spawning one probe process for every registry entry. The OwnershipContext lets the registration preflight pass a trusted-live candidate; other callers pass the zero value.
type OwnershipContext ¶
type OwnershipContext struct {
// TrustedTarget is the adapter-native target string (e.g. a cmux surface
// target) the caller has proven live. Empty means no trusted candidate.
TrustedTarget string
}
OwnershipContext carries optional trust the caller has already established about a candidate target. Only the registration preflight may populate it: the target it is registering was discovered from the live local surface, so it is live by construction and can break an otherwise-ambiguous physical ownership tie. Supervisor and inject passes pass the zero value, which trusts nothing.
type ProviderAcceptanceReporter ¶ added in v0.77.0
type ProviderAcceptanceReporter interface {
ReportsProviderAcceptance()
}
ProviderAcceptanceReporter is an opt-in marker for adapters whose Inject implementation observes an application-level acceptance receipt. It is deliberately separate from Capability.DeliverySubmitted: a submitted TTY or file write does not prove that the provider accepted the payload.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
func DefaultRegistry ¶
func DefaultRegistry() Registry
func DefaultRegistryWithLogf ¶
DefaultRegistryWithLogf returns the production adapter set with non-fatal adapter diagnostics wired to logf. Callers that do not own a diagnostic stream can continue using DefaultRegistry.
func NewRegistry ¶
type SessionScope ¶ added in v0.70.0
type SessionScope int // none < new < existing-exact
SessionScope is how precisely the seat addresses an existing surface.
const ( SessionNone SessionScope = iota SessionNew SessionExistingExact )
type TargetCapabilityDeclarer ¶ added in v0.70.0
type TargetCapabilityDeclarer interface {
CapabilityForTarget(target string) (Capability, error)
}
TargetCapabilityDeclarer is implemented by adapters whose capability vector depends on the resolved target (for example, a deep-link adapter that can prefill either a new session or an exact existing conversation). When an adapter implements this, the registration gate prefers CapabilityForTarget(target) over the target-blind Capability() so a caller requesting an existing-exact session is refused a new-only target rather than silently downgraded. An error from CapabilityForTarget fails closed.
type TargetInventory ¶
type TargetInventory interface {
Probe(target string) error
OwnershipKey(target string) (string, error)
}
TargetInventory is a point-in-time existence snapshot. Implementations must return ErrTargetNotFound only when absence is proven by the snapshot; parse, transport, and permission failures remain ambiguous errors. When ownership is degraded, callers remain fail-closed unless a concrete adapter-specific capability proves that the same immutable inventory also established a different physical identity. The generic interface deliberately exposes no constructor for such a capability.
type TargetNormalizer ¶
type WriterLockInspector ¶ added in v0.71.0
WriterLockInspector reports whether a Codex thread-writer lock is held WITHOUT acquiring it. The idle-thread case is an untested follow-up; this seat only claims submitted delivery when a writer already holds the lock. Native inspection errors fail closed (no lsof fallback: an open file is not a held flock).