@sickr/cli 0.9.8 → 0.9.9

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/dist/recorder.js CHANGED
@@ -93,7 +93,8 @@ export function mapEvent(cc, now = new Date(), ctx = {}) {
93
93
  const rawSession = String(cc.session_id ?? '');
94
94
  const session = rawSession ? rawSession.slice(0, 12) : undefined;
95
95
  const agent = ctx.agent;
96
- const base = { at, agent, session };
96
+ const runner = process.env.SICKR_RUN_INSTANCE ? process.env.SICKR_RUN_INSTANCE.slice(0, 36) : undefined;
97
+ const base = { at, agent, session, runner };
97
98
  switch (name) {
98
99
  case 'SessionStart':
99
100
  return { kind: 'start', label: 'Session', detail: redact(String(cc.cwd ?? '')), ...base };
package/dist/run.js CHANGED
@@ -23,6 +23,7 @@
23
23
  import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync, readdirSync, statSync, openSync, readSync, closeSync, unlinkSync } from 'node:fs';
24
24
  import { homedir } from 'node:os';
25
25
  import { join } from 'node:path';
26
+ import { randomUUID } from 'node:crypto';
26
27
  import { execFileSync } from 'node:child_process';
27
28
  import { setTimeout as sleep } from 'node:timers/promises';
28
29
  import { readCredentials } from './auth.js';
@@ -111,6 +112,18 @@ function appendInbox(urlid, text, at) {
111
112
  writeFileSync(file, `# steer inbox — ${urlid}\n\n`);
112
113
  appendFileSync(file, `\n## ${at}\n\n${text}\n`);
113
114
  }
115
+ function normTarget(s) {
116
+ return String(s ?? '').trim().toLowerCase();
117
+ }
118
+ export function steerMatchesRunner(msg, identity) {
119
+ if (msg.targetRunner)
120
+ return msg.targetRunner === identity.runner;
121
+ if (msg.targetSession)
122
+ return identity.sessions.has(msg.targetSession);
123
+ if (msg.targetAgent)
124
+ return normTarget(msg.targetAgent) === normTarget(identity.agent);
125
+ return true;
126
+ }
114
127
  export function decideSteer(msg, defaultMode = 'pty') {
115
128
  const text = String(msg.text ?? '');
116
129
  if (!text)
@@ -282,6 +295,10 @@ export async function startRun(opts) {
282
295
  // browser /r/<urlid> stays empty until events flow.
283
296
  ensureRecordingHooks(opts.agent);
284
297
  const agentBin = resolveAgent(opts.agent);
298
+ const provider = providerForAgent(opts.agent);
299
+ const agentLabel = provider ? PROVIDERS[provider].recordLabel : opts.agent;
300
+ const runnerId = randomUUID();
301
+ const runnerIdentity = { agent: agentLabel, runner: runnerId, sessions: new Set() };
285
302
  const cols = process.stdout.columns ?? 80;
286
303
  const rows = process.stdout.rows ?? 24;
287
304
  // mode=auto (default) injects the agent's "no prompt / full perms" flag
@@ -307,7 +324,7 @@ export async function startRun(opts) {
307
324
  name: 'xterm-256color',
308
325
  cols, rows,
309
326
  cwd: process.cwd(),
310
- env: process.env,
327
+ env: { ...process.env, SICKR_RUN_INSTANCE: runnerId, SICKR_RUN_AGENT: agentLabel },
311
328
  });
312
329
  // Wire stdio: parent stdin -> pty; pty -> parent stdout.
313
330
  if (process.stdin.isTTY)
@@ -379,7 +396,11 @@ export async function startRun(opts) {
379
396
  let opened = false;
380
397
  ws.addEventListener('open', () => {
381
398
  opened = true;
382
- tailTimer = setInterval(() => pumpNewLines(ws, offsets), 500);
399
+ try {
400
+ ws.send(JSON.stringify({ kind: 'hello', agent: runnerIdentity.agent, runner: runnerIdentity.runner }));
401
+ }
402
+ catch { /* reconnect loop handles failures */ }
403
+ tailTimer = setInterval(() => pumpNewLines(ws, offsets, runnerIdentity), 500);
383
404
  });
384
405
  ws.addEventListener('message', (ev) => {
385
406
  const raw = decodeWsPayload(ev.data);
@@ -393,6 +414,11 @@ export async function startRun(opts) {
393
414
  return;
394
415
  }
395
416
  if (m.kind === 'steer' && m.text) {
417
+ if (!steerMatchesRunner(m, runnerIdentity)) {
418
+ if (opts.verbose)
419
+ process.stderr.write(`sickr: ignored steer for another agent/session\n`);
420
+ return;
421
+ }
396
422
  const decision = decideSteer(m);
397
423
  if (decision.target === 'pty' && decision.bytes != null) {
398
424
  // Submit-fix (2026-06-01): Claude Code's TUI treats a burst of
@@ -459,7 +485,7 @@ async function loadWsShim() {
459
485
  throw new Error('`ws` is unavailable. It ships as an optional dependency of @sickr/cli; if it failed to install, try: `npm install -g ws`.');
460
486
  }
461
487
  }
462
- function pumpNewLines(ws, offsets) {
488
+ function pumpNewLines(ws, offsets, identity) {
463
489
  const dir = runsDir();
464
490
  if (!existsSync(dir))
465
491
  return;
@@ -484,6 +510,12 @@ function pumpNewLines(ws, offsets) {
484
510
  for (const fragment of splitJsonObjects(line)) {
485
511
  try {
486
512
  const event = JSON.parse(fragment);
513
+ if (event.runner && event.runner !== identity.runner)
514
+ continue;
515
+ if (!event.runner && event.agent && normTarget(event.agent) !== normTarget(identity.agent))
516
+ continue;
517
+ if (event.session)
518
+ identity.sessions.add(event.session);
487
519
  ws.send(JSON.stringify({ kind: 'event', event }));
488
520
  }
489
521
  catch { /* skip malformed */ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sickr/cli",
3
- "version": "0.9.8",
3
+ "version": "0.9.9",
4
4
  "type": "module",
5
5
  "description": "npx @sickr/cli - replay, live look, and workflow orchestration for AI coding agents.",
6
6
  "bin": {