0xray 4.0.20 → 4.0.22
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/.grok-plugin/plugin.json +1 -1
- package/.opencode/skills/hermes-agent/SKILL.md +1 -1
- package/.opencode/skills/orchestrator/SKILL.md +23 -1
- package/.xray/features.json +1 -1
- package/AGENTS-consumer.md +15 -15
- package/AGENTS.md +12 -12
- package/README.md +40 -40
- package/SKILLS.md +4 -4
- package/dist/AGENTS.md +12 -12
- package/dist/CHANGELOG.md +91 -8
- package/dist/README.md +40 -40
- package/dist/SKILLS.md +4 -4
- package/dist/cli/commands/validate.d.ts +1 -1
- package/dist/cli/commands/validate.js +7 -1
- package/dist/governance/governance-core.d.ts +2 -1
- package/dist/governance/governance-core.js +20 -11
- package/dist/governance/governance-service.js +9 -7
- package/dist/inference/inference-cycle.js +22 -5
- package/dist/integrations/cursor/README.md +7 -6
- package/dist/integrations/cursor/hooks/after-file-edit.sh +3 -0
- package/dist/integrations/cursor/hooks/before-read-file.sh +3 -0
- package/dist/integrations/cursor/hooks/before-shell-execution.sh +3 -0
- package/dist/integrations/cursor/hooks/cursor-hook-utils.js +112 -12
- package/dist/integrations/cursor/hooks/cursor-usage-receipt.js +3 -0
- package/dist/integrations/cursor/hooks/hooks.json +13 -3
- package/dist/integrations/cursor/hooks/pre-compact.js +9 -0
- package/dist/integrations/cursor/hooks/pre-compact.sh +3 -0
- package/dist/integrations/cursor/hooks/pre-tool-use.js +17 -12
- package/dist/integrations/cursor/hooks/pre-tool-use.sh +3 -0
- package/dist/integrations/cursor/hooks/xray-cloud-hook.sh +63 -0
- package/dist/integrations/grok/hooks/grok-hook-utils.js +3 -2
- package/dist/integrations/hooks/station-hook-runtime.cjs +310 -98
- package/dist/integrations/hooks/station-hook-runtime.mjs +10 -0
- package/dist/integrations/hooks/station-memory-ingest.mjs +4 -3
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/memory-routing/provider-loader.d.ts +3 -1
- package/dist/memory-routing/provider-loader.js +3 -1
- package/dist/nucleus/suit-temperament.d.ts +0 -5
- package/dist/nucleus/suit-temperament.js +13 -1
- package/dist/nucleus/thin-dispatch.js +11 -2
- package/dist/processors/implementations/agents-md-validation-processor.js +1 -1
- package/dist/skills/hermes-agent/SKILL.md +1 -1
- package/dist/skills/orchestrator/SKILL.md +23 -1
- package/llms.txt +1 -1
- package/package.json +3 -3
- package/scripts/foundry/CHANGELOG.md +2 -0
- package/scripts/foundry/README.md +4 -4
- package/scripts/foundry/assert-packed-dist-cli.mjs +4 -0
- package/scripts/foundry/cli.mjs +1 -1
- package/scripts/foundry/inspect.mjs +160 -5
- package/scripts/foundry/plant/skills/inspect/SKILL.md +2 -0
- package/scripts/foundry/plant/skills/mill/SKILL.md +1 -1
- package/scripts/foundry/validate-release-docs.mjs +48 -0
- package/scripts/foundry/version-manager.mjs +127 -7
- package/scripts/mjs/test-consumer-readiness.mjs +10 -3
- package/scripts/node/install-bridges.cjs +256 -10
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/skills/hermes-agent/SKILL.md +1 -1
- package/src/skills/orchestrator/SKILL.md +23 -1
- package/vendor/@0xray/repertoire/data/stack-overlay.json +154 -1
- package/vendor/@0xray/repertoire/dist/RepertoireService.js +7 -6
- package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js +10 -11
- package/vendor/@0xray/repertoire/dist/paths.js +66 -4
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js +54 -14
- package/xray/agents_template.md +10 -5
- package/xray/features.json +1 -1
- package/dist/mcps/shared/prompt-security-validator.d.ts +0 -44
- package/dist/mcps/shared/prompt-security-validator.js +0 -148
- package/dist/mcps/shared/security-scanner.d.ts +0 -119
- package/dist/mcps/shared/security-scanner.js +0 -429
- package/dist/scripts/activate-kernel-pipeline.d.ts +0 -7
- package/dist/scripts/activate-kernel-pipeline.js +0 -101
- package/src/mcps/shared/prompt-security-validator.ts +0 -199
- package/src/mcps/shared/security-scanner.ts +0 -599
package/dist/README.md
CHANGED
|
@@ -14,31 +14,31 @@ Not a catalog of agents. The product is the **skeleton you wear**.
|
|
|
14
14
|
- **Always on** — Codex 11 / 29 / 69, destructive shell, no new MCP/skill/handler surface
|
|
15
15
|
- **Temperament** — `frontier` | `guided` | `strict` | `auto` — how loud the engine is, not whether governance exists
|
|
16
16
|
- **Wear** — Grok, OpenCode, Hermes, OpenClaw, plus Cursor project hooks. One SSOT gate
|
|
17
|
-
- **Muscle** — Repertoire
|
|
17
|
+
- **Muscle** — **Repertoire is preferred.** Vendored 0.2.5 ships **on**. Dest is named laws, not hangar `repo-*` or git slugs. Station is the compact-survival ticket — not a substitute for Repertoire.
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
npm install 0xray
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
Consumer `npm install 0xray` auto-wires four chat bridges, Cursor project hooks, and seven MCP servers. Repertoire **0.2**
|
|
23
|
+
Consumer `npm install 0xray` auto-wires four chat bridges, Cursor project hooks, and seven MCP servers. **Repertoire is preferred** — vendored **0.2.5** ships on: factory + stack laws, in-process routing, extra host MCP `repertoire`. Not an eighth `xray-*` server. Dest is a judgment index. Explicit opt-out only: `"enabled": false, "provider": "repertoire"`. Do not pin 0.1.8.
|
|
24
24
|
|
|
25
25
|
## Quick Start
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
npm install 0xray
|
|
29
|
-
npx 0xray status
|
|
30
|
-
npx 0xray setup
|
|
31
|
-
npx @0xray/foundry inspect --skip-live
|
|
28
|
+
npm install 0xray # postinstall: mill+inspect plant + 4 bridges + 7 MCPs + AGENTS.md + .mcp.json
|
|
29
|
+
npx 0xray status # verify install
|
|
30
|
+
npx 0xray setup # optional: symlinks, hook extras
|
|
31
|
+
npx @0xray/foundry inspect --skip-live # mill receipt (chat is not a receipt)
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
Manual per-platform install (idempotent, same result as postinstall):
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
37
|
npx 0xray opencode install
|
|
38
|
-
npx 0xray grok install
|
|
38
|
+
npx 0xray grok install # 7 MCPs servers + project .grok/plugins/0xray (not machine last-wins)
|
|
39
39
|
npx 0xray hermes install
|
|
40
40
|
npx 0xray openclaw install
|
|
41
|
-
npx 0xray skill:install
|
|
41
|
+
npx 0xray skill:install # starter skills
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
## Autonomy command (default operating model)
|
|
@@ -51,7 +51,7 @@ Docs: [guides/autonomy-command](docs-site/docs/guides/autonomy-command.md) · Sk
|
|
|
51
51
|
|
|
52
52
|
| Version | Highlights |
|
|
53
53
|
|---------|------------|
|
|
54
|
-
| **Unreleased** | Fasten is **foundry-plant/0**: builtin plant is mill+inspect only. `foundry.json` `"plant": "@0xray/blip"` (aliases `"plant": "blip"` / `"plant": "sound"`) fastens whatever that mill declares (looker / vibe / mixer live in the mill). Nested mill `@0xray/foundry@0.1.12`. CLI shims `foundry blip\|sound` remain (0.1.11+). Shop plant first-class (`shop-extract`, `shop-witness`, `shop-pin` + `foundry.json` `shopPlant`).
|
|
54
|
+
| **Unreleased** | Fasten is **foundry-plant/0**: builtin plant is mill+inspect only. `foundry.json` `"plant": "@0xray/blip"` (aliases `"plant": "blip"` / `"plant": "sound"`) fastens whatever that mill declares (looker / vibe / mixer live in the mill). Nested mill `@0xray/foundry@0.1.12`. CLI shims `foundry blip\|sound` remain (0.1.11+). Shop plant first-class (`shop-extract`, `shop-witness`, `shop-pin` + `foundry.json` `shopPlant`). |
|
|
55
55
|
| **4.0.9** | Mill target: postinstall does not mill npm global prefix or `_npx` as a consumer. Isolated HOME skips machine `~/.grok`. |
|
|
56
56
|
| **4.0.0** | Exo + temperament. Constitution always on; ceremony by host. Repertoire organ on (vendored 0.2). Station card survives compact/host-swap. **On npm.** [vision](docs-site/docs/architecture/v4-vision.md) · [now](docs-site/docs/architecture/v4-now.md) |
|
|
57
57
|
| **3.4.1** | Unified `install-bridges.cjs` on postinstall — OpenCode, Grok, Hermes, OpenClaw in one pass. All 7 MCPs servers via `npx -y 0xray mcp <cmd>` (no `dist/` paths). Canonical `release.mjs` pipeline. |
|
|
@@ -63,7 +63,7 @@ Docs: [guides/autonomy-command](docs-site/docs/guides/autonomy-command.md) · Sk
|
|
|
63
63
|
|
|
64
64
|
### Consolidations
|
|
65
65
|
|
|
66
|
-
- **One consumer install path** — `postinstall.cjs` → `installAllBridges()` replaces scattered per-platform setup.
|
|
66
|
+
- **One consumer install path** — `postinstall.cjs` → `installAllBridges()` replaces scattered per-platform setup. Mill target is the consumer project, not npm global prefix or `_npx`.
|
|
67
67
|
- **7-server MCP surface** — `.mcp.json` SSOT; Grok plugin and all bridges share `XRAY_MCP_SERVERS`.
|
|
68
68
|
- **Dev vs consumer AGENTS** — `AGENTS.md` (framework) vs `AGENTS-consumer.md` (copied to consumer projects on install).
|
|
69
69
|
- **Release pipeline** — `npm run release:patch|minor|major` → reconcile → gate → artifacts → tag → publish.
|
|
@@ -81,16 +81,16 @@ Docs: [guides/autonomy-command](docs-site/docs/guides/autonomy-command.md) · Sk
|
|
|
81
81
|
|
|
82
82
|
```
|
|
83
83
|
┌─────────────────────────────────────────────────┐
|
|
84
|
-
│
|
|
85
|
-
│
|
|
84
|
+
│ Inference │
|
|
85
|
+
│ Proposals · Reflection · Memory routing │
|
|
86
86
|
├─────────────────────────────────────────────────┤
|
|
87
|
-
│
|
|
88
|
-
│
|
|
89
|
-
│
|
|
90
|
-
│
|
|
87
|
+
│ External Governance (Dynamo) │
|
|
88
|
+
│ Codex enforcement · Resonance/Isotopic · SSOT │
|
|
89
|
+
│ 3 deliberation MCPs: code-review, security, │
|
|
90
|
+
│ researcher (within 7-server consumer surface) │
|
|
91
91
|
├─────────────────────────────────────────────────┤
|
|
92
|
-
│
|
|
93
|
-
│
|
|
92
|
+
│ Autonomous Engine (thinDispatch) │
|
|
93
|
+
│ 7-flow · AsideContext · Confidence gate │
|
|
94
94
|
└─────────────────────────────────────────────────┘
|
|
95
95
|
```
|
|
96
96
|
|
|
@@ -149,20 +149,20 @@ Core governance agents:
|
|
|
149
149
|
|
|
150
150
|
See [AGENTS.md](AGENTS.md) and [SKILLS.md](SKILLS.md) for the full agent and skill catalog.
|
|
151
151
|
|
|
152
|
-
## Memory Routing + Repertoire (v3.3+)
|
|
152
|
+
## Memory Routing + Repertoire (preferred, v3.3+)
|
|
153
153
|
|
|
154
|
-
Pluggable `memory_routing`
|
|
154
|
+
**Wear Repertoire.** It is the long-term judgment organ. Station holds the ticket across compact. Dest is named invariants, not keywords. Pluggable `memory_routing` in `features.json` (validated by `features.schema.json`) ships **on**:
|
|
155
155
|
|
|
156
156
|
```json
|
|
157
157
|
"memory_routing": {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
158
|
+
"enabled": true,
|
|
159
|
+
"provider": "repertoire",
|
|
160
|
+
"module_path": "node_modules/@0xray/repertoire/dist/provider/memory-routing-provider.js",
|
|
161
|
+
"config": {
|
|
162
|
+
"signalsPath": ".xray/state/repertoire/curated_signals.json",
|
|
163
|
+
"statePath": ".xray/state/repertoire/inference-state.json",
|
|
164
|
+
"feedbackDir": ".xray/state/repertoire/feedback"
|
|
165
|
+
}
|
|
166
166
|
}
|
|
167
167
|
```
|
|
168
168
|
|
|
@@ -174,9 +174,9 @@ Pluggable `memory_routing` block in `features.json` (validated by `features.sche
|
|
|
174
174
|
| **AsideContext** (v3.2) | `buildInheritedContext` → `inheritedContext.memoryRouting` on orchestrator `spawnAside` |
|
|
175
175
|
| **Feedback** | Per-task `ingestFeedback()` closes the learning loop |
|
|
176
176
|
|
|
177
|
-
**External hosts** (Hermes/Grok): add `repertoire-mcp` to `.mcp.json` — see Repertoire docs.
|
|
177
|
+
**External hosts** (Hermes/Grok): add `repertoire-mcp` to `.mcp.json` — see Repertoire docs. Preferred. Not optional flavor.
|
|
178
178
|
|
|
179
|
-
|
|
179
|
+
Explicit opt-out only: `"memory_routing": { "enabled": false, "provider": "repertoire" }`.
|
|
180
180
|
|
|
181
181
|
Docs: [memory routing](docs-site/docs/guides/memory-routing.md) · [Repertoire](docs-site/docs/guides/repertoire.md) · [features.json](docs-site/docs/guides/features-json.md) · [all features since 3.1](docs-site/docs/guides/features-since-3.1.md)
|
|
182
182
|
|
|
@@ -188,7 +188,7 @@ Docs: [memory routing](docs-site/docs/guides/memory-routing.md) · [Repertoire](
|
|
|
188
188
|
| **Grok CLI / Build** | `npx 0xray grok install` | Project `.grok/plugins/0xray` (shared HOME does not clobber machine plugin), 7 MCPs servers |
|
|
189
189
|
| **Hermes Agent** | `npx 0xray hermes install` | `~/.hermes/plugins/xray-hermes`, consumer root marker |
|
|
190
190
|
| **OpenClaw** | `npx 0xray openclaw install` | `.xray/config/openclaw.json`, skill sync |
|
|
191
|
-
| **Cursor** | postinstall (`installCursorBridge`) | Fastens `.cursor/hooks.json`
|
|
191
|
+
| **Cursor** | postinstall (`installCursorBridge`) | Fastens `.cursor/hooks.json` + relative `.cursor/hooks/*.sh`. Rewrites leftover env-assignment one-liners. Not a fifth chat TUI. |
|
|
192
192
|
|
|
193
193
|
## Governance & Codex
|
|
194
194
|
|
|
@@ -200,27 +200,27 @@ Docs: [memory routing](docs-site/docs/guides/memory-routing.md) · [Repertoire](
|
|
|
200
200
|
|
|
201
201
|
## Testing
|
|
202
202
|
|
|
203
|
-
| Suite | Status
|
|
204
|
-
|
|
205
|
-
| Four-floor consumer e2e | OpenCode 34/0 · Grok 63/0 · Hermes 39/0/2 · OpenClaw 96/0/1
|
|
203
|
+
| Suite | Status |
|
|
204
|
+
|-------|--------|
|
|
205
|
+
| Four-floor consumer e2e | OpenCode 34/0 · Grok 63/0 · Hermes 39/0/2 · OpenClaw 96/0/1 |
|
|
206
206
|
| Consumer smoke | `npm run release:gate` — pack → clean install → 7 MCPs + 4 chat bridges + Cursor hooks + organ on |
|
|
207
207
|
| Pack → tmp proof | `npm run pack:tmp-proof` — tgz install + `foundry mint --skip-live` + hangar shops + inspect (no costume dump). Playwright n/a (CLI). |
|
|
208
208
|
|
|
209
209
|
```bash
|
|
210
210
|
npm test
|
|
211
|
-
npm run pack:tmp-proof
|
|
212
|
-
npm run release:gate
|
|
213
|
-
npm run release:npm
|
|
211
|
+
npm run pack:tmp-proof # pack → tmp fasten + hangar inspect
|
|
212
|
+
npm run release:gate # full release gate (before upload)
|
|
213
|
+
npm run release:npm # gate + prepare + npm publish --access public
|
|
214
214
|
```
|
|
215
215
|
|
|
216
216
|
## Release
|
|
217
217
|
|
|
218
|
-
|
|
218
|
+
The **4.0** line is on npm. Do **not** run `release:major` to ship a 4.x fix (that becomes 5.0.0). Do **not** put a `scripts.publish` lifecycle that re-runs the gate after the registry PUT. Patch number lives in `package.json` + CHANGELOG.
|
|
219
219
|
|
|
220
220
|
```bash
|
|
221
221
|
npm run release:gate
|
|
222
|
-
npm run release:npm
|
|
223
|
-
npm run release:patch
|
|
222
|
+
npm run release:npm # after gate is green; uses npm publish --access public
|
|
223
|
+
npm run release:patch # version bump pipeline (not for an already-bumped package.json)
|
|
224
224
|
```
|
|
225
225
|
|
|
226
226
|
Pipeline: reconcile-version → release-gate (build + test + consumer smoke) → CHANGELOG/README/AGENTS artifacts → commit → tag → npm publish.
|
package/dist/SKILLS.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**45 skills** in this exo catalog. Default consumer plant is **`mill` + `inspect`**, not this dump. `npm install 0xray` does **not** sync 45 skills unless `foundry.json` `"costume": true`. Factory shop plant (`shop-extract`, `shop-witness`, `shop-pin`) coexists with mill plant when a hangar wears those shops. Extra shops: `foundry.json` `shopPlant`. Fasten is **foundry-plant/0**: builtin plant is mill+inspect only. `foundry.json` `"plant": "@0xray/blip"` (aliases `"plant": "blip"` / `"plant": "sound"`) fastens whatever that mill package declares — organs (looker, vibe, mixer) live in the mill, not this catalog. Nested mill `@0xray/foundry@0.1.12`. CLI shims `foundry blip|sound` still exist on foundry 0.1.11+. Agent map: [llms.txt](llms.txt).
|
|
6
6
|
|
|
7
|
-
xray ships 45 knowledge skills (`src/skills/<name>/SKILL.md`) as the **costume / self-plant catalog**. Lead-dev operating model lives in **`orchestrator`** skill + `multi_agent_orchestration.lead_dev_mode` in features.json. Skills load on demand via the **xray-skills** MCP server (`npx -y 0xray mcp skills`) or when you plant them. This file is the exo catalog — mill does **not** write it to the consumer root.
|
|
7
|
+
xray ships 45 knowledge skills (`src/skills/<name>/SKILL.md`) as the **costume / self-plant catalog**. Lead-dev operating model lives in **`orchestrator`** skill + `multi_agent_orchestration.lead_dev_mode` in features.json. **Repertoire is preferred** for long-term judgment (vendored 0.2.5, dest = named laws). Station is the compact ticket, not a skill and not a keyword store. Skills load on demand via the **xray-skills** MCP server (`npx -y 0xray mcp skills`) or when you plant them. This file is the exo catalog — mill does **not** write it to the consumer root.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -129,7 +129,7 @@ xray ships 45 knowledge skills (`src/skills/<name>/SKILL.md`) as the **costume /
|
|
|
129
129
|
Configured in project `.mcp.json` on postinstall:
|
|
130
130
|
|
|
131
131
|
```bash
|
|
132
|
-
npx -y 0xray mcp skills
|
|
132
|
+
npx -y 0xray mcp skills # stdio server
|
|
133
133
|
```
|
|
134
134
|
|
|
135
135
|
MCP tools include: `invoke-skill`, `list-skills`, `skill-code-review`, `skill-security-audit`, `skill-api-design`, `skill-testing-strategy`, and more.
|
|
@@ -156,9 +156,9 @@ npx 0xray skill:install <source>
|
|
|
156
156
|
|
|
157
157
|
| Feature | Skill connection |
|
|
158
158
|
|---------|------------------|
|
|
159
|
-
| **Memory routing** (v3.3) |
|
|
159
|
+
| **Memory routing** (v3.3) | **Repertoire preferred** — researcher + thinDispatch + planner use dest laws. Extra MCP `npx @0xray/repertoire mcp` (not an 8th `xray-*`). Dest is named invariants, not `repo-*` slugs. |
|
|
160
160
|
| **AsideContext** (v3.2) | Orchestrator subcontexts during skill dispatch |
|
|
161
|
-
| **7 MCP servers** (v4.0
|
|
161
|
+
| **7 MCP servers** (v4.0) | `xray-skills` is the primary skill invocation surface |
|
|
162
162
|
|
|
163
163
|
Docs: [AGENTS.md](AGENTS.md) · [README.md](README.md) · [Docusaurus guides](docs-site/docs/guides/)
|
|
164
164
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
/** Same paths as `REQUIRED_PACK_PATHS` in assert-packed-dist-cli.mjs — typed here so src/cli does not import untyped .mjs. */
|
|
8
8
|
export declare const VALIDATE_PACK_PATHS: readonly string[];
|
|
9
|
-
export declare const CONSUMER_WEAR_PATHS: readonly ["AGENTS.md", ".mcp.json", ".xray/codex.json", ".xray/features.json", ".cursor/hooks.json"];
|
|
9
|
+
export declare const CONSUMER_WEAR_PATHS: readonly ["AGENTS.md", ".mcp.json", ".xray/codex.json", ".xray/features.json", ".cursor/hooks.json", ".cursor/hooks/pre-tool-use.sh", ".cursor/hooks/xray-cloud-hook.sh"];
|
|
10
10
|
export declare const MILL_PLANT_SKILLS: readonly ["mill", "inspect"];
|
|
11
11
|
export type ValidateCheck = {
|
|
12
12
|
id: string;
|
|
@@ -18,6 +18,10 @@ export const VALIDATE_PACK_PATHS = [
|
|
|
18
18
|
"dist/integrations/cursor/hooks/after-file-edit.js",
|
|
19
19
|
"dist/integrations/cursor/hooks/cursor-hook-utils.js",
|
|
20
20
|
"dist/integrations/cursor/hooks/cursor-usage-receipt.js",
|
|
21
|
+
"dist/integrations/cursor/hooks/xray-cloud-hook.sh",
|
|
22
|
+
"dist/integrations/cursor/hooks/pre-tool-use.sh",
|
|
23
|
+
"dist/integrations/cursor/hooks/pre-compact.sh",
|
|
24
|
+
"dist/integrations/cursor/hooks/after-file-edit.sh",
|
|
21
25
|
];
|
|
22
26
|
export const CONSUMER_WEAR_PATHS = [
|
|
23
27
|
"AGENTS.md",
|
|
@@ -25,6 +29,8 @@ export const CONSUMER_WEAR_PATHS = [
|
|
|
25
29
|
".xray/codex.json",
|
|
26
30
|
".xray/features.json",
|
|
27
31
|
".cursor/hooks.json",
|
|
32
|
+
".cursor/hooks/pre-tool-use.sh",
|
|
33
|
+
".cursor/hooks/xray-cloud-hook.sh",
|
|
28
34
|
];
|
|
29
35
|
export const MILL_PLANT_SKILLS = ["mill", "inspect"];
|
|
30
36
|
export function resolveInstalledPackageRoot(cwd) {
|
|
@@ -101,7 +107,7 @@ export function collectValidateReport(cwd) {
|
|
|
101
107
|
id: "consumer-wear",
|
|
102
108
|
ok: missingWear.length === 0,
|
|
103
109
|
detail: missingWear.length === 0
|
|
104
|
-
? "AGENTS.md · .mcp.json · .xray · .cursor/hooks.json"
|
|
110
|
+
? "AGENTS.md · .mcp.json · .xray · .cursor/hooks.json · .cursor/hooks/*.sh"
|
|
105
111
|
: `missing ${missingWear.join(", ")}`,
|
|
106
112
|
});
|
|
107
113
|
const millRoot = join(cwd, ".opencode", "skills");
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
import type { GovernanceVote, GovernanceResult } from './governance-types.js';
|
|
10
10
|
export interface DecisionMatrixInput {
|
|
11
11
|
resonance: number;
|
|
12
|
-
|
|
12
|
+
/** Present only when a real moralFusion number was supplied. Absent is not 0.5. */
|
|
13
|
+
isotopicRatio?: number;
|
|
13
14
|
vortexVolume?: number;
|
|
14
15
|
historicalCoherence?: number;
|
|
15
16
|
solarActivity?: 'quiet' | 'moderate' | 'active' | 'storm';
|
|
@@ -61,22 +61,28 @@ export function applyDecisionMatrix(input) {
|
|
|
61
61
|
voteWeight *= 1.05;
|
|
62
62
|
reasons.push(`Moral alignment confirmed (score: ${(moralScore * 100).toFixed(0)}%)`);
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
// Cutoffs stay the literals the tests lock (0.92/0.95, 0.82/0.88, 0.75/0.80).
|
|
65
|
+
// PHI and TAU are not those cutoffs, so reasons do not claim them.
|
|
66
|
+
const ratio = typeof isotopicRatio === 'number' && Number.isFinite(isotopicRatio) ? isotopicRatio : undefined;
|
|
67
|
+
const highBand = resonance >= 0.92 && (ratio === undefined || ratio >= 0.95);
|
|
68
|
+
const solidBand = resonance >= 0.82 && (ratio === undefined || ratio >= 0.88);
|
|
69
|
+
const belowCutoff = resonance < 0.75 || (ratio !== undefined && ratio < 0.80);
|
|
70
|
+
if (highBand) {
|
|
65
71
|
recommendation = 'PASS';
|
|
66
72
|
confidence = 0.97;
|
|
67
73
|
voteWeight = 1.4;
|
|
68
|
-
reasons.push('High symbiotic resonance
|
|
74
|
+
reasons.push('High symbiotic resonance');
|
|
69
75
|
}
|
|
70
|
-
else if (
|
|
76
|
+
else if (solidBand) {
|
|
71
77
|
recommendation = 'PASS';
|
|
72
78
|
confidence = 0.89;
|
|
73
79
|
voteWeight = 1.15;
|
|
74
|
-
reasons.push('Solid alignment above
|
|
80
|
+
reasons.push('Solid alignment above the resonance cutoff');
|
|
75
81
|
}
|
|
76
|
-
else if (
|
|
82
|
+
else if (belowCutoff) {
|
|
77
83
|
recommendation = 'REJECT';
|
|
78
84
|
confidence = 0.84;
|
|
79
|
-
reasons.push('Signal below critical
|
|
85
|
+
reasons.push('Signal below the critical resonance or isotopic cutoff');
|
|
80
86
|
}
|
|
81
87
|
else {
|
|
82
88
|
reasons.push('Moderate resonance - requires refinement');
|
|
@@ -124,14 +130,17 @@ export function mergeVotes(votes) {
|
|
|
124
130
|
reasoningSummary: 'No votes received',
|
|
125
131
|
};
|
|
126
132
|
}
|
|
133
|
+
const weightOf = (vote) => vote.weight ?? 1;
|
|
127
134
|
const approveWeight = votes
|
|
128
135
|
.filter(v => v.decision === 'approve')
|
|
129
|
-
.reduce((sum, v) => sum + (v
|
|
130
|
-
const
|
|
131
|
-
const
|
|
136
|
+
.reduce((sum, v) => sum + weightOf(v) * v.confidence, 0);
|
|
137
|
+
const weightedConfidenceSum = votes.reduce((sum, v) => sum + weightOf(v) * v.confidence, 0);
|
|
138
|
+
const weightSum = votes.reduce((sum, v) => sum + weightOf(v), 0);
|
|
139
|
+
const rawMean = weightSum > 0 ? weightedConfidenceSum / weightSum : 0.5;
|
|
140
|
+
const avgConfidence = Math.min(1, Math.max(0, rawMean));
|
|
132
141
|
let finalDecision = 'needs_revision';
|
|
133
|
-
if (
|
|
134
|
-
const approveRatio = approveWeight /
|
|
142
|
+
if (weightedConfidenceSum > 0) {
|
|
143
|
+
const approveRatio = approveWeight / weightedConfidenceSum;
|
|
135
144
|
if (approveRatio > 0.66) {
|
|
136
145
|
finalDecision = 'approve';
|
|
137
146
|
}
|
|
@@ -136,22 +136,24 @@ export class GovernanceService {
|
|
|
136
136
|
let finalDecision = merged.finalDecision;
|
|
137
137
|
let averageConfidence = merged.averageConfidence;
|
|
138
138
|
let moralOverride = 'none';
|
|
139
|
-
if (externalVote
|
|
140
|
-
externalVote
|
|
141
|
-
|
|
139
|
+
if (externalVote &&
|
|
140
|
+
(externalVote.moralTension ||
|
|
141
|
+
externalVote.moralScore != null ||
|
|
142
|
+
externalVote.moralFusion != null)) {
|
|
142
143
|
const matrixInput = {
|
|
143
144
|
resonance: averageConfidence,
|
|
144
|
-
isotopicRatio: externalVote.moralFusion ?? 0.5,
|
|
145
145
|
};
|
|
146
|
+
const fusion = externalVote.moralFusion;
|
|
147
|
+
if (typeof fusion === 'number' && Number.isFinite(fusion)) {
|
|
148
|
+
matrixInput.isotopicRatio = fusion;
|
|
149
|
+
matrixInput.moralFusion = fusion;
|
|
150
|
+
}
|
|
146
151
|
if (externalVote.moralTension != null) {
|
|
147
152
|
matrixInput.moralTension = externalVote.moralTension;
|
|
148
153
|
}
|
|
149
154
|
if (externalVote.moralScore != null) {
|
|
150
155
|
matrixInput.moralScore = externalVote.moralScore;
|
|
151
156
|
}
|
|
152
|
-
if (externalVote.moralFusion != null) {
|
|
153
|
-
matrixInput.moralFusion = externalVote.moralFusion;
|
|
154
|
-
}
|
|
155
157
|
const matrix = applyDecisionMatrix(matrixInput);
|
|
156
158
|
moralOverride = matrix.moralOverride ?? 'none';
|
|
157
159
|
if (matrix.recommendation === 'REJECT') {
|
|
@@ -10,16 +10,30 @@ import { generateProposals } from "./inference-proposal-generator.js";
|
|
|
10
10
|
import { applyProposals as applyProposalsEx } from "./inference-applier.js";
|
|
11
11
|
const CYCLE_STATE_FILE = "inference-cycle-state.json";
|
|
12
12
|
const CYCLE_HISTORY_FILE = "inference-cycle-history.json";
|
|
13
|
+
function governanceToolText(result) {
|
|
14
|
+
if (typeof result !== "object" || result === null || !("content" in result)) {
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
const content = result.content;
|
|
18
|
+
const text = content?.[0]?.text;
|
|
19
|
+
return typeof text === "string" ? text : "";
|
|
20
|
+
}
|
|
13
21
|
export class InferenceCycle {
|
|
14
22
|
static instances = new Map();
|
|
15
23
|
static reEntryLock = false;
|
|
16
24
|
static governedProposalIds = new Set();
|
|
17
25
|
static getInstance(projectRoot, options) {
|
|
18
26
|
const root = path.resolve(projectRoot || process.cwd());
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
const existing = InferenceCycle.instances.get(root);
|
|
28
|
+
if (existing) {
|
|
29
|
+
if (options) {
|
|
30
|
+
existing.options = { ...existing.options, ...options };
|
|
31
|
+
}
|
|
32
|
+
return existing;
|
|
21
33
|
}
|
|
22
|
-
|
|
34
|
+
const created = new InferenceCycle(root, undefined, options);
|
|
35
|
+
InferenceCycle.instances.set(root, created);
|
|
36
|
+
return created;
|
|
23
37
|
}
|
|
24
38
|
static resetInstance() {
|
|
25
39
|
InferenceCycle.instances.clear();
|
|
@@ -121,7 +135,10 @@ export class InferenceCycle {
|
|
|
121
135
|
const proposals = generateProposals(corpus, this.loadHistory());
|
|
122
136
|
if (proposals.length === 0) {
|
|
123
137
|
this.setPhase("complete");
|
|
124
|
-
|
|
138
|
+
this.saveCycleState(cycleId);
|
|
139
|
+
const emptyResult = this.buildResult(cycleId, true, threshold.reason, startTime, corpus, proposals);
|
|
140
|
+
this.appendHistory(emptyResult);
|
|
141
|
+
return emptyResult;
|
|
125
142
|
}
|
|
126
143
|
this.setPhase("governing");
|
|
127
144
|
let votes = [];
|
|
@@ -223,7 +240,7 @@ export class InferenceCycle {
|
|
|
223
240
|
}),
|
|
224
241
|
new Promise((_, reject) => setTimeout(() => reject(new Error("Governance MCP timed out after 8s")), 8000)),
|
|
225
242
|
]);
|
|
226
|
-
const text = result
|
|
243
|
+
const text = governanceToolText(result);
|
|
227
244
|
const parsed = this.parseGovernanceMcpResponse(text, proposals);
|
|
228
245
|
frameworkLogger.log("inference-cycle", "governance-mcp-primary-path", "info", {
|
|
229
246
|
proposalCount: proposals.length,
|
|
@@ -2,24 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
Thin adapter. Repo `.cursor/hooks.json` (schema **version 1**) calls 0xRay gates and the Station PreCompact writer. **sessionStart is not used** — managed Cursor cloud does not fire it.
|
|
4
4
|
|
|
5
|
-
Repertoire
|
|
5
|
+
Repertoire is preferred (vendored, dest = named laws). Station + Codex gates still run if memory-routing is explicitly off — that is fallback, not the preferred brain.
|
|
6
6
|
|
|
7
7
|
## How cloud picks up project hooks
|
|
8
8
|
|
|
9
9
|
1. Commit `.cursor/hooks.json` at the **project root** (this file, not `~/.cursor/hooks.json`).
|
|
10
10
|
2. Cursor Cloud Agent clones the repo and loads **project** hooks. Desktop Agent Chat does the same when the folder is the workspace.
|
|
11
|
-
3. Hook names are camelCase: `preToolUse`, `preCompact`, `afterFileEdit`.
|
|
12
|
-
4. First `preToolUse` or `afterFileEdit` writes `.xray/state/session-boot.json` + `.xray/state/STATION.md`. After a compact, **Read STATION.md**. The host does not inject it.
|
|
13
|
-
5. `
|
|
11
|
+
3. Hook names are camelCase: `preToolUse`, `preCompact`, `afterFileEdit`, plus Cloud `beforeShellExecution` / `beforeReadFile`. Command is a **relative** `.cursor/hooks/*.sh` — Cloud execs argv[0] without a shell. Do not put `XRAY_AI_PATH=` in `hooks.json`. Stdin is JSON; stdout is JSON.
|
|
12
|
+
4. First `preToolUse` or `afterFileEdit` writes `.xray/state/session-boot.json` + `.xray/state/STATION.md`. After a compact, **Read STATION.md**. The host does not inject it. A multi-repo Cloud wrapper (`/agent` with `repos/xray`) is not a dest heat root, even if a leftover wrapper card exists. Heat the mill under `repos/` and the Read path. Dest `EACCES` fail-opens — a wired hook must not deny every tool.
|
|
13
|
+
5. `install-bridges` also fastens the daemon workspace root when it can see `repos/xray` above the checkout, then pokes local `ReloadAgentSkills` so a mid-session wear binds this daemon. Command stays a relative `.cursor/hooks/*.sh`.
|
|
14
|
+
6. `preCompact` is observational (cannot block). Cloud **does** fire it ([hooks.md](https://cursor.com/docs/hooks.md) support matrix: Yes) once hooks are bound and the host actually compacts. Stdout is only `{ "user_message": "…" }` — that is a notice, not a spawn. The spawn id is stdin `conversation_id` (same `bc-`) plus Task's returned agent id. Resume that id. Do not cold-start a twin. Station merge keeps ticket / seed / Durable / unfinished keys.
|
|
14
15
|
|
|
15
|
-
Copy the consumer template from `src/integrations/cursor/hooks/hooks.json`
|
|
16
|
+
Copy the consumer template from `src/integrations/cursor/hooks/hooks.json` and the sibling `.sh` runners. `xray-cloud-hook.sh` finds mill JS at `XRAY_AI_PATH`, `../xray`, or `node_modules/0xray` (dist then src). This exo repo wears `src/integrations/cursor/hooks/*.js` so a cloud can run before `npm run build`.
|
|
16
17
|
|
|
17
18
|
## Contracts
|
|
18
19
|
|
|
19
20
|
| Hook | Stdin | Stdout |
|
|
20
21
|
|------|--------|--------|
|
|
21
22
|
| `preToolUse` | Cursor tool event (`tool_name`, `tool_input`, `cwd`, `workspace_roots`, `conversation_id`) | `{ "permission": "allow" \| "deny" }` plus `user_message` / `agent_message` on deny |
|
|
22
|
-
| `preCompact` | `{ trigger, context_tokens, … }` | `{ "user_message": "…" }` after Station write |
|
|
23
|
+
| `preCompact` | `{ trigger, context_tokens, conversation_id, generation_id, … }` | `{ "user_message": "…" }` after Station write. Receipt keeps `conversation_id` + `generation_id`. That id is enough to resume. |
|
|
23
24
|
| `afterFileEdit` | file event | `{}` — boots Station only |
|
|
24
25
|
|
|
25
26
|
`preCompact` labels `event_class`:
|
|
@@ -84,32 +84,123 @@ function addHeatRoot(out, seen, value) {
|
|
|
84
84
|
out.push(resolved);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
export function isCursorWorkspaceWrapper(root) {
|
|
88
|
+
if (!root) return false;
|
|
89
|
+
const resolved = path.resolve(String(root));
|
|
90
|
+
return (
|
|
91
|
+
fs.existsSync(path.join(resolved, 'repos', 'xray')) ||
|
|
92
|
+
fs.existsSync(path.join(resolved, 'repos', 'repertoire'))
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function heatRootHasCard(root) {
|
|
97
|
+
if (!root) return false;
|
|
98
|
+
const resolved = path.resolve(String(root));
|
|
99
|
+
return (
|
|
100
|
+
fs.existsSync(path.join(resolved, '.xray', 'state', 'STATION.md')) ||
|
|
101
|
+
fs.existsSync(path.join(resolved, '.xray', 'features.json'))
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function isHeatRootWritable(root) {
|
|
106
|
+
if (!root) return false;
|
|
107
|
+
const resolved = path.resolve(String(root));
|
|
108
|
+
try {
|
|
109
|
+
const probe = path.join(resolved, '.xray', 'state');
|
|
110
|
+
fs.mkdirSync(probe, { recursive: true });
|
|
111
|
+
fs.accessSync(probe, fs.constants.W_OK);
|
|
112
|
+
return true;
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function shouldHeatRoot(root) {
|
|
119
|
+
if (!root) return false;
|
|
120
|
+
const resolved = path.resolve(String(root));
|
|
121
|
+
if (isCursorWorkspaceWrapper(resolved)) return false;
|
|
122
|
+
if (heatRootHasCard(resolved)) return isHeatRootWritable(resolved);
|
|
123
|
+
return isHeatRootWritable(resolved);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function millRootFromToolPath(filePath) {
|
|
127
|
+
if (!filePath) return null;
|
|
128
|
+
const resolved = path.resolve(String(filePath));
|
|
129
|
+
const marker = `${path.sep}repos${path.sep}`;
|
|
130
|
+
const idx = resolved.indexOf(marker);
|
|
131
|
+
if (idx === -1) return null;
|
|
132
|
+
const after = resolved.slice(idx + marker.length);
|
|
133
|
+
const repo = after.split(path.sep)[0];
|
|
134
|
+
if (!repo) return null;
|
|
135
|
+
return resolved.slice(0, idx + marker.length + repo.length);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function addMillCardsUnderWrapper(out, seen, wrapper) {
|
|
139
|
+
if (!wrapper || !isCursorWorkspaceWrapper(wrapper)) return;
|
|
140
|
+
const mill = path.join(path.resolve(String(wrapper)), 'repos', 'xray');
|
|
141
|
+
if (heatRootHasCard(mill) && isHeatRootWritable(mill)) addHeatRoot(out, seen, mill);
|
|
142
|
+
}
|
|
143
|
+
|
|
87
144
|
/** Every live context that already has a Station card — cwd plus mill when they differ. */
|
|
88
145
|
export function cursorHeatRoots(event = {}) {
|
|
89
146
|
const seen = new Set();
|
|
90
147
|
const out = [];
|
|
91
|
-
|
|
92
|
-
addHeatRoot(out, seen, event.workspaceRoot);
|
|
148
|
+
const candidates = [event.cwd, event.workspaceRoot];
|
|
93
149
|
const listed = event.workspace_roots || event.workspaceRoots;
|
|
94
150
|
if (Array.isArray(listed)) {
|
|
95
151
|
for (const row of listed) {
|
|
96
|
-
if (typeof row === 'string')
|
|
97
|
-
else if (row && typeof row === 'object' && row.path)
|
|
152
|
+
if (typeof row === 'string') candidates.push(row);
|
|
153
|
+
else if (row && typeof row === 'object' && row.path) candidates.push(row.path);
|
|
98
154
|
}
|
|
99
155
|
}
|
|
100
|
-
|
|
101
|
-
const
|
|
156
|
+
candidates.push(process.env.CURSOR_PROJECT_DIR);
|
|
157
|
+
for (const value of candidates) {
|
|
158
|
+
if (shouldHeatRoot(value)) addHeatRoot(out, seen, value);
|
|
159
|
+
addMillCardsUnderWrapper(out, seen, value);
|
|
160
|
+
}
|
|
161
|
+
const mill = process.env.XRAY_AI_PATH || process.env.XRAY_ROOT;
|
|
102
162
|
if (mill) {
|
|
103
163
|
const resolved = path.resolve(String(mill));
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
164
|
+
if (shouldHeatRoot(resolved)) addHeatRoot(out, seen, resolved);
|
|
165
|
+
}
|
|
166
|
+
const ctx = cursorToolContext(event);
|
|
167
|
+
for (const filePath of ctx.paths) {
|
|
168
|
+
const fromPath = millRootFromToolPath(filePath);
|
|
169
|
+
if (
|
|
170
|
+
fromPath &&
|
|
171
|
+
fs.existsSync(path.join(fromPath, '.xray', 'state', 'STATION.md')) &&
|
|
172
|
+
isHeatRootWritable(fromPath)
|
|
173
|
+
) {
|
|
174
|
+
addHeatRoot(out, seen, fromPath);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (out.length === 0) {
|
|
178
|
+
const fallback = cursorWorkspaceRoot(event);
|
|
179
|
+
if (shouldHeatRoot(fallback)) addHeatRoot(out, seen, fallback);
|
|
180
|
+
addMillCardsUnderWrapper(out, seen, fallback);
|
|
108
181
|
}
|
|
109
|
-
if (out.length === 0) addHeatRoot(out, seen, cursorWorkspaceRoot(event));
|
|
110
182
|
return out;
|
|
111
183
|
}
|
|
112
184
|
|
|
185
|
+
function packageNameAt(root) {
|
|
186
|
+
try {
|
|
187
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
188
|
+
return typeof pkg.name === 'string' ? pkg.name : '';
|
|
189
|
+
} catch {
|
|
190
|
+
return '';
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Constitution follows the suit mill when that package is in the heat set.
|
|
196
|
+
* A sibling repo's leftover synthesis checkpoint must not deny the host.
|
|
197
|
+
*/
|
|
198
|
+
export function cursorGateRoot(heatRoots, eventRoot) {
|
|
199
|
+
const mill = heatRoots.find((root) => packageNameAt(root) === '0xray');
|
|
200
|
+
if (mill) return mill;
|
|
201
|
+
return heatRoots[0] || eventRoot;
|
|
202
|
+
}
|
|
203
|
+
|
|
113
204
|
export function cursorSessionId(event = {}) {
|
|
114
205
|
return (
|
|
115
206
|
event.conversation_id ||
|
|
@@ -121,6 +212,10 @@ export function cursorSessionId(event = {}) {
|
|
|
121
212
|
);
|
|
122
213
|
}
|
|
123
214
|
|
|
215
|
+
export function cursorGenerationId(event = {}) {
|
|
216
|
+
return event.generation_id || event.generationId || null;
|
|
217
|
+
}
|
|
218
|
+
|
|
124
219
|
export function cursorToolContext(event = {}) {
|
|
125
220
|
const toolName = event.tool_name || event.toolName || process.env.TOOL_NAME || 'unknown';
|
|
126
221
|
const toolInput = event.tool_input ?? event.toolInput ?? {};
|
|
@@ -179,11 +274,16 @@ export function cursorBootNeedsRefresh(existing, root) {
|
|
|
179
274
|
|
|
180
275
|
export function ensureCursorSessionBoot(root, source = '0xray/cursor-pre-tool-use-boot', extra = {}) {
|
|
181
276
|
const bootPath = sessionBootPath(root);
|
|
277
|
+
if (!shouldHeatRoot(root)) return bootPath;
|
|
182
278
|
if (fs.existsSync(bootPath)) {
|
|
183
279
|
try {
|
|
184
280
|
const existing = JSON.parse(fs.readFileSync(bootPath, 'utf8'));
|
|
185
281
|
if (!cursorBootNeedsRefresh(existing, root)) {
|
|
186
|
-
|
|
282
|
+
try {
|
|
283
|
+
heatLiveMemory(root);
|
|
284
|
+
} catch {
|
|
285
|
+
/* dest EACCES must not deny the host tool */
|
|
286
|
+
}
|
|
187
287
|
return bootPath;
|
|
188
288
|
}
|
|
189
289
|
} catch {
|
|
@@ -69,6 +69,9 @@ export function hostUsageFromPreCompactEvent(event = {}) {
|
|
|
69
69
|
if (typeof event.windowCite === 'string' && event.windowCite) usage.windowCite = event.windowCite;
|
|
70
70
|
if (typeof event.model === 'string' && event.model) usage.model = event.model;
|
|
71
71
|
if (typeof event.bcId === 'string' && event.bcId) usage.bcId = event.bcId;
|
|
72
|
+
else if (typeof event.conversation_id === 'string' && event.conversation_id) {
|
|
73
|
+
usage.bcId = event.conversation_id;
|
|
74
|
+
}
|
|
72
75
|
return usage;
|
|
73
76
|
}
|
|
74
77
|
|