@rebon/cli-win32-x64 1.2.0 → 1.4.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.
Files changed (98) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +10 -0
  3. package/README.md +203 -144
  4. package/THIRD_PARTY_NOTICES.txt +1232 -128
  5. package/bin/rebon.js +22 -22
  6. package/lib/windows-managed.js +390 -390
  7. package/package.json +9 -2
  8. package/payload/compose-runtime/package.json +17 -17
  9. package/payload/compose-runtime/payload/compose/shims/dsh-anonymous-user-id.js +10 -10
  10. package/payload/compose-runtime/payload/compose/shims/dsh-credentials.js +13 -13
  11. package/payload/compose-runtime/payload/compose/shims/dsh-launch-environment.js +21 -21
  12. package/payload/compose-runtime/payload/compose/shims/dsh-llm.js +313 -313
  13. package/payload/compose-runtime/payload/compose/shims/dsh-settings.js +43 -43
  14. package/payload/compose-runtime/payload/compose/shims/dsh-tools.js +20 -20
  15. package/payload/compose-runtime/payload/compose/shims/dsh-web.js +10 -10
  16. package/payload/compose-runtime/payload/compose/shims/llm-adapter.js +32 -32
  17. package/payload/compose-runtime/payload/compose/shims/zod-lite.js +49 -49
  18. package/payload/compose-runtime/payload/vendor/cordis/LICENSE +21 -0
  19. package/payload/compose-runtime/payload/vendor/cordis/index.js +1530 -1530
  20. package/payload/compose-runtime/payload/vendor/cosmokit/LICENSE +21 -21
  21. package/payload/compose-runtime/payload/vendor/cosmokit/index.mjs +357 -357
  22. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-agent +21 -21
  23. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-agent-loop +21 -21
  24. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-llm-core +21 -21
  25. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-llm-deepseek +21 -21
  26. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-logger-console +21 -21
  27. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-scope +21 -21
  28. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-session +21 -21
  29. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-system-prompt +21 -21
  30. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-timeout +21 -21
  31. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-timer +21 -21
  32. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tool-todo +21 -21
  33. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tool-web +21 -21
  34. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tools-schema +21 -21
  35. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-turndown +21 -0
  36. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-turndown-plugin-gfm +21 -0
  37. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-web-search-exa +21 -21
  38. package/payload/compose-runtime/payload/vendor/dsh/agent-loop.js +1193 -1193
  39. package/payload/compose-runtime/payload/vendor/dsh/agent.js +714 -714
  40. package/payload/compose-runtime/payload/vendor/dsh/llm-core.js +269 -269
  41. package/payload/compose-runtime/payload/vendor/dsh/llm-deepseek.js +672 -672
  42. package/payload/compose-runtime/payload/vendor/dsh/logger-console.js +83 -83
  43. package/payload/compose-runtime/payload/vendor/dsh/scope.js +287 -287
  44. package/payload/compose-runtime/payload/vendor/dsh/session.js +1668 -1668
  45. package/payload/compose-runtime/payload/vendor/dsh/system-prompt.js +309 -309
  46. package/payload/compose-runtime/payload/vendor/dsh/timeout.js +100 -100
  47. package/payload/compose-runtime/payload/vendor/dsh/timer.js +128 -128
  48. package/payload/compose-runtime/payload/vendor/dsh/tool-todo.js +143 -143
  49. package/payload/compose-runtime/payload/vendor/dsh/tool-web.js +1527 -1527
  50. package/payload/compose-runtime/payload/vendor/dsh/tools-schema.js +849 -849
  51. package/payload/compose-runtime/payload/vendor/dsh/web-search-exa.js +122 -122
  52. package/payload/compose-runtime/payload/vendor/eventsource-parser/LICENSE +21 -21
  53. package/payload/compose-runtime/payload/vendor/eventsource-parser/index.js +177 -177
  54. package/payload/compose-runtime/payload/vendor/eventsource-parser/stream.js +48 -48
  55. package/payload/compose-runtime/payload/vendor/schemastery/LICENSE +21 -0
  56. package/payload/compose-runtime/payload/vendor/schemastery/index.mjs +656 -656
  57. package/payload/compose-runtime/payload-manifests.json +48 -48
  58. package/payload/compose-runtime/src/bridge.mjs +193 -195
  59. package/payload/compose-runtime/src/credentials-runtime.mjs +50 -50
  60. package/payload/compose-runtime/src/eventsource-stream.mjs +48 -48
  61. package/payload/compose-runtime/src/index.mjs +7 -7
  62. package/payload/compose-runtime/src/llm-runtime.mjs +326 -326
  63. package/payload/compose-runtime/src/loader.mjs +78 -78
  64. package/payload/compose-runtime/src/loop-assembly.mjs +237 -237
  65. package/payload/compose-runtime/src/payload.mjs +49 -49
  66. package/payload/compose-runtime/src/plugin.mjs +59 -59
  67. package/payload/compose-runtime/src/realm.mjs +223 -223
  68. package/payload/compose-runtime/src/registry.mjs +191 -191
  69. package/payload/compose-runtime/src/resolve.mjs +115 -115
  70. package/payload/compose-runtime/src/systemprompt-runtime.mjs +67 -67
  71. package/payload/compose-runtime/src/tools-runtime.mjs +239 -239
  72. package/payload/compose-runtime/src/web-runtime.mjs +209 -209
  73. package/payload/plugin-host/package.json +11 -11
  74. package/payload/plugin-host/src/bridge.mjs +64 -64
  75. package/payload/plugin-host/src/cli.mjs +147 -147
  76. package/payload/plugin-host/src/framing.mjs +113 -113
  77. package/payload/plugin-host/src/host.mjs +691 -691
  78. package/payload/plugin-host/src/json.mjs +208 -208
  79. package/payload/plugin-host/src/lifecycle.mjs +114 -114
  80. package/payload/plugin-host/src/loader.mjs +183 -183
  81. package/payload/plugin-host/src/methods.mjs +377 -377
  82. package/payload/plugin-host/src/ownership.mjs +57 -57
  83. package/payload/plugin-host/src/protocol.mjs +129 -129
  84. package/payload/plugin-host/src/sdk.mjs +7 -7
  85. package/payload/rebon-boa-helper.exe +0 -0
  86. package/payload/rebon-browser-mcp.exe +0 -0
  87. package/payload/rebon-computer-use.exe +0 -0
  88. package/payload/rebon-lsp-mcp.exe +0 -0
  89. package/payload/rebon.exe +2 -2
  90. package/payload/sandbox-win.exe +0 -0
  91. package/scripts/postinstall.js +112 -112
  92. package/payload/compose-runtime/payload/compose/credentials-runtime.js +0 -38
  93. package/payload/compose-runtime/payload/compose/llm-runtime.js +0 -330
  94. package/payload/compose-runtime/payload/compose/loop-assembly.js +0 -149
  95. package/payload/compose-runtime/payload/compose/serve-dispatch.js +0 -82
  96. package/payload/compose-runtime/payload/compose/systemprompt-runtime.js +0 -74
  97. package/payload/compose-runtime/payload/compose/tools-runtime.js +0 -228
  98. package/payload/compose-runtime/payload/compose/web-runtime.js +0 -204
@@ -1,114 +1,114 @@
1
- import { ProtocolError, identityOf } from './protocol.mjs';
2
-
3
- const scopeKey = (pluginId, scopeId) => JSON.stringify([pluginId, scopeId]);
4
- const key = (identity) => scopeKey(identity.plugin_id, identity.scope_id);
5
- const same = (a, b) => ['host_epoch','plugin_id','scope_id','scope_generation','call_id'].every((field) => a[field] === b[field]);
6
-
7
- export class CallLedger {
8
- #scopes = new Map(); #calls = new Map();
9
- constructor(hostEpoch) {
10
- if (!Number.isSafeInteger(hostEpoch) || hostEpoch < 0) throw new ProtocolError('unsafe_integer', 'host_epoch is invalid');
11
- this.hostEpoch = Object.is(hostEpoch, -0) ? 0 : hostEpoch;
12
- }
13
- scopeGeneration(pluginId, scopeId) { return this.#scopes.get(scopeKey(pluginId, scopeId)); }
14
- advanceScope(pluginId, scopeId, generation, open = true) {
15
- if (!Number.isSafeInteger(generation) || generation < 0) throw new ProtocolError('unsafe_integer', 'scope_generation is invalid');
16
- const key = scopeKey(pluginId, scopeId);
17
- const current = this.#scopes.get(key);
18
- if (current && generation < current.generation) throw new ProtocolError('scope_generation_regression', 'scope generation regressed');
19
- if (current && generation === current.generation) {
20
- if (open && current.open) return 'idempotent';
21
- if (!open && !current.open) return 'idempotent';
22
- current.open = open;
23
- return 'updated';
24
- }
25
- this.#scopes.set(key, { generation, open });
26
- return current ? 'advanced' : 'established';
27
- }
28
- closeScope(pluginId, scopeId, generation) {
29
- const current = this.#scopes.get(scopeKey(pluginId, scopeId));
30
- if (!current) throw new ProtocolError('unknown_scope', 'scope is not registered');
31
- if (generation < current.generation) throw new ProtocolError('scope_generation_regression', 'scope generation regressed');
32
- if (generation === current.generation && current.open) throw new ProtocolError('scope_close_not_advanced', 'scope close must carry an advanced generation');
33
- return this.advanceScope(pluginId, scopeId, generation, false);
34
- }
35
- register(identity, direction, owner) {
36
- if (direction !== 'inbound' && direction !== 'outbound') throw new ProtocolError('bad_direction', 'call direction is invalid');
37
- if (direction === 'inbound') this.#classifyEpoch(identity, true);
38
- else this.#classify(identity, true);
39
- if (this.#calls.has(identity.call_id)) throw new ProtocolError('call_id_reused', 'call_id is globally unique within an epoch');
40
- this.#calls.set(identity.call_id, Object.freeze({ identity: identityOf(identity), direction, owner, terminal: null, registered: true }));
41
- }
42
- retire(identity) {
43
- const current = this.#calls.get(identity.call_id);
44
- if (!current || !current.registered) throw new ProtocolError('unknown_call', 'call is not registered');
45
- if (!same(current.identity, identity)) throw new ProtocolError('identity_mismatch', 'full call identity does not match');
46
- if (current.terminal) throw new ProtocolError('already_terminal', 'terminal call cannot be aborted');
47
- this.#calls.set(identity.call_id, Object.freeze({ ...current, registered: false }));
48
- }
49
- terminal(envelope, direction) {
50
- if (direction !== 'inbound' && direction !== 'outbound') throw new ProtocolError('bad_direction', 'terminal direction is invalid');
51
- const epochStale = this.#classifyEpoch(envelope, false); if (epochStale) return { stale: epochStale };
52
- const current = this.#calls.get(envelope.call_id);
53
- if (current && (current.identity.plugin_id !== envelope.plugin_id || current.identity.scope_id !== envelope.scope_id))
54
- throw new ProtocolError('identity_mismatch', 'plugin_id or scope_id does not match');
55
- const scopeStale = direction === 'outbound' ? this.#classifyScope(envelope, false) : null; if (scopeStale) return { stale: scopeStale };
56
- if (!current || !current.registered) throw new ProtocolError('unknown_call', 'call is not registered');
57
- if (current.direction !== direction) throw new ProtocolError('direction_mismatch', `call belongs to ${current.direction} direction`);
58
- if (!same(current.identity, envelope)) throw new ProtocolError('identity_mismatch', 'full call identity does not match');
59
- if (envelope.message.type !== 'terminal') throw new ProtocolError('not_terminal', 'message is not terminal');
60
- if (current.terminal) throw new ProtocolError('duplicate_terminal', `terminal already committed as ${current.terminal}`);
61
- this.#calls.set(envelope.call_id, Object.freeze({ ...current, terminal: envelope.message.status }));
62
- return { committed: envelope.message.status, owner: current.owner };
63
- }
64
- /// Validates one chunk of a call's answer. Chunks are checked, not recorded:
65
- /// a stream of a million of them must not grow the ledger.
66
- ///
67
- /// The boundary is what the ledger owns — **a chunk after the terminal is an
68
- /// error**, not a late arrival to drop. A duplicate terminal is a race two
69
- /// peers can legitimately lose; a chunk after the end means the producer kept
70
- /// emitting after saying it was done.
71
- chunk(envelope, direction) {
72
- if (direction !== 'inbound' && direction !== 'outbound') throw new ProtocolError('bad_direction', 'chunk direction is invalid');
73
- const epochStale = this.#classifyEpoch(envelope, false); if (epochStale) return { stale: epochStale };
74
- const current = this.#calls.get(envelope.call_id);
75
- if (current && (current.identity.plugin_id !== envelope.plugin_id || current.identity.scope_id !== envelope.scope_id))
76
- throw new ProtocolError('identity_mismatch', 'plugin_id or scope_id does not match');
77
- const scopeStale = this.#classifyScope(envelope, false); if (scopeStale) return { stale: scopeStale };
78
- if (!current || !current.registered) throw new ProtocolError('unknown_call', 'call is not registered');
79
- if (current.direction !== direction) throw new ProtocolError('direction_mismatch', `call belongs to ${current.direction} direction`);
80
- if (!same(current.identity, envelope)) throw new ProtocolError('identity_mismatch', 'full call identity does not match');
81
- if (envelope.message.type !== 'chunk') throw new ProtocolError('not_chunk', 'message is not a chunk');
82
- if (current.terminal) throw new ProtocolError('chunk_after_terminal', `call already ended as ${current.terminal}`);
83
- return { accepted: true };
84
- }
85
- cancel(envelope) {
86
- const epochStale = this.#classifyEpoch(envelope, false); if (epochStale) return { stale: epochStale };
87
- const current = this.#calls.get(envelope.call_id);
88
- if (current && (current.identity.plugin_id !== envelope.plugin_id || current.identity.scope_id !== envelope.scope_id))
89
- throw new ProtocolError('identity_mismatch', 'plugin_id or scope_id does not match');
90
- const scopeStale = this.#classifyScope(envelope, false); if (scopeStale) return { stale: scopeStale };
91
- if (!current || !current.registered) throw new ProtocolError('unknown_call', 'call is not registered');
92
- if (!same(current.identity, envelope)) throw new ProtocolError('identity_mismatch', 'full call identity does not match');
93
- if (envelope.message.type !== 'notification' || envelope.message.method !== 'call/cancel' || envelope.message.payload !== null)
94
- throw new ProtocolError('not_cancel', 'message is not an exact cancel intent');
95
- return { accepted: true };
96
- }
97
- #classify(identity, registration) {
98
- const epochStale = this.#classifyEpoch(identity, registration); if (epochStale) return epochStale;
99
- return this.#classifyScope(identity, registration);
100
- }
101
- #classifyEpoch(identity, registration) {
102
- if (identity.host_epoch < this.hostEpoch) return registration ? (() => { throw new ProtocolError('stale_identity', 'old host epoch'); })() : 'host_epoch';
103
- if (identity.host_epoch > this.hostEpoch) throw new ProtocolError('future_host_epoch', 'future host epoch');
104
- return null;
105
- }
106
- #classifyScope(identity, registration) {
107
- const scope = this.#scopes.get(key(identity));
108
- if (!scope) throw new ProtocolError('unknown_scope', 'scope is not registered');
109
- if (identity.scope_generation < scope.generation) return registration ? (() => { throw new ProtocolError('stale_identity', 'old scope generation'); })() : 'scope_generation';
110
- if (identity.scope_generation > scope.generation) throw new ProtocolError('future_scope_generation', 'future scope generation');
111
- if (registration && !scope.open) throw new ProtocolError('scope_closed', 'scope is closed to admission');
112
- return null;
113
- }
114
- }
1
+ import { ProtocolError, identityOf } from './protocol.mjs';
2
+
3
+ const scopeKey = (pluginId, scopeId) => JSON.stringify([pluginId, scopeId]);
4
+ const key = (identity) => scopeKey(identity.plugin_id, identity.scope_id);
5
+ const same = (a, b) => ['host_epoch','plugin_id','scope_id','scope_generation','call_id'].every((field) => a[field] === b[field]);
6
+
7
+ export class CallLedger {
8
+ #scopes = new Map(); #calls = new Map();
9
+ constructor(hostEpoch) {
10
+ if (!Number.isSafeInteger(hostEpoch) || hostEpoch < 0) throw new ProtocolError('unsafe_integer', 'host_epoch is invalid');
11
+ this.hostEpoch = Object.is(hostEpoch, -0) ? 0 : hostEpoch;
12
+ }
13
+ scopeGeneration(pluginId, scopeId) { return this.#scopes.get(scopeKey(pluginId, scopeId)); }
14
+ advanceScope(pluginId, scopeId, generation, open = true) {
15
+ if (!Number.isSafeInteger(generation) || generation < 0) throw new ProtocolError('unsafe_integer', 'scope_generation is invalid');
16
+ const key = scopeKey(pluginId, scopeId);
17
+ const current = this.#scopes.get(key);
18
+ if (current && generation < current.generation) throw new ProtocolError('scope_generation_regression', 'scope generation regressed');
19
+ if (current && generation === current.generation) {
20
+ if (open && current.open) return 'idempotent';
21
+ if (!open && !current.open) return 'idempotent';
22
+ current.open = open;
23
+ return 'updated';
24
+ }
25
+ this.#scopes.set(key, { generation, open });
26
+ return current ? 'advanced' : 'established';
27
+ }
28
+ closeScope(pluginId, scopeId, generation) {
29
+ const current = this.#scopes.get(scopeKey(pluginId, scopeId));
30
+ if (!current) throw new ProtocolError('unknown_scope', 'scope is not registered');
31
+ if (generation < current.generation) throw new ProtocolError('scope_generation_regression', 'scope generation regressed');
32
+ if (generation === current.generation && current.open) throw new ProtocolError('scope_close_not_advanced', 'scope close must carry an advanced generation');
33
+ return this.advanceScope(pluginId, scopeId, generation, false);
34
+ }
35
+ register(identity, direction, owner) {
36
+ if (direction !== 'inbound' && direction !== 'outbound') throw new ProtocolError('bad_direction', 'call direction is invalid');
37
+ if (direction === 'inbound') this.#classifyEpoch(identity, true);
38
+ else this.#classify(identity, true);
39
+ if (this.#calls.has(identity.call_id)) throw new ProtocolError('call_id_reused', 'call_id is globally unique within an epoch');
40
+ this.#calls.set(identity.call_id, Object.freeze({ identity: identityOf(identity), direction, owner, terminal: null, registered: true }));
41
+ }
42
+ retire(identity) {
43
+ const current = this.#calls.get(identity.call_id);
44
+ if (!current || !current.registered) throw new ProtocolError('unknown_call', 'call is not registered');
45
+ if (!same(current.identity, identity)) throw new ProtocolError('identity_mismatch', 'full call identity does not match');
46
+ if (current.terminal) throw new ProtocolError('already_terminal', 'terminal call cannot be aborted');
47
+ this.#calls.set(identity.call_id, Object.freeze({ ...current, registered: false }));
48
+ }
49
+ terminal(envelope, direction) {
50
+ if (direction !== 'inbound' && direction !== 'outbound') throw new ProtocolError('bad_direction', 'terminal direction is invalid');
51
+ const epochStale = this.#classifyEpoch(envelope, false); if (epochStale) return { stale: epochStale };
52
+ const current = this.#calls.get(envelope.call_id);
53
+ if (current && (current.identity.plugin_id !== envelope.plugin_id || current.identity.scope_id !== envelope.scope_id))
54
+ throw new ProtocolError('identity_mismatch', 'plugin_id or scope_id does not match');
55
+ const scopeStale = direction === 'outbound' ? this.#classifyScope(envelope, false) : null; if (scopeStale) return { stale: scopeStale };
56
+ if (!current || !current.registered) throw new ProtocolError('unknown_call', 'call is not registered');
57
+ if (current.direction !== direction) throw new ProtocolError('direction_mismatch', `call belongs to ${current.direction} direction`);
58
+ if (!same(current.identity, envelope)) throw new ProtocolError('identity_mismatch', 'full call identity does not match');
59
+ if (envelope.message.type !== 'terminal') throw new ProtocolError('not_terminal', 'message is not terminal');
60
+ if (current.terminal) throw new ProtocolError('duplicate_terminal', `terminal already committed as ${current.terminal}`);
61
+ this.#calls.set(envelope.call_id, Object.freeze({ ...current, terminal: envelope.message.status }));
62
+ return { committed: envelope.message.status, owner: current.owner };
63
+ }
64
+ /// Validates one chunk of a call's answer. Chunks are checked, not recorded:
65
+ /// a stream of a million of them must not grow the ledger.
66
+ ///
67
+ /// The boundary is what the ledger owns — **a chunk after the terminal is an
68
+ /// error**, not a late arrival to drop. A duplicate terminal is a race two
69
+ /// peers can legitimately lose; a chunk after the end means the producer kept
70
+ /// emitting after saying it was done.
71
+ chunk(envelope, direction) {
72
+ if (direction !== 'inbound' && direction !== 'outbound') throw new ProtocolError('bad_direction', 'chunk direction is invalid');
73
+ const epochStale = this.#classifyEpoch(envelope, false); if (epochStale) return { stale: epochStale };
74
+ const current = this.#calls.get(envelope.call_id);
75
+ if (current && (current.identity.plugin_id !== envelope.plugin_id || current.identity.scope_id !== envelope.scope_id))
76
+ throw new ProtocolError('identity_mismatch', 'plugin_id or scope_id does not match');
77
+ const scopeStale = this.#classifyScope(envelope, false); if (scopeStale) return { stale: scopeStale };
78
+ if (!current || !current.registered) throw new ProtocolError('unknown_call', 'call is not registered');
79
+ if (current.direction !== direction) throw new ProtocolError('direction_mismatch', `call belongs to ${current.direction} direction`);
80
+ if (!same(current.identity, envelope)) throw new ProtocolError('identity_mismatch', 'full call identity does not match');
81
+ if (envelope.message.type !== 'chunk') throw new ProtocolError('not_chunk', 'message is not a chunk');
82
+ if (current.terminal) throw new ProtocolError('chunk_after_terminal', `call already ended as ${current.terminal}`);
83
+ return { accepted: true };
84
+ }
85
+ cancel(envelope) {
86
+ const epochStale = this.#classifyEpoch(envelope, false); if (epochStale) return { stale: epochStale };
87
+ const current = this.#calls.get(envelope.call_id);
88
+ if (current && (current.identity.plugin_id !== envelope.plugin_id || current.identity.scope_id !== envelope.scope_id))
89
+ throw new ProtocolError('identity_mismatch', 'plugin_id or scope_id does not match');
90
+ const scopeStale = this.#classifyScope(envelope, false); if (scopeStale) return { stale: scopeStale };
91
+ if (!current || !current.registered) throw new ProtocolError('unknown_call', 'call is not registered');
92
+ if (!same(current.identity, envelope)) throw new ProtocolError('identity_mismatch', 'full call identity does not match');
93
+ if (envelope.message.type !== 'notification' || envelope.message.method !== 'call/cancel' || envelope.message.payload !== null)
94
+ throw new ProtocolError('not_cancel', 'message is not an exact cancel intent');
95
+ return { accepted: true };
96
+ }
97
+ #classify(identity, registration) {
98
+ const epochStale = this.#classifyEpoch(identity, registration); if (epochStale) return epochStale;
99
+ return this.#classifyScope(identity, registration);
100
+ }
101
+ #classifyEpoch(identity, registration) {
102
+ if (identity.host_epoch < this.hostEpoch) return registration ? (() => { throw new ProtocolError('stale_identity', 'old host epoch'); })() : 'host_epoch';
103
+ if (identity.host_epoch > this.hostEpoch) throw new ProtocolError('future_host_epoch', 'future host epoch');
104
+ return null;
105
+ }
106
+ #classifyScope(identity, registration) {
107
+ const scope = this.#scopes.get(key(identity));
108
+ if (!scope) throw new ProtocolError('unknown_scope', 'scope is not registered');
109
+ if (identity.scope_generation < scope.generation) return registration ? (() => { throw new ProtocolError('stale_identity', 'old scope generation'); })() : 'scope_generation';
110
+ if (identity.scope_generation > scope.generation) throw new ProtocolError('future_scope_generation', 'future scope generation');
111
+ if (registration && !scope.open) throw new ProtocolError('scope_closed', 'scope is closed to admission');
112
+ return null;
113
+ }
114
+ }
@@ -1,183 +1,183 @@
1
- // Turning a `plugin/load` request into a loaded plugin.
2
- //
3
- // The registrar is the only thing a plugin's code is handed, and it enforces
4
- // the same rule the Rust registry does from the other side: a plugin may
5
- // register a service or a topic only if its manifest declared one. Checking it
6
- // here as well is not redundancy for its own sake — it means the refusal names
7
- // the plugin's own line rather than arriving as a rejected ready report after
8
- // the module has already run and had its side effects.
9
- //
10
- // Registration closes when the entry module's `activate` returns. A plugin that
11
- // registers later would be adding capabilities the host already reported as the
12
- // complete set, and nothing downstream would know.
13
- import path from 'node:path';
14
- import { pathToFileURL } from 'node:url';
15
- import { ProtocolError } from './protocol.mjs';
16
- import { pluginCommandDefinition, pluginToolDefinition, validateName } from './methods.mjs';
17
-
18
- /// Resolves an entry against its package root and refuses anything outside it.
19
- ///
20
- /// The payload validator already rejects `..`, absolute entries, and
21
- /// backslashes; this is the check that survives a symlink or a root that is
22
- /// itself a relative-looking string, because it asks the filesystem's own
23
- /// resolver rather than the text.
24
- export function resolveEntry(root, entry) {
25
- const base = path.resolve(root);
26
- const resolved = path.resolve(base, entry);
27
- const inside = resolved === base || resolved.startsWith(base + path.sep);
28
- if (!inside) throw new ProtocolError('[PATH_ESCAPES]', `entry ${entry} resolves outside the package root`);
29
- return pathToFileURL(resolved).href;
30
- }
31
-
32
- class Registrar {
33
- #declaredServices; #declaredTopics; #declaredProviders; #declaredTools; #declaredCommands;
34
- #commands = new Map(); #commandHandlers = new Map();
35
- #services = new Map(); #topics = new Map(); #adapters = new Map();
36
- #adapterInfo = new Map();
37
- #tools = new Map(); #toolHandlers = new Map(); #scopes = []; #sealed = false;
38
- constructor(declaredServices, declaredTopics, declaredProviders, declaredTools, declaredCommands) {
39
- this.#declaredServices = new Set(declaredServices);
40
- this.#declaredTopics = new Set(declaredTopics);
41
- this.#declaredProviders = new Set(declaredProviders);
42
- this.#declaredTools = new Set(declaredTools);
43
- this.#declaredCommands = new Set(declaredCommands);
44
- }
45
-
46
- /// A command is a tool for a person: the manifest declares the name, and
47
- /// the registration says what it looks like in a menu. Only a `prompt`
48
- /// command carries a handler — an `explain` says its sentence at
49
- /// registration and a `panel` names a dialog, so neither has anything left
50
- /// to ask the plugin at invoke time.
51
- #admitCommand(definition, handler) {
52
- if (this.#sealed) throw new ProtocolError('[REGISTRATION_CLOSED]', 'a command was registered after activation finished');
53
- const command = pluginCommandDefinition(definition);
54
- if (!this.#declaredCommands.has(command.name)) throw new ProtocolError('[UNAUTHORIZED_REGISTER]', `command ${JSON.stringify(command.name)} is not declared by this plugin's manifest`);
55
- if (this.#commands.has(command.name)) throw new ProtocolError('[DUPLICATE_DECLARATION]', `command ${JSON.stringify(command.name)} is registered twice`);
56
- if (command.kind.type === 'prompt') {
57
- if (typeof handler !== 'function') throw new ProtocolError('[WRONG_SHAPE]', `prompt command ${JSON.stringify(command.name)} needs a function`);
58
- this.#commandHandlers.set(command.name, handler);
59
- } else if (handler !== undefined) {
60
- throw new ProtocolError('[WRONG_SHAPE]', `${command.kind.type} command ${JSON.stringify(command.name)} takes no handler`);
61
- }
62
- this.#commands.set(command.name, command);
63
- }
64
-
65
- /// A tool differs from a service in one way that matters: it has to be
66
- /// *offered* to a model, so it cannot be registered by name alone. The
67
- /// manifest still declares only the name — description and schema are how a
68
- /// tool describes itself, not what it is permitted to be.
69
- #admitTool(definition, handler) {
70
- if (this.#sealed) throw new ProtocolError('[REGISTRATION_CLOSED]', 'a tool was registered after activation finished');
71
- const tool = pluginToolDefinition(definition);
72
- if (typeof handler !== 'function') throw new ProtocolError('[WRONG_SHAPE]', `tool ${JSON.stringify(tool.name)} needs a function`);
73
- if (!this.#declaredTools.has(tool.name)) throw new ProtocolError('[UNAUTHORIZED_REGISTER]', `tool ${JSON.stringify(tool.name)} is not declared by this plugin's manifest`);
74
- if (this.#tools.has(tool.name)) throw new ProtocolError('[DUPLICATE_DECLARATION]', `tool ${JSON.stringify(tool.name)} is registered twice`);
75
- this.#tools.set(tool.name, tool);
76
- this.#toolHandlers.set(tool.name, handler);
77
- }
78
-
79
- #admit(kind, name, handler, declared, into) {
80
- if (this.#sealed) throw new ProtocolError('[REGISTRATION_CLOSED]', `${kind} ${JSON.stringify(name)} was registered after activation finished`);
81
- validateName(kind, name);
82
- if (typeof handler !== 'function') throw new ProtocolError('[WRONG_SHAPE]', `${kind} ${JSON.stringify(name)} needs a function`);
83
- if (!declared.has(name)) throw new ProtocolError('[UNAUTHORIZED_REGISTER]', `${kind} ${JSON.stringify(name)} is not declared by this plugin's manifest`);
84
- if (into.has(name)) throw new ProtocolError('[DUPLICATE_DECLARATION]', `${kind} ${JSON.stringify(name)} is registered twice`);
85
- into.set(name, handler);
86
- }
87
-
88
- /// The surface a plugin's `activate` receives. Frozen, and holding no
89
- /// reference back to the host: at activation a plugin registers, it does not
90
- /// reach.
91
- ///
92
- /// Reaching happens later and only from inside a handler, which is called as
93
- /// `handler(request, ctx)`. That `ctx` is bound to the scope incarnation the
94
- /// call arrived on and carries `invoke(tool, input)` — so a plugin can act on
95
- /// the session it was called for and has no way to name another.
96
- api() {
97
- return Object.freeze({
98
- service: (name, handler) => this.#admit('service', name, handler, this.#declaredServices, this.#services),
99
- topic: (name, handler) => this.#admit('topic', name, handler, this.#declaredTopics, this.#topics),
100
- // An llm adapter is a service under a different routing key: declared in
101
- // the manifest, registered here, and answered with chunks instead of one
102
- // value.
103
- //
104
- // The optional third argument is what the adapter says about itself —
105
- // which models, which default, what the provider can do. It is reported
106
- // once with the ready report rather than asked for per turn, because a
107
- // caller has to know whether a provider streams reasoning text before it
108
- // decides how to budget the turn it is about to send. Opaque to the
109
- // host: reading it is the model layer's business.
110
- llm: (provider, adapter, info) => {
111
- this.#admit('provider', provider, adapter, this.#declaredProviders, this.#adapters);
112
- if (info !== undefined) {
113
- if (info === null || typeof info !== 'object' || Array.isArray(info)) {
114
- throw new ProtocolError('[WRONG_SHAPE]', `adapter info for ${JSON.stringify(provider)} must be a plain object`);
115
- }
116
- this.#adapterInfo.set(provider, info);
117
- }
118
- },
119
- tool: (definition, handler) => this.#admitTool(definition, handler),
120
- command: (definition, handler) => this.#admitCommand(definition, handler),
121
- // Not a capability, so nothing to declare: everything a scope handle can
122
- // do is gated by the declarations above. What it is, is a lifetime — the
123
- // session, rather than one call inside it. A plugin that produces facts
124
- // on its own schedule (an agent loop, a watcher) has no inbound call to
125
- // hang them on, and requiring one would mean it could only speak when
126
- // spoken to.
127
- scope: (handler) => {
128
- if (this.#sealed) throw new ProtocolError('[REGISTRATION_CLOSED]', 'a scope handler was registered after activation finished');
129
- if (typeof handler !== 'function') throw new ProtocolError('[WRONG_SHAPE]', 'a scope handler must be a function');
130
- this.#scopes.push(handler);
131
- },
132
- });
133
- }
134
-
135
- seal() {
136
- this.#sealed = true;
137
- return Object.freeze({
138
- services: Object.freeze([...this.#services.keys()]),
139
- eventTopics: Object.freeze([...this.#topics.keys()]),
140
- llmProviders: Object.freeze([...this.#adapters.keys()]),
141
- llmAdapterInfo: Object.freeze(Object.fromEntries(this.#adapterInfo)),
142
- tools: Object.freeze([...this.#tools.values()]),
143
- commands: Object.freeze([...this.#commands.values()]),
144
- commandHandlers: this.#commandHandlers,
145
- serviceHandlers: this.#services,
146
- topicHandlers: this.#topics,
147
- llmAdapters: this.#adapters,
148
- toolHandlers: this.#toolHandlers,
149
- scopeHandlers: Object.freeze([...this.#scopes]),
150
- });
151
- }
152
- }
153
-
154
- /// Imports a plugin and collects what it registered.
155
- ///
156
- /// `activate` is called as `activate(api, config)`: the package says what the
157
- /// plugin can do, the load request's `config` says what this installation wants
158
- /// it to do. The second argument is opaque here — reading it is the plugin's
159
- /// business, and validating it is the plugin's job rather than the wire's.
160
- ///
161
- /// `importModule` is injectable so the loader's rules can be tested without a
162
- /// file on disk for every case.
163
- export async function loadPlugin(request, importModule = (url) => import(url)) {
164
- const url = resolveEntry(request.root, request.entry);
165
- let module;
166
- try {
167
- module = await importModule(url);
168
- } catch (cause) {
169
- throw new ProtocolError('[ENTRY_FAILED]', `plugin entry ${request.entry} failed to load: ${cause?.message ?? cause}`);
170
- }
171
- const activate = module?.activate ?? module?.default;
172
- if (typeof activate !== 'function') {
173
- throw new ProtocolError('[NO_ACTIVATE]', `plugin entry ${request.entry} exports no activate function`);
174
- }
175
- const registrar = new Registrar(request.services, request.eventTopics, request.llmProviders, request.tools, request.commands);
176
- try {
177
- await activate(registrar.api(), request.config);
178
- } catch (cause) {
179
- if (cause instanceof ProtocolError) throw cause;
180
- throw new ProtocolError('[ACTIVATE_FAILED]', `plugin activation failed: ${cause?.message ?? cause}`);
181
- }
182
- return registrar.seal();
183
- }
1
+ // Turning a `plugin/load` request into a loaded plugin.
2
+ //
3
+ // The registrar is the only thing a plugin's code is handed, and it enforces
4
+ // the same rule the Rust registry does from the other side: a plugin may
5
+ // register a service or a topic only if its manifest declared one. Checking it
6
+ // here as well is not redundancy for its own sake — it means the refusal names
7
+ // the plugin's own line rather than arriving as a rejected ready report after
8
+ // the module has already run and had its side effects.
9
+ //
10
+ // Registration closes when the entry module's `activate` returns. A plugin that
11
+ // registers later would be adding capabilities the host already reported as the
12
+ // complete set, and nothing downstream would know.
13
+ import path from 'node:path';
14
+ import { pathToFileURL } from 'node:url';
15
+ import { ProtocolError } from './protocol.mjs';
16
+ import { pluginCommandDefinition, pluginToolDefinition, validateName } from './methods.mjs';
17
+
18
+ /// Resolves an entry against its package root and refuses anything outside it.
19
+ ///
20
+ /// The payload validator already rejects `..`, absolute entries, and
21
+ /// backslashes; this is the check that survives a symlink or a root that is
22
+ /// itself a relative-looking string, because it asks the filesystem's own
23
+ /// resolver rather than the text.
24
+ export function resolveEntry(root, entry) {
25
+ const base = path.resolve(root);
26
+ const resolved = path.resolve(base, entry);
27
+ const inside = resolved === base || resolved.startsWith(base + path.sep);
28
+ if (!inside) throw new ProtocolError('[PATH_ESCAPES]', `entry ${entry} resolves outside the package root`);
29
+ return pathToFileURL(resolved).href;
30
+ }
31
+
32
+ class Registrar {
33
+ #declaredServices; #declaredTopics; #declaredProviders; #declaredTools; #declaredCommands;
34
+ #commands = new Map(); #commandHandlers = new Map();
35
+ #services = new Map(); #topics = new Map(); #adapters = new Map();
36
+ #adapterInfo = new Map();
37
+ #tools = new Map(); #toolHandlers = new Map(); #scopes = []; #sealed = false;
38
+ constructor(declaredServices, declaredTopics, declaredProviders, declaredTools, declaredCommands) {
39
+ this.#declaredServices = new Set(declaredServices);
40
+ this.#declaredTopics = new Set(declaredTopics);
41
+ this.#declaredProviders = new Set(declaredProviders);
42
+ this.#declaredTools = new Set(declaredTools);
43
+ this.#declaredCommands = new Set(declaredCommands);
44
+ }
45
+
46
+ /// A command is a tool for a person: the manifest declares the name, and
47
+ /// the registration says what it looks like in a menu. Only a `prompt`
48
+ /// command carries a handler — an `explain` says its sentence at
49
+ /// registration and a `panel` names a dialog, so neither has anything left
50
+ /// to ask the plugin at invoke time.
51
+ #admitCommand(definition, handler) {
52
+ if (this.#sealed) throw new ProtocolError('[REGISTRATION_CLOSED]', 'a command was registered after activation finished');
53
+ const command = pluginCommandDefinition(definition);
54
+ if (!this.#declaredCommands.has(command.name)) throw new ProtocolError('[UNAUTHORIZED_REGISTER]', `command ${JSON.stringify(command.name)} is not declared by this plugin's manifest`);
55
+ if (this.#commands.has(command.name)) throw new ProtocolError('[DUPLICATE_DECLARATION]', `command ${JSON.stringify(command.name)} is registered twice`);
56
+ if (command.kind.type === 'prompt') {
57
+ if (typeof handler !== 'function') throw new ProtocolError('[WRONG_SHAPE]', `prompt command ${JSON.stringify(command.name)} needs a function`);
58
+ this.#commandHandlers.set(command.name, handler);
59
+ } else if (handler !== undefined) {
60
+ throw new ProtocolError('[WRONG_SHAPE]', `${command.kind.type} command ${JSON.stringify(command.name)} takes no handler`);
61
+ }
62
+ this.#commands.set(command.name, command);
63
+ }
64
+
65
+ /// A tool differs from a service in one way that matters: it has to be
66
+ /// *offered* to a model, so it cannot be registered by name alone. The
67
+ /// manifest still declares only the name — description and schema are how a
68
+ /// tool describes itself, not what it is permitted to be.
69
+ #admitTool(definition, handler) {
70
+ if (this.#sealed) throw new ProtocolError('[REGISTRATION_CLOSED]', 'a tool was registered after activation finished');
71
+ const tool = pluginToolDefinition(definition);
72
+ if (typeof handler !== 'function') throw new ProtocolError('[WRONG_SHAPE]', `tool ${JSON.stringify(tool.name)} needs a function`);
73
+ if (!this.#declaredTools.has(tool.name)) throw new ProtocolError('[UNAUTHORIZED_REGISTER]', `tool ${JSON.stringify(tool.name)} is not declared by this plugin's manifest`);
74
+ if (this.#tools.has(tool.name)) throw new ProtocolError('[DUPLICATE_DECLARATION]', `tool ${JSON.stringify(tool.name)} is registered twice`);
75
+ this.#tools.set(tool.name, tool);
76
+ this.#toolHandlers.set(tool.name, handler);
77
+ }
78
+
79
+ #admit(kind, name, handler, declared, into) {
80
+ if (this.#sealed) throw new ProtocolError('[REGISTRATION_CLOSED]', `${kind} ${JSON.stringify(name)} was registered after activation finished`);
81
+ validateName(kind, name);
82
+ if (typeof handler !== 'function') throw new ProtocolError('[WRONG_SHAPE]', `${kind} ${JSON.stringify(name)} needs a function`);
83
+ if (!declared.has(name)) throw new ProtocolError('[UNAUTHORIZED_REGISTER]', `${kind} ${JSON.stringify(name)} is not declared by this plugin's manifest`);
84
+ if (into.has(name)) throw new ProtocolError('[DUPLICATE_DECLARATION]', `${kind} ${JSON.stringify(name)} is registered twice`);
85
+ into.set(name, handler);
86
+ }
87
+
88
+ /// The surface a plugin's `activate` receives. Frozen, and holding no
89
+ /// reference back to the host: at activation a plugin registers, it does not
90
+ /// reach.
91
+ ///
92
+ /// Reaching happens later and only from inside a handler, which is called as
93
+ /// `handler(request, ctx)`. That `ctx` is bound to the scope incarnation the
94
+ /// call arrived on and carries `invoke(tool, input)` — so a plugin can act on
95
+ /// the session it was called for and has no way to name another.
96
+ api() {
97
+ return Object.freeze({
98
+ service: (name, handler) => this.#admit('service', name, handler, this.#declaredServices, this.#services),
99
+ topic: (name, handler) => this.#admit('topic', name, handler, this.#declaredTopics, this.#topics),
100
+ // An llm adapter is a service under a different routing key: declared in
101
+ // the manifest, registered here, and answered with chunks instead of one
102
+ // value.
103
+ //
104
+ // The optional third argument is what the adapter says about itself —
105
+ // which models, which default, what the provider can do. It is reported
106
+ // once with the ready report rather than asked for per turn, because a
107
+ // caller has to know whether a provider streams reasoning text before it
108
+ // decides how to budget the turn it is about to send. Opaque to the
109
+ // host: reading it is the model layer's business.
110
+ llm: (provider, adapter, info) => {
111
+ this.#admit('provider', provider, adapter, this.#declaredProviders, this.#adapters);
112
+ if (info !== undefined) {
113
+ if (info === null || typeof info !== 'object' || Array.isArray(info)) {
114
+ throw new ProtocolError('[WRONG_SHAPE]', `adapter info for ${JSON.stringify(provider)} must be a plain object`);
115
+ }
116
+ this.#adapterInfo.set(provider, info);
117
+ }
118
+ },
119
+ tool: (definition, handler) => this.#admitTool(definition, handler),
120
+ command: (definition, handler) => this.#admitCommand(definition, handler),
121
+ // Not a capability, so nothing to declare: everything a scope handle can
122
+ // do is gated by the declarations above. What it is, is a lifetime — the
123
+ // session, rather than one call inside it. A plugin that produces facts
124
+ // on its own schedule (an agent loop, a watcher) has no inbound call to
125
+ // hang them on, and requiring one would mean it could only speak when
126
+ // spoken to.
127
+ scope: (handler) => {
128
+ if (this.#sealed) throw new ProtocolError('[REGISTRATION_CLOSED]', 'a scope handler was registered after activation finished');
129
+ if (typeof handler !== 'function') throw new ProtocolError('[WRONG_SHAPE]', 'a scope handler must be a function');
130
+ this.#scopes.push(handler);
131
+ },
132
+ });
133
+ }
134
+
135
+ seal() {
136
+ this.#sealed = true;
137
+ return Object.freeze({
138
+ services: Object.freeze([...this.#services.keys()]),
139
+ eventTopics: Object.freeze([...this.#topics.keys()]),
140
+ llmProviders: Object.freeze([...this.#adapters.keys()]),
141
+ llmAdapterInfo: Object.freeze(Object.fromEntries(this.#adapterInfo)),
142
+ tools: Object.freeze([...this.#tools.values()]),
143
+ commands: Object.freeze([...this.#commands.values()]),
144
+ commandHandlers: this.#commandHandlers,
145
+ serviceHandlers: this.#services,
146
+ topicHandlers: this.#topics,
147
+ llmAdapters: this.#adapters,
148
+ toolHandlers: this.#toolHandlers,
149
+ scopeHandlers: Object.freeze([...this.#scopes]),
150
+ });
151
+ }
152
+ }
153
+
154
+ /// Imports a plugin and collects what it registered.
155
+ ///
156
+ /// `activate` is called as `activate(api, config)`: the package says what the
157
+ /// plugin can do, the load request's `config` says what this installation wants
158
+ /// it to do. The second argument is opaque here — reading it is the plugin's
159
+ /// business, and validating it is the plugin's job rather than the wire's.
160
+ ///
161
+ /// `importModule` is injectable so the loader's rules can be tested without a
162
+ /// file on disk for every case.
163
+ export async function loadPlugin(request, importModule = (url) => import(url)) {
164
+ const url = resolveEntry(request.root, request.entry);
165
+ let module;
166
+ try {
167
+ module = await importModule(url);
168
+ } catch (cause) {
169
+ throw new ProtocolError('[ENTRY_FAILED]', `plugin entry ${request.entry} failed to load: ${cause?.message ?? cause}`);
170
+ }
171
+ const activate = module?.activate ?? module?.default;
172
+ if (typeof activate !== 'function') {
173
+ throw new ProtocolError('[NO_ACTIVATE]', `plugin entry ${request.entry} exports no activate function`);
174
+ }
175
+ const registrar = new Registrar(request.services, request.eventTopics, request.llmProviders, request.tools, request.commands);
176
+ try {
177
+ await activate(registrar.api(), request.config);
178
+ } catch (cause) {
179
+ if (cause instanceof ProtocolError) throw cause;
180
+ throw new ProtocolError('[ACTIVATE_FAILED]', `plugin activation failed: ${cause?.message ?? cause}`);
181
+ }
182
+ return registrar.seal();
183
+ }