@geravant/sinain 1.18.1 → 1.18.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/onboard.js CHANGED
@@ -154,25 +154,54 @@ export async function runOnboard(args = {}) {
154
154
  p.log.success("API key saved.");
155
155
 
156
156
  if (flow === "quickstart") {
157
- // QuickStart: sensible defaults. Gateway is opt-in (skip by default);
158
- // agent + escalation defaults go to agents.json.
157
+ // QuickStart: sensible defaults + a single opt-in question for OpenClaw.
158
+ // Gateway integration is off by default; users who want it run Advanced
159
+ // (or answer Yes here, which then walks them through stepGateway).
159
160
  vars.TRANSCRIPTION_BACKEND = base.TRANSCRIPTION_BACKEND || "openrouter";
160
161
  vars.PRIVACY_MODE = base.PRIVACY_MODE || "standard";
161
162
  vars.AGENT_MODEL = base.AGENT_MODEL || "google/gemini-2.5-flash-lite";
163
+
164
+ // Ask explicitly so first-run installs don't silently inherit a gateway
165
+ // profile from agents.example.json. Default reflects current state — No
166
+ // for fresh installs (silences the WS reconnect loop), Yes for re-runs
167
+ // that already had OpenClaw configured (so we don't surprise-delete it).
168
+ const hasExistingGateway = (() => {
169
+ try {
170
+ const agentsPath = path.join(SINAIN_DIR, "agents.json");
171
+ if (!fs.existsSync(agentsPath)) return false;
172
+ const cfg = JSON.parse(fs.readFileSync(agentsPath, "utf-8"));
173
+ return !!cfg?.profiles?.openclaw;
174
+ } catch { return false; }
175
+ })();
176
+ const enableGateway = guard(await p.confirm({
177
+ message: "Enable OpenClaw gateway integration?",
178
+ initialValue: hasExistingGateway,
179
+ }));
180
+
162
181
  agentsPatch = {
163
182
  default: base.SINAIN_AGENT || "claude",
164
183
  escalationMode: "off",
165
- // Don't touch openclawProfile in quickstart — keeps existing config
166
- // intact for re-runs; first-time users get the "skip" state from
167
- // agents.example.json bootstrap (no openclaw profile means no gateway).
168
184
  };
169
185
 
186
+ if (enableGateway) {
187
+ // Walk through full gateway setup (URL, tokens, session key) — same
188
+ // step Advanced uses. Returns { envVars, agentsPatch } we merge in.
189
+ const gatewayResult = await stepGateway(base, "OpenClaw gateway");
190
+ Object.assign(vars, gatewayResult.envVars);
191
+ Object.assign(agentsPatch, gatewayResult.agentsPatch);
192
+ } else {
193
+ // Explicitly clear any inherited openclaw profile so the runtime
194
+ // doesn't auto-register the gateway or attempt WS reconnects.
195
+ agentsPatch.openclawProfile = null;
196
+ }
197
+
170
198
  p.note(
171
199
  [
172
200
  `Transcription: ${vars.TRANSCRIPTION_BACKEND}`,
173
201
  `Privacy: ${vars.PRIVACY_MODE}`,
174
202
  `Model: ${vars.AGENT_MODEL}`,
175
- `Escalation: off (pick a gateway agent in the overlay to enable)`,
203
+ `OpenClaw gateway: ${enableGateway ? "enabled" : "disabled"}`,
204
+ `Escalation: ${agentsPatch.escalationMode || "off"}`,
176
205
  "",
177
206
  `Change later: sinain config`,
178
207
  ].join("\n"),
@@ -368,10 +397,9 @@ if (flags.nonInteractive) {
368
397
  }
369
398
 
370
399
  writeEnv(vars);
371
- // Default agent + escalation off (no gateway by default for non-interactive).
372
- // openclaw profile is left as-is from the example template (or absent
373
- // if first run) gateway is opt-in via the wizard or `sinain config`.
374
- writeAgentsConfig({ default: "claude", escalationMode: "off" });
400
+ // Default agent + escalation off + openclaw explicitly disabled. Gateway is
401
+ // opt-in via the interactive wizard (`sinain onboard`) or `sinain config`.
402
+ writeAgentsConfig({ default: "claude", escalationMode: "off", openclawProfile: null });
375
403
  console.log(c.green(` Config written to ${ENV_PATH} + ~/.sinain/agents.json`));
376
404
  process.exit(0);
377
405
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geravant/sinain",
3
- "version": "1.18.1",
3
+ "version": "1.18.2",
4
4
  "description": "Ambient intelligence that sees what you see, hears what you hear, and acts on your behalf",
5
5
  "type": "module",
6
6
  "bin": {
@@ -64,7 +64,7 @@
64
64
  },
65
65
 
66
66
  "escalation": {
67
- "mode": "rich",
67
+ "mode": "off",
68
68
  "cooldownMs": 30000,
69
69
  "staleMs": 90000
70
70
  },
@@ -81,8 +81,15 @@
81
81
  "OPENAI_API_KEY": "${OPENROUTER_API_KEY}"
82
82
  }
83
83
  },
84
+ "codex": { "type": "codex" },
85
+ "goose": { "type": "goose" },
86
+ "junie": { "type": "junie" },
87
+ "aider": { "type": "aider" }
88
+ },
89
+
90
+ "_examples": {
84
91
  "openclaw": {
85
- "_comment": "Remote gateway routing. Selecting openclaw for a lane sends that lane's traffic to the OpenClaw gateway via WS RPC instead of the local bare agent. sinain-core reads these fields directly at startup to construct the WS client. Removing this profile disables the gateway path entirely (no WS client, no openclaw chip in the overlay roster).",
92
+ "_comment": "OpenClaw gateway routing. Move this entry into `profiles` to enable. The wizard's `sinain onboard --advanced` step or `sinain config gateway` populates this section automatically with your gateway URL/tokens. Selecting openclaw for a lane sends that lane's traffic via WS RPC to the gateway instead of the local bare agent. Disabled by default so first-run installs don't attempt WS connections to a gateway that isn't running.",
86
93
  "type": "openclaw",
87
94
  "wsUrl": "ws://localhost:18789",
88
95
  "wsToken": "${OPENCLAW_WS_TOKEN}",
@@ -93,13 +100,6 @@
93
100
  "phase2TimeoutMs": 120000,
94
101
  "pingIntervalMs": 30000
95
102
  },
96
- "codex": { "type": "codex" },
97
- "goose": { "type": "goose" },
98
- "junie": { "type": "junie" },
99
- "aider": { "type": "aider" }
100
- },
101
-
102
- "_examples": {
103
103
  "pclaude": {
104
104
  "_comment": "Personal claude config. `bin` must be a real PATH binary — replicate a shell alias as bin+env (the alias `pclaude=CLAUDE_CONFIG_DIR=$HOME/.claude-personal claude` becomes the entry below).",
105
105
  "type": "claude",
@@ -99,6 +99,12 @@ export class Escalator {
99
99
  // Store context from last escalation for response handling
100
100
  private lastEscalationContext: ContextWindow | null = null;
101
101
 
102
+ // Knowledge enrichment is skipped on the very first escalation per process
103
+ // to avoid the 5s fetchKnowledgeFacts() cold-start tax on user-perceived
104
+ // first-response latency. Each subsequent escalation does its own fetch
105
+ // independently — no cross-escalation cache, no shared content state.
106
+ private firstEscalationDone = false;
107
+
102
108
  // User command to inject into the next escalation
103
109
  private pendingUserCommand: UserCommand | null = null;
104
110
  private static readonly USER_COMMAND_EXPIRY_MS = 120_000; // 2 minutes
@@ -281,8 +287,10 @@ export class Escalator {
281
287
  // Clear user command after building the message (consumed once)
282
288
  this.pendingUserCommand = null;
283
289
 
284
- // Enrich with long-term knowledge facts (best-effort, 5s max)
285
- if (this.deps.queryKnowledgeFacts) {
290
+ // Enrich with long-term knowledge facts (best-effort, 5s max).
291
+ // Skipped on the inaugural escalation per process to eliminate cold-start
292
+ // latency — the user's first response shouldn't wait for KG warmup.
293
+ if (this.deps.queryKnowledgeFacts && this.firstEscalationDone) {
286
294
  try {
287
295
  const knowledgeSection = await fetchKnowledgeFacts(
288
296
  contextWindow, entry.digest, this.deps.queryKnowledgeFacts,
@@ -294,7 +302,10 @@ export class Escalator {
294
302
  } catch (err) {
295
303
  log(TAG, `knowledge enrichment failed: ${String(err)}`);
296
304
  }
305
+ } else if (!this.firstEscalationDone) {
306
+ log(TAG, `first escalation: skipping knowledge fetch (fast path)`);
297
307
  }
308
+ this.firstEscalationDone = true;
298
309
 
299
310
  const slotId = createHash("sha256").update(this.deps.openclawConfig.sessionKey + entry.ts).digest("hex").slice(0, 16);
300
311
  const slotEntry: SlotEntry = {