pi-agent-browser-native 0.8.2 → 0.9.0
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/CHANGELOG.md +35 -0
- package/README.md +24 -15
- package/dist/extensions/agent-browser/index.js +496 -343
- package/dist/extensions/agent-browser/lib/browser-journal.js +439 -0
- package/dist/extensions/agent-browser/lib/browser-session-conversion.js +381 -0
- package/dist/extensions/agent-browser/lib/browser-transcript.js +119 -19
- package/dist/extensions/agent-browser/lib/execution-cwd.js +0 -16
- package/dist/extensions/agent-browser/lib/input-modes/job.js +49 -2
- package/dist/extensions/agent-browser/lib/input-modes/params.js +32 -35
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +25 -5
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +20 -5
- package/dist/extensions/agent-browser/lib/native-output.js +46 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +97 -78
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +23 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +52 -17
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +13 -8
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +30 -16
- package/dist/extensions/agent-browser/lib/playbook.js +9 -2
- package/dist/extensions/agent-browser/lib/prompt-policy.js +16 -26
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +4 -0
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +17 -9
- package/dist/extensions/agent-browser/lib/session-page-state.js +95 -157
- package/dist/extensions/agent-browser/lib/temp.js +14 -6
- package/dist/extensions/agent-browser/lib/tool-surface.js +50 -33
- package/dist/extensions/agent-browser/lib/web-search.js +85 -72
- package/docs/ARCHITECTURE.md +25 -25
- package/docs/COMMAND_REFERENCE.md +13 -9
- package/docs/RELEASE.md +25 -19
- package/docs/REQUIREMENTS.md +6 -6
- package/docs/SESSION_STORAGE.md +60 -0
- package/docs/SUPPORT_MATRIX.md +16 -11
- package/docs/TOOL_CONTRACT.md +26 -19
- package/package.json +9 -5
- package/scripts/build.mjs +4 -3
- package/scripts/convert-browser-session.mjs +22 -0
- package/scripts/doctor.mjs +2 -2
|
@@ -15,7 +15,7 @@ import { buildToolPresentation } from "../../results/presentation.js";
|
|
|
15
15
|
import { compactLargePresentationOutput } from "../../results/presentation/large-output.js";
|
|
16
16
|
import { extractEnvelopeErrorText, getAgentBrowserErrorText, parseAgentBrowserEnvelope } from "../../results/envelope.js";
|
|
17
17
|
import { detectConfirmationRequired } from "../../results/confirmation.js";
|
|
18
|
-
import { omitUpstreamLifecycle } from "../../results/presentation/common.js";
|
|
18
|
+
import { extractAgentBrowserLifecycle, omitUpstreamLifecycle } from "../../results/presentation/common.js";
|
|
19
19
|
import { getClipboardWritePayloadCandidates, redactClipboardPermissionEcho, redactClipboardPermissionErrorValue } from "../../results/presentation/errors.js";
|
|
20
20
|
import { shouldCaptureSemanticActionNavigationSummary } from "../../results/presentation/semantic-action.js";
|
|
21
21
|
import { buildPageTransitionRefSnapshotInvalidation, commandExplicitlyTargetsAboutBlank, getCommandRefSnapshotInvalidation, deriveSessionTabTarget, extractLatestRefSnapshotStateFromBatchResults, extractRefSnapshotFromData, extractSessionTabTargetFromBatchResults, extractSessionTabTargetFromCommandData, isAboutBlankSessionTabTarget, normalizeSessionTabTarget, } from "../../session-page-state.js";
|
|
@@ -23,10 +23,10 @@ import { isRecord } from "../../parsing.js";
|
|
|
23
23
|
import { buildReadConfirmationNextActions, nextReadConfirmation } from "../../read-confirmation.js";
|
|
24
24
|
import { pruneOwnedManagedSessionRestoreSnapshots } from "../../managed-session-restore.js";
|
|
25
25
|
import { isManagedSessionRestoreKey } from "../../managed-session-storage.js";
|
|
26
|
-
import { createFreshSessionName, extractUpstreamCommandTokens, resolveManagedSessionState } from "../../runtime.js";
|
|
26
|
+
import { createFreshSessionName, extractUpstreamCommandTokens, redactInvocationArgs, resolveManagedSessionState } from "../../runtime.js";
|
|
27
27
|
import { getUpstreamEffectiveBatchSteps } from "../batch-stdin.js";
|
|
28
28
|
import { closeManagedSession, inspectManagedSessionDaemon } from "./managed-session-daemon-policy.js";
|
|
29
|
-
import { applyOpenResultTabCorrection, buildAboutBlankRecoveryHint, buildAboutBlankWarning, buildElectronPostCommandHealthDiagnostic, buildElectronRefFreshnessDiagnostic, buildElectronSessionMismatch, buildManagedSessionOutcome, collectOpenResultTabCorrection, collectSessionTabSelection, collectSessionTabTarget, commandChoosesSessionTabTarget, extractNavigationSummaryFromData, extractStringResultField, findElectronLaunchRecordForSession, formatElectronPostCommandHealthText, formatElectronSessionMismatchText, getSessionContextKey, getStaleRefArgs, mergeNavigationSummaryIntoData, runSessionCommandData, shouldCaptureNavigationSummary, shouldCorrectSessionTabAfterCommand, shouldInspectElectronPostCommandHealth, updateTraceOwnerState, } from "./session-state.js";
|
|
29
|
+
import { applyOpenResultTabCorrection, applyBrowserRunStatePatch, buildAboutBlankRecoveryHint, buildAboutBlankWarning, buildElectronPostCommandHealthDiagnostic, buildElectronRefFreshnessDiagnostic, buildElectronSessionMismatch, buildManagedSessionOutcome, collectOpenResultTabCorrection, collectSessionTabSelection, collectSessionTabTarget, commandChoosesSessionTabTarget, extractNavigationSummaryFromData, extractStringResultField, findElectronLaunchRecordForSession, formatElectronPostCommandHealthText, formatElectronSessionMismatchText, getSessionContextKey, getStaleRefArgs, mergeNavigationSummaryIntoData, runSessionCommandData, shouldCaptureNavigationSummary, shouldCorrectSessionTabAfterCommand, shouldInspectElectronPostCommandHealth, updateTraceOwnerState, } from "./session-state.js";
|
|
30
30
|
import { collectClickDispatchDiagnostic } from "./click-dispatch.js";
|
|
31
31
|
import { buildScrollNoopDiagnostic, collectComboboxFocusDiagnostic, collectElectronBroadGetTextScopeDiagnostics, collectElectronHandoff, collectFillVerificationDiagnostic, collectNavigationSummary, collectOverlayBlockerDiagnostic, collectQaAttachedTarget, collectSnapshotOverlayBlockerDiagnostic, collectRecordingDependencyWarning, collectScrollPositionSnapshot, collectSelectorTextVisibilityDiagnostics, collectTimeoutPartialProgress, sleepMs, formatQaAttachedTargetText, getArtifactCleanupGuidance, getEvalResultWarning, getEvalStdinHint, getSourceLookupElectronContext, } from "./diagnostics.js";
|
|
32
32
|
import { repairScreenshotData } from "./prepare.js";
|
|
@@ -617,7 +617,7 @@ export async function processBrowserOutput(input) {
|
|
|
617
617
|
networkRoutesBySession.delete(replacedSessionStateKey ?? replacedManagedSessionName);
|
|
618
618
|
sessionPageState.clearSession(replacedSessionStateKey ?? replacedManagedSessionName);
|
|
619
619
|
const replacedSessionKey = replacedSessionStateKey ?? replacedManagedSessionName;
|
|
620
|
-
const replacedCloseError = await closeManagedSession({ cwd: priorManagedSessionCwd, headedManagedAutosaveInterval: priorManagedSessionHeadedAutosaveInterval, namespace: priorManagedSessionNamespace, preserveAttachedBrowserSession: state.attachedSessionKeys.has(replacedSessionKey), restoreState: state.managedSessionRestoreState, sessionName: replacedManagedSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
|
|
620
|
+
const replacedCloseError = await closeManagedSession({ cwd: priorManagedSessionCwd, headedManagedAutosaveInterval: priorManagedSessionHeadedAutosaveInterval, namespace: priorManagedSessionNamespace, preserveAttachedBrowserSession: state.attachedSessionKeys.has(replacedSessionKey), restoreState: state.managedSessionRestoreState, sessionName: replacedManagedSessionName, socketDir: state.ownedManagedSessions.get(replacedSessionKey)?.socketDir, timeoutMs: implicitSessionCloseTimeoutMs });
|
|
621
621
|
if (managedSessionOutcome) {
|
|
622
622
|
managedSessionOutcome = {
|
|
623
623
|
...managedSessionOutcome,
|
|
@@ -709,6 +709,30 @@ export async function processBrowserOutput(input) {
|
|
|
709
709
|
}
|
|
710
710
|
if (plainTextUpgrade && errorText)
|
|
711
711
|
presentationEnvelope = { ...presentationEnvelope, success: false, error: errorText };
|
|
712
|
+
const resultRetainsPreparedManagedSession = !managedSessionOutcome || (managedSessionOutcome.activeAfter
|
|
713
|
+
&& managedSessionOutcome.attemptedSessionName === managedSessionOutcome.currentSessionName);
|
|
714
|
+
const resultHeadedManagedAutosaveDisabled = prepared.ownedManagedSessionContext?.headedManagedAutosaveDisabled === true
|
|
715
|
+
&& resultRetainsPreparedManagedSession
|
|
716
|
+
&& !(commandClosesSession && succeeded);
|
|
717
|
+
const resultHeadedManagedAutosaveInterval = resultRetainsPreparedManagedSession && !(commandClosesSession && succeeded)
|
|
718
|
+
? prepared.ownedManagedSessionContext?.headedManagedAutosaveInterval
|
|
719
|
+
: undefined;
|
|
720
|
+
// Commit native lifecycle facts to the execution state before rendering/export can fail.
|
|
721
|
+
applyBrowserRunStatePatch(state, { artifactManifest, freshSessionOrdinal, managedSessionActive, managedSessionCompatibilityWorkaround, managedSessionHeadedAutosaveDisabled, managedSessionHeadedAutosaveInterval, managedSessionCwd, managedSessionName, managedSessionNamespace, networkRoutesBySession });
|
|
722
|
+
state.observedBrowserEffects = {
|
|
723
|
+
args: prepared.redactedArgs, command: prepared.executionPlan.commandInfo.command, subcommand: prepared.executionPlan.commandInfo.subcommand,
|
|
724
|
+
sessionName: prepared.executionPlan.sessionName, namespace: prepared.executionPlan.namespace, sessionMode: prepared.sessionMode,
|
|
725
|
+
usedImplicitSession: prepared.executionPlan.usedImplicitSession, agentBrowserStarted: processResult.agentBrowserStarted,
|
|
726
|
+
nativeSucceeded: succeeded, managedSessionOutcome, managedSessionCwd: prepared.ownedManagedSessionContext?.cwd ?? managedSessionCwd, compatibilityWorkaround: prepared.compatibilityWorkaround,
|
|
727
|
+
managedSessionHeadedAutosaveDisabled: resultHeadedManagedAutosaveDisabled, managedSessionHeadedAutosaveInterval: resultHeadedManagedAutosaveInterval,
|
|
728
|
+
managedSessionRestoreDisabled: state.managedSessionRestoreState.isDisabled(prepared.executionPlan.sessionName, prepared.executionPlan.namespace),
|
|
729
|
+
closeAllApplied, readConfirmation: readConfirmationEvent ?? prepared.readConfirmation,
|
|
730
|
+
electron: electronLaunchRecord ? { launch: electronLaunchRecord, cleanup: electronFailedConnectCleanup } : undefined,
|
|
731
|
+
batchSteps: Array.isArray(presentationEnvelope?.data) ? presentationEnvelope.data.filter(isRecord).map(row => ({
|
|
732
|
+
command: Array.isArray(row.command) && row.command.every(token => typeof token === "string") ? redactInvocationArgs(row.command) : undefined,
|
|
733
|
+
success: row.success, lifecycle: extractAgentBrowserLifecycle(row.result),
|
|
734
|
+
})) : undefined,
|
|
735
|
+
};
|
|
712
736
|
let presentation = plainTextInspection ? { artifacts: undefined, batchFailure: undefined, batchSteps: undefined, content: [{ type: "text", text: inspectionText ?? "" }], data: undefined, fullOutputPath: undefined, fullOutputPaths: undefined, imagePath: undefined, imagePaths: undefined, savedFile: undefined, savedFilePath: undefined, summary: `${prepared.redactedArgs.join(" ")} completed` } : recordingStopRecovery && !recordingStopRecovery.batch ? recordingStopRecovery.presentation : await buildToolPresentation({ modelVisible: input.modelVisible, args: prepared.redactedProcessArgs, artifactManifest, artifactMaxUpdatedAtMs: Date.now(), artifactMinUpdatedAtMs: input.artifactRunStartedAtMs, artifactRequest: screenshotArtifactRequest, batchArtifactRequests: batchScreenshotArtifactRequests, commandInfo: prepared.executionPlan.commandInfo, compiledSemanticAction: prepared.compiledSemanticAction, cwd: operationCwd, envelope: presentationEnvelope, errorText, namespace: prepared.executionPlan.namespace, networkRouteDiagnostics, networkRoutes: activeNetworkRoutes, persistentArtifactStore, previousRecordingContactSheetPath: sessionStateKey ? state.activeRecordingReservations?.get(sessionStateKey)?.contactSheetPath : undefined, piCleanupOwnership: sessionStateKey && (state.ownedManagedSessions.has(sessionStateKey) || prepared.executionPlan.managedSessionName !== undefined) ? "wrapper-managed" : "caller-owned", sessionName: prepared.executionPlan.sessionName });
|
|
713
737
|
if (recordingStopRecovery)
|
|
714
738
|
presentation = mergeRecordingRecoveryPresentation(presentation, recordingStopRecovery);
|
|
@@ -794,13 +818,14 @@ export async function processBrowserOutput(input) {
|
|
|
794
818
|
presentation.failureCategory = "qa-failure";
|
|
795
819
|
presentation.summary = qaPreset.summary;
|
|
796
820
|
const compactText = buildQaCompactFailureText({
|
|
797
|
-
|
|
798
|
-
|
|
821
|
+
causalError: presentation.batchFailure?.failedStep.text,
|
|
822
|
+
executedStepCount: presentation.batchSteps?.length,
|
|
799
823
|
page: extractQaPageContext({
|
|
800
824
|
attachedTarget: qaAttachedTarget,
|
|
801
825
|
batchData: presentationEnvelope?.data,
|
|
802
826
|
compiled: prepared.compiledQaPreset,
|
|
803
827
|
}),
|
|
828
|
+
plannedStepCount: prepared.compiledQaPreset.steps.length,
|
|
804
829
|
qaPreset,
|
|
805
830
|
});
|
|
806
831
|
const nonTextContent = presentation.content.filter((item) => item.type !== "text");
|
|
@@ -829,7 +854,12 @@ export async function processBrowserOutput(input) {
|
|
|
829
854
|
const qaAttachedBannerText = [qaAttachedTargetText, qaAttachedDiagnosticsText].filter((part) => typeof part === "string" && part.length > 0).join("\n");
|
|
830
855
|
const skipAttachedTargetBanner = qaPreset?.passed && prepared.compiledQaPreset?.checks.attached;
|
|
831
856
|
if (!skipAttachedTargetBanner && qaAttachedBannerText && presentation.content[0]?.type === "text")
|
|
832
|
-
presentation.content[0] = {
|
|
857
|
+
presentation.content[0] = {
|
|
858
|
+
...presentation.content[0],
|
|
859
|
+
text: qaPreset?.passed === false
|
|
860
|
+
? `${presentation.content[0].text}\n\n${qaAttachedBannerText}`
|
|
861
|
+
: `${qaAttachedBannerText}\n\n${presentation.content[0].text}`,
|
|
862
|
+
};
|
|
833
863
|
else if (!skipAttachedTargetBanner && qaAttachedBannerText)
|
|
834
864
|
presentation.content.unshift({ type: "text", text: qaAttachedBannerText });
|
|
835
865
|
if (managedSessionOutcome && managedSessionOutcome.succeeded !== succeeded)
|
|
@@ -856,19 +886,24 @@ export async function processBrowserOutput(input) {
|
|
|
856
886
|
currentRefSnapshot = finalRecoveryState.currentRefSnapshot;
|
|
857
887
|
currentRefSnapshotInvalidation = finalRecoveryState.currentRefSnapshotInvalidation;
|
|
858
888
|
const authoritativePageState = sessionStateKey ? sessionPageState.get(sessionStateKey) : undefined;
|
|
889
|
+
const newSnapshot = authoritativePageState?.refSnapshot && authoritativePageState.refSnapshot.snapshotId !== prepared.priorRefSnapshotState?.snapshotId;
|
|
890
|
+
const needsDaemonReceipt = prepared.ownedManagedSessionContext && !prepared.ownedManagedSessionContext.reuseOnly && !commandClosesSession && processResult.agentBrowserStarted
|
|
891
|
+
&& (succeeded || managedSessionOutcome?.activeAfter === true && managedSessionOutcome.currentSessionName === prepared.executionPlan.sessionName)
|
|
892
|
+
&& !state.managedSessionRestoreState.getDaemonReceipt(prepared.executionPlan.sessionName, prepared.executionPlan.namespace);
|
|
893
|
+
if (sessionStateKey && prepared.executionPlan.sessionName && (newSnapshot || needsDaemonReceipt)) {
|
|
894
|
+
const daemon = await inspectManagedSessionDaemon({ cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal, includeGeneration: true, headedManagedAutosaveInterval: prepared.ownedManagedSessionContext?.headedManagedAutosaveInterval ?? (prepared.ownedManagedSessionContext?.headedManagedAutosaveDisabled ? "0" : undefined), timeoutMs: prepared.processTimeoutMs });
|
|
895
|
+
if (newSnapshot) {
|
|
896
|
+
sessionPageState.bindSnapshotGeneration(sessionStateKey, daemon.status === "active" ? daemon.generation : undefined);
|
|
897
|
+
currentRefSnapshot = sessionPageState.get(sessionStateKey).refSnapshot;
|
|
898
|
+
}
|
|
899
|
+
if (prepared.ownedManagedSessionContext && daemon.status === "active" && daemon.generation) {
|
|
900
|
+
state.managedSessionRestoreState.recordDaemonRestoreKey(prepared.executionPlan.sessionName, prepared.executionPlan.namespace, daemon.restoreKey, daemon.generation);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
859
903
|
if (sessionStateKey)
|
|
860
904
|
currentSessionTabTarget = authoritativePageState?.tabTarget;
|
|
861
905
|
const currentSessionTabTargetUnknown = authoritativePageState?.tabTargetUnknown === true ? true : undefined;
|
|
862
|
-
const
|
|
863
|
-
&& managedSessionOutcome.attemptedSessionName === managedSessionOutcome.currentSessionName);
|
|
864
|
-
const resultHeadedManagedAutosaveDisabled = prepared.ownedManagedSessionContext?.headedManagedAutosaveDisabled === true
|
|
865
|
-
&& !prepared.ownedManagedSessionContext.reuseOnly
|
|
866
|
-
&& resultRetainsPreparedManagedSession
|
|
867
|
-
&& !(commandClosesSession && succeeded);
|
|
868
|
-
const resultHeadedManagedAutosaveInterval = resultRetainsPreparedManagedSession && !prepared.ownedManagedSessionContext?.reuseOnly && !(commandClosesSession && succeeded)
|
|
869
|
-
? prepared.ownedManagedSessionContext?.headedManagedAutosaveInterval
|
|
870
|
-
: undefined;
|
|
871
|
-
const result = buildFinalAgentBrowserToolResult({ modelVisible: input.modelVisible, aboutBlankSessionMismatch, artifactCleanup, categoryDetails: finalRecoveryState.categoryDetails, clickDispatchDiagnostic, commandTokens: prepared.commandTokens, comboboxFocusDiagnostic, compiledNetworkSourceLookup: prepared.compiledNetworkSourceLookup, compiledSemanticAction: prepared.compiledSemanticAction, compatibilityWorkaround: prepared.compatibilityWorkaround, currentRefSnapshot, currentRefSnapshotInvalidation, currentSessionTabTarget, currentSessionTabTargetUnknown, electronBroadGetTextScopeDiagnostics, electronFailedConnectCleanup, electronHandoff, electronLaunch: prepared.electronLaunch, electronLaunchRecord, electronLaunchRecords, electronPostCommandHealth, electronProfileIsolationDetails: input.electronProfileIsolationDetails, electronRefFreshnessDiagnostic, electronSessionMismatch, errorText, evalResultWarning, evalStdinHint, exactSensitiveValues: prepared.exactSensitiveValues, executionPlan: prepared.executionPlan, fillVerificationDiagnostic, headedLaunch: prepared.headedLaunch, inspectionText, preserveAttachedBrowserSession: input.preserveAttachedBrowserSession === true, providerLaunch: prepared.providerLaunch, managedSessionHeadedAutosaveDisabled: resultHeadedManagedAutosaveDisabled || undefined, managedSessionHeadedAutosaveInterval: resultHeadedManagedAutosaveInterval, managedSessionOutcome, managedSessionRestoreDisabled: state.managedSessionRestoreState.isDisabled(prepared.executionPlan.sessionName, prepared.executionPlan.namespace), navigationSummary, networkSourceLookup, noActivePageSnapshotFailure: finalRecoveryState.noActivePageSnapshotFailure, openResultTabCorrection, overlayBlockerDiagnostic, parseError, parseFailureOutput, parseSucceeded, plainTextInspection, presentation, presentationEnvelope, priorSessionTabTarget: prepared.priorSessionTabTarget, processResult, qaAttachedTarget, qaPreset, recordingDependencyWarning, redactedArgs: prepared.redactedArgs, redactedCompiledElectron: prepared.redactedCompiledElectron, redactedCompiledJob: prepared.redactedCompiledJob, redactedCompiledNetworkSourceLookup: prepared.redactedCompiledNetworkSourceLookup, redactedCompiledQaPreset: prepared.redactedCompiledQaPreset, redactedCompiledSemanticAction: prepared.redactedCompiledSemanticAction, redactedCompiledSourceLookup: prepared.redactedCompiledSourceLookup, redactedContent, redactedProcessArgs: prepared.redactedProcessArgs, redactedRecoveryHint: prepared.redactedRecoveryHint, resultArtifactManifest, richInputRecoveryDiagnostic: finalRecoveryState.richInputRecoveryDiagnostic, scrollNoopDiagnostic, selectorTextVisibilityDiagnostics, sessionMode: prepared.sessionMode, sessionTabCorrection, sourceLookup, succeeded, timeoutPartialProgress, unsettledWebMcpMutation, userRequestedJson: prepared.userRequestedJson, visibleRefFallbackDiagnostic: finalRecoveryState.visibleRefFallbackDiagnostic, visibleRefFallbackSessionName: finalRecoveryState.visibleRefFallbackSessionName });
|
|
906
|
+
const result = buildFinalAgentBrowserToolResult({ modelVisible: input.modelVisible, aboutBlankSessionMismatch, artifactCleanup, categoryDetails: finalRecoveryState.categoryDetails, clickDispatchDiagnostic, commandTokens: prepared.commandTokens, comboboxFocusDiagnostic, compiledNetworkSourceLookup: prepared.compiledNetworkSourceLookup, compiledSemanticAction: prepared.compiledSemanticAction, compatibilityWorkaround: prepared.compatibilityWorkaround, currentRefSnapshot, currentRefSnapshotInvalidation, currentSessionTabTarget, currentSessionTabTargetUnknown, electronBroadGetTextScopeDiagnostics, electronFailedConnectCleanup, electronHandoff, electronLaunch: prepared.electronLaunch, electronLaunchRecord, electronLaunchRecords, electronPostCommandHealth, electronProfileIsolationDetails: input.electronProfileIsolationDetails, electronRefFreshnessDiagnostic, electronSessionMismatch, errorText, evalResultWarning, evalStdinHint, exactSensitiveValues: prepared.exactSensitiveValues, executionPlan: prepared.executionPlan, fillVerificationDiagnostic, headedLaunch: prepared.headedLaunch, inspectionText, preserveAttachedBrowserSession: input.preserveAttachedBrowserSession === true, providerLaunch: prepared.providerLaunch, managedSessionHeadedAutosaveDisabled: !prepared.ownedManagedSessionContext?.reuseOnly && resultHeadedManagedAutosaveDisabled || undefined, managedSessionHeadedAutosaveInterval: prepared.ownedManagedSessionContext?.reuseOnly ? undefined : resultHeadedManagedAutosaveInterval, managedSessionOutcome, managedSessionRestoreDisabled: state.managedSessionRestoreState.isDisabled(prepared.executionPlan.sessionName, prepared.executionPlan.namespace), navigationSummary, networkSourceLookup, noActivePageSnapshotFailure: finalRecoveryState.noActivePageSnapshotFailure, openResultTabCorrection, overlayBlockerDiagnostic, parseError, parseFailureOutput, parseSucceeded, plainTextInspection, presentation, presentationEnvelope, priorSessionTabTarget: prepared.priorSessionTabTarget, processResult, qaAttachedTarget, qaPreset, recordingDependencyWarning, redactedArgs: prepared.redactedArgs, redactedCompiledElectron: prepared.redactedCompiledElectron, redactedCompiledJob: prepared.redactedCompiledJob, redactedCompiledNetworkSourceLookup: prepared.redactedCompiledNetworkSourceLookup, redactedCompiledQaPreset: prepared.redactedCompiledQaPreset, redactedCompiledSemanticAction: prepared.redactedCompiledSemanticAction, redactedCompiledSourceLookup: prepared.redactedCompiledSourceLookup, redactedContent, redactedProcessArgs: prepared.redactedProcessArgs, redactedRecoveryHint: prepared.redactedRecoveryHint, resultArtifactManifest, richInputRecoveryDiagnostic: finalRecoveryState.richInputRecoveryDiagnostic, scrollNoopDiagnostic, selectorTextVisibilityDiagnostics, sessionMode: prepared.sessionMode, sessionTabCorrection, sourceLookup, succeeded, timeoutPartialProgress, unsettledWebMcpMutation, userRequestedJson: prepared.userRequestedJson, visibleRefFallbackDiagnostic: finalRecoveryState.visibleRefFallbackDiagnostic, visibleRefFallbackSessionName: finalRecoveryState.visibleRefFallbackSessionName });
|
|
872
907
|
const resultWithCloseAll = closeAllApplied
|
|
873
908
|
? { ...result, details: { ...(isRecord(result.details) ? result.details : {}), closeAllApplied: true } }
|
|
874
909
|
: result;
|
|
@@ -603,7 +603,7 @@ export async function cleanupTrackedElectronHostLaunches(options) {
|
|
|
603
603
|
const sessionKey = getSessionPageStateKey(record.sessionName, record.namespace) ?? record.sessionName;
|
|
604
604
|
const managedSessionOwner = sessionKey ? options.ownedManagedSessions.get(sessionKey) : undefined;
|
|
605
605
|
const managedSessionCloseError = record.sessionName
|
|
606
|
-
? await closeManagedSession({ cwd: options.cwd, headedManagedAutosaveInterval: managedSessionOwner?.headedManagedAutosaveInterval, namespace: record.namespace, preserveAttachedBrowserSession: options.attachedSessionKeys.has(sessionKey ?? record.sessionName), restoreState: options.managedSessionRestoreState, sessionName: record.sessionName, timeoutMs: options.timeoutMs })
|
|
606
|
+
? await closeManagedSession({ cwd: options.cwd, headedManagedAutosaveInterval: managedSessionOwner?.headedManagedAutosaveInterval, namespace: record.namespace, preserveAttachedBrowserSession: options.attachedSessionKeys.has(sessionKey ?? record.sessionName), restoreState: options.managedSessionRestoreState, sessionName: record.sessionName, socketDir: managedSessionOwner?.socketDir, timeoutMs: options.timeoutMs })
|
|
607
607
|
: undefined;
|
|
608
608
|
const managedSessionStep = record.sessionName
|
|
609
609
|
? managedSessionCloseError
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import { stat } from "node:fs/promises";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { AGENT_BROWSER_SCRIPT_FINAL_OUTPUT_MAX_BYTES, createAgentBrowserScriptCloseArgs, isAgentBrowserScriptSessionName, } from "../input-modes/script.js";
|
|
3
4
|
import { isRecord } from "../parsing.js";
|
|
5
|
+
import { appendBrowserTransition, getBrowserRecord } from "../browser-transcript.js";
|
|
4
6
|
import { redactSensitiveText } from "../runtime.js";
|
|
5
7
|
import { attachInlineImage } from "../results/presentation/artifacts.js";
|
|
6
8
|
import { projectAgentBrowserObservation } from "../results/presentation/content.js";
|
|
7
9
|
import { redactPresentationData } from "../results/presentation/diagnostics.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export function getScriptSessionLeasesFromBranch(branch) {
|
|
10
|
+
// The offline converter retains outstanding isolated-session cleanup facts in canonical events.
|
|
11
|
+
export function getScriptSessionLeasesFromBranch(branch, ownerSessionId) {
|
|
11
12
|
const leases = new Map();
|
|
12
13
|
for (const entry of branch) {
|
|
13
|
-
|
|
14
|
+
const lease = getBrowserRecord(entry)?.event.state.scriptLease;
|
|
15
|
+
if (!isRecord(lease))
|
|
14
16
|
continue;
|
|
15
|
-
|
|
17
|
+
if (ownerSessionId !== undefined && lease.ownerSessionId !== ownerSessionId)
|
|
18
|
+
continue;
|
|
19
|
+
const { cleanup, closeCommandArgs, launchAttempted, sessionName } = lease;
|
|
16
20
|
if (!isAgentBrowserScriptSessionName(sessionName))
|
|
17
21
|
continue;
|
|
18
22
|
const expected = createAgentBrowserScriptCloseArgs(sessionName);
|
|
@@ -20,12 +24,13 @@ export function getScriptSessionLeasesFromBranch(branch) {
|
|
|
20
24
|
|| !Array.isArray(closeCommandArgs) || closeCommandArgs.length !== expected.length
|
|
21
25
|
|| !closeCommandArgs.every((token, index) => token === expected[index]))
|
|
22
26
|
continue;
|
|
23
|
-
leases.set(sessionName, { cleanup, closeCommandArgs: expected, launchAttempted: true, sessionName });
|
|
27
|
+
leases.set(sessionName, { cleanup, closeCommandArgs: expected, launchAttempted: true, sessionName, ownerSessionId: typeof lease.ownerSessionId === "string" ? lease.ownerSessionId : undefined });
|
|
24
28
|
}
|
|
25
29
|
return leases;
|
|
26
30
|
}
|
|
27
|
-
export function appendScriptSessionLease(pi, sessionName, cleanup) {
|
|
28
|
-
pi
|
|
31
|
+
export function appendScriptSessionLease(pi, sessionName, cleanup, ownerSessionId) {
|
|
32
|
+
appendBrowserTransition(pi, { event: { version: 1, phase: "state", operationId: randomUUID(), toolCallId: "cleanup", commandIndex: 0, isError: cleanup !== "closed",
|
|
33
|
+
state: { scriptLease: { cleanup, closeCommandArgs: createAgentBrowserScriptCloseArgs(sessionName), launchAttempted: true, sessionName, ownerSessionId } } } });
|
|
29
34
|
}
|
|
30
35
|
export function createBrowserCodeOutput() {
|
|
31
36
|
const images = new Map();
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { getKeybindings, Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
|
-
import { BROWSER_RESULT_TOOLS } from "./browser-transcript.js";
|
|
3
2
|
import { compileAgentBrowserElectron } from "./input-modes/electron.js";
|
|
4
3
|
import { compileAgentBrowserQaPreset } from "./input-modes/job.js";
|
|
5
4
|
import { compileAgentBrowserNetworkSourceLookup, compileAgentBrowserSourceLookup } from "./input-modes/lookups.js";
|
|
@@ -185,10 +184,10 @@ function formatModelVisibleFailureCategoryNotice(details) {
|
|
|
185
184
|
: undefined;
|
|
186
185
|
return `Result category: failure${failureCategory ? `; failureCategory: ${failureCategory}` : ""}; Pi tool isError: true.`;
|
|
187
186
|
}
|
|
188
|
-
function agentBrowserToolResultRequestedJson(
|
|
189
|
-
const details = isRecord(
|
|
187
|
+
function agentBrowserToolResultRequestedJson(result, input) {
|
|
188
|
+
const details = isRecord(result.details) ? result.details : undefined;
|
|
190
189
|
const detailArgs = Array.isArray(details?.args) ? details.args : undefined;
|
|
191
|
-
const inputArgs = isRecord(
|
|
190
|
+
const inputArgs = isRecord(input) && Array.isArray(input.args) ? input.args : undefined;
|
|
192
191
|
return detailArgs?.includes("--json") === true || inputArgs?.includes("--json") === true;
|
|
193
192
|
}
|
|
194
193
|
function agentBrowserToolResultHasParseableJsonContent(content) {
|
|
@@ -219,26 +218,41 @@ function appendModelVisibleFailureCategoryNotice(content, notice) {
|
|
|
219
218
|
? { ...item, text: `${textItem.text}\n\n${notice}` }
|
|
220
219
|
: item);
|
|
221
220
|
}
|
|
222
|
-
export function
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
const content = notice ? appendModelVisibleFailureCategoryNotice(event.content, notice) : undefined;
|
|
228
|
-
const shouldMarkError = isRecord(event.details) && event.details.resultCategory === "failure" && event.isError !== true;
|
|
229
|
-
if (!shouldMarkError && !content)
|
|
230
|
-
return undefined;
|
|
221
|
+
export function finalizeAgentBrowserFailure(result, input) {
|
|
222
|
+
const failed = result.isError === true || (isRecord(result.details) && result.details.resultCategory === "failure");
|
|
223
|
+
const preservesParseableJson = (isRecord(input) && "code" in input || agentBrowserToolResultRequestedJson(result, input)) && agentBrowserToolResultHasParseableJsonContent(result.content);
|
|
224
|
+
const notice = preservesParseableJson ? undefined : formatModelVisibleFailureCategoryNotice(result.details);
|
|
225
|
+
const content = notice ? appendModelVisibleFailureCategoryNotice(result.content, notice) : undefined;
|
|
231
226
|
return {
|
|
232
|
-
...
|
|
233
|
-
|
|
227
|
+
...result,
|
|
228
|
+
content: content ?? result.content,
|
|
229
|
+
isError: failed,
|
|
234
230
|
};
|
|
235
231
|
}
|
|
236
232
|
export class AgentBrowserResultComponent {
|
|
237
233
|
expanded = false;
|
|
238
234
|
theme;
|
|
239
235
|
text = new Text("", 0, 0);
|
|
236
|
+
value;
|
|
237
|
+
formatKey;
|
|
238
|
+
setResult(result, options, theme, isError) {
|
|
239
|
+
const details = isRecord(result.details) ? result.details : undefined;
|
|
240
|
+
// Theme is a stable proxy in Pi; its resolved colors identify theme/terminal-color changes.
|
|
241
|
+
const key = [getPrimaryTextContent(result), details?.summary, details?.resultCategory, details?.failureCategory, options.isPartial, isError, theme, theme.colors, theme.fg];
|
|
242
|
+
if (!this.formatKey || key.some((value, index) => value !== this.formatKey[index])) {
|
|
243
|
+
this.formatKey = key;
|
|
244
|
+
this.setState(formatAgentBrowserRenderResult(result, options, theme, isError), options.expanded, theme);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
this.expanded = options.expanded;
|
|
248
|
+
this.theme = theme;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
240
251
|
setState(value, expanded, theme) {
|
|
241
|
-
this.
|
|
252
|
+
if (value !== this.value) {
|
|
253
|
+
this.text.setText(value);
|
|
254
|
+
this.value = value;
|
|
255
|
+
}
|
|
242
256
|
this.expanded = expanded;
|
|
243
257
|
this.theme = theme;
|
|
244
258
|
}
|
|
@@ -12,7 +12,7 @@ export const QUICK_START_GUIDELINES = [
|
|
|
12
12
|
"Use { args: [\"batch\", \"--bail\"], stdin: \"[[\\\"fill\\\",\\\"@e1\\\",\\\"text\\\"],[\\\"click\\\",\\\"@e2\\\"]]\" } for same-snapshot form fields followed by submit. Split if a fill rerenders later fields. Add native condition waits after navigation-prone actions; a click alone does not prove the destination loaded. Avoid blind mutation retries after timeout.",
|
|
13
13
|
"agent_browser_code takes { code, session?, namespace?, timeoutMs?, outputPath? }. JavaScript globals are fresh for every call; the selected browser persists and is shared with agent_browser. Use await browser({ args, stdin?, timeoutMs? }), inspect result.success, and emit selected JSON instead of whole envelopes. Use emitImage(result.imageObservations[0]) only when image inspection is needed; image handles belong to the current code call. No imports or host filesystem/network/process APIs are exposed.",
|
|
14
14
|
"Example agent_browser_code: { code: 'const result = await browser({args:[\"get\",\"title\"]}); if (!result.success) { emit(result); } else { emit({title:result.data}); }' }. Native batch still avoids extra CLI processes for fixed segments inside code. Explicit session/namespace controls select the browser for this cell without changing the default browser.",
|
|
15
|
-
"Call agent_browser_tools with no fields for inventory, or { enable: [\"action\", \"qa\", \"electron\", \"source\", \"network\"] } for advanced native tools. It adds agent_browser_action, agent_browser_qa, agent_browser_electron, agent_browser_source, and agent_browser_network_source without disabling other tools.
|
|
15
|
+
"Call agent_browser_tools with no fields for inventory, or { enable: [\"action\", \"qa\", \"electron\", \"source\", \"network\"] } for advanced native tools. It adds agent_browser_action, agent_browser_qa, agent_browser_electron, agent_browser_source, and agent_browser_network_source without disabling other tools. The browser instructions cover these capabilities; activation reveals their selected tool declarations.",
|
|
16
16
|
"Use read <url> for readable web text; get title/url, get text/html/value/count <selector>, or get attr <selector> <name> for targeted state. For eval --stdin, put JavaScript in stdin and return the desired value. outputPath saves result data and must differ from screenshot/download/recording paths. Use --json only when JSON text is needed.",
|
|
17
17
|
"For artifacts, verify artifactVerification and artifacts before reporting success. A pending recording is not verified output; install ffmpeg before starting and pair start with stop. Native download owns the click and saves Blob/redirect downloads. Close keeps explicitly saved files. Save promptGuard-required paths before close. Screenshot pixels are not browser CSS coordinates: use imageObservations geometry (dimensions, viewport, crop/origin, scroll and DPR) before any visual-coordinate action; full-page and element captures are not current-viewport coordinates, and Pi resizing only maps sent pixels to original image pixels.",
|
|
18
18
|
"Follow model-visible nextActions when present, including identity and stdin, instead of guessing commands. Treat native pending confirmations and unknown mutation outcomes as inspection boundaries. For dense snapshots, inspect Omitted high-value controls before reading a full spill.",
|
|
@@ -38,6 +38,13 @@ export const ADVANCED_TOOL_PROMPT_GUIDELINES = {
|
|
|
38
38
|
],
|
|
39
39
|
};
|
|
40
40
|
export const WEB_SEARCH_PROMPT_GUIDELINE = "Prefer agent_browser_web_search for current or external web facts and URL discovery over public search-engine forms that can hit anti-bot/CAPTCHA-gated pages. For research before implementation, pass searchType: deep-lite unless webSearch.defaultSearchType already does; omit it for everyday lookups so config/auto wins. Provider rank is not proof of authority: when correctness or version matters, prefer the vendor or project's primary current docs, inspect page-date and version clues, and constrain one follow-up after discovering the official domain (Exa includeDomains; Brave site: in query). Do not count URL aliases as independent sources. Use agent_browser after you have a target URL that needs interaction, screenshots, or DOM inspection.";
|
|
41
|
+
export const WEB_SEARCH_TOOL_PROMPT_GUIDELINES = [
|
|
42
|
+
WEB_SEARCH_PROMPT_GUIDELINE,
|
|
43
|
+
"agent_browser_web_search chooses Exa or Brave from configured keys; when both are available, Exa is preferred by default unless webSearch.preferredProvider says otherwise. Use provider only when the user/config calls for a specific provider.",
|
|
44
|
+
"Use Exa deep only when deep-lite may miss angles, and deep-reasoning only for exhaustive or still-thin research. Do not run parallel agent_browser_web_search calls; make one high-signal query, inspect its results, then at most one follow-up.",
|
|
45
|
+
"If agent_browser_web_search returns HTTP 429, stop searching and tell the user the API plan/rate limit needs time or a plan change.",
|
|
46
|
+
"After using agent_browser_web_search, cite result URLs in the final answer when web evidence informed the answer.",
|
|
47
|
+
];
|
|
41
48
|
export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
|
|
42
49
|
QUICK_START_GUIDELINES[0],
|
|
43
50
|
QUICK_START_GUIDELINES[3],
|
|
@@ -96,7 +103,7 @@ export const WRAPPER_TAB_RECOVERY_BEHAVIOR = [
|
|
|
96
103
|
"If a known session target unexpectedly reports about:blank, agent_browser best-effort re-selects the prior intended target when it still exists; if recovery fails, it records the observed about:blank target and reports exact recovery guidance instead of treating the prior page as active.",
|
|
97
104
|
"If upstream reports tab_gone, the pinned bound tab is gone; use details.nextActions (tab list / tab new) instead of assuming another tab is yours.",
|
|
98
105
|
];
|
|
99
|
-
/**
|
|
106
|
+
/** Concise orientation included alongside the full operating playbook. */
|
|
100
107
|
export const RUNTIME_PROMPT_GUIDELINES = [
|
|
101
108
|
"Use agent_browser for one native command; batch --bail with JSON-array stdin for fixed sequences; agent_browser_code for loops/branches/aggregation. Return to the model at judgment boundaries. Use agent_browser_tools to discover/enable action, qa, electron, source, or network tools.",
|
|
102
109
|
"agent_browser_code runs fresh JavaScript against the persistent browser: await browser({args, stdin?, timeoutMs?}), check result.success, emit(selected JSON), or emitImage(result.imageObservations[0]). No host APIs/imports; globals do not persist. Explicit session/namespace selects this cell's browser without changing the default.",
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
const BROWSER_PROMPT_PATTERNS = [
|
|
2
|
-
/\b(?:agent[_ -]?browser|browser automation|eval\s+--stdin|screenshot|snapshot|tab\s+list)\b/i,
|
|
3
|
-
/\b(?:react\s+(?:tree|inspect|renders|suspense)|web\s+vitals|core\s+web\s+vitals|pushstate)\b/i,
|
|
4
|
-
/\b(?:live\s+docs?|online\s+research|research\s+(?:online|the\s+web)|search\s+(?:online|the\s+web)|web\s+research)\b/i,
|
|
5
|
-
/\bbrowser\b.*\b(?:automation|click|fill|navigate|open|page|screenshot|site|snapshot|tab|url|visit|web(?:site| page)?)\b/i,
|
|
6
|
-
/\b(?:browse|click|fill|login|navigate|open|visit)\b.*\b(?:https?:\/\/\S+|page|site|tab|url|web(?:site| page)?)\b/i,
|
|
7
|
-
];
|
|
8
1
|
const LEGACY_BASH_ALLOW_PATTERNS = [
|
|
9
2
|
/\b(?:bash-oriented workflow|bash workflow)\b/i,
|
|
10
3
|
/\b(?:use|via|through|with)\s+bash\b/i,
|
|
@@ -206,7 +199,7 @@ export function buildPromptPolicy(prompt) {
|
|
|
206
199
|
requestedArtifacts: extractPromptRequestedArtifacts(prompt),
|
|
207
200
|
};
|
|
208
201
|
}
|
|
209
|
-
function getMessageText(content) {
|
|
202
|
+
export function getMessageText(content) {
|
|
210
203
|
if (typeof content === "string")
|
|
211
204
|
return content;
|
|
212
205
|
if (!Array.isArray(content))
|
|
@@ -220,24 +213,21 @@ function getMessageText(content) {
|
|
|
220
213
|
.filter((text) => text.length > 0)
|
|
221
214
|
.join("\n");
|
|
222
215
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
const message = "message" in entry ? entry.message : undefined;
|
|
237
|
-
if (typeof message !== "object" || message === null || !("role" in message) || message.role !== "user") {
|
|
238
|
-
continue;
|
|
216
|
+
/** Raw prompt intent survives compaction/context edits; only restoration walks ancestry. */
|
|
217
|
+
export function getLatestUserMessage(manager) {
|
|
218
|
+
let id = manager.getLeafId();
|
|
219
|
+
const seen = new Set();
|
|
220
|
+
while (id !== null) {
|
|
221
|
+
if (typeof id !== "string" || seen.has(id))
|
|
222
|
+
throw new Error("Latest user prompt ancestry is invalid or cyclic.");
|
|
223
|
+
seen.add(id);
|
|
224
|
+
const entry = manager.getEntry(id);
|
|
225
|
+
if (!entry || entry.id !== id || entry.parentId !== null && typeof entry.parentId !== "string") {
|
|
226
|
+
throw new Error("Latest user prompt ancestry is incomplete.");
|
|
239
227
|
}
|
|
240
|
-
|
|
228
|
+
if (entry.type === "message" && entry.message.role === "user")
|
|
229
|
+
return entry.message;
|
|
230
|
+
id = entry.parentId;
|
|
241
231
|
}
|
|
242
|
-
return
|
|
232
|
+
return undefined;
|
|
243
233
|
}
|
|
@@ -2,6 +2,7 @@ import { isRecord } from "../../parsing.js";
|
|
|
2
2
|
import { redactSensitiveValue } from "../../runtime.js";
|
|
3
3
|
import { omitUpstreamLifecycle } from "./common.js";
|
|
4
4
|
import { redactTimeoutPartialProgress } from "../../orchestration/browser-run/diagnostics.js";
|
|
5
|
+
export const OBSERVATION_INLINE_MAX_CHARS = 16_000;
|
|
5
6
|
export function isStringArray(value) {
|
|
6
7
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
7
8
|
}
|
|
@@ -40,6 +41,9 @@ export function projectAgentBrowserObservation(details, succeeded) {
|
|
|
40
41
|
}
|
|
41
42
|
if (details.inspection === true && details.data === undefined && typeof details.stdout === "string")
|
|
42
43
|
observation.data = details.stdout;
|
|
44
|
+
// Identical error/summary strings double-count large failure text against the observation bound; keep error.
|
|
45
|
+
if (typeof observation.error === "string" && observation.error === observation.summary)
|
|
46
|
+
observation.summary = undefined;
|
|
43
47
|
if (isRecord(observation.data))
|
|
44
48
|
observation.data = omitUpstreamLifecycle(observation.data);
|
|
45
49
|
if (Array.isArray(details.batchSteps)) {
|
|
@@ -2,7 +2,7 @@ import { writePersistentSessionArtifactFile, writeSecureTempFile, } from "../../
|
|
|
2
2
|
import { buildEvictedSessionArtifactEntries } from "../artifact-manifest.js";
|
|
3
3
|
import { countLines, truncateText } from "../text.js";
|
|
4
4
|
import { applyArtifactManifest } from "./artifacts.js";
|
|
5
|
-
import { getPresentationText, projectAgentBrowserObservation } from "./content.js";
|
|
5
|
+
import { getPresentationText, projectAgentBrowserObservation, OBSERVATION_INLINE_MAX_CHARS } from "./content.js";
|
|
6
6
|
import { redactModelFacingText, stringifyModelFacing } from "./common.js";
|
|
7
7
|
const LARGE_OUTPUT_INLINE_MAX_CHARS = 8_000;
|
|
8
8
|
const LARGE_OUTPUT_INLINE_MAX_LINES = 120;
|
|
@@ -88,20 +88,22 @@ function buildSpillArtifactEntries(options) {
|
|
|
88
88
|
...buildEvictedSessionArtifactEntries(options.evictedArtifacts, nowMs),
|
|
89
89
|
];
|
|
90
90
|
}
|
|
91
|
-
export
|
|
91
|
+
export { OBSERVATION_INLINE_MAX_CHARS } from "./content.js";
|
|
92
92
|
/** Bound the final model-visible result, after recovery/diagnostic assembly. Never truncate executable actions. */
|
|
93
93
|
export async function renderAgentBrowserObservation(options) {
|
|
94
94
|
const observation = projectAgentBrowserObservation(options.details, options.succeeded);
|
|
95
|
+
let structuredContent = observation;
|
|
95
96
|
const images = options.content.filter(part => part.type === "image");
|
|
96
97
|
const prose = options.content.filter(part => part.type === "text").map(part => part.text).join("\n\n");
|
|
97
98
|
const { data: _data, error: _error, summary: _summary, ...metadata } = observation;
|
|
98
99
|
let text = options.json ? JSON.stringify(observation, null, 2) : `${prose}\n\nObservation: ${JSON.stringify(metadata)}`;
|
|
99
100
|
let artifactManifest = options.details.artifactManifest;
|
|
100
|
-
if (text.length > OBSERVATION_INLINE_MAX_CHARS) {
|
|
101
|
+
if (text.length > OBSERVATION_INLINE_MAX_CHARS || JSON.stringify(observation).length > OBSERVATION_INLINE_MAX_CHARS) {
|
|
101
102
|
let spill;
|
|
102
103
|
let spillError;
|
|
103
104
|
try {
|
|
104
|
-
|
|
105
|
+
const write = () => writeLargeOutputSpillFile({ data: { ...observation, ...(!options.json ? { text: prose } : {}) }, persistentArtifactStore: options.persistentArtifactStore, text });
|
|
106
|
+
spill = await (options.withArtifactWrite ? options.withArtifactWrite(write) : write());
|
|
105
107
|
artifactManifest = applyArtifactManifest({ content: [], summary: "" }, artifactManifest, buildSpillArtifactEntries({
|
|
106
108
|
commandInfo: { command: typeof options.details.command === "string" ? options.details.command : undefined },
|
|
107
109
|
evictedArtifacts: spill.evictedArtifacts, path: spill.path, storageScope: spill.storageScope,
|
|
@@ -113,7 +115,8 @@ export async function renderAgentBrowserObservation(options) {
|
|
|
113
115
|
const compact = {
|
|
114
116
|
success: options.succeeded, resultCategory: observation.resultCategory,
|
|
115
117
|
failureCategory: observation.failureCategory, successCategory: observation.successCategory,
|
|
116
|
-
summary: typeof observation.summary === "string" ? truncateText(observation.summary, 700)
|
|
118
|
+
summary: typeof observation.summary === "string" ? truncateText(observation.summary, 700)
|
|
119
|
+
: typeof observation.error === "string" ? truncateText(observation.error, 700) : undefined,
|
|
117
120
|
compacted: true,
|
|
118
121
|
...(spill ? { observationPath: spill.path, retrieve: "Read observationPath for the complete redacted observation, including exact recovery actions and requested data." }
|
|
119
122
|
: { observationUnavailable: truncateText(redactModelFacingText(spillError ?? "Spill could not be written; request a smaller result."), 1_000) }),
|
|
@@ -122,11 +125,16 @@ export async function renderAgentBrowserObservation(options) {
|
|
|
122
125
|
if (observation[key] !== undefined && JSON.stringify({ ...compact, [key]: observation[key] }, null, 2).length <= OBSERVATION_INLINE_MAX_CHARS - 500)
|
|
123
126
|
compact[key] = observation[key];
|
|
124
127
|
}
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
text =
|
|
128
|
+
structuredContent = compact;
|
|
129
|
+
if (options.preserveContent)
|
|
130
|
+
text = prose;
|
|
131
|
+
else {
|
|
132
|
+
text = JSON.stringify(compact, null, 2);
|
|
133
|
+
if (!options.json)
|
|
134
|
+
text = `Browser observation compacted.\n${text}`;
|
|
135
|
+
}
|
|
128
136
|
}
|
|
129
|
-
return { content: [{ type: "text", text }, ...images], artifactManifest };
|
|
137
|
+
return { content: [{ type: "text", text }, ...images], artifactManifest, structuredContent: JSON.parse(JSON.stringify(structuredContent)) };
|
|
130
138
|
}
|
|
131
139
|
export async function compactLargePresentationOutput(options) {
|
|
132
140
|
const text = getPresentationText(options.presentation);
|