salt-agent-sdk 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -3
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +72 -1
- package/dist/actions.js.map +1 -1
- package/dist/callBundle.d.ts +90 -0
- package/dist/callBundle.d.ts.map +1 -0
- package/dist/callBundle.js +199 -0
- package/dist/callBundle.js.map +1 -0
- package/dist/client.d.ts +154 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +139 -1
- package/dist/client.js.map +1 -1
- package/dist/crypto.d.ts +18 -4
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +34 -11
- package/dist/crypto.js.map +1 -1
- package/dist/device/client.d.ts +12 -4
- package/dist/device/client.d.ts.map +1 -1
- package/dist/device/client.js +44 -1
- package/dist/device/client.js.map +1 -1
- package/dist/device/host.d.ts +74 -3
- package/dist/device/host.d.ts.map +1 -1
- package/dist/device/host.js +262 -12
- package/dist/device/host.js.map +1 -1
- package/dist/device/http.d.ts.map +1 -1
- package/dist/device/http.js +3 -2
- package/dist/device/http.js.map +1 -1
- package/dist/device/protocol.d.ts +97 -1
- package/dist/device/protocol.d.ts.map +1 -1
- package/dist/device/protocol.js +155 -1
- package/dist/device/protocol.js.map +1 -1
- package/dist/help.d.ts +54 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +62 -0
- package/dist/help.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/keyReset.d.ts +117 -0
- package/dist/keyReset.d.ts.map +1 -0
- package/dist/keyReset.js +267 -0
- package/dist/keyReset.js.map +1 -0
- package/dist/webhook.d.ts +80 -2
- package/dist/webhook.d.ts.map +1 -1
- package/dist/webhook.js +130 -17
- package/dist/webhook.js.map +1 -1
- package/dist/work.d.ts +2 -0
- package/dist/work.d.ts.map +1 -1
- package/dist/work.js +4 -0
- package/dist/work.js.map +1 -1
- package/package.json +1 -1
package/dist/device/host.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { DeviceProtocolError, type AppsResult, type ClickArgs, type DeviceCountClass, type DeviceErrorCode, type DeviceOp, type DevicePauseReason, type FileResult, type KeyArgs, type ObservationResult, type ObserveArgs, type Progress, type ScrollArgs, type SubmitArgs, type TaskArgs, type TaskResult } from "./protocol.js";
|
|
1
|
+
import { DeviceProtocolError, type AppsResult, type ClickArgs, type DeviceCountClass, type DeviceErrorCode, type DeviceOp, type DevicePauseReason, type FileResult, type KeyArgs, type ObservationResult, type ObserveArgs, type PointedBody, type Progress, type ScrollArgs, type StillBody, type SubmitArgs, type TaskArgs, type TaskResult } from "./protocol.js";
|
|
2
2
|
import type { LaneReaders } from "./client.js";
|
|
3
|
+
import { type HelpRequest, type HelpViewer } from "../help.js";
|
|
3
4
|
import { type SaltId } from "../ids.js";
|
|
4
5
|
/** The seams. The desktop app implements every method; the host calls one only
|
|
5
6
|
* after it has passed every gate below. A handler may throw -- the host turns
|
|
@@ -30,7 +31,24 @@ export interface DeviceHostHandlers {
|
|
|
30
31
|
* extra field never reaches the agent. Let a `DeviceBudgetSpentError` from `ctx.spend`
|
|
31
32
|
* bubble: it ends the task and the session. */
|
|
32
33
|
task?(args: TaskArgs, ctx: DeviceTaskContext): Promise<TaskResult>;
|
|
34
|
+
/** Show Me: what is under the spot a person pointed at? Optional: a host without it answers a
|
|
35
|
+
* point `refused: "not_allowed"`. Called only after the host accepted the point (a person over
|
|
36
|
+
* the device, a help request or watch still open, a fresh point id). `x` and `y` are fractions
|
|
37
|
+
* of the picture the person tapped (`obs` names the still; absent = the live frame). Return the
|
|
38
|
+
* `screen` spot, the frontmost `app`, the `element` under it and, when the answer must be withheld,
|
|
39
|
+
* `refused`. The host drops the element of a refused answer and of any never-allowed app, so a
|
|
40
|
+
* handler that forgets cannot leak a name. A throw is answered `not_allowed`. */
|
|
41
|
+
resolvePoint?(args: ResolvePointArgs): Promise<ResolvedPoint>;
|
|
33
42
|
}
|
|
43
|
+
export interface ResolvePointArgs {
|
|
44
|
+
help_id: string;
|
|
45
|
+
obs?: string;
|
|
46
|
+
x: number;
|
|
47
|
+
y: number;
|
|
48
|
+
words: string;
|
|
49
|
+
}
|
|
50
|
+
/** What `resolvePoint` hands back; the host adds `help_id`, `x`, `y` and `words` itself. */
|
|
51
|
+
export type ResolvedPoint = Pick<PointedBody, "screen" | "app" | "element" | "refused">;
|
|
34
52
|
/** What `ctx.spend` counts a task step as. `act` is counted as a `click` (the closest of the
|
|
35
53
|
* existing classes: a press of a control) and `apps` as `app_focus`. */
|
|
36
54
|
export type TaskSpendClass = "command" | "look" | "act" | "observe" | "apps";
|
|
@@ -126,7 +144,9 @@ export interface DeviceHostTransport {
|
|
|
126
144
|
* endpoint's shape lives is httpDeviceHostTransport (http.ts). */
|
|
127
145
|
pause?(sessionId: SaltId, reason: DevicePauseReason): Promise<void>;
|
|
128
146
|
/** POST /device_sessions/:sid/resume (device key). */
|
|
129
|
-
resume?(sessionId: SaltId
|
|
147
|
+
resume?(sessionId: SaltId, opts?: {
|
|
148
|
+
local?: boolean;
|
|
149
|
+
}): Promise<void>;
|
|
130
150
|
}
|
|
131
151
|
/** The optional body of a beat: when the device last executed a command
|
|
132
152
|
* (ISO8601) and that command's seq. Sent on every beat once one has run. */
|
|
@@ -169,6 +189,14 @@ export interface DeviceHostCallbacks {
|
|
|
169
189
|
/** Every gate decision, for the app's append-only on-disk audit log. Content
|
|
170
190
|
* is the caller's to redact; the host passes op + decision + reason only. */
|
|
171
191
|
onAudit?(entry: DeviceAuditEntry): void;
|
|
192
|
+
/** Show Me: a help request opened on this session. The app posts a still now and then stills on
|
|
193
|
+
* change (via `host.postStill`) for ten minutes. */
|
|
194
|
+
onHelpOpened?(req: HelpRequest): void;
|
|
195
|
+
/** Show Me: the person is looking from their phone. The app posts a still now, restarts its
|
|
196
|
+
* ten-minute window and shows "<name> is looking from their phone". */
|
|
197
|
+
onHelpViewing?(req: HelpRequest, viewer: HelpViewer | null): void;
|
|
198
|
+
/** Show Me: the help request closed (answered, withdrawn or ended). The app stops and clears its lines. */
|
|
199
|
+
onHelpClosed?(req: HelpRequest): void;
|
|
172
200
|
}
|
|
173
201
|
export interface DeviceAuditEntry {
|
|
174
202
|
at: number;
|
|
@@ -202,9 +230,38 @@ export interface DeviceHost {
|
|
|
202
230
|
* interrupt a command already executing. */
|
|
203
231
|
pause(reason: DevicePauseReason): Promise<void>;
|
|
204
232
|
/** Clear the pause and tell Salt. No-op when not paused. */
|
|
205
|
-
resume(
|
|
233
|
+
resume(opts?: {
|
|
234
|
+
local?: boolean;
|
|
235
|
+
}): Promise<void>;
|
|
206
236
|
/** The current pause reason, or null. */
|
|
207
237
|
pausedReason(): DevicePauseReason | null;
|
|
238
|
+
/** Show Me: feed a lane message in together with WHO sent it (the decrypted text and the sender's
|
|
239
|
+
* account id). Returns true when it was a `[[SALT-POINT]]` line, consumed here whether or not it
|
|
240
|
+
* was acted on; false for anything else (pass that to `handleCommand`). A point is accepted only
|
|
241
|
+
* from a person `options.isPersonOverDevice` names, only while its help request is open (or
|
|
242
|
+
* `options.isWatchLive()`), and once per point id; the answer is a `pointed` op with `re` = the
|
|
243
|
+
* point id. Every refusal is one audit line (op `point`) and no reply. */
|
|
244
|
+
handleLaneMessage(plaintext: string, senderId: SaltId): Promise<boolean>;
|
|
245
|
+
/** Also takes `device_session_paused {reason:"phone"}` / `device_session_resumed` for this session (a person
|
|
246
|
+
* pausing from their phone): the host pauses or resumes the PHONE pause itself, never a local guard. */
|
|
247
|
+
/** Show Me: feed a `help_request_opened|viewing|closed` delivery in (the `onDeviceDelivery` type
|
|
248
|
+
* and body). Returns true when it was one of those for THIS session and the matching callback
|
|
249
|
+
* ran. */
|
|
250
|
+
handleDelivery(type: string, body: unknown): boolean;
|
|
251
|
+
/** Show Me: post a still into the lane (op `still`, no `re`). Resolves `{posted: true}`, or
|
|
252
|
+
* `{posted: false, reason}` when held back: `too_soon` (under 2 s since the last one),
|
|
253
|
+
* `budget` (the grant's `max_actions` is spent; a still the agent receives costs one `observe`),
|
|
254
|
+
* `too_many` (30 in the last minute), `no_help` (no help request open for that id and no live
|
|
255
|
+
* watch), `secure_field` (the session is paused for a secure field), `ended`. Throws
|
|
256
|
+
* `bad_args` for a malformed body and `too_large` over the per-message cap. */
|
|
257
|
+
postStill(body: StillBody): Promise<{
|
|
258
|
+
posted: true;
|
|
259
|
+
} | {
|
|
260
|
+
posted: false;
|
|
261
|
+
reason: "too_soon" | "too_many" | "no_help" | "budget" | "paused" | "not_viewed" | "not_granted" | "ended";
|
|
262
|
+
}>;
|
|
263
|
+
/** The help request ids currently open on this session. */
|
|
264
|
+
openHelpIds(): string[];
|
|
208
265
|
}
|
|
209
266
|
export declare function createDeviceHost(snapshot: DeviceCapsSnapshot, handlers: DeviceHostHandlers, transport: DeviceHostTransport, callbacks?: DeviceHostCallbacks, options?: {
|
|
210
267
|
beatIntervalMs?: number;
|
|
@@ -218,5 +275,19 @@ export declare function createDeviceHost(snapshot: DeviceCapsSnapshot, handlers:
|
|
|
218
275
|
progressMinIntervalMs?: number;
|
|
219
276
|
/** How long past `timeout_s` a task handler may run before it is cut off (default 15 s). */
|
|
220
277
|
taskGraceMs?: number;
|
|
278
|
+
/** Show Me: is this account one of the device's people (owner, chain root or the session's
|
|
279
|
+
* grantor), per the host's cached `GET /devices/:id/grants`? A point from anyone else is ignored.
|
|
280
|
+
* Absent, every point is ignored. */
|
|
281
|
+
isPersonOverDevice?: (accountId: SaltId) => boolean;
|
|
282
|
+
/** Show Me: is a live watch call on this device right now? While it is, a point is accepted
|
|
283
|
+
* without an open help request. Default: never. */
|
|
284
|
+
isWatchLive?: () => boolean;
|
|
285
|
+
/** Show Me: armored public keys of the people over the device (owner, root, this session's grantor).
|
|
286
|
+
* Used when the grant lacks `device.observe` in auto or notify mode: a still is then encrypted for
|
|
287
|
+
* these keys ONLY, never the agent's. Absent or empty in that case, stills are refused `not_granted`. */
|
|
288
|
+
peopleReaderKeys?: () => string[];
|
|
289
|
+
/** Least gap between two stills (default STILL_MIN_INTERVAL_MS, 2 s). For tests: the 30-a-minute
|
|
290
|
+
* cap can only be reached below the default gap. */
|
|
291
|
+
stillMinIntervalMs?: number;
|
|
221
292
|
}): DeviceHost;
|
|
222
293
|
//# sourceMappingURL=host.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/device/host.ts"],"names":[],"mappings":"AAiCA,OAAO,EAKL,mBAAmB,
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/device/host.ts"],"names":[],"mappings":"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"}
|
package/dist/device/host.js
CHANGED
|
@@ -40,6 +40,7 @@ exports.ackBody = ackBody;
|
|
|
40
40
|
exports.createDeviceHost = createDeviceHost;
|
|
41
41
|
const protocol_js_1 = require("./protocol.js");
|
|
42
42
|
const node_path_1 = __importDefault(require("node:path"));
|
|
43
|
+
const help_js_1 = require("../help.js");
|
|
43
44
|
const ids_js_1 = require("../ids.js");
|
|
44
45
|
const SPEND_COUNT = {
|
|
45
46
|
command: "command",
|
|
@@ -150,7 +151,17 @@ function createDeviceHost(snapshot, handlers, transport, callbacks = {}, options
|
|
|
150
151
|
// Newest command this host actually executed (for lease reports).
|
|
151
152
|
let lastRanAt = null;
|
|
152
153
|
let lastRanSeq = null;
|
|
154
|
+
// The effective pause reason is a LOCAL guard pause (person_active, secure_field, person, ...) if one
|
|
155
|
+
// holds, else "phone" while the person has paused from their phone. The two are tracked apart: a
|
|
156
|
+
// phone resume never clears a local guard, and the machine's own idle resume never clears the phone.
|
|
157
|
+
let localPause = null;
|
|
158
|
+
let phonePause = false;
|
|
153
159
|
let pausedReason = null;
|
|
160
|
+
// Salt still holds a phone pause that the person at this machine released; told on the next sync.
|
|
161
|
+
let serverPhoneToClear = false;
|
|
162
|
+
const recomputePause = () => {
|
|
163
|
+
pausedReason = localPause ?? (phonePause ? "phone" : null);
|
|
164
|
+
};
|
|
154
165
|
// One command at a time per session: every command runs behind this tail.
|
|
155
166
|
let tail = Promise.resolve();
|
|
156
167
|
// Ids received and not yet started / ids a `cancel` asked us to drop.
|
|
@@ -816,6 +827,8 @@ function createDeviceHost(snapshot, handlers, transport, callbacks = {}, options
|
|
|
816
827
|
beatTimer = durationTimer = null;
|
|
817
828
|
for (const ac of runningTasks.values())
|
|
818
829
|
ac.abort("session_ended");
|
|
830
|
+
openHelp.clear();
|
|
831
|
+
viewedHelp.clear();
|
|
819
832
|
audit({ commandId: "-", op: "stop", decision: "ended", reason });
|
|
820
833
|
callbacks.onEnded?.(reason);
|
|
821
834
|
try {
|
|
@@ -827,15 +840,27 @@ function createDeviceHost(snapshot, handlers, transport, callbacks = {}, options
|
|
|
827
840
|
}
|
|
828
841
|
/** Make Salt's picture of our pause state match ours. Salt only accepts a
|
|
829
842
|
* pause from `active`, so a reason CHANGE is resume-then-pause. A failed
|
|
830
|
-
* report is retried on the next beat (the local pause stands meanwhile).
|
|
843
|
+
* report is retried on the next beat (the local pause stands meanwhile).
|
|
844
|
+
* Only LOCAL reasons are reported; a phone pause is Salt's own, and while it holds Salt keeps
|
|
845
|
+
* its reason whatever we report. */
|
|
831
846
|
function syncPause() {
|
|
832
847
|
pauseSync = pauseSync.then(async () => {
|
|
833
848
|
if (ended)
|
|
834
849
|
return;
|
|
835
|
-
const want = pausedReason;
|
|
836
|
-
if (reportedPause === want)
|
|
837
|
-
return;
|
|
838
850
|
try {
|
|
851
|
+
if (serverPhoneToClear) {
|
|
852
|
+
// The person pressed Resume at this machine: the one resume Salt accepts for a phone pause.
|
|
853
|
+
await transport.resume?.(snapshot.sessionId, { local: true });
|
|
854
|
+
serverPhoneToClear = false;
|
|
855
|
+
reportedPause = null;
|
|
856
|
+
}
|
|
857
|
+
const want = localPause;
|
|
858
|
+
if (phonePause) {
|
|
859
|
+
reportedPause = want;
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
if (reportedPause === want)
|
|
863
|
+
return;
|
|
839
864
|
if (reportedPause !== null) {
|
|
840
865
|
await transport.resume?.(snapshot.sessionId);
|
|
841
866
|
reportedPause = null;
|
|
@@ -854,23 +879,248 @@ function createDeviceHost(snapshot, handlers, transport, callbacks = {}, options
|
|
|
854
879
|
async function pause(reason) {
|
|
855
880
|
if (ended)
|
|
856
881
|
return;
|
|
857
|
-
|
|
858
|
-
|
|
882
|
+
if (reason === "phone") {
|
|
883
|
+
// Not a local guard: only the phone delivery path sets it.
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
const changed = localPause !== reason;
|
|
887
|
+
localPause = reason;
|
|
888
|
+
recomputePause();
|
|
859
889
|
audit({ commandId: "-", op: "pause", decision: "refused", reason });
|
|
860
890
|
if (!changed)
|
|
861
891
|
return;
|
|
862
892
|
await syncPause();
|
|
863
893
|
}
|
|
864
|
-
|
|
865
|
-
|
|
894
|
+
/** Clear the local pause. A phone pause is cleared only by `{local: true}`: the person pressed
|
|
895
|
+
* Resume at the machine. An automatic resume (the idle timer, the end of a secure field) leaves it. */
|
|
896
|
+
async function resume(opts = {}) {
|
|
897
|
+
if (ended)
|
|
898
|
+
return;
|
|
899
|
+
const clearPhone = opts.local === true && phonePause;
|
|
900
|
+
if (localPause === null && !clearPhone)
|
|
866
901
|
return;
|
|
867
|
-
|
|
902
|
+
localPause = null;
|
|
903
|
+
if (clearPhone) {
|
|
904
|
+
phonePause = false;
|
|
905
|
+
serverPhoneToClear = true;
|
|
906
|
+
}
|
|
907
|
+
recomputePause();
|
|
868
908
|
audit({ commandId: "-", op: "resume", decision: "ran" });
|
|
869
|
-
|
|
870
|
-
release
|
|
909
|
+
if (pausedReason === null)
|
|
910
|
+
for (const release of [...resumeWaiters])
|
|
911
|
+
release();
|
|
871
912
|
await syncPause();
|
|
872
913
|
}
|
|
873
|
-
|
|
914
|
+
// ---- Show Me: help requests, stills and points (CONTRACT.md sections 3 and 4) ----------------
|
|
915
|
+
// Help requests open on THIS session, by id. A point is acted on only while its help id is here
|
|
916
|
+
// (or a watch is live).
|
|
917
|
+
const openHelp = new Map();
|
|
918
|
+
// Help requests the person has tapped (help_request_viewing). Stills start only then.
|
|
919
|
+
const viewedHelp = new Set();
|
|
920
|
+
// Point ids already answered, oldest dropped: a replayed or re-delivered point is answered once.
|
|
921
|
+
const seenPoints = new Set();
|
|
922
|
+
const SEEN_POINTS_CAP = 256;
|
|
923
|
+
// Times of recently posted stills (the 2 s gap and the 30-a-minute window).
|
|
924
|
+
const stillTimes = [];
|
|
925
|
+
function helpFromBody(body) {
|
|
926
|
+
const hr = body?.help_request;
|
|
927
|
+
if (!hr || typeof hr !== "object")
|
|
928
|
+
return null;
|
|
929
|
+
const r = hr;
|
|
930
|
+
if (typeof r.id !== "string" || r.id === "")
|
|
931
|
+
return null;
|
|
932
|
+
// A delivery about another session is not ours.
|
|
933
|
+
if (r.device_session_id == null || !(0, ids_js_1.sameId)(r.device_session_id, snapshot.sessionId))
|
|
934
|
+
return null;
|
|
935
|
+
return r;
|
|
936
|
+
}
|
|
937
|
+
// A person pausing or resuming from their phone reaches the device as a server delivery for THIS
|
|
938
|
+
// session: `device_session_paused {session_id, reason:"phone"}` and `device_session_resumed {session_id}`.
|
|
939
|
+
// Only the phone's own reason is taken from the wire. A phone pause never replaces a local guard
|
|
940
|
+
// (it waits underneath it), and a resume clears only the phone pause, never a local guard.
|
|
941
|
+
function handleSessionPauseDelivery(type, body) {
|
|
942
|
+
const b = (body ?? {});
|
|
943
|
+
const sid = b.session_id ?? b.session?.id ?? b.id;
|
|
944
|
+
if (sid == null || !(0, ids_js_1.sameId)(sid, snapshot.sessionId))
|
|
945
|
+
return false;
|
|
946
|
+
if (type === "device_session_paused") {
|
|
947
|
+
if ((b.reason ?? b.pause_reason) !== "phone")
|
|
948
|
+
return false;
|
|
949
|
+
phonePause = true;
|
|
950
|
+
serverPhoneToClear = false;
|
|
951
|
+
recomputePause();
|
|
952
|
+
audit({ commandId: "-", op: "pause", decision: "refused", reason: "phone" });
|
|
953
|
+
return true;
|
|
954
|
+
}
|
|
955
|
+
if (!phonePause)
|
|
956
|
+
return true;
|
|
957
|
+
phonePause = false;
|
|
958
|
+
serverPhoneToClear = false;
|
|
959
|
+
reportedPause = null; // Salt is active again; re-report any local pause that still holds
|
|
960
|
+
recomputePause();
|
|
961
|
+
if (pausedReason === null)
|
|
962
|
+
for (const release of [...resumeWaiters])
|
|
963
|
+
release();
|
|
964
|
+
void syncPause();
|
|
965
|
+
return true;
|
|
966
|
+
}
|
|
967
|
+
function handleDelivery(type, body) {
|
|
968
|
+
if (ended)
|
|
969
|
+
return false;
|
|
970
|
+
if (type === "device_session_paused" || type === "device_session_resumed")
|
|
971
|
+
return handleSessionPauseDelivery(type, body);
|
|
972
|
+
if (!help_js_1.HELP_DELIVERY_TYPES.includes(type))
|
|
973
|
+
return false;
|
|
974
|
+
if (ended)
|
|
975
|
+
return false;
|
|
976
|
+
const req = helpFromBody(body);
|
|
977
|
+
if (!req)
|
|
978
|
+
return false;
|
|
979
|
+
if (type === "help_request_opened") {
|
|
980
|
+
openHelp.set(req.id, req);
|
|
981
|
+
callbacks.onHelpOpened?.(req);
|
|
982
|
+
}
|
|
983
|
+
else if (type === "help_request_viewing") {
|
|
984
|
+
openHelp.set(req.id, req);
|
|
985
|
+
viewedHelp.add(req.id);
|
|
986
|
+
const v = body.viewer;
|
|
987
|
+
callbacks.onHelpViewing?.(req, v && typeof v === "object" && typeof v.id === "string" ? v : null);
|
|
988
|
+
}
|
|
989
|
+
else {
|
|
990
|
+
openHelp.delete(req.id);
|
|
991
|
+
viewedHelp.delete(req.id);
|
|
992
|
+
callbacks.onHelpClosed?.(req);
|
|
993
|
+
}
|
|
994
|
+
return true;
|
|
995
|
+
}
|
|
996
|
+
function helpAllowsStills(helpId) {
|
|
997
|
+
return openHelp.has(helpId) || (options.isWatchLive?.() ?? false);
|
|
998
|
+
}
|
|
999
|
+
async function postStill(raw) {
|
|
1000
|
+
if (ended)
|
|
1001
|
+
return { posted: false, reason: "ended" };
|
|
1002
|
+
const body = (0, protocol_js_1.normalizeStill)(raw);
|
|
1003
|
+
if (!body)
|
|
1004
|
+
throw new protocol_js_1.DeviceProtocolError("bad_args", "malformed still");
|
|
1005
|
+
if (!helpAllowsStills(body.help_id))
|
|
1006
|
+
return { posted: false, reason: "no_help" };
|
|
1007
|
+
// Never while paused, for any reason (the person has the computer, a secure field, the phone).
|
|
1008
|
+
if (pausedReason !== null)
|
|
1009
|
+
return { posted: false, reason: "paused" };
|
|
1010
|
+
// Only after the person tapped (help_request_viewing), never on `opened`. A live watch is its own consent.
|
|
1011
|
+
if (!viewedHelp.has(body.help_id) && !(options.isWatchLive?.() ?? false))
|
|
1012
|
+
return { posted: false, reason: "not_viewed" };
|
|
1013
|
+
// A picture goes to the agent only when its grant allows observing. Otherwise it goes to the people
|
|
1014
|
+
// over the device alone (the lane readers minus the agent), which the transport can do because every
|
|
1015
|
+
// post names its readers.
|
|
1016
|
+
const observeMode = snapshot.caps["device.observe"]?.mode;
|
|
1017
|
+
const agentMayObserve = observeMode === "auto" || observeMode === "notify";
|
|
1018
|
+
let readers = snapshot.readers;
|
|
1019
|
+
if (!agentMayObserve) {
|
|
1020
|
+
const keys = options.peopleReaderKeys?.() ?? [];
|
|
1021
|
+
if (keys.length === 0)
|
|
1022
|
+
return { posted: false, reason: "not_granted" };
|
|
1023
|
+
readers = { ...snapshot.readers, publicKeys: keys };
|
|
1024
|
+
}
|
|
1025
|
+
const now = clock();
|
|
1026
|
+
while (stillTimes.length && now - stillTimes[0] >= 60_000)
|
|
1027
|
+
stillTimes.shift();
|
|
1028
|
+
if (stillTimes.length && now - stillTimes[stillTimes.length - 1] < (options.stillMinIntervalMs ?? protocol_js_1.STILL_MIN_INTERVAL_MS))
|
|
1029
|
+
return { posted: false, reason: "too_soon" };
|
|
1030
|
+
if (stillTimes.length >= protocol_js_1.STILL_MAX_PER_MINUTE)
|
|
1031
|
+
return { posted: false, reason: "too_many" };
|
|
1032
|
+
// A still the agent receives is an observation: it counts against the session's action budget and
|
|
1033
|
+
// the per-second observe cap. A people-only still (agent not granted) is the agent's no business.
|
|
1034
|
+
const maxActions = snapshot.budget?.maxActions;
|
|
1035
|
+
if (agentMayObserve) {
|
|
1036
|
+
if (maxActions !== undefined && spent >= maxActions)
|
|
1037
|
+
return { posted: false, reason: "budget" };
|
|
1038
|
+
while (obsTimes.length && now - obsTimes[0] >= 1000)
|
|
1039
|
+
obsTimes.shift();
|
|
1040
|
+
if (obsTimes.length >= protocol_js_1.MAX_OBSERVE_PER_SECOND)
|
|
1041
|
+
return { posted: false, reason: "too_soon" };
|
|
1042
|
+
}
|
|
1043
|
+
const plaintext = (0, protocol_js_1.encodeDeviceMessage)({ v: protocol_js_1.DEVICE_PROTOCOL_VERSION, id: (0, protocol_js_1.deviceMessageId)(), seq: nextOutSeq(), op: "still", session: snapshot.sessionId }, body);
|
|
1044
|
+
if (Buffer.byteLength(plaintext, "utf8") > protocol_js_1.MAX_DEVICE_PLAINTEXT_BYTES)
|
|
1045
|
+
throw new protocol_js_1.DeviceProtocolError("too_large", "still exceeds the per-message limit");
|
|
1046
|
+
stillTimes.push(now);
|
|
1047
|
+
await transport.postResult(plaintext, readers);
|
|
1048
|
+
if (agentMayObserve) {
|
|
1049
|
+
spent += 1;
|
|
1050
|
+
obsTimes.push(now);
|
|
1051
|
+
bump("observe");
|
|
1052
|
+
// Spending the last action does not end the session here: the next still is refused "budget" and
|
|
1053
|
+
// the next command hits the budget gate, which ends it (budget_exhausted).
|
|
1054
|
+
}
|
|
1055
|
+
return { posted: true };
|
|
1056
|
+
}
|
|
1057
|
+
function pointRefused(pointId, why) {
|
|
1058
|
+
audit({ commandId: pointId, op: "point", decision: "refused", code: "forbidden", reason: why });
|
|
1059
|
+
}
|
|
1060
|
+
async function handleLaneMessage(plaintext, senderId) {
|
|
1061
|
+
if (!(0, protocol_js_1.isPointMessage)(plaintext))
|
|
1062
|
+
return false;
|
|
1063
|
+
if (ended)
|
|
1064
|
+
return true;
|
|
1065
|
+
let point;
|
|
1066
|
+
try {
|
|
1067
|
+
point = (0, protocol_js_1.decodePoint)(plaintext);
|
|
1068
|
+
}
|
|
1069
|
+
catch (e) {
|
|
1070
|
+
pointRefused("-", `malformed point: ${e.message}`);
|
|
1071
|
+
return true;
|
|
1072
|
+
}
|
|
1073
|
+
if (!point)
|
|
1074
|
+
return true;
|
|
1075
|
+
if (!options.isPersonOverDevice?.(senderId)) {
|
|
1076
|
+
pointRefused(point.id, "sender is not a person over this device");
|
|
1077
|
+
return true;
|
|
1078
|
+
}
|
|
1079
|
+
if (!helpAllowsStills(point.help)) {
|
|
1080
|
+
pointRefused(point.id, "no open help request for that id");
|
|
1081
|
+
return true;
|
|
1082
|
+
}
|
|
1083
|
+
if (seenPoints.has(point.id)) {
|
|
1084
|
+
pointRefused(point.id, "point id already answered");
|
|
1085
|
+
return true;
|
|
1086
|
+
}
|
|
1087
|
+
seenPoints.add(point.id);
|
|
1088
|
+
while (seenPoints.size > SEEN_POINTS_CAP)
|
|
1089
|
+
seenPoints.delete(seenPoints.values().next().value);
|
|
1090
|
+
let resolved = null;
|
|
1091
|
+
let refused = null;
|
|
1092
|
+
if (!handlers.resolvePoint) {
|
|
1093
|
+
refused = "not_allowed";
|
|
1094
|
+
}
|
|
1095
|
+
else {
|
|
1096
|
+
try {
|
|
1097
|
+
resolved = await handlers.resolvePoint({ help_id: point.help, obs: point.obs, x: point.x, y: point.y, words: point.words });
|
|
1098
|
+
}
|
|
1099
|
+
catch {
|
|
1100
|
+
refused = "not_allowed";
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
// The floor, again: never name anything in a never-allowed app, whatever the handler said.
|
|
1104
|
+
if (resolved && refused === null) {
|
|
1105
|
+
refused = resolved.refused ?? null;
|
|
1106
|
+
if (refused === null && resolved.app && isNeverAllowed(resolved.app))
|
|
1107
|
+
refused = "forbidden_app";
|
|
1108
|
+
}
|
|
1109
|
+
const answer = (0, protocol_js_1.normalizePointed)({
|
|
1110
|
+
help_id: point.help,
|
|
1111
|
+
x: point.x,
|
|
1112
|
+
y: point.y,
|
|
1113
|
+
screen: resolved?.screen ?? { x: 0, y: 0 },
|
|
1114
|
+
app: refused === "forbidden_app" ? null : (resolved?.app ?? null),
|
|
1115
|
+
element: refused === null ? (resolved?.element ?? null) : null,
|
|
1116
|
+
refused,
|
|
1117
|
+
words: point.words,
|
|
1118
|
+
}) ?? { help_id: point.help, x: point.x, y: point.y, screen: { x: 0, y: 0 }, app: null, element: null, refused: "not_allowed", words: "" };
|
|
1119
|
+
audit({ commandId: point.id, op: "point", decision: "ran", reason: answer.refused ?? undefined });
|
|
1120
|
+
await reply({ id: point.id }, "pointed", answer);
|
|
1121
|
+
return true;
|
|
1122
|
+
}
|
|
1123
|
+
return { handleCommand, start, stop, actionsSpent: () => spent, pause, resume, pausedReason: () => pausedReason, handleLaneMessage, handleDelivery, postStill, openHelpIds: () => [...openHelp.keys()] };
|
|
874
1124
|
}
|
|
875
1125
|
const AGENT_OP_SET = new Set([
|
|
876
1126
|
"observe",
|