imprint-mcp 0.6.0 → 0.6.2
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/README.md +2 -0
- package/package.json +4 -2
- package/prompts/audit-agent.md +4 -2
- package/prompts/auth-compile-agent.md +149 -158
- package/prompts/build-planning.md +14 -9
- package/prompts/compile-agent.md +46 -23
- package/prompts/live-verifier-agent.md +101 -0
- package/prompts/playbook-compilation.md +8 -1
- package/prompts/tool-candidate-detection.md +9 -29
- package/src/cli.ts +83 -1
- package/src/imprint/audit.ts +219 -20
- package/src/imprint/auth-compile-agent.ts +215 -155
- package/src/imprint/auth-compile-tools.ts +300 -118
- package/src/imprint/auth-verifier.ts +167 -180
- package/src/imprint/backend-cache.ts +18 -11
- package/src/imprint/backend-ladder.ts +225 -177
- package/src/imprint/build-plan.ts +3 -8
- package/src/imprint/cdp-browser-fetch.ts +930 -83
- package/src/imprint/claude-cli-compile.ts +46 -20
- package/src/imprint/codex-cli-compile.ts +436 -139
- package/src/imprint/compile-agent-types.ts +15 -10
- package/src/imprint/compile-agent.ts +88 -28
- package/src/imprint/compile-tools.ts +228 -39
- package/src/imprint/compile-verification.ts +187 -0
- package/src/imprint/compile.ts +71 -2
- package/src/imprint/credential-store.ts +57 -44
- package/src/imprint/emit.ts +3 -1
- package/src/imprint/freeform-redact.ts +27 -6
- package/src/imprint/live-verifier.ts +1665 -0
- package/src/imprint/llm.ts +22 -3
- package/src/imprint/login.ts +107 -25
- package/src/imprint/mcp-compile-server.ts +447 -114
- package/src/imprint/mcp-live-verifier-server.ts +359 -0
- package/src/imprint/mcp-server.ts +116 -35
- package/src/imprint/probe-backends.ts +88 -7
- package/src/imprint/recording-request.ts +36 -0
- package/src/imprint/request-capture.ts +40 -17
- package/src/imprint/runtime.ts +490 -337
- package/src/imprint/teach-state.ts +36 -2
- package/src/imprint/teach.ts +302 -146
- package/src/imprint/tool-candidates.ts +8 -10
- package/src/imprint/tool-loader.ts +12 -4
- package/src/imprint/types.ts +137 -68
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agentic auth tool compilation — the agent examines the session, writes
|
|
3
|
-
* workflow.json, tests
|
|
3
|
+
* workflow.json, tests its declared actions live, and iterates until login works.
|
|
4
4
|
*
|
|
5
5
|
* Mirrors compile-agent.ts for data tools but with the auth-specific live-test
|
|
6
6
|
* tool (run_verification) and lighter verification. Authentication runs on a
|
|
7
|
-
*
|
|
8
|
-
* recorded requests in-page); see AUTH_RUNGS in auth-verifier.ts. There is no
|
|
9
|
-
* bespoke login backend and no playbook rung in the auth path.
|
|
7
|
+
* persistent headed cdp-replay session so browser state survives checkpoints.
|
|
10
8
|
*
|
|
11
9
|
* Provider paths mirror compile-agent.ts exactly:
|
|
12
10
|
* - claude-cli / codex-cli: shell out with the auth toolset registered as a
|
|
@@ -15,7 +13,7 @@
|
|
|
15
13
|
* - anthropic-api (or any ToolUseProvider): drive in-process via runAgentLoop.
|
|
16
14
|
*/
|
|
17
15
|
|
|
18
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
16
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
19
17
|
import { join as pathJoin } from 'node:path';
|
|
20
18
|
import {
|
|
21
19
|
type AgentProgress,
|
|
@@ -25,15 +23,18 @@ import {
|
|
|
25
23
|
giveUpTool,
|
|
26
24
|
runAgentLoop,
|
|
27
25
|
} from './agent.ts';
|
|
28
|
-
import { ensureAuthBootstrap } from './auth-bootstrap.ts';
|
|
29
26
|
import {
|
|
30
27
|
AUTH_COMPILE_TOOL_NAMES,
|
|
28
|
+
AUTH_VERIFICATION_ATTEMPT_SENTINEL,
|
|
31
29
|
authExternalVerification,
|
|
30
|
+
authWorkflowHash,
|
|
31
|
+
authWorkflowPreflightFailures,
|
|
32
32
|
buildAuthCompileTools,
|
|
33
33
|
} from './auth-compile-tools.ts';
|
|
34
|
-
import { type
|
|
34
|
+
import { type AuthActionResult, AuthVerifier } from './auth-verifier.ts';
|
|
35
35
|
import type { AuthToolPlan } from './build-plan.ts';
|
|
36
36
|
import { compileAuthViaClaudeCli } from './claude-cli-compile.ts';
|
|
37
|
+
import { compileViaCodexCli } from './codex-cli-compile.ts';
|
|
37
38
|
import type {
|
|
38
39
|
AuthCheckpoint,
|
|
39
40
|
CompileAgentProgress,
|
|
@@ -47,7 +48,8 @@ import {
|
|
|
47
48
|
} from './llm.ts';
|
|
48
49
|
import { createLog } from './log.ts';
|
|
49
50
|
import { localToolDir } from './paths.ts';
|
|
50
|
-
import
|
|
51
|
+
import { loadCredentialStore } from './runtime.ts';
|
|
52
|
+
import { type Session, WorkflowSchema } from './types.ts';
|
|
51
53
|
|
|
52
54
|
const log = createLog('auth-compile-agent');
|
|
53
55
|
|
|
@@ -66,20 +68,14 @@ interface CompileAuthAgentOptions {
|
|
|
66
68
|
onProgress?: (p: CompileAgentProgress) => void;
|
|
67
69
|
/** Interactive bridge for the agent's `prompt_user` checkpoint: shows the
|
|
68
70
|
* agent-generated message (+ optional choices) in the teach TUI and returns
|
|
69
|
-
* the user's input
|
|
70
|
-
*
|
|
71
|
-
* completion unattended. */
|
|
71
|
+
* the user's input. When omitted, the agent receives an empty response and
|
|
72
|
+
* decides how to proceed. */
|
|
72
73
|
onPrompt?: (message: string, options?: string[]) => Promise<string>;
|
|
73
74
|
/** Cool-off bridge for the agent's `wait_for_cooldown` checkpoint: wait the
|
|
74
75
|
* given minutes (informing the user, firing NO login). Default sleeps. */
|
|
75
76
|
onCooldown?: (minutes: number, reason?: string) => Promise<void>;
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
/** Unattended OTP placeholder when no interactive prompt bridge is supplied. */
|
|
79
|
-
const ATTEMPT_OTP_PLACEHOLDER = '000000';
|
|
80
|
-
/** Runaway guard on the number of agent segments per auth run. */
|
|
81
|
-
const MAX_AUTH_SEGMENTS = 16;
|
|
82
|
-
|
|
83
79
|
/** Build the initial user message handed to the agent on its first turn.
|
|
84
80
|
* Shared verbatim by every provider path so the agent's framing is identical. */
|
|
85
81
|
function buildAuthInitialMessage(opts: {
|
|
@@ -94,15 +90,15 @@ function buildAuthInitialMessage(opts: {
|
|
|
94
90
|
// Cookies persist automatically — only surface NON-cookie header contracts.
|
|
95
91
|
return u.startsWith('header:') && u !== 'header:cookie' && u !== 'header:set-cookie';
|
|
96
92
|
});
|
|
97
|
-
const
|
|
93
|
+
const durableCaptureNote =
|
|
98
94
|
headerCaptures.length > 0
|
|
99
|
-
? `\n-
|
|
95
|
+
? `\n- durable credential contracts (data tools consume these as \${credential.<name>}): ${headerCaptures
|
|
100
96
|
.map(
|
|
101
97
|
(c) => `${c.name} (used as ${c.usedAs}; seed source ${c.source}, locator ${c.locator})`,
|
|
102
98
|
)
|
|
103
99
|
.join(
|
|
104
100
|
'; ',
|
|
105
|
-
)}\n →
|
|
101
|
+
)}\n → Capture each value on the recording-grounded producing request and include its name in authConfig.persist. The seed source/locator is only a hint.`
|
|
106
102
|
: '';
|
|
107
103
|
return `A new auth compile task is starting.
|
|
108
104
|
|
|
@@ -111,13 +107,12 @@ Tool name: ${toolName}
|
|
|
111
107
|
Tool directory: ${toolDir}
|
|
112
108
|
|
|
113
109
|
Auth tool plan:
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
-
|
|
117
|
-
- credentialNames: ${JSON.stringify(authToolPlan.credentialNames)}${sessionCaptureNote}
|
|
110
|
+
- credential-request hints: ${JSON.stringify(authToolPlan.credentialRequestSeqs)}
|
|
111
|
+
- related-auth-request hints: ${JSON.stringify(authToolPlan.authRequestSeqs)}
|
|
112
|
+
- credentialNames: ${JSON.stringify(authToolPlan.credentialNames)}${durableCaptureNote}
|
|
118
113
|
- notes: ${authToolPlan.notes || '(none)'}
|
|
119
114
|
|
|
120
|
-
|
|
115
|
+
MANDATORY FIRST ACTION: call read_session_summary now. Do not write prose, do not inspect repository files, and do not plan silently before that tool call. After read_session_summary returns, examine the login requests and write workflow.json per the system prompt.`;
|
|
121
116
|
}
|
|
122
117
|
|
|
123
118
|
export async function compileAuthAgent(opts: CompileAuthAgentOptions): Promise<CompileAgentResult> {
|
|
@@ -144,6 +139,7 @@ export async function compileAuthAgent(opts: CompileAuthAgentOptions): Promise<C
|
|
|
144
139
|
const resolved = resolveProvider(opts.llmConfig);
|
|
145
140
|
if (resolved.name === 'claude-cli') {
|
|
146
141
|
return await runAuthSegmentLoop({
|
|
142
|
+
driver: 'claude-cli',
|
|
147
143
|
site,
|
|
148
144
|
session,
|
|
149
145
|
sessionPath: opts.sessionPath,
|
|
@@ -153,6 +149,7 @@ export async function compileAuthAgent(opts: CompileAuthAgentOptions): Promise<C
|
|
|
153
149
|
toolDir,
|
|
154
150
|
authToolPlan,
|
|
155
151
|
teachCredentials: opts.teachCredentials,
|
|
152
|
+
model: opts.llmConfig?.model,
|
|
156
153
|
initialPrompt: initialUserMessage,
|
|
157
154
|
onProgress: opts.onProgress,
|
|
158
155
|
onPrompt: opts.onPrompt,
|
|
@@ -160,20 +157,23 @@ export async function compileAuthAgent(opts: CompileAuthAgentOptions): Promise<C
|
|
|
160
157
|
});
|
|
161
158
|
}
|
|
162
159
|
if (resolved.name === 'codex-cli') {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
160
|
+
return await runAuthSegmentLoop({
|
|
161
|
+
driver: 'codex-cli',
|
|
162
|
+
site,
|
|
163
|
+
session,
|
|
164
|
+
sessionPath: opts.sessionPath,
|
|
165
|
+
systemPromptPath,
|
|
166
|
+
deadlineMs,
|
|
167
|
+
startTime,
|
|
168
|
+
toolDir,
|
|
169
|
+
authToolPlan,
|
|
170
|
+
teachCredentials: opts.teachCredentials,
|
|
171
|
+
model: opts.llmConfig?.model,
|
|
172
|
+
initialPrompt: initialUserMessage,
|
|
173
|
+
onProgress: opts.onProgress,
|
|
174
|
+
onPrompt: opts.onPrompt,
|
|
175
|
+
onCooldown: opts.onCooldown,
|
|
176
|
+
});
|
|
177
177
|
}
|
|
178
178
|
if (!isToolUseProvider(resolved)) {
|
|
179
179
|
throw new Error(
|
|
@@ -203,11 +203,10 @@ export async function compileAuthAgent(opts: CompileAuthAgentOptions): Promise<C
|
|
|
203
203
|
let message = '';
|
|
204
204
|
let conversationLog: AgentResult['conversationLog'] = [];
|
|
205
205
|
|
|
206
|
-
const MAX_VERIFICATION_CYCLES = 3;
|
|
207
206
|
let verificationCycle = 0;
|
|
208
207
|
let currentInitialMessage = initialUserMessage;
|
|
209
208
|
|
|
210
|
-
while (
|
|
209
|
+
while (true) {
|
|
211
210
|
verificationCycle++;
|
|
212
211
|
|
|
213
212
|
const userOnProgress = opts.onProgress;
|
|
@@ -216,7 +215,6 @@ export async function compileAuthAgent(opts: CompileAuthAgentOptions): Promise<C
|
|
|
216
215
|
userOnProgress({
|
|
217
216
|
...p,
|
|
218
217
|
verificationCycle,
|
|
219
|
-
maxVerificationCycles: MAX_VERIFICATION_CYCLES,
|
|
220
218
|
})
|
|
221
219
|
: undefined;
|
|
222
220
|
|
|
@@ -249,6 +247,10 @@ export async function compileAuthAgent(opts: CompileAuthAgentOptions): Promise<C
|
|
|
249
247
|
const failures = authExternalVerification(
|
|
250
248
|
toolDir,
|
|
251
249
|
(authToolPlan.captures ?? []).map((c) => ({ name: c.name, usedAs: c.usedAs })),
|
|
250
|
+
{
|
|
251
|
+
requireLiveAttempt: true,
|
|
252
|
+
requiredCredentialNames: authToolPlan.credentialNames,
|
|
253
|
+
},
|
|
252
254
|
);
|
|
253
255
|
|
|
254
256
|
if (failures.length === 0) {
|
|
@@ -256,12 +258,6 @@ export async function compileAuthAgent(opts: CompileAuthAgentOptions): Promise<C
|
|
|
256
258
|
break;
|
|
257
259
|
}
|
|
258
260
|
|
|
259
|
-
if (verificationCycle >= MAX_VERIFICATION_CYCLES) {
|
|
260
|
-
outcome = 'error';
|
|
261
|
-
message = `Auth verification failed after ${MAX_VERIFICATION_CYCLES} cycles. Failures:\n${failures.join('\n')}`;
|
|
262
|
-
break;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
261
|
log(`auth verification failed (cycle ${verificationCycle}), resuming agent loop...`);
|
|
266
262
|
currentInitialMessage = `You called done but verification failed:
|
|
267
263
|
|
|
@@ -272,9 +268,6 @@ Fix the issues in workflow.json, re-test with run_verification, and call done ag
|
|
|
272
268
|
|
|
273
269
|
writeFileSync(conversationLogPath, JSON.stringify(conversationLog, null, 2), 'utf8');
|
|
274
270
|
|
|
275
|
-
// Same credential-entry bootstrap safety net as the segmented path.
|
|
276
|
-
injectAuthBootstrapArtifact(toolDir, session, authToolPlan);
|
|
277
|
-
|
|
278
271
|
const workflowPath = pathJoin(toolDir, 'workflow.json');
|
|
279
272
|
|
|
280
273
|
return {
|
|
@@ -293,6 +286,7 @@ Fix the issues in workflow.json, re-test with run_verification, and call done ag
|
|
|
293
286
|
}
|
|
294
287
|
|
|
295
288
|
interface AuthSegmentLoopOptions {
|
|
289
|
+
driver: 'claude-cli' | 'codex-cli';
|
|
296
290
|
site: string;
|
|
297
291
|
session: Session;
|
|
298
292
|
sessionPath: string;
|
|
@@ -302,6 +296,7 @@ interface AuthSegmentLoopOptions {
|
|
|
302
296
|
toolDir: string;
|
|
303
297
|
authToolPlan: NonNullable<AuthToolPlan>;
|
|
304
298
|
teachCredentials: { site: string; values: Record<string, string> };
|
|
299
|
+
model?: string;
|
|
305
300
|
initialPrompt: string;
|
|
306
301
|
onProgress?: (p: CompileAgentProgress) => void;
|
|
307
302
|
onPrompt?: (message: string, options?: string[]) => Promise<string>;
|
|
@@ -315,7 +310,7 @@ function sleep(ms: number): Promise<void> {
|
|
|
315
310
|
/** The full set of live-execution facts every verification result carries, so the
|
|
316
311
|
* agent never has to re-run (or reverse-engineer the runtime) to see what
|
|
317
312
|
* happened: rung, timing, HTTP status, error code, and the response body. */
|
|
318
|
-
function verifyFacts(r:
|
|
313
|
+
function verifyFacts(r: AuthActionResult): string {
|
|
319
314
|
const parts = [`backend=${r.usedBackend}`, `duration=${r.durationMs}ms`];
|
|
320
315
|
if (typeof r.status === 'number') parts.push(`httpStatus=${r.status}`);
|
|
321
316
|
if (r.error) parts.push(`error=${r.error}`);
|
|
@@ -324,40 +319,42 @@ function verifyFacts(r: AuthPhaseResult): string {
|
|
|
324
319
|
return s;
|
|
325
320
|
}
|
|
326
321
|
|
|
327
|
-
/** Render an AuthVerifier
|
|
322
|
+
/** Render an AuthVerifier action result into the message the agent receives on
|
|
328
323
|
* resume — channel-agnostic, grounded only in the result. Always includes the
|
|
329
324
|
* full execution facts (status, timing, backend, body) so the agent can decide
|
|
330
325
|
* its next move without inspecting the runtime. */
|
|
331
|
-
function formatVerifyResult(
|
|
326
|
+
function formatVerifyResult(action: string, r: AuthActionResult): string {
|
|
332
327
|
const facts = verifyFacts(r);
|
|
333
328
|
if (r.ok) {
|
|
334
|
-
return `Verification
|
|
335
|
-
}
|
|
336
|
-
if (r.error === 'AWAITING_2FA') {
|
|
337
|
-
const ctxKeys = r.twoFactorContext ? Object.keys(r.twoFactorContext) : [];
|
|
338
|
-
return `Verification phase "${phase}" reached the 2FA challenge (AWAITING_2FA, type=${r.twoFactorType ?? 'unknown'}). ${facts}\nThe OTP/push has been delivered to the user${ctxKeys.length ? ` (carried token keys: ${ctxKeys.join(', ')})` : ''}. Now call prompt_user to ask them for the live second factor, then run_verification for the completion phase.`;
|
|
339
|
-
}
|
|
340
|
-
if (r.error === 'BUDGET_EXHAUSTED') {
|
|
341
|
-
return `Verification refused: ${r.message} ${facts}\nDo not request another initiate — give_up if you cannot complete.`;
|
|
329
|
+
return `Verification action "${action}" SUCCEEDED. ${facts}\nThe action's declared success outcome was reached. Call done with a one-line summary.`;
|
|
342
330
|
}
|
|
343
|
-
if (r.error === '
|
|
344
|
-
return `Verification
|
|
331
|
+
if (r.error === 'ACTION_REQUIRED') {
|
|
332
|
+
return `Verification action "${action}" reached its declared pause outcome. ${facts}\nnextAction=${r.nextAction ?? '(missing)'}\n${r.message ?? ''}\nUse the recording and this result to decide whether to prompt the user or run the next action directly.`;
|
|
345
333
|
}
|
|
346
|
-
return `Verification
|
|
334
|
+
return `Verification action "${action}" FAILED. ${facts}\n${r.message ?? ''}\nUse the recording and observed response to revise the action program or choose the next checkpoint.`;
|
|
347
335
|
}
|
|
348
336
|
|
|
349
337
|
/**
|
|
350
|
-
* Drive
|
|
338
|
+
* Drive a CLI auth compile as a sequence of checkpointed SEGMENTS. The
|
|
351
339
|
* agent shapes from the recording, then pauses at checkpoint tools; this loop
|
|
352
|
-
* (the durable orchestrator) executes each checkpoint — run_verification
|
|
353
|
-
* persistent AuthVerifier session, prompt_user via the
|
|
354
|
-
* cool-off wait —
|
|
355
|
-
* stateful thing (the live browser) lives in the AuthVerifier and is
|
|
356
|
-
* the end.
|
|
340
|
+
* (the durable orchestrator) executes each checkpoint — run_verification or
|
|
341
|
+
* page inspection on the persistent AuthVerifier session, prompt_user via the
|
|
342
|
+
* TUI bridge, or a cool-off wait — then feeds the result into the next segment.
|
|
343
|
+
* The ONE stateful thing (the live browser) lives in the AuthVerifier and is
|
|
344
|
+
* drained at the end. CLI conversation state is carried by provider-native resume
|
|
345
|
+
* (`claude --resume`, `codex exec resume`), not retained here.
|
|
357
346
|
*/
|
|
358
347
|
async function runAuthSegmentLoop(opts: AuthSegmentLoopOptions): Promise<CompileAgentResult> {
|
|
359
348
|
const workflowPath = pathJoin(opts.toolDir, 'workflow.json');
|
|
360
|
-
const
|
|
349
|
+
const verificationAttemptPath = pathJoin(opts.toolDir, AUTH_VERIFICATION_ATTEMPT_SENTINEL);
|
|
350
|
+
if (existsSync(verificationAttemptPath)) unlinkSync(verificationAttemptPath);
|
|
351
|
+
const storedCredentials = await loadCredentialStore(opts.site);
|
|
352
|
+
const credsForRun = {
|
|
353
|
+
site: opts.site,
|
|
354
|
+
cookies: storedCredentials?.cookies ?? [],
|
|
355
|
+
values: { ...(storedCredentials?.values ?? {}), ...opts.teachCredentials.values },
|
|
356
|
+
storage: storedCredentials?.storage ?? [],
|
|
357
|
+
};
|
|
361
358
|
const verifier = new AuthVerifier(workflowPath, credsForRun);
|
|
362
359
|
const authMode = {
|
|
363
360
|
site: opts.site,
|
|
@@ -366,20 +363,24 @@ async function runAuthSegmentLoop(opts: AuthSegmentLoopOptions): Promise<Compile
|
|
|
366
363
|
initialPrompt: opts.initialPrompt,
|
|
367
364
|
};
|
|
368
365
|
|
|
369
|
-
const onPrompt = opts.onPrompt ?? (async () =>
|
|
370
|
-
const onCooldown =
|
|
371
|
-
opts.onCooldown ?? (async (minutes: number) => sleep(Math.min(minutes, 10) * 60_000));
|
|
366
|
+
const onPrompt = opts.onPrompt ?? (async () => '');
|
|
367
|
+
const onCooldown = opts.onCooldown ?? (async (minutes: number) => sleep(minutes * 60_000));
|
|
372
368
|
|
|
373
369
|
let resume: { sessionId: string; message: string } | undefined;
|
|
374
370
|
let last: CompileAgentResult | undefined;
|
|
375
371
|
let totalTurns = 0;
|
|
372
|
+
let activeDeadlineMs = opts.deadlineMs;
|
|
376
373
|
// The most recent live verification, surfaced on the orchestrator's progress
|
|
377
374
|
// line so a failure (e.g. a 403) is visible the instant it happens.
|
|
378
375
|
let lastVerification: CompileAgentProgress['lastVerification'];
|
|
376
|
+
let segment = 0;
|
|
377
|
+
const promptSecrets = new Map<string, string>();
|
|
378
|
+
let promptSecretSequence = 0;
|
|
379
379
|
|
|
380
380
|
try {
|
|
381
|
-
|
|
382
|
-
|
|
381
|
+
while (true) {
|
|
382
|
+
segment++;
|
|
383
|
+
// Each resumed/restarted segment has its own per-segment `turn` at 0; add
|
|
383
384
|
// the prior segments' turns so the displayed count is monotonic (no reset).
|
|
384
385
|
const offset = totalTurns; // turns from prior segments only (read BEFORE the += below)
|
|
385
386
|
const wrappedOnProgress = opts.onProgress
|
|
@@ -387,85 +388,176 @@ async function runAuthSegmentLoop(opts: AuthSegmentLoopOptions): Promise<Compile
|
|
|
387
388
|
opts.onProgress?.({
|
|
388
389
|
...p,
|
|
389
390
|
turn: offset + p.turn,
|
|
390
|
-
segment
|
|
391
|
-
maxSegments: MAX_AUTH_SEGMENTS,
|
|
392
|
-
attempt: verifier.attemptsUsed,
|
|
393
|
-
maxAttempts: verifier.maxInitiateAttempts,
|
|
391
|
+
segment,
|
|
394
392
|
lastVerification,
|
|
395
393
|
})
|
|
396
394
|
: undefined;
|
|
397
395
|
|
|
398
|
-
const result =
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
396
|
+
const result =
|
|
397
|
+
opts.driver === 'claude-cli'
|
|
398
|
+
? await compileAuthViaClaudeCli({
|
|
399
|
+
session: opts.session,
|
|
400
|
+
absoluteToolDir: opts.toolDir,
|
|
401
|
+
sessionPath: opts.sessionPath,
|
|
402
|
+
systemPromptPath: opts.systemPromptPath,
|
|
403
|
+
deadlineMs: activeDeadlineMs,
|
|
404
|
+
startTime: opts.startTime,
|
|
405
|
+
onProgress: wrappedOnProgress,
|
|
406
|
+
authMode,
|
|
407
|
+
resume,
|
|
408
|
+
model: opts.model,
|
|
409
|
+
})
|
|
410
|
+
: await compileViaCodexCli({
|
|
411
|
+
session: opts.session,
|
|
412
|
+
absoluteToolDir: opts.toolDir,
|
|
413
|
+
sessionPath: opts.sessionPath,
|
|
414
|
+
systemPromptPath: opts.systemPromptPath,
|
|
415
|
+
deadlineMs: activeDeadlineMs,
|
|
416
|
+
startTime: opts.startTime,
|
|
417
|
+
onProgress: wrappedOnProgress,
|
|
418
|
+
authMode,
|
|
419
|
+
resume,
|
|
420
|
+
model: opts.model,
|
|
421
|
+
});
|
|
409
422
|
last = result;
|
|
410
423
|
totalTurns += result.turns;
|
|
411
424
|
|
|
412
|
-
if (result.outcome !== 'checkpoint' || !result.checkpoint)
|
|
425
|
+
if (result.outcome !== 'checkpoint' || !result.checkpoint) {
|
|
426
|
+
if (result.outcome === 'soft_cap' && result.sessionId && Date.now() < activeDeadlineMs) {
|
|
427
|
+
const failures = authWorkflowPreflightFailures(
|
|
428
|
+
opts.toolDir,
|
|
429
|
+
opts.session,
|
|
430
|
+
opts.authToolPlan.credentialNames,
|
|
431
|
+
);
|
|
432
|
+
resume = {
|
|
433
|
+
sessionId: result.sessionId,
|
|
434
|
+
message: `You stopped without calling done, give_up, or recording a checkpoint. Continue the auth compile now.${
|
|
435
|
+
failures.length > 0
|
|
436
|
+
? ` Current workflow preflight failures:\n${failures.map((failure) => `- ${failure}`).join('\n')}`
|
|
437
|
+
: ''
|
|
438
|
+
}`,
|
|
439
|
+
};
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
413
444
|
if (!result.sessionId) {
|
|
414
445
|
last = {
|
|
415
446
|
...result,
|
|
416
447
|
outcome: 'error',
|
|
417
448
|
success: false,
|
|
418
|
-
message:
|
|
449
|
+
message: `checkpoint reached but no ${opts.driver} session id was captured — cannot resume the agent.`,
|
|
419
450
|
};
|
|
420
451
|
break;
|
|
421
452
|
}
|
|
422
453
|
|
|
423
454
|
const cp: AuthCheckpoint = result.checkpoint;
|
|
424
455
|
let resultMsg: string;
|
|
456
|
+
const checkpointStartedAt = Date.now();
|
|
425
457
|
try {
|
|
426
458
|
if (cp.kind === 'run_verification') {
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
459
|
+
const preflightFailures = authWorkflowPreflightFailures(
|
|
460
|
+
opts.toolDir,
|
|
461
|
+
opts.session,
|
|
462
|
+
opts.authToolPlan.credentialNames,
|
|
463
|
+
);
|
|
464
|
+
if (Date.now() >= activeDeadlineMs) {
|
|
465
|
+
resultMsg =
|
|
466
|
+
'run_verification was blocked because the auth compile deadline expired; no live login was fired.';
|
|
467
|
+
} else if (preflightFailures.length > 0) {
|
|
468
|
+
resultMsg = `run_verification was blocked before any live login was fired because workflow.json failed auth preflight:\n${preflightFailures
|
|
469
|
+
.map((failure) => `- ${failure}`)
|
|
470
|
+
.join('\n')}`;
|
|
471
|
+
} else {
|
|
472
|
+
const verifiedWorkflowHash = authWorkflowHash(
|
|
473
|
+
WorkflowSchema.parse(
|
|
474
|
+
JSON.parse(readFileSync(pathJoin(opts.toolDir, 'workflow.json'), 'utf8')),
|
|
475
|
+
),
|
|
476
|
+
);
|
|
477
|
+
const resolvedParameters = Object.fromEntries(
|
|
478
|
+
Object.entries(cp.parameters ?? {}).map(([name, value]) => [
|
|
479
|
+
name,
|
|
480
|
+
typeof value === 'string' && promptSecrets.has(value)
|
|
481
|
+
? (promptSecrets.get(value) ?? '')
|
|
482
|
+
: value,
|
|
483
|
+
]),
|
|
484
|
+
);
|
|
485
|
+
const r = await verifier.runAction(cp.action, resolvedParameters, {
|
|
486
|
+
freshSession: cp.freshSession,
|
|
487
|
+
cleanSession: cp.cleanSession,
|
|
488
|
+
});
|
|
489
|
+
writeFileSync(
|
|
490
|
+
verificationAttemptPath,
|
|
491
|
+
JSON.stringify(
|
|
492
|
+
{
|
|
493
|
+
action: cp.action,
|
|
494
|
+
ok: r.ok,
|
|
495
|
+
status: r.status,
|
|
496
|
+
error: r.error,
|
|
497
|
+
backend: r.usedBackend,
|
|
498
|
+
workflowHash: verifiedWorkflowHash,
|
|
499
|
+
timestamp: Date.now(),
|
|
500
|
+
},
|
|
501
|
+
null,
|
|
502
|
+
2,
|
|
503
|
+
),
|
|
504
|
+
'utf8',
|
|
505
|
+
);
|
|
506
|
+
// Record + immediately surface the result so the spinner reflects a
|
|
507
|
+
// failure the moment it happens — not only on the next agent turn.
|
|
508
|
+
lastVerification = {
|
|
509
|
+
action: cp.action,
|
|
510
|
+
ok: r.ok,
|
|
511
|
+
status: r.status,
|
|
512
|
+
error: r.error,
|
|
513
|
+
backend: r.usedBackend,
|
|
514
|
+
durationMs: r.durationMs,
|
|
515
|
+
checkpoint: 'run_verification',
|
|
516
|
+
};
|
|
517
|
+
opts.onProgress?.({
|
|
518
|
+
turn: totalTurns,
|
|
519
|
+
phase: 'tool',
|
|
520
|
+
elapsedMs: Date.now() - opts.startTime,
|
|
521
|
+
budgetMs: Math.max(0, activeDeadlineMs - Date.now()),
|
|
522
|
+
inputTokens: 0,
|
|
523
|
+
outputTokens: 0,
|
|
524
|
+
verificationCycle: 1,
|
|
525
|
+
segment,
|
|
526
|
+
lastVerification,
|
|
527
|
+
});
|
|
528
|
+
resultMsg = formatVerifyResult(cp.action, r);
|
|
529
|
+
}
|
|
530
|
+
} else if (cp.kind === 'inspect_verification_page') {
|
|
531
|
+
const inspected = await verifier.inspectPage({
|
|
532
|
+
maxChars: cp.maxChars,
|
|
533
|
+
includeCookies: cp.includeCookies,
|
|
453
534
|
});
|
|
454
|
-
resultMsg =
|
|
535
|
+
resultMsg = inspected.ok
|
|
536
|
+
? `Current verification page snapshot:\n${JSON.stringify(inspected, null, 2)}`
|
|
537
|
+
: `Verification page inspection was unavailable: ${inspected.message ?? 'unknown reason'}`;
|
|
455
538
|
} else if (cp.kind === 'prompt_user') {
|
|
456
539
|
const answer = await onPrompt(cp.message, cp.options);
|
|
457
|
-
|
|
540
|
+
const reference = `\${prompt.${++promptSecretSequence}}`;
|
|
541
|
+
promptSecrets.set(reference, answer);
|
|
542
|
+
resultMsg = answer
|
|
543
|
+
? `The user response is stored locally as ${reference}. Pass that exact reference as the appropriate run_verification parameter value; the orchestrator resolves it without exposing the answer to the model.`
|
|
544
|
+
: 'The user provided no input.';
|
|
458
545
|
} else {
|
|
459
546
|
await onCooldown(cp.minutes, cp.reason);
|
|
460
|
-
resultMsg = `
|
|
547
|
+
resultMsg = `Requested delay of ~${cp.minutes} min completed; no live action ran during the wait.`;
|
|
461
548
|
}
|
|
462
549
|
} catch (err) {
|
|
463
550
|
resultMsg = `The orchestrator could not perform ${cp.kind}: ${err instanceof Error ? err.message : String(err)}`;
|
|
551
|
+
} finally {
|
|
552
|
+
if (cp.kind === 'prompt_user' || cp.kind === 'wait_for_cooldown') {
|
|
553
|
+
activeDeadlineMs += Date.now() - checkpointStartedAt;
|
|
554
|
+
}
|
|
464
555
|
}
|
|
465
556
|
|
|
557
|
+
const resumeMessage = `[orchestrator result for your ${cp.kind} request]\n${resultMsg}\n\nContinue from the recording and observed result. You control the next action or checkpoint.`;
|
|
466
558
|
resume = {
|
|
467
559
|
sessionId: result.sessionId,
|
|
468
|
-
message:
|
|
560
|
+
message: resumeMessage,
|
|
469
561
|
};
|
|
470
562
|
}
|
|
471
563
|
} finally {
|
|
@@ -486,42 +578,10 @@ async function runAuthSegmentLoop(opts: AuthSegmentLoopOptions): Promise<Compile
|
|
|
486
578
|
cacheCreationInputTokens: 0,
|
|
487
579
|
};
|
|
488
580
|
}
|
|
489
|
-
// Safety net: make sure the auth workflow navigates the credential-entry page
|
|
490
|
-
// before the login POST (so cdp-replay validates the login page's anti-bot
|
|
491
|
-
// token). The agent is told to set this; fill it in deterministically if it
|
|
492
|
-
// didn't, so a forgetful LLM never costs a wasted live-login attempt.
|
|
493
|
-
injectAuthBootstrapArtifact(opts.toolDir, opts.session, opts.authToolPlan);
|
|
494
|
-
|
|
495
581
|
// Surface the cumulative turn count across segments.
|
|
496
582
|
return { ...last, turns: totalTurns };
|
|
497
583
|
}
|
|
498
584
|
|
|
499
|
-
/** Read the compiled auth workflow.json and inject a derived credential-entry
|
|
500
|
-
* `bootstrap` if it lacks one. Best-effort: never fails the compile. */
|
|
501
|
-
function injectAuthBootstrapArtifact(
|
|
502
|
-
toolDir: string,
|
|
503
|
-
session: Session,
|
|
504
|
-
plan: NonNullable<AuthToolPlan>,
|
|
505
|
-
): void {
|
|
506
|
-
const workflowPath = pathJoin(toolDir, 'workflow.json');
|
|
507
|
-
try {
|
|
508
|
-
if (!existsSync(workflowPath)) return;
|
|
509
|
-
const wf = JSON.parse(readFileSync(workflowPath, 'utf8'));
|
|
510
|
-
const { changed, url } = ensureAuthBootstrap(
|
|
511
|
-
wf,
|
|
512
|
-
session,
|
|
513
|
-
plan.loginRequestSeqs,
|
|
514
|
-
plan.credentialNames,
|
|
515
|
-
);
|
|
516
|
-
if (changed) {
|
|
517
|
-
writeFileSync(workflowPath, JSON.stringify(wf, null, 2), 'utf8');
|
|
518
|
-
log(`injected credential-entry bootstrap into workflow.json: ${url}`);
|
|
519
|
-
}
|
|
520
|
-
} catch {
|
|
521
|
-
// best-effort — a bootstrap convenience must never break the compile
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
585
|
function buildMessageFromOutcome(result: AgentResult): string {
|
|
526
586
|
switch (result.outcome) {
|
|
527
587
|
case 'give_up':
|