@geravant/sinain 1.4.0 → 1.5.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/cli.js +29 -5
- package/launcher.js +41 -10
- package/package.json +2 -2
- package/sinain-agent/run.sh +31 -0
- package/sinain-core/src/escalation/escalator.ts +40 -2
- package/sinain-core/src/index.ts +15 -0
- package/sinain-core/src/overlay/commands.ts +13 -0
- package/sinain-core/src/server.ts +42 -0
- package/sinain-core/src/types.ts +7 -1
- package/sinain-mcp-server/index.ts +18 -0
- package/sinain-core/.env.example +0 -92
package/cli.js
CHANGED
|
@@ -162,12 +162,36 @@ async function runSetupWizard() {
|
|
|
162
162
|
vars.SINAIN_HEARTBEAT_INTERVAL = "900";
|
|
163
163
|
vars.PRIVACY_MODE = "standard";
|
|
164
164
|
|
|
165
|
-
// Write
|
|
165
|
+
// Write — start from .env.example template, patch wizard values in
|
|
166
166
|
fs.mkdirSync(SINAIN_DIR, { recursive: true });
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
167
|
+
|
|
168
|
+
const PKG_DIR = path.dirname(new URL(import.meta.url).pathname);
|
|
169
|
+
const examplePath = path.join(PKG_DIR, ".env.example");
|
|
170
|
+
const siblingExample = path.join(PKG_DIR, "..", ".env.example");
|
|
171
|
+
let template = "";
|
|
172
|
+
if (fs.existsSync(examplePath)) {
|
|
173
|
+
template = fs.readFileSync(examplePath, "utf-8");
|
|
174
|
+
} else if (fs.existsSync(siblingExample)) {
|
|
175
|
+
template = fs.readFileSync(siblingExample, "utf-8");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (template) {
|
|
179
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
180
|
+
const regex = new RegExp(`^#?\\s*${k}=.*$`, "m");
|
|
181
|
+
if (regex.test(template)) {
|
|
182
|
+
template = template.replace(regex, `${k}=${v}`);
|
|
183
|
+
} else {
|
|
184
|
+
template += `\n${k}=${v}`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
template = `# Generated by sinain setup wizard — ${new Date().toISOString()}\n${template}`;
|
|
188
|
+
fs.writeFileSync(envPath, template);
|
|
189
|
+
} else {
|
|
190
|
+
const lines = ["# sinain configuration — generated by setup wizard", `# ${new Date().toISOString()}`, ""];
|
|
191
|
+
for (const [k, v] of Object.entries(vars)) lines.push(`${k}=${v}`);
|
|
192
|
+
lines.push("");
|
|
193
|
+
fs.writeFileSync(envPath, lines.join("\n"));
|
|
194
|
+
}
|
|
171
195
|
|
|
172
196
|
rl.close();
|
|
173
197
|
console.log(`\n ${GREEN}✓${RESET} Config written to ${envPath}\n`);
|
package/launcher.js
CHANGED
|
@@ -409,17 +409,48 @@ async function setupWizard(envPath) {
|
|
|
409
409
|
vars.SINAIN_HEARTBEAT_INTERVAL = "900";
|
|
410
410
|
vars.PRIVACY_MODE = "standard";
|
|
411
411
|
|
|
412
|
-
// Write .env
|
|
412
|
+
// Write .env — start from .env.example template, patch wizard values in
|
|
413
413
|
fs.mkdirSync(path.dirname(envPath), { recursive: true });
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
414
|
+
|
|
415
|
+
const examplePath = path.join(PKG_DIR, ".env.example");
|
|
416
|
+
let template = "";
|
|
417
|
+
if (fs.existsSync(examplePath)) {
|
|
418
|
+
template = fs.readFileSync(examplePath, "utf-8");
|
|
419
|
+
} else {
|
|
420
|
+
// Fallback: try sibling (running from cloned repo)
|
|
421
|
+
const siblingExample = path.join(PKG_DIR, "..", ".env.example");
|
|
422
|
+
if (fs.existsSync(siblingExample)) {
|
|
423
|
+
template = fs.readFileSync(siblingExample, "utf-8");
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (template) {
|
|
428
|
+
// Patch each wizard var into the template by replacing the KEY=... line
|
|
429
|
+
for (const [key, val] of Object.entries(vars)) {
|
|
430
|
+
// Match KEY=anything (possibly commented out with #)
|
|
431
|
+
const regex = new RegExp(`^#?\\s*${key}=.*$`, "m");
|
|
432
|
+
if (regex.test(template)) {
|
|
433
|
+
template = template.replace(regex, `${key}=${val}`);
|
|
434
|
+
} else {
|
|
435
|
+
// Key not in template — append it
|
|
436
|
+
template += `\n${key}=${val}`;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// Add wizard timestamp header
|
|
440
|
+
template = `# Generated by sinain setup wizard — ${new Date().toISOString()}\n${template}`;
|
|
441
|
+
fs.writeFileSync(envPath, template);
|
|
442
|
+
} else {
|
|
443
|
+
// No template found — write bare vars (fallback)
|
|
444
|
+
const lines = [];
|
|
445
|
+
lines.push("# sinain configuration — generated by setup wizard");
|
|
446
|
+
lines.push(`# ${new Date().toISOString()}`);
|
|
447
|
+
lines.push("");
|
|
448
|
+
for (const [key, val] of Object.entries(vars)) {
|
|
449
|
+
lines.push(`${key}=${val}`);
|
|
450
|
+
}
|
|
451
|
+
lines.push("");
|
|
452
|
+
fs.writeFileSync(envPath, lines.join("\n"));
|
|
453
|
+
}
|
|
423
454
|
|
|
424
455
|
rl.close();
|
|
425
456
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geravant/sinain",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Ambient AI overlay invisible to screen capture — real-time insights from audio + screen context",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"sinain-core/package.json",
|
|
27
27
|
"sinain-core/package-lock.json",
|
|
28
28
|
"sinain-core/tsconfig.json",
|
|
29
|
-
"
|
|
29
|
+
".env.example",
|
|
30
30
|
"sinain-mcp-server/index.ts",
|
|
31
31
|
"sinain-mcp-server/package.json",
|
|
32
32
|
"sinain-mcp-server/tsconfig.json",
|
package/sinain-agent/run.sh
CHANGED
|
@@ -224,6 +224,37 @@ while true; do
|
|
|
224
224
|
echo ""
|
|
225
225
|
fi
|
|
226
226
|
|
|
227
|
+
# Poll for pending spawn task (queued via HUD Shift+Enter or POST /spawn)
|
|
228
|
+
SPAWN=$(curl -sf "$CORE_URL/spawn/pending" 2>/dev/null || echo '{"ok":false}')
|
|
229
|
+
SPAWN_ID=$(echo "$SPAWN" | python3 -c "import sys,json; d=json.load(sys.stdin); t=d.get('task'); print(t['id'] if t else '')" 2>/dev/null || true)
|
|
230
|
+
|
|
231
|
+
if [ -n "$SPAWN_ID" ]; then
|
|
232
|
+
SPAWN_TASK=$(echo "$SPAWN" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['task']['task'])" 2>/dev/null)
|
|
233
|
+
SPAWN_LABEL=$(echo "$SPAWN" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['task'].get('label','task'))" 2>/dev/null)
|
|
234
|
+
|
|
235
|
+
echo "[$(date +%H:%M:%S)] Spawn task $SPAWN_ID ($SPAWN_LABEL)"
|
|
236
|
+
|
|
237
|
+
if agent_has_mcp; then
|
|
238
|
+
# MCP path: agent runs task with sinain tools available
|
|
239
|
+
SPAWN_PROMPT="You have a background task to complete. Task: $SPAWN_TASK
|
|
240
|
+
|
|
241
|
+
Complete this task thoroughly. Use sinain_get_knowledge and sinain_knowledge_query if you need context from past sessions. Summarize your findings concisely."
|
|
242
|
+
SPAWN_RESULT=$(invoke_agent "$SPAWN_PROMPT" || echo "ERROR: agent invocation failed")
|
|
243
|
+
else
|
|
244
|
+
# Pipe path: agent gets task text directly
|
|
245
|
+
SPAWN_RESULT=$(invoke_pipe "Background task: $SPAWN_TASK" || echo "No output")
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
# Post result back
|
|
249
|
+
if [ -n "$SPAWN_RESULT" ]; then
|
|
250
|
+
curl -sf -X POST "$CORE_URL/spawn/respond" \
|
|
251
|
+
-H 'Content-Type: application/json' \
|
|
252
|
+
-d "{\"id\":\"$SPAWN_ID\",\"result\":$(echo "$SPAWN_RESULT" | json_encode)}" >/dev/null 2>&1 || true
|
|
253
|
+
echo "[$(date +%H:%M:%S)] Spawn $SPAWN_ID completed: ${SPAWN_RESULT:0:120}..."
|
|
254
|
+
fi
|
|
255
|
+
echo ""
|
|
256
|
+
fi
|
|
257
|
+
|
|
227
258
|
# Heartbeat check
|
|
228
259
|
NOW=$(date +%s)
|
|
229
260
|
ELAPSED=$((NOW - LAST_HEARTBEAT))
|
|
@@ -78,6 +78,9 @@ export class Escalator {
|
|
|
78
78
|
private pendingUserCommand: UserCommand | null = null;
|
|
79
79
|
private static readonly USER_COMMAND_EXPIRY_MS = 120_000; // 2 minutes
|
|
80
80
|
|
|
81
|
+
// HTTP spawn queue — for bare agents that poll (mirrors httpPending for escalation)
|
|
82
|
+
private spawnHttpPending: { id: string; task: string; label: string; ts: number } | null = null;
|
|
83
|
+
|
|
81
84
|
private stats = {
|
|
82
85
|
totalEscalations: 0,
|
|
83
86
|
totalResponses: 0,
|
|
@@ -397,6 +400,37 @@ ${recentLines.join("\n")}`;
|
|
|
397
400
|
return { ok: true };
|
|
398
401
|
}
|
|
399
402
|
|
|
403
|
+
/** Return the current HTTP pending spawn task (or null). */
|
|
404
|
+
getSpawnPending(): { id: string; task: string; label: string; ts: number } | null {
|
|
405
|
+
return this.spawnHttpPending;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** Respond to a pending spawn task from a bare agent. */
|
|
409
|
+
respondSpawn(id: string, result: string): { ok: boolean; error?: string } {
|
|
410
|
+
if (!this.spawnHttpPending) {
|
|
411
|
+
return { ok: false, error: "no pending spawn task" };
|
|
412
|
+
}
|
|
413
|
+
if (this.spawnHttpPending.id !== id) {
|
|
414
|
+
return { ok: false, error: `id mismatch: expected ${this.spawnHttpPending.id}` };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const label = this.spawnHttpPending.label;
|
|
418
|
+
const startedAt = this.spawnHttpPending.ts;
|
|
419
|
+
|
|
420
|
+
// Push result to HUD feed
|
|
421
|
+
const maxLen = 3000;
|
|
422
|
+
const text = `[🔧 ${label}] ${result.trim().slice(0, maxLen)}`;
|
|
423
|
+
this.deps.feedBuffer.push(text, "high", "openclaw", "agent");
|
|
424
|
+
this.deps.wsHandler.broadcast(text, "high", "agent");
|
|
425
|
+
|
|
426
|
+
// Broadcast completion
|
|
427
|
+
this.broadcastTaskEvent(id, "completed", label, startedAt, result.slice(0, 200));
|
|
428
|
+
|
|
429
|
+
log(TAG, `spawn ${id} responded (${result.length} chars)`);
|
|
430
|
+
this.spawnHttpPending = null;
|
|
431
|
+
return { ok: true };
|
|
432
|
+
}
|
|
433
|
+
|
|
400
434
|
/** Whether the gateway WS client is currently connected. */
|
|
401
435
|
get isGatewayConnected(): boolean {
|
|
402
436
|
return this.wsClient.isConnected;
|
|
@@ -468,8 +502,12 @@ ${recentLines.join("\n")}`;
|
|
|
468
502
|
this.broadcastTaskEvent(taskId, "spawned", label, startedAt);
|
|
469
503
|
|
|
470
504
|
if (!this.wsClient.isConnected) {
|
|
471
|
-
|
|
472
|
-
this.
|
|
505
|
+
// No OpenClaw gateway — queue for bare agent HTTP polling
|
|
506
|
+
this.spawnHttpPending = { id: taskId, task, label: label || "background-task", ts: startedAt };
|
|
507
|
+
const preview = task.length > 60 ? task.slice(0, 60) + "…" : task;
|
|
508
|
+
this.deps.feedBuffer.push(`🔧 Task queued for agent: ${preview}`, "normal", "system", "stream");
|
|
509
|
+
this.deps.wsHandler.broadcast(`🔧 Task queued for agent: ${preview}`, "normal");
|
|
510
|
+
log(TAG, `spawn-task ${taskId}: WS disconnected — queued for bare agent polling`);
|
|
473
511
|
return;
|
|
474
512
|
}
|
|
475
513
|
|
package/sinain-core/src/index.ts
CHANGED
|
@@ -416,6 +416,15 @@ async function main() {
|
|
|
416
416
|
return execFileSync("python3", args, { timeout: 5000, encoding: "utf-8" });
|
|
417
417
|
} catch { return ""; }
|
|
418
418
|
},
|
|
419
|
+
|
|
420
|
+
// Spawn background agent task (from HUD Shift+Enter or bare agent POST /spawn)
|
|
421
|
+
onSpawnCommand: (text: string) => {
|
|
422
|
+
escalator.dispatchSpawnTask(text, "user-command").catch((err) => {
|
|
423
|
+
log("srv", `spawn via HTTP failed: ${err}`);
|
|
424
|
+
});
|
|
425
|
+
},
|
|
426
|
+
getSpawnPending: () => escalator.getSpawnPending(),
|
|
427
|
+
respondSpawn: (id: string, result: string) => escalator.respondSpawn(id, result),
|
|
419
428
|
});
|
|
420
429
|
|
|
421
430
|
// ── Wire overlay profiling ──
|
|
@@ -435,6 +444,12 @@ async function main() {
|
|
|
435
444
|
onUserCommand: (text) => {
|
|
436
445
|
escalator.setUserCommand(text);
|
|
437
446
|
},
|
|
447
|
+
onSpawnCommand: (text) => {
|
|
448
|
+
escalator.dispatchSpawnTask(text, "user-command").catch((err) => {
|
|
449
|
+
log("cmd", `spawn command failed: ${err}`);
|
|
450
|
+
wsHandler.broadcast(`\u26a0 Spawn failed: ${String(err).slice(0, 100)}`, "normal");
|
|
451
|
+
});
|
|
452
|
+
},
|
|
438
453
|
onToggleScreen: () => {
|
|
439
454
|
screenActive = !screenActive;
|
|
440
455
|
if (!screenActive) {
|
|
@@ -15,6 +15,8 @@ export interface CommandDeps {
|
|
|
15
15
|
onUserMessage: (text: string) => Promise<void>;
|
|
16
16
|
/** Queue a user command to augment the next escalation */
|
|
17
17
|
onUserCommand: (text: string) => void;
|
|
18
|
+
/** Spawn a background agent task */
|
|
19
|
+
onSpawnCommand?: (text: string) => void;
|
|
18
20
|
/** Toggle screen capture — returns new state */
|
|
19
21
|
onToggleScreen: () => boolean;
|
|
20
22
|
/** Toggle trait voices — returns new enabled state */
|
|
@@ -44,6 +46,17 @@ export function setupCommands(deps: CommandDeps): void {
|
|
|
44
46
|
deps.onUserCommand(msg.text);
|
|
45
47
|
break;
|
|
46
48
|
}
|
|
49
|
+
case "spawn_command": {
|
|
50
|
+
const preview = msg.text.length > 60 ? msg.text.slice(0, 60) + "…" : msg.text;
|
|
51
|
+
log(TAG, `spawn command received: "${preview}"`);
|
|
52
|
+
if (deps.onSpawnCommand) {
|
|
53
|
+
deps.onSpawnCommand(msg.text);
|
|
54
|
+
} else {
|
|
55
|
+
log(TAG, `spawn command ignored — no handler configured`);
|
|
56
|
+
wsHandler.broadcast(`⚠ Spawn not available (no agent gateway connected)`, "normal");
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
47
60
|
case "command": {
|
|
48
61
|
handleCommand(msg.action, deps);
|
|
49
62
|
log(TAG, `command processed: ${msg.action}`);
|
|
@@ -39,6 +39,9 @@ export interface ServerDeps {
|
|
|
39
39
|
respondEscalation?: (id: string, response: string) => any;
|
|
40
40
|
getKnowledgeDocPath?: () => string | null;
|
|
41
41
|
queryKnowledgeFacts?: (entities: string[], maxFacts: number) => Promise<string>;
|
|
42
|
+
onSpawnCommand?: (text: string) => void;
|
|
43
|
+
getSpawnPending?: () => { id: string; task: string; label: string; ts: number } | null;
|
|
44
|
+
respondSpawn?: (id: string, result: string) => { ok: boolean; error?: string };
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
function readBody(req: IncomingMessage, maxBytes: number): Promise<string> {
|
|
@@ -341,6 +344,45 @@ export function createAppServer(deps: ServerDeps) {
|
|
|
341
344
|
return;
|
|
342
345
|
}
|
|
343
346
|
|
|
347
|
+
// ── /spawn ──
|
|
348
|
+
if (req.method === "POST" && url.pathname === "/spawn") {
|
|
349
|
+
const body = await readBody(req, 65536);
|
|
350
|
+
const { text, label } = JSON.parse(body);
|
|
351
|
+
if (!text) {
|
|
352
|
+
res.writeHead(400);
|
|
353
|
+
res.end(JSON.stringify({ ok: false, error: "missing text" }));
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (deps.onSpawnCommand) {
|
|
357
|
+
deps.onSpawnCommand(text);
|
|
358
|
+
res.end(JSON.stringify({ ok: true, spawned: true }));
|
|
359
|
+
} else {
|
|
360
|
+
res.end(JSON.stringify({ ok: false, error: "spawn not configured" }));
|
|
361
|
+
}
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ── /spawn/pending (bare agent polls for queued tasks) ──
|
|
366
|
+
if (req.method === "GET" && url.pathname === "/spawn/pending") {
|
|
367
|
+
const task = deps.getSpawnPending?.() ?? null;
|
|
368
|
+
res.end(JSON.stringify({ ok: true, task }));
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ── /spawn/respond (bare agent returns task result) ──
|
|
373
|
+
if (req.method === "POST" && url.pathname === "/spawn/respond") {
|
|
374
|
+
const body = await readBody(req, 65536);
|
|
375
|
+
const { id, result } = JSON.parse(body);
|
|
376
|
+
if (!id || !result) {
|
|
377
|
+
res.writeHead(400);
|
|
378
|
+
res.end(JSON.stringify({ ok: false, error: "missing id or result" }));
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const resp = deps.respondSpawn?.(id, result) ?? { ok: false, error: "spawn not configured" };
|
|
382
|
+
res.end(JSON.stringify(resp));
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
344
386
|
res.writeHead(404);
|
|
345
387
|
res.end(JSON.stringify({ error: "not found" }));
|
|
346
388
|
} catch (err: any) {
|
package/sinain-core/src/types.ts
CHANGED
|
@@ -72,8 +72,14 @@ export interface UserCommandMessage {
|
|
|
72
72
|
text: string;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/** Overlay → sinain-core: spawn a background agent task */
|
|
76
|
+
export interface SpawnCommandMessage {
|
|
77
|
+
type: "spawn_command";
|
|
78
|
+
text: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
75
81
|
export type OutboundMessage = FeedMessage | StatusMessage | PingMessage | SpawnTaskMessage;
|
|
76
|
-
export type InboundMessage = UserMessage | CommandMessage | PongMessage | ProfilingMessage | UserCommandMessage;
|
|
82
|
+
export type InboundMessage = UserMessage | CommandMessage | PongMessage | ProfilingMessage | UserCommandMessage | SpawnCommandMessage;
|
|
77
83
|
|
|
78
84
|
/** Abstraction for user commands (text now, voice later). */
|
|
79
85
|
export interface UserCommand {
|
|
@@ -152,6 +152,24 @@ server.tool(
|
|
|
152
152
|
);
|
|
153
153
|
|
|
154
154
|
// 6. sinain_post_feed
|
|
155
|
+
// 6b. sinain_spawn
|
|
156
|
+
server.tool(
|
|
157
|
+
"sinain_spawn",
|
|
158
|
+
"Spawn a background agent task via sinain-core",
|
|
159
|
+
{
|
|
160
|
+
task: z.string(),
|
|
161
|
+
label: z.string().optional().default("background-task"),
|
|
162
|
+
},
|
|
163
|
+
async ({ task, label }) => {
|
|
164
|
+
try {
|
|
165
|
+
const data = await coreRequest("POST", "/spawn", { text: task, label });
|
|
166
|
+
return textResult(JSON.stringify(data, null, 2));
|
|
167
|
+
} catch (err: any) {
|
|
168
|
+
return textResult(`Error spawning task: ${err.message}`);
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
|
|
155
173
|
server.tool(
|
|
156
174
|
"sinain_post_feed",
|
|
157
175
|
"Post a message to the sinain-core HUD feed",
|
package/sinain-core/.env.example
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
# sinain-core configuration
|
|
2
|
-
# Copy to .env and fill in your values: cp .env.example .env
|
|
3
|
-
|
|
4
|
-
# ── Server ──
|
|
5
|
-
PORT=9500
|
|
6
|
-
|
|
7
|
-
# ── System Audio ──
|
|
8
|
-
# Default: ScreenCaptureKit (zero-setup, macOS 13+). Fallback: ffmpeg + BlackHole.
|
|
9
|
-
AUDIO_CAPTURE_CMD=screencapturekit # screencapturekit | sox | ffmpeg
|
|
10
|
-
AUDIO_DEVICE=BlackHole 2ch # macOS audio device (only used by sox/ffmpeg)
|
|
11
|
-
AUDIO_SAMPLE_RATE=16000
|
|
12
|
-
AUDIO_CHUNK_MS=5000
|
|
13
|
-
AUDIO_VAD_ENABLED=true
|
|
14
|
-
AUDIO_VAD_THRESHOLD=0.003
|
|
15
|
-
AUDIO_AUTO_START=true
|
|
16
|
-
AUDIO_GAIN_DB=20
|
|
17
|
-
|
|
18
|
-
# ── Microphone (opt-in for privacy) ──
|
|
19
|
-
MIC_ENABLED=false # set true to capture user's microphone
|
|
20
|
-
MIC_DEVICE=default # "default" = system mic. For specific device: use exact CoreAudio name
|
|
21
|
-
MIC_CAPTURE_CMD=sox # sox or ffmpeg (mic uses sox by default)
|
|
22
|
-
MIC_SAMPLE_RATE=16000
|
|
23
|
-
MIC_CHUNK_MS=5000
|
|
24
|
-
MIC_VAD_ENABLED=true
|
|
25
|
-
MIC_VAD_THRESHOLD=0.008 # higher threshold (ambient noise)
|
|
26
|
-
MIC_AUTO_START=false
|
|
27
|
-
MIC_GAIN_DB=0
|
|
28
|
-
|
|
29
|
-
# ── Transcription ──
|
|
30
|
-
OPENROUTER_API_KEY= # required (unless TRANSCRIPTION_BACKEND=local)
|
|
31
|
-
TRANSCRIPTION_BACKEND=openrouter # openrouter | local (local = whisper.cpp on-device)
|
|
32
|
-
TRANSCRIPTION_MODEL=google/gemini-2.5-flash
|
|
33
|
-
TRANSCRIPTION_LANGUAGE=en-US
|
|
34
|
-
|
|
35
|
-
# ── Local Transcription (only when TRANSCRIPTION_BACKEND=local) ──
|
|
36
|
-
# Install: brew install whisper-cpp
|
|
37
|
-
# Models: https://huggingface.co/ggerganov/whisper.cpp/tree/main
|
|
38
|
-
# LOCAL_WHISPER_BIN=whisper-cli
|
|
39
|
-
# LOCAL_WHISPER_MODEL=~/models/ggml-large-v3-turbo.bin
|
|
40
|
-
# LOCAL_WHISPER_TIMEOUT_MS=15000
|
|
41
|
-
|
|
42
|
-
# ── Agent ──
|
|
43
|
-
AGENT_ENABLED=true
|
|
44
|
-
AGENT_MODEL=google/gemini-2.5-flash-lite
|
|
45
|
-
# AGENT_FALLBACK_MODELS=google/gemini-2.5-flash,anthropic/claude-3.5-haiku
|
|
46
|
-
AGENT_MAX_TOKENS=300
|
|
47
|
-
AGENT_TEMPERATURE=0.3
|
|
48
|
-
AGENT_PUSH_TO_FEED=true
|
|
49
|
-
AGENT_DEBOUNCE_MS=3000
|
|
50
|
-
AGENT_MAX_INTERVAL_MS=30000
|
|
51
|
-
AGENT_COOLDOWN_MS=10000
|
|
52
|
-
AGENT_MAX_AGE_MS=120000 # context window lookback (2 min)
|
|
53
|
-
|
|
54
|
-
# ── Escalation ──
|
|
55
|
-
ESCALATION_MODE=selective # off | selective | focus | rich
|
|
56
|
-
ESCALATION_COOLDOWN_MS=30000
|
|
57
|
-
# ESCALATION_TRANSPORT=auto # ws | http | auto — use http for bare agent (no gateway)
|
|
58
|
-
# auto = WS when gateway connected, HTTP fallback
|
|
59
|
-
# http = skip gateway entirely, poll via GET /escalation/pending
|
|
60
|
-
# See docs/INSTALL-BARE-AGENT.md for bare agent setup
|
|
61
|
-
|
|
62
|
-
# ── OpenClaw / NemoClaw Gateway ─────────────────────────────────────────────
|
|
63
|
-
# Run ./setup-nemoclaw.sh to fill these in interactively (recommended).
|
|
64
|
-
#
|
|
65
|
-
# NemoClaw (NVIDIA Brev) quick-start:
|
|
66
|
-
# 1. In Brev dashboard: Expose Port(s) → enter 18789 → TCP → note the IP
|
|
67
|
-
# 2. In Code-Server terminal: npx sinain (installs plugin, prints token)
|
|
68
|
-
# 3. On Mac: ./setup-nemoclaw.sh (interactive wizard)
|
|
69
|
-
#
|
|
70
|
-
# URL: ws://YOUR-IP:18789 (use the IP shown after exposing port 18789)
|
|
71
|
-
# Token: printed by `npx sinain` / visible in Brev dashboard → Gateway Token
|
|
72
|
-
OPENCLAW_WS_URL=ws://localhost:18789
|
|
73
|
-
OPENCLAW_WS_TOKEN= # 48-char hex — from gateway config or `npx sinain` output
|
|
74
|
-
OPENCLAW_HTTP_URL=http://localhost:18789/hooks/agent
|
|
75
|
-
OPENCLAW_HTTP_TOKEN= # same token as WS_TOKEN
|
|
76
|
-
OPENCLAW_SESSION_KEY=agent:main:sinain # MUST be agent:main:sinain — see README § Session Key
|
|
77
|
-
# OPENCLAW_PHASE1_TIMEOUT_MS=10000 # Phase 1 (delivery) timeout — circuit trips on failure
|
|
78
|
-
# OPENCLAW_PHASE2_TIMEOUT_MS=120000 # Phase 2 (agent response) timeout — no circuit trip
|
|
79
|
-
# OPENCLAW_QUEUE_TTL_MS=300000 # Outbound queue message TTL (5 min)
|
|
80
|
-
# OPENCLAW_QUEUE_MAX_SIZE=10 # Max queued escalations (oldest dropped on overflow)
|
|
81
|
-
# OPENCLAW_PING_INTERVAL_MS=30000 # WS ping keepalive interval
|
|
82
|
-
|
|
83
|
-
# ── SITUATION.md ──
|
|
84
|
-
SITUATION_MD_PATH=~/.openclaw/workspace/SITUATION.md
|
|
85
|
-
# OPENCLAW_WORKSPACE_DIR=~/.openclaw/workspace
|
|
86
|
-
|
|
87
|
-
# ── Debug ──
|
|
88
|
-
# DEBUG=true # verbose logging (every tick, every chunk)
|
|
89
|
-
|
|
90
|
-
# ── Tracing ──
|
|
91
|
-
TRACE_ENABLED=true
|
|
92
|
-
TRACE_DIR=~/.sinain-core/traces
|