@online5880/opensession 0.1.1 โ 0.1.3
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.ko.md +133 -0
- package/README.md +137 -32
- package/package.json +23 -20
- package/site/index.html +35 -0
- package/sql/schema.sql +38 -27
- package/src/automation.js +197 -0
- package/src/cli.js +1317 -284
- package/src/config.js +118 -32
- package/src/hook-server.js +194 -0
- package/src/idempotency.js +66 -0
- package/src/metrics.js +110 -0
- package/src/supabase.js +309 -129
- package/src/tui.js +159 -0
- package/src/viewer.js +708 -0
- package/test/cli-compatibility.test.js +63 -0
- package/test/config-secrets.test.js +47 -0
- package/test/idempotency.test.js +30 -0
- package/test/supabase-append-event.test.js +133 -0
package/README.ko.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# ๐ OpenSession
|
|
2
|
+
|
|
3
|
+
**[English](README.md) | [ํ๊ตญ์ด](README.ko.md)**
|
|
4
|
+
|
|
5
|
+
> **AI ์์ด์ ํธ ์ด์์ ์ํ ์คํ ์ฐ์์ฑ ๊ณ์ธต (Execution Continuity Layer)**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@online5880/opensession)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](#)
|
|
10
|
+
[](https://supabase.com)
|
|
11
|
+
|
|
12
|
+
**OpenSession**์ AI ์์ด์ ํธ๊ฐ ๋ค์ํ ๋๊ตฌ, ํ๊ฒฝ, ๋คํธ์ํฌ ์ํ ์ฌ์ด์์ ์์
์ ๋งฅ๋ฝ(Context)๊ณผ ์ฐ์์ฑ(Continuity)์ ์์ง ์๋๋ก ๋๋ **์คํ ์ฐ์์ฑ OS**์
๋๋ค. ํ ๋ฒ ์์ํ๋ฉด ์ด๋์๋ ์ฌ๊ฐํ ์ ์์ต๋๋ค.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## ๐ ์ OpenSession ์ธ๊ฐ์?
|
|
17
|
+
|
|
18
|
+
AI ์์ด์ ํธ์ ํ์
ํ ๋ ๊ฐ์ฅ ํฐ ๋ฌธ์ ๋ **"๋งฅ๋ฝ์ ๋จ์ (Context Fragmentation)"**์
๋๋ค.
|
|
19
|
+
- ๋ก์ปฌ์์ ์์
ํ๋ ์์ด์ ํธ๋ฅผ ์๋ฒ๋ก ์ฎ๊ธฐ๋ฉด?
|
|
20
|
+
- ๋คํธ์ํฌ ์ค๋ฅ๋ก ์ธ์
์ด ๋๊ธฐ๋ฉด?
|
|
21
|
+
- ์ฌ๋ฌ ๋๊ตฌ๊ฐ ๊ฐ์์ ๋ก๊ทธ๋ฅผ ๋จ๊ฒจ์ ์ ์ฒด ํ๋ฆ์ ํ์
ํ๊ธฐ ํ๋ค๋ค๋ฉด?
|
|
22
|
+
|
|
23
|
+
**OpenSession์ ์ด ๋ชจ๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๋ค.** ๋จ์ผ ์ธ์
ID๋ก ๋ชจ๋ ํ๋์ Supabase์ ์์ํํ๊ณ , CLI/Web/TUI๋ฅผ ํตํด ๋๊น ์์ด ์ค์๊ฐ์ผ๋ก ๋ชจ๋ํฐ๋งํ ์ ์์ต๋๋ค.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## โจ ํต์ฌ ๊ธฐ๋ฅ
|
|
28
|
+
|
|
29
|
+
### 1. ์์์ ์ธ์
๋ชจ๋ธ (Stable Session Model)
|
|
30
|
+
- ์ด๋ค ํ๊ฒฝ์์๋ ๋์ผํ `session_id`๋ฅผ ์ฌ์ฉํ์ฌ ์์
์ ์ด์ด๊ฐ๋๋ค.
|
|
31
|
+
- `start` -> `pause` -> `resume` ์ํฌํ๋ก์ฐ๋ฅผ ์๋ฒฝํ๊ฒ ์ง์ํฉ๋๋ค.
|
|
32
|
+
|
|
33
|
+
### 2. ๊ฐ๋ ฅํ ์ด๋ฒคํธ ํ์๋ผ์ธ (Durable Event Timeline)
|
|
34
|
+
- **์๋ (Intent)**: ๋ฌด์์ ํ๋ ค๊ณ ํ๋๊ฐ?
|
|
35
|
+
- **์ก์
(Action)**: ์ค์ ์ด๋ค ๋ช
๋ น์ ์คํํ๋๊ฐ?
|
|
36
|
+
- **๊ฒฐ๊ณผ๋ฌผ (Artifact)**: ๋ฌด์์ ๋ง๋ค์ด๋๋๊ฐ?
|
|
37
|
+
- ์ ์ธ ๊ฐ์ง ํต์ฌ ์์๋ฅผ ๊ตฌ์กฐํ๋ JSON์ผ๋ก ๊ธฐ๋กํ์ฌ ์ฌ์ธต ๋ถ์์ด ๊ฐ๋ฅํฉ๋๋ค.
|
|
38
|
+
|
|
39
|
+
### 3. ๋ค์ค ๋ชจ๋ํฐ๋ง ์ธํฐํ์ด์ค (Multi-Surface Monitoring)
|
|
40
|
+
- **CLI**: ์ง๊ด์ ์ธ ํฐ๋ฏธ๋ ๊ธฐ๋ฐ ์ ์ด.
|
|
41
|
+
- **WebUI (Viewer)**: ๋ธ๋ผ์ฐ์ ์์ ํ์ธํ๋ ๊ณ ํด์๋ ํ์๋ผ์ธ ๋์๋ณด๋.
|
|
42
|
+
- **TUI (Terminal UI)**: ํฐ๋ฏธ๋ ์์์ ํค๋ณด๋๋ก ์กฐ์ํ๋ ๋ํํ ๋์๋ณด๋.
|
|
43
|
+
|
|
44
|
+
### 4. ์ํฐํ๋ผ์ด์ฆ๊ธ ์ ๋ขฐ์ฑ (Enterprise-Grade Reliability)
|
|
45
|
+
- **๋ฉฑ๋ฑ์ฑ(Idempotency)** ๋ณด์ฅ: ๋์ผํ ์ด๋ฒคํธ๊ฐ ์ค๋ณต ๊ธฐ๋ก๋๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค.
|
|
46
|
+
- **์ง์ ๋ฐฑ์คํ(Exponential Backoff)**: ๋ถ์์ ํ ๋คํธ์ํฌ ํ๊ฒฝ์์๋ ์๋์ผ๋ก ์ฌ์๋ํฉ๋๋ค.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## ๐บ๏ธ ๋ก๋๋งต: 3๋จ๊ณ ์ธํฐํ์ด์ค
|
|
51
|
+
|
|
52
|
+
| ๋จ๊ณ (Phase) | ์ธํฐํ์ด์ค | ์ํ | ๊ธฐ๋ฅ ์ค๋ช
|
|
|
53
|
+
| :--- | :--- | :--- | :--- |
|
|
54
|
+
| **Phase 1** | **CLI ์ฝ์ด** | โ
์์ ๋จ | ์ธ์
์ ์ด, ๊ธฐ๋ณธ ๋ก๊น
, ์ค์ ๊ด๋ฆฌ |
|
|
55
|
+
| **Phase 2** | **WebUI ๋ทฐ์ด** | โ
์์ ๋จ | ๋คํฌ ํ
๋ง, ํต๊ณ(KPI) ๋ฆฌํฌํธ, JSON ํ์ด๋ก๋ ๋ทฐ์ด |
|
|
56
|
+
| **Phase 3** | **์ธํฐ๋ํฐ๋ธ TUI** | โ
ํ์ฑํ | ๋ํํ ์ธ์
์ ํ, ์ค์๊ฐ ์ด๋ฒคํธ ์คํธ๋ฆฌ๋ฐ ์๋ ๊ฐฑ์ |
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## ๐ ๏ธ ์์ํ๊ธฐ
|
|
61
|
+
|
|
62
|
+
### ์ค์น ๋ฐ ๋จ์ถ์ด ์ค์ (Alias)
|
|
63
|
+
|
|
64
|
+
๋น ๋ฅธ ์คํ์ ์ํด ์ ํ๋กํ์ ๋จ์ถ์ด๋ฅผ ์ถ๊ฐํ์ธ์:
|
|
65
|
+
|
|
66
|
+
**macOS / Linux (Bash/Zsh):**
|
|
67
|
+
```bash
|
|
68
|
+
alias opss='npx -y @online5880/opensession'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Windows (PowerShell):**
|
|
72
|
+
```powershell
|
|
73
|
+
function opss { npx -y @online5880/opensession @args }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 1๋ถ ํต ์คํํธ
|
|
77
|
+
|
|
78
|
+
1. **์ด๊ธฐํ**: Supabase URL๊ณผ API ํค๋ฅผ ์ค์ ํฉ๋๋ค.
|
|
79
|
+
```bash
|
|
80
|
+
opss init
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
2. **์ธ์
์์**: ์๋ก์ด ํ๋ก์ ํธ ์ธ์
์ ์์ํฉ๋๋ค.
|
|
84
|
+
```bash
|
|
85
|
+
opss start --project-key my-ai-lab --actor mane
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
3. **์ด๋ฒคํธ ๊ธฐ๋ก**: ์์ด์ ํธ์ ํ๋์ ๊ธฐ๋กํฉ๋๋ค.
|
|
89
|
+
```bash
|
|
90
|
+
opss log --limit 10
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
4. **๋์๋ณด๋ ์คํ**: ์ํ๋ ๋ทฐ์ด๋ฅผ ์ ํํด ๋ชจ๋ํฐ๋งํ์ธ์.
|
|
94
|
+
```bash
|
|
95
|
+
opss tui # ํฐ๋ฏธ๋ ๋์๋ณด๋ (์ถ์ฒ)
|
|
96
|
+
opss viewer # ์น ๋ธ๋ผ์ฐ์ ๋ทฐ์ด
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## ๐ ๋ช
๋ น์ด ๊ฐ์ด๋
|
|
102
|
+
|
|
103
|
+
| ๋ช
๋ น์ด | ๋จ์ถ์ด | ์ค๋ช
|
|
|
104
|
+
| :--- | :--- | :--- |
|
|
105
|
+
| `init` | `setup` | Supabase ์๋ฒ ์ฐ๊ฒฐ ๋ฐ ๋ก์ปฌ ์ค์ ์ ์ด๊ธฐํํฉ๋๋ค. |
|
|
106
|
+
| `start` | `st` | ์๋ก์ด ์ธ์
์ ์์ฑํ๊ณ ํ์๋ผ์ธ์ ์์ํฉ๋๋ค. |
|
|
107
|
+
| `resume` | `rs` | ๋ฉฑ๋ฑ์ฑ ๋ณดํธ๋ฅผ ํตํด ๊ธฐ์กด ์ธ์
์ ์ฌ๊ฐํฉ๋๋ค. |
|
|
108
|
+
| `tui` | - | **(New)** ์ธํฐ๋ํฐ๋ธ ํฐ๋ฏธ๋ ๋์๋ณด๋(TUI)๋ฅผ ์คํํฉ๋๋ค. |
|
|
109
|
+
| `viewer` | `vw` | ๋ก์ปฌ ์น ๋ทฐ์ด ์๋ฒ๋ฅผ ์คํํฉ๋๋ค (๊ธฐ๋ณธ 8787 ํฌํธ). |
|
|
110
|
+
| `status` | `ps` | CLI ๋ฒ์ ์ ๋ณด ๋ฐ ํ์ฑ ์ธ์
์ํ๋ฅผ ํ์ธํฉ๋๋ค. |
|
|
111
|
+
| `report` | - | 28์ผ๊ฐ์ KPI ํต๊ณ ๋ฐ ์ฃผ๊ฐ ํธ๋ ๋ ๋ถ์ ๋ฆฌํฌํธ๋ฅผ ์์ฑํฉ๋๋ค. |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## ๐๏ธ ์ํคํ
์ฒ
|
|
116
|
+
|
|
117
|
+
OpenSession์ ์์ด์ ํธ ๋ฐํ์๊ณผ ์๊ตฌ ์คํ ๋ฆฌ์ง ์ฌ์ด์ **์ ๋ขฐ์ฑ ๋์ ์ฐ๊ฒฐ ๊ณ์ธต**์ผ๋ก ์๋ํฉ๋๋ค.
|
|
118
|
+
|
|
119
|
+
```mermaid
|
|
120
|
+
graph TD
|
|
121
|
+
Agent[AI Agent / Developer] -->|Log/Control| CLI[OpenSession CLI]
|
|
122
|
+
CLI -->|Persist| DB[(Supabase Cloud)]
|
|
123
|
+
DB -->|Stream| TUI[TUI Dashboard]
|
|
124
|
+
DB -->|Render| Web[Web Viewer]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## ๐ค ๊ธฐ์ฌํ๊ธฐ (Contributing)
|
|
130
|
+
|
|
131
|
+
๊ธฐ์ฌ๋ ์ธ์ ๋ ํ์ํฉ๋๋ค! ๋ฒ๊ทธ ์ ๋ณด ๋ฐ ๊ธฐ๋ฅ ์ ์์ [GitHub Issues](https://github.com/online5880/opensession/issues)๋ฅผ ์ด์ฉํด ์ฃผ์ธ์.
|
|
132
|
+
|
|
133
|
+
MIT ยฉ [online5880](https://github.com/online5880)
|
package/README.md
CHANGED
|
@@ -1,32 +1,137 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
1
|
+
# ๐ OpenSession
|
|
2
|
+
|
|
3
|
+
**[English](README.md) | [ํ๊ตญ์ด](README.ko.md)**
|
|
4
|
+
|
|
5
|
+
> **The Execution Continuity Layer for AI Agent Operations**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@online5880/opensession)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](#)
|
|
10
|
+
[](https://supabase.com)
|
|
11
|
+
|
|
12
|
+
**OpenSession** is an execution continuity layer designed to help AI agents maintain context and workflow stability across different tools, environments, and network conditions.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## ๐ Why OpenSession?
|
|
17
|
+
|
|
18
|
+
The biggest challenge in collaborating with AI agents is **"Context Fragmentation."**
|
|
19
|
+
- What happens if you move an agent from local to a remote server?
|
|
20
|
+
- What if the session drops due to network issues?
|
|
21
|
+
- How do you track the flow across multiple tools with disparate logs?
|
|
22
|
+
|
|
23
|
+
**OpenSession solves this.** By using a single session ID, all activities are persisted to Supabase, allowing you to monitor and resume work seamlessly via CLI, Web, or TUI.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## โจ Key Capabilities
|
|
28
|
+
|
|
29
|
+
### 1. Stable Session Model
|
|
30
|
+
- Resume work anywhere using the same `session_id`.
|
|
31
|
+
- Native support for `start` -> `pause` -> `resume` workflows.
|
|
32
|
+
|
|
33
|
+
### 2. Durable Event Timeline
|
|
34
|
+
- **Intent**: What is the goal?
|
|
35
|
+
- **Action**: What command was executed?
|
|
36
|
+
- **Artifact**: What was produced?
|
|
37
|
+
- All elements are recorded in structured JSON for deep analysis.
|
|
38
|
+
|
|
39
|
+
### 3. Multi-Surface Monitoring
|
|
40
|
+
- **CLI**: Intuitive terminal-based control.
|
|
41
|
+
- **WebUI (Viewer)**: High-resolution timeline dashboard in your browser.
|
|
42
|
+
- **TUI (Terminal UI)**: Keyboard-driven interactive dashboard inside your terminal.
|
|
43
|
+
|
|
44
|
+
### 4. Enterprise-Grade Reliability
|
|
45
|
+
- **Idempotency**: Prevents duplicate event recording.
|
|
46
|
+
- **Exponential Backoff**: Automatic retries for unstable network scenarios.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## ๐บ๏ธ Roadmap: The 3-Layer Interface
|
|
51
|
+
|
|
52
|
+
| Phase | Surface | Status | Features |
|
|
53
|
+
| :--- | :--- | :--- | :--- |
|
|
54
|
+
| **Phase 1** | **CLI Core** | โ
Stable | Session control, basic logging, config management |
|
|
55
|
+
| **Phase 2** | **WebUI Viewer** | โ
Stable | Dark theme, KPI reports, JSON payload viewer |
|
|
56
|
+
| **Phase 3** | **Interactive TUI** | โ
Active | Real-time session switching, live event streaming |
|
|
57
|
+
|
|
58
|
+
### Installation & Setup
|
|
59
|
+
|
|
60
|
+
#### 1. Alias / Function Setup
|
|
61
|
+
To use the `opss` shorthand, add the following to your shell profile:
|
|
62
|
+
|
|
63
|
+
**macOS / Linux (Bash/Zsh):**
|
|
64
|
+
```bash
|
|
65
|
+
alias opss='npx -y @online5880/opensession'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Windows (PowerShell):**
|
|
69
|
+
```powershell
|
|
70
|
+
function opss { npx -y @online5880/opensession @args }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### 2. Global Install (Optional)
|
|
74
|
+
```bash
|
|
75
|
+
npm install -g @online5880/opensession
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## ๐ 1-Minute Quickstart
|
|
81
|
+
|
|
82
|
+
1. **Initialize**: Set up your Supabase URL and API Key.
|
|
83
|
+
```bash
|
|
84
|
+
opss init
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
2. **Start Session**: Begin a new project session.
|
|
88
|
+
```bash
|
|
89
|
+
opss start --project-key my-ai-lab --actor mane
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
3. **Log Events**: Record agent activities.
|
|
93
|
+
```bash
|
|
94
|
+
opss log --limit 10
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
4. **Launch Dashboard**: Choose your preferred view.
|
|
98
|
+
```bash
|
|
99
|
+
opss tui # Terminal dashboard (Recommended)
|
|
100
|
+
opss viewer # Web browser viewer
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## ๐ Command Reference
|
|
106
|
+
|
|
107
|
+
| Command | Alias | Description |
|
|
108
|
+
| :--- | :--- | :--- |
|
|
109
|
+
| `init` | `setup` | Initialize Supabase connection and local config. |
|
|
110
|
+
| `start` | `st` | Create a new session and start the timeline. |
|
|
111
|
+
| `resume` | `rs` | Resume an existing session with idempotency protection. |
|
|
112
|
+
| `tui` | - | **(New)** Launch the interactive Terminal UI dashboard. |
|
|
113
|
+
| `viewer` | `vw` | Run a local read-only web viewer server. |
|
|
114
|
+
| `status` | `ps` | Check CLI version and active session status. |
|
|
115
|
+
| `report` | - | Generate 28-day KPI stats and weekly trend analysis. |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## ๐๏ธ Architecture
|
|
120
|
+
|
|
121
|
+
OpenSession acts as a high-reliability bridge between agent runtimes and persistent storage.
|
|
122
|
+
|
|
123
|
+
```mermaid
|
|
124
|
+
graph TD
|
|
125
|
+
Agent[AI Agent / Developer] -->|Log/Control| CLI[OpenSession CLI]
|
|
126
|
+
CLI -->|Persist| DB[(Supabase Cloud)]
|
|
127
|
+
DB -->|Stream| TUI[TUI Dashboard]
|
|
128
|
+
DB -->|Render| Web[Web Viewer]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## ๐ค Contributing
|
|
134
|
+
|
|
135
|
+
Contributions are welcome! Please use [GitHub Issues](https://github.com/online5880/opensession/issues) for bug reports and feature requests.
|
|
136
|
+
|
|
137
|
+
MIT ยฉ [online5880](https://github.com/online5880)
|
package/package.json
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@online5880/opensession",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Session continuity bridge CLI with Supabase backend",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"opensession": "src/cli.js"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@online5880/opensession",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Session continuity bridge CLI with Supabase backend",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"opensession": "src/cli.js",
|
|
8
|
+
"opss": "src/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/cli.js",
|
|
12
|
+
"lint": "sh -c 'for f in src/*.js; do node --check \"$f\"; done'",
|
|
13
|
+
"test": "node --test test/*.test.js"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@supabase/supabase-js": "^2.52.1",
|
|
20
|
+
"blessed": "^0.1.81",
|
|
21
|
+
"commander": "^14.0.1"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/site/index.html
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>OpenSession โ Agent Execution Continuity OS</title>
|
|
7
|
+
<meta name="description" content="Start once, resume anywhere. OpenSession is the execution continuity layer for AI agent operations." />
|
|
8
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
|
11
|
+
<style>
|
|
12
|
+
:root {--bg:#080b12;--bg-soft:#0f1420;--panel:#121a28;--panel-2:#182234;--text:#e9f1ff;--muted:#99abc9;--line:#22314a;--brand:#64d6ff;--brand-2:#8c7bff;--ok:#37d67a;}
|
|
13
|
+
*{box-sizing:border-box} body{margin:0;font-family:Inter,system-ui,-apple-system,sans-serif;color:var(--text);background:radial-gradient(1200px 700px at 85% -10%,rgba(140,123,255,.22),transparent 60%),radial-gradient(1000px 600px at 10% -10%,rgba(100,214,255,.18),transparent 58%),var(--bg)}
|
|
14
|
+
.container{width:min(1120px,92vw);margin:0 auto}.topbar{display:flex;justify-content:space-between;align-items:center;padding:18px 0;border-bottom:1px solid rgba(255,255,255,.06);position:sticky;top:0;backdrop-filter:blur(10px);background:rgba(8,11,18,.75)}
|
|
15
|
+
.brand{font-weight:800}.brand span{color:var(--brand)}.nav a{color:var(--muted);text-decoration:none;margin-left:18px;font-size:.95rem}.hero{padding:74px 0 34px;text-align:center}
|
|
16
|
+
.eyebrow{color:var(--brand);font-weight:600;letter-spacing:.12em;text-transform:uppercase;font-size:.78rem}h1{margin:14px auto;max-width:900px;font-size:clamp(2rem,5vw,3.8rem);line-height:1.08}.sub{margin:0 auto;max-width:760px;color:var(--muted);font-size:1.08rem}
|
|
17
|
+
.cta{margin-top:28px;display:flex;gap:12px;justify-content:center;flex-wrap:wrap}.btn{padding:12px 18px;border-radius:12px;text-decoration:none;font-weight:600}.btn.primary{color:#06121a;background:linear-gradient(90deg,var(--brand),#7de6ff)}.btn.secondary{color:var(--text);border:1px solid #2a3c59;background:rgba(255,255,255,.02)}
|
|
18
|
+
.metrics{margin-top:28px;display:grid;grid-template-columns:repeat(4,1fr);gap:10px;background:rgba(255,255,255,.02);border:1px solid var(--line);border-radius:14px;padding:10px}.metric{background:linear-gradient(180deg,var(--panel),var(--panel-2));border-radius:10px;padding:14px}.metric .n{font-size:1.1rem;font-weight:800}.metric .l{color:var(--muted);font-size:.85rem;margin-top:4px}
|
|
19
|
+
section{padding:36px 0}h2{margin:0 0 14px;font-size:1.45rem}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}.card{border:1px solid var(--line);background:linear-gradient(180deg,var(--panel),#111827);border-radius:14px;padding:16px}.card h3{margin:0 0 8px}.card p{margin:0;color:var(--muted);font-size:.93rem}
|
|
20
|
+
.arch{border:1px solid var(--line);border-radius:14px;padding:16px;background:linear-gradient(180deg,#101827,#0d1421)}.layers{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-top:12px}.layer{border:1px solid #2a3e60;border-radius:10px;padding:12px;background:#0c1320}
|
|
21
|
+
.timeline{border-left:2px solid #2a3b5a;margin-left:6px;padding-left:16px}.event{margin:0 0 14px}.event span{color:var(--muted);display:block;margin-top:4px}
|
|
22
|
+
.code{border:1px solid #2a3f60;border-radius:12px;background:#08111d;overflow:auto;font-family:"JetBrains Mono",monospace;font-size:.9rem;line-height:1.6;padding:14px}.code .ok{color:var(--ok)}.code .cmd{color:#8fd0ff}
|
|
23
|
+
footer{padding:30px 0 42px;color:var(--muted);font-size:.9rem;border-top:1px solid rgba(255,255,255,.06);margin-top:22px}
|
|
24
|
+
@media (max-width:900px){.cards{grid-template-columns:1fr}.layers{grid-template-columns:1fr 1fr}.metrics{grid-template-columns:1fr 1fr}}
|
|
25
|
+
</style>
|
|
26
|
+
</head>
|
|
27
|
+
<body><div class="container"><div class="topbar"><div class="brand">Open<span>Session</span></div><div class="nav"><a href="#features">Features</a><a href="#docs">Docs</a><a href="#updates">What's New</a></div></div>
|
|
28
|
+
<section class="hero"><div class="eyebrow">Agent Execution Continuity OS</div><h1>Start once, resume anywhere.<br/>Keep every agent run connected.</h1><p class="sub">OpenSession keeps intent, actions, and artifacts connected so teams can hand off work without losing context.</p><div class="cta"><a class="btn primary" href="#docs">Quickstart</a><a class="btn secondary" href="https://github.com/online5880/opensession" target="_blank" rel="noreferrer">GitHub</a></div>
|
|
29
|
+
<div class="metrics"><div class="metric"><div class="n">Resume Anywhere</div><div class="l">single session id across environments</div></div><div class="metric"><div class="n">Intent โ Action โ Artifact</div><div class="l">structured execution chain</div></div><div class="metric"><div class="n">Handoff Packet</div><div class="l">3-minute transfer context</div></div><div class="metric"><div class="n">Guardrails</div><div class="l">risk-level automation control</div></div></div></section>
|
|
30
|
+
<section id="features"><h2>Core capabilities</h2><div class="cards"><article class="card"><h3>Resume Anywhere ID</h3><p>Continue the same session from CLI, automation, or another machine using one stable session id.</p></article><article class="card"><h3>Automation Guardrails</h3><p>Classify risky automation paths and enforce approval policies before external side effects.</p></article><article class="card"><h3>Session Health Score</h3><p>Detect likely breakage early with health indicators for interruptions and loops.</p></article></div></section>
|
|
31
|
+
<section><h2>Architecture</h2><div class="arch">OpenSession runs as an execution continuity layer above your agent/runtime stack.<div class="layers"><div class="layer"><b>Surface</b>CLI / WebUI / TUI</div><div class="layer"><b>Core</b>Sessions / Events / Handoff</div><div class="layer"><b>Policy</b>Guardrails / Approvals</div><div class="layer"><b>Storage</b>Supabase + Artifact links</div></div></div></section>
|
|
32
|
+
<section id="updates"><h2>Whatโs New</h2><div class="timeline"><div class="event"><b>v0.1.x โ continuity baseline</b><span>init/sync/start/resume/status/log stabilized with Supabase-backed runtime flow.</span></div><div class="event"><b>Viewer/TUI surfaces</b><span>read-only viewer and operational terminal flow added for daily ops visibility.</span></div><div class="event"><b>Reliability hardening</b><span>retry/idempotency and runbook-oriented operations added for unstable runtime scenarios.</span></div></div></section>
|
|
33
|
+
<section id="docs"><h2>Docs ยท One-command start</h2><div class="code"><div><span class="cmd">alias opss='npx -y @online5880/opensession'</span></div><div><span class="cmd">opss init</span> <span class="ok"># set Supabase URL/key</span></div><div><span class="cmd">opss sync --project demo</span></div><div><span class="cmd">opss start --project-key demo --actor mane</span></div><div><span class="cmd">opss status --project demo</span></div><div><span class="cmd">opss log --limit 50</span></div></div></section>
|
|
34
|
+
<footer>OpenSession ยท Execution continuity for AI agent operations ยท <a href="https://github.com/online5880/opensession" target="_blank" rel="noreferrer" style="color:#9fd6ff">GitHub</a></footer></div></body>
|
|
35
|
+
</html>
|
package/sql/schema.sql
CHANGED
|
@@ -1,27 +1,38 @@
|
|
|
1
|
-
create table if not exists projects (
|
|
2
|
-
id uuid primary key default gen_random_uuid(),
|
|
3
|
-
project_key text unique not null,
|
|
4
|
-
name text not null,
|
|
5
|
-
created_at timestamptz not null default now()
|
|
6
|
-
);
|
|
7
|
-
|
|
8
|
-
create table if not exists sessions (
|
|
9
|
-
id uuid primary key default gen_random_uuid(),
|
|
10
|
-
project_id uuid not null references projects(id) on delete cascade,
|
|
11
|
-
actor text not null,
|
|
12
|
-
status text not null default 'active',
|
|
13
|
-
started_at timestamptz not null default now(),
|
|
14
|
-
ended_at timestamptz
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
create index if not exists sessions_project_status_idx on sessions(project_id, status);
|
|
18
|
-
|
|
19
|
-
create table if not exists session_events (
|
|
20
|
-
id uuid primary key default gen_random_uuid(),
|
|
21
|
-
session_id uuid not null references sessions(id) on delete cascade,
|
|
22
|
-
type text not null,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
|
|
1
|
+
create table if not exists projects (
|
|
2
|
+
id uuid primary key default gen_random_uuid(),
|
|
3
|
+
project_key text unique not null,
|
|
4
|
+
name text not null,
|
|
5
|
+
created_at timestamptz not null default now()
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
create table if not exists sessions (
|
|
9
|
+
id uuid primary key default gen_random_uuid(),
|
|
10
|
+
project_id uuid not null references projects(id) on delete cascade,
|
|
11
|
+
actor text not null,
|
|
12
|
+
status text not null default 'active',
|
|
13
|
+
started_at timestamptz not null default now(),
|
|
14
|
+
ended_at timestamptz
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
create index if not exists sessions_project_status_idx on sessions(project_id, status);
|
|
18
|
+
|
|
19
|
+
create table if not exists session_events (
|
|
20
|
+
id uuid primary key default gen_random_uuid(),
|
|
21
|
+
session_id uuid not null references sessions(id) on delete cascade,
|
|
22
|
+
type text not null,
|
|
23
|
+
idempotency_key text,
|
|
24
|
+
payload jsonb not null default '{}'::jsonb,
|
|
25
|
+
created_at timestamptz not null default now()
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
update session_events
|
|
29
|
+
set idempotency_key = payload->>'idempotencyKey'
|
|
30
|
+
where idempotency_key is null
|
|
31
|
+
and payload ? 'idempotencyKey'
|
|
32
|
+
and nullif(payload->>'idempotencyKey', '') is not null;
|
|
33
|
+
|
|
34
|
+
create index if not exists session_events_session_created_idx on session_events(session_id, created_at);
|
|
35
|
+
create index if not exists session_events_idempotency_idx on session_events(session_id, type, idempotency_key);
|
|
36
|
+
create unique index if not exists session_events_session_type_idempotency_key_uidx
|
|
37
|
+
on session_events(session_id, type, idempotency_key)
|
|
38
|
+
where idempotency_key is not null;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
2
|
+
|
|
3
|
+
function withTimeout(ms) {
|
|
4
|
+
return AbortSignal.timeout(Number.isInteger(ms) && ms > 0 ? ms : DEFAULT_TIMEOUT_MS);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function normalizeArray(value) {
|
|
8
|
+
if (!Array.isArray(value)) {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeHeaders(value) {
|
|
15
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const output = {};
|
|
20
|
+
for (const [key, headerValue] of Object.entries(value)) {
|
|
21
|
+
if (typeof headerValue === 'string' && headerValue.trim().length > 0) {
|
|
22
|
+
output[key] = headerValue;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return output;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeWebhookTarget(target) {
|
|
29
|
+
if (!target || typeof target !== 'object') {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const url = typeof target.url === 'string' ? target.url.trim() : '';
|
|
34
|
+
if (!url) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const eventTypes = normalizeArray(target.eventTypes).filter((item) => typeof item === 'string' && item.trim().length > 0);
|
|
39
|
+
const source = typeof target.source === 'string' && target.source.trim().length > 0 ? target.source.trim() : null;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
type: 'webhook',
|
|
43
|
+
url,
|
|
44
|
+
eventTypes,
|
|
45
|
+
source,
|
|
46
|
+
headers: normalizeHeaders(target.headers),
|
|
47
|
+
timeoutMs: Number.isInteger(target.timeoutMs) && target.timeoutMs > 0 ? target.timeoutMs : DEFAULT_TIMEOUT_MS
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizeRule(rule) {
|
|
52
|
+
if (!rule || typeof rule !== 'object') {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const name = typeof rule.name === 'string' && rule.name.trim().length > 0 ? rule.name.trim() : 'unnamed-rule';
|
|
57
|
+
const when = rule.when && typeof rule.when === 'object' && !Array.isArray(rule.when) ? rule.when : {};
|
|
58
|
+
const actions = normalizeArray(rule.actions)
|
|
59
|
+
.map((action) => normalizeWebhookTarget(action))
|
|
60
|
+
.filter(Boolean);
|
|
61
|
+
|
|
62
|
+
if (actions.length === 0) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
name,
|
|
68
|
+
when: {
|
|
69
|
+
eventType: typeof when.eventType === 'string' ? when.eventType.trim() : null,
|
|
70
|
+
source: typeof when.source === 'string' ? when.source.trim() : null,
|
|
71
|
+
projectKey: typeof when.projectKey === 'string' ? when.projectKey.trim() : null
|
|
72
|
+
},
|
|
73
|
+
actions
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function shouldApplyTarget(target, envelope) {
|
|
78
|
+
if (!target) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (target.source && target.source !== envelope.source) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (target.eventTypes.length > 0 && !target.eventTypes.includes(envelope.eventType)) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function ruleMatches(rule, envelope) {
|
|
94
|
+
const { when } = rule;
|
|
95
|
+
|
|
96
|
+
if (when.eventType && when.eventType !== envelope.eventType) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (when.source && when.source !== envelope.source) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (when.projectKey && when.projectKey !== envelope.projectKey) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function postJson(url, body, options = {}) {
|
|
112
|
+
const headers = {
|
|
113
|
+
'Content-Type': 'application/json',
|
|
114
|
+
...(options.headers ?? {})
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const response = await fetch(url, {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers,
|
|
120
|
+
body: JSON.stringify(body),
|
|
121
|
+
signal: withTimeout(options.timeoutMs)
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const text = await response.text();
|
|
125
|
+
return {
|
|
126
|
+
ok: response.ok,
|
|
127
|
+
status: response.status,
|
|
128
|
+
body: text.slice(0, 400)
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function dispatchAutomation(envelope, automationConfig = {}) {
|
|
133
|
+
const rules = normalizeArray(automationConfig.rules)
|
|
134
|
+
.map((rule) => normalizeRule(rule))
|
|
135
|
+
.filter(Boolean);
|
|
136
|
+
const directTargets = normalizeArray(automationConfig.webhooks)
|
|
137
|
+
.map((target) => normalizeWebhookTarget(target))
|
|
138
|
+
.filter(Boolean)
|
|
139
|
+
.filter((target) => shouldApplyTarget(target, envelope));
|
|
140
|
+
|
|
141
|
+
const results = [];
|
|
142
|
+
|
|
143
|
+
for (const target of directTargets) {
|
|
144
|
+
try {
|
|
145
|
+
const response = await postJson(target.url, envelope, target);
|
|
146
|
+
results.push({
|
|
147
|
+
channel: 'webhook',
|
|
148
|
+
mode: 'direct',
|
|
149
|
+
destination: target.url,
|
|
150
|
+
...response
|
|
151
|
+
});
|
|
152
|
+
} catch (error) {
|
|
153
|
+
results.push({
|
|
154
|
+
channel: 'webhook',
|
|
155
|
+
mode: 'direct',
|
|
156
|
+
destination: target.url,
|
|
157
|
+
ok: false,
|
|
158
|
+
status: 0,
|
|
159
|
+
body: error instanceof Error ? error.message : String(error)
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for (const rule of rules) {
|
|
165
|
+
if (!ruleMatches(rule, envelope)) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (const action of rule.actions) {
|
|
170
|
+
try {
|
|
171
|
+
const response = await postJson(action.url, {
|
|
172
|
+
rule: rule.name,
|
|
173
|
+
event: envelope
|
|
174
|
+
}, action);
|
|
175
|
+
results.push({
|
|
176
|
+
channel: 'webhook',
|
|
177
|
+
mode: 'rule',
|
|
178
|
+
rule: rule.name,
|
|
179
|
+
destination: action.url,
|
|
180
|
+
...response
|
|
181
|
+
});
|
|
182
|
+
} catch (error) {
|
|
183
|
+
results.push({
|
|
184
|
+
channel: 'webhook',
|
|
185
|
+
mode: 'rule',
|
|
186
|
+
rule: rule.name,
|
|
187
|
+
destination: action.url,
|
|
188
|
+
ok: false,
|
|
189
|
+
status: 0,
|
|
190
|
+
body: error instanceof Error ? error.message : String(error)
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return results;
|
|
197
|
+
}
|