local-operator-ui 0.31.10 → 0.31.12
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/README.md +110 -126
- package/out/main/index.js +193 -7
- package/out/renderer/assets/{_basePickBy-9ROOwmxL.js → _basePickBy-BmRJBnL9.js} +1 -1
- package/out/renderer/assets/{_baseUniq-eE-r5ZZ4.js → _baseUniq-7Lo40p8f.js} +1 -1
- package/out/renderer/assets/{agent-details-page-Vs9AZBwz.js → agent-details-page-Dr0_4VXU.js} +2 -2
- package/out/renderer/assets/{agent-hub-page-CGJbElaY.js → agent-hub-page-CWhlINHl.js} +2 -2
- package/out/renderer/assets/agents-page-CfRvRbxk.js +2 -0
- package/out/renderer/assets/{arc-DN4pJSIq.js → arc-BzsVSHwA.js} +1 -1
- package/out/renderer/assets/{architectureDiagram-IEHRJDOE-uYP9fQLK.js → architectureDiagram-IEHRJDOE-Bw5-VATM.js} +1 -1
- package/out/renderer/assets/{blockDiagram-JOT3LUYC-CmsXiKwe.js → blockDiagram-JOT3LUYC-BGagYddg.js} +1 -1
- package/out/renderer/assets/{book-open-CNgko0U5.js → book-open-6gMxcFCn.js} +1 -1
- package/out/renderer/assets/browser-page-B6fHPjNZ.js +1 -0
- package/out/renderer/assets/{browser-webauthn-prompt-CCZq7UZY.js → browser-webauthn-prompt-7nbHSC-s.js} +1 -1
- package/out/renderer/assets/{c4Diagram-VJAJSXHY-DSjqK2s4.js → c4Diagram-VJAJSXHY-baq7nLZj.js} +1 -1
- package/out/renderer/assets/channel-DwqaTZnF.js +1 -0
- package/out/renderer/assets/{chunk-4BMEZGHF-C1_SAsaq.js → chunk-4BMEZGHF-g1gXYo4V.js} +1 -1
- package/out/renderer/assets/{chunk-A2AXSNBT-Cq0zFbJT.js → chunk-A2AXSNBT--A5WU7y8.js} +1 -1
- package/out/renderer/assets/{chunk-AEK57VVT-BcKdPjvg.js → chunk-AEK57VVT-DzJ3UVbN.js} +1 -1
- package/out/renderer/assets/{chunk-D6G4REZN-DRByxdTc.js → chunk-D6G4REZN-C-t_FTD9.js} +1 -1
- package/out/renderer/assets/{chunk-RZ5BOZE2-Duscm0Bg.js → chunk-RZ5BOZE2-BYlLeO8W.js} +1 -1
- package/out/renderer/assets/{chunk-XZIHB7SX-BWEXg8AD.js → chunk-XZIHB7SX-lFzFuHTM.js} +1 -1
- package/out/renderer/assets/classDiagram-GIVACNV2-D8nKB_Ad.js +1 -0
- package/out/renderer/assets/classDiagram-v2-COTLJTTW-D8nKB_Ad.js +1 -0
- package/out/renderer/assets/clone-C2OvbMzz.js +1 -0
- package/out/renderer/assets/{console-mirror-DjoNpI7u.js → console-mirror-Dw_ms9Pv.js} +13 -13
- package/out/renderer/assets/{consoleCapture-BTYJP9Fs.js → consoleCapture-BSLdNXhT.js} +1 -1
- package/out/renderer/assets/{dagre-OKDRZEBW-Cn7pFiK5.js → dagre-OKDRZEBW-BVSzUiqR.js} +1 -1
- package/out/renderer/assets/{diagram-SSKATNLV-LyA2eojc.js → diagram-SSKATNLV-B2vAnmRK.js} +1 -1
- package/out/renderer/assets/{diagram-VNBRO52H-LPidUHcc.js → diagram-VNBRO52H-DWMJqgBu.js} +1 -1
- package/out/renderer/assets/{erDiagram-Q7BY3M3F-CTjCMg7T.js → erDiagram-Q7BY3M3F-CUzlaIt5.js} +1 -1
- package/out/renderer/assets/{error-boundary-Be38BRVp.js → error-boundary-CAHYSCjh.js} +2 -2
- package/out/renderer/assets/{flowDiagram-4HSFHLVR-BmWZ8kNr.js → flowDiagram-4HSFHLVR-DD5CmZz9.js} +1 -1
- package/out/renderer/assets/{ganttDiagram-APWFNJXF-CfDRHTNJ.js → ganttDiagram-APWFNJXF-C1VcnkEW.js} +4 -4
- package/out/renderer/assets/{gitGraphDiagram-7IBYFJ6S-CEYh6RxP.js → gitGraphDiagram-7IBYFJ6S-hEwYc4s_.js} +1 -1
- package/out/renderer/assets/{graph-_dzRI-I_.js → graph-iFEIY46g.js} +1 -1
- package/out/renderer/assets/{index-tykR0DI9.js → index-BZY6SC3C.js} +396 -395
- package/out/renderer/assets/index-DLdCxgd4.css +1 -0
- package/out/renderer/assets/{index-DPFT_2au.js → index-Dws1_64y.js} +1 -1
- package/out/renderer/assets/{index-BKqWQaFB.js → index-xYgenPjE.js} +1 -1
- package/out/renderer/assets/{infoDiagram-PH2N3AL5-BRBj70Iz.js → infoDiagram-PH2N3AL5-Fb3LRSKa.js} +1 -1
- package/out/renderer/assets/{installer-DOOu7eM6.js → installer-CzcDRHum.js} +1 -1
- package/out/renderer/assets/{journeyDiagram-U35MCT3I-kHoqijou.js → journeyDiagram-U35MCT3I-CQoYP1F8.js} +1 -1
- package/out/renderer/assets/{kanban-definition-NDS4AKOZ-DEpxCCmE.js → kanban-definition-NDS4AKOZ-CteRmk-W.js} +1 -1
- package/out/renderer/assets/{layout-C8CaQmGy.js → layout-DYvIW0gu.js} +1 -1
- package/out/renderer/assets/{legacy-agents-page-TbzorhDj.js → legacy-agents-page-1fM02PQU.js} +3 -3
- package/out/renderer/assets/{mermaid.core-xrJy-S5f.js → mermaid.core-CF1Z-P7X.js} +5 -5
- package/out/renderer/assets/mesh-page-DI8M-fjS.js +2 -0
- package/out/renderer/assets/{mindmap-definition-ALO5MXBD-BUYs8m2Y.js → mindmap-definition-ALO5MXBD-DndDQMdO.js} +1 -1
- package/out/renderer/assets/{org-surface-gate-9zY-gRI7.js → org-surface-gate-BfrGIWSX.js} +1 -1
- package/out/renderer/assets/{page-header-BXIWLsA1.js → page-header-DuARVVAM.js} +1 -1
- package/out/renderer/assets/{pieDiagram-IB7DONF6-DLyxJ6_z.js → pieDiagram-IB7DONF6-CBrJnfZ7.js} +1 -1
- package/out/renderer/assets/{projects-page-pUwqeYdO.js → projects-page-MsZOyOOD.js} +3 -3
- package/out/renderer/assets/{quadrantDiagram-7GDLP6J5-DH7de3iA.js → quadrantDiagram-7GDLP6J5-BVP2IC46.js} +1 -1
- package/out/renderer/assets/{radar-MK3ICKWK-07d8mdrE.js → radar-MK3ICKWK-DjZTeeh9.js} +1 -1
- package/out/renderer/assets/{radient-auth-buttons-8U1osKLe.js → radient-auth-buttons-D7orNp3U.js} +1 -1
- package/out/renderer/assets/{requirementDiagram-KVF5MWMF-B32qFUIl.js → requirementDiagram-KVF5MWMF-lJJAXVZZ.js} +1 -1
- package/out/renderer/assets/{sankeyDiagram-QLVOVGJD-sbqxByYH.js → sankeyDiagram-QLVOVGJD-LQXNh0UX.js} +1 -1
- package/out/renderer/assets/{schedules-page-XTDmccje.js → schedules-page-CtMM9uV6.js} +10 -10
- package/out/renderer/assets/{sequenceDiagram-X6HHIX6F-B73M11ec.js → sequenceDiagram-X6HHIX6F-1jw8SEPO.js} +1 -1
- package/out/renderer/assets/{settings-page-CK0UZY_b.js → settings-page-DRypESjz.js} +10 -10
- package/out/renderer/assets/{settings-section-BeU3N8-t.js → settings-section-j4eB5zEy.js} +1 -1
- package/out/renderer/assets/{stateDiagram-DGXRK772-B4WvgL0d.js → stateDiagram-DGXRK772-B0lcRZrZ.js} +1 -1
- package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-BfR0vu7W.js +1 -0
- package/out/renderer/assets/{timeline-definition-BDJGKUSR-C4QiFAyx.js → timeline-definition-BDJGKUSR-TbB_U2GW.js} +1 -1
- package/out/renderer/assets/{use-download-agent-mutation-uGo2AQXO.js → use-download-agent-mutation-BdgO7Jmf.js} +2 -2
- package/out/renderer/assets/{use-memberships-query-CjIPH1YX.js → use-memberships-query-f3E2Ca39.js} +1 -1
- package/out/renderer/assets/{xychartDiagram-VJFVF3MP-wcBKtlf2.js → xychartDiagram-VJFVF3MP-CGIvaGsN.js} +1 -1
- package/out/renderer/console-capture.html +4 -4
- package/out/renderer/index.html +5 -5
- package/out/renderer/installer.html +4 -4
- package/package.json +4 -2
- package/out/renderer/assets/agents-page-tftebSEL.js +0 -2
- package/out/renderer/assets/browser-page-Bu7bkVij.js +0 -1
- package/out/renderer/assets/channel-bHKgHeS_.js +0 -1
- package/out/renderer/assets/classDiagram-GIVACNV2-CU02kTw_.js +0 -1
- package/out/renderer/assets/classDiagram-v2-COTLJTTW-CU02kTw_.js +0 -1
- package/out/renderer/assets/clone-ARdM9vXs.js +0 -1
- package/out/renderer/assets/index-BjeBA__i.css +0 -1
- package/out/renderer/assets/mesh-page-Do2sLCUJ.js +0 -1
- package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-DNtZ1inO.js +0 -1
- /package/out/renderer/assets/{index-C-P4UaBn.js → index-Ci5ykqkd.js} +0 -0
package/README.md
CHANGED
|
@@ -5,70 +5,58 @@
|
|
|
5
5
|
src="./resources/local-operator-icon-2-light-clear.png">
|
|
6
6
|
</picture>
|
|
7
7
|
|
|
8
|
-
<h1 align="center">Local Operator
|
|
9
|
-
<div align="center">
|
|
10
|
-
<h2>🤖 Personal AI Assistants that Turn Ideas into Action</h2>
|
|
11
|
-
<p><i>Real-time code execution on your device through natural conversation</i></p>
|
|
12
|
-
</div>
|
|
8
|
+
<h1 align="center">Local Operator</h1>
|
|
13
9
|
|
|
14
|
-
<
|
|
10
|
+
<p align="center"><i>AI agent assistants on your device — they plan, write, and run Python, and every step stays in the conversation.</i></p>
|
|
15
11
|
|
|
16
12
|
<p align="center">
|
|
17
|
-
<
|
|
18
|
-
|
|
13
|
+
<a href="https://github.com/damianvtran/local-operator-ui/releases/latest"><img alt="Latest release" src="https://img.shields.io/github/v/release/damianvtran/local-operator-ui"></a>
|
|
14
|
+
<a href="https://github.com/damianvtran/local-operator-ui/actions/workflows/ci.yml"><img alt="CI status" src="https://img.shields.io/github/actions/workflow/status/damianvtran/local-operator-ui/ci.yml?branch=main"></a>
|
|
15
|
+
<a href="https://www.npmjs.com/package/local-operator-ui"><img alt="npm version" src="https://img.shields.io/npm/v/local-operator-ui"></a>
|
|
16
|
+
<a href="./LICENSE"><img alt="License: MIT" src="https://img.shields.io/github/license/damianvtran/local-operator-ui"></a>
|
|
17
|
+
<img alt="Platforms: macOS, Windows, and Linux" src="https://img.shields.io/badge/platform-macOS%20%7C%20Windows%20%7C%20Linux-lightgrey">
|
|
18
|
+
</p>
|
|
19
19
|
|
|
20
20
|
<br />
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
<p align="center">
|
|
23
|
+
<picture>
|
|
24
|
+
<source media="(prefers-color-scheme: dark)" srcset="./resources/readme/app-shell-chat-dark.webp">
|
|
25
|
+
<source media="(prefers-color-scheme: light)" srcset="./resources/readme/app-shell-chat-light.webp">
|
|
26
|
+
<img alt="The app's chat view: an agent's reply, the run details panel with two subagents running, and their shared to-do list." src="./resources/readme/app-shell-chat-light.webp">
|
|
27
|
+
</picture>
|
|
28
|
+
<br />
|
|
29
|
+
<sub><code>the app at work</code> — a conversation with an agent, two subagents running beside it, and the to-do list they share.</sub>
|
|
30
|
+
</p>
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
<br />
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
**Local Operator** runs AI agent assistants on your own machine, from a desktop chat app. You give an agent a goal; it plans the work, writes and runs Python on your device, reads and writes files, and asks before it takes anything risky. The whole run — messages, tool calls, commands, and results — stays in the conversation in front of you.
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
For the agent environment CLI and server backend, see the [Local Operator repository](https://github.com/damianvtran/local-operator).
|
|
35
37
|
|
|
36
|
-
<
|
|
37
|
-
<a href="https://github.com/damianvtran/local-operator">Agent
|
|
38
|
-
<a href="https://local-operator.com">
|
|
38
|
+
<p align="center">
|
|
39
|
+
<a href="https://github.com/damianvtran/local-operator">Agent backend</a> •
|
|
40
|
+
<a href="https://local-operator.com">Website</a> •
|
|
39
41
|
<a href="https://github.com/damianvtran/local-operator/tree/main/examples/notebooks">Examples</a>
|
|
40
|
-
</
|
|
42
|
+
</p>
|
|
41
43
|
|
|
42
|
-
##
|
|
43
|
-
|
|
44
|
-
The Local Operator UI is a user interface for managing and interacting with the Local Operator agent environment. It is built using Electron, React, and TypeScript, leveraging modern web technologies for a rich and responsive user experience.
|
|
45
|
-
|
|
46
|
-
👉 For the agent environment CLI and Server backend, see the [Local Operator GitHub repository](https://github.com/damianvtran/local-operator).
|
|
47
|
-
|
|
48
|
-
## 🚀 Getting Started
|
|
44
|
+
## Getting started
|
|
49
45
|
|
|
50
46
|
### Prerequisites
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
- **Node.js**: Version 22.13.1 or higher. It's recommended to use [nvm](https://github.com/nvm-sh/nvm) for managing Node.js versions.
|
|
55
|
-
|
|
56
|
-
The Local Operator backend is now bundled with the application and will be installed automatically when you first run the application. If you prefer to use an existing Local Operator backend installation, the application will detect it and use it instead.
|
|
57
|
-
|
|
58
|
-
### NPM Installation
|
|
48
|
+
- **Node.js** 22.13.1 or newer, for the npm install paths below. [nvm](https://github.com/nvm-sh/nvm) is a good way to manage Node versions. The desktop installers need no toolchain.
|
|
59
49
|
|
|
60
|
-
|
|
50
|
+
### Run with npx
|
|
61
51
|
|
|
62
52
|
```bash
|
|
63
|
-
#
|
|
53
|
+
# Download and run in one command
|
|
64
54
|
npx local-operator-ui
|
|
65
55
|
```
|
|
66
56
|
|
|
67
|
-
This
|
|
57
|
+
This runs the latest version and launches the app.
|
|
68
58
|
|
|
69
|
-
###
|
|
70
|
-
|
|
71
|
-
Alternatively, without npx, you can install the package globally with standard npm:
|
|
59
|
+
### Install with npm
|
|
72
60
|
|
|
73
61
|
```bash
|
|
74
62
|
# Install globally
|
|
@@ -78,52 +66,67 @@ npm install -g local-operator-ui
|
|
|
78
66
|
local-operator-ui
|
|
79
67
|
```
|
|
80
68
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
### Desktop Applications
|
|
69
|
+
### Desktop applications
|
|
84
70
|
|
|
85
|
-
|
|
71
|
+
Prebuilt apps are on the [download page](https://local-operator.com/download) and the [releases page](https://github.com/damianvtran/local-operator-ui/releases):
|
|
86
72
|
|
|
87
|
-
- **macOS**: Download the `.dmg`
|
|
73
|
+
- **macOS**: Download the `.dmg` (or `.zip`) and open it.
|
|
88
74
|
- **Windows**: Download the `.exe` installer and follow the installation prompts.
|
|
89
|
-
- **Linux**: Download the
|
|
75
|
+
- **Linux**: Download the `.deb`, `.rpm`, or `.AppImage` for your distribution.
|
|
90
76
|
|
|
91
|
-
|
|
77
|
+
The Local Operator backend is bundled with the application and is installed automatically on first run. If you already have a Local Operator backend installed, the application detects it and uses it instead; by default it connects to the backend API at `http://localhost:1111`.
|
|
92
78
|
|
|
93
|
-
|
|
79
|
+
## What it looks like
|
|
94
80
|
|
|
95
|
-
|
|
81
|
+
### Chat and agents
|
|
96
82
|
|
|
97
|
-
|
|
83
|
+
| Light | Dark |
|
|
84
|
+
| :---: | :---: |
|
|
85
|
+
| <img src="./resources/readme/chat-trace-light.webp" alt="Light theme: a conversation showing tool rows, a blocked risky action, and a question the agent asked." width="420"> | <img src="./resources/readme/chat-trace-dark.webp" alt="Dark theme: the same conversation." width="420"> |
|
|
98
86
|
|
|
99
|
-
|
|
100
|
-
- Makes the application more self-contained
|
|
101
|
-
- Works offline
|
|
102
|
-
- Provides a more reliable user experience
|
|
103
|
-
- Supports installation of Python packages via pip
|
|
87
|
+
<sub><code>chat</code> — a working agent's tool rows, a blocked risky action, and the question it asked before continuing.</sub>
|
|
104
88
|
|
|
105
|
-
|
|
89
|
+
### Agent hub
|
|
106
90
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
```
|
|
91
|
+
| Light | Dark |
|
|
92
|
+
| :---: | :---: |
|
|
93
|
+
| <img src="./resources/readme/agent-hub-light.webp" alt="Light theme: the agent hub with categories and a grid of community agents." width="420"> | <img src="./resources/readme/agent-hub-dark.webp" alt="Dark theme: the agent hub grid." width="420"> |
|
|
111
94
|
|
|
112
|
-
|
|
95
|
+
<sub><code>agent hub</code> — community agents you can browse and download, by category.</sub>
|
|
113
96
|
|
|
114
|
-
|
|
97
|
+
### Schedules
|
|
115
98
|
|
|
116
|
-
|
|
99
|
+
| Light | Dark |
|
|
100
|
+
| :---: | :---: |
|
|
101
|
+
| <img src="./resources/readme/schedules-light.webp" alt="Light theme: the schedules page, listing scheduled wakes with their cadence and run history." width="420"> | <img src="./resources/readme/schedules-dark.webp" alt="Dark theme: the schedules page." width="420"> |
|
|
117
102
|
|
|
118
|
-
|
|
103
|
+
<sub><code>schedules</code> — conversations that wake on a timer, with what ran and when they run next.</sub>
|
|
119
104
|
|
|
120
|
-
|
|
121
|
-
- **Windows**: Applications are signed with a trusted code signing certificate
|
|
122
|
-
- **Linux**: While code signing is less common on Linux, packages are built with integrity checks
|
|
105
|
+
### Projects
|
|
123
106
|
|
|
124
|
-
|
|
107
|
+
| Light | Dark |
|
|
108
|
+
| :---: | :---: |
|
|
109
|
+
| <img src="./resources/readme/projects-board-light.webp" alt="Light theme: the projects board with active, paused, and done columns." width="420"> | <img src="./resources/readme/projects-board-dark.webp" alt="Dark theme: the projects board." width="420"> |
|
|
110
|
+
|
|
111
|
+
<sub><code>projects</code> — workstreams you and your agents track across sessions, as a board or a timeline.</sub>
|
|
112
|
+
|
|
113
|
+
### Appearance
|
|
114
|
+
|
|
115
|
+
| Light | Dark |
|
|
116
|
+
| :---: | :---: |
|
|
117
|
+
| <img src="./resources/readme/appearance-light.webp" alt="Light theme: the appearance picker showing the Local Operator themes as swatches." width="420"> | <img src="./resources/readme/appearance-dark.webp" alt="Dark theme: the appearance picker." width="420"> |
|
|
118
|
+
|
|
119
|
+
<sub><code>appearance</code> — 59 colour themes ship with the app; Local Operator Dark is the default.</sub>
|
|
125
120
|
|
|
126
|
-
##
|
|
121
|
+
## Features
|
|
122
|
+
|
|
123
|
+
- **Chat with agents** — real-time conversation with markdown rendering for code blocks and formatted text, syntax highlighting, and per-conversation history.
|
|
124
|
+
- **Agent management** — create, update, and delete agents, and configure their settings: model and description (general), temperature and top_p (chat), and security prompt and execution permissions (security).
|
|
125
|
+
- **Settings** — system prompt configuration, API credentials management, and application configuration in one place.
|
|
126
|
+
- **Local Operator API integration** — the app talks to the Local Operator backend API and shows real-time status updates for long-running operations.
|
|
127
|
+
- **Bundled backend** — the Local Operator backend is installed automatically on first run on every platform; an existing backend installation is detected and used instead.
|
|
128
|
+
|
|
129
|
+
## Project structure
|
|
127
130
|
|
|
128
131
|
The codebase is organized for modularity and code reuse:
|
|
129
132
|
|
|
@@ -132,59 +135,55 @@ The codebase is organized for modularity and code reuse:
|
|
|
132
135
|
- `src/renderer/src/app.tsx`, `main.tsx`, etc.: Entry points for the Electron renderer process.
|
|
133
136
|
- `build/`, `resources/`, `scripts/`: Build assets, static resources, and build scripts.
|
|
134
137
|
|
|
135
|
-
**Import
|
|
138
|
+
**Import conventions:**
|
|
136
139
|
|
|
137
140
|
- Use `@shared/` for shared modules (e.g., `import { useAgents } from "@shared/hooks/use-agents"`).
|
|
138
141
|
- Use `@features/` for feature-specific modules.
|
|
139
|
-
- The
|
|
142
|
+
- The build also resolves the older aliases (`@renderer`, `@components`, `@hooks`, etc.) for the files that still import them.
|
|
140
143
|
|
|
141
144
|
For more details on building or contributing, see the [Contributing Guide](./CONTRIBUTING.md) and [BUILD.md](./docs/BUILD.md).
|
|
142
145
|
|
|
143
|
-
##
|
|
146
|
+
## Building from source
|
|
147
|
+
|
|
148
|
+
If you want to build the application from source, see the [BUILD.md](./docs/BUILD.md) file for detailed instructions.
|
|
144
149
|
|
|
145
|
-
|
|
150
|
+
### Python bundling
|
|
146
151
|
|
|
147
|
-
|
|
152
|
+
For macOS builds, the app bundles a standalone Python directly instead of requiring a Homebrew installation. This approach:
|
|
148
153
|
|
|
149
|
-
-
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
-
|
|
154
|
+
- Needs no admin privileges during installation
|
|
155
|
+
- Keeps the application self-contained
|
|
156
|
+
- Works offline
|
|
157
|
+
- Supports installing Python packages with pip
|
|
153
158
|
|
|
154
|
-
|
|
159
|
+
To set up the standalone Python for development:
|
|
155
160
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
- Security settings (security prompt, execution permissions)
|
|
161
|
+
```bash
|
|
162
|
+
# Run the setup script to download and configure standalone Python
|
|
163
|
+
pnpm setup-python-standalone
|
|
164
|
+
```
|
|
161
165
|
|
|
162
|
-
|
|
166
|
+
This uses [python-build-standalone](https://github.com/indygreg/python-build-standalone), the same approach used by Datasette Desktop and PyOxidizer.
|
|
163
167
|
|
|
164
|
-
|
|
165
|
-
- API credentials management
|
|
166
|
-
- Application configuration
|
|
168
|
+
For more details, see the [PYTHON_BUNDLING.md](./docs/PYTHON_BUNDLING.md) documentation.
|
|
167
169
|
|
|
168
|
-
###
|
|
170
|
+
### Code signing and notarization
|
|
169
171
|
|
|
170
|
-
|
|
171
|
-
- Real-time status updates for long-running operations
|
|
172
|
-
- Error handling and retry mechanisms
|
|
172
|
+
All desktop applications are code signed and notarized to ensure security and trust:
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
- **macOS**: Applications are signed with an Apple Developer ID and notarized with Apple's notarization service
|
|
175
|
+
- **Windows**: Applications are signed with a trusted code signing certificate
|
|
176
|
+
- **Linux**: While code signing is less common on Linux, packages are built with integrity checks
|
|
175
177
|
|
|
176
|
-
|
|
177
|
-
- Automatic installation of the backend when first running the application
|
|
178
|
-
- Automatic detection and use of existing backend installations
|
|
179
|
-
- Cross-platform support for Windows, macOS, and Linux
|
|
178
|
+
For detailed information about the code signing and notarization process, see the [CODE_SIGNING.md](./docs/CODE_SIGNING.md) document.
|
|
180
179
|
|
|
181
|
-
##
|
|
180
|
+
## Contributing
|
|
182
181
|
|
|
183
182
|
Contributions are welcome! Please see our [Contributing Guide](./CONTRIBUTING.md) for details on how to get started with development, code style guidelines, and our contribution process.
|
|
184
183
|
|
|
185
|
-
##
|
|
184
|
+
## Troubleshooting
|
|
186
185
|
|
|
187
|
-
### Common
|
|
186
|
+
### Common issues
|
|
188
187
|
|
|
189
188
|
#### Application fails to connect to the backend
|
|
190
189
|
|
|
@@ -197,7 +196,7 @@ Contributions are welcome! Please see our [Contributing Guide](./CONTRIBUTING.md
|
|
|
197
196
|
#### Backend installation fails
|
|
198
197
|
|
|
199
198
|
- Check the application logs for error messages
|
|
200
|
-
- For macOS, the application
|
|
199
|
+
- For macOS, the application uses a bundled Python framework instead of requiring Homebrew and pyenv
|
|
201
200
|
- If you encounter issues with the bundled Python, see the [PYTHON_BUNDLING.md](./docs/PYTHON_BUNDLING.md) documentation
|
|
202
201
|
- As a fallback, you can try installing the backend manually with `pip install local-operator` and then start it with `local-operator serve`
|
|
203
202
|
|
|
@@ -215,38 +214,23 @@ Contributions are welcome! Please see our [Contributing Guide](./CONTRIBUTING.md
|
|
|
215
214
|
|
|
216
215
|
#### The window came to the front on its own, or a run never appeared
|
|
217
216
|
|
|
218
|
-
Every time the app brings a window to the front it writes one line to its own
|
|
219
|
-
backend log (`~/Library/Application Support/Local Operator/logs/backend-service.log`
|
|
220
|
-
on macOS; the `LOCAL_OPERATOR_LOG_DIR` environment variable moves it):
|
|
217
|
+
Every time the app brings a window to the front it writes one line to its own backend log (`~/Library/Application Support/Local Operator/logs/backend-service.log` on macOS; the `LOCAL_OPERATOR_LOG_DIR` environment variable moves it):
|
|
221
218
|
|
|
222
219
|
```
|
|
223
220
|
[window-raise] trigger=second-instance mode=normal requested=focus pid=9182 cwd=/Users/you/project applied=restore+show+focus
|
|
224
221
|
```
|
|
225
222
|
|
|
226
|
-
`trigger` names what asked: `initial-present` (the app starting up),
|
|
227
|
-
|
|
228
|
-
`
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
at all — although a conversation that is WAITING for a window does: a `headless`
|
|
232
|
-
launch against an app with no window open writes `applied=parked`, `parked=<id>`
|
|
233
|
-
rather than a raise, and that conversation opens the next window you give the app.
|
|
234
|
-
In the line above, `mode` is the window mode the raise ran under and `applied` is
|
|
235
|
-
what it actually did.
|
|
236
|
-
|
|
237
|
-
A second launch only brings the window as far as IT asked: a `headless` run never
|
|
238
|
-
raises it (it can still load the conversation it names), an `inactive` one orders
|
|
239
|
-
the window without activating the app and without pulling it back out of the
|
|
240
|
-
Dock, and a launch that declares nothing — you double-clicking the app while it is
|
|
241
|
-
already running — still comes to the front.
|
|
242
|
-
|
|
243
|
-
### Getting Help
|
|
223
|
+
`trigger` names what asked: `initial-present` (the app starting up), `second-instance` (a second launch sharing this profile), `banner-click`, `viewer-focus` or `viewer-resume`. `pid` and `cwd`, when they are there, name the process that asked — that is the one to stop if something keeps doing it. A run that raises nothing writes nothing, so an app that never came forward has no line at all — although a conversation that is WAITING for a window does: a `headless` launch against an app with no window open writes `applied=parked`, `parked=<id>` rather than a raise, and that conversation opens the next window you give the app. In the line above, `mode` is the window mode the raise ran under and `applied` is what it actually did.
|
|
224
|
+
|
|
225
|
+
A second launch only brings the window as far as IT asked: a `headless` run never raises it (it can still load the conversation it names), an `inactive` one orders the window without activating the app and without pulling it back out of the Dock, and a launch that declares nothing — you double-clicking the app while it is already running — still comes to the front.
|
|
226
|
+
|
|
227
|
+
## Getting help
|
|
244
228
|
|
|
245
229
|
If you encounter issues not covered here, please:
|
|
246
230
|
|
|
247
231
|
1. Check the [GitHub Issues](https://github.com/damianvtran/local-operator-ui/issues) for similar problems
|
|
248
232
|
2. Open a new issue if your problem hasn't been reported
|
|
249
233
|
|
|
250
|
-
##
|
|
234
|
+
## License
|
|
251
235
|
|
|
252
|
-
This project is licensed under the MIT License
|
|
236
|
+
This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details. It is open source because AI tools should be accessible to everyone, and your contributions and feedback help make that real.
|
package/out/main/index.js
CHANGED
|
@@ -67,6 +67,7 @@ const mcpOperationId = zod.z.string().regex(/^[a-f0-9]{32}$/);
|
|
|
67
67
|
const mcpCatalogCwd = zod.z.string().min(1).max(4096).regex(/^(\/|[A-Za-z]:[\\/])/);
|
|
68
68
|
const SUBSCRIPTION_ID_PATTERN = /^[a-f0-9]{32}$/;
|
|
69
69
|
const requestId = zod.z.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/);
|
|
70
|
+
const meshId = zod.z.string().regex(/^[A-Za-z0-9_-]{1,128}$/);
|
|
70
71
|
const sessionImage = zod.z.object({
|
|
71
72
|
data_b64: zod.z.string().min(1).max(1e6),
|
|
72
73
|
mime_type: zod.z.enum(["image/png", "image/jpeg", "image/gif", "image/webp"])
|
|
@@ -296,6 +297,24 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
|
|
|
296
297
|
* archived state, and an unarchive control on a row found by search.
|
|
297
298
|
*/
|
|
298
299
|
include_archived: zod.z.boolean().optional(),
|
|
300
|
+
/*
|
|
301
|
+
* Whether conversations OTHER devices hold belong in the answer.
|
|
302
|
+
*
|
|
303
|
+
* ABSENT MEANS `false`, the same compatibility promise `include_archived`
|
|
304
|
+
* makes and for the same reason: the app's own sidebar fetch has always
|
|
305
|
+
* meant "this device's catalogue", and a client that predates the mesh must
|
|
306
|
+
* keep reading exactly that. The Mesh tab is the ONE surface that asks for
|
|
307
|
+
* the federated list, because "which conversation is on which device" is its
|
|
308
|
+
* question and it cannot answer it from `session_count` alone.
|
|
309
|
+
*
|
|
310
|
+
* THE COST IS NOT ZERO, which is why only that surface asks: the backend's
|
|
311
|
+
* peer projection dials each peer's relay under a 12 s fan-out budget and is
|
|
312
|
+
* TTL-cached at 20 s (`network/relay.py`, `session/peer_rows.py`), while a
|
|
313
|
+
* machine in no network short-circuits to no call at all — so the flag costs
|
|
314
|
+
* a paired device one cached fan-out per cadence, and an unpaired one
|
|
315
|
+
* nothing. The sidebar's two-second poll must never carry it.
|
|
316
|
+
*/
|
|
317
|
+
include_peers: zod.z.boolean().optional(),
|
|
299
318
|
/*
|
|
300
319
|
* The four parameters that make the catalogue PAGEABLE, and the switch that
|
|
301
320
|
* makes the daemon count it.
|
|
@@ -1384,14 +1403,81 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
|
|
|
1384
1403
|
* UI that could would have to re-implement the relay's authorisation model in
|
|
1385
1404
|
* JavaScript.
|
|
1386
1405
|
*
|
|
1387
|
-
* THE MUTATING MESH OPS
|
|
1388
|
-
*
|
|
1389
|
-
*
|
|
1390
|
-
*
|
|
1391
|
-
*
|
|
1406
|
+
* THE MUTATING MESH OPS LANDED WITH THE SURFACES THAT USE THEM (slice 2: the drag
|
|
1407
|
+
* layer, the invite action, the member list), which is the rule this block stated
|
|
1408
|
+
* while they were still absent: a request schema entry with no caller is a
|
|
1409
|
+
* capability this app advertises but cannot exercise.
|
|
1410
|
+
*
|
|
1411
|
+
* `features.session_transfer` gates the TRANSFER and only it (`capabilities.py`):
|
|
1412
|
+
* a backend can host a network, mint invites and remove members without being able
|
|
1413
|
+
* to move a conversation, and the surfaces that gate on the wrong key draw a
|
|
1414
|
+
* control that 404s. The three ops below therefore sit behind different keys —
|
|
1415
|
+
* `networks.invite`/`networks.member.remove` behind `features.peers` (they are
|
|
1416
|
+
* routes the mesh itself introduced), `sessions.transfer` behind
|
|
1417
|
+
* `features.session_transfer`.
|
|
1418
|
+
*
|
|
1419
|
+
* WHAT TRAVELS, AND WHAT DOES NOT. `to` is the destination device id or the
|
|
1420
|
+
* literal `"local"` (a RECALL), because that is the route's own shape: one route,
|
|
1421
|
+
* two protocols, and the direction is decided by which of the two ends is asking
|
|
1422
|
+
* — see `guide://network`. `request_id` is minted by the CALLER and is what makes
|
|
1423
|
+
* a retry replay a recorded outcome instead of starting a second move for a
|
|
1424
|
+
* request that may still be running, so it is sent on every drop rather than kept
|
|
1425
|
+
* for a retry path this surface does not have.
|
|
1392
1426
|
*/
|
|
1393
1427
|
zod.z.object({ op: zod.z.literal("peers.list") }).strict(),
|
|
1394
1428
|
zod.z.object({ op: zod.z.literal("networks.list") }).strict(),
|
|
1429
|
+
/*
|
|
1430
|
+
* Mint an invite. `role` is the joined device's own role in the network and
|
|
1431
|
+
* `device` BINDS the token to one device id, so a token intercepted on its way to
|
|
1432
|
+
* another machine cannot be redeemed by a third one. The token itself NEVER
|
|
1433
|
+
* crosses this API (the receipt carries a path, and it is written where the
|
|
1434
|
+
* renderer cannot read it) — which is why the answer is a receipt and not a
|
|
1435
|
+
* secret.
|
|
1436
|
+
*/
|
|
1437
|
+
zod.z.object({
|
|
1438
|
+
op: zod.z.literal("networks.invite"),
|
|
1439
|
+
networkId: meshId,
|
|
1440
|
+
role: zod.z.enum(["read", "drive", "admin"]),
|
|
1441
|
+
deviceId: meshId.optional()
|
|
1442
|
+
}).strict(),
|
|
1443
|
+
/*
|
|
1444
|
+
* Revoke a membership. `confirm` is the NETWORK'S NAME, typed by the user, and
|
|
1445
|
+
* the route compares it exactly: this is the one mesh act that changes other
|
|
1446
|
+
* devices' state (every peer is rekeyed and the removed device is locked out on
|
|
1447
|
+
* its next handshake), so the request must carry what the user was shown rather
|
|
1448
|
+
* than a bool a stray retry could also send.
|
|
1449
|
+
*/
|
|
1450
|
+
zod.z.object({
|
|
1451
|
+
op: zod.z.literal("networks.member.remove"),
|
|
1452
|
+
networkId: meshId,
|
|
1453
|
+
deviceId: meshId,
|
|
1454
|
+
confirm: zod.z.string().min(1).max(256)
|
|
1455
|
+
}).strict(),
|
|
1456
|
+
/*
|
|
1457
|
+
* Ask a device to take a conversation, or ask THIS device to take one back.
|
|
1458
|
+
*
|
|
1459
|
+
* THE DIRECTION IS THE PROTOCOL'S, not a UI preference: there is no push verb, so
|
|
1460
|
+
* a drop on a peer is this device asking that peer to PULL (`to: <device_id>`),
|
|
1461
|
+
* and a drop on this device is a recall (`to: "local"`). `keep` is the reversible
|
|
1462
|
+
* half — it mints a new id at the destination and leaves the source running —
|
|
1463
|
+
* while a move deletes the source's copy once the handoff commits, which is why
|
|
1464
|
+
* `source_retired = (mode == "move")` on the receipt.
|
|
1465
|
+
*
|
|
1466
|
+
* `wait_s` is a CEILING ON WAITING INSIDE THE REQUEST, not a promise: the route
|
|
1467
|
+
* returns as soon as it has a definite outcome, and a `busy` source refuses
|
|
1468
|
+
* rather than being interrupted. The desktop's own deadline for this op is
|
|
1469
|
+
* derived from the route's published bound rather than from the 20 s control
|
|
1470
|
+
* budget (see `moveClientBoundMs`) — the defect this avoids is a client that gives
|
|
1471
|
+
* up first and reports its own timeout for a move the backend was about to answer.
|
|
1472
|
+
*/
|
|
1473
|
+
zod.z.object({
|
|
1474
|
+
op: zod.z.literal("sessions.transfer"),
|
|
1475
|
+
sessionId,
|
|
1476
|
+
to: zod.z.union([zod.z.literal("local"), meshId]),
|
|
1477
|
+
keep: zod.z.boolean().optional(),
|
|
1478
|
+
waitS: zod.z.number().min(0).max(300).optional(),
|
|
1479
|
+
requestId: requestId.optional()
|
|
1480
|
+
}).strict(),
|
|
1395
1481
|
/*
|
|
1396
1482
|
* The Projects surface (`/v1/desktop/projects*`), APPENDED to the union
|
|
1397
1483
|
* rather than inserted beside the other catalogue ops: the backend serves
|
|
@@ -1466,6 +1552,30 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
|
|
|
1466
1552
|
op: zod.z.literal("projects.milestone.remove"),
|
|
1467
1553
|
key: projectKey,
|
|
1468
1554
|
name: zod.z.string().min(1).max(PROJECT_MILESTONE_NAME_MAX_CHARS)
|
|
1555
|
+
}).strict(),
|
|
1556
|
+
/*
|
|
1557
|
+
* AIDA'S CONTROL PLANE: one read and one control op on the same route
|
|
1558
|
+
* (`/v1/desktop/aida`), because the rail's row and the composer's `/aida`
|
|
1559
|
+
* need the SAME state and a second spelling of it would be a second answer
|
|
1560
|
+
* about her one long session (`design.md` § 4 freezes the route).
|
|
1561
|
+
*
|
|
1562
|
+
* The feature is gated by its OWN capability key (`features.aida`), never a
|
|
1563
|
+
* bump of `commands`: a renderer that does not read it keeps working against
|
|
1564
|
+
* this backend, and this renderer must not call the route while the key is
|
|
1565
|
+
* absent or 0 (§ 3.4's version skew).
|
|
1566
|
+
*
|
|
1567
|
+
* Deliberately NOT a `MESSAGE_OPS` member (see `desktopRequestByteBudget`):
|
|
1568
|
+
* an enum word and a receipt are not prose, so this costs the control budget.
|
|
1569
|
+
*/
|
|
1570
|
+
zod.z.object({ op: zod.z.literal("aida.status") }).strict(),
|
|
1571
|
+
zod.z.object({
|
|
1572
|
+
op: zod.z.literal("aida.control"),
|
|
1573
|
+
/*
|
|
1574
|
+
* The route's own op vocabulary, held to it here: a word the backend does
|
|
1575
|
+
* not serve must fail at this boundary rather than travel as a 422 the
|
|
1576
|
+
* user reads as a defect of their press.
|
|
1577
|
+
*/
|
|
1578
|
+
action: zod.z.enum(["open", "pause", "resume", "greet", "status"])
|
|
1469
1579
|
}).strict()
|
|
1470
1580
|
]);
|
|
1471
1581
|
const desktopRequestSchema = desktopRequestUnion.superRefine(
|
|
@@ -1568,7 +1678,23 @@ const LONG_READ_OPS = /* @__PURE__ */ new Set([
|
|
|
1568
1678
|
...PROVIDER_READ_OPS,
|
|
1569
1679
|
...MESH_READ_OPS
|
|
1570
1680
|
]);
|
|
1571
|
-
|
|
1681
|
+
const MOVE_OP_DEADLINE_S = 90;
|
|
1682
|
+
const MOVE_OFFLOAD_CONFIRM_S = 30;
|
|
1683
|
+
const MOVE_COPY_WAIT_S = 300;
|
|
1684
|
+
const MOVE_CONTROL_SLACK_S = 10;
|
|
1685
|
+
const MOVE_CLIENT_MARGIN_S = 15;
|
|
1686
|
+
const MOVE_APP_MARGIN_MS = 1e4;
|
|
1687
|
+
function moveClientBoundMs(shape) {
|
|
1688
|
+
const recall = shape.to === "local";
|
|
1689
|
+
const hold = recall || shape.keep ? MOVE_COPY_WAIT_S : MOVE_OFFLOAD_CONFIRM_S;
|
|
1690
|
+
const waitS = Math.max(0, shape.waitS ?? 0);
|
|
1691
|
+
return (MOVE_OP_DEADLINE_S + hold + MOVE_CONTROL_SLACK_S + MOVE_CLIENT_MARGIN_S) * 1e3 + waitS * 1e3;
|
|
1692
|
+
}
|
|
1693
|
+
function desktopRequestDeadlineMs(request) {
|
|
1694
|
+
if (typeof request !== "string" && request.op === "sessions.transfer") {
|
|
1695
|
+
return moveClientBoundMs(request) + MOVE_APP_MARGIN_MS;
|
|
1696
|
+
}
|
|
1697
|
+
const op = typeof request === "string" ? request : request.op;
|
|
1572
1698
|
return LONG_READ_OPS.has(op) ? DESKTOP_LONG_READ_DEADLINE_MS : DESKTOP_CONTROL_DEADLINE_MS;
|
|
1573
1699
|
}
|
|
1574
1700
|
const DESKTOP_DEADLINE_EXCEEDED_CODE = "deadline_exceeded";
|
|
@@ -1630,6 +1756,12 @@ const PANEL_READ_OPS = /* @__PURE__ */ new Set([
|
|
|
1630
1756
|
function desktopRequestDeadlineDetail(op, deadlineMs) {
|
|
1631
1757
|
const seconds = Math.round(deadlineMs / 1e3);
|
|
1632
1758
|
const code = DESKTOP_DEADLINE_EXCEEDED_CODE;
|
|
1759
|
+
if (op === "sessions.transfer") {
|
|
1760
|
+
return {
|
|
1761
|
+
code,
|
|
1762
|
+
message: `The app waits up to ${seconds} seconds for a move, and it was still running when the app stopped waiting. The move was asked for, so its outcome is unknown from here: read the session again before moving it anywhere else.`
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1633
1765
|
if (READ_ONLY_OPS.has(op)) {
|
|
1634
1766
|
return {
|
|
1635
1767
|
code,
|
|
@@ -1689,6 +1821,46 @@ function desktopEndpoint(request) {
|
|
|
1689
1821
|
return { path: "/v1/desktop/peers", method: "GET" };
|
|
1690
1822
|
case "networks.list":
|
|
1691
1823
|
return { path: "/v1/desktop/networks", method: "GET" };
|
|
1824
|
+
/*
|
|
1825
|
+
* THE THREE MESH WRITES. Each path segment is `encodeURIComponent`ed even
|
|
1826
|
+
* though `meshId` already refuses `/`, `.` and `%`: the schema is this
|
|
1827
|
+
* client's check, and a redirect or a hand-built request must not be able to
|
|
1828
|
+
* turn a device id into a path fragment. `device: null` on an invite is the
|
|
1829
|
+
* route's own "unbound token" — an invite any device may redeem once.
|
|
1830
|
+
*/
|
|
1831
|
+
case "networks.invite":
|
|
1832
|
+
return {
|
|
1833
|
+
path: `/v1/desktop/networks/${encodeURIComponent(request.networkId)}/invite`,
|
|
1834
|
+
method: "POST",
|
|
1835
|
+
body: { role: request.role, device: request.deviceId ?? null }
|
|
1836
|
+
};
|
|
1837
|
+
case "networks.member.remove":
|
|
1838
|
+
return {
|
|
1839
|
+
path: `/v1/desktop/networks/${encodeURIComponent(request.networkId)}/members/${encodeURIComponent(request.deviceId)}`,
|
|
1840
|
+
method: "DELETE",
|
|
1841
|
+
// The NETWORK'S NAME, as typed: see the op's own comment for why the
|
|
1842
|
+
// route takes a name rather than a bool.
|
|
1843
|
+
body: { confirm: request.confirm }
|
|
1844
|
+
};
|
|
1845
|
+
case "sessions.transfer":
|
|
1846
|
+
return {
|
|
1847
|
+
path: `/v1/desktop/sessions/${encodeURIComponent(request.sessionId)}/transfer`,
|
|
1848
|
+
method: "POST",
|
|
1849
|
+
body: {
|
|
1850
|
+
to: request.to,
|
|
1851
|
+
// Sent explicitly rather than omitted-when-false: `keep` decides whether
|
|
1852
|
+
// the SOURCE'S COPY IS DELETED, so the request says which move it is
|
|
1853
|
+
// rather than leaving the route's default to answer for a drop a user
|
|
1854
|
+
// made from a menu that offered the copy.
|
|
1855
|
+
keep: request.keep ?? false,
|
|
1856
|
+
wait_s: request.waitS ?? 0,
|
|
1857
|
+
// Omitted while absent, and this one is load-bearing: the route
|
|
1858
|
+
// journals an UNCONFIRMED move under this key so a retry replays the
|
|
1859
|
+
// recorded outcome instead of moving twice, and a request with no key
|
|
1860
|
+
// is a different (unjournalled) request on purpose.
|
|
1861
|
+
...request.requestId ? { request_id: request.requestId } : {}
|
|
1862
|
+
}
|
|
1863
|
+
};
|
|
1692
1864
|
case "profiles.list":
|
|
1693
1865
|
return { path: "/v1/desktop/profiles", method: "GET" };
|
|
1694
1866
|
case "profiles.get":
|
|
@@ -1745,6 +1917,7 @@ function desktopEndpoint(request) {
|
|
|
1745
1917
|
if (request.scope_name) params.set("scope_name", request.scope_name);
|
|
1746
1918
|
if (request.cursor) params.set("cursor", request.cursor);
|
|
1747
1919
|
if (request.with_counts) params.set("with_counts", "true");
|
|
1920
|
+
if (request.include_peers) params.set("include_peers", "true");
|
|
1748
1921
|
return {
|
|
1749
1922
|
path: `/v1/desktop/sessions?${params}`,
|
|
1750
1923
|
method: "GET"
|
|
@@ -2526,6 +2699,19 @@ function desktopEndpoint(request) {
|
|
|
2526
2699
|
path: `/v1/desktop/projects/${encodeURIComponent(request.key)}/milestones/${encodeURIComponent(request.name)}`,
|
|
2527
2700
|
method: "DELETE"
|
|
2528
2701
|
};
|
|
2702
|
+
case "aida.status":
|
|
2703
|
+
return { path: "/v1/desktop/aida", method: "GET" };
|
|
2704
|
+
case "aida.control":
|
|
2705
|
+
return {
|
|
2706
|
+
path: "/v1/desktop/aida",
|
|
2707
|
+
method: "POST",
|
|
2708
|
+
/*
|
|
2709
|
+
* The route's body is `{"op": ...}` — its own word, not this envelope's
|
|
2710
|
+
* — so the ACTION travels under the route's field name and the two `op`s
|
|
2711
|
+
* cannot be read as one.
|
|
2712
|
+
*/
|
|
2713
|
+
body: { op: request.action }
|
|
2714
|
+
};
|
|
2529
2715
|
}
|
|
2530
2716
|
}
|
|
2531
2717
|
const MAX_PROBE_PATHS = 64;
|
|
@@ -6129,7 +6315,7 @@ async function requestDesktopOutcome(input, backendUrl, token) {
|
|
|
6129
6315
|
}
|
|
6130
6316
|
const target2 = desktopEndpoint(request);
|
|
6131
6317
|
let answered = false;
|
|
6132
|
-
const deadlineMs = desktopRequestDeadlineMs(request
|
|
6318
|
+
const deadlineMs = desktopRequestDeadlineMs(request);
|
|
6133
6319
|
try {
|
|
6134
6320
|
const body = target2.body === void 0 ? void 0 : JSON.stringify(target2.body);
|
|
6135
6321
|
if (body && Buffer.byteLength(body) > desktopRequestByteBudget(request.op)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{e as o,c as b,g as v,k as O,h as P,j as p,l as w,m as A,n as x,t as c,o as N}from"./_baseUniq-
|
|
1
|
+
import{e as o,c as b,g as v,k as O,h as P,j as p,l as w,m as A,n as x,t as c,o as N}from"./_baseUniq-7Lo40p8f.js";import{as as g,aa as y,at as E,au as F,av as M,aw as I,ax as _,ay as $,az as B,aA as T}from"./mermaid.core-CF1Z-P7X.js";var S=/\s/;function z(n){for(var r=n.length;r--&&S.test(n.charAt(r)););return r}var G=/^\s+/;function H(n){return n&&n.slice(0,z(n)+1).replace(G,"")}var m=NaN,L=/^[-+]0x[0-9a-f]+$/i,R=/^0b[01]+$/i,q=/^0o[0-7]+$/i,C=parseInt;function K(n){if(typeof n=="number")return n;if(o(n))return m;if(g(n)){var r=typeof n.valueOf=="function"?n.valueOf():n;n=g(r)?r+"":r}if(typeof n!="string")return n===0?n:+n;n=H(n);var t=R.test(n);return t||q.test(n)?C(n.slice(2),t?2:8):L.test(n)?m:+n}var W=1/0,X=17976931348623157e292;function Y(n){if(!n)return n===0?n:0;if(n=K(n),n===W||n===-1/0){var r=n<0?-1:1;return r*X}return n===n?n:0}function D(n){var r=Y(n),t=r%1;return r===r?t?r-t:r:0}function fn(n){var r=n==null?0:n.length;return r?b(n):[]}var l=Object.prototype,J=l.hasOwnProperty,dn=y(function(n,r){n=Object(n);var t=-1,e=r.length,a=e>2?r[2]:void 0;for(a&&E(r[0],r[1],a)&&(e=1);++t<e;)for(var f=r[t],i=F(f),s=-1,d=i.length;++s<d;){var u=i[s],h=n[u];(h===void 0||M(h,l[u])&&!J.call(n,u))&&(n[u]=f[u])}return n});function un(n){var r=n==null?0:n.length;return r?n[r-1]:void 0}function Q(n){return function(r,t,e){var a=Object(r);if(!I(r)){var f=v(t);r=O(r),t=function(s){return f(a[s],s,a)}}var i=n(r,t,e);return i>-1?a[f?r[i]:i]:void 0}}var U=Math.max;function Z(n,r,t){var e=n==null?0:n.length;if(!e)return-1;var a=t==null?0:D(t);return a<0&&(a=U(e+a,0)),P(n,v(r),a)}var hn=Q(Z);function V(n,r){var t=-1,e=I(n)?Array(n.length):[];return p(n,function(a,f,i){e[++t]=r(a,f,i)}),e}function gn(n,r){var t=_(n)?w:V;return t(n,v(r))}var j=Object.prototype,k=j.hasOwnProperty;function nn(n,r){return n!=null&&k.call(n,r)}function vn(n,r){return n!=null&&A(n,r,nn)}function rn(n,r){return n<r}function tn(n,r,t){for(var e=-1,a=n.length;++e<a;){var f=n[e],i=r(f);if(i!=null&&(s===void 0?i===i&&!o(i):t(i,s)))var s=i,d=f}return d}function mn(n){return n&&n.length?tn(n,$,rn):void 0}function an(n,r,t,e){if(!g(n))return n;r=x(r,n);for(var a=-1,f=r.length,i=f-1,s=n;s!=null&&++a<f;){var d=c(r[a]),u=t;if(d==="__proto__"||d==="constructor"||d==="prototype")return n;if(a!=i){var h=s[d];u=void 0,u===void 0&&(u=g(h)?h:B(r[a+1])?[]:{})}T(s,d,u),s=s[d]}return n}function on(n,r,t){for(var e=-1,a=r.length,f={};++e<a;){var i=r[e],s=N(n,i);t(s,i)&&an(f,x(i,n),s)}return f}export{rn as a,tn as b,V as c,on as d,mn as e,fn as f,hn as g,vn as h,dn as i,D as j,un as l,gn as m,Y as t};
|