@mrc2204/opencode-bridge 0.1.2 → 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.en.md +73 -94
- package/README.md +73 -94
- package/dist/opencode-plugin/openclaw-bridge-callback.d.ts +5 -0
- package/dist/opencode-plugin/openclaw-bridge-callback.js +179 -0
- package/dist/{chunk-LCJRXKI3.js → src/chunk-OVQ5X54C.js} +4 -3
- package/dist/src/chunk-TDVN5AFB.js +36 -0
- package/dist/{index.js → src/index.js} +480 -14
- package/dist/{observability.d.ts → src/observability.d.ts} +1 -0
- package/dist/{observability.js → src/observability.js} +1 -1
- package/dist/src/shared-contracts.d.ts +33 -0
- package/dist/src/shared-contracts.js +10 -0
- package/openclaw.plugin.json +2 -2
- package/opencode-plugin/README.md +25 -0
- package/opencode-plugin/openclaw-bridge-callback.ts +186 -0
- package/package.json +17 -9
- package/scripts/install-bridge.mjs +60 -0
- package/scripts/materialize-opencode-plugin.mjs +75 -0
- package/skills/opencode-orchestration/SKILL.md +76 -66
- package/src/shared-contracts.ts +58 -0
- /package/dist/{index.d.ts → src/index.d.ts} +0 -0
package/README.en.md
CHANGED
|
@@ -1,115 +1,94 @@
|
|
|
1
1
|
# @mrc2204/opencode-bridge
|
|
2
2
|
|
|
3
|
-
OpenClaw ↔ OpenCode bridge
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
OpenClaw ↔ OpenCode bridge for hybrid execution, callback orchestration, and multi-project-safe runtime control.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
`opencode-bridge` connects two runtime surfaces:
|
|
7
|
+
- **OpenClaw side**: project-aware routing, run state, serve management, observability, and callback policy
|
|
8
|
+
- **OpenCode side**: event-driven callback plugin for terminal session lifecycle events
|
|
9
|
+
|
|
10
|
+
It is designed for teams that want:
|
|
11
|
+
- project-aware OpenCode execution
|
|
12
|
+
- callback flow back into OpenClaw via `/hooks/agent`
|
|
13
|
+
- installable OpenCode-side plugin artifacts
|
|
14
|
+
- multi-project-safe runtime boundaries
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
### 1) OpenClaw side
|
|
15
18
|
```bash
|
|
16
19
|
openclaw plugins install @mrc2204/openclaw-opencode-bridge
|
|
17
20
|
```
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
├── openclaw.plugin.json
|
|
23
|
-
├── package.json
|
|
24
|
-
├── tsconfig.json
|
|
25
|
-
├── src/
|
|
26
|
-
│ ├── index.ts
|
|
27
|
-
│ ├── observability.ts
|
|
28
|
-
│ └── types.ts
|
|
29
|
-
├── dist/ # generated by npm run build
|
|
30
|
-
├── test/
|
|
31
|
-
│ ├── run-tests.ts
|
|
32
|
-
│ └── observability.test.ts
|
|
33
|
-
├── skills/
|
|
34
|
-
│ └── opencode-orchestration/
|
|
35
|
-
│ └── SKILL.md
|
|
36
|
-
├── README.md
|
|
37
|
-
└── README.en.md
|
|
22
|
+
### 2) OpenCode side — project install
|
|
23
|
+
```bash
|
|
24
|
+
npm run materialize:opencode-plugin:project
|
|
38
25
|
```
|
|
39
26
|
|
|
40
|
-
|
|
41
|
-
- `opencode_status`
|
|
42
|
-
- `opencode_resolve_project`
|
|
43
|
-
- `opencode_build_envelope`
|
|
44
|
-
- `opencode_check_hook_policy`
|
|
45
|
-
- `opencode_evaluate_lifecycle`
|
|
46
|
-
- `opencode_run_status`
|
|
47
|
-
- `opencode_run_events`
|
|
48
|
-
- `opencode_session_tail`
|
|
49
|
-
- `opencode_serve_spawn`
|
|
50
|
-
- `opencode_registry_get`
|
|
51
|
-
- `opencode_registry_upsert`
|
|
52
|
-
- `opencode_registry_cleanup`
|
|
53
|
-
- `opencode_serve_shutdown`
|
|
54
|
-
- `opencode_serve_idle_check`
|
|
55
|
-
|
|
56
|
-
## Current assumptions
|
|
57
|
-
- `1 project = 1 opencode serve instance`
|
|
58
|
-
- callback primary = `/hooks/agent`
|
|
59
|
-
- `sessionKey` convention = `hook:opencode:<agentId>:<taskId>`
|
|
60
|
-
- `opencode_server_url` is required in practical routing envelopes
|
|
61
|
-
|
|
62
|
-
## Build & test
|
|
27
|
+
### 3) OpenCode side — global install
|
|
63
28
|
```bash
|
|
64
|
-
npm
|
|
65
|
-
npm run build
|
|
66
|
-
npm run typecheck
|
|
67
|
-
npm run test
|
|
29
|
+
npm run materialize:opencode-plugin:global
|
|
68
30
|
```
|
|
69
31
|
|
|
70
|
-
|
|
71
|
-
- `main`: `./dist/index.js`
|
|
72
|
-
- `types`: `./dist/index.d.ts`
|
|
73
|
-
- `openclaw.extensions`: `./dist/index.js`
|
|
32
|
+
Global mode auto-patches `~/.config/opencode/opencode.json` when it exists.
|
|
74
33
|
|
|
75
|
-
|
|
34
|
+
### 4) One-command install
|
|
35
|
+
Project mode:
|
|
76
36
|
```bash
|
|
77
|
-
npm run
|
|
78
|
-
npm run test
|
|
79
|
-
npm pack --dry-run
|
|
80
|
-
# if all good:
|
|
81
|
-
# npm publish
|
|
37
|
+
npm run install:bridge:project
|
|
82
38
|
```
|
|
83
39
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Plugin-owned config path:
|
|
88
|
-
```text
|
|
89
|
-
~/.openclaw/opencode-bridge/config.json
|
|
40
|
+
Global mode:
|
|
41
|
+
```bash
|
|
42
|
+
npm run install:bridge:global
|
|
90
43
|
```
|
|
91
44
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
```
|
|
45
|
+
## Required callback environment
|
|
46
|
+
The OpenCode-side callback plugin needs:
|
|
47
|
+
- `OPENCLAW_HOOK_BASE_URL`
|
|
48
|
+
- `OPENCLAW_HOOK_TOKEN`
|
|
49
|
+
|
|
50
|
+
Optional:
|
|
51
|
+
- `OPENCLAW_BRIDGE_AUDIT_DIR`
|
|
52
|
+
- `OPENCLAW_BRIDGE_OPENCLAW_AUDIT_PATH`
|
|
53
|
+
|
|
54
|
+
## Runtime model
|
|
55
|
+
### Hybrid execution strategy
|
|
56
|
+
`opencode-bridge` supports two practical execution lanes:
|
|
57
|
+
- **CLI-direct**: lightweight execution for simpler tasks
|
|
58
|
+
- **serve/plugin mode**: canonical path for callback, observability, and event-driven lifecycle handling
|
|
108
59
|
|
|
109
|
-
|
|
60
|
+
### Multi-project safety
|
|
61
|
+
Current contracts assume:
|
|
62
|
+
- `1 project = 1 correctly bound OpenCode serve instance`
|
|
63
|
+
- serve reuse is allowed only when runtime introspection confirms the expected `repo_root`
|
|
64
|
+
- bridge session tags must preserve callback identity per run/session
|
|
65
|
+
|
|
66
|
+
## Build and verify
|
|
110
67
|
```bash
|
|
111
|
-
|
|
68
|
+
npm install
|
|
69
|
+
npm run build
|
|
70
|
+
npm test -- --runInBand
|
|
112
71
|
```
|
|
113
72
|
|
|
114
|
-
##
|
|
115
|
-
|
|
73
|
+
## Package / artifact layout
|
|
74
|
+
```text
|
|
75
|
+
openclaw-opencode-bridge/
|
|
76
|
+
├── src/ # OpenClaw-side runtime
|
|
77
|
+
├── opencode-plugin/ # canonical OpenCode-side plugin source
|
|
78
|
+
├── scripts/ # materialize/install helpers
|
|
79
|
+
├── dist/ # built artifacts
|
|
80
|
+
├── docs/ # install / architecture / contracts
|
|
81
|
+
└── skills/ # bridge-related skills/docs
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## More docs
|
|
85
|
+
- `docs/install/quick-start-2026-03-22.md`
|
|
86
|
+
- `docs/install/production-ish-install-2026-03-22.md`
|
|
87
|
+
- `docs/contracts/multi-project-contract-draft-2026-03-22.md`
|
|
88
|
+
- `docs/architecture/hybrid-execution-strategy-2026-03-22.md`
|
|
89
|
+
|
|
90
|
+
Developer-only local debug notes are intentionally kept out of this README. See:
|
|
91
|
+
- `docs/install/developer-local-debug-2026-03-22.md`
|
|
92
|
+
|
|
93
|
+
## Status
|
|
94
|
+
Current status: functional and productized enough for real use, with hardening history reflected in tests and docs.
|
package/README.md
CHANGED
|
@@ -2,116 +2,95 @@
|
|
|
2
2
|
|
|
3
3
|
> English README: [README.en.md](./README.en.md)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
5
|
+
`opencode-bridge` là cầu nối giữa OpenClaw và OpenCode theo hướng hybrid execution, callback orchestration và runtime control an toàn cho nhiều project.
|
|
6
|
+
|
|
7
|
+
## Package này làm gì
|
|
8
|
+
Package này nối 2 phía runtime:
|
|
9
|
+
- **OpenClaw side**: routing theo project, run state, serve management, observability, callback policy
|
|
10
|
+
- **OpenCode side**: plugin callback bắt event terminal bên trong OpenCode runtime
|
|
11
|
+
|
|
12
|
+
Mục tiêu thực dụng:
|
|
13
|
+
- execution theo project rõ ràng
|
|
14
|
+
- callback quay về OpenClaw qua `/hooks/agent`
|
|
15
|
+
- artifact OpenCode-side cài được theo project hoặc global
|
|
16
|
+
- multi-project-safe boundary
|
|
17
|
+
|
|
18
|
+
## Cài đặt
|
|
19
|
+
### 1) OpenClaw side
|
|
17
20
|
```bash
|
|
18
21
|
openclaw plugins install @mrc2204/openclaw-opencode-bridge
|
|
19
22
|
```
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
├── openclaw.plugin.json
|
|
25
|
-
├── package.json
|
|
26
|
-
├── tsconfig.json
|
|
27
|
-
├── src/
|
|
28
|
-
│ ├── index.ts
|
|
29
|
-
│ ├── observability.ts
|
|
30
|
-
│ └── types.ts
|
|
31
|
-
├── dist/ # artifact tạo bởi npm run build
|
|
32
|
-
├── test/
|
|
33
|
-
│ ├── run-tests.ts
|
|
34
|
-
│ └── observability.test.ts
|
|
35
|
-
├── skills/
|
|
36
|
-
│ └── opencode-orchestration/
|
|
37
|
-
│ └── SKILL.md
|
|
38
|
-
├── README.md
|
|
39
|
-
└── README.en.md
|
|
24
|
+
### 2) OpenCode side — cài theo project
|
|
25
|
+
```bash
|
|
26
|
+
npm run materialize:opencode-plugin:project
|
|
40
27
|
```
|
|
41
28
|
|
|
42
|
-
|
|
43
|
-
- `opencode_status`
|
|
44
|
-
- `opencode_resolve_project`
|
|
45
|
-
- `opencode_build_envelope`
|
|
46
|
-
- `opencode_check_hook_policy`
|
|
47
|
-
- `opencode_evaluate_lifecycle`
|
|
48
|
-
- `opencode_run_status`
|
|
49
|
-
- `opencode_run_events`
|
|
50
|
-
- `opencode_session_tail`
|
|
51
|
-
- `opencode_serve_spawn`
|
|
52
|
-
- `opencode_registry_get`
|
|
53
|
-
- `opencode_registry_upsert`
|
|
54
|
-
- `opencode_registry_cleanup`
|
|
55
|
-
- `opencode_serve_shutdown`
|
|
56
|
-
- `opencode_serve_idle_check`
|
|
57
|
-
|
|
58
|
-
## Assumptions hiện tại
|
|
59
|
-
- `1 project = 1 opencode serve instance`
|
|
60
|
-
- callback primary = `/hooks/agent`
|
|
61
|
-
- `sessionKey` convention = `hook:opencode:<agentId>:<taskId>`
|
|
62
|
-
- `opencode_server_url` là field bắt buộc trong envelope routing thực tế
|
|
63
|
-
|
|
64
|
-
## Build/Test
|
|
29
|
+
### 3) OpenCode side — cài global
|
|
65
30
|
```bash
|
|
66
|
-
npm
|
|
67
|
-
npm run build
|
|
68
|
-
npm run typecheck
|
|
69
|
-
npm run test
|
|
31
|
+
npm run materialize:opencode-plugin:global
|
|
70
32
|
```
|
|
71
33
|
|
|
72
|
-
|
|
73
|
-
- `main`: `./dist/index.js`
|
|
74
|
-
- `types`: `./dist/index.d.ts`
|
|
75
|
-
- `openclaw.extensions`: `./dist/index.js`
|
|
34
|
+
Global mode sẽ tự patch `~/.config/opencode/opencode.json` khi file này tồn tại.
|
|
76
35
|
|
|
77
|
-
|
|
36
|
+
### 4) Một lệnh cài
|
|
37
|
+
Project mode:
|
|
78
38
|
```bash
|
|
79
|
-
npm run
|
|
80
|
-
npm run test
|
|
81
|
-
npm pack --dry-run
|
|
82
|
-
# nếu OK:
|
|
83
|
-
# npm publish
|
|
39
|
+
npm run install:bridge:project
|
|
84
40
|
```
|
|
85
41
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Plugin dùng plugin-owned config tại:
|
|
90
|
-
```text
|
|
91
|
-
~/.openclaw/opencode-bridge/config.json
|
|
42
|
+
Global mode:
|
|
43
|
+
```bash
|
|
44
|
+
npm run install:bridge:global
|
|
92
45
|
```
|
|
93
46
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
```
|
|
47
|
+
## Environment callback tối thiểu
|
|
48
|
+
OpenCode-side callback plugin cần:
|
|
49
|
+
- `OPENCLAW_HOOK_BASE_URL`
|
|
50
|
+
- `OPENCLAW_HOOK_TOKEN`
|
|
51
|
+
|
|
52
|
+
Optional:
|
|
53
|
+
- `OPENCLAW_BRIDGE_AUDIT_DIR`
|
|
54
|
+
- `OPENCLAW_BRIDGE_OPENCLAW_AUDIT_PATH`
|
|
55
|
+
|
|
56
|
+
## Mô hình runtime
|
|
57
|
+
### Hybrid execution strategy
|
|
58
|
+
`opencode-bridge` hỗ trợ 2 lane thực dụng:
|
|
59
|
+
- **CLI-direct**: execution nhẹ cho task đơn giản
|
|
60
|
+
- **serve/plugin mode**: đường canonical cho callback, observability và event-driven lifecycle
|
|
110
61
|
|
|
111
|
-
|
|
62
|
+
### Multi-project safety
|
|
63
|
+
Contract hiện tại giả định:
|
|
64
|
+
- `1 project = 1 OpenCode serve bind đúng repo`
|
|
65
|
+
- chỉ reuse serve khi runtime introspection xác nhận đúng `repo_root`
|
|
66
|
+
- session tag phải giữ đủ callback identity theo run/session
|
|
67
|
+
|
|
68
|
+
## Build và verify
|
|
112
69
|
```bash
|
|
113
|
-
|
|
70
|
+
npm install
|
|
71
|
+
npm run build
|
|
72
|
+
npm test -- --runInBand
|
|
114
73
|
```
|
|
115
74
|
|
|
116
|
-
##
|
|
117
|
-
|
|
75
|
+
## Cấu trúc artifact
|
|
76
|
+
```text
|
|
77
|
+
openclaw-opencode-bridge/
|
|
78
|
+
├── src/ # OpenClaw-side runtime
|
|
79
|
+
├── opencode-plugin/ # canonical OpenCode-side plugin source
|
|
80
|
+
├── scripts/ # materialize/install helpers
|
|
81
|
+
├── dist/ # built artifacts
|
|
82
|
+
├── docs/ # install / architecture / contracts
|
|
83
|
+
└── skills/ # bridge-related skills/docs
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Tài liệu liên quan
|
|
87
|
+
- `docs/install/quick-start-2026-03-22.md`
|
|
88
|
+
- `docs/install/production-ish-install-2026-03-22.md`
|
|
89
|
+
- `docs/contracts/multi-project-contract-draft-2026-03-22.md`
|
|
90
|
+
- `docs/architecture/hybrid-execution-strategy-2026-03-22.md`
|
|
91
|
+
|
|
92
|
+
Ghi chú: local debug/dev notes được tách khỏi README public-facing. Xem:
|
|
93
|
+
- `docs/install/developer-local-debug-2026-03-22.md`
|
|
94
|
+
|
|
95
|
+
## Trạng thái
|
|
96
|
+
Hiện tại package đã ở mức functional + productized usable, với hardening/test/docs đã đi khá xa.
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// opencode-plugin/openclaw-bridge-callback.ts
|
|
2
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
|
|
5
|
+
// src/shared-contracts.ts
|
|
6
|
+
function parseTaggedSessionTitle(title) {
|
|
7
|
+
if (!title || !title.trim()) return null;
|
|
8
|
+
const tags = {};
|
|
9
|
+
for (const token of title.split(/\s+/)) {
|
|
10
|
+
const idx = token.indexOf("=");
|
|
11
|
+
if (idx <= 0) continue;
|
|
12
|
+
const key = token.slice(0, idx).trim();
|
|
13
|
+
const raw = token.slice(idx + 1).trim();
|
|
14
|
+
if (!key || !raw) continue;
|
|
15
|
+
tags[key] = raw;
|
|
16
|
+
}
|
|
17
|
+
return Object.keys(tags).length > 0 ? tags : null;
|
|
18
|
+
}
|
|
19
|
+
function buildPluginCallbackDedupeKey(input) {
|
|
20
|
+
return `${input.sessionId || "no-session"}|${input.runId || "no-run"}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// opencode-plugin/openclaw-bridge-callback.ts
|
|
24
|
+
function asString(value) {
|
|
25
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
26
|
+
}
|
|
27
|
+
function ensureAuditDir(directory) {
|
|
28
|
+
mkdirSync(directory, { recursive: true });
|
|
29
|
+
return directory;
|
|
30
|
+
}
|
|
31
|
+
function getAuditPath(directory) {
|
|
32
|
+
const auditDir = asString(process.env.OPENCLAW_BRIDGE_AUDIT_DIR) || join(directory, ".opencode");
|
|
33
|
+
ensureAuditDir(auditDir);
|
|
34
|
+
return join(auditDir, "bridge-callback-audit.jsonl");
|
|
35
|
+
}
|
|
36
|
+
function appendAudit(directory, record) {
|
|
37
|
+
const path = getAuditPath(directory);
|
|
38
|
+
appendFileSync(path, JSON.stringify({ ...record, created_at: (/* @__PURE__ */ new Date()).toISOString() }) + "\n", "utf8");
|
|
39
|
+
}
|
|
40
|
+
function getOpenClawAuditPath() {
|
|
41
|
+
const explicit = asString(process.env.OPENCLAW_BRIDGE_OPENCLAW_AUDIT_PATH);
|
|
42
|
+
if (explicit) {
|
|
43
|
+
ensureAuditDir(join(explicit, ".."));
|
|
44
|
+
return explicit;
|
|
45
|
+
}
|
|
46
|
+
const home = asString(process.env.HOME);
|
|
47
|
+
if (!home) return null;
|
|
48
|
+
const auditDir = join(home, ".openclaw", "opencode-bridge", "audit");
|
|
49
|
+
ensureAuditDir(auditDir);
|
|
50
|
+
return join(auditDir, "callbacks.jsonl");
|
|
51
|
+
}
|
|
52
|
+
function appendOpenClawAudit(record) {
|
|
53
|
+
const path = getOpenClawAuditPath();
|
|
54
|
+
if (!path) return;
|
|
55
|
+
appendFileSync(path, JSON.stringify({ ...record, createdAt: (/* @__PURE__ */ new Date()).toISOString() }) + "\n", "utf8");
|
|
56
|
+
}
|
|
57
|
+
function buildCallbackPayload(tags, eventType) {
|
|
58
|
+
const agentId = tags.requested || tags.requested_agent_id;
|
|
59
|
+
const sessionKey = tags.callbackSession || tags.callback_target_session_key;
|
|
60
|
+
const sessionId = tags.callbackSessionId || tags.callback_target_session_id;
|
|
61
|
+
if (!agentId || !sessionKey) return null;
|
|
62
|
+
return {
|
|
63
|
+
message: `OpenCode plugin event=${eventType} run=${tags.runId || tags.run_id || "unknown"} task=${tags.taskId || tags.task_id || "unknown"}`,
|
|
64
|
+
name: "OpenCode",
|
|
65
|
+
agentId,
|
|
66
|
+
sessionKey,
|
|
67
|
+
...sessionId ? { sessionId } : {},
|
|
68
|
+
wakeMode: "now",
|
|
69
|
+
deliver: false
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async function postCallback(directory, payload, meta) {
|
|
73
|
+
const hookBaseUrl = asString(process.env.OPENCLAW_HOOK_BASE_URL);
|
|
74
|
+
const hookToken = asString(process.env.OPENCLAW_HOOK_TOKEN);
|
|
75
|
+
if (!hookBaseUrl || !hookToken) {
|
|
76
|
+
appendAudit(directory, { ok: false, status: 0, reason: "missing_hook_env", payload, meta });
|
|
77
|
+
appendOpenClawAudit({
|
|
78
|
+
taskId: meta?.taskId,
|
|
79
|
+
runId: meta?.runId,
|
|
80
|
+
agentId: payload?.agentId,
|
|
81
|
+
requestedAgentId: meta?.requestedAgentId,
|
|
82
|
+
resolvedAgentId: meta?.resolvedAgentId,
|
|
83
|
+
sessionKey: void 0,
|
|
84
|
+
callbackTargetSessionKey: meta?.callbackTargetSessionKey,
|
|
85
|
+
callbackTargetSessionId: meta?.callbackTargetSessionId,
|
|
86
|
+
event: meta?.eventType,
|
|
87
|
+
callbackStatus: 0,
|
|
88
|
+
callbackOk: false,
|
|
89
|
+
callbackBody: "missing_hook_env"
|
|
90
|
+
});
|
|
91
|
+
return { ok: false, status: 0, reason: "missing_hook_env" };
|
|
92
|
+
}
|
|
93
|
+
const response = await fetch(`${hookBaseUrl.replace(/\/$/, "")}/hooks/agent`, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: {
|
|
96
|
+
Authorization: `Bearer ${hookToken}`,
|
|
97
|
+
"Content-Type": "application/json"
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify(payload)
|
|
100
|
+
});
|
|
101
|
+
const text = await response.text();
|
|
102
|
+
appendAudit(directory, { ok: response.ok, status: response.status, body: text, payload, meta });
|
|
103
|
+
const openClawAuditRecord = {
|
|
104
|
+
taskId: meta?.taskId,
|
|
105
|
+
runId: meta?.runId,
|
|
106
|
+
agentId: payload?.agentId,
|
|
107
|
+
requestedAgentId: meta?.requestedAgentId,
|
|
108
|
+
resolvedAgentId: meta?.resolvedAgentId,
|
|
109
|
+
sessionKey: void 0,
|
|
110
|
+
callbackTargetSessionKey: meta?.callbackTargetSessionKey,
|
|
111
|
+
callbackTargetSessionId: meta?.callbackTargetSessionId,
|
|
112
|
+
event: meta?.eventType,
|
|
113
|
+
callbackStatus: response.status,
|
|
114
|
+
callbackOk: response.ok,
|
|
115
|
+
callbackBody: text
|
|
116
|
+
};
|
|
117
|
+
appendAudit(directory, { phase: "openclaw_audit_mirror", record: openClawAuditRecord });
|
|
118
|
+
appendOpenClawAudit(openClawAuditRecord);
|
|
119
|
+
return { ok: response.ok, status: response.status, body: text };
|
|
120
|
+
}
|
|
121
|
+
var callbackDedupe = /* @__PURE__ */ new Set();
|
|
122
|
+
var sessionTagCache = /* @__PURE__ */ new Map();
|
|
123
|
+
function readSessionId(event) {
|
|
124
|
+
return asString(event?.session?.id) || asString(event?.session?.sessionID) || asString(event?.data?.session?.id) || asString(event?.data?.sessionID) || asString(event?.properties?.sessionID) || asString(event?.properties?.info?.sessionID) || asString(event?.properties?.info?.id) || asString(event?.payload?.sessionID);
|
|
125
|
+
}
|
|
126
|
+
function isTerminalEvent(_event, type) {
|
|
127
|
+
return type === "session.idle" || type === "session.error";
|
|
128
|
+
}
|
|
129
|
+
var OpenClawBridgeCallbackPlugin = async ({ client, directory }) => {
|
|
130
|
+
await client.app.log({
|
|
131
|
+
body: {
|
|
132
|
+
service: "openclaw-bridge-callback",
|
|
133
|
+
level: "info",
|
|
134
|
+
message: "OpenClaw bridge callback plugin initialized",
|
|
135
|
+
extra: { directory }
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
event: async ({ event }) => {
|
|
140
|
+
const type = asString(event?.type) || "unknown";
|
|
141
|
+
const sessionId = readSessionId(event);
|
|
142
|
+
const title = asString(event?.session?.title) || asString(event?.data?.session?.title) || asString(event?.data?.title) || asString(event?.properties?.info?.title) || asString(event?.properties?.title) || asString(event?.payload?.session?.title) || asString(event?.payload?.title);
|
|
143
|
+
const parsedTags = parseTaggedSessionTitle(title);
|
|
144
|
+
if (sessionId && parsedTags && (parsedTags.callbackSession || parsedTags.callback_target_session_key)) {
|
|
145
|
+
sessionTagCache.set(sessionId, parsedTags);
|
|
146
|
+
}
|
|
147
|
+
const tags = parsedTags || (sessionId ? sessionTagCache.get(sessionId) || null : null);
|
|
148
|
+
appendAudit(directory, { phase: "event_seen", event_type: type, session_id: sessionId, title, tags, raw: event });
|
|
149
|
+
if (!tags || !(tags.callbackSession || tags.callback_target_session_key)) return;
|
|
150
|
+
if (!isTerminalEvent(event, type)) return;
|
|
151
|
+
const dedupeKey = buildPluginCallbackDedupeKey({ sessionId, runId: tags.runId || tags.run_id });
|
|
152
|
+
if (callbackDedupe.has(dedupeKey)) {
|
|
153
|
+
appendAudit(directory, { phase: "deduped", event_type: type, session_id: sessionId, dedupeKey, tags });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
callbackDedupe.add(dedupeKey);
|
|
157
|
+
const payload = buildCallbackPayload(tags, type);
|
|
158
|
+
if (!payload) {
|
|
159
|
+
appendAudit(directory, { phase: "skipped_no_payload", event_type: type, session_id: sessionId, tags });
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
await postCallback(directory, payload, {
|
|
163
|
+
eventType: type,
|
|
164
|
+
sessionId,
|
|
165
|
+
runId: tags.runId || tags.run_id,
|
|
166
|
+
taskId: tags.taskId || tags.task_id,
|
|
167
|
+
requestedAgentId: tags.requested || tags.requested_agent_id,
|
|
168
|
+
resolvedAgentId: tags.resolved || tags.resolved_agent_id,
|
|
169
|
+
callbackTargetSessionKey: tags.callbackSession || tags.callback_target_session_key,
|
|
170
|
+
callbackTargetSessionId: tags.callbackSessionId || tags.callback_target_session_id
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
var openclaw_bridge_callback_default = OpenClawBridgeCallbackPlugin;
|
|
176
|
+
export {
|
|
177
|
+
OpenClawBridgeCallbackPlugin,
|
|
178
|
+
openclaw_bridge_callback_default as default
|
|
179
|
+
};
|
|
@@ -215,7 +215,7 @@ function scoreSessionCandidate(candidate, ctx) {
|
|
|
215
215
|
return score;
|
|
216
216
|
}
|
|
217
217
|
function summarizeLifecycle(events = []) {
|
|
218
|
-
let
|
|
218
|
+
let currentState = null;
|
|
219
219
|
let last_event_kind = null;
|
|
220
220
|
let last_event_at = null;
|
|
221
221
|
const files_changed = /* @__PURE__ */ new Set();
|
|
@@ -223,7 +223,7 @@ function summarizeLifecycle(events = []) {
|
|
|
223
223
|
const blockers = /* @__PURE__ */ new Set();
|
|
224
224
|
let completion_summary = null;
|
|
225
225
|
for (const item of events) {
|
|
226
|
-
if (item.lifecycleState)
|
|
226
|
+
if (item.lifecycleState) currentState = item.lifecycleState;
|
|
227
227
|
if (item.kind) last_event_kind = item.kind;
|
|
228
228
|
if (item.timestamp) last_event_at = item.timestamp;
|
|
229
229
|
for (const file of item.filesChanged || []) files_changed.add(file);
|
|
@@ -232,7 +232,8 @@ function summarizeLifecycle(events = []) {
|
|
|
232
232
|
if (item.completionSummary) completion_summary = item.completionSummary;
|
|
233
233
|
}
|
|
234
234
|
return {
|
|
235
|
-
|
|
235
|
+
currentState,
|
|
236
|
+
current_state: currentState,
|
|
236
237
|
last_event_kind,
|
|
237
238
|
last_event_at,
|
|
238
239
|
files_changed: [...files_changed],
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/shared-contracts.ts
|
|
2
|
+
function buildTaggedSessionTitle(fields) {
|
|
3
|
+
return [
|
|
4
|
+
`${fields.taskId}`,
|
|
5
|
+
`runId=${fields.runId}`,
|
|
6
|
+
`taskId=${fields.taskId}`,
|
|
7
|
+
`requested=${fields.requested}`,
|
|
8
|
+
`resolved=${fields.resolved}`,
|
|
9
|
+
`callbackSession=${fields.callbackSession}`,
|
|
10
|
+
...fields.callbackSessionId ? [`callbackSessionId=${fields.callbackSessionId}`] : [],
|
|
11
|
+
...fields.projectId ? [`projectId=${fields.projectId}`] : [],
|
|
12
|
+
...fields.repoRoot ? [`repoRoot=${fields.repoRoot}`] : []
|
|
13
|
+
].join(" ");
|
|
14
|
+
}
|
|
15
|
+
function parseTaggedSessionTitle(title) {
|
|
16
|
+
if (!title || !title.trim()) return null;
|
|
17
|
+
const tags = {};
|
|
18
|
+
for (const token of title.split(/\s+/)) {
|
|
19
|
+
const idx = token.indexOf("=");
|
|
20
|
+
if (idx <= 0) continue;
|
|
21
|
+
const key = token.slice(0, idx).trim();
|
|
22
|
+
const raw = token.slice(idx + 1).trim();
|
|
23
|
+
if (!key || !raw) continue;
|
|
24
|
+
tags[key] = raw;
|
|
25
|
+
}
|
|
26
|
+
return Object.keys(tags).length > 0 ? tags : null;
|
|
27
|
+
}
|
|
28
|
+
function buildPluginCallbackDedupeKey(input) {
|
|
29
|
+
return `${input.sessionId || "no-session"}|${input.runId || "no-run"}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
buildTaggedSessionTitle,
|
|
34
|
+
parseTaggedSessionTitle,
|
|
35
|
+
buildPluginCallbackDedupeKey
|
|
36
|
+
};
|