visionclaw 0.1.187-beta.9 → 0.1.187
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 +45 -0
- package/dist/agent/applied-credential-signature.d.ts +53 -0
- package/dist/agent/applied-credential-signature.d.ts.map +1 -0
- package/dist/agent/applied-credential-signature.js +137 -0
- package/dist/agent/applied-credential-signature.js.map +1 -0
- package/dist/agent/tunnel-credential-handler.d.ts +90 -0
- package/dist/agent/tunnel-credential-handler.d.ts.map +1 -0
- package/dist/agent/tunnel-credential-handler.js +162 -0
- package/dist/agent/tunnel-credential-handler.js.map +1 -0
- package/dist/billing/payg-handler.d.ts +29 -0
- package/dist/billing/payg-handler.d.ts.map +1 -0
- package/dist/billing/payg-handler.js +92 -0
- package/dist/billing/payg-handler.js.map +1 -0
- package/dist/billing/payment-handler.d.ts +24 -0
- package/dist/billing/payment-handler.d.ts.map +1 -0
- package/dist/billing/payment-handler.js +101 -0
- package/dist/billing/payment-handler.js.map +1 -0
- package/dist/builtin-skills/catalog/phone-adb-automation/SKILL.md +412 -0
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_input.sh +132 -0
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_launch.sh +166 -0
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_screenshot.sh +87 -0
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_security_kbd.py +174 -0
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_setup.sh +274 -0
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_swipe.sh +111 -0
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_tap.sh +87 -0
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_ui_parse.py +176 -0
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_wake_unlock.sh +67 -0
- package/dist/builtin-skills/transcribe-audio/SKILL.md +122 -0
- package/dist/data-processing/convert-demo-cli.d.ts +7 -0
- package/dist/data-processing/convert-demo-cli.d.ts.map +1 -0
- package/dist/data-processing/convert-demo-cli.js +30 -0
- package/dist/data-processing/convert-demo-cli.js.map +1 -0
- package/dist/data-processing/convert-demo.d.ts +26 -0
- package/dist/data-processing/convert-demo.d.ts.map +1 -0
- package/dist/data-processing/convert-demo.js +233 -0
- package/dist/data-processing/convert-demo.js.map +1 -0
- package/dist/obs/rdp/icons/icons/app_windows.svg +4 -0
- package/dist/obs/rdp/icons/icons/clip_get.svg +4 -0
- package/dist/obs/rdp/icons/icons/clip_send.svg +4 -0
- package/dist/obs/rdp/icons/icons/clip_shared.svg +4 -0
- package/dist/obs/rdp/icons/icons/clipboard.svg +4 -0
- package/dist/obs/rdp/icons/icons/clipboard_shared.svg +4 -0
- package/dist/obs/rdp/icons/icons/control.svg +4 -0
- package/dist/obs/rdp/icons/icons/desktop.svg +4 -0
- package/dist/obs/rdp/icons/icons/display.svg +4 -0
- package/dist/obs/rdp/icons/icons/launchpad.svg +4 -0
- package/dist/obs/rdp/icons/icons/mission_control.svg +4 -0
- package/dist/obs/rdp/icons/icons/screenshot.svg +4 -0
- package/dist/obs/rdp/icons/icons/zoom_actual.svg +4 -0
- package/dist/obs/rdp/icons/icons/zoom_fit.svg +4 -0
- package/dist/obs/rdp/icons/icons/zoom_in.svg +4 -0
- package/dist/obs/rdp/icons/icons/zoom_out.svg +4 -0
- package/dist/obs/tunnel-telemetry.d.ts +46 -0
- package/dist/obs/tunnel-telemetry.d.ts.map +1 -0
- package/dist/obs/tunnel-telemetry.js +70 -0
- package/dist/obs/tunnel-telemetry.js.map +1 -0
- package/dist/onboarding/generate-wallpaper.d.ts +3 -8
- package/dist/onboarding/generate-wallpaper.d.ts.map +1 -1
- package/dist/onboarding/generate-wallpaper.js +3 -123
- package/dist/onboarding/generate-wallpaper.js.map +1 -1
- package/dist/service/gbox-tun.d.ts +14 -0
- package/dist/service/gbox-tun.d.ts.map +1 -0
- package/dist/service/gbox-tun.js +315 -0
- package/dist/service/gbox-tun.js.map +1 -0
- package/dist/utils/wechat-monitor.d.ts +21 -0
- package/dist/utils/wechat-monitor.d.ts.map +1 -0
- package/dist/utils/wechat-monitor.js +88 -0
- package/dist/utils/wechat-monitor.js.map +1 -0
- package/dist-agent/bundle.cjs +7 -110
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,48 @@
|
|
|
1
|
+
## [0.1.187](https://github.com/babelcloud/visionclaw/compare/v0.1.186...v0.1.187) (2026-04-24)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* remove @latest from Playwright MCP to use local PATH resolution ([c56fd95](https://github.com/babelcloud/visionclaw/commit/c56fd95ed8ab40804a57bc6c478e7e7dada62bfd))
|
|
6
|
+
* also prepend node_modules/.bin to PATH for Playwright MCP server ([657ab9b](https://github.com/babelcloud/visionclaw/commit/657ab9b7e0611848e0f7de9838b041108c7b3079))
|
|
7
|
+
* use npx with PATH set to package's node_modules/.bin ([8fb8c1e](https://github.com/babelcloud/visionclaw/commit/8fb8c1e147672ab640660a3e1398a62a3f8760de))
|
|
8
|
+
* resolve Playwriter MCP server path with createRequire instead of npx ([28924ff](https://github.com/babelcloud/visionclaw/commit/28924ffaa56b9f210d048d6e8693bb415a30bb9e))
|
|
9
|
+
* correct laptop battery power management in prepare-mac ([30e27ff](https://github.com/babelcloud/visionclaw/commit/30e27ffe8fbaa6610cb35dd2c677167f782b5cef))
|
|
10
|
+
* differentiate default effort by session type (coding=high, general=medium) ([77c65d2](https://github.com/babelcloud/visionclaw/commit/77c65d231dd91b96e3ea697a638aaf954e9322fb))
|
|
11
|
+
* change default reasoning effort from high to medium ([3c411a8](https://github.com/babelcloud/visionclaw/commit/3c411a8f48b586ed9246b7283ce048dd2aa66dde))
|
|
12
|
+
* **onboarding:** include runtime node_modules targets for Next standalone ([49fffd7](https://github.com/babelcloud/visionclaw/commit/49fffd7daff3a6733cfb6ef83576a31be7f3f63e))
|
|
13
|
+
* **onboarding:** restore deploy build after lockfile removal ([05a2d7d](https://github.com/babelcloud/visionclaw/commit/05a2d7d25205257b21fc54c76fe73c3fff38b4b9))
|
|
14
|
+
* **test:** restore darwin platform stubs for browser backend detection ([1ee1162](https://github.com/babelcloud/visionclaw/commit/1ee11627248f49e8035737eabcab83923b358579))
|
|
15
|
+
* **onboarding:** preserve manually managed remote access on register updates ([665c5b4](https://github.com/babelcloud/visionclaw/commit/665c5b489806decbaba122c767cdf9367ca99e29))
|
|
16
|
+
* **onboarding:** avoid nullable entries in remote access payload conversion ([16c51cc](https://github.com/babelcloud/visionclaw/commit/16c51ccb98d289b434225773b3b1264e220483ab))
|
|
17
|
+
* enqueue system message after /refresh compaction ([16f58bf](https://github.com/babelcloud/visionclaw/commit/16f58bf53d098168121675a5df64a1aee0d651e2))
|
|
18
|
+
* increase LLM stream silence timeouts from [60,120,180]s to [180,240,300]s ([125c8e7](https://github.com/babelcloud/visionclaw/commit/125c8e735b40750e5db968c2734b36fcbbf42183))
|
|
19
|
+
* **onboarding:** sync agent avatar from profile image ([e570d2b](https://github.com/babelcloud/visionclaw/commit/e570d2bd7694a69135d9e73eafa265f09410ba13))
|
|
20
|
+
* destructure ownerConfig from deps to fix lint errors ([271431e](https://github.com/babelcloud/visionclaw/commit/271431e2193235faf20548d66585c18ee87fcf86))
|
|
21
|
+
* remove OBS server and tunnel from /status healthchecks ([dd64288](https://github.com/babelcloud/visionclaw/commit/dd64288bbe8fa90fb38bb3bb47b2314f7ea35e6a))
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
* fall back to Playwright when Playwriter relay is unreachable ([886ae88](https://github.com/babelcloud/visionclaw/commit/886ae88a62357d1704d2112f0da7bf6b991e6984))
|
|
26
|
+
* add fallback wallpaper row and improve font handling ([571b5c2](https://github.com/babelcloud/visionclaw/commit/571b5c2e06fb9c0d75f34ce19dd5993895a7280b))
|
|
27
|
+
* **onboarding:** improve machine remote access management ([5f50b96](https://github.com/babelcloud/visionclaw/commit/5f50b9697376c25123ad5c3311ed484ddc4cf7d6))
|
|
28
|
+
* implement Chrome session management for Playwriter ([856e8db](https://github.com/babelcloud/visionclaw/commit/856e8db1b3166289c7d46ace568e5661dca6c44f))
|
|
29
|
+
* enhance dual browser backend detection and update UI labels for Playwriter ([43c615f](https://github.com/babelcloud/visionclaw/commit/43c615f6bff5a2e695463fd1ebb343e572e1f39f))
|
|
30
|
+
* add CLI maintenance commands (ctx status, ctx compact, agent status) (#218) ([989c14b](https://github.com/babelcloud/visionclaw/commit/989c14b3e1a627b7a54663a6be0796df80038518))
|
|
31
|
+
* **onboarding:** support multi-software remote access credentials ([af3d489](https://github.com/babelcloud/visionclaw/commit/af3d4894e4fb5d85daac073892679204beadca3b))
|
|
32
|
+
* send "back online" notification after agent restart ([aa8b8e7](https://github.com/babelcloud/visionclaw/commit/aa8b8e740f26d59653fc5bf4d67db4c5fc40773b))
|
|
33
|
+
* reduce thinking effort on silence timeout retries ([931455a](https://github.com/babelcloud/visionclaw/commit/931455a131c1b663022e4506a3d6c3d7811a0eaa))
|
|
34
|
+
* Enhance memory tracking and recall ([a911451](https://github.com/babelcloud/visionclaw/commit/a911451280ce7ac795153aec5426f4fc5008ee5a))
|
|
35
|
+
* Implement subagent tracking and reporting in ActivityTracker ([4ace786](https://github.com/babelcloud/visionclaw/commit/4ace7860e64e0af80f32914451d38e0a9468b234))
|
|
36
|
+
* Enhance /refresh command handling and session management ([0a17d9a](https://github.com/babelcloud/visionclaw/commit/0a17d9a0e033e355f17855548a385187f07ffbb8))
|
|
37
|
+
* Support language and timezone settings ([7fd7036](https://github.com/babelcloud/visionclaw/commit/7fd70361f1cc29f1b8ef74d7d3bcb9a80bb2de56))
|
|
38
|
+
* **onboarding:** constrain machine status editing and lock serial updates ([e97c6f2](https://github.com/babelcloud/visionclaw/commit/e97c6f27af3a9d2f9982326f8180deaf200340fa))
|
|
39
|
+
* **onboarding:** add TLS certificate check to Tunnel/DNS tab ([3bb8318](https://github.com/babelcloud/visionclaw/commit/3bb8318cfeac9939f1eae79d1df77fc6f45e44fa))
|
|
40
|
+
* add /set command for language + timezone, and onboarding preferences ([b1fae91](https://github.com/babelcloud/visionclaw/commit/b1fae91ee878edb1fd542174864402f65c5343b6))
|
|
41
|
+
* add i18n support for system messages (en + zh) ([a9d16c2](https://github.com/babelcloud/visionclaw/commit/a9d16c21703834a9e795a0b6127f73e79bbee50f))
|
|
42
|
+
* auto-detect Playwriter extension and enable prepare-mac install step ([74db129](https://github.com/babelcloud/visionclaw/commit/74db1293edcad67b36b1724b9e327182a265e397))
|
|
43
|
+
* add dual browser backend support (Playwright CDP + Playwriter Extension) ([88620b0](https://github.com/babelcloud/visionclaw/commit/88620b04dc768daefb7bc871d1db0106652ad5b7))
|
|
44
|
+
|
|
45
|
+
|
|
1
46
|
## [0.1.186](https://github.com/babelcloud/visionclaw/compare/v0.1.185...v0.1.186) (2026-04-22)
|
|
2
47
|
|
|
3
48
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applied-credential signature manifest.
|
|
3
|
+
*
|
|
4
|
+
* Persisted alongside materialised tunnel credentials at
|
|
5
|
+
* `<profileTunnelDir>/.applied-signature.json`. Records the signature
|
|
6
|
+
* the server handed us with the last bundle we successfully applied.
|
|
7
|
+
*
|
|
8
|
+
* The manifest is the *only* piece of state the agent has to remember
|
|
9
|
+
* across process restarts about credential bundles — everything else
|
|
10
|
+
* (creds JSON, config.yml) is reconstructable from a fresh server
|
|
11
|
+
* delivery. The signature is opaque to us; we just echo it back on the
|
|
12
|
+
* next heartbeat so the server can decide whether to re-ship the body.
|
|
13
|
+
*
|
|
14
|
+
* Self-healing properties (intentional):
|
|
15
|
+
* - Manifest missing on disk → no signature sent → server sends
|
|
16
|
+
* bundle → we re-apply and rewrite the manifest. Recovers any
|
|
17
|
+
* install where the manifest was deleted but credentials weren't,
|
|
18
|
+
* or vice versa.
|
|
19
|
+
* - Manifest present but probe says no live tunnel files → we treat
|
|
20
|
+
* the manifest as unusable (return null from `loadIfValid`), the
|
|
21
|
+
* next heartbeat goes signature-free, the server re-ships the
|
|
22
|
+
* bundle, and we land back in a coherent state.
|
|
23
|
+
* - Process restart with everything intact → manifest signature is
|
|
24
|
+
* sent on the startup heartbeat, server replies with no
|
|
25
|
+
* `credentialBundle`, no disk writes, no tunnel restart.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Read the persisted signature, if any. Returns `null` when the file
|
|
29
|
+
* does not exist, fails to parse, has the wrong shape, or refers to a
|
|
30
|
+
* tunnel state that no longer matches what's on disk.
|
|
31
|
+
*
|
|
32
|
+
* The on-disk-coherence check (`probeLocalTunnelCredential()`) is what
|
|
33
|
+
* gives us self-healing: a stranded manifest from a wiped tunnel dir
|
|
34
|
+
* gets ignored, forcing a fresh bundle on the next heartbeat.
|
|
35
|
+
*/
|
|
36
|
+
export declare function loadAppliedSignatureIfValid(): string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Persist the signature for the most recently applied bundle. Best-
|
|
39
|
+
* effort: a write failure is logged but does not throw, since the
|
|
40
|
+
* worst case is just an extra full-bundle delivery on the next
|
|
41
|
+
* heartbeat.
|
|
42
|
+
*
|
|
43
|
+
* The file is written with 0o600 to match the credentials sitting next
|
|
44
|
+
* to it; the directory itself is already 0o700 from
|
|
45
|
+
* `runtime-credentials.ts`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function writeAppliedSignature(signature: string): void;
|
|
48
|
+
/**
|
|
49
|
+
* Forget the persisted signature. Used by tests; production code never
|
|
50
|
+
* needs to delete it explicitly because writes overwrite in place.
|
|
51
|
+
*/
|
|
52
|
+
export declare function __clearAppliedSignatureForTests(): void;
|
|
53
|
+
//# sourceMappingURL=applied-credential-signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"applied-credential-signature.d.ts","sourceRoot":"","sources":["../../src/agent/applied-credential-signature.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAwBH;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,GAAG,IAAI,CA2C3D;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAgC7D;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,IAAI,IAAI,CAMtD"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applied-credential signature manifest.
|
|
3
|
+
*
|
|
4
|
+
* Persisted alongside materialised tunnel credentials at
|
|
5
|
+
* `<profileTunnelDir>/.applied-signature.json`. Records the signature
|
|
6
|
+
* the server handed us with the last bundle we successfully applied.
|
|
7
|
+
*
|
|
8
|
+
* The manifest is the *only* piece of state the agent has to remember
|
|
9
|
+
* across process restarts about credential bundles — everything else
|
|
10
|
+
* (creds JSON, config.yml) is reconstructable from a fresh server
|
|
11
|
+
* delivery. The signature is opaque to us; we just echo it back on the
|
|
12
|
+
* next heartbeat so the server can decide whether to re-ship the body.
|
|
13
|
+
*
|
|
14
|
+
* Self-healing properties (intentional):
|
|
15
|
+
* - Manifest missing on disk → no signature sent → server sends
|
|
16
|
+
* bundle → we re-apply and rewrite the manifest. Recovers any
|
|
17
|
+
* install where the manifest was deleted but credentials weren't,
|
|
18
|
+
* or vice versa.
|
|
19
|
+
* - Manifest present but probe says no live tunnel files → we treat
|
|
20
|
+
* the manifest as unusable (return null from `loadIfValid`), the
|
|
21
|
+
* next heartbeat goes signature-free, the server re-ships the
|
|
22
|
+
* bundle, and we land back in a coherent state.
|
|
23
|
+
* - Process restart with everything intact → manifest signature is
|
|
24
|
+
* sent on the startup heartbeat, server replies with no
|
|
25
|
+
* `credentialBundle`, no disk writes, no tunnel restart.
|
|
26
|
+
*/
|
|
27
|
+
import fs from "node:fs";
|
|
28
|
+
import path from "node:path";
|
|
29
|
+
import { getProfileTunnelDir } from "../config/index.js";
|
|
30
|
+
import { probeLocalTunnelCredential } from "./runtime-credentials.js";
|
|
31
|
+
import { logger } from "../logger.js";
|
|
32
|
+
const MANIFEST_FILENAME = ".applied-signature.json";
|
|
33
|
+
const MANIFEST_VERSION = 1;
|
|
34
|
+
function getManifestPath() {
|
|
35
|
+
return path.join(getProfileTunnelDir(), MANIFEST_FILENAME);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Read the persisted signature, if any. Returns `null` when the file
|
|
39
|
+
* does not exist, fails to parse, has the wrong shape, or refers to a
|
|
40
|
+
* tunnel state that no longer matches what's on disk.
|
|
41
|
+
*
|
|
42
|
+
* The on-disk-coherence check (`probeLocalTunnelCredential()`) is what
|
|
43
|
+
* gives us self-healing: a stranded manifest from a wiped tunnel dir
|
|
44
|
+
* gets ignored, forcing a fresh bundle on the next heartbeat.
|
|
45
|
+
*/
|
|
46
|
+
export function loadAppliedSignatureIfValid() {
|
|
47
|
+
const manifestPath = getManifestPath();
|
|
48
|
+
if (!fs.existsSync(manifestPath))
|
|
49
|
+
return null;
|
|
50
|
+
let raw;
|
|
51
|
+
try {
|
|
52
|
+
raw = fs.readFileSync(manifestPath, "utf-8");
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
logger.warn(`Applied-signature manifest unreadable: ${err instanceof Error ? err.message : String(err)}`);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(raw);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
logger.warn(`Applied-signature manifest malformed; ignoring`);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (!parsed ||
|
|
67
|
+
typeof parsed !== "object" ||
|
|
68
|
+
!("signature" in parsed) ||
|
|
69
|
+
typeof parsed.signature !== "string" ||
|
|
70
|
+
parsed.signature.trim() === "") {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const signature = parsed.signature.trim();
|
|
74
|
+
// Coherence: a manifest is only meaningful when the credentials it
|
|
75
|
+
// refers to actually still exist on disk. If the tunnel files were
|
|
76
|
+
// wiped (e.g. operator deleted `~/.visionclaw/profiles/default/tunnel`)
|
|
77
|
+
// the stored signature lies about our state, so pretend we have none
|
|
78
|
+
// and let the server reissue the bundle.
|
|
79
|
+
if (probeLocalTunnelCredential() === null) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return signature;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Persist the signature for the most recently applied bundle. Best-
|
|
86
|
+
* effort: a write failure is logged but does not throw, since the
|
|
87
|
+
* worst case is just an extra full-bundle delivery on the next
|
|
88
|
+
* heartbeat.
|
|
89
|
+
*
|
|
90
|
+
* The file is written with 0o600 to match the credentials sitting next
|
|
91
|
+
* to it; the directory itself is already 0o700 from
|
|
92
|
+
* `runtime-credentials.ts`.
|
|
93
|
+
*/
|
|
94
|
+
export function writeAppliedSignature(signature) {
|
|
95
|
+
if (typeof signature !== "string" || signature.trim() === "")
|
|
96
|
+
return;
|
|
97
|
+
const manifestPath = getManifestPath();
|
|
98
|
+
const dir = path.dirname(manifestPath);
|
|
99
|
+
try {
|
|
100
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// best-effort; the credential apply path will have created it
|
|
104
|
+
}
|
|
105
|
+
const payload = {
|
|
106
|
+
version: MANIFEST_VERSION,
|
|
107
|
+
signature: signature.trim(),
|
|
108
|
+
appliedAt: new Date().toISOString(),
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
fs.writeFileSync(manifestPath, JSON.stringify(payload, null, 2), "utf-8");
|
|
112
|
+
if (process.platform !== "win32") {
|
|
113
|
+
try {
|
|
114
|
+
fs.chmodSync(manifestPath, 0o600);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// best-effort; directory perms still protect
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
logger.warn(`Failed to persist applied-signature manifest: ${err instanceof Error ? err.message : String(err)}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Forget the persisted signature. Used by tests; production code never
|
|
127
|
+
* needs to delete it explicitly because writes overwrite in place.
|
|
128
|
+
*/
|
|
129
|
+
export function __clearAppliedSignatureForTests() {
|
|
130
|
+
try {
|
|
131
|
+
fs.rmSync(getManifestPath(), { force: true });
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// ignore
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=applied-credential-signature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"applied-credential-signature.js","sourceRoot":"","sources":["../../src/agent/applied-credential-signature.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tunnel credential coordinator.
|
|
3
|
+
*
|
|
4
|
+
* Owns every tunnel-specific concern around a heartbeat-delivered
|
|
5
|
+
* `runtimeCredentials` bundle:
|
|
6
|
+
*
|
|
7
|
+
* - materialising the bundle to disk (via `runtime-credentials.ts`)
|
|
8
|
+
* - deciding whether a cloudflared bounce is needed
|
|
9
|
+
* - coalescing concurrent restart triggers
|
|
10
|
+
* - tracking whether the next heartbeat should still ask the server
|
|
11
|
+
* to bootstrap a credential (i.e. nothing usable is on disk yet)
|
|
12
|
+
*
|
|
13
|
+
* Extracted out of `HeartbeatManager` so the heartbeat layer stays
|
|
14
|
+
* concerned with heartbeats and command dispatch; the tunnel coupling
|
|
15
|
+
* lives here instead.
|
|
16
|
+
*/
|
|
17
|
+
import type { RuntimeCredentials } from "../onboarding/onboarding-service-client.js";
|
|
18
|
+
export interface TunnelCredentialHandlerOptions {
|
|
19
|
+
/** Canonical agent name used to validate tunnel-name drift. */
|
|
20
|
+
agentName: string;
|
|
21
|
+
/**
|
|
22
|
+
* Callback that bounces cloudflared (typically the `restartTunnel`
|
|
23
|
+
* returned from `startObsServer`). Invoked when a newly materialised
|
|
24
|
+
* credential differs from what's on disk — otherwise cloudflared keeps
|
|
25
|
+
* running against the UUID it was spawned with (it does not hot-reload
|
|
26
|
+
* `config.yml`).
|
|
27
|
+
*
|
|
28
|
+
* Optional so test harnesses and non-obs builds can skip the restart
|
|
29
|
+
* side-effect entirely.
|
|
30
|
+
*/
|
|
31
|
+
restartTunnel?: () => Promise<string | null>;
|
|
32
|
+
}
|
|
33
|
+
export declare class TunnelCredentialHandler {
|
|
34
|
+
private readonly agentName;
|
|
35
|
+
private readonly restartTunnel?;
|
|
36
|
+
private _restartInFlight;
|
|
37
|
+
/**
|
|
38
|
+
* Set whenever a `tunnelChanged` apply lands while a previous restart
|
|
39
|
+
* is still in flight. The in-flight restart already snapshotted disk
|
|
40
|
+
* state at its start (`resolveTunnelOpts()` in `obs/server.ts`), so
|
|
41
|
+
* the second rotation needs its own bounce — we re-kick from the
|
|
42
|
+
* restart's `finally` block when this flag is set.
|
|
43
|
+
*
|
|
44
|
+
* Stored as the latest `ApplyRuntimeCredentialsResult` so the rotation
|
|
45
|
+
* label in the follow-up log line still reflects the *latest* tunnel
|
|
46
|
+
* delivery, not the first one that was already restarted.
|
|
47
|
+
*/
|
|
48
|
+
private _pendingRestart;
|
|
49
|
+
/**
|
|
50
|
+
* True whenever we still need the server to include a
|
|
51
|
+
* `runtimeCredentials` bundle on the next heartbeat — i.e. nothing
|
|
52
|
+
* usable is on disk yet. Flips to false the first time we materialise
|
|
53
|
+
* a credential successfully, and flips back to true whenever a probe
|
|
54
|
+
* finds the profile tunnel dir empty again.
|
|
55
|
+
*/
|
|
56
|
+
private _needsBootstrap;
|
|
57
|
+
/**
|
|
58
|
+
* Latch so we only emit the "running on pre-existing local credential,
|
|
59
|
+
* awaiting server-issued bundle" notice once per process. The state it
|
|
60
|
+
* describes is benign but operators want to see it called out in logs
|
|
61
|
+
* right after an upgrade from a legacy-adopted agent when the server
|
|
62
|
+
* hasn't been provisioned yet.
|
|
63
|
+
*/
|
|
64
|
+
private _loggedAwaitingServerBundle;
|
|
65
|
+
constructor(options: TunnelCredentialHandlerOptions);
|
|
66
|
+
/** Whether the next heartbeat should request a bootstrap bundle. */
|
|
67
|
+
get needsBootstrap(): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Handle a `runtimeCredentials` bundle from a heartbeat response.
|
|
70
|
+
*
|
|
71
|
+
* Defensive by contract: never throws — a failed materialisation must
|
|
72
|
+
* not block heartbeats. Missing/undefined bundles are a no-op.
|
|
73
|
+
*/
|
|
74
|
+
handle(bundle: RuntimeCredentials | undefined): void;
|
|
75
|
+
/**
|
|
76
|
+
* Kick `restartTunnel` in the background, coalescing concurrent
|
|
77
|
+
* invocations. Logged at `system` level because a restart is a
|
|
78
|
+
* visible event for operators tailing pm2 logs.
|
|
79
|
+
*
|
|
80
|
+
* If a restart is already in flight when we're called, we don't drop
|
|
81
|
+
* the trigger — the in-flight restart sampled disk before this newer
|
|
82
|
+
* credential was materialised and would launch cloudflared against
|
|
83
|
+
* the stale UUID. Instead we record the latest result in
|
|
84
|
+
* `_pendingRestart` and re-kick from the in-flight restart's
|
|
85
|
+
* `finally` block, which guarantees one extra restart that picks up
|
|
86
|
+
* whatever's now on disk.
|
|
87
|
+
*/
|
|
88
|
+
private triggerRestart;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=tunnel-credential-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel-credential-handler.d.ts","sourceRoot":"","sources":["../../src/agent/tunnel-credential-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AAQrF,MAAM,WAAW,8BAA8B;IAC7C,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC9C;AAED,qBAAa,uBAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAA+B;IAC9D,OAAO,CAAC,gBAAgB,CAA8B;IACtD;;;;;;;;;;OAUG;IACH,OAAO,CAAC,eAAe,CAA8C;IACrE;;;;;;OAMG;IACH,OAAO,CAAC,eAAe,CAAQ;IAC/B;;;;;;OAMG;IACH,OAAO,CAAC,2BAA2B,CAAS;gBAEhC,OAAO,EAAE,8BAA8B;IAKnD,oEAAoE;IACpE,IAAI,cAAc,IAAI,OAAO,CAE5B;IAED;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,SAAS,GAAG,IAAI;IAiDpD;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,cAAc;CA6CvB"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tunnel credential coordinator.
|
|
3
|
+
*
|
|
4
|
+
* Owns every tunnel-specific concern around a heartbeat-delivered
|
|
5
|
+
* `runtimeCredentials` bundle:
|
|
6
|
+
*
|
|
7
|
+
* - materialising the bundle to disk (via `runtime-credentials.ts`)
|
|
8
|
+
* - deciding whether a cloudflared bounce is needed
|
|
9
|
+
* - coalescing concurrent restart triggers
|
|
10
|
+
* - tracking whether the next heartbeat should still ask the server
|
|
11
|
+
* to bootstrap a credential (i.e. nothing usable is on disk yet)
|
|
12
|
+
*
|
|
13
|
+
* Extracted out of `HeartbeatManager` so the heartbeat layer stays
|
|
14
|
+
* concerned with heartbeats and command dispatch; the tunnel coupling
|
|
15
|
+
* lives here instead.
|
|
16
|
+
*/
|
|
17
|
+
import { applyRuntimeCredentials, probeLocalTunnelCredential, } from "./runtime-credentials.js";
|
|
18
|
+
import { logger } from "../logger.js";
|
|
19
|
+
export class TunnelCredentialHandler {
|
|
20
|
+
agentName;
|
|
21
|
+
restartTunnel;
|
|
22
|
+
_restartInFlight = null;
|
|
23
|
+
/**
|
|
24
|
+
* Set whenever a `tunnelChanged` apply lands while a previous restart
|
|
25
|
+
* is still in flight. The in-flight restart already snapshotted disk
|
|
26
|
+
* state at its start (`resolveTunnelOpts()` in `obs/server.ts`), so
|
|
27
|
+
* the second rotation needs its own bounce — we re-kick from the
|
|
28
|
+
* restart's `finally` block when this flag is set.
|
|
29
|
+
*
|
|
30
|
+
* Stored as the latest `ApplyRuntimeCredentialsResult` so the rotation
|
|
31
|
+
* label in the follow-up log line still reflects the *latest* tunnel
|
|
32
|
+
* delivery, not the first one that was already restarted.
|
|
33
|
+
*/
|
|
34
|
+
_pendingRestart = null;
|
|
35
|
+
/**
|
|
36
|
+
* True whenever we still need the server to include a
|
|
37
|
+
* `runtimeCredentials` bundle on the next heartbeat — i.e. nothing
|
|
38
|
+
* usable is on disk yet. Flips to false the first time we materialise
|
|
39
|
+
* a credential successfully, and flips back to true whenever a probe
|
|
40
|
+
* finds the profile tunnel dir empty again.
|
|
41
|
+
*/
|
|
42
|
+
_needsBootstrap = true;
|
|
43
|
+
/**
|
|
44
|
+
* Latch so we only emit the "running on pre-existing local credential,
|
|
45
|
+
* awaiting server-issued bundle" notice once per process. The state it
|
|
46
|
+
* describes is benign but operators want to see it called out in logs
|
|
47
|
+
* right after an upgrade from a legacy-adopted agent when the server
|
|
48
|
+
* hasn't been provisioned yet.
|
|
49
|
+
*/
|
|
50
|
+
_loggedAwaitingServerBundle = false;
|
|
51
|
+
constructor(options) {
|
|
52
|
+
this.agentName = options.agentName;
|
|
53
|
+
this.restartTunnel = options.restartTunnel;
|
|
54
|
+
}
|
|
55
|
+
/** Whether the next heartbeat should request a bootstrap bundle. */
|
|
56
|
+
get needsBootstrap() {
|
|
57
|
+
return this._needsBootstrap;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Handle a `runtimeCredentials` bundle from a heartbeat response.
|
|
61
|
+
*
|
|
62
|
+
* Defensive by contract: never throws — a failed materialisation must
|
|
63
|
+
* not block heartbeats. Missing/undefined bundles are a no-op.
|
|
64
|
+
*/
|
|
65
|
+
handle(bundle) {
|
|
66
|
+
let result = null;
|
|
67
|
+
try {
|
|
68
|
+
result = applyRuntimeCredentials(bundle, { agentName: this.agentName });
|
|
69
|
+
if (result.tunnelApplied) {
|
|
70
|
+
this._needsBootstrap = false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
logger.warn(`Runtime credential apply failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
75
|
+
}
|
|
76
|
+
// If the apply wrote new files to disk, bounce cloudflared so it
|
|
77
|
+
// picks up the fresh UUID/secret. Without this the obs server
|
|
78
|
+
// spawned cloudflared against whatever was on disk at startup, and
|
|
79
|
+
// a subsequent credential rotation (e.g. operator clicked
|
|
80
|
+
// "Provision tunnel" on the admin UI) would sit unused until the
|
|
81
|
+
// next manual agent restart.
|
|
82
|
+
if (result?.tunnelApplied && result.tunnelChanged && result.tunnel) {
|
|
83
|
+
this.triggerRestart(result);
|
|
84
|
+
}
|
|
85
|
+
// If we still don't have a local tunnel credential after applying,
|
|
86
|
+
// keep asking the server to bootstrap on the next heartbeat. Also
|
|
87
|
+
// covers the case where the server hasn't provisioned a tunnel for
|
|
88
|
+
// this agent yet — subsequent heartbeats stay cheap (only the flag
|
|
89
|
+
// is sent), and we pick up the credential the moment it exists.
|
|
90
|
+
const probed = probeLocalTunnelCredential();
|
|
91
|
+
if (probed === null) {
|
|
92
|
+
this._needsBootstrap = true;
|
|
93
|
+
}
|
|
94
|
+
else if (!result?.tunnelApplied &&
|
|
95
|
+
!this._loggedAwaitingServerBundle) {
|
|
96
|
+
// Covers the legacy-adoption scenario: the agent was upgraded and
|
|
97
|
+
// obs is already running on an adopted `~/.cloudflared` credential
|
|
98
|
+
// that's now in the profile dir, but the server returned no
|
|
99
|
+
// runtimeCredentials bundle (operator hasn't clicked "Provision
|
|
100
|
+
// tunnel" yet). Call this out exactly once so the state is
|
|
101
|
+
// unambiguous in logs — every subsequent heartbeat will still
|
|
102
|
+
// re-ask via `needsBootstrap`, silently, until the bundle lands.
|
|
103
|
+
this._loggedAwaitingServerBundle = true;
|
|
104
|
+
logger.system(`Runtime credential: operating on pre-existing local tunnel ${probed.tunnelId}; awaiting server-issued bundle`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Kick `restartTunnel` in the background, coalescing concurrent
|
|
109
|
+
* invocations. Logged at `system` level because a restart is a
|
|
110
|
+
* visible event for operators tailing pm2 logs.
|
|
111
|
+
*
|
|
112
|
+
* If a restart is already in flight when we're called, we don't drop
|
|
113
|
+
* the trigger — the in-flight restart sampled disk before this newer
|
|
114
|
+
* credential was materialised and would launch cloudflared against
|
|
115
|
+
* the stale UUID. Instead we record the latest result in
|
|
116
|
+
* `_pendingRestart` and re-kick from the in-flight restart's
|
|
117
|
+
* `finally` block, which guarantees one extra restart that picks up
|
|
118
|
+
* whatever's now on disk.
|
|
119
|
+
*/
|
|
120
|
+
triggerRestart(result) {
|
|
121
|
+
if (!this.restartTunnel || !result.tunnel)
|
|
122
|
+
return;
|
|
123
|
+
if (this._restartInFlight) {
|
|
124
|
+
// Always overwrite — only the most recent credential matters; any
|
|
125
|
+
// intermediate bundles will be on disk by the time the follow-up
|
|
126
|
+
// restart re-probes via `obs/server.ts:resolveTunnelOpts()`.
|
|
127
|
+
this._pendingRestart = result;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const newTunnelId = result.tunnel.tunnelId;
|
|
131
|
+
const previousTunnelId = result.previousTunnelId;
|
|
132
|
+
const rotationLabel = previousTunnelId === null
|
|
133
|
+
? "initial"
|
|
134
|
+
: previousTunnelId === newTunnelId
|
|
135
|
+
? "refreshed"
|
|
136
|
+
: `rotated (${previousTunnelId} → ${newTunnelId})`;
|
|
137
|
+
logger.system(`Runtime credential: tunnel ${rotationLabel}; restarting cloudflared`);
|
|
138
|
+
const restart = this.restartTunnel;
|
|
139
|
+
this._restartInFlight = (async () => {
|
|
140
|
+
try {
|
|
141
|
+
await restart();
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
logger.warn(`Failed to restart tunnel after credential update: ${err instanceof Error ? err.message : String(err)}`);
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
this._restartInFlight = null;
|
|
148
|
+
// Drain any rotation that landed mid-flight. We re-enter
|
|
149
|
+
// triggerRestart synchronously (not via setImmediate) so the
|
|
150
|
+
// follow-up restart starts as soon as the previous Promise
|
|
151
|
+
// resolves, but `_restartInFlight` is already null above so the
|
|
152
|
+
// recursive call goes down the spawn path, not the coalesce path.
|
|
153
|
+
const pending = this._pendingRestart;
|
|
154
|
+
if (pending) {
|
|
155
|
+
this._pendingRestart = null;
|
|
156
|
+
this.triggerRestart(pending);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
})();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=tunnel-credential-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel-credential-handler.js","sourceRoot":"","sources":["../../src/agent/tunnel-credential-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PAYG (Pay as You Go) command handler for the `request_payment` heartbeat command.
|
|
3
|
+
*
|
|
4
|
+
* This is deterministic bot code u2014 the LLM agent is never involved in
|
|
5
|
+
* payment flows. The handler:
|
|
6
|
+
*
|
|
7
|
+
* 1. Receives `request_payment` from the backend via heartbeat (includes amounts)
|
|
8
|
+
* 2. Sends an amount selection prompt (inline keyboard) to the owner via Telegram
|
|
9
|
+
* 3. Listens for `billing_payg_selected` events from the Telegram adapter
|
|
10
|
+
* 4. Calls the backend to create a Stripe Payment Link (one-time)
|
|
11
|
+
* 5. Sends the payment URL back to the owner as a URL button
|
|
12
|
+
*/
|
|
13
|
+
import type { CommandHandler } from "../agent/command-dispatcher.js";
|
|
14
|
+
import type { ChannelManager } from "../channels/manager.js";
|
|
15
|
+
import type { BillingPaygSelectedEvent } from "../channels/interface.js";
|
|
16
|
+
import type { OnboardingServiceClient } from "../onboarding/onboarding-service-client.js";
|
|
17
|
+
export interface PaygHandler extends CommandHandler {
|
|
18
|
+
/** Handle a billing_payg_selected event (user tapped an amount button). */
|
|
19
|
+
handleAmountSelected: (event: BillingPaygSelectedEvent) => Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create a CommandHandler for the "request_payment" heartbeat command.
|
|
23
|
+
*
|
|
24
|
+
* @param channelManager - To send PAYG prompts and listen for amount selection events
|
|
25
|
+
* @param ownerChatId - The owner's Telegram chat ID
|
|
26
|
+
* @param serviceClient - Onboarding service client for creating payment links
|
|
27
|
+
*/
|
|
28
|
+
export declare function createPaygHandler(channelManager: ChannelManager, ownerChatId: number, serviceClient: OnboardingServiceClient | null): PaygHandler;
|
|
29
|
+
//# sourceMappingURL=payg-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payg-handler.d.ts","sourceRoot":"","sources":["../../src/billing/payg-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACzE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,4CAA4C,CAAC;AAW1F,MAAM,WAAW,WAAY,SAAQ,cAAc;IACjD,2EAA2E;IAC3E,oBAAoB,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1E;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,uBAAuB,GAAG,IAAI,GAC5C,WAAW,CAmGb"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PAYG (Pay as You Go) command handler for the `request_payment` heartbeat command.
|
|
3
|
+
*
|
|
4
|
+
* This is deterministic bot code u2014 the LLM agent is never involved in
|
|
5
|
+
* payment flows. The handler:
|
|
6
|
+
*
|
|
7
|
+
* 1. Receives `request_payment` from the backend via heartbeat (includes amounts)
|
|
8
|
+
* 2. Sends an amount selection prompt (inline keyboard) to the owner via Telegram
|
|
9
|
+
* 3. Listens for `billing_payg_selected` events from the Telegram adapter
|
|
10
|
+
* 4. Calls the backend to create a Stripe Payment Link (one-time)
|
|
11
|
+
* 5. Sends the payment URL back to the owner as a URL button
|
|
12
|
+
*/
|
|
13
|
+
import { logger } from "../logger.js";
|
|
14
|
+
const MAX_RETRIES = 2;
|
|
15
|
+
const RETRY_DELAY_MS = 2_000;
|
|
16
|
+
function sleep(ms) {
|
|
17
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a CommandHandler for the "request_payment" heartbeat command.
|
|
21
|
+
*
|
|
22
|
+
* @param channelManager - To send PAYG prompts and listen for amount selection events
|
|
23
|
+
* @param ownerChatId - The owner's Telegram chat ID
|
|
24
|
+
* @param serviceClient - Onboarding service client for creating payment links
|
|
25
|
+
*/
|
|
26
|
+
export function createPaygHandler(channelManager, ownerChatId, serviceClient) {
|
|
27
|
+
async function handleAmountSelected(event) {
|
|
28
|
+
if (!serviceClient) {
|
|
29
|
+
logger.warn("[payg] No service client u2014 cannot create payment link");
|
|
30
|
+
await channelManager.sendMessage("telegram", String(event.chatId), "Payment is not configured. Please contact support.");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
logger.system(`[payg] Amount selected: $${event.amount} by chat ${event.chatId}`);
|
|
34
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
35
|
+
try {
|
|
36
|
+
const result = await serviceClient.createPaymentLink(event.amount, event.chatId);
|
|
37
|
+
if (result?.checkoutUrl) {
|
|
38
|
+
await channelManager.sendTelegramCheckoutLink(event.chatId, `$${event.amount} Top-Up`, result.checkoutUrl);
|
|
39
|
+
logger.system(`[payg] Payment link sent to chat ${event.chatId} for $${event.amount}`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (attempt < MAX_RETRIES) {
|
|
43
|
+
logger.warn(`[payg] Payment link attempt ${attempt + 1} failed, retrying...`);
|
|
44
|
+
await channelManager.sendMessage("telegram", String(event.chatId), "Payment setup failed, retrying...");
|
|
45
|
+
await sleep(RETRY_DELAY_MS);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
50
|
+
logger.err(`[payg] Payment link attempt ${attempt + 1} error: ${errMsg}`);
|
|
51
|
+
if (attempt < MAX_RETRIES) {
|
|
52
|
+
await sleep(RETRY_DELAY_MS);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
logger.err("[payg] All payment link attempts failed");
|
|
57
|
+
await channelManager.sendMessage("telegram", String(event.chatId), "Payment setup failed. Please try again later.");
|
|
58
|
+
}
|
|
59
|
+
const handler = Object.assign(async (_commandId, payload) => {
|
|
60
|
+
const raw = (payload ?? {});
|
|
61
|
+
const amounts = Array.isArray(raw.amounts) ? raw.amounts : [];
|
|
62
|
+
if (amounts.length === 0) {
|
|
63
|
+
logger.warn("[payg] request_payment received with no amounts");
|
|
64
|
+
return {
|
|
65
|
+
status: "failed",
|
|
66
|
+
result: JSON.stringify({ error: "No amounts in payload" }),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const typedPayload = {
|
|
70
|
+
reason: typeof raw.reason === "string" ? raw.reason : undefined,
|
|
71
|
+
amounts,
|
|
72
|
+
};
|
|
73
|
+
logger.system(`[payg] Request payment received with amounts: ${typedPayload.amounts.join(", ")}`);
|
|
74
|
+
try {
|
|
75
|
+
await channelManager.sendTelegramPaygPrompt(ownerChatId, typedPayload);
|
|
76
|
+
return {
|
|
77
|
+
status: "completed",
|
|
78
|
+
result: JSON.stringify({ message: "PAYG prompt sent to owner" }),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
83
|
+
logger.err(`[payg] Failed to send PAYG prompt: ${errMsg}`);
|
|
84
|
+
return {
|
|
85
|
+
status: "failed",
|
|
86
|
+
result: JSON.stringify({ error: errMsg }),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}, { handleAmountSelected });
|
|
90
|
+
return handler;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=payg-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payg-handler.js","sourceRoot":"","sources":["../../src/billing/payg-handler.ts"],"names":[],"mappings":"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"}
|