ocuclaw 0.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -8
- package/dist/config/runtime-config.js +81 -3
- package/dist/domain/activity-status-adapter.js +138 -605
- package/dist/domain/activity-status-arbiter.js +109 -0
- package/dist/domain/activity-status-labels.js +906 -0
- package/dist/domain/code-span-regions.js +103 -0
- package/dist/domain/conversation-state.js +14 -1
- package/dist/domain/debug-store.js +41 -184
- package/dist/domain/glasses-ui-content-summary.js +62 -0
- package/dist/domain/glasses-ui-system-prompt.js +28 -0
- package/dist/domain/message-emoji-allowlist.js +16 -0
- package/dist/domain/message-emoji-filter.js +33 -55
- package/dist/domain/neural-emoji-reactor-system-prompt.js +43 -0
- package/dist/domain/neural-emoji-reactor-tag-config.js +56 -0
- package/dist/domain/neural-pace-modulator-system-prompt.js +32 -0
- package/dist/domain/neural-pace-modulator-tag-config.js +51 -0
- package/dist/domain/tagged-span-parser.js +121 -0
- package/dist/domain/tagged-span-strip.js +38 -0
- package/dist/even-ai/even-ai-endpoint.js +91 -0
- package/dist/even-ai/even-ai-run-waiter.js +14 -0
- package/dist/even-ai/even-ai-settings-store.js +14 -0
- package/dist/gateway/gateway-bridge.js +14 -2
- package/dist/gateway/gateway-timing-ledger.js +457 -0
- package/dist/gateway/openclaw-client.js +462 -38
- package/dist/index.js +28 -1
- package/dist/runtime/downstream-handler.js +909 -68
- package/dist/runtime/downstream-server.js +1004 -512
- package/dist/runtime/ocuclaw-settings-store.js +74 -31
- package/dist/runtime/plugin-update-service.js +216 -0
- package/dist/runtime/protocol-adapter.js +9 -0
- package/dist/runtime/provider-usage-select.js +168 -0
- package/dist/runtime/relay-client-nudge-controller.js +553 -0
- package/dist/runtime/relay-core.js +1357 -210
- package/dist/runtime/relay-health-monitor.js +172 -0
- package/dist/runtime/relay-operation-registry.js +263 -0
- package/dist/runtime/relay-service.js +201 -1
- package/dist/runtime/relay-worker-approval-replay-cache.js +68 -0
- package/dist/runtime/relay-worker-entry.js +32 -0
- package/dist/runtime/relay-worker-health.js +272 -0
- package/dist/runtime/relay-worker-protocol.js +285 -0
- package/dist/runtime/relay-worker-queue.js +202 -0
- package/dist/runtime/relay-worker-supervisor.js +1081 -0
- package/dist/runtime/relay-worker-transport.js +1051 -0
- package/dist/runtime/session-context-service.js +189 -0
- package/dist/runtime/session-service.js +656 -38
- package/dist/runtime/upstream-runtime.js +1167 -60
- package/dist/tools/device-info-tool.js +242 -0
- package/dist/tools/glasses-ui-cron.js +427 -0
- package/dist/tools/glasses-ui-descriptors.js +261 -0
- package/dist/tools/glasses-ui-limits.js +21 -0
- package/dist/tools/glasses-ui-paint-floor.js +99 -0
- package/dist/tools/glasses-ui-recipes.js +746 -0
- package/dist/tools/glasses-ui-surfaces.js +278 -0
- package/dist/tools/glasses-ui-template.js +182 -0
- package/dist/tools/glasses-ui-tool.js +1147 -0
- package/dist/tools/session-title-tool.js +209 -0
- package/dist/version.js +2 -0
- package/openclaw.plugin.json +163 -15
- package/package.json +12 -4
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// session-context-service.ts
|
|
2
|
+
//
|
|
3
|
+
// Owns the active-session "context tokens vs context window" snapshot
|
|
4
|
+
// lifecycle. Reads via gateway sessions.describe; reads compaction
|
|
5
|
+
// checkpoint count via sessions.compaction.list; broadcasts on changes.
|
|
6
|
+
//
|
|
7
|
+
// Field mapping (gateway → snapshot):
|
|
8
|
+
// session.totalTokens → contextTokens (current cumulative usage)
|
|
9
|
+
// session.contextTokens → contextWindow (model's configured window)
|
|
10
|
+
// openclaw uses `contextTokens` on the row for window size; `totalTokens`
|
|
11
|
+
// is the running usage. NOTE: the window is NOT resolved on a fresh session —
|
|
12
|
+
// the gateway warms a model's window only after the first turn (sessions.describe
|
|
13
|
+
// returns no window pre-turn). So we cache the observed window per model and
|
|
14
|
+
// reuse it for subsequent cold/new-session reads. The cache is persisted under
|
|
15
|
+
// `stateDir` (keyed by provider/model) so it survives relay restarts.
|
|
16
|
+
|
|
17
|
+
import * as fs from "node:fs";
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
|
|
20
|
+
const MODEL_CONTEXT_WINDOW_CACHE_FILE = "ocuclaw-model-context-windows.json";
|
|
21
|
+
|
|
22
|
+
function normalizeStateDir(stateDir) {
|
|
23
|
+
if (typeof stateDir !== "string") return null;
|
|
24
|
+
const trimmed = stateDir.trim();
|
|
25
|
+
return trimmed ? trimmed : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resolveModelContextWindowCachePath(stateDir) {
|
|
29
|
+
const resolved = normalizeStateDir(stateDir);
|
|
30
|
+
return resolved ? path.join(resolved, MODEL_CONTEXT_WINDOW_CACHE_FILE) : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function loadModelContextWindowCache(cachePath) {
|
|
34
|
+
const cache = new Map();
|
|
35
|
+
if (!cachePath) return cache;
|
|
36
|
+
try {
|
|
37
|
+
if (!fs.existsSync(cachePath)) return cache;
|
|
38
|
+
const parsed = JSON.parse(fs.readFileSync(cachePath, "utf8"));
|
|
39
|
+
for (const [key, value] of Object.entries(parsed ?? {})) {
|
|
40
|
+
if (typeof key === "string" && Number.isFinite(value) && value > 0) {
|
|
41
|
+
cache.set(key, Math.floor(value));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
// Missing/corrupt cache file — relearn from the next turn.
|
|
46
|
+
}
|
|
47
|
+
return cache;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function persistModelContextWindowCache(cachePath, cache) {
|
|
51
|
+
if (!cachePath) return;
|
|
52
|
+
try {
|
|
53
|
+
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
54
|
+
const obj = {};
|
|
55
|
+
for (const [key, value] of cache.entries()) obj[key] = value;
|
|
56
|
+
fs.writeFileSync(cachePath, JSON.stringify(obj), "utf8");
|
|
57
|
+
} catch {
|
|
58
|
+
// Best-effort persistence; the in-memory cache still works this process.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @typedef SessionContextSnapshot
|
|
64
|
+
* @property {string} type
|
|
65
|
+
* @property {string} sessionKey
|
|
66
|
+
* @property {number} contextTokens
|
|
67
|
+
* @property {number} contextWindow
|
|
68
|
+
* @property {number} compactionCount
|
|
69
|
+
* @property {boolean} runActive
|
|
70
|
+
* @property {number} snapshotAtMs
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {object} opts
|
|
75
|
+
* @param {{request: (method: string, params: object) => Promise<any>}} opts.gatewayBridge
|
|
76
|
+
* @param {() => string|null} opts.getActiveSessionKey
|
|
77
|
+
* @param {() => boolean} opts.getRunActive
|
|
78
|
+
* @param {() => number} opts.nowMs
|
|
79
|
+
* @param {(frame: SessionContextSnapshot) => void} opts.broadcast
|
|
80
|
+
*/
|
|
81
|
+
export function createSessionContextService(opts) {
|
|
82
|
+
const gatewayBridge = opts.gatewayBridge;
|
|
83
|
+
const getActiveSessionKey = opts.getActiveSessionKey;
|
|
84
|
+
const getRunActive = opts.getRunActive;
|
|
85
|
+
const nowMs = opts.nowMs;
|
|
86
|
+
const broadcast = opts.broadcast;
|
|
87
|
+
const getActiveModelKey =
|
|
88
|
+
typeof opts.getActiveModelKey === "function" ? opts.getActiveModelKey : () => null;
|
|
89
|
+
|
|
90
|
+
/** @type {SessionContextSnapshot|null} */
|
|
91
|
+
let lastSnapshot = null;
|
|
92
|
+
|
|
93
|
+
// Per-model context-window cache: the gateway only resolves a model's window
|
|
94
|
+
// after the first turn warms its discovery, so once we observe a real window
|
|
95
|
+
// we remember it per model and reuse it for later cold/new-session reads.
|
|
96
|
+
// Persisted under stateDir so it survives relay restarts.
|
|
97
|
+
const modelContextWindowCachePath = resolveModelContextWindowCachePath(opts.stateDir);
|
|
98
|
+
/** @type {Map<string, number>} */
|
|
99
|
+
const modelContextWindowCache = loadModelContextWindowCache(modelContextWindowCachePath);
|
|
100
|
+
|
|
101
|
+
async function refreshActiveSessionContext() {
|
|
102
|
+
const sessionKey = getActiveSessionKey();
|
|
103
|
+
if (!sessionKey) return null;
|
|
104
|
+
let describeResp;
|
|
105
|
+
let compactionResp;
|
|
106
|
+
try {
|
|
107
|
+
[describeResp, compactionResp] = await Promise.all([
|
|
108
|
+
gatewayBridge.request("sessions.describe", { key: sessionKey }),
|
|
109
|
+
gatewayBridge
|
|
110
|
+
.request("sessions.compaction.list", { key: sessionKey })
|
|
111
|
+
.catch(() => null),
|
|
112
|
+
]);
|
|
113
|
+
} catch {
|
|
114
|
+
return lastSnapshot;
|
|
115
|
+
}
|
|
116
|
+
const session =
|
|
117
|
+
describeResp && typeof describeResp === "object" && describeResp.session && typeof describeResp.session === "object"
|
|
118
|
+
? describeResp.session
|
|
119
|
+
: null;
|
|
120
|
+
if (!session) return lastSnapshot;
|
|
121
|
+
|
|
122
|
+
const contextTokens = Number.isFinite(session.totalTokens)
|
|
123
|
+
? Math.floor(session.totalTokens)
|
|
124
|
+
: 0;
|
|
125
|
+
const describeWindow = Number.isFinite(session.contextTokens)
|
|
126
|
+
? Math.floor(session.contextTokens)
|
|
127
|
+
: 0;
|
|
128
|
+
const modelKey = getActiveModelKey();
|
|
129
|
+
let contextWindow = describeWindow;
|
|
130
|
+
if (describeWindow > 0) {
|
|
131
|
+
// Learned naturally (post-turn): remember (and persist) for future cold reads.
|
|
132
|
+
if (modelKey && modelContextWindowCache.get(modelKey) !== describeWindow) {
|
|
133
|
+
modelContextWindowCache.set(modelKey, describeWindow);
|
|
134
|
+
persistModelContextWindowCache(modelContextWindowCachePath, modelContextWindowCache);
|
|
135
|
+
}
|
|
136
|
+
} else if (modelKey && modelContextWindowCache.has(modelKey)) {
|
|
137
|
+
// Cold on a new session: reuse the window learned earlier for this model.
|
|
138
|
+
contextWindow = modelContextWindowCache.get(modelKey);
|
|
139
|
+
}
|
|
140
|
+
const checkpoints =
|
|
141
|
+
compactionResp && Array.isArray(compactionResp.checkpoints)
|
|
142
|
+
? compactionResp.checkpoints
|
|
143
|
+
: [];
|
|
144
|
+
const compactionCount = checkpoints.length;
|
|
145
|
+
|
|
146
|
+
const snapshot = {
|
|
147
|
+
type: "ocuclaw.session.context.snapshot",
|
|
148
|
+
sessionKey,
|
|
149
|
+
contextTokens,
|
|
150
|
+
contextWindow,
|
|
151
|
+
compactionCount,
|
|
152
|
+
runActive: !!getRunActive(),
|
|
153
|
+
snapshotAtMs: nowMs(),
|
|
154
|
+
};
|
|
155
|
+
lastSnapshot = snapshot;
|
|
156
|
+
broadcast(snapshot);
|
|
157
|
+
return snapshot;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function broadcastRunActive(runActive) {
|
|
161
|
+
if (!lastSnapshot) return;
|
|
162
|
+
const snapshot = { ...lastSnapshot, runActive: !!runActive, snapshotAtMs: nowMs() };
|
|
163
|
+
lastSnapshot = snapshot;
|
|
164
|
+
broadcast(snapshot);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function compactActiveSession(sessionKey) {
|
|
168
|
+
try {
|
|
169
|
+
await gatewayBridge.request("sessions.compact", { key: sessionKey });
|
|
170
|
+
return { status: "accepted" };
|
|
171
|
+
} catch (err) {
|
|
172
|
+
return {
|
|
173
|
+
status: "rejected",
|
|
174
|
+
error: err && err.message ? String(err.message) : "sessions.compact failed",
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function lastSnapshotForResume() {
|
|
180
|
+
return lastSnapshot;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
refreshActiveSessionContext,
|
|
185
|
+
broadcastRunActive,
|
|
186
|
+
compactActiveSession,
|
|
187
|
+
lastSnapshotForResume,
|
|
188
|
+
};
|
|
189
|
+
}
|