things-api 0.19.2 → 0.19.4

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 (66) hide show
  1. package/README.md +1 -1
  2. package/deputy/prebuilt/Things API Helper.app/Contents/CodeResources +0 -0
  3. package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/MacOS/things-reader +0 -0
  4. package/deputy/prebuilt/Things API Helper.app/Contents/MacOS/things-deputy +0 -0
  5. package/dist/audit/schema.d.ts +14 -0
  6. package/dist/audit/schema.js.map +1 -1
  7. package/dist/cli/commands/op-result.js +13 -1
  8. package/dist/cli/commands/op-result.js.map +1 -1
  9. package/dist/cli/commands/writes.js +51 -7
  10. package/dist/cli/commands/writes.js.map +1 -1
  11. package/dist/contracts.d.ts +1 -1
  12. package/dist/contracts.js +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/mcp/server.js +7 -0
  16. package/dist/mcp/server.js.map +1 -1
  17. package/dist/op-result.d.ts +9 -0
  18. package/dist/op-result.js +5 -0
  19. package/dist/op-result.js.map +1 -1
  20. package/dist/ui-state.d.ts +1 -1
  21. package/dist/ui-state.js +33 -8
  22. package/dist/ui-state.js.map +1 -1
  23. package/dist/write/batch.js +4 -1
  24. package/dist/write/batch.js.map +1 -1
  25. package/dist/write/clone.js +12 -6
  26. package/dist/write/clone.js.map +1 -1
  27. package/dist/write/disclosures.d.ts +263 -0
  28. package/dist/write/disclosures.js +265 -0
  29. package/dist/write/disclosures.js.map +1 -0
  30. package/dist/write/heading.js +4 -7
  31. package/dist/write/heading.js.map +1 -1
  32. package/dist/write/make-repeating-project.js +2 -1
  33. package/dist/write/make-repeating-project.js.map +1 -1
  34. package/dist/write/opid.js +5 -3
  35. package/dist/write/opid.js.map +1 -1
  36. package/dist/write/pipeline.d.ts +39 -1
  37. package/dist/write/pipeline.js +49 -23
  38. package/dist/write/pipeline.js.map +1 -1
  39. package/dist/write/promote-clone.js +46 -36
  40. package/dist/write/promote-clone.js.map +1 -1
  41. package/dist/write/reorder.js +45 -39
  42. package/dist/write/reorder.js.map +1 -1
  43. package/dist/write/resolution-timestamps.js +4 -1
  44. package/dist/write/resolution-timestamps.js.map +1 -1
  45. package/dist/write/spawn-expectation.d.ts +135 -0
  46. package/dist/write/spawn-expectation.js +237 -0
  47. package/dist/write/spawn-expectation.js.map +1 -0
  48. package/dist/write/template-mutation.js +20 -33
  49. package/dist/write/template-mutation.js.map +1 -1
  50. package/dist/write/vectors/types.d.ts +12 -0
  51. package/dist/write/vectors/ui-recipes.js +11 -1
  52. package/dist/write/vectors/ui-recipes.js.map +1 -1
  53. package/dist/write/vectors/ui-state.d.ts +136 -6
  54. package/dist/write/vectors/ui-state.js +372 -70
  55. package/dist/write/vectors/ui-state.js.map +1 -1
  56. package/dist/write/vectors/ui.d.ts +62 -2
  57. package/dist/write/vectors/ui.js +401 -151
  58. package/dist/write/vectors/ui.js.map +1 -1
  59. package/dist/write/verify/delta.d.ts +17 -2
  60. package/dist/write/verify/delta.js +46 -16
  61. package/dist/write/verify/delta.js.map +1 -1
  62. package/dist/write/verify/poller.d.ts +3 -2
  63. package/dist/write/verify/poller.js +3 -1
  64. package/dist/write/verify/poller.js.map +1 -1
  65. package/package.json +1 -1
  66. package/skills/things-cli/SKILL.md +1 -1
@@ -65,6 +65,39 @@ export type UiSheetForm = "none" | "attached" | "detached";
65
65
  * `missing value`), `sheetForm` and `sheetDepth` bound.
66
66
  */
67
67
  export declare const AX_DIALOG_SHELL_SNIPPET = "\t\tset shellRef to missing value\n\t\tset sheetForm to \"none\"\n\t\tset sheetDepth to 0\n\t\ttry\n\t\t\t-- positional-ok: the one attached sheet a window can present, which is\n\t\t\t-- the BOTTOM of any stack; the walk below climbs to the top.\n\t\t\tset shellRef to sheet 1 of (first window whose subrole is \"AXStandardWindow\")\n\t\t\tset sheetForm to \"attached\"\n\t\t\tset sheetDepth to 1\n\t\tend try\n\t\tif shellRef is missing value then\n\t\t\ttry\n\t\t\t\tset dws to (windows whose subrole is \"AXUnknown\" and size is not {40, 40})\n\t\t\t\tif (count of dws) > 0 then\n\t\t\t\t\tset shellRef to item 1 of dws\n\t\t\t\t\tset sheetForm to \"detached\"\n\t\t\t\t\tset sheetDepth to 1\n\t\t\t\tend if\n\t\t\tend try\n\t\tend if\n\t\tif shellRef is not missing value then\n\t\t\trepeat 6 times\n\t\t\t\tset nested to missing value\n\t\t\t\ttry\n\t\t\t\t\tif (exists sheet 1 of shellRef) then set nested to sheet 1 of shellRef\n\t\t\t\tend try\n\t\t\t\tif nested is missing value then exit repeat\n\t\t\t\tset shellRef to nested\n\t\t\t\tset sheetDepth to sheetDepth + 1\n\t\t\tend repeat\n\t\tend if";
68
+ /**
69
+ * The census's individually-budgeted probes, in the order they run. The first
70
+ * three are DECISION-CRITICAL — the guard, the drive preflight and the cleanup
71
+ * ladder all decide on them — and the last two are DECORATION for the refusal
72
+ * copy. That order is the point: everything a caller acts on is proven before a
73
+ * single unaddressed query is attempted.
74
+ *
75
+ * - `running` — is there a Things process at all (a name-keyed lookup);
76
+ * - `frontmost` — `frontmost of process "Things3"`, ADDRESSED: the one fact a
77
+ * keystroke guard actually needs;
78
+ * - `dialog` — the dialog shell, its form, its stack depth and its control
79
+ * census, all addressed inside `process "Things3"`;
80
+ * - `frontapp` — WHICH other application owns the screen. This is the whole
81
+ * process table enumerated (`first application process whose
82
+ * frontmost is true`), so it runs only when the addressed
83
+ * `frontmost` probe already said the answer is not Things, and
84
+ * only for the sentence that names the thief;
85
+ * - `focus` — the focused element's role, through
86
+ * `AXFocusedUIElement`. MEASURED at ~3.5x the cost of every
87
+ * addressed probe in this vector even on a bare clone
88
+ * (docs/lab/fgrd2-census-hardening.md §2), and it decides
89
+ * nothing — so it goes last, inside its own budget, and its
90
+ * absence costs a clause in a sentence.
91
+ */
92
+ export type UiProbe = "running" | "frontmost" | "dialog" | "frontapp" | "focus";
93
+ /**
94
+ * The transport deadline for one census hop — the backstop under the in-script
95
+ * budgets, not the mechanism. Deliberately far below the 15s step timeout that
96
+ * bounded the old single-shot script: nothing here may take this long, and if
97
+ * it does the caller must hear about it while the drive can still be aborted
98
+ * cleanly rather than after four of them have gone by.
99
+ */
100
+ export declare const CENSUS_TIMEOUT_MS = 12000;
68
101
  /** Who owns keyboard focus. Role only — never the element's value or title (see PRIVACY). */
69
102
  export interface UiFocusOwner {
70
103
  /** The frontmost application's process name (e.g. "Things3"). */
@@ -105,9 +138,30 @@ export interface UiState {
105
138
  * of a SECURE SYSTEM MODAL (a macOS privacy/consent dialog), which belongs to
106
139
  * no application's Accessibility tree. Everything else in the census is then
107
140
  * "what could still be proven", never a guess.
141
+ *
142
+ * A probe that TIMED OUT does not set this: "macOS refused to describe the
143
+ * screen" and "the screen did not answer in time" are different facts with
144
+ * different remediations, and #629 is what conflating them costs.
108
145
  */
109
146
  inspectable: boolean;
147
+ /**
148
+ * The probes that did not answer within their budget. Non-empty means the
149
+ * corresponding fields are UNPROVEN, not false — every caller reads them that
150
+ * way, and a critical probe here aborts a drive instead of being retried.
151
+ */
152
+ stalledProbes: UiProbe[];
153
+ /**
154
+ * The probes that answered with an error rather than a value (the surface is
155
+ * there but would not describe itself — a secure modal, a process that exited
156
+ * between two reads). Reported for the same reason: what could not be proven
157
+ * is named, never guessed at.
158
+ */
159
+ failedProbes: UiProbe[];
110
160
  }
161
+ /** Did a probe the caller has to DECIDE on fail to answer? */
162
+ export declare function censusUnverifiable(state: UiState | null): boolean;
163
+ /** Name the probes that did not answer, for a diagnostic someone has to act on. */
164
+ export declare function describeUnprovenProbes(state: UiState): string;
111
165
  /** A recognizable token in the script so a test runner can key off the ui-state command. */
112
166
  export declare const UI_STATE_MARKER = "-- ui-state census (read-only)";
113
167
  /** The label every ui-state dispatch carries (one stable command shape). */
@@ -115,12 +169,78 @@ export declare const UI_STATE_LABEL = "read the window and focus state";
115
169
  /** The Things application's process name — the frontmost value that means "us". */
116
170
  export declare const THINGS_PROCESS = "Things3";
117
171
  /**
118
- * The census script. Every read is wrapped: a surface that will not answer
119
- * degrades that ONE field rather than failing the whole census, because the
120
- * cases this exists for — a foreign modal, an AX-blind session — are exactly
121
- * the cases where half the tree is unreadable.
172
+ * The census script ADDRESSED probes, each on its own Apple-event budget
173
+ * (issue #629).
174
+ *
175
+ * WHAT CHANGED, AND WHY IT HAD TO. The 0.19.2 census was one unbounded script
176
+ * that opened with two UNADDRESSED queries: `first application process whose
177
+ * frontmost is true` (the entire process table enumerated) and `value of
178
+ * attribute "AXFocusedUIElement"` on whatever that returned (a system-wide
179
+ * focused-element resolution). Everything else the ui vector runs — every step
180
+ * the field incident's log shows succeeding, inside the very sheet the census
181
+ * could not describe — is addressed: `tell process "Things3" to …`. So the one
182
+ * script that stalled was the one script that left the addressed style, and it
183
+ * stalled on the critical path of BOTH the per-step guard and the cleanup that
184
+ * was supposed to recover from it. Rebuilt here so that:
185
+ *
186
+ * - the decision-critical facts are ADDRESSED and are proven FIRST;
187
+ * - every read carries `with timeout of ${PROBE_TIMEOUT_S} seconds`, so a
188
+ * surface that will not answer costs seconds, not the caller's whole
189
+ * deadline;
190
+ * - a critical probe that does not answer STOPS the census then and there —
191
+ * the remaining probes cannot change what the caller must now do (abort and
192
+ * clean up), so waiting for them is pure latency;
193
+ * - the two unaddressed queries survive only as DECORATION, last, skipped
194
+ * entirely when the addressed probes already answered the question they
195
+ * were there to answer.
196
+ *
197
+ * Every probe still degrades one field rather than failing the census, and what
198
+ * could not be proven is NAMED (`stalled=` / `failed=`) instead of silently
199
+ * reading as a clean "nothing is open".
122
200
  */
123
201
  export declare function axUiStateScript(): string;
202
+ /** The stderr line prefix carrying a folded guard's census record. */
203
+ export declare const GUARD_LOG_PREFIX = "#FGCENSUS ";
204
+ /** The separator joining that record's fields on its single line. */
205
+ export declare const GUARD_LOG_SEP = " ~|~ ";
206
+ /**
207
+ * What a folded guard RAISES when it refuses. Deliberately a machine tag rather
208
+ * than a sentence: the refusal a caller reads is still built by
209
+ * {@link judgeFocusGuard} in TypeScript, from the census this same hop logged, so
210
+ * there is exactly ONE place the wording lives and the in-script judgement can
211
+ * never drift from it.
212
+ */
213
+ export declare const GUARD_REFUSED_TAG = "#FGREFUSE";
214
+ /**
215
+ * The census as the IN-SCRIPT PRELUDE of the hop it guards (DRVLAT1, issue #633).
216
+ *
217
+ * The per-step focus guard used to be its own osascript round-trip: census hop,
218
+ * then keystroke hop. That is both a hop of latency per typed control AND a
219
+ * TOCTOU window — the screen can change between the census that approved the
220
+ * keystroke and the keystroke itself. Prepending the census to the very script
221
+ * that types closes both: the probes and the input now run in one process, in
222
+ * order, with nothing dispatched in between.
223
+ *
224
+ * The prelude LOGS its census record (stderr, one line) and then judges it
225
+ * IN-SCRIPT, raising {@link GUARD_REFUSED_TAG} — a bare tag — when the input must
226
+ * not be sent. The caller recovers the logged record, parses it into the same
227
+ * {@link UiState} the stand-alone census yields, and asks {@link judgeFocusGuard}
228
+ * for the sentence. So the DECISION is made before the keystroke, in-script, and
229
+ * the WORDING is still single-sourced in TypeScript.
230
+ *
231
+ * `expectedSheet` is the dialog the drive has observed itself driving; pass null
232
+ * where no dialog invariant applies.
233
+ */
234
+ export declare function axFocusGuardPrelude(expectedSheet: UiSheetKind | null): string;
235
+ /**
236
+ * Recover a folded guard's census from the hop's stderr: the parsed state (null
237
+ * when no record was logged) and the stderr with that line removed, so a failure
238
+ * message a caller reads never carries the machinery.
239
+ */
240
+ export declare function parseGuardLog(stderr: string): {
241
+ state: UiState | null;
242
+ stderr: string;
243
+ };
124
244
  /**
125
245
  * Parse the census record into a {@link UiState}. Returns null when the output
126
246
  * carries none of the expected keys (a transport error, or a script that never
@@ -131,8 +251,11 @@ export declare function parseUiState(stdout: string): UiState | null;
131
251
  * Read the census through the injected runner. Returns null on a transport
132
252
  * failure — an UNKNOWN state, which every caller treats fail-closed (the guard
133
253
  * refuses; the cleanup path does not send a blind Escape).
254
+ *
255
+ * `timeoutMs` defaults to {@link CENSUS_TIMEOUT_MS}: a census is not a drive
256
+ * step and must not borrow a drive step's patience (issue #629).
134
257
  */
135
- export declare function readUiState(run: (command: UiCommand, timeoutMs: number) => Promise<UiRunResult>, timeoutMs: number): Promise<UiState | null>;
258
+ export declare function readUiState(run: (command: UiCommand, timeoutMs: number) => Promise<UiRunResult>, timeoutMs?: number): Promise<UiState | null>;
136
259
  /**
137
260
  * How to name the surface that owns the screen, for a refusal someone has to
138
261
  * act on. Names the APPLICATION and the focused element's ROLE — never its
@@ -141,7 +264,14 @@ export declare function readUiState(run: (command: UiCommand, timeoutMs: number)
141
264
  * at the keyboard has to look at the screen themselves.
142
265
  */
143
266
  export declare function describeFocusOwner(state: UiState | null): string;
144
- /** A one-line human summary of the census, for a diagnostic line or a warning. */
267
+ /**
268
+ * A one-line human summary of the census, for a diagnostic line or a warning.
269
+ *
270
+ * Reports WHAT EACH PROBE PROVED. A stalled probe leaves its clause reading
271
+ * "could not be determined" and is named at the end — never omitted, and never
272
+ * rendered as its default (issue #629: a census that could not see the open
273
+ * Repeat sheet used to report a clean screen).
274
+ */
145
275
  export declare function describeUiState(state: UiState): string;
146
276
  /**
147
277
  * The warning an OPEN Things dialog earns, wherever it is reported.