@pentoshi/clai 3.11.7 → 3.11.9

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.
Files changed (115) hide show
  1. package/README.md +15 -0
  2. package/dist/agent/confirm-port.d.ts +4 -0
  3. package/dist/agent/confirm-port.js +9 -1
  4. package/dist/agent/confirm-port.js.map +1 -1
  5. package/dist/agent/finalize-gate.d.ts +57 -0
  6. package/dist/agent/finalize-gate.js +146 -0
  7. package/dist/agent/finalize-gate.js.map +1 -0
  8. package/dist/agent/runner.js +71 -179
  9. package/dist/agent/runner.js.map +1 -1
  10. package/dist/agent/tool-call-parser.js +3 -0
  11. package/dist/agent/tool-call-parser.js.map +1 -1
  12. package/dist/app/adapters/current-interactive-sessions-adapter.d.ts +4 -0
  13. package/dist/app/adapters/current-interactive-sessions-adapter.js +12 -0
  14. package/dist/app/adapters/current-interactive-sessions-adapter.js.map +1 -0
  15. package/dist/app/controllers/cancel-all-result.d.ts +8 -0
  16. package/dist/app/controllers/cancel-all-result.js +32 -0
  17. package/dist/app/controllers/cancel-all-result.js.map +1 -0
  18. package/dist/app/controllers/session-controller.d.ts +3 -0
  19. package/dist/app/controllers/session-controller.js +22 -6
  20. package/dist/app/controllers/session-controller.js.map +1 -1
  21. package/dist/app/ports/interactive-sessions-port.d.ts +22 -0
  22. package/dist/app/ports/interactive-sessions-port.js +9 -0
  23. package/dist/app/ports/interactive-sessions-port.js.map +1 -0
  24. package/dist/index.js +24 -13
  25. package/dist/index.js.map +1 -1
  26. package/dist/interactive-session/artifact-writer.d.ts +54 -0
  27. package/dist/interactive-session/artifact-writer.js +161 -0
  28. package/dist/interactive-session/artifact-writer.js.map +1 -0
  29. package/dist/interactive-session/cleanup.d.ts +37 -0
  30. package/dist/interactive-session/cleanup.js +191 -0
  31. package/dist/interactive-session/cleanup.js.map +1 -0
  32. package/dist/interactive-session/config.d.ts +118 -0
  33. package/dist/interactive-session/config.js +141 -0
  34. package/dist/interactive-session/config.js.map +1 -0
  35. package/dist/interactive-session/input-policy.d.ts +26 -0
  36. package/dist/interactive-session/input-policy.js +70 -0
  37. package/dist/interactive-session/input-policy.js.map +1 -0
  38. package/dist/interactive-session/manager.d.ts +118 -0
  39. package/dist/interactive-session/manager.js +1214 -0
  40. package/dist/interactive-session/manager.js.map +1 -0
  41. package/dist/interactive-session/output-store.d.ts +81 -0
  42. package/dist/interactive-session/output-store.js +239 -0
  43. package/dist/interactive-session/output-store.js.map +1 -0
  44. package/dist/interactive-session/output-view.d.ts +20 -0
  45. package/dist/interactive-session/output-view.js +142 -0
  46. package/dist/interactive-session/output-view.js.map +1 -0
  47. package/dist/interactive-session/recovery-journal.d.ts +53 -0
  48. package/dist/interactive-session/recovery-journal.js +167 -0
  49. package/dist/interactive-session/recovery-journal.js.map +1 -0
  50. package/dist/interactive-session/registry.d.ts +59 -0
  51. package/dist/interactive-session/registry.js +170 -0
  52. package/dist/interactive-session/registry.js.map +1 -0
  53. package/dist/interactive-session/runtime.d.ts +107 -0
  54. package/dist/interactive-session/runtime.js +191 -0
  55. package/dist/interactive-session/runtime.js.map +1 -0
  56. package/dist/interactive-session/session-runtime.d.ts +53 -0
  57. package/dist/interactive-session/session-runtime.js +65 -0
  58. package/dist/interactive-session/session-runtime.js.map +1 -0
  59. package/dist/interactive-session/streaming-redactor.d.ts +53 -0
  60. package/dist/interactive-session/streaming-redactor.js +271 -0
  61. package/dist/interactive-session/streaming-redactor.js.map +1 -0
  62. package/dist/interactive-session/telemetry.d.ts +30 -0
  63. package/dist/interactive-session/telemetry.js +47 -0
  64. package/dist/interactive-session/telemetry.js.map +1 -0
  65. package/dist/interactive-session/transport-factory.d.ts +8 -0
  66. package/dist/interactive-session/transport-factory.js +16 -0
  67. package/dist/interactive-session/transport-factory.js.map +1 -0
  68. package/dist/interactive-session/transport-node-pty.d.ts +38 -0
  69. package/dist/interactive-session/transport-node-pty.js +390 -0
  70. package/dist/interactive-session/transport-node-pty.js.map +1 -0
  71. package/dist/interactive-session/transport-pipe.d.ts +9 -0
  72. package/dist/interactive-session/transport-pipe.js +267 -0
  73. package/dist/interactive-session/transport-pipe.js.map +1 -0
  74. package/dist/interactive-session/transport.d.ts +110 -0
  75. package/dist/interactive-session/transport.js +89 -0
  76. package/dist/interactive-session/transport.js.map +1 -0
  77. package/dist/interactive-session/types.d.ts +258 -0
  78. package/dist/interactive-session/types.js +191 -0
  79. package/dist/interactive-session/types.js.map +1 -0
  80. package/dist/os/process-identity.d.ts +46 -0
  81. package/dist/os/process-identity.js +147 -0
  82. package/dist/os/process-identity.js.map +1 -0
  83. package/dist/os/process-tree.d.ts +1 -0
  84. package/dist/os/process-tree.js +11 -0
  85. package/dist/os/process-tree.js.map +1 -1
  86. package/dist/prompts/embedded.js +1 -1
  87. package/dist/prompts/embedded.js.map +1 -1
  88. package/dist/prompts/index.d.ts +7 -0
  89. package/dist/prompts/index.js +23 -0
  90. package/dist/prompts/index.js.map +1 -1
  91. package/dist/prompts/system.agent.md +3 -0
  92. package/dist/repl.js +16 -1
  93. package/dist/repl.js.map +1 -1
  94. package/dist/safety/classifier.d.ts +9 -0
  95. package/dist/safety/classifier.js +119 -18
  96. package/dist/safety/classifier.js.map +1 -1
  97. package/dist/safety/engagement-policy.d.ts +13 -0
  98. package/dist/safety/engagement-policy.js +129 -34
  99. package/dist/safety/engagement-policy.js.map +1 -1
  100. package/dist/tools/definitions.js +94 -0
  101. package/dist/tools/definitions.js.map +1 -1
  102. package/dist/tools/interactive-session-tools.d.ts +11 -0
  103. package/dist/tools/interactive-session-tools.js +276 -0
  104. package/dist/tools/interactive-session-tools.js.map +1 -0
  105. package/dist/tools/registry.js +3 -0
  106. package/dist/tools/registry.js.map +1 -1
  107. package/dist/tui-v2/bootstrap/composition-root.d.ts +3 -0
  108. package/dist/tui-v2/bootstrap/composition-root.js +3 -0
  109. package/dist/tui-v2/bootstrap/composition-root.js.map +1 -1
  110. package/dist/tui-v2/bootstrap/start-tui-v2.js +8 -0
  111. package/dist/tui-v2/bootstrap/start-tui-v2.js.map +1 -1
  112. package/dist/types.d.ts +1 -0
  113. package/dist/version.generated.d.ts +2 -2
  114. package/dist/version.generated.js +2 -2
  115. package/package.json +7 -6
package/README.md CHANGED
@@ -19,6 +19,7 @@ Two things make it practical for everyday use:
19
19
  - **Scope-based pentesting.** Opt-in engagement scope with authorized/excluded targets, allowed phases, rate and concurrency ceilings, redirect and DNS-rebinding escape detection, and out-of-scope flagging — designed for authorized pentests and bug-bounty programs.
20
20
  - **Real building & debugging.** Scaffolds apps, edits code surgically, installs packages, runs builds/tests, starts dev servers as background jobs, and probes them before reporting success.
21
21
  - **Durable plans.** `plan.create` / `task.update` drive a live checklist that survives context compaction and reloads with `/history` — the agent works task-by-task and won't fake completion.
22
+ - **Persistent interactive terminals.** Conversation-owned PTY or pipe sessions keep REPLs such as Python, Metasploit, Meterpreter, database consoles, and debuggers open across model turns. The agent can send follow-up input, read incremental output, resize, interrupt, and close them without losing state.
22
23
  - **Native + text tool calling.** Uses provider-native function calling where available, with a text-fence fallback (`toolCalling: auto|native|text`).
23
24
  - **Safety gate you control.** Every action is classified safe / confirm / block; deletes always confirm with a preview; destructive patterns are blocked.
24
25
 
@@ -416,6 +417,20 @@ Drop a `.clai/context.md` in a repo and its contents are injected every turn —
416
417
 
417
418
  ---
418
419
 
420
+ ## Interactive REPLs and terminals
421
+
422
+ Ask clai to use an interactive terminal whenever a program needs more than one input step. It keeps the process attached to the current conversation instead of treating it like a one-line shell command:
423
+
424
+ ```text
425
+ Start a Python REPL, inspect the failing parser, make a small change, and show me the result.
426
+ ```
427
+
428
+ The agent uses `terminal.start`, `terminal.send`, `terminal.read`, `terminal.status`, `terminal.list`, `terminal.resize`, and `terminal.close` internally. Sessions are isolated per conversation, capture output as cursor-addressed pages, redact prompted secret input before it reaches transcripts or artifacts, and reliably terminate their process trees on close. In plan mode, starting or sending terminal input waits for plan approval; read, status, list, and close operations remain available.
429
+
430
+ For authorized security work, interactive effects are re-evaluated against the active engagement scope at the time they are delivered. That includes the target, port, phase, and expiry accumulated from prior REPL commands, so a split sequence cannot bypass scope controls.
431
+
432
+ ---
433
+
419
434
  ## Configuration & privacy
420
435
 
421
436
  ```sh
@@ -29,6 +29,10 @@ export interface ConfirmPort {
29
29
  */
30
30
  export declare function restoreInteractiveStdin(): void;
31
31
  export declare const inquirerConfirmPort: ConfirmPort;
32
+ export declare function inquirerSecretRequester(request: {
33
+ title: string;
34
+ prompt: string;
35
+ }): Promise<string | undefined>;
32
36
  export declare function ensurePentestAuthorization(call: ToolCall, autoConfirm: boolean, session: SessionPolicy, confirmPort: ConfirmPort): Promise<boolean>;
33
37
  export declare function confirmToolExecution(call: ToolCall, autoConfirm: boolean, session: SessionPolicy, confirmPort: ConfirmPort, options?: {
34
38
  forceConfirm?: boolean | undefined;
@@ -1,4 +1,4 @@
1
- import { confirm } from "@inquirer/prompts";
1
+ import { confirm, password } from "@inquirer/prompts";
2
2
  import chalk from "chalk";
3
3
  import { getConfig } from "../store/config.js";
4
4
  import { isPentestToolCall } from "../safety/classifier.js";
@@ -58,6 +58,14 @@ export const inquirerConfirmPort = {
58
58
  });
59
59
  },
60
60
  };
61
+ export async function inquirerSecretRequester(request) {
62
+ try {
63
+ return await password({ message: request.prompt || request.title });
64
+ }
65
+ finally {
66
+ restoreInteractiveStdin();
67
+ }
68
+ }
61
69
  export async function ensurePentestAuthorization(call, autoConfirm, session, confirmPort) {
62
70
  if (!isPentestToolCall(call))
63
71
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"confirm-port.js","sourceRoot":"","sources":["../../src/agent/confirm-port.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"confirm-port.js","sourceRoot":"","sources":["../../src/agent/confirm-port.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Pure finalize-time gate: pick the recovery nudge that must replace a final
3
+ * answer, or nothing when the turn may finalize. No I/O, no budget mutation.
4
+ */
5
+ import { type RecoveryAction, type RecoveryBudgets } from "./must-continue.js";
6
+ /** Task fields the gate reads. */
7
+ export interface FinalizeGatePlanTask {
8
+ id: string;
9
+ title: string;
10
+ state: string;
11
+ responderOwned?: boolean | undefined;
12
+ }
13
+ /** Plain-data snapshot of the live plan, resolved once by the caller. */
14
+ export interface FinalizeGatePlan {
15
+ kind: string;
16
+ hasVerifiedRuntime: boolean;
17
+ tasks: readonly FinalizeGatePlanTask[];
18
+ }
19
+ /** Everything the finalize cascade reads, as data. */
20
+ export interface FinalizeGateInput {
21
+ cleaned: string;
22
+ recovery: RecoveryBudgets;
23
+ toolsAttached: boolean;
24
+ productiveSteps: number;
25
+ planApproved: boolean;
26
+ planHasOpenWork: boolean;
27
+ activePlanExists: boolean;
28
+ wantsAction: boolean;
29
+ narratedAction: boolean;
30
+ narratedWebAction: boolean;
31
+ isPlanMode: boolean;
32
+ buildLikeTurn: boolean;
33
+ pentestLikeTurn: boolean;
34
+ buildLike: boolean;
35
+ pentestLike: boolean;
36
+ pentestSession: boolean;
37
+ informationalQuery: boolean;
38
+ idleOrSocialPrompt: boolean;
39
+ freshWebSearchRequired: boolean;
40
+ freshnessGuardText: string;
41
+ sawFreshWebSearch: boolean;
42
+ sawPlanCreateOk: boolean;
43
+ sawFeatureImplWrite: boolean;
44
+ sawScaffoldOk: boolean;
45
+ sawLocalAppMaterialWork: boolean;
46
+ sawServerStart: boolean;
47
+ sawServerTail: boolean;
48
+ sawLocalHttpProbe: boolean;
49
+ sawFailedLocalHttpProbe: boolean;
50
+ sawActivePentestTest: boolean;
51
+ sawSuccessfulMutation: boolean;
52
+ featureAppAsk: boolean;
53
+ projectRoot: string | undefined;
54
+ plan: FinalizeGatePlan | undefined;
55
+ deferResponderReport: boolean;
56
+ }
57
+ export declare function chooseFinalizeRecovery(input: FinalizeGateInput): RecoveryAction | undefined;
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Pure finalize-time gate: pick the recovery nudge that must replace a final
3
+ * answer, or nothing when the turn may finalize. No I/O, no budget mutation.
4
+ */
5
+ import { budgetRemaining, freestyleClaimsAppReady, looksLikeShallowPentestReport, recoveryForErrorDiagnosis, recoveryForFailedProbe, recoveryForFreshness, recoveryForMissingFeature, recoveryForMissingPlan, recoveryForNarration, recoveryForPrematureComplete, recoveryForRuntimeVerify, recoveryForShallowPentest, } from "./must-continue.js";
6
+ import { looksLikeErrorDiagnosisWithFixIntent, looksLikePlanNarration, } from "./tool-call-parser.js";
7
+ export function chooseFinalizeRecovery(input) {
8
+ const { cleaned, recovery, toolsAttached, productiveSteps, plan } = input;
9
+ const planNarrated = (input.buildLikeTurn || input.pentestLikeTurn) &&
10
+ !input.activePlanExists &&
11
+ looksLikePlanNarration(cleaned);
12
+ const errorFixNarration = !input.sawSuccessfulMutation &&
13
+ looksLikeErrorDiagnosisWithFixIntent(cleaned);
14
+ const shouldRetryBeforeFinalizing = productiveSteps === 0 ||
15
+ planNarrated ||
16
+ ((input.narratedAction || input.narratedWebAction) &&
17
+ !input.informationalQuery) ||
18
+ (input.planApproved &&
19
+ input.planHasOpenWork &&
20
+ (input.narratedAction || errorFixNarration)) ||
21
+ (input.planApproved && errorFixNarration) ||
22
+ (input.buildLikeTurn && errorFixNarration);
23
+ if (input.wantsAction &&
24
+ cleaned.trim().length > 0 &&
25
+ shouldRetryBeforeFinalizing) {
26
+ let action;
27
+ if (errorFixNarration && budgetRemaining(recovery, "errorFix")) {
28
+ action = recoveryForErrorDiagnosis(toolsAttached);
29
+ }
30
+ else if (budgetRemaining(recovery, "actionIntent") &&
31
+ input.planHasOpenWork &&
32
+ input.planApproved) {
33
+ action = recoveryForNarration(toolsAttached, "plan_open");
34
+ }
35
+ else if (budgetRemaining(recovery, "actionIntent") &&
36
+ input.pentestLikeTurn) {
37
+ action = recoveryForNarration(toolsAttached, "pentest");
38
+ }
39
+ else if (budgetRemaining(recovery, "actionIntent") &&
40
+ (input.freshWebSearchRequired || input.narratedWebAction)) {
41
+ action = recoveryForNarration(toolsAttached, "web");
42
+ }
43
+ else if (budgetRemaining(recovery, "actionIntent") &&
44
+ input.buildLikeTurn &&
45
+ (planNarrated || productiveSteps > 0)) {
46
+ action = recoveryForNarration(toolsAttached, "build_plan_prose");
47
+ }
48
+ else if (budgetRemaining(recovery, "actionIntent") &&
49
+ input.buildLikeTurn) {
50
+ action = recoveryForNarration(toolsAttached, "build");
51
+ }
52
+ else if (budgetRemaining(recovery, "actionIntent")) {
53
+ action = recoveryForNarration(toolsAttached, "generic");
54
+ }
55
+ if (action)
56
+ return action;
57
+ }
58
+ if (input.freshWebSearchRequired &&
59
+ !input.sawFreshWebSearch &&
60
+ budgetRemaining(recovery, "freshnessUsed")) {
61
+ return recoveryForFreshness(input.freshnessGuardText +
62
+ (toolsAttached
63
+ ? " Call the web_search tool now."
64
+ : " Reply with ONLY a fenced ```tool block for web.search now."));
65
+ }
66
+ if (input.isPlanMode &&
67
+ !input.informationalQuery &&
68
+ !input.idleOrSocialPrompt &&
69
+ budgetRemaining(recovery, "forcePlan")) {
70
+ if (!plan && !input.sawPlanCreateOk) {
71
+ return recoveryForMissingPlan(toolsAttached);
72
+ }
73
+ }
74
+ if (input.buildLike &&
75
+ !input.pentestLike &&
76
+ !input.pentestSession &&
77
+ input.planApproved &&
78
+ input.featureAppAsk &&
79
+ !input.sawFeatureImplWrite &&
80
+ (input.sawScaffoldOk || input.sawLocalAppMaterialWork) &&
81
+ productiveSteps > 0 &&
82
+ budgetRemaining(recovery, "featureImpl")) {
83
+ return recoveryForMissingFeature(input.projectRoot);
84
+ }
85
+ if (input.buildLike &&
86
+ !input.pentestLike &&
87
+ !input.pentestSession &&
88
+ budgetRemaining(recovery, "runtimeVerify") &&
89
+ (!input.featureAppAsk || input.sawFeatureImplWrite)) {
90
+ const planRuntimeOk = Boolean(plan && plan.hasVerifiedRuntime);
91
+ const sessionRuntimeOk = input.sawServerStart &&
92
+ (input.sawServerTail || input.sawLocalHttpProbe || planRuntimeOk);
93
+ if (!planRuntimeOk && !sessionRuntimeOk) {
94
+ const codingPlanFinished = Boolean(plan &&
95
+ input.planApproved &&
96
+ plan.kind !== "pentest" &&
97
+ plan.tasks.length > 0 &&
98
+ plan.tasks.every((task) => task.state === "done" || task.state === "skipped"));
99
+ const freestyleLocalAppDone = !input.planApproved &&
100
+ input.sawLocalAppMaterialWork &&
101
+ productiveSteps > 0 &&
102
+ freestyleClaimsAppReady(cleaned) &&
103
+ (input.projectRoot !== undefined ||
104
+ /\b(?:npm|pnpm|yarn|bun)\s+run\s+dev\b/i.test(cleaned) ||
105
+ /\bopen\s+http:\/\/localhost\b/i.test(cleaned));
106
+ if (codingPlanFinished || freestyleLocalAppDone) {
107
+ return recoveryForRuntimeVerify(input.projectRoot);
108
+ }
109
+ }
110
+ }
111
+ if (input.buildLike &&
112
+ !input.pentestLike &&
113
+ !input.pentestSession &&
114
+ input.sawFailedLocalHttpProbe &&
115
+ !input.sawLocalHttpProbe &&
116
+ budgetRemaining(recovery, "failedProbe") &&
117
+ cleaned.trim().length > 0) {
118
+ return recoveryForFailedProbe();
119
+ }
120
+ if ((input.pentestLike || input.pentestSession) &&
121
+ budgetRemaining(recovery, "shallowPentest") &&
122
+ looksLikeShallowPentestReport(cleaned, {
123
+ productiveSteps,
124
+ sawActiveTest: input.sawActivePentestTest,
125
+ })) {
126
+ return recoveryForShallowPentest();
127
+ }
128
+ if (input.planApproved && budgetRemaining(recovery, "prematureComplete")) {
129
+ const unfinished = plan?.tasks.filter((task) => !task.responderOwned &&
130
+ (task.state === "pending" || task.state === "in_progress"));
131
+ if (plan &&
132
+ unfinished &&
133
+ unfinished.length > 0 &&
134
+ !input.deferResponderReport) {
135
+ const next = unfinished[0];
136
+ return recoveryForPrematureComplete({
137
+ unfinished,
138
+ next,
139
+ pentest: plan.kind === "pentest" || input.pentestSession,
140
+ errorFix: errorFixNarration,
141
+ });
142
+ }
143
+ }
144
+ return undefined;
145
+ }
146
+ //# sourceMappingURL=finalize-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finalize-gate.js","sourceRoot":"","sources":["../../src/agent/finalize-gate.ts"],"names":[],"mappings":"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"}
@@ -64,7 +64,7 @@ import { resolveRequestBudget } from "./request-budget.js";
64
64
  import { loadPlan, mutatePlan, markTask, appendPlanTask, readyPlanTasks, foregroundRemaining, responderOpenTasks, isPlanTerminal, isPlanSuccessful, } from "../store/plan.js";
65
65
  import { stat } from "node:fs/promises";
66
66
  import { isOutsideWorkingDirectory, resolveFsToolPath, } from "../tools/fs.js";
67
- import { stripSentinelTokens, parseToolCall, recognizeBareToolJson, looksLikeTruncatedToolCall, salvageTruncatedWrite, salvageTruncatedWriteFromNative, countToolFences, parseAllToolCalls, groupToolCallsForExecution, buildTurnHistory, collapseRepeatedText, textBeforeToolCall, formatToolArgs, looksLikePentestTask, looksLikeBuildTask, looksLikeInformationalQuery, looksLikeIdleOrSocialPrompt, looksLikeActionNarration, looksLikeWebActionNarration, looksLikePlanNarration, looksLikeErrorDiagnosisWithFixIntent, localHttpProbeIsFailure, localHttpProbeIsSuccess, requiresFreshWebSearch, freshnessGuardMessage, buildWorkflowDirective, narrowNmapOperationDirective, pentestWorkflowDirective, pentestNoLocalServerDirective, shouldDimToolChatter, looksLikePromptLeak, } from "./tool-call-parser.js";
67
+ import { stripSentinelTokens, parseToolCall, recognizeBareToolJson, looksLikeTruncatedToolCall, salvageTruncatedWrite, salvageTruncatedWriteFromNative, countToolFences, parseAllToolCalls, groupToolCallsForExecution, buildTurnHistory, collapseRepeatedText, textBeforeToolCall, formatToolArgs, looksLikePentestTask, looksLikeBuildTask, looksLikeInformationalQuery, looksLikeIdleOrSocialPrompt, looksLikeActionNarration, looksLikeWebActionNarration, localHttpProbeIsFailure, localHttpProbeIsSuccess, requiresFreshWebSearch, freshnessGuardMessage, buildWorkflowDirective, narrowNmapOperationDirective, pentestWorkflowDirective, pentestNoLocalServerDirective, shouldDimToolChatter, looksLikePromptLeak, } from "./tool-call-parser.js";
68
68
  import { createSessionPolicy, isPreApprovalAllowedTool, isPlanModeAllowedShellCommand, isPlanModeAllowedTool, isPlanApprovedByStatus, planHasOpenWork, isAbortError, shouldEnableImageOcr, } from "./session-policy.js";
69
69
  import { saveToolOutput, summarizeOutput, formatToolContext, } from "./tool-output-formatting.js";
70
70
  import { codingSessionFromContext, isProtocolPlaceholderOutput, progressPauseMode, } from "./progress-pause-policy.js";
@@ -75,13 +75,14 @@ import { absorbLooseWorkIntoLedger, applyDestinationCwd, canMarkTaskDone, hasLoc
75
75
  import { buildSessionStateBlock, inferNextHint, upsertSessionStateMessage, } from "./session-state.js";
76
76
  import { buildContinueOrientation, looksLikeContinueOrResumePrompt, } from "./continue-orient.js";
77
77
  import { detectPackageManager } from "./workspace-orient.js";
78
- import { budgetRemaining, consumeBudget, createRecoveryBudgets, freestyleClaimsAppReady, looksLikeShallowPentestReport, recoveryForErrorDiagnosis, recoveryForFailedProbe, recoveryForFreshness, recoveryForMissingFeature, recoveryForMissingPlan, recoveryForNarration, recoveryForPrematureComplete, recoveryForRuntimeVerify, recoveryForShallowPentest, } from "./must-continue.js";
78
+ import { budgetRemaining, consumeBudget, createRecoveryBudgets, } from "./must-continue.js";
79
+ import { chooseFinalizeRecovery } from "./finalize-gate.js";
79
80
  import { outOfScopeToolMessage, scopeContextMessage } from "./scope-context.js";
80
81
  import { EngagementPolicyEngine, actionFromUrl, engagementActionsForToolCall, evaluateEngagementAction, } from "../safety/engagement-policy.js";
81
82
  import { patchPlanMeta } from "../store/plan.js";
82
83
  import { extractProjectRootFromPlan, extractProjectRootFromScaffold, extractProjectRootFromText, getActiveProjectRoot, setActiveProjectRootIfValid, } from "./project-root.js";
83
84
  import { buildWorkspaceOrientation, discoverImmediateProjectRoots, guessProjectFolderName, isBareParentDirectory, isScaffoldCancelledOutput, scaffoldLooksMaterialized, scaffoldTargetConflictMessage, resolveScaffoldTargetPath, } from "./workspace-orient.js";
84
- import { inquirerConfirmPort, restoreInteractiveStdin, ensurePentestAuthorization, confirmToolExecution, } from "./confirm-port.js";
85
+ import { inquirerConfirmPort, inquirerSecretRequester, restoreInteractiveStdin, ensurePentestAuthorization, confirmToolExecution, } from "./confirm-port.js";
85
86
  import { buildRichStopSummary } from "./stop-summary.js";
86
87
  import { composeAgentSystemPrompt } from "./prompt-composer.js";
87
88
  import { createGovernorState, governProgress, } from "./evidence-governor.js";
@@ -543,6 +544,9 @@ export async function runAgentTurn(prompt, options = {}) {
543
544
  // Request, project, workspace, recovery, scope, and plan state are appended
544
545
  // later as system-marked turns so a changing byte cannot invalidate the
545
546
  // constitution (and, on Anthropic, the native tool schemas before it).
547
+ // The red-team methodology block is ~940 tokens on every request. Attach it
548
+ // only when this turn is actually a remote-security engagement.
549
+ const pentestPromptTurn = pentestLikeTurn || activePlan?.kind === "pentest";
546
550
  const buildStableSystemContent = (native) => {
547
551
  const reliability = getReliabilityPolicy();
548
552
  const visionAvailable = modelSupportsVision(provider, model);
@@ -554,6 +558,7 @@ export async function runAgentTurn(prompt, options = {}) {
554
558
  imageView: visionAvailable,
555
559
  // E6: slim native constitution when API tool schemas are attached.
556
560
  ...(native ? { slimNative: reliability.slimNativePrompt } : {}),
561
+ ...(pentestPromptTurn ? { pentest: true } : {}),
557
562
  });
558
563
  };
559
564
  const systemSections = [renderRequestEnvironmentContext()];
@@ -1633,9 +1638,17 @@ export async function runAgentTurn(prompt, options = {}) {
1633
1638
  if (isPlanMode &&
1634
1639
  !session.planApproved.value &&
1635
1640
  !isScratchOnlyWrite(call, scratchDir)) {
1636
- const cmd = typeof call.args.command === "string" ? call.args.command : "";
1637
- const shellBlocked = (call.name === "shell.exec" || call.name === "shell.start") &&
1638
- !isPlanModeAllowedShellCommand(cmd);
1641
+ const cmd = call.name === "terminal.send"
1642
+ ? typeof call.args.text === "string"
1643
+ ? call.args.text
1644
+ : ""
1645
+ : typeof call.args.command === "string"
1646
+ ? call.args.command
1647
+ : "";
1648
+ const shellBlocked = call.name === "terminal.start" ||
1649
+ call.name === "terminal.send" ||
1650
+ ((call.name === "shell.exec" || call.name === "shell.start") &&
1651
+ !isPlanModeAllowedShellCommand(cmd));
1639
1652
  const allowed = isPlanModeAllowedTool(call.name) && !shellBlocked;
1640
1653
  if (!allowed) {
1641
1654
  const reason = `plan mode — ${call.name} is blocked (gather-only). ` +
@@ -2078,7 +2091,7 @@ export async function runAgentTurn(prompt, options = {}) {
2078
2091
  const runToolWithForcedSettle = () => {
2079
2092
  const work = runToolCall(call, {
2080
2093
  signal: toolAc.signal,
2081
- requestSecret: options.requestSecret,
2094
+ requestSecret: options.requestSecret ?? inquirerSecretRequester,
2082
2095
  onOutput: (chunk) => {
2083
2096
  if (toolAc.signal.aborted)
2084
2097
  return;
@@ -4022,182 +4035,61 @@ export async function runAgentTurn(prompt, options = {}) {
4022
4035
  "If analysis is incomplete, call only the bounded evidence tool needed now. If each result has been analyzed and is satisfactory, you MUST call job.read with its jobId or exact notificationId before giving a final response. job.read does not require an active plan; do not create or update a plan merely to acknowledge a result."));
4023
4036
  continue;
4024
4037
  }
4025
- const planNarrated = (buildLikeTurn || pentestLikeTurn) &&
4026
- !activePlan &&
4027
- looksLikePlanNarration(cleaned);
4028
- // Only force "diagnosed but not fixed" when the model is still
4029
- // narrating a fix without having applied one this turn. Post-fix
4030
- // summaries ("I've fixed…", build passed) must never re-enter.
4031
- const errorFixNarration = !sawSuccessfulMutation &&
4032
- looksLikeErrorDiagnosisWithFixIntent(cleaned);
4033
- const shouldRetryBeforeFinalizing = productiveSteps === 0 ||
4034
- planNarrated ||
4035
- ((narratedAction || narratedWebAction) && !informationalQuery) ||
4036
- (session.planApproved.value &&
4037
- planHasOpenWorkNow &&
4038
- (narratedAction || errorFixNarration)) ||
4039
- // errorFix only when no mutation yet (gate is in errorFixNarration)
4040
- (session.planApproved.value && errorFixNarration) ||
4041
- (buildLikeTurn && errorFixNarration);
4042
- if (wantsAction &&
4043
- cleaned.trim().length > 0 &&
4044
- shouldRetryBeforeFinalizing) {
4045
- let action;
4046
- if (errorFixNarration && budgetRemaining(recovery, "errorFix")) {
4047
- action = recoveryForErrorDiagnosis(toolsAttached);
4048
- }
4049
- else if (budgetRemaining(recovery, "actionIntent") &&
4050
- planHasOpenWorkNow &&
4051
- session.planApproved.value) {
4052
- action = recoveryForNarration(toolsAttached, "plan_open");
4053
- }
4054
- else if (budgetRemaining(recovery, "actionIntent") &&
4055
- pentestLikeTurn) {
4056
- action = recoveryForNarration(toolsAttached, "pentest");
4057
- }
4058
- else if (budgetRemaining(recovery, "actionIntent") &&
4059
- (freshWebSearchRequired || narratedWebAction)) {
4060
- action = recoveryForNarration(toolsAttached, "web");
4061
- }
4062
- else if (budgetRemaining(recovery, "actionIntent") &&
4063
- buildLikeTurn &&
4064
- (planNarrated || productiveSteps > 0)) {
4065
- action = recoveryForNarration(toolsAttached, "build_plan_prose");
4066
- }
4067
- else if (budgetRemaining(recovery, "actionIntent") &&
4068
- buildLikeTurn) {
4069
- action = recoveryForNarration(toolsAttached, "build");
4070
- }
4071
- else if (budgetRemaining(recovery, "actionIntent")) {
4072
- action = recoveryForNarration(toolsAttached, "generic");
4073
- }
4074
- if (action) {
4075
- consumeBudget(recovery, action.budgetKey);
4076
- commitAssistantRetry(assistantText.visible);
4077
- messages.push(recoveryUserMessage(action.message));
4078
- continue;
4079
- }
4080
- }
4081
- if (freshWebSearchRequired &&
4082
- !sawFreshWebSearch &&
4083
- budgetRemaining(recovery, "freshnessUsed")) {
4084
- const action = recoveryForFreshness(freshnessGuardMessage() +
4085
- (toolsAttached
4086
- ? " Call the web_search tool now."
4087
- : " Reply with ONLY a fenced ```tool block for web.search now."));
4088
- consumeBudget(recovery, action.budgetKey);
4089
- commitAssistantRetry(assistantText.visible);
4090
- messages.push(recoveryUserMessage(action.message));
4091
- continue;
4092
- }
4093
- if (isPlanMode &&
4094
- !informationalQuery &&
4095
- !idleOrSocialPrompt &&
4096
- budgetRemaining(recovery, "forcePlan")) {
4097
- const planAtEnd = await loadPlan(session.sessionId).catch(() => undefined);
4098
- if (!planAtEnd && !sawPlanCreateOk) {
4099
- const action = recoveryForMissingPlan(toolsAttached);
4100
- consumeBudget(recovery, action.budgetKey);
4101
- commitAssistantRetry(assistantText.visible);
4102
- messages.push(recoveryUserMessage(action.message));
4103
- continue;
4104
- }
4105
- }
4106
- if (buildLike &&
4107
- !pentestLike &&
4108
- !pentestSession &&
4109
- session.planApproved.value &&
4110
- featureAppAsk &&
4111
- !sawFeatureImplWrite &&
4112
- (sawScaffoldOk || sawLocalAppMaterialWork) &&
4113
- productiveSteps > 0 &&
4114
- budgetRemaining(recovery, "featureImpl")) {
4115
- const action = recoveryForMissingFeature(getActiveProjectRoot());
4116
- consumeBudget(recovery, action.budgetKey);
4117
- commitAssistantRetry(assistantText.visible);
4118
- messages.push(recoveryUserMessage(action.message));
4119
- continue;
4120
- }
4121
- if (buildLike &&
4122
- !pentestLike &&
4123
- !pentestSession &&
4124
- budgetRemaining(recovery, "runtimeVerify") &&
4125
- (!featureAppAsk || sawFeatureImplWrite)) {
4126
- const runtimePlan = await loadPlan(session.sessionId).catch(() => undefined);
4127
- // Durable plan evidence or multi-signal proof this turn is enough
4128
- const planRuntimeOk = Boolean(runtimePlan && planHasVerifiedRuntime(runtimePlan));
4129
- const sessionRuntimeOk = sawServerStart &&
4130
- (sawServerTail || sawLocalHttpProbe || planRuntimeOk);
4131
- if (!planRuntimeOk && !sessionRuntimeOk) {
4132
- const codingPlanFinished = Boolean(runtimePlan &&
4133
- session.planApproved.value &&
4134
- runtimePlan.kind !== "pentest" &&
4135
- runtimePlan.tasks.length > 0 &&
4136
- runtimePlan.tasks.every((task) => task.state === "done" || task.state === "skipped"));
4137
- const freestyleLocalAppDone = !session.planApproved.value &&
4138
- sawLocalAppMaterialWork &&
4139
- productiveSteps > 0 &&
4140
- freestyleClaimsAppReady(cleaned) &&
4141
- (getActiveProjectRoot() !== undefined ||
4142
- /\b(?:npm|pnpm|yarn|bun)\s+run\s+dev\b/i.test(cleaned) ||
4143
- /\bopen\s+http:\/\/localhost\b/i.test(cleaned));
4144
- if (codingPlanFinished || freestyleLocalAppDone) {
4145
- const action = recoveryForRuntimeVerify(getActiveProjectRoot());
4146
- consumeBudget(recovery, action.budgetKey);
4147
- commitAssistantRetry(assistantText.visible);
4148
- messages.push(recoveryUserMessage(action.message));
4149
- continue;
4038
+ const deferResponderReport = session.planApproved.value &&
4039
+ budgetRemaining(recovery, "prematureComplete")
4040
+ ? shouldYieldForDeclaredResponderDependency(livePlanAtCompletion, jobManager.getRunningJobs(session.sessionId), jobManager.getPendingNotifications(session.sessionId), responderWakeNotificationId)
4041
+ : false;
4042
+ const finalizeRecovery = chooseFinalizeRecovery({
4043
+ cleaned,
4044
+ recovery,
4045
+ toolsAttached,
4046
+ productiveSteps,
4047
+ planApproved: session.planApproved.value,
4048
+ planHasOpenWork: planHasOpenWorkNow,
4049
+ activePlanExists: Boolean(activePlan),
4050
+ wantsAction,
4051
+ narratedAction,
4052
+ narratedWebAction,
4053
+ isPlanMode,
4054
+ buildLikeTurn,
4055
+ pentestLikeTurn,
4056
+ buildLike,
4057
+ pentestLike,
4058
+ pentestSession,
4059
+ informationalQuery,
4060
+ idleOrSocialPrompt,
4061
+ freshWebSearchRequired,
4062
+ freshnessGuardText: freshWebSearchRequired
4063
+ ? freshnessGuardMessage()
4064
+ : "",
4065
+ sawFreshWebSearch,
4066
+ sawPlanCreateOk,
4067
+ sawFeatureImplWrite,
4068
+ sawScaffoldOk,
4069
+ sawLocalAppMaterialWork,
4070
+ sawServerStart,
4071
+ sawServerTail,
4072
+ sawLocalHttpProbe,
4073
+ sawFailedLocalHttpProbe,
4074
+ sawActivePentestTest,
4075
+ sawSuccessfulMutation,
4076
+ featureAppAsk,
4077
+ projectRoot: getActiveProjectRoot(),
4078
+ plan: livePlanAtCompletion
4079
+ ? {
4080
+ kind: livePlanAtCompletion.kind,
4081
+ hasVerifiedRuntime: planHasVerifiedRuntime(livePlanAtCompletion),
4082
+ tasks: livePlanAtCompletion.tasks,
4150
4083
  }
4151
- }
4152
- }
4153
- if (buildLike &&
4154
- !pentestLike &&
4155
- !pentestSession &&
4156
- sawFailedLocalHttpProbe &&
4157
- !sawLocalHttpProbe &&
4158
- budgetRemaining(recovery, "failedProbe") &&
4159
- cleaned.trim().length > 0) {
4160
- const action = recoveryForFailedProbe();
4161
- consumeBudget(recovery, action.budgetKey);
4162
- commitAssistantRetry(assistantText.visible);
4163
- messages.push(recoveryUserMessage(action.message));
4164
- continue;
4165
- }
4166
- if ((pentestLike || pentestSession) &&
4167
- budgetRemaining(recovery, "shallowPentest") &&
4168
- looksLikeShallowPentestReport(cleaned, {
4169
- productiveSteps,
4170
- sawActiveTest: sawActivePentestTest,
4171
- })) {
4172
- const action = recoveryForShallowPentest();
4173
- consumeBudget(recovery, action.budgetKey);
4084
+ : undefined,
4085
+ deferResponderReport,
4086
+ });
4087
+ if (finalizeRecovery) {
4088
+ consumeBudget(recovery, finalizeRecovery.budgetKey);
4174
4089
  commitAssistantRetry(assistantText.visible);
4175
- messages.push(recoveryUserMessage(action.message));
4090
+ messages.push(recoveryUserMessage(finalizeRecovery.message));
4176
4091
  continue;
4177
4092
  }
4178
- if (session.planApproved.value &&
4179
- budgetRemaining(recovery, "prematureComplete")) {
4180
- const livePlan = await loadPlan(session.sessionId).catch(() => undefined);
4181
- const unfinished = livePlan?.tasks.filter((task) => !task.responderOwned &&
4182
- (task.state === "pending" || task.state === "in_progress"));
4183
- const deferReport = shouldYieldForDeclaredResponderDependency(livePlan, jobManager.getRunningJobs(session.sessionId), jobManager.getPendingNotifications(session.sessionId), responderWakeNotificationId);
4184
- if (livePlan &&
4185
- unfinished &&
4186
- unfinished.length > 0 &&
4187
- !deferReport) {
4188
- const next = unfinished[0];
4189
- const action = recoveryForPrematureComplete({
4190
- unfinished,
4191
- next,
4192
- pentest: livePlan.kind === "pentest" || pentestSession,
4193
- errorFix: errorFixNarration,
4194
- });
4195
- consumeBudget(recovery, action.budgetKey);
4196
- commitAssistantRetry(assistantText.visible);
4197
- messages.push(recoveryUserMessage(action.message));
4198
- continue;
4199
- }
4200
- }
4201
4093
  let outcomeStatus = "succeeded";
4202
4094
  const remainingCriteria = [];
4203
4095
  if (session.planApproved.value) {