@reefclaw/connect 0.1.0 → 0.1.2
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/assets/bridge/bridge.d.ts +13 -0
- package/assets/bridge/bridge.js +67 -0
- package/assets/bridge/utils/skill-version.js +4 -1
- package/assets/skill/SKILL.md +33 -1303
- package/package.json +1 -1
|
@@ -22,6 +22,8 @@ export declare class Bridge {
|
|
|
22
22
|
* round-trip. `null` means "unknown yet" — the plugin is the authority. */
|
|
23
23
|
private lastKnownTradingMode;
|
|
24
24
|
private readonly listeners;
|
|
25
|
+
/** rc_ connection token — also authenticates the webapp skill-content pull */
|
|
26
|
+
private readonly connectionToken;
|
|
25
27
|
constructor(provider: OpenClawProvider, connectorConfig: ConnectorConfig);
|
|
26
28
|
/** Start the bridge: connect to relay + start provider */
|
|
27
29
|
start(): void;
|
|
@@ -91,6 +93,17 @@ export declare class Bridge {
|
|
|
91
93
|
* has nothing stored; throws on transport error.
|
|
92
94
|
*/
|
|
93
95
|
private pullAndApplySkillUpdate;
|
|
96
|
+
/**
|
|
97
|
+
* Pull the full SKILL.md from the webapp's authenticated endpoint and apply
|
|
98
|
+
* it when newer than local. This is how a FRESH install upgrades from the
|
|
99
|
+
* bundled bootstrap (v0.0.x) to the real trading instructions — the relay's
|
|
100
|
+
* per-room Durable-Object store is empty for a new user, so the relay push
|
|
101
|
+
* channel alone can never deliver the first full copy. Token-gated: no valid
|
|
102
|
+
* rc_ token → 401 → no content (the full SKILL.md never ships unauthenticated).
|
|
103
|
+
* Same downgrade guard + applySkillUpdate pipeline (incl. the C1 signature
|
|
104
|
+
* gate when enforced) as the relay pull path.
|
|
105
|
+
*/
|
|
106
|
+
private pullSkillContentFromWebapp;
|
|
94
107
|
/** Check relay's latest SKILL.md version against local and auto-update if stale */
|
|
95
108
|
private handleConnectAck;
|
|
96
109
|
}
|
package/assets/bridge/bridge.js
CHANGED
|
@@ -77,10 +77,13 @@ export class Bridge {
|
|
|
77
77
|
// Bound listeners for cleanup
|
|
78
78
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
79
|
listeners = [];
|
|
80
|
+
/** rc_ connection token — also authenticates the webapp skill-content pull */
|
|
81
|
+
connectionToken;
|
|
80
82
|
constructor(provider, connectorConfig) {
|
|
81
83
|
this.provider = provider;
|
|
82
84
|
this.currentSkillVersion = readLocalSkillVersion();
|
|
83
85
|
logger.info(TAG, `Local SKILL.md version: ${this.currentSkillVersion ?? 'unknown'}`);
|
|
86
|
+
this.connectionToken = connectorConfig.token;
|
|
84
87
|
this.connector = new Connector(connectorConfig, {
|
|
85
88
|
onRequest: (frame) => this.handleRequest(frame),
|
|
86
89
|
onStateChange: (state) => this.handleStateChange(state),
|
|
@@ -94,6 +97,11 @@ export class Bridge {
|
|
|
94
97
|
this.startThrottleTimer();
|
|
95
98
|
this.provider.start();
|
|
96
99
|
this.connector.connect();
|
|
100
|
+
// Fresh installs ship only the thin BOOTSTRAP SKILL.md (v0.0.x) — the full
|
|
101
|
+
// trading instructions live behind the authenticated webapp endpoint, not
|
|
102
|
+
// in the public npm package. Fire-and-forget with retries; the relay OTA
|
|
103
|
+
// path (handleConnectAck) remains the push channel for updates.
|
|
104
|
+
void this.pullSkillContentFromWebapp();
|
|
97
105
|
}
|
|
98
106
|
/** Stop the bridge: disconnect + stop provider */
|
|
99
107
|
stop() {
|
|
@@ -897,6 +905,65 @@ export class Bridge {
|
|
|
897
905
|
});
|
|
898
906
|
return updateResult;
|
|
899
907
|
}
|
|
908
|
+
/**
|
|
909
|
+
* Pull the full SKILL.md from the webapp's authenticated endpoint and apply
|
|
910
|
+
* it when newer than local. This is how a FRESH install upgrades from the
|
|
911
|
+
* bundled bootstrap (v0.0.x) to the real trading instructions — the relay's
|
|
912
|
+
* per-room Durable-Object store is empty for a new user, so the relay push
|
|
913
|
+
* channel alone can never deliver the first full copy. Token-gated: no valid
|
|
914
|
+
* rc_ token → 401 → no content (the full SKILL.md never ships unauthenticated).
|
|
915
|
+
* Same downgrade guard + applySkillUpdate pipeline (incl. the C1 signature
|
|
916
|
+
* gate when enforced) as the relay pull path.
|
|
917
|
+
*/
|
|
918
|
+
async pullSkillContentFromWebapp(attempt = 1) {
|
|
919
|
+
const MAX_ATTEMPTS = 5;
|
|
920
|
+
// www is load-bearing: reefclaw.com 307-redirects and Node fetch strips the
|
|
921
|
+
// Authorization header on cross-origin redirect (verified 2026-03-18).
|
|
922
|
+
const base = (process.env.REEFCLAW_API_URL || 'https://www.reefclaw.com').replace(/\/$/, '');
|
|
923
|
+
try {
|
|
924
|
+
const res = await fetch(`${base}/api/internal/skill-content`, {
|
|
925
|
+
headers: { Authorization: `Bearer ${this.connectionToken}` },
|
|
926
|
+
signal: AbortSignal.timeout(15_000),
|
|
927
|
+
});
|
|
928
|
+
if (res.status === 401 || res.status === 403) {
|
|
929
|
+
logger.warn(TAG, `SKILL.md webapp pull: not authorized (${res.status}) — connect the account first`);
|
|
930
|
+
return; // A bad token won't get better by retrying.
|
|
931
|
+
}
|
|
932
|
+
if (!res.ok)
|
|
933
|
+
throw new Error(`HTTP ${res.status}`);
|
|
934
|
+
const body = (await res.json());
|
|
935
|
+
if (!body?.version || !body?.content) {
|
|
936
|
+
logger.warn(TAG, 'SKILL.md webapp pull: response missing version/content');
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
this.currentSkillVersion = readLocalSkillVersion();
|
|
940
|
+
if (compareSemver(body.version, this.currentSkillVersion) <= 0) {
|
|
941
|
+
logger.info(TAG, `SKILL.md webapp pull: local v${this.currentSkillVersion ?? 'unknown'} already >= webapp v${body.version} — nothing to do`);
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
logger.info(TAG, `SKILL.md webapp pull: local v${this.currentSkillVersion ?? 'unknown'} behind webapp v${body.version} — applying`);
|
|
945
|
+
const result = await this.applySkillUpdate({ version: body.version, content: body.content });
|
|
946
|
+
this.emit('agent_state', 'skill_update_applied', {
|
|
947
|
+
event: 'skill_update_applied',
|
|
948
|
+
version: body.version,
|
|
949
|
+
success: result.success,
|
|
950
|
+
source: 'webapp_pull',
|
|
951
|
+
timestamp: new Date().toISOString(),
|
|
952
|
+
});
|
|
953
|
+
if (!result.success)
|
|
954
|
+
logger.error(TAG, `SKILL.md webapp pull: apply failed: ${result.message}`);
|
|
955
|
+
}
|
|
956
|
+
catch (err) {
|
|
957
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
958
|
+
if (attempt >= MAX_ATTEMPTS) {
|
|
959
|
+
logger.error(TAG, `SKILL.md webapp pull failed after ${MAX_ATTEMPTS} attempts: ${msg}`);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
const delayMs = attempt * 30_000;
|
|
963
|
+
logger.warn(TAG, `SKILL.md webapp pull failed (attempt ${attempt}/${MAX_ATTEMPTS}): ${msg} — retrying in ${delayMs / 1000}s`);
|
|
964
|
+
setTimeout(() => void this.pullSkillContentFromWebapp(attempt + 1), delayMs).unref?.();
|
|
965
|
+
}
|
|
966
|
+
}
|
|
900
967
|
/** Check relay's latest SKILL.md version against local and auto-update if stale */
|
|
901
968
|
async handleConnectAck(payload) {
|
|
902
969
|
const latestVersion = payload.latestSkillVersion;
|
|
@@ -18,7 +18,10 @@ export function readLocalSkillVersion() {
|
|
|
18
18
|
return null;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
// 120KB — the full SKILL.md crossed 100KB at v2.20.0 (103.7KB), which silently
|
|
22
|
+
// broke every OTA apply against the old 100KB cap. Keep comfortably under the
|
|
23
|
+
// 128KiB Cloudflare Durable-Object per-value hard limit the relay stores into.
|
|
24
|
+
const MAX_SKILL_CONTENT_SIZE = 120_000;
|
|
22
25
|
const SEMVER_REGEX = /^\d+\.\d+\.\d+$/;
|
|
23
26
|
/**
|
|
24
27
|
* Compare two semver strings.
|