@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 +906 -59
- package/package.json +1 -1
- package/skills/remits-cli/SKILL.md +20 -5
- package/skills/remits-cli/references/account-targeting.md +34 -0
- package/skills/remits-cli/references/branch-variants.md +10 -5
- package/skills/remits-cli/references/command-reference.md +82 -14
- package/skills/remits-cli/references/component-resolution.md +16 -0
- package/skills/remits-cli/references/development-loop.md +20 -0
- package/skills/remits-cli/references/investigation.md +21 -0
- package/skills/remits-cli/references/support-tickets.md +9 -2
- package/skills/remits-cli/references/tool-reference.md +31 -15
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
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
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)
|
|
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
|
|
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:
|
|
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
|
-
|
|
3442
|
-
|
|
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
|
|
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:',
|
|
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
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
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, {
|
|
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
|
|
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:',
|
|
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:
|
|
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:
|
|
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: {
|
|
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, {
|
|
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
|
|
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
|
|
12167
|
-
|
|
12168
|
-
|
|
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) =>
|
|
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
|
-
' ·
|
|
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 \'{"
|
|
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] [--
|
|
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();
|