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.
- package/README.md +1 -1
- package/deputy/prebuilt/Things API Helper.app/Contents/CodeResources +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/MacOS/things-reader +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/MacOS/things-deputy +0 -0
- package/dist/audit/schema.d.ts +14 -0
- package/dist/audit/schema.js.map +1 -1
- package/dist/cli/commands/op-result.js +13 -1
- package/dist/cli/commands/op-result.js.map +1 -1
- package/dist/cli/commands/writes.js +51 -7
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/contracts.d.ts +1 -1
- package/dist/contracts.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +7 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/op-result.d.ts +9 -0
- package/dist/op-result.js +5 -0
- package/dist/op-result.js.map +1 -1
- package/dist/ui-state.d.ts +1 -1
- package/dist/ui-state.js +33 -8
- package/dist/ui-state.js.map +1 -1
- package/dist/write/batch.js +4 -1
- package/dist/write/batch.js.map +1 -1
- package/dist/write/clone.js +12 -6
- package/dist/write/clone.js.map +1 -1
- package/dist/write/disclosures.d.ts +263 -0
- package/dist/write/disclosures.js +265 -0
- package/dist/write/disclosures.js.map +1 -0
- package/dist/write/heading.js +4 -7
- package/dist/write/heading.js.map +1 -1
- package/dist/write/make-repeating-project.js +2 -1
- package/dist/write/make-repeating-project.js.map +1 -1
- package/dist/write/opid.js +5 -3
- package/dist/write/opid.js.map +1 -1
- package/dist/write/pipeline.d.ts +39 -1
- package/dist/write/pipeline.js +49 -23
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/promote-clone.js +46 -36
- package/dist/write/promote-clone.js.map +1 -1
- package/dist/write/reorder.js +45 -39
- package/dist/write/reorder.js.map +1 -1
- package/dist/write/resolution-timestamps.js +4 -1
- package/dist/write/resolution-timestamps.js.map +1 -1
- package/dist/write/spawn-expectation.d.ts +135 -0
- package/dist/write/spawn-expectation.js +237 -0
- package/dist/write/spawn-expectation.js.map +1 -0
- package/dist/write/template-mutation.js +20 -33
- package/dist/write/template-mutation.js.map +1 -1
- package/dist/write/vectors/types.d.ts +12 -0
- package/dist/write/vectors/ui-recipes.js +11 -1
- package/dist/write/vectors/ui-recipes.js.map +1 -1
- package/dist/write/vectors/ui-state.d.ts +136 -6
- package/dist/write/vectors/ui-state.js +372 -70
- package/dist/write/vectors/ui-state.js.map +1 -1
- package/dist/write/vectors/ui.d.ts +62 -2
- package/dist/write/vectors/ui.js +401 -151
- package/dist/write/vectors/ui.js.map +1 -1
- package/dist/write/verify/delta.d.ts +17 -2
- package/dist/write/verify/delta.js +46 -16
- package/dist/write/verify/delta.js.map +1 -1
- package/dist/write/verify/poller.d.ts +3 -2
- package/dist/write/verify/poller.js +3 -1
- package/dist/write/verify/poller.js.map +1 -1
- package/package.json +1 -1
- package/skills/things-cli/SKILL.md +1 -1
|
@@ -47,6 +47,65 @@ export const AX_DIALOG_SHELL_SNIPPET = ` set shellRef to missing value
|
|
|
47
47
|
set sheetDepth to sheetDepth + 1
|
|
48
48
|
end repeat
|
|
49
49
|
end if`;
|
|
50
|
+
/** Probes whose absence makes the census unusable for a keystroke decision. */
|
|
51
|
+
const CRITICAL_PROBES = ["running", "frontmost", "dialog"];
|
|
52
|
+
/** Every probe name the census can report, for parsing its record back. */
|
|
53
|
+
const KNOWN_PROBES = new Set([
|
|
54
|
+
"running",
|
|
55
|
+
"frontmost",
|
|
56
|
+
"dialog",
|
|
57
|
+
"frontapp",
|
|
58
|
+
"focus",
|
|
59
|
+
]);
|
|
60
|
+
/**
|
|
61
|
+
* Per-APPLE-EVENT budget for every read in the census (`with timeout of N
|
|
62
|
+
* seconds`). Without one, osascript's default is two MINUTES: a System Events
|
|
63
|
+
* call that does not come back is then bounded only by the caller's own process
|
|
64
|
+
* deadline, which is how issue #629's field incident spent ~15s per inspection
|
|
65
|
+
* and ~56s per drive discovering nothing. With one, a read that will not answer
|
|
66
|
+
* raises AppleScript error -1712 and the census carries on to the next probe —
|
|
67
|
+
* or stops, if the probe was one the caller decides on.
|
|
68
|
+
*/
|
|
69
|
+
const PROBE_TIMEOUT_S = 2;
|
|
70
|
+
/**
|
|
71
|
+
* Wall-clock budget for the WHOLE census, checked between probes. `with
|
|
72
|
+
* timeout` bounds one Apple event, not a run of them, so a surface that is
|
|
73
|
+
* merely slow (rather than wedged) could still add up. Past this, the remaining
|
|
74
|
+
* probes are skipped and reported as stalled rather than waited on.
|
|
75
|
+
*/
|
|
76
|
+
const CENSUS_BUDGET_S = 8;
|
|
77
|
+
/**
|
|
78
|
+
* The transport deadline for one census hop — the backstop under the in-script
|
|
79
|
+
* budgets, not the mechanism. Deliberately far below the 15s step timeout that
|
|
80
|
+
* bounded the old single-shot script: nothing here may take this long, and if
|
|
81
|
+
* it does the caller must hear about it while the drive can still be aborted
|
|
82
|
+
* cleanly rather than after four of them have gone by.
|
|
83
|
+
*/
|
|
84
|
+
export const CENSUS_TIMEOUT_MS = 12_000;
|
|
85
|
+
/** Did a probe the caller has to DECIDE on fail to answer? */
|
|
86
|
+
export function censusUnverifiable(state) {
|
|
87
|
+
if (state === null)
|
|
88
|
+
return true;
|
|
89
|
+
return CRITICAL_PROBES.some((p) => state.stalledProbes.includes(p) || state.failedProbes.includes(p));
|
|
90
|
+
}
|
|
91
|
+
/** Name the probes that did not answer, for a diagnostic someone has to act on. */
|
|
92
|
+
export function describeUnprovenProbes(state) {
|
|
93
|
+
const label = {
|
|
94
|
+
running: "whether Things is running",
|
|
95
|
+
frontmost: "whether Things owns the screen",
|
|
96
|
+
dialog: "which dialog is open",
|
|
97
|
+
frontapp: "which application owns the screen",
|
|
98
|
+
focus: "which element has keyboard focus",
|
|
99
|
+
};
|
|
100
|
+
const parts = [];
|
|
101
|
+
if (state.stalledProbes.length > 0) {
|
|
102
|
+
parts.push(`did not answer in time: ${state.stalledProbes.map((p) => label[p]).join(", ")}`);
|
|
103
|
+
}
|
|
104
|
+
if (state.failedProbes.length > 0) {
|
|
105
|
+
parts.push(`could not be read: ${state.failedProbes.map((p) => label[p]).join(", ")}`);
|
|
106
|
+
}
|
|
107
|
+
return parts.join("; ");
|
|
108
|
+
}
|
|
50
109
|
/** A recognizable token in the script so a test runner can key off the ui-state command. */
|
|
51
110
|
export const UI_STATE_MARKER = "-- ui-state census (read-only)";
|
|
52
111
|
/** The label every ui-state dispatch carries (one stable command shape). */
|
|
@@ -54,14 +113,51 @@ export const UI_STATE_LABEL = "read the window and focus state";
|
|
|
54
113
|
/** The Things application's process name — the frontmost value that means "us". */
|
|
55
114
|
export const THINGS_PROCESS = "Things3";
|
|
56
115
|
/**
|
|
57
|
-
* The census script
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
116
|
+
* The census script — ADDRESSED probes, each on its own Apple-event budget
|
|
117
|
+
* (issue #629).
|
|
118
|
+
*
|
|
119
|
+
* WHAT CHANGED, AND WHY IT HAD TO. The 0.19.2 census was one unbounded script
|
|
120
|
+
* that opened with two UNADDRESSED queries: `first application process whose
|
|
121
|
+
* frontmost is true` (the entire process table enumerated) and `value of
|
|
122
|
+
* attribute "AXFocusedUIElement"` on whatever that returned (a system-wide
|
|
123
|
+
* focused-element resolution). Everything else the ui vector runs — every step
|
|
124
|
+
* the field incident's log shows succeeding, inside the very sheet the census
|
|
125
|
+
* could not describe — is addressed: `tell process "Things3" to …`. So the one
|
|
126
|
+
* script that stalled was the one script that left the addressed style, and it
|
|
127
|
+
* stalled on the critical path of BOTH the per-step guard and the cleanup that
|
|
128
|
+
* was supposed to recover from it. Rebuilt here so that:
|
|
129
|
+
*
|
|
130
|
+
* - the decision-critical facts are ADDRESSED and are proven FIRST;
|
|
131
|
+
* - every read carries `with timeout of ${PROBE_TIMEOUT_S} seconds`, so a
|
|
132
|
+
* surface that will not answer costs seconds, not the caller's whole
|
|
133
|
+
* deadline;
|
|
134
|
+
* - a critical probe that does not answer STOPS the census then and there —
|
|
135
|
+
* the remaining probes cannot change what the caller must now do (abort and
|
|
136
|
+
* clean up), so waiting for them is pure latency;
|
|
137
|
+
* - the two unaddressed queries survive only as DECORATION, last, skipped
|
|
138
|
+
* entirely when the addressed probes already answered the question they
|
|
139
|
+
* were there to answer.
|
|
140
|
+
*
|
|
141
|
+
* Every probe still degrades one field rather than failing the census, and what
|
|
142
|
+
* could not be proven is NAMED (`stalled=` / `failed=`) instead of silently
|
|
143
|
+
* reading as a clean "nothing is open".
|
|
61
144
|
*/
|
|
62
145
|
export function axUiStateScript() {
|
|
63
146
|
return `${UI_STATE_MARKER}
|
|
64
|
-
|
|
147
|
+
${CENSUS_BODY}
|
|
148
|
+
|
|
149
|
+
return ${censusRecord("linefeed")}`;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The census BODY — every probe, leaving its verdict in the AppleScript variables
|
|
153
|
+
* the record below reads. Split out of {@link axUiStateScript} (DRVLAT1, issue
|
|
154
|
+
* #633) so the SAME probes can run as the in-script prelude of a keystroke hop
|
|
155
|
+
* ({@link axFocusGuardPrelude}) rather than as a separate osascript round-trip.
|
|
156
|
+
* Nothing about the probes, their budgets or their order differs between the two
|
|
157
|
+
* uses: it is one body, compiled into two scripts.
|
|
158
|
+
*/
|
|
159
|
+
const CENSUS_BODY = `set frontName to ""
|
|
160
|
+
set frontIsThings to false
|
|
65
161
|
set focusRole to ""
|
|
66
162
|
set focusSub to ""
|
|
67
163
|
set canInspect to true
|
|
@@ -70,70 +166,234 @@ set sheetForm to "none"
|
|
|
70
166
|
set sheetKind to "none"
|
|
71
167
|
set sheetDepth to 0
|
|
72
168
|
set census to ""
|
|
73
|
-
|
|
169
|
+
set stalled to ""
|
|
170
|
+
set failed to ""
|
|
171
|
+
set halted to false
|
|
172
|
+
set t0 to (current date)
|
|
173
|
+
|
|
174
|
+
-- P1 running: a name-keyed lookup, no Accessibility round-trip.
|
|
175
|
+
try
|
|
176
|
+
with timeout of ${PROBE_TIMEOUT_S} seconds
|
|
177
|
+
tell application "System Events" to set thingsRunning to (exists application process "${THINGS_PROCESS}")
|
|
178
|
+
end timeout
|
|
179
|
+
on error errMsg number errNum
|
|
180
|
+
if errNum is -1712 then
|
|
181
|
+
set stalled to stalled & "running "
|
|
182
|
+
else
|
|
183
|
+
set failed to failed & "running "
|
|
184
|
+
end if
|
|
185
|
+
set halted to true
|
|
186
|
+
end try
|
|
187
|
+
|
|
188
|
+
-- P2 frontmost, ADDRESSED: the single fact the per-step input guard decides on.
|
|
189
|
+
if (not halted) and thingsRunning then
|
|
74
190
|
try
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
on error
|
|
87
|
-
set canInspect to false
|
|
191
|
+
with timeout of ${PROBE_TIMEOUT_S} seconds
|
|
192
|
+
tell application "System Events" to tell process "${THINGS_PROCESS}" to set frontIsThings to (frontmost as boolean)
|
|
193
|
+
end timeout
|
|
194
|
+
if frontIsThings then set frontName to "${THINGS_PROCESS}"
|
|
195
|
+
on error errMsg number errNum
|
|
196
|
+
if errNum is -1712 then
|
|
197
|
+
set stalled to stalled & "frontmost "
|
|
198
|
+
else
|
|
199
|
+
set failed to failed & "frontmost "
|
|
200
|
+
end if
|
|
201
|
+
set halted to true
|
|
88
202
|
end try
|
|
203
|
+
end if
|
|
204
|
+
|
|
205
|
+
-- P3 dialog: shell, form, stack depth and control census — all addressed
|
|
206
|
+
-- inside the Things process, which is the shape every working drive step uses.
|
|
207
|
+
if (not halted) and thingsRunning then
|
|
89
208
|
try
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
end tell
|
|
93
|
-
if thingsRunning then
|
|
94
|
-
tell application "System Events" to tell process "${THINGS_PROCESS}"
|
|
209
|
+
with timeout of ${PROBE_TIMEOUT_S} seconds
|
|
210
|
+
tell application "System Events" to tell process "${THINGS_PROCESS}"
|
|
95
211
|
${AX_DIALOG_SHELL_SNIPPET}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
212
|
+
if shellRef is not missing value then
|
|
213
|
+
set nCb to -1
|
|
214
|
+
set nPu to -1
|
|
215
|
+
set nBt to -1
|
|
216
|
+
set nGp to -1
|
|
217
|
+
set nTf to -1
|
|
218
|
+
try
|
|
219
|
+
set nCb to (count of checkboxes of shellRef)
|
|
220
|
+
end try
|
|
221
|
+
try
|
|
222
|
+
set nPu to (count of pop up buttons of shellRef)
|
|
223
|
+
end try
|
|
224
|
+
try
|
|
225
|
+
set nBt to (count of buttons of shellRef)
|
|
226
|
+
end try
|
|
227
|
+
try
|
|
228
|
+
set nGp to (count of groups of shellRef)
|
|
229
|
+
end try
|
|
230
|
+
try
|
|
231
|
+
set nTf to (count of text fields of shellRef)
|
|
232
|
+
end try
|
|
233
|
+
set census to "cb:" & nCb & " pu:" & nPu & " bt:" & nBt & " gp:" & nGp & " tf:" & nTf
|
|
234
|
+
set winId to ""
|
|
235
|
+
try
|
|
236
|
+
set winId to (value of attribute "AXIdentifier" of shellRef) as text
|
|
237
|
+
end try
|
|
238
|
+
if winId starts with "MovePopUpDialog-" then
|
|
239
|
+
set sheetKind to "move-picker"
|
|
240
|
+
else if nCb is 2 and nPu is 1 and nBt is 2 and nGp is 1 and nTf is 0 then
|
|
241
|
+
set groupOk to false
|
|
242
|
+
try
|
|
243
|
+
set g to group 1 of shellRef
|
|
244
|
+
if ((count of text fields of g) + (count of pop up buttons of g)) > 0 then set groupOk to true
|
|
245
|
+
end try
|
|
246
|
+
if groupOk then set sheetKind to "repeat"
|
|
247
|
+
end if
|
|
248
|
+
if sheetKind is "none" then set sheetKind to "other"
|
|
249
|
+
end if
|
|
250
|
+
end tell
|
|
251
|
+
end timeout
|
|
252
|
+
on error errMsg number errNum
|
|
253
|
+
if errNum is -1712 then
|
|
254
|
+
set stalled to stalled & "dialog "
|
|
255
|
+
else
|
|
256
|
+
set failed to failed & "dialog "
|
|
257
|
+
end if
|
|
258
|
+
set halted to true
|
|
259
|
+
end try
|
|
260
|
+
end if
|
|
261
|
+
|
|
262
|
+
-- P4 frontapp: the ONLY enumeration left, and it runs only when the addressed
|
|
263
|
+
-- probe has already said the screen is not ours — its whole job is naming the
|
|
264
|
+
-- application in the refusal sentence.
|
|
265
|
+
if (not halted) and (not frontIsThings) then
|
|
266
|
+
if ((current date) - t0) > ${CENSUS_BUDGET_S} then
|
|
267
|
+
set stalled to stalled & "frontapp "
|
|
268
|
+
else
|
|
269
|
+
try
|
|
270
|
+
with timeout of ${PROBE_TIMEOUT_S} seconds
|
|
271
|
+
tell application "System Events" to set frontName to (name of first application process whose frontmost is true) as text
|
|
272
|
+
end timeout
|
|
273
|
+
on error errMsg number errNum
|
|
274
|
+
if errNum is -1712 then
|
|
275
|
+
set stalled to stalled & "frontapp "
|
|
276
|
+
else
|
|
277
|
+
set failed to failed & "frontapp "
|
|
278
|
+
end if
|
|
279
|
+
end try
|
|
280
|
+
end if
|
|
281
|
+
end if
|
|
282
|
+
|
|
283
|
+
-- P5 focus: decoration, and the most expensive read in the census. Addressed at
|
|
284
|
+
-- the process that owns the screen rather than resolved system-wide, run last,
|
|
285
|
+
-- and skipped outright once the budget is spent. An ERROR here (as opposed to a
|
|
286
|
+
-- timeout) is the secure-system-modal signature: macOS exposes no tree for one.
|
|
287
|
+
if not halted then
|
|
288
|
+
if ((current date) - t0) > ${CENSUS_BUDGET_S} then
|
|
289
|
+
set stalled to stalled & "focus "
|
|
290
|
+
else
|
|
291
|
+
set focusTarget to frontName
|
|
292
|
+
if focusTarget is "" then
|
|
293
|
+
set stalled to stalled & "focus "
|
|
294
|
+
else
|
|
119
295
|
try
|
|
120
|
-
|
|
296
|
+
with timeout of ${PROBE_TIMEOUT_S} seconds
|
|
297
|
+
tell application "System Events" to tell process focusTarget
|
|
298
|
+
set fe to value of attribute "AXFocusedUIElement"
|
|
299
|
+
-- No focused element is a perfectly ordinary state (an app
|
|
300
|
+
-- with no key window); it is not an unreadable screen.
|
|
301
|
+
if fe is not missing value then
|
|
302
|
+
set focusRole to (role of fe) as text
|
|
303
|
+
try
|
|
304
|
+
set focusSub to (subrole of fe) as text
|
|
305
|
+
end try
|
|
306
|
+
end if
|
|
307
|
+
end tell
|
|
308
|
+
end timeout
|
|
309
|
+
on error errMsg number errNum
|
|
310
|
+
if errNum is -1712 then
|
|
311
|
+
set stalled to stalled & "focus "
|
|
312
|
+
else
|
|
313
|
+
set canInspect to false
|
|
314
|
+
set failed to failed & "focus "
|
|
315
|
+
end if
|
|
121
316
|
end try
|
|
122
|
-
if winId starts with "MovePopUpDialog-" then
|
|
123
|
-
set sheetKind to "move-picker"
|
|
124
|
-
else if nCb is 2 and nPu is 1 and nBt is 2 and nGp is 1 and nTf is 0 then
|
|
125
|
-
set groupOk to false
|
|
126
|
-
try
|
|
127
|
-
set g to group 1 of shellRef
|
|
128
|
-
if ((count of text fields of g) + (count of pop up buttons of g)) > 0 then set groupOk to true
|
|
129
|
-
end try
|
|
130
|
-
if groupOk then set sheetKind to "repeat"
|
|
131
|
-
end if
|
|
132
|
-
if sheetKind is "none" then set sheetKind to "other"
|
|
133
317
|
end if
|
|
134
|
-
end
|
|
135
|
-
end if
|
|
136
|
-
|
|
318
|
+
end if
|
|
319
|
+
end if`;
|
|
320
|
+
/**
|
|
321
|
+
* The census RECORD, joined by `sep` — `linefeed` for the stand-alone census
|
|
322
|
+
* script, a single-line separator for the guard prelude's `log` line (a logged
|
|
323
|
+
* record has to survive as ONE stderr line to be recovered).
|
|
324
|
+
*/
|
|
325
|
+
function censusRecord(sep) {
|
|
326
|
+
return `"front=" & frontName & ${sep} & "isfront=" & frontIsThings & ${sep} & "running=" & thingsRunning & ${sep} & "form=" & sheetForm & ${sep} & "depth=" & sheetDepth & ${sep} & "kind=" & sheetKind & ${sep} & "census=" & census & ${sep} & "role=" & focusRole & ${sep} & "subrole=" & focusSub & ${sep} & "inspectable=" & canInspect & ${sep} & "stalled=" & stalled & ${sep} & "failed=" & failed`;
|
|
327
|
+
}
|
|
328
|
+
/** The stderr line prefix carrying a folded guard's census record. */
|
|
329
|
+
export const GUARD_LOG_PREFIX = "#FGCENSUS ";
|
|
330
|
+
/** The separator joining that record's fields on its single line. */
|
|
331
|
+
export const GUARD_LOG_SEP = " ~|~ ";
|
|
332
|
+
/**
|
|
333
|
+
* What a folded guard RAISES when it refuses. Deliberately a machine tag rather
|
|
334
|
+
* than a sentence: the refusal a caller reads is still built by
|
|
335
|
+
* {@link judgeFocusGuard} in TypeScript, from the census this same hop logged, so
|
|
336
|
+
* there is exactly ONE place the wording lives and the in-script judgement can
|
|
337
|
+
* never drift from it.
|
|
338
|
+
*/
|
|
339
|
+
export const GUARD_REFUSED_TAG = "#FGREFUSE";
|
|
340
|
+
/**
|
|
341
|
+
* The census as the IN-SCRIPT PRELUDE of the hop it guards (DRVLAT1, issue #633).
|
|
342
|
+
*
|
|
343
|
+
* The per-step focus guard used to be its own osascript round-trip: census hop,
|
|
344
|
+
* then keystroke hop. That is both a hop of latency per typed control AND a
|
|
345
|
+
* TOCTOU window — the screen can change between the census that approved the
|
|
346
|
+
* keystroke and the keystroke itself. Prepending the census to the very script
|
|
347
|
+
* that types closes both: the probes and the input now run in one process, in
|
|
348
|
+
* order, with nothing dispatched in between.
|
|
349
|
+
*
|
|
350
|
+
* The prelude LOGS its census record (stderr, one line) and then judges it
|
|
351
|
+
* IN-SCRIPT, raising {@link GUARD_REFUSED_TAG} — a bare tag — when the input must
|
|
352
|
+
* not be sent. The caller recovers the logged record, parses it into the same
|
|
353
|
+
* {@link UiState} the stand-alone census yields, and asks {@link judgeFocusGuard}
|
|
354
|
+
* for the sentence. So the DECISION is made before the keystroke, in-script, and
|
|
355
|
+
* the WORDING is still single-sourced in TypeScript.
|
|
356
|
+
*
|
|
357
|
+
* `expectedSheet` is the dialog the drive has observed itself driving; pass null
|
|
358
|
+
* where no dialog invariant applies.
|
|
359
|
+
*/
|
|
360
|
+
export function axFocusGuardPrelude(expectedSheet) {
|
|
361
|
+
const critical = CRITICAL_PROBES.map((p) => ` if stalled contains "${p} " then set fgBad to true
|
|
362
|
+
if failed contains "${p} " then set fgBad to true`).join("\n");
|
|
363
|
+
const sheetClause = expectedSheet === null
|
|
364
|
+
? ""
|
|
365
|
+
: `\n if sheetKind is not "${expectedSheet}" then set fgBad to true`;
|
|
366
|
+
return `${UI_STATE_MARKER}
|
|
367
|
+
${CENSUS_BODY}
|
|
368
|
+
|
|
369
|
+
log "${GUARD_LOG_PREFIX}" & ${censusRecord(`"${GUARD_LOG_SEP}"`)}
|
|
370
|
+
set fgBad to false
|
|
371
|
+
${critical}
|
|
372
|
+
if not canInspect then set fgBad to true
|
|
373
|
+
if not frontIsThings then set fgBad to true${sheetClause}
|
|
374
|
+
if fgBad then error "${GUARD_REFUSED_TAG}"
|
|
375
|
+
`;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Recover a folded guard's census from the hop's stderr: the parsed state (null
|
|
379
|
+
* when no record was logged) and the stderr with that line removed, so a failure
|
|
380
|
+
* message a caller reads never carries the machinery.
|
|
381
|
+
*/
|
|
382
|
+
export function parseGuardLog(stderr) {
|
|
383
|
+
const kept = [];
|
|
384
|
+
let record = null;
|
|
385
|
+
for (const line of stderr.split(/\r?\n/)) {
|
|
386
|
+
const at = line.indexOf(GUARD_LOG_PREFIX);
|
|
387
|
+
if (at >= 0) {
|
|
388
|
+
record = line.slice(at + GUARD_LOG_PREFIX.length);
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
kept.push(line);
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
state: record === null ? null : parseUiState(record.split(GUARD_LOG_SEP).join("\n")),
|
|
395
|
+
stderr: kept.join("\n").trim(),
|
|
396
|
+
};
|
|
137
397
|
}
|
|
138
398
|
/**
|
|
139
399
|
* Parse the census record into a {@link UiState}. Returns null when the output
|
|
@@ -150,6 +410,7 @@ export function parseUiState(stdout) {
|
|
|
150
410
|
}
|
|
151
411
|
if (!fields.has("kind") || !fields.has("inspectable"))
|
|
152
412
|
return null;
|
|
413
|
+
const probes = (key) => (fields.get(key) ?? "").split(/\s+/).filter((p) => KNOWN_PROBES.has(p));
|
|
153
414
|
const kindRaw = fields.get("kind") ?? "none";
|
|
154
415
|
const sheetKind = kindRaw === "repeat" || kindRaw === "move-picker" || kindRaw === "other" ? kindRaw : "none";
|
|
155
416
|
const formRaw = fields.get("form") ?? "none";
|
|
@@ -157,12 +418,17 @@ export function parseUiState(stdout) {
|
|
|
157
418
|
const frontRaw = fields.get("front") ?? "";
|
|
158
419
|
const frontmostApp = frontRaw === "" ? null : frontRaw;
|
|
159
420
|
const role = fields.get("role") ?? "";
|
|
160
|
-
|
|
421
|
+
// AppleScript coerces an absent attribute to the literal words "missing
|
|
422
|
+
// value"; that is "no subrole", not a subrole named that.
|
|
423
|
+
const subroleRaw = fields.get("subrole") ?? "";
|
|
424
|
+
const subrole = subroleRaw === "missing value" ? "" : subroleRaw;
|
|
161
425
|
const census = fields.get("census") ?? "";
|
|
162
426
|
const depth = Number(fields.get("depth") ?? "0");
|
|
163
427
|
return {
|
|
164
428
|
thingsRunning: fields.get("running") === "true",
|
|
165
|
-
|
|
429
|
+
// Decided by the ADDRESSED probe, not by comparing a name the enumeration
|
|
430
|
+
// may never have been asked for (issue #629).
|
|
431
|
+
thingsFrontmost: fields.get("isfront") === "true",
|
|
166
432
|
frontmostApp,
|
|
167
433
|
sheetOpen: sheetKind !== "none",
|
|
168
434
|
sheetKind,
|
|
@@ -171,14 +437,19 @@ export function parseUiState(stdout) {
|
|
|
171
437
|
sheetControls: census === "" ? null : census,
|
|
172
438
|
focusOwner: frontmostApp === null ? null : { app: frontmostApp, role, subrole: subrole || null },
|
|
173
439
|
inspectable: fields.get("inspectable") === "true",
|
|
440
|
+
stalledProbes: probes("stalled"),
|
|
441
|
+
failedProbes: probes("failed"),
|
|
174
442
|
};
|
|
175
443
|
}
|
|
176
444
|
/**
|
|
177
445
|
* Read the census through the injected runner. Returns null on a transport
|
|
178
446
|
* failure — an UNKNOWN state, which every caller treats fail-closed (the guard
|
|
179
447
|
* refuses; the cleanup path does not send a blind Escape).
|
|
448
|
+
*
|
|
449
|
+
* `timeoutMs` defaults to {@link CENSUS_TIMEOUT_MS}: a census is not a drive
|
|
450
|
+
* step and must not borrow a drive step's patience (issue #629).
|
|
180
451
|
*/
|
|
181
|
-
export async function readUiState(run, timeoutMs) {
|
|
452
|
+
export async function readUiState(run, timeoutMs = CENSUS_TIMEOUT_MS) {
|
|
182
453
|
const res = await run({ primitive: "resolve", label: UI_STATE_LABEL, script: axUiStateScript() }, timeoutMs);
|
|
183
454
|
if (!res.ok)
|
|
184
455
|
return null;
|
|
@@ -194,6 +465,12 @@ export async function readUiState(run, timeoutMs) {
|
|
|
194
465
|
export function describeFocusOwner(state) {
|
|
195
466
|
if (state === null)
|
|
196
467
|
return "the window state could not be read";
|
|
468
|
+
// #629: an inspection that did not come back says nothing about who owns the
|
|
469
|
+
// screen — and saying "an unidentified application is frontmost" would read
|
|
470
|
+
// as a measurement. Name the probe that stalled instead.
|
|
471
|
+
if (censusUnverifiable(state)) {
|
|
472
|
+
return `the window state inspection did not complete (${describeUnprovenProbes(state)})`;
|
|
473
|
+
}
|
|
197
474
|
if (!state.inspectable) {
|
|
198
475
|
return ("a system dialog owns the screen — macOS does not expose it to other apps, so it cannot be " +
|
|
199
476
|
`identified from here${state.frontmostApp === null ? "" : ` (frontmost application: ${state.frontmostApp})`}`);
|
|
@@ -204,11 +481,36 @@ export function describeFocusOwner(state) {
|
|
|
204
481
|
? `${app} is frontmost`
|
|
205
482
|
: `${app} is frontmost and keyboard focus is on a ${role}`;
|
|
206
483
|
}
|
|
207
|
-
/**
|
|
484
|
+
/**
|
|
485
|
+
* A one-line human summary of the census, for a diagnostic line or a warning.
|
|
486
|
+
*
|
|
487
|
+
* Reports WHAT EACH PROBE PROVED. A stalled probe leaves its clause reading
|
|
488
|
+
* "could not be determined" and is named at the end — never omitted, and never
|
|
489
|
+
* rendered as its default (issue #629: a census that could not see the open
|
|
490
|
+
* Repeat sheet used to report a clean screen).
|
|
491
|
+
*/
|
|
208
492
|
export function describeUiState(state) {
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
493
|
+
const unproven = (p) => state.stalledProbes.includes(p) || state.failedProbes.includes(p);
|
|
494
|
+
const trailer = () => {
|
|
495
|
+
const unprovenText = describeUnprovenProbes(state);
|
|
496
|
+
return unprovenText === "" ? "" : ` — ${unprovenText}`;
|
|
497
|
+
};
|
|
498
|
+
if (unproven("running")) {
|
|
499
|
+
return `nothing about the screen could be established${trailer()}`;
|
|
500
|
+
}
|
|
501
|
+
if (!state.thingsRunning && !unproven("frontmost")) {
|
|
502
|
+
return `Things is not running${trailer()}`;
|
|
503
|
+
}
|
|
504
|
+
const front = unproven("frontmost")
|
|
505
|
+
? "whether Things owns the screen could not be determined"
|
|
506
|
+
: state.thingsFrontmost
|
|
507
|
+
? "Things is frontmost"
|
|
508
|
+
: unproven("frontapp")
|
|
509
|
+
? "another application is frontmost (it could not be named)"
|
|
510
|
+
: `${state.frontmostApp ?? "an unidentified application"} is frontmost`;
|
|
511
|
+
if (unproven("dialog")) {
|
|
512
|
+
return `${front}; whether a dialog is open in Things could not be determined${trailer()}`;
|
|
513
|
+
}
|
|
212
514
|
const sheet = state.sheetKind === "none"
|
|
213
515
|
? "no dialog is open in Things"
|
|
214
516
|
: state.sheetKind === "repeat"
|
|
@@ -219,7 +521,7 @@ export function describeUiState(state) {
|
|
|
219
521
|
// A stack means the thing in front is sitting ON another dialog, and each one
|
|
220
522
|
// has to be dismissed in turn (MODALX1 §6).
|
|
221
523
|
const stacked = state.sheetDepth > 1 ? `, on top of ${state.sheetDepth - 1} more` : "";
|
|
222
|
-
return `${front}; ${sheet}${stacked}`;
|
|
524
|
+
return `${front}; ${sheet}${stacked}${trailer()}`;
|
|
223
525
|
}
|
|
224
526
|
/**
|
|
225
527
|
* The warning an OPEN Things dialog earns, wherever it is reported.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-state.js","sourceRoot":"","sources":["../../../src/write/vectors/ui-state.ts"],"names":[],"mappings":"AAqDA,yEAAyE;AACzE,MAAM,eAAe,GAAG,CAAC,CAAC;AAE1B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;;;;;;;;;YAqB3B,eAAe;;;;;;;;;SASlB,CAAC;
|
|
1
|
+
{"version":3,"file":"ui-state.js","sourceRoot":"","sources":["../../../src/write/vectors/ui-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -63,6 +63,41 @@ export type UiRunner = (command: UiCommand, timeoutMs: number) => Promise<UiRunR
|
|
|
63
63
|
export declare const AX_FOCUS_GUARD_HANDLERS = "on fgFrontApp()\n\tset frontName to \"\"\n\ttry\n\t\ttell application \"System Events\" to set frontName to (name of first application process whose frontmost is true) as text\n\tend try\n\treturn frontName\nend fgFrontApp\n\non fgAssertFront(what)\n\tset f to my fgFrontApp()\n\tif f is \"Things3\" then return true\n\tif f is \"\" then\n\t\terror \"refused to \" & what & \": the frontmost application could not be read, so there is no proof the keystrokes would reach Things \u2014 nothing was typed\"\n\tend if\n\terror \"refused to \" & what & \": \" & f & \" is frontmost, not Things \u2014 a keystroke goes to whatever owns the screen, so nothing was typed\"\nend fgAssertFront";
|
|
64
64
|
/** resolve-element: does the element exist right now? Returns "true"/"false". */
|
|
65
65
|
export declare function axResolveScript(path: string): string;
|
|
66
|
+
/**
|
|
67
|
+
* The AppleScript variable a folded candidate resolution binds the live element
|
|
68
|
+
* to. Every addressed script takes its target as `(<path>)`, so handing it this
|
|
69
|
+
* name — with {@link axCandidatePrelude} in front — makes the resolution and the
|
|
70
|
+
* action ONE hop instead of two (DRVLAT1, issue #633).
|
|
71
|
+
*/
|
|
72
|
+
export declare const STEP_ELEMENT_REF = "fgStepEl";
|
|
73
|
+
/**
|
|
74
|
+
* The message a folded candidate resolution raises when NONE of a step's expected
|
|
75
|
+
* element shapes appeared. Byte-identical to the driver's own wording for the
|
|
76
|
+
* separate-hop resolution it replaces, so a report reads the same either way.
|
|
77
|
+
*/
|
|
78
|
+
export declare const CANDIDATES_MISSED: string;
|
|
79
|
+
/**
|
|
80
|
+
* IN-SCRIPT CANDIDATE RESOLUTION (DRVLAT1, issue #633).
|
|
81
|
+
*
|
|
82
|
+
* A candidate-addressed step used to dispatch one `resolve` hop PER CANDIDATE PER
|
|
83
|
+
* POLL ROUND before the hop that acted on whichever answered — a process spawn
|
|
84
|
+
* each, on top of a 300ms JS-side poll floor. This prelude does the same work in
|
|
85
|
+
* the acting hop's own process: it polls the candidates in the SAME priority
|
|
86
|
+
* order, binds the first that exists to {@link STEP_ELEMENT_REF}, and fails closed
|
|
87
|
+
* with {@link CANDIDATES_MISSED} when the window elapses with none of them there.
|
|
88
|
+
*
|
|
89
|
+
* Collapsing it is also strictly better against TOCTOU: the element the script
|
|
90
|
+
* acts on is the one it just proved exists, with nothing dispatched in between.
|
|
91
|
+
*/
|
|
92
|
+
export declare function axCandidatePrelude(paths: string[], timeoutMs?: number): string;
|
|
93
|
+
/**
|
|
94
|
+
* wait: poll for ANY of the awaited element shapes to appear, IN-SCRIPT (DRVLAT1).
|
|
95
|
+
* One hop for the whole wait instead of one per candidate per round — which is
|
|
96
|
+
* what a slow host paid most: the dialog the drive is waiting for is exactly the
|
|
97
|
+
* thing that is slow when the app is busy. Returns "true", or "false" when the
|
|
98
|
+
* window elapses with none of them present (the driver's abort path is unchanged).
|
|
99
|
+
*/
|
|
100
|
+
export declare function axWaitAnyScript(paths: string[], timeoutMs: number): string;
|
|
66
101
|
/** press: AXPress the element. */
|
|
67
102
|
export declare function axPressScript(path: string): string;
|
|
68
103
|
/**
|
|
@@ -458,7 +493,7 @@ export declare function axSelectHeadingRowScript(tablePath: string, ordinal: num
|
|
|
458
493
|
* naming expected vs observed. Pure System Events + Things scripting, background-
|
|
459
494
|
* capable. One stable command shape per primitive.
|
|
460
495
|
*/
|
|
461
|
-
export declare function axAssertEligibleScript(targetUuid: string, menuItemPath: string): string;
|
|
496
|
+
export declare function axAssertEligibleScript(targetUuid: string, menuItemPath: string, settleMs?: number): string;
|
|
462
497
|
/** activate: foreground Things (the fallback preamble step). */
|
|
463
498
|
export declare function axActivateScript(): string;
|
|
464
499
|
/**
|
|
@@ -558,6 +593,15 @@ export declare function axAbortScript(): string;
|
|
|
558
593
|
* the census to decide that.
|
|
559
594
|
*/
|
|
560
595
|
export declare function axCancelDialogScript(): string;
|
|
596
|
+
/**
|
|
597
|
+
* The Cancel button's on-screen FRAME, resolved through the same addressed
|
|
598
|
+
* dialog-shell path the AXPress dismissal uses (issue #629). Feeds the pointer
|
|
599
|
+
* fallback: if `AXPress` on the button reports success and the dialog is still
|
|
600
|
+
* standing, a real click at the button's own AX-resolved centre is the next
|
|
601
|
+
* thing to try before discarding the window wholesale. The frame comes from the
|
|
602
|
+
* tree, never from a remembered coordinate, so a moved dialog fails closed.
|
|
603
|
+
*/
|
|
604
|
+
export declare function axCancelFrameScript(): string;
|
|
561
605
|
/**
|
|
562
606
|
* The PROVEN app-level clearance / relocation maneuver (SESSGATE, #480, live-host
|
|
563
607
|
* recovery): close the front Things window — which takes an attached modal sheet
|
|
@@ -606,6 +650,13 @@ export interface ClearResult {
|
|
|
606
650
|
sheetKind?: UiSheetKind;
|
|
607
651
|
/** Who owned the screen when the cleanup started, when it was not Things. */
|
|
608
652
|
focusOwner?: string;
|
|
653
|
+
/**
|
|
654
|
+
* True when the cleanup ran WITHOUT a working window-state inspection (issue
|
|
655
|
+
* #629) — the dismissal and its proof came from addressed reads alone, so the
|
|
656
|
+
* outcome is real but the identity of what was dismissed was taken from what
|
|
657
|
+
* this drive had already observed rather than re-confirmed.
|
|
658
|
+
*/
|
|
659
|
+
unverified?: boolean;
|
|
609
660
|
}
|
|
610
661
|
/**
|
|
611
662
|
* resolve-frame: read the element's on-screen frame (top-left origin, points)
|
|
@@ -693,7 +744,16 @@ export declare function judgeFocusGuard(state: UiState | null, expectedSheet: Ui
|
|
|
693
744
|
* cannot see and would otherwise discover hours later on another device.
|
|
694
745
|
*/
|
|
695
746
|
export declare function describeCleanup(clear: ClearResult): string;
|
|
696
|
-
/**
|
|
747
|
+
/**
|
|
748
|
+
* Compile one recipe step into its primitive command (no dispatch).
|
|
749
|
+
*
|
|
750
|
+
* A CANDIDATE-ADDRESSED step compiles to ONE script that resolves its own element
|
|
751
|
+
* and then acts on it (DRVLAT1, issue #633): the addressed body is generated
|
|
752
|
+
* against {@link STEP_ELEMENT_REF} and {@link axCandidatePrelude} is prepended, so
|
|
753
|
+
* the resolution that used to be a separate `resolve` hop (or several) now rides
|
|
754
|
+
* the acting hop. Steps whose script is JXA, or that resolve their own target,
|
|
755
|
+
* are unaffected.
|
|
756
|
+
*/
|
|
697
757
|
export declare function commandForStep(step: UiStep, targetUuid: string): UiCommand;
|
|
698
758
|
/**
|
|
699
759
|
* The ui vector. Config-gated: when `ui.enabled` is false the matrix reports
|