Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
623 lines
22 KiB
Go
623 lines
22 KiB
Go
package agentruntime
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
|
|
activityshared "github.com/tutti-os/tutti/packages/agent/daemon/activity/events"
|
|
sessionreplay "github.com/tutti-os/tutti/packages/agent/session-replay"
|
|
)
|
|
|
|
var ErrLiveSessionBusy = errors.New("agent live session is busy")
|
|
|
|
type ProcessSpec struct {
|
|
Provider string
|
|
AgentSessionID string
|
|
RootAgentSessionID string
|
|
RoomID string
|
|
CWD string
|
|
ProtocolCWD string
|
|
Command []string
|
|
Env []string
|
|
DirectStart bool
|
|
ExecutableIdentity *ExecutableIdentity
|
|
ArtifactTrees []ArtifactTreeIdentity
|
|
// SensitiveInheritedFiles are opt-in descriptors whose contents must never
|
|
// be copied into argv or the process environment. Generic process
|
|
// transports reject this field; the connector transport maps each file to
|
|
// fd 3+n and publishes only that descriptor number through DescriptorEnvKey.
|
|
SensitiveInheritedFiles []SensitiveInheritedFile
|
|
}
|
|
|
|
// ArtifactTreeIdentity binds a connector artifact snapshot to the inventory
|
|
// digest verified from the signed artifact. The connector transport rechecks
|
|
// it immediately before launch, closing the receipt-to-launch pathname gap.
|
|
type ArtifactTreeIdentity struct {
|
|
Root string
|
|
SHA256 string
|
|
}
|
|
|
|
// SensitiveInheritedFile describes one host-owned secret-bearing descriptor.
|
|
// DescriptorEnvKey is metadata, not a secret, and must use the reserved
|
|
// TUTTI_CONNECTOR_FD_ prefix. Ownership stays with the caller; transports dup
|
|
// the descriptor for the child and never close File.
|
|
type SensitiveInheritedFile struct {
|
|
File *os.File
|
|
DescriptorEnvKey string
|
|
Purpose string
|
|
}
|
|
|
|
// ExecutableIdentity binds a process launch to bytes verified by an owning
|
|
// managed-runtime boundary. When present, local transport executes the opened,
|
|
// verified descriptor rather than resolving the pathname again.
|
|
type ExecutableIdentity struct {
|
|
SHA256 string
|
|
SizeBytes int64
|
|
}
|
|
|
|
type ProcessFrame struct {
|
|
Stdout []byte
|
|
Stderr []byte
|
|
ExitCode *int
|
|
Message string
|
|
RecordingID string
|
|
ConnectionID string
|
|
ChunkSeq uint64
|
|
// Synthetic marks optional-probe / startup-metadata responses invented by
|
|
// replay. They are not cassette units and must not advance the provider
|
|
// input barrier (ChunkSeq is unset / zero).
|
|
Synthetic bool
|
|
}
|
|
|
|
type ProcessConnection interface {
|
|
Send([]byte) error
|
|
Recv() (ProcessFrame, error)
|
|
Close() error
|
|
}
|
|
|
|
// RuntimeTransportFailure carries a bounded machine-readable failure code
|
|
// from a host-owned process transport into canonical Agent failure events.
|
|
// Implementations must not encode raw paths, request payloads, or credentials
|
|
// in the code.
|
|
type RuntimeTransportFailure interface {
|
|
error
|
|
RuntimeTransportFailureCode() string
|
|
}
|
|
|
|
// RuntimeTransportGRPCFailure optionally preserves the bounded gRPC status
|
|
// code for transport diagnostics without exposing the raw status message.
|
|
type RuntimeTransportGRPCFailure interface {
|
|
RuntimeTransportFailure
|
|
RuntimeTransportGRPCCode() string
|
|
}
|
|
|
|
// ProcessCassetteCheckpointConnection is implemented by replay connections so
|
|
// provider adapters can distinguish a cold process tape from capture attached
|
|
// to an already initialized live provider connection.
|
|
type ProcessCassetteCheckpointConnection interface {
|
|
ProcessConnection
|
|
ProcessCassetteCaptureOrigin() ProcessCassetteCaptureOrigin
|
|
}
|
|
|
|
func processCassetteCaptureOrigin(
|
|
connection ProcessConnection,
|
|
) ProcessCassetteCaptureOrigin {
|
|
checkpoint, ok := connection.(ProcessCassetteCheckpointConnection)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
return checkpoint.ProcessCassetteCaptureOrigin()
|
|
}
|
|
|
|
// ContextProcessConnection lets protocol readers stop waiting for output
|
|
// without terminating the provider process. Long-lived provider startups use
|
|
// this to detach a UI request timeout from the process lifecycle.
|
|
type ContextProcessConnection interface {
|
|
ProcessConnection
|
|
RecvContext(context.Context) (ProcessFrame, error)
|
|
}
|
|
|
|
// ProviderInputUnitCompletion is the decoder-to-transport barrier. Recording
|
|
// uses it to anchor semantic observations to transport positions. Replay uses
|
|
// it to hold a connection after a complete input unit has been handled.
|
|
type ProviderInputUnitCompletion interface {
|
|
CompleteProviderInputUnit(context.Context, ProviderInputUnit) error
|
|
}
|
|
|
|
// ProviderInputUnitTrackingTransport marks transports whose connections emit
|
|
// stable process positions for recording or deterministic replay.
|
|
type ProviderInputUnitTrackingTransport interface {
|
|
TracksProviderInputUnits() bool
|
|
}
|
|
|
|
type ProviderInputUnit struct {
|
|
RecordingID string
|
|
Position sessionreplay.ProviderUnitPosition
|
|
Kind sessionreplay.ProviderInputUnitKind
|
|
}
|
|
|
|
type providerInputUnitContextKey struct{}
|
|
|
|
func contextWithProviderInputUnit(
|
|
ctx context.Context,
|
|
unit ProviderInputUnit,
|
|
) context.Context {
|
|
return context.WithValue(ctx, providerInputUnitContextKey{}, unit)
|
|
}
|
|
|
|
func ProviderInputUnitFromContext(ctx context.Context) (ProviderInputUnit, bool) {
|
|
if ctx == nil {
|
|
return ProviderInputUnit{}, false
|
|
}
|
|
unit, ok := ctx.Value(providerInputUnitContextKey{}).(ProviderInputUnit)
|
|
return unit, ok
|
|
}
|
|
|
|
type GracefulProcessConnection interface {
|
|
ProcessConnection
|
|
CloseInput() error
|
|
Terminate() error
|
|
Kill() error
|
|
}
|
|
|
|
type ProcessTransport interface {
|
|
Start(context.Context, ProcessSpec) (ProcessConnection, error)
|
|
}
|
|
|
|
type EventSink func([]activityshared.Event)
|
|
type SessionEventSink func(string, []activityshared.Event)
|
|
type CommandSnapshotSink func(AgentSessionCommandSnapshot)
|
|
type ConfigOptionsUpdateSink func(AgentSessionConfigOptionsUpdate)
|
|
|
|
type Adapter interface {
|
|
Provider() string
|
|
Start(context.Context, Session) ([]activityshared.Event, error)
|
|
// Resume reconnects provider context only. It must not initiate a Turn;
|
|
// execution remains gated on a later explicit Controller operation.
|
|
Resume(context.Context, Session) error
|
|
Close(context.Context, Session) error
|
|
Exec(context.Context, Session, []PromptContentBlock, string, string, EventSink, CommandSnapshotSink) ([]activityshared.Event, error)
|
|
Cancel(context.Context, Session, string) ([]activityshared.Event, error)
|
|
}
|
|
|
|
// CloseQuiesceAdapter lets a shared provider connection stop session-owned
|
|
// work before Controller cancels the local Exec context and detaches the
|
|
// session. Without this phase a local cancellation can erase the provider Turn
|
|
// handle while the shared process remains alive for another session.
|
|
type CloseQuiesceAdapter interface {
|
|
QuiesceForClose(context.Context, Session) error
|
|
}
|
|
|
|
// ConnectorCapabilityAdapter reports whether this exact provider runtime can
|
|
// accept Tutti's session-scoped Connector binding. Implementations must return
|
|
// false unless support is explicit; a missing implementation is unsupported.
|
|
type ConnectorCapabilityAdapter interface {
|
|
ConnectorCapabilities(context.Context, Session) (ConnectorCapabilities, error)
|
|
}
|
|
|
|
type ConnectorCapabilities struct {
|
|
HTTPMCP bool
|
|
}
|
|
|
|
// SessionForkAdapter is an optional provider-native capability. Providers that
|
|
// cannot prove an exact fork boundary do not implement it.
|
|
type SessionForkAdapter interface {
|
|
ForkCapabilities(context.Context, Session) (SessionForkCapabilities, error)
|
|
Fork(context.Context, SessionForkInput) (SessionForkResult, error)
|
|
}
|
|
|
|
// ProviderTurnBindingAdapter owns the provider-specific Turn binding payload.
|
|
// The host and store persist ProviderTurnBindingJSON opaquely and never infer
|
|
// forkability from provider-specific fields.
|
|
type ProviderTurnBindingAdapter interface {
|
|
WriteProviderTurnBinding(
|
|
ProviderTurnBindingWriteInput,
|
|
) (json.RawMessage, error)
|
|
CanForkProviderTurn(
|
|
context.Context,
|
|
ProviderTurnForkabilityInput,
|
|
) (bool, error)
|
|
}
|
|
|
|
type ProviderTurnBindingRecoveryAdapter interface {
|
|
RecoverProviderTurnBinding(
|
|
context.Context,
|
|
ProviderTurnBindingRecoveryInput,
|
|
) (ProviderTurnBindingRecoveryResult, error)
|
|
}
|
|
|
|
// SideConversationAdapter is the provider-specific open surface for
|
|
// runtime-only side conversations. The adapter decides whether a live source
|
|
// connection or persisted provider identity can satisfy the request;
|
|
// interactive Side flows additionally require the ordinary InteractiveAdapter
|
|
// contract. Once OpenSide returns, the Controller
|
|
// reuses Adapter Exec/Cancel/Close and the configured event/command/config
|
|
// sinks against the returned side-scoped Session.
|
|
//
|
|
// OpenSide owns its failure cleanup: before returning an error it must
|
|
// quiesce callbacks and release any provider child it created. Ownership
|
|
// transfers to Adapter.Close only when the Controller validates the exact
|
|
// side-scoped identity in the successful result; an invalid result remains
|
|
// the provider's cleanup responsibility and is never passed to ordinary Close.
|
|
type SideConversationAdapter interface {
|
|
SideCapabilities(context.Context, Session) (SideConversationCapabilities, error)
|
|
OpenSide(context.Context, SideConversationAdapterOpenInput) (SideConversationOpenResult, error)
|
|
}
|
|
|
|
// TargetedCancelAdapter maps canonical root/child targets onto provider-native
|
|
// handles. The controller supplies the root live session and never asks the
|
|
// adapter to discover the durable child tree itself.
|
|
type TargetedCancelAdapter interface {
|
|
CancelTargets(context.Context, Session, []CancelTarget, string) (TargetedCancelResult, error)
|
|
}
|
|
|
|
// TargetedCancelResult separates provider-confirmed cancellation from the
|
|
// normalized UI events produced while issuing the command. A missing target
|
|
// is not confirmation: services/tuttid will settle an unconfirmed child turn
|
|
// as interrupted after this bounded provider call returns.
|
|
type TargetedCancelResult struct {
|
|
Events []activityshared.Event
|
|
ConfirmedTargets []CancelTarget
|
|
}
|
|
|
|
// ResolveInputBoundAdapter lets a provider-keyed migration cache fail closed
|
|
// when a dynamic adapter was created for a different trusted Target binding.
|
|
type ResolveInputBoundAdapter interface {
|
|
MatchesAdapterResolveInput(AdapterResolveInput) bool
|
|
}
|
|
|
|
type AsyncExecAdapter interface {
|
|
ExecAsync(context.Context, Session, []PromptContentBlock, string, string, EventSink, CommandSnapshotSink) error
|
|
}
|
|
|
|
// ProviderAcceptanceExecAdapter exposes the provider's exact acceptance
|
|
// receipt without making canonical lifecycle depend on provider-specific
|
|
// notifications arriving later through the ordinary report queue.
|
|
type ProviderAcceptanceExecAdapter interface {
|
|
Adapter
|
|
ExecWithProviderAcceptance(
|
|
context.Context,
|
|
Session,
|
|
[]PromptContentBlock,
|
|
string,
|
|
string,
|
|
EventSink,
|
|
CommandSnapshotSink,
|
|
ProviderDispatchSink,
|
|
ProviderAcceptanceBarrier,
|
|
) ([]activityshared.Event, error)
|
|
}
|
|
|
|
type EffectiveHistoryTurn struct {
|
|
ID string
|
|
Status string
|
|
ClientUserMessageID string
|
|
}
|
|
|
|
// EffectiveHistorySnapshot is the provider's authoritative thread membership
|
|
// after a read or rollback. Canonical messages remain store-owned.
|
|
type EffectiveHistorySnapshot struct {
|
|
ProviderSessionID string
|
|
Turns []EffectiveHistoryTurn
|
|
}
|
|
|
|
type HistoryMutationResult struct {
|
|
Disposition DispatchDisposition
|
|
Snapshot *EffectiveHistorySnapshot
|
|
}
|
|
|
|
type HistoryReplacementExecInput struct {
|
|
Content []PromptContentBlock
|
|
DisplayPrompt string
|
|
TurnID string
|
|
}
|
|
|
|
// ProviderDispatchSink reports the provider's direct operation outcome. A
|
|
// successful operation that does not create a provider Turn uses
|
|
// DispatchDispositionAppliedWithoutProviderTurn. Implementations must report
|
|
// exactly once before a typed execution method returns.
|
|
type ProviderDispatchSink func(ProviderDispatchResult)
|
|
|
|
// ProviderAcceptanceBarrier durably binds the canonical Turn to the exact
|
|
// provider session and provider Turn before the adapter exposes any event that
|
|
// depends on that identity. Implementations must call it synchronously on the
|
|
// provider event path and stop event publication when it fails.
|
|
type ProviderAcceptanceBarrier func(ProviderAcceptanceReceipt) error
|
|
|
|
// EffectiveHistoryAdapter is the complete provider capability required by
|
|
// edit-retry: authoritative history reads, rollback, and a fresh replacement
|
|
// turn start with typed acceptance evidence. Shared code fails closed unless
|
|
// the provider implements the entire seam.
|
|
type EffectiveHistoryAdapter interface {
|
|
Adapter
|
|
ReadEffectiveHistory(context.Context, Session) (EffectiveHistorySnapshot, error)
|
|
RollbackLatestTurn(context.Context, Session) (HistoryMutationResult, error)
|
|
ExecHistoryReplacement(
|
|
context.Context,
|
|
Session,
|
|
HistoryReplacementExecInput,
|
|
EventSink,
|
|
CommandSnapshotSink,
|
|
ProviderDispatchSink,
|
|
) ([]activityshared.Event, error)
|
|
}
|
|
|
|
// RootProviderTurnLifecycleAdapter reports provider-turn lifecycle facts
|
|
// without claiming the canonical root WorkspaceAgentTurn is terminal. The
|
|
// durable daemon settles that root turn after checking every child turn.
|
|
type RootProviderTurnLifecycleAdapter interface {
|
|
UsesRootProviderTurnLifecycle() bool
|
|
}
|
|
|
|
type ActiveTurnGuidanceAdapter interface {
|
|
// GuideActiveTurn applies guidance to the exact controller turn identified
|
|
// by turnID. It must terminate the provider's current response and close its
|
|
// streaming projections before admitting the guided response, while keeping
|
|
// the same canonical turn lifecycle.
|
|
GuideActiveTurn(context.Context, Session, []PromptContentBlock, string, string, EventSink, CommandSnapshotSink) ([]activityshared.Event, error)
|
|
}
|
|
|
|
// ActiveTurnGuidanceProviderDispatchAdapter adds the provider-dispatch verdict
|
|
// needed to distinguish adapter-local preflight failures from a request whose
|
|
// provider acknowledgement is unknown. Adapters that do not implement this
|
|
// optional seam retain the conservative legacy behavior: any returned error is
|
|
// treated as outcome_unknown.
|
|
type ActiveTurnGuidanceProviderDispatchAdapter interface {
|
|
ActiveTurnGuidanceAdapter
|
|
GuideActiveTurnWithProviderDispatch(
|
|
context.Context,
|
|
Session,
|
|
[]PromptContentBlock,
|
|
string,
|
|
string,
|
|
EventSink,
|
|
CommandSnapshotSink,
|
|
ProviderDispatchSink,
|
|
) ([]activityshared.Event, error)
|
|
}
|
|
|
|
type ResumeProbeAdapter interface {
|
|
CanResume(Session) bool
|
|
}
|
|
|
|
type LiveSessionProbeAdapter interface {
|
|
HasLiveSession(Session) bool
|
|
}
|
|
|
|
type LiveSessionResourceCleanupResult struct {
|
|
Attempted int
|
|
Cleaned int
|
|
Failed int
|
|
}
|
|
|
|
// LiveSessionResourceCleanupAdapter owns physical handles that can outlive a
|
|
// canonical runtime Session after a failed close. Cleanup is bounded by limit
|
|
// so one reaper or shutdown pass cannot block on every retired process.
|
|
type LiveSessionResourceCleanupAdapter interface {
|
|
CleanupLiveSessionResources(context.Context, int) LiveSessionResourceCleanupResult
|
|
}
|
|
|
|
type LiveSessionReleaseAdapter interface {
|
|
ReleaseLiveSession(context.Context, Session) error
|
|
}
|
|
|
|
// LiveSessionDisconnectAdapter force-releases a live provider transport when
|
|
// its owning Workspace runtime becomes unavailable. Unlike Adapter.Close it
|
|
// must not send a destructive provider session-close operation; unlike the
|
|
// idle reaper it must settle active work and pending interactions.
|
|
type LiveSessionDisconnectAdapter interface {
|
|
DisconnectLiveSession(context.Context, Session) error
|
|
}
|
|
|
|
// LiveSessionReleaseCapabilityAdapter narrows live-session release for
|
|
// adapters whose ability to resume is learned from the current provider
|
|
// handshake rather than known statically by adapter type.
|
|
type LiveSessionReleaseCapabilityAdapter interface {
|
|
CanReleaseLiveSession(Session) bool
|
|
}
|
|
|
|
type StateAdapter interface {
|
|
SessionState(Session) SessionStateSnapshot
|
|
}
|
|
|
|
type CommandSnapshotAdapter interface {
|
|
SessionCommandSnapshot(Session) (AgentSessionCommandSnapshot, bool)
|
|
}
|
|
|
|
type CommandSnapshotSinkAdapter interface {
|
|
SetCommandSnapshotSink(CommandSnapshotSink)
|
|
}
|
|
|
|
type SessionEventSinkAdapter interface {
|
|
SetSessionEventSink(SessionEventSink)
|
|
}
|
|
|
|
type GoalReconcileDurableRequest struct {
|
|
RequestID string
|
|
Phase string
|
|
ProviderTurnID string
|
|
Reason string
|
|
FenceMode string
|
|
ExpectedOperationID string
|
|
ExpectedRevision int64
|
|
ExpectedRepairEpoch int64
|
|
QuiesceSucceeded bool
|
|
QuiesceError string
|
|
}
|
|
|
|
type GoalReconcileDurableSink func(context.Context, Session, GoalReconcileDurableRequest) error
|
|
|
|
type GoalReconcileDurableSinkAdapter interface {
|
|
SetGoalReconcileDurableSink(GoalReconcileDurableSink)
|
|
}
|
|
|
|
// GoalProvenanceBinding is the exact durable association between a
|
|
// provider-authored Goal generation and the business operation that created
|
|
// it. Ambiguous is a permanent tombstone: a reused fingerprint must never be
|
|
// rebound to either operation.
|
|
type GoalProvenanceBinding struct {
|
|
OperationID string
|
|
Revision int64
|
|
RepairEpoch int64
|
|
Ambiguous bool
|
|
}
|
|
|
|
type GoalProvenanceDurableSink interface {
|
|
BindGoalProvenance(context.Context, Session, string, GoalProvenanceBinding) (GoalProvenanceBinding, error)
|
|
LookupGoalProvenance(context.Context, Session, string) (GoalProvenanceBinding, bool, error)
|
|
}
|
|
|
|
type GoalProvenanceDurableSinkAdapter interface {
|
|
SetGoalProvenanceDurableSink(GoalProvenanceDurableSink)
|
|
}
|
|
|
|
type ProviderGoalAdoptionRequest struct {
|
|
Fingerprint string
|
|
ExpectedRevision int64
|
|
Goal map[string]any
|
|
}
|
|
|
|
type ProviderGoalAdoptionSink func(context.Context, Session, ProviderGoalAdoptionRequest) (GoalProvenanceBinding, error)
|
|
|
|
type ProviderGoalAdoptionSinkAdapter interface {
|
|
SetProviderGoalAdoptionSink(ProviderGoalAdoptionSink)
|
|
}
|
|
|
|
type ConfigOptionsUpdateSinkAdapter interface {
|
|
SetConfigOptionsUpdateSink(ConfigOptionsUpdateSink)
|
|
}
|
|
|
|
type InteractiveAdapter interface {
|
|
// SubmitInteractive returns ErrInteractiveResponseInvalid for malformed or
|
|
// unavailable response options, ErrInteractiveRequestNotLive for stale
|
|
// requests, and ErrInteractiveAlreadyAnswered after terminal resolution.
|
|
// This taxonomy is part of the provider-neutral Host/API contract.
|
|
SubmitInteractive(context.Context, Session, SubmitInteractiveInput) (SubmitInteractiveResult, error)
|
|
}
|
|
|
|
type InteractiveDispositionAdapter interface {
|
|
InteractiveDisposition(Session, string, string) InteractiveDisposition
|
|
}
|
|
|
|
type TargetedInteractiveDispositionAdapter interface {
|
|
InteractiveDispositionForTarget(Session, string, string, string) InteractiveDisposition
|
|
}
|
|
|
|
type InteractiveDispositionSink func(string, string, string, InteractiveDisposition)
|
|
|
|
type InteractiveDispositionSinkAdapter interface {
|
|
SetInteractiveDispositionSink(InteractiveDispositionSink)
|
|
}
|
|
|
|
// InteractiveSelectionState is the provider adapter's narrow projection of a
|
|
// successful interactive choice onto generic session settings. The controller
|
|
// owns persistence/publication; adapters own protocol vocabulary.
|
|
type InteractiveSelectionState struct {
|
|
PlanMode bool
|
|
PermissionMode string
|
|
}
|
|
|
|
type InteractiveSelectionStateAdapter interface {
|
|
StateAfterInteractiveSelection(Session, string) (InteractiveSelectionState, bool)
|
|
}
|
|
|
|
type InteractiveDenyFollowUpPolicyAdapter interface {
|
|
ControllerSendsInteractiveDenyFollowUp() bool
|
|
}
|
|
|
|
type PromptContentAdapter interface {
|
|
ValidatePromptContent(Session, []PromptContentBlock) error
|
|
}
|
|
|
|
// GoalControlAction is a direct goal operation invoked from the GUI (banner
|
|
// buttons) without going through the prompt pipeline.
|
|
type GoalControlAction string
|
|
|
|
const (
|
|
GoalControlPause GoalControlAction = "pause"
|
|
GoalControlResume GoalControlAction = "resume"
|
|
GoalControlClear GoalControlAction = "clear"
|
|
GoalControlSet GoalControlAction = "set"
|
|
)
|
|
|
|
type GoalAdapterCapabilities struct {
|
|
QuerySupported bool
|
|
ClearSupported bool
|
|
PauseSupported bool
|
|
QuiesceGoalTurns bool
|
|
ReplaySetAfterRestart bool
|
|
}
|
|
|
|
type GoalGenerationFenceInput struct {
|
|
OperationID string
|
|
Revision int64
|
|
RepairEpoch int64
|
|
Reason string
|
|
}
|
|
|
|
// GoalGenerationFencer installs an exact, idempotent admission fence for one
|
|
// Goal generation. It must never retarget the session's current Turn.
|
|
type GoalGenerationFencer interface {
|
|
FenceGoalGeneration(context.Context, Session, GoalGenerationFenceInput) error
|
|
}
|
|
|
|
type GoalAdapterResult struct {
|
|
Events []activityshared.Event
|
|
Observation map[string]any
|
|
Evidence map[string]any
|
|
// ProviderPhase separates transport acceptance from evidence that the
|
|
// provider actually consumed/applied the command.
|
|
ProviderPhase string
|
|
// ExecutionPending is set only when the provider accepted a command that
|
|
// is expected to start autonomous Goal execution.
|
|
ExecutionPending bool
|
|
}
|
|
|
|
// GoalApplyInput carries the durable control identity allocated above the
|
|
// provider boundary. It is not a Turn identity: providers copy it onto any
|
|
// Turn they later create so activity can be traced back to the goal revision.
|
|
type GoalApplyInput struct {
|
|
Action GoalControlAction
|
|
Objective string
|
|
OperationID string
|
|
Revision int64
|
|
RepairEpoch int64
|
|
// SubmissionMetadata links a composer-originated control to its turnless
|
|
// audit message. It is intentionally independent of Turn identity.
|
|
SubmissionMetadata map[string]any
|
|
}
|
|
|
|
// GoalAdapter is the semantic provider boundary for goal state. Providers
|
|
// return observations and evidence; only the upper persistence layer decides
|
|
// desired/observed convergence and writes durable state.
|
|
type GoalAdapter interface {
|
|
GoalCapabilities() GoalAdapterCapabilities
|
|
ApplyGoal(ctx context.Context, session Session, input GoalApplyInput) (GoalAdapterResult, error)
|
|
ReconcileGoal(ctx context.Context, session Session) (GoalAdapterResult, error)
|
|
NormalizeGoalObservation(raw map[string]any) map[string]any
|
|
// ExecGoalControl handles a typed "/goal …" prompt as a session-level
|
|
// operation. The controller calls it before allocating a turn identity;
|
|
// audit messages emitted by the adapter therefore remain turnless.
|
|
ExecGoalControl(ctx context.Context, session Session, content []PromptContentBlock, displayPrompt string) (events []activityshared.Event, handled bool, err error)
|
|
}
|
|
|
|
type PermissionModeAdapter interface {
|
|
ApplyPermissionMode(context.Context, Session) error
|
|
}
|
|
|
|
type LiveSettingsAdapter interface {
|
|
ApplySessionSettings(context.Context, Session, SessionSettingsPatch) error
|
|
}
|
|
|
|
// SessionSettingsValidationAdapter validates a settings patch against live
|
|
// runtime facts before the controller mutates or applies any part of it.
|
|
type SessionSettingsValidationAdapter interface {
|
|
ValidateSessionSettings(Session, SessionSettingsPatch) error
|
|
}
|
|
|
|
type NewSessionSettingsAdapter interface {
|
|
RequiresNewSessionForSettings(Session, SessionSettingsPatch) bool
|
|
}
|