Add daemon-attached goal recovery operator

Resume goals after transient API failures with persisted exponential backoff while respecting pauses and interruptions.
This commit is contained in:
ms2k 2026-10-18 14:01:07 +09:00
commit e4b84d9802

113
codex-keep/protocol.go Normal file
View file

@ -0,0 +1,113 @@
package main
import "encoding/json"
type rpcError struct {
Code int `json:"code"`
Message string `json:"message"`
}
func (e *rpcError) Error() string { return e.Message }
type message struct {
ID json.RawMessage `json:"id"`
Method string `json:"method"`
Params json.RawMessage `json:"params"`
Result json.RawMessage `json:"result"`
Error *rpcError `json:"error"`
}
type turnError struct {
Info errorInfo `json:"codexErrorInfo"`
}
type errorInfo struct {
Kind string
HTTP *int
}
func (e errorInfo) MarshalJSON() ([]byte, error) {
if e.HTTP == nil {
return json.Marshal(e.Kind)
}
return json.Marshal(map[string]struct {
HTTP *int `json:"httpStatusCode"`
}{e.Kind: {e.HTTP}})
}
func (e *errorInfo) UnmarshalJSON(data []byte) error {
*e = errorInfo{}
if string(data) == "null" {
return nil
}
if json.Unmarshal(data, &e.Kind) == nil {
return nil
}
var variants map[string]struct {
HTTP *int `json:"httpStatusCode"`
}
if err := json.Unmarshal(data, &variants); err != nil {
return err
}
if len(variants) == 1 {
for kind, value := range variants {
e.Kind, e.HTTP = kind, value.HTTP
}
}
return nil
}
func (e errorInfo) retryable() bool {
switch e.Kind {
case "serverOverloaded", "rateLimitExceeded", "internalServerError", "flexUnavailable":
return true
case "httpConnectionFailed", "responseStreamConnectionFailed", "responseStreamDisconnected", "responseTooManyFailedAttempts":
return e.HTTP == nil || *e.HTTP == 408 || *e.HTTP == 429 || *e.HTTP >= 500 && *e.HTTP <= 599
}
return false
}
type turn struct {
ID string `json:"id"`
Status string `json:"status"`
Error *turnError `json:"error,omitempty"`
}
type params struct {
ThreadID string `json:"threadId"`
Thread struct {
ID string `json:"id"`
} `json:"thread"`
Turn turn `json:"turn"`
Goal *goal `json:"goal"`
}
type goalResult struct {
Goal *goal `json:"goal"`
}
type turnsResult struct {
Data []turn `json:"data"`
}
type loadedResult struct {
Data []string `json:"data"`
Next string `json:"nextCursor"`
}
type threadParams struct {
ThreadID string `json:"threadId"`
}
type setParams struct {
ThreadID string `json:"threadId"`
Status goalStatus `json:"status"`
Origin string `json:"origin"`
}
type initParams struct {
Client struct {
Name string `json:"name"`
Version string `json:"version"`
} `json:"clientInfo"`
Capabilities struct {
Experimental bool `json:"experimentalApi"`
} `json:"capabilities"`
}