crewly 1.20.197 → 1.20.198
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/roles/orchestrator/prompt.md +8 -9
- package/config/roles/team-leader/prompt.md +6 -0
- package/config/skills/agent/computer-use/SKILL.md +16 -0
- package/config/skills/agent/core/ask-owner/SKILL.md +14 -0
- package/config/skills/agent/core/ask-owner/execute.sh +19 -1
- package/config/skills/agent/core/ask-owner/execute.test.sh +12 -0
- package/config/skills/agent/core/gmail-send/SKILL.md +16 -0
- package/config/skills/agent/core/reply-channel/SKILL.md +16 -0
- package/config/skills/agent/core/slack-post/SKILL.md +16 -0
- package/config/skills/agent/core/whatsapp-send/SKILL.md +16 -0
- package/config/skills/agent/remote-browser/SKILL.md +43 -7
- package/config/skills/registry.json +7 -7
- package/dist/backend/backend/src/constants.d.ts +143 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +159 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts +12 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.js +118 -3
- package/dist/backend/backend/src/controllers/browser/browser.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.js +5 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js +37 -52
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/team/team.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/team/team.controller.js +13 -0
- package/dist/backend/backend/src/controllers/team/team.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts +17 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +102 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-heartbeat-monitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-heartbeat-monitor.service.js +5 -8
- package/dist/backend/backend/src/services/agent/agent-heartbeat-monitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +12 -0
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +275 -241
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/context-window-monitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/context-window-monitor.service.js +5 -6
- package/dist/backend/backend/src/services/agent/context-window-monitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/control-plane-guard.service.d.ts +6 -0
- package/dist/backend/backend/src/services/agent/control-plane-guard.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/control-plane-guard.service.js +2 -1
- package/dist/backend/backend/src/services/agent/control-plane-guard.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/fresh-task-conversation.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/fresh-task-conversation.service.js +12 -8
- package/dist/backend/backend/src/services/agent/fresh-task-conversation.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/gemini-runtime.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/gemini-runtime.service.js +32 -27
- package/dist/backend/backend/src/services/agent/gemini-runtime.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/owner-stopped.registry.d.ts +44 -0
- package/dist/backend/backend/src/services/agent/owner-stopped.registry.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/owner-stopped.registry.js +54 -0
- package/dist/backend/backend/src/services/agent/owner-stopped.registry.js.map +1 -0
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js +4 -4
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js +4 -8
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/decision-rights.module.d.ts +19 -4
- package/dist/backend/backend/src/services/ai/prompt-modules/decision-rights.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/decision-rights.module.js +28 -5
- package/dist/backend/backend/src/services/ai/prompt-modules/decision-rights.module.js.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-approval.service.d.ts +2 -2
- package/dist/backend/backend/src/services/browser/browser-approval.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-approval.service.js +13 -2
- package/dist/backend/backend/src/services/browser/browser-approval.service.js.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-outbound-guard.d.ts +214 -0
- package/dist/backend/backend/src/services/browser/browser-outbound-guard.d.ts.map +1 -0
- package/dist/backend/backend/src/services/browser/browser-outbound-guard.js +466 -0
- package/dist/backend/backend/src/services/browser/browser-outbound-guard.js.map +1 -0
- package/dist/backend/backend/src/services/browser/browser-session.service.d.ts +23 -2
- package/dist/backend/backend/src/services/browser/browser-session.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-session.service.js +110 -62
- package/dist/backend/backend/src/services/browser/browser-session.service.js.map +1 -1
- package/dist/backend/backend/src/services/browser/held-action-store.d.ts +4 -0
- package/dist/backend/backend/src/services/browser/held-action-store.d.ts.map +1 -1
- package/dist/backend/backend/src/services/browser/held-action-store.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-initializer.js +6 -8
- package/dist/backend/backend/src/services/cloud/cloud-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/input-blocked-retry.service.d.ts +129 -0
- package/dist/backend/backend/src/services/messaging/input-blocked-retry.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/messaging/input-blocked-retry.service.js +196 -0
- package/dist/backend/backend/src/services/messaging/input-blocked-retry.service.js.map +1 -0
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +42 -0
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js +88 -10
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
- package/dist/backend/backend/src/services/monitoring/agent-hook-state.d.ts.map +1 -1
- package/dist/backend/backend/src/services/monitoring/agent-hook-state.js +5 -0
- package/dist/backend/backend/src/services/monitoring/agent-hook-state.js.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/orchestrator-heartbeat-monitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/orchestrator-heartbeat-monitor.service.js +5 -9
- package/dist/backend/backend/src/services/orchestrator/orchestrator-heartbeat-monitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/session/pty/pty-session-backend.d.ts +11 -0
- package/dist/backend/backend/src/services/session/pty/pty-session-backend.d.ts.map +1 -1
- package/dist/backend/backend/src/services/session/pty/pty-session-backend.js +14 -0
- package/dist/backend/backend/src/services/session/pty/pty-session-backend.js.map +1 -1
- package/dist/backend/backend/src/services/session/pty/pty-terminal-buffer.d.ts +37 -0
- package/dist/backend/backend/src/services/session/pty/pty-terminal-buffer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/session/pty/pty-terminal-buffer.js +78 -0
- package/dist/backend/backend/src/services/session/pty/pty-terminal-buffer.js.map +1 -1
- package/dist/backend/backend/src/services/session/session-backend.interface.d.ts +13 -0
- package/dist/backend/backend/src/services/session/session-backend.interface.d.ts.map +1 -1
- package/dist/backend/backend/src/services/session/session-backend.interface.js.map +1 -1
- package/dist/backend/backend/src/services/session/session-command-helper.d.ts +118 -105
- package/dist/backend/backend/src/services/session/session-command-helper.d.ts.map +1 -1
- package/dist/backend/backend/src/services/session/session-command-helper.js +267 -298
- package/dist/backend/backend/src/services/session/session-command-helper.js.map +1 -1
- package/dist/backend/backend/src/services/session/tui-input-guard.d.ts +144 -0
- package/dist/backend/backend/src/services/session/tui-input-guard.d.ts.map +1 -0
- package/dist/backend/backend/src/services/session/tui-input-guard.js +312 -0
- package/dist/backend/backend/src/services/session/tui-input-guard.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +14 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +31 -0
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +82 -4
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/backend/backend/src/services/trace/trace.types.d.ts +2 -2
- package/dist/backend/backend/src/services/trace/trace.types.d.ts.map +1 -1
- package/dist/backend/backend/src/services/trace/trace.types.js +4 -0
- package/dist/backend/backend/src/services/trace/trace.types.js.map +1 -1
- package/dist/backend/backend/src/services/trace/turn-origin.d.ts +70 -0
- package/dist/backend/backend/src/services/trace/turn-origin.d.ts.map +1 -0
- package/dist/backend/backend/src/services/trace/turn-origin.js +198 -0
- package/dist/backend/backend/src/services/trace/turn-origin.js.map +1 -0
- package/dist/backend/backend/src/types/slack.types.d.ts +8 -0
- package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/slack.types.js.map +1 -1
- package/dist/backend/backend/src/utils/terminal-string-ops.d.ts.map +1 -1
- package/dist/backend/backend/src/utils/terminal-string-ops.js +3 -2
- package/dist/backend/backend/src/utils/terminal-string-ops.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +143 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +159 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts +12 -0
- package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.js +275 -241
- package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/context-window-monitor.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/context-window-monitor.service.js +5 -6
- package/dist/cli/backend/src/services/agent/context-window-monitor.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/control-plane-guard.service.d.ts +6 -0
- package/dist/cli/backend/src/services/agent/control-plane-guard.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/control-plane-guard.service.js +2 -1
- package/dist/cli/backend/src/services/agent/control-plane-guard.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/fresh-task-conversation.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/fresh-task-conversation.service.js +12 -8
- package/dist/cli/backend/src/services/agent/fresh-task-conversation.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/gemini-runtime.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/gemini-runtime.service.js +32 -27
- package/dist/cli/backend/src/services/agent/gemini-runtime.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js +4 -4
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js +4 -8
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.d.ts +19 -4
- package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.js +28 -5
- package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.js.map +1 -1
- package/dist/cli/backend/src/services/messaging/input-blocked-retry.service.d.ts +129 -0
- package/dist/cli/backend/src/services/messaging/input-blocked-retry.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/input-blocked-retry.service.js +196 -0
- package/dist/cli/backend/src/services/messaging/input-blocked-retry.service.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +42 -0
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js +88 -10
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
- package/dist/cli/backend/src/services/session/pty/pty-session-backend.d.ts +11 -0
- package/dist/cli/backend/src/services/session/pty/pty-session-backend.d.ts.map +1 -1
- package/dist/cli/backend/src/services/session/pty/pty-session-backend.js +14 -0
- package/dist/cli/backend/src/services/session/pty/pty-session-backend.js.map +1 -1
- package/dist/cli/backend/src/services/session/pty/pty-terminal-buffer.d.ts +37 -0
- package/dist/cli/backend/src/services/session/pty/pty-terminal-buffer.d.ts.map +1 -1
- package/dist/cli/backend/src/services/session/pty/pty-terminal-buffer.js +78 -0
- package/dist/cli/backend/src/services/session/pty/pty-terminal-buffer.js.map +1 -1
- package/dist/cli/backend/src/services/session/session-backend.interface.d.ts +13 -0
- package/dist/cli/backend/src/services/session/session-backend.interface.d.ts.map +1 -1
- package/dist/cli/backend/src/services/session/session-backend.interface.js.map +1 -1
- package/dist/cli/backend/src/services/session/session-command-helper.d.ts +118 -105
- package/dist/cli/backend/src/services/session/session-command-helper.d.ts.map +1 -1
- package/dist/cli/backend/src/services/session/session-command-helper.js +267 -298
- package/dist/cli/backend/src/services/session/session-command-helper.js.map +1 -1
- package/dist/cli/backend/src/services/session/tui-input-guard.d.ts +144 -0
- package/dist/cli/backend/src/services/session/tui-input-guard.d.ts.map +1 -0
- package/dist/cli/backend/src/services/session/tui-input-guard.js +312 -0
- package/dist/cli/backend/src/services/session/tui-input-guard.js.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +14 -0
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +31 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +82 -4
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/cli/backend/src/services/trace/trace.types.d.ts +2 -2
- package/dist/cli/backend/src/services/trace/trace.types.d.ts.map +1 -1
- package/dist/cli/backend/src/services/trace/trace.types.js +4 -0
- package/dist/cli/backend/src/services/trace/trace.types.js.map +1 -1
- package/dist/cli/backend/src/services/trace/turn-origin.d.ts +70 -0
- package/dist/cli/backend/src/services/trace/turn-origin.d.ts.map +1 -0
- package/dist/cli/backend/src/services/trace/turn-origin.js +198 -0
- package/dist/cli/backend/src/services/trace/turn-origin.js.map +1 -0
- package/dist/cli/backend/src/types/slack.types.d.ts +8 -0
- package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/slack.types.js.map +1 -1
- package/dist/cli/backend/src/utils/terminal-string-ops.d.ts.map +1 -1
- package/dist/cli/backend/src/utils/terminal-string-ops.js +3 -2
- package/dist/cli/backend/src/utils/terminal-string-ops.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/backend/src/services/agent/agent-suspend.service.d.ts +0 -118
- package/dist/cli/backend/src/services/agent/agent-suspend.service.d.ts.map +0 -1
- package/dist/cli/backend/src/services/agent/agent-suspend.service.js +0 -304
- package/dist/cli/backend/src/services/agent/agent-suspend.service.js.map +0 -1
- package/dist/cli/backend/src/services/agent/disk-cleanup.service.d.ts +0 -100
- package/dist/cli/backend/src/services/agent/disk-cleanup.service.d.ts.map +0 -1
- package/dist/cli/backend/src/services/agent/disk-cleanup.service.js +0 -149
- package/dist/cli/backend/src/services/agent/disk-cleanup.service.js.map +0 -1
|
@@ -11,7 +11,9 @@ import { RuntimeServiceFactory } from './runtime-service.factory.js';
|
|
|
11
11
|
import { OrcReplyRouteService } from '../orc/orc-reply-route.service.js';
|
|
12
12
|
import { registerInProcessRuntime, unregisterInProcessRuntime, } from './crewly-agent/in-process-runtime-registry.js';
|
|
13
13
|
import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
|
|
14
|
-
import { CREWLY_CONSTANTS, ENV_CONSTANTS, OWNER_AUTH_CONSTANTS, AGENT_TIMEOUTS, ORCHESTRATOR_SESSION_NAME, ORCHESTRATOR_ROLE, RUNTIME_TYPES, SESSION_COMMAND_DELAYS, SESSION_RECREATION_CONSTANTS, EVENT_DELIVERY_CONSTANTS, TERMINAL_PATTERNS, GEMINI_SHELL_MODE_CONSTANTS, GEMINI_STUCK_CONNECTIVITY_PATTERN, GEMINI_ERROR_STATE_CONSTANTS, REGISTRATION_DELIVERY_CONSTANTS, ORC_CONVERSATION_CONSTANTS, SLACK_THREAD_KEY_CONSTANTS, OPEN_ITEMS_CONSTANTS, SPEND_CAP_CONSTANTS, } from '../../constants.js';
|
|
14
|
+
import { CREWLY_CONSTANTS, ENV_CONSTANTS, OWNER_AUTH_CONSTANTS, AGENT_TIMEOUTS, ORCHESTRATOR_SESSION_NAME, ORCHESTRATOR_ROLE, RUNTIME_TYPES, SESSION_COMMAND_DELAYS, SESSION_RECREATION_CONSTANTS, EVENT_DELIVERY_CONSTANTS, TERMINAL_PATTERNS, GEMINI_SHELL_MODE_CONSTANTS, GEMINI_STUCK_CONNECTIVITY_PATTERN, GEMINI_ERROR_STATE_CONSTANTS, REGISTRATION_DELIVERY_CONSTANTS, ORC_CONVERSATION_CONSTANTS, SLACK_THREAD_KEY_CONSTANTS, OPEN_ITEMS_CONSTANTS, SPEND_CAP_CONSTANTS, RUNTIME_INPUT_SAFETY, TUI_INPUT_GUARD, } from '../../constants.js';
|
|
15
|
+
import { TuiInputGuardError } from '../session/tui-input-guard.js';
|
|
16
|
+
import { InputBlockedRetryService } from '../messaging/input-blocked-retry.service.js';
|
|
15
17
|
import { extractSlackThreadKeys, formatSlackThreadKey } from '../slack/slack-thread-key.js';
|
|
16
18
|
import { delay } from '../../utils/async.utils.js';
|
|
17
19
|
import { buildRuntimeModelFlags } from '../../utils/runtime-model-flags.utils.js';
|
|
@@ -31,7 +33,6 @@ import { InFlightTurnTracker } from '../restart/in-flight-turn-tracker.service.j
|
|
|
31
33
|
import { AgentTurnStateService } from '../monitoring/agent-turn-state.js';
|
|
32
34
|
import { FreshTaskConversationService } from './fresh-task-conversation.service.js';
|
|
33
35
|
import { RestartDrainService } from '../restart/restart-drain.service.js';
|
|
34
|
-
import { AgentSuspendService } from './agent-suspend.service.js';
|
|
35
36
|
import { PromptBuilderService, buildModuleConfigFromTeamMember, memberSkillsJson, } from '../ai/prompt-builder.service.js';
|
|
36
37
|
import { PromptAssemblyService } from '../ai/prompt-modules/prompt-assembly.service.js';
|
|
37
38
|
import { renderActiveWorkSection } from '../ai/prompt-modules/active-work.module.js';
|
|
@@ -157,6 +158,12 @@ export class AgentRegistrationService {
|
|
|
157
158
|
// Per-session hash of last sent message to prevent duplicate writes (#128).
|
|
158
159
|
// Key: sessionName, Value: { hash, sentAt }
|
|
159
160
|
lastSentMessageHash = new Map();
|
|
161
|
+
// Last input-guard refusal per session during the current delivery: the
|
|
162
|
+
// box was unreadable or held text we did not write. Such a message is
|
|
163
|
+
// kept queued and retried, never dropped (2026-10-03 review).
|
|
164
|
+
lastGuardRefusal = new Map();
|
|
165
|
+
// Consecutive guard refusals per session, for escalation.
|
|
166
|
+
guardRefusalCount = new Map();
|
|
160
167
|
// In-process Crewly Agent runtimes (sessionName → runtime instance).
|
|
161
168
|
// Used for crewly-agent runtimeType agents that run without PTY sessions.
|
|
162
169
|
inProcessRuntimes = new Map();
|
|
@@ -178,12 +185,14 @@ export class AgentRegistrationService {
|
|
|
178
185
|
RuntimeExitMonitorService.getInstance().setOnExitDetectedCallback((sessionName) => {
|
|
179
186
|
this.cancelPendingRegistration(sessionName);
|
|
180
187
|
this.unregisterTuiSession(sessionName);
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
188
|
+
// Queued messages are kept when the runtime exits (crewly#1014
|
|
189
|
+
// review #4): they are someone's undelivered words. They go out
|
|
190
|
+
// when the agent is started again (registration flushes the
|
|
191
|
+
// queue; an agent that went down with messages queued is woken
|
|
192
|
+
// for them), and the queue reports anything it drops.
|
|
193
|
+
const pending = SubAgentMessageQueue.getInstance().getQueueSize(sessionName);
|
|
194
|
+
if (pending > 0) {
|
|
195
|
+
this.logger.warn('Runtime exited with messages still queued — keeping them for its next start', { sessionName, pending });
|
|
187
196
|
}
|
|
188
197
|
InFlightTurnTracker.getInstance().markTurnComplete(sessionName, 'runtime exited');
|
|
189
198
|
// Its hook state belonged to the process that just exited.
|
|
@@ -1279,8 +1288,16 @@ export class AgentRegistrationService {
|
|
|
1279
1288
|
* Tries to reset the runtime session and start fresh
|
|
1280
1289
|
*/
|
|
1281
1290
|
async tryCleanupAndReinit(sessionName, role, timeout, projectPath, memberId, runtimeType = RUNTIME_TYPES.CLAUDE_CODE, runtimeFlags, additionalAllowlistPaths) {
|
|
1282
|
-
// Clear
|
|
1283
|
-
|
|
1291
|
+
// Clear the shell's command line before the launch command is typed —
|
|
1292
|
+
// only when no runtime input box is on screen (a plain shell), and with
|
|
1293
|
+
// Ctrl+U alone: never Ctrl+C into a live runtime, never wipe an agent
|
|
1294
|
+
// box the harness cannot prove is its own (crewly#1014 review #3).
|
|
1295
|
+
{
|
|
1296
|
+
const helper = await this.getSessionHelper();
|
|
1297
|
+
if (helper.readInputBox(sessionName, '', 'before-write').state === 'unknown') {
|
|
1298
|
+
await helper.sendKey(sessionName, 'C-u');
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1284
1301
|
// Conversation id: preset for Claude Code (--session-id), resumed on
|
|
1285
1302
|
// restore (--resume / `codex resume`), discovered after launch for Codex.
|
|
1286
1303
|
const effectiveFlags = runtimeFlags ? [...runtimeFlags] : [];
|
|
@@ -1347,15 +1364,9 @@ export class AgentRegistrationService {
|
|
|
1347
1364
|
// Drain stale terminal escape sequences (e.g. DA1 [?1;2c) that may have
|
|
1348
1365
|
// arrived during postInitialize commands, so they don't leak into the prompt input
|
|
1349
1366
|
await delay(500);
|
|
1350
|
-
//
|
|
1351
|
-
//
|
|
1352
|
-
//
|
|
1353
|
-
// prompt. Ctrl+C at an empty Gemini CLI prompt triggers /quit and exits
|
|
1354
|
-
// the CLI. Escape defocuses the TUI. Ctrl+U is ignored. The delay(500)
|
|
1355
|
-
// above is sufficient to drain stale escape sequences.
|
|
1356
|
-
if (runtimeType === RUNTIME_TYPES.CLAUDE_CODE) {
|
|
1357
|
-
await (await this.getSessionHelper()).clearCurrentCommandLine(sessionName);
|
|
1358
|
-
}
|
|
1367
|
+
// No blind Ctrl+C / Ctrl+U after post-initialization (crewly#1014):
|
|
1368
|
+
// the guarded writer types only into a box it reads as empty, so
|
|
1369
|
+
// stale input is refused, never wiped unseen.
|
|
1359
1370
|
}
|
|
1360
1371
|
catch (postInitError) {
|
|
1361
1372
|
this.logger.warn('Post-initialization hook failed (non-fatal)', {
|
|
@@ -1909,14 +1920,7 @@ export class AgentRegistrationService {
|
|
|
1909
1920
|
// Drain stale terminal escape sequences (e.g. DA1 [?1;2c) that may have
|
|
1910
1921
|
// arrived during postInitialize commands, so they don't leak into the prompt input
|
|
1911
1922
|
await delay(500);
|
|
1912
|
-
//
|
|
1913
|
-
// Gemini CLI (Ink TUI): Do NOT send any cleanup keystrokes.
|
|
1914
|
-
// Escape defocuses the Ink TUI input permanently. Ctrl+C at empty
|
|
1915
|
-
// prompt triggers /quit. Ctrl+U is ignored. The delay above is
|
|
1916
|
-
// sufficient to drain stale escape sequences.
|
|
1917
|
-
if (runtimeType === RUNTIME_TYPES.CLAUDE_CODE) {
|
|
1918
|
-
await (await this.getSessionHelper()).clearCurrentCommandLine(sessionName);
|
|
1919
|
-
}
|
|
1923
|
+
// No blind Ctrl+C / Ctrl+U (crewly#1014): see post-initialization above.
|
|
1920
1924
|
}
|
|
1921
1925
|
catch (postInitError) {
|
|
1922
1926
|
this.logger.warn('Post-initialization hook failed after recreation (non-fatal)', {
|
|
@@ -2637,11 +2641,12 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
2637
2641
|
maxRetries,
|
|
2638
2642
|
});
|
|
2639
2643
|
try {
|
|
2640
|
-
// Step 1:
|
|
2644
|
+
// Step 1 (crewly#1014): no blind Ctrl+C / Ctrl+U before the
|
|
2645
|
+
// registration prompt — the guarded writer refuses a box that is
|
|
2646
|
+
// not empty, and only our own leftover text is ever cleared.
|
|
2641
2647
|
if (!skipInitialCleanup || attempt > 1) {
|
|
2642
|
-
await (await this.getSessionHelper()).clearCurrentCommandLine(sessionName);
|
|
2643
2648
|
await delay(500);
|
|
2644
|
-
this.logger.debug('
|
|
2649
|
+
this.logger.debug('Settled before the registration prompt (no clearing keys)', {
|
|
2645
2650
|
sessionName,
|
|
2646
2651
|
attempt,
|
|
2647
2652
|
});
|
|
@@ -2893,6 +2898,8 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
2893
2898
|
return {
|
|
2894
2899
|
...harnessEnvForAgents(process.env, runtimeType),
|
|
2895
2900
|
...(account ? claudeAccountEnv(account) : {}),
|
|
2901
|
+
// No predicted "user" text in the input box (2026-10-03 incident).
|
|
2902
|
+
...(runtimeType === RUNTIME_TYPES.CLAUDE_CODE ? RUNTIME_INPUT_SAFETY.CLAUDE_CODE_ENV : {}),
|
|
2896
2903
|
[ENV_CONSTANTS.CREWLY_SESSION_NAME]: sessionName,
|
|
2897
2904
|
// The agent's credential (#999): every skill sends it, and a request
|
|
2898
2905
|
// carrying it is this agent — never the owner.
|
|
@@ -4079,7 +4086,45 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
4079
4086
|
// a finished turn. After the write the echo makes it look busy.
|
|
4080
4087
|
const turnTracker = InFlightTurnTracker.getInstance();
|
|
4081
4088
|
turnTracker.settle(sessionName);
|
|
4089
|
+
this.lastGuardRefusal.delete(sessionName);
|
|
4082
4090
|
const delivered = await this.sendMessageWithRetry(sessionName, message, maxDeliveryAttempts, runtimeType);
|
|
4091
|
+
const refusal = this.lastGuardRefusal.get(sessionName);
|
|
4092
|
+
if (delivered) {
|
|
4093
|
+
// Never re-send a confirmed delivery (the scanner's re-queue).
|
|
4094
|
+
for (const e of this.sentMessageTracker.get(sessionName) ?? [])
|
|
4095
|
+
if (e.message === message)
|
|
4096
|
+
e.confirmed = true;
|
|
4097
|
+
this.guardRefusalCount.delete(sessionName);
|
|
4098
|
+
InputBlockedRetryService.getInstance().noteDelivered(sessionName);
|
|
4099
|
+
}
|
|
4100
|
+
if (!delivered && refusal) {
|
|
4101
|
+
// The harness would not type into, or submit, the agent's input box
|
|
4102
|
+
// (unreadable, or holding text it did not write). Keep the message
|
|
4103
|
+
// queued — it is retried when the agent is idle — and say so.
|
|
4104
|
+
SubAgentMessageQueue.getInstance().enqueue(sessionName, message);
|
|
4105
|
+
// Retried on a timer with backoff, and the owner told if it stays blocked.
|
|
4106
|
+
InputBlockedRetryService.getInstance().noteRefusal(sessionName, {
|
|
4107
|
+
state: refusal.reading.state,
|
|
4108
|
+
inputLength: refusal.reading.text.length,
|
|
4109
|
+
message,
|
|
4110
|
+
});
|
|
4111
|
+
const count = (this.guardRefusalCount.get(sessionName) ?? 0) + 1;
|
|
4112
|
+
this.guardRefusalCount.set(sessionName, count);
|
|
4113
|
+
const log = count >= TUI_INPUT_GUARD.ESCALATE_AFTER_REFUSALS ? this.logger.error.bind(this.logger) : this.logger.warn.bind(this.logger);
|
|
4114
|
+
log('Message held: the agent input box is unreadable or holds text the harness did not write — queued for retry', {
|
|
4115
|
+
sessionName,
|
|
4116
|
+
stage: refusal.stage,
|
|
4117
|
+
state: refusal.reading.state,
|
|
4118
|
+
layout: refusal.reading.layout,
|
|
4119
|
+
inputPreview: refusal.reading.text.slice(0, 80),
|
|
4120
|
+
consecutiveRefusals: count,
|
|
4121
|
+
});
|
|
4122
|
+
return {
|
|
4123
|
+
success: true,
|
|
4124
|
+
queued: true,
|
|
4125
|
+
message: '[INPUT_NOT_OURS] Message queued: the agent\'s input box is unreadable or holds text the harness did not write. It will be retried.',
|
|
4126
|
+
};
|
|
4127
|
+
}
|
|
4083
4128
|
if (delivered) {
|
|
4084
4129
|
turnTracker.recordDelivery(sessionName, message, 'pty');
|
|
4085
4130
|
noteTurnDelivery(sessionName, message, 'pty');
|
|
@@ -4294,6 +4339,37 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
4294
4339
|
* @returns true if message was delivered successfully
|
|
4295
4340
|
*/
|
|
4296
4341
|
async sendMessageWithRetry(sessionName, message, maxAttempts = 3, runtimeType = RUNTIME_TYPES.CLAUDE_CODE) {
|
|
4342
|
+
const delivered = await this.sendMessageWithRetryAttempts(sessionName, message, maxAttempts, runtimeType);
|
|
4343
|
+
if (!delivered)
|
|
4344
|
+
return false;
|
|
4345
|
+
// Whatever the checks above concluded (a fast reply, a weak output
|
|
4346
|
+
// change), a collapsed paste of ours still in the box means its Enter
|
|
4347
|
+
// was lost: press Enter once and look again (crewly#1014 review #4).
|
|
4348
|
+
try {
|
|
4349
|
+
const outcome = await (await this.getSessionHelper()).ensureOwnPasteSubmitted(sessionName);
|
|
4350
|
+
if (outcome === 'stuck') {
|
|
4351
|
+
this.logger.warn('Delivery looked confirmed but our paste is still in the input box — not delivered', { sessionName });
|
|
4352
|
+
return false;
|
|
4353
|
+
}
|
|
4354
|
+
if (outcome === 'submitted') {
|
|
4355
|
+
this.logger.warn('Delivery looked confirmed but its Enter was lost — submitted it now', { sessionName });
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
catch {
|
|
4359
|
+
// No session helper / session gone: the checks above stand.
|
|
4360
|
+
}
|
|
4361
|
+
return true;
|
|
4362
|
+
}
|
|
4363
|
+
/**
|
|
4364
|
+
* The delivery attempts behind {@link sendMessageWithRetry}.
|
|
4365
|
+
*
|
|
4366
|
+
* @param sessionName - Target session
|
|
4367
|
+
* @param message - Message to deliver
|
|
4368
|
+
* @param maxAttempts - Attempts before giving up
|
|
4369
|
+
* @param runtimeType - The session's runtime
|
|
4370
|
+
* @returns Whether the checks saw the message accepted
|
|
4371
|
+
*/
|
|
4372
|
+
async sendMessageWithRetryAttempts(sessionName, message, maxAttempts = 3, runtimeType = RUNTIME_TYPES.CLAUDE_CODE) {
|
|
4297
4373
|
const sessionHelper = await this.getSessionHelper();
|
|
4298
4374
|
const isClaudeCode = runtimeType === RUNTIME_TYPES.CLAUDE_CODE;
|
|
4299
4375
|
const isCodexCli = runtimeType === RUNTIME_TYPES.CODEX_CLI;
|
|
@@ -4395,12 +4471,9 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
4395
4471
|
// This may restore the internal focus state. Then Tab + Enter
|
|
4396
4472
|
// for focus cycling and overlay dismissal.
|
|
4397
4473
|
if (isClaudeCode) {
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
await sessionHelper.sendCtrlC(sessionName);
|
|
4402
|
-
await delay(300);
|
|
4403
|
-
}
|
|
4474
|
+
// No Ctrl+C on retry (2026-10-03 review): it wipes whatever is in
|
|
4475
|
+
// the box — possibly the owner's half-typed text — and cancels a
|
|
4476
|
+
// running turn. The guarded write only types into an empty box.
|
|
4404
4477
|
// PTY resize on ALL attempts to force SIGWINCH → Ink TUI re-render.
|
|
4405
4478
|
// Claude Code's Ink TUI can lose internal input focus after stop hooks
|
|
4406
4479
|
// (especially ones that error), state transitions, or idle periods.
|
|
@@ -4420,18 +4493,17 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
4420
4493
|
}
|
|
4421
4494
|
}
|
|
4422
4495
|
catch { /* non-fatal */ }
|
|
4423
|
-
|
|
4424
|
-
|
|
4496
|
+
// No Tab here (2026-10-03): in an empty Claude Code input, Tab
|
|
4497
|
+
// ACCEPTS the faint prompt suggestion — a predicted user message —
|
|
4498
|
+
// which the paste below then got appended to and submitted as if
|
|
4499
|
+
// the owner had typed it. The resize above restores focus.
|
|
4425
4500
|
}
|
|
4426
4501
|
else if (isAntigravity) {
|
|
4427
4502
|
// Antigravity CLI: Ctrl+U clears stale input (verified on agy
|
|
4428
4503
|
// 1.2.11). No Tab/Enter nudges: Tab completes slash commands and
|
|
4429
4504
|
// the binary labels its spinner "Generating... (Enter/Esc to
|
|
4430
4505
|
// cancel)", so a stray Enter or Esc can cancel a running turn.
|
|
4431
|
-
|
|
4432
|
-
await sessionHelper.sendKey(sessionName, 'C-u');
|
|
4433
|
-
await delay(300);
|
|
4434
|
-
}
|
|
4506
|
+
// No blind Ctrl+U: only text proven ours is ever cleared.
|
|
4435
4507
|
}
|
|
4436
4508
|
else if (isCodexCli) {
|
|
4437
4509
|
// #246: Codex CLI TUI recovery — simpler than Gemini.
|
|
@@ -4449,9 +4521,7 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
4449
4521
|
}
|
|
4450
4522
|
}
|
|
4451
4523
|
catch { /* non-fatal */ }
|
|
4452
|
-
// Ctrl+U
|
|
4453
|
-
await sessionHelper.sendKey(sessionName, 'C-u');
|
|
4454
|
-
await delay(300);
|
|
4524
|
+
// No blind Ctrl+U: only text proven ours is ever cleared.
|
|
4455
4525
|
}
|
|
4456
4526
|
}
|
|
4457
4527
|
else {
|
|
@@ -4512,25 +4582,11 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
4512
4582
|
// F12 to close error details panel if open
|
|
4513
4583
|
await sessionHelper.sendKey(sessionName, 'F12');
|
|
4514
4584
|
await delay(300);
|
|
4515
|
-
// Enter to dismiss any remaining overlay/notification
|
|
4516
|
-
await sessionHelper.sendEnter(sessionName);
|
|
4517
|
-
await delay(500);
|
|
4518
4585
|
}
|
|
4519
|
-
//
|
|
4520
|
-
//
|
|
4521
|
-
//
|
|
4522
|
-
//
|
|
4523
|
-
// framework level, not the component level.
|
|
4524
|
-
// Send two Tabs to cycle through error/notification components
|
|
4525
|
-
// that may be in the focus chain (#130).
|
|
4526
|
-
await sessionHelper.sendKey(sessionName, 'Tab');
|
|
4527
|
-
await delay(200);
|
|
4528
|
-
await sessionHelper.sendKey(sessionName, 'Tab');
|
|
4529
|
-
await delay(300);
|
|
4530
|
-
// Then Enter to dismiss any notification overlay and ensure
|
|
4531
|
-
// the input is engaged. Enter on an empty `> ` prompt is a
|
|
4532
|
-
// safe no-op (just shows a new blank prompt line).
|
|
4533
|
-
await sessionHelper.sendEnter(sessionName);
|
|
4586
|
+
// No Tab/Enter focus nudges (2026-10-03): the harness never presses
|
|
4587
|
+
// Tab or Enter unless its own text is in the input box. Tab can
|
|
4588
|
+
// accept a completion/suggestion and Enter submits whatever the box
|
|
4589
|
+
// holds. The resize above re-renders the TUI to restore focus.
|
|
4534
4590
|
// Extra settling time after /compress or error state to let Ink TUI stabilize
|
|
4535
4591
|
await delay((compressDetected || errorStateDetected) ? 1000 : 500);
|
|
4536
4592
|
}
|
|
@@ -4547,14 +4603,15 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
4547
4603
|
];
|
|
4548
4604
|
const inInteractiveMode = interactiveModePatterns.some(p => preOutput.includes(p));
|
|
4549
4605
|
if (inInteractiveMode) {
|
|
4550
|
-
|
|
4606
|
+
// No Tab/Enter: they would pick an entry in the picker. Skip
|
|
4607
|
+
// this attempt and look again; the input guard in sendMessage
|
|
4608
|
+
// refuses to type while the box is not an empty prompt.
|
|
4609
|
+
this.logger.warn('Gemini CLI in interactive mode, skipping this delivery attempt', {
|
|
4551
4610
|
sessionName,
|
|
4552
4611
|
detectedPattern: interactiveModePatterns.find(p => preOutput.includes(p)),
|
|
4553
4612
|
});
|
|
4554
|
-
await
|
|
4555
|
-
|
|
4556
|
-
await sessionHelper.sendEnter(sessionName);
|
|
4557
|
-
await delay(1000);
|
|
4613
|
+
await delay(SESSION_COMMAND_DELAYS.MESSAGE_RETRY_DELAY);
|
|
4614
|
+
continue;
|
|
4558
4615
|
}
|
|
4559
4616
|
}
|
|
4560
4617
|
// Capture output BEFORE sending to detect changes for ALL runtimes.
|
|
@@ -4651,7 +4708,8 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
4651
4708
|
// 2. await delay(scaled) — waits for paste processing
|
|
4652
4709
|
// 3. session.write('\r') — sends Enter separately
|
|
4653
4710
|
// 4. await delay(KEY_DELAY) — waits for key processing
|
|
4654
|
-
|
|
4711
|
+
// Recorded marker: this path checks the box after delivery.
|
|
4712
|
+
await sessionHelper.sendMessage(sessionName, message, { recordPasteMarker: true });
|
|
4655
4713
|
// Register for background stuck-detection safety net (all runtimes).
|
|
4656
4714
|
// If progressive verification below misses an Enter drop, the
|
|
4657
4715
|
// background scanner will catch it within 30s.
|
|
@@ -4727,20 +4785,17 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
4727
4785
|
: message).replace(/\s+/g, ' ').trim();
|
|
4728
4786
|
const promptBottomLines = currentOutput.split('\n').slice(-10).join(' ').replace(/\s+/g, ' ');
|
|
4729
4787
|
if (promptBottomLines.includes(promptMsgSnippet)) {
|
|
4730
|
-
|
|
4788
|
+
// Our text near the bottom may be the transcript echo of a
|
|
4789
|
+
// message already submitted. Press Enter only if the input
|
|
4790
|
+
// box itself holds exactly our text (never Tab: it accepts a
|
|
4791
|
+
// prompt suggestion — the 2026-10-03 phantom owner input).
|
|
4792
|
+
const pressed = (await sessionHelper.submitIfInputIsOurs(sessionName, message)).state === 'ours';
|
|
4793
|
+
this.logger.warn('At prompt with message text at bottom', {
|
|
4731
4794
|
sessionName,
|
|
4732
4795
|
attempt,
|
|
4733
4796
|
intervalMs,
|
|
4797
|
+
enterPressed: pressed,
|
|
4734
4798
|
});
|
|
4735
|
-
// Tab restores Ink TUI focus before Enter — without this,
|
|
4736
|
-
// Enter may be consumed by the framework but not routed to
|
|
4737
|
-
// the input component if focus was lost during a re-render
|
|
4738
|
-
// (e.g., after stop hooks, state transitions).
|
|
4739
|
-
await sessionHelper.sendKey(sessionName, 'Tab');
|
|
4740
|
-
await delay(200);
|
|
4741
|
-
await sessionHelper.sendEnter(sessionName);
|
|
4742
|
-
await delay(500);
|
|
4743
|
-
await sessionHelper.sendEnter(sessionName); // backup
|
|
4744
4799
|
await delay(SESSION_COMMAND_DELAYS.MESSAGE_PROCESSING_DELAY);
|
|
4745
4800
|
// Verify recovery
|
|
4746
4801
|
const postEnterOutput = sessionHelper.capturePane(sessionName);
|
|
@@ -4796,16 +4851,14 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
4796
4851
|
// prompt is no longer in its idle form — Enter was dropped.
|
|
4797
4852
|
// Instead of waiting and doing a full Ctrl+C + resend retry,
|
|
4798
4853
|
// use Tab to restore TUI focus, then Enter to submit.
|
|
4799
|
-
|
|
4854
|
+
// Enter only when the input box holds exactly our text.
|
|
4855
|
+
const pressed = (await sessionHelper.submitIfInputIsOurs(sessionName, message)).state === 'ours';
|
|
4856
|
+
this.logger.warn('Message text stuck at bottom', {
|
|
4800
4857
|
sessionName,
|
|
4801
4858
|
attempt,
|
|
4802
4859
|
intervalMs,
|
|
4860
|
+
enterPressed: pressed,
|
|
4803
4861
|
});
|
|
4804
|
-
await sessionHelper.sendKey(sessionName, 'Tab');
|
|
4805
|
-
await delay(200);
|
|
4806
|
-
await sessionHelper.sendEnter(sessionName);
|
|
4807
|
-
await delay(500);
|
|
4808
|
-
await sessionHelper.sendEnter(sessionName); // backup Enter
|
|
4809
4862
|
// Give the agent a moment to start processing after Enter
|
|
4810
4863
|
await delay(SESSION_COMMAND_DELAYS.MESSAGE_PROCESSING_DELAY);
|
|
4811
4864
|
// Verify recovery: check if processing started
|
|
@@ -4849,7 +4902,7 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
4849
4902
|
// If progressive verification didn't confirm delivery, run an
|
|
4850
4903
|
// extended check loop (every 3s, up to 30s total). Each iteration
|
|
4851
4904
|
// captures the pane, checks for spinner/working indicators, and
|
|
4852
|
-
// presses
|
|
4905
|
+
// presses Enter if our message text is still in the input box.
|
|
4853
4906
|
if (!claudeDelivered) {
|
|
4854
4907
|
const CONFIRM_INTERVAL_MS = 3000;
|
|
4855
4908
|
const CONFIRM_MAX_MS = 30000;
|
|
@@ -4874,7 +4927,7 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
4874
4927
|
break;
|
|
4875
4928
|
}
|
|
4876
4929
|
// Text still stuck → Enter was dropped, NOT delivered. Try
|
|
4877
|
-
//
|
|
4930
|
+
// Enter (only if the box holds our text) and re-check next iteration. This is
|
|
4878
4931
|
// checked BEFORE the output-change shortcut below: when our
|
|
4879
4932
|
// own pasted message is echoed at the prompt the output DID
|
|
4880
4933
|
// change vs the pre-send baseline, but that change is the
|
|
@@ -4882,14 +4935,10 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
4882
4935
|
// is the false-positive that masked the orc 假死 (#686
|
|
4883
4936
|
// follow-up: a hung session looked "delivered" forever).
|
|
4884
4937
|
if (textStuck) {
|
|
4885
|
-
|
|
4886
|
-
|
|
4938
|
+
const pressed = (await sessionHelper.submitIfInputIsOurs(sessionName, message)).state === 'ours';
|
|
4939
|
+
this.logger.warn('Confirmation loop: text still stuck', {
|
|
4940
|
+
sessionName, attempt, confirmAttempt, enterPressed: pressed,
|
|
4887
4941
|
});
|
|
4888
|
-
await sessionHelper.sendKey(sessionName, 'Tab');
|
|
4889
|
-
await delay(200);
|
|
4890
|
-
await sessionHelper.sendEnter(sessionName);
|
|
4891
|
-
await delay(500);
|
|
4892
|
-
await sessionHelper.sendEnter(sessionName); // backup
|
|
4893
4942
|
continue;
|
|
4894
4943
|
}
|
|
4895
4944
|
// Fast-response detection: output changed since send AND our
|
|
@@ -4942,8 +4991,9 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
4942
4991
|
this.logger.debug('Antigravity: message left the prompt box — delivery trusted', { sessionName, attempt });
|
|
4943
4992
|
return true;
|
|
4944
4993
|
}
|
|
4945
|
-
|
|
4946
|
-
await sessionHelper.
|
|
4994
|
+
// Enter only when the box holds exactly our message.
|
|
4995
|
+
const pressed = (await sessionHelper.submitIfInputIsOurs(sessionName, message)).state === 'ours';
|
|
4996
|
+
this.logger.warn('Antigravity: message still in the prompt box', { sessionName, attempt, enterPressed: pressed });
|
|
4947
4997
|
await delay(SESSION_COMMAND_DELAYS.MESSAGE_PROCESSING_DELAY);
|
|
4948
4998
|
if (!isTextInAntigravityInputBox(sessionHelper.capturePane(sessionName), message)) {
|
|
4949
4999
|
return true;
|
|
@@ -4981,6 +5031,17 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
4981
5031
|
}
|
|
4982
5032
|
else {
|
|
4983
5033
|
// --- Non-Gemini TUI runtimes: full verification ---
|
|
5034
|
+
// Phase 0: read the box. The guarded writer pressed Enter only
|
|
5035
|
+
// on our own text; a box that now reads empty in a verified
|
|
5036
|
+
// layout means it went in — the output-length heuristics below
|
|
5037
|
+
// miss that (Codex: "did not change" → a retry → a duplicate).
|
|
5038
|
+
{
|
|
5039
|
+
const boxAfter = sessionHelper.readInputBox(sessionName, message, 'recovery');
|
|
5040
|
+
if (boxAfter.state === 'empty' && boxAfter.verified) {
|
|
5041
|
+
this.logger.debug('Input box empty after our Enter — delivery confirmed', { sessionName, attempt, layout: boxAfter.layout });
|
|
5042
|
+
return true;
|
|
5043
|
+
}
|
|
5044
|
+
}
|
|
4984
5045
|
// Phase 1: Direct stuck-at-prompt detection
|
|
4985
5046
|
const stuckAtPrompt = this.isTextStuckAtTuiPrompt(sessionName, message);
|
|
4986
5047
|
if (stuckAtPrompt) {
|
|
@@ -5048,28 +5109,13 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
5048
5109
|
sessionName,
|
|
5049
5110
|
attempt,
|
|
5050
5111
|
});
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
await delay(300);
|
|
5059
|
-
}
|
|
5060
|
-
else {
|
|
5061
|
-
// Gemini CLI retry cleanup: NEVER send Ctrl+C — it triggers /quit
|
|
5062
|
-
// and kills the CLI entirely, regardless of whether text is in the
|
|
5063
|
-
// input box or not. Use Tab to cycle Ink focus back to the input
|
|
5064
|
-
// prompt, then Enter to submit any pending text.
|
|
5065
|
-
this.logger.warn('Gemini CLI message stuck — using Tab focus recovery (no Ctrl+C)', {
|
|
5066
|
-
sessionName,
|
|
5067
|
-
attempt,
|
|
5068
|
-
});
|
|
5069
|
-
await sessionHelper.sendKey(sessionName, 'Tab');
|
|
5070
|
-
await delay(300);
|
|
5071
|
-
await sessionHelper.sendEnter(sessionName);
|
|
5072
|
-
await delay(300);
|
|
5112
|
+
// Clear the box only when it holds exactly our message (2026-10-03
|
|
5113
|
+
// review): never Ctrl+C (cancels a turn; twice exits Claude Code;
|
|
5114
|
+
// /quit in Gemini), never wipe text we cannot prove is ours.
|
|
5115
|
+
{
|
|
5116
|
+
const box = sessionHelper.readInputBox(sessionName, message, 'before-write');
|
|
5117
|
+
if (box.state === 'ours')
|
|
5118
|
+
await sessionHelper.clearInputBox(sessionName, message, box);
|
|
5073
5119
|
}
|
|
5074
5120
|
await delay(SESSION_COMMAND_DELAYS.CLEAR_COMMAND_DELAY);
|
|
5075
5121
|
if (attempt < maxAttempts) {
|
|
@@ -5077,6 +5123,9 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
5077
5123
|
}
|
|
5078
5124
|
}
|
|
5079
5125
|
catch (error) {
|
|
5126
|
+
if (error instanceof TuiInputGuardError) {
|
|
5127
|
+
this.lastGuardRefusal.set(sessionName, error);
|
|
5128
|
+
}
|
|
5080
5129
|
this.logger.error('Error during message delivery', {
|
|
5081
5130
|
sessionName,
|
|
5082
5131
|
attempt,
|
|
@@ -5237,16 +5286,15 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
5237
5286
|
*/
|
|
5238
5287
|
async recoverStuckTuiMessage(sessionName, message) {
|
|
5239
5288
|
const sessionHelper = await this.getSessionHelper();
|
|
5240
|
-
//
|
|
5241
|
-
//
|
|
5242
|
-
//
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
await sessionHelper.sendEnter(sessionName);
|
|
5289
|
+
// Enter only when the input box holds exactly our text — never Tab
|
|
5290
|
+
// (accepts a suggestion) and no blind backup Enter (submits whatever
|
|
5291
|
+
// the box holds once ours is gone).
|
|
5292
|
+
const reading = await sessionHelper.submitIfInputIsOurs(sessionName, message);
|
|
5293
|
+
// Enter only for text read as ours — never on an unreadable box.
|
|
5294
|
+
const pressed = reading.state === 'ours';
|
|
5295
|
+
this.logger.info('Stuck TUI message: Enter only if the box holds our text', { sessionName, enterPressed: pressed, state: reading.state });
|
|
5296
|
+
if (!pressed)
|
|
5297
|
+
return false;
|
|
5250
5298
|
await delay(2000);
|
|
5251
5299
|
// Verify text is no longer at prompt
|
|
5252
5300
|
const stillStuck = this.isTextStuckAtTuiPrompt(sessionName, message);
|
|
@@ -5327,90 +5375,22 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
5327
5375
|
// Non-fatal: session may have been destroyed
|
|
5328
5376
|
}
|
|
5329
5377
|
}
|
|
5330
|
-
// --- Part 1
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
// of every submitted message sit on `>` lines, and Enter during a
|
|
5337
|
-
// turn can cancel it; its deliveries are verified on the prompt box.
|
|
5338
|
-
// Skip Codex: its empty composer shows a rotating suggestion
|
|
5339
|
-
// ("Ask Codex to do anything", "Explain this codebase", ...) on the
|
|
5340
|
-
// `›` line, indistinguishable from typed text once the dim styling is
|
|
5341
|
-
// lost, so every scan of a working Codex agent pressed Tab+Enter
|
|
5342
|
-
// (47 times in one turn). Tab queues input in Codex; real Enter drops
|
|
5343
|
-
// are still caught by Part 2, which matches the text actually sent.
|
|
5344
|
-
if (runtimeType === RUNTIME_TYPES.GEMINI_CLI ||
|
|
5345
|
-
runtimeType === RUNTIME_TYPES.ANTIGRAVITY_CLI ||
|
|
5346
|
-
runtimeType === RUNTIME_TYPES.CODEX_CLI) {
|
|
5347
|
-
continue;
|
|
5348
|
-
}
|
|
5349
|
-
// #213: Skip orchestrator — it frequently waits for long-running
|
|
5350
|
-
// agent tasks (30+ min), and false stuck detection causes disruptive
|
|
5351
|
-
// Tab+Enter key injections that interrupt its workflow.
|
|
5352
|
-
if (sessionName === ORCHESTRATOR_SESSION_NAME) {
|
|
5353
|
-
continue;
|
|
5354
|
-
}
|
|
5355
|
-
// Skip sessions that no longer exist
|
|
5356
|
-
if (!sessionHelper.sessionExists(sessionName)) {
|
|
5357
|
-
this.tuiSessionRegistry.delete(sessionName);
|
|
5358
|
-
continue;
|
|
5359
|
-
}
|
|
5360
|
-
const output = sessionHelper.capturePane(sessionName);
|
|
5361
|
-
if (!output || output.trim().length === 0)
|
|
5362
|
-
continue;
|
|
5363
|
-
// Look for any text sitting on the prompt line
|
|
5364
|
-
const lines = output.split('\n');
|
|
5365
|
-
for (let i = lines.length - 1; i >= Math.max(0, lines.length - 25); i--) {
|
|
5366
|
-
const promptContent = matchTuiPromptLine(lines[i]);
|
|
5367
|
-
if (promptContent !== null) {
|
|
5368
|
-
const trimmedContent = stripTuiLineBorders(promptContent).trim();
|
|
5369
|
-
// Only act on substantial text (> 10 chars) to avoid false positives
|
|
5370
|
-
// from TUI rendering artifacts or short status text
|
|
5371
|
-
if (trimmedContent.length > 10) {
|
|
5372
|
-
// Skip known idle placeholder text that sits at
|
|
5373
|
-
// the `> ` prompt when no user input is present. These are
|
|
5374
|
-
// NOT stuck messages — they are TUI decoration.
|
|
5375
|
-
const lowerContent = trimmedContent.toLowerCase();
|
|
5376
|
-
const isPlaceholder = lowerContent.startsWith('type your message') ||
|
|
5377
|
-
trimmedContent.startsWith('@'); // e.g., "@path/to/file"
|
|
5378
|
-
if (isPlaceholder) {
|
|
5379
|
-
break;
|
|
5380
|
-
}
|
|
5381
|
-
this.logger.warn('Background scan: text stuck at TUI prompt, pressing Tab+Enter', {
|
|
5382
|
-
sessionName,
|
|
5383
|
-
promptContent: promptContent.slice(0, 80),
|
|
5384
|
-
});
|
|
5385
|
-
// Tab restores Ink TUI focus before Enter
|
|
5386
|
-
await sessionHelper.sendKey(sessionName, 'Tab');
|
|
5387
|
-
await delay(200);
|
|
5388
|
-
await sessionHelper.sendEnter(sessionName);
|
|
5389
|
-
await delay(500);
|
|
5390
|
-
// Backup Enter
|
|
5391
|
-
await sessionHelper.sendEnter(sessionName);
|
|
5392
|
-
}
|
|
5393
|
-
break; // Only check the first prompt line found from the bottom
|
|
5394
|
-
}
|
|
5395
|
-
}
|
|
5396
|
-
}
|
|
5397
|
-
catch (error) {
|
|
5398
|
-
this.logger.debug('Error scanning session for stuck messages', {
|
|
5399
|
-
sessionName,
|
|
5400
|
-
error: error instanceof Error ? error.message : String(error),
|
|
5401
|
-
});
|
|
5402
|
-
}
|
|
5403
|
-
}
|
|
5378
|
+
// --- Part 1 (removed 2026-10-03) ---
|
|
5379
|
+
// The old Part 1 pressed Tab+Enter on ANY text sitting on a TUI prompt
|
|
5380
|
+
// line — text the harness never wrote (a prompt suggestion accepted by
|
|
5381
|
+
// the Tab, or someone's half-typed input) got submitted. The harness
|
|
5382
|
+
// now only submits its own tracked messages (Part 2), and only when
|
|
5383
|
+
// the input box holds exactly that text.
|
|
5404
5384
|
// --- Part 2: Tracked-message scanning (non-Gemini runtimes) ---
|
|
5405
5385
|
const now = Date.now();
|
|
5406
5386
|
const MIN_AGE_MS = 10000; // Check messages older than 10s
|
|
5407
|
-
const MAX_RECOVERY_ATTEMPTS = 5; // Give up after 5
|
|
5387
|
+
const MAX_RECOVERY_ATTEMPTS = 5; // Give up after 5 Enter attempts
|
|
5408
5388
|
for (const [sessionName, entries] of this.sentMessageTracker) {
|
|
5409
5389
|
if (!sessionHelper.sessionExists(sessionName)) {
|
|
5410
5390
|
this.sentMessageTracker.delete(sessionName);
|
|
5411
5391
|
continue;
|
|
5412
5392
|
}
|
|
5413
|
-
// Skip Gemini CLI sessions —
|
|
5393
|
+
// Skip Gemini CLI sessions — Enter recovery causes more harm
|
|
5414
5394
|
// than good because the Gemini TUI handles input differently.
|
|
5415
5395
|
const trackedRuntime = this.tuiSessionRegistry.get(sessionName);
|
|
5416
5396
|
if (trackedRuntime === RUNTIME_TYPES.GEMINI_CLI || trackedRuntime === RUNTIME_TYPES.ANTIGRAVITY_CLI) {
|
|
@@ -5427,9 +5407,33 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
5427
5407
|
if (now - entry.sentAt < MIN_AGE_MS)
|
|
5428
5408
|
continue;
|
|
5429
5409
|
if (entry.recoveryAttempts >= MAX_RECOVERY_ATTEMPTS) {
|
|
5430
|
-
// Exhausted recovery attempts — mark as recovered to stop retrying
|
|
5431
5410
|
entry.recovered = true;
|
|
5432
|
-
|
|
5411
|
+
const alreadyQueued = !!entry.message && SubAgentMessageQueue.getInstance().contains(sessionName, entry.message);
|
|
5412
|
+
if (entry.unreadable && entry.message && (entry.confirmed || alreadyQueued)) {
|
|
5413
|
+
// Delivery was confirmed earlier (or the message is queued
|
|
5414
|
+
// already): re-sending would duplicate it.
|
|
5415
|
+
this.logger.warn('Background scan: input box stayed unreadable — message was already delivered or queued, not re-sending', {
|
|
5416
|
+
sessionName,
|
|
5417
|
+
snippet: entry.snippet.slice(0, 50),
|
|
5418
|
+
confirmed: !!entry.confirmed,
|
|
5419
|
+
});
|
|
5420
|
+
continue;
|
|
5421
|
+
}
|
|
5422
|
+
if (entry.unreadable && entry.message) {
|
|
5423
|
+
// The box never became readable: we cannot tell whether
|
|
5424
|
+
// the message went in. Hand it back to the queue (retried
|
|
5425
|
+
// with backoff, the owner told if it stays blocked) rather
|
|
5426
|
+
// than give up on it (review #3 of crewly#1014).
|
|
5427
|
+
SubAgentMessageQueue.getInstance().enqueue(sessionName, entry.message);
|
|
5428
|
+
InputBlockedRetryService.getInstance().noteRefusal(sessionName, { state: 'unknown', inputLength: 0, message: entry.message });
|
|
5429
|
+
this.logger.error('Background scan: input box stayed unreadable — message re-queued', {
|
|
5430
|
+
sessionName,
|
|
5431
|
+
snippet: entry.snippet.slice(0, 50),
|
|
5432
|
+
attempts: entry.recoveryAttempts,
|
|
5433
|
+
});
|
|
5434
|
+
continue;
|
|
5435
|
+
}
|
|
5436
|
+
this.logger.error('Background scan: max recovery attempts exhausted — the message may not have been delivered', {
|
|
5433
5437
|
sessionName,
|
|
5434
5438
|
snippet: entry.snippet.slice(0, 50),
|
|
5435
5439
|
attempts: entry.recoveryAttempts,
|
|
@@ -5437,20 +5441,74 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
5437
5441
|
continue;
|
|
5438
5442
|
}
|
|
5439
5443
|
if (bottomText.includes(entry.snippet)) {
|
|
5444
|
+
// The text near the bottom is often only the transcript echo
|
|
5445
|
+
// of a message already submitted. Enter is pressed only when
|
|
5446
|
+
// the input box itself holds exactly this message — never on
|
|
5447
|
+
// an empty box (it may show a ghost prompt suggestion; Enter
|
|
5448
|
+
// or Tab would submit it as the owner — 2026-10-03).
|
|
5449
|
+
const reading = await sessionHelper.submitIfInputIsOurs(sessionName, entry.message);
|
|
5450
|
+
if (reading.state === 'empty') {
|
|
5451
|
+
// The box is empty: our message was submitted (the text
|
|
5452
|
+
// seen was its echo). Nothing to do.
|
|
5453
|
+
entry.recovered = true;
|
|
5454
|
+
continue;
|
|
5455
|
+
}
|
|
5456
|
+
if (reading.state === 'foreign') {
|
|
5457
|
+
const squashed = (t) => t.replace(/\s+/g, '');
|
|
5458
|
+
if (squashed(reading.text).includes(squashed(entry.snippet))) {
|
|
5459
|
+
// Our message is in the box mixed with text we did not
|
|
5460
|
+
// write. Never wipe text we cannot prove is ours: leave the
|
|
5461
|
+
// box as it is and hand the message back to delivery, which
|
|
5462
|
+
// keeps it queued until the box is empty. The entry is marked
|
|
5463
|
+
// recovered only once delivery has it (2026-10-03 review).
|
|
5464
|
+
this.logger.warn('Background scan: tracked message stuck with foreign text in the box — not touching it, re-queuing the message', {
|
|
5465
|
+
sessionName,
|
|
5466
|
+
snippet: entry.snippet.slice(0, 50),
|
|
5467
|
+
});
|
|
5468
|
+
this.logger.warn('Background scan: input box text (not ours)', { sessionName, inputPreview: reading.text.slice(0, 80) });
|
|
5469
|
+
try {
|
|
5470
|
+
const result = await this.sendMessageToAgent(sessionName, entry.message);
|
|
5471
|
+
if (result.success) {
|
|
5472
|
+
entry.recovered = true;
|
|
5473
|
+
}
|
|
5474
|
+
else {
|
|
5475
|
+
entry.recoveryAttempts++;
|
|
5476
|
+
this.logger.error('Background scan: re-delivery failed — will try again next scan', { sessionName, error: result.error });
|
|
5477
|
+
}
|
|
5478
|
+
}
|
|
5479
|
+
catch (err) {
|
|
5480
|
+
entry.recoveryAttempts++;
|
|
5481
|
+
this.logger.error('Background scan: re-delivery threw — will try again next scan', {
|
|
5482
|
+
sessionName,
|
|
5483
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5484
|
+
});
|
|
5485
|
+
}
|
|
5486
|
+
break;
|
|
5487
|
+
}
|
|
5488
|
+
// Our text is not in the box (someone else's is): ours was submitted.
|
|
5489
|
+
entry.recovered = true;
|
|
5490
|
+
continue;
|
|
5491
|
+
}
|
|
5440
5492
|
entry.recoveryAttempts++;
|
|
5441
|
-
|
|
5493
|
+
entry.unreadable = reading.state === 'unknown';
|
|
5494
|
+
if (reading.state === 'unknown') {
|
|
5495
|
+
// Cannot see the box: never press Enter blindly, never mark
|
|
5496
|
+
// it recovered — look again next scan; exhausting attempts
|
|
5497
|
+
// is logged as an error above.
|
|
5498
|
+
this.logger.warn('Background scan: tracked message near the bottom but the input box cannot be read — not pressing Enter', {
|
|
5499
|
+
sessionName,
|
|
5500
|
+
snippet: entry.snippet.slice(0, 50),
|
|
5501
|
+
attempt: entry.recoveryAttempts,
|
|
5502
|
+
});
|
|
5503
|
+
break;
|
|
5504
|
+
}
|
|
5505
|
+
this.logger.warn('Background scan: tracked message stuck in the input box, pressed Enter', {
|
|
5442
5506
|
sessionName,
|
|
5443
5507
|
snippet: entry.snippet.slice(0, 50),
|
|
5444
5508
|
ageMs: now - entry.sentAt,
|
|
5445
5509
|
attempt: entry.recoveryAttempts,
|
|
5446
5510
|
maxAttempts: MAX_RECOVERY_ATTEMPTS,
|
|
5447
5511
|
});
|
|
5448
|
-
// Tab restores Ink TUI focus before Enter
|
|
5449
|
-
await sessionHelper.sendKey(sessionName, 'Tab');
|
|
5450
|
-
await delay(200);
|
|
5451
|
-
await sessionHelper.sendEnter(sessionName);
|
|
5452
|
-
await delay(500);
|
|
5453
|
-
await sessionHelper.sendEnter(sessionName); // backup
|
|
5454
5512
|
break; // One recovery per session per scan cycle
|
|
5455
5513
|
}
|
|
5456
5514
|
}
|
|
@@ -5501,7 +5559,7 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
5501
5559
|
if (snippet.length < 10)
|
|
5502
5560
|
return; // Too short to reliably match
|
|
5503
5561
|
const entries = this.sentMessageTracker.get(sessionName) || [];
|
|
5504
|
-
entries.push({ snippet, sentAt: Date.now(), recovered: false, recoveryAttempts: 0 });
|
|
5562
|
+
entries.push({ snippet, message, sentAt: Date.now(), recovered: false, recoveryAttempts: 0 });
|
|
5505
5563
|
// Keep only last 5 minutes of entries
|
|
5506
5564
|
const cutoff = Date.now() - 5 * 60 * 1000;
|
|
5507
5565
|
this.sentMessageTracker.set(sessionName, entries.filter(e => e.sentAt > cutoff));
|
|
@@ -5823,33 +5881,9 @@ Loop until done, blocked, or explicitly reassigned:
|
|
|
5823
5881
|
this.logger.info('Prompt delivery aborted before send (runtime exited)', { sessionName });
|
|
5824
5882
|
return false;
|
|
5825
5883
|
}
|
|
5826
|
-
//
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
// Claude Code: Escape closes slash menus + Ctrl+U clears line.
|
|
5830
|
-
await sessionHelper.sendEscape(sessionName);
|
|
5831
|
-
await delay(200);
|
|
5832
|
-
await sessionHelper.sendKey(sessionName, 'C-u');
|
|
5833
|
-
await delay(300);
|
|
5834
|
-
}
|
|
5835
|
-
else if (runtimeType === RUNTIME_TYPES.CODEX_CLI || runtimeType === RUNTIME_TYPES.ANTIGRAVITY_CLI) {
|
|
5836
|
-
// #246: Codex CLI — Ctrl+U to clear any stale input text.
|
|
5837
|
-
// Unlike Gemini, Codex doesn't need Enter flush; its Ink TUI
|
|
5838
|
-
// handles Ctrl+U for line clear similar to Claude Code.
|
|
5839
|
-
// Antigravity clears its prompt box on Ctrl+U the same way.
|
|
5840
|
-
await sessionHelper.sendKey(sessionName, 'C-u');
|
|
5841
|
-
await delay(300);
|
|
5842
|
-
}
|
|
5843
|
-
else {
|
|
5844
|
-
// Gemini CLI (Ink TUI): After /directory add processing, the TUI
|
|
5845
|
-
// input may lose focus or have stale invisible characters.
|
|
5846
|
-
// Send Enter to flush any residual input, then wait for the
|
|
5847
|
-
// prompt to settle before sending the real instruction.
|
|
5848
|
-
this.logger.debug('Gemini CLI pre-send: flushing input with Enter', { sessionName });
|
|
5849
|
-
await sessionHelper.sendEnter(sessionName);
|
|
5850
|
-
await delay(1000);
|
|
5851
|
-
}
|
|
5852
|
-
}
|
|
5884
|
+
// No pre-clearing (2026-10-03 review): Escape / Ctrl+U would wipe
|
|
5885
|
+
// whatever the box holds without knowing whose it is. The guarded
|
|
5886
|
+
// sendMessage below types only into a box it reads as empty.
|
|
5853
5887
|
// Check abort right before writing instruction to terminal
|
|
5854
5888
|
if (this.isFlowCancelled(sessionName, abortSignal)) {
|
|
5855
5889
|
this.logger.info('Prompt delivery aborted before instruction send (runtime exited)', { sessionName });
|