@jobshimo/browser-link 0.5.2 → 0.5.4
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/dist/bridge/dispatch.d.ts +6 -0
- package/dist/bridge/dispatch.js +2 -2
- package/dist/bridge/dispatch.js.map +1 -1
- package/dist/bridge/events.d.ts +1 -1
- package/dist/bridge/events.js.map +1 -1
- package/dist/bridge/server.d.ts +7 -1
- package/dist/bridge/server.js +42 -5
- package/dist/bridge/server.js.map +1 -1
- package/dist/commands/self-update.d.ts +59 -0
- package/dist/commands/self-update.js +110 -0
- package/dist/commands/self-update.js.map +1 -0
- package/dist/commands/updates.js +27 -1
- package/dist/commands/updates.js.map +1 -1
- package/dist/config.d.ts +12 -10
- package/dist/config.js +55 -20
- package/dist/config.js.map +1 -1
- package/dist/extension/background.js +56 -6
- package/dist/extension/background.js.map +1 -1
- package/dist/extension/manifest.json +8 -3
- package/dist/extension/popup.d.ts +4 -0
- package/dist/extension/popup.html +243 -48
- package/dist/extension/popup.js +37 -24
- package/dist/extension/popup.js.map +1 -1
- package/dist/server.js +50 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/browser-definitions.js +36 -1
- package/dist/tools/browser-definitions.js.map +1 -1
- package/dist/tools/browser-dispatch.d.ts +39 -3
- package/dist/tools/browser-dispatch.js +170 -63
- package/dist/tools/browser-dispatch.js.map +1 -1
- package/dist/tools/server-instructions.d.ts +1 -1
- package/dist/tools/server-instructions.js +32 -0
- package/dist/tools/server-instructions.js.map +1 -1
- package/dist/tools/tab-claims.d.ts +117 -0
- package/dist/tools/tab-claims.js +186 -0
- package/dist/tools/tab-claims.js.map +1 -0
- package/dist/ui/screens.js +57 -6
- package/dist/ui/screens.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-dispatch.js","sourceRoot":"","sources":["../../src/tools/browser-dispatch.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"browser-dispatch.js","sourceRoot":"","sources":["../../src/tools/browser-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EACL,mBAAmB,GAIpB,MAAM,iBAAiB,CAAC;AA2CzB;;;gEAGgE;AAChE,MAAM,kBAAkB,GAAG;IACzB,mBAAmB;IACnB,mBAAmB;IACnB,qBAAqB;IACrB,iBAAiB;IACjB,cAAc;IACd,kBAAkB;IAClB,kBAAkB;IAClB,iBAAiB;IACjB,iBAAiB;IACjB,sBAAsB;IACtB,eAAe;IACf,cAAc;IACd,kBAAkB;IAClB,gBAAgB;CACR,CAAC;AAEX,MAAM,qBAAqB,GAAwB,IAAI,GAAG,CAAC,kBAAkB,CAAC,CAAC;AAE/E,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,qBAAqB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEnC,qEAAqE;AACrE,SAAS,aAAa,CAAC,KAAe;IACpC,MAAM,WAAW,GAAgB;QAC/B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,MAAM,EAAE,KAAK,CAAC,MAAM;KACrB,CAAC;IACF,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;QAAE,WAAW,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IAC/D,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;;2DAG2D;AAC3D,KAAK,UAAU,SAAS,CACtB,IAAY,EACZ,KAAa,EACb,MAAe,EACf,IAAqB,EACrB,MAAmB,EACnB,SAAkB;IAElB,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAClE,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;IACD,yEAAyE;IACzE,yEAAyE;IACzE,OAAO,SAAS,KAAK,SAAS;QAC5B,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC;QACtD,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,cAAc,CAAC,IAAqB,EAAE,MAAmB;IAChE,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC;QACzD,OAAO;YACL,GAAG,CAAC;YACJ,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI;YAC/C,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK;SACpC,CAAC;IAClC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,cAAc,CAAC,IAAa,EAAE,IAAqB,EAAE,MAAmB;IAC/E,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAIjD,CAAC;IACF,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAChD,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;QACpB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,aAAa;YACrB,OAAO,EAAE,6DAA6D;SACvE,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;IACzF,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;QAChB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC1F,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;AACrF,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAa,EAAE,IAAqB,EAAE,MAAmB;IACjF,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACzC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtD,IAAI,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACnC,MAAM,OAAO,GAIT,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IACzC,IAAI,MAAM,CAAC,QAAQ;QAAE,OAAO,CAAC,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACvE,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,YAAY,CAAC,IAAqB,EAAE,MAAmB;IAC9D,IAAI,CAAC,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC3C,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;AACtE,CAAC;AAED,SAAS,YAAY,CACnB,IAAa,EACb,IAAqB;IAErB,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAI,IAA8C,IAAI,EAAE,CAAC;IAClF,IAAI,CAAC,IAAI,CAAC,YAAY;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC;IACtF,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAC/B,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAY,EACZ,IAAa,EACb,IAAqB,EACrB,MAAmB;IAEnB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,EAAE,CAAC,CAAC;IAC3E,MAAM,QAAQ,GAAG,IAAuB,CAAC;IACzC,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,mBAAmB;YACtB,OAAO,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACtC,KAAK,mBAAmB;YACtB,OAAO,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAC5C,KAAK,qBAAqB;YACxB,OAAO,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAC9C,KAAK,iBAAiB;YACpB,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACpC,KAAK,cAAc;YACjB,OAAO,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;QAC9D,KAAK,kBAAkB,CAAC,CAAC,CAAC;YACxB,MAAM,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI,EAAE,GAAG,IAAgD,CAAC;YACvF,OAAO,SAAS,CACd,UAAU,EACV,YAAY,CAAC,IAAI,CAAC,EAClB,EAAE,GAAG,EAAE,aAAa,EAAE,EACtB,IAAI,EACJ,MAAM,EACN,mBAAmB,CACpB,CAAC;QACJ,CAAC;QACD,KAAK,kBAAkB;YACrB,OAAO,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;QAClE,KAAK,iBAAiB,CAAC,CAAC,CAAC;YACvB,MAAM,EAAE,KAAK,EAAE,GAAI,IAA2B,IAAI,EAAE,CAAC;YACrD,OAAO,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACxE,CAAC;QACD,KAAK,iBAAiB,CAAC,CAAC,CAAC;YACvB,MAAM,EAAE,UAAU,EAAE,GAAI,IAAgC,IAAI,EAAE,CAAC;YAC/D,OAAO,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;QAC7E,CAAC;QACD,KAAK,sBAAsB,CAAC,CAAC,CAAC;YAC5B,MAAM,EAAE,UAAU,EAAE,GAAG,IAA8B,CAAC;YACtD,OAAO,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;QAClF,CAAC;QACD,KAAK,eAAe,CAAC,CAAC,CAAC;YACrB,MAAM,EAAE,QAAQ,EAAE,GAAG,IAA4B,CAAC;YAClD,OAAO,SAAS,CAAC,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAC5E,CAAC;QACD,KAAK,cAAc,CAAC,CAAC,CAAC;YACpB,MAAM,EACJ,QAAQ,EACR,IAAI,EACJ,KAAK,GAAG,KAAK,GACd,GAAG,IAA2D,CAAC;YAChE,OAAO,SAAS,CAAC,MAAM,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QACxF,CAAC;QACD,KAAK,kBAAkB,CAAC,CAAC,CAAC;YACxB,MAAM,EAAE,UAAU,EAAE,GAAG,IAA8B,CAAC;YACtD,OAAO,SAAS,CAAC,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QACjF,CAAC;QACD,KAAK,gBAAgB;YACnB,OAAO,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAClC,OAAO,CAAC,CAAC,CAAC;YACR,wEAAwE;YACxE,yEAAyE;YACzE,uEAAuE;YACvE,QAAQ;YACR,MAAM,WAAW,GAAU,QAAQ,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,2BAA2B,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** Usage protocol pushed to the MCP client on `initialize`. Plain string,
|
|
2
2
|
* intentionally kept short. Edit here when the protocol changes. */
|
|
3
|
-
export declare const SERVER_INSTRUCTIONS = "browser-link bridges Claude Code to the Chrome tabs the user has\nexplicitly connected through the companion extension, and ships a\npersistent UI map backed by a local SQLite DB. The data dir resolves\nper-OS via env-paths ($XDG_DATA_HOME/browser-link on Linux,\n~/Library/Application Support/browser-link on macOS, %APPDATA%/browser-link\non Windows). Override with $BROWSER_LINK_DATA_DIR. The map is private\nand per-machine; never persisted in any repo.\n\n## When operating on a tab\n\n1. Before doing anything on a tab whose URL you don't already know,\n call `browser.map.recall` with { origin } (and optionally url) to load\n selectors, flows and gotchas previously learned for that app.\n2. If recall returns entries with `failed_at` more recent than\n `verified_at`, treat them as suspect: re-verify (snapshot / evaluate)\n before reusing, or replace them.\n3. After every interaction that used a map entry, call\n `browser.map.record_use` with { entry_id, ok }. ok=true updates\n verified_at; ok=false updates failed_at. Keep the map honest.\n4. After a non-trivial flow that worked end-to-end, persist it with\n `browser.map.save`. Three `kind` values:\n - selector: { selector, evidence? } \u2014 a CSS selector tied to a purpose.\n - flow: { steps: [...] } \u2014 an ordered list of actions to reach an outcome.\n - gotcha: { body } \u2014 free-form note about something non-obvious.\n Use `url_pattern` = pathname (exact). Promote to glob only if you have\n evidence of a parametric route. Provide `purpose` as a stable, reusable\n label (\"open task detail dialog\", not \"open IB0311 detail\").\n5. Never save selectors or flows you have not just successfully executed.\n6. Never store domain data (IDs, user names, dates, etc.). The map captures\n UI structure only.\n\n## Identifying the app\n\n- `origin` = scheme://host:port of the tab.\n- `app_key` distinguishes apps that share an origin over time. On first\n save you may omit it; it will be derived from the page title (slugified).\n Use `browser.map.rename_app` if that initial guess is poor.\n\n## When something is wrong\n\n- A selector from recall fails \u2192 record_use({ok:false}), learn the new\n one, save it (upsert on purpose).\n- A whole app got refactored \u2192 `browser.map.forget` the app_id and let\n the map repopulate as you learn the new structure.\n- A tool call fails with \"Tab not connected: tab_X\" \u2192 call\n `browser.events` to see whether the bridge changed primary (the\n Chrome tab probably got a new tab_id after a reconnect). Look for a\n `tab-renamed` event with previous=tab_X and resume on the current id.\n\nThe map is a cache of navigation, not a substitute for `browser.snapshot`.\nThe live snapshot is always the source of truth.";
|
|
3
|
+
export declare const SERVER_INSTRUCTIONS = "browser-link bridges Claude Code to the Chrome tabs the user has\nexplicitly connected through the companion extension, and ships a\npersistent UI map backed by a local SQLite DB. The data dir resolves\nper-OS via env-paths ($XDG_DATA_HOME/browser-link on Linux,\n~/Library/Application Support/browser-link on macOS, %APPDATA%/browser-link\non Windows). Override with $BROWSER_LINK_DATA_DIR. The map is private\nand per-machine; never persisted in any repo.\n\n## When operating on a tab\n\n1. Before doing anything on a tab whose URL you don't already know,\n call `browser.map.recall` with { origin } (and optionally url) to load\n selectors, flows and gotchas previously learned for that app.\n2. If recall returns entries with `failed_at` more recent than\n `verified_at`, treat them as suspect: re-verify (snapshot / evaluate)\n before reusing, or replace them.\n3. After every interaction that used a map entry, call\n `browser.map.record_use` with { entry_id, ok }. ok=true updates\n verified_at; ok=false updates failed_at. Keep the map honest.\n4. After a non-trivial flow that worked end-to-end, persist it with\n `browser.map.save`. Three `kind` values:\n - selector: { selector, evidence? } \u2014 a CSS selector tied to a purpose.\n - flow: { steps: [...] } \u2014 an ordered list of actions to reach an outcome.\n - gotcha: { body } \u2014 free-form note about something non-obvious.\n Use `url_pattern` = pathname (exact). Promote to glob only if you have\n evidence of a parametric route. Provide `purpose` as a stable, reusable\n label (\"open task detail dialog\", not \"open IB0311 detail\").\n5. Never save selectors or flows you have not just successfully executed.\n6. Never store domain data (IDs, user names, dates, etc.). The map captures\n UI structure only.\n\n## Identifying the app\n\n- `origin` = scheme://host:port of the tab.\n- `app_key` distinguishes apps that share an origin over time. On first\n save you may omit it; it will be derived from the page title (slugified).\n Use `browser.map.rename_app` if that initial guess is poor.\n\n## Sharing tabs with other agents\n\nThis primary may be serving several MCP clients at once (multi-agent mode).\nTo stop two agents fighting over the same Chrome tab there is a cooperative\nclaim layer:\n\n- `browser.list_tabs` includes `claimed_by` (null if free, otherwise the\n agent that holds the claim) and `claimed_by_me` (boolean). Use it before\n starting work on a tab whose state you don't already own.\n- `browser.my_tabs` returns YOUR active claims with timestamps. If the\n user asks which tab you are using, this is the answer.\n- Action tools (`browser.click`, `browser.type`, `browser.navigate`,\n `browser.evaluate`) auto-claim a free tab on first use and refresh\n activity on subsequent calls. If another agent holds the tab, they\n return an error naming the owner \u2014 do NOT retry blindly; ask the user\n whose tab it should be, or use a different tab.\n- Read tools (`browser.snapshot`, `browser.console`, `browser.network`,\n `browser.network_body`, `browser.events`, `browser.ping`) ignore claims.\n- `browser.claim_tab({ tab_id, ttl_minutes?, label? })` reserves a tab\n explicitly. Provide a stable `label` (eg \"claude-code\", \"opencode\") so\n other agents and the user see WHO holds the tab. The label is display\n only \u2014 security relies on the IPC session id (kernel-vetted), not on\n what an agent calls itself.\n- `browser.release_tab({ tab_id })` hands a tab back. Claims also auto-\n release when an agent disconnects or after the inactivity TTL elapses\n (default 10 minutes), so explicit release is only needed for early\n hand-off.\n\nWhen you get a claim-conflict error: do NOT spin-retry. Either work on a\ndifferent tab from `list_tabs`, or surface the conflict to the user and\nlet them decide.\n\n## When something is wrong\n\n- A selector from recall fails \u2192 record_use({ok:false}), learn the new\n one, save it (upsert on purpose).\n- A whole app got refactored \u2192 `browser.map.forget` the app_id and let\n the map repopulate as you learn the new structure.\n- A tool call fails with \"Tab not connected: tab_X\" \u2192 call\n `browser.events` to see whether the bridge changed primary (the\n Chrome tab probably got a new tab_id after a reconnect). Look for a\n `tab-renamed` event with previous=tab_X and resume on the current id.\n\nThe map is a cache of navigation, not a substitute for `browser.snapshot`.\nThe live snapshot is always the source of truth.";
|
|
@@ -38,6 +38,38 @@ and per-machine; never persisted in any repo.
|
|
|
38
38
|
save you may omit it; it will be derived from the page title (slugified).
|
|
39
39
|
Use \`browser.map.rename_app\` if that initial guess is poor.
|
|
40
40
|
|
|
41
|
+
## Sharing tabs with other agents
|
|
42
|
+
|
|
43
|
+
This primary may be serving several MCP clients at once (multi-agent mode).
|
|
44
|
+
To stop two agents fighting over the same Chrome tab there is a cooperative
|
|
45
|
+
claim layer:
|
|
46
|
+
|
|
47
|
+
- \`browser.list_tabs\` includes \`claimed_by\` (null if free, otherwise the
|
|
48
|
+
agent that holds the claim) and \`claimed_by_me\` (boolean). Use it before
|
|
49
|
+
starting work on a tab whose state you don't already own.
|
|
50
|
+
- \`browser.my_tabs\` returns YOUR active claims with timestamps. If the
|
|
51
|
+
user asks which tab you are using, this is the answer.
|
|
52
|
+
- Action tools (\`browser.click\`, \`browser.type\`, \`browser.navigate\`,
|
|
53
|
+
\`browser.evaluate\`) auto-claim a free tab on first use and refresh
|
|
54
|
+
activity on subsequent calls. If another agent holds the tab, they
|
|
55
|
+
return an error naming the owner — do NOT retry blindly; ask the user
|
|
56
|
+
whose tab it should be, or use a different tab.
|
|
57
|
+
- Read tools (\`browser.snapshot\`, \`browser.console\`, \`browser.network\`,
|
|
58
|
+
\`browser.network_body\`, \`browser.events\`, \`browser.ping\`) ignore claims.
|
|
59
|
+
- \`browser.claim_tab({ tab_id, ttl_minutes?, label? })\` reserves a tab
|
|
60
|
+
explicitly. Provide a stable \`label\` (eg "claude-code", "opencode") so
|
|
61
|
+
other agents and the user see WHO holds the tab. The label is display
|
|
62
|
+
only — security relies on the IPC session id (kernel-vetted), not on
|
|
63
|
+
what an agent calls itself.
|
|
64
|
+
- \`browser.release_tab({ tab_id })\` hands a tab back. Claims also auto-
|
|
65
|
+
release when an agent disconnects or after the inactivity TTL elapses
|
|
66
|
+
(default 10 minutes), so explicit release is only needed for early
|
|
67
|
+
hand-off.
|
|
68
|
+
|
|
69
|
+
When you get a claim-conflict error: do NOT spin-retry. Either work on a
|
|
70
|
+
different tab from \`list_tabs\`, or surface the conflict to the user and
|
|
71
|
+
let them decide.
|
|
72
|
+
|
|
41
73
|
## When something is wrong
|
|
42
74
|
|
|
43
75
|
- A selector from recall fails → record_use({ok:false}), learn the new
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-instructions.js","sourceRoot":"","sources":["../../src/tools/server-instructions.ts"],"names":[],"mappings":"AAAA;oEACoE;AACpE,MAAM,CAAC,MAAM,mBAAmB,GAAG
|
|
1
|
+
{"version":3,"file":"server-instructions.js","sourceRoot":"","sources":["../../src/tools/server-instructions.ts"],"names":[],"mappings":"AAAA;oEACoE;AACpE,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iDAkFc,CAAC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cooperative tab-ownership registry for multi-agent mode.
|
|
3
|
+
*
|
|
4
|
+
* Several MCP clients can share a single browser-link primary. Without
|
|
5
|
+
* coordination they end up racing on the same Chrome tab — clicking,
|
|
6
|
+
* navigating, evaluating in interleaved order. This module tracks who
|
|
7
|
+
* "owns" each tab and lets the dispatcher refuse cross-agent action.
|
|
8
|
+
*
|
|
9
|
+
* Identity model:
|
|
10
|
+
* - `agent_id` is the IPC session id for proxies (UUID minted after the
|
|
11
|
+
* hello/token handshake) and the literal string `"primary"` for the
|
|
12
|
+
* primary's own MCP client.
|
|
13
|
+
* - The auth that the agent passed (peerLookup + Node binary allowlist +
|
|
14
|
+
* rotated token) is the trust anchor. This registry never re-validates
|
|
15
|
+
* identity — it consumes the `AgentCaller` the bridge layer supplies.
|
|
16
|
+
* - Self-declared `label` (eg `"claude-code"`) is display-only and is
|
|
17
|
+
* never used for ownership comparisons.
|
|
18
|
+
*
|
|
19
|
+
* TTL model:
|
|
20
|
+
* - Claims expire after `ttl_minutes` of inactivity. Every action on the
|
|
21
|
+
* tab refreshes `last_activity_at`. Stale claims are dropped by
|
|
22
|
+
* `pruneStale()` (called periodically by the primary).
|
|
23
|
+
* - Sessions ending (proxy disconnect) drop the agent's claims
|
|
24
|
+
* immediately via `onAgentDisconnect()`.
|
|
25
|
+
*/
|
|
26
|
+
export interface AgentCaller {
|
|
27
|
+
agent_id: string;
|
|
28
|
+
pid: number;
|
|
29
|
+
binary: string;
|
|
30
|
+
label?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface TabClaim {
|
|
33
|
+
tab_id: string;
|
|
34
|
+
agent_id: string;
|
|
35
|
+
pid: number;
|
|
36
|
+
binary: string;
|
|
37
|
+
label?: string;
|
|
38
|
+
claimed_at: number;
|
|
39
|
+
last_activity_at: number;
|
|
40
|
+
ttl_ms: number;
|
|
41
|
+
}
|
|
42
|
+
export type ClaimEvent = {
|
|
43
|
+
kind: 'tab-claimed';
|
|
44
|
+
tab_id: string;
|
|
45
|
+
agent_id: string;
|
|
46
|
+
pid: number;
|
|
47
|
+
binary: string;
|
|
48
|
+
label?: string;
|
|
49
|
+
ttl_ms: number;
|
|
50
|
+
auto: boolean;
|
|
51
|
+
} | {
|
|
52
|
+
kind: 'tab-released';
|
|
53
|
+
tab_id: string;
|
|
54
|
+
agent_id: string;
|
|
55
|
+
reason: 'explicit' | 'agent-disconnect' | 'ttl';
|
|
56
|
+
} | {
|
|
57
|
+
kind: 'tab-claim-rejected';
|
|
58
|
+
tab_id: string;
|
|
59
|
+
requester_agent_id: string;
|
|
60
|
+
existing_agent_id: string;
|
|
61
|
+
};
|
|
62
|
+
export type ClaimOutcome = {
|
|
63
|
+
ok: true;
|
|
64
|
+
claim: TabClaim;
|
|
65
|
+
created: boolean;
|
|
66
|
+
} | {
|
|
67
|
+
ok: false;
|
|
68
|
+
reason: 'conflict';
|
|
69
|
+
existing: TabClaim;
|
|
70
|
+
};
|
|
71
|
+
export interface TabClaimRegistryOptions {
|
|
72
|
+
/** Default TTL when a claim does not specify one. Defaults to 10 minutes. */
|
|
73
|
+
defaultTtlMinutes?: number;
|
|
74
|
+
/** Upper bound on TTL a caller may request. Defaults to 60 minutes. */
|
|
75
|
+
maxTtlMinutes?: number;
|
|
76
|
+
/** Time source. Tests inject a fake clock to make TTL assertions deterministic. */
|
|
77
|
+
nowMs?: () => number;
|
|
78
|
+
/** Event callback. The primary wires this to its `BridgeEventLog`. */
|
|
79
|
+
onEvent?: (event: ClaimEvent) => void;
|
|
80
|
+
}
|
|
81
|
+
export declare class TabClaimRegistry {
|
|
82
|
+
private claims;
|
|
83
|
+
private readonly defaultTtlMs;
|
|
84
|
+
private readonly maxTtlMs;
|
|
85
|
+
private readonly now;
|
|
86
|
+
private readonly onEvent;
|
|
87
|
+
constructor(opts?: TabClaimRegistryOptions);
|
|
88
|
+
/** Snapshot of the current claim for a tab, or null when free or expired. */
|
|
89
|
+
getClaim(tab_id: string): TabClaim | null;
|
|
90
|
+
/** Explicit claim. Returns conflict if another agent owns the tab. Same-agent re-claims refresh activity and update the label/TTL. */
|
|
91
|
+
claim(tab_id: string, caller: AgentCaller, opts?: {
|
|
92
|
+
ttlMinutes?: number;
|
|
93
|
+
label?: string;
|
|
94
|
+
}): ClaimOutcome;
|
|
95
|
+
/** For action tools. Auto-claims a free tab for the caller, refreshes when the caller already owns it, conflicts otherwise. */
|
|
96
|
+
ensureActionAllowed(tab_id: string, caller: AgentCaller): ClaimOutcome;
|
|
97
|
+
/** Explicit release. Only the owner may release. */
|
|
98
|
+
release(tab_id: string, caller: AgentCaller): {
|
|
99
|
+
ok: true;
|
|
100
|
+
} | {
|
|
101
|
+
ok: false;
|
|
102
|
+
reason: 'not-claimed' | 'not-owner';
|
|
103
|
+
existing?: TabClaim;
|
|
104
|
+
};
|
|
105
|
+
/** Claims owned by this caller, sorted by `claimed_at`. */
|
|
106
|
+
myTabs(caller: AgentCaller): TabClaim[];
|
|
107
|
+
/** Drop every claim held by `agent_id` (called when a proxy disconnects). */
|
|
108
|
+
onAgentDisconnect(agent_id: string): TabClaim[];
|
|
109
|
+
/** Sweep claims past their TTL. Returns the dropped claims. */
|
|
110
|
+
pruneStale(): TabClaim[];
|
|
111
|
+
/** Test helper. Production callers should not need this. */
|
|
112
|
+
size(): number;
|
|
113
|
+
private isExpired;
|
|
114
|
+
private claimInternal;
|
|
115
|
+
}
|
|
116
|
+
/** User-facing error string for action tools when another agent holds the tab. Kept here so the wording is consistent across handlers. */
|
|
117
|
+
export declare function formatClaimConflict(caller: AgentCaller, existing: TabClaim, nowMs?: number): string;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cooperative tab-ownership registry for multi-agent mode.
|
|
3
|
+
*
|
|
4
|
+
* Several MCP clients can share a single browser-link primary. Without
|
|
5
|
+
* coordination they end up racing on the same Chrome tab — clicking,
|
|
6
|
+
* navigating, evaluating in interleaved order. This module tracks who
|
|
7
|
+
* "owns" each tab and lets the dispatcher refuse cross-agent action.
|
|
8
|
+
*
|
|
9
|
+
* Identity model:
|
|
10
|
+
* - `agent_id` is the IPC session id for proxies (UUID minted after the
|
|
11
|
+
* hello/token handshake) and the literal string `"primary"` for the
|
|
12
|
+
* primary's own MCP client.
|
|
13
|
+
* - The auth that the agent passed (peerLookup + Node binary allowlist +
|
|
14
|
+
* rotated token) is the trust anchor. This registry never re-validates
|
|
15
|
+
* identity — it consumes the `AgentCaller` the bridge layer supplies.
|
|
16
|
+
* - Self-declared `label` (eg `"claude-code"`) is display-only and is
|
|
17
|
+
* never used for ownership comparisons.
|
|
18
|
+
*
|
|
19
|
+
* TTL model:
|
|
20
|
+
* - Claims expire after `ttl_minutes` of inactivity. Every action on the
|
|
21
|
+
* tab refreshes `last_activity_at`. Stale claims are dropped by
|
|
22
|
+
* `pruneStale()` (called periodically by the primary).
|
|
23
|
+
* - Sessions ending (proxy disconnect) drop the agent's claims
|
|
24
|
+
* immediately via `onAgentDisconnect()`.
|
|
25
|
+
*/
|
|
26
|
+
const MS_PER_MINUTE = 60_000;
|
|
27
|
+
export class TabClaimRegistry {
|
|
28
|
+
claims = new Map();
|
|
29
|
+
defaultTtlMs;
|
|
30
|
+
maxTtlMs;
|
|
31
|
+
now;
|
|
32
|
+
onEvent;
|
|
33
|
+
constructor(opts = {}) {
|
|
34
|
+
this.defaultTtlMs = (opts.defaultTtlMinutes ?? 10) * MS_PER_MINUTE;
|
|
35
|
+
this.maxTtlMs = (opts.maxTtlMinutes ?? 60) * MS_PER_MINUTE;
|
|
36
|
+
this.now = opts.nowMs ?? (() => Date.now());
|
|
37
|
+
this.onEvent = opts.onEvent ?? (() => { });
|
|
38
|
+
}
|
|
39
|
+
/** Snapshot of the current claim for a tab, or null when free or expired. */
|
|
40
|
+
getClaim(tab_id) {
|
|
41
|
+
const claim = this.claims.get(tab_id);
|
|
42
|
+
if (!claim)
|
|
43
|
+
return null;
|
|
44
|
+
if (this.isExpired(claim)) {
|
|
45
|
+
this.claims.delete(tab_id);
|
|
46
|
+
this.onEvent({
|
|
47
|
+
kind: 'tab-released',
|
|
48
|
+
tab_id,
|
|
49
|
+
agent_id: claim.agent_id,
|
|
50
|
+
reason: 'ttl',
|
|
51
|
+
});
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return claim;
|
|
55
|
+
}
|
|
56
|
+
/** Explicit claim. Returns conflict if another agent owns the tab. Same-agent re-claims refresh activity and update the label/TTL. */
|
|
57
|
+
claim(tab_id, caller, opts = {}) {
|
|
58
|
+
return this.claimInternal(tab_id, caller, opts, { auto: false });
|
|
59
|
+
}
|
|
60
|
+
/** For action tools. Auto-claims a free tab for the caller, refreshes when the caller already owns it, conflicts otherwise. */
|
|
61
|
+
ensureActionAllowed(tab_id, caller) {
|
|
62
|
+
return this.claimInternal(tab_id, caller, {}, { auto: true });
|
|
63
|
+
}
|
|
64
|
+
/** Explicit release. Only the owner may release. */
|
|
65
|
+
release(tab_id, caller) {
|
|
66
|
+
const claim = this.getClaim(tab_id);
|
|
67
|
+
if (!claim)
|
|
68
|
+
return { ok: false, reason: 'not-claimed' };
|
|
69
|
+
if (claim.agent_id !== caller.agent_id) {
|
|
70
|
+
return { ok: false, reason: 'not-owner', existing: claim };
|
|
71
|
+
}
|
|
72
|
+
this.claims.delete(tab_id);
|
|
73
|
+
this.onEvent({
|
|
74
|
+
kind: 'tab-released',
|
|
75
|
+
tab_id,
|
|
76
|
+
agent_id: claim.agent_id,
|
|
77
|
+
reason: 'explicit',
|
|
78
|
+
});
|
|
79
|
+
return { ok: true };
|
|
80
|
+
}
|
|
81
|
+
/** Claims owned by this caller, sorted by `claimed_at`. */
|
|
82
|
+
myTabs(caller) {
|
|
83
|
+
const owned = [];
|
|
84
|
+
for (const tabId of this.claims.keys()) {
|
|
85
|
+
const claim = this.getClaim(tabId);
|
|
86
|
+
if (claim && claim.agent_id === caller.agent_id)
|
|
87
|
+
owned.push(claim);
|
|
88
|
+
}
|
|
89
|
+
return owned.sort((a, b) => a.claimed_at - b.claimed_at);
|
|
90
|
+
}
|
|
91
|
+
/** Drop every claim held by `agent_id` (called when a proxy disconnects). */
|
|
92
|
+
onAgentDisconnect(agent_id) {
|
|
93
|
+
const released = [];
|
|
94
|
+
for (const [tabId, claim] of this.claims) {
|
|
95
|
+
if (claim.agent_id === agent_id) {
|
|
96
|
+
released.push(claim);
|
|
97
|
+
this.claims.delete(tabId);
|
|
98
|
+
this.onEvent({
|
|
99
|
+
kind: 'tab-released',
|
|
100
|
+
tab_id: tabId,
|
|
101
|
+
agent_id,
|
|
102
|
+
reason: 'agent-disconnect',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return released;
|
|
107
|
+
}
|
|
108
|
+
/** Sweep claims past their TTL. Returns the dropped claims. */
|
|
109
|
+
pruneStale() {
|
|
110
|
+
const expired = [];
|
|
111
|
+
for (const [tabId, claim] of this.claims) {
|
|
112
|
+
if (this.isExpired(claim)) {
|
|
113
|
+
expired.push(claim);
|
|
114
|
+
this.claims.delete(tabId);
|
|
115
|
+
this.onEvent({
|
|
116
|
+
kind: 'tab-released',
|
|
117
|
+
tab_id: tabId,
|
|
118
|
+
agent_id: claim.agent_id,
|
|
119
|
+
reason: 'ttl',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return expired;
|
|
124
|
+
}
|
|
125
|
+
/** Test helper. Production callers should not need this. */
|
|
126
|
+
size() {
|
|
127
|
+
return this.claims.size;
|
|
128
|
+
}
|
|
129
|
+
isExpired(claim) {
|
|
130
|
+
return this.now() - claim.last_activity_at >= claim.ttl_ms;
|
|
131
|
+
}
|
|
132
|
+
claimInternal(tab_id, caller, opts, flags) {
|
|
133
|
+
const existing = this.getClaim(tab_id);
|
|
134
|
+
if (existing && existing.agent_id !== caller.agent_id) {
|
|
135
|
+
this.onEvent({
|
|
136
|
+
kind: 'tab-claim-rejected',
|
|
137
|
+
tab_id,
|
|
138
|
+
requester_agent_id: caller.agent_id,
|
|
139
|
+
existing_agent_id: existing.agent_id,
|
|
140
|
+
});
|
|
141
|
+
return { ok: false, reason: 'conflict', existing };
|
|
142
|
+
}
|
|
143
|
+
const now = this.now();
|
|
144
|
+
const requestedTtlMs = opts.ttlMinutes != null ? opts.ttlMinutes * MS_PER_MINUTE : undefined;
|
|
145
|
+
const ttlMs = clamp(requestedTtlMs ?? existing?.ttl_ms ?? this.defaultTtlMs, 1, this.maxTtlMs);
|
|
146
|
+
const label = opts.label ?? existing?.label ?? caller.label;
|
|
147
|
+
const claim = {
|
|
148
|
+
tab_id,
|
|
149
|
+
agent_id: caller.agent_id,
|
|
150
|
+
pid: caller.pid,
|
|
151
|
+
binary: caller.binary,
|
|
152
|
+
label,
|
|
153
|
+
claimed_at: existing?.claimed_at ?? now,
|
|
154
|
+
last_activity_at: now,
|
|
155
|
+
ttl_ms: ttlMs,
|
|
156
|
+
};
|
|
157
|
+
const created = !existing;
|
|
158
|
+
this.claims.set(tab_id, claim);
|
|
159
|
+
if (created) {
|
|
160
|
+
this.onEvent({
|
|
161
|
+
kind: 'tab-claimed',
|
|
162
|
+
tab_id,
|
|
163
|
+
agent_id: caller.agent_id,
|
|
164
|
+
pid: caller.pid,
|
|
165
|
+
binary: caller.binary,
|
|
166
|
+
label,
|
|
167
|
+
ttl_ms: ttlMs,
|
|
168
|
+
auto: flags.auto,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return { ok: true, claim, created };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function clamp(value, lo, hi) {
|
|
175
|
+
return Math.min(Math.max(value, lo), hi);
|
|
176
|
+
}
|
|
177
|
+
/** User-facing error string for action tools when another agent holds the tab. Kept here so the wording is consistent across handlers. */
|
|
178
|
+
export function formatClaimConflict(caller, existing, nowMs = Date.now()) {
|
|
179
|
+
const labelOrPid = existing.label ?? `pid ${existing.pid}`;
|
|
180
|
+
const ageMin = Math.max(1, Math.round((nowMs - existing.claimed_at) / MS_PER_MINUTE));
|
|
181
|
+
return (`Tab ${existing.tab_id} is in use by another agent (${labelOrPid}, agent_id=${existing.agent_id}) ` +
|
|
182
|
+
`for ${ageMin} min. Your agent_id is ${caller.agent_id}. ` +
|
|
183
|
+
`Call browser.list_tabs to see what's free, browser.my_tabs to see what you already own, ` +
|
|
184
|
+
`or ask the user whose tab this should be.`);
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=tab-claims.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab-claims.js","sourceRoot":"","sources":["../../src/tools/tab-claims.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/screens.js
CHANGED
|
@@ -8,6 +8,7 @@ import { INSTALLERS } from '../installers/index.js';
|
|
|
8
8
|
import { runDoctor, formatDoctor } from '../commands/doctor.js';
|
|
9
9
|
import { getExtensionInfo } from '../commands/extension.js';
|
|
10
10
|
import { checkUpdates } from '../commands/updates.js';
|
|
11
|
+
import { runSelfUpdate, } from '../commands/self-update.js';
|
|
11
12
|
import { runFreePort } from '../commands/free-port.js';
|
|
12
13
|
import { PACKAGE_NAME } from '../version.js';
|
|
13
14
|
import { loadConfig, saveConfig } from '../config.js';
|
|
@@ -402,9 +403,15 @@ const UPDATES_I18N = {
|
|
|
402
403
|
latest: 'Latest on npm ',
|
|
403
404
|
upToDate: '✓ You are on the latest published version.',
|
|
404
405
|
available: '⚠ A newer version is available.',
|
|
405
|
-
upgradeCmd: '
|
|
406
|
+
upgradeCmd: 'Or, to upgrade manually, run:',
|
|
407
|
+
updateKeyHint: 'Press u to update now (stops any running primary, then installs).',
|
|
408
|
+
updateRunning: 'Updating…',
|
|
406
409
|
error: 'Could not check the registry',
|
|
407
|
-
|
|
410
|
+
footerIdle: 'r retry · ↵ / Esc back to menu',
|
|
411
|
+
footerWithUpdate: 'u update · r retry · ↵ / Esc back to menu',
|
|
412
|
+
footerUpdating: 'updating — please wait…',
|
|
413
|
+
footerDone: '↵ / Esc back to menu — restart your MCP client to pick up the new version',
|
|
414
|
+
footerFailed: 'r retry check · ↵ / Esc back to menu',
|
|
408
415
|
},
|
|
409
416
|
es: {
|
|
410
417
|
title: 'Buscar actualizaciones',
|
|
@@ -413,18 +420,26 @@ const UPDATES_I18N = {
|
|
|
413
420
|
latest: 'Última en npm ',
|
|
414
421
|
upToDate: '✓ Estás en la última versión publicada.',
|
|
415
422
|
available: '⚠ Hay una versión más nueva disponible.',
|
|
416
|
-
upgradeCmd: '
|
|
423
|
+
upgradeCmd: 'O, para actualizar a mano, corré:',
|
|
424
|
+
updateKeyHint: 'Tocá u para actualizar ahora (corta el primary en uso e instala).',
|
|
425
|
+
updateRunning: 'Actualizando…',
|
|
417
426
|
error: 'No se pudo consultar el registry',
|
|
418
|
-
|
|
427
|
+
footerIdle: 'r reintentar · ↵ / Esc volver al menú',
|
|
428
|
+
footerWithUpdate: 'u actualizar · r reintentar · ↵ / Esc volver al menú',
|
|
429
|
+
footerUpdating: 'actualizando — esperá…',
|
|
430
|
+
footerDone: '↵ / Esc volver al menú — reiniciá tu cliente MCP para que tome la nueva versión',
|
|
431
|
+
footerFailed: 'r reintentar el chequeo · ↵ / Esc volver al menú',
|
|
419
432
|
},
|
|
420
433
|
};
|
|
421
434
|
export function UpdatesView({ language, onBack }) {
|
|
422
435
|
const t = UPDATES_I18N[language];
|
|
423
436
|
const [info, setInfo] = useState(null);
|
|
424
437
|
const [refreshKey, setRefreshKey] = useState(0);
|
|
438
|
+
const [update, setUpdate] = useState({ kind: 'idle' });
|
|
425
439
|
useEffect(() => {
|
|
426
440
|
let cancelled = false;
|
|
427
441
|
setInfo(null);
|
|
442
|
+
setUpdate({ kind: 'idle' });
|
|
428
443
|
checkUpdates().then((r) => {
|
|
429
444
|
if (!cancelled)
|
|
430
445
|
setInfo(r);
|
|
@@ -433,13 +448,49 @@ export function UpdatesView({ language, onBack }) {
|
|
|
433
448
|
cancelled = true;
|
|
434
449
|
};
|
|
435
450
|
}, [refreshKey]);
|
|
451
|
+
const isUpdating = update.kind === 'running';
|
|
452
|
+
const canUpdate = info !== null && info.newer === true && info.latest !== null && !isUpdating;
|
|
453
|
+
const startUpdate = () => {
|
|
454
|
+
if (!canUpdate || info === null || info.latest === null)
|
|
455
|
+
return;
|
|
456
|
+
const target = info.latest;
|
|
457
|
+
setUpdate({ kind: 'running', stage: 'preflight', message: t.updateRunning });
|
|
458
|
+
runSelfUpdate(target, language, (event) => {
|
|
459
|
+
setUpdate({ kind: 'running', stage: event.stage, message: event.message });
|
|
460
|
+
})
|
|
461
|
+
.then((result) => {
|
|
462
|
+
setUpdate({
|
|
463
|
+
kind: result.ok ? 'done' : 'failed',
|
|
464
|
+
message: result.message,
|
|
465
|
+
});
|
|
466
|
+
})
|
|
467
|
+
.catch((err) => {
|
|
468
|
+
setUpdate({
|
|
469
|
+
kind: 'failed',
|
|
470
|
+
message: err instanceof Error ? err.message : String(err),
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
};
|
|
436
474
|
useInput((input, key) => {
|
|
475
|
+
if (isUpdating)
|
|
476
|
+
return; // ignore keys while the install is in flight
|
|
437
477
|
if (key.return || key.escape)
|
|
438
478
|
onBack();
|
|
439
|
-
else if (input === 'r')
|
|
479
|
+
else if (input === 'r' && update.kind !== 'done')
|
|
440
480
|
setRefreshKey((k) => k + 1);
|
|
481
|
+
else if (input === 'u')
|
|
482
|
+
startUpdate();
|
|
441
483
|
});
|
|
442
|
-
|
|
484
|
+
const footer = update.kind === 'running'
|
|
485
|
+
? t.footerUpdating
|
|
486
|
+
: update.kind === 'done'
|
|
487
|
+
? t.footerDone
|
|
488
|
+
: update.kind === 'failed'
|
|
489
|
+
? t.footerFailed
|
|
490
|
+
: canUpdate
|
|
491
|
+
? t.footerWithUpdate
|
|
492
|
+
: t.footerIdle;
|
|
493
|
+
return (_jsx(Frame, { title: t.title, footer: footer, children: info === null ? (_jsx(Text, { color: "gray", children: t.checking })) : info.error || info.latest === null ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsxs(Text, { color: "white", children: [t.current, ": "] }), _jsx(Text, { bold: true, children: info.current })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: [t.error, ": ", info.error ?? 'unknown error'] }) })] })) : (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsxs(Text, { color: "white", children: [t.current, ": "] }), _jsx(Text, { bold: true, children: info.current })] }), _jsxs(Text, { children: [_jsxs(Text, { color: "white", children: [t.latest, ": "] }), _jsx(Text, { bold: true, color: info.newer ? 'yellow' : 'green', children: info.latest })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: info.newer ? 'yellow' : 'green', children: info.newer ? t.available : t.upToDate }) }), info.newer && update.kind === 'idle' && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "cyan", children: t.updateKeyHint }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "gray", children: t.upgradeCmd }), _jsx(Text, { color: "cyan", children: `npm install -g ${PACKAGE_NAME}@latest` })] })] })), update.kind === 'running' && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "cyan", children: t.updateRunning }), _jsx(Text, { color: "gray", children: update.message })] })), update.kind === 'done' && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "green", children: update.message }) })), update.kind === 'failed' && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", children: update.message }) }))] })) }));
|
|
443
494
|
}
|
|
444
495
|
/* ── Language ──────────────────────────────────────────────────────────── */
|
|
445
496
|
const LANG_I18N = {
|