vibeostheog 0.19.3 → 0.19.5
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/.opencode/plugins/vibeOS-tui.tsx +93 -4
- package/CHANGELOG.md +5 -0
- package/README.md +11 -17
- package/package.json +4 -5
- package/src/flow-enforcer.js +1 -0
- package/src/flow-rules.json +39 -0
- package/src/index.js +1 -1
- package/src/lib/api-client.js +415 -0
- package/src/lib/classifiers.js +173 -0
- package/src/lib/constants.js +19 -0
- package/src/lib/credit-api.js +145 -0
- package/src/lib/hooks/chat-transform.js +664 -0
- package/src/lib/hooks/footer.js +307 -0
- package/src/lib/hooks/session-compact.js +64 -0
- package/src/lib/hooks/shell-env.js +18 -0
- package/src/lib/hooks/tests/footer.test.js +185 -0
- package/src/lib/hooks/tool-execute.js +912 -0
- package/src/lib/index-helpers.js +306 -0
- package/src/lib/mode-policy.js +190 -0
- package/src/lib/pattern-helpers.js +126 -0
- package/src/lib/pricing.js +552 -0
- package/src/lib/reporting.js +228 -0
- package/src/lib/research-audit.js +134 -0
- package/src/lib/runtime-state.js +42 -0
- package/src/lib/runtime-surface.js +183 -0
- package/src/lib/selection-manager.js +150 -0
- package/src/lib/state.js +1802 -0
- package/src/lib/tdd-enforcer.js +312 -0
- package/src/lib/templates.js +92 -0
- package/src/lib/test-skeletons.js +470 -0
- package/src/lib/tests/api-client.test.js +220 -0
- package/src/lib/tests/pricing.test.js +745 -0
- package/src/lib/tests/state.test.js +686 -0
- package/src/lib/text-compress.js +94 -0
- package/src/lib/trinity-rebuild.js +254 -0
- package/src/lib/trinity-tool.js +997 -0
- package/src/lib/turn-classify.js +518 -0
- package/src/tests/index.test.js +91 -0
- package/src/utils/cost-formatter.js +28 -0
- package/src/utils/math.js +10 -0
- package/src/utils/tdd-helpers.js +267 -0
- package/src/utils/timer.js +72 -0
- package/src/vibeOS-lib/blackbox/advice-layer.js +339 -0
- package/src/vibeOS-lib/blackbox/crew-constants.js +68 -0
- package/src/vibeOS-lib/blackbox/exposure-model.js +42 -0
- package/src/vibeOS-lib/blackbox/index.js +10 -0
- package/src/vibeOS-lib/blackbox/local-stub.js +142 -0
- package/src/vibeOS-lib/blackbox/meta-controller.js +280 -0
- package/src/vibeOS-lib/blackbox/resolution-tracker.js +494 -0
- package/src/vibeOS-lib/blackbox/taxonomy.js +106 -0
- package/src/vibeOS-lib/flow-enforcer.js +378 -0
- package/src/vibeOS-lib/flow-rules.json +39 -0
- package/src/vibeOS-lib/ml-router.js +276 -0
- package/src/vibeOS-lib/session-metrics.js +138 -0
- package/src/vibeOS-lib/smart-cache.js +211 -0
- package/src/vibeOS-lib/tests/experiment-data-export.json +12743 -0
- package/src/vibeOS-lib/tests/experiment-scenarios-progressive.json +115 -0
- package/src/vibeOS-lib/tests/experiment-scenarios-token-latency.json +57 -0
- package/src/vibeOS-lib/tests/experiment-scenarios.json +94 -0
- package/src/vibeOS-lib/tests/reports/mode-benchmark-final-2026-05-23T07-01-58Z.json +129 -0
- package/src/vibeOS-lib/tests/reports/mode-calibration-2026-05-23T07-07-08Z.json +440 -0
- package/src/vibeOS-lib/tests/reports/mode-signal-analysis-2026-05-23T06-59-30Z.json +89 -0
|
@@ -99,10 +99,6 @@ const plugin: TuiPlugin = async (api, _options, _meta) => {
|
|
|
99
99
|
await poll()
|
|
100
100
|
const timer = setInterval(poll, 3000)
|
|
101
101
|
|
|
102
|
-
api.lifecycle.onDispose(() => {
|
|
103
|
-
clearInterval(timer)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
102
|
const doAction = async (body: Record<string, unknown>) => {
|
|
107
103
|
try {
|
|
108
104
|
const res = await fetch(`${getBaseUrl()}/trinity`, {
|
|
@@ -122,6 +118,99 @@ const plugin: TuiPlugin = async (api, _options, _meta) => {
|
|
|
122
118
|
}
|
|
123
119
|
}
|
|
124
120
|
|
|
121
|
+
const trinityCommands = [
|
|
122
|
+
{
|
|
123
|
+
title: "vibeOS: trinity",
|
|
124
|
+
value: "trinity",
|
|
125
|
+
description: "Open vibeOS controls and show the current status",
|
|
126
|
+
category: "vibeOS",
|
|
127
|
+
suggested: true,
|
|
128
|
+
slash: {
|
|
129
|
+
name: "trinity",
|
|
130
|
+
aliases: ["vibeos"],
|
|
131
|
+
},
|
|
132
|
+
onSelect: async () => {
|
|
133
|
+
await doAction({ action: "status" })
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
title: "vibeOS: trinity status",
|
|
138
|
+
value: "trinity status",
|
|
139
|
+
description: "Show the current tier, savings, and guard state",
|
|
140
|
+
category: "vibeOS",
|
|
141
|
+
slash: {
|
|
142
|
+
name: "trinity-status",
|
|
143
|
+
aliases: ["vibeos-status"],
|
|
144
|
+
},
|
|
145
|
+
onSelect: async () => {
|
|
146
|
+
await doAction({ action: "status" })
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
title: "vibeOS: trinity rebuild",
|
|
151
|
+
value: "trinity rebuild",
|
|
152
|
+
description: "Re-detect available models and repopulate the slots",
|
|
153
|
+
category: "vibeOS",
|
|
154
|
+
slash: {
|
|
155
|
+
name: "trinity-rebuild",
|
|
156
|
+
aliases: ["vibeos-rebuild"],
|
|
157
|
+
},
|
|
158
|
+
onSelect: async () => {
|
|
159
|
+
await doAction({ action: "rebuild" })
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
title: "vibeOS: trinity brain",
|
|
164
|
+
value: "trinity brain",
|
|
165
|
+
description: "Switch to the brain slot",
|
|
166
|
+
category: "vibeOS",
|
|
167
|
+
slash: {
|
|
168
|
+
name: "trinity-brain",
|
|
169
|
+
aliases: ["trinity-high"],
|
|
170
|
+
},
|
|
171
|
+
onSelect: async () => {
|
|
172
|
+
await doAction({ action: "set", slot: "brain" })
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
title: "vibeOS: trinity medium",
|
|
177
|
+
value: "trinity medium",
|
|
178
|
+
description: "Switch to the medium slot",
|
|
179
|
+
category: "vibeOS",
|
|
180
|
+
slash: {
|
|
181
|
+
name: "trinity-medium",
|
|
182
|
+
},
|
|
183
|
+
onSelect: async () => {
|
|
184
|
+
await doAction({ action: "set", slot: "medium" })
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
title: "vibeOS: trinity cheap",
|
|
189
|
+
value: "trinity cheap",
|
|
190
|
+
description: "Switch to the cheap slot",
|
|
191
|
+
category: "vibeOS",
|
|
192
|
+
slash: {
|
|
193
|
+
name: "trinity-cheap",
|
|
194
|
+
},
|
|
195
|
+
onSelect: async () => {
|
|
196
|
+
await doAction({ action: "set", slot: "cheap" })
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
const unregisterTrinityCommands =
|
|
202
|
+
api.keymap?.registerLayer?.({ commands: trinityCommands, bindings: [] }) ??
|
|
203
|
+
api.command?.register?.(() => trinityCommands)
|
|
204
|
+
|
|
205
|
+
api.lifecycle.onDispose(() => {
|
|
206
|
+
clearInterval(timer)
|
|
207
|
+
if (typeof unregisterTrinityCommands === "function") {
|
|
208
|
+
unregisterTrinityCommands()
|
|
209
|
+
} else {
|
|
210
|
+
unregisterTrinityCommands?.dispose?.()
|
|
211
|
+
}
|
|
212
|
+
})
|
|
213
|
+
|
|
125
214
|
const Slot = api.ui.Slot
|
|
126
215
|
|
|
127
216
|
api.slots.register((props: { session_id: string }) => {
|
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -18,30 +18,22 @@ It also adds guardrails: delegation enforcement, flow and TDD controls, pattern
|
|
|
18
18
|
|
|
19
19
|
### OpenCode plugin
|
|
20
20
|
|
|
21
|
-
1.
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
npm install vibeostheog
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
2. Register it in `~/.config/opencode/opencode.json`:
|
|
21
|
+
1. Add the package to your OpenCode config. OpenCode installs npm plugins automatically at startup:
|
|
28
22
|
|
|
29
23
|
```json
|
|
30
24
|
{
|
|
31
|
-
"
|
|
32
|
-
|
|
25
|
+
"plugin": [
|
|
26
|
+
"vibeostheog@0.19.5"
|
|
33
27
|
]
|
|
34
28
|
}
|
|
35
29
|
```
|
|
36
30
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
If you keep a local checkout of the plugin, point OpenCode at the built file instead:
|
|
31
|
+
2. If you keep a local checkout of the plugin, point OpenCode at the built file instead:
|
|
40
32
|
|
|
41
33
|
```json
|
|
42
34
|
{
|
|
43
|
-
"
|
|
44
|
-
|
|
35
|
+
"plugin": [
|
|
36
|
+
"/absolute/path/to/theSaver-oc/src/index.js"
|
|
45
37
|
]
|
|
46
38
|
}
|
|
47
39
|
```
|
|
@@ -60,11 +52,13 @@ npm test
|
|
|
60
52
|
npm run release:patch
|
|
61
53
|
```
|
|
62
54
|
|
|
63
|
-
`npm run build` compiles `src/index.ts` to `src/index.js`
|
|
55
|
+
`npm run build` compiles `src/index.ts` to `src/index.js` for the local checkout. `npm run typecheck` validates the TypeScript sources without emitting files.
|
|
64
56
|
|
|
65
57
|
## Core Controls
|
|
66
58
|
|
|
67
|
-
Use `trinity help` for the full command list. The
|
|
59
|
+
`trinity` is an OpenCode plugin command. Run it from inside OpenCode, not from a normal terminal shell. Use `trinity help` for the full command list. The bundled TUI plugin also registers `trinity` and the common slot actions in OpenCode's command palette.
|
|
60
|
+
|
|
61
|
+
The most common controls are:
|
|
68
62
|
|
|
69
63
|
- `trinity status` - show current tier, enforcement, savings, stress, and lock state
|
|
70
64
|
- `trinity set brain|medium|cheap` - switch the active tier
|
|
@@ -109,7 +103,7 @@ Savings are persisted in `~/.claude/delegation-state.json`.
|
|
|
109
103
|
| Variable | Default | Purpose |
|
|
110
104
|
|---|---|---|
|
|
111
105
|
| `VIBEOS_API_URL` | `https://api.vibetheog.com` | Remote API server URL |
|
|
112
|
-
| `VIBEOS_API_TOKEN` |
|
|
106
|
+
| `VIBEOS_API_TOKEN` | unset | vos_ffa6c7dacb244a03 |
|
|
113
107
|
| `VIBEOS_API_ENABLED` | `true` | Set to `false` for local-only mode |
|
|
114
108
|
| `CLAUDE_CREDIT_PERCENT` | `100` | Credit override |
|
|
115
109
|
| `CLAUDE_CONTEXT7_AVAILABLE` | unset | Enables context7 optimization |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibeostheog",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.5",
|
|
4
4
|
"description": "Cost-aware delegation enforcer for OpenCode. Tracks model usage, routes Task subagents to cheaper tiers, surfaces cumulative savings in chat. Includes research audit, reporting framework, project memory, progressive scratchpad decadence, and trinity CLI for brain/medium/cheap slot switching.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"release": "node scripts/release.mjs",
|
|
@@ -21,8 +21,7 @@
|
|
|
21
21
|
"codex:hook:summary": "bash plugins/vibetheog-codex/hooks/post-command-summary.sh",
|
|
22
22
|
"precommit": "node scripts/pre-commit.mjs",
|
|
23
23
|
"audit-state": "node scripts/audit-state.mjs",
|
|
24
|
-
"migrate-ledger": "node scripts/migrate-ledger.mjs"
|
|
25
|
-
"postinstall": "node scripts/deploy.mjs"
|
|
24
|
+
"migrate-ledger": "node scripts/migrate-ledger.mjs"
|
|
26
25
|
},
|
|
27
26
|
"type": "module",
|
|
28
27
|
"exports": {
|
|
@@ -47,8 +46,8 @@
|
|
|
47
46
|
"@opencode-ai/plugin": "^1.15.7"
|
|
48
47
|
},
|
|
49
48
|
"files": [
|
|
50
|
-
"src
|
|
51
|
-
"src
|
|
49
|
+
"src/**/*.js",
|
|
50
|
+
"src/**/*.json",
|
|
52
51
|
".opencode/plugins/vibeOS-tui.tsx",
|
|
53
52
|
"scripts/deploy.mjs",
|
|
54
53
|
"model-tiers.sample.json",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { checkFlowRules, getFlowWarns, getSessionFlowCounts, recordFlowTodo, resetForTest, } from "./vibeOS-lib/flow-enforcer.js";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"rules": [
|
|
3
|
+
{
|
|
4
|
+
"id": "new-md-file",
|
|
5
|
+
"severity": "warn",
|
|
6
|
+
"trigger": "Write",
|
|
7
|
+
"pattern": "\\.md$",
|
|
8
|
+
"description": "New markdown file created without explicit user request"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "new-file-outside-src",
|
|
12
|
+
"severity": "hint",
|
|
13
|
+
"trigger": "Write",
|
|
14
|
+
"pattern": "^(?!src/|\\.)",
|
|
15
|
+
"description": "New file created outside src/ or hidden file — verify intent"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "compat-shim",
|
|
19
|
+
"severity": "warn",
|
|
20
|
+
"trigger": "Edit",
|
|
21
|
+
"pattern": "_old|_legacy|# removed",
|
|
22
|
+
"description": "Backwards-compat shim pattern detected"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "todo-comment",
|
|
26
|
+
"severity": "hint",
|
|
27
|
+
"trigger": "Edit",
|
|
28
|
+
"pattern": "TODO|FIXME|HACK",
|
|
29
|
+
"description": "TODO/FIXME left in output"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "detect-secrets",
|
|
33
|
+
"severity": "flag",
|
|
34
|
+
"trigger": "Edit",
|
|
35
|
+
"pattern": "(?<![a-zA-Z0-9])(sk-[a-zA-Z0-9_-]{10,}|gh[pousr]_[a-zA-Z0-9]{15,}|github_pat_[a-zA-Z0-9_]{15,}|xox[bpras]-[a-zA-Z0-9-]{10,}|-----BEGIN (?:RSA|EC|OPENSSH|ENCRYPTED) PRIVATE KEY-----)",
|
|
36
|
+
"description": "Potential secret/API key detected in content"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
package/src/index.js
CHANGED
|
@@ -3625,7 +3625,7 @@ var MODEL_USD_PER_TURN = {
|
|
|
3625
3625
|
"haiku": 22e-4,
|
|
3626
3626
|
// ── DeepSeek (OC platform + OpenRouter) ──────────────────
|
|
3627
3627
|
"deepseek/deepseek-v4-pro": 57e-5,
|
|
3628
|
-
"deepseek/deepseek-v4-flash":
|
|
3628
|
+
"deepseek/deepseek-v4-flash": 182e-6,
|
|
3629
3629
|
"deepseek/deepseek-chat": 182e-6,
|
|
3630
3630
|
"deepseek-chat": 182e-6,
|
|
3631
3631
|
"deepseek/deepseek-v3": 182e-6,
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { markApiConnected, markApiDisconnected, resetApiConnection } from "./runtime-state.js";
|
|
7
|
+
const DEFAULT_API_URL = "https://api.vibetheog.com";
|
|
8
|
+
const REQUEST_TIMEOUT = 10000;
|
|
9
|
+
const MAX_RETRIES = 3;
|
|
10
|
+
const BASE_RETRY_DELAY = 1000;
|
|
11
|
+
export class VibeOSAuthError extends Error {
|
|
12
|
+
statusCode;
|
|
13
|
+
code;
|
|
14
|
+
constructor(message, statusCode, code) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "VibeOSAuthError";
|
|
17
|
+
this.statusCode = statusCode;
|
|
18
|
+
this.code = code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class VibeOSTimeoutError extends Error {
|
|
22
|
+
constructor(message) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = "VibeOSTimeoutError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class VibeOSNetworkError extends Error {
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = "VibeOSNetworkError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export class VibeOSApiClient {
|
|
34
|
+
baseUrl;
|
|
35
|
+
apiToken;
|
|
36
|
+
masterKey;
|
|
37
|
+
timeout;
|
|
38
|
+
fallbackMode;
|
|
39
|
+
fallbackStubs;
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
this.baseUrl = options.baseUrl || process.env.VIBEOS_API_URL || DEFAULT_API_URL;
|
|
42
|
+
this.apiToken = options.apiToken || process.env.VIBEOS_API_TOKEN || null;
|
|
43
|
+
this.masterKey = options.masterKey || process.env.VIBEOS_API_MASTER_KEY || null;
|
|
44
|
+
this.timeout = options.timeout || REQUEST_TIMEOUT;
|
|
45
|
+
this.fallbackMode = false;
|
|
46
|
+
this.fallbackStubs = options.fallbackStubs || null;
|
|
47
|
+
}
|
|
48
|
+
async request(path, body = null, isAdmin = false) {
|
|
49
|
+
if (!this.apiToken && !isAdmin) {
|
|
50
|
+
throw new Error("VIBEOS_API_TOKEN is not set");
|
|
51
|
+
}
|
|
52
|
+
const url = this.baseUrl + path;
|
|
53
|
+
const headers = {
|
|
54
|
+
"Content-Type": "application/json",
|
|
55
|
+
Authorization: "Bearer " + (isAdmin ? this.masterKey : this.apiToken),
|
|
56
|
+
};
|
|
57
|
+
let lastError = null;
|
|
58
|
+
let attempt = 0;
|
|
59
|
+
while (attempt <= MAX_RETRIES) {
|
|
60
|
+
if (attempt > 0) {
|
|
61
|
+
const delay = BASE_RETRY_DELAY * Math.pow(2, attempt - 1);
|
|
62
|
+
await new Promise(r => setTimeout(r, delay));
|
|
63
|
+
}
|
|
64
|
+
attempt++;
|
|
65
|
+
try {
|
|
66
|
+
const controller = new AbortController();
|
|
67
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
68
|
+
const res = await fetch(url, {
|
|
69
|
+
method: body ? "POST" : "GET",
|
|
70
|
+
headers,
|
|
71
|
+
body: body ? JSON.stringify(body) : null,
|
|
72
|
+
signal: controller.signal,
|
|
73
|
+
});
|
|
74
|
+
clearTimeout(timeoutId);
|
|
75
|
+
if (res.status === 401 || res.status === 403) {
|
|
76
|
+
const errorBody = await res.json().catch(() => ({}));
|
|
77
|
+
this.fallbackMode = true;
|
|
78
|
+
throw new VibeOSAuthError(errorBody.message || "Authentication failed", res.status, errorBody.code);
|
|
79
|
+
}
|
|
80
|
+
if (!res.ok) {
|
|
81
|
+
const errorBody = await res.json().catch(() => ({}));
|
|
82
|
+
if (res.status >= 500 && attempt <= MAX_RETRIES) {
|
|
83
|
+
lastError = new Error("API error " + res.status + ": " + (errorBody.error || res.statusText));
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
throw new Error("API error " + res.status + ": " + (errorBody.error || res.statusText));
|
|
87
|
+
}
|
|
88
|
+
this.fallbackMode = false;
|
|
89
|
+
return res.json();
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
if (err instanceof VibeOSAuthError)
|
|
93
|
+
throw err;
|
|
94
|
+
const error = err;
|
|
95
|
+
if (error.name === "AbortError") {
|
|
96
|
+
if (attempt <= MAX_RETRIES) {
|
|
97
|
+
lastError = new VibeOSTimeoutError("Request to " + url + " timed out after " + this.timeout + "ms");
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
this.fallbackMode = true;
|
|
101
|
+
throw new VibeOSTimeoutError("Request to " + url + " timed out after " + this.timeout + "ms");
|
|
102
|
+
}
|
|
103
|
+
lastError = err;
|
|
104
|
+
if (attempt <= MAX_RETRIES && error.message && (error.message.includes("fetch") || error.message.includes("network") || error.message.includes("ECONNREFUSED"))) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
this.fallbackMode = true;
|
|
110
|
+
throw new VibeOSNetworkError("Failed to reach API after " + MAX_RETRIES + " retries: " + (lastError ? lastError.message : "unknown error"));
|
|
111
|
+
}
|
|
112
|
+
async delegateCheck(tool, tier, model, prompt, dynamicCache = {}) {
|
|
113
|
+
return this.request("/api/v1/delegate/check", { tool, tier, model, prompt, dynamic_cache: dynamicCache });
|
|
114
|
+
}
|
|
115
|
+
async delegateSoftQuota(tool, currentCount, limit = 5) {
|
|
116
|
+
return this.request("/api/v1/delegate/soft-quota", { tool, current_count: currentCount, limit });
|
|
117
|
+
}
|
|
118
|
+
async delegateCost(model, dynamicCache = {}) {
|
|
119
|
+
return this.request("/api/v1/delegation/cost", { model, dynamic_cache: dynamicCache });
|
|
120
|
+
}
|
|
121
|
+
async routeModel(prompt, currentTier, trinityCheap, trinityMedium, learnedExploratory = [], stressScore = 0) {
|
|
122
|
+
return this.request("/api/v1/route/model", {
|
|
123
|
+
prompt,
|
|
124
|
+
current_tier: currentTier,
|
|
125
|
+
trinity_cheap: trinityCheap,
|
|
126
|
+
trinity_medium: trinityMedium,
|
|
127
|
+
learned_exploratory: learnedExploratory,
|
|
128
|
+
stress_score: stressScore,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async classifyTier(model, customRegex = null) {
|
|
132
|
+
return this.request("/api/v1/tier/classify", { model, custom_regex: customRegex });
|
|
133
|
+
}
|
|
134
|
+
async isExploratory(prompt, learnedExploratory = []) {
|
|
135
|
+
return this.request("/api/v1/tier/exploratory", { prompt, learned_exploratory: learnedExploratory });
|
|
136
|
+
}
|
|
137
|
+
async scoreStress(text) {
|
|
138
|
+
return this.request("/api/v1/stress/score", { text });
|
|
139
|
+
}
|
|
140
|
+
async stressLevel(score) {
|
|
141
|
+
return this.request("/api/v1/stress/level", { score });
|
|
142
|
+
}
|
|
143
|
+
async blackboxAnalyze(sessionId, entry) {
|
|
144
|
+
return this.request("/api/v1/blackbox/analyze", {
|
|
145
|
+
session_id: sessionId,
|
|
146
|
+
project_id: entry.project_id || null,
|
|
147
|
+
user_text: entry.userText || "",
|
|
148
|
+
features: entry.features || {},
|
|
149
|
+
action: entry.action || "explore",
|
|
150
|
+
entropy: entry.entropy ?? 1.0,
|
|
151
|
+
uncertainty: entry.uncertainty ?? 50,
|
|
152
|
+
embedding: entry.embedding || null,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
async blackboxState(sessionId) {
|
|
156
|
+
return this.request("/api/v1/blackbox/state", { session_id: sessionId });
|
|
157
|
+
}
|
|
158
|
+
async blackboxReset(sessionId) {
|
|
159
|
+
return this.request("/api/v1/blackbox/reset", { session_id: sessionId });
|
|
160
|
+
}
|
|
161
|
+
async blackboxOutcome(sessionId, outcome) {
|
|
162
|
+
return this.request("/api/v1/blackbox/outcome", { session_id: sessionId, outcome });
|
|
163
|
+
}
|
|
164
|
+
async blackboxCalibrate(projectId) {
|
|
165
|
+
return this.request("/api/v1/blackbox/calibrate", { project_id: projectId || "global" });
|
|
166
|
+
}
|
|
167
|
+
async blackboxCalibration(projectId) {
|
|
168
|
+
return this.request("/api/v1/blackbox/calibration?project_id=" + (projectId || "global"), null);
|
|
169
|
+
}
|
|
170
|
+
async blackboxControlVector(state, action, optimizationMode) {
|
|
171
|
+
return this.request("/api/v1/blackbox/control-vector", { ...state, action, optimization_mode: optimizationMode });
|
|
172
|
+
}
|
|
173
|
+
async blackboxSelectMode(subRegime, stressMultiplier) {
|
|
174
|
+
return this.request("/api/v1/blackbox/select-mode", { sub_regime: subRegime, stress_multiplier: stressMultiplier });
|
|
175
|
+
}
|
|
176
|
+
async tddExports(sourceContent, ext) {
|
|
177
|
+
return this.request("/api/v1/tdd/exports", { source_content: sourceContent, ext });
|
|
178
|
+
}
|
|
179
|
+
async tddParams(sourceContent, funcName) {
|
|
180
|
+
return this.request("/api/v1/tdd/params", { source_content: sourceContent, func_name: funcName });
|
|
181
|
+
}
|
|
182
|
+
async tddInferType(paramName, defaultValue) {
|
|
183
|
+
return this.request("/api/v1/tdd/infer-type", { param_name: paramName, default_value: defaultValue });
|
|
184
|
+
}
|
|
185
|
+
async tddSkeleton(language, fileName, exports, options = {}) {
|
|
186
|
+
return this.request("/api/v1/tdd/skeleton", { language, file_name: fileName, exports, options });
|
|
187
|
+
}
|
|
188
|
+
async patternsObserve(sessionId, toolName, input, output, directory) {
|
|
189
|
+
return this.request("/api/v1/patterns/observe", {
|
|
190
|
+
session_id: sessionId,
|
|
191
|
+
tool_name: toolName,
|
|
192
|
+
input,
|
|
193
|
+
output,
|
|
194
|
+
directory,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
async patternsRecord(sessionId, kind, key, summary, meta = {}) {
|
|
198
|
+
return this.request("/api/v1/patterns/record", {
|
|
199
|
+
session_id: sessionId,
|
|
200
|
+
kind,
|
|
201
|
+
key,
|
|
202
|
+
summary,
|
|
203
|
+
meta,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
async patternsQuery(sessionId, kind = null) {
|
|
207
|
+
return this.request("/api/v1/patterns/query?kind=" + (kind || ""), null);
|
|
208
|
+
}
|
|
209
|
+
async patternsExploratoryWords(sessionId) {
|
|
210
|
+
return this.request("/api/v1/patterns/exploratory-words", null);
|
|
211
|
+
}
|
|
212
|
+
async patternsClear(sessionId) {
|
|
213
|
+
return this.request("/api/v1/patterns/clear", { session_id: sessionId });
|
|
214
|
+
}
|
|
215
|
+
async pricingFetch(openrouterKey, force = false) {
|
|
216
|
+
return this.request("/api/v1/pricing/fetch", { openrouter_key: openrouterKey, force });
|
|
217
|
+
}
|
|
218
|
+
async pricingLookup(model) {
|
|
219
|
+
return this.request("/api/v1/pricing/lookup", { model });
|
|
220
|
+
}
|
|
221
|
+
async pricingStatic() {
|
|
222
|
+
return this.request("/api/v1/pricing/static", null);
|
|
223
|
+
}
|
|
224
|
+
async compressContext(text, threshold = 2000) {
|
|
225
|
+
return this.request("/api/v1/compress/context", { text, threshold });
|
|
226
|
+
}
|
|
227
|
+
async adminCreateSeat(name, email) {
|
|
228
|
+
return this.request("/admin/seats", { name, email }, true);
|
|
229
|
+
}
|
|
230
|
+
async adminCreateSeatWithToken(name, email, tokenLabel = null) {
|
|
231
|
+
return this.request("/admin/seats", { name, email, with_token: tokenLabel || true }, true);
|
|
232
|
+
}
|
|
233
|
+
async adminListSeats() {
|
|
234
|
+
return this.request("/admin/seats", null, true);
|
|
235
|
+
}
|
|
236
|
+
async adminUpdateSeat(seatId, status) {
|
|
237
|
+
return this.request("/admin/seats/" + seatId, { status }, true);
|
|
238
|
+
}
|
|
239
|
+
async adminCreateToken(seatId, label, expiresAt) {
|
|
240
|
+
return this.request("/admin/tokens", { seat_id: seatId, label, expires_at: expiresAt }, true);
|
|
241
|
+
}
|
|
242
|
+
async adminListTokens() {
|
|
243
|
+
return this.request("/admin/tokens", null, true);
|
|
244
|
+
}
|
|
245
|
+
async adminUpdateToken(tokenId, status) {
|
|
246
|
+
return this.request("/admin/tokens/" + tokenId, { status }, true);
|
|
247
|
+
}
|
|
248
|
+
async adminDeleteToken(tokenId) {
|
|
249
|
+
return this.request("/admin/tokens/" + tokenId, null, true);
|
|
250
|
+
}
|
|
251
|
+
async adminUsage(days = 30) {
|
|
252
|
+
return this.request("/admin/usage?days=" + days, null, true);
|
|
253
|
+
}
|
|
254
|
+
async health() {
|
|
255
|
+
return this.request("/health", null, false);
|
|
256
|
+
}
|
|
257
|
+
isFallback() {
|
|
258
|
+
return this.fallbackMode;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// ── Remote API client (Phase 2) ─────────────────────────────────────
|
|
262
|
+
export const VIBEOS_API_URL = process.env.VIBEOS_API_URL || "https://api.vibetheog.com";
|
|
263
|
+
const _apiDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url));
|
|
264
|
+
const _envPaths = [homedir() + "/.claude", _apiDir, process.cwd(), homedir()];
|
|
265
|
+
function readTokenFromDisk() {
|
|
266
|
+
for (const dir of _envPaths) {
|
|
267
|
+
try {
|
|
268
|
+
const env = readFileSync(dir + "/.env.production", "utf8");
|
|
269
|
+
const m = env.match(/^VIBEOS_API_TOKEN=(.+)$/m);
|
|
270
|
+
if (m)
|
|
271
|
+
return m[1].trim();
|
|
272
|
+
}
|
|
273
|
+
catch { }
|
|
274
|
+
}
|
|
275
|
+
return "";
|
|
276
|
+
}
|
|
277
|
+
export let VIBEOS_API_TOKEN = readTokenFromDisk() || process.env.VIBEOS_API_TOKEN || "";
|
|
278
|
+
export let VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && !!VIBEOS_API_TOKEN;
|
|
279
|
+
export function setApiToken(newToken) {
|
|
280
|
+
try {
|
|
281
|
+
VIBEOS_API_TOKEN = String(newToken || "").trim();
|
|
282
|
+
VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && !!VIBEOS_API_TOKEN;
|
|
283
|
+
const primaryPath = _envPaths[0] + "/.env.production";
|
|
284
|
+
try {
|
|
285
|
+
if (existsSync(primaryPath)) {
|
|
286
|
+
let envContent = readFileSync(primaryPath, "utf8");
|
|
287
|
+
if (/^VIBEOS_API_TOKEN=/m.test(envContent)) {
|
|
288
|
+
envContent = envContent.replace(/^VIBEOS_API_TOKEN=.+$/m, `VIBEOS_API_TOKEN=${VIBEOS_API_TOKEN}`);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
envContent = envContent.trimEnd() + `\nVIBEOS_API_TOKEN=${VIBEOS_API_TOKEN}\n`;
|
|
292
|
+
}
|
|
293
|
+
writeFileSync(primaryPath, envContent, "utf8");
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
const parentDir = _envPaths[0];
|
|
297
|
+
if (!existsSync(parentDir))
|
|
298
|
+
mkdirSync(parentDir, { recursive: true });
|
|
299
|
+
writeFileSync(primaryPath, `VIBEOS_API_TOKEN=${VIBEOS_API_TOKEN}\n`, "utf8");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
catch (diskErr) {
|
|
303
|
+
console.error("[vibeOS] Failed to persist API token to disk:", diskErr.message);
|
|
304
|
+
}
|
|
305
|
+
console.error("[vibeOS] API token updated via setApiToken");
|
|
306
|
+
}
|
|
307
|
+
catch (e) {
|
|
308
|
+
console.error("[vibeOS] Failed to update API token:", e.message);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
let _apiClient = null;
|
|
312
|
+
let _apiFallbackMode = false;
|
|
313
|
+
let _apiFallbackSince = null;
|
|
314
|
+
function syncApiTokenFromDisk() {
|
|
315
|
+
const diskToken = readTokenFromDisk() || "";
|
|
316
|
+
const envToken = process.env.VIBEOS_API_TOKEN || "";
|
|
317
|
+
if (diskToken && diskToken !== VIBEOS_API_TOKEN) {
|
|
318
|
+
VIBEOS_API_TOKEN = diskToken;
|
|
319
|
+
VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && !!VIBEOS_API_TOKEN;
|
|
320
|
+
_apiClient = null;
|
|
321
|
+
_apiFallbackMode = false;
|
|
322
|
+
_apiFallbackSince = null;
|
|
323
|
+
resetApiConnection();
|
|
324
|
+
console.error("[vibeOS] API token synced from disk (disk is newer)");
|
|
325
|
+
}
|
|
326
|
+
else if (!diskToken && VIBEOS_API_TOKEN) {
|
|
327
|
+
const primaryPath = _envPaths[0] + "/.env.production";
|
|
328
|
+
try {
|
|
329
|
+
if (existsSync(primaryPath)) {
|
|
330
|
+
let envContent = readFileSync(primaryPath, "utf8");
|
|
331
|
+
if (/^VIBEOS_API_TOKEN=/m.test(envContent)) {
|
|
332
|
+
envContent = envContent.replace(/^VIBEOS_API_TOKEN=.+$/m, `VIBEOS_API_TOKEN=${VIBEOS_API_TOKEN}`);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
envContent = envContent.trimEnd() + `\nVIBEOS_API_TOKEN=${VIBEOS_API_TOKEN}\n`;
|
|
336
|
+
}
|
|
337
|
+
writeFileSync(primaryPath, envContent, "utf8");
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
const parentDir = _envPaths[0];
|
|
341
|
+
if (!existsSync(parentDir))
|
|
342
|
+
mkdirSync(parentDir, { recursive: true });
|
|
343
|
+
writeFileSync(primaryPath, `VIBEOS_API_TOKEN=${VIBEOS_API_TOKEN}\n`, "utf8");
|
|
344
|
+
}
|
|
345
|
+
console.error("[vibeOS] API token persisted to disk from memory (disk was empty)");
|
|
346
|
+
}
|
|
347
|
+
catch (diskErr) {
|
|
348
|
+
console.error("[vibeOS] Failed to persist API token to disk from sync:", diskErr.message);
|
|
349
|
+
}
|
|
350
|
+
VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && !!VIBEOS_API_TOKEN;
|
|
351
|
+
}
|
|
352
|
+
else if (envToken && !diskToken && !VIBEOS_API_TOKEN) {
|
|
353
|
+
VIBEOS_API_TOKEN = envToken;
|
|
354
|
+
VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && !!VIBEOS_API_TOKEN;
|
|
355
|
+
console.error("[vibeOS] API token loaded from VIBEOS_API_TOKEN env var");
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && !!VIBEOS_API_TOKEN;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
export function getApiClient() {
|
|
362
|
+
syncApiTokenFromDisk();
|
|
363
|
+
if (!_apiClient && VIBEOS_API_ENABLED) {
|
|
364
|
+
_apiClient = new VibeOSApiClient({
|
|
365
|
+
baseUrl: VIBEOS_API_URL,
|
|
366
|
+
apiToken: VIBEOS_API_TOKEN,
|
|
367
|
+
timeout: 5000,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
return _apiClient;
|
|
371
|
+
}
|
|
372
|
+
export function isApiFallback() {
|
|
373
|
+
return _apiFallbackMode || !VIBEOS_API_ENABLED;
|
|
374
|
+
}
|
|
375
|
+
export function isApiConnected() {
|
|
376
|
+
return VIBEOS_API_ENABLED && !_apiFallbackMode;
|
|
377
|
+
}
|
|
378
|
+
export async function remoteCall(method, args, fallbackFn) {
|
|
379
|
+
syncApiTokenFromDisk();
|
|
380
|
+
if (!VIBEOS_API_ENABLED || _apiFallbackMode) {
|
|
381
|
+
if (fallbackFn)
|
|
382
|
+
return fallbackFn();
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
const client = getApiClient();
|
|
387
|
+
if (!client) {
|
|
388
|
+
if (fallbackFn)
|
|
389
|
+
return fallbackFn();
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
const result = await client[method](...args);
|
|
393
|
+
_apiFallbackMode = false;
|
|
394
|
+
_apiFallbackSince = null;
|
|
395
|
+
markApiConnected();
|
|
396
|
+
return result;
|
|
397
|
+
}
|
|
398
|
+
catch (err) {
|
|
399
|
+
if (!_apiFallbackMode) {
|
|
400
|
+
_apiFallbackMode = true;
|
|
401
|
+
_apiFallbackSince = new Date().toISOString();
|
|
402
|
+
console.error(`[vibeOS] API fallback activated: ${err.message}`);
|
|
403
|
+
}
|
|
404
|
+
markApiDisconnected();
|
|
405
|
+
if (fallbackFn) {
|
|
406
|
+
try {
|
|
407
|
+
return fallbackFn();
|
|
408
|
+
}
|
|
409
|
+
catch (fe) {
|
|
410
|
+
console.error(`[vibeOS] fallback also failed: ${fe.message}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
}
|