package cli import ( "context" "fmt" "os" "regexp" "iop/apps/node/internal/runtime" "iop/packages/go/config" ) var antigravityConversationPatterns = []*regexp.Regexp{ regexp.MustCompile(`(?m)Created conversation (` + uuidPattern + `)`), regexp.MustCompile(`(?m)Streaming conversation (` + uuidPattern + `)`), regexp.MustCompile(`(?m)Print mode: conversation=(` + uuidPattern + `)`), regexp.MustCompile(`(?m)conversationID="(` + uuidPattern + `)"`), } const uuidPattern = `[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}` func (e *antigravityExecutor) Execute(ctx context.Context, spec runtime.ExecutionSpec, profile config.CLIProfileConf, sink runtime.EventSink) error { if len(profile.ResumeArgs) == 0 { return fmt.Errorf("cli adapter: antigravity-print mode requires resume_args in profile %q", spec.Target) } sess, err := e.resolveAntigravitySession(spec) if err != nil { return err } sess.mu.Lock() defer sess.mu.Unlock() logFile, cleanup, err := createAntigravityLogFile() if err != nil { return err } defer cleanup() prompt := extractPrompt(spec.Input) args := antigravityPrintArgs(profile, sess.conversationID, logFile, prompt) output, err := e.cli.executeCommand(ctx, spec, profile, args, prompt, sink) if err != nil { return err } logOutput, readErr := os.ReadFile(logFile) if readErr != nil { return fmt.Errorf("cli adapter: read antigravity log: %w", readErr) } if conversationID := parseAntigravityConversationID(string(logOutput) + output); conversationID != "" { sess.conversationID = conversationID } if sess.conversationID == "" { return fmt.Errorf("cli adapter: antigravity-print did not report a conversation id") } return nil } func (e *antigravityExecutor) resolveAntigravitySession(spec runtime.ExecutionSpec) (*antigravitySession, error) { target := cliTargetName(spec) key := newSessionKey(spec) e.mu.Lock() defer e.mu.Unlock() if sess, ok := e.sessions[key]; ok { return sess, nil } if spec.SessionMode == runtime.SessionModeRequireExisting { return nil, fmt.Errorf("cli adapter: no antigravity conversation for target %q session %q", target, key.sessionID) } sess := &antigravitySession{key: key} e.sessions[key] = sess return sess, nil } func (e *antigravityExecutor) Sessions() []sessionListEntry { e.mu.Lock() defer e.mu.Unlock() snaps := make([]sessionListEntry, 0, len(e.sessions)) for k := range e.sessions { snaps = append(snaps, sessionListEntry{"antigravity-print", k.target, k.sessionID, k.workspace}) } return snaps } // Terminate removes every workspace variant of the given target/sessionID. func (e *antigravityExecutor) Terminate(_ context.Context, target, sessionID string) (bool, error) { sid := normalizeSessionID(sessionID) e.mu.Lock() defer e.mu.Unlock() removed := false for k := range e.sessions { if k.target == target && k.sessionID == sid { delete(e.sessions, k) removed = true } } return removed, nil } func (e *antigravityExecutor) Stop(ctx context.Context) error { e.mu.Lock() e.sessions = make(map[sessionKey]*antigravitySession) e.mu.Unlock() return nil } func antigravityPrintArgs(profile config.CLIProfileConf, conversationID, logFile, prompt string) []string { args := []string{"--log-file", logFile} if conversationID == "" { args = append(args, profile.Args...) } else { args = append(args, removeAntigravityPrintFlag(profile.ResumeArgs)...) args = append(args, conversationID) args = append(args, "--print") } return append(args, prompt) } func removeAntigravityPrintFlag(args []string) []string { filtered := make([]string, 0, len(args)) for _, arg := range args { switch arg { case "--print", "--prompt", "-p": continue default: filtered = append(filtered, arg) } } return filtered } func createAntigravityLogFile() (string, func(), error) { f, err := os.CreateTemp("", "iop-antigravity-*.log") if err != nil { return "", nil, fmt.Errorf("cli adapter: create antigravity log file: %w", err) } path := f.Name() if err := f.Close(); err != nil { _ = os.Remove(path) return "", nil, fmt.Errorf("cli adapter: close antigravity log file: %w", err) } return path, func() { _ = os.Remove(path) }, nil } func parseAntigravityConversationID(s string) string { var id string for _, re := range antigravityConversationPatterns { for _, match := range re.FindAllStringSubmatch(s, -1) { if len(match) >= 2 { id = match[1] } } } return id }