@byokit/openclaw 0.11.1 → 0.12.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/CHANGELOG.md +16 -0
- package/README.md +38 -6
- package/SECURITY.md +3 -2
- package/dist/accounts.d.ts +15 -2
- package/dist/accounts.js +480 -162
- package/dist/activation.d.ts +17 -0
- package/dist/activation.js +9 -0
- package/dist/approvals.d.ts +2 -0
- package/dist/approvals.js +9 -9
- package/dist/attribution.d.ts +3 -0
- package/dist/attribution.js +25 -0
- package/dist/bridge.d.ts +3 -1
- package/dist/bridge.js +4 -6
- package/dist/config.js +10 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -0
- package/dist/device.d.ts +4 -4
- package/dist/device.js +11 -1
- package/dist/engine.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/keys.d.ts +19 -5
- package/dist/keys.js +135 -23
- package/dist/kit.d.ts +21 -2
- package/dist/kit.js +132 -29
- package/dist/link.js +21 -11
- package/dist/routes.d.ts +1 -0
- package/dist/routes.js +5 -3
- package/dist/routes.json +31 -31
- package/dist/runs.d.ts +1 -1
- package/dist/runs.js +32 -10
- package/dist/signin.d.ts +2 -0
- package/dist/signin.js +39 -15
- package/dist/testing/fake-gateway.d.ts +12 -0
- package/dist/testing/fake-gateway.js +124 -12
- package/dist/testing/model-stub.d.ts +1 -0
- package/dist/testing/model-stub.js +2 -1
- package/dist/words.json +8 -1
- package/package.json +1 -1
- package/plugin/accounts.js +12 -3
- package/plugin/catalog.js +48 -0
- package/plugin/index.js +2 -1
- package/plugin/keys.js +22 -4
package/dist/routes.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"prerequisite": null,
|
|
9
9
|
"offer": false,
|
|
10
10
|
"offerPolicy": "explicit",
|
|
11
|
-
"reason": "
|
|
11
|
+
"reason": "The pinned Gateway has no app-guided setup for this choice.",
|
|
12
12
|
"source": "extensions/alibaba/openclaw.plugin.json (2026.8.1)",
|
|
13
13
|
"revision": "2026.8.1",
|
|
14
14
|
"checked": "2026-10-02",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"id": "alibaba",
|
|
34
34
|
"method": "api-key",
|
|
35
35
|
"revision": "ea806575e6450e4d1efdfc72c19f04be982a1b9b",
|
|
36
|
-
"flow": "
|
|
36
|
+
"flow": "absent"
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
39
|
{
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
"prerequisite": null,
|
|
177
177
|
"offer": false,
|
|
178
178
|
"offerPolicy": "explicit",
|
|
179
|
-
"reason": "The pinned Gateway has no app-guided
|
|
179
|
+
"reason": "The pinned Gateway has no app-guided setup for this choice.",
|
|
180
180
|
"source": "extensions/anthropic/openclaw.plugin.json (2026.8.1)",
|
|
181
181
|
"revision": "2026.8.1",
|
|
182
182
|
"checked": "2026-10-02",
|
|
@@ -610,7 +610,7 @@
|
|
|
610
610
|
"prerequisite": null,
|
|
611
611
|
"offer": false,
|
|
612
612
|
"offerPolicy": "explicit",
|
|
613
|
-
"reason": "
|
|
613
|
+
"reason": "The pinned Gateway has no app-guided setup for this choice.",
|
|
614
614
|
"source": "extensions/clawrouter/openclaw.plugin.json (2026.8.1)",
|
|
615
615
|
"revision": "2026.8.1",
|
|
616
616
|
"checked": "2026-10-02",
|
|
@@ -635,7 +635,7 @@
|
|
|
635
635
|
"id": "clawrouter",
|
|
636
636
|
"method": "api-key",
|
|
637
637
|
"revision": "ea806575e6450e4d1efdfc72c19f04be982a1b9b",
|
|
638
|
-
"flow": "
|
|
638
|
+
"flow": "absent"
|
|
639
639
|
}
|
|
640
640
|
},
|
|
641
641
|
{
|
|
@@ -647,7 +647,7 @@
|
|
|
647
647
|
"prerequisite": null,
|
|
648
648
|
"offer": false,
|
|
649
649
|
"offerPolicy": "explicit",
|
|
650
|
-
"reason": "
|
|
650
|
+
"reason": "The pinned Gateway has no app-guided setup for this choice.",
|
|
651
651
|
"source": "extensions/cloudflare-ai-gateway/openclaw.plugin.json (2026.8.1)",
|
|
652
652
|
"revision": "2026.8.1",
|
|
653
653
|
"checked": "2026-10-02",
|
|
@@ -681,7 +681,7 @@
|
|
|
681
681
|
"id": "cloudflare-ai-gateway",
|
|
682
682
|
"method": "api-key",
|
|
683
683
|
"revision": "ea806575e6450e4d1efdfc72c19f04be982a1b9b",
|
|
684
|
-
"flow": "
|
|
684
|
+
"flow": "absent"
|
|
685
685
|
}
|
|
686
686
|
},
|
|
687
687
|
{
|
|
@@ -739,7 +739,7 @@
|
|
|
739
739
|
"prerequisite": null,
|
|
740
740
|
"offer": false,
|
|
741
741
|
"offerPolicy": "explicit",
|
|
742
|
-
"reason": "
|
|
742
|
+
"reason": "The pinned Gateway has no app-guided setup for this choice.",
|
|
743
743
|
"source": "extensions/comfy/openclaw.plugin.json (2026.8.1)",
|
|
744
744
|
"revision": "2026.8.1",
|
|
745
745
|
"checked": "2026-10-02",
|
|
@@ -773,7 +773,7 @@
|
|
|
773
773
|
"id": "comfy",
|
|
774
774
|
"method": "cloud-api-key",
|
|
775
775
|
"revision": "ea806575e6450e4d1efdfc72c19f04be982a1b9b",
|
|
776
|
-
"flow": "
|
|
776
|
+
"flow": "absent"
|
|
777
777
|
}
|
|
778
778
|
},
|
|
779
779
|
{
|
|
@@ -785,7 +785,7 @@
|
|
|
785
785
|
"prerequisite": null,
|
|
786
786
|
"offer": false,
|
|
787
787
|
"offerPolicy": "explicit",
|
|
788
|
-
"reason": "
|
|
788
|
+
"reason": "The pinned Gateway has no app-guided setup for this choice.",
|
|
789
789
|
"source": "extensions/copilot-proxy/openclaw.plugin.json (2026.8.1)",
|
|
790
790
|
"revision": "2026.8.1",
|
|
791
791
|
"checked": "2026-10-02",
|
|
@@ -807,7 +807,7 @@
|
|
|
807
807
|
"id": "copilot-proxy",
|
|
808
808
|
"method": "local",
|
|
809
809
|
"revision": "ea806575e6450e4d1efdfc72c19f04be982a1b9b",
|
|
810
|
-
"flow": "
|
|
810
|
+
"flow": "absent"
|
|
811
811
|
}
|
|
812
812
|
},
|
|
813
813
|
{
|
|
@@ -911,7 +911,7 @@
|
|
|
911
911
|
"prerequisite": null,
|
|
912
912
|
"offer": false,
|
|
913
913
|
"offerPolicy": "explicit",
|
|
914
|
-
"reason": "
|
|
914
|
+
"reason": "The pinned Gateway has no app-guided setup for this choice.",
|
|
915
915
|
"source": "extensions/fal/openclaw.plugin.json (2026.8.1)",
|
|
916
916
|
"revision": "2026.8.1",
|
|
917
917
|
"checked": "2026-10-02",
|
|
@@ -936,7 +936,7 @@
|
|
|
936
936
|
"id": "fal",
|
|
937
937
|
"method": "api-key",
|
|
938
938
|
"revision": "ea806575e6450e4d1efdfc72c19f04be982a1b9b",
|
|
939
|
-
"flow": "
|
|
939
|
+
"flow": "absent"
|
|
940
940
|
}
|
|
941
941
|
},
|
|
942
942
|
{
|
|
@@ -1585,7 +1585,7 @@
|
|
|
1585
1585
|
"prerequisite": null,
|
|
1586
1586
|
"offer": false,
|
|
1587
1587
|
"offerPolicy": "explicit",
|
|
1588
|
-
"reason": "
|
|
1588
|
+
"reason": "The pinned Gateway has no app-guided setup for this choice.",
|
|
1589
1589
|
"source": "extensions/longcat/openclaw.plugin.json (2026.8.1)",
|
|
1590
1590
|
"revision": "2026.8.1",
|
|
1591
1591
|
"checked": "2026-10-02",
|
|
@@ -1619,7 +1619,7 @@
|
|
|
1619
1619
|
"id": "longcat",
|
|
1620
1620
|
"method": "api-key",
|
|
1621
1621
|
"revision": "ea806575e6450e4d1efdfc72c19f04be982a1b9b",
|
|
1622
|
-
"flow": "
|
|
1622
|
+
"flow": "absent"
|
|
1623
1623
|
}
|
|
1624
1624
|
},
|
|
1625
1625
|
{
|
|
@@ -1677,7 +1677,7 @@
|
|
|
1677
1677
|
"prerequisite": null,
|
|
1678
1678
|
"offer": false,
|
|
1679
1679
|
"offerPolicy": "explicit",
|
|
1680
|
-
"reason": "
|
|
1680
|
+
"reason": "The pinned Gateway has no app-guided setup for this choice.",
|
|
1681
1681
|
"source": "extensions/microsoft-foundry/openclaw.plugin.json (2026.8.1)",
|
|
1682
1682
|
"revision": "2026.8.1",
|
|
1683
1683
|
"checked": "2026-10-02",
|
|
@@ -1699,7 +1699,7 @@
|
|
|
1699
1699
|
"id": "microsoft-foundry",
|
|
1700
1700
|
"method": "entra-id",
|
|
1701
1701
|
"revision": "ea806575e6450e4d1efdfc72c19f04be982a1b9b",
|
|
1702
|
-
"flow": "
|
|
1702
|
+
"flow": "absent"
|
|
1703
1703
|
}
|
|
1704
1704
|
},
|
|
1705
1705
|
{
|
|
@@ -1711,7 +1711,7 @@
|
|
|
1711
1711
|
"prerequisite": null,
|
|
1712
1712
|
"offer": false,
|
|
1713
1713
|
"offerPolicy": "explicit",
|
|
1714
|
-
"reason": "
|
|
1714
|
+
"reason": "The pinned Gateway has no app-guided setup for this choice.",
|
|
1715
1715
|
"source": "extensions/microsoft-foundry/openclaw.plugin.json (2026.8.1)",
|
|
1716
1716
|
"revision": "2026.8.1",
|
|
1717
1717
|
"checked": "2026-10-02",
|
|
@@ -1736,7 +1736,7 @@
|
|
|
1736
1736
|
"id": "microsoft-foundry",
|
|
1737
1737
|
"method": "api-key",
|
|
1738
1738
|
"revision": "ea806575e6450e4d1efdfc72c19f04be982a1b9b",
|
|
1739
|
-
"flow": "
|
|
1739
|
+
"flow": "absent"
|
|
1740
1740
|
}
|
|
1741
1741
|
},
|
|
1742
1742
|
{
|
|
@@ -2458,7 +2458,7 @@
|
|
|
2458
2458
|
"prerequisite": null,
|
|
2459
2459
|
"offer": false,
|
|
2460
2460
|
"offerPolicy": "explicit",
|
|
2461
|
-
"reason": "
|
|
2461
|
+
"reason": "The pinned Gateway has no app-guided setup for this choice.",
|
|
2462
2462
|
"source": "extensions/pixverse/openclaw.plugin.json (2026.8.1)",
|
|
2463
2463
|
"revision": "2026.8.1",
|
|
2464
2464
|
"checked": "2026-10-02",
|
|
@@ -2492,7 +2492,7 @@
|
|
|
2492
2492
|
"id": "pixverse",
|
|
2493
2493
|
"method": "api-key",
|
|
2494
2494
|
"revision": "ea806575e6450e4d1efdfc72c19f04be982a1b9b",
|
|
2495
|
-
"flow": "
|
|
2495
|
+
"flow": "absent"
|
|
2496
2496
|
}
|
|
2497
2497
|
},
|
|
2498
2498
|
{
|
|
@@ -2838,7 +2838,7 @@
|
|
|
2838
2838
|
"prerequisite": null,
|
|
2839
2839
|
"offer": false,
|
|
2840
2840
|
"offerPolicy": "explicit",
|
|
2841
|
-
"reason": "
|
|
2841
|
+
"reason": "The pinned Gateway has no app-guided setup for this choice.",
|
|
2842
2842
|
"source": "extensions/runway/openclaw.plugin.json (2026.8.1)",
|
|
2843
2843
|
"revision": "2026.8.1",
|
|
2844
2844
|
"checked": "2026-10-02",
|
|
@@ -2863,7 +2863,7 @@
|
|
|
2863
2863
|
"id": "runway",
|
|
2864
2864
|
"method": "api-key",
|
|
2865
2865
|
"revision": "ea806575e6450e4d1efdfc72c19f04be982a1b9b",
|
|
2866
|
-
"flow": "
|
|
2866
|
+
"flow": "absent"
|
|
2867
2867
|
}
|
|
2868
2868
|
},
|
|
2869
2869
|
{
|
|
@@ -2875,7 +2875,7 @@
|
|
|
2875
2875
|
"prerequisite": null,
|
|
2876
2876
|
"offer": false,
|
|
2877
2877
|
"offerPolicy": "explicit",
|
|
2878
|
-
"reason": "
|
|
2878
|
+
"reason": "The pinned Gateway has no app-guided setup for this choice.",
|
|
2879
2879
|
"source": "extensions/sglang/openclaw.plugin.json (2026.8.1)",
|
|
2880
2880
|
"revision": "2026.8.1",
|
|
2881
2881
|
"checked": "2026-10-02",
|
|
@@ -2897,7 +2897,7 @@
|
|
|
2897
2897
|
"id": "sglang",
|
|
2898
2898
|
"method": "custom",
|
|
2899
2899
|
"revision": "ea806575e6450e4d1efdfc72c19f04be982a1b9b",
|
|
2900
|
-
"flow": "
|
|
2900
|
+
"flow": "absent"
|
|
2901
2901
|
}
|
|
2902
2902
|
},
|
|
2903
2903
|
{
|
|
@@ -3360,7 +3360,7 @@
|
|
|
3360
3360
|
"prerequisite": null,
|
|
3361
3361
|
"offer": false,
|
|
3362
3362
|
"offerPolicy": "explicit",
|
|
3363
|
-
"reason": "
|
|
3363
|
+
"reason": "The pinned Gateway has no app-guided setup for this choice.",
|
|
3364
3364
|
"source": "extensions/vllm/openclaw.plugin.json (2026.8.1)",
|
|
3365
3365
|
"revision": "2026.8.1",
|
|
3366
3366
|
"checked": "2026-10-02",
|
|
@@ -3382,7 +3382,7 @@
|
|
|
3382
3382
|
"id": "vllm",
|
|
3383
3383
|
"method": "custom",
|
|
3384
3384
|
"revision": "ea806575e6450e4d1efdfc72c19f04be982a1b9b",
|
|
3385
|
-
"flow": "
|
|
3385
|
+
"flow": "absent"
|
|
3386
3386
|
}
|
|
3387
3387
|
},
|
|
3388
3388
|
{
|
|
@@ -3440,7 +3440,7 @@
|
|
|
3440
3440
|
"prerequisite": null,
|
|
3441
3441
|
"offer": false,
|
|
3442
3442
|
"offerPolicy": "explicit",
|
|
3443
|
-
"reason": "
|
|
3443
|
+
"reason": "The pinned Gateway has no app-guided setup for this choice.",
|
|
3444
3444
|
"source": "extensions/vydra/openclaw.plugin.json (2026.8.1)",
|
|
3445
3445
|
"revision": "2026.8.1",
|
|
3446
3446
|
"checked": "2026-10-02",
|
|
@@ -3474,7 +3474,7 @@
|
|
|
3474
3474
|
"id": "vydra",
|
|
3475
3475
|
"method": "api-key",
|
|
3476
3476
|
"revision": "ea806575e6450e4d1efdfc72c19f04be982a1b9b",
|
|
3477
|
-
"flow": "
|
|
3477
|
+
"flow": "absent"
|
|
3478
3478
|
}
|
|
3479
3479
|
},
|
|
3480
3480
|
{
|
|
@@ -4005,7 +4005,7 @@
|
|
|
4005
4005
|
"prerequisite": null,
|
|
4006
4006
|
"offer": false,
|
|
4007
4007
|
"offerPolicy": "explicit",
|
|
4008
|
-
"reason": "
|
|
4008
|
+
"reason": "The pinned Gateway has no app-guided setup for this choice.",
|
|
4009
4009
|
"source": "src/commands/auth-choice-options.static.ts (2026.8.1)",
|
|
4010
4010
|
"revision": "2026.8.1",
|
|
4011
4011
|
"checked": "2026-10-02",
|
|
@@ -4030,7 +4030,7 @@
|
|
|
4030
4030
|
"id": "custom",
|
|
4031
4031
|
"method": "custom",
|
|
4032
4032
|
"revision": "ea806575e6450e4d1efdfc72c19f04be982a1b9b",
|
|
4033
|
-
"flow": "
|
|
4033
|
+
"flow": "absent"
|
|
4034
4034
|
}
|
|
4035
4035
|
}
|
|
4036
4036
|
]
|
package/dist/runs.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare function createRuns(ctx: {
|
|
|
20
20
|
}>;
|
|
21
21
|
bridge: Pick<Bridge, 'register'>;
|
|
22
22
|
tools: ReadonlySet<string>;
|
|
23
|
-
accounts?: Pick<ReturnType<typeof createAccounts>, 'pick' | 'rest'>;
|
|
23
|
+
accounts?: Pick<ReturnType<typeof createAccounts>, 'bind' | 'keyRefusal' | 'pick' | 'rest'>;
|
|
24
24
|
locks?: ReturnType<typeof createLocks>;
|
|
25
25
|
routes: Map<string, {
|
|
26
26
|
session: string;
|
package/dist/runs.js
CHANGED
|
@@ -90,8 +90,9 @@ export function createRuns(ctx) {
|
|
|
90
90
|
return { ok: false, ...classified, message };
|
|
91
91
|
};
|
|
92
92
|
const run = async (spec, on, keyAgent, preparedOutput) => {
|
|
93
|
-
// Member boundary first: a member never speaks in another member's session, refused before any request.
|
|
94
|
-
|
|
93
|
+
// Member boundary first: a member never speaks in another member's session, refused before any request. A key
|
|
94
|
+
// of one of the member's account agents is the member's too; binding below checks the index names it.
|
|
95
|
+
if (!spec.sessionKey.startsWith(`agent:${spec.member}:`) && !(ctx.accounts && !keyAgent && spec.sessionKey.startsWith(`agent:${spec.member}--`)))
|
|
95
96
|
throw new Error(`refused: "${spec.sessionKey}" is not a session of member "${spec.member}"`);
|
|
96
97
|
// Same validation as the pin's NonEmptyString; preserve the caller's bytes (including whitespace).
|
|
97
98
|
if (spec.idempotencyKey !== undefined && (typeof spec.idempotencyKey !== 'string' || !spec.idempotencyKey.length))
|
|
@@ -105,11 +106,29 @@ export function createRuns(ctx) {
|
|
|
105
106
|
for (const tool of spec.tools ?? [])
|
|
106
107
|
if (!ctx.tools.has(tool))
|
|
107
108
|
throw new Error(`refused: "${tool}" is not one of this kit's tools`);
|
|
108
|
-
const tail = spec.sessionKey.slice(
|
|
109
|
-
// Runs step
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
const tail = spec.sessionKey.slice(spec.sessionKey.indexOf(':', 'agent:'.length) + 1);
|
|
110
|
+
// Runs step 1 (5.15): every run first finds the account its session is bound to, the API-key lane included.
|
|
111
|
+
const binding = ctx.accounts ? await ctx.accounts.bind(spec.member, spec.sessionKey) : undefined;
|
|
112
|
+
// Step 2 on the API-key lane: only an API-billed conversation continues on its key; any other binding is refused.
|
|
113
|
+
if (keyAgent && ctx.accounts && binding) {
|
|
114
|
+
const message = await ctx.accounts.keyRefusal(spec.member, binding);
|
|
115
|
+
if (message !== undefined)
|
|
116
|
+
return { ok: false, kind: 'other', message };
|
|
117
|
+
}
|
|
118
|
+
// A run that waited for its agent's lock across a move (5.15) binds again under it: a changed binding means its
|
|
119
|
+
// session went to another account, so it ends before any event and the caller's retry lands there.
|
|
120
|
+
const moved = async () => !!binding && (await ctx.accounts.bind(spec.member, spec.sessionKey))?.agentId !== binding.agentId;
|
|
121
|
+
const movedEnd = { ok: false, kind: 'other', message: words('run.moved') };
|
|
122
|
+
// Step 3: with `account` and `needs` both absent, on a key no account agent holds (or an API-key run, whose
|
|
123
|
+
// binding is API-billed), exactly the run on the agent it always was (a bound key under its shared lock).
|
|
124
|
+
if ((keyAgent || binding?.id === undefined) && spec.account === undefined && !spec.needs?.length) {
|
|
125
|
+
if (keyAgent)
|
|
126
|
+
return start(spec, on, { agentId: keyAgent, sessionKey: `agent:${keyAgent}:${tail}`, idempotencyKey, system, picked, output });
|
|
127
|
+
const { agentId } = await ctx.ensure(spec.member);
|
|
128
|
+
const member = () => start(spec, on, { agentId, sessionKey: spec.sessionKey, idempotencyKey, system, picked, output });
|
|
129
|
+
if (!binding || !ctx.locks)
|
|
130
|
+
return member();
|
|
131
|
+
return ctx.locks.shared(agentId, async () => await moved() ? movedEnd : member());
|
|
113
132
|
}
|
|
114
133
|
if (keyAgent)
|
|
115
134
|
throw new Error('refused: an API-key run uses its own account');
|
|
@@ -125,9 +144,10 @@ export function createRuns(ctx) {
|
|
|
125
144
|
const signedIn = async (agentId, provider) => !!(await ctx.request('byokit.accounts', { action: 'ready', agentId, provider })).ok;
|
|
126
145
|
const skipped = [];
|
|
127
146
|
for (;;) {
|
|
128
|
-
//
|
|
147
|
+
// Steps 2 and 4: the selection is resolved once per attempt, before any request, narrowed by the binding; `needs`
|
|
148
|
+
// alone resolves as 'default'. A refusal (`bound`, `paid`) ends the run with its words before any room, model or agent request.
|
|
129
149
|
const { pick, agent } = await accounts.pick(spec.member, { account: spec.account ?? 'default',
|
|
130
|
-
...(spec.model !== undefined ? { model: spec.model } : {}), ...(spec.needs ? { needs: spec.needs } : {}) }, skipped);
|
|
150
|
+
...(spec.model !== undefined ? { model: spec.model } : {}), ...(spec.needs ? { needs: spec.needs } : {}) }, skipped, binding);
|
|
131
151
|
if (!pick.ok) {
|
|
132
152
|
if (pick.code === 'unknown_account')
|
|
133
153
|
throw new Error(`unknown account: ${spec.account}`); // a caller bug
|
|
@@ -137,10 +157,12 @@ export function createRuns(ctx) {
|
|
|
137
157
|
}
|
|
138
158
|
const sessionKey = `agent:${agent}:${tail}`;
|
|
139
159
|
const automatic = pick.how === 'auto' || pick.why === 'first_ready';
|
|
140
|
-
//
|
|
160
|
+
// The run goes to the picked account's agent, under the key's tail. The run holds the agent's shared
|
|
141
161
|
// lock from its ready check to its end. An automatic pick not signed in is skipped before any event; an explicit
|
|
142
162
|
// one announces itself, then ends signed-out.
|
|
143
163
|
const end = await locks.shared(agent, async () => {
|
|
164
|
+
if (await moved())
|
|
165
|
+
return movedEnd;
|
|
144
166
|
let ready;
|
|
145
167
|
try {
|
|
146
168
|
ready = await signedIn(agent, pick.account.provider);
|
package/dist/signin.d.ts
CHANGED
package/dist/signin.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// sign-in; OAuth credentials stay in the engine/CLI. Explicit API keys go only to engine activation (D11).
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { createServer } from 'node:http';
|
|
5
|
+
import { activate, LATE_RESTART } from "./activation.js";
|
|
5
6
|
import { authStatus } from "./auth-status.js";
|
|
7
|
+
import { routes } from "./routes.js";
|
|
6
8
|
import { signedInProviders } from "./runs.js";
|
|
7
9
|
import { words } from "./words.js";
|
|
8
10
|
// The pin's wizard contract (2026.8.1): steps arrive only from `wizard.next` (wizard.status answers {status, error}
|
|
@@ -15,6 +17,8 @@ const CANCEL_MS = 10_000;
|
|
|
15
17
|
const RECONFIRM_MS = 60_000;
|
|
16
18
|
const TURNS = 200;
|
|
17
19
|
const PASTE_MS = 15 * 60_000;
|
|
20
|
+
const KEY_UNAVAILABLE = 'This key type is not available on this engine.';
|
|
21
|
+
const KEY_FAILED = 'API key activation failed';
|
|
18
22
|
// Two of the pin's code routes print their code only into a note's text ("Code: X" with
|
|
19
23
|
// "Code expires in N minutes", or "enter the code X." with "Expires at: <ISO>"), never as `deviceCode`.
|
|
20
24
|
// Read it back so every code route reaches the caller in the one structured shape.
|
|
@@ -58,6 +62,7 @@ export function signIn(ctx, member, o, on) {
|
|
|
58
62
|
let settleStart;
|
|
59
63
|
const startingSettled = new Promise((resolve) => { settleStart = resolve; });
|
|
60
64
|
let over = false;
|
|
65
|
+
let activating = false;
|
|
61
66
|
let recovering = false;
|
|
62
67
|
let offDisconnect;
|
|
63
68
|
let agentId;
|
|
@@ -92,13 +97,21 @@ export function signIn(ctx, member, o, on) {
|
|
|
92
97
|
let pasteIn;
|
|
93
98
|
let returned;
|
|
94
99
|
// Once a secret step is seen, no later gateway prose, link or code is safe to display.
|
|
95
|
-
let sensitive = o.authChoice === 'setup-token'
|
|
96
|
-
const secretFailure =
|
|
100
|
+
let sensitive = o.authChoice === 'setup-token';
|
|
101
|
+
const secretFailure = 'Sign-in failed. Try again.';
|
|
102
|
+
// A pasted key is scrubbed from every failure on top of the engine's own redaction.
|
|
103
|
+
const keyRoute = routes().find((r) => r.choice === o.authChoice && r.keyEntry);
|
|
104
|
+
let secret;
|
|
105
|
+
const clean = (value) => {
|
|
106
|
+
const text = value instanceof Error ? value.message : String(value);
|
|
107
|
+
return cut(secret ? text.split(secret).join('…') : text);
|
|
108
|
+
};
|
|
109
|
+
const keyFailed = (status) => status === 'unavailable' ? KEY_UNAVAILABLE : KEY_FAILED;
|
|
97
110
|
const done = new Promise((resolve) => { settle = resolve; });
|
|
98
111
|
const say = (v) => {
|
|
99
112
|
if (!over && !recovering && !signal.aborted)
|
|
100
113
|
on({ state: 'waiting', via, ...(sensitive
|
|
101
|
-
? { ...(v.prompt ? { prompt: v.prompt } : {}), ...(v.error ? { error: secretFailure
|
|
114
|
+
? { ...(v.prompt ? { prompt: v.prompt } : {}), ...(v.error ? { error: secretFailure } : {}) }
|
|
102
115
|
: v) });
|
|
103
116
|
};
|
|
104
117
|
const closeCallback = async () => {
|
|
@@ -119,7 +132,7 @@ export function signIn(ctx, member, o, on) {
|
|
|
119
132
|
pasteIn = undefined;
|
|
120
133
|
await closeCallback();
|
|
121
134
|
if (sensitive && view.state === 'failed' && view.why === 'failed')
|
|
122
|
-
view = { ...view, error: secretFailure
|
|
135
|
+
view = { ...view, error: secretFailure };
|
|
123
136
|
on(view);
|
|
124
137
|
settle(view);
|
|
125
138
|
};
|
|
@@ -144,11 +157,13 @@ export function signIn(ctx, member, o, on) {
|
|
|
144
157
|
};
|
|
145
158
|
const cancel = () => {
|
|
146
159
|
// Stop the drive now; the start itself is never aborted, and release waits for it to settle before cancelling,
|
|
147
|
-
// so the gateway's one setup admission is freed by a cancel that the engine can actually find.
|
|
160
|
+
// so the gateway's one setup admission is freed by a cancel that the engine can actually find. An activation in
|
|
161
|
+
// flight is not finished here: the drive returns once the engine answers, so cleanup follows the engine's answer.
|
|
148
162
|
if (signal.aborted)
|
|
149
163
|
return;
|
|
150
164
|
owner.abort();
|
|
151
|
-
void (async () => { await release();
|
|
165
|
+
void (async () => { await release(); if (!activating)
|
|
166
|
+
await finish(cancelled()); })();
|
|
152
167
|
};
|
|
153
168
|
o.signal?.addEventListener('abort', cancel, { once: true });
|
|
154
169
|
if (o.signal?.aborted)
|
|
@@ -198,7 +213,9 @@ export function signIn(ctx, member, o, on) {
|
|
|
198
213
|
}
|
|
199
214
|
// Native login is completed by the person in unmodified Claude Code. The pin does not expose this
|
|
200
215
|
// choice via auth.start; activation verifies the CLI route and persists it only after a live test succeeds.
|
|
201
|
-
|
|
216
|
+
// A key-entry choice (API key or plan key) is not an auth.start login either: the pin's Gateway sets it up only
|
|
217
|
+
// from the pasted key through typed activation, and refuses it there ("provider setup is not available").
|
|
218
|
+
if (o.authChoice === 'anthropic-cli' || keyRoute) {
|
|
202
219
|
settleStart();
|
|
203
220
|
let apiKey;
|
|
204
221
|
if (o.authChoice === 'anthropic-cli') {
|
|
@@ -207,7 +224,7 @@ export function signIn(ctx, member, o, on) {
|
|
|
207
224
|
return { state: 'failed', via, why: 'failed', error: 'Sign in with Claude Code on this machine in the kit’s isolated HOME, then try again. The login stays in Claude Code.' };
|
|
208
225
|
}
|
|
209
226
|
else {
|
|
210
|
-
say({ prompt: 'API key (billed per use)' });
|
|
227
|
+
say({ prompt: keyRoute?.billing === 'api' ? 'API key (billed per use)' : 'Plan key' });
|
|
211
228
|
apiKey = returned ?? await new Promise((resolve) => {
|
|
212
229
|
const stop = (value) => { clearTimeout(timer); signal.removeEventListener('abort', abort); pasteIn = undefined; resolve(value); };
|
|
213
230
|
const abort = () => stop();
|
|
@@ -220,16 +237,23 @@ export function signIn(ctx, member, o, on) {
|
|
|
220
237
|
returned = undefined;
|
|
221
238
|
if (!apiKey)
|
|
222
239
|
return { state: 'failed', via, why: signal.aborted ? 'declined' : 'expired' };
|
|
240
|
+
secret = apiKey.trim() || undefined;
|
|
223
241
|
}
|
|
224
242
|
if (signal.aborted)
|
|
225
243
|
return { state: 'failed', via, why: 'declined' };
|
|
226
|
-
|
|
244
|
+
// Done waits for the gateway restart an `auth` change causes (activation.ts); a run started before it runs on the
|
|
245
|
+
// old config. A restart that runs late or is cancelled ends failed, and the account is cleaned up by the caller.
|
|
246
|
+
activating = true;
|
|
247
|
+
const result = await activate(ctx, {
|
|
227
248
|
agentId, kind: apiKey === undefined ? 'claude-cli' : 'api-key',
|
|
228
|
-
...(apiKey === undefined ? {} : { authChoice:
|
|
229
|
-
}, { timeoutMs: PULL_MS, signal });
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
249
|
+
...(apiKey === undefined ? {} : { authChoice: o.authChoice, apiKey }),
|
|
250
|
+
}, { timeoutMs: PULL_MS, signal }).finally(() => { activating = false; });
|
|
251
|
+
if (result.late)
|
|
252
|
+
return signal.aborted ? cancelled() : { state: 'failed', via, why: 'failed', error: LATE_RESTART };
|
|
253
|
+
if (!result.ok)
|
|
254
|
+
return { state: 'failed', via, why: 'failed',
|
|
255
|
+
error: apiKey === undefined ? clean(result.error ?? 'Claude Code activation failed') : keyFailed(result.status) };
|
|
256
|
+
return signal.aborted ? cancelled() : { state: 'done', via };
|
|
233
257
|
}
|
|
234
258
|
// The client chooses the wizard id; cancellation waits for the engine to register it.
|
|
235
259
|
sessionId = `byokit-${randomUUID()}`;
|
|
@@ -428,7 +452,7 @@ export function signIn(ctx, member, o, on) {
|
|
|
428
452
|
else if (BUSY.test(cut(error)))
|
|
429
453
|
view = { state: 'failed', via, why: 'busy' };
|
|
430
454
|
else
|
|
431
|
-
view = { state: 'failed', via, why: 'failed', error:
|
|
455
|
+
view = { state: 'failed', via, why: 'failed', error: keyRoute ? KEY_FAILED : clean(error) };
|
|
432
456
|
}
|
|
433
457
|
settleStart();
|
|
434
458
|
if (view.state !== 'done')
|
|
@@ -26,6 +26,18 @@ export type FakeGateway = {
|
|
|
26
26
|
usedPercent?: number;
|
|
27
27
|
resetAt?: number;
|
|
28
28
|
}[]): void;
|
|
29
|
+
/** Profile fields every later wizard sign-in stores (O14 identity): `accountId: undefined` signs in with no account
|
|
30
|
+
* id, so no `who`. With no call, or `signInAs()`, the account id is `fake-<agentId>`. */
|
|
31
|
+
signInAs(fields?: {
|
|
32
|
+
accountId?: string;
|
|
33
|
+
email?: string;
|
|
34
|
+
expires?: number;
|
|
35
|
+
}): void;
|
|
36
|
+
/** A sealed profile already in an agent's store, as a sign-in of another provider leaves it; the agent's model points at it. */
|
|
37
|
+
seedProfile(agentId: string, profileId: string, profile: {
|
|
38
|
+
provider: string;
|
|
39
|
+
type: string;
|
|
40
|
+
} & Record<string, unknown>): void;
|
|
29
41
|
} & {
|
|
30
42
|
transport: GatewayTransport;
|
|
31
43
|
};
|