humanish 0.110.4 → 0.111.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/CONTEXT.md +2 -2
  3. package/dist/actors/codex/restricted-participant-policy.d.ts +2 -0
  4. package/dist/actors/codex/restricted-participant-policy.js +30 -2
  5. package/dist/actors/codex/restricted-participant.js +1 -1
  6. package/dist/actors/computer-use/executor-diagnostic.d.ts +15 -0
  7. package/dist/actors/computer-use/executor-diagnostic.js +105 -0
  8. package/dist/actors/computer-use/executor-error.d.ts +18 -3
  9. package/dist/actors/computer-use/executor-error.js +30 -4
  10. package/dist/actors/computer-use/loop/actions.d.ts +6 -3
  11. package/dist/actors/computer-use/loop/actions.js +27 -7
  12. package/dist/actors/computer-use/loop/ending.js +9 -1
  13. package/dist/actors/computer-use/loop/types.d.ts +14 -0
  14. package/dist/actors/computer-use/loop.d.ts +1 -1
  15. package/dist/actors/computer-use/loop.js +1 -1
  16. package/dist/actors/computer-use/openai-context.d.ts +8 -2
  17. package/dist/actors/computer-use/openai-context.js +91 -26
  18. package/dist/actors/computer-use/openai-provider.js +55 -12
  19. package/dist/actors/computer-use/openai-requests.d.ts +2 -1
  20. package/dist/actors/computer-use/openai-requests.js +4 -1
  21. package/dist/actors/contract.d.ts +7 -1
  22. package/dist/actors/local-agent/claude-session.js +1 -1
  23. package/dist/actors/local-agent/cli.js +1 -1
  24. package/dist/actors/scripted-browser/actor.d.ts +2 -2
  25. package/dist/browser-control/client.js +5 -5
  26. package/dist/browser-control/protocol.d.ts +30 -1
  27. package/dist/browser-control/protocol.js +19 -2
  28. package/dist/browser-control/transport.d.ts +3 -2
  29. package/dist/browser-control/transport.js +12 -7
  30. package/dist/cli/commands/study-route-computer-use.js +2 -2
  31. package/dist/cli/commands/study-route-shared-world.js +2 -2
  32. package/dist/cli/commands/study.js +1 -4
  33. package/dist/cli/doctor.js +11 -0
  34. package/dist/evidence/encoded-text.d.ts +3 -3
  35. package/dist/evidence/encoded-text.js +10 -5
  36. package/dist/evidence/redaction.d.ts +2 -2
  37. package/dist/evidence/redaction.js +18 -7
  38. package/dist/guest/browser-tools.js +5 -1
  39. package/dist/guest/chromium-text.js +42 -18
  40. package/dist/guest/desktop-executor.js +5 -1
  41. package/dist/keys/key-resolution.d.ts +3 -0
  42. package/dist/keys/key-resolution.js +15 -2
  43. package/dist/routes/computer-use/bundle-parts.d.ts +4 -0
  44. package/dist/routes/computer-use/bundle-parts.js +7 -2
  45. package/dist/routes/computer-use/fanout-bundle.js +4 -2
  46. package/dist/routes/computer-use/local-vm.js +1 -2
  47. package/dist/routes/computer-use/participant-model.js +7 -2
  48. package/dist/routes/computer-use/plan.js +7 -10
  49. package/dist/routes/computer-use/preflight.js +1 -1
  50. package/dist/routes/computer-use/result.js +1 -1
  51. package/dist/routes/computer-use/route.js +2 -3
  52. package/dist/routes/computer-use/setup.js +2 -2
  53. package/dist/routes/computer-use/types.d.ts +4 -4
  54. package/dist/routes/preview.js +1 -0
  55. package/dist/routes/scripted/plan.js +4 -5
  56. package/dist/routes/scripted/route.js +3 -3
  57. package/dist/routes/scripted/scenario.d.ts +1 -1
  58. package/dist/routes/scripted/scenario.js +3 -3
  59. package/dist/routes/scripted/types.d.ts +2 -2
  60. package/dist/routes/shared-world/handoff.js +1 -1
  61. package/dist/routes/shared-world/participant-specs.js +1 -1
  62. package/dist/routes/shared-world/plan.js +6 -8
  63. package/dist/routes/shared-world/route.js +2 -2
  64. package/dist/routes/shared-world/setup.js +1 -0
  65. package/dist/routes/shared-world/types.d.ts +1 -1
  66. package/dist/routes/terminal/bundle.d.ts +3 -3
  67. package/dist/routes/terminal/dry-run.js +1 -0
  68. package/dist/routes/terminal/ledger.d.ts +2 -2
  69. package/dist/routes/terminal/lifetime.js +1 -1
  70. package/dist/routes/terminal/live-finish.js +25 -6
  71. package/dist/routes/terminal/live-sandbox.js +5 -1
  72. package/dist/routes/terminal/plan.js +3 -4
  73. package/dist/routes/terminal/recorder.d.ts +6 -0
  74. package/dist/routes/terminal/recorder.js +61 -6
  75. package/dist/routes/terminal/route.js +2 -3
  76. package/dist/routes/terminal/session.js +2 -1
  77. package/dist/routes/terminal/token-usage.d.ts +3 -0
  78. package/dist/routes/terminal/token-usage.js +5 -0
  79. package/dist/routes/terminal/types.d.ts +3 -2
  80. package/dist/routes/terminal/types.js +2 -1
  81. package/dist/run/dry-run.js +1 -0
  82. package/dist/run/results.d.ts +3 -1
  83. package/dist/run/run.d.ts +2 -0
  84. package/dist/run/run.js +16 -2
  85. package/dist/run/terminal-contract.d.ts +10 -0
  86. package/dist/run/terminal-contract.js +27 -9
  87. package/dist/run-study.d.ts +5 -3
  88. package/dist/run-study.js +7 -11
  89. package/dist/study/adapter-scorer-loader.d.ts +1 -1
  90. package/dist/study/adapter-scorer-loader.js +3 -3
  91. package/dist/study/composition-rules.js +36 -27
  92. package/dist/study/config.d.ts +29 -13
  93. package/dist/study/config.js +174 -157
  94. package/dist/study/discover.d.ts +1 -1
  95. package/dist/study/discover.js +1 -1
  96. package/dist/study/doctor.js +4 -5
  97. package/dist/study/keys.d.ts +250 -2
  98. package/dist/study/keys.js +32 -62
  99. package/dist/study/migrate/convert.js +3 -4
  100. package/dist/study/migrate/v2.d.ts +22 -7
  101. package/dist/study/migrate/v2.js +173 -23
  102. package/dist/study/parse/actors.d.ts +11 -32
  103. package/dist/study/parse/actors.js +11 -156
  104. package/dist/study/parse/comms.js +6 -2
  105. package/dist/study/parse/execution.d.ts +9 -4
  106. package/dist/study/parse/execution.js +4 -39
  107. package/dist/study/parse/{study-v3.d.ts → front.d.ts} +6 -17
  108. package/dist/study/parse/{study-v3.js → front.js} +24 -112
  109. package/dist/study/parse/subject.d.ts +2 -0
  110. package/dist/study/parse/subject.js +15 -18
  111. package/dist/study/parse/url-credentials.d.ts +3 -3
  112. package/dist/study/parse/url-credentials.js +155 -13
  113. package/dist/study/persona-resolve.js +7 -9
  114. package/dist/study/plan-base.d.ts +1 -1
  115. package/dist/study/plan-base.js +5 -7
  116. package/dist/study/plan-participants.d.ts +2 -2
  117. package/dist/study/plan-participants.js +3 -3
  118. package/dist/study/plan-types.d.ts +7 -5
  119. package/dist/study/plan.js +12 -6
  120. package/dist/study/preflight-probes.js +2 -2
  121. package/dist/study/preflight.d.ts +1 -1
  122. package/dist/study/preflight.js +1 -1
  123. package/dist/study/routing.d.ts +5 -5
  124. package/dist/study/routing.js +16 -16
  125. package/dist/study/run-study-options.d.ts +1 -1
  126. package/dist/study/run-study-options.js +2 -2
  127. package/dist/study/study-deps.d.ts +1 -1
  128. package/dist/study/study-fields.d.ts +4 -25
  129. package/dist/study/study-fields.js +15 -44
  130. package/dist/study/summary.js +13 -8
  131. package/dist/study/types.d.ts +51 -128
  132. package/dist/study/types.js +0 -1
  133. package/dist/study/url-credentials.js +3 -4
  134. package/dist/study/validation.d.ts +2 -10
  135. package/dist/study/validation.js +23 -53
  136. package/dist/study/warnings.d.ts +11 -3
  137. package/dist/study/warnings.js +56 -110
  138. package/dist/substrates/local/runtime-config.js +11 -13
  139. package/docs/architecture/browser-control.md +12 -2
  140. package/docs/architecture/guest-desktop.md +7 -1
  141. package/docs/architecture/terminal-product-route.md +3 -2
  142. package/docs/contracts/schemas.md +39 -9
  143. package/package.json +1 -1
  144. package/dist/study/parse/study.d.ts +0 -38
  145. package/dist/study/parse/study.js +0 -225
package/CHANGELOG.md CHANGED
@@ -8,6 +8,37 @@ The Unreleased section holds the full notes for the next version until it is tag
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 0.111.1: E2B URLs in study URLs, fan-out dry runs verify, zero-data-retention cache holds (2026-10-06)
12
+
13
+ humanish 0.111.1 fixes a study URL refusal and two verify problems that an adopter met on 0.111.0,
14
+ and the input cost of long computer-use sessions on zero-data-retention OpenAI organizations. A
15
+ study URL may carry an E2B app URL in a query parameter or fragment, which 0.110.2 through 0.111.0
16
+ refused as a credential. A computer-use dry run with more than one participant against an E2B app
17
+ URL verifies `share_ready`, and verify finds a hex-encoded secret or E2B URL after `=`. On a
18
+ zero-data-retention organization, a participant past about 64,000 tokens of carried conversation
19
+ keeps the provider's prompt cache, and an OpenAI 404 for a stored item switches it to carrying its
20
+ own conversation where 0.111.0 stopped it. A Codex participant's wait over 30 seconds is shortened
21
+ to 30 seconds, a terminal session past 512 KiB of output keeps its verdict, and `humanish doctor`
22
+ in a terminal study's sandbox reports the egress placeholder as no key. `execution.egressAllow` on
23
+ a route other than `terminal` now warns, and 0.112.0 refuses it there.
24
+
25
+ [Release notes](https://github.com/danielgwilson/humanish/releases/tag/v0.111.1)
26
+
27
+ ## 0.111.0: StudyConfig has the keys of a v3 study file (2026-10-05)
28
+
29
+ humanish 0.111.0 is the first breaking release under the compatibility policy. It breaks library
30
+ callers that read or build a `StudyConfig`, the type `parseStudy` returns and `runStudy` takes:
31
+ the type now has the keys of a `humanish.study.v3` file, such as `actor`, `route`,
32
+ `participants`, `caps` and `mode`, where it had `actors[0]`, `execution.caps` and
33
+ `scenario.mode`. The release notes carry the migration table. `runStudy` refuses a config that
34
+ still sets a 0.110 field with `HUMANISH_STUDY_V2_UNSUPPORTED`, so a budget left in
35
+ `execution.caps` cannot run uncapped. Study files and CLI commands are unchanged, except two JSON
36
+ outputs: `humanish study show --json` prints the v3 config, and `humanish study check --json`
37
+ names a participant's target `participants[<i>].target`. The `--env-file` and `cleanup` aliases
38
+ still work and are removed in 0.112.0, on or after 2026-11-03.
39
+
40
+ [Release notes](https://github.com/danielgwilson/humanish/releases/tag/v0.111.0)
41
+
11
42
  ## 0.110.4: Scripted runs reclaim clean, review says interrupted, Chrome under a long TMPDIR (2026-10-05)
12
43
 
13
44
  humanish 0.110.4 fixes four findings of the 0.110.3 live smoke. `humanish reclaim --check` reports
package/CONTEXT.md CHANGED
@@ -18,7 +18,7 @@ bundle and output fields keep older spellings; the second table maps them.
18
18
  | persona | Who a participant is: traits, background, device. `humanish/personas/<id>.yaml`. | `src/study/persona.ts`, `src/study/persona-resolve.ts` |
19
19
  | mission | The free-form brief a model-driven participant receives: `actor.mission`. `actor.tasks[]` adds discrete tasks: each has a `goal` the participant sees and an optional `success` check it does not. | `StudyActor` in `src/study/types.ts` |
20
20
  | assignment | What one participant was asked to do: the mission, a `participants` entry's `instruction` or the homogeneous `participants.instruction` (recorded as `focus`), and the tasks. Each stream records it as `streams[].assignment`. | `participantAssignment` in `src/study/participant-assignment.ts`; `RunParticipantAssignment` in `src/run/streams.ts` |
21
- | scenario | A scenario file, `humanish/scenarios/<id>.yaml` (`humanish.scenario.v1`): a goal and ordered steps with expectations. A scripted study names one in `scenario:`, and only the scripted route executes those steps. A v2 study file's `scenario` block held run settings (`mode`, `caps`, `ref`), which v3 moves to the top level. A model-driven participant follows its mission. | `StudyScenario` in `src/study/types.ts`; `src/routes/scripted/route.ts` resolves the scenario, and `executeBrowserPersonaStep` (`src/actors/scripted-browser/steps.ts`) runs each step |
21
+ | scenario | A scenario file, `humanish/scenarios/<id>.yaml` (`humanish.scenario.v1`): a goal and ordered steps with expectations. A scripted study names one in `scenario:`, and only the scripted route executes those steps. A v2 study file's `scenario` block held run settings (`mode`, `caps`, `ref`), which v3 moves to the top level. A model-driven participant follows its mission. | `scenario` in `StudyConfig` (`src/study/types.ts`); `src/routes/scripted/route.ts` resolves the scenario, and `executeBrowserPersonaStep` (`src/actors/scripted-browser/steps.ts`) runs each step |
22
22
  | surface | One browser viewport that a scripted run replays its journey on: `desktop` or `mobile`. A scripted study runs the desktop surface by default, and `surfaces: [desktop, mobile]` adds the mobile one. Each surface writes `actor-<surface>.json` and `traces/<surface>.json`. | `BrowserSurface` and `browserSurfaces` in `src/actors/scripted-browser/types.ts`; the plan's `surfaces` in `src/routes/scripted/plan.ts` |
23
23
  | journey | A scenario's `browser.steps` parsed into ordered steps with a goal and a start path. The scripted route replays it once on each surface, and each surface's session reports the first step that did not pass. | `BrowserPersonaJourney` in `src/actors/scripted-browser/types.ts`; `parseBrowserPersonaJourneyFromScenario` in `src/actors/scripted-browser/journey.ts` |
24
24
  | subject | The product under study, declared by `subject.source`: `clone`, `app-url`, `local-tree`, `terminal-product`, `desktop-cli`, `local-app` or `this-repo`. `run.json` records its provenance under `subject`. | `StudySubject` in `src/study/types.ts`, parsed in `src/study/parse/subject.ts` |
@@ -33,7 +33,7 @@ bundle and output fields keep older spellings; the second table maps them.
33
33
  | participant desktop | The desktop one computer-use or shared-world participant drives: a substrate plus the route's concerns, such as subject provisioning, comms and the participant's plan. It lives in the route, so `src/substrates/` holds only provider primitives ([ADR 0004](docs/decisions/0004-participant-desktops-stay-in-the-route.md)). | `ParticipantDesktop` in `src/routes/computer-use/participant-desktop.ts`; `createE2BParticipantDesktop` in `src/routes/computer-use/e2b-desktop/desktop.ts`; the local VM's in `src/routes/computer-use/local-vm.ts` |
34
34
  | receipt | A line in a run's `sandbox-receipts.ndjson` that records a sandbox's id, provider, the participant or subject it belongs to (`laneId`), and its time limit as soon as the sandbox is created, before any work runs in it. `humanish reclaim` kills sandboxes by these ids after an interrupted run. | `SandboxReceipt` in `src/run/sandbox-receipts.ts`; `reclaimRunSandboxes` in `src/run/reclaim.ts` |
35
35
  | owner tags | The E2B metadata every sandbox humanish creates carries: `tool: humanish`, the run's `runId`, and `runKey`, a digest of the run directory's inode and creation time. The run journals them in an owner line before its first create. `humanish reclaim` lists sandboxes that match all three, which finds one whose id never reached a receipt. | `sandboxOwnerTags` in `src/run/sandbox-creates.ts`; `findE2BSandboxesByTags` in `src/substrates/e2b/sandbox.ts` |
36
- | topology | Whether participants share one instance of the subject. `route: shared-world` gives one for all; every other route gives each participant its own (`per-lane-worlds`). `run.json` records it as `sharedWorld.topology`. | `StudySubjectTopology` in `src/study/types.ts` |
36
+ | topology | Whether participants share one instance of the subject. `route: shared-world` gives one for all; every other route gives each participant its own (`per-lane-worlds`). `run.json` records it as `sharedWorld.topology`. | `route` in `StudyConfig` (`src/study/types.ts`); `isSharedWorldComposition` in `src/study/routing.ts` |
37
37
  | plane | The one instance of the subject that a shared-world study's participants use together. A `provisioned` plane is a `clone` or `local-tree` subject that humanish serves in a sandbox, with a declared subject state. An `external-public` plane is a deployment already running at `subject.appUrl`, whose owner the study names in `subject.publicTarget.owner`. Each plane has at least two participants. | `SharedWorldPlane` in `src/study/plan-types.ts`; `src/routes/shared-world/` |
38
38
  | stream | One live or recorded view of a participant, such as a browser or a terminal, in `run.json` and the Observer. | `RunStream` in `src/run/streams.ts` |
39
39
  | Observer | The single-file review page built from `observer/observer-data.json` (`humanish.observer-data.v1`). | `observer/` (the page), `src/observer/` (data, render, serve) |
@@ -9,6 +9,8 @@ export declare const PARTICIPANT_LIMITS: Readonly<{
9
9
  narration: 2000;
10
10
  output: number;
11
11
  actions: 4;
12
+ /** The longest wait one action lasts; a longer one is shortened to it. */
13
+ waitMs: 30000;
12
14
  requestMs: 180000;
13
15
  cleanupMs: 5000;
14
16
  }>;
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { browserControlActionSchema, browserOnlyControlActionSchema, validateBrowserControlAction, } from "../../browser-control/protocol.js";
2
+ import { BROWSER_CONTROL_LIMITS, browserControlActionSchema, browserOnlyControlActionSchema, validateBrowserControlAction, } from "../../browser-control/protocol.js";
3
3
  import { validClosingReport } from "../computer-use/loop.js";
4
4
  /** The declared profile. A participant replaces cliVersion with its detected CLI release, or with
5
5
  * this host's newest qualified release before its first launch. */
@@ -21,6 +21,8 @@ export const PARTICIPANT_LIMITS = Object.freeze({
21
21
  narration: 2000,
22
22
  output: 256 * 1024,
23
23
  actions: 4,
24
+ /** The longest wait one action lasts; a longer one is shortened to it. */
25
+ waitMs: BROWSER_CONTROL_LIMITS.waitMs,
24
26
  requestMs: 180_000,
25
27
  cleanupMs: 5000,
26
28
  });
@@ -49,15 +51,41 @@ export const PARTICIPANT_FINAL_SCHEMA = {
49
51
  },
50
52
  },
51
53
  };
54
+ /**
55
+ * Shorten each wait longer than one browser-control request may wait. A failed tool call ends the
56
+ * native run, so a wait that asks too much would otherwise end the whole session.
57
+ */
58
+ function shortenWaits(value) {
59
+ const actions = value?.actions;
60
+ if (typeof value !== "object" || !Array.isArray(actions))
61
+ return { value, shortened: [] };
62
+ const shortened = [];
63
+ const limit = PARTICIPANT_LIMITS.waitMs;
64
+ const next = actions.map((action, index) => {
65
+ const wait = action;
66
+ if (wait?.kind !== "wait" ||
67
+ typeof wait.ms !== "number" ||
68
+ !Number.isFinite(wait.ms) ||
69
+ wait.ms <= limit)
70
+ return action;
71
+ shortened.push({ index, requestedMs: wait.ms, ms: limit });
72
+ return { ...wait, ms: limit };
73
+ });
74
+ return shortened.length === 0
75
+ ? { value, shortened }
76
+ : { value: { ...value, actions: next }, shortened };
77
+ }
52
78
  /** humanish tool arguments, validated before the shared executor sees a batch. */
53
79
  export function parseParticipantTool(value, speechEnabled = false) {
54
80
  if (Buffer.byteLength(JSON.stringify(value) ?? "") > PARTICIPANT_LIMITS.output)
55
81
  throw new Error("invalid_response");
56
- const v = toolInput(speechEnabled).parse(value);
82
+ const { value: admitted, shortened } = shortenWaits(value);
83
+ const v = toolInput(speechEnabled).parse(admitted);
57
84
  if (Buffer.byteLength(v.narration) > PARTICIPANT_LIMITS.narration)
58
85
  throw new Error("invalid_response");
59
86
  return {
60
87
  actions: v.actions.map(validateBrowserControlAction),
88
+ ...(shortened.length === 0 ? {} : { shortenedWaits: shortened }),
61
89
  pendingSafetyChecks: [],
62
90
  done: false,
63
91
  ...(v.narration ? { message: v.narration } : {}),
@@ -39,7 +39,7 @@ function deferred() {
39
39
  return { promise, resolve, reject };
40
40
  }
41
41
  // prose-check: model prompt (Codex reads this tool description, not a person)
42
- const toolDescription = (speechEnabled) => `Act on the participant's browser through humanish. Submit one to four UI actions${speechEnabled ? ", including speak when you need to reply aloud," : ""} and a short public comment; never private reasoning. humanish returns a JSON STRING with execution acknowledgments${speechEnabled ? ", speech heard from the actual participant speaker sink," : ""} and a fresh screenshot. In Code Mode use: const r = JSON.parse(await tools.humanish_ui({narration: "...", actions: [...]})); text({acknowledgments: r.acknowledgments${speechEnabled ? ", heardSpeech: r.heardSpeech" : ""}, contextHint: r.contextHint, closing: r.closing}); image(r.imageUrl). Call serially and inspect each returned screenshot${speechEnabled ? " and heardSpeech array" : ""} before deciding what to do next.${speechEnabled ? " Use speak only after the visible UI shows that you joined the call and your microphone is unmuted; it sends audio into that call." : ""} Acknowledged input does not prove an application outcome. If closing is true, stop calling tools and give your final account.`;
42
+ const toolDescription = (speechEnabled) => `Act on the participant's browser through humanish. Submit one to four UI actions${speechEnabled ? ", including speak when you need to reply aloud," : ""} and a short public comment; never private reasoning. humanish returns a JSON STRING with execution acknowledgments${speechEnabled ? ", speech heard from the actual participant speaker sink," : ""} and a fresh screenshot. In Code Mode use: const r = JSON.parse(await tools.humanish_ui({narration: "...", actions: [...]})); text({acknowledgments: r.acknowledgments${speechEnabled ? ", heardSpeech: r.heardSpeech" : ""}, contextHint: r.contextHint, closing: r.closing}); image(r.imageUrl). Call serially and inspect each returned screenshot${speechEnabled ? " and heardSpeech array" : ""} before deciding what to do next. A wait action lasts at most ${L.waitMs} ms; a longer wait is shortened to ${L.waitMs} ms, so wait longer with another call.${speechEnabled ? " Use speak only after the visible UI shows that you joined the call and your microphone is unmuted; it sends audio into that call." : ""} Acknowledged input does not prove an application outcome. If closing is true, stop calling tools and give your final account.`;
43
43
  const participantInstructions = (prompt, speechEnabled) => `${prompt}\n\nYou are the study participant throughout this conversation, including its closing account. Use only the supplied screenshots and humanish_ui tool to interact. The tool returns a JSON string: parse it, inspect acknowledgments${speechEnabled ? " and heardSpeech captured from the actual participant speaker sink" : ""}, and display imageUrl with Code Mode image(). Do not print the image data URL as text. Keep your persona and earlier observations throughout the session.${speechEnabled ? " A speak action plays into the participant microphone; use it only after the visible UI shows that you joined the call and the microphone is unmuted." : ""} Speak publicly about your experience, never reveal private reasoning. Completed inputs do not prove application outcomes; verify on the next screenshot. When the task ends, return only the required final JSON with outcome, summary and frictionReports. Report observed confusion and recovered mistakes as well as blockers. Do not invent observations.`;
44
44
  /** Turns and failures from the native run, handed in order to the next provider request. */
45
45
  class ParticipantEvents {
@@ -0,0 +1,15 @@
1
+ /** The driver steps a diagnostic can name. */
2
+ export declare const CUA_DIAGNOSTIC_STEPS: readonly ["owner_window_check", "cdp_session", "frame_tree", "isolated_world", "focus_probe", "insert_text", "cdp_detach", "text_port", "channel", "channel_write"];
3
+ export type CuaDiagnosticStep = (typeof CUA_DIAGNOSTIC_STEPS)[number];
4
+ /** The kinds of failure a diagnostic can name. */
5
+ export declare const CUA_DIAGNOSTIC_CATEGORIES: readonly ["target_closed", "detached", "navigation", "timeout", "protocol_error", "channel_closed", "unknown"];
6
+ export type CuaDiagnosticCategory = (typeof CUA_DIAGNOSTIC_CATEGORIES)[number];
7
+ export interface CuaExecutorDiagnostic {
8
+ readonly step: CuaDiagnosticStep;
9
+ readonly category: CuaDiagnosticCategory;
10
+ }
11
+ /** The diagnostic for `error`, thrown during `step`: the step and a category, never its text. */
12
+ export declare function classifyExecutorFailure(step: CuaDiagnosticStep, error: unknown): CuaExecutorDiagnostic;
13
+ export declare function isCuaExecutorDiagnostic(value: unknown): value is CuaExecutorDiagnostic;
14
+ /** The fixed sentence for a trace or a failure record, built only from the two words. */
15
+ export declare function describeExecutorDiagnostic(diagnostic: CuaExecutorDiagnostic): string;
@@ -0,0 +1,105 @@
1
+ // The diagnostic an executor attaches to a failure its driver did not declare: which step failed
2
+ // and what kind of failure it was, as two fixed words. It carries no error text. A browser error
3
+ // message can hold a form value, page text, a cookie pair or a file path, and redaction on the
4
+ // host cannot be relied on to catch all of them, so the message never leaves the classifier.
5
+ /** The driver steps a diagnostic can name. */
6
+ export const CUA_DIAGNOSTIC_STEPS = Object.freeze([
7
+ "owner_window_check",
8
+ "cdp_session",
9
+ "frame_tree",
10
+ "isolated_world",
11
+ "focus_probe",
12
+ "insert_text",
13
+ "cdp_detach",
14
+ "text_port",
15
+ "channel",
16
+ "channel_write",
17
+ ]);
18
+ /** The kinds of failure a diagnostic can name. */
19
+ export const CUA_DIAGNOSTIC_CATEGORIES = Object.freeze([
20
+ "target_closed",
21
+ "detached",
22
+ "navigation",
23
+ "timeout",
24
+ "protocol_error",
25
+ "channel_closed",
26
+ "unknown",
27
+ ]);
28
+ const STEP_LABELS = Object.freeze({
29
+ owner_window_check: "checking the owner window",
30
+ cdp_session: "opening a DevTools session (newCDPSession)",
31
+ frame_tree: "reading the frame tree (Page.getFrameTree)",
32
+ isolated_world: "creating the isolated world (Page.createIsolatedWorld)",
33
+ focus_probe: "probing focus (Runtime.evaluate)",
34
+ insert_text: "inserting text (Input.insertText)",
35
+ cdp_detach: "detaching the DevTools session (CDPSession.detach)",
36
+ text_port: "running a text port step",
37
+ channel: "reading the browser-control channel",
38
+ channel_write: "writing to the browser-control channel",
39
+ });
40
+ const CATEGORY_SENTENCES = Object.freeze({
41
+ target_closed: "the page, its browser context or the browser closed",
42
+ detached: "the DevTools session or the frame detached",
43
+ navigation: "the page navigated or its document was replaced",
44
+ timeout: "the call timed out",
45
+ protocol_error: "the browser refused a DevTools protocol call",
46
+ channel_closed: "the channel to the guest closed",
47
+ unknown: "the error matched no known kind",
48
+ });
49
+ /** Error classes that name their category on their own. */
50
+ const ERROR_CLASSES = Object.freeze({
51
+ TargetClosedError: "target_closed",
52
+ TimeoutError: "timeout",
53
+ ProtocolError: "protocol_error",
54
+ });
55
+ /** Node system error codes a broken channel or socket reports. */
56
+ const SYSTEM_CODES = Object.freeze({
57
+ EPIPE: "channel_closed",
58
+ ECONNRESET: "channel_closed",
59
+ ERR_STREAM_DESTROYED: "channel_closed",
60
+ ERR_STREAM_PREMATURE_CLOSE: "channel_closed",
61
+ ERR_STREAM_WRITE_AFTER_END: "channel_closed",
62
+ ETIMEDOUT: "timeout",
63
+ });
64
+ /**
65
+ * Known Playwright and Chromium message shapes, checked in order; the first match names the
66
+ * category. The message is only tested here, never copied.
67
+ */
68
+ const MESSAGE_SHAPES = Object.freeze([
69
+ [/detached|No session with given id/i, "detached"],
70
+ [
71
+ /\b(?:target|page|context|browser|session)\b[^.]*\b(?:closed|crashed|disconnected)\b/i,
72
+ "target_closed",
73
+ ],
74
+ [/navigat|Execution context was destroyed|Cannot find context with specified id/i, "navigation"],
75
+ [/timed? ?out|timeout/i, "timeout"],
76
+ [/\bProtocol error\b/i, "protocol_error"],
77
+ ]);
78
+ function categoryOf(error) {
79
+ if (!(error instanceof Error))
80
+ return "unknown";
81
+ if (Object.hasOwn(ERROR_CLASSES, error.name))
82
+ return ERROR_CLASSES[error.name];
83
+ const code = error.code;
84
+ if (typeof code === "string" && Object.hasOwn(SYSTEM_CODES, code))
85
+ return SYSTEM_CODES[code];
86
+ const message = typeof error.message === "string" ? error.message : "";
87
+ return MESSAGE_SHAPES.find(([shape]) => shape.test(message))?.[1] ?? "unknown";
88
+ }
89
+ /** The diagnostic for `error`, thrown during `step`: the step and a category, never its text. */
90
+ export function classifyExecutorFailure(step, error) {
91
+ return { step, category: categoryOf(error) };
92
+ }
93
+ export function isCuaExecutorDiagnostic(value) {
94
+ if (typeof value !== "object" || value === null)
95
+ return false;
96
+ const { step, category } = value;
97
+ return (Object.keys(value).length === 2 &&
98
+ CUA_DIAGNOSTIC_STEPS.includes(step) &&
99
+ CUA_DIAGNOSTIC_CATEGORIES.includes(category));
100
+ }
101
+ /** The fixed sentence for a trace or a failure record, built only from the two words. */
102
+ export function describeExecutorDiagnostic(diagnostic) {
103
+ const { step, category } = diagnostic;
104
+ return `${step}, ${category}: ${CATEGORY_SENTENCES[category]} while ${STEP_LABELS[step]}`;
105
+ }
@@ -1,3 +1,4 @@
1
+ import { type CuaExecutorDiagnostic } from "./executor-diagnostic.js";
1
2
  declare const messages: Readonly<{
2
3
  executor_closed: "Desktop session is closed.";
3
4
  executor_not_ready: "Desktop executor is not ready.";
@@ -14,17 +15,31 @@ declare const messages: Readonly<{
14
15
  }>;
15
16
  export type CuaExecutorErrorCode = keyof typeof messages;
16
17
  export type CuaExecutorDisposition = "not_dispatched" | "outcome_uncertain";
18
+ /**
19
+ * Why an executor refused an action before dispatch, when the participant can act on it. Each is
20
+ * a fixed word: `extra_tab` means the local browser types only while the study's tab is its only
21
+ * tab, and another tab is open.
22
+ */
23
+ export declare const CUA_REJECTION_REASONS: readonly ["extra_tab"];
24
+ export type CuaRejectionReason = (typeof CUA_REJECTION_REASONS)[number];
17
25
  export declare function isCuaExecutorErrorCode(value: unknown): value is CuaExecutorErrorCode;
18
26
  /**
19
27
  * An executor's bounded failure declaration. No backend message, cause, action payload or
20
- * transport details belong here. `not_dispatched` requires evidence that dispatch never
21
- * began; a lost acknowledgement or partial action is `outcome_uncertain`, never a retry.
28
+ * transport details belong here; `diagnostic` names a failed step and a category in fixed words.
29
+ * `not_dispatched` requires evidence that dispatch never began; a lost acknowledgement or partial
30
+ * action is `outcome_uncertain`, never a retry.
31
+ * `reason` is allowed only on an `action_rejected` / `not_dispatched` refusal.
22
32
  * Import from the same installation as the loop: names and lookalike objects do not qualify.
23
33
  */
24
34
  export declare class ComputerUseExecutorError extends Error {
25
35
  readonly code: CuaExecutorErrorCode;
26
36
  readonly disposition: CuaExecutorDisposition;
27
- constructor(code: CuaExecutorErrorCode, disposition: CuaExecutorDisposition);
37
+ readonly reason?: CuaRejectionReason;
38
+ readonly diagnostic?: CuaExecutorDiagnostic;
39
+ constructor(code: CuaExecutorErrorCode, disposition: CuaExecutorDisposition, details?: {
40
+ reason?: CuaRejectionReason | undefined;
41
+ diagnostic?: CuaExecutorDiagnostic | undefined;
42
+ });
28
43
  }
29
44
  /** A forged prototype is not an executor declaration. */
30
45
  export declare function isComputerUseExecutorError(error: unknown): error is ComputerUseExecutorError;
@@ -1,3 +1,4 @@
1
+ import { isCuaExecutorDiagnostic } from "./executor-diagnostic.js";
1
2
  const messages = Object.freeze({
2
3
  executor_closed: "Desktop session is closed.",
3
4
  executor_not_ready: "Desktop executor is not ready.",
@@ -12,32 +13,57 @@ const messages = Object.freeze({
12
13
  execution_failed: "Desktop executor could not complete the request.",
13
14
  cancelled: "Desktop executor request was cancelled.",
14
15
  });
16
+ /**
17
+ * Why an executor refused an action before dispatch, when the participant can act on it. Each is
18
+ * a fixed word: `extra_tab` means the local browser types only while the study's tab is its only
19
+ * tab, and another tab is open.
20
+ */
21
+ export const CUA_REJECTION_REASONS = Object.freeze(["extra_tab"]);
22
+ function isCuaRejectionReason(value) {
23
+ return CUA_REJECTION_REASONS.includes(value);
24
+ }
15
25
  export function isCuaExecutorErrorCode(value) {
16
26
  return typeof value === "string" && Object.hasOwn(messages, value);
17
27
  }
18
28
  const executorErrors = new WeakSet();
19
29
  /**
20
30
  * An executor's bounded failure declaration. No backend message, cause, action payload or
21
- * transport details belong here. `not_dispatched` requires evidence that dispatch never
22
- * began; a lost acknowledgement or partial action is `outcome_uncertain`, never a retry.
31
+ * transport details belong here; `diagnostic` names a failed step and a category in fixed words.
32
+ * `not_dispatched` requires evidence that dispatch never began; a lost acknowledgement or partial
33
+ * action is `outcome_uncertain`, never a retry.
34
+ * `reason` is allowed only on an `action_rejected` / `not_dispatched` refusal.
23
35
  * Import from the same installation as the loop: names and lookalike objects do not qualify.
24
36
  */
25
37
  export class ComputerUseExecutorError extends Error {
26
38
  code;
27
39
  disposition;
28
- constructor(code, disposition) {
40
+ reason;
41
+ diagnostic;
42
+ constructor(code, disposition, details = {}) {
43
+ const { reason, diagnostic } = details;
29
44
  if (!isCuaExecutorErrorCode(code) ||
30
- (disposition !== "not_dispatched" && disposition !== "outcome_uncertain")) {
45
+ (disposition !== "not_dispatched" && disposition !== "outcome_uncertain") ||
46
+ (reason !== undefined &&
47
+ (!isCuaRejectionReason(reason) ||
48
+ code !== "action_rejected" ||
49
+ disposition !== "not_dispatched")) ||
50
+ (diagnostic !== undefined && !isCuaExecutorDiagnostic(diagnostic))) {
31
51
  throw new TypeError("Invalid desktop executor error declaration.");
32
52
  }
33
53
  super(messages[code]);
34
54
  this.name = "ComputerUseExecutorError";
35
55
  this.code = code;
36
56
  this.disposition = disposition;
57
+ if (reason !== undefined)
58
+ this.reason = reason;
59
+ if (diagnostic !== undefined)
60
+ this.diagnostic = Object.freeze({ step: diagnostic.step, category: diagnostic.category });
37
61
  // Keep the values used in durable diagnostics finite even for JavaScript callers.
38
62
  Object.defineProperties(this, {
39
63
  code: { writable: false, configurable: false },
40
64
  disposition: { writable: false, configurable: false },
65
+ reason: { writable: false, configurable: false },
66
+ diagnostic: { writable: false, configurable: false },
41
67
  });
42
68
  executorErrors.add(this);
43
69
  }
@@ -1,5 +1,5 @@
1
1
  import type { LoopSession } from "./session.js";
2
- import type { CuaAction, CuaTurnRequest } from "./types.js";
2
+ import type { CuaAction, CuaTurnRequest, ShortenedWait } from "./types.js";
3
3
  /** A public-safe one-line action label. Never includes raw typed text. */
4
4
  export declare function describeCuaAction(action: CuaAction): string;
5
5
  /**
@@ -15,7 +15,10 @@ export declare function isIdleTurn(actions: readonly CuaAction[]): boolean;
15
15
  export interface ActionBatch {
16
16
  /** Host input acknowledgements for the provider's next request. */
17
17
  readonly execution: NonNullable<CuaTurnRequest["previousExecution"]>;
18
- /** Guidance for the next request, set when a pre-dispatch rejection stopped the batch. */
18
+ /**
19
+ * Guidance for the next request, set when the provider shortened a wait or a pre-dispatch
20
+ * rejection stopped the batch.
21
+ */
19
22
  readonly hint: string | undefined;
20
23
  }
21
24
  /**
@@ -23,4 +26,4 @@ export interface ActionBatch {
23
26
  * leaves the desktop usable; a failed desktop command skips its action; any other failure ends
24
27
  * the session.
25
28
  */
26
- export declare function runActionBatch(session: LoopSession, actions: readonly CuaAction[]): Promise<ActionBatch>;
29
+ export declare function runActionBatch(session: LoopSession, actions: readonly CuaAction[], shortenedWaits?: readonly ShortenedWait[]): Promise<ActionBatch>;
@@ -1,6 +1,6 @@
1
1
  import { classifyCuaAction } from "../../affordance.js";
2
2
  import { commandFailureInfo, isCommandExitError } from "../../../substrates/command-failure.js";
3
- import { ComputerUseExecutorError, isComputerUseExecutorError } from "../executor-error.js";
3
+ import { ComputerUseExecutorError, isComputerUseExecutorError, } from "../executor-error.js";
4
4
  import { CuaAbortError, CuaDeadlineError, CuaStallError, raceCallBound, raceSessionDeadline, requestScope, } from "./race.js";
5
5
  import { notice } from "./trace.js";
6
6
  // The participant's actions: their public labels, what counts as a material action, and how one
@@ -101,21 +101,39 @@ export function isIdleTurn(actions) {
101
101
  function isMaterialAttempt(action, status) {
102
102
  return !isIdleAction(action) && status !== "skipped" && status !== "not_dispatched";
103
103
  }
104
- function rejectedActionHint(title) {
105
- return `Your action (${title}) was rejected before dispatch. No input from that action or the rest of its batch was sent. Choose your next action from the fresh screenshot; do not assume the rejected action succeeded.`;
104
+ /** What the participant can do about a rejection whose reason the executor named. */
105
+ const REJECTION_REMEDIES = Object.freeze({
106
+ extra_tab: "It was rejected because more than one browser tab is open: typing works only while the study's original tab is the only tab. Switch to the other tab and close it, keep the study's original tab open, then type again.",
107
+ });
108
+ function rejectedActionHint(title, reason) {
109
+ const remedy = reason === undefined ? "" : ` ${REJECTION_REMEDIES[reason]}`;
110
+ return `Your action (${title}) was rejected before dispatch. No input from that action or the rest of its batch was sent.${remedy} Choose your next action from the fresh screenshot; do not assume the rejected action succeeded.`;
111
+ }
112
+ function shortenedWaitHint(wait) {
113
+ return `Your wait of ${wait.requestedMs}ms was shortened to ${wait.ms}ms, the longest one wait action lasts. To keep waiting, send another wait.`;
114
+ }
115
+ /** Record a wait the provider shortened, and return the participant's note about it. */
116
+ function recordShortenedWait(session, title, wait) {
117
+ session.trace.record("notice", () => notice("warn", "wait shortened", `action: ${title}; requested: ${wait.requestedMs}ms; waited: ${wait.ms}ms, the longest one wait action lasts; the session continued`));
118
+ return shortenedWaitHint(wait);
106
119
  }
107
120
  /**
108
121
  * Dispatch one turn's actions in order. A declared pre-dispatch rejection stops the batch and
109
122
  * leaves the desktop usable; a failed desktop command skips its action; any other failure ends
110
123
  * the session.
111
124
  */
112
- export async function runActionBatch(session, actions) {
125
+ export async function runActionBatch(session, actions, shortenedWaits = []) {
113
126
  const execution = { actions: [] };
114
127
  const { actionHistory, trace } = session;
128
+ const notes = [];
129
+ const hint = () => (notes.length === 0 ? undefined : notes.join(" "));
115
130
  for (const [index, action] of actions.entries()) {
116
131
  if (session.signal?.aborted)
117
132
  throw new CuaAbortError();
118
133
  const title = describeCuaAction(action);
134
+ const shortened = shortenedWaits.find((wait) => wait.index === index);
135
+ if (shortened !== undefined)
136
+ notes.push(recordShortenedWait(session, title, shortened));
119
137
  actionHistory.lastActionTitle = title;
120
138
  actionHistory.recentActionTitles.push(title);
121
139
  if (actionHistory.recentActionTitles.length > RECENT_ACTION_TITLES)
@@ -147,8 +165,10 @@ export async function runActionBatch(session, actions) {
147
165
  for (let later = index + 1; later < actions.length; later++) {
148
166
  execution.actions.push({ index: later, status: "not_dispatched" });
149
167
  }
150
- trace.record("notice", () => notice("warn", "action rejected before dispatch", session.redactNarration(`action: ${title}; code: action_rejected; disposition: not_dispatched; remaining batch actions not dispatched: ${actions.length - index - 1}`)));
151
- return { execution, hint: rejectedActionHint(title) };
168
+ const { reason } = error;
169
+ trace.record("notice", () => notice("warn", "action rejected before dispatch", session.redactNarration(`action: ${title}; code: action_rejected; disposition: not_dispatched;${reason === undefined ? "" : ` reason: ${reason};`} remaining batch actions not dispatched: ${actions.length - index - 1}`)));
170
+ notes.push(rejectedActionHint(title, reason));
171
+ return { execution, hint: hint() };
152
172
  }
153
173
  if (isComputerUseExecutorError(error) || !isCommandExitError(error))
154
174
  throw error;
@@ -171,7 +191,7 @@ export async function runActionBatch(session, actions) {
171
191
  : {}),
172
192
  }));
173
193
  }
174
- return { execution, hint: undefined };
194
+ return { execution, hint: hint() };
175
195
  }
176
196
  function countAttempt(session, action, title, status) {
177
197
  if (!isMaterialAttempt(action, status))
@@ -1,3 +1,4 @@
1
+ import { describeExecutorDiagnostic } from "../executor-diagnostic.js";
1
2
  import { isComputerUseExecutorError } from "../executor-error.js";
2
3
  import { isComputerUsePromptRefusedError, isComputerUseProviderError } from "../provider-error.js";
3
4
  import { CuaAbortError, CuaDeadlineError } from "./race.js";
@@ -278,17 +279,24 @@ export function stopForError(session, error) {
278
279
  };
279
280
  }
280
281
  if (isComputerUseExecutorError(error)) {
282
+ // The cause of a failure the driver did not declare, such as a closed browser target.
283
+ const cause = error.diagnostic === undefined
284
+ ? undefined
285
+ : `cause: ${describeExecutorDiagnostic(error.diagnostic)}`;
281
286
  const detail = () => [
282
287
  `phase: ${redact(session.phase)}`,
283
288
  `code: ${error.code}`,
284
289
  `disposition: ${error.disposition}`,
290
+ cause,
285
291
  lastAction(),
286
292
  ]
287
293
  .filter(Boolean)
288
294
  .join("; ");
289
295
  return {
290
296
  completionReason: "harness_error",
291
- reason: `desktop executor error: ${error.code}; disposition: ${error.disposition}`,
297
+ reason: [`desktop executor error: ${error.code}`, `disposition: ${error.disposition}`, cause]
298
+ .filter(Boolean)
299
+ .join("; "),
292
300
  evidence: {
293
301
  kind: "notice",
294
302
  body: () => ({ ...notice("error", "desktop executor error", detail()), ...screenshot }),
@@ -55,6 +55,15 @@ export type CuaAction = {
55
55
  } | {
56
56
  kind: "screenshot";
57
57
  };
58
+ /** A wait action whose duration the provider shortened before the loop dispatched it. */
59
+ export interface ShortenedWait {
60
+ /** The action's index in its turn. */
61
+ index: number;
62
+ /** What the participant asked for. */
63
+ requestedMs: number;
64
+ /** What the action waits: the `ms` it carries. */
65
+ ms: number;
66
+ }
58
67
  /** A captured desktop state: the (optional) frame plus a coarse signature for progress. */
59
68
  export interface CuaObservation {
60
69
  /**
@@ -162,6 +171,11 @@ export interface CuaTurn {
162
171
  message?: string;
163
172
  /** Actions to perform this turn. Empty means the model is done. */
164
173
  actions: CuaAction[];
174
+ /**
175
+ * Waits the provider shortened to the longest wait its desktop accepts, by index into
176
+ * `actions`. The loop records each one and tells the participant on its next request.
177
+ */
178
+ shortenedWaits?: ReadonlyArray<ShortenedWait>;
165
179
  /** Safety checks the provider flagged this turn. Non-empty pauses the run. */
166
180
  pendingSafetyChecks: CuaSafetyCheck[];
167
181
  /** Token accounting for this turn, if available. */
@@ -1,5 +1,5 @@
1
1
  import type { CuaLoopOptions, CuaLoopResult, LoopRunOptions } from "./loop/types.js";
2
- export type { CuaAction, CuaExecutor, CuaLiveMetadata, CuaLoopOptions, CuaLoopResult, CuaObservation, CuaProvider, CuaSafetyCheck, CuaSpendGate, CuaTurn, CuaTurnRequest, } from "./loop/types.js";
2
+ export type { CuaAction, CuaExecutor, CuaLiveMetadata, CuaLoopOptions, CuaLoopResult, CuaObservation, CuaProvider, CuaSafetyCheck, CuaSpendGate, CuaTurn, CuaTurnRequest, ShortenedWait, } from "./loop/types.js";
3
3
  export { describeCuaAction } from "./loop/actions.js";
4
4
  export { stableProgressKey } from "./loop/backstop.js";
5
5
  export { validClosingReport } from "./loop/debrief.js";
@@ -121,7 +121,7 @@ async function runTurns(session, conversation) {
121
121
  await observer.observeFinalTasks(turnNumber);
122
122
  return ended;
123
123
  }
124
- const batch = await runActionBatch(session, turn.actions);
124
+ const batch = await runActionBatch(session, turn.actions, turn.shortenedWaits);
125
125
  conversation.previousExecution = batch.execution;
126
126
  const checkpoint = await observer.checkpoint(turnNumber);
127
127
  if ("stop" in checkpoint)
@@ -1,4 +1,4 @@
1
- /** The estimated input a carried conversation may reach before its oldest turns are summarized. */
1
+ /** The estimated input a carried conversation may reach before it is cut. */
2
2
  export declare const CONTEXT_TOKEN_BUDGET = 64000;
3
3
  /** What the next request would carry, for the trace. */
4
4
  export interface CarriedContextSize {
@@ -14,7 +14,11 @@ export declare class CarriedConversation {
14
14
  /** The latest accepted reply's output items, which the next request answers. */
15
15
  private pending;
16
16
  private readonly notes;
17
- private notesDropped;
17
+ /**
18
+ * The turns whose note lines were dropped to keep the note under its cap. Lines are dropped in
19
+ * turn order from just after the head, so they are one run of consecutive turns.
20
+ */
21
+ private elided;
18
22
  private collapsedTurns;
19
23
  constructor(budget?: number);
20
24
  /**
@@ -27,6 +31,8 @@ export declare class CarriedConversation {
27
31
  /** The carried context's size, for the trace. */
28
32
  size(): CarriedContextSize;
29
33
  private trim;
34
+ private capNote;
35
+ private noteText;
30
36
  private noteItem;
31
37
  }
32
38
  /**