@commonlyai/cli 0.1.187 → 0.1.189

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonlyai/cli",
3
- "version": "0.1.187",
3
+ "version": "0.1.189",
4
4
  "license": "Apache-2.0",
5
5
  "description": "The Commonly CLI — connect agents, manage pods, iterate fast",
6
6
  "type": "module",
@@ -2982,8 +2982,11 @@ export const performRun = ({
2982
2982
  // below reaches the SAME composite turn before it can age into the
2983
2983
  // requeue sweep. The service selects the pod before claiming any row;
2984
2984
  // do not reintroduce a mixed-pod `limit: 10` here.
2985
+ // TASK-317: name this CLI for the chat chip. The server keeps it as a
2986
+ // display-only label (runtime.reportedCli) and never as a binding.
2985
2987
  const { events = [], inboxCount } = await client.get('/api/agents/runtime/events', {
2986
2988
  agentName, instanceId, limit: batchLimit,
2989
+ ...(adapter?.name ? { reportedCli: adapter.name } : {}),
2987
2990
  });
2988
2991
  consecutiveAuthErrors = 0;
2989
2992
  consecutivePollFailures = 0;
@@ -301,12 +301,19 @@ const validateProviderEnvironment = async (environment, workspacePath) => {
301
301
  return resolveProviderKeyFile(environment.provider, workspacePath);
302
302
  };
303
303
 
304
+ const STALE_SESSION_PATTERN = /session not found/i;
305
+ // Set by runOpenCode only when the rejected run produced no turn output, so a
306
+ // phrase that shows up after the model acted cannot replay the turn.
307
+ const isStaleSessionError = (err) => err?.staleSession === true;
308
+
304
309
  const makeEventParser = () => {
305
310
  let buffer = '';
306
311
  let sessionId = null;
307
312
  let text = '';
308
313
  let usage = null;
309
314
  let errorMessage = null;
315
+ // Any text, tool call or step result means the model acted this run.
316
+ let turnOutput = false;
310
317
  // Every tool call that came back as an error, for the public-seat check in
311
318
  // spawn (publicToolFailure).
312
319
  const toolErrors = [];
@@ -315,11 +322,15 @@ const makeEventParser = () => {
315
322
  try {
316
323
  const event = JSON.parse(line);
317
324
  if (event.sessionID) sessionId = event.sessionID;
325
+ // Any step event means the run started a turn, so it is not safe to replay.
326
+ if (String(event.type || '').startsWith('step_') || event.type === 'tool_use') turnOutput = true;
318
327
  if (event.type === 'text' && typeof event.part?.text === 'string') {
328
+ turnOutput = true;
319
329
  text += event.part.text;
320
330
  } else if (event.type === 'step_finish' && event.part?.tokens) {
321
331
  usage = event.part.tokens;
322
- } else if (event.type === 'tool_use' && event.part?.state?.status === 'error') {
332
+ }
333
+ if (event.type === 'tool_use' && event.part?.state?.status === 'error') {
323
334
  const { error } = event.part.state;
324
335
  toolErrors.push({
325
336
  tool: String(event.part.tool || ''),
@@ -334,6 +345,7 @@ const makeEventParser = () => {
334
345
  }
335
346
  };
336
347
  return {
348
+ get turnOutput() { return turnOutput; },
337
349
  consume(chunk) {
338
350
  buffer += chunk.toString();
339
351
  let newline;
@@ -428,14 +440,18 @@ const runOpenCode = ({ cmd, args, cwd, env, timeoutMs, credentials, spawnImpl =
428
440
  events.flush();
429
441
  if (timedOut) return reject(new Error(`opencode timed out after ${timeoutMs}ms`));
430
442
  if (events.errorMessage) {
431
- return reject(adapterFailure('opencode turn failed', events.errorMessage, {
443
+ const err = adapterFailure('opencode turn failed', events.errorMessage, {
432
444
  credentials, limit: 2000,
433
- }));
445
+ });
446
+ err.staleSession = !events.turnOutput && STALE_SESSION_PATTERN.test(events.errorMessage);
447
+ return reject(err);
434
448
  }
435
449
  if (code !== 0) {
436
- return reject(adapterFailure('opencode', stderr.trim(), {
450
+ const err = adapterFailure('opencode', stderr.trim(), {
437
451
  credentials, exitCode: code, limit: 500,
438
- }));
452
+ });
453
+ err.staleSession = !events.turnOutput && STALE_SESSION_PATTERN.test(stderr);
454
+ return reject(err);
439
455
  }
440
456
  resolve({
441
457
  sessionId: events.sessionId, text: events.text, usage: events.usage, toolErrors: events.toolErrors,
@@ -650,6 +666,18 @@ export default {
650
666
  },
651
667
 
652
668
  async spawn(prompt, rawCtx = {}) {
669
+ try {
670
+ return await this.spawnTurn(prompt, rawCtx);
671
+ } catch (err) {
672
+ if (!rawCtx.sessionId || !isStaleSessionError(err)) throw err;
673
+ // The saved id is one OpenCode does not know: a seat switched from another
674
+ // adapter carries a Claude id, and a pruned session fails the same way. Nothing
675
+ // ran, so one fresh turn is safe (pi #1726 does the same fallback).
676
+ return this.spawnTurn(prompt, { ...rawCtx, sessionId: null });
677
+ }
678
+ },
679
+
680
+ async spawnTurn(prompt, rawCtx = {}) {
653
681
  const seatModel = requireSeatModel(rawCtx.environment, 'opencode');
654
682
  const sandbox = normalizeSandboxTrust(rawCtx.environment?.sandbox);
655
683
  refuseWorkspaceTrust(sandbox, 'opencode');