@geravant/sinain 1.18.1 → 1.18.3

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,58 @@ 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
- 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).
183
+ // No `escalationMode` written — lane (default agent) is the single
184
+ // source of truth for whether escalation runs. If the user picks a
185
+ // local agent (claude), the runtime's default mode ("rich" from
186
+ // config.ts) takes effect and registerBareAgent ensures lane and
187
+ // mode stay reconciled at boot.
168
188
  };
169
189
 
190
+ if (enableGateway) {
191
+ // Walk through full gateway setup (URL, tokens, session key) — same
192
+ // step Advanced uses. Returns { envVars, agentsPatch } we merge in.
193
+ const gatewayResult = await stepGateway(base, "OpenClaw gateway");
194
+ Object.assign(vars, gatewayResult.envVars);
195
+ Object.assign(agentsPatch, gatewayResult.agentsPatch);
196
+ } else {
197
+ // Explicitly clear any inherited openclaw profile so the runtime
198
+ // doesn't auto-register the gateway or attempt WS reconnects.
199
+ agentsPatch.openclawProfile = null;
200
+ }
201
+
170
202
  p.note(
171
203
  [
172
204
  `Transcription: ${vars.TRANSCRIPTION_BACKEND}`,
173
205
  `Privacy: ${vars.PRIVACY_MODE}`,
174
206
  `Model: ${vars.AGENT_MODEL}`,
175
- `Escalation: off (pick a gateway agent in the overlay to enable)`,
207
+ `OpenClaw gateway: ${enableGateway ? "enabled" : "disabled"}`,
208
+ `Escalation: ${agentsPatch.escalationMode || "off"}`,
176
209
  "",
177
210
  `Change later: sinain config`,
178
211
  ].join("\n"),
@@ -368,10 +401,9 @@ if (flags.nonInteractive) {
368
401
  }
369
402
 
370
403
  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" });
404
+ // Default agent + openclaw explicitly disabled. No escalation mode written
405
+ // lane is the source of truth, registerBareAgent reconciles at boot.
406
+ writeAgentsConfig({ default: "claude", openclawProfile: null });
375
407
  console.log(c.green(` Config written to ${ENV_PATH} + ~/.sinain/agents.json`));
376
408
  process.exit(0);
377
409
  } 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.3",
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
+ "_comment": "Lane (the agent picked for escalation in agents.json `default` or via the overlay chip) is the source of truth for whether escalation runs. `mode` is intentionally omitted here — the runtime default (rich) applies, and registerBareAgent reconciles mode with lane at boot. Override `mode` only if you want to force selective/focus/off semantics.",
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 = {
@@ -734,6 +734,16 @@ async function main() {
734
734
  }
735
735
  wsHandler.updateState({ agents: { ...bareAgentState } });
736
736
  log(TAG, `bareagent register: available=[${clean.join(",")}] current=${current} → lanes esc=${bareAgentState.escalationAgent} spawn=${bareAgentState.spawnAgent}`);
737
+
738
+ // Lane is the source of truth for "is escalation active?". If a lane is
739
+ // set but mode is still "off" (e.g. an old wizard run wrote mode=off and
740
+ // the user has since picked an agent in the chip selector — or we just
741
+ // booted from agents.json with that combination), reconcile by promoting
742
+ // mode to match. Mirrors the existing set_agent → resumeEscalation flow,
743
+ // applied at register time so the boot-from-disk case isn't an exception.
744
+ if (bareAgentState.escalationAgent && config.escalationConfig.mode === "off") {
745
+ resumeEscalationInternal();
746
+ }
737
747
  }
738
748
 
739
749
  // ── Create HTTP + WS server ──