humanish 0.100.1 → 0.102.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/README.md +3 -2
- package/dist/actor-contract.d.ts +9 -9
- package/dist/actor-contract.js +15 -5
- package/dist/actor-contract.js.map +1 -1
- package/dist/affordance.d.ts +2 -0
- package/dist/affordance.js +3 -0
- package/dist/affordance.js.map +1 -1
- package/dist/browser-control-client.d.ts +2 -0
- package/dist/browser-control-client.js +4 -0
- package/dist/browser-control-client.js.map +1 -1
- package/dist/browser-control-dispatcher.js +3 -0
- package/dist/browser-control-dispatcher.js.map +1 -1
- package/dist/browser-control-protocol.d.ts +77 -0
- package/dist/browser-control-protocol.js +31 -4
- package/dist/browser-control-protocol.js.map +1 -1
- package/dist/computer-use.d.ts +22 -2
- package/dist/computer-use.js +144 -32
- package/dist/computer-use.js.map +1 -1
- package/dist/cua-actor-lab.d.ts +1 -0
- package/dist/cua-actor-lab.js +44 -21
- package/dist/cua-actor-lab.js.map +1 -1
- package/dist/cua-speech.d.ts +13 -0
- package/dist/cua-speech.js +2 -0
- package/dist/cua-speech.js.map +1 -0
- package/dist/desktop-session.js +1 -0
- package/dist/desktop-session.js.map +1 -1
- package/dist/doctor-lab.js +1 -1
- package/dist/doctor-lab.js.map +1 -1
- package/dist/e2b-cua-desktop.js +51 -42
- package/dist/e2b-cua-desktop.js.map +1 -1
- package/dist/e2b-cua-provisioning.d.ts +4 -1
- package/dist/e2b-cua-provisioning.js +8 -2
- package/dist/e2b-cua-provisioning.js.map +1 -1
- package/dist/e2b-desktop-executor.js +3 -0
- package/dist/e2b-desktop-executor.js.map +1 -1
- package/dist/e2b-desktop-launch.d.ts +11 -1
- package/dist/e2b-desktop-launch.js.map +1 -1
- package/dist/e2b-desktop-media.d.ts +14 -0
- package/dist/e2b-desktop-media.js +58 -0
- package/dist/e2b-desktop-media.js.map +1 -0
- package/dist/guest-bootstrap.d.ts +4 -2
- package/dist/guest-bootstrap.js +16 -9
- package/dist/guest-bootstrap.js.map +1 -1
- package/dist/guest-desktop-executor.js +1 -0
- package/dist/guest-desktop-executor.js.map +1 -1
- package/dist/guest-desktop-media.d.ts +33 -0
- package/dist/guest-desktop-media.js +281 -0
- package/dist/guest-desktop-media.js.map +1 -0
- package/dist/guest-media-config.d.ts +15 -0
- package/dist/guest-media-config.js +8 -0
- package/dist/guest-media-config.js.map +1 -0
- package/dist/guest-media-worker.d.ts +1 -0
- package/dist/guest-media-worker.js +319 -0
- package/dist/guest-media-worker.js.map +1 -0
- package/dist/guest-runtime-desktop.d.ts +3 -1
- package/dist/guest-runtime-desktop.js +20 -3
- package/dist/guest-runtime-desktop.js.map +1 -1
- package/dist/guest-runtime-main.js +2 -2
- package/dist/guest-runtime-main.js.map +1 -1
- package/dist/guest-runtime.d.ts +2 -1
- package/dist/guest-runtime.js +1 -1
- package/dist/guest-runtime.js.map +1 -1
- package/dist/lab-config.d.ts +2 -2
- package/dist/lab-config.js +15 -4
- package/dist/lab-config.js.map +1 -1
- package/dist/lab-summary.js +1 -1
- package/dist/lab-summary.js.map +1 -1
- package/dist/local-agent-cli.d.ts +10 -0
- package/dist/local-agent-cli.js +23 -7
- package/dist/local-agent-cli.js.map +1 -1
- package/dist/local-firecracker-desktop.d.ts +3 -0
- package/dist/local-firecracker-desktop.js +7 -1
- package/dist/local-firecracker-desktop.js.map +1 -1
- package/dist/local-firecracker-study.js +16 -2
- package/dist/local-firecracker-study.js.map +1 -1
- package/dist/local-runtime-config.js +5 -2
- package/dist/local-runtime-config.js.map +1 -1
- package/dist/local-runtime-release.d.ts +2 -0
- package/dist/local-runtime-release.js +15 -0
- package/dist/local-runtime-release.js.map +1 -1
- package/dist/local-runtime.d.ts +1 -0
- package/dist/local-runtime.js +11 -9
- package/dist/local-runtime.js.map +1 -1
- package/dist/observer-app.html +1 -1
- package/dist/program.js +3 -2
- package/dist/program.js.map +1 -1
- package/dist/restricted-codex-participant-policy.d.ts +12 -5
- package/dist/restricted-codex-participant-policy.js +24 -49
- package/dist/restricted-codex-participant-policy.js.map +1 -1
- package/dist/restricted-codex-participant-run.js +2 -1
- package/dist/restricted-codex-participant-run.js.map +1 -1
- package/dist/restricted-codex-participant.d.ts +7 -1
- package/dist/restricted-codex-participant.js +168 -103
- package/dist/restricted-codex-participant.js.map +1 -1
- package/dist/restricted-codex-policy.d.ts +12 -5
- package/dist/restricted-codex-policy.js +62 -37
- package/dist/restricted-codex-policy.js.map +1 -1
- package/dist/restricted-codex-session.d.ts +24 -3
- package/dist/restricted-codex-session.js +208 -51
- package/dist/restricted-codex-session.js.map +1 -1
- package/dist/restricted-codex-transport.d.ts +11 -2
- package/dist/restricted-codex-transport.js +57 -6
- package/dist/restricted-codex-transport.js.map +1 -1
- package/dist/run.d.ts +8 -5
- package/dist/run.js +6 -6
- package/dist/run.js.map +1 -1
- package/dist/study-analysis-engine.d.ts +1 -1
- package/dist/study-analysis-engine.js +53 -10
- package/dist/study-analysis-engine.js.map +1 -1
- package/docs/architecture/local-browser-runtime.md +3 -2
- package/docs/architecture/participant-media.md +90 -0
- package/docs/architecture/restricted-codex-analysis.md +7 -4
- package/docs/contracts/schemas.md +1 -1
- package/docs/contracts/study-analysis.md +14 -0
- package/docs/goals/current.md +13 -9
- package/docs/ramp/README.md +10 -1
- package/docs/release/0.101.0-codex-ui-tools.md +28 -0
- package/docs/release/0.102.0-participant-media.md +43 -0
- package/package.json +5 -4
- package/skills/humanish/SKILL.md +8 -5
- package/dist/local-agent-appserver.d.ts +0 -44
- package/dist/local-agent-appserver.js +0 -269
- package/dist/local-agent-appserver.js.map +0 -1
package/dist/computer-use.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type HeardSpeech } from "./cua-speech.js";
|
|
2
|
+
export { CUA_SPEECH_LIMITS, type HeardSpeech } from "./cua-speech.js";
|
|
1
3
|
import { type ActorCapabilities, type ActorCompletionReason, type ActorPersonaRef, type ActorStatus, type ActorTokenUsage, type ActorTrace, type ActorTraceItem, type ParticipantDeclaredOutcome, type ParticipantClosingReport } from "./actor-contract.js";
|
|
2
4
|
import type { ActorExecutionProfile, ProviderRequestReceipt } from "./actor-contract.js";
|
|
3
5
|
import type { RedactionHooks } from "./redaction.js";
|
|
@@ -38,6 +40,9 @@ export type CuaAction = {
|
|
|
38
40
|
} | {
|
|
39
41
|
kind: "wait";
|
|
40
42
|
ms?: number;
|
|
43
|
+
} | {
|
|
44
|
+
kind: "speak";
|
|
45
|
+
text: string;
|
|
41
46
|
} | {
|
|
42
47
|
kind: "screenshot";
|
|
43
48
|
};
|
|
@@ -93,6 +98,8 @@ export interface CuaObservation {
|
|
|
93
98
|
scrollY?: number;
|
|
94
99
|
title?: string;
|
|
95
100
|
text?: string;
|
|
101
|
+
/** Finalized speech captured from the participant's speaker sink. */
|
|
102
|
+
heardSpeech?: HeardSpeech[];
|
|
96
103
|
}
|
|
97
104
|
/**
|
|
98
105
|
* A safety check the model raised. The triple is preserved verbatim from the
|
|
@@ -127,6 +134,8 @@ export interface CuaTurnRequest {
|
|
|
127
134
|
contextHint?: string;
|
|
128
135
|
}
|
|
129
136
|
export interface CuaTurn {
|
|
137
|
+
/** This action proposal yielded from a provider request that remains active. No receipt or usage is settled yet. */
|
|
138
|
+
providerRequestPending?: true;
|
|
130
139
|
providerRequest?: ProviderRequestReceipt;
|
|
131
140
|
/** Continuation handle for the next turn. */
|
|
132
141
|
responseId?: string;
|
|
@@ -144,6 +153,13 @@ export interface CuaTurn {
|
|
|
144
153
|
output?: number;
|
|
145
154
|
cachedInput?: number;
|
|
146
155
|
cacheWriteInput?: number;
|
|
156
|
+
/** Per model inference inside this provider interaction. */
|
|
157
|
+
turns?: Array<{
|
|
158
|
+
input?: number;
|
|
159
|
+
output?: number;
|
|
160
|
+
cachedInput?: number;
|
|
161
|
+
cacheWriteInput?: number;
|
|
162
|
+
}>;
|
|
147
163
|
};
|
|
148
164
|
/** True when the model reported a natural endpoint (no further action). */
|
|
149
165
|
done: boolean;
|
|
@@ -158,10 +174,10 @@ export interface CuaTurn {
|
|
|
158
174
|
export interface CuaProvider {
|
|
159
175
|
/** Single dispatch; the promise includes owned request cleanup. No stall retry. */
|
|
160
176
|
readonly requestPolicy?: "fail_closed";
|
|
161
|
-
readonly executionProfile?: ActorExecutionProfile;
|
|
177
|
+
readonly executionProfile?: ActorExecutionProfile | undefined;
|
|
162
178
|
readonly historyTurnsOmitted?: number;
|
|
163
179
|
readonly id: string;
|
|
164
|
-
readonly version?: string;
|
|
180
|
+
readonly version?: string | undefined;
|
|
165
181
|
/**
|
|
166
182
|
* The request settings this provider will actually send, for the trace to record. `version` says
|
|
167
183
|
* WHICH model; this says how it was asked to run. Optional: a provider with no such settings
|
|
@@ -188,6 +204,8 @@ export interface CuaProvider {
|
|
|
188
204
|
/** Latched uncertainty from hidden interactive attempts (for example, a transport retry).
|
|
189
205
|
* A later success or pre-dispatch refusal cannot make earlier unreported usage complete. */
|
|
190
206
|
readonly interactionUsageIncomplete?: boolean;
|
|
207
|
+
/** Latest known usage for the active, unsettled request. Used only for runtime spend guards. */
|
|
208
|
+
readonly pendingRequestUsage?: CuaTurn["usage"];
|
|
191
209
|
nextTurn(req: CuaTurnRequest, signal: AbortSignal): Promise<CuaTurn>;
|
|
192
210
|
/** Optional read-only closing report. Implementations must disable tools and make no retries. */
|
|
193
211
|
debrief?: ((req: CuaTurnRequest, signal: AbortSignal) => Promise<CuaTurn>) | undefined;
|
|
@@ -201,6 +219,8 @@ export interface CuaExecutor {
|
|
|
201
219
|
* legacy observation/idle stall recovery. Wrappers must preserve this value.
|
|
202
220
|
*/
|
|
203
221
|
readonly stallRecovery?: "fail_closed";
|
|
222
|
+
/** True only when this executor can play a `speak` action into the participant microphone. */
|
|
223
|
+
readonly speechEnabled?: boolean;
|
|
204
224
|
/** Capture the current desktop frame and its state signature. */
|
|
205
225
|
observe(): Promise<CuaObservation>;
|
|
206
226
|
/**
|
package/dist/computer-use.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { CUA_SPEECH_LIMITS } from "./cua-speech.js";
|
|
2
|
+
export { CUA_SPEECH_LIMITS } from "./cua-speech.js";
|
|
1
3
|
import { ACTOR_TRACE_SCHEMA } from "./actor-contract.js";
|
|
2
4
|
import { CuaProviderError, isCuaProviderError } from "./cua-provider-error.js";
|
|
3
5
|
import { classifyCuaAction, summarizeAffordanceUse } from "./affordance.js";
|
|
@@ -20,11 +22,29 @@ export function validClosingReport(value) {
|
|
|
20
22
|
function validTokenCount(value) {
|
|
21
23
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
22
24
|
}
|
|
25
|
+
function normalizedUsageTurns(usage) {
|
|
26
|
+
if (!Array.isArray(usage.turns) || usage.turns.length === 0)
|
|
27
|
+
return undefined;
|
|
28
|
+
const fields = ["input", "output", "cachedInput", "cacheWriteInput"];
|
|
29
|
+
const turns = [];
|
|
30
|
+
for (const raw of usage.turns) {
|
|
31
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
32
|
+
return undefined;
|
|
33
|
+
if (fields.every(field => raw[field] === undefined) || fields.some(field => raw[field] !== undefined && !validTokenCount(raw[field])))
|
|
34
|
+
return undefined;
|
|
35
|
+
if ((raw.cachedInput ?? 0) + (raw.cacheWriteInput ?? 0) > (raw.input ?? 0))
|
|
36
|
+
return undefined;
|
|
37
|
+
turns.push(Object.fromEntries(fields.flatMap(field => raw[field] === undefined ? [] : [[field, raw[field]]])));
|
|
38
|
+
}
|
|
39
|
+
return fields.every(field => turns.reduce((sum, turn) => sum + (turn[field] ?? 0), 0) === (usage[field] ?? 0))
|
|
40
|
+
? turns : undefined;
|
|
41
|
+
}
|
|
23
42
|
function completeTurnUsage(usage) {
|
|
24
43
|
return usage !== undefined && validTokenCount(usage.input) && validTokenCount(usage.output)
|
|
25
44
|
&& (usage.cachedInput === undefined || validTokenCount(usage.cachedInput))
|
|
26
45
|
&& (usage.cacheWriteInput === undefined || validTokenCount(usage.cacheWriteInput))
|
|
27
|
-
&& (usage.cachedInput ?? 0) + (usage.cacheWriteInput ?? 0) <= usage.input
|
|
46
|
+
&& (usage.cachedInput ?? 0) + (usage.cacheWriteInput ?? 0) <= usage.input
|
|
47
|
+
&& (usage.turns === undefined || normalizedUsageTurns(usage) !== undefined);
|
|
28
48
|
}
|
|
29
49
|
// Waiting is a legitimate strategy, not idleness. A persona told to sign up and verify by email
|
|
30
50
|
// polls its inbox — screenshot, wait, screenshot, wait — and at 6 steps that ended the session as
|
|
@@ -62,6 +82,8 @@ export function actionFingerprint(actions) {
|
|
|
62
82
|
return `scroll@${bucket(action.x)},${bucket(action.y)}:${Math.sign(action.dx)},${Math.sign(action.dy)}`;
|
|
63
83
|
case "type":
|
|
64
84
|
return `type:${action.text.length}`;
|
|
85
|
+
case "speak":
|
|
86
|
+
return `speak:${action.text.length}`;
|
|
65
87
|
case "keypress":
|
|
66
88
|
return `keypress:${action.keys.join("+")}`;
|
|
67
89
|
case "drag":
|
|
@@ -195,6 +217,8 @@ export function describeCuaAction(action) {
|
|
|
195
217
|
return `scroll (${action.dx}, ${action.dy}) at (${action.x}, ${action.y})`;
|
|
196
218
|
case "type":
|
|
197
219
|
return `type [${action.text.length} chars]`;
|
|
220
|
+
case "speak":
|
|
221
|
+
return `speak [${action.text.length} chars]`;
|
|
198
222
|
case "keypress":
|
|
199
223
|
return `keypress ${action.keys.join("+")}`;
|
|
200
224
|
case "drag":
|
|
@@ -389,9 +413,44 @@ export async function runComputerUseLoop(options) {
|
|
|
389
413
|
let usageInput = 0;
|
|
390
414
|
let usageCachedInput = 0;
|
|
391
415
|
let usageCacheWriteInput = 0;
|
|
392
|
-
|
|
393
|
-
|
|
416
|
+
let usageOutput = 0;
|
|
417
|
+
let sawUsage = false;
|
|
418
|
+
let incompleteInteractionUsage = false;
|
|
419
|
+
let unreportedInteractionUsage = false;
|
|
420
|
+
let interactionRequestPending = false;
|
|
421
|
+
// Per model-inference usage, in order (#334): the recorded fact long-context pricing tiers
|
|
422
|
+
// need. A continuing native tool interaction may contain several inference requests.
|
|
394
423
|
const usageTurns = [];
|
|
424
|
+
const knownPendingUsage = () => {
|
|
425
|
+
const usage = interactionRequestPending ? provider.pendingRequestUsage : undefined;
|
|
426
|
+
if (!completeTurnUsage(usage))
|
|
427
|
+
return undefined;
|
|
428
|
+
const turns = usage.turns === undefined ? undefined : normalizedUsageTurns(usage);
|
|
429
|
+
return { input: usage.input, output: usage.output,
|
|
430
|
+
...(usage.cachedInput === undefined ? {} : { cachedInput: usage.cachedInput }),
|
|
431
|
+
...(usage.cacheWriteInput === undefined ? {} : { cacheWriteInput: usage.cacheWriteInput }),
|
|
432
|
+
...(turns === undefined ? {} : { turns }) };
|
|
433
|
+
};
|
|
434
|
+
const withPendingUsage = (settled) => {
|
|
435
|
+
const pendingUsage = knownPendingUsage();
|
|
436
|
+
if (pendingUsage === undefined)
|
|
437
|
+
return settled;
|
|
438
|
+
const input = (settled.input ?? 0) + pendingUsage.input;
|
|
439
|
+
const output = (settled.output ?? 0) + pendingUsage.output;
|
|
440
|
+
return {
|
|
441
|
+
...settled,
|
|
442
|
+
input,
|
|
443
|
+
output,
|
|
444
|
+
...((settled.cachedInput !== undefined || pendingUsage.cachedInput !== undefined)
|
|
445
|
+
? { cachedInput: (settled.cachedInput ?? 0) + (pendingUsage.cachedInput ?? 0) } : {}),
|
|
446
|
+
...((settled.cacheWriteInput !== undefined || pendingUsage.cacheWriteInput !== undefined)
|
|
447
|
+
? { cacheWriteInput: (settled.cacheWriteInput ?? 0) + (pendingUsage.cacheWriteInput ?? 0) } : {}),
|
|
448
|
+
turns: [...(settled.turns ?? []), ...(pendingUsage.turns ?? [{ input: pendingUsage.input, output: pendingUsage.output,
|
|
449
|
+
...(pendingUsage.cachedInput === undefined ? {} : { cachedInput: pendingUsage.cachedInput }),
|
|
450
|
+
...(pendingUsage.cacheWriteInput === undefined ? {} : { cacheWriteInput: pendingUsage.cacheWriteInput }) }])],
|
|
451
|
+
...(settled.total === undefined ? {} : { total: input + output })
|
|
452
|
+
};
|
|
453
|
+
};
|
|
395
454
|
// The running usage snapshot both spend guards consume: totals plus the per-request ledger,
|
|
396
455
|
// shaped exactly like the trace's final tokenUsage so one estimator prices both identically.
|
|
397
456
|
// Unlike the persisted trace (where absent means "unreported"), this runtime callback arg
|
|
@@ -408,18 +467,16 @@ export async function runComputerUseLoop(options) {
|
|
|
408
467
|
...(providerRequests.length > 0 && providerRequests.every(r => r.usageComplete && completeTurnUsage(r.usage))
|
|
409
468
|
? { total: usageInput + usageOutput } : {})
|
|
410
469
|
});
|
|
411
|
-
const runningUsage = () => provider.executionProfile?.billing === "account-unknown" ? accountUsage() : ({
|
|
470
|
+
const runningUsage = () => withPendingUsage(provider.executionProfile?.billing === "account-unknown" ? accountUsage() : ({
|
|
412
471
|
input: usageInput,
|
|
413
472
|
output: usageOutput,
|
|
414
473
|
cachedInput: usageCachedInput,
|
|
415
474
|
cacheWriteInput: usageCacheWriteInput,
|
|
416
475
|
...(usageTurns.length > 0 ? { turns: usageTurns } : {})
|
|
417
|
-
});
|
|
418
|
-
let usageOutput = 0;
|
|
419
|
-
let sawUsage = false;
|
|
420
|
-
let incompleteInteractionUsage = false;
|
|
421
|
-
let unreportedInteractionUsage = false;
|
|
476
|
+
}));
|
|
422
477
|
const hasUnreportedInteractionUsage = () => unreportedInteractionUsage || provider.interactionUsageIncomplete === true;
|
|
478
|
+
const usageUnavailableForCap = () => incompleteInteractionUsage || unreportedInteractionUsage ||
|
|
479
|
+
(interactionRequestPending ? knownPendingUsage() === undefined : provider.interactionUsageIncomplete === true);
|
|
423
480
|
let lastResponseId;
|
|
424
481
|
let currentPhase = "initializing computer-use loop";
|
|
425
482
|
let lastActionTitle;
|
|
@@ -431,9 +488,14 @@ export async function runComputerUseLoop(options) {
|
|
|
431
488
|
counts[key] = (counts[key] ?? 0) + 1;
|
|
432
489
|
};
|
|
433
490
|
const recordUsage = (turn, interaction = true) => {
|
|
434
|
-
if (
|
|
435
|
-
|
|
491
|
+
if (turn.providerRequestPending === true)
|
|
492
|
+
return;
|
|
436
493
|
const raw = turn.usage;
|
|
494
|
+
const turns = raw?.turns === undefined ? undefined : normalizedUsageTurns(raw);
|
|
495
|
+
if (interaction && (!completeTurnUsage(raw) || turn.providerRequest?.usageComplete === false))
|
|
496
|
+
incompleteInteractionUsage = true;
|
|
497
|
+
if (interaction && raw?.turns !== undefined && turns === undefined)
|
|
498
|
+
unreportedInteractionUsage = true;
|
|
437
499
|
if (raw === undefined)
|
|
438
500
|
return;
|
|
439
501
|
const usage = {
|
|
@@ -449,7 +511,10 @@ export async function runComputerUseLoop(options) {
|
|
|
449
511
|
usageCachedInput += usage.cachedInput ?? 0;
|
|
450
512
|
usageCacheWriteInput += usage.cacheWriteInput ?? 0;
|
|
451
513
|
usageOutput += usage.output ?? 0;
|
|
452
|
-
|
|
514
|
+
if (raw.turns === undefined)
|
|
515
|
+
usageTurns.push(usage);
|
|
516
|
+
else if (turns !== undefined)
|
|
517
|
+
usageTurns.push(...turns);
|
|
453
518
|
};
|
|
454
519
|
/** Marked providers own one attempt through settlement; an outer race never retries it. */
|
|
455
520
|
const markedRequest = async (request, kind, capMs) => {
|
|
@@ -463,6 +528,7 @@ export async function runComputerUseLoop(options) {
|
|
|
463
528
|
let failure;
|
|
464
529
|
let failed = false;
|
|
465
530
|
let accepted;
|
|
531
|
+
let pendingYield = false;
|
|
466
532
|
const pending = Promise.resolve().then(() => {
|
|
467
533
|
if (controller.signal.aborted)
|
|
468
534
|
throw new CuaProviderError("cancelled", { dispatched: false, usageComplete: false, cleanup: "confirmed" });
|
|
@@ -471,9 +537,18 @@ export async function runComputerUseLoop(options) {
|
|
|
471
537
|
void pending.catch(() => undefined);
|
|
472
538
|
try {
|
|
473
539
|
accepted = await raceBounded(`participant ${kind}`, pending, remaining(), capMs, signal);
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
540
|
+
if (accepted.providerRequestPending === true) {
|
|
541
|
+
if (kind !== "interaction" || accepted.done || accepted.actions.length === 0 || accepted.pendingSafetyChecks.length > 0 ||
|
|
542
|
+
accepted.providerRequest !== undefined || accepted.usage !== undefined || accepted.interruption !== undefined || accepted.closingReport !== undefined) {
|
|
543
|
+
throw new CuaProviderError("invalid_response", { dispatched: "unknown", usageComplete: false, cleanup: "unconfirmed" });
|
|
544
|
+
}
|
|
545
|
+
pendingYield = true;
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
const r = accepted.providerRequest;
|
|
549
|
+
if (!r || r.dispatched !== true || typeof r.usageComplete !== "boolean" || r.cleanup !== "confirmed") {
|
|
550
|
+
throw new CuaProviderError("invalid_response", { dispatched: "unknown", usageComplete: false, cleanup: "unconfirmed" }, accepted.usage);
|
|
551
|
+
}
|
|
477
552
|
}
|
|
478
553
|
}
|
|
479
554
|
catch (error) {
|
|
@@ -495,6 +570,10 @@ export async function runComputerUseLoop(options) {
|
|
|
495
570
|
}
|
|
496
571
|
}
|
|
497
572
|
}
|
|
573
|
+
if (!failed && pendingYield) {
|
|
574
|
+
interactionRequestPending = true;
|
|
575
|
+
return accepted;
|
|
576
|
+
}
|
|
498
577
|
const final = settlement;
|
|
499
578
|
const typed = isCuaProviderError(failure) ? failure : final && "error" in final && isCuaProviderError(final.error) ? final.error : undefined;
|
|
500
579
|
const turn = final && "turn" in final ? final.turn : undefined;
|
|
@@ -502,14 +581,15 @@ export async function runComputerUseLoop(options) {
|
|
|
502
581
|
const receipt = raw && (typeof raw.dispatched === "boolean" || raw.dispatched === "unknown") &&
|
|
503
582
|
typeof raw.usageComplete === "boolean" && ["confirmed", "unconfirmed"].includes(raw.cleanup) ? { dispatched: raw.dispatched, usageComplete: raw.usageComplete, cleanup: raw.cleanup }
|
|
504
583
|
: { dispatched: "unknown", usageComplete: false, cleanup: "unconfirmed" };
|
|
505
|
-
const rawUsage = typed?.usage ?? turn?.usage;
|
|
584
|
+
const rawUsage = typed?.usage ?? (turn?.providerRequestPending === true ? undefined : turn?.usage);
|
|
506
585
|
const usage = rawUsage === undefined ? undefined : Object.fromEntries(["input", "output", "cachedInput", "cacheWriteInput", "total"].flatMap(key => {
|
|
507
586
|
const value = rawUsage[key];
|
|
508
587
|
return validTokenCount(value) ? [[key, value]] : [];
|
|
509
588
|
}));
|
|
510
589
|
// restricted-codex-session sets dispatched only after initialize/config/account/
|
|
511
590
|
// thread/MCP admission, immediately before turn/start; it is not a success claim.
|
|
512
|
-
|
|
591
|
+
const settledKind = interactionRequestPending ? "interaction" : kind;
|
|
592
|
+
providerRequests.push({ ordinal: providerRequests.length + 1, kind: settledKind, ...receipt,
|
|
513
593
|
profileVerified: provider.executionProfile !== undefined && receipt.dispatched === true,
|
|
514
594
|
...(typed ? { errorCode: typed.code, ...(typed.failurePhase === undefined ? {} : { failurePhase: typed.failurePhase }) } : {}),
|
|
515
595
|
...(usage === undefined ? {} : { usage: { ...usage } }) });
|
|
@@ -518,11 +598,12 @@ export async function runComputerUseLoop(options) {
|
|
|
518
598
|
record({ id: nextId("notice"), kind: "notice", lifecycle: "completed", status: "error",
|
|
519
599
|
title: "participant request cleanup unconfirmed", text: "The request did not confirm cleanup within the settlement boundary. No further participant request or action is admitted." });
|
|
520
600
|
}
|
|
521
|
-
if (
|
|
601
|
+
if (settledKind === "interaction" && receipt.dispatched !== false && (!receipt.usageComplete || !completeTurnUsage(usage)))
|
|
522
602
|
unreportedInteractionUsage = true;
|
|
603
|
+
interactionRequestPending = false;
|
|
523
604
|
if (failed) {
|
|
524
605
|
if (usage)
|
|
525
|
-
recordUsage({ actions: [], pendingSafetyChecks: [], done: false, usage, providerRequest: receipt },
|
|
606
|
+
recordUsage({ actions: [], pendingSafetyChecks: [], done: false, usage, providerRequest: receipt }, settledKind === "interaction");
|
|
526
607
|
if (failure instanceof CuaAbortError || failure instanceof CuaDeadlineError)
|
|
527
608
|
throw failure;
|
|
528
609
|
if (failure instanceof CuaStallError)
|
|
@@ -667,6 +748,24 @@ export async function runComputerUseLoop(options) {
|
|
|
667
748
|
return await raceBounded(`observe (${label}, retry)`, executor.observe(), remaining(), observationTimeoutMs, signal);
|
|
668
749
|
}
|
|
669
750
|
};
|
|
751
|
+
const seenSpeechIds = new Set();
|
|
752
|
+
let pendingHeardSpeech = [];
|
|
753
|
+
let heardSpeechChanged = false;
|
|
754
|
+
const collectHeardSpeech = (value) => {
|
|
755
|
+
for (const utterance of value.heardSpeech ?? []) {
|
|
756
|
+
if (seenSpeechIds.has(utterance.id))
|
|
757
|
+
continue;
|
|
758
|
+
seenSpeechIds.add(utterance.id);
|
|
759
|
+
pendingHeardSpeech.push(utterance);
|
|
760
|
+
heardSpeechChanged = true;
|
|
761
|
+
record({ id: nextId("notice"), kind: "notice", lifecycle: "completed", status: "ok",
|
|
762
|
+
title: "speech heard", text: redactNarration(utterance.text) });
|
|
763
|
+
}
|
|
764
|
+
if (pendingHeardSpeech.length > CUA_SPEECH_LIMITS.utterances) {
|
|
765
|
+
throw new CuaExecutorError("invalid_response", "outcome_uncertain");
|
|
766
|
+
}
|
|
767
|
+
return pendingHeardSpeech.length === 0 ? value : { ...value, heardSpeech: pendingHeardSpeech.slice() };
|
|
768
|
+
};
|
|
670
769
|
// The declared observation window (#510). The harness holds, looks, and takes nothing back to
|
|
671
770
|
// the model: no action, no turn, no tokens. It runs once, cut to whatever session budget is
|
|
672
771
|
// left, and says in the trace that the time was deliberate.
|
|
@@ -705,7 +804,7 @@ export async function runComputerUseLoop(options) {
|
|
|
705
804
|
throw new CuaAbortError();
|
|
706
805
|
await sleep(Math.min(dwell.everyMs, budget - (now() - dwellStartedAtMs)));
|
|
707
806
|
currentPhase = `dwell frame ${frames + 1}`;
|
|
708
|
-
const frameObservation = await observeBounded(`dwell frame ${frames + 1}`);
|
|
807
|
+
const frameObservation = collectHeardSpeech(await observeBounded(`dwell frame ${frames + 1}`));
|
|
709
808
|
frames += 1;
|
|
710
809
|
if (frameObservation.screenshot !== undefined)
|
|
711
810
|
onScreenshot?.(frameObservation.screenshot);
|
|
@@ -729,7 +828,7 @@ export async function runComputerUseLoop(options) {
|
|
|
729
828
|
contextHint = `The study held this page under observation for ${Math.round(dwellHeldMs / 1000)} seconds (a declared dwell window; you took no actions in that time). Continue the mission from the current state of the page.`;
|
|
730
829
|
return "continue";
|
|
731
830
|
};
|
|
732
|
-
let observation = await observeBounded("initial");
|
|
831
|
+
let observation = collectHeardSpeech(await observeBounded("initial"));
|
|
733
832
|
// Runtime-only: hand the seat's live location.href back to the orchestrator (never persisted).
|
|
734
833
|
onObservedUrl?.(observation.url);
|
|
735
834
|
if (observation.screenshot !== undefined)
|
|
@@ -744,7 +843,7 @@ export async function runComputerUseLoop(options) {
|
|
|
744
843
|
observeTasks(observation, 0);
|
|
745
844
|
const initialDwell = await dwellIfDue(observation, 0);
|
|
746
845
|
if (initialDwell !== undefined) {
|
|
747
|
-
observation = await observeBounded("after dwell");
|
|
846
|
+
observation = collectHeardSpeech(await observeBounded("after dwell"));
|
|
748
847
|
onObservedUrl?.(observation.url);
|
|
749
848
|
if (observation.screenshot !== undefined)
|
|
750
849
|
onScreenshot?.(observation.screenshot);
|
|
@@ -877,9 +976,19 @@ export async function runComputerUseLoop(options) {
|
|
|
877
976
|
}
|
|
878
977
|
}
|
|
879
978
|
bump("turns");
|
|
979
|
+
pendingHeardSpeech = [];
|
|
980
|
+
heardSpeechChanged = false;
|
|
880
981
|
previousResponseId = turn.responseId ?? previousResponseId;
|
|
881
982
|
lastResponseId = turn.responseId ?? lastResponseId;
|
|
882
983
|
recordUsage(turn);
|
|
984
|
+
// A host-authenticated provider may learn its account billing class during
|
|
985
|
+
// startup. Never price that account usage from an API model rate.
|
|
986
|
+
if (provider.executionProfile?.billing === "account-unknown" &&
|
|
987
|
+
(maxUsd !== undefined || overRunBudget !== undefined || estimateTurnCostUsd !== undefined)) {
|
|
988
|
+
completionReason = "harness_error";
|
|
989
|
+
reason = "Codex is using a ChatGPT account; API dollar caps cannot bound account usage. Use a finite timeout or the OpenAI API participant.";
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
883
992
|
if (turn.interruption !== undefined) {
|
|
884
993
|
// A provider can exhaust its response budget before producing visible text, or midway
|
|
885
994
|
// through an action. Preserve usage and partial narration, but never interpret either as
|
|
@@ -909,7 +1018,7 @@ export async function runComputerUseLoop(options) {
|
|
|
909
1018
|
title: tokenLimit ? "provider token limit reached" : unexpectedStatus ? "unexpected provider response status" : "provider response incomplete", text: reason });
|
|
910
1019
|
break;
|
|
911
1020
|
}
|
|
912
|
-
if (requiresUsage && (
|
|
1021
|
+
if (requiresUsage && usageUnavailableForCap()) {
|
|
913
1022
|
stopForUnreportedUsage();
|
|
914
1023
|
break;
|
|
915
1024
|
}
|
|
@@ -1029,7 +1138,7 @@ export async function runComputerUseLoop(options) {
|
|
|
1029
1138
|
if (taskTracker !== undefined) {
|
|
1030
1139
|
try {
|
|
1031
1140
|
currentPhase = "observing closing task state";
|
|
1032
|
-
const closing = await observeBounded("closing");
|
|
1141
|
+
const closing = collectHeardSpeech(await observeBounded("closing"));
|
|
1033
1142
|
observeTasks(closing, turnNumber);
|
|
1034
1143
|
}
|
|
1035
1144
|
catch (error) {
|
|
@@ -1203,6 +1312,7 @@ export async function runComputerUseLoop(options) {
|
|
|
1203
1312
|
kind: "ui_action",
|
|
1204
1313
|
lifecycle: "completed",
|
|
1205
1314
|
title: actionTitle,
|
|
1315
|
+
...(action.kind === "speak" ? { text: redactNarration(action.text) } : {}),
|
|
1206
1316
|
// Structured pin coordinates (#441), exactly the click classes the Observer
|
|
1207
1317
|
// pins render — recorded fact instead of a title re-parse downstream.
|
|
1208
1318
|
...(action.kind === "click" || action.kind === "double_click"
|
|
@@ -1213,7 +1323,7 @@ export async function runComputerUseLoop(options) {
|
|
|
1213
1323
|
if (signal?.aborted)
|
|
1214
1324
|
throw new CuaAbortError();
|
|
1215
1325
|
currentPhase = `observing UI state after turn ${turnNumber}`;
|
|
1216
|
-
observation = await observeBounded(`after turn ${turnNumber}`);
|
|
1326
|
+
observation = collectHeardSpeech(await observeBounded(`after turn ${turnNumber}`));
|
|
1217
1327
|
// Runtime-only: hand the seat's live location.href back to the orchestrator (never persisted).
|
|
1218
1328
|
onObservedUrl?.(observation.url);
|
|
1219
1329
|
if (observation.screenshot !== undefined)
|
|
@@ -1228,7 +1338,7 @@ export async function runComputerUseLoop(options) {
|
|
|
1228
1338
|
observeTasks(observation, turnNumber);
|
|
1229
1339
|
const dwellOutcome = await dwellIfDue(observation, turnNumber);
|
|
1230
1340
|
if (dwellOutcome !== undefined) {
|
|
1231
|
-
observation = await observeBounded("after dwell");
|
|
1341
|
+
observation = collectHeardSpeech(await observeBounded("after dwell"));
|
|
1232
1342
|
onObservedUrl?.(observation.url);
|
|
1233
1343
|
if (observation.screenshot !== undefined)
|
|
1234
1344
|
onScreenshot?.(observation.screenshot);
|
|
@@ -1276,13 +1386,14 @@ export async function runComputerUseLoop(options) {
|
|
|
1276
1386
|
// behavioral signal already (the agent took nothing but screenshots and waits), so it keeps
|
|
1277
1387
|
// reading the frame on its own — a repeated screenshot is exactly what an idle streak IS, and
|
|
1278
1388
|
// feeding repetition into it would grant an extra forgiveness step for being idle.
|
|
1279
|
-
const
|
|
1389
|
+
const observedProgress = frameChanged || heardSpeechChanged;
|
|
1390
|
+
const progressed = observedProgress || !repeatingRecentAction;
|
|
1280
1391
|
// A screenshot/wait turn while the UI visibly changes may be patience through loading or a
|
|
1281
1392
|
// transition, so grant a bounded recovery window. Do not grant infinite immunity: animated
|
|
1282
1393
|
// pixels or state-executor turn counters can otherwise keep a screenshot/wait loop alive
|
|
1283
1394
|
// until the wall-clock timeout.
|
|
1284
1395
|
if (idleThisTurn) {
|
|
1285
|
-
if (
|
|
1396
|
+
if (observedProgress && idleProgressForgivenessUsed < IDLE_PROGRESS_FORGIVENESS_STEPS) {
|
|
1286
1397
|
idleProgressForgivenessUsed += 1;
|
|
1287
1398
|
consecutiveIdle = 0;
|
|
1288
1399
|
}
|
|
@@ -1434,11 +1545,11 @@ export async function runComputerUseLoop(options) {
|
|
|
1434
1545
|
skip = "the session deadline was reached";
|
|
1435
1546
|
else if (provider.requiresFrame && closingObservation.screenshot === undefined)
|
|
1436
1547
|
skip = "the final observation has no required frame";
|
|
1437
|
-
if (skip === undefined && (maxUsd !== undefined || overRunBudget !== undefined) && (
|
|
1548
|
+
if (skip === undefined && (maxUsd !== undefined || overRunBudget !== undefined) && usageUnavailableForCap()) {
|
|
1438
1549
|
skip = "remaining model budget is unknown because an earlier participant turn did not report complete usage";
|
|
1439
1550
|
}
|
|
1440
1551
|
if (skip === undefined && maxUsd !== undefined) {
|
|
1441
|
-
const estimate = sawUsage ? estimateTurnCostUsd?.(runningUsage()) : undefined;
|
|
1552
|
+
const estimate = sawUsage || knownPendingUsage() !== undefined ? estimateTurnCostUsd?.(runningUsage()) : undefined;
|
|
1442
1553
|
if (estimate === undefined || estimate === null || !Number.isFinite(estimate))
|
|
1443
1554
|
skip = "remaining model budget could not be established";
|
|
1444
1555
|
else if (estimate >= maxUsd)
|
|
@@ -1512,7 +1623,7 @@ export async function runComputerUseLoop(options) {
|
|
|
1512
1623
|
const detail = signal?.aborted ? "cancelled" : controller.signal.aborted || error instanceof CuaDeadlineError || error instanceof CuaStallError
|
|
1513
1624
|
? "closing report deadline reached" : error instanceof Error ? error.message : String(error);
|
|
1514
1625
|
const closingReceipt = provider.requestPolicy === "fail_closed" ? providerRequests.at(-1) : undefined;
|
|
1515
|
-
const usageReported = closingReceipt?.
|
|
1626
|
+
const usageReported = closingReceipt?.usageComplete === true && completeTurnUsage(closingReceipt.usage);
|
|
1516
1627
|
note("failed", `${detail}; closing request usage is ${usageReported ? "reported" : "unreported"}`, usageReported);
|
|
1517
1628
|
}
|
|
1518
1629
|
}
|
|
@@ -1579,7 +1690,8 @@ export async function runComputerUseLoop(options) {
|
|
|
1579
1690
|
...(affordanceObservations.length > 0 ? { affordanceUse: summarizeAffordanceUse(affordanceObservations) } : {}),
|
|
1580
1691
|
...(declaredOutcome === undefined ? {} : { declaredOutcome }),
|
|
1581
1692
|
...(debrief === undefined ? {} : { debrief }),
|
|
1582
|
-
...(hasUnreportedInteractionUsage() || (requiresUsage && incompleteInteractionUsage)
|
|
1693
|
+
...(interactionRequestPending || hasUnreportedInteractionUsage() || (requiresUsage && incompleteInteractionUsage)
|
|
1694
|
+
? { interactionUsageIncomplete: true } : {}),
|
|
1583
1695
|
// The funnel is present exactly when a protocol was declared — including a session that ended
|
|
1584
1696
|
// on turn 0, whose funnel honestly reads 0/N. No tasks declared means no funnel, not an empty one.
|
|
1585
1697
|
...(taskTracker === undefined ? {} : { taskFunnel: taskTracker.funnel() }),
|