imprint-mcp 0.7.0 → 0.7.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 +66 -2
- package/package.json +1 -1
- package/prompts/auth-compile-agent.md +173 -22
- package/prompts/compile-agent.md +45 -8
- package/prompts/component-recovery.md +40 -0
- package/prompts/master-teach-decision.md +28 -3
- package/prompts/master-teach-focused-planner.md +18 -5
- package/src/cli.ts +26 -18
- package/src/imprint/agent-tool.ts +15 -0
- package/src/imprint/agent.ts +2 -11
- package/src/imprint/audit.ts +35 -13
- package/src/imprint/auth-browser-discovery.ts +276 -0
- package/src/imprint/auth-compile-agent.ts +513 -224
- package/src/imprint/auth-compile-tools.ts +213 -25
- package/src/imprint/auth-discovery-evidence.ts +81 -0
- package/src/imprint/auth-plan.ts +28 -0
- package/src/imprint/auth-verifier.ts +234 -20
- package/src/imprint/authentication-interaction.ts +137 -0
- package/src/imprint/backend-ladder.ts +271 -43
- package/src/imprint/build-plan.ts +2 -22
- package/src/imprint/cdp-browser-fetch.ts +242 -60
- package/src/imprint/chromium.ts +93 -29
- package/src/imprint/claude-cli-compile.ts +2 -0
- package/src/imprint/codex-cli-compile.ts +99 -46
- package/src/imprint/compile-agent-types.ts +9 -5
- package/src/imprint/compile-agent.ts +33 -0
- package/src/imprint/compile-tool-executor.ts +783 -0
- package/src/imprint/compile-tools.ts +42 -9
- package/src/imprint/compile-verification.ts +2 -0
- package/src/imprint/compile.ts +3 -0
- package/src/imprint/compiler-log.ts +16 -0
- package/src/imprint/component-recovery.ts +120 -0
- package/src/imprint/cookie-jar.ts +117 -7
- package/src/imprint/credential-extract.ts +9 -2
- package/src/imprint/credential-lock.ts +147 -0
- package/src/imprint/credential-observation.ts +261 -0
- package/src/imprint/credential-store.ts +284 -44
- package/src/imprint/live-component-recovery.ts +73 -0
- package/src/imprint/llm.ts +4 -3
- package/src/imprint/log.ts +16 -1
- package/src/imprint/login.ts +9 -0
- package/src/imprint/master-teach-agent-contracts.ts +40 -3
- package/src/imprint/master-teach-agents.ts +229 -14
- package/src/imprint/master-teach-checks.ts +8 -1
- package/src/imprint/master-teach-controller.ts +681 -138
- package/src/imprint/master-teach-prompt-projections.ts +2 -0
- package/src/imprint/mcp-cdp-sessions.ts +7 -2
- package/src/imprint/mcp-compile-server.ts +62 -850
- package/src/imprint/mcp-server.ts +667 -91
- package/src/imprint/mcp-session-recovery.ts +167 -0
- package/src/imprint/native-teach-agents.ts +232 -108
- package/src/imprint/runtime.ts +416 -47
- package/src/imprint/stealth-chromium.ts +67 -7
- package/src/imprint/teach-browser-resources.ts +88 -0
- package/src/imprint/teach-stage-progress.ts +38 -0
- package/src/imprint/terminal-user-prompt.ts +42 -0
- package/src/imprint/types.ts +51 -18
- package/src/imprint/user-interaction.ts +836 -0
|
@@ -13,17 +13,21 @@
|
|
|
13
13
|
* - anthropic-api (or any ToolUseProvider): drive in-process via runAgentLoop.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { randomUUID } from 'node:crypto';
|
|
16
17
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
17
18
|
import { join as pathJoin } from 'node:path';
|
|
18
19
|
import {
|
|
19
20
|
type AgentProgress,
|
|
20
21
|
type AgentResult,
|
|
21
22
|
type AgentTool,
|
|
23
|
+
type ConversationLogEntry,
|
|
22
24
|
doneTool,
|
|
23
25
|
giveUpTool,
|
|
24
26
|
runAgentLoop,
|
|
25
27
|
} from './agent.ts';
|
|
28
|
+
import { AuthBrowserActionSchema } from './auth-browser-discovery.ts';
|
|
26
29
|
import {
|
|
30
|
+
AUTH_CHECKPOINT_TOOLS,
|
|
27
31
|
AUTH_COMPILE_TOOL_NAMES,
|
|
28
32
|
AUTH_VERIFICATION_ATTEMPT_SENTINEL,
|
|
29
33
|
authExternalVerification,
|
|
@@ -31,18 +35,22 @@ import {
|
|
|
31
35
|
authWorkflowHash,
|
|
32
36
|
authWorkflowPreflightFailures,
|
|
33
37
|
buildAuthCompileTools,
|
|
38
|
+
parseAuthCheckpoint,
|
|
34
39
|
} from './auth-compile-tools.ts';
|
|
40
|
+
import { AuthDiscoveryEvidence } from './auth-discovery-evidence.ts';
|
|
35
41
|
import { type AuthActionResult, AuthVerifier } from './auth-verifier.ts';
|
|
36
42
|
import type { AuthToolPlan } from './build-plan.ts';
|
|
37
43
|
import { compileAuthViaClaudeCli } from './claude-cli-compile.ts';
|
|
38
44
|
import { compileViaCodexCli } from './codex-cli-compile.ts';
|
|
39
45
|
import type {
|
|
40
46
|
AuthCheckpoint,
|
|
47
|
+
AuthCheckpointResult,
|
|
41
48
|
CompileAgentProgress,
|
|
42
49
|
CompileAgentResult,
|
|
43
50
|
} from './compile-agent-types.ts';
|
|
44
51
|
import { runCompileWithProviderRecovery } from './compile-provider-recovery.ts';
|
|
45
52
|
import { abortSignalError, abortableDelay, withAbortSignal } from './concurrency.ts';
|
|
53
|
+
import { isCredentialReference } from './credential-extract.ts';
|
|
46
54
|
import {
|
|
47
55
|
type LLMOptions,
|
|
48
56
|
type ToolUseProvider,
|
|
@@ -58,15 +66,278 @@ import {
|
|
|
58
66
|
providerControlError,
|
|
59
67
|
resolvedRunDeadline,
|
|
60
68
|
} from './provider-retry.ts';
|
|
61
|
-
import { loadCredentialStore } from './runtime.ts';
|
|
69
|
+
import { type CredentialStore, loadCredentialStore } from './runtime.ts';
|
|
62
70
|
import { type Session, WorkflowSchema } from './types.ts';
|
|
71
|
+
import { requestAgentInteraction, resolveUserInput } from './user-interaction.ts';
|
|
63
72
|
|
|
64
73
|
const log = createLog('auth-compile-agent');
|
|
65
74
|
|
|
66
75
|
const REPO_ROOT = pathJoin(import.meta.dir, '..', '..');
|
|
67
76
|
const PROMPTS_DIR = pathJoin(REPO_ROOT, 'prompts');
|
|
68
77
|
|
|
78
|
+
async function loadAuthCredentials(
|
|
79
|
+
site: string,
|
|
80
|
+
recordedValues: Record<string, string>,
|
|
81
|
+
): Promise<CredentialStore> {
|
|
82
|
+
const stored = await loadCredentialStore(site);
|
|
83
|
+
// Already-redacted recordings can reach the compiler through other callers.
|
|
84
|
+
// Never let their references shadow real values from the credential backend.
|
|
85
|
+
const literalValues = Object.fromEntries(
|
|
86
|
+
Object.entries(recordedValues).filter(([, value]) => !isCredentialReference(value)),
|
|
87
|
+
);
|
|
88
|
+
return {
|
|
89
|
+
site,
|
|
90
|
+
cookies: stored?.cookies ?? [],
|
|
91
|
+
storage: stored?.storage ?? [],
|
|
92
|
+
values: { ...stored?.values, ...literalValues },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function promptAuthUser(
|
|
97
|
+
message: string,
|
|
98
|
+
options?: string[],
|
|
99
|
+
signal?: AbortSignal,
|
|
100
|
+
): Promise<string> {
|
|
101
|
+
const result = await requestAgentInteraction(
|
|
102
|
+
{
|
|
103
|
+
action: 'prompt_user',
|
|
104
|
+
prompt: { message, options, sensitive: options === undefined },
|
|
105
|
+
},
|
|
106
|
+
signal,
|
|
107
|
+
);
|
|
108
|
+
if (result.status !== 'answered' || !('value' in result)) {
|
|
109
|
+
throw new Error('reason' in result ? result.reason : 'User input is unavailable');
|
|
110
|
+
}
|
|
111
|
+
return await resolveUserInput(result.value);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface AuthCheckpointExecutorOptions {
|
|
115
|
+
context: AuthCompileContext;
|
|
116
|
+
session: Session;
|
|
117
|
+
sessionPath: string;
|
|
118
|
+
toolDir: string;
|
|
119
|
+
authToolPlan: NonNullable<AuthToolPlan>;
|
|
120
|
+
verifier: AuthVerifier;
|
|
121
|
+
signal?: AbortSignal;
|
|
122
|
+
deadline?: () => number | undefined;
|
|
123
|
+
onPrompt?: CompileAuthAgentOptions['onPrompt'];
|
|
124
|
+
onCooldown?: CompileAuthAgentOptions['onCooldown'];
|
|
125
|
+
onVerification?: (action: string, result: AuthActionResult) => void;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** One operation owns its current human references. All provider adapters execute
|
|
129
|
+
* the same host actions; native/API calls await them without ending an assignment. */
|
|
130
|
+
function createAuthCheckpointExecutor(options: AuthCheckpointExecutorOptions) {
|
|
131
|
+
const promptSecrets = new Map<string, string>();
|
|
132
|
+
const lifetime = new AbortController();
|
|
133
|
+
return {
|
|
134
|
+
dispose: () => {
|
|
135
|
+
promptSecrets.clear();
|
|
136
|
+
lifetime.abort(new Error('Authentication checkpoint operation ended'));
|
|
137
|
+
},
|
|
138
|
+
async execute(cp: AuthCheckpoint, requestSignal?: AbortSignal): Promise<AuthCheckpointResult> {
|
|
139
|
+
const signal = AbortSignal.any(
|
|
140
|
+
[lifetime.signal, options.signal, requestSignal].filter(
|
|
141
|
+
(value): value is AbortSignal => value !== undefined,
|
|
142
|
+
),
|
|
143
|
+
);
|
|
144
|
+
try {
|
|
145
|
+
if (signal?.aborted) throw abortSignalError(signal);
|
|
146
|
+
if (cp.kind === 'prompt_user') {
|
|
147
|
+
const answer = await withAbortSignal(
|
|
148
|
+
() =>
|
|
149
|
+
options.onPrompt
|
|
150
|
+
? options.onPrompt(cp.message, cp.options)
|
|
151
|
+
: promptAuthUser(cp.message, cp.options, signal),
|
|
152
|
+
signal,
|
|
153
|
+
);
|
|
154
|
+
if (cp.options?.includes(answer))
|
|
155
|
+
return {
|
|
156
|
+
result: JSON.stringify({ status: 'answered', kind: 'choice', value: answer }),
|
|
157
|
+
};
|
|
158
|
+
const reference = `\${prompt.${randomUUID()}}`;
|
|
159
|
+
promptSecrets.set(reference, answer);
|
|
160
|
+
return {
|
|
161
|
+
result: answer
|
|
162
|
+
? `The user response is stored locally as ${reference}. Pass that exact reference as the appropriate run_verification parameter value; the host resolves it without exposing the answer to the model.`
|
|
163
|
+
: 'The user provided no input.',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (cp.kind === 'inspect_verification_page') {
|
|
167
|
+
const inspected = await options.verifier.inspectPage({
|
|
168
|
+
maxChars: cp.maxChars,
|
|
169
|
+
includeCookies: cp.includeCookies,
|
|
170
|
+
signal,
|
|
171
|
+
});
|
|
172
|
+
return {
|
|
173
|
+
result: inspected.ok
|
|
174
|
+
? `Current verification page snapshot:\n${JSON.stringify(inspected, null, 2)}`
|
|
175
|
+
: `Verification page inspection was unavailable: ${inspected.message ?? 'unknown reason'}`,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (cp.kind === 'interact_verification_browser') {
|
|
179
|
+
const action = AuthBrowserActionSchema.parse(cp);
|
|
180
|
+
if ('value' in action)
|
|
181
|
+
action.value = await resolveUserInput(
|
|
182
|
+
promptSecrets.get(action.value) ?? action.value,
|
|
183
|
+
signal,
|
|
184
|
+
);
|
|
185
|
+
return {
|
|
186
|
+
result: options.context
|
|
187
|
+
.discoveryEvidence(options.session, options.toolDir)
|
|
188
|
+
.append(await options.verifier.interactBrowser(action, signal)),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (cp.kind === 'wait_for_cooldown') {
|
|
192
|
+
await withAbortSignal(
|
|
193
|
+
() =>
|
|
194
|
+
options.onCooldown
|
|
195
|
+
? options.onCooldown(cp.minutes, cp.reason, signal)
|
|
196
|
+
: abortableDelay(cp.minutes * 60_000, signal),
|
|
197
|
+
signal,
|
|
198
|
+
);
|
|
199
|
+
return {
|
|
200
|
+
result: `Requested delay of ~${cp.minutes} min completed; no live action ran during the wait.`,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const failures = await authLivePreflightFailures(
|
|
204
|
+
options.toolDir,
|
|
205
|
+
options.session,
|
|
206
|
+
options.authToolPlan.credentialNames,
|
|
207
|
+
[...options.authToolPlan.credentialRequestSeqs, ...options.authToolPlan.authRequestSeqs],
|
|
208
|
+
options.sessionPath,
|
|
209
|
+
);
|
|
210
|
+
if (signal?.aborted) throw abortSignalError(signal);
|
|
211
|
+
const deadline = options.deadline?.();
|
|
212
|
+
if (deadline !== undefined && Date.now() >= deadline)
|
|
213
|
+
throw new ProviderDeadlineError(deadline);
|
|
214
|
+
if (failures.length)
|
|
215
|
+
return {
|
|
216
|
+
result: `run_verification was blocked before any live login was fired because workflow.json failed auth preflight:\n${failures.map((failure) => `- ${failure}`).join('\n')}`,
|
|
217
|
+
isError: true,
|
|
218
|
+
};
|
|
219
|
+
const workflowHash = authWorkflowHash(
|
|
220
|
+
WorkflowSchema.parse(
|
|
221
|
+
JSON.parse(readFileSync(pathJoin(options.toolDir, 'workflow.json'), 'utf8')),
|
|
222
|
+
),
|
|
223
|
+
);
|
|
224
|
+
if (cp.freshSession || cp.cleanSession) promptSecrets.clear();
|
|
225
|
+
const parameters = await resolveUserInput(
|
|
226
|
+
Object.fromEntries(
|
|
227
|
+
Object.entries(cp.parameters ?? {}).map(([name, value]) => [
|
|
228
|
+
name,
|
|
229
|
+
typeof value === 'string' && promptSecrets.has(value)
|
|
230
|
+
? (promptSecrets.get(value) ?? value)
|
|
231
|
+
: value,
|
|
232
|
+
]),
|
|
233
|
+
),
|
|
234
|
+
signal,
|
|
235
|
+
);
|
|
236
|
+
const result = await options.verifier.runAction(cp.action, parameters, {
|
|
237
|
+
freshSession: cp.freshSession,
|
|
238
|
+
cleanSession: cp.cleanSession,
|
|
239
|
+
signal,
|
|
240
|
+
});
|
|
241
|
+
writeFileSync(
|
|
242
|
+
pathJoin(options.toolDir, AUTH_VERIFICATION_ATTEMPT_SENTINEL),
|
|
243
|
+
JSON.stringify({
|
|
244
|
+
action: cp.action,
|
|
245
|
+
ok: result.ok,
|
|
246
|
+
status: result.status,
|
|
247
|
+
error: result.error,
|
|
248
|
+
backend: result.usedBackend,
|
|
249
|
+
workflowHash,
|
|
250
|
+
timestamp: Date.now(),
|
|
251
|
+
}),
|
|
252
|
+
{ mode: 0o600 },
|
|
253
|
+
);
|
|
254
|
+
options.onVerification?.(cp.action, result);
|
|
255
|
+
return { result: formatVerifyResult(cp.action, result) };
|
|
256
|
+
} catch (error) {
|
|
257
|
+
if (signal?.aborted) throw abortSignalError(signal, 'Auth checkpoint cancelled');
|
|
258
|
+
const control = providerControlError(error);
|
|
259
|
+
if (control) throw control;
|
|
260
|
+
return {
|
|
261
|
+
result: `The host could not perform ${cp.kind}: ${error instanceof Error ? error.message : String(error)}`,
|
|
262
|
+
isError: true,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function liveAuthTools(executor: ReturnType<typeof createAuthCheckpointExecutor>): AgentTool[] {
|
|
270
|
+
return AUTH_CHECKPOINT_TOOLS.map((definition) => ({
|
|
271
|
+
...definition,
|
|
272
|
+
description: `${definition.description} Await the host result and continue in this assignment.`,
|
|
273
|
+
handler: async (input, request) =>
|
|
274
|
+
executor.execute(parseAuthCheckpoint(definition.name, input), request?.signal),
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Run-local ownership, never persisted or shared across teach runs. */
|
|
279
|
+
export class AuthCompileContext {
|
|
280
|
+
readonly #abort = new AbortController();
|
|
281
|
+
#verifier?: AuthVerifier;
|
|
282
|
+
#workflowPath?: string;
|
|
283
|
+
#evidence?: AuthDiscoveryEvidence;
|
|
284
|
+
#dispose?: Promise<void>;
|
|
285
|
+
cliSession?: { driver: 'claude-cli' | 'codex-cli'; sessionId: string };
|
|
286
|
+
history: ConversationLogEntry[] = [];
|
|
287
|
+
previousResult?: Pick<CompileAgentResult, 'outcome' | 'message'>;
|
|
288
|
+
|
|
289
|
+
get signal(): AbortSignal {
|
|
290
|
+
return this.#abort.signal;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async verifier(
|
|
294
|
+
workflowPath: string,
|
|
295
|
+
credentials: () => Promise<CredentialStore>,
|
|
296
|
+
discoveryUrl?: string,
|
|
297
|
+
): Promise<AuthVerifier> {
|
|
298
|
+
if (this.signal.aborted) throw abortSignalError(this.signal);
|
|
299
|
+
if (this.#workflowPath && this.#workflowPath !== workflowPath)
|
|
300
|
+
throw new Error('An authentication context belongs to one workflow workspace');
|
|
301
|
+
this.#workflowPath = workflowPath;
|
|
302
|
+
const values = await credentials();
|
|
303
|
+
if (this.signal.aborted) throw abortSignalError(this.signal);
|
|
304
|
+
if (this.#verifier) this.#verifier.updateCredentials(values);
|
|
305
|
+
else this.#verifier = new AuthVerifier(workflowPath, values, discoveryUrl);
|
|
306
|
+
return this.#verifier;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
discoveryEvidence(session: Session, toolDir: string): AuthDiscoveryEvidence {
|
|
310
|
+
this.#evidence ??= new AuthDiscoveryEvidence(session, toolDir);
|
|
311
|
+
return this.#evidence;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
recoveryMessage(plan: NonNullable<AuthToolPlan>): string {
|
|
315
|
+
return `Continue the existing authentication task in this same workspace and browser.
|
|
316
|
+
Existing artifacts and earlier observations remain available; do not repeat the initial recording read by default.
|
|
317
|
+
Current request: ${JSON.stringify(plan)}
|
|
318
|
+
Previous compiler outcome: ${this.previousResult?.outcome ?? 'interrupted'}
|
|
319
|
+
Previous report: ${this.previousResult?.message ?? '(no terminal report)'}
|
|
320
|
+
Current verification metadata: ${JSON.stringify(this.#verifier?.state ?? {})}
|
|
321
|
+
Earlier human-response references have expired. Ask for fresh input when an action requires it.
|
|
322
|
+
First run a proven noninteractive session check or context-restoration action in the retained browser. If it passes, return the result so the caller can retry its own protected operation. Before starting another login or delivering a factor, inspect current evidence that the existing access is unusable and the new login/challenge is required. Revise the procedure with response-based branches when needed. A prior auth success does not prove the caller's data operation works.`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
dispose(): Promise<void> {
|
|
326
|
+
if (this.#dispose) return this.#dispose;
|
|
327
|
+
this.#abort.abort(new DOMException('Authentication context closed', 'AbortError'));
|
|
328
|
+
this.cliSession = undefined;
|
|
329
|
+
this.history = [];
|
|
330
|
+
this.previousResult = undefined;
|
|
331
|
+
this.#dispose = this.#verifier?.drain() ?? Promise.resolve();
|
|
332
|
+
return this.#dispose;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
69
336
|
interface CompileAuthAgentOptions {
|
|
337
|
+
/** Keep a teach's internal authentication build in its run staging directory. */
|
|
338
|
+
outDir?: string;
|
|
339
|
+
/** Keep browser and provider conversation for later recovery in this teach. */
|
|
340
|
+
context?: AuthCompileContext;
|
|
70
341
|
site: string;
|
|
71
342
|
session: Session;
|
|
72
343
|
sessionPath: string;
|
|
@@ -131,11 +402,38 @@ MANDATORY FIRST ACTION: call read_session_summary now. Do not write prose, do no
|
|
|
131
402
|
}
|
|
132
403
|
|
|
133
404
|
export async function compileAuthAgent(opts: CompileAuthAgentOptions): Promise<CompileAgentResult> {
|
|
405
|
+
const context = opts.context ?? new AuthCompileContext();
|
|
406
|
+
try {
|
|
407
|
+
const result = await compileAuthWithContext({
|
|
408
|
+
...opts,
|
|
409
|
+
context,
|
|
410
|
+
signal: opts.signal ? AbortSignal.any([opts.signal, context.signal]) : context.signal,
|
|
411
|
+
});
|
|
412
|
+
context.previousResult = { outcome: result.outcome, message: result.message };
|
|
413
|
+
return result;
|
|
414
|
+
} catch (error) {
|
|
415
|
+
context.previousResult = {
|
|
416
|
+
outcome: 'error',
|
|
417
|
+
message:
|
|
418
|
+
'The previous authentication request was interrupted; its retained state is available for inspection.',
|
|
419
|
+
};
|
|
420
|
+
throw error;
|
|
421
|
+
} finally {
|
|
422
|
+
if (!opts.context) await context.dispose();
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function compileAuthWithContext(
|
|
427
|
+
input: CompileAuthAgentOptions & { context: AuthCompileContext },
|
|
428
|
+
): Promise<CompileAgentResult> {
|
|
134
429
|
const startTime = Date.now();
|
|
135
|
-
const { site,
|
|
430
|
+
const { site, authToolPlan } = input;
|
|
136
431
|
const toolName = authToolPlan.toolName;
|
|
137
|
-
const toolDir = localToolDir(site, toolName);
|
|
432
|
+
const toolDir = input.outDir ?? localToolDir(site, toolName);
|
|
138
433
|
mkdirSync(toolDir, { recursive: true });
|
|
434
|
+
const evidence = input.context.discoveryEvidence(input.session, toolDir);
|
|
435
|
+
const opts = { ...input, session: evidence.session, sessionPath: evidence.sessionPath };
|
|
436
|
+
const session = evidence.session;
|
|
139
437
|
|
|
140
438
|
const systemPromptPath = pathJoin(PROMPTS_DIR, 'auth-compile-agent.md');
|
|
141
439
|
if (!existsSync(systemPromptPath)) {
|
|
@@ -146,7 +444,10 @@ export async function compileAuthAgent(opts: CompileAuthAgentOptions): Promise<C
|
|
|
146
444
|
opts.runDeadline,
|
|
147
445
|
opts.deadlineMs ?? Date.now() + (opts.maxDurationMs ?? 10 * 60 * 1000),
|
|
148
446
|
);
|
|
149
|
-
const
|
|
447
|
+
const recovering = opts.context.cliSession !== undefined || opts.context.history.length > 0;
|
|
448
|
+
const initialUserMessage = recovering
|
|
449
|
+
? opts.context.recoveryMessage(authToolPlan)
|
|
450
|
+
: buildAuthInitialMessage({ site, toolName, toolDir, authToolPlan });
|
|
150
451
|
|
|
151
452
|
// Provider dispatch mirrors compile-agent.ts. CLI providers don't implement
|
|
152
453
|
// messageWithTools — shell out with the auth toolset as a stdio MCP server.
|
|
@@ -158,6 +459,7 @@ export async function compileAuthAgent(opts: CompileAuthAgentOptions): Promise<C
|
|
|
158
459
|
if (resolved.name === 'claude-cli') {
|
|
159
460
|
return await runAuthSegmentLoop({
|
|
160
461
|
driver: 'claude-cli',
|
|
462
|
+
context: opts.context,
|
|
161
463
|
site,
|
|
162
464
|
session,
|
|
163
465
|
sessionPath: opts.sessionPath,
|
|
@@ -180,6 +482,7 @@ export async function compileAuthAgent(opts: CompileAuthAgentOptions): Promise<C
|
|
|
180
482
|
if (resolved.name === 'codex-cli') {
|
|
181
483
|
return await runAuthSegmentLoop({
|
|
182
484
|
driver: 'codex-cli',
|
|
485
|
+
context: opts.context,
|
|
183
486
|
site,
|
|
184
487
|
session,
|
|
185
488
|
sessionPath: opts.sessionPath,
|
|
@@ -211,108 +514,147 @@ export async function compileAuthAgent(opts: CompileAuthAgentOptions): Promise<C
|
|
|
211
514
|
}
|
|
212
515
|
|
|
213
516
|
// ─── In-process runAgentLoop path (anthropic-api / injected provider) ───────
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
517
|
+
const active = combinedDeadlineSignal(
|
|
518
|
+
runDeadline,
|
|
519
|
+
undefined,
|
|
520
|
+
opts.signal,
|
|
521
|
+
Date.now,
|
|
522
|
+
undefined,
|
|
523
|
+
opts.onDeadlineReached,
|
|
524
|
+
);
|
|
525
|
+
const attemptPath = pathJoin(toolDir, AUTH_VERIFICATION_ATTEMPT_SENTINEL);
|
|
526
|
+
if (existsSync(attemptPath)) unlinkSync(attemptPath);
|
|
527
|
+
const verifier = await opts.context.verifier(
|
|
528
|
+
pathJoin(toolDir, 'workflow.json'),
|
|
529
|
+
() => loadAuthCredentials(site, opts.teachCredentials.values),
|
|
530
|
+
session.url,
|
|
531
|
+
);
|
|
532
|
+
const checkpointExecutor = createAuthCheckpointExecutor({
|
|
533
|
+
...opts,
|
|
534
|
+
toolDir,
|
|
535
|
+
verifier,
|
|
536
|
+
signal: active.signal,
|
|
537
|
+
deadline: () => runDeadline?.deadlineMs,
|
|
538
|
+
});
|
|
539
|
+
try {
|
|
540
|
+
const systemPrompt = `${readFileSync(systemPromptPath, 'utf8')}\n\nToday's date is ${new Date().toISOString().slice(0, 10)}.`;
|
|
541
|
+
const tools: AgentTool[] = [
|
|
542
|
+
...buildAuthCompileTools(session, toolDir, opts.sessionPath, opts.teachCredentials),
|
|
543
|
+
...liveAuthTools(checkpointExecutor),
|
|
544
|
+
doneTool(),
|
|
545
|
+
giveUpTool(),
|
|
546
|
+
];
|
|
547
|
+
|
|
548
|
+
const conversationLogPath = pathJoin(toolDir, '.compile-log.json');
|
|
549
|
+
|
|
550
|
+
let totalTurns = 0;
|
|
551
|
+
let totalInputTokens = 0;
|
|
552
|
+
let totalOutputTokens = 0;
|
|
553
|
+
let outcome: AgentResult['outcome'] = 'error';
|
|
554
|
+
let message = '';
|
|
555
|
+
let conversationLog: AgentResult['conversationLog'] = [...opts.context.history];
|
|
556
|
+
|
|
557
|
+
let verificationCycle = 0;
|
|
558
|
+
let currentInitialMessage = initialUserMessage;
|
|
220
559
|
|
|
221
|
-
|
|
560
|
+
while (true) {
|
|
561
|
+
verificationCycle++;
|
|
562
|
+
conversationLog = completeAuthHistory(conversationLog);
|
|
222
563
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
let currentInitialMessage = initialUserMessage;
|
|
232
|
-
|
|
233
|
-
while (true) {
|
|
234
|
-
verificationCycle++;
|
|
235
|
-
|
|
236
|
-
const userOnProgress = opts.onProgress;
|
|
237
|
-
const wrappedOnProgress = userOnProgress
|
|
238
|
-
? (p: AgentProgress) =>
|
|
239
|
-
userOnProgress({
|
|
240
|
-
...p,
|
|
241
|
-
verificationCycle,
|
|
242
|
-
})
|
|
243
|
-
: undefined;
|
|
564
|
+
const userOnProgress = opts.onProgress;
|
|
565
|
+
const wrappedOnProgress = userOnProgress
|
|
566
|
+
? (p: AgentProgress) =>
|
|
567
|
+
userOnProgress({
|
|
568
|
+
...p,
|
|
569
|
+
verificationCycle,
|
|
570
|
+
})
|
|
571
|
+
: undefined;
|
|
244
572
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
573
|
+
const result = await runAgentLoop({
|
|
574
|
+
systemPrompt,
|
|
575
|
+
initialUserMessage: currentInitialMessage,
|
|
576
|
+
tools,
|
|
577
|
+
deadlineMs: runDeadline?.deadlineMs ?? Date.now(),
|
|
578
|
+
runDeadline,
|
|
579
|
+
softTurnCap: 30,
|
|
580
|
+
llm: {
|
|
581
|
+
...provider,
|
|
582
|
+
messageWithTools: (input) =>
|
|
583
|
+
provider.messageWithTools({
|
|
584
|
+
...input,
|
|
585
|
+
messages: [...authHistoryMessages(conversationLog), ...input.messages],
|
|
586
|
+
}),
|
|
587
|
+
},
|
|
588
|
+
onProgress: wrappedOnProgress,
|
|
589
|
+
onConversationUpdate: (currentCycleLog) => {
|
|
590
|
+
const fullLog = [...conversationLog, ...currentCycleLog];
|
|
591
|
+
opts.context.history = fullLog;
|
|
592
|
+
writeFileSync(conversationLogPath, JSON.stringify(fullLog, null, 2), 'utf8');
|
|
593
|
+
},
|
|
594
|
+
onDeadlineReached: opts.onDeadlineReached,
|
|
595
|
+
signal: active.signal,
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
totalTurns += result.turns;
|
|
599
|
+
totalInputTokens += result.inputTokens;
|
|
600
|
+
totalOutputTokens += result.outputTokens;
|
|
601
|
+
conversationLog = [...conversationLog, ...result.conversationLog];
|
|
602
|
+
opts.context.history = conversationLog;
|
|
261
603
|
|
|
262
|
-
|
|
263
|
-
totalInputTokens += result.inputTokens;
|
|
264
|
-
totalOutputTokens += result.outputTokens;
|
|
265
|
-
conversationLog = [...conversationLog, ...result.conversationLog];
|
|
604
|
+
outcome = result.outcome;
|
|
266
605
|
|
|
267
|
-
|
|
606
|
+
if (result.outcome !== 'done') {
|
|
607
|
+
message = buildMessageFromOutcome(result);
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
268
610
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
611
|
+
const failures = authExternalVerification(
|
|
612
|
+
toolDir,
|
|
613
|
+
(authToolPlan.captures ?? []).map((c) => ({ name: c.name, usedAs: c.usedAs })),
|
|
614
|
+
{
|
|
615
|
+
requireLiveAttempt: true,
|
|
616
|
+
requiredCredentialNames: authToolPlan.credentialNames,
|
|
617
|
+
},
|
|
618
|
+
);
|
|
273
619
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
requireLiveAttempt: true,
|
|
279
|
-
requiredCredentialNames: authToolPlan.credentialNames,
|
|
280
|
-
},
|
|
281
|
-
);
|
|
282
|
-
|
|
283
|
-
if (failures.length === 0) {
|
|
284
|
-
message = result.doneSummary ?? 'Auth tool compiled';
|
|
285
|
-
break;
|
|
286
|
-
}
|
|
620
|
+
if (failures.length === 0) {
|
|
621
|
+
message = result.doneSummary ?? 'Auth tool compiled';
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
287
624
|
|
|
288
|
-
|
|
289
|
-
|
|
625
|
+
log(`auth verification failed (cycle ${verificationCycle}), resuming agent loop...`);
|
|
626
|
+
currentInitialMessage = `You called done but verification failed:
|
|
290
627
|
|
|
291
628
|
${failures.map((f) => `- ${f}`).join('\n')}
|
|
292
629
|
|
|
293
630
|
Fix the issues in workflow.json, re-test with run_verification, and call done again.`;
|
|
294
|
-
|
|
631
|
+
}
|
|
295
632
|
|
|
296
|
-
|
|
633
|
+
writeFileSync(conversationLogPath, JSON.stringify(conversationLog, null, 2), 'utf8');
|
|
297
634
|
|
|
298
|
-
|
|
635
|
+
const workflowPath = pathJoin(toolDir, 'workflow.json');
|
|
299
636
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
637
|
+
return {
|
|
638
|
+
success: outcome === 'done',
|
|
639
|
+
outcome,
|
|
640
|
+
workflowPath: existsSync(workflowPath) ? workflowPath : undefined,
|
|
641
|
+
message,
|
|
642
|
+
conversationLogPath,
|
|
643
|
+
turns: totalTurns,
|
|
644
|
+
durationMs: Date.now() - startTime,
|
|
645
|
+
inputTokens: totalInputTokens,
|
|
646
|
+
outputTokens: totalOutputTokens,
|
|
647
|
+
cacheReadInputTokens: 0,
|
|
648
|
+
cacheCreationInputTokens: 0,
|
|
649
|
+
};
|
|
650
|
+
} finally {
|
|
651
|
+
checkpointExecutor.dispose();
|
|
652
|
+
active.dispose();
|
|
653
|
+
}
|
|
313
654
|
}
|
|
314
655
|
|
|
315
656
|
interface AuthSegmentLoopOptions {
|
|
657
|
+
context: AuthCompileContext;
|
|
316
658
|
driver: 'claude-cli' | 'codex-cli';
|
|
317
659
|
site: string;
|
|
318
660
|
session: Session;
|
|
@@ -341,6 +683,10 @@ function verifyFacts(r: AuthActionResult): string {
|
|
|
341
683
|
if (typeof r.status === 'number') parts.push(`httpStatus=${r.status}`);
|
|
342
684
|
if (r.error) parts.push(`error=${r.error}`);
|
|
343
685
|
let s = `[${parts.join(' | ')}]`;
|
|
686
|
+
if (r.requestStageFacts) s += `\nRequest stages: ${JSON.stringify(r.requestStageFacts)}`;
|
|
687
|
+
if (r.authExecution) s += `\nAction execution: ${JSON.stringify(r.authExecution)}`;
|
|
688
|
+
if (r.responseObservations)
|
|
689
|
+
s += `\nResponse observations: ${JSON.stringify(r.responseObservations)}`;
|
|
344
690
|
if (r.responseBodyPreview) s += `\nResponse body (truncated): ${r.responseBodyPreview}`;
|
|
345
691
|
return s;
|
|
346
692
|
}
|
|
@@ -367,7 +713,7 @@ function formatVerifyResult(action: string, r: AuthActionResult): string {
|
|
|
367
713
|
* page inspection on the persistent AuthVerifier session, prompt_user via the
|
|
368
714
|
* TUI bridge, or a cool-off wait — then feeds the result into the next segment.
|
|
369
715
|
* The ONE stateful thing (the live browser) lives in the AuthVerifier and is
|
|
370
|
-
*
|
|
716
|
+
* retained until its owning context closes. CLI conversation state uses provider-native resume
|
|
371
717
|
* (`claude --resume`, `codex exec resume`), not retained here.
|
|
372
718
|
*/
|
|
373
719
|
async function runAuthSegmentLoop(opts: AuthSegmentLoopOptions): Promise<CompileAgentResult> {
|
|
@@ -383,28 +729,15 @@ async function runAuthSegmentLoop(opts: AuthSegmentLoopOptions): Promise<Compile
|
|
|
383
729
|
const workflowPath = pathJoin(opts.toolDir, 'workflow.json');
|
|
384
730
|
const verificationAttemptPath = pathJoin(opts.toolDir, AUTH_VERIFICATION_ATTEMPT_SENTINEL);
|
|
385
731
|
if (existsSync(verificationAttemptPath)) unlinkSync(verificationAttemptPath);
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
site: opts.site,
|
|
396
|
-
authPlanJson: JSON.stringify(opts.authToolPlan),
|
|
397
|
-
allowedTools: AUTH_COMPILE_TOOL_NAMES,
|
|
398
|
-
initialPrompt: opts.initialPrompt,
|
|
399
|
-
};
|
|
400
|
-
|
|
401
|
-
const onPrompt = opts.onPrompt ?? (async () => '');
|
|
402
|
-
const onCooldown =
|
|
403
|
-
opts.onCooldown ??
|
|
404
|
-
(async (minutes: number, _reason?: string, signal?: AbortSignal) =>
|
|
405
|
-
abortableDelay(minutes * 60_000, signal));
|
|
406
|
-
|
|
407
|
-
let resume: { sessionId: string; message: string } | undefined;
|
|
732
|
+
const verifier = await opts.context.verifier(
|
|
733
|
+
workflowPath,
|
|
734
|
+
() => loadAuthCredentials(opts.site, opts.teachCredentials.values),
|
|
735
|
+
opts.session.url,
|
|
736
|
+
);
|
|
737
|
+
let resume: { sessionId: string; message: string } | undefined =
|
|
738
|
+
opts.context.cliSession?.driver === opts.driver
|
|
739
|
+
? { sessionId: opts.context.cliSession.sessionId, message: opts.initialPrompt }
|
|
740
|
+
: undefined;
|
|
408
741
|
let last: CompileAgentResult | undefined;
|
|
409
742
|
let totalTurns = 0;
|
|
410
743
|
let totalInputTokens = 0;
|
|
@@ -415,8 +748,42 @@ async function runAuthSegmentLoop(opts: AuthSegmentLoopOptions): Promise<Compile
|
|
|
415
748
|
// line so a failure (e.g. a 403) is visible the instant it happens.
|
|
416
749
|
let lastVerification: CompileAgentProgress['lastVerification'];
|
|
417
750
|
let segment = 0;
|
|
418
|
-
const
|
|
419
|
-
|
|
751
|
+
const checkpointExecutor = createAuthCheckpointExecutor({
|
|
752
|
+
...opts,
|
|
753
|
+
verifier,
|
|
754
|
+
signal: active.signal,
|
|
755
|
+
deadline: () => runDeadline?.deadlineMs ?? opts.deadlineMs,
|
|
756
|
+
onVerification: (action, result) => {
|
|
757
|
+
lastVerification = {
|
|
758
|
+
action,
|
|
759
|
+
ok: result.ok,
|
|
760
|
+
status: result.status,
|
|
761
|
+
error: result.error,
|
|
762
|
+
backend: result.usedBackend,
|
|
763
|
+
durationMs: result.durationMs,
|
|
764
|
+
checkpoint: 'run_verification',
|
|
765
|
+
};
|
|
766
|
+
opts.onProgress?.({
|
|
767
|
+
turn: totalTurns,
|
|
768
|
+
phase: 'tool',
|
|
769
|
+
elapsedMs: Date.now() - opts.startTime,
|
|
770
|
+
budgetMs: Math.max(0, (runDeadline?.deadlineMs ?? opts.deadlineMs) - Date.now()),
|
|
771
|
+
inputTokens: totalInputTokens,
|
|
772
|
+
outputTokens: totalOutputTokens,
|
|
773
|
+
verificationCycle: 1,
|
|
774
|
+
segment,
|
|
775
|
+
lastVerification,
|
|
776
|
+
});
|
|
777
|
+
},
|
|
778
|
+
});
|
|
779
|
+
const authMode = {
|
|
780
|
+
site: opts.site,
|
|
781
|
+
contextRef: opts.authToolPlan.contextRef,
|
|
782
|
+
authPlanJson: JSON.stringify(opts.authToolPlan),
|
|
783
|
+
allowedTools: AUTH_COMPILE_TOOL_NAMES,
|
|
784
|
+
initialPrompt: opts.initialPrompt,
|
|
785
|
+
executeCheckpoint: checkpointExecutor.execute,
|
|
786
|
+
};
|
|
420
787
|
|
|
421
788
|
try {
|
|
422
789
|
while (true) {
|
|
@@ -467,6 +834,8 @@ async function runAuthSegmentLoop(opts: AuthSegmentLoopOptions): Promise<Compile
|
|
|
467
834
|
: compileViaCodexCli(common);
|
|
468
835
|
},
|
|
469
836
|
});
|
|
837
|
+
if (result.sessionId)
|
|
838
|
+
opts.context.cliSession = { driver: opts.driver, sessionId: result.sessionId };
|
|
470
839
|
last = result;
|
|
471
840
|
totalTurns += result.turns;
|
|
472
841
|
totalInputTokens += result.inputTokens;
|
|
@@ -507,116 +876,8 @@ async function runAuthSegmentLoop(opts: AuthSegmentLoopOptions): Promise<Compile
|
|
|
507
876
|
break;
|
|
508
877
|
}
|
|
509
878
|
|
|
510
|
-
const cp
|
|
511
|
-
|
|
512
|
-
try {
|
|
513
|
-
if (cp.kind === 'run_verification') {
|
|
514
|
-
const preflightFailures = await authLivePreflightFailures(
|
|
515
|
-
opts.toolDir,
|
|
516
|
-
opts.session,
|
|
517
|
-
opts.authToolPlan.credentialNames,
|
|
518
|
-
[...opts.authToolPlan.credentialRequestSeqs, ...opts.authToolPlan.authRequestSeqs],
|
|
519
|
-
opts.sessionPath,
|
|
520
|
-
);
|
|
521
|
-
if (Date.now() >= (runDeadline?.deadlineMs ?? opts.deadlineMs)) {
|
|
522
|
-
throw new ProviderDeadlineError(runDeadline?.deadlineMs ?? opts.deadlineMs);
|
|
523
|
-
}
|
|
524
|
-
if (preflightFailures.length > 0) {
|
|
525
|
-
resultMsg = `run_verification was blocked before any live login was fired because workflow.json failed auth preflight:\n${preflightFailures
|
|
526
|
-
.map((failure) => `- ${failure}`)
|
|
527
|
-
.join('\n')}`;
|
|
528
|
-
} else {
|
|
529
|
-
const verifiedWorkflowHash = authWorkflowHash(
|
|
530
|
-
WorkflowSchema.parse(
|
|
531
|
-
JSON.parse(readFileSync(pathJoin(opts.toolDir, 'workflow.json'), 'utf8')),
|
|
532
|
-
),
|
|
533
|
-
);
|
|
534
|
-
const resolvedParameters = Object.fromEntries(
|
|
535
|
-
Object.entries(cp.parameters ?? {}).map(([name, value]) => [
|
|
536
|
-
name,
|
|
537
|
-
typeof value === 'string' && promptSecrets.has(value)
|
|
538
|
-
? (promptSecrets.get(value) ?? '')
|
|
539
|
-
: value,
|
|
540
|
-
]),
|
|
541
|
-
);
|
|
542
|
-
const r = await verifier.runAction(cp.action, resolvedParameters, {
|
|
543
|
-
freshSession: cp.freshSession,
|
|
544
|
-
cleanSession: cp.cleanSession,
|
|
545
|
-
signal: active.signal,
|
|
546
|
-
});
|
|
547
|
-
writeFileSync(
|
|
548
|
-
verificationAttemptPath,
|
|
549
|
-
JSON.stringify(
|
|
550
|
-
{
|
|
551
|
-
action: cp.action,
|
|
552
|
-
ok: r.ok,
|
|
553
|
-
status: r.status,
|
|
554
|
-
error: r.error,
|
|
555
|
-
backend: r.usedBackend,
|
|
556
|
-
workflowHash: verifiedWorkflowHash,
|
|
557
|
-
timestamp: Date.now(),
|
|
558
|
-
},
|
|
559
|
-
null,
|
|
560
|
-
2,
|
|
561
|
-
),
|
|
562
|
-
'utf8',
|
|
563
|
-
);
|
|
564
|
-
// Record + immediately surface the result so the spinner reflects a
|
|
565
|
-
// failure the moment it happens — not only on the next agent turn.
|
|
566
|
-
lastVerification = {
|
|
567
|
-
action: cp.action,
|
|
568
|
-
ok: r.ok,
|
|
569
|
-
status: r.status,
|
|
570
|
-
error: r.error,
|
|
571
|
-
backend: r.usedBackend,
|
|
572
|
-
durationMs: r.durationMs,
|
|
573
|
-
checkpoint: 'run_verification',
|
|
574
|
-
};
|
|
575
|
-
opts.onProgress?.({
|
|
576
|
-
turn: totalTurns,
|
|
577
|
-
phase: 'tool',
|
|
578
|
-
elapsedMs: Date.now() - opts.startTime,
|
|
579
|
-
budgetMs: Math.max(0, (runDeadline?.deadlineMs ?? opts.deadlineMs) - Date.now()),
|
|
580
|
-
inputTokens: totalInputTokens,
|
|
581
|
-
outputTokens: totalOutputTokens,
|
|
582
|
-
verificationCycle: 1,
|
|
583
|
-
segment,
|
|
584
|
-
lastVerification,
|
|
585
|
-
});
|
|
586
|
-
resultMsg = formatVerifyResult(cp.action, r);
|
|
587
|
-
}
|
|
588
|
-
} else if (cp.kind === 'inspect_verification_page') {
|
|
589
|
-
const inspected = await verifier.inspectPage({
|
|
590
|
-
maxChars: cp.maxChars,
|
|
591
|
-
includeCookies: cp.includeCookies,
|
|
592
|
-
signal: active.signal,
|
|
593
|
-
});
|
|
594
|
-
resultMsg = inspected.ok
|
|
595
|
-
? `Current verification page snapshot:\n${JSON.stringify(inspected, null, 2)}`
|
|
596
|
-
: `Verification page inspection was unavailable: ${inspected.message ?? 'unknown reason'}`;
|
|
597
|
-
} else if (cp.kind === 'prompt_user') {
|
|
598
|
-
const answer = await withAbortSignal(
|
|
599
|
-
() => onPrompt(cp.message, cp.options),
|
|
600
|
-
active.signal,
|
|
601
|
-
);
|
|
602
|
-
const reference = `\${prompt.${++promptSecretSequence}}`;
|
|
603
|
-
promptSecrets.set(reference, answer);
|
|
604
|
-
resultMsg = answer
|
|
605
|
-
? `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.`
|
|
606
|
-
: 'The user provided no input.';
|
|
607
|
-
} else {
|
|
608
|
-
await withAbortSignal(
|
|
609
|
-
() => onCooldown(cp.minutes, cp.reason, active.signal),
|
|
610
|
-
active.signal,
|
|
611
|
-
);
|
|
612
|
-
resultMsg = `Requested delay of ~${cp.minutes} min completed; no live action ran during the wait.`;
|
|
613
|
-
}
|
|
614
|
-
} catch (err) {
|
|
615
|
-
if (opts.signal?.aborted) throw abortSignalError(opts.signal, 'Auth compile cancelled');
|
|
616
|
-
const control = providerControlError(active.signal?.aborted ? active.signal.reason : err);
|
|
617
|
-
if (control) throw control;
|
|
618
|
-
resultMsg = `The orchestrator could not perform ${cp.kind}: ${err instanceof Error ? err.message : String(err)}`;
|
|
619
|
-
}
|
|
879
|
+
const cp = result.checkpoint;
|
|
880
|
+
const { result: resultMsg } = await checkpointExecutor.execute(cp);
|
|
620
881
|
|
|
621
882
|
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.`;
|
|
622
883
|
resume = {
|
|
@@ -626,7 +887,7 @@ async function runAuthSegmentLoop(opts: AuthSegmentLoopOptions): Promise<Compile
|
|
|
626
887
|
}
|
|
627
888
|
} finally {
|
|
628
889
|
active.dispose();
|
|
629
|
-
|
|
890
|
+
checkpointExecutor.dispose();
|
|
630
891
|
}
|
|
631
892
|
|
|
632
893
|
if (!last) {
|
|
@@ -656,6 +917,34 @@ async function runAuthSegmentLoop(opts: AuthSegmentLoopOptions): Promise<Compile
|
|
|
656
917
|
};
|
|
657
918
|
}
|
|
658
919
|
|
|
920
|
+
/** Complete terminal tool calls before a later API recovery turn. */
|
|
921
|
+
function completeAuthHistory(history: ConversationLogEntry[]): ConversationLogEntry[] {
|
|
922
|
+
const last = history.at(-1);
|
|
923
|
+
if (last?.role !== 'assistant' || !Array.isArray(last.content)) return history;
|
|
924
|
+
const calls = last.content.filter((block) => block.type === 'tool_use');
|
|
925
|
+
if (!calls.length) return history;
|
|
926
|
+
return [
|
|
927
|
+
...history,
|
|
928
|
+
{
|
|
929
|
+
turn: last.turn,
|
|
930
|
+
role: 'user',
|
|
931
|
+
content: calls.map((block) => ({
|
|
932
|
+
type: 'tool_result',
|
|
933
|
+
tool_use_id: block.id,
|
|
934
|
+
content: 'Previous authentication request ended. Continue from current evidence.',
|
|
935
|
+
})),
|
|
936
|
+
},
|
|
937
|
+
];
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function authHistoryMessages(
|
|
941
|
+
history: ConversationLogEntry[],
|
|
942
|
+
): Parameters<ToolUseProvider['messageWithTools']>[0]['messages'] {
|
|
943
|
+
return completeAuthHistory(history).map(({ role, content }) => ({ role, content })) as Parameters<
|
|
944
|
+
ToolUseProvider['messageWithTools']
|
|
945
|
+
>[0]['messages'];
|
|
946
|
+
}
|
|
947
|
+
|
|
659
948
|
function buildMessageFromOutcome(result: AgentResult): string {
|
|
660
949
|
switch (result.outcome) {
|
|
661
950
|
case 'give_up':
|