@remits/remits-cli 0.1.133 → 0.1.135

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/index.js CHANGED
@@ -6,16 +6,16 @@
6
6
  - L22 Runtime Bootstrap And Shared State
7
7
  - L119 Sessions, Account Resolution, And Production Guards
8
8
  - L603 Local State, Workspaces, And Verification Evidence
9
- - L2255 Account Repos, Guide Sync, And Platform Repo
10
- - L2949 Component Discovery And HTTP Logging
11
- - L3525 Skill Delivery And TOC Resolution
12
- - L3835 Auth And Component Staging
13
- - L4449 Component Summaries, Status, And Sync Gates
14
- - L6518 Branches, Promotion, Commit, And Test Runs
15
- - L8183 Tokens, Tools, Verification, And Config
16
- - L9405 Service Dashboard And WebSocket Listener
17
- - L11521 Agent And Ticket Workflows
18
- - L14815 Help, Auto Update, And Command Dispatch
9
+ - L2474 Account Repos, Guide Sync, And Platform Repo
10
+ - L3282 Component Discovery And HTTP Logging
11
+ - L3923 Skill Delivery And TOC Resolution
12
+ - L4233 Auth And Component Staging
13
+ - L4848 Component Summaries, Status, And Sync Gates
14
+ - L6917 Branches, Promotion, Commit, And Test Runs
15
+ - L8600 Tokens, Tools, Verification, And Config
16
+ - L9980 Service Dashboard And WebSocket Listener
17
+ - L12101 Agent And Ticket Workflows
18
+ - L15573 Help, Auto Update, And Command Dispatch
19
19
  */
20
20
 
21
21
  /*
@@ -621,6 +621,7 @@ function localStatePaths(cwd) {
621
621
  actorDir,
622
622
  sharedDir: path.join(base, 'shared'),
623
623
  toolsDir: path.join(base, 'shared', 'tools'),
624
+ workstreamsDir: path.join(base, 'shared', 'workstreams'),
624
625
  sessionsDir: path.join(actorDir, 'sessions'),
625
626
  toolResponsesDir: path.join(actorDir, 'tool-responses'),
626
627
  verificationDir: path.join(actorDir, 'verification'),
@@ -924,6 +925,112 @@ function localStateSummary(cwd, flags = {}) {
924
925
  };
925
926
  }
926
927
 
928
+ function normalizeWorkstreamId(value) {
929
+ if (value === undefined || value === null) return null;
930
+ const trimmed = String(value).trim();
931
+ if (!trimmed) return null;
932
+ let normalized = trimmed
933
+ .toLowerCase()
934
+ .replace(/[^a-z0-9._-]/g, '-')
935
+ .replace(/-+/g, '-')
936
+ .replace(/^[-.]+/, '')
937
+ .replace(/[-.]+$/, '');
938
+ return normalized ? normalized.slice(0, 96).replace(/[-.]+$/, '') : null;
939
+ }
940
+
941
+ // The LOCAL proof-campaign id: the thing that groups several actor directories working one goal. It is not
942
+ // the same `workstream` a support ticket carries — that one is an account's own routing label, matched by
943
+ // `agent serve --serves`. They share a flag name and nothing else; keep the distinction in any text that
944
+ // mentions either.
945
+ function resolveWorkstreamId(cwd, flags = {}, context = {}) {
946
+ return normalizeWorkstreamId(flags.workstream || flags['workstream-id'] || process.env.REMITS_WORKSTREAM_ID ||
947
+ context.workstream || context.workspace || resolveWorkspace(cwd, flags));
948
+ }
949
+
950
+ const WORKSTREAM_ROLE_KEYS = ['repoAccountId', 'accountId', 'executionAccountId', 'targetAccountId',
951
+ 'branchName', 'componentBranch', 'workspace', 'dataMode'];
952
+
953
+ function workstreamRoleSignature(world) {
954
+ if (!world || typeof world !== 'object') return null;
955
+ const signature = {};
956
+ WORKSTREAM_ROLE_KEYS.forEach((key) => {
957
+ if (world[key] !== undefined && world[key] !== null) signature[key] = world[key];
958
+ });
959
+ return Object.keys(signature).length ? signature : null;
960
+ }
961
+
962
+ function workstreamWorldParts(world) {
963
+ const roles = world || {};
964
+ return [
965
+ roles.repoAccountId ? 'repo ' + roles.repoAccountId : null,
966
+ roles.accountId ? 'account ' + roles.accountId : null,
967
+ roles.executionAccountId ? 'exec ' + roles.executionAccountId : null,
968
+ roles.targetAccountId ? 'target ' + roles.targetAccountId : null,
969
+ roles.branchName ? 'branch ' + roles.branchName : null,
970
+ roles.componentBranch ? 'componentBranch ' + roles.componentBranch : null,
971
+ roles.workspace ? 'ws:' + roles.workspace : null,
972
+ roles.dataMode ? 'data ' + roles.dataMode : null
973
+ ].filter(Boolean);
974
+ }
975
+
976
+ function dedupeWorkstreamWorlds(worlds) {
977
+ const seen = new Set();
978
+ const out = [];
979
+ (worlds || []).forEach((world) => {
980
+ if (!world || !Object.keys(world).length) return;
981
+ const key = JSON.stringify(WORKSTREAM_ROLE_KEYS.map((k) => world[k] === undefined ? null : world[k]));
982
+ if (seen.has(key)) return;
983
+ seen.add(key);
984
+ out.push(world);
985
+ });
986
+ return out.slice(-12);
987
+ }
988
+
989
+ function workstreamFile(cwd, workstreamId) {
990
+ const paths = ensureLocalState(cwd);
991
+ ensureDir(paths.workstreamsDir);
992
+ return path.join(paths.workstreamsDir, normalizeWorkstreamId(workstreamId) + '.json');
993
+ }
994
+
995
+ function recordWorkstreamArtifact(cwd, flags = {}, artifact = {}) {
996
+ const workstreamId = resolveWorkstreamId(cwd, flags, artifact);
997
+ if (!workstreamId) return null;
998
+ const file = workstreamFile(cwd, workstreamId);
999
+ let existing = {};
1000
+ try {
1001
+ if (fs.existsSync(file)) existing = JSON.parse(fs.readFileSync(file, 'utf8')) || {};
1002
+ } catch (_) {
1003
+ existing = {};
1004
+ }
1005
+ const now = new Date().toISOString();
1006
+ const actor = localStatePaths(cwd).actor.id;
1007
+ const item = Object.assign({
1008
+ actor,
1009
+ recordedAt: now
1010
+ }, artifact || {});
1011
+ const artifacts = Array.isArray(existing.artifacts) ? existing.artifacts : [];
1012
+ const dedupeKey = [item.type, item.callId, item.envelopeId, item.packetId, item.path].filter(Boolean).join('|');
1013
+ const filtered = dedupeKey
1014
+ ? artifacts.filter((row) => [row.type, row.callId, row.envelopeId, row.packetId, row.path].filter(Boolean).join('|') !== dedupeKey)
1015
+ : artifacts;
1016
+ // The LAST world, never a merge of all of them. Merging produced a "World:" line that no single run ever
1017
+ // executed in — a leftover targetAccountId from one command printed next to the executionAccountId of
1018
+ // another. A campaign that legitimately spans worlds is reported as several worlds, not as one blend.
1019
+ const roles = workstreamRoleSignature(artifact.world) || existing.roles || {};
1020
+ const priorWorlds = Array.isArray(existing.worlds) ? existing.worlds : (existing.roles ? [existing.roles] : []);
1021
+ const worlds = dedupeWorkstreamWorlds(priorWorlds.concat([roles]));
1022
+ const next = Object.assign({}, existing, {
1023
+ workstreamId,
1024
+ updatedAt: now,
1025
+ actors: Array.from(new Set([].concat(existing.actors || [], actor))).sort(),
1026
+ roles,
1027
+ worlds,
1028
+ artifacts: filtered.concat([item]).slice(-200)
1029
+ });
1030
+ atomicWriteFile(file, JSON.stringify(next, null, 2));
1031
+ return file;
1032
+ }
1033
+
927
1034
  // ---------------------------------------------------------------------------------------------------
928
1035
  // Staging workspace
929
1036
  // ---------------------------------------------------------------------------------------------------
@@ -1463,6 +1570,40 @@ function writeActiveVerificationContexts(cwd, contexts) {
1463
1570
  atomicWriteFile(paths.activeVerificationContextsFile, JSON.stringify(contexts || {}, null, 2));
1464
1571
  }
1465
1572
 
1573
+ function describeVerificationWorld(context = {}) {
1574
+ return ['account ' + (context.accountId != null ? context.accountId : '?'),
1575
+ 'branch ' + (context.branchName || context.branch || '?'),
1576
+ 'workspace ' + (normalizeWorkspace(context.workspace) || 'shared'),
1577
+ context.dataMode ? 'data ' + context.dataMode : null].filter(Boolean).join(', ');
1578
+ }
1579
+
1580
+ /**
1581
+ * Active envelope pointers this actor holds for a DIFFERENT checkout world than the one running now. Used
1582
+ * only to explain a silence: an agent that started an envelope on `main` and then worked `--branch forked`
1583
+ * otherwise gets no envelope, no detach message, and no reason.
1584
+ */
1585
+ function otherWorldVerificationEnvelopes(cwd, context) {
1586
+ try {
1587
+ const currentKey = verificationContextKey(context);
1588
+ const contexts = readActiveVerificationContexts(cwd);
1589
+ return Object.keys(contexts)
1590
+ .filter((key) => key !== currentKey && !legacyVerificationContextKeys(currentKey).includes(key))
1591
+ .map((key) => {
1592
+ const entry = typeof contexts[key] === 'string' ? { envelopeId: contexts[key] } : contexts[key];
1593
+ if (!entry || !entry.envelopeId) return null;
1594
+ return { envelopeId: String(entry.envelopeId), updatedAt: entry.updatedAt, context: entry.context || {} };
1595
+ })
1596
+ .filter(Boolean)
1597
+ // Only pointers for THIS host and account: an envelope for another account is not a missing binding,
1598
+ // it is different work.
1599
+ .filter((entry) => String(entry.context.accountId) === String(context.accountId) &&
1600
+ normalizeBaseUrl(entry.context.baseUrl || '') === normalizeBaseUrl(context.baseUrl || ''))
1601
+ .sort((left, right) => String(right.updatedAt || '').localeCompare(String(left.updatedAt || '')));
1602
+ } catch (_) {
1603
+ return [];
1604
+ }
1605
+ }
1606
+
1466
1607
  function activeVerificationContext(cwd, flags = {}, session = null, accountId = null) {
1467
1608
  return {
1468
1609
  baseUrl: normalizeBaseUrl(flags['base-url'] || (session && session.baseUrl) || DEFAULT_BASE_URL),
@@ -1768,7 +1909,25 @@ async function verificationPreflight(api, cwd, session, accountId, flags = {}, p
1768
1909
  const label = options.command || 'this command';
1769
1910
  const context = activeVerificationContext(cwd, flags, session, accountId);
1770
1911
  const envelopeId = verificationEnvelopeIdForCommand(cwd, flags, context);
1771
- if (!envelopeId) return { envelopeId: null, verdict: 'none', evidenceFlags: flags };
1912
+ if (!envelopeId) {
1913
+ // An envelope is bound per CHECKOUT WORLD, so `verify start` on one branch/workspace and the work on
1914
+ // another leaves the agent with an active envelope it cannot see and a command that says nothing at all:
1915
+ // it did the work, attached no evidence, and reported neither. Silence is only correct when there is no
1916
+ // envelope to speak of.
1917
+ if (!options.quiet) {
1918
+ const orphan = otherWorldVerificationEnvelopes(cwd, context);
1919
+ if (orphan.length) {
1920
+ console.error('No verification envelope is active for this world (' + describeVerificationWorld(context) + '), ' +
1921
+ 'so ' + label + ' attaches no evidence. You have ' + orphan.length + ' active envelope(s) bound to other worlds:');
1922
+ orphan.slice(0, 3).forEach((entry) => {
1923
+ console.error(' - ' + entry.envelopeId + ' ' + describeVerificationWorld(entry.context));
1924
+ });
1925
+ console.error(' Start one here with `remits-cli verify start --summary "..."`, adopt one with ' +
1926
+ '`remits-cli verify use <id>`, or re-run in the world that envelope was started in.');
1927
+ }
1928
+ }
1929
+ return { envelopeId: null, verdict: 'none', evidenceFlags: flags };
1930
+ }
1772
1931
  const pinned = Object.assign({}, flags, { 'verify-envelope': envelopeId });
1773
1932
  let result;
1774
1933
  try {
@@ -1851,7 +2010,23 @@ async function appendVerificationPacket(api, cwd, session, accountId, flags, pac
1851
2010
  }, packet || {});
1852
2011
  finalPacket.envelopeId = envelopeId;
1853
2012
  stampPacketRepoAccount(finalPacket, accountId);
2013
+ // The workstream id travels WITH the evidence. Without it the local index and the platform's
2014
+ // `activity inspect --scope related` share no identifier, so two agents on one campaign — the case this
2015
+ // whole surface exists for — could never be shown the same set of work.
2016
+ const packetWorkstreamId = resolveWorkstreamId(cwd, flags, finalPacket.world || {});
2017
+ if (packetWorkstreamId) {
2018
+ if (!finalPacket.world || typeof finalPacket.world !== 'object') finalPacket.world = {};
2019
+ if (!finalPacket.world.workstreamId) finalPacket.world.workstreamId = packetWorkstreamId;
2020
+ }
1854
2021
  writeLocalVerificationPacket(cwd, envelopeId, finalPacket);
2022
+ recordWorkstreamArtifact(cwd, flags, {
2023
+ type: 'verification_packet',
2024
+ envelopeId,
2025
+ packetId: finalPacket.packetId,
2026
+ packetType: finalPacket.type,
2027
+ world: finalPacket.world || {},
2028
+ workspace: finalPacket.world && finalPacket.world.workspace
2029
+ });
1855
2030
  try {
1856
2031
  const response = await postVerificationCommand(api, cwd, session, accountId, 'packet', {
1857
2032
  envelopeId,
@@ -1934,13 +2109,14 @@ function printVerificationEnvelope(envelope, fallbackEnvelopeId) {
1934
2109
  }
1935
2110
  }
1936
2111
 
1937
- function slimVerificationPacket(packet) {
2112
+ function slimVerificationPacket(packet, options = {}) {
1938
2113
  const p = packet || {};
1939
2114
  const world = p.world || {};
2115
+ const hasSuccess = Object.prototype.hasOwnProperty.call(p, 'success');
1940
2116
  return {
1941
2117
  packetId: p.packetId || p.id || null,
1942
2118
  type: p.type || 'unknown',
1943
- success: p.success !== false,
2119
+ success: hasSuccess ? p.success !== false : options.defaultSuccess !== false,
1944
2120
  stale: Boolean(p.stale),
1945
2121
  claim: p.claim || p.summary || null,
1946
2122
  taskId: p.taskId || (p.testRun && p.testRun.taskId) || null,
@@ -2002,7 +2178,7 @@ function compactVerificationEnvelope(envelope, fallbackEnvelopeId) {
2002
2178
  recent: packets.slice(-5).map(slimVerificationPacket)
2003
2179
  },
2004
2180
  latestByType,
2005
- currentFailures: failures.slice(0, 10).map(slimVerificationPacket),
2181
+ currentFailures: failures.slice(0, 10).map((packet) => slimVerificationPacket(packet, { defaultSuccess: false })),
2006
2182
  stale: stale.slice(0, 10).map(slimVerificationPacket),
2007
2183
  warnings: (env.warnings || []).slice(0, 10),
2008
2184
  nextActions
@@ -2096,16 +2272,35 @@ async function readVerificationPacketsForDiagnostics(api, cwd, session, accountI
2096
2272
  }
2097
2273
 
2098
2274
  function buildCommandWorld(response, fallback = {}) {
2275
+ if (response && response.world && typeof response.world === 'object') {
2276
+ return Object.assign({}, response.world, {
2277
+ host: response.world.host || fallback.host,
2278
+ dataMode: response.world.dataMode || response.dataMode || fallback.dataMode,
2279
+ branchName: response.world.branchName || response.branchName || fallback.branchName,
2280
+ workspace: response.world.workspace !== undefined ? response.world.workspace : fallback.workspace
2281
+ });
2282
+ }
2099
2283
  const source = (response && response.staging) || {};
2100
2284
  const resolution = (response && response.resolution) || {};
2101
2285
  const result = (response && response.result) || {};
2102
2286
  const laneHash = laneContentHashFrom(response) || fallback.laneContentHash || fallback.stagedOverlayHash;
2103
- const executionAccountId = response && (response.executionAccountId || result.accountId || source.executionAccountId || response.accountId) ||
2287
+ const addressedAccountId = response && (response.repoAccountId || response.checkoutAccountId || response.scopeRootAccountId ||
2288
+ response.accountId || response.ownerAccountId) ||
2289
+ fallback.repoAccountId || fallback.checkoutAccountId || fallback.scopeRootAccountId || fallback.addressedAccountId || fallback.accountId;
2290
+ const executionAccountId = response && (response.executionAccountId || result.accountId || source.executionAccountId) ||
2104
2291
  resolution.executionAccountId ||
2105
- fallback.executionAccountId;
2292
+ fallback.executionAccountId ||
2293
+ // Legacy/fallback callers historically supplied only `accountId`, and for all non-split worlds that
2294
+ // value is both repo and execution. Never let a top-level response.accountId (usually addressed/repo) be
2295
+ // the execution account unless the caller intentionally passed it as the fallback.
2296
+ fallback.accountId;
2106
2297
  return {
2107
- accountId: response && (response.accountId || response.ownerAccountId) || fallback.accountId,
2298
+ accountId: executionAccountId,
2299
+ repoAccountId: response && response.repoAccountId || fallback.repoAccountId || addressedAccountId,
2300
+ scopeRootAccountId: response && response.scopeRootAccountId || fallback.scopeRootAccountId,
2301
+ checkoutAccountId: response && response.checkoutAccountId || fallback.checkoutAccountId || addressedAccountId,
2108
2302
  executionAccountId,
2303
+ targetAccountId: response && response.targetAccountId || fallback.targetAccountId,
2109
2304
  addressedAccountId: resolution.addressedAccountId || fallback.addressedAccountId,
2110
2305
  host: fallback.host,
2111
2306
  dataMode: response && response.dataMode || fallback.dataMode,
@@ -2122,10 +2317,34 @@ function buildCommandWorld(response, fallback = {}) {
2122
2317
  fallback.componentBranch,
2123
2318
  variantBranchSource: response && (response.variantBranchSource || (response.variantWorld && response.variantWorld.variantBranchSource)) || fallback.variantBranchSource,
2124
2319
  variantId: resolution.variantId || fallback.variantId,
2320
+ componentSource: response && response.componentSource || fallback.componentSource,
2321
+ componentSignature: response && response.componentSignature || fallback.componentSignature,
2125
2322
  sourceLayer: fallback.sourceLayer || (source.testComponentSource === 'staged' ? 'staged' : undefined)
2126
2323
  };
2127
2324
  }
2128
2325
 
2326
+ function buildToolStatusWorld(data, stored = {}, fallback = {}) {
2327
+ const response = data && typeof data === 'object' ? data : {};
2328
+ const previous = stored && typeof stored === 'object' ? stored : {};
2329
+ return buildCommandWorld(response, {
2330
+ repoAccountId: response.repoAccountId || previous.repoAccountId || fallback.repoAccountId || previous.checkoutAccountId || previous.accountId || fallback.accountId,
2331
+ scopeRootAccountId: response.scopeRootAccountId || previous.scopeRootAccountId || fallback.scopeRootAccountId,
2332
+ checkoutAccountId: response.checkoutAccountId || previous.checkoutAccountId || fallback.checkoutAccountId,
2333
+ accountId: response.executionAccountId || previous.executionAccountId || fallback.executionAccountId || fallback.accountId,
2334
+ executionAccountId: response.executionAccountId || previous.executionAccountId || fallback.executionAccountId,
2335
+ targetAccountId: response.targetAccountId || previous.targetAccountId || fallback.targetAccountId,
2336
+ dataMode: response.dataMode || previous.dataMode || fallback.dataMode,
2337
+ branchName: response.branchName || previous.branchName || fallback.branchName,
2338
+ workspace: response.workspace !== undefined ? response.workspace : (previous.workspace !== undefined ? previous.workspace : fallback.workspace),
2339
+ host: fallback.host,
2340
+ componentSource: response.componentSource || previous.componentSource || fallback.componentSource,
2341
+ componentSignature: response.componentSignature || previous.componentSignature || fallback.componentSignature,
2342
+ componentBranch: response.componentBranch || previous.componentBranch || fallback.componentBranch,
2343
+ variantBranch: response.variantBranch || previous.variantBranch || fallback.variantBranch,
2344
+ variantBranchSource: response.variantBranchSource || previous.variantBranchSource || fallback.variantBranchSource
2345
+ });
2346
+ }
2347
+
2129
2348
  // A variant sync writes overlays on exactly one component branch; say so in the packet world, where a manifest's
2130
2349
  // componentBranch is compared. Without it no sync_mutation could satisfy a manifest that declares one.
2131
2350
  function syncPacketWorld(response, world, branchName) {
@@ -2444,6 +2663,120 @@ async function downloadAndSyncGuides(cwd, ctx) {
2444
2663
  return writeGuidePayloadIntoRepo(cwd, payload);
2445
2664
  }
2446
2665
 
2666
+ function guideSyncSummaryLine(summary) {
2667
+ if (!summary) {
2668
+ return 'guide sync skipped';
2669
+ }
2670
+ const repoCount = Number(summary.repoCount || 0);
2671
+ const updatedRepos = Number(summary.updatedRepos || 0);
2672
+ const updatedFiles = Number(summary.updatedFiles || 0);
2673
+ const skipped = (summary.skipped || []).length;
2674
+ return updatedRepos + '/' + repoCount + ' repo(s) updated, ' + updatedFiles +
2675
+ ' file(s) changed' + (skipped ? ', ' + skipped + ' skipped' : '');
2676
+ }
2677
+
2678
+ function guideSyncSessions(requestedBaseUrl = null) {
2679
+ return listSessionsFromStore(readAllSessions()).filter((session) => {
2680
+ if (!session || !session.token) return false;
2681
+ if (!requestedBaseUrl) return true;
2682
+ return normalizeBaseUrl(session.baseUrl || DEFAULT_BASE_URL) === normalizeBaseUrl(requestedBaseUrl);
2683
+ });
2684
+ }
2685
+
2686
+ function guideSessionForRepo(entry, sessions) {
2687
+ const accountId = Number(entry && entry.accountId);
2688
+ return sessions.find((session) => Number(session.accountId) === accountId) || sessions[0] || null;
2689
+ }
2690
+
2691
+ // Refresh every indexed account checkout from the authenticated platform guide payload. This is the
2692
+ // operator-level repair path for "agents are reading stale front-stage docs": it does not wait for each
2693
+ // checkout to happen to run a component command.
2694
+ async function syncGuidesAcrossIndexedRepos(options = {}) {
2695
+ if (options.rescan !== false) {
2696
+ try { discoverAccountRepos(); } catch (_) { /* use the current index */ }
2697
+ }
2698
+
2699
+ const sessions = guideSyncSessions(options.baseUrl || null);
2700
+ const entries = listAccountRepoEntries(readAccountRepoIndex())
2701
+ .sort((a, b) => Number(a.accountId) - Number(b.accountId));
2702
+ const result = {
2703
+ repoCount: entries.length,
2704
+ updatedRepos: 0,
2705
+ unchangedRepos: 0,
2706
+ updatedFiles: 0,
2707
+ removedFiles: 0,
2708
+ skipped: [],
2709
+ repos: []
2710
+ };
2711
+
2712
+ if (!sessions.length) {
2713
+ result.skipped.push({
2714
+ reason: 'no_authenticated_session',
2715
+ message: 'Run remits-cli auth before syncing guides.'
2716
+ });
2717
+ return result;
2718
+ }
2719
+
2720
+ for (const entry of entries) {
2721
+ const repo = {
2722
+ accountId: Number(entry.accountId),
2723
+ name: entry.name || '',
2724
+ directory: entry.directory,
2725
+ updated: [],
2726
+ removed: []
2727
+ };
2728
+ if (!entry.directory || !isAccountCheckout(entry.directory)) {
2729
+ repo.skipped = true;
2730
+ repo.reason = 'not_a_git_checkout';
2731
+ result.skipped.push(repo);
2732
+ result.repos.push(repo);
2733
+ continue;
2734
+ }
2735
+
2736
+ const session = guideSessionForRepo(entry, sessions);
2737
+ if (!session) {
2738
+ repo.skipped = true;
2739
+ repo.reason = 'no_session_for_repo';
2740
+ result.skipped.push(repo);
2741
+ result.repos.push(repo);
2742
+ continue;
2743
+ }
2744
+
2745
+ const baseUrl = normalizeBaseUrl(session.baseUrl || options.baseUrl || DEFAULT_BASE_URL);
2746
+ const summary = await downloadAndSyncGuides(entry.directory, {
2747
+ session,
2748
+ baseUrl,
2749
+ accountId: Number(entry.accountId) || session.accountId
2750
+ });
2751
+ if (!summary) {
2752
+ repo.skipped = true;
2753
+ repo.reason = 'guide_payload_unavailable';
2754
+ result.skipped.push(repo);
2755
+ result.repos.push(repo);
2756
+ continue;
2757
+ }
2758
+
2759
+ repo.updated = summary.updated || [];
2760
+ repo.removed = summary.removed || [];
2761
+ result.updatedFiles += repo.updated.length;
2762
+ result.removedFiles += repo.removed.length;
2763
+ if (repo.updated.length || repo.removed.length) {
2764
+ result.updatedRepos += 1;
2765
+ } else {
2766
+ result.unchangedRepos += 1;
2767
+ }
2768
+ result.repos.push(repo);
2769
+ }
2770
+
2771
+ appendGlobalActivityLog('guides', 'sync_all', {
2772
+ repoCount: result.repoCount,
2773
+ updatedRepos: result.updatedRepos,
2774
+ updatedFiles: result.updatedFiles,
2775
+ skipped: result.skipped.length
2776
+ });
2777
+ return result;
2778
+ }
2779
+
2447
2780
  // Ensure the current account repo has the latest guides. Authenticates first
2448
2781
  // (interactive terminals only) so guides are only ever delivered to users who
2449
2782
  // can authenticate to the platform.
@@ -3435,11 +3768,76 @@ function sandboxNetworkHint(err) {
3435
3768
  return ' Network access to ' + baseUrl + ' appears unavailable from the current sandbox. If remits-cli is being run by a local AI agent (for example Codex or Claude Code), retry the command with escalated permissions or outside the sandbox.';
3436
3769
  }
3437
3770
 
3771
+ // The tool inventory is ONE file per checkout, shared by every actor in it, and every refresh replaces it
3772
+ // wholesale. That is fine for one agent and wrong for several: `--as-account`, `--branch` and `--data-mode`
3773
+ // each produce a different inventory, so a second agent's refresh silently becomes the first agent's
3774
+ // "current tools". The file cannot be per-world without breaking the documented path, so instead: write it
3775
+ // atomically (a concurrent reader never sees a half-written file) and return the world it REPLACED, so the
3776
+ // caller can say out loud that it just overwrote somebody else's.
3777
+ function toolsSnapshotWorldSignature(snapshot) {
3778
+ if (!snapshot || typeof snapshot !== 'object') return null;
3779
+ return {
3780
+ accountId: snapshot.accountId,
3781
+ executionAccountId: snapshot.executionAccountId,
3782
+ branchName: snapshot.branchName,
3783
+ variantBranch: snapshot.variantBranch || null,
3784
+ dataMode: snapshot.dataMode
3785
+ };
3786
+ }
3787
+
3438
3788
  function saveToolsSnapshot(cwd, data) {
3439
3789
  const paths = ensureLocalState(cwd);
3440
3790
  const file = path.join(paths.toolsDir, 'tools.json');
3441
- fs.writeFileSync(file, JSON.stringify(data, null, 2));
3442
- return file;
3791
+ let previous = null;
3792
+ try {
3793
+ if (fs.existsSync(file)) previous = JSON.parse(fs.readFileSync(file, 'utf8'));
3794
+ } catch (_) {
3795
+ previous = null;
3796
+ }
3797
+ atomicWriteFile(file, JSON.stringify(data, null, 2));
3798
+ const before = toolsSnapshotWorldSignature(previous);
3799
+ const after = toolsSnapshotWorldSignature(data);
3800
+ const replaced = before && JSON.stringify(before) !== JSON.stringify(after) ? before : null;
3801
+ return { file, replaced };
3802
+ }
3803
+
3804
+ // The other half of the shared-snapshot problem: the writer is warned, but the agent that READS
3805
+ // .remits-cli/shared/tools/tools.json — to pick a tool name, or an input shape — gets no signal that the
3806
+ // file describes somebody else's account, branch or data lane. Answered at the moment it matters: the call.
3807
+ function staleToolsSnapshotWarning(cwd, world) {
3808
+ try {
3809
+ const file = path.join(localStatePaths(cwd).toolsDir, 'tools.json');
3810
+ if (!fs.existsSync(file)) return null;
3811
+ const snapshot = JSON.parse(fs.readFileSync(file, 'utf8'));
3812
+ const snapshotExec = snapshot.executionAccountId || snapshot.accountId;
3813
+ const mismatch = [];
3814
+ if (snapshotExec != null && String(snapshotExec) !== String(world.executionAccountId)) {
3815
+ mismatch.push('account ' + snapshotExec + ' (this call: ' + world.executionAccountId + ')');
3816
+ }
3817
+ if (snapshot.branchName && String(snapshot.branchName) !== String(world.branchName)) {
3818
+ mismatch.push('branch ' + snapshot.branchName + ' (this call: ' + world.branchName + ')');
3819
+ }
3820
+ if (snapshot.dataMode && String(snapshot.dataMode) !== String(world.dataMode)) {
3821
+ mismatch.push('data ' + snapshot.dataMode + ' (this call: ' + world.dataMode + ')');
3822
+ }
3823
+ if (!mismatch.length) return null;
3824
+ return {
3825
+ code: 'tools_snapshot_other_world',
3826
+ message: '.remits-cli/shared/tools/tools.json was last refreshed for ' + mismatch.join(', ') +
3827
+ '. It is one file per checkout, so it may be another agent\'s. Run `remits-cli tools` with your own ' +
3828
+ 'flags before trusting the tool list or an input schema read from it.'
3829
+ };
3830
+ } catch (_) {
3831
+ return null;
3832
+ }
3833
+ }
3834
+
3835
+ function describeToolsSnapshotWorld(world) {
3836
+ if (!world) return 'unknown world';
3837
+ return ['account ' + (world.executionAccountId || world.accountId || '?'),
3838
+ 'branch ' + (world.branchName || '?'),
3839
+ world.variantBranch ? "variant '" + world.variantBranch + "'" : null,
3840
+ 'data ' + (world.dataMode || '?')].filter(Boolean).join(', ');
3443
3841
  }
3444
3842
 
3445
3843
  async function loggedPost(api, cwd, endpoint, payload, options = {}) {
@@ -3979,14 +4377,15 @@ async function authCommand(flags) {
3979
4377
  dataMode: session.dataMode
3980
4378
  }).then((r) => r.data);
3981
4379
  if (toolsData && toolsData.success) {
3982
- const toolsFile = saveToolsSnapshot(cwd, {
4380
+ const toolsSnapshot = saveToolsSnapshot(cwd, {
3983
4381
  accountId: session.accountId,
4382
+ executionAccountId: session.accountId,
3984
4383
  branchName,
3985
4384
  dataMode: toolsData.dataMode || session.dataMode,
3986
4385
  fetchedAt: new Date().toISOString(),
3987
4386
  tools: toolsData.tools || []
3988
4387
  });
3989
- console.log('Tools cached at:', toolsFile);
4388
+ console.log('Tools cached at:', toolsSnapshot.file);
3990
4389
  }
3991
4390
  } catch (err) {
3992
4391
  console.log('Warning: failed to refresh tools after auth:', err.message);
@@ -4152,7 +4551,7 @@ async function pushComponentsCommand(flags) {
4152
4551
  const api = buildAxios(baseUrl, session.token, stageTimeoutMs(components, flags));
4153
4552
  const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
4154
4553
  packetType: 'stage',
4155
- world: { accountId, dataMode, branchName, workspace }
4554
+ world: { repoAccountId: accountId, accountId, executionAccountId: accountId, dataMode, branchName, workspace }
4156
4555
  }, { command: 'components stage' });
4157
4556
  const stagePayload = {
4158
4557
  token: session.token,
@@ -5434,7 +5833,7 @@ async function syncComponentsCommand(rawFlags) {
5434
5833
 
5435
5834
  const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
5436
5835
  packetType: dryRun || namesOnly ? 'sync_dry_run' : 'sync_mutation',
5437
- world: { accountId, dataMode, branchName, workspace }
5836
+ world: { repoAccountId: accountId, accountId, executionAccountId: accountId, dataMode, branchName, workspace }
5438
5837
  }, { command: 'components sync' });
5439
5838
 
5440
5839
  printProdDataBanner({
@@ -6911,7 +7310,7 @@ async function commitComponentsCommand(flags) {
6911
7310
  buildAxios(flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL, session.token),
6912
7311
  cwd, session, accountId, Object.assign({}, flags, { branch: branchName }), {
6913
7312
  packetType: 'sync_mutation',
6914
- world: { accountId, dataMode: resolveDataMode(flags, session), branchName, workspace: resolveWorkspace(cwd, flags) }
7313
+ world: { repoAccountId: accountId, accountId, executionAccountId: accountId, dataMode: resolveDataMode(flags, session), branchName, workspace: resolveWorkspace(cwd, flags) }
6915
7314
  }, { command: 'components commit' });
6916
7315
 
6917
7316
  // Landing is serial and nothing can make it concurrent: `git add -A` sweeps a shared checkout, and the
@@ -7614,17 +8013,31 @@ function runWorldLines(world, context = {}) {
7614
8013
  if (!world || typeof world !== 'object') return [];
7615
8014
  const lines = ['World:'];
7616
8015
  if (context.host) lines.push(' host: ' + context.host);
8016
+ const scopeAccount = world.scopeRootAccountId || world.repoAccountId || world.checkoutAccountId || context.scopeRootAccountId;
8017
+ if (scopeAccount) lines.push(' scoped from: account ' + scopeAccount);
7617
8018
  const accounts = ['account ' + (world.executionAccountId || context.accountId || 'unknown')];
7618
8019
  if (world.checkoutAccountId && world.checkoutAccountId !== world.executionAccountId) accounts.push('checkout ' + world.checkoutAccountId);
7619
8020
  if (world.componentOwnerAccountId && world.componentOwnerAccountId !== world.executionAccountId) accounts.push('component owner ' + world.componentOwnerAccountId);
7620
8021
  lines.push(' runs as: ' + accounts.join(', '));
8022
+ if (world.targetAccountId && world.targetAccountId !== world.executionAccountId) {
8023
+ lines.push(' targets: account ' + world.targetAccountId);
8024
+ }
7621
8025
  lines.push(' data lane: ' + (world.dataMode || context.dataMode || 'unknown'));
7622
8026
  const contextVariantWorld = context.variantWorld && typeof context.variantWorld === 'object' ? context.variantWorld : {};
7623
8027
  const subscribedComponentBranch = world.subscribedComponentBranch || contextVariantWorld.subscribedComponentBranch;
7624
8028
  const componentBranch = world.componentBranch || subscribedComponentBranch || contextVariantWorld.componentBranch || world.variantBranch;
7625
- const branchSource = componentBranch && subscribedComponentBranch && String(componentBranch) === String(subscribedComponentBranch)
7626
- ? 'subscription'
7627
- : (world.componentBranchSource || contextVariantWorld.componentBranchSource || world.variantBranchSource || contextVariantWorld.variantBranchSource);
8029
+ // The platform owns this answer (remits.cli.CliVariantWorld). Reporting a locally re-derived source is how
8030
+ // a line ends up contradicting itself: an --variant-branch probe REPLACES the subscription, and a variant
8031
+ // branch's working tree decides the world before any edge is consulted, but both resolve to a branch the
8032
+ // account often also subscribes to — so "componentBranch == subscribedComponentBranch" cannot mean
8033
+ // "the subscription chose it". Only fill the source in when the platform reports the TRUNK case (the git
8034
+ // branch named no world, so the account's own subscription decided) or reports nothing at all.
8035
+ const resolvedBranchSource = world.componentBranchSource || contextVariantWorld.componentBranchSource ||
8036
+ world.variantBranchSource || contextVariantWorld.variantBranchSource;
8037
+ const subscriptionDecided = componentBranch && subscribedComponentBranch &&
8038
+ String(componentBranch) === String(subscribedComponentBranch) &&
8039
+ (!resolvedBranchSource || resolvedBranchSource === 'trunk' || resolvedBranchSource === 'no-branch');
8040
+ const branchSource = subscriptionDecided ? 'subscription' : resolvedBranchSource;
7628
8041
  const branchLabel = componentBranch
7629
8042
  ? "componentBranch=" + componentBranch + (branchSource ? ' via ' + branchSource : '')
7630
8043
  : (branchSource || '');
@@ -7649,6 +8062,9 @@ function runWorldLines(world, context = {}) {
7649
8062
  lines.push(' test component: ' + world.testComponentSource + (world.testComponentSignature ? ' ' + shortSignature(world.testComponentSignature) : '') +
7650
8063
  (world.testComponentBranch ? " variant '" + world.testComponentBranch + "'" : ''));
7651
8064
  }
8065
+ if (world.componentSource) {
8066
+ lines.push(' component: ' + world.componentSource + (world.componentSignature ? ' ' + shortSignature(world.componentSignature) : ''));
8067
+ }
7652
8068
  lines.push(...stagedMaskedByVariantLines(world.stagedMaskedByVariant, world.executionAccountId || context.accountId));
7653
8069
  return lines;
7654
8070
  }
@@ -7878,7 +8294,8 @@ async function testCommand(flags) {
7878
8294
  const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
7879
8295
  packetType: 'test_run',
7880
8296
  world: {
7881
- accountId,
8297
+ repoAccountId: accountId,
8298
+ accountId: asAccountId ? Number(asAccountId) : Number(accountId),
7882
8299
  executionAccountId: asAccountId ? Number(asAccountId) : Number(accountId),
7883
8300
  dataMode,
7884
8301
  branchName,
@@ -8208,7 +8625,8 @@ async function tokenCommand(flags) {
8208
8625
  const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
8209
8626
  packetType: 'token_inspect',
8210
8627
  world: {
8211
- accountId,
8628
+ repoAccountId: accountId,
8629
+ accountId: tokenAsAccountId ? Number(tokenAsAccountId) : Number(accountId),
8212
8630
  executionAccountId: tokenAsAccountId ? Number(tokenAsAccountId) : Number(accountId),
8213
8631
  dataMode,
8214
8632
  branchName,
@@ -8250,6 +8668,12 @@ async function tokenCommand(flags) {
8250
8668
  baseUrl: normalizeBaseUrl(baseUrl),
8251
8669
  tokenKey: data.tokenKey,
8252
8670
  accountId: data.accountId,
8671
+ executionAccountId: data.accountId,
8672
+ // The account the command was addressed to, i.e. the repo/checkout account. Distinct from `accountId`
8673
+ // (which account the URL EXECUTES as, moved by --as-account) and from
8674
+ // `resolution.componentOwnerAccountId` (whose components run). Three different accounts on a subscriber
8675
+ // token, and the response used to carry only one of them.
8676
+ repoAccountId: data.ownerAccountId,
8253
8677
  branchName: data.branchName,
8254
8678
  variantBranch: data.variantBranch,
8255
8679
  // Why the variant branch is (or is not) set — the platform's one rule, so a null is explainable.
@@ -8293,6 +8717,29 @@ async function tokenCommand(flags) {
8293
8717
  printSessionResolutionWarning(sessionContext);
8294
8718
  printLocalCommandContext(cwd, flags, { accountId: output.accountId || accountId, branchName, workspace: output.workspace, dataMode: output.dataMode || dataMode });
8295
8719
  printLocalStateWarnings(cwd, flags);
8720
+ // The same World block `tools`, `tool` and `test run` print. A token is the surface that hands a browser
8721
+ // a whole resolved world, and it was the one that made the reader dig it out of a JSON dump — including
8722
+ // the `resolution.summary` sentence that says, in words, which account's components the page will run.
8723
+ runWorldLines({
8724
+ accountId: output.accountId,
8725
+ executionAccountId: output.accountId,
8726
+ checkoutAccountId: output.repoAccountId,
8727
+ repoAccountId: output.repoAccountId,
8728
+ componentOwnerAccountId: output.resolution && output.resolution.componentOwnerAccountId,
8729
+ dataMode: output.dataMode || dataMode,
8730
+ branchName: output.branchName || branchName,
8731
+ workspace: output.workspace,
8732
+ stagingLane: output.stagingLane,
8733
+ componentBranch: output.variantWorld && output.variantWorld.componentBranch,
8734
+ subscribedComponentBranch: output.variantWorld && output.variantWorld.subscribedComponentBranch,
8735
+ variantBranch: output.variantBranch,
8736
+ variantBranchSource: output.variantBranchSource,
8737
+ resolves: output.variantWorld && output.variantWorld.resolves,
8738
+ stagedMaskedByVariant: output.stagedMaskedByVariant
8739
+ }, { host: normalizeBaseUrl(baseUrl) }).forEach((line) => console.log(line));
8740
+ if (output.resolution && output.resolution.summary) {
8741
+ console.log(' resolves as: ' + output.resolution.summary);
8742
+ }
8296
8743
  }
8297
8744
  console.log(JSON.stringify(output, null, 2));
8298
8745
 
@@ -8300,7 +8747,16 @@ async function tokenCommand(flags) {
8300
8747
  type: 'token_inspect',
8301
8748
  success: true,
8302
8749
  claim: 'Verification token minted',
8303
- world: buildCommandWorld(output, { accountId: output.accountId || accountId, dataMode: output.dataMode || dataMode, branchName, workspace: output.workspace, host: normalizeBaseUrl(baseUrl) }),
8750
+ world: buildCommandWorld(output, {
8751
+ repoAccountId: output.repoAccountId || accountId,
8752
+ checkoutAccountId: output.repoAccountId || accountId,
8753
+ accountId: output.executionAccountId || output.accountId || accountId,
8754
+ executionAccountId: output.executionAccountId || output.accountId || accountId,
8755
+ dataMode: output.dataMode || dataMode,
8756
+ branchName,
8757
+ workspace: output.workspace,
8758
+ host: normalizeBaseUrl(baseUrl)
8759
+ }),
8304
8760
  revision: collectVerificationSource(cwd, flags),
8305
8761
  browser: {
8306
8762
  tokenKeyHash: output.tokenKey ? sha256(String(output.tokenKey)) : null,
@@ -8369,6 +8825,7 @@ async function toolsCommand(flags) {
8369
8825
  const data = await loggedPost(api, cwd, '/cli/tools', {
8370
8826
  token: session.token,
8371
8827
  accountId,
8828
+ asAccountId: flags['as-account'] || flags['as-account-id'],
8372
8829
  variantBranch,
8373
8830
  branchName,
8374
8831
  workspace,
@@ -8379,12 +8836,17 @@ async function toolsCommand(flags) {
8379
8836
  throw new Error('Failed to fetch tools');
8380
8837
  }
8381
8838
 
8382
- const toolsFile = saveToolsSnapshot(cwd, {
8383
- accountId,
8839
+ const toolsSnapshot = saveToolsSnapshot(cwd, {
8840
+ accountId: data.accountId || accountId,
8841
+ repoAccountId: data.repoAccountId || accountId,
8842
+ checkoutAccountId: data.checkoutAccountId || accountId,
8843
+ executionAccountId: data.executionAccountId || data.accountId || accountId,
8384
8844
  baseUrl: normalizeBaseUrl(baseUrl),
8385
- branchName,
8845
+ branchName: data.branchName || branchName,
8386
8846
  variantBranch: data.variantBranch || variantBranch,
8387
8847
  dataMode: data.dataMode || dataMode,
8848
+ world: data.world,
8849
+ staging: data.staging,
8388
8850
  fetchedAt: new Date().toISOString(),
8389
8851
  tools: data.tools || []
8390
8852
  });
@@ -8392,10 +8854,17 @@ async function toolsCommand(flags) {
8392
8854
  printSessionResolutionWarning(sessionContext);
8393
8855
  printResolvedBaseUrl(baseUrl);
8394
8856
  console.log('Tools refreshed.');
8857
+ if (data.world) runWorldLines(data.world, { host: normalizeBaseUrl(baseUrl), gitHead: safeGitValue(cwd, 'git rev-parse HEAD') }).forEach((line) => console.log(line));
8395
8858
  console.log('Data mode:', data.dataMode || dataMode);
8396
8859
  if (data.variantBranch || variantBranch) console.log('Variant branch:', data.variantBranch || variantBranch);
8397
- console.log('Tools file:', toolsFile);
8860
+ console.log('Tools file:', toolsSnapshot.file);
8398
8861
  console.log('Tool count:', (data.tools || []).length);
8862
+ if (toolsSnapshot.replaced) {
8863
+ console.error('Shared snapshot warning: this refresh replaced the tool inventory for ' +
8864
+ describeToolsSnapshotWorld(toolsSnapshot.replaced) + '. ' +
8865
+ '.remits-cli/shared/tools/tools.json is one file per checkout, so another agent reading it now sees ' +
8866
+ 'YOUR world. Re-run `remits-cli tools` with your own flags before trusting it.');
8867
+ }
8399
8868
  }
8400
8869
 
8401
8870
  function parseToolInput(flags) {
@@ -8429,10 +8898,18 @@ function applyScopeFlags(input, flags) {
8429
8898
  merged.scope = normalized;
8430
8899
  }
8431
8900
 
8432
- const targetAccountId = flags && (flags['target-account-id'] || flags.targetAccountId);
8901
+ const targetAccountId = flags && (flags['target-account'] || flags.targetAccount || flags['target-account-id'] || flags.targetAccountId);
8433
8902
  if (targetAccountId && merged.targetAccountId === undefined) {
8434
8903
  merged.targetAccountId = String(targetAccountId).trim();
8435
8904
  }
8905
+ // ...and as `accountId`, because that is the key the platform's account-targeting tools actually read
8906
+ // (mcp_run_action resolves `input.accountId`, falling back to the RUN SCOPE when it is absent). Without
8907
+ // this, `--target-account 36` printed "targets: account 36" in the World block while the tool ran against
8908
+ // the scope account — a flag that reads like a target and silently is not. An explicit `accountId` in
8909
+ // --input still wins, like every other scope flag.
8910
+ if (targetAccountId && merged.accountId === undefined && merged.account_id === undefined) {
8911
+ merged.accountId = String(targetAccountId).trim();
8912
+ }
8436
8913
 
8437
8914
  const anchorAccountId = flags && (flags['anchor-account-id'] || flags.anchorAccountId);
8438
8915
  if (anchorAccountId && merged.anchorAccountId === undefined) {
@@ -8477,6 +8954,8 @@ async function toolCommand(flags) {
8477
8954
  const branchName = flags.branch || currentBranch(cwd);
8478
8955
  const dataMode = resolveDataMode(flags, session);
8479
8956
  const variantBranch = flags['variant-branch'];
8957
+ const asAccountId = flags['as-account'] || flags['as-account-id'] || flags.asAccountId;
8958
+ const targetAccountId = flags['target-account'] || flags.targetAccount || flags['target-account-id'] || flags.targetAccountId;
8480
8959
  const timeoutMs = parsePositiveInt(flags['timeout-ms'] || flags.timeoutMs, 60000);
8481
8960
  const callId = String(flags['call-id'] || flags.callId || crypto.randomUUID());
8482
8961
  const asyncMode = flagEnabled(flags.async) || flagEnabled(input.async);
@@ -8512,6 +8991,24 @@ async function toolCommand(flags) {
8512
8991
  data.responseFile = statusResponse.responseFile;
8513
8992
  data.sessionLog = sessionJsonlFile(cwd);
8514
8993
  data.localState = localStateSummary(cwd, flags);
8994
+ const statusBranchName = flags.branch || data.branchName || (stored && stored.branchName) || branchName;
8995
+ const statusWorkspace = data.workspace !== undefined ? data.workspace : resolveWorkspace(cwd, flags);
8996
+ const statusWorld = buildToolStatusWorld(data, stored, {
8997
+ repoAccountId: accountId,
8998
+ accountId: statusAccountId,
8999
+ checkoutAccountId: accountId,
9000
+ dataMode: data.dataMode || dataMode,
9001
+ branchName: statusBranchName,
9002
+ workspace: statusWorkspace,
9003
+ host: normalizeBaseUrl(baseUrl)
9004
+ });
9005
+ recordWorkstreamArtifact(cwd, flags, {
9006
+ type: 'tool_status',
9007
+ callId: requestedCallId,
9008
+ path: statusResponse.responseFile,
9009
+ world: statusWorld,
9010
+ workspace: data.workspace
9011
+ });
8515
9012
  // A completed run can still carry a tool-level refusal — see toolResultFailed.
8516
9013
  const polledFailed = data.toolSuccess === false || toolResultFailed(data.result);
8517
9014
  if (polledFailed) {
@@ -8521,13 +9018,13 @@ async function toolCommand(flags) {
8521
9018
  // Same question as the call that started it: a poll must not attach where the call itself would not.
8522
9019
  const statusVerification = await verificationPreflight(api, cwd, session, accountId, flags, {
8523
9020
  packetType: 'tool_call',
8524
- world: { accountId: statusAccountId, dataMode: data.dataMode || dataMode, branchName: flags.branch || (stored && stored.branchName) || branchName, workspace: resolveWorkspace(cwd, flags) }
9021
+ world: statusWorld
8525
9022
  }, { command: 'tool status', neverRefuse: true, quiet: jsonOutput });
8526
9023
  await appendVerificationPacket(api, cwd, session, accountId, statusVerification.evidenceFlags, {
8527
9024
  type: 'tool_call',
8528
9025
  success: data.status !== 'failed' && !polledFailed,
8529
9026
  claim: 'Tool status ' + requestedCallId,
8530
- world: buildCommandWorld(data, { accountId: statusAccountId, dataMode: data.dataMode || dataMode, branchName: flags.branch || (stored && stored.branchName) || branchName, host: normalizeBaseUrl(baseUrl) }),
9027
+ world: statusWorld,
8531
9028
  revision: collectVerificationSource(cwd, flags),
8532
9029
  evidenceCategories: ['tool_call'],
8533
9030
  rawRefs: { toolResponsePath: statusResponse.responseFile, callId: requestedCallId },
@@ -8542,6 +9039,7 @@ async function toolCommand(flags) {
8542
9039
  printLocalCommandContext(cwd, flags, { accountId: statusAccountId, branchName: flags.branch || (stored && stored.branchName) || branchName, dataMode: data.dataMode || dataMode });
8543
9040
  printLocalStateWarnings(cwd, flags);
8544
9041
  console.log('Tool call status:', data.status);
9042
+ if (data.world) runWorldLines(data.world, { host: normalizeBaseUrl(baseUrl) }).forEach((line) => console.log(line));
8545
9043
  console.log('Call ID:', requestedCallId);
8546
9044
  console.log('Data mode:', data.dataMode || dataMode);
8547
9045
  if (data.threadGroupingId) console.log('Thread grouping ID:', data.threadGroupingId);
@@ -8564,15 +9062,49 @@ async function toolCommand(flags) {
8564
9062
  });
8565
9063
 
8566
9064
  const toolWorkspace = resolveWorkspace(cwd, flags);
9065
+ const legacyInputAccountId = input && (input.accountId || input.account_id);
9066
+ // A STRUCTURED warning, not only a printed one: agents run with --json, and `input.accountId` no longer
9067
+ // decides the execution account, so the one signal that the command's component resolution belongs to a
9068
+ // different account than the tool's data must survive into the payload the agent actually reads.
9069
+ const toolWarnings = [];
9070
+ if (legacyInputAccountId && !asAccountId && !targetAccountId && String(legacyInputAccountId) !== String(accountId)) {
9071
+ toolWarnings.push({
9072
+ code: 'legacy_input_account_id',
9073
+ inputAccountId: String(legacyInputAccountId),
9074
+ scopeAccountId: String(accountId),
9075
+ message: 'input.accountId=' + legacyInputAccountId +
9076
+ ' is treated as a tool target while the command stays scoped from account ' + accountId +
9077
+ '. Use --as-account ' + legacyInputAccountId + ' for execution-account semantics, or --target-account ' + legacyInputAccountId + ' to make the target explicit.'
9078
+ });
9079
+ }
9080
+ const snapshotWarning = staleToolsSnapshotWarning(cwd, {
9081
+ executionAccountId: asAccountId || accountId,
9082
+ branchName,
9083
+ dataMode
9084
+ });
9085
+ if (snapshotWarning) toolWarnings.push(snapshotWarning);
9086
+ // stderr always: it never corrupts --json stdout, and suppressing it there hid the warning from exactly
9087
+ // the callers that cannot see the human output.
9088
+ toolWarnings.forEach((warning) => console.error('Warning: ' + warning.message));
8567
9089
  const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
8568
9090
  packetType: 'tool_call',
8569
- world: { accountId, dataMode, branchName, workspace: toolWorkspace }
9091
+ world: {
9092
+ repoAccountId: accountId,
9093
+ accountId: asAccountId || accountId,
9094
+ executionAccountId: asAccountId || accountId,
9095
+ targetAccountId: targetAccountId || legacyInputAccountId,
9096
+ dataMode,
9097
+ branchName,
9098
+ workspace: toolWorkspace
9099
+ }
8570
9100
  }, { command: 'tool --name ' + String(toolName) });
8571
9101
 
8572
9102
  const response = await loggedPost(api, cwd, '/cli/tool', {
8573
9103
  token: session.token,
8574
9104
  accountId,
8575
9105
  accountIdExplicit,
9106
+ asAccountId,
9107
+ targetAccountId: targetAccountId || undefined,
8576
9108
  // The repo/session account, sent as a FIRST-CLASS value rather than left to be inferred from
8577
9109
  // `accountId`: a read tool may reinterpret its own `input.accountId`, and the scope root — what bounds
8578
9110
  // what the call may discover — has to survive that. The server validates it; it is never authorization.
@@ -8602,6 +9134,15 @@ async function toolCommand(flags) {
8602
9134
  data.responseFile = response.responseFile;
8603
9135
  data.sessionLog = sessionJsonlFile(cwd);
8604
9136
  data.localState = localStateSummary(cwd, flags);
9137
+ if (toolWarnings.length) data.warnings = (data.warnings || []).concat(toolWarnings);
9138
+ recordWorkstreamArtifact(cwd, flags, {
9139
+ type: asyncMode ? 'tool_async_start' : 'tool_call',
9140
+ callId,
9141
+ path: response.responseFile,
9142
+ tool: String(toolName),
9143
+ world: buildCommandWorld(data, { repoAccountId: accountId, accountId: data.executionAccountId || asAccountId || accountId, dataMode: data.dataMode || dataMode, branchName, workspace: toolWorkspace, host: normalizeBaseUrl(baseUrl) }),
9144
+ workspace: data.workspace || toolWorkspace
9145
+ });
8605
9146
  if (toolFailureMessage && !data.toolMessage) data.toolMessage = toolFailureMessage;
8606
9147
 
8607
9148
  let evidenceData = data;
@@ -8614,6 +9155,7 @@ async function toolCommand(flags) {
8614
9155
  printLocalStateWarnings(cwd, flags);
8615
9156
  console.log(asyncMode ? 'Tool call started.'
8616
9157
  : (toolFailed ? 'Tool call FAILED — the tool ran and returned an error.' : 'Tool call succeeded.'));
9158
+ if (data.world) runWorldLines(data.world, { host: normalizeBaseUrl(baseUrl), gitHead: safeGitValue(cwd, 'git rev-parse HEAD') }).forEach((line) => console.log(line));
8617
9159
  if (toolFailed && toolFailureMessage) console.log('Tool error:', toolFailureMessage);
8618
9160
  console.log('Call ID:', callId);
8619
9161
  console.log('Data mode:', data.dataMode || dataMode);
@@ -8646,10 +9188,26 @@ async function toolCommand(flags) {
8646
9188
 
8647
9189
  finalStatus.data.responseFile = finalStatus.responseFile;
8648
9190
  finalStatus.data.sessionLog = sessionJsonlFile(cwd);
9191
+ recordWorkstreamArtifact(cwd, flags, {
9192
+ type: 'tool_async_final',
9193
+ callId,
9194
+ path: finalStatus.responseFile,
9195
+ tool: String(toolName),
9196
+ world: buildCommandWorld(finalStatus.data, { repoAccountId: accountId, accountId: finalStatus.data.executionAccountId || asAccountId || accountId, dataMode: finalStatus.data.dataMode || dataMode, branchName: finalStatus.data.branchName || branchName, workspace: toolWorkspace, host: normalizeBaseUrl(baseUrl) }),
9197
+ workspace: finalStatus.data.workspace || toolWorkspace
9198
+ });
8649
9199
  evidenceData = finalStatus.data;
8650
9200
  evidenceResponseFile = finalStatus.responseFile;
8651
9201
  if (!jsonOutput) {
8652
9202
  console.log('Final status:', finalStatus.data.status);
9203
+ if (finalStatus.data.world) {
9204
+ runWorldLines(finalStatus.data.world, { host: normalizeBaseUrl(baseUrl), gitHead: safeGitValue(cwd, 'git rev-parse HEAD') }).forEach((line) => console.log(line));
9205
+ const startWorldText = JSON.stringify(data.world || {});
9206
+ const finalWorldText = JSON.stringify(finalStatus.data.world || {});
9207
+ if (data.world && startWorldText !== finalWorldText) {
9208
+ console.log('World changed since start: inspect the final response file for the exact resolved execution world.');
9209
+ }
9210
+ }
8653
9211
  printToolResponseFileLine('Tool response file', finalStatus.responseFile);
8654
9212
  }
8655
9213
  if (finalStatus.data.status !== 'completed') {
@@ -8663,7 +9221,20 @@ async function toolCommand(flags) {
8663
9221
  success: asyncMode && !waitForAsync ? null : (!toolFailed && (!asyncMode || process.exitCode !== 1)),
8664
9222
  pending: asyncMode && !waitForAsync,
8665
9223
  claim: 'Tool call ' + String(toolName),
8666
- world: buildCommandWorld(evidenceData, { accountId: evidenceData.accountId || accountId, dataMode: evidenceData.dataMode || dataMode, branchName: evidenceData.branchName || branchName, workspace: resolveWorkspace(cwd, flags), host: normalizeBaseUrl(baseUrl), sourceLayer: evidenceData.componentSource }),
9224
+ world: buildCommandWorld(evidenceData, {
9225
+ accountId: evidenceData.executionAccountId || asAccountId || evidenceData.accountId || accountId,
9226
+ repoAccountId: accountId,
9227
+ checkoutAccountId: accountId,
9228
+ executionAccountId: evidenceData.executionAccountId || asAccountId || accountId,
9229
+ targetAccountId: evidenceData.targetAccountId || targetAccountId || legacyInputAccountId,
9230
+ dataMode: evidenceData.dataMode || dataMode,
9231
+ branchName: evidenceData.branchName || branchName,
9232
+ workspace: resolveWorkspace(cwd, flags),
9233
+ host: normalizeBaseUrl(baseUrl),
9234
+ componentSource: evidenceData.componentSource,
9235
+ componentSignature: evidenceData.componentSignature,
9236
+ sourceLayer: evidenceData.componentSource
9237
+ }),
8667
9238
  revision: Object.assign(collectVerificationSource(cwd, flags), {
8668
9239
  compileSignatures: evidenceData.componentSignature ? [evidenceData.componentSignature] : []
8669
9240
  }),
@@ -9063,7 +9634,11 @@ async function verifyCommand(flags, subcommand) {
9063
9634
  return response;
9064
9635
  }
9065
9636
 
9066
- const envelopeId = flags.envelope || flags['verify-envelope'] || flags.id || readActiveVerificationEnvelope(cwd, activeContext);
9637
+ const positionalEnvelopeId = flags._ && flags._[2];
9638
+ const showLikeSubcommands = ['show', 'read', 'get', 'packets', 'status', 'report'];
9639
+ const explicitEnvelopeId = flags.envelope || flags['verify-envelope'] || flags.id ||
9640
+ (showLikeSubcommands.includes(sub) ? positionalEnvelopeId : null);
9641
+ const envelopeId = explicitEnvelopeId || readActiveVerificationEnvelope(cwd, activeContext);
9067
9642
  if (!envelopeId) {
9068
9643
  throw new Error('No verification envelope selected. Run `remits-cli verify start --summary "..."` or pass --envelope <id>.');
9069
9644
  }
@@ -9222,7 +9797,7 @@ async function verifyCommand(flags, subcommand) {
9222
9797
  // report would list as wrong-world evidence on every status check.
9223
9798
  const statusVerification = await verificationPreflight(api, cwd, session, accountId, { 'verify-envelope': envelopeId }, {
9224
9799
  packetType: 'component_status',
9225
- world: { accountId, dataMode, branchName, workspace }
9800
+ world: { repoAccountId: accountId, accountId, executionAccountId: accountId, dataMode, branchName, workspace }
9226
9801
  }, { command: 'verify status', quiet: true, neverRefuse: true });
9227
9802
  const componentStatus = await loggedPost(api, cwd, '/cli/components', {
9228
9803
  token: session.token,
@@ -10591,6 +11166,7 @@ function renderDashboardHtml(snapshot) {
10591
11166
  '<div class="action-row">' +
10592
11167
  '<button class="action-btn" data-action="export-issues">Export Issue Bundle</button>' +
10593
11168
  '<button class="action-btn" data-action="rescan-repos">Rescan Account Repos</button>' +
11169
+ '<button class="action-btn" data-action="sync-guides">Update Guides In All Repos</button>' +
10594
11170
  '<button class="action-btn" data-action="reconnect-websockets">Reconnect Websockets</button>' +
10595
11171
  '<button class="action-btn" data-action="refresh-agents">Refresh Agents</button>' +
10596
11172
  '</div>' +
@@ -10663,7 +11239,7 @@ function renderDashboardHtml(snapshot) {
10663
11239
  'summaryItem("Available Agents",String(agentList.length)),' +
10664
11240
  'summaryItem("Routed Tickets",String(ticketCounts.routed||0))' +
10665
11241
  '].join("");}' +
10666
- 'function renderLastAction(state){const target=document.getElementById("last-action");if(!state.lastAction){target.className="empty";target.textContent="No dashboard actions have been triggered yet.";return;}target.className="summary-item";target.innerHTML=\'<span class="k">Last Action</span><span class="v">\'+escapeHtml(state.lastAction.action||"unknown")+\'</span><div class="muted" style="margin-top:8px;">\'+escapeHtml(formatTime(state.lastAction.at))+\'</div>\'+(state.lastAction.bundlePath?\'<div class="muted" style="margin-top:8px;word-break:break-word;"><strong>Bundle:</strong> \'+escapeHtml(state.lastAction.bundlePath)+\'</div>\':"");}' +
11242
+ 'function renderLastAction(state){const target=document.getElementById("last-action");if(!state.lastAction){target.className="empty";target.textContent="No dashboard actions have been triggered yet.";return;}target.className="summary-item";target.innerHTML=\'<span class="k">Last Action</span><span class="v">\'+escapeHtml(state.lastAction.action||"unknown")+\'</span><div class="muted" style="margin-top:8px;">\'+escapeHtml(formatTime(state.lastAction.at))+\'</div>\'+(state.lastAction.summary?\'<div class="muted" style="margin-top:8px;">\'+escapeHtml(state.lastAction.summary)+\'</div>\':"")+(state.lastAction.bundlePath?\'<div class="muted" style="margin-top:8px;word-break:break-word;"><strong>Bundle:</strong> \'+escapeHtml(state.lastAction.bundlePath)+\'</div>\':"");}' +
10667
11243
  'function renderIssues(state){const target=document.getElementById("issues-section");const issues=state.issues||[];if(!issues.length){target.innerHTML=\'<div class="empty">No automatic issue signals are currently detected. If something still feels wrong, you can still click "Export Issue Bundle" to prepare a package for a Remits system admin.</div>\';return;}target.innerHTML=issues.map(function(issue,index){const tone=issue.severity==="bad"?"bad":"warn";const meta=badge(issue.severity==="bad"?"critical":"warning",tone)+\' <span class="muted">\'+escapeHtml(issue.scope+(issue.repoAccountId?(" account "+issue.repoAccountId):""))+\'</span>\';const evidence=(issue.evidence||[]).map(function(line){return "<li>"+escapeHtml(line)+"</li>";}).join("");const body=\'<div class="stack"><div class="muted">\'+escapeHtml(issue.summary||"")+\'</div><div class="mini-card"><strong>Recommendation</strong>\'+escapeHtml(issue.recommendation||"") + \'</div><div><strong style="display:block;margin-bottom:8px;">Evidence</strong><ul style="margin:0;padding-left:18px;display:grid;gap:8px;">\'+(evidence||"<li>No evidence lines captured.</li>")+\'</ul></div></div>\';return detailsBlock("issue:"+issue.id,(index+1)+". "+issue.title,meta,body,index===0);}).join("");}' +
10668
11244
  'function renderSupportTickets(state){const target=document.getElementById("support-ticket-section");const support=state.supportTickets||{};const parts=support.compartments||[];const errors=support.errors||[];let html="";if(errors.length){html+=detailsBlock("support:errors","Queue fetch errors",badge(String(errors.length)+" error(s)","warn"),codeBlock("support-errors",JSON.stringify(errors,null,2),"log"),false);}if(!parts.length&&!errors.length){target.innerHTML=html+\'<div class="empty">No authenticated support-ticket queue is available yet.</div>\';return;}html+=parts.map(function(part,pi){const lane=(part.dataMode||"prod");const meta=badge(lane+" lane",lane==="test"?"warn":"ok")+\' \'+badge(String(part.open||0)+" open","ok")+\' <span class="muted">\'+escapeHtml(String(part.queueCount||0))+\' account queue(s)\'+(part.overlappingQueuesDropped?", "+part.overlappingQueuesDropped+" overlapping dropped":"")+\'</span>\';const caveat=part.scopeUnknown?\'<div class="mini-card"><strong>Note</strong>A queue did not report which accounts it scanned, so this total may count a ticket more than once.</div>\':"";const cards=\'<div class="mini-grid"><div class="mini-card"><strong>Open</strong>\'+escapeHtml(String(part.open||0))+\'</div><div class="mini-card"><strong>Routed To An Agent</strong>\'+escapeHtml(String(part.routed||0))+\'</div><div class="mini-card"><strong>Unrouted</strong>\'+escapeHtml(String(part.unrouted||0))+\'</div><div class="mini-card"><strong>Awaiting A Reply</strong>\'+escapeHtml(String(part.awaitingReplyOnPage||0))+\' <span class="muted">of \'+escapeHtml(String((part.tickets||[]).length))+\' shown</span></div></div>\'+caveat;const rows=(part.tickets||[]).length?(part.tickets||[]).map(function(ticket,index){const routed=!!ticket.routedAgentId;const waiting=!!ticket.awaitingResponseSince;const meta2=badge(ticket.priority||"priority",ticket.priority==="critical"||ticket.priority==="high"?"bad":"warn")+\' \'+badge(ticket.status||"status",ticket.status==="open"?"ok":"warn")+\' \'+badge(routed?("routed: "+(ticket.routedAgentLabel||ticket.routedAgentId)):"unrouted",routed?"ok":"warn")+(waiting?\' \'+badge("awaiting reply","warn"):"")+\' <span class="muted">\'+escapeHtml(ticket.accountName||ticket.accountId||"unknown account")+\'</span>\';return detailsBlock("support-ticket:"+pi+":"+ticket.ticketId,String(ticket.ticketId||"ticket")+" "+(ticket.subject||""),meta2,supportTicketDetails(state,ticket,"support-ticket:"+pi+":"+index),false);}).join(""):\'<div class="empty">No open support tickets in this compartment.</div>\';return detailsBlock("compartment:"+part.compartment,part.baseUrl||part.compartment,meta,cards+\'<div class="accordion">\'+rows+\'</div>\',pi===0);}).join("");html+=\'<div class="muted" style="margin-top:10px;">Each platform and data lane is counted separately. A localhost test-lane queue is full of suite fixtures and is not comparable to a production queue — summing them is what made this panel report 583 open tickets against a production queue of 24.</div>\';target.innerHTML=html;}' +
10669
11245
  'function supportTicketDetails(state,ticket,key){const agents=((state.agents||{}).registered)||[];const ticketAccountId=String(ticket.accountId||"");const eligible=agents.filter(function(a){if(ticket.baseUrl&&a.baseUrl&&a.baseUrl!==ticket.baseUrl)return false;if(ticket.dataMode&&a.dataMode&&a.dataMode!==ticket.dataMode)return false;return (a.accounts||[]).some(function(acc){return String(acc.accountId)===ticketAccountId||acc.routingScope==="self_and_descendants";});});const options=[\'<option value="">- unrouted -</option>\'].concat(eligible.map(function(a){const sel=a.agentId===ticket.routedAgentId?" selected":"";return \'<option value="\'+escapeHtml(a.agentId)+\'"\'+sel+\'>\'+escapeHtml((a.label||a.userName||a.agentId)+" - "+(a.state||"idle")+(a.activity?" ("+a.activity+")":""))+\'</option>\';})).join("");const routeControl=eligible.length?(\'<div class="mini-card"><strong>Route To Agent</strong><div style="display:flex;gap:8px;margin-top:6px;"><select data-route-ticket="\'+escapeHtml(String(ticket.ticketId||""))+\'" data-route-account="\'+escapeHtml(ticketAccountId)+\'" data-route-base="\'+escapeHtml(ticket.baseUrl||"")+\'" data-route-mode="\'+escapeHtml(ticket.dataMode||"")+\'" style="flex:1;min-width:0;">\'+options+\'</select><button class="action-btn" data-action="route-ticket" data-ticket="\'+escapeHtml(String(ticket.ticketId||""))+\'">Route</button></div></div>\'):\'<div class="mini-card"><strong>Route To Agent</strong>No registered agent covers this account.</div>\';const lane=ticket.dataMode||"prod";const planning=[ticket.workstream?("Workstream "+ticket.workstream):"",ticket.plannedIn?("Planned "+ticket.plannedIn):"",ticket.boardStage?("Board "+ticket.boardStage):"",ticket.rank!==undefined&&ticket.rank!==null&&ticket.rank!==""?("Rank "+ticket.rank):"",ticket.size?("Size "+ticket.size):"",ticket.blockedBy?("Blocked by "+ticket.blockedBy):""].filter(Boolean).join(" / ");const command="remits-cli ticket read --ticket "+String(ticket.ticketId||"")+" --data-mode "+lane+"\\nremits-cli ticket accept --ticket "+String(ticket.ticketId||"")+" --data-mode "+lane;return \'<div class="stack"><div class="mini-grid"><div class="mini-card"><strong>Ticket</strong>\'+escapeHtml(ticket.ticketId||"n/a")+\'</div><div class="mini-card"><strong>Account</strong>\'+escapeHtml((ticket.accountName||"n/a")+" ("+(ticket.accountId||"n/a")+")")+\'</div><div class="mini-card"><strong>Type</strong>\'+escapeHtml(ticket.type||"n/a")+\'</div><div class="mini-card"><strong>Owned By</strong>\'+escapeHtml(ticket.assignedTo||"unassigned")+\'</div><div class="mini-card"><strong>Routed To</strong>\'+escapeHtml(ticket.routedAgentLabel||ticket.routedAgentId||"nobody")+\'</div><div class="mini-card"><strong>Implementation</strong>\'+escapeHtml((ticket.implementationAccountName||"n/a")+" ("+(ticket.implementationAccountId||"n/a")+")")+\'</div><div class="mini-card"><strong>Planning</strong>\'+escapeHtml(planning||"No planning fields")+\'</div></div>\'+routeControl+\'<div class="muted">\'+escapeHtml(ticket.affectedComponent||"No affected component recorded.")+\'</div>\'+codeBlock(key+":cmd",command)+\'</div>\';}' +
@@ -11054,6 +11630,10 @@ function startDashboardServer(preferredPort) {
11054
11630
  } else if (action === 'rescan-repos') {
11055
11631
  recordDiscoverySummary(discoverAccountRepos());
11056
11632
  supportTicketSnapshotCache = { fetchedAtMs: 0, value: null };
11633
+ } else if (action === 'sync-guides') {
11634
+ actionResult = await syncGuidesAcrossIndexedRepos({ rescan: true });
11635
+ runtimeState.lastAction.summary = guideSyncSummaryLine(actionResult);
11636
+ recordDiscoverySummary(discoverAccountRepos());
11057
11637
  } else if (action === 'refresh-agents') {
11058
11638
  agentSnapshotCache = { fetchedAtMs: 0, value: null };
11059
11639
  supportTicketSnapshotCache = { fetchedAtMs: 0, value: null };
@@ -12151,6 +12731,11 @@ function resolveAgentId(flags, options = {}) {
12151
12731
  * agent is silently useless for support: it registers, appears online, and can never be routed a
12152
12732
  * production ticket. Ask for `--data-mode test` when you want a fixture agent.
12153
12733
  */
12734
+ /**
12735
+ * The host and lane an agent command runs against: the flag, then this agent's own prior registration, then
12736
+ * the PRODUCTION platform and lane. That last default is deliberate — `agent register` joins the support
12737
+ * fleet, and a fleet member on a test lane appears online and can never be routed a real ticket.
12738
+ */
12154
12739
  function agentDefaults(flags, entry) {
12155
12740
  return {
12156
12741
  baseUrl: normalizeBaseUrl(
@@ -12163,10 +12748,29 @@ function agentDefaults(flags, entry) {
12163
12748
  function agentSessionFor(flags, entry) {
12164
12749
  const cwd = (entry && entry.cwd) || process.cwd();
12165
12750
  const defaults = agentDefaults(flags, entry);
12166
- const context = resolveSessionContext(cwd, Object.assign({}, flags, {
12167
- 'base-url': defaults.baseUrl,
12168
- 'data-mode': defaults.dataMode
12169
- }));
12751
+ const pinnedByCaller = Boolean((flags && flags['base-url']) || (entry && entry.baseUrl));
12752
+ let context;
12753
+ try {
12754
+ context = resolveSessionContext(cwd, Object.assign({}, flags, {
12755
+ 'base-url': defaults.baseUrl,
12756
+ 'data-mode': defaults.dataMode
12757
+ }));
12758
+ } catch (err) {
12759
+ // The prod default is intentional, and invisible: every OTHER command in this checkout follows the
12760
+ // active session, so "not authenticated at <some prod url>" reads as a broken session rather than as
12761
+ // "this command deliberately went somewhere else". Say which host it chose and why.
12762
+ if (!pinnedByCaller) {
12763
+ const local = readSession({}) || {};
12764
+ const refusal = new Error('Agent commands join the SUPPORT FLEET, which lives on the production ' +
12765
+ 'platform, so this one targeted ' + defaults.baseUrl + ' — not the session this checkout uses' +
12766
+ (local.baseUrl ? ' (' + normalizeBaseUrl(local.baseUrl) + ')' : '') + '. ' +
12767
+ 'Authenticate there with `remits-cli auth --base-url ' + defaults.baseUrl + '`, or point this ' +
12768
+ 'command at another platform with `--base-url <url>`.');
12769
+ refusal.cause = err;
12770
+ throw refusal;
12771
+ }
12772
+ throw err;
12773
+ }
12170
12774
  if (!context.session || !context.session.token) {
12171
12775
  throw new Error('No authenticated session for ' + defaults.baseUrl +
12172
12776
  '. Run: remits-cli auth --base-url ' + defaults.baseUrl);
@@ -12747,7 +13351,7 @@ async function postAgentStatus(context, agentId, update) {
12747
13351
  * the same thing. Only the local supervisor can add machine-local repository hints; the worker owns the
12748
13352
  * final grounding step, including creating or choosing a git worktree before it edits.
12749
13353
  */
12750
- function spawnTicketWorker(context, ctx, ticket) {
13354
+ async function spawnTicketWorker(context, ctx, ticket) {
12751
13355
  const { spawn } = require('child_process');
12752
13356
  const agentId = ctx.agentId;
12753
13357
  const ticketId = String(ticket.id || ticket.ticketId);
@@ -12766,6 +13370,32 @@ function spawnTicketWorker(context, ctx, ticket) {
12766
13370
  const targetBranch = repositoryWork ? ticketTargetBranch(ticket, baseWorkingDirectory) : gitBranchNameInDirectory(baseWorkingDirectory);
12767
13371
  const suggestedWorktree = repositoryWork && baseWorkingDirectory ? ticketWorktreePath(baseWorkingDirectory, ticketId) : null;
12768
13372
  const workingDirectory = baseWorkingDirectory;
13373
+ if (workingDirectory) {
13374
+ try {
13375
+ const guideSummary = await downloadAndSyncGuides(workingDirectory, {
13376
+ session: context.session,
13377
+ baseUrl: context.baseUrl,
13378
+ accountId: Number(ticket.accountId) || context.accountId
13379
+ });
13380
+ if (guideSummary && ((guideSummary.updated || []).length || (guideSummary.removed || []).length)) {
13381
+ appendGlobalActivityLog('guides', 'worker_repo_synced', {
13382
+ agentId,
13383
+ ticketId,
13384
+ accountId: Number(ticket.accountId) || null,
13385
+ directory: workingDirectory,
13386
+ updated: (guideSummary.updated || []).length,
13387
+ removed: (guideSummary.removed || []).length
13388
+ });
13389
+ }
13390
+ } catch (err) {
13391
+ appendGlobalActivityLog('guides', 'worker_repo_sync_failed', {
13392
+ agentId,
13393
+ ticketId,
13394
+ directory: workingDirectory,
13395
+ error: describeError(err)
13396
+ }, 'error');
13397
+ }
13398
+ }
12769
13399
  // The brief is rendered server-side without the local path (the platform cannot know it), so the
12770
13400
  // resolved local repository hint is appended here. When nothing resolved, the server-rendered brief already
12771
13401
  // tells the worker to find it rather than guess — do not paper over that with a default.
@@ -12796,6 +13426,7 @@ function spawnTicketWorker(context, ctx, ticket) {
12796
13426
  'git -C ' + shellQuote(workingDirectory) + ' worktree add --force ' + shellQuote(suggestedWorktree) +
12797
13427
  (targetBranch ? ' ' + targetBranch : ' <branch>') + '\n' +
12798
13428
  'cd ' + shellQuote(suggestedWorktree) + '\n' +
13429
+ 'remits-cli guides sync\n' +
12799
13430
  (targetBranch
12800
13431
  ? 'git pull --ff-only origin ' + shellQuote(targetBranch) + '\n' +
12801
13432
  'git rev-list --left-right --count HEAD...origin/' + shellQuote(targetBranch) + ' # must print: 0 0\n'
@@ -13169,7 +13800,7 @@ async function agentHeartbeatDaemonCommand(flags) {
13169
13800
  if (!ticketId || inFlight.has(ticketId)) {
13170
13801
  continue;
13171
13802
  }
13172
- const started = spawnTicketWorker(context, ctx, ticket);
13803
+ const started = await spawnTicketWorker(context, ctx, ticket);
13173
13804
  inFlight.set(ticketId, { pid: started.pid, accountId: ticket.accountId });
13174
13805
  started.done.then(() => {
13175
13806
  inFlight.delete(ticketId);
@@ -14018,6 +14649,13 @@ async function activityInspectCommand(flags) {
14018
14649
  dataMode: resolveDataMode(flags, session),
14019
14650
  userId: flags['user-id'] || flags.userId || flags['cli-user-id'],
14020
14651
  userEmail: flags['user-email'] || flags.email,
14652
+ workspace: flags.workspace,
14653
+ branchName: flags.branch || flags['branch-name'],
14654
+ variantBranch: flags['variant-branch'],
14655
+ // The local proof-campaign id, so the platform-side related view and `workstream status` can be asked
14656
+ // the same question. Never defaulted from the workspace here: an unasked-for filter that silently
14657
+ // narrows an investigation is worse than no filter.
14658
+ workstreamId: normalizeWorkstreamId(flags.workstream || flags['workstream-id']),
14021
14659
  limit: flags.limit || flags.max || 20,
14022
14660
  envelopeLimit: flags['envelope-limit'] || flags.envelopeLimit
14023
14661
  }).then((r) => r.data);
@@ -14059,13 +14697,18 @@ function slimActivitySmell(smell) {
14059
14697
  return {
14060
14698
  severity: s.severity || 'info',
14061
14699
  type: s.type || 'signal',
14700
+ title: s.title || s.envelopeSummary || s.testName || null,
14062
14701
  summary: s.summary || null,
14702
+ status: s.status || s.health || null,
14703
+ currentFailureCount: s.currentFailureCount == null ? null : s.currentFailureCount,
14063
14704
  accountId: s.accountId || null,
14064
14705
  laneId: s.laneId || null,
14065
14706
  envelopeId: s.envelopeId || null,
14066
14707
  taskId: s.taskId || null,
14708
+ testName: s.testName || null,
14067
14709
  branchName: s.branchName || null,
14068
- workspace: s.workspace || null
14710
+ workspace: s.workspace || null,
14711
+ updatedAt: formatTime(s.updatedAtMs || s.updatedAt || s.at || s.createdAt)
14069
14712
  };
14070
14713
  }
14071
14714
 
@@ -14078,7 +14721,10 @@ function compactActivityInspect(response) {
14078
14721
  const nextActions = smells
14079
14722
  .filter((smell) => smellSeverityRank(smell) <= 2)
14080
14723
  .slice(0, 5)
14081
- .map((smell) => (smell.type || 'signal') + ': ' + (smell.summary || 'inspect this signal'));
14724
+ .map((smell) => {
14725
+ const title = smell.title || smell.envelopeSummary || smell.testName;
14726
+ return (smell.type || 'signal') + (title ? ' (' + title + ')' : '') + ': ' + (smell.summary || 'inspect this signal');
14727
+ });
14082
14728
 
14083
14729
  return {
14084
14730
  success: response.success,
@@ -14134,9 +14780,29 @@ function printActivityInspectSummary(response) {
14134
14780
  ? ' for ' + (response.subjectUser.email || response.subjectUser.name || ('user ' + response.subjectUser.id))
14135
14781
  : '';
14136
14782
  console.log('Activity inspection' + subject + ' — ' + response.dataMode + ' lane');
14783
+ // A scope of several hundred accounts printed as one line is not a scope statement, it is a wall. Say how
14784
+ // many, show the head, and point at --json for the rest.
14785
+ const scopeIds = response.accountIds || [];
14786
+ const scopeIdText = scopeIds.length > 12
14787
+ ? scopeIds.slice(0, 12).join(', ') + ' … (+' + (scopeIds.length - 12) + ' more, --json for all)'
14788
+ : (scopeIds.join(', ') || String(response.accountId));
14137
14789
  console.log('Scope: ' + (response.accountName || ('account ' + response.accountId)) +
14138
14790
  ' (' + response.accountId + '), ' + (response.scope || 'self') +
14139
- ' · accounts ' + ((response.accountIds || []).join(', ') || response.accountId));
14791
+ ' · ' + (scopeIds.length || 1) + ' account(s): ' + scopeIdText);
14792
+ if (response.filters && (response.filters.workspace || response.filters.branchName || response.filters.workstreamId)) {
14793
+ console.log('Filters: ' + [
14794
+ response.filters.workspace ? 'workspace ' + response.filters.workspace : null,
14795
+ response.filters.branchName ? 'branch ' + response.filters.branchName : null,
14796
+ response.filters.workstreamId ? 'workstream ' + response.filters.workstreamId : null
14797
+ ].filter(Boolean).join(' · ') + ' (evidence below is narrowed to it)');
14798
+ }
14799
+ if (Array.isArray(response.relatedAccounts) && response.relatedAccounts.length) {
14800
+ const related = response.relatedAccounts;
14801
+ const shown = related.slice(0, 12).map((row) =>
14802
+ row.id + ' ' + (row.name || '') + (row.role ? ' (' + row.role + ')' : '')).join('; ');
14803
+ console.log('Related accounts: ' + shown +
14804
+ (related.length > 12 ? '; … (+' + (related.length - 12) + ' more, --json for all)' : ''));
14805
+ }
14140
14806
  const counts = response.counts || {};
14141
14807
  console.log('Counts: ' +
14142
14808
  (counts.agents || 0) + ' agent(s), ' +
@@ -14173,6 +14839,27 @@ function printActivityInspectSummary(response) {
14173
14839
  }
14174
14840
  }
14175
14841
 
14842
+ // "2 agent(s)" with no names is the shape of the question, not the answer: an agent asking who else is in
14843
+ // this repo right now could read the count and still have to go find them. The payload already carries the
14844
+ // directory, branch, workspace and last-seen of each.
14845
+ const liveAgents = response.agents || [];
14846
+ if (liveAgents.length) {
14847
+ console.log('');
14848
+ console.log('Live agents here:');
14849
+ liveAgents.slice(0, 10).forEach((agent) => {
14850
+ const who = agent.label || agent.userEmail || agent.agentId || 'unknown agent';
14851
+ const where = [agent.branch ? 'branch ' + agent.branch : null,
14852
+ 'workspace ' + (agent.workspace || 'shared'),
14853
+ agent.dataMode ? 'data ' + agent.dataMode : null].filter(Boolean).join(' · ');
14854
+ const seen = agent.lastSeenAtMs ? ' · seen ' + formatTime(agent.lastSeenAtMs) : '';
14855
+ console.log(' - ' + who + ' [' + (agent.state || 'unknown') + ']' +
14856
+ (agent.source === 'implicit' ? ' (implicit)' : '') + seen);
14857
+ console.log(' ' + where);
14858
+ if (agent.workingDirectory) console.log(' ' + agent.workingDirectory);
14859
+ });
14860
+ if (liveAgents.length > 10) console.log(' ...' + (liveAgents.length - 10) + ' more agent(s)');
14861
+ }
14862
+
14176
14863
  const lanes = response.lanes || [];
14177
14864
  if (lanes.length) {
14178
14865
  console.log('');
@@ -14237,6 +14924,77 @@ function printActivityInspectSummary(response) {
14237
14924
  console.log('Full structured payload: remits-cli activity inspect --json');
14238
14925
  }
14239
14926
 
14927
+ function readWorkstreamIndex(cwd, workstreamId) {
14928
+ const id = normalizeWorkstreamId(workstreamId);
14929
+ if (!id) return null;
14930
+ const file = workstreamFile(cwd, id);
14931
+ if (!fs.existsSync(file)) return null;
14932
+ try {
14933
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
14934
+ } catch (_) {
14935
+ return null;
14936
+ }
14937
+ }
14938
+
14939
+ function listWorkstreamIndexes(cwd) {
14940
+ const paths = ensureLocalState(cwd);
14941
+ if (!fs.existsSync(paths.workstreamsDir)) return [];
14942
+ return fs.readdirSync(paths.workstreamsDir, { withFileTypes: true })
14943
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
14944
+ .map((entry) => readWorkstreamIndex(cwd, entry.name.replace(/\.json$/, '')))
14945
+ .filter(Boolean)
14946
+ .sort((a, b) => String(b.updatedAt || '').localeCompare(String(a.updatedAt || '')));
14947
+ }
14948
+
14949
+ async function workstreamCommand(flags, subcommand) {
14950
+ const cwd = process.cwd();
14951
+ ensureLocalState(cwd);
14952
+ const action = subcommand || 'status';
14953
+ if (action !== 'status') throw new Error('Unknown workstream subcommand: ' + action);
14954
+ const requested = resolveWorkstreamId(cwd, flags, { workspace: flags.workspace });
14955
+ let rows = requested ? [readWorkstreamIndex(cwd, requested)].filter(Boolean) : listWorkstreamIndexes(cwd);
14956
+ if (flags.workspace) {
14957
+ const normalizedWorkspace = normalizeWorkspace(flags.workspace);
14958
+ rows = rows.filter((row) => {
14959
+ if (normalizeWorkspace(row.roles && row.roles.workspace) === normalizedWorkspace) return true;
14960
+ return (row.artifacts || []).some((artifact) => normalizeWorkspace(artifact.workspace || (artifact.world && artifact.world.workspace)) === normalizedWorkspace);
14961
+ });
14962
+ }
14963
+ if (flagEnabled(flags.json)) {
14964
+ console.log(JSON.stringify({ success: true, workstreams: rows }, null, 2));
14965
+ return;
14966
+ }
14967
+ if (!rows.length) {
14968
+ console.log('No local workstream index found.');
14969
+ console.log('Start one implicitly with a workspace, or pass --workstream <id> / REMITS_WORKSTREAM_ID while running tool or verify commands.');
14970
+ return;
14971
+ }
14972
+ rows.forEach((row) => {
14973
+ console.log('Workstream:', row.workstreamId);
14974
+ console.log('Updated:', row.updatedAt || 'unknown');
14975
+ console.log('Actors:', (row.actors || []).join(', ') || 'none recorded');
14976
+ const roleParts = workstreamWorldParts(row.roles);
14977
+ if (roleParts.length) console.log('Latest world:', roleParts.join(' · '));
14978
+ // A campaign that spanned several worlds says so. One blended line would describe a run that never
14979
+ // happened, and "which world proved this?" is the question the whole surface exists to answer.
14980
+ const worlds = Array.isArray(row.worlds) ? row.worlds : [];
14981
+ if (worlds.length > 1) {
14982
+ console.log('Worlds in this workstream: ' + worlds.length);
14983
+ worlds.slice(-6).forEach((world) => {
14984
+ const parts = workstreamWorldParts(world);
14985
+ if (parts.length) console.log(' - ' + parts.join(' · '));
14986
+ });
14987
+ }
14988
+ const artifacts = Array.isArray(row.artifacts) ? row.artifacts.slice(-12) : [];
14989
+ console.log('Artifacts:', artifacts.length ? '' : 'none recorded');
14990
+ artifacts.forEach((artifact) => {
14991
+ const label = [artifact.type, artifact.tool, artifact.callId, artifact.envelopeId, artifact.packetType].filter(Boolean).join(' ');
14992
+ console.log(' - ' + (artifact.recordedAt || '') + ' ' + label);
14993
+ if (artifact.path) console.log(' ' + artifact.path);
14994
+ });
14995
+ });
14996
+ }
14997
+
14240
14998
  /**
14241
14999
  * The live work map: which repositories are being edited, by whom, and who else is around.
14242
15000
  *
@@ -15166,12 +15924,16 @@ function printCorpusHelp() {
15166
15924
  }
15167
15925
 
15168
15926
  function printToolHelp() {
15169
- console.log('Usage: remits-cli tool --name <toolName> [--base-url URL] [--account-id ID] [--branch BRANCH] [--input "{...}"|--input-file file.json] [--data-mode test|prod] [--variant-branch NAME|none] [--timeout-ms 60000] [--async true --wait true]');
15927
+ console.log('Usage: remits-cli tool --name <toolName> [--base-url URL] [--account-id ID] [--as-account ID] [--target-account ID] [--branch BRANCH] [--input "{...}"|--input-file file.json] [--data-mode test|prod] [--variant-branch NAME|none] [--timeout-ms 60000] [--async true --wait true]');
15170
15928
  console.log(' remits-cli tool status --call-id <callId> [--base-url URL] [--account-id ID] [--data-mode test|prod]');
15171
15929
  console.log('');
15172
15930
  console.log('Hierarchy scope (read/discovery tools) — you do NOT need the owning child account id:');
15173
15931
  console.log(' --scope self|children|hierarchy expand from the repo account when searching');
15174
15932
  console.log(' --target-account-id ID the exact account to act on (required by writes)');
15933
+ console.log(' --as-account ID execute as this reachable account; subscriber branch edges apply');
15934
+ console.log(' --target-account ID the account the tool acts ON (sent as input.accountId and');
15935
+ console.log(' input.targetAccountId when --input omits them). It does NOT change');
15936
+ console.log(' component resolution — add --as-account ID for that.');
15175
15937
  console.log(' --account-ids 1,2,3 an explicit bounded list of owner accounts');
15176
15938
  console.log(' --anchor-account-id ID disambiguate the path for a multi-parent account');
15177
15939
  console.log(' These are merged into --input; a value already in --input wins.');
@@ -15179,17 +15941,68 @@ function printToolHelp() {
15179
15941
  console.log('Examples:');
15180
15942
  console.log(' remits-cli tool --name mcp_firestore_search --input-file query.json --data-mode prod');
15181
15943
  console.log(' remits-cli tool --name mcp_firestore_search --input \'{"collection":"statements","documentId":"1234"}\' --scope children');
15182
- console.log(' remits-cli tool --name mcp_run_action --input \'{"accountId":49,"actionId":200,"executionMode":"async"}\' --data-mode prod');
15944
+ console.log(' remits-cli tool --name mcp_run_action --account-id 21 --as-account 36 --target-account 36 --input \'{"actionId":200,"executionMode":"async"}\' --data-mode prod');
15183
15945
  console.log('');
15184
15946
  console.log('For mcp_run_action and mcp_run_agent, prefer the tool\'s own executionMode:"async" and poll with that tool\'s controlAction:"status".');
15185
15947
  }
15186
15948
 
15187
15949
  function printToolsHelp() {
15188
- console.log('Usage: remits-cli tools [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--variant-branch NAME|none]');
15950
+ console.log('Usage: remits-cli tools [--base-url URL] [--account-id ID] [--as-account ID] [--branch BRANCH] [--data-mode test|prod] [--variant-branch NAME|none]');
15189
15951
  console.log('');
15190
15952
  console.log('Refreshes .remits-cli/tools/tools.json for the current account/branch/data-mode context.');
15191
15953
  }
15192
15954
 
15955
+ function printGuidesHelp() {
15956
+ console.log('Usage: remits-cli guides sync [--all] [--base-url URL] [--json]');
15957
+ console.log('');
15958
+ console.log('Refreshes front-stage guides from the authenticated platform.');
15959
+ console.log(' --all updates every indexed account checkout on this machine.');
15960
+ console.log(' without --all, run from an account repo to refresh only that checkout.');
15961
+ }
15962
+
15963
+ async function guidesCommand(flags, subcommand) {
15964
+ const mode = subcommand || 'sync';
15965
+ if (mode === 'help' || mode === '--help' || flagEnabled(flags.help)) {
15966
+ printGuidesHelp();
15967
+ return;
15968
+ }
15969
+ if (mode !== 'sync' && mode !== 'refresh' && mode !== 'update') {
15970
+ throw new Error('Unknown guides command: ' + mode + '. Use: remits-cli guides sync [--all]');
15971
+ }
15972
+
15973
+ if (flagEnabled(flags.all)) {
15974
+ const result = await syncGuidesAcrossIndexedRepos({
15975
+ baseUrl: flags['base-url'] || null,
15976
+ rescan: !flagEnabled(flags['no-rescan'])
15977
+ });
15978
+ if (flagEnabled(flags.json)) {
15979
+ console.log(JSON.stringify(result, null, 2));
15980
+ return;
15981
+ }
15982
+ console.log('Guides sync: ' + guideSyncSummaryLine(result));
15983
+ for (const repo of result.repos || []) {
15984
+ if (repo.skipped) {
15985
+ console.log(' skipped account ' + (repo.accountId || '?') + ': ' + repo.reason + ' ' + repo.directory);
15986
+ } else if ((repo.updated || []).length || (repo.removed || []).length) {
15987
+ console.log(' updated account ' + repo.accountId + ': ' +
15988
+ ((repo.updated || []).length + (repo.removed || []).length) + ' file(s) ' + repo.directory);
15989
+ }
15990
+ }
15991
+ return;
15992
+ }
15993
+
15994
+ if (!loadAccountInfo(process.cwd())) {
15995
+ throw new Error('Run `remits-cli guides sync` from an account repo, or pass --all.');
15996
+ }
15997
+ const before = Date.now();
15998
+ await ensureGuidesSynced(process.cwd(), flags);
15999
+ if (flagEnabled(flags.json)) {
16000
+ console.log(JSON.stringify({ success: true, directory: process.cwd(), elapsedMs: Date.now() - before }, null, 2));
16001
+ return;
16002
+ }
16003
+ console.log('Guides synced for ' + process.cwd());
16004
+ }
16005
+
15193
16006
  function printTokenHelp() {
15194
16007
  console.log('Usage: remits-cli token [--base-url URL] [--branch BRANCH] [--path embeddable/path] [--data-mode test|prod] [--as-account ID] [--variant-branch NAME|none]');
15195
16008
  console.log(' remits-cli token inspect --token <token|tokenKey|URL> [--base-url URL] [--account-id ID]');
@@ -15276,7 +16089,7 @@ async function main() {
15276
16089
  try { updateAccountRepoIndex(process.cwd()); } catch (_) { /* not in account repo */ }
15277
16090
  // Auto-start the background service if not already running.
15278
16091
  // Skip for lifecycle subcommands and help.
15279
- const isLifecycleCmd = command === 'listen' || command === 'start' || command === 'stop' || command === 'status' || command === 'whoami' || command === 'doctor' || command === 'agent' || command === 'ticket';
16092
+ const isLifecycleCmd = command === 'listen' || command === 'start' || command === 'stop' || command === 'status' || command === 'whoami' || command === 'doctor' || command === 'agent' || command === 'ticket' || command === 'guides';
15280
16093
  const isHelpCmd = !command || command === 'help' || command === '--help' || wantsHelp;
15281
16094
  if (!wantsJson && !isHelpCmd && command === 'components') {
15282
16095
  const commandName = 'components ' + (subcommand || 'command');
@@ -15313,18 +16126,20 @@ async function main() {
15313
16126
  console.log(' remits-cli agent status [--state idle|working|paused] [--ticket ID] [--activity "..."]');
15314
16127
  console.log(' remits-cli agent list [--account-id ID] # who else is available');
15315
16128
  console.log(' remits-cli agent map [--account-ids 1,4] [--json] # who is EDITING what, where, right now');
15316
- console.log(' remits-cli activity inspect [--account-id ID] [--scope self|children] [--user-email E] [--summary|--compact] [--json]');
16129
+ console.log(' remits-cli activity inspect [--account-id ID] [--scope self|children|related] [--workspace NAME] [--workstream ID] [--user-email E] [--summary|--compact] [--json]');
16130
+ console.log(' remits-cli workstream status [--workstream ID|--workspace NAME] [--json]');
15317
16131
  console.log(' remits-cli agent release # stop receiving tickets now');
15318
16132
  console.log(' remits-cli start [--foreground true] [--port 8787] # human dashboard in the browser');
15319
16133
  console.log(' remits-cli stop');
15320
16134
  console.log(' remits-cli status [--base-url URL] [--account-id ID] [--data-mode test|prod] [--json]');
15321
16135
  console.log(' remits-cli doctor local-state [--json] # actor/workspace/local state hygiene');
15322
16136
  console.log(' remits-cli whoami [--base-url URL] [--account-id ID] [--data-mode test|prod] [--json]');
16137
+ console.log(' remits-cli guides sync [--all] [--base-url URL] [--json] # refresh front-stage guides');
15323
16138
  console.log(' remits-cli listen [stop|status] [--foreground true] # compatibility alias');
15324
16139
  console.log(' remits-cli data-mode [set test|prod]');
15325
16140
  console.log(' remits-cli install --skills [--target codex|claude|gemini|all] [--overwrite true]');
15326
- console.log(' remits-cli tools [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--variant-branch NAME|none]');
15327
- console.log(' remits-cli tool --name <toolName> [--base-url URL] [--branch BRANCH] [--input \"{...}\"|--input-file file.json] [--data-mode test|prod] [--scope self|children|hierarchy] [--target-account-id ID] [--account-ids 1,2,3] [--variant-branch NAME|none] [--timeout-ms 60000] [--async true --wait true]');
16141
+ console.log(' remits-cli tools [--base-url URL] [--account-id ID] [--as-account ID] [--branch BRANCH] [--data-mode test|prod] [--variant-branch NAME|none]');
16142
+ console.log(' remits-cli tool --name <toolName> [--base-url URL] [--account-id ID] [--as-account ID] [--target-account ID] [--branch BRANCH] [--input \"{...}\"|--input-file file.json] [--data-mode test|prod] [--scope self|children|hierarchy] [--account-ids 1,2,3] [--variant-branch NAME|none] [--timeout-ms 60000] [--async true --wait true]');
15328
16143
  console.log(' remits-cli tool status --call-id <callId> [--base-url URL] [--account-id ID] [--data-mode test|prod]');
15329
16144
  console.log(' remits-cli workspace [show|use <name>|use --auto|clear] # isolate staging when several agents share a repo');
15330
16145
  console.log(' remits-cli verify start --summary "..." [--manifest file.json] # start a verification envelope');
@@ -15407,6 +16222,11 @@ async function main() {
15407
16222
  process.exit(0);
15408
16223
  }
15409
16224
 
16225
+ if (wantsHelp && command === 'guides') {
16226
+ printGuidesHelp();
16227
+ process.exit(0);
16228
+ }
16229
+
15410
16230
  if (wantsHelp && command === 'token') {
15411
16231
  printTokenHelp();
15412
16232
  process.exit(0);
@@ -15427,6 +16247,11 @@ async function main() {
15427
16247
  return;
15428
16248
  }
15429
16249
 
16250
+ if (command === 'guides') {
16251
+ await guidesCommand(args, subcommand);
16252
+ return;
16253
+ }
16254
+
15430
16255
  if (command === 'components' && (subcommand === 'push' || subcommand === 'stage')) {
15431
16256
  if (subcommand === 'push' && !args.mode) {
15432
16257
  args.mode = 'push';
@@ -15539,11 +16364,16 @@ async function main() {
15539
16364
 
15540
16365
  if (command === 'activity') {
15541
16366
  if (wantsHelp || !subcommand) {
15542
- console.log('Usage: remits-cli activity inspect [--account-id ID] [--scope self|children] [--user-id ID|--user-email EMAIL] [--limit N] [--summary|--compact] [--json]');
16367
+ console.log('Usage: remits-cli activity inspect [--account-id ID] [--scope self|children|related] [--workspace NAME] [--branch NAME] [--workstream ID] [--user-id ID|--user-email EMAIL] [--limit N] [--summary|--compact] [--json]');
15543
16368
  console.log('');
15544
16369
  console.log('Read-only workstream inspection for agent/operator review. Joins current staging lanes,');
15545
16370
  console.log('verification envelopes, durable test runs, corpus measurements, live agent presence,');
15546
16371
  console.log('and heuristic smell signals without changing any lane or checkout.');
16372
+ console.log('');
16373
+ console.log('--scope related also follows component owners, staging lanes and verification packet worlds');
16374
+ console.log('out of the hierarchy. --workspace / --branch / --workstream NARROW it: the account set becomes');
16375
+ console.log('the accounts with matching evidence (plus this account and its component owners), and the');
16376
+ console.log('lanes, envelopes, test runs and corpus results are filtered to that workstream.');
15547
16377
  return;
15548
16378
  }
15549
16379
  if (subcommand === 'inspect') {
@@ -15553,6 +16383,23 @@ async function main() {
15553
16383
  throw new Error('Unknown activity subcommand: ' + subcommand);
15554
16384
  }
15555
16385
 
16386
+ if (command === 'workstream') {
16387
+ if (wantsHelp) {
16388
+ console.log('Usage: remits-cli workstream status [--workstream ID|--workspace NAME] [--json]');
16389
+ console.log('');
16390
+ console.log('Shows the local logical proof campaign index across actor-scoped state. It references');
16391
+ console.log('tool response files, verification packets, actors and account/world roles without copying payloads.');
16392
+ console.log('');
16393
+ console.log('The id is stamped onto every verification packet world, so the same value works server-side:');
16394
+ console.log(' remits-cli activity inspect --scope related --workstream <id>');
16395
+ console.log('This is NOT a support ticket\'s --workstream, which is an account\'s own routing label matched');
16396
+ console.log('by `agent serve --serves`. Same flag name, unrelated namespaces.');
16397
+ return;
16398
+ }
16399
+ await workstreamCommand(args, subcommand);
16400
+ return;
16401
+ }
16402
+
15556
16403
  if (command === 'ticket') {
15557
16404
  if (wantsHelp || !subcommand) {
15558
16405
  printTicketHelp();