nemoris 0.1.0 → 0.1.2
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/.env.example +49 -49
- package/LICENSE +21 -21
- package/README.md +209 -209
- package/SECURITY.md +59 -119
- package/bin/nemoris +46 -46
- package/config/agents/agent.toml.example +28 -28
- package/config/agents/content.toml +23 -0
- package/config/agents/default.toml +22 -22
- package/config/agents/heartbeat.toml +35 -0
- package/config/agents/iris.toml +23 -0
- package/config/agents/lab.toml +23 -0
- package/config/agents/main.toml +45 -0
- package/config/agents/nemo.toml +21 -0
- package/config/agents/ops.toml +38 -0
- package/config/agents/orchestrator.toml +18 -18
- package/config/agents/revenue.toml +23 -0
- package/config/agents/testyboo.toml +19 -0
- package/config/delivery.toml +73 -73
- package/config/embeddings.toml +5 -5
- package/config/identity/content-purpose.md +11 -0
- package/config/identity/content-soul.md +45 -0
- package/config/identity/default-purpose.md +1 -1
- package/config/identity/default-soul.md +3 -3
- package/config/identity/heartbeat-purpose.md +9 -0
- package/config/identity/heartbeat-soul.md +16 -0
- package/config/identity/iris-purpose.md +17 -0
- package/config/identity/iris-soul.md +68 -0
- package/config/identity/lab-purpose.md +10 -0
- package/config/identity/lab-soul.md +38 -0
- package/config/identity/main-purpose.md +17 -0
- package/config/identity/main-soul.md +66 -0
- package/config/identity/main-user.md +22 -0
- package/config/identity/ops-purpose.md +9 -0
- package/config/identity/ops-soul.md +16 -0
- package/config/identity/orchestrator-purpose.md +1 -1
- package/config/identity/orchestrator-soul.md +1 -1
- package/config/identity/revenue-purpose.md +9 -0
- package/config/identity/revenue-soul.md +41 -0
- package/config/identity/testyboo-purpose.md +13 -0
- package/config/identity/testyboo-soul.md +20 -0
- package/config/improvement-targets.toml +15 -15
- package/config/jobs/heartbeat-check.toml +30 -30
- package/config/jobs/memory-rollup.toml +46 -46
- package/config/jobs/workspace-health.toml +63 -63
- package/config/mcp.toml +16 -16
- package/config/output-contracts.toml +17 -17
- package/config/peers.toml +32 -32
- package/config/peers.toml.example +32 -32
- package/config/policies/memory-default.toml +10 -10
- package/config/policies/memory-heartbeat.toml +5 -5
- package/config/policies/memory-ops.toml +10 -10
- package/config/policies/tools-heartbeat-minimal.toml +8 -8
- package/config/policies/tools-interactive-safe.toml +8 -8
- package/config/policies/tools-ops-bounded.toml +8 -8
- package/config/policies/tools-orchestrator.toml +7 -7
- package/config/providers/anthropic.toml +15 -15
- package/config/providers/ollama.toml +5 -5
- package/config/providers/openai-codex.toml +9 -9
- package/config/providers/openrouter.toml +5 -5
- package/config/router.toml +22 -22
- package/config/runtime.toml +114 -114
- package/config/skills/self-improvement.toml +15 -15
- package/config/skills/telegram-onboarding-spec.md +240 -240
- package/config/skills/workspace-monitor.toml +15 -15
- package/config/task-router.toml +42 -42
- package/install.sh +50 -50
- package/package.json +91 -90
- package/src/auth/auth-profiles.js +169 -169
- package/src/auth/openai-codex-oauth.js +285 -285
- package/src/battle.js +449 -449
- package/src/cli/help.js +265 -265
- package/src/cli/output-filter.js +49 -49
- package/src/cli/runtime-control.js +704 -704
- package/src/cli-main.js +2763 -2763
- package/src/cli.js +78 -78
- package/src/config/loader.js +332 -332
- package/src/config/schema-validator.js +214 -214
- package/src/config/toml-lite.js +8 -8
- package/src/daemon/action-handlers.js +71 -71
- package/src/daemon/healing-tick.js +87 -87
- package/src/daemon/health-probes.js +90 -90
- package/src/daemon/notifier.js +57 -57
- package/src/daemon/nurse.js +218 -218
- package/src/daemon/repair-log.js +106 -106
- package/src/daemon/rule-staging.js +90 -90
- package/src/daemon/rules.js +29 -29
- package/src/daemon/telegram-commands.js +54 -54
- package/src/daemon/updater.js +85 -85
- package/src/jobs/job-runner.js +78 -78
- package/src/mcp/consumer.js +129 -129
- package/src/memory/active-recall.js +171 -171
- package/src/memory/backend-manager.js +97 -97
- package/src/memory/backends/file-backend.js +38 -38
- package/src/memory/backends/qmd-backend.js +219 -219
- package/src/memory/embedding-guards.js +24 -24
- package/src/memory/embedding-index.js +118 -118
- package/src/memory/embedding-service.js +179 -179
- package/src/memory/file-index.js +177 -177
- package/src/memory/memory-signature.js +5 -5
- package/src/memory/memory-store.js +648 -648
- package/src/memory/retrieval-planner.js +66 -66
- package/src/memory/scoring.js +145 -145
- package/src/memory/simhash.js +78 -78
- package/src/memory/sqlite-active-store.js +824 -824
- package/src/memory/write-policy.js +36 -36
- package/src/onboarding/aliases.js +33 -33
- package/src/onboarding/auth/api-key.js +224 -224
- package/src/onboarding/auth/ollama-detect.js +42 -42
- package/src/onboarding/clack-prompter.js +77 -77
- package/src/onboarding/doctor.js +530 -530
- package/src/onboarding/lock.js +42 -42
- package/src/onboarding/model-catalog.js +344 -344
- package/src/onboarding/phases/auth.js +576 -589
- package/src/onboarding/phases/build.js +130 -130
- package/src/onboarding/phases/choose.js +82 -82
- package/src/onboarding/phases/detect.js +98 -98
- package/src/onboarding/phases/hatch.js +216 -216
- package/src/onboarding/phases/identity.js +79 -79
- package/src/onboarding/phases/ollama.js +345 -345
- package/src/onboarding/phases/scaffold.js +99 -99
- package/src/onboarding/phases/telegram.js +377 -377
- package/src/onboarding/phases/validate.js +204 -204
- package/src/onboarding/phases/verify.js +206 -206
- package/src/onboarding/platform.js +482 -482
- package/src/onboarding/status-bar.js +95 -95
- package/src/onboarding/templates.js +794 -794
- package/src/onboarding/toml-writer.js +38 -38
- package/src/onboarding/tui.js +250 -250
- package/src/onboarding/uninstall.js +153 -153
- package/src/onboarding/wizard.js +516 -499
- package/src/providers/anthropic.js +168 -168
- package/src/providers/base.js +247 -247
- package/src/providers/circuit-breaker.js +136 -136
- package/src/providers/ollama.js +163 -163
- package/src/providers/openai-codex.js +149 -149
- package/src/providers/openrouter.js +136 -136
- package/src/providers/registry.js +36 -36
- package/src/providers/router.js +16 -16
- package/src/runtime/bootstrap-cache.js +47 -47
- package/src/runtime/capabilities-prompt.js +25 -25
- package/src/runtime/completion-ping.js +99 -99
- package/src/runtime/config-validator.js +121 -121
- package/src/runtime/context-ledger.js +360 -360
- package/src/runtime/cutover-readiness.js +42 -42
- package/src/runtime/daemon.js +729 -729
- package/src/runtime/delivery-ack.js +195 -195
- package/src/runtime/delivery-adapters/local-file.js +41 -41
- package/src/runtime/delivery-adapters/openclaw-cli.js +94 -94
- package/src/runtime/delivery-adapters/openclaw-peer.js +98 -98
- package/src/runtime/delivery-adapters/shadow.js +13 -13
- package/src/runtime/delivery-adapters/standalone-http.js +98 -98
- package/src/runtime/delivery-adapters/telegram.js +104 -104
- package/src/runtime/delivery-adapters/tui.js +128 -128
- package/src/runtime/delivery-manager.js +807 -807
- package/src/runtime/delivery-store.js +168 -168
- package/src/runtime/dependency-health.js +118 -118
- package/src/runtime/envelope.js +114 -114
- package/src/runtime/evaluation.js +1089 -1089
- package/src/runtime/exec-approvals.js +216 -216
- package/src/runtime/executor.js +500 -500
- package/src/runtime/failure-ping.js +67 -67
- package/src/runtime/flows.js +83 -83
- package/src/runtime/guards.js +45 -45
- package/src/runtime/handoff.js +51 -51
- package/src/runtime/identity-cache.js +28 -28
- package/src/runtime/improvement-engine.js +109 -109
- package/src/runtime/improvement-harness.js +581 -581
- package/src/runtime/input-sanitiser.js +72 -72
- package/src/runtime/interaction-contract.js +347 -347
- package/src/runtime/lane-readiness.js +226 -226
- package/src/runtime/migration.js +323 -323
- package/src/runtime/model-resolution.js +78 -78
- package/src/runtime/network.js +64 -64
- package/src/runtime/notification-store.js +97 -97
- package/src/runtime/notifier.js +256 -256
- package/src/runtime/orchestrator.js +53 -53
- package/src/runtime/orphan-reaper.js +41 -41
- package/src/runtime/output-contract-schema.js +139 -139
- package/src/runtime/output-contract-validator.js +439 -439
- package/src/runtime/peer-readiness.js +69 -69
- package/src/runtime/peer-registry.js +133 -133
- package/src/runtime/pilot-status.js +108 -108
- package/src/runtime/prompt-builder.js +261 -261
- package/src/runtime/provider-attempt.js +582 -582
- package/src/runtime/report-fallback.js +71 -71
- package/src/runtime/result-normalizer.js +183 -183
- package/src/runtime/retention.js +74 -74
- package/src/runtime/review.js +244 -244
- package/src/runtime/route-job.js +15 -15
- package/src/runtime/run-store.js +38 -38
- package/src/runtime/schedule.js +88 -88
- package/src/runtime/scheduler-state.js +434 -434
- package/src/runtime/scheduler.js +656 -656
- package/src/runtime/session-compactor.js +182 -182
- package/src/runtime/session-search.js +155 -155
- package/src/runtime/slack-inbound.js +249 -249
- package/src/runtime/ssrf.js +102 -102
- package/src/runtime/status-aggregator.js +330 -330
- package/src/runtime/task-contract.js +140 -140
- package/src/runtime/task-packet.js +107 -107
- package/src/runtime/task-router.js +140 -140
- package/src/runtime/telegram-inbound.js +1565 -1565
- package/src/runtime/token-counter.js +134 -134
- package/src/runtime/token-estimator.js +59 -59
- package/src/runtime/tool-loop.js +200 -200
- package/src/runtime/transport-server.js +311 -311
- package/src/runtime/tui-server.js +411 -411
- package/src/runtime/ulid.js +44 -44
- package/src/security/ssrf-check.js +197 -197
- package/src/setup.js +369 -369
- package/src/shadow/bridge.js +303 -303
- package/src/skills/loader.js +84 -84
- package/src/tools/catalog.json +49 -49
- package/src/tools/cli-delegate.js +44 -44
- package/src/tools/mcp-client.js +106 -106
- package/src/tools/micro/cancel-task.js +6 -6
- package/src/tools/micro/complete-task.js +6 -6
- package/src/tools/micro/fail-task.js +6 -6
- package/src/tools/micro/http-fetch.js +74 -74
- package/src/tools/micro/index.js +36 -36
- package/src/tools/micro/lcm-recall.js +60 -60
- package/src/tools/micro/list-dir.js +17 -17
- package/src/tools/micro/list-skills.js +46 -46
- package/src/tools/micro/load-skill.js +38 -38
- package/src/tools/micro/memory-search.js +45 -45
- package/src/tools/micro/read-file.js +11 -11
- package/src/tools/micro/session-search.js +54 -54
- package/src/tools/micro/shell-exec.js +43 -43
- package/src/tools/micro/trigger-job.js +79 -79
- package/src/tools/micro/web-search.js +58 -58
- package/src/tools/micro/workspace-paths.js +39 -39
- package/src/tools/micro/write-file.js +14 -14
- package/src/tools/micro/write-memory.js +41 -41
- package/src/tools/registry.js +348 -348
- package/src/tools/tool-result-contract.js +36 -36
- package/src/tui/chat.js +835 -835
- package/src/tui/renderer.js +175 -175
- package/src/tui/socket-client.js +217 -217
- package/src/utils/canonical-json.js +29 -29
- package/src/utils/compaction.js +30 -30
- package/src/utils/env-loader.js +5 -5
- package/src/utils/errors.js +80 -80
- package/src/utils/fs.js +101 -101
- package/src/utils/ids.js +5 -5
- package/src/utils/model-context-limits.js +30 -30
- package/src/utils/token-budget.js +74 -74
- package/src/utils/usage-cost.js +25 -25
- package/src/utils/usage-metrics.js +14 -14
package/src/tui/socket-client.js
CHANGED
|
@@ -1,217 +1,217 @@
|
|
|
1
|
-
import net from "node:net";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { EventEmitter } from "node:events";
|
|
5
|
-
|
|
6
|
-
export function resolveSocketPath({ platform = process.platform, stateDir, homedir = os.homedir } = {}) {
|
|
7
|
-
if (platform === "win32") {
|
|
8
|
-
return "\\\\.\\pipe\\nemoris-daemon";
|
|
9
|
-
}
|
|
10
|
-
const root = stateDir || path.join(homedir(), ".nemoris", "state");
|
|
11
|
-
return path.join(root, "daemon.sock");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function getReconnectDelay(attempt, { baseMs = 1000, maxMs = 30000 } = {}) {
|
|
15
|
-
return Math.min(maxMs, baseMs * (2 ** Math.max(0, attempt)));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function parseNdjsonChunk(buffer = "", chunk = "") {
|
|
19
|
-
const input = `${buffer}${chunk}`;
|
|
20
|
-
const lines = input.split("\n");
|
|
21
|
-
const nextBuffer = lines.pop() || "";
|
|
22
|
-
const messages = [];
|
|
23
|
-
const errors = [];
|
|
24
|
-
|
|
25
|
-
for (const rawLine of lines) {
|
|
26
|
-
const line = rawLine.trim();
|
|
27
|
-
if (!line) {
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
try {
|
|
31
|
-
messages.push(JSON.parse(line));
|
|
32
|
-
} catch (error) {
|
|
33
|
-
errors.push(error);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return { buffer: nextBuffer, messages, errors };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function emitSafeError(target, error) {
|
|
41
|
-
if (target.listenerCount("error") > 0) {
|
|
42
|
-
target.emit("error", error);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export class TuiSocketClient extends EventEmitter {
|
|
47
|
-
constructor({
|
|
48
|
-
socketPath,
|
|
49
|
-
stateDir,
|
|
50
|
-
platform = process.platform,
|
|
51
|
-
homedir = os.homedir,
|
|
52
|
-
createConnection = net.createConnection,
|
|
53
|
-
reconnectBaseMs = 1000,
|
|
54
|
-
reconnectMaxMs = 30000,
|
|
55
|
-
keepaliveMs = 30000,
|
|
56
|
-
autoReconnect = true,
|
|
57
|
-
setTimeoutFn = setTimeout,
|
|
58
|
-
clearTimeoutFn = clearTimeout,
|
|
59
|
-
setIntervalFn = setInterval,
|
|
60
|
-
clearIntervalFn = clearInterval,
|
|
61
|
-
} = {}) {
|
|
62
|
-
super();
|
|
63
|
-
this.socketPath = socketPath || resolveSocketPath({ platform, stateDir, homedir });
|
|
64
|
-
this.createConnection = createConnection;
|
|
65
|
-
this.reconnectBaseMs = reconnectBaseMs;
|
|
66
|
-
this.reconnectMaxMs = reconnectMaxMs;
|
|
67
|
-
this.keepaliveMs = keepaliveMs;
|
|
68
|
-
this.autoReconnect = autoReconnect;
|
|
69
|
-
this.setTimeoutFn = setTimeoutFn;
|
|
70
|
-
this.clearTimeoutFn = clearTimeoutFn;
|
|
71
|
-
this.setIntervalFn = setIntervalFn;
|
|
72
|
-
this.clearIntervalFn = clearIntervalFn;
|
|
73
|
-
|
|
74
|
-
this.socket = null;
|
|
75
|
-
this.buffer = "";
|
|
76
|
-
this.connected = false;
|
|
77
|
-
this.closed = false;
|
|
78
|
-
this.reconnectAttempt = 0;
|
|
79
|
-
this.reconnectTimer = null;
|
|
80
|
-
this.keepaliveTimer = null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async connect() {
|
|
84
|
-
this.closed = false;
|
|
85
|
-
return new Promise((resolve, reject) => {
|
|
86
|
-
let settled = false;
|
|
87
|
-
const cleanup = () => {
|
|
88
|
-
this.off("connected", onConnected);
|
|
89
|
-
this.off("error", onError);
|
|
90
|
-
};
|
|
91
|
-
const onConnected = () => {
|
|
92
|
-
if (settled) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
settled = true;
|
|
96
|
-
cleanup();
|
|
97
|
-
resolve(this);
|
|
98
|
-
};
|
|
99
|
-
const onError = (error) => {
|
|
100
|
-
if (settled) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
settled = true;
|
|
104
|
-
cleanup();
|
|
105
|
-
reject(error);
|
|
106
|
-
};
|
|
107
|
-
this.once("connected", onConnected);
|
|
108
|
-
this.once("error", onError);
|
|
109
|
-
this._openSocket();
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
_openSocket() {
|
|
114
|
-
if (this.closed) {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
this.buffer = "";
|
|
118
|
-
const socket = this.createConnection(this.socketPath);
|
|
119
|
-
this.socket = socket;
|
|
120
|
-
socket.setEncoding?.("utf8");
|
|
121
|
-
|
|
122
|
-
socket.on("connect", () => {
|
|
123
|
-
this.connected = true;
|
|
124
|
-
this.reconnectAttempt = 0;
|
|
125
|
-
this._clearReconnectTimer();
|
|
126
|
-
this._startKeepalive();
|
|
127
|
-
this.emit("connected");
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
socket.on("data", (chunk) => {
|
|
131
|
-
const parsed = parseNdjsonChunk(this.buffer, chunk);
|
|
132
|
-
this.buffer = parsed.buffer;
|
|
133
|
-
for (const message of parsed.messages) {
|
|
134
|
-
this.emit("message", message);
|
|
135
|
-
}
|
|
136
|
-
for (const error of parsed.errors) {
|
|
137
|
-
emitSafeError(this, error);
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
socket.on("error", (error) => {
|
|
142
|
-
emitSafeError(this, error);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
socket.on("close", () => {
|
|
146
|
-
const wasConnected = this.connected;
|
|
147
|
-
this.connected = false;
|
|
148
|
-
this._stopKeepalive();
|
|
149
|
-
this.emit("disconnected");
|
|
150
|
-
if (!this.closed && this.autoReconnect) {
|
|
151
|
-
this._scheduleReconnect();
|
|
152
|
-
}
|
|
153
|
-
if (!wasConnected && this.closed) {
|
|
154
|
-
this.buffer = "";
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
_scheduleReconnect() {
|
|
160
|
-
if (this.reconnectTimer || this.closed) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
const delay = getReconnectDelay(this.reconnectAttempt, {
|
|
164
|
-
baseMs: this.reconnectBaseMs,
|
|
165
|
-
maxMs: this.reconnectMaxMs,
|
|
166
|
-
});
|
|
167
|
-
this.reconnectAttempt += 1;
|
|
168
|
-
this.reconnectTimer = this.setTimeoutFn(() => {
|
|
169
|
-
this.reconnectTimer = null;
|
|
170
|
-
this._openSocket();
|
|
171
|
-
}, delay);
|
|
172
|
-
return delay;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
_clearReconnectTimer() {
|
|
176
|
-
if (!this.reconnectTimer) {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
this.clearTimeoutFn(this.reconnectTimer);
|
|
180
|
-
this.reconnectTimer = null;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
_startKeepalive() {
|
|
184
|
-
this._stopKeepalive();
|
|
185
|
-
this.keepaliveTimer = this.setIntervalFn(() => {
|
|
186
|
-
this.send({ type: "ping" });
|
|
187
|
-
}, this.keepaliveMs);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
_stopKeepalive() {
|
|
191
|
-
if (!this.keepaliveTimer) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
this.clearIntervalFn(this.keepaliveTimer);
|
|
195
|
-
this.keepaliveTimer = null;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
send(payload) {
|
|
199
|
-
if (!this.socket || this.socket.destroyed || !this.socket.writable) {
|
|
200
|
-
return false;
|
|
201
|
-
}
|
|
202
|
-
this.socket.write(`${JSON.stringify(payload)}\n`);
|
|
203
|
-
return true;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
close() {
|
|
207
|
-
this.closed = true;
|
|
208
|
-
this.connected = false;
|
|
209
|
-
this._clearReconnectTimer();
|
|
210
|
-
this._stopKeepalive();
|
|
211
|
-
if (this.socket && !this.socket.destroyed) {
|
|
212
|
-
this.socket.end();
|
|
213
|
-
this.socket.destroy();
|
|
214
|
-
}
|
|
215
|
-
this.socket = null;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
1
|
+
import net from "node:net";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { EventEmitter } from "node:events";
|
|
5
|
+
|
|
6
|
+
export function resolveSocketPath({ platform = process.platform, stateDir, homedir = os.homedir } = {}) {
|
|
7
|
+
if (platform === "win32") {
|
|
8
|
+
return "\\\\.\\pipe\\nemoris-daemon";
|
|
9
|
+
}
|
|
10
|
+
const root = stateDir || path.join(homedir(), ".nemoris", "state");
|
|
11
|
+
return path.join(root, "daemon.sock");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getReconnectDelay(attempt, { baseMs = 1000, maxMs = 30000 } = {}) {
|
|
15
|
+
return Math.min(maxMs, baseMs * (2 ** Math.max(0, attempt)));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function parseNdjsonChunk(buffer = "", chunk = "") {
|
|
19
|
+
const input = `${buffer}${chunk}`;
|
|
20
|
+
const lines = input.split("\n");
|
|
21
|
+
const nextBuffer = lines.pop() || "";
|
|
22
|
+
const messages = [];
|
|
23
|
+
const errors = [];
|
|
24
|
+
|
|
25
|
+
for (const rawLine of lines) {
|
|
26
|
+
const line = rawLine.trim();
|
|
27
|
+
if (!line) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
messages.push(JSON.parse(line));
|
|
32
|
+
} catch (error) {
|
|
33
|
+
errors.push(error);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { buffer: nextBuffer, messages, errors };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function emitSafeError(target, error) {
|
|
41
|
+
if (target.listenerCount("error") > 0) {
|
|
42
|
+
target.emit("error", error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class TuiSocketClient extends EventEmitter {
|
|
47
|
+
constructor({
|
|
48
|
+
socketPath,
|
|
49
|
+
stateDir,
|
|
50
|
+
platform = process.platform,
|
|
51
|
+
homedir = os.homedir,
|
|
52
|
+
createConnection = net.createConnection,
|
|
53
|
+
reconnectBaseMs = 1000,
|
|
54
|
+
reconnectMaxMs = 30000,
|
|
55
|
+
keepaliveMs = 30000,
|
|
56
|
+
autoReconnect = true,
|
|
57
|
+
setTimeoutFn = setTimeout,
|
|
58
|
+
clearTimeoutFn = clearTimeout,
|
|
59
|
+
setIntervalFn = setInterval,
|
|
60
|
+
clearIntervalFn = clearInterval,
|
|
61
|
+
} = {}) {
|
|
62
|
+
super();
|
|
63
|
+
this.socketPath = socketPath || resolveSocketPath({ platform, stateDir, homedir });
|
|
64
|
+
this.createConnection = createConnection;
|
|
65
|
+
this.reconnectBaseMs = reconnectBaseMs;
|
|
66
|
+
this.reconnectMaxMs = reconnectMaxMs;
|
|
67
|
+
this.keepaliveMs = keepaliveMs;
|
|
68
|
+
this.autoReconnect = autoReconnect;
|
|
69
|
+
this.setTimeoutFn = setTimeoutFn;
|
|
70
|
+
this.clearTimeoutFn = clearTimeoutFn;
|
|
71
|
+
this.setIntervalFn = setIntervalFn;
|
|
72
|
+
this.clearIntervalFn = clearIntervalFn;
|
|
73
|
+
|
|
74
|
+
this.socket = null;
|
|
75
|
+
this.buffer = "";
|
|
76
|
+
this.connected = false;
|
|
77
|
+
this.closed = false;
|
|
78
|
+
this.reconnectAttempt = 0;
|
|
79
|
+
this.reconnectTimer = null;
|
|
80
|
+
this.keepaliveTimer = null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async connect() {
|
|
84
|
+
this.closed = false;
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
let settled = false;
|
|
87
|
+
const cleanup = () => {
|
|
88
|
+
this.off("connected", onConnected);
|
|
89
|
+
this.off("error", onError);
|
|
90
|
+
};
|
|
91
|
+
const onConnected = () => {
|
|
92
|
+
if (settled) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
settled = true;
|
|
96
|
+
cleanup();
|
|
97
|
+
resolve(this);
|
|
98
|
+
};
|
|
99
|
+
const onError = (error) => {
|
|
100
|
+
if (settled) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
settled = true;
|
|
104
|
+
cleanup();
|
|
105
|
+
reject(error);
|
|
106
|
+
};
|
|
107
|
+
this.once("connected", onConnected);
|
|
108
|
+
this.once("error", onError);
|
|
109
|
+
this._openSocket();
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_openSocket() {
|
|
114
|
+
if (this.closed) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
this.buffer = "";
|
|
118
|
+
const socket = this.createConnection(this.socketPath);
|
|
119
|
+
this.socket = socket;
|
|
120
|
+
socket.setEncoding?.("utf8");
|
|
121
|
+
|
|
122
|
+
socket.on("connect", () => {
|
|
123
|
+
this.connected = true;
|
|
124
|
+
this.reconnectAttempt = 0;
|
|
125
|
+
this._clearReconnectTimer();
|
|
126
|
+
this._startKeepalive();
|
|
127
|
+
this.emit("connected");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
socket.on("data", (chunk) => {
|
|
131
|
+
const parsed = parseNdjsonChunk(this.buffer, chunk);
|
|
132
|
+
this.buffer = parsed.buffer;
|
|
133
|
+
for (const message of parsed.messages) {
|
|
134
|
+
this.emit("message", message);
|
|
135
|
+
}
|
|
136
|
+
for (const error of parsed.errors) {
|
|
137
|
+
emitSafeError(this, error);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
socket.on("error", (error) => {
|
|
142
|
+
emitSafeError(this, error);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
socket.on("close", () => {
|
|
146
|
+
const wasConnected = this.connected;
|
|
147
|
+
this.connected = false;
|
|
148
|
+
this._stopKeepalive();
|
|
149
|
+
this.emit("disconnected");
|
|
150
|
+
if (!this.closed && this.autoReconnect) {
|
|
151
|
+
this._scheduleReconnect();
|
|
152
|
+
}
|
|
153
|
+
if (!wasConnected && this.closed) {
|
|
154
|
+
this.buffer = "";
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
_scheduleReconnect() {
|
|
160
|
+
if (this.reconnectTimer || this.closed) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
const delay = getReconnectDelay(this.reconnectAttempt, {
|
|
164
|
+
baseMs: this.reconnectBaseMs,
|
|
165
|
+
maxMs: this.reconnectMaxMs,
|
|
166
|
+
});
|
|
167
|
+
this.reconnectAttempt += 1;
|
|
168
|
+
this.reconnectTimer = this.setTimeoutFn(() => {
|
|
169
|
+
this.reconnectTimer = null;
|
|
170
|
+
this._openSocket();
|
|
171
|
+
}, delay);
|
|
172
|
+
return delay;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
_clearReconnectTimer() {
|
|
176
|
+
if (!this.reconnectTimer) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
this.clearTimeoutFn(this.reconnectTimer);
|
|
180
|
+
this.reconnectTimer = null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
_startKeepalive() {
|
|
184
|
+
this._stopKeepalive();
|
|
185
|
+
this.keepaliveTimer = this.setIntervalFn(() => {
|
|
186
|
+
this.send({ type: "ping" });
|
|
187
|
+
}, this.keepaliveMs);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
_stopKeepalive() {
|
|
191
|
+
if (!this.keepaliveTimer) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
this.clearIntervalFn(this.keepaliveTimer);
|
|
195
|
+
this.keepaliveTimer = null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
send(payload) {
|
|
199
|
+
if (!this.socket || this.socket.destroyed || !this.socket.writable) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
this.socket.write(`${JSON.stringify(payload)}\n`);
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
close() {
|
|
207
|
+
this.closed = true;
|
|
208
|
+
this.connected = false;
|
|
209
|
+
this._clearReconnectTimer();
|
|
210
|
+
this._stopKeepalive();
|
|
211
|
+
if (this.socket && !this.socket.destroyed) {
|
|
212
|
+
this.socket.end();
|
|
213
|
+
this.socket.destroy();
|
|
214
|
+
}
|
|
215
|
+
this.socket = null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Canonical JSON serializer — deterministic, stable output.
|
|
3
|
-
*
|
|
4
|
-
* Rules:
|
|
5
|
-
* - Object keys sorted lexicographically (recursive)
|
|
6
|
-
* - No insignificant whitespace
|
|
7
|
-
* - Stable number formatting (JSON.stringify default)
|
|
8
|
-
* - null, boolean, string, number pass through normally
|
|
9
|
-
* - UTF-8 (Node default)
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export function canonicalJson(value) {
|
|
13
|
-
if (value === null || value === undefined) return "null";
|
|
14
|
-
if (typeof value === "boolean" || typeof value === "number") return JSON.stringify(value);
|
|
15
|
-
if (typeof value === "string") return JSON.stringify(value);
|
|
16
|
-
|
|
17
|
-
if (Array.isArray(value)) {
|
|
18
|
-
const items = value.map((item) => canonicalJson(item));
|
|
19
|
-
return "[" + items.join(",") + "]";
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (typeof value === "object") {
|
|
23
|
-
const keys = Object.keys(value).sort();
|
|
24
|
-
const pairs = keys.map((key) => JSON.stringify(key) + ":" + canonicalJson(value[key]));
|
|
25
|
-
return "{" + pairs.join(",") + "}";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return JSON.stringify(value);
|
|
29
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Canonical JSON serializer — deterministic, stable output.
|
|
3
|
+
*
|
|
4
|
+
* Rules:
|
|
5
|
+
* - Object keys sorted lexicographically (recursive)
|
|
6
|
+
* - No insignificant whitespace
|
|
7
|
+
* - Stable number formatting (JSON.stringify default)
|
|
8
|
+
* - null, boolean, string, number pass through normally
|
|
9
|
+
* - UTF-8 (Node default)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export function canonicalJson(value) {
|
|
13
|
+
if (value === null || value === undefined) return "null";
|
|
14
|
+
if (typeof value === "boolean" || typeof value === "number") return JSON.stringify(value);
|
|
15
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
16
|
+
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
const items = value.map((item) => canonicalJson(item));
|
|
19
|
+
return "[" + items.join(",") + "]";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (typeof value === "object") {
|
|
23
|
+
const keys = Object.keys(value).sort();
|
|
24
|
+
const pairs = keys.map((key) => JSON.stringify(key) + ":" + canonicalJson(value[key]));
|
|
25
|
+
return "{" + pairs.join(",") + "}";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return JSON.stringify(value);
|
|
29
|
+
}
|
package/src/utils/compaction.js
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
export const COMPACTION_NOTICE_TEXT = "Compressing context to stay within limits — resuming in a moment…";
|
|
2
|
-
|
|
3
|
-
export function resolveCompactionThreshold(agentConfig) {
|
|
4
|
-
return agentConfig?.limits?.compaction_threshold_turns ?? 20;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function resolveCompactionCondensedFanout(agentConfig) {
|
|
8
|
-
return agentConfig?.limits?.compaction_condensed_fanout ?? 4;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function parseConversationTurns(contextJson) {
|
|
12
|
-
if (!contextJson) return [];
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
const turns = JSON.parse(contextJson);
|
|
16
|
-
return Array.isArray(turns) ? turns : [];
|
|
17
|
-
} catch {
|
|
18
|
-
return [];
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function shouldCompactConversation(contextJson, agentConfig) {
|
|
23
|
-
return parseConversationTurns(contextJson).length >= resolveCompactionThreshold(agentConfig);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function notifyCompaction(sendFn, chatId) {
|
|
27
|
-
if (!sendFn || !chatId) return false;
|
|
28
|
-
await sendFn(chatId, COMPACTION_NOTICE_TEXT);
|
|
29
|
-
return true;
|
|
30
|
-
}
|
|
1
|
+
export const COMPACTION_NOTICE_TEXT = "Compressing context to stay within limits — resuming in a moment…";
|
|
2
|
+
|
|
3
|
+
export function resolveCompactionThreshold(agentConfig) {
|
|
4
|
+
return agentConfig?.limits?.compaction_threshold_turns ?? 20;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function resolveCompactionCondensedFanout(agentConfig) {
|
|
8
|
+
return agentConfig?.limits?.compaction_condensed_fanout ?? 4;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function parseConversationTurns(contextJson) {
|
|
12
|
+
if (!contextJson) return [];
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const turns = JSON.parse(contextJson);
|
|
16
|
+
return Array.isArray(turns) ? turns : [];
|
|
17
|
+
} catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function shouldCompactConversation(contextJson, agentConfig) {
|
|
23
|
+
return parseConversationTurns(contextJson).length >= resolveCompactionThreshold(agentConfig);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function notifyCompaction(sendFn, chatId) {
|
|
27
|
+
if (!sendFn || !chatId) return false;
|
|
28
|
+
await sendFn(chatId, COMPACTION_NOTICE_TEXT);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
package/src/utils/env-loader.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
|
|
3
|
-
export function resolveEnvPath(srcDir) {
|
|
4
|
-
return path.resolve(srcDir, "..", ".env");
|
|
5
|
-
}
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
export function resolveEnvPath(srcDir) {
|
|
4
|
+
return path.resolve(srcDir, "..", ".env");
|
|
5
|
+
}
|