@pushary/server 1.5.0 → 2.1.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 +22 -4
- package/dist/adapters.cjs +132 -76
- package/dist/adapters.cjs.map +1 -1
- package/dist/adapters.d.cts +13 -6
- package/dist/adapters.d.ts +13 -6
- package/dist/adapters.js +38 -19
- package/dist/adapters.js.map +1 -1
- package/dist/{chunk-E6DWMKLZ.js → chunk-AG6DI4RF.js} +97 -59
- package/dist/chunk-AG6DI4RF.js.map +1 -0
- package/dist/index.cjs +76 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{util-DeGBh5at.d.cts → util-CX6-cXFb.d.cts} +6 -1
- package/dist/{util-DeGBh5at.d.ts → util-CX6-cXFb.d.ts} +6 -1
- package/package.json +1 -1
- package/dist/chunk-E6DWMKLZ.js.map +0 -1
package/README.md
CHANGED
|
@@ -140,8 +140,8 @@ const { approved, value, status } = await pushary.decisions.ask({
|
|
|
140
140
|
if (approved) await issueRefund()
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
`ask()` creates
|
|
144
|
-
durably until the human answers or the deadline passes (default 55s
|
|
143
|
+
`ask()` creates a fresh decision per call and polls
|
|
144
|
+
durably until the human answers or the deadline passes (default 55s for a positive local wait budget).
|
|
145
145
|
`approved` is true only when the person actually said yes, so a declined, expired, or
|
|
146
146
|
unanswered decision safely blocks the action.
|
|
147
147
|
|
|
@@ -195,8 +195,8 @@ import {
|
|
|
195
195
|
const idempotencyKey = deterministicKey(['run-abc', 'step-3', 'user-123'])
|
|
196
196
|
|
|
197
197
|
// isApproved(decision: { status, type?, value }): boolean
|
|
198
|
-
//
|
|
199
|
-
//
|
|
198
|
+
// For confirm: true only for an affirmative answer. For select/input: true
|
|
199
|
+
// means answered, not permission to act. Check the actual value for those types.
|
|
200
200
|
isApproved({ status: 'answered', type: 'confirm', value: 'yes' }) // true
|
|
201
201
|
isApproved({ status: 'answered', type: 'confirm', value: 'no' }) // false
|
|
202
202
|
isApproved({ status: 'pending', type: 'confirm', value: null }) // false
|
|
@@ -226,6 +226,14 @@ app.post('/webhooks/pushary', async (req, res) => {
|
|
|
226
226
|
|
|
227
227
|
## Writing a framework adapter: `@pushary/server/adapters`
|
|
228
228
|
|
|
229
|
+
Customers answer in the native Pushary app. Confirm, select, and input questions keep their existing meanings; a choice or typed answer is data, not permission to execute a protected tool. Enroll the customer from trusted application identity before asking. Legacy web links remain an optional delivery path.
|
|
230
|
+
|
|
231
|
+
Both blocking and durable adapter calls accept `DecisionSubject` fields, including `parameters` and `presentation`, plus expiration, reachability, and placeholder settings. Durable calls require an operation-specific idempotency key; persist the returned decision ID alongside the framework's suspended run. Verify callbacks and read the authoritative decision before resuming that run.
|
|
232
|
+
|
|
233
|
+
For an explicit human gate, set `policy: false`. The gate keeps the full review subject and binds its decision key to the run, call, recipient, question, and complete JSON arguments. `decisionFingerprint(value)` exposes the same stable JSON fingerprint for deferred bindings. It rejects cycles, nonfinite numbers, and non-JSON objects instead of silently dropping action details.
|
|
234
|
+
|
|
235
|
+
**Upgrade handling:** version 2.1 changes human gate keys. Finish already-started operations on the SDK version that created them, or migrate their persisted bindings explicitly. Do not replay previously executed operations through the new key scheme. A fingerprint identifies the proposed action; the application's execution record and framework persistence still own recovery after an uncertain tool execution.
|
|
236
|
+
|
|
229
237
|
Every official Pushary adapter (`@pushary/eve`, `@pushary/ai-sdk`,
|
|
230
238
|
`@pushary/langgraph`, `@pushary/mastra`, `@pushary/openai-agents`) is a thin binding
|
|
231
239
|
over one shared kernel, and that kernel is public. If you run an in-house harness, or
|
|
@@ -394,3 +402,13 @@ import {
|
|
|
394
402
|
## License
|
|
395
403
|
|
|
396
404
|
MIT. See `LICENSE`.
|
|
405
|
+
|
|
406
|
+
## Retries and deadlines
|
|
407
|
+
|
|
408
|
+
For retries of one business operation, pass `idempotencyKey: deterministicKey([runId, stepId, userId])`. Independent asks get fresh keys, even with identical questions. Shared adapter durable creates require this key; missing keys fail before a request is sent. Keep a run ID unique per operation and stable across its retries.
|
|
409
|
+
|
|
410
|
+
A positive `timeoutMs` covers creation and polling. `timeoutMs: 0` retains create-only behavior. A timeout during creation throws; retry with the same explicit key to recover the decision. A timeout during polling returns the known pending decision ID for later `get()` or a webhook. `signal` cancels the local wait and throws; it does not cancel the remote decision. Other HTTP calls use `requestTimeoutMs` (65 seconds by default).
|
|
411
|
+
|
|
412
|
+
### Upgrading to 2.0
|
|
413
|
+
|
|
414
|
+
Upgrade the framework adapter alongside this SDK. Durable helpers now require an explicit `idempotencyKey` tied to the run and step. The 0.3 LangGraph, Mastra and OpenAI adapters support this contract. Earlier adapters must keep the 1.x SDK until migrated. Independent blocking calls no longer deduplicate by question text.
|
package/dist/adapters.cjs
CHANGED
|
@@ -27,6 +27,7 @@ __export(adapters_exports, {
|
|
|
27
27
|
UNBOUND_APPROVAL: () => UNBOUND_APPROVAL,
|
|
28
28
|
UNCONFIRMED_PERMIT: () => UNCONFIRMED_PERMIT,
|
|
29
29
|
createAdapterKernel: () => createAdapterKernel,
|
|
30
|
+
decisionFingerprint: () => decisionFingerprint,
|
|
30
31
|
defaultProtectQuestion: () => defaultProtectQuestion,
|
|
31
32
|
deniedByPolicy: () => deniedByPolicy,
|
|
32
33
|
deriveParameters: () => deriveParameters,
|
|
@@ -42,6 +43,7 @@ __export(adapters_exports, {
|
|
|
42
43
|
verifyWebhookSignature: () => verifyWebhookSignature
|
|
43
44
|
});
|
|
44
45
|
module.exports = __toCommonJS(adapters_exports);
|
|
46
|
+
var import_node_crypto = require("crypto");
|
|
45
47
|
|
|
46
48
|
// src/error.ts
|
|
47
49
|
var PusharyApiError = class _PusharyApiError extends Error {
|
|
@@ -92,11 +94,12 @@ var handleResponse = async (response) => {
|
|
|
92
94
|
}
|
|
93
95
|
return response.json();
|
|
94
96
|
};
|
|
95
|
-
var createRequest = (ctx) => async (method, path, body) => {
|
|
97
|
+
var createRequest = (ctx) => async (method, path, body, options) => {
|
|
96
98
|
const isGet = method === "GET";
|
|
97
99
|
const url = isGet && body ? buildUrl(ctx.baseUrl, path, body) : `${ctx.baseUrl}${path}`;
|
|
98
100
|
const response = await fetch(url, {
|
|
99
101
|
method,
|
|
102
|
+
signal: options?.signal ?? AbortSignal.timeout(ctx.timeoutMs ?? 65e3),
|
|
100
103
|
headers: ctx.headers,
|
|
101
104
|
body: !isGet && body ? JSON.stringify(body) : void 0
|
|
102
105
|
});
|
|
@@ -156,6 +159,25 @@ var import_crypto2 = require("crypto");
|
|
|
156
159
|
// src/util.ts
|
|
157
160
|
var import_crypto = require("crypto");
|
|
158
161
|
var deterministicKey = (parts) => (0, import_crypto.createHash)("sha256").update(parts.join("\0")).digest("hex").slice(0, 40);
|
|
162
|
+
var decisionFingerprint = (value) => {
|
|
163
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
164
|
+
const serialize = (item) => {
|
|
165
|
+
if (item === null || typeof item === "string" || typeof item === "boolean") return JSON.stringify(item);
|
|
166
|
+
if (typeof item === "number" && Number.isFinite(item)) return JSON.stringify(item);
|
|
167
|
+
if (typeof item !== "object" || ancestors.has(item)) {
|
|
168
|
+
throw new TypeError("Pushary: decision identity must contain finite, acyclic JSON values.");
|
|
169
|
+
}
|
|
170
|
+
if (!Array.isArray(item) && Object.getPrototypeOf(item) !== Object.prototype && Object.getPrototypeOf(item) !== null) {
|
|
171
|
+
throw new TypeError("Pushary: decision identity must contain plain JSON objects.");
|
|
172
|
+
}
|
|
173
|
+
ancestors.add(item);
|
|
174
|
+
const entries = Object.entries(item).filter(([, entry]) => entry !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
175
|
+
const serialized = Array.isArray(item) ? `[${Array.from(item, serialize).join(",")}]` : `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${serialize(entry)}`).join(",")}}`;
|
|
176
|
+
ancestors.delete(item);
|
|
177
|
+
return serialized;
|
|
178
|
+
};
|
|
179
|
+
return (0, import_crypto.createHash)("sha256").update(serialize(value)).digest("hex");
|
|
180
|
+
};
|
|
159
181
|
var AFFIRMATIVE = /* @__PURE__ */ new Set([
|
|
160
182
|
"yes",
|
|
161
183
|
"y",
|
|
@@ -179,64 +201,80 @@ var DEFAULT_ASK_TIMEOUT_MS = 55e3;
|
|
|
179
201
|
var createDecisionsResource = (request) => {
|
|
180
202
|
const ask = async (data) => {
|
|
181
203
|
const idempotencyKey = data.idempotencyKey ?? (0, import_crypto2.randomUUID)();
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
204
|
+
const timeoutMs = Math.max(0, data.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS);
|
|
205
|
+
const deadline = Date.now() + timeoutMs;
|
|
206
|
+
const controller = new AbortController();
|
|
207
|
+
const abort = () => controller.abort(data.signal?.reason);
|
|
208
|
+
if (data.signal?.aborted) abort();
|
|
209
|
+
else data.signal?.addEventListener("abort", abort, { once: true });
|
|
210
|
+
const timer = setTimeout(() => controller.abort(new DOMException("Pushary ask timed out", "TimeoutError")), timeoutMs || 65e3);
|
|
211
|
+
try {
|
|
212
|
+
const created = await request("POST", "/decisions", {
|
|
213
|
+
question: data.question,
|
|
214
|
+
type: data.type,
|
|
215
|
+
options: data.options,
|
|
216
|
+
externalId: data.externalId,
|
|
217
|
+
email: data.email,
|
|
218
|
+
agentName: data.agentName,
|
|
219
|
+
context: data.context,
|
|
220
|
+
placeholder: data.placeholder,
|
|
221
|
+
toolName: data.toolName,
|
|
222
|
+
toolTarget: data.toolTarget,
|
|
223
|
+
actor: data.actor,
|
|
224
|
+
environment: data.environment,
|
|
225
|
+
parameters: data.parameters,
|
|
226
|
+
// Its changes name parameters rather than carrying values, so what the
|
|
227
|
+
// person sees is read from the same numbers a rule compares.
|
|
228
|
+
presentation: data.presentation,
|
|
229
|
+
approvalUrl: data.approvalUrl,
|
|
230
|
+
expiresInSeconds: data.expiresInSeconds,
|
|
231
|
+
poweredBy: data.poweredBy,
|
|
232
|
+
callbackUrl: data.callbackUrl,
|
|
233
|
+
requireReachable: data.requireReachable,
|
|
234
|
+
// Read only by a key issued on your workspace's sandbox site, where the
|
|
235
|
+
// simulator answers instead of a person. Ignored in production.
|
|
236
|
+
sandboxOutcome: data.sandboxOutcome,
|
|
237
|
+
idempotencyKey,
|
|
238
|
+
wait: false
|
|
239
|
+
}, { signal: controller.signal });
|
|
240
|
+
const type = created.type ?? data.type ?? "confirm";
|
|
241
|
+
let status = created.status;
|
|
242
|
+
let answered = created.answered;
|
|
243
|
+
let value = created.value ?? null;
|
|
244
|
+
while (status === "pending" && Date.now() < deadline) {
|
|
245
|
+
const waitSeconds = Math.max(
|
|
246
|
+
1,
|
|
247
|
+
Math.min(POLL_WINDOW_SECONDS, Math.ceil((deadline - Date.now()) / 1e3))
|
|
248
|
+
);
|
|
249
|
+
try {
|
|
250
|
+
const polled = await request("GET", `/decisions/${created.decisionId}`, {
|
|
251
|
+
wait: waitSeconds
|
|
252
|
+
}, { signal: controller.signal });
|
|
253
|
+
status = polled.status;
|
|
254
|
+
answered = polled.answered;
|
|
255
|
+
value = polled.value;
|
|
256
|
+
} catch (error) {
|
|
257
|
+
if (controller.signal.aborted && !data.signal?.aborted) break;
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
decisionId: created.decisionId,
|
|
263
|
+
status,
|
|
264
|
+
answered,
|
|
265
|
+
value,
|
|
266
|
+
type,
|
|
267
|
+
approved: isApproved({ status, type, value }),
|
|
268
|
+
// Surfaced from the create response so a caller can tell an unanswered
|
|
269
|
+
// decision that reached nobody (reachable:false) apart from a real decline.
|
|
270
|
+
reachable: created.reachable,
|
|
271
|
+
reachableChannels: created.reachableChannels,
|
|
272
|
+
deviceCount: created.deviceCount
|
|
273
|
+
};
|
|
274
|
+
} finally {
|
|
275
|
+
clearTimeout(timer);
|
|
276
|
+
data.signal?.removeEventListener("abort", abort);
|
|
226
277
|
}
|
|
227
|
-
return {
|
|
228
|
-
decisionId: created.decisionId,
|
|
229
|
-
status,
|
|
230
|
-
answered,
|
|
231
|
-
value,
|
|
232
|
-
type,
|
|
233
|
-
approved: isApproved({ status, type, value }),
|
|
234
|
-
// Surfaced from the create response so a caller can tell an unanswered
|
|
235
|
-
// decision that reached nobody (reachable:false) apart from a real decline.
|
|
236
|
-
reachable: created.reachable,
|
|
237
|
-
reachableChannels: created.reachableChannels,
|
|
238
|
-
deviceCount: created.deviceCount
|
|
239
|
-
};
|
|
240
278
|
};
|
|
241
279
|
return Object.freeze({
|
|
242
280
|
create: (data) => request("POST", "/decisions", data),
|
|
@@ -366,6 +404,7 @@ var createContext = (config) => {
|
|
|
366
404
|
validateApiKey(config.apiKey);
|
|
367
405
|
return Object.freeze({
|
|
368
406
|
baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
|
|
407
|
+
timeoutMs: config.requestTimeoutMs ?? 65e3,
|
|
369
408
|
headers: Object.freeze({
|
|
370
409
|
"Content-Type": "application/json",
|
|
371
410
|
"Authorization": `Bearer ${config.apiKey}`
|
|
@@ -424,9 +463,20 @@ var parseDecisionCallback = (rawBody) => {
|
|
|
424
463
|
};
|
|
425
464
|
|
|
426
465
|
// src/adapters.ts
|
|
427
|
-
var DEFAULT_NODE = "ask-human";
|
|
428
466
|
var defaultProtectQuestion = (action, target) => `Approve ${action}${target ? ` ${target}` : ""}?`;
|
|
429
|
-
var idempotencyKeyFor = (input) =>
|
|
467
|
+
var idempotencyKeyFor = (input) => input.idempotencyKey ?? (0, import_node_crypto.randomUUID)();
|
|
468
|
+
var decisionSubject = (input) => ({
|
|
469
|
+
toolTarget: input.toolTarget,
|
|
470
|
+
actor: input.actor,
|
|
471
|
+
environment: input.environment,
|
|
472
|
+
parameters: input.parameters,
|
|
473
|
+
presentation: input.presentation,
|
|
474
|
+
approvalUrl: input.approvalUrl,
|
|
475
|
+
expiresInSeconds: input.expiresInSeconds,
|
|
476
|
+
requireReachable: input.requireReachable,
|
|
477
|
+
placeholder: input.placeholder,
|
|
478
|
+
callbackUrl: input.callbackUrl
|
|
479
|
+
});
|
|
430
480
|
var isAffirmative = (answer) => isApproved({ status: "answered", type: "confirm", value: answer ?? null });
|
|
431
481
|
var describeAnswer = (type, result) => {
|
|
432
482
|
if (!result.answered) {
|
|
@@ -515,11 +565,12 @@ var createAdapterKernel = (helpersLabel) => {
|
|
|
515
565
|
...config.baseUrl ? { baseUrl: config.baseUrl } : {}
|
|
516
566
|
});
|
|
517
567
|
const requireExternalId = (externalId) => {
|
|
518
|
-
if (!externalId) {
|
|
568
|
+
if (!externalId?.trim()) {
|
|
519
569
|
throw new Error(
|
|
520
570
|
`Pushary: no end-user to ask. Pass { externalId } to ${helpersLabel}, or run user-scoped auth so the framework supplies one.`
|
|
521
571
|
);
|
|
522
572
|
}
|
|
573
|
+
if (externalId.length > 256) throw new Error("Pushary: externalId must not exceed 256 characters.");
|
|
523
574
|
return externalId;
|
|
524
575
|
};
|
|
525
576
|
const createGate = (config) => {
|
|
@@ -546,8 +597,12 @@ var createAdapterKernel = (helpersLabel) => {
|
|
|
546
597
|
}
|
|
547
598
|
};
|
|
548
599
|
return async (ask) => {
|
|
600
|
+
if (!ask.callId?.trim()) throw new Error("Pushary: a stable tool call ID is required to resolve an approval.");
|
|
601
|
+
requireExternalId(ask.externalId);
|
|
549
602
|
const facts = ask.parameters ?? deriveParameters(ask.input);
|
|
603
|
+
const idempotencyKey = decisionFingerprint({ ...ask, parameters: facts });
|
|
550
604
|
const evaluation = await evaluate(ask, facts);
|
|
605
|
+
const includeSubject = evaluation !== null || !usePolicy;
|
|
551
606
|
if (evaluation?.verdict === "allow") {
|
|
552
607
|
return {
|
|
553
608
|
approved: true,
|
|
@@ -573,6 +628,7 @@ var createAdapterKernel = (helpersLabel) => {
|
|
|
573
628
|
question: ask.question,
|
|
574
629
|
type: "confirm",
|
|
575
630
|
externalId: ask.externalId,
|
|
631
|
+
context: ask.context,
|
|
576
632
|
...config.agentName ? { agentName: config.agentName } : {},
|
|
577
633
|
...config.expiresInSeconds ? { expiresInSeconds: config.expiresInSeconds } : {},
|
|
578
634
|
...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {},
|
|
@@ -581,11 +637,8 @@ var createAdapterKernel = (helpersLabel) => {
|
|
|
581
637
|
// and keyed on it below; sending it is what lets the decision be grouped
|
|
582
638
|
// and matched later instead of surviving only as prose in the question.
|
|
583
639
|
toolName: ask.toolName,
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
// this ask would accept, and this path has to stay byte-for-byte the call
|
|
587
|
-
// that worked before policy existed.
|
|
588
|
-
...evaluation ? {
|
|
640
|
+
...includeSubject ? {
|
|
641
|
+
presentation: ask.presentation,
|
|
589
642
|
toolTarget: ask.toolTarget,
|
|
590
643
|
actor: ask.actor,
|
|
591
644
|
environment: ask.environment,
|
|
@@ -593,7 +646,7 @@ var createAdapterKernel = (helpersLabel) => {
|
|
|
593
646
|
} : {},
|
|
594
647
|
// Stable across replays, so a re-run of this call resolves to the same
|
|
595
648
|
// decision instead of asking twice.
|
|
596
|
-
idempotencyKey
|
|
649
|
+
idempotencyKey
|
|
597
650
|
});
|
|
598
651
|
if (result.approved) {
|
|
599
652
|
return {
|
|
@@ -602,10 +655,7 @@ var createAdapterKernel = (helpersLabel) => {
|
|
|
602
655
|
authorizationId: result.decisionId,
|
|
603
656
|
toolName: ask.toolName,
|
|
604
657
|
externalId: ask.externalId,
|
|
605
|
-
|
|
606
|
-
// the subject only when the evaluation carried it, so claiming it
|
|
607
|
-
// here regardless would describe an action the record does not hold.
|
|
608
|
-
...evaluation ? {
|
|
658
|
+
...includeSubject ? {
|
|
609
659
|
toolTarget: ask.toolTarget,
|
|
610
660
|
actor: ask.actor,
|
|
611
661
|
environment: ask.environment,
|
|
@@ -618,10 +668,11 @@ var createAdapterKernel = (helpersLabel) => {
|
|
|
618
668
|
};
|
|
619
669
|
};
|
|
620
670
|
const askExternalUser = (config, input) => client(config).decisions.ask({
|
|
671
|
+
...decisionSubject(input),
|
|
621
672
|
question: input.question,
|
|
622
673
|
type: input.type,
|
|
623
674
|
options: input.options,
|
|
624
|
-
externalId: input.externalId,
|
|
675
|
+
externalId: requireExternalId(input.externalId),
|
|
625
676
|
context: input.context,
|
|
626
677
|
agentName: config.agentName,
|
|
627
678
|
timeoutMs: config.timeoutMs,
|
|
@@ -629,19 +680,23 @@ var createAdapterKernel = (helpersLabel) => {
|
|
|
629
680
|
// sent: grouping every unnamed ask across every framework under one
|
|
630
681
|
// bucket would mine into a suggestion to auto-approve "ask-human", which
|
|
631
682
|
// spans unrelated questions.
|
|
632
|
-
toolName: input.node,
|
|
683
|
+
toolName: input.toolName ?? input.node,
|
|
633
684
|
idempotencyKey: idempotencyKeyFor(input)
|
|
634
685
|
});
|
|
635
686
|
const createDurableDecision = async (config, input) => {
|
|
687
|
+
if (!input.idempotencyKey?.trim()) {
|
|
688
|
+
throw new Error("Pushary: durable decisions require an idempotencyKey tied to the run and step.");
|
|
689
|
+
}
|
|
636
690
|
const created = await client(config).decisions.create({
|
|
691
|
+
...decisionSubject(input),
|
|
637
692
|
question: input.question,
|
|
638
693
|
type: input.type,
|
|
639
694
|
options: input.options,
|
|
640
|
-
externalId: input.externalId,
|
|
695
|
+
externalId: requireExternalId(input.externalId),
|
|
641
696
|
context: input.context,
|
|
642
697
|
callbackUrl: input.callbackUrl,
|
|
643
698
|
agentName: config.agentName,
|
|
644
|
-
toolName: input.node,
|
|
699
|
+
toolName: input.toolName ?? input.node,
|
|
645
700
|
idempotencyKey: idempotencyKeyFor(input),
|
|
646
701
|
wait: false
|
|
647
702
|
});
|
|
@@ -654,7 +709,7 @@ var createAdapterKernel = (helpersLabel) => {
|
|
|
654
709
|
deviceCount: created.deviceCount
|
|
655
710
|
};
|
|
656
711
|
};
|
|
657
|
-
const connect = (config, externalId) => client(config).enroll(externalId);
|
|
712
|
+
const connect = (config, externalId) => client(config).enroll(requireExternalId(externalId));
|
|
658
713
|
const protect = (config) => {
|
|
659
714
|
const gate = createGate(config);
|
|
660
715
|
const permitClient = client(config);
|
|
@@ -716,6 +771,7 @@ var createAdapterKernel = (helpersLabel) => {
|
|
|
716
771
|
UNBOUND_APPROVAL,
|
|
717
772
|
UNCONFIRMED_PERMIT,
|
|
718
773
|
createAdapterKernel,
|
|
774
|
+
decisionFingerprint,
|
|
719
775
|
defaultProtectQuestion,
|
|
720
776
|
deniedByPolicy,
|
|
721
777
|
deriveParameters,
|