@geravant/sinain 1.14.0 → 1.15.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/.env.example +33 -29
- package/cli.js +30 -14
- package/config-shared.js +172 -30
- package/launcher.js +38 -21
- package/onboard.js +36 -20
- package/package.json +1 -1
- package/sinain-agent/run.sh +567 -126
- package/sinain-core/src/agents-loader.ts +254 -0
- package/sinain-core/src/config.ts +77 -15
- package/sinain-core/src/escalation/escalator.ts +178 -18
- package/sinain-core/src/index.ts +168 -12
- package/sinain-core/src/learning/local-curation.ts +81 -27
- package/sinain-core/src/overlay/commands.ts +25 -0
- package/sinain-core/src/overlay/ws-handler.ts +3 -0
- package/sinain-core/src/server.ts +101 -10
- package/sinain-core/src/types.ts +29 -3
package/.env.example
CHANGED
|
@@ -22,32 +22,34 @@ PRIVACY_MODE=standard # off | standard | strict | paranoid
|
|
|
22
22
|
# strict: only summaries leave your machine
|
|
23
23
|
# paranoid: almost nothing leaves your machine
|
|
24
24
|
|
|
25
|
-
# ── Agent
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
# ── Agent + OpenClaw config → sinain-agent/agents.json ──────────────────────
|
|
26
|
+
# All bare-agent and OpenClaw gateway config (default agent, allowed-tools
|
|
27
|
+
# whitelists, max turns, poll interval, escalation mode/transport, gateway
|
|
28
|
+
# URLs/timeouts) lives in sinain-agent/agents.json. See agents.example.json
|
|
29
|
+
# in that directory for the full schema and per-profile overrides.
|
|
30
|
+
#
|
|
31
|
+
# .env still works as a fallback for individual values during migration —
|
|
32
|
+
# uncomment any of the lines below to override agents.json on this host:
|
|
33
|
+
# SINAIN_AGENT=openclaude # → agents.json `default`
|
|
34
|
+
# SINAIN_POLL_INTERVAL=5 # → agents.json `pollIntervalSec`
|
|
35
|
+
# SINAIN_AGENT_MAX_TURNS=8 # → agents.json `agentMaxTurns`
|
|
36
|
+
# SINAIN_SPAWN_MAX_TURNS=25 # → agents.json `spawnMaxTurns`
|
|
37
|
+
# SINAIN_ALLOWED_TOOLS=mcp__sinain # → agents.json `allowedTools`
|
|
38
|
+
# SINAIN_ESC_ALLOWED_TOOLS=... # → agents.json `escAllowedTools`
|
|
39
|
+
# SINAIN_SPAWN_ALLOWED_TOOLS=... # → agents.json `spawnAllowedTools`
|
|
40
|
+
# SINAIN_AUTO_APPROVE_TOOLS=... # → agents.json `autoApproveTools`
|
|
41
|
+
# AGENT_DEBOUNCE_MS=6000 # → agents.json `analyzer.debounceMs`
|
|
42
|
+
# AGENT_MAX_INTERVAL_MS=60000 # → agents.json `analyzer.maxIntervalMs`
|
|
43
|
+
# ESCALATION_MODE=rich # → agents.json `escalation.mode`
|
|
44
|
+
# ESCALATION_COOLDOWN_MS=30000 # → agents.json `escalation.cooldownMs`
|
|
45
|
+
# ESCALATION_TRANSPORT was REMOVED — agent identity (openclaw vs local) is
|
|
46
|
+
# the transport now. Pick "openclaw" in the overlay selector for WS dispatch.
|
|
47
|
+
|
|
31
48
|
SINAIN_CORE_URL=http://localhost:9500
|
|
32
|
-
SINAIN_POLL_INTERVAL=5 # seconds between escalation polls
|
|
33
|
-
SINAIN_HEARTBEAT_INTERVAL=900 # seconds between heartbeat ticks (15 min)
|
|
34
49
|
SINAIN_WORKSPACE=~/.openclaw/workspace # knowledge files, curation scripts, playbook
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# auto-derived from mcp-config.json if unset
|
|
39
|
-
# format: mcp__<server> (all) | mcp__<server>__<tool> (specific)
|
|
40
|
-
|
|
41
|
-
# ── Escalation ───────────────────────────────────────────────────────────────
|
|
42
|
-
ESCALATION_MODE=rich # off | selective | focus | rich
|
|
43
|
-
# off: no escalation
|
|
44
|
-
# selective: score-based (errors, questions trigger it)
|
|
45
|
-
# focus: always escalate every tick
|
|
46
|
-
# rich: always escalate with maximum context
|
|
47
|
-
# ESCALATION_COOLDOWN_MS=10000
|
|
48
|
-
# ESCALATION_TRANSPORT=auto # ws | http | auto
|
|
49
|
-
# auto = WS when gateway connected, HTTP fallback
|
|
50
|
-
# http = bare agent only (no gateway)
|
|
50
|
+
# Heartbeat (signal/insight/memory/playbook) now runs natively in sinain-core's
|
|
51
|
+
# LocalCurationService every 30 min — no bare-agent heartbeat configuration needed.
|
|
52
|
+
SINAIN_HEARTBEAT_INTERVAL=900
|
|
51
53
|
|
|
52
54
|
# ── Server ───────────────────────────────────────────────────────────────────
|
|
53
55
|
PORT=9500
|
|
@@ -90,15 +92,17 @@ TRANSCRIPTION_LANGUAGE=en-US
|
|
|
90
92
|
# ── OpenClaw / NemoClaw Gateway ──────────────────────────────────────────────
|
|
91
93
|
# Leave blank to run without a gateway (bare agent mode).
|
|
92
94
|
# The setup wizard fills these in if you have an OpenClaw gateway.
|
|
93
|
-
|
|
95
|
+
# Gateway URLs + session moved to agents.json (`profiles.openclaw` block).
|
|
96
|
+
# Tokens stay here as secrets and are referenced from agents.json via
|
|
97
|
+
# ${OPENCLAW_WS_TOKEN} / ${OPENCLAW_HTTP_TOKEN} indirection.
|
|
94
98
|
OPENCLAW_WS_TOKEN= # 48-char hex — from gateway config
|
|
95
|
-
OPENCLAW_HTTP_URL=http://localhost:18789/hooks/agent
|
|
96
99
|
OPENCLAW_HTTP_TOKEN= # same token as WS_TOKEN
|
|
97
|
-
|
|
100
|
+
# Override URLs/timeouts here too if you don't want to edit agents.json:
|
|
101
|
+
# OPENCLAW_WS_URL=ws://localhost:18789
|
|
102
|
+
# OPENCLAW_HTTP_URL=http://localhost:18789/hooks/agent
|
|
103
|
+
# OPENCLAW_SESSION_KEY=agent:main:sinain
|
|
98
104
|
# OPENCLAW_PHASE1_TIMEOUT_MS=10000
|
|
99
105
|
# OPENCLAW_PHASE2_TIMEOUT_MS=120000
|
|
100
|
-
# OPENCLAW_QUEUE_TTL_MS=300000
|
|
101
|
-
# OPENCLAW_QUEUE_MAX_SIZE=10
|
|
102
106
|
# OPENCLAW_PING_INTERVAL_MS=30000
|
|
103
107
|
|
|
104
108
|
# ── SITUATION.md ─────────────────────────────────────────────────────────────
|
package/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import net from "net";
|
|
|
4
4
|
import os from "os";
|
|
5
5
|
import fs from "fs";
|
|
6
6
|
import path from "path";
|
|
7
|
+
import { writeAgentsConfig } from "./config-shared.js";
|
|
7
8
|
|
|
8
9
|
const cmd = process.argv[2];
|
|
9
10
|
const IS_WINDOWS = os.platform() === "win32";
|
|
@@ -168,34 +169,46 @@ async function runSetupWizard() {
|
|
|
168
169
|
if (key.trim()) vars.OPENROUTER_API_KEY = key.trim();
|
|
169
170
|
}
|
|
170
171
|
|
|
171
|
-
// Agent
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
// Agent + escalation + gateway → agents.json (not .env). Tokens stay in
|
|
173
|
+
// .env as secrets, referenced from agents.json via ${VAR} indirection.
|
|
174
|
+
const agentsPatch = {};
|
|
175
|
+
|
|
176
|
+
// Default agent — overlay's chip selector lets the user switch at runtime;
|
|
177
|
+
// this just sets the boot-time default.
|
|
178
|
+
const agent = await ask(` Default agent? [${BOLD}claude${RESET}/openclaude/codex/goose/junie/aider]: `);
|
|
179
|
+
agentsPatch.default = agent.trim().toLowerCase() || "claude";
|
|
174
180
|
|
|
175
|
-
// Escalation
|
|
181
|
+
// Escalation mode
|
|
176
182
|
console.log(`\n ${DIM}Escalation: off | selective | focus | rich${RESET}`);
|
|
177
183
|
const esc = await ask(` Escalation mode? [${BOLD}selective${RESET}]: `);
|
|
178
|
-
|
|
184
|
+
agentsPatch.escalationMode = esc.trim().toLowerCase() || "selective";
|
|
179
185
|
|
|
180
|
-
// Gateway
|
|
186
|
+
// Gateway — when enabled, writes the openclaw profile to agents.json
|
|
187
|
+
// (URLs + session) and tokens to .env. There's no transport choice
|
|
188
|
+
// anymore; picking openclaw vs a local agent in the overlay determines
|
|
189
|
+
// dispatch (WS vs HTTP).
|
|
181
190
|
const gw = await ask(` OpenClaw gateway? [y/N]: `);
|
|
182
191
|
if (gw.trim().toLowerCase() === "y") {
|
|
183
192
|
const url = await ask(` Gateway WS URL [ws://localhost:18789]: `);
|
|
184
|
-
|
|
193
|
+
const wsUrl = url.trim() || "ws://localhost:18789";
|
|
185
194
|
const token = await ask(` Auth token (48-char hex): `);
|
|
186
195
|
if (token.trim()) {
|
|
187
196
|
vars.OPENCLAW_WS_TOKEN = token.trim();
|
|
188
197
|
vars.OPENCLAW_HTTP_TOKEN = token.trim();
|
|
189
198
|
}
|
|
190
|
-
|
|
191
|
-
|
|
199
|
+
agentsPatch.openclawProfile = {
|
|
200
|
+
wsUrl,
|
|
201
|
+
httpUrl: wsUrl.replace(/^ws/, "http") + "/hooks/agent",
|
|
202
|
+
wsToken: "${OPENCLAW_WS_TOKEN}",
|
|
203
|
+
httpToken: "${OPENCLAW_HTTP_TOKEN}",
|
|
204
|
+
sessionKey: "agent:main:sinain",
|
|
205
|
+
};
|
|
192
206
|
} else {
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
207
|
+
// Skip / disable: drop the openclaw profile entirely.
|
|
208
|
+
agentsPatch.openclawProfile = null;
|
|
209
|
+
agentsPatch.escalationMode = "off";
|
|
196
210
|
}
|
|
197
211
|
|
|
198
|
-
vars.SINAIN_POLL_INTERVAL = "5";
|
|
199
212
|
vars.SINAIN_HEARTBEAT_INTERVAL = "900";
|
|
200
213
|
vars.PRIVACY_MODE = "standard";
|
|
201
214
|
|
|
@@ -230,8 +243,11 @@ async function runSetupWizard() {
|
|
|
230
243
|
fs.writeFileSync(envPath, lines.join("\n"));
|
|
231
244
|
}
|
|
232
245
|
|
|
246
|
+
// Patch ~/.sinain/agents.json with the wizard's agent + gateway choices.
|
|
247
|
+
writeAgentsConfig(agentsPatch);
|
|
248
|
+
|
|
233
249
|
rl.close();
|
|
234
|
-
console.log(`\n ${GREEN}✓${RESET} Config written to ${envPath}\n`);
|
|
250
|
+
console.log(`\n ${GREEN}✓${RESET} Config written to ${envPath} + ~/.sinain/agents.json\n`);
|
|
235
251
|
}
|
|
236
252
|
|
|
237
253
|
// ── Stop ──────────────────────────────────────────────────────────────────────
|
package/config-shared.js
CHANGED
|
@@ -16,6 +16,16 @@ export const PKG_DIR = path.dirname(new URL(import.meta.url).pathname);
|
|
|
16
16
|
export const IS_WINDOWS = os.platform() === "win32";
|
|
17
17
|
export const IS_MAC = os.platform() === "darwin";
|
|
18
18
|
|
|
19
|
+
// Wizard write target for agents.json. Lives in user home so npm-installed
|
|
20
|
+
// users (whose package dir is often read-only) get a writable path.
|
|
21
|
+
// sinain-agent/run.sh and sinain-core/agents-loader.ts both check this
|
|
22
|
+
// path with the highest priority after AGENTS_CONFIG_PATH env override.
|
|
23
|
+
export const AGENTS_JSON_PATH = path.join(SINAIN_DIR, "agents.json");
|
|
24
|
+
// agents.example.json ships inside the package as the bootstrap template
|
|
25
|
+
// for first-run wizard writes (and as the fallback when no agents.json
|
|
26
|
+
// exists yet on this host).
|
|
27
|
+
export const AGENTS_EXAMPLE_PATH = path.join(PKG_DIR, "sinain-agent", "agents.example.json");
|
|
28
|
+
|
|
19
29
|
// ── Colors ──────────────────────────────────────────────────────────────────
|
|
20
30
|
|
|
21
31
|
export const c = {
|
|
@@ -111,6 +121,74 @@ export function writeEnv(vars) {
|
|
|
111
121
|
}
|
|
112
122
|
}
|
|
113
123
|
|
|
124
|
+
/** Read agents.json (the wizard's authoritative agent + gateway config).
|
|
125
|
+
*
|
|
126
|
+
* Resolution order mirrors agents-loader.ts and run.sh:
|
|
127
|
+
* 1. ~/.sinain/agents.json (the wizard write target)
|
|
128
|
+
* 2. <pkg>/sinain-agent/agents.example.json (bootstrap template fallback)
|
|
129
|
+
*
|
|
130
|
+
* Returns an empty object if neither file exists or parsing fails — callers
|
|
131
|
+
* treat null/missing fields as "use schema defaults".
|
|
132
|
+
*/
|
|
133
|
+
export function readAgentsConfig() {
|
|
134
|
+
for (const p of [AGENTS_JSON_PATH, AGENTS_EXAMPLE_PATH]) {
|
|
135
|
+
if (!fs.existsSync(p)) continue;
|
|
136
|
+
try {
|
|
137
|
+
return JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
138
|
+
} catch (err) {
|
|
139
|
+
// Bad JSON — surface, but don't crash the wizard
|
|
140
|
+
console.warn(`[config] failed to parse ${p}: ${err.message}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return {};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Patch ~/.sinain/agents.json with the given updates.
|
|
147
|
+
*
|
|
148
|
+
* `patch` shape (all optional):
|
|
149
|
+
* - default: string — top-level default agent name
|
|
150
|
+
* - escalationMode: string — sets escalation.mode
|
|
151
|
+
* - openclawProfile: object | null — sets profiles.openclaw
|
|
152
|
+
* (null deletes the openclaw profile entirely,
|
|
153
|
+
* which disables the gateway path)
|
|
154
|
+
*
|
|
155
|
+
* Reads the existing config first (or falls back to the shipped example),
|
|
156
|
+
* applies the patch, writes pretty-printed JSON to AGENTS_JSON_PATH.
|
|
157
|
+
* The example file is the "schema source of truth" — patches preserve any
|
|
158
|
+
* other fields the user has customized (custom profiles, allowedTools,
|
|
159
|
+
* analyzer pacing, etc.).
|
|
160
|
+
*/
|
|
161
|
+
export function writeAgentsConfig(patch) {
|
|
162
|
+
fs.mkdirSync(SINAIN_DIR, { recursive: true });
|
|
163
|
+
// Start from existing user config if present, else the example template
|
|
164
|
+
// (so first-run writes get the full schema, not just the patched fields).
|
|
165
|
+
let cfg;
|
|
166
|
+
if (fs.existsSync(AGENTS_JSON_PATH)) {
|
|
167
|
+
cfg = JSON.parse(fs.readFileSync(AGENTS_JSON_PATH, "utf-8"));
|
|
168
|
+
} else if (fs.existsSync(AGENTS_EXAMPLE_PATH)) {
|
|
169
|
+
cfg = JSON.parse(fs.readFileSync(AGENTS_EXAMPLE_PATH, "utf-8"));
|
|
170
|
+
} else {
|
|
171
|
+
cfg = { profiles: {} };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (patch.default !== undefined) {
|
|
175
|
+
cfg.default = patch.default;
|
|
176
|
+
}
|
|
177
|
+
if (patch.escalationMode !== undefined) {
|
|
178
|
+
cfg.escalation = cfg.escalation || {};
|
|
179
|
+
cfg.escalation.mode = patch.escalationMode;
|
|
180
|
+
}
|
|
181
|
+
if (patch.openclawProfile === null) {
|
|
182
|
+
// Explicit removal: user picked "skip / no gateway"
|
|
183
|
+
if (cfg.profiles?.openclaw) delete cfg.profiles.openclaw;
|
|
184
|
+
} else if (patch.openclawProfile) {
|
|
185
|
+
cfg.profiles = cfg.profiles || {};
|
|
186
|
+
cfg.profiles.openclaw = { type: "openclaw", ...patch.openclawProfile };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
fs.writeFileSync(AGENTS_JSON_PATH, JSON.stringify(cfg, null, 2) + "\n");
|
|
190
|
+
}
|
|
191
|
+
|
|
114
192
|
export async function fetchHealth(port = 9500) {
|
|
115
193
|
try {
|
|
116
194
|
const res = await fetch(`http://127.0.0.1:${port}/health`, { signal: AbortSignal.timeout(2000) });
|
|
@@ -118,16 +196,28 @@ export async function fetchHealth(port = 9500) {
|
|
|
118
196
|
} catch { return null; }
|
|
119
197
|
}
|
|
120
198
|
|
|
121
|
-
/** Build a config summary for display
|
|
199
|
+
/** Build a config summary for display.
|
|
200
|
+
* Reads BOTH .env (existing arg) AND ~/.sinain/agents.json — agent + gateway
|
|
201
|
+
* config moved out of .env into agents.json after the migration, so we
|
|
202
|
+
* have to consult both to render an accurate snapshot. */
|
|
122
203
|
export function summarizeConfig(existing) {
|
|
123
204
|
const lines = [];
|
|
205
|
+
const agentsCfg = readAgentsConfig();
|
|
206
|
+
const openclaw = agentsCfg?.profiles?.openclaw;
|
|
207
|
+
|
|
124
208
|
if (existing.OPENROUTER_API_KEY) lines.push(`API Key: ${maskKey(existing.OPENROUTER_API_KEY)}`);
|
|
125
209
|
if (existing.TRANSCRIPTION_BACKEND) lines.push(`Transcription: ${existing.TRANSCRIPTION_BACKEND}`);
|
|
126
210
|
if (existing.AGENT_MODEL) lines.push(`Model: ${existing.AGENT_MODEL}`);
|
|
127
211
|
if (existing.PRIVACY_MODE) lines.push(`Privacy: ${existing.PRIVACY_MODE}`);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
212
|
+
|
|
213
|
+
// Escalation mode + agent default + gateway URL all live in agents.json
|
|
214
|
+
// now (with .env as a fallback override during the migration window).
|
|
215
|
+
const escMode = agentsCfg?.escalation?.mode || existing.ESCALATION_MODE;
|
|
216
|
+
if (escMode) lines.push(`Escalation: ${escMode}`);
|
|
217
|
+
const gatewayUrl = openclaw?.wsUrl || existing.OPENCLAW_WS_URL;
|
|
218
|
+
if (gatewayUrl) lines.push(`Gateway: ${gatewayUrl}`);
|
|
219
|
+
const defaultAgent = agentsCfg?.default || existing.SINAIN_AGENT;
|
|
220
|
+
if (defaultAgent) lines.push(`Agent: ${defaultAgent}`);
|
|
131
221
|
return lines;
|
|
132
222
|
}
|
|
133
223
|
|
|
@@ -245,12 +335,40 @@ export async function stepTranscription(existing, label = "Audio transcription")
|
|
|
245
335
|
return choice;
|
|
246
336
|
}
|
|
247
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Gateway setup step.
|
|
340
|
+
*
|
|
341
|
+
* Returns `{ envVars, agentsPatch }`:
|
|
342
|
+
* - envVars: tokens (OPENCLAW_WS_TOKEN, OPENCLAW_HTTP_TOKEN) — stay in
|
|
343
|
+
* .env as secrets, referenced from agents.json via ${VAR} indirection
|
|
344
|
+
* - agentsPatch: feed to writeAgentsConfig(); writes the openclaw profile
|
|
345
|
+
* (urls + session) and escalation.mode into ~/.sinain/agents.json.
|
|
346
|
+
*
|
|
347
|
+
* The user picks whether to wire up the gateway-style profile at all.
|
|
348
|
+
* The dispatch decision (which lane uses WS vs HTTP) is made later via
|
|
349
|
+
* the overlay's per-lane agent selector — there's no transport question
|
|
350
|
+
* here anymore; agent identity IS the transport.
|
|
351
|
+
*
|
|
352
|
+
* Pre-existing gateway config is read from agents.json first, then .env
|
|
353
|
+
* as a backwards-compat fallback for users migrating from before the
|
|
354
|
+
* profile refactor.
|
|
355
|
+
*/
|
|
248
356
|
export async function stepGateway(existing, label = "OpenClaw gateway") {
|
|
357
|
+
// Read existing gateway config from BOTH places — wizard might be
|
|
358
|
+
// running mid-migration where some users still have OPENCLAW_WS_URL in
|
|
359
|
+
// .env but agents.json's openclaw profile is absent.
|
|
360
|
+
const agentsCfg = readAgentsConfig();
|
|
361
|
+
const oc = agentsCfg?.profiles?.openclaw;
|
|
362
|
+
const existingWsUrl = oc?.wsUrl || existing.OPENCLAW_WS_URL || "";
|
|
363
|
+
|
|
249
364
|
p.note(
|
|
250
|
-
"The gateway gives sinain a persistent
|
|
251
|
-
"that handles
|
|
252
|
-
"
|
|
253
|
-
"
|
|
365
|
+
"The gateway gives sinain a persistent agent backend (OpenClaw/NemoClaw)\n" +
|
|
366
|
+
"that handles escalations and background tasks via WS RPC. Without it,\n" +
|
|
367
|
+
"the local bare agent (claude, openclaude, etc.) handles everything via\n" +
|
|
368
|
+
"the HTTP path.\n\n" +
|
|
369
|
+
"You can pick which agent handles each lane (escalation/spawn) at runtime\n" +
|
|
370
|
+
"via the overlay's flash-icon selector.",
|
|
371
|
+
"About the gateway",
|
|
254
372
|
);
|
|
255
373
|
|
|
256
374
|
const choice = guard(await p.select({
|
|
@@ -259,7 +377,7 @@ export async function stepGateway(existing, label = "OpenClaw gateway") {
|
|
|
259
377
|
{
|
|
260
378
|
value: "skip",
|
|
261
379
|
label: "Skip / Disable",
|
|
262
|
-
hint: "
|
|
380
|
+
hint: "Local agents only — add a gateway later with `sinain config`",
|
|
263
381
|
},
|
|
264
382
|
{
|
|
265
383
|
value: "local",
|
|
@@ -272,16 +390,15 @@ export async function stepGateway(existing, label = "OpenClaw gateway") {
|
|
|
272
390
|
hint: "Connect to existing gateway (URL + token)",
|
|
273
391
|
},
|
|
274
392
|
],
|
|
275
|
-
initialValue:
|
|
393
|
+
initialValue: existingWsUrl ? "remote" : "skip",
|
|
276
394
|
}));
|
|
277
395
|
|
|
278
396
|
if (choice === "skip") {
|
|
397
|
+
// Drop the openclaw profile from agents.json entirely. Tokens cleared
|
|
398
|
+
// in .env so they don't linger as misleading secrets.
|
|
279
399
|
return {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
OPENCLAW_WS_TOKEN: "",
|
|
283
|
-
OPENCLAW_HTTP_TOKEN: "",
|
|
284
|
-
ESCALATION_MODE: "off",
|
|
400
|
+
envVars: { OPENCLAW_WS_TOKEN: "", OPENCLAW_HTTP_TOKEN: "" },
|
|
401
|
+
agentsPatch: { escalationMode: "off", openclawProfile: null },
|
|
285
402
|
};
|
|
286
403
|
}
|
|
287
404
|
|
|
@@ -293,7 +410,7 @@ export async function stepGateway(existing, label = "OpenClaw gateway") {
|
|
|
293
410
|
const wsUrl = guard(await p.text({
|
|
294
411
|
message: "Gateway WebSocket URL",
|
|
295
412
|
placeholder: "Example: ws://192.168.1.100:18789",
|
|
296
|
-
defaultValue:
|
|
413
|
+
defaultValue: existingWsUrl,
|
|
297
414
|
validate: (val) => {
|
|
298
415
|
if (!val) return "URL is required";
|
|
299
416
|
if (!val.startsWith("ws://") && !val.startsWith("wss://")) return "Must start with ws:// or wss://";
|
|
@@ -322,13 +439,24 @@ export async function stepGateway(existing, label = "OpenClaw gateway") {
|
|
|
322
439
|
}
|
|
323
440
|
|
|
324
441
|
const httpUrl = wsUrl.replace(/^ws/, "http") + "/hooks/agent";
|
|
442
|
+
// Tokens → .env; URLs + session → agents.json. The agents.json profile
|
|
443
|
+
// references the env tokens via ${OPENCLAW_WS_TOKEN}/${OPENCLAW_HTTP_TOKEN}
|
|
444
|
+
// indirection (resolved by sinain-core's agents-loader at startup).
|
|
325
445
|
return {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
446
|
+
envVars: {
|
|
447
|
+
OPENCLAW_WS_TOKEN: token,
|
|
448
|
+
OPENCLAW_HTTP_TOKEN: token,
|
|
449
|
+
},
|
|
450
|
+
agentsPatch: {
|
|
451
|
+
escalationMode: "rich",
|
|
452
|
+
openclawProfile: {
|
|
453
|
+
wsUrl,
|
|
454
|
+
httpUrl,
|
|
455
|
+
wsToken: "${OPENCLAW_WS_TOKEN}",
|
|
456
|
+
httpToken: "${OPENCLAW_HTTP_TOKEN}",
|
|
457
|
+
sessionKey: "agent:main:sinain",
|
|
458
|
+
},
|
|
459
|
+
},
|
|
332
460
|
};
|
|
333
461
|
}
|
|
334
462
|
|
|
@@ -352,10 +480,16 @@ async function setupLocalGateway(existing) {
|
|
|
352
480
|
"Install manually: npm install -g openclaw\nThen re-run setup.",
|
|
353
481
|
"Manual install",
|
|
354
482
|
);
|
|
355
|
-
return {
|
|
483
|
+
return {
|
|
484
|
+
envVars: { OPENCLAW_WS_TOKEN: "", OPENCLAW_HTTP_TOKEN: "" },
|
|
485
|
+
agentsPatch: { escalationMode: "off", openclawProfile: null },
|
|
486
|
+
};
|
|
356
487
|
}
|
|
357
488
|
} else {
|
|
358
|
-
return {
|
|
489
|
+
return {
|
|
490
|
+
envVars: { OPENCLAW_WS_TOKEN: "", OPENCLAW_HTTP_TOKEN: "" },
|
|
491
|
+
agentsPatch: { escalationMode: "off", openclawProfile: null },
|
|
492
|
+
};
|
|
359
493
|
}
|
|
360
494
|
}
|
|
361
495
|
|
|
@@ -380,12 +514,20 @@ async function setupLocalGateway(existing) {
|
|
|
380
514
|
}
|
|
381
515
|
|
|
382
516
|
return {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
517
|
+
envVars: {
|
|
518
|
+
OPENCLAW_WS_TOKEN: token,
|
|
519
|
+
OPENCLAW_HTTP_TOKEN: token,
|
|
520
|
+
},
|
|
521
|
+
agentsPatch: {
|
|
522
|
+
escalationMode: "rich",
|
|
523
|
+
openclawProfile: {
|
|
524
|
+
wsUrl: "ws://localhost:18789",
|
|
525
|
+
httpUrl: "http://localhost:18789/hooks/agent",
|
|
526
|
+
wsToken: "${OPENCLAW_WS_TOKEN}",
|
|
527
|
+
httpToken: "${OPENCLAW_HTTP_TOKEN}",
|
|
528
|
+
sessionKey: "agent:main:sinain",
|
|
529
|
+
},
|
|
530
|
+
},
|
|
389
531
|
};
|
|
390
532
|
}
|
|
391
533
|
|
package/launcher.js
CHANGED
|
@@ -8,6 +8,7 @@ import path from "path";
|
|
|
8
8
|
import os from "os";
|
|
9
9
|
import net from "net";
|
|
10
10
|
import readline from "readline";
|
|
11
|
+
import { writeAgentsConfig, readAgentsConfig } from "./config-shared.js";
|
|
11
12
|
|
|
12
13
|
// ── Colors ──────────────────────────────────────────────────────────────────
|
|
13
14
|
|
|
@@ -486,10 +487,14 @@ async function setupWizard(envPath) {
|
|
|
486
487
|
else if (existingKey) vars.OPENROUTER_API_KEY = existingKey;
|
|
487
488
|
}
|
|
488
489
|
|
|
489
|
-
// 3. Agent selection
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
490
|
+
// 3. Agent selection — written to agents.json `default` field. Existing
|
|
491
|
+
// value is read from agents.json first, falling back to .env for users
|
|
492
|
+
// mid-migration.
|
|
493
|
+
const existingAgentsCfg = readAgentsConfig();
|
|
494
|
+
const agentsPatch = {};
|
|
495
|
+
const defaultAgent = existingAgentsCfg?.default || existing.SINAIN_AGENT || "claude";
|
|
496
|
+
const agentChoice = await ask(` Default agent? [${BOLD}${defaultAgent}${RESET}/claude/openclaude/codex/goose/junie/aider]: `);
|
|
497
|
+
agentsPatch.default = agentChoice.trim().toLowerCase() || defaultAgent;
|
|
493
498
|
|
|
494
499
|
// 3b. Local vision (Ollama)
|
|
495
500
|
const IS_MACOS = os.platform() === "darwin";
|
|
@@ -543,28 +548,32 @@ async function setupWizard(envPath) {
|
|
|
543
548
|
}
|
|
544
549
|
}
|
|
545
550
|
|
|
546
|
-
// 4. Escalation mode
|
|
551
|
+
// 4. Escalation mode — agents.json `escalation.mode`
|
|
547
552
|
console.log();
|
|
548
553
|
console.log(` ${DIM}Escalation modes:${RESET}`);
|
|
549
|
-
console.log(` off — no escalation
|
|
554
|
+
console.log(` off — no escalation`);
|
|
550
555
|
console.log(` selective — score-based (errors, questions trigger it)`);
|
|
551
556
|
console.log(` focus — always escalate every tick`);
|
|
552
557
|
console.log(` rich — always escalate with maximum context`);
|
|
553
|
-
const defaultEsc = existing.ESCALATION_MODE || "selective";
|
|
558
|
+
const defaultEsc = existingAgentsCfg?.escalation?.mode || existing.ESCALATION_MODE || "selective";
|
|
554
559
|
const escMode = await ask(` Escalation mode? [off/${BOLD}${defaultEsc}${RESET}/selective/focus/rich]: `);
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
// 5. OpenClaw gateway
|
|
558
|
-
|
|
560
|
+
agentsPatch.escalationMode = escMode.trim().toLowerCase() || defaultEsc;
|
|
561
|
+
|
|
562
|
+
// 5. OpenClaw gateway — URLs/session → agents.json (openclaw profile),
|
|
563
|
+
// tokens → .env. No transport question; agent identity (openclaw vs
|
|
564
|
+
// local) determines dispatch path.
|
|
565
|
+
const existingOpenclaw = existingAgentsCfg?.profiles?.openclaw;
|
|
566
|
+
const existingWsUrl = existingOpenclaw?.wsUrl || existing.OPENCLAW_WS_URL || "";
|
|
567
|
+
const hadGateway = !!existingWsUrl;
|
|
559
568
|
const gatewayDefault = hadGateway ? "Y" : "N";
|
|
560
569
|
const hasGateway = await ask(` Do you have an OpenClaw gateway? [${gatewayDefault === "Y" ? "Y/n" : "y/N"}]: `);
|
|
561
570
|
const wantsGateway = hasGateway.trim()
|
|
562
571
|
? hasGateway.trim().toLowerCase() === "y"
|
|
563
572
|
: hadGateway;
|
|
564
573
|
if (wantsGateway) {
|
|
565
|
-
const defaultWs =
|
|
574
|
+
const defaultWs = existingWsUrl || "ws://localhost:18789";
|
|
566
575
|
const wsUrl = await ask(` Gateway WebSocket URL [${defaultWs}]: `);
|
|
567
|
-
|
|
576
|
+
const finalWsUrl = wsUrl.trim() || defaultWs;
|
|
568
577
|
|
|
569
578
|
const existingToken = existing.OPENCLAW_WS_TOKEN;
|
|
570
579
|
const tokenHint = existingToken ? ` [${existingToken.slice(0, 6)}...${existingToken.slice(-4)}]` : "";
|
|
@@ -577,14 +586,16 @@ async function setupWizard(envPath) {
|
|
|
577
586
|
vars.OPENCLAW_HTTP_TOKEN = existing.OPENCLAW_HTTP_TOKEN || existingToken;
|
|
578
587
|
}
|
|
579
588
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
589
|
+
agentsPatch.openclawProfile = {
|
|
590
|
+
wsUrl: finalWsUrl,
|
|
591
|
+
httpUrl: finalWsUrl.replace(/^ws/, "http") + "/hooks/agent",
|
|
592
|
+
wsToken: "${OPENCLAW_WS_TOKEN}",
|
|
593
|
+
httpToken: "${OPENCLAW_HTTP_TOKEN}",
|
|
594
|
+
sessionKey: existingOpenclaw?.sessionKey || "agent:main:sinain",
|
|
595
|
+
};
|
|
584
596
|
} else {
|
|
585
|
-
// No gateway —
|
|
586
|
-
|
|
587
|
-
vars.OPENCLAW_HTTP_URL = "";
|
|
597
|
+
// No gateway — drop the openclaw profile entirely.
|
|
598
|
+
agentsPatch.openclawProfile = null;
|
|
588
599
|
}
|
|
589
600
|
|
|
590
601
|
// 6. Knowledge import (for standalone machines)
|
|
@@ -662,10 +673,16 @@ async function setupWizard(envPath) {
|
|
|
662
673
|
fs.writeFileSync(envPath, lines.join("\n"));
|
|
663
674
|
}
|
|
664
675
|
|
|
676
|
+
// Flush agent + gateway answers to ~/.sinain/agents.json (separate file
|
|
677
|
+
// from .env after the profile-config refactor).
|
|
678
|
+
if (Object.keys(agentsPatch).length > 0) {
|
|
679
|
+
writeAgentsConfig(agentsPatch);
|
|
680
|
+
}
|
|
681
|
+
|
|
665
682
|
rl.close();
|
|
666
683
|
|
|
667
684
|
console.log();
|
|
668
|
-
ok(`Config written to ${envPath}`);
|
|
685
|
+
ok(`Config written to ${envPath} + ~/.sinain/agents.json`);
|
|
669
686
|
console.log();
|
|
670
687
|
}
|
|
671
688
|
|