@openagents-org/agent-launcher 0.2.194 → 0.2.195

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": "@openagents-org/agent-launcher",
3
- "version": "0.2.194",
3
+ "version": "0.2.195",
4
4
  "description": "OpenAgents Launcher \u2014 install, configure, and run AI coding agents from your terminal",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -239,6 +239,7 @@ class BaseAdapter {
239
239
  // "this turn started after the stop and is meant to run".
240
240
  this._channelRunGeneration = {};
241
241
  this._channelRequestContent = {};
242
+ this._channelTriggerId = {};
242
243
  // Channels a user stopped whose next turn has not started yet. Everything
243
244
  // the send* helpers would post there is dropped: output a killed run was
244
245
  // still flushing lands after the stop notice otherwise, and the workspace
@@ -1428,6 +1429,7 @@ class BaseAdapter {
1428
1429
  // instructions (v1.1 M4); the block is prepended to the message.
1429
1430
  // Gated synchronously so messages it cannot apply to (agents,
1430
1431
  // anonymous humans, system) add no extra tick before the stop check.
1432
+ this._applyThreadCollaboration(channel, current);
1431
1433
  if (this._sharedContextCandidate(current)) await this._applySharedContext(channel, current);
1432
1434
  // Checked after the round trips above, which a stop can land inside:
1433
1435
  // a message from before the stop never reaches the CLI.
@@ -1439,6 +1441,7 @@ class BaseAdapter {
1439
1441
  this._mutedChannels.delete(channel);
1440
1442
  this._stopNoticeSent.delete(channel);
1441
1443
  this._stoppingChannels.delete(channel);
1444
+ this._channelTriggerId[channel] = current.messageId || null;
1442
1445
  await this._handleMessage(current);
1443
1446
  }
1444
1447
  } catch (e) {
@@ -1447,6 +1450,7 @@ class BaseAdapter {
1447
1450
  }
1448
1451
  delete this._channelRunGeneration[channel];
1449
1452
  delete this._channelRequestContent[channel];
1453
+ delete this._channelTriggerId[channel];
1450
1454
  first = false;
1451
1455
 
1452
1456
  const queue = this._channelQueues[channel];
@@ -1498,8 +1502,13 @@ class BaseAdapter {
1498
1502
  * the per-run MCP server. Never throws.
1499
1503
  */
1500
1504
  _sharedContextEmail(msg) {
1501
- if (!msg || msg.senderType !== 'human') return null;
1502
- const raw = msg.senderEmail || (msg.metadata && msg.metadata.sender_email) || '';
1505
+ if (!msg) return null;
1506
+ const continuation = msg.senderType === 'agent' && msg.metadata?.orchestration?.action === 'next';
1507
+ if (msg.senderType !== 'human' && !continuation) return null;
1508
+ // Continuations retain the authenticated original requester so the next
1509
+ // agent applies the same owner-reviewed shared context and tool identity.
1510
+ const raw = continuation ? (msg.metadata.requester_email || '')
1511
+ : (msg.senderEmail || (msg.metadata && msg.metadata.sender_email) || '');
1503
1512
  const email = String(raw).trim().toLowerCase();
1504
1513
  return email && email.includes('@') ? email : null;
1505
1514
  }
@@ -1508,6 +1517,15 @@ class BaseAdapter {
1508
1517
  return this._sharedContextEmail(msg) !== null;
1509
1518
  }
1510
1519
 
1520
+ _applyThreadCollaboration(channel, msg) {
1521
+ if (msg._threadCollaborationApplied || !channel || channel.startsWith('dm:')) return;
1522
+ const instructions = msg.metadata?.thread_agent_instructions?.[this.agentName];
1523
+ if (typeof instructions !== 'string' || !instructions.trim()) return;
1524
+ if (msg.originalContent === undefined) msg.originalContent = msg.content;
1525
+ msg.content = `COLLABORATION INSTRUCTIONS FOR THIS THREAD ONLY (do not carry these into other threads; existing owner restrictions still apply):\n${instructions.slice(0, 10000)}\nEND THREAD INSTRUCTIONS\n\n${msg.content || ''}`;
1526
+ msg._threadCollaborationApplied = true;
1527
+ }
1528
+
1511
1529
  async _applySharedContext(channel, msg) {
1512
1530
  const email = this._sharedContextEmail(msg);
1513
1531
  if (!email) return false;
@@ -1805,7 +1823,7 @@ class BaseAdapter {
1805
1823
  * structured field: an earlier attempt matched the word "completed" in the
1806
1824
  * message text and fired on every bash for-loop that ended in `done`.
1807
1825
  */
1808
- async sendResponse(channel, content) {
1826
+ async sendResponse(channel, content, { statusKind = 'completed' } = {}) {
1809
1827
  if (this._isMuted(channel)) return;
1810
1828
  try {
1811
1829
  let attachments = [];
@@ -1825,7 +1843,7 @@ class BaseAdapter {
1825
1843
  attachments,
1826
1844
  senderType: 'agent',
1827
1845
  senderName: this.agentName,
1828
- metadata: { status_kind: 'completed' },
1846
+ metadata: { status_kind: statusKind, ...(this._channelTriggerId?.[channel] ? { in_reply_to: this._channelTriggerId[channel] } : {}) },
1829
1847
  sessionId: this._sessionId,
1830
1848
  });
1831
1849
  } catch (e) {
@@ -1125,7 +1125,7 @@ class ClaudeAdapter extends BaseAdapter {
1125
1125
  */
1126
1126
  async _postTurnOutcome(pp, msgChannel, finalResponse) {
1127
1127
  if (finalResponse) {
1128
- try { await this.sendResponse(msgChannel, finalResponse); } catch {}
1128
+ try { await this.sendResponse(msgChannel, finalResponse, { statusKind: pp.lastErrorText ? 'error' : 'completed' }); } catch {}
1129
1129
  } else if (pp.lastErrorText) {
1130
1130
  try { await this.sendError(msgChannel, this._formatClaudeError(pp.lastErrorText)); } catch {}
1131
1131
  } else if (!pp.everPostedAnything) {
@@ -1477,7 +1477,7 @@ class ClaudeAdapter extends BaseAdapter {
1477
1477
  if (detail) {
1478
1478
  try { await this.sendError(msgChannel, this._formatClaudeError(detail)); } catch {}
1479
1479
  } else {
1480
- try { await this.sendResponse(msgChannel, `No response generated (exit code ${result.code === undefined ? 'unknown' : result.code}). Please try again.`); } catch {}
1480
+ try { await this.sendError(msgChannel, `No response generated (exit code ${result.code === undefined ? 'unknown' : result.code}). Please try again.`); } catch {}
1481
1481
  }
1482
1482
  }
1483
1483
  break;
@@ -376,7 +376,9 @@ class CodexAdapter extends BaseAdapter {
376
376
  if (result.stopped) return;
377
377
 
378
378
  if (result.responseText) {
379
- await this.sendResponse(msgChannel, result.responseText);
379
+ await this.sendResponse(msgChannel, result.responseText, {
380
+ statusKind: CodexAdapter._endedEarly(result) ? 'error' : 'completed',
381
+ });
380
382
  // A run that said something and then died (turn.failed, non-zero
381
383
  // exit) used to post only that opening line as the finished reply,
382
384
  // so it read as "Codex says one sentence and stops" with the real