@remits/remits-cli 0.1.132 → 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 +1179 -70
- 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,6 +2109,107 @@ function printVerificationEnvelope(envelope, fallbackEnvelopeId) {
|
|
|
1934
2109
|
}
|
|
1935
2110
|
}
|
|
1936
2111
|
|
|
2112
|
+
function slimVerificationPacket(packet, options = {}) {
|
|
2113
|
+
const p = packet || {};
|
|
2114
|
+
const world = p.world || {};
|
|
2115
|
+
const hasSuccess = Object.prototype.hasOwnProperty.call(p, 'success');
|
|
2116
|
+
return {
|
|
2117
|
+
packetId: p.packetId || p.id || null,
|
|
2118
|
+
type: p.type || 'unknown',
|
|
2119
|
+
success: hasSuccess ? p.success !== false : options.defaultSuccess !== false,
|
|
2120
|
+
stale: Boolean(p.stale),
|
|
2121
|
+
claim: p.claim || p.summary || null,
|
|
2122
|
+
taskId: p.taskId || (p.testRun && p.testRun.taskId) || null,
|
|
2123
|
+
testName: p.testName || (p.testRun && p.testRun.testName) || null,
|
|
2124
|
+
accountId: world.accountId || p.accountId || null,
|
|
2125
|
+
dataMode: world.dataMode || p.dataMode || null,
|
|
2126
|
+
branchName: world.branchName || p.branchName || null,
|
|
2127
|
+
workspace: world.workspace || p.workspace || null,
|
|
2128
|
+
recordedAt: formatTime(p.recordedAt || p.createdAt || p.at || p.timestamp),
|
|
2129
|
+
message: p.message || p.error || null
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
function compactVerificationEnvelope(envelope, fallbackEnvelopeId) {
|
|
2134
|
+
const env = envelope || {};
|
|
2135
|
+
const evaluation = env.evaluation || {};
|
|
2136
|
+
const packets = Array.isArray(env.packets) ? env.packets : [];
|
|
2137
|
+
const failures = Array.isArray(evaluation.currentFailures)
|
|
2138
|
+
? evaluation.currentFailures
|
|
2139
|
+
: packets.filter((packet) => packet && packet.success === false && !packet.stale);
|
|
2140
|
+
const stale = Array.isArray(evaluation.stale) ? evaluation.stale : packets.filter((packet) => packet && packet.stale);
|
|
2141
|
+
const requiredEvidence = ((env.acceptance || {}).requiredEvidence || evaluation.requiredEvidence || []);
|
|
2142
|
+
const latestByType = evaluation.latestByType || env.latestByType || {};
|
|
2143
|
+
const nextActions = [];
|
|
2144
|
+
|
|
2145
|
+
if (evaluation.noRequiredEvidence) {
|
|
2146
|
+
nextActions.push('Attach a manifest or required evidence contract; packets are context, not proof of completion.');
|
|
2147
|
+
}
|
|
2148
|
+
if (failures.length) {
|
|
2149
|
+
nextActions.push('Inspect and replace the current failing evidence packet(s).');
|
|
2150
|
+
}
|
|
2151
|
+
if (stale.length) {
|
|
2152
|
+
nextActions.push('Re-run stale evidence after the requirement/source change.');
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
return {
|
|
2156
|
+
envelopeId: env.envelopeId || fallbackEnvelopeId || null,
|
|
2157
|
+
summary: env.summary || null,
|
|
2158
|
+
status: env.status || 'unknown',
|
|
2159
|
+
health: evaluation.health || null,
|
|
2160
|
+
acceptanceStatus: evaluation.acceptanceStatus || env.acceptanceStatus || null,
|
|
2161
|
+
requiredEvidence: {
|
|
2162
|
+
satisfiedCount: evaluation.satisfiedCount || 0,
|
|
2163
|
+
requiredCount: evaluation.requiredCount || requiredEvidence.length || 0,
|
|
2164
|
+
missingCount: evaluation.missingCount || 0,
|
|
2165
|
+
staleCount: evaluation.staleCount || stale.length || 0,
|
|
2166
|
+
noRequiredEvidence: Boolean(evaluation.noRequiredEvidence),
|
|
2167
|
+
declared: requiredEvidence.slice(0, 10).map((entry) => ({
|
|
2168
|
+
type: entry && entry.type || entry && entry.evidenceType || null,
|
|
2169
|
+
category: entry && entry.category || null,
|
|
2170
|
+
claim: entry && entry.claim || entry && entry.description || null
|
|
2171
|
+
}))
|
|
2172
|
+
},
|
|
2173
|
+
packets: {
|
|
2174
|
+
count: packets.length,
|
|
2175
|
+
failedCount: evaluation.failedCount || failures.length || 0,
|
|
2176
|
+
currentFailureCount: evaluation.currentFailureCount || failures.length || 0,
|
|
2177
|
+
lastPacketAt: formatTime(env.lastPacketAtMs || env.latestPacketAtMs || env.updatedAtMs),
|
|
2178
|
+
recent: packets.slice(-5).map(slimVerificationPacket)
|
|
2179
|
+
},
|
|
2180
|
+
latestByType,
|
|
2181
|
+
currentFailures: failures.slice(0, 10).map((packet) => slimVerificationPacket(packet, { defaultSuccess: false })),
|
|
2182
|
+
stale: stale.slice(0, 10).map(slimVerificationPacket),
|
|
2183
|
+
warnings: (env.warnings || []).slice(0, 10),
|
|
2184
|
+
nextActions
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
function printCompactVerificationEnvelope(compact) {
|
|
2189
|
+
console.log('Verification envelope:', compact.envelopeId || '(no id)');
|
|
2190
|
+
if (compact.summary) console.log('Summary:', compact.summary);
|
|
2191
|
+
console.log('Status:', compact.status + (compact.health ? ' health=' + compact.health : ''));
|
|
2192
|
+
if (compact.requiredEvidence.noRequiredEvidence) {
|
|
2193
|
+
console.log('Required evidence: none declared');
|
|
2194
|
+
} else {
|
|
2195
|
+
console.log('Required evidence:', compact.requiredEvidence.satisfiedCount + '/' + compact.requiredEvidence.requiredCount +
|
|
2196
|
+
(compact.requiredEvidence.staleCount ? ' stale=' + compact.requiredEvidence.staleCount : ''));
|
|
2197
|
+
}
|
|
2198
|
+
console.log('Packets:', compact.packets.count + ' current failures=' + compact.packets.currentFailureCount);
|
|
2199
|
+
if (compact.currentFailures.length) {
|
|
2200
|
+
console.log('Current failures:');
|
|
2201
|
+
compact.currentFailures.forEach((packet) => {
|
|
2202
|
+
console.log(' - ' + packet.type + ' ' + (packet.packetId || '') + (packet.claim ? ' - ' + packet.claim : '') +
|
|
2203
|
+
(packet.message ? ' (' + packet.message + ')' : ''));
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2206
|
+
if (compact.nextActions.length) {
|
|
2207
|
+
console.log('Next actions:');
|
|
2208
|
+
compact.nextActions.forEach((action) => console.log(' - ' + action));
|
|
2209
|
+
}
|
|
2210
|
+
console.log('Full envelope: remits-cli verify show --json');
|
|
2211
|
+
}
|
|
2212
|
+
|
|
1937
2213
|
function printAcceptanceWarnings(envelope) {
|
|
1938
2214
|
const warnings = envelope && envelope.acceptance && Array.isArray(envelope.acceptance.warnings)
|
|
1939
2215
|
? envelope.acceptance.warnings
|
|
@@ -1996,16 +2272,35 @@ async function readVerificationPacketsForDiagnostics(api, cwd, session, accountI
|
|
|
1996
2272
|
}
|
|
1997
2273
|
|
|
1998
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
|
+
}
|
|
1999
2283
|
const source = (response && response.staging) || {};
|
|
2000
2284
|
const resolution = (response && response.resolution) || {};
|
|
2001
2285
|
const result = (response && response.result) || {};
|
|
2002
2286
|
const laneHash = laneContentHashFrom(response) || fallback.laneContentHash || fallback.stagedOverlayHash;
|
|
2003
|
-
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) ||
|
|
2004
2291
|
resolution.executionAccountId ||
|
|
2005
|
-
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;
|
|
2006
2297
|
return {
|
|
2007
|
-
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,
|
|
2008
2302
|
executionAccountId,
|
|
2303
|
+
targetAccountId: response && response.targetAccountId || fallback.targetAccountId,
|
|
2009
2304
|
addressedAccountId: resolution.addressedAccountId || fallback.addressedAccountId,
|
|
2010
2305
|
host: fallback.host,
|
|
2011
2306
|
dataMode: response && response.dataMode || fallback.dataMode,
|
|
@@ -2022,10 +2317,34 @@ function buildCommandWorld(response, fallback = {}) {
|
|
|
2022
2317
|
fallback.componentBranch,
|
|
2023
2318
|
variantBranchSource: response && (response.variantBranchSource || (response.variantWorld && response.variantWorld.variantBranchSource)) || fallback.variantBranchSource,
|
|
2024
2319
|
variantId: resolution.variantId || fallback.variantId,
|
|
2320
|
+
componentSource: response && response.componentSource || fallback.componentSource,
|
|
2321
|
+
componentSignature: response && response.componentSignature || fallback.componentSignature,
|
|
2025
2322
|
sourceLayer: fallback.sourceLayer || (source.testComponentSource === 'staged' ? 'staged' : undefined)
|
|
2026
2323
|
};
|
|
2027
2324
|
}
|
|
2028
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
|
+
|
|
2029
2348
|
// A variant sync writes overlays on exactly one component branch; say so in the packet world, where a manifest's
|
|
2030
2349
|
// componentBranch is compared. Without it no sync_mutation could satisfy a manifest that declares one.
|
|
2031
2350
|
function syncPacketWorld(response, world, branchName) {
|
|
@@ -2344,6 +2663,120 @@ async function downloadAndSyncGuides(cwd, ctx) {
|
|
|
2344
2663
|
return writeGuidePayloadIntoRepo(cwd, payload);
|
|
2345
2664
|
}
|
|
2346
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
|
+
|
|
2347
2780
|
// Ensure the current account repo has the latest guides. Authenticates first
|
|
2348
2781
|
// (interactive terminals only) so guides are only ever delivered to users who
|
|
2349
2782
|
// can authenticate to the platform.
|
|
@@ -2809,6 +3242,42 @@ function resolveSessionContext(cwd, flags) {
|
|
|
2809
3242
|
};
|
|
2810
3243
|
}
|
|
2811
3244
|
|
|
3245
|
+
function hasExplicitAccountIdFlag(flags) {
|
|
3246
|
+
const value = Number(flags && flags['account-id']);
|
|
3247
|
+
return Number.isFinite(value) && value > 0;
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
function platformRepoEntryMatchesCwd(cwd) {
|
|
3251
|
+
const entry = getPlatformRepoEntry();
|
|
3252
|
+
if (!entry || !entry.directory || !cwd) return false;
|
|
3253
|
+
try {
|
|
3254
|
+
return path.resolve(entry.directory) === path.resolve(cwd);
|
|
3255
|
+
} catch (_) {
|
|
3256
|
+
return false;
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
function platformRepoAccountCommandProblem(cwd, flags, commandName, sessionContext) {
|
|
3261
|
+
if (hasExplicitAccountIdFlag(flags)) return null;
|
|
3262
|
+
try {
|
|
3263
|
+
if (loadAccountInfo(cwd)) return null;
|
|
3264
|
+
} catch (_) {
|
|
3265
|
+
// No account-info.json means the command would fall back to session state.
|
|
3266
|
+
}
|
|
3267
|
+
if (!looksLikePlatformRepo(cwd) && !platformRepoEntryMatchesCwd(cwd)) return null;
|
|
3268
|
+
|
|
3269
|
+
const account = sessionContext && sessionContext.accountId
|
|
3270
|
+
? 'account ' + sessionContext.accountId
|
|
3271
|
+
: 'the last authenticated account';
|
|
3272
|
+
return 'Refusing ' + commandName + ' from the Remits platform repo. This checkout is back-stage platform source, not an account repository, so the command would target ' +
|
|
3273
|
+
account + ' from session state. Run it from that account repo, or pass --account-id <id> explicitly if you really intend to inspect that account from here.';
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3276
|
+
function requireAccountRepoContext(cwd, flags, commandName, sessionContext) {
|
|
3277
|
+
const problem = platformRepoAccountCommandProblem(cwd, flags, commandName, sessionContext);
|
|
3278
|
+
if (problem) throw new Error(problem);
|
|
3279
|
+
}
|
|
3280
|
+
|
|
2812
3281
|
/*
|
|
2813
3282
|
## Component Discovery And HTTP Logging
|
|
2814
3283
|
*/
|
|
@@ -3299,11 +3768,76 @@ function sandboxNetworkHint(err) {
|
|
|
3299
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.';
|
|
3300
3769
|
}
|
|
3301
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
|
+
|
|
3302
3788
|
function saveToolsSnapshot(cwd, data) {
|
|
3303
3789
|
const paths = ensureLocalState(cwd);
|
|
3304
3790
|
const file = path.join(paths.toolsDir, 'tools.json');
|
|
3305
|
-
|
|
3306
|
-
|
|
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(', ');
|
|
3307
3841
|
}
|
|
3308
3842
|
|
|
3309
3843
|
async function loggedPost(api, cwd, endpoint, payload, options = {}) {
|
|
@@ -3843,14 +4377,15 @@ async function authCommand(flags) {
|
|
|
3843
4377
|
dataMode: session.dataMode
|
|
3844
4378
|
}).then((r) => r.data);
|
|
3845
4379
|
if (toolsData && toolsData.success) {
|
|
3846
|
-
const
|
|
4380
|
+
const toolsSnapshot = saveToolsSnapshot(cwd, {
|
|
3847
4381
|
accountId: session.accountId,
|
|
4382
|
+
executionAccountId: session.accountId,
|
|
3848
4383
|
branchName,
|
|
3849
4384
|
dataMode: toolsData.dataMode || session.dataMode,
|
|
3850
4385
|
fetchedAt: new Date().toISOString(),
|
|
3851
4386
|
tools: toolsData.tools || []
|
|
3852
4387
|
});
|
|
3853
|
-
console.log('Tools cached at:',
|
|
4388
|
+
console.log('Tools cached at:', toolsSnapshot.file);
|
|
3854
4389
|
}
|
|
3855
4390
|
} catch (err) {
|
|
3856
4391
|
console.log('Warning: failed to refresh tools after auth:', err.message);
|
|
@@ -3892,6 +4427,7 @@ async function pushComponentsCommand(flags) {
|
|
|
3892
4427
|
const cwd = process.cwd();
|
|
3893
4428
|
ensureLocalState(cwd);
|
|
3894
4429
|
const sessionContext = resolveSessionContext(cwd, flags);
|
|
4430
|
+
requireAccountRepoContext(cwd, flags, 'components stage', sessionContext);
|
|
3895
4431
|
const { session, accountId } = sessionContext;
|
|
3896
4432
|
const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
|
|
3897
4433
|
const branchName = flags.branch || currentBranch(cwd);
|
|
@@ -4015,7 +4551,7 @@ async function pushComponentsCommand(flags) {
|
|
|
4015
4551
|
const api = buildAxios(baseUrl, session.token, stageTimeoutMs(components, flags));
|
|
4016
4552
|
const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
|
|
4017
4553
|
packetType: 'stage',
|
|
4018
|
-
world: { accountId, dataMode, branchName, workspace }
|
|
4554
|
+
world: { repoAccountId: accountId, accountId, executionAccountId: accountId, dataMode, branchName, workspace }
|
|
4019
4555
|
}, { command: 'components stage' });
|
|
4020
4556
|
const stagePayload = {
|
|
4021
4557
|
token: session.token,
|
|
@@ -4823,7 +5359,8 @@ function printBranchContext(response) {
|
|
|
4823
5359
|
console.log(' commit writes: ' + ctx.commitWrites);
|
|
4824
5360
|
if (Object.prototype.hasOwnProperty.call(ctx, 'lastSyncedSha')) {
|
|
4825
5361
|
// null is "unknown" (never synced, expired, or the last sync had errors) — never "nothing landed".
|
|
4826
|
-
|
|
5362
|
+
const source = ctx.lastSyncedShaSource ? ' [' + ctx.lastSyncedShaSource + ']' : '';
|
|
5363
|
+
console.log(' last platform sync of this branch: ' + (ctx.lastSyncedSha ? short(ctx.lastSyncedSha) : 'unknown') + source);
|
|
4827
5364
|
}
|
|
4828
5365
|
if (ctx.repository) {
|
|
4829
5366
|
console.log(' sync reads repository: ' + ctx.repository +
|
|
@@ -5014,6 +5551,7 @@ async function statusComponentsCommand(flags) {
|
|
|
5014
5551
|
const cwd = process.cwd();
|
|
5015
5552
|
ensureLocalState(cwd);
|
|
5016
5553
|
const sessionContext = resolveSessionContext(cwd, flags);
|
|
5554
|
+
requireAccountRepoContext(cwd, flags, 'components status', sessionContext);
|
|
5017
5555
|
const { session, accountId } = sessionContext;
|
|
5018
5556
|
const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
|
|
5019
5557
|
const branchName = flags.branch || currentBranch(cwd);
|
|
@@ -5082,6 +5620,7 @@ async function lanesComponentsCommand(flags) {
|
|
|
5082
5620
|
const cwd = process.cwd();
|
|
5083
5621
|
ensureLocalState(cwd);
|
|
5084
5622
|
const sessionContext = resolveSessionContext(cwd, flags);
|
|
5623
|
+
requireAccountRepoContext(cwd, flags, 'components lanes', sessionContext);
|
|
5085
5624
|
const { session, accountId } = sessionContext;
|
|
5086
5625
|
const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
|
|
5087
5626
|
const branchName = flags.branch || currentBranch(cwd);
|
|
@@ -5173,6 +5712,7 @@ async function entriesComponentsCommand(flags) {
|
|
|
5173
5712
|
const cwd = process.cwd();
|
|
5174
5713
|
ensureLocalState(cwd);
|
|
5175
5714
|
const sessionContext = resolveSessionContext(cwd, flags);
|
|
5715
|
+
requireAccountRepoContext(cwd, flags, 'components entries', sessionContext);
|
|
5176
5716
|
const { session, accountId } = sessionContext;
|
|
5177
5717
|
const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
|
|
5178
5718
|
const branchName = flags.branch || currentBranch(cwd);
|
|
@@ -5216,6 +5756,7 @@ async function clearComponentsCommand(flags) {
|
|
|
5216
5756
|
const cwd = process.cwd();
|
|
5217
5757
|
ensureLocalState(cwd);
|
|
5218
5758
|
const sessionContext = resolveSessionContext(cwd, flags);
|
|
5759
|
+
requireAccountRepoContext(cwd, flags, 'components clear', sessionContext);
|
|
5219
5760
|
const { session, accountId } = sessionContext;
|
|
5220
5761
|
const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
|
|
5221
5762
|
const branchName = flags.branch || currentBranch(cwd);
|
|
@@ -5275,6 +5816,7 @@ async function syncComponentsCommand(rawFlags) {
|
|
|
5275
5816
|
const flags = safe ? expandSafeSyncFlags(rawFlags) : rawFlags;
|
|
5276
5817
|
ensureLocalState(cwd);
|
|
5277
5818
|
const sessionContext = resolveSessionContext(cwd, flags);
|
|
5819
|
+
requireAccountRepoContext(cwd, flags, 'components sync', sessionContext);
|
|
5278
5820
|
const { session, accountId } = sessionContext;
|
|
5279
5821
|
const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
|
|
5280
5822
|
const branchName = flags.branch || currentBranch(cwd);
|
|
@@ -5291,7 +5833,7 @@ async function syncComponentsCommand(rawFlags) {
|
|
|
5291
5833
|
|
|
5292
5834
|
const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
|
|
5293
5835
|
packetType: dryRun || namesOnly ? 'sync_dry_run' : 'sync_mutation',
|
|
5294
|
-
world: { accountId, dataMode, branchName, workspace }
|
|
5836
|
+
world: { repoAccountId: accountId, accountId, executionAccountId: accountId, dataMode, branchName, workspace }
|
|
5295
5837
|
}, { command: 'components sync' });
|
|
5296
5838
|
|
|
5297
5839
|
printProdDataBanner({
|
|
@@ -6382,6 +6924,7 @@ async function branchesComponentsCommand(flags) {
|
|
|
6382
6924
|
const cwd = process.cwd();
|
|
6383
6925
|
ensureLocalState(cwd);
|
|
6384
6926
|
const sessionContext = resolveSessionContext(cwd, flags);
|
|
6927
|
+
requireAccountRepoContext(cwd, flags, 'components branches', sessionContext);
|
|
6385
6928
|
const { session, accountId } = sessionContext;
|
|
6386
6929
|
const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
|
|
6387
6930
|
const api = buildAxios(baseUrl, session.token);
|
|
@@ -6579,6 +7122,7 @@ async function promotionComponentsCommand(flags) {
|
|
|
6579
7122
|
const cwd = process.cwd();
|
|
6580
7123
|
ensureLocalState(cwd);
|
|
6581
7124
|
const sessionContext = resolvePromotionSessionContext(cwd, flags);
|
|
7125
|
+
requireAccountRepoContext(cwd, flags, 'components promotion', sessionContext);
|
|
6582
7126
|
const { session, accountId } = sessionContext;
|
|
6583
7127
|
const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
|
|
6584
7128
|
const api = buildAxios(baseUrl, session.token);
|
|
@@ -6744,7 +7288,9 @@ function shortSignature(signature) {
|
|
|
6744
7288
|
async function commitComponentsCommand(flags) {
|
|
6745
7289
|
const cwd = process.cwd();
|
|
6746
7290
|
ensureLocalState(cwd);
|
|
6747
|
-
const
|
|
7291
|
+
const sessionContext = resolveSessionContext(cwd, flags);
|
|
7292
|
+
requireAccountRepoContext(cwd, flags, 'components commit', sessionContext);
|
|
7293
|
+
const { session, accountId } = sessionContext;
|
|
6748
7294
|
const branchName = flags.branch || currentBranch(cwd);
|
|
6749
7295
|
const commitMessage = String(flags.message || ('remits-cli commit sync ' + new Date().toISOString()));
|
|
6750
7296
|
const allowEmpty = flags['allow-empty'] === true || flags['allow-empty'] === 'true';
|
|
@@ -6764,7 +7310,7 @@ async function commitComponentsCommand(flags) {
|
|
|
6764
7310
|
buildAxios(flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL, session.token),
|
|
6765
7311
|
cwd, session, accountId, Object.assign({}, flags, { branch: branchName }), {
|
|
6766
7312
|
packetType: 'sync_mutation',
|
|
6767
|
-
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) }
|
|
6768
7314
|
}, { command: 'components commit' });
|
|
6769
7315
|
|
|
6770
7316
|
// Landing is serial and nothing can make it concurrent: `git add -A` sweeps a shared checkout, and the
|
|
@@ -7467,17 +8013,31 @@ function runWorldLines(world, context = {}) {
|
|
|
7467
8013
|
if (!world || typeof world !== 'object') return [];
|
|
7468
8014
|
const lines = ['World:'];
|
|
7469
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);
|
|
7470
8018
|
const accounts = ['account ' + (world.executionAccountId || context.accountId || 'unknown')];
|
|
7471
8019
|
if (world.checkoutAccountId && world.checkoutAccountId !== world.executionAccountId) accounts.push('checkout ' + world.checkoutAccountId);
|
|
7472
8020
|
if (world.componentOwnerAccountId && world.componentOwnerAccountId !== world.executionAccountId) accounts.push('component owner ' + world.componentOwnerAccountId);
|
|
7473
8021
|
lines.push(' runs as: ' + accounts.join(', '));
|
|
8022
|
+
if (world.targetAccountId && world.targetAccountId !== world.executionAccountId) {
|
|
8023
|
+
lines.push(' targets: account ' + world.targetAccountId);
|
|
8024
|
+
}
|
|
7474
8025
|
lines.push(' data lane: ' + (world.dataMode || context.dataMode || 'unknown'));
|
|
7475
8026
|
const contextVariantWorld = context.variantWorld && typeof context.variantWorld === 'object' ? context.variantWorld : {};
|
|
7476
8027
|
const subscribedComponentBranch = world.subscribedComponentBranch || contextVariantWorld.subscribedComponentBranch;
|
|
7477
8028
|
const componentBranch = world.componentBranch || subscribedComponentBranch || contextVariantWorld.componentBranch || world.variantBranch;
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
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;
|
|
7481
8041
|
const branchLabel = componentBranch
|
|
7482
8042
|
? "componentBranch=" + componentBranch + (branchSource ? ' via ' + branchSource : '')
|
|
7483
8043
|
: (branchSource || '');
|
|
@@ -7502,6 +8062,9 @@ function runWorldLines(world, context = {}) {
|
|
|
7502
8062
|
lines.push(' test component: ' + world.testComponentSource + (world.testComponentSignature ? ' ' + shortSignature(world.testComponentSignature) : '') +
|
|
7503
8063
|
(world.testComponentBranch ? " variant '" + world.testComponentBranch + "'" : ''));
|
|
7504
8064
|
}
|
|
8065
|
+
if (world.componentSource) {
|
|
8066
|
+
lines.push(' component: ' + world.componentSource + (world.componentSignature ? ' ' + shortSignature(world.componentSignature) : ''));
|
|
8067
|
+
}
|
|
7505
8068
|
lines.push(...stagedMaskedByVariantLines(world.stagedMaskedByVariant, world.executionAccountId || context.accountId));
|
|
7506
8069
|
return lines;
|
|
7507
8070
|
}
|
|
@@ -7731,7 +8294,8 @@ async function testCommand(flags) {
|
|
|
7731
8294
|
const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
|
|
7732
8295
|
packetType: 'test_run',
|
|
7733
8296
|
world: {
|
|
7734
|
-
accountId,
|
|
8297
|
+
repoAccountId: accountId,
|
|
8298
|
+
accountId: asAccountId ? Number(asAccountId) : Number(accountId),
|
|
7735
8299
|
executionAccountId: asAccountId ? Number(asAccountId) : Number(accountId),
|
|
7736
8300
|
dataMode,
|
|
7737
8301
|
branchName,
|
|
@@ -8061,7 +8625,8 @@ async function tokenCommand(flags) {
|
|
|
8061
8625
|
const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
|
|
8062
8626
|
packetType: 'token_inspect',
|
|
8063
8627
|
world: {
|
|
8064
|
-
accountId,
|
|
8628
|
+
repoAccountId: accountId,
|
|
8629
|
+
accountId: tokenAsAccountId ? Number(tokenAsAccountId) : Number(accountId),
|
|
8065
8630
|
executionAccountId: tokenAsAccountId ? Number(tokenAsAccountId) : Number(accountId),
|
|
8066
8631
|
dataMode,
|
|
8067
8632
|
branchName,
|
|
@@ -8103,6 +8668,12 @@ async function tokenCommand(flags) {
|
|
|
8103
8668
|
baseUrl: normalizeBaseUrl(baseUrl),
|
|
8104
8669
|
tokenKey: data.tokenKey,
|
|
8105
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,
|
|
8106
8677
|
branchName: data.branchName,
|
|
8107
8678
|
variantBranch: data.variantBranch,
|
|
8108
8679
|
// Why the variant branch is (or is not) set — the platform's one rule, so a null is explainable.
|
|
@@ -8146,6 +8717,29 @@ async function tokenCommand(flags) {
|
|
|
8146
8717
|
printSessionResolutionWarning(sessionContext);
|
|
8147
8718
|
printLocalCommandContext(cwd, flags, { accountId: output.accountId || accountId, branchName, workspace: output.workspace, dataMode: output.dataMode || dataMode });
|
|
8148
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
|
+
}
|
|
8149
8743
|
}
|
|
8150
8744
|
console.log(JSON.stringify(output, null, 2));
|
|
8151
8745
|
|
|
@@ -8153,7 +8747,16 @@ async function tokenCommand(flags) {
|
|
|
8153
8747
|
type: 'token_inspect',
|
|
8154
8748
|
success: true,
|
|
8155
8749
|
claim: 'Verification token minted',
|
|
8156
|
-
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
|
+
}),
|
|
8157
8760
|
revision: collectVerificationSource(cwd, flags),
|
|
8158
8761
|
browser: {
|
|
8159
8762
|
tokenKeyHash: output.tokenKey ? sha256(String(output.tokenKey)) : null,
|
|
@@ -8222,6 +8825,7 @@ async function toolsCommand(flags) {
|
|
|
8222
8825
|
const data = await loggedPost(api, cwd, '/cli/tools', {
|
|
8223
8826
|
token: session.token,
|
|
8224
8827
|
accountId,
|
|
8828
|
+
asAccountId: flags['as-account'] || flags['as-account-id'],
|
|
8225
8829
|
variantBranch,
|
|
8226
8830
|
branchName,
|
|
8227
8831
|
workspace,
|
|
@@ -8232,12 +8836,17 @@ async function toolsCommand(flags) {
|
|
|
8232
8836
|
throw new Error('Failed to fetch tools');
|
|
8233
8837
|
}
|
|
8234
8838
|
|
|
8235
|
-
const
|
|
8236
|
-
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,
|
|
8237
8844
|
baseUrl: normalizeBaseUrl(baseUrl),
|
|
8238
|
-
branchName,
|
|
8845
|
+
branchName: data.branchName || branchName,
|
|
8239
8846
|
variantBranch: data.variantBranch || variantBranch,
|
|
8240
8847
|
dataMode: data.dataMode || dataMode,
|
|
8848
|
+
world: data.world,
|
|
8849
|
+
staging: data.staging,
|
|
8241
8850
|
fetchedAt: new Date().toISOString(),
|
|
8242
8851
|
tools: data.tools || []
|
|
8243
8852
|
});
|
|
@@ -8245,10 +8854,17 @@ async function toolsCommand(flags) {
|
|
|
8245
8854
|
printSessionResolutionWarning(sessionContext);
|
|
8246
8855
|
printResolvedBaseUrl(baseUrl);
|
|
8247
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));
|
|
8248
8858
|
console.log('Data mode:', data.dataMode || dataMode);
|
|
8249
8859
|
if (data.variantBranch || variantBranch) console.log('Variant branch:', data.variantBranch || variantBranch);
|
|
8250
|
-
console.log('Tools file:',
|
|
8860
|
+
console.log('Tools file:', toolsSnapshot.file);
|
|
8251
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
|
+
}
|
|
8252
8868
|
}
|
|
8253
8869
|
|
|
8254
8870
|
function parseToolInput(flags) {
|
|
@@ -8282,10 +8898,18 @@ function applyScopeFlags(input, flags) {
|
|
|
8282
8898
|
merged.scope = normalized;
|
|
8283
8899
|
}
|
|
8284
8900
|
|
|
8285
|
-
const targetAccountId = flags && (flags['target-account-id'] || flags.targetAccountId);
|
|
8901
|
+
const targetAccountId = flags && (flags['target-account'] || flags.targetAccount || flags['target-account-id'] || flags.targetAccountId);
|
|
8286
8902
|
if (targetAccountId && merged.targetAccountId === undefined) {
|
|
8287
8903
|
merged.targetAccountId = String(targetAccountId).trim();
|
|
8288
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
|
+
}
|
|
8289
8913
|
|
|
8290
8914
|
const anchorAccountId = flags && (flags['anchor-account-id'] || flags.anchorAccountId);
|
|
8291
8915
|
if (anchorAccountId && merged.anchorAccountId === undefined) {
|
|
@@ -8330,6 +8954,8 @@ async function toolCommand(flags) {
|
|
|
8330
8954
|
const branchName = flags.branch || currentBranch(cwd);
|
|
8331
8955
|
const dataMode = resolveDataMode(flags, session);
|
|
8332
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;
|
|
8333
8959
|
const timeoutMs = parsePositiveInt(flags['timeout-ms'] || flags.timeoutMs, 60000);
|
|
8334
8960
|
const callId = String(flags['call-id'] || flags.callId || crypto.randomUUID());
|
|
8335
8961
|
const asyncMode = flagEnabled(flags.async) || flagEnabled(input.async);
|
|
@@ -8365,6 +8991,24 @@ async function toolCommand(flags) {
|
|
|
8365
8991
|
data.responseFile = statusResponse.responseFile;
|
|
8366
8992
|
data.sessionLog = sessionJsonlFile(cwd);
|
|
8367
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
|
+
});
|
|
8368
9012
|
// A completed run can still carry a tool-level refusal — see toolResultFailed.
|
|
8369
9013
|
const polledFailed = data.toolSuccess === false || toolResultFailed(data.result);
|
|
8370
9014
|
if (polledFailed) {
|
|
@@ -8374,13 +9018,13 @@ async function toolCommand(flags) {
|
|
|
8374
9018
|
// Same question as the call that started it: a poll must not attach where the call itself would not.
|
|
8375
9019
|
const statusVerification = await verificationPreflight(api, cwd, session, accountId, flags, {
|
|
8376
9020
|
packetType: 'tool_call',
|
|
8377
|
-
world:
|
|
9021
|
+
world: statusWorld
|
|
8378
9022
|
}, { command: 'tool status', neverRefuse: true, quiet: jsonOutput });
|
|
8379
9023
|
await appendVerificationPacket(api, cwd, session, accountId, statusVerification.evidenceFlags, {
|
|
8380
9024
|
type: 'tool_call',
|
|
8381
9025
|
success: data.status !== 'failed' && !polledFailed,
|
|
8382
9026
|
claim: 'Tool status ' + requestedCallId,
|
|
8383
|
-
world:
|
|
9027
|
+
world: statusWorld,
|
|
8384
9028
|
revision: collectVerificationSource(cwd, flags),
|
|
8385
9029
|
evidenceCategories: ['tool_call'],
|
|
8386
9030
|
rawRefs: { toolResponsePath: statusResponse.responseFile, callId: requestedCallId },
|
|
@@ -8395,6 +9039,7 @@ async function toolCommand(flags) {
|
|
|
8395
9039
|
printLocalCommandContext(cwd, flags, { accountId: statusAccountId, branchName: flags.branch || (stored && stored.branchName) || branchName, dataMode: data.dataMode || dataMode });
|
|
8396
9040
|
printLocalStateWarnings(cwd, flags);
|
|
8397
9041
|
console.log('Tool call status:', data.status);
|
|
9042
|
+
if (data.world) runWorldLines(data.world, { host: normalizeBaseUrl(baseUrl) }).forEach((line) => console.log(line));
|
|
8398
9043
|
console.log('Call ID:', requestedCallId);
|
|
8399
9044
|
console.log('Data mode:', data.dataMode || dataMode);
|
|
8400
9045
|
if (data.threadGroupingId) console.log('Thread grouping ID:', data.threadGroupingId);
|
|
@@ -8417,15 +9062,49 @@ async function toolCommand(flags) {
|
|
|
8417
9062
|
});
|
|
8418
9063
|
|
|
8419
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));
|
|
8420
9089
|
const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
|
|
8421
9090
|
packetType: 'tool_call',
|
|
8422
|
-
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
|
+
}
|
|
8423
9100
|
}, { command: 'tool --name ' + String(toolName) });
|
|
8424
9101
|
|
|
8425
9102
|
const response = await loggedPost(api, cwd, '/cli/tool', {
|
|
8426
9103
|
token: session.token,
|
|
8427
9104
|
accountId,
|
|
8428
9105
|
accountIdExplicit,
|
|
9106
|
+
asAccountId,
|
|
9107
|
+
targetAccountId: targetAccountId || undefined,
|
|
8429
9108
|
// The repo/session account, sent as a FIRST-CLASS value rather than left to be inferred from
|
|
8430
9109
|
// `accountId`: a read tool may reinterpret its own `input.accountId`, and the scope root — what bounds
|
|
8431
9110
|
// what the call may discover — has to survive that. The server validates it; it is never authorization.
|
|
@@ -8455,6 +9134,15 @@ async function toolCommand(flags) {
|
|
|
8455
9134
|
data.responseFile = response.responseFile;
|
|
8456
9135
|
data.sessionLog = sessionJsonlFile(cwd);
|
|
8457
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
|
+
});
|
|
8458
9146
|
if (toolFailureMessage && !data.toolMessage) data.toolMessage = toolFailureMessage;
|
|
8459
9147
|
|
|
8460
9148
|
let evidenceData = data;
|
|
@@ -8467,6 +9155,7 @@ async function toolCommand(flags) {
|
|
|
8467
9155
|
printLocalStateWarnings(cwd, flags);
|
|
8468
9156
|
console.log(asyncMode ? 'Tool call started.'
|
|
8469
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));
|
|
8470
9159
|
if (toolFailed && toolFailureMessage) console.log('Tool error:', toolFailureMessage);
|
|
8471
9160
|
console.log('Call ID:', callId);
|
|
8472
9161
|
console.log('Data mode:', data.dataMode || dataMode);
|
|
@@ -8499,10 +9188,26 @@ async function toolCommand(flags) {
|
|
|
8499
9188
|
|
|
8500
9189
|
finalStatus.data.responseFile = finalStatus.responseFile;
|
|
8501
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
|
+
});
|
|
8502
9199
|
evidenceData = finalStatus.data;
|
|
8503
9200
|
evidenceResponseFile = finalStatus.responseFile;
|
|
8504
9201
|
if (!jsonOutput) {
|
|
8505
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
|
+
}
|
|
8506
9211
|
printToolResponseFileLine('Tool response file', finalStatus.responseFile);
|
|
8507
9212
|
}
|
|
8508
9213
|
if (finalStatus.data.status !== 'completed') {
|
|
@@ -8516,7 +9221,20 @@ async function toolCommand(flags) {
|
|
|
8516
9221
|
success: asyncMode && !waitForAsync ? null : (!toolFailed && (!asyncMode || process.exitCode !== 1)),
|
|
8517
9222
|
pending: asyncMode && !waitForAsync,
|
|
8518
9223
|
claim: 'Tool call ' + String(toolName),
|
|
8519
|
-
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
|
+
}),
|
|
8520
9238
|
revision: Object.assign(collectVerificationSource(cwd, flags), {
|
|
8521
9239
|
compileSignatures: evidenceData.componentSignature ? [evidenceData.componentSignature] : []
|
|
8522
9240
|
}),
|
|
@@ -8916,7 +9634,11 @@ async function verifyCommand(flags, subcommand) {
|
|
|
8916
9634
|
return response;
|
|
8917
9635
|
}
|
|
8918
9636
|
|
|
8919
|
-
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);
|
|
8920
9642
|
if (!envelopeId) {
|
|
8921
9643
|
throw new Error('No verification envelope selected. Run `remits-cli verify start --summary "..."` or pass --envelope <id>.');
|
|
8922
9644
|
}
|
|
@@ -8945,11 +9667,18 @@ async function verifyCommand(flags, subcommand) {
|
|
|
8945
9667
|
if (sub === 'show' || sub === 'read' || sub === 'get' || sub === 'packets') {
|
|
8946
9668
|
const response = await postVerificationCommand(api, cwd, session, accountId, 'show', { envelopeId });
|
|
8947
9669
|
if (response.envelope) writeLocalVerificationEnvelope(cwd, response.envelope);
|
|
9670
|
+
const envelope = response.envelope || {};
|
|
9671
|
+
const compact = flagEnabled(flags.summary) || flagEnabled(flags.compact);
|
|
8948
9672
|
if (flagEnabled(flags.json)) {
|
|
8949
|
-
|
|
8950
|
-
|
|
9673
|
+
const payload = compact ? compactVerificationEnvelope(envelope, envelopeId) : (response.envelope || response);
|
|
9674
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
9675
|
+
return payload;
|
|
9676
|
+
}
|
|
9677
|
+
if (compact) {
|
|
9678
|
+
const payload = compactVerificationEnvelope(envelope, envelopeId);
|
|
9679
|
+
printCompactVerificationEnvelope(payload);
|
|
9680
|
+
return payload;
|
|
8951
9681
|
}
|
|
8952
|
-
const envelope = response.envelope || {};
|
|
8953
9682
|
printVerificationEnvelope(envelope, envelopeId);
|
|
8954
9683
|
return envelope;
|
|
8955
9684
|
}
|
|
@@ -9068,7 +9797,7 @@ async function verifyCommand(flags, subcommand) {
|
|
|
9068
9797
|
// report would list as wrong-world evidence on every status check.
|
|
9069
9798
|
const statusVerification = await verificationPreflight(api, cwd, session, accountId, { 'verify-envelope': envelopeId }, {
|
|
9070
9799
|
packetType: 'component_status',
|
|
9071
|
-
world: { accountId, dataMode, branchName, workspace }
|
|
9800
|
+
world: { repoAccountId: accountId, accountId, executionAccountId: accountId, dataMode, branchName, workspace }
|
|
9072
9801
|
}, { command: 'verify status', quiet: true, neverRefuse: true });
|
|
9073
9802
|
const componentStatus = await loggedPost(api, cwd, '/cli/components', {
|
|
9074
9803
|
token: session.token,
|
|
@@ -10437,6 +11166,7 @@ function renderDashboardHtml(snapshot) {
|
|
|
10437
11166
|
'<div class="action-row">' +
|
|
10438
11167
|
'<button class="action-btn" data-action="export-issues">Export Issue Bundle</button>' +
|
|
10439
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>' +
|
|
10440
11170
|
'<button class="action-btn" data-action="reconnect-websockets">Reconnect Websockets</button>' +
|
|
10441
11171
|
'<button class="action-btn" data-action="refresh-agents">Refresh Agents</button>' +
|
|
10442
11172
|
'</div>' +
|
|
@@ -10509,7 +11239,7 @@ function renderDashboardHtml(snapshot) {
|
|
|
10509
11239
|
'summaryItem("Available Agents",String(agentList.length)),' +
|
|
10510
11240
|
'summaryItem("Routed Tickets",String(ticketCounts.routed||0))' +
|
|
10511
11241
|
'].join("");}' +
|
|
10512
|
-
'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>\':"");}' +
|
|
10513
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("");}' +
|
|
10514
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;}' +
|
|
10515
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>\';}' +
|
|
@@ -10900,6 +11630,10 @@ function startDashboardServer(preferredPort) {
|
|
|
10900
11630
|
} else if (action === 'rescan-repos') {
|
|
10901
11631
|
recordDiscoverySummary(discoverAccountRepos());
|
|
10902
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());
|
|
10903
11637
|
} else if (action === 'refresh-agents') {
|
|
10904
11638
|
agentSnapshotCache = { fetchedAtMs: 0, value: null };
|
|
10905
11639
|
supportTicketSnapshotCache = { fetchedAtMs: 0, value: null };
|
|
@@ -11997,6 +12731,11 @@ function resolveAgentId(flags, options = {}) {
|
|
|
11997
12731
|
* agent is silently useless for support: it registers, appears online, and can never be routed a
|
|
11998
12732
|
* production ticket. Ask for `--data-mode test` when you want a fixture agent.
|
|
11999
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
|
+
*/
|
|
12000
12739
|
function agentDefaults(flags, entry) {
|
|
12001
12740
|
return {
|
|
12002
12741
|
baseUrl: normalizeBaseUrl(
|
|
@@ -12009,10 +12748,29 @@ function agentDefaults(flags, entry) {
|
|
|
12009
12748
|
function agentSessionFor(flags, entry) {
|
|
12010
12749
|
const cwd = (entry && entry.cwd) || process.cwd();
|
|
12011
12750
|
const defaults = agentDefaults(flags, entry);
|
|
12012
|
-
const
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
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
|
+
}
|
|
12016
12774
|
if (!context.session || !context.session.token) {
|
|
12017
12775
|
throw new Error('No authenticated session for ' + defaults.baseUrl +
|
|
12018
12776
|
'. Run: remits-cli auth --base-url ' + defaults.baseUrl);
|
|
@@ -12593,7 +13351,7 @@ async function postAgentStatus(context, agentId, update) {
|
|
|
12593
13351
|
* the same thing. Only the local supervisor can add machine-local repository hints; the worker owns the
|
|
12594
13352
|
* final grounding step, including creating or choosing a git worktree before it edits.
|
|
12595
13353
|
*/
|
|
12596
|
-
function spawnTicketWorker(context, ctx, ticket) {
|
|
13354
|
+
async function spawnTicketWorker(context, ctx, ticket) {
|
|
12597
13355
|
const { spawn } = require('child_process');
|
|
12598
13356
|
const agentId = ctx.agentId;
|
|
12599
13357
|
const ticketId = String(ticket.id || ticket.ticketId);
|
|
@@ -12612,6 +13370,32 @@ function spawnTicketWorker(context, ctx, ticket) {
|
|
|
12612
13370
|
const targetBranch = repositoryWork ? ticketTargetBranch(ticket, baseWorkingDirectory) : gitBranchNameInDirectory(baseWorkingDirectory);
|
|
12613
13371
|
const suggestedWorktree = repositoryWork && baseWorkingDirectory ? ticketWorktreePath(baseWorkingDirectory, ticketId) : null;
|
|
12614
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
|
+
}
|
|
12615
13399
|
// The brief is rendered server-side without the local path (the platform cannot know it), so the
|
|
12616
13400
|
// resolved local repository hint is appended here. When nothing resolved, the server-rendered brief already
|
|
12617
13401
|
// tells the worker to find it rather than guess — do not paper over that with a default.
|
|
@@ -12642,6 +13426,7 @@ function spawnTicketWorker(context, ctx, ticket) {
|
|
|
12642
13426
|
'git -C ' + shellQuote(workingDirectory) + ' worktree add --force ' + shellQuote(suggestedWorktree) +
|
|
12643
13427
|
(targetBranch ? ' ' + targetBranch : ' <branch>') + '\n' +
|
|
12644
13428
|
'cd ' + shellQuote(suggestedWorktree) + '\n' +
|
|
13429
|
+
'remits-cli guides sync\n' +
|
|
12645
13430
|
(targetBranch
|
|
12646
13431
|
? 'git pull --ff-only origin ' + shellQuote(targetBranch) + '\n' +
|
|
12647
13432
|
'git rev-list --left-right --count HEAD...origin/' + shellQuote(targetBranch) + ' # must print: 0 0\n'
|
|
@@ -13015,7 +13800,7 @@ async function agentHeartbeatDaemonCommand(flags) {
|
|
|
13015
13800
|
if (!ticketId || inFlight.has(ticketId)) {
|
|
13016
13801
|
continue;
|
|
13017
13802
|
}
|
|
13018
|
-
const started = spawnTicketWorker(context, ctx, ticket);
|
|
13803
|
+
const started = await spawnTicketWorker(context, ctx, ticket);
|
|
13019
13804
|
inFlight.set(ticketId, { pid: started.pid, accountId: ticket.accountId });
|
|
13020
13805
|
started.done.then(() => {
|
|
13021
13806
|
inFlight.delete(ticketId);
|
|
@@ -13864,14 +14649,24 @@ async function activityInspectCommand(flags) {
|
|
|
13864
14649
|
dataMode: resolveDataMode(flags, session),
|
|
13865
14650
|
userId: flags['user-id'] || flags.userId || flags['cli-user-id'],
|
|
13866
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']),
|
|
13867
14659
|
limit: flags.limit || flags.max || 20,
|
|
13868
14660
|
envelopeLimit: flags['envelope-limit'] || flags.envelopeLimit
|
|
13869
14661
|
}).then((r) => r.data);
|
|
13870
14662
|
|
|
13871
14663
|
if (!response.success) throw new Error(response.message || 'Activity inspection failed');
|
|
13872
14664
|
if (flagEnabled(flags.json)) {
|
|
13873
|
-
|
|
13874
|
-
|
|
14665
|
+
const payload = (flagEnabled(flags.summary) || flagEnabled(flags.compact))
|
|
14666
|
+
? compactActivityInspect(response)
|
|
14667
|
+
: response;
|
|
14668
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
14669
|
+
return payload;
|
|
13875
14670
|
}
|
|
13876
14671
|
|
|
13877
14672
|
printSessionResolutionWarning(sessionContext);
|
|
@@ -13880,14 +14675,134 @@ async function activityInspectCommand(flags) {
|
|
|
13880
14675
|
return response;
|
|
13881
14676
|
}
|
|
13882
14677
|
|
|
14678
|
+
function smellSeverityRank(smell) {
|
|
14679
|
+
const severity = String((smell && smell.severity) || 'info').toLowerCase();
|
|
14680
|
+
if (severity === 'critical') return 0;
|
|
14681
|
+
if (severity === 'high') return 1;
|
|
14682
|
+
if (severity === 'medium') return 2;
|
|
14683
|
+
if (severity === 'low') return 3;
|
|
14684
|
+
return 4;
|
|
14685
|
+
}
|
|
14686
|
+
|
|
14687
|
+
function sortedActivitySmells(smells) {
|
|
14688
|
+
return (Array.isArray(smells) ? smells.slice() : []).sort((left, right) => {
|
|
14689
|
+
const severity = smellSeverityRank(left) - smellSeverityRank(right);
|
|
14690
|
+
if (severity) return severity;
|
|
14691
|
+
return String(right.updatedAt || right.at || '').localeCompare(String(left.updatedAt || left.at || ''));
|
|
14692
|
+
});
|
|
14693
|
+
}
|
|
14694
|
+
|
|
14695
|
+
function slimActivitySmell(smell) {
|
|
14696
|
+
const s = smell || {};
|
|
14697
|
+
return {
|
|
14698
|
+
severity: s.severity || 'info',
|
|
14699
|
+
type: s.type || 'signal',
|
|
14700
|
+
title: s.title || s.envelopeSummary || s.testName || null,
|
|
14701
|
+
summary: s.summary || null,
|
|
14702
|
+
status: s.status || s.health || null,
|
|
14703
|
+
currentFailureCount: s.currentFailureCount == null ? null : s.currentFailureCount,
|
|
14704
|
+
accountId: s.accountId || null,
|
|
14705
|
+
laneId: s.laneId || null,
|
|
14706
|
+
envelopeId: s.envelopeId || null,
|
|
14707
|
+
taskId: s.taskId || null,
|
|
14708
|
+
testName: s.testName || null,
|
|
14709
|
+
branchName: s.branchName || null,
|
|
14710
|
+
workspace: s.workspace || null,
|
|
14711
|
+
updatedAt: formatTime(s.updatedAtMs || s.updatedAt || s.at || s.createdAt)
|
|
14712
|
+
};
|
|
14713
|
+
}
|
|
14714
|
+
|
|
14715
|
+
function compactActivityInspect(response) {
|
|
14716
|
+
const counts = response.counts || {};
|
|
14717
|
+
const smells = sortedActivitySmells(response.smells);
|
|
14718
|
+
const lanes = Array.isArray(response.lanes) ? response.lanes : [];
|
|
14719
|
+
const envelopes = Array.isArray(response.envelopes) ? response.envelopes : [];
|
|
14720
|
+
const testRuns = Array.isArray(response.testRuns) ? response.testRuns : [];
|
|
14721
|
+
const nextActions = smells
|
|
14722
|
+
.filter((smell) => smellSeverityRank(smell) <= 2)
|
|
14723
|
+
.slice(0, 5)
|
|
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
|
+
});
|
|
14728
|
+
|
|
14729
|
+
return {
|
|
14730
|
+
success: response.success,
|
|
14731
|
+
accountId: response.accountId,
|
|
14732
|
+
accountName: response.accountName || null,
|
|
14733
|
+
scope: response.scope || 'self',
|
|
14734
|
+
dataMode: response.dataMode,
|
|
14735
|
+
accounts: response.accountIds || [],
|
|
14736
|
+
counts: {
|
|
14737
|
+
agents: counts.agents || 0,
|
|
14738
|
+
lanes: counts.lanes || lanes.length || 0,
|
|
14739
|
+
verificationEnvelopes: counts.verificationEnvelopes || envelopes.length || 0,
|
|
14740
|
+
testRuns: counts.testRuns || testRuns.length || 0,
|
|
14741
|
+
corpusResults: counts.corpusResults || 0,
|
|
14742
|
+
smells: smells.length
|
|
14743
|
+
},
|
|
14744
|
+
topSmells: smells.slice(0, 10).map(slimActivitySmell),
|
|
14745
|
+
currentLanes: lanes.slice(0, 10).map((lane) => ({
|
|
14746
|
+
laneId: lane.laneId || null,
|
|
14747
|
+
accountId: lane.accountId || null,
|
|
14748
|
+
branchName: lane.branchName || null,
|
|
14749
|
+
workspace: lane.workspace || null,
|
|
14750
|
+
stagedCount: lane.stagedCountAsOf || lane.stagedCount || 0,
|
|
14751
|
+
worksetCount: lane.worksetCountAsOf == null ? null : lane.worksetCountAsOf,
|
|
14752
|
+
userEmail: lane.userEmail || null,
|
|
14753
|
+
expiresInSeconds: lane.expiresInSeconds == null ? null : lane.expiresInSeconds
|
|
14754
|
+
})),
|
|
14755
|
+
recentEnvelopes: envelopes.slice(0, 8).map((env) => ({
|
|
14756
|
+
envelopeId: env.envelopeId || null,
|
|
14757
|
+
status: env.status || 'unknown',
|
|
14758
|
+
summary: env.summary || null,
|
|
14759
|
+
packetCount: env.packetCount || 0,
|
|
14760
|
+
currentFailureCount: env.currentFailureCount || 0,
|
|
14761
|
+
health: env.health || null,
|
|
14762
|
+
updatedAt: formatTime(env.updatedAtMs || env.lastPacketAtMs || env.createdAt)
|
|
14763
|
+
})),
|
|
14764
|
+
recentTestRuns: testRuns.slice(0, 8).map((run) => ({
|
|
14765
|
+
taskId: run.taskId || null,
|
|
14766
|
+
testName: run.testName || null,
|
|
14767
|
+
passed: run.passed || 0,
|
|
14768
|
+
total: run.total || 0,
|
|
14769
|
+
branchName: run.branchName || null,
|
|
14770
|
+
workspace: run.workspace || null,
|
|
14771
|
+
ai: corpusAiLabel(run),
|
|
14772
|
+
completedAt: formatTime(run.completedAt || run.startedAt)
|
|
14773
|
+
})),
|
|
14774
|
+
nextActions
|
|
14775
|
+
};
|
|
14776
|
+
}
|
|
14777
|
+
|
|
13883
14778
|
function printActivityInspectSummary(response) {
|
|
13884
14779
|
const subject = response.subjectUser
|
|
13885
14780
|
? ' for ' + (response.subjectUser.email || response.subjectUser.name || ('user ' + response.subjectUser.id))
|
|
13886
14781
|
: '';
|
|
13887
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));
|
|
13888
14789
|
console.log('Scope: ' + (response.accountName || ('account ' + response.accountId)) +
|
|
13889
14790
|
' (' + response.accountId + '), ' + (response.scope || 'self') +
|
|
13890
|
-
' ·
|
|
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
|
+
}
|
|
13891
14806
|
const counts = response.counts || {};
|
|
13892
14807
|
console.log('Counts: ' +
|
|
13893
14808
|
(counts.agents || 0) + ' agent(s), ' +
|
|
@@ -13896,12 +14811,12 @@ function printActivityInspectSummary(response) {
|
|
|
13896
14811
|
(counts.testRuns || 0) + ' test run(s), ' +
|
|
13897
14812
|
(counts.corpusResults || 0) + ' corpus result(s)');
|
|
13898
14813
|
|
|
13899
|
-
const smells = response.smells
|
|
14814
|
+
const smells = sortedActivitySmells(response.smells);
|
|
13900
14815
|
console.log('');
|
|
13901
14816
|
if (!smells.length) {
|
|
13902
14817
|
console.log('Smells: none detected in this slice.');
|
|
13903
14818
|
} else {
|
|
13904
|
-
console.log('
|
|
14819
|
+
console.log('Top smells (' + smells.length + '):');
|
|
13905
14820
|
smells.slice(0, 20).forEach((smell) => {
|
|
13906
14821
|
console.log(' [' + (smell.severity || 'info') + '] ' + (smell.type || 'signal') + ': ' + smell.summary);
|
|
13907
14822
|
const pivots = [
|
|
@@ -13916,6 +14831,33 @@ function printActivityInspectSummary(response) {
|
|
|
13916
14831
|
if (pivots.length) console.log(' ' + pivots.join(' · '));
|
|
13917
14832
|
});
|
|
13918
14833
|
if (smells.length > 20) console.log(' ...' + (smells.length - 20) + ' more; rerun with --json');
|
|
14834
|
+
const actions = compactActivityInspect(response).nextActions;
|
|
14835
|
+
if (actions.length) {
|
|
14836
|
+
console.log('');
|
|
14837
|
+
console.log('Next actions:');
|
|
14838
|
+
actions.forEach((action) => console.log(' - ' + action));
|
|
14839
|
+
}
|
|
14840
|
+
}
|
|
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)');
|
|
13919
14861
|
}
|
|
13920
14862
|
|
|
13921
14863
|
const lanes = response.lanes || [];
|
|
@@ -13978,9 +14920,81 @@ function printActivityInspectSummary(response) {
|
|
|
13978
14920
|
});
|
|
13979
14921
|
}
|
|
13980
14922
|
console.log('');
|
|
14923
|
+
console.log('Compact JSON: remits-cli activity inspect --summary --json');
|
|
13981
14924
|
console.log('Full structured payload: remits-cli activity inspect --json');
|
|
13982
14925
|
}
|
|
13983
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
|
+
|
|
13984
14998
|
/**
|
|
13985
14999
|
* The live work map: which repositories are being edited, by whom, and who else is around.
|
|
13986
15000
|
*
|
|
@@ -14910,12 +15924,16 @@ function printCorpusHelp() {
|
|
|
14910
15924
|
}
|
|
14911
15925
|
|
|
14912
15926
|
function printToolHelp() {
|
|
14913
|
-
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]');
|
|
14914
15928
|
console.log(' remits-cli tool status --call-id <callId> [--base-url URL] [--account-id ID] [--data-mode test|prod]');
|
|
14915
15929
|
console.log('');
|
|
14916
15930
|
console.log('Hierarchy scope (read/discovery tools) — you do NOT need the owning child account id:');
|
|
14917
15931
|
console.log(' --scope self|children|hierarchy expand from the repo account when searching');
|
|
14918
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.');
|
|
14919
15937
|
console.log(' --account-ids 1,2,3 an explicit bounded list of owner accounts');
|
|
14920
15938
|
console.log(' --anchor-account-id ID disambiguate the path for a multi-parent account');
|
|
14921
15939
|
console.log(' These are merged into --input; a value already in --input wins.');
|
|
@@ -14923,17 +15941,68 @@ function printToolHelp() {
|
|
|
14923
15941
|
console.log('Examples:');
|
|
14924
15942
|
console.log(' remits-cli tool --name mcp_firestore_search --input-file query.json --data-mode prod');
|
|
14925
15943
|
console.log(' remits-cli tool --name mcp_firestore_search --input \'{"collection":"statements","documentId":"1234"}\' --scope children');
|
|
14926
|
-
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');
|
|
14927
15945
|
console.log('');
|
|
14928
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".');
|
|
14929
15947
|
}
|
|
14930
15948
|
|
|
14931
15949
|
function printToolsHelp() {
|
|
14932
|
-
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]');
|
|
14933
15951
|
console.log('');
|
|
14934
15952
|
console.log('Refreshes .remits-cli/tools/tools.json for the current account/branch/data-mode context.');
|
|
14935
15953
|
}
|
|
14936
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
|
+
|
|
14937
16006
|
function printTokenHelp() {
|
|
14938
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]');
|
|
14939
16008
|
console.log(' remits-cli token inspect --token <token|tokenKey|URL> [--base-url URL] [--account-id ID]');
|
|
@@ -14952,7 +16021,7 @@ function printVerifyHelp() {
|
|
|
14952
16021
|
console.log(' remits-cli verify use <envelopeId>');
|
|
14953
16022
|
console.log(' remits-cli verify current');
|
|
14954
16023
|
console.log(' remits-cli verify clear [--all]');
|
|
14955
|
-
console.log(' remits-cli verify show [--envelope ID] [--json]');
|
|
16024
|
+
console.log(' remits-cli verify show [--envelope ID] [--summary|--compact] [--json]');
|
|
14956
16025
|
console.log(' remits-cli verify status [--envelope ID]');
|
|
14957
16026
|
console.log(' remits-cli verify report [--envelope ID]');
|
|
14958
16027
|
console.log(' remits-cli verify abandon --envelope ID --reason "..."');
|
|
@@ -15020,8 +16089,13 @@ async function main() {
|
|
|
15020
16089
|
try { updateAccountRepoIndex(process.cwd()); } catch (_) { /* not in account repo */ }
|
|
15021
16090
|
// Auto-start the background service if not already running.
|
|
15022
16091
|
// Skip for lifecycle subcommands and help.
|
|
15023
|
-
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';
|
|
15024
16093
|
const isHelpCmd = !command || command === 'help' || command === '--help' || wantsHelp;
|
|
16094
|
+
if (!wantsJson && !isHelpCmd && command === 'components') {
|
|
16095
|
+
const commandName = 'components ' + (subcommand || 'command');
|
|
16096
|
+
const problem = platformRepoAccountCommandProblem(process.cwd(), args, commandName, null);
|
|
16097
|
+
if (problem) throw new Error(problem);
|
|
16098
|
+
}
|
|
15025
16099
|
if (!wantsJson && !isHelpCmd && !isLifecycleCmd && !isListenerRunning()) {
|
|
15026
16100
|
try {
|
|
15027
16101
|
const dashboardUrl = await autoStartServiceIfNeeded();
|
|
@@ -15052,18 +16126,20 @@ async function main() {
|
|
|
15052
16126
|
console.log(' remits-cli agent status [--state idle|working|paused] [--ticket ID] [--activity "..."]');
|
|
15053
16127
|
console.log(' remits-cli agent list [--account-id ID] # who else is available');
|
|
15054
16128
|
console.log(' remits-cli agent map [--account-ids 1,4] [--json] # who is EDITING what, where, right now');
|
|
15055
|
-
console.log(' remits-cli activity inspect [--account-id ID] [--scope self|children] [--user-email E] [--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]');
|
|
15056
16131
|
console.log(' remits-cli agent release # stop receiving tickets now');
|
|
15057
16132
|
console.log(' remits-cli start [--foreground true] [--port 8787] # human dashboard in the browser');
|
|
15058
16133
|
console.log(' remits-cli stop');
|
|
15059
16134
|
console.log(' remits-cli status [--base-url URL] [--account-id ID] [--data-mode test|prod] [--json]');
|
|
15060
16135
|
console.log(' remits-cli doctor local-state [--json] # actor/workspace/local state hygiene');
|
|
15061
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');
|
|
15062
16138
|
console.log(' remits-cli listen [stop|status] [--foreground true] # compatibility alias');
|
|
15063
16139
|
console.log(' remits-cli data-mode [set test|prod]');
|
|
15064
16140
|
console.log(' remits-cli install --skills [--target codex|claude|gemini|all] [--overwrite true]');
|
|
15065
|
-
console.log(' remits-cli tools [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--variant-branch NAME|none]');
|
|
15066
|
-
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]');
|
|
15067
16143
|
console.log(' remits-cli tool status --call-id <callId> [--base-url URL] [--account-id ID] [--data-mode test|prod]');
|
|
15068
16144
|
console.log(' remits-cli workspace [show|use <name>|use --auto|clear] # isolate staging when several agents share a repo');
|
|
15069
16145
|
console.log(' remits-cli verify start --summary "..." [--manifest file.json] # start a verification envelope');
|
|
@@ -15103,7 +16179,7 @@ async function main() {
|
|
|
15103
16179
|
console.log('A WORKSPACE is an optional second isolation axis for staging, alongside the git branch.');
|
|
15104
16180
|
console.log('Staging is keyed by (account, cli user, branch, workspace) — and account and user are fixed');
|
|
15105
16181
|
console.log('for a given repo, so without a workspace the branch is the ONLY axis. That is why two coding');
|
|
15106
|
-
console.log('agents in two
|
|
16182
|
+
console.log('agents in two checkouts without their own workspace overwrite each other: `components stage` uploads');
|
|
15107
16183
|
console.log('the whole repo and replaces the lane wholesale.');
|
|
15108
16184
|
console.log('');
|
|
15109
16185
|
console.log('A workspace isolates staging WITHOUT changing anything else:');
|
|
@@ -15111,13 +16187,13 @@ async function main() {
|
|
|
15111
16187
|
console.log(' - the run still resolves whatever committed ComponentVariant branch the account subscribes to');
|
|
15112
16188
|
console.log(' - only which staged entries shadow them changes');
|
|
15113
16189
|
console.log('');
|
|
15114
|
-
console.log('Typical setup, one
|
|
15115
|
-
console.log(' git
|
|
16190
|
+
console.log('Typical setup, one clone per agent, all on the same branch:');
|
|
16191
|
+
console.log(' git clone <repo-url> ../ms-agent-a');
|
|
16192
|
+
console.log(' cd ../ms-agent-a');
|
|
15116
16193
|
console.log(' git switch forked');
|
|
15117
16194
|
console.log(' git pull --ff-only origin forked');
|
|
15118
16195
|
console.log(' git rev-list --left-right --count HEAD...origin/forked # must print: 0 0');
|
|
15119
|
-
console.log('
|
|
15120
|
-
console.log(' cd ../ms-agent-a && remits-cli workspace use --auto');
|
|
16196
|
+
console.log(' remits-cli workspace use --auto');
|
|
15121
16197
|
console.log(' remits-cli components status # branch world + staging lane + other lanes');
|
|
15122
16198
|
console.log(' remits-cli components stage # isolated lane; nobody else sees it');
|
|
15123
16199
|
console.log(' remits-cli test run --test 42 # resolves this lane over the forked variants');
|
|
@@ -15126,7 +16202,8 @@ async function main() {
|
|
|
15126
16202
|
console.log('Precedence: --workspace > REMITS_WORKSPACE > .remits-cli/workspace > shared default lane');
|
|
15127
16203
|
console.log('Use --no-workspace on a single command to target the shared lane without clearing the file.');
|
|
15128
16204
|
console.log('');
|
|
15129
|
-
console.log('`.remits-cli/workspace` is per-checkout and gitignored, so
|
|
16205
|
+
console.log('`.remits-cli/workspace` is per-checkout and gitignored, so each clone keeps its own lane.');
|
|
16206
|
+
console.log('Avoid same-branch worktrees for concurrent agents: they share the branch ref, so one pull moves HEAD under the others, and `components commit` refuses there.');
|
|
15130
16207
|
process.exit(0);
|
|
15131
16208
|
}
|
|
15132
16209
|
|
|
@@ -15145,6 +16222,11 @@ async function main() {
|
|
|
15145
16222
|
process.exit(0);
|
|
15146
16223
|
}
|
|
15147
16224
|
|
|
16225
|
+
if (wantsHelp && command === 'guides') {
|
|
16226
|
+
printGuidesHelp();
|
|
16227
|
+
process.exit(0);
|
|
16228
|
+
}
|
|
16229
|
+
|
|
15148
16230
|
if (wantsHelp && command === 'token') {
|
|
15149
16231
|
printTokenHelp();
|
|
15150
16232
|
process.exit(0);
|
|
@@ -15165,6 +16247,11 @@ async function main() {
|
|
|
15165
16247
|
return;
|
|
15166
16248
|
}
|
|
15167
16249
|
|
|
16250
|
+
if (command === 'guides') {
|
|
16251
|
+
await guidesCommand(args, subcommand);
|
|
16252
|
+
return;
|
|
16253
|
+
}
|
|
16254
|
+
|
|
15168
16255
|
if (command === 'components' && (subcommand === 'push' || subcommand === 'stage')) {
|
|
15169
16256
|
if (subcommand === 'push' && !args.mode) {
|
|
15170
16257
|
args.mode = 'push';
|
|
@@ -15277,11 +16364,16 @@ async function main() {
|
|
|
15277
16364
|
|
|
15278
16365
|
if (command === 'activity') {
|
|
15279
16366
|
if (wantsHelp || !subcommand) {
|
|
15280
|
-
console.log('Usage: remits-cli activity inspect [--account-id ID] [--scope self|children] [--user-id ID|--user-email EMAIL] [--limit N] [--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]');
|
|
15281
16368
|
console.log('');
|
|
15282
16369
|
console.log('Read-only workstream inspection for agent/operator review. Joins current staging lanes,');
|
|
15283
16370
|
console.log('verification envelopes, durable test runs, corpus measurements, live agent presence,');
|
|
15284
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.');
|
|
15285
16377
|
return;
|
|
15286
16378
|
}
|
|
15287
16379
|
if (subcommand === 'inspect') {
|
|
@@ -15291,6 +16383,23 @@ async function main() {
|
|
|
15291
16383
|
throw new Error('Unknown activity subcommand: ' + subcommand);
|
|
15292
16384
|
}
|
|
15293
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
|
+
|
|
15294
16403
|
if (command === 'ticket') {
|
|
15295
16404
|
if (wantsHelp || !subcommand) {
|
|
15296
16405
|
printTicketHelp();
|