@remits/remits-cli 0.1.133 → 0.1.136
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 +1264 -82
- package/package.json +1 -1
- package/skills/remits-cli/SKILL.md +35 -12
- 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 +134 -23
- package/skills/remits-cli/references/component-resolution.md +16 -0
- package/skills/remits-cli/references/development-loop.md +53 -3
- 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
|
+
- L2624 Account Repos, Guide Sync, And Platform Repo
|
|
10
|
+
- L3432 Component Discovery And HTTP Logging
|
|
11
|
+
- L4073 Skill Delivery And TOC Resolution
|
|
12
|
+
- L4383 Auth And Component Staging
|
|
13
|
+
- L4998 Component Summaries, Status, And Sync Gates
|
|
14
|
+
- L7067 Branches, Promotion, Commit, And Test Runs
|
|
15
|
+
- L8750 Tokens, Tools, Verification, And Config
|
|
16
|
+
- L10177 Service Dashboard And WebSocket Listener
|
|
17
|
+
- L12298 Agent And Ticket Workflows
|
|
18
|
+
- L15888 Help, Auto Update, And Command Dispatch
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
/*
|
|
@@ -621,10 +621,12 @@ 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'),
|
|
627
628
|
diagnosticsDir: path.join(actorDir, 'diagnostics'),
|
|
629
|
+
evidenceFile: path.join(actorDir, 'evidence.jsonl'),
|
|
628
630
|
activeVerificationFile: path.join(actorDir, 'verification', 'active'),
|
|
629
631
|
activeVerificationContextsFile: path.join(actorDir, 'verification', 'active-contexts.json'),
|
|
630
632
|
currentSessionFile: path.join(actorDir, 'current-session.txt'),
|
|
@@ -924,6 +926,112 @@ function localStateSummary(cwd, flags = {}) {
|
|
|
924
926
|
};
|
|
925
927
|
}
|
|
926
928
|
|
|
929
|
+
function normalizeWorkstreamId(value) {
|
|
930
|
+
if (value === undefined || value === null) return null;
|
|
931
|
+
const trimmed = String(value).trim();
|
|
932
|
+
if (!trimmed) return null;
|
|
933
|
+
let normalized = trimmed
|
|
934
|
+
.toLowerCase()
|
|
935
|
+
.replace(/[^a-z0-9._-]/g, '-')
|
|
936
|
+
.replace(/-+/g, '-')
|
|
937
|
+
.replace(/^[-.]+/, '')
|
|
938
|
+
.replace(/[-.]+$/, '');
|
|
939
|
+
return normalized ? normalized.slice(0, 96).replace(/[-.]+$/, '') : null;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// The LOCAL proof-campaign id: the thing that groups several actor directories working one goal. It is not
|
|
943
|
+
// the same `workstream` a support ticket carries — that one is an account's own routing label, matched by
|
|
944
|
+
// `agent serve --serves`. They share a flag name and nothing else; keep the distinction in any text that
|
|
945
|
+
// mentions either.
|
|
946
|
+
function resolveWorkstreamId(cwd, flags = {}, context = {}) {
|
|
947
|
+
return normalizeWorkstreamId(flags.workstream || flags['workstream-id'] || process.env.REMITS_WORKSTREAM_ID ||
|
|
948
|
+
context.workstream || context.workspace || resolveWorkspace(cwd, flags));
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
const WORKSTREAM_ROLE_KEYS = ['repoAccountId', 'accountId', 'executionAccountId', 'targetAccountId',
|
|
952
|
+
'branchName', 'componentBranch', 'workspace', 'dataMode'];
|
|
953
|
+
|
|
954
|
+
function workstreamRoleSignature(world) {
|
|
955
|
+
if (!world || typeof world !== 'object') return null;
|
|
956
|
+
const signature = {};
|
|
957
|
+
WORKSTREAM_ROLE_KEYS.forEach((key) => {
|
|
958
|
+
if (world[key] !== undefined && world[key] !== null) signature[key] = world[key];
|
|
959
|
+
});
|
|
960
|
+
return Object.keys(signature).length ? signature : null;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function workstreamWorldParts(world) {
|
|
964
|
+
const roles = world || {};
|
|
965
|
+
return [
|
|
966
|
+
roles.repoAccountId ? 'repo ' + roles.repoAccountId : null,
|
|
967
|
+
roles.accountId ? 'account ' + roles.accountId : null,
|
|
968
|
+
roles.executionAccountId ? 'exec ' + roles.executionAccountId : null,
|
|
969
|
+
roles.targetAccountId ? 'target ' + roles.targetAccountId : null,
|
|
970
|
+
roles.branchName ? 'branch ' + roles.branchName : null,
|
|
971
|
+
roles.componentBranch ? 'componentBranch ' + roles.componentBranch : null,
|
|
972
|
+
roles.workspace ? 'ws:' + roles.workspace : null,
|
|
973
|
+
roles.dataMode ? 'data ' + roles.dataMode : null
|
|
974
|
+
].filter(Boolean);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function dedupeWorkstreamWorlds(worlds) {
|
|
978
|
+
const seen = new Set();
|
|
979
|
+
const out = [];
|
|
980
|
+
(worlds || []).forEach((world) => {
|
|
981
|
+
if (!world || !Object.keys(world).length) return;
|
|
982
|
+
const key = JSON.stringify(WORKSTREAM_ROLE_KEYS.map((k) => world[k] === undefined ? null : world[k]));
|
|
983
|
+
if (seen.has(key)) return;
|
|
984
|
+
seen.add(key);
|
|
985
|
+
out.push(world);
|
|
986
|
+
});
|
|
987
|
+
return out.slice(-12);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function workstreamFile(cwd, workstreamId) {
|
|
991
|
+
const paths = ensureLocalState(cwd);
|
|
992
|
+
ensureDir(paths.workstreamsDir);
|
|
993
|
+
return path.join(paths.workstreamsDir, normalizeWorkstreamId(workstreamId) + '.json');
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
function recordWorkstreamArtifact(cwd, flags = {}, artifact = {}) {
|
|
997
|
+
const workstreamId = resolveWorkstreamId(cwd, flags, artifact);
|
|
998
|
+
if (!workstreamId) return null;
|
|
999
|
+
const file = workstreamFile(cwd, workstreamId);
|
|
1000
|
+
let existing = {};
|
|
1001
|
+
try {
|
|
1002
|
+
if (fs.existsSync(file)) existing = JSON.parse(fs.readFileSync(file, 'utf8')) || {};
|
|
1003
|
+
} catch (_) {
|
|
1004
|
+
existing = {};
|
|
1005
|
+
}
|
|
1006
|
+
const now = new Date().toISOString();
|
|
1007
|
+
const actor = localStatePaths(cwd).actor.id;
|
|
1008
|
+
const item = Object.assign({
|
|
1009
|
+
actor,
|
|
1010
|
+
recordedAt: now
|
|
1011
|
+
}, artifact || {});
|
|
1012
|
+
const artifacts = Array.isArray(existing.artifacts) ? existing.artifacts : [];
|
|
1013
|
+
const dedupeKey = [item.type, item.callId, item.envelopeId, item.packetId, item.path].filter(Boolean).join('|');
|
|
1014
|
+
const filtered = dedupeKey
|
|
1015
|
+
? artifacts.filter((row) => [row.type, row.callId, row.envelopeId, row.packetId, row.path].filter(Boolean).join('|') !== dedupeKey)
|
|
1016
|
+
: artifacts;
|
|
1017
|
+
// The LAST world, never a merge of all of them. Merging produced a "World:" line that no single run ever
|
|
1018
|
+
// executed in — a leftover targetAccountId from one command printed next to the executionAccountId of
|
|
1019
|
+
// another. A campaign that legitimately spans worlds is reported as several worlds, not as one blend.
|
|
1020
|
+
const roles = workstreamRoleSignature(artifact.world) || existing.roles || {};
|
|
1021
|
+
const priorWorlds = Array.isArray(existing.worlds) ? existing.worlds : (existing.roles ? [existing.roles] : []);
|
|
1022
|
+
const worlds = dedupeWorkstreamWorlds(priorWorlds.concat([roles]));
|
|
1023
|
+
const next = Object.assign({}, existing, {
|
|
1024
|
+
workstreamId,
|
|
1025
|
+
updatedAt: now,
|
|
1026
|
+
actors: Array.from(new Set([].concat(existing.actors || [], actor))).sort(),
|
|
1027
|
+
roles,
|
|
1028
|
+
worlds,
|
|
1029
|
+
artifacts: filtered.concat([item]).slice(-200)
|
|
1030
|
+
});
|
|
1031
|
+
atomicWriteFile(file, JSON.stringify(next, null, 2));
|
|
1032
|
+
return file;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
927
1035
|
// ---------------------------------------------------------------------------------------------------
|
|
928
1036
|
// Staging workspace
|
|
929
1037
|
// ---------------------------------------------------------------------------------------------------
|
|
@@ -1463,6 +1571,40 @@ function writeActiveVerificationContexts(cwd, contexts) {
|
|
|
1463
1571
|
atomicWriteFile(paths.activeVerificationContextsFile, JSON.stringify(contexts || {}, null, 2));
|
|
1464
1572
|
}
|
|
1465
1573
|
|
|
1574
|
+
function describeVerificationWorld(context = {}) {
|
|
1575
|
+
return ['account ' + (context.accountId != null ? context.accountId : '?'),
|
|
1576
|
+
'branch ' + (context.branchName || context.branch || '?'),
|
|
1577
|
+
'workspace ' + (normalizeWorkspace(context.workspace) || 'shared'),
|
|
1578
|
+
context.dataMode ? 'data ' + context.dataMode : null].filter(Boolean).join(', ');
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
/**
|
|
1582
|
+
* Active envelope pointers this actor holds for a DIFFERENT checkout world than the one running now. Used
|
|
1583
|
+
* only to explain a silence: an agent that started an envelope on `main` and then worked `--branch forked`
|
|
1584
|
+
* otherwise gets no envelope, no detach message, and no reason.
|
|
1585
|
+
*/
|
|
1586
|
+
function otherWorldVerificationEnvelopes(cwd, context) {
|
|
1587
|
+
try {
|
|
1588
|
+
const currentKey = verificationContextKey(context);
|
|
1589
|
+
const contexts = readActiveVerificationContexts(cwd);
|
|
1590
|
+
return Object.keys(contexts)
|
|
1591
|
+
.filter((key) => key !== currentKey && !legacyVerificationContextKeys(currentKey).includes(key))
|
|
1592
|
+
.map((key) => {
|
|
1593
|
+
const entry = typeof contexts[key] === 'string' ? { envelopeId: contexts[key] } : contexts[key];
|
|
1594
|
+
if (!entry || !entry.envelopeId) return null;
|
|
1595
|
+
return { envelopeId: String(entry.envelopeId), updatedAt: entry.updatedAt, context: entry.context || {} };
|
|
1596
|
+
})
|
|
1597
|
+
.filter(Boolean)
|
|
1598
|
+
// Only pointers for THIS host and account: an envelope for another account is not a missing binding,
|
|
1599
|
+
// it is different work.
|
|
1600
|
+
.filter((entry) => String(entry.context.accountId) === String(context.accountId) &&
|
|
1601
|
+
normalizeBaseUrl(entry.context.baseUrl || '') === normalizeBaseUrl(context.baseUrl || ''))
|
|
1602
|
+
.sort((left, right) => String(right.updatedAt || '').localeCompare(String(left.updatedAt || '')));
|
|
1603
|
+
} catch (_) {
|
|
1604
|
+
return [];
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1466
1608
|
function activeVerificationContext(cwd, flags = {}, session = null, accountId = null) {
|
|
1467
1609
|
return {
|
|
1468
1610
|
baseUrl: normalizeBaseUrl(flags['base-url'] || (session && session.baseUrl) || DEFAULT_BASE_URL),
|
|
@@ -1768,7 +1910,25 @@ async function verificationPreflight(api, cwd, session, accountId, flags = {}, p
|
|
|
1768
1910
|
const label = options.command || 'this command';
|
|
1769
1911
|
const context = activeVerificationContext(cwd, flags, session, accountId);
|
|
1770
1912
|
const envelopeId = verificationEnvelopeIdForCommand(cwd, flags, context);
|
|
1771
|
-
if (!envelopeId)
|
|
1913
|
+
if (!envelopeId) {
|
|
1914
|
+
// An envelope is bound per CHECKOUT WORLD, so `verify start` on one branch/workspace and the work on
|
|
1915
|
+
// another leaves the agent with an active envelope it cannot see and a command that says nothing at all:
|
|
1916
|
+
// it did the work, attached no evidence, and reported neither. Silence is only correct when there is no
|
|
1917
|
+
// envelope to speak of.
|
|
1918
|
+
if (!options.quiet) {
|
|
1919
|
+
const orphan = otherWorldVerificationEnvelopes(cwd, context);
|
|
1920
|
+
if (orphan.length) {
|
|
1921
|
+
console.error('No verification envelope is active for this world (' + describeVerificationWorld(context) + '), ' +
|
|
1922
|
+
'so ' + label + ' attaches no evidence. You have ' + orphan.length + ' active envelope(s) bound to other worlds:');
|
|
1923
|
+
orphan.slice(0, 3).forEach((entry) => {
|
|
1924
|
+
console.error(' - ' + entry.envelopeId + ' ' + describeVerificationWorld(entry.context));
|
|
1925
|
+
});
|
|
1926
|
+
console.error(' Start one here with `remits-cli verify start --summary "..."`, adopt one with ' +
|
|
1927
|
+
'`remits-cli verify use <id>`, or re-run in the world that envelope was started in.');
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
return { envelopeId: null, verdict: 'none', evidenceFlags: flags };
|
|
1931
|
+
}
|
|
1772
1932
|
const pinned = Object.assign({}, flags, { 'verify-envelope': envelopeId });
|
|
1773
1933
|
let result;
|
|
1774
1934
|
try {
|
|
@@ -1840,9 +2000,145 @@ async function printVerificationRunProbe(api, cwd, session, accountId, envelopeI
|
|
|
1840
2000
|
return result;
|
|
1841
2001
|
}
|
|
1842
2002
|
|
|
2003
|
+
/**
|
|
2004
|
+
* The always-on evidence trail.
|
|
2005
|
+
*
|
|
2006
|
+
* <p>One world-stamped line per evidence-producing command, written whether or not any verification envelope
|
|
2007
|
+
* exists. This is the answer to "what have I actually run, and in which world?" - the question agents were
|
|
2008
|
+
* opening envelopes to answer, and an envelope is a far heavier instrument than that question needs. It is
|
|
2009
|
+
* per-actor, so two agents in one checkout never read each other's trail, and it is append-only with a bounded
|
|
2010
|
+
* tail so it cannot grow without limit.</p>
|
|
2011
|
+
*
|
|
2012
|
+
* <p>Recording is deliberately decision-free: there is no flag to remember, no state to start, and nothing an
|
|
2013
|
+
* agent can fail to satisfy.</p>
|
|
2014
|
+
*/
|
|
2015
|
+
const EVIDENCE_TRAIL_MAX_LINES = 2000;
|
|
2016
|
+
|
|
2017
|
+
function recordEvidenceEntry(cwd, packet, context = {}) {
|
|
2018
|
+
try {
|
|
2019
|
+
const paths = ensureLocalState(cwd);
|
|
2020
|
+
const world = (packet && packet.world && typeof packet.world === 'object') ? packet.world : {};
|
|
2021
|
+
const hasSuccess = packet && Object.prototype.hasOwnProperty.call(packet, 'success');
|
|
2022
|
+
const entry = {
|
|
2023
|
+
ts: new Date().toISOString(),
|
|
2024
|
+
type: packet && packet.type || 'unknown',
|
|
2025
|
+
claim: packet && packet.claim || null,
|
|
2026
|
+
success: hasSuccess ? packet.success : true,
|
|
2027
|
+
pending: packet && packet.pending === true || (hasSuccess && packet.success === null),
|
|
2028
|
+
world: {
|
|
2029
|
+
repoAccountId: world.repoAccountId != null ? world.repoAccountId : context.accountId || null,
|
|
2030
|
+
executionAccountId: world.executionAccountId != null ? world.executionAccountId : (world.accountId != null ? world.accountId : null),
|
|
2031
|
+
targetAccountId: world.targetAccountId != null ? world.targetAccountId : null,
|
|
2032
|
+
dataMode: world.dataMode || context.dataMode || null,
|
|
2033
|
+
branchName: world.branchName || world.gitBranch || context.branchName || null,
|
|
2034
|
+
componentBranch: world.componentBranch || world.variantBranch || null,
|
|
2035
|
+
workspace: world.workspace !== undefined ? world.workspace : (context.workspace !== undefined ? context.workspace : null),
|
|
2036
|
+
sourceLayer: world.sourceLayer || null,
|
|
2037
|
+
laneContentHash: world.laneContentHash || world.stagedOverlayHash || null,
|
|
2038
|
+
host: world.host || context.baseUrl || null
|
|
2039
|
+
},
|
|
2040
|
+
ref: {
|
|
2041
|
+
packetId: packet && packet.packetId || null,
|
|
2042
|
+
taskId: (packet && packet.test && packet.test.taskId) || (packet && packet.status && packet.status.taskId) || null,
|
|
2043
|
+
callId: (packet && packet.tool && packet.tool.callId) || null,
|
|
2044
|
+
responseFile: (packet && packet.tool && packet.tool.responseFile) || null
|
|
2045
|
+
},
|
|
2046
|
+
// Present when an envelope happened to be active. Never required, and never something to chase.
|
|
2047
|
+
envelopeId: context.envelopeId || null,
|
|
2048
|
+
claims: Array.isArray(packet && packet.claims) ? packet.claims : undefined
|
|
2049
|
+
};
|
|
2050
|
+
fs.appendFileSync(paths.evidenceFile, JSON.stringify(entry) + '\n');
|
|
2051
|
+
trimEvidenceTrail(paths.evidenceFile);
|
|
2052
|
+
return entry;
|
|
2053
|
+
} catch (_) {
|
|
2054
|
+
// A trail write must never fail the command it is describing.
|
|
2055
|
+
return null;
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
function evidenceOutcome(row) {
|
|
2060
|
+
if (row && (row.pending === true || row.success === null)) return 'pending';
|
|
2061
|
+
return row && row.success === false ? 'failed' : 'ok';
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
function evidenceOutcomeLabel(row) {
|
|
2065
|
+
const outcome = evidenceOutcome(row);
|
|
2066
|
+
if (outcome === 'failed') return 'FAILED ';
|
|
2067
|
+
if (outcome === 'pending') return 'PENDING ';
|
|
2068
|
+
return '';
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
function trimEvidenceTrail(file) {
|
|
2072
|
+
try {
|
|
2073
|
+
const lines = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean);
|
|
2074
|
+
if (lines.length <= EVIDENCE_TRAIL_MAX_LINES) return;
|
|
2075
|
+
atomicWriteFile(file, lines.slice(lines.length - EVIDENCE_TRAIL_MAX_LINES).join('\n') + '\n');
|
|
2076
|
+
} catch (_) { /* best effort */ }
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
function readEvidenceTrail(cwd, limit = 50) {
|
|
2080
|
+
try {
|
|
2081
|
+
const paths = localStatePaths(cwd);
|
|
2082
|
+
if (!fs.existsSync(paths.evidenceFile)) return [];
|
|
2083
|
+
const lines = fs.readFileSync(paths.evidenceFile, 'utf8').split('\n').filter(Boolean);
|
|
2084
|
+
return lines.slice(Math.max(0, lines.length - limit)).map((line) => {
|
|
2085
|
+
try { return JSON.parse(line); } catch (_) { return null; }
|
|
2086
|
+
}).filter(Boolean);
|
|
2087
|
+
} catch (_) {
|
|
2088
|
+
return [];
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
/**
|
|
2093
|
+
* WHERE a command ran: accounts, lane, branch, workspace. Deliberately NOT `sourceLayer` or
|
|
2094
|
+
* `laneContentHash` - those legitimately differ between two commands in the same world (a stage is `staged`,
|
|
2095
|
+
* the test that follows resolves `db`), so keying the grouping on them split one world into several and made
|
|
2096
|
+
* two entries from the same context look like two different places. They belong on the entry.
|
|
2097
|
+
*/
|
|
2098
|
+
function describeEvidenceWorld(world) {
|
|
2099
|
+
const w = world || {};
|
|
2100
|
+
const accounts = w.executionAccountId && String(w.executionAccountId) !== String(w.repoAccountId)
|
|
2101
|
+
? 'account ' + w.repoAccountId + ' -> runs as ' + w.executionAccountId
|
|
2102
|
+
: 'account ' + (w.repoAccountId == null ? '?' : w.repoAccountId);
|
|
2103
|
+
return [
|
|
2104
|
+
accounts,
|
|
2105
|
+
w.targetAccountId && String(w.targetAccountId) !== String(w.executionAccountId) ? 'targets ' + w.targetAccountId : null,
|
|
2106
|
+
w.dataMode ? w.dataMode + ' lane' : null,
|
|
2107
|
+
w.branchName ? 'branch ' + w.branchName : null,
|
|
2108
|
+
w.componentBranch ? 'components ' + w.componentBranch : null,
|
|
2109
|
+
'ws:' + (w.workspace || 'shared')
|
|
2110
|
+
].filter(Boolean).join(' · ');
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
function claimIdsFromFlags(flags) {
|
|
2114
|
+
return []
|
|
2115
|
+
.concat((flags && flags.claim) || [])
|
|
2116
|
+
.concat((flags && flags.claims) || [])
|
|
2117
|
+
.filter(Boolean)
|
|
2118
|
+
.flatMap((value) => String(value).split(','))
|
|
2119
|
+
.map((value) => value.trim())
|
|
2120
|
+
.filter(Boolean);
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
function stampPacketClaims(packet, flags) {
|
|
2124
|
+
const claims = claimIdsFromFlags(flags);
|
|
2125
|
+
if (!claims.length) return packet;
|
|
2126
|
+
if (!Array.isArray(packet.evidenceCategories)) packet.evidenceCategories = [];
|
|
2127
|
+
claims.forEach((claim) => {
|
|
2128
|
+
if (!packet.evidenceCategories.includes(claim)) packet.evidenceCategories.push(claim);
|
|
2129
|
+
});
|
|
2130
|
+
packet.claims = claims;
|
|
2131
|
+
return packet;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
1843
2134
|
async function appendVerificationPacket(api, cwd, session, accountId, flags, packet, options = {}) {
|
|
1844
2135
|
const context = activeVerificationContext(cwd, flags, session, accountId);
|
|
1845
2136
|
const envelopeId = verificationEnvelopeIdForCommand(cwd, flags, context);
|
|
2137
|
+
// The trail is written FIRST and unconditionally. Evidence recording must not depend on an agent having
|
|
2138
|
+
// remembered to start anything: an envelope is an optional verdict on top of this, never the thing that
|
|
2139
|
+
// makes a command's world durable.
|
|
2140
|
+
stampPacketClaims(packet, flags);
|
|
2141
|
+
recordEvidenceEntry(cwd, packet, Object.assign({}, context, { accountId, envelopeId }));
|
|
1846
2142
|
if (!envelopeId) return null;
|
|
1847
2143
|
const finalPacket = Object.assign({
|
|
1848
2144
|
packetId: crypto.randomUUID(),
|
|
@@ -1851,7 +2147,23 @@ async function appendVerificationPacket(api, cwd, session, accountId, flags, pac
|
|
|
1851
2147
|
}, packet || {});
|
|
1852
2148
|
finalPacket.envelopeId = envelopeId;
|
|
1853
2149
|
stampPacketRepoAccount(finalPacket, accountId);
|
|
2150
|
+
// The workstream id travels WITH the evidence. Without it the local index and the platform's
|
|
2151
|
+
// `activity inspect --scope related` share no identifier, so two agents on one campaign — the case this
|
|
2152
|
+
// whole surface exists for — could never be shown the same set of work.
|
|
2153
|
+
const packetWorkstreamId = resolveWorkstreamId(cwd, flags, finalPacket.world || {});
|
|
2154
|
+
if (packetWorkstreamId) {
|
|
2155
|
+
if (!finalPacket.world || typeof finalPacket.world !== 'object') finalPacket.world = {};
|
|
2156
|
+
if (!finalPacket.world.workstreamId) finalPacket.world.workstreamId = packetWorkstreamId;
|
|
2157
|
+
}
|
|
1854
2158
|
writeLocalVerificationPacket(cwd, envelopeId, finalPacket);
|
|
2159
|
+
recordWorkstreamArtifact(cwd, flags, {
|
|
2160
|
+
type: 'verification_packet',
|
|
2161
|
+
envelopeId,
|
|
2162
|
+
packetId: finalPacket.packetId,
|
|
2163
|
+
packetType: finalPacket.type,
|
|
2164
|
+
world: finalPacket.world || {},
|
|
2165
|
+
workspace: finalPacket.world && finalPacket.world.workspace
|
|
2166
|
+
});
|
|
1855
2167
|
try {
|
|
1856
2168
|
const response = await postVerificationCommand(api, cwd, session, accountId, 'packet', {
|
|
1857
2169
|
envelopeId,
|
|
@@ -1892,7 +2204,7 @@ function printVerificationSummaryLine(env) {
|
|
|
1892
2204
|
const health = env.health ? ' health=' + env.health : '';
|
|
1893
2205
|
const failures = env.currentFailureCount ? ' currentFailures=' + env.currentFailureCount : (env.failedCount ? ' failed=' + env.failedCount : '');
|
|
1894
2206
|
const required = ' required=' + (env.satisfiedCount || 0) + '/' + (env.requiredCount || 0);
|
|
1895
|
-
const noManifest = env.noRequiredEvidence ? '
|
|
2207
|
+
const noManifest = env.noRequiredEvidence ? ' evidence-log' : '';
|
|
1896
2208
|
const stale = env.staleCount ? ' stale=' + env.staleCount : '';
|
|
1897
2209
|
const changed = env.requirementsChangedAfterEvidence ? ' requirements-changed' : '';
|
|
1898
2210
|
const lane = [env.branchName || 'unknown-branch', env.workspace ? 'ws:' + env.workspace : 'shared', env.dataMode || null, env.sourceLayer || null]
|
|
@@ -1909,7 +2221,7 @@ function printVerificationEnvelope(envelope, fallbackEnvelopeId) {
|
|
|
1909
2221
|
const evaluation = envelope.evaluation || {};
|
|
1910
2222
|
if (evaluation.health) console.log('Health:', evaluation.health);
|
|
1911
2223
|
if (evaluation.noRequiredEvidence) {
|
|
1912
|
-
console.log('Required evidence: none declared - evidence
|
|
2224
|
+
console.log('Required evidence: none declared - evidence log only; no verdict is outstanding.');
|
|
1913
2225
|
} else {
|
|
1914
2226
|
console.log('Required evidence:', (evaluation.satisfiedCount || 0) + '/' + (evaluation.requiredCount || 0));
|
|
1915
2227
|
}
|
|
@@ -1934,13 +2246,14 @@ function printVerificationEnvelope(envelope, fallbackEnvelopeId) {
|
|
|
1934
2246
|
}
|
|
1935
2247
|
}
|
|
1936
2248
|
|
|
1937
|
-
function slimVerificationPacket(packet) {
|
|
2249
|
+
function slimVerificationPacket(packet, options = {}) {
|
|
1938
2250
|
const p = packet || {};
|
|
1939
2251
|
const world = p.world || {};
|
|
2252
|
+
const hasSuccess = Object.prototype.hasOwnProperty.call(p, 'success');
|
|
1940
2253
|
return {
|
|
1941
2254
|
packetId: p.packetId || p.id || null,
|
|
1942
2255
|
type: p.type || 'unknown',
|
|
1943
|
-
success: p.success !== false,
|
|
2256
|
+
success: hasSuccess ? p.success !== false : options.defaultSuccess !== false,
|
|
1944
2257
|
stale: Boolean(p.stale),
|
|
1945
2258
|
claim: p.claim || p.summary || null,
|
|
1946
2259
|
taskId: p.taskId || (p.testRun && p.testRun.taskId) || null,
|
|
@@ -1967,7 +2280,7 @@ function compactVerificationEnvelope(envelope, fallbackEnvelopeId) {
|
|
|
1967
2280
|
const nextActions = [];
|
|
1968
2281
|
|
|
1969
2282
|
if (evaluation.noRequiredEvidence) {
|
|
1970
|
-
nextActions.push('
|
|
2283
|
+
nextActions.push('No verdict was requested. Leave this as an evidence log, or add `verify claim <id> --text "..."` only if someone needs a checkable verdict.');
|
|
1971
2284
|
}
|
|
1972
2285
|
if (failures.length) {
|
|
1973
2286
|
nextActions.push('Inspect and replace the current failing evidence packet(s).');
|
|
@@ -2002,7 +2315,7 @@ function compactVerificationEnvelope(envelope, fallbackEnvelopeId) {
|
|
|
2002
2315
|
recent: packets.slice(-5).map(slimVerificationPacket)
|
|
2003
2316
|
},
|
|
2004
2317
|
latestByType,
|
|
2005
|
-
currentFailures: failures.slice(0, 10).map(slimVerificationPacket),
|
|
2318
|
+
currentFailures: failures.slice(0, 10).map((packet) => slimVerificationPacket(packet, { defaultSuccess: false })),
|
|
2006
2319
|
stale: stale.slice(0, 10).map(slimVerificationPacket),
|
|
2007
2320
|
warnings: (env.warnings || []).slice(0, 10),
|
|
2008
2321
|
nextActions
|
|
@@ -2051,14 +2364,27 @@ function printEnvelopeWarnings(envelope) {
|
|
|
2051
2364
|
console.log('Envelope warnings:');
|
|
2052
2365
|
warnings.forEach((warning) => {
|
|
2053
2366
|
console.log('- ' + (warning.message || warning.code || JSON.stringify(warning)));
|
|
2367
|
+
if (warning.command) console.log(' ' + warning.command);
|
|
2054
2368
|
if (Array.isArray(warning.envelopes) && warning.envelopes.length) {
|
|
2055
2369
|
warning.envelopes.forEach((env) => {
|
|
2056
|
-
|
|
2370
|
+
// Say whether the duplicate is in THIS world. A restart in a genuinely different lane or branch is
|
|
2371
|
+
// sometimes exactly right; a second envelope for the same goal in the same world is the loop.
|
|
2372
|
+
const world = env.world && typeof env.world === 'object'
|
|
2373
|
+
? Object.keys(env.world).map((key) => key + '=' + env.world[key]).join(' ')
|
|
2374
|
+
: '';
|
|
2375
|
+
console.log(' ' + (env.envelopeId || '(no id)') + (env.status ? ' ' + env.status : '') +
|
|
2376
|
+
(env.sameWorld === true ? ' [same world]' : (world ? ' [' + world + ']' : '')));
|
|
2057
2377
|
});
|
|
2378
|
+
console.log(' Continue one of those with `remits-cli verify use <id>`, or supersede it once this one supersedes its goal.');
|
|
2058
2379
|
}
|
|
2059
2380
|
});
|
|
2060
2381
|
}
|
|
2061
2382
|
|
|
2383
|
+
function manifestClaims(manifest) {
|
|
2384
|
+
const claims = manifest && manifest.claims;
|
|
2385
|
+
return Array.isArray(claims) ? claims.filter(Boolean) : [];
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2062
2388
|
function manifestRequiredEvidence(manifest = {}) {
|
|
2063
2389
|
const required = [];
|
|
2064
2390
|
if (Array.isArray(manifest.requiredEvidence)) required.push(...manifest.requiredEvidence);
|
|
@@ -2096,16 +2422,35 @@ async function readVerificationPacketsForDiagnostics(api, cwd, session, accountI
|
|
|
2096
2422
|
}
|
|
2097
2423
|
|
|
2098
2424
|
function buildCommandWorld(response, fallback = {}) {
|
|
2425
|
+
if (response && response.world && typeof response.world === 'object') {
|
|
2426
|
+
return Object.assign({}, response.world, {
|
|
2427
|
+
host: response.world.host || fallback.host,
|
|
2428
|
+
dataMode: response.world.dataMode || response.dataMode || fallback.dataMode,
|
|
2429
|
+
branchName: response.world.branchName || response.branchName || fallback.branchName,
|
|
2430
|
+
workspace: response.world.workspace !== undefined ? response.world.workspace : fallback.workspace
|
|
2431
|
+
});
|
|
2432
|
+
}
|
|
2099
2433
|
const source = (response && response.staging) || {};
|
|
2100
2434
|
const resolution = (response && response.resolution) || {};
|
|
2101
2435
|
const result = (response && response.result) || {};
|
|
2102
2436
|
const laneHash = laneContentHashFrom(response) || fallback.laneContentHash || fallback.stagedOverlayHash;
|
|
2103
|
-
const
|
|
2437
|
+
const addressedAccountId = response && (response.repoAccountId || response.checkoutAccountId || response.scopeRootAccountId ||
|
|
2438
|
+
response.accountId || response.ownerAccountId) ||
|
|
2439
|
+
fallback.repoAccountId || fallback.checkoutAccountId || fallback.scopeRootAccountId || fallback.addressedAccountId || fallback.accountId;
|
|
2440
|
+
const executionAccountId = response && (response.executionAccountId || result.accountId || source.executionAccountId) ||
|
|
2104
2441
|
resolution.executionAccountId ||
|
|
2105
|
-
fallback.executionAccountId
|
|
2442
|
+
fallback.executionAccountId ||
|
|
2443
|
+
// Legacy/fallback callers historically supplied only `accountId`, and for all non-split worlds that
|
|
2444
|
+
// value is both repo and execution. Never let a top-level response.accountId (usually addressed/repo) be
|
|
2445
|
+
// the execution account unless the caller intentionally passed it as the fallback.
|
|
2446
|
+
fallback.accountId;
|
|
2106
2447
|
return {
|
|
2107
|
-
accountId:
|
|
2448
|
+
accountId: executionAccountId,
|
|
2449
|
+
repoAccountId: response && response.repoAccountId || fallback.repoAccountId || addressedAccountId,
|
|
2450
|
+
scopeRootAccountId: response && response.scopeRootAccountId || fallback.scopeRootAccountId,
|
|
2451
|
+
checkoutAccountId: response && response.checkoutAccountId || fallback.checkoutAccountId || addressedAccountId,
|
|
2108
2452
|
executionAccountId,
|
|
2453
|
+
targetAccountId: response && response.targetAccountId || fallback.targetAccountId,
|
|
2109
2454
|
addressedAccountId: resolution.addressedAccountId || fallback.addressedAccountId,
|
|
2110
2455
|
host: fallback.host,
|
|
2111
2456
|
dataMode: response && response.dataMode || fallback.dataMode,
|
|
@@ -2122,10 +2467,34 @@ function buildCommandWorld(response, fallback = {}) {
|
|
|
2122
2467
|
fallback.componentBranch,
|
|
2123
2468
|
variantBranchSource: response && (response.variantBranchSource || (response.variantWorld && response.variantWorld.variantBranchSource)) || fallback.variantBranchSource,
|
|
2124
2469
|
variantId: resolution.variantId || fallback.variantId,
|
|
2470
|
+
componentSource: response && response.componentSource || fallback.componentSource,
|
|
2471
|
+
componentSignature: response && response.componentSignature || fallback.componentSignature,
|
|
2125
2472
|
sourceLayer: fallback.sourceLayer || (source.testComponentSource === 'staged' ? 'staged' : undefined)
|
|
2126
2473
|
};
|
|
2127
2474
|
}
|
|
2128
2475
|
|
|
2476
|
+
function buildToolStatusWorld(data, stored = {}, fallback = {}) {
|
|
2477
|
+
const response = data && typeof data === 'object' ? data : {};
|
|
2478
|
+
const previous = stored && typeof stored === 'object' ? stored : {};
|
|
2479
|
+
return buildCommandWorld(response, {
|
|
2480
|
+
repoAccountId: response.repoAccountId || previous.repoAccountId || fallback.repoAccountId || previous.checkoutAccountId || previous.accountId || fallback.accountId,
|
|
2481
|
+
scopeRootAccountId: response.scopeRootAccountId || previous.scopeRootAccountId || fallback.scopeRootAccountId,
|
|
2482
|
+
checkoutAccountId: response.checkoutAccountId || previous.checkoutAccountId || fallback.checkoutAccountId,
|
|
2483
|
+
accountId: response.executionAccountId || previous.executionAccountId || fallback.executionAccountId || fallback.accountId,
|
|
2484
|
+
executionAccountId: response.executionAccountId || previous.executionAccountId || fallback.executionAccountId,
|
|
2485
|
+
targetAccountId: response.targetAccountId || previous.targetAccountId || fallback.targetAccountId,
|
|
2486
|
+
dataMode: response.dataMode || previous.dataMode || fallback.dataMode,
|
|
2487
|
+
branchName: response.branchName || previous.branchName || fallback.branchName,
|
|
2488
|
+
workspace: response.workspace !== undefined ? response.workspace : (previous.workspace !== undefined ? previous.workspace : fallback.workspace),
|
|
2489
|
+
host: fallback.host,
|
|
2490
|
+
componentSource: response.componentSource || previous.componentSource || fallback.componentSource,
|
|
2491
|
+
componentSignature: response.componentSignature || previous.componentSignature || fallback.componentSignature,
|
|
2492
|
+
componentBranch: response.componentBranch || previous.componentBranch || fallback.componentBranch,
|
|
2493
|
+
variantBranch: response.variantBranch || previous.variantBranch || fallback.variantBranch,
|
|
2494
|
+
variantBranchSource: response.variantBranchSource || previous.variantBranchSource || fallback.variantBranchSource
|
|
2495
|
+
});
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2129
2498
|
// A variant sync writes overlays on exactly one component branch; say so in the packet world, where a manifest's
|
|
2130
2499
|
// componentBranch is compared. Without it no sync_mutation could satisfy a manifest that declares one.
|
|
2131
2500
|
function syncPacketWorld(response, world, branchName) {
|
|
@@ -2444,6 +2813,120 @@ async function downloadAndSyncGuides(cwd, ctx) {
|
|
|
2444
2813
|
return writeGuidePayloadIntoRepo(cwd, payload);
|
|
2445
2814
|
}
|
|
2446
2815
|
|
|
2816
|
+
function guideSyncSummaryLine(summary) {
|
|
2817
|
+
if (!summary) {
|
|
2818
|
+
return 'guide sync skipped';
|
|
2819
|
+
}
|
|
2820
|
+
const repoCount = Number(summary.repoCount || 0);
|
|
2821
|
+
const updatedRepos = Number(summary.updatedRepos || 0);
|
|
2822
|
+
const updatedFiles = Number(summary.updatedFiles || 0);
|
|
2823
|
+
const skipped = (summary.skipped || []).length;
|
|
2824
|
+
return updatedRepos + '/' + repoCount + ' repo(s) updated, ' + updatedFiles +
|
|
2825
|
+
' file(s) changed' + (skipped ? ', ' + skipped + ' skipped' : '');
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
function guideSyncSessions(requestedBaseUrl = null) {
|
|
2829
|
+
return listSessionsFromStore(readAllSessions()).filter((session) => {
|
|
2830
|
+
if (!session || !session.token) return false;
|
|
2831
|
+
if (!requestedBaseUrl) return true;
|
|
2832
|
+
return normalizeBaseUrl(session.baseUrl || DEFAULT_BASE_URL) === normalizeBaseUrl(requestedBaseUrl);
|
|
2833
|
+
});
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
function guideSessionForRepo(entry, sessions) {
|
|
2837
|
+
const accountId = Number(entry && entry.accountId);
|
|
2838
|
+
return sessions.find((session) => Number(session.accountId) === accountId) || sessions[0] || null;
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
// Refresh every indexed account checkout from the authenticated platform guide payload. This is the
|
|
2842
|
+
// operator-level repair path for "agents are reading stale front-stage docs": it does not wait for each
|
|
2843
|
+
// checkout to happen to run a component command.
|
|
2844
|
+
async function syncGuidesAcrossIndexedRepos(options = {}) {
|
|
2845
|
+
if (options.rescan !== false) {
|
|
2846
|
+
try { discoverAccountRepos(); } catch (_) { /* use the current index */ }
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
const sessions = guideSyncSessions(options.baseUrl || null);
|
|
2850
|
+
const entries = listAccountRepoEntries(readAccountRepoIndex())
|
|
2851
|
+
.sort((a, b) => Number(a.accountId) - Number(b.accountId));
|
|
2852
|
+
const result = {
|
|
2853
|
+
repoCount: entries.length,
|
|
2854
|
+
updatedRepos: 0,
|
|
2855
|
+
unchangedRepos: 0,
|
|
2856
|
+
updatedFiles: 0,
|
|
2857
|
+
removedFiles: 0,
|
|
2858
|
+
skipped: [],
|
|
2859
|
+
repos: []
|
|
2860
|
+
};
|
|
2861
|
+
|
|
2862
|
+
if (!sessions.length) {
|
|
2863
|
+
result.skipped.push({
|
|
2864
|
+
reason: 'no_authenticated_session',
|
|
2865
|
+
message: 'Run remits-cli auth before syncing guides.'
|
|
2866
|
+
});
|
|
2867
|
+
return result;
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
for (const entry of entries) {
|
|
2871
|
+
const repo = {
|
|
2872
|
+
accountId: Number(entry.accountId),
|
|
2873
|
+
name: entry.name || '',
|
|
2874
|
+
directory: entry.directory,
|
|
2875
|
+
updated: [],
|
|
2876
|
+
removed: []
|
|
2877
|
+
};
|
|
2878
|
+
if (!entry.directory || !isAccountCheckout(entry.directory)) {
|
|
2879
|
+
repo.skipped = true;
|
|
2880
|
+
repo.reason = 'not_a_git_checkout';
|
|
2881
|
+
result.skipped.push(repo);
|
|
2882
|
+
result.repos.push(repo);
|
|
2883
|
+
continue;
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
const session = guideSessionForRepo(entry, sessions);
|
|
2887
|
+
if (!session) {
|
|
2888
|
+
repo.skipped = true;
|
|
2889
|
+
repo.reason = 'no_session_for_repo';
|
|
2890
|
+
result.skipped.push(repo);
|
|
2891
|
+
result.repos.push(repo);
|
|
2892
|
+
continue;
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
const baseUrl = normalizeBaseUrl(session.baseUrl || options.baseUrl || DEFAULT_BASE_URL);
|
|
2896
|
+
const summary = await downloadAndSyncGuides(entry.directory, {
|
|
2897
|
+
session,
|
|
2898
|
+
baseUrl,
|
|
2899
|
+
accountId: Number(entry.accountId) || session.accountId
|
|
2900
|
+
});
|
|
2901
|
+
if (!summary) {
|
|
2902
|
+
repo.skipped = true;
|
|
2903
|
+
repo.reason = 'guide_payload_unavailable';
|
|
2904
|
+
result.skipped.push(repo);
|
|
2905
|
+
result.repos.push(repo);
|
|
2906
|
+
continue;
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
repo.updated = summary.updated || [];
|
|
2910
|
+
repo.removed = summary.removed || [];
|
|
2911
|
+
result.updatedFiles += repo.updated.length;
|
|
2912
|
+
result.removedFiles += repo.removed.length;
|
|
2913
|
+
if (repo.updated.length || repo.removed.length) {
|
|
2914
|
+
result.updatedRepos += 1;
|
|
2915
|
+
} else {
|
|
2916
|
+
result.unchangedRepos += 1;
|
|
2917
|
+
}
|
|
2918
|
+
result.repos.push(repo);
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
appendGlobalActivityLog('guides', 'sync_all', {
|
|
2922
|
+
repoCount: result.repoCount,
|
|
2923
|
+
updatedRepos: result.updatedRepos,
|
|
2924
|
+
updatedFiles: result.updatedFiles,
|
|
2925
|
+
skipped: result.skipped.length
|
|
2926
|
+
});
|
|
2927
|
+
return result;
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2447
2930
|
// Ensure the current account repo has the latest guides. Authenticates first
|
|
2448
2931
|
// (interactive terminals only) so guides are only ever delivered to users who
|
|
2449
2932
|
// can authenticate to the platform.
|
|
@@ -3435,11 +3918,76 @@ function sandboxNetworkHint(err) {
|
|
|
3435
3918
|
return ' Network access to ' + baseUrl + ' appears unavailable from the current sandbox. If remits-cli is being run by a local AI agent (for example Codex or Claude Code), retry the command with escalated permissions or outside the sandbox.';
|
|
3436
3919
|
}
|
|
3437
3920
|
|
|
3921
|
+
// The tool inventory is ONE file per checkout, shared by every actor in it, and every refresh replaces it
|
|
3922
|
+
// wholesale. That is fine for one agent and wrong for several: `--as-account`, `--branch` and `--data-mode`
|
|
3923
|
+
// each produce a different inventory, so a second agent's refresh silently becomes the first agent's
|
|
3924
|
+
// "current tools". The file cannot be per-world without breaking the documented path, so instead: write it
|
|
3925
|
+
// atomically (a concurrent reader never sees a half-written file) and return the world it REPLACED, so the
|
|
3926
|
+
// caller can say out loud that it just overwrote somebody else's.
|
|
3927
|
+
function toolsSnapshotWorldSignature(snapshot) {
|
|
3928
|
+
if (!snapshot || typeof snapshot !== 'object') return null;
|
|
3929
|
+
return {
|
|
3930
|
+
accountId: snapshot.accountId,
|
|
3931
|
+
executionAccountId: snapshot.executionAccountId,
|
|
3932
|
+
branchName: snapshot.branchName,
|
|
3933
|
+
variantBranch: snapshot.variantBranch || null,
|
|
3934
|
+
dataMode: snapshot.dataMode
|
|
3935
|
+
};
|
|
3936
|
+
}
|
|
3937
|
+
|
|
3438
3938
|
function saveToolsSnapshot(cwd, data) {
|
|
3439
3939
|
const paths = ensureLocalState(cwd);
|
|
3440
3940
|
const file = path.join(paths.toolsDir, 'tools.json');
|
|
3441
|
-
|
|
3442
|
-
|
|
3941
|
+
let previous = null;
|
|
3942
|
+
try {
|
|
3943
|
+
if (fs.existsSync(file)) previous = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
3944
|
+
} catch (_) {
|
|
3945
|
+
previous = null;
|
|
3946
|
+
}
|
|
3947
|
+
atomicWriteFile(file, JSON.stringify(data, null, 2));
|
|
3948
|
+
const before = toolsSnapshotWorldSignature(previous);
|
|
3949
|
+
const after = toolsSnapshotWorldSignature(data);
|
|
3950
|
+
const replaced = before && JSON.stringify(before) !== JSON.stringify(after) ? before : null;
|
|
3951
|
+
return { file, replaced };
|
|
3952
|
+
}
|
|
3953
|
+
|
|
3954
|
+
// The other half of the shared-snapshot problem: the writer is warned, but the agent that READS
|
|
3955
|
+
// .remits-cli/shared/tools/tools.json — to pick a tool name, or an input shape — gets no signal that the
|
|
3956
|
+
// file describes somebody else's account, branch or data lane. Answered at the moment it matters: the call.
|
|
3957
|
+
function staleToolsSnapshotWarning(cwd, world) {
|
|
3958
|
+
try {
|
|
3959
|
+
const file = path.join(localStatePaths(cwd).toolsDir, 'tools.json');
|
|
3960
|
+
if (!fs.existsSync(file)) return null;
|
|
3961
|
+
const snapshot = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
3962
|
+
const snapshotExec = snapshot.executionAccountId || snapshot.accountId;
|
|
3963
|
+
const mismatch = [];
|
|
3964
|
+
if (snapshotExec != null && String(snapshotExec) !== String(world.executionAccountId)) {
|
|
3965
|
+
mismatch.push('account ' + snapshotExec + ' (this call: ' + world.executionAccountId + ')');
|
|
3966
|
+
}
|
|
3967
|
+
if (snapshot.branchName && String(snapshot.branchName) !== String(world.branchName)) {
|
|
3968
|
+
mismatch.push('branch ' + snapshot.branchName + ' (this call: ' + world.branchName + ')');
|
|
3969
|
+
}
|
|
3970
|
+
if (snapshot.dataMode && String(snapshot.dataMode) !== String(world.dataMode)) {
|
|
3971
|
+
mismatch.push('data ' + snapshot.dataMode + ' (this call: ' + world.dataMode + ')');
|
|
3972
|
+
}
|
|
3973
|
+
if (!mismatch.length) return null;
|
|
3974
|
+
return {
|
|
3975
|
+
code: 'tools_snapshot_other_world',
|
|
3976
|
+
message: '.remits-cli/shared/tools/tools.json was last refreshed for ' + mismatch.join(', ') +
|
|
3977
|
+
'. It is one file per checkout, so it may be another agent\'s. Run `remits-cli tools` with your own ' +
|
|
3978
|
+
'flags before trusting the tool list or an input schema read from it.'
|
|
3979
|
+
};
|
|
3980
|
+
} catch (_) {
|
|
3981
|
+
return null;
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
|
|
3985
|
+
function describeToolsSnapshotWorld(world) {
|
|
3986
|
+
if (!world) return 'unknown world';
|
|
3987
|
+
return ['account ' + (world.executionAccountId || world.accountId || '?'),
|
|
3988
|
+
'branch ' + (world.branchName || '?'),
|
|
3989
|
+
world.variantBranch ? "variant '" + world.variantBranch + "'" : null,
|
|
3990
|
+
'data ' + (world.dataMode || '?')].filter(Boolean).join(', ');
|
|
3443
3991
|
}
|
|
3444
3992
|
|
|
3445
3993
|
async function loggedPost(api, cwd, endpoint, payload, options = {}) {
|
|
@@ -3979,14 +4527,15 @@ async function authCommand(flags) {
|
|
|
3979
4527
|
dataMode: session.dataMode
|
|
3980
4528
|
}).then((r) => r.data);
|
|
3981
4529
|
if (toolsData && toolsData.success) {
|
|
3982
|
-
const
|
|
4530
|
+
const toolsSnapshot = saveToolsSnapshot(cwd, {
|
|
3983
4531
|
accountId: session.accountId,
|
|
4532
|
+
executionAccountId: session.accountId,
|
|
3984
4533
|
branchName,
|
|
3985
4534
|
dataMode: toolsData.dataMode || session.dataMode,
|
|
3986
4535
|
fetchedAt: new Date().toISOString(),
|
|
3987
4536
|
tools: toolsData.tools || []
|
|
3988
4537
|
});
|
|
3989
|
-
console.log('Tools cached at:',
|
|
4538
|
+
console.log('Tools cached at:', toolsSnapshot.file);
|
|
3990
4539
|
}
|
|
3991
4540
|
} catch (err) {
|
|
3992
4541
|
console.log('Warning: failed to refresh tools after auth:', err.message);
|
|
@@ -4152,7 +4701,7 @@ async function pushComponentsCommand(flags) {
|
|
|
4152
4701
|
const api = buildAxios(baseUrl, session.token, stageTimeoutMs(components, flags));
|
|
4153
4702
|
const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
|
|
4154
4703
|
packetType: 'stage',
|
|
4155
|
-
world: { accountId, dataMode, branchName, workspace }
|
|
4704
|
+
world: { repoAccountId: accountId, accountId, executionAccountId: accountId, dataMode, branchName, workspace }
|
|
4156
4705
|
}, { command: 'components stage' });
|
|
4157
4706
|
const stagePayload = {
|
|
4158
4707
|
token: session.token,
|
|
@@ -5434,7 +5983,7 @@ async function syncComponentsCommand(rawFlags) {
|
|
|
5434
5983
|
|
|
5435
5984
|
const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
|
|
5436
5985
|
packetType: dryRun || namesOnly ? 'sync_dry_run' : 'sync_mutation',
|
|
5437
|
-
world: { accountId, dataMode, branchName, workspace }
|
|
5986
|
+
world: { repoAccountId: accountId, accountId, executionAccountId: accountId, dataMode, branchName, workspace }
|
|
5438
5987
|
}, { command: 'components sync' });
|
|
5439
5988
|
|
|
5440
5989
|
printProdDataBanner({
|
|
@@ -6911,7 +7460,7 @@ async function commitComponentsCommand(flags) {
|
|
|
6911
7460
|
buildAxios(flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL, session.token),
|
|
6912
7461
|
cwd, session, accountId, Object.assign({}, flags, { branch: branchName }), {
|
|
6913
7462
|
packetType: 'sync_mutation',
|
|
6914
|
-
world: { accountId, dataMode: resolveDataMode(flags, session), branchName, workspace: resolveWorkspace(cwd, flags) }
|
|
7463
|
+
world: { repoAccountId: accountId, accountId, executionAccountId: accountId, dataMode: resolveDataMode(flags, session), branchName, workspace: resolveWorkspace(cwd, flags) }
|
|
6915
7464
|
}, { command: 'components commit' });
|
|
6916
7465
|
|
|
6917
7466
|
// Landing is serial and nothing can make it concurrent: `git add -A` sweeps a shared checkout, and the
|
|
@@ -7614,17 +8163,31 @@ function runWorldLines(world, context = {}) {
|
|
|
7614
8163
|
if (!world || typeof world !== 'object') return [];
|
|
7615
8164
|
const lines = ['World:'];
|
|
7616
8165
|
if (context.host) lines.push(' host: ' + context.host);
|
|
8166
|
+
const scopeAccount = world.scopeRootAccountId || world.repoAccountId || world.checkoutAccountId || context.scopeRootAccountId;
|
|
8167
|
+
if (scopeAccount) lines.push(' scoped from: account ' + scopeAccount);
|
|
7617
8168
|
const accounts = ['account ' + (world.executionAccountId || context.accountId || 'unknown')];
|
|
7618
8169
|
if (world.checkoutAccountId && world.checkoutAccountId !== world.executionAccountId) accounts.push('checkout ' + world.checkoutAccountId);
|
|
7619
8170
|
if (world.componentOwnerAccountId && world.componentOwnerAccountId !== world.executionAccountId) accounts.push('component owner ' + world.componentOwnerAccountId);
|
|
7620
8171
|
lines.push(' runs as: ' + accounts.join(', '));
|
|
8172
|
+
if (world.targetAccountId && world.targetAccountId !== world.executionAccountId) {
|
|
8173
|
+
lines.push(' targets: account ' + world.targetAccountId);
|
|
8174
|
+
}
|
|
7621
8175
|
lines.push(' data lane: ' + (world.dataMode || context.dataMode || 'unknown'));
|
|
7622
8176
|
const contextVariantWorld = context.variantWorld && typeof context.variantWorld === 'object' ? context.variantWorld : {};
|
|
7623
8177
|
const subscribedComponentBranch = world.subscribedComponentBranch || contextVariantWorld.subscribedComponentBranch;
|
|
7624
8178
|
const componentBranch = world.componentBranch || subscribedComponentBranch || contextVariantWorld.componentBranch || world.variantBranch;
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
8179
|
+
// The platform owns this answer (remits.cli.CliVariantWorld). Reporting a locally re-derived source is how
|
|
8180
|
+
// a line ends up contradicting itself: an --variant-branch probe REPLACES the subscription, and a variant
|
|
8181
|
+
// branch's working tree decides the world before any edge is consulted, but both resolve to a branch the
|
|
8182
|
+
// account often also subscribes to — so "componentBranch == subscribedComponentBranch" cannot mean
|
|
8183
|
+
// "the subscription chose it". Only fill the source in when the platform reports the TRUNK case (the git
|
|
8184
|
+
// branch named no world, so the account's own subscription decided) or reports nothing at all.
|
|
8185
|
+
const resolvedBranchSource = world.componentBranchSource || contextVariantWorld.componentBranchSource ||
|
|
8186
|
+
world.variantBranchSource || contextVariantWorld.variantBranchSource;
|
|
8187
|
+
const subscriptionDecided = componentBranch && subscribedComponentBranch &&
|
|
8188
|
+
String(componentBranch) === String(subscribedComponentBranch) &&
|
|
8189
|
+
(!resolvedBranchSource || resolvedBranchSource === 'trunk' || resolvedBranchSource === 'no-branch');
|
|
8190
|
+
const branchSource = subscriptionDecided ? 'subscription' : resolvedBranchSource;
|
|
7628
8191
|
const branchLabel = componentBranch
|
|
7629
8192
|
? "componentBranch=" + componentBranch + (branchSource ? ' via ' + branchSource : '')
|
|
7630
8193
|
: (branchSource || '');
|
|
@@ -7649,6 +8212,9 @@ function runWorldLines(world, context = {}) {
|
|
|
7649
8212
|
lines.push(' test component: ' + world.testComponentSource + (world.testComponentSignature ? ' ' + shortSignature(world.testComponentSignature) : '') +
|
|
7650
8213
|
(world.testComponentBranch ? " variant '" + world.testComponentBranch + "'" : ''));
|
|
7651
8214
|
}
|
|
8215
|
+
if (world.componentSource) {
|
|
8216
|
+
lines.push(' component: ' + world.componentSource + (world.componentSignature ? ' ' + shortSignature(world.componentSignature) : ''));
|
|
8217
|
+
}
|
|
7652
8218
|
lines.push(...stagedMaskedByVariantLines(world.stagedMaskedByVariant, world.executionAccountId || context.accountId));
|
|
7653
8219
|
return lines;
|
|
7654
8220
|
}
|
|
@@ -7878,7 +8444,8 @@ async function testCommand(flags) {
|
|
|
7878
8444
|
const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
|
|
7879
8445
|
packetType: 'test_run',
|
|
7880
8446
|
world: {
|
|
7881
|
-
accountId,
|
|
8447
|
+
repoAccountId: accountId,
|
|
8448
|
+
accountId: asAccountId ? Number(asAccountId) : Number(accountId),
|
|
7882
8449
|
executionAccountId: asAccountId ? Number(asAccountId) : Number(accountId),
|
|
7883
8450
|
dataMode,
|
|
7884
8451
|
branchName,
|
|
@@ -8208,7 +8775,8 @@ async function tokenCommand(flags) {
|
|
|
8208
8775
|
const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
|
|
8209
8776
|
packetType: 'token_inspect',
|
|
8210
8777
|
world: {
|
|
8211
|
-
accountId,
|
|
8778
|
+
repoAccountId: accountId,
|
|
8779
|
+
accountId: tokenAsAccountId ? Number(tokenAsAccountId) : Number(accountId),
|
|
8212
8780
|
executionAccountId: tokenAsAccountId ? Number(tokenAsAccountId) : Number(accountId),
|
|
8213
8781
|
dataMode,
|
|
8214
8782
|
branchName,
|
|
@@ -8250,6 +8818,12 @@ async function tokenCommand(flags) {
|
|
|
8250
8818
|
baseUrl: normalizeBaseUrl(baseUrl),
|
|
8251
8819
|
tokenKey: data.tokenKey,
|
|
8252
8820
|
accountId: data.accountId,
|
|
8821
|
+
executionAccountId: data.accountId,
|
|
8822
|
+
// The account the command was addressed to, i.e. the repo/checkout account. Distinct from `accountId`
|
|
8823
|
+
// (which account the URL EXECUTES as, moved by --as-account) and from
|
|
8824
|
+
// `resolution.componentOwnerAccountId` (whose components run). Three different accounts on a subscriber
|
|
8825
|
+
// token, and the response used to carry only one of them.
|
|
8826
|
+
repoAccountId: data.ownerAccountId,
|
|
8253
8827
|
branchName: data.branchName,
|
|
8254
8828
|
variantBranch: data.variantBranch,
|
|
8255
8829
|
// Why the variant branch is (or is not) set — the platform's one rule, so a null is explainable.
|
|
@@ -8293,6 +8867,29 @@ async function tokenCommand(flags) {
|
|
|
8293
8867
|
printSessionResolutionWarning(sessionContext);
|
|
8294
8868
|
printLocalCommandContext(cwd, flags, { accountId: output.accountId || accountId, branchName, workspace: output.workspace, dataMode: output.dataMode || dataMode });
|
|
8295
8869
|
printLocalStateWarnings(cwd, flags);
|
|
8870
|
+
// The same World block `tools`, `tool` and `test run` print. A token is the surface that hands a browser
|
|
8871
|
+
// a whole resolved world, and it was the one that made the reader dig it out of a JSON dump — including
|
|
8872
|
+
// the `resolution.summary` sentence that says, in words, which account's components the page will run.
|
|
8873
|
+
runWorldLines({
|
|
8874
|
+
accountId: output.accountId,
|
|
8875
|
+
executionAccountId: output.accountId,
|
|
8876
|
+
checkoutAccountId: output.repoAccountId,
|
|
8877
|
+
repoAccountId: output.repoAccountId,
|
|
8878
|
+
componentOwnerAccountId: output.resolution && output.resolution.componentOwnerAccountId,
|
|
8879
|
+
dataMode: output.dataMode || dataMode,
|
|
8880
|
+
branchName: output.branchName || branchName,
|
|
8881
|
+
workspace: output.workspace,
|
|
8882
|
+
stagingLane: output.stagingLane,
|
|
8883
|
+
componentBranch: output.variantWorld && output.variantWorld.componentBranch,
|
|
8884
|
+
subscribedComponentBranch: output.variantWorld && output.variantWorld.subscribedComponentBranch,
|
|
8885
|
+
variantBranch: output.variantBranch,
|
|
8886
|
+
variantBranchSource: output.variantBranchSource,
|
|
8887
|
+
resolves: output.variantWorld && output.variantWorld.resolves,
|
|
8888
|
+
stagedMaskedByVariant: output.stagedMaskedByVariant
|
|
8889
|
+
}, { host: normalizeBaseUrl(baseUrl) }).forEach((line) => console.log(line));
|
|
8890
|
+
if (output.resolution && output.resolution.summary) {
|
|
8891
|
+
console.log(' resolves as: ' + output.resolution.summary);
|
|
8892
|
+
}
|
|
8296
8893
|
}
|
|
8297
8894
|
console.log(JSON.stringify(output, null, 2));
|
|
8298
8895
|
|
|
@@ -8300,7 +8897,16 @@ async function tokenCommand(flags) {
|
|
|
8300
8897
|
type: 'token_inspect',
|
|
8301
8898
|
success: true,
|
|
8302
8899
|
claim: 'Verification token minted',
|
|
8303
|
-
world: buildCommandWorld(output, {
|
|
8900
|
+
world: buildCommandWorld(output, {
|
|
8901
|
+
repoAccountId: output.repoAccountId || accountId,
|
|
8902
|
+
checkoutAccountId: output.repoAccountId || accountId,
|
|
8903
|
+
accountId: output.executionAccountId || output.accountId || accountId,
|
|
8904
|
+
executionAccountId: output.executionAccountId || output.accountId || accountId,
|
|
8905
|
+
dataMode: output.dataMode || dataMode,
|
|
8906
|
+
branchName,
|
|
8907
|
+
workspace: output.workspace,
|
|
8908
|
+
host: normalizeBaseUrl(baseUrl)
|
|
8909
|
+
}),
|
|
8304
8910
|
revision: collectVerificationSource(cwd, flags),
|
|
8305
8911
|
browser: {
|
|
8306
8912
|
tokenKeyHash: output.tokenKey ? sha256(String(output.tokenKey)) : null,
|
|
@@ -8369,6 +8975,7 @@ async function toolsCommand(flags) {
|
|
|
8369
8975
|
const data = await loggedPost(api, cwd, '/cli/tools', {
|
|
8370
8976
|
token: session.token,
|
|
8371
8977
|
accountId,
|
|
8978
|
+
asAccountId: flags['as-account'] || flags['as-account-id'],
|
|
8372
8979
|
variantBranch,
|
|
8373
8980
|
branchName,
|
|
8374
8981
|
workspace,
|
|
@@ -8379,12 +8986,17 @@ async function toolsCommand(flags) {
|
|
|
8379
8986
|
throw new Error('Failed to fetch tools');
|
|
8380
8987
|
}
|
|
8381
8988
|
|
|
8382
|
-
const
|
|
8383
|
-
accountId,
|
|
8989
|
+
const toolsSnapshot = saveToolsSnapshot(cwd, {
|
|
8990
|
+
accountId: data.accountId || accountId,
|
|
8991
|
+
repoAccountId: data.repoAccountId || accountId,
|
|
8992
|
+
checkoutAccountId: data.checkoutAccountId || accountId,
|
|
8993
|
+
executionAccountId: data.executionAccountId || data.accountId || accountId,
|
|
8384
8994
|
baseUrl: normalizeBaseUrl(baseUrl),
|
|
8385
|
-
branchName,
|
|
8995
|
+
branchName: data.branchName || branchName,
|
|
8386
8996
|
variantBranch: data.variantBranch || variantBranch,
|
|
8387
8997
|
dataMode: data.dataMode || dataMode,
|
|
8998
|
+
world: data.world,
|
|
8999
|
+
staging: data.staging,
|
|
8388
9000
|
fetchedAt: new Date().toISOString(),
|
|
8389
9001
|
tools: data.tools || []
|
|
8390
9002
|
});
|
|
@@ -8392,10 +9004,17 @@ async function toolsCommand(flags) {
|
|
|
8392
9004
|
printSessionResolutionWarning(sessionContext);
|
|
8393
9005
|
printResolvedBaseUrl(baseUrl);
|
|
8394
9006
|
console.log('Tools refreshed.');
|
|
9007
|
+
if (data.world) runWorldLines(data.world, { host: normalizeBaseUrl(baseUrl), gitHead: safeGitValue(cwd, 'git rev-parse HEAD') }).forEach((line) => console.log(line));
|
|
8395
9008
|
console.log('Data mode:', data.dataMode || dataMode);
|
|
8396
9009
|
if (data.variantBranch || variantBranch) console.log('Variant branch:', data.variantBranch || variantBranch);
|
|
8397
|
-
console.log('Tools file:',
|
|
9010
|
+
console.log('Tools file:', toolsSnapshot.file);
|
|
8398
9011
|
console.log('Tool count:', (data.tools || []).length);
|
|
9012
|
+
if (toolsSnapshot.replaced) {
|
|
9013
|
+
console.error('Shared snapshot warning: this refresh replaced the tool inventory for ' +
|
|
9014
|
+
describeToolsSnapshotWorld(toolsSnapshot.replaced) + '. ' +
|
|
9015
|
+
'.remits-cli/shared/tools/tools.json is one file per checkout, so another agent reading it now sees ' +
|
|
9016
|
+
'YOUR world. Re-run `remits-cli tools` with your own flags before trusting it.');
|
|
9017
|
+
}
|
|
8399
9018
|
}
|
|
8400
9019
|
|
|
8401
9020
|
function parseToolInput(flags) {
|
|
@@ -8429,10 +9048,18 @@ function applyScopeFlags(input, flags) {
|
|
|
8429
9048
|
merged.scope = normalized;
|
|
8430
9049
|
}
|
|
8431
9050
|
|
|
8432
|
-
const targetAccountId = flags && (flags['target-account-id'] || flags.targetAccountId);
|
|
9051
|
+
const targetAccountId = flags && (flags['target-account'] || flags.targetAccount || flags['target-account-id'] || flags.targetAccountId);
|
|
8433
9052
|
if (targetAccountId && merged.targetAccountId === undefined) {
|
|
8434
9053
|
merged.targetAccountId = String(targetAccountId).trim();
|
|
8435
9054
|
}
|
|
9055
|
+
// ...and as `accountId`, because that is the key the platform's account-targeting tools actually read
|
|
9056
|
+
// (mcp_run_action resolves `input.accountId`, falling back to the RUN SCOPE when it is absent). Without
|
|
9057
|
+
// this, `--target-account 36` printed "targets: account 36" in the World block while the tool ran against
|
|
9058
|
+
// the scope account — a flag that reads like a target and silently is not. An explicit `accountId` in
|
|
9059
|
+
// --input still wins, like every other scope flag.
|
|
9060
|
+
if (targetAccountId && merged.accountId === undefined && merged.account_id === undefined) {
|
|
9061
|
+
merged.accountId = String(targetAccountId).trim();
|
|
9062
|
+
}
|
|
8436
9063
|
|
|
8437
9064
|
const anchorAccountId = flags && (flags['anchor-account-id'] || flags.anchorAccountId);
|
|
8438
9065
|
if (anchorAccountId && merged.anchorAccountId === undefined) {
|
|
@@ -8477,6 +9104,8 @@ async function toolCommand(flags) {
|
|
|
8477
9104
|
const branchName = flags.branch || currentBranch(cwd);
|
|
8478
9105
|
const dataMode = resolveDataMode(flags, session);
|
|
8479
9106
|
const variantBranch = flags['variant-branch'];
|
|
9107
|
+
const asAccountId = flags['as-account'] || flags['as-account-id'] || flags.asAccountId;
|
|
9108
|
+
const targetAccountId = flags['target-account'] || flags.targetAccount || flags['target-account-id'] || flags.targetAccountId;
|
|
8480
9109
|
const timeoutMs = parsePositiveInt(flags['timeout-ms'] || flags.timeoutMs, 60000);
|
|
8481
9110
|
const callId = String(flags['call-id'] || flags.callId || crypto.randomUUID());
|
|
8482
9111
|
const asyncMode = flagEnabled(flags.async) || flagEnabled(input.async);
|
|
@@ -8512,6 +9141,24 @@ async function toolCommand(flags) {
|
|
|
8512
9141
|
data.responseFile = statusResponse.responseFile;
|
|
8513
9142
|
data.sessionLog = sessionJsonlFile(cwd);
|
|
8514
9143
|
data.localState = localStateSummary(cwd, flags);
|
|
9144
|
+
const statusBranchName = flags.branch || data.branchName || (stored && stored.branchName) || branchName;
|
|
9145
|
+
const statusWorkspace = data.workspace !== undefined ? data.workspace : resolveWorkspace(cwd, flags);
|
|
9146
|
+
const statusWorld = buildToolStatusWorld(data, stored, {
|
|
9147
|
+
repoAccountId: accountId,
|
|
9148
|
+
accountId: statusAccountId,
|
|
9149
|
+
checkoutAccountId: accountId,
|
|
9150
|
+
dataMode: data.dataMode || dataMode,
|
|
9151
|
+
branchName: statusBranchName,
|
|
9152
|
+
workspace: statusWorkspace,
|
|
9153
|
+
host: normalizeBaseUrl(baseUrl)
|
|
9154
|
+
});
|
|
9155
|
+
recordWorkstreamArtifact(cwd, flags, {
|
|
9156
|
+
type: 'tool_status',
|
|
9157
|
+
callId: requestedCallId,
|
|
9158
|
+
path: statusResponse.responseFile,
|
|
9159
|
+
world: statusWorld,
|
|
9160
|
+
workspace: data.workspace
|
|
9161
|
+
});
|
|
8515
9162
|
// A completed run can still carry a tool-level refusal — see toolResultFailed.
|
|
8516
9163
|
const polledFailed = data.toolSuccess === false || toolResultFailed(data.result);
|
|
8517
9164
|
if (polledFailed) {
|
|
@@ -8521,13 +9168,13 @@ async function toolCommand(flags) {
|
|
|
8521
9168
|
// Same question as the call that started it: a poll must not attach where the call itself would not.
|
|
8522
9169
|
const statusVerification = await verificationPreflight(api, cwd, session, accountId, flags, {
|
|
8523
9170
|
packetType: 'tool_call',
|
|
8524
|
-
world:
|
|
9171
|
+
world: statusWorld
|
|
8525
9172
|
}, { command: 'tool status', neverRefuse: true, quiet: jsonOutput });
|
|
8526
9173
|
await appendVerificationPacket(api, cwd, session, accountId, statusVerification.evidenceFlags, {
|
|
8527
9174
|
type: 'tool_call',
|
|
8528
9175
|
success: data.status !== 'failed' && !polledFailed,
|
|
8529
9176
|
claim: 'Tool status ' + requestedCallId,
|
|
8530
|
-
world:
|
|
9177
|
+
world: statusWorld,
|
|
8531
9178
|
revision: collectVerificationSource(cwd, flags),
|
|
8532
9179
|
evidenceCategories: ['tool_call'],
|
|
8533
9180
|
rawRefs: { toolResponsePath: statusResponse.responseFile, callId: requestedCallId },
|
|
@@ -8542,6 +9189,7 @@ async function toolCommand(flags) {
|
|
|
8542
9189
|
printLocalCommandContext(cwd, flags, { accountId: statusAccountId, branchName: flags.branch || (stored && stored.branchName) || branchName, dataMode: data.dataMode || dataMode });
|
|
8543
9190
|
printLocalStateWarnings(cwd, flags);
|
|
8544
9191
|
console.log('Tool call status:', data.status);
|
|
9192
|
+
if (data.world) runWorldLines(data.world, { host: normalizeBaseUrl(baseUrl) }).forEach((line) => console.log(line));
|
|
8545
9193
|
console.log('Call ID:', requestedCallId);
|
|
8546
9194
|
console.log('Data mode:', data.dataMode || dataMode);
|
|
8547
9195
|
if (data.threadGroupingId) console.log('Thread grouping ID:', data.threadGroupingId);
|
|
@@ -8564,15 +9212,49 @@ async function toolCommand(flags) {
|
|
|
8564
9212
|
});
|
|
8565
9213
|
|
|
8566
9214
|
const toolWorkspace = resolveWorkspace(cwd, flags);
|
|
9215
|
+
const legacyInputAccountId = input && (input.accountId || input.account_id);
|
|
9216
|
+
// A STRUCTURED warning, not only a printed one: agents run with --json, and `input.accountId` no longer
|
|
9217
|
+
// decides the execution account, so the one signal that the command's component resolution belongs to a
|
|
9218
|
+
// different account than the tool's data must survive into the payload the agent actually reads.
|
|
9219
|
+
const toolWarnings = [];
|
|
9220
|
+
if (legacyInputAccountId && !asAccountId && !targetAccountId && String(legacyInputAccountId) !== String(accountId)) {
|
|
9221
|
+
toolWarnings.push({
|
|
9222
|
+
code: 'legacy_input_account_id',
|
|
9223
|
+
inputAccountId: String(legacyInputAccountId),
|
|
9224
|
+
scopeAccountId: String(accountId),
|
|
9225
|
+
message: 'input.accountId=' + legacyInputAccountId +
|
|
9226
|
+
' is treated as a tool target while the command stays scoped from account ' + accountId +
|
|
9227
|
+
'. Use --as-account ' + legacyInputAccountId + ' for execution-account semantics, or --target-account ' + legacyInputAccountId + ' to make the target explicit.'
|
|
9228
|
+
});
|
|
9229
|
+
}
|
|
9230
|
+
const snapshotWarning = staleToolsSnapshotWarning(cwd, {
|
|
9231
|
+
executionAccountId: asAccountId || accountId,
|
|
9232
|
+
branchName,
|
|
9233
|
+
dataMode
|
|
9234
|
+
});
|
|
9235
|
+
if (snapshotWarning) toolWarnings.push(snapshotWarning);
|
|
9236
|
+
// stderr always: it never corrupts --json stdout, and suppressing it there hid the warning from exactly
|
|
9237
|
+
// the callers that cannot see the human output.
|
|
9238
|
+
toolWarnings.forEach((warning) => console.error('Warning: ' + warning.message));
|
|
8567
9239
|
const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
|
|
8568
9240
|
packetType: 'tool_call',
|
|
8569
|
-
world: {
|
|
9241
|
+
world: {
|
|
9242
|
+
repoAccountId: accountId,
|
|
9243
|
+
accountId: asAccountId || accountId,
|
|
9244
|
+
executionAccountId: asAccountId || accountId,
|
|
9245
|
+
targetAccountId: targetAccountId || legacyInputAccountId,
|
|
9246
|
+
dataMode,
|
|
9247
|
+
branchName,
|
|
9248
|
+
workspace: toolWorkspace
|
|
9249
|
+
}
|
|
8570
9250
|
}, { command: 'tool --name ' + String(toolName) });
|
|
8571
9251
|
|
|
8572
9252
|
const response = await loggedPost(api, cwd, '/cli/tool', {
|
|
8573
9253
|
token: session.token,
|
|
8574
9254
|
accountId,
|
|
8575
9255
|
accountIdExplicit,
|
|
9256
|
+
asAccountId,
|
|
9257
|
+
targetAccountId: targetAccountId || undefined,
|
|
8576
9258
|
// The repo/session account, sent as a FIRST-CLASS value rather than left to be inferred from
|
|
8577
9259
|
// `accountId`: a read tool may reinterpret its own `input.accountId`, and the scope root — what bounds
|
|
8578
9260
|
// what the call may discover — has to survive that. The server validates it; it is never authorization.
|
|
@@ -8602,6 +9284,15 @@ async function toolCommand(flags) {
|
|
|
8602
9284
|
data.responseFile = response.responseFile;
|
|
8603
9285
|
data.sessionLog = sessionJsonlFile(cwd);
|
|
8604
9286
|
data.localState = localStateSummary(cwd, flags);
|
|
9287
|
+
if (toolWarnings.length) data.warnings = (data.warnings || []).concat(toolWarnings);
|
|
9288
|
+
recordWorkstreamArtifact(cwd, flags, {
|
|
9289
|
+
type: asyncMode ? 'tool_async_start' : 'tool_call',
|
|
9290
|
+
callId,
|
|
9291
|
+
path: response.responseFile,
|
|
9292
|
+
tool: String(toolName),
|
|
9293
|
+
world: buildCommandWorld(data, { repoAccountId: accountId, accountId: data.executionAccountId || asAccountId || accountId, dataMode: data.dataMode || dataMode, branchName, workspace: toolWorkspace, host: normalizeBaseUrl(baseUrl) }),
|
|
9294
|
+
workspace: data.workspace || toolWorkspace
|
|
9295
|
+
});
|
|
8605
9296
|
if (toolFailureMessage && !data.toolMessage) data.toolMessage = toolFailureMessage;
|
|
8606
9297
|
|
|
8607
9298
|
let evidenceData = data;
|
|
@@ -8614,6 +9305,7 @@ async function toolCommand(flags) {
|
|
|
8614
9305
|
printLocalStateWarnings(cwd, flags);
|
|
8615
9306
|
console.log(asyncMode ? 'Tool call started.'
|
|
8616
9307
|
: (toolFailed ? 'Tool call FAILED — the tool ran and returned an error.' : 'Tool call succeeded.'));
|
|
9308
|
+
if (data.world) runWorldLines(data.world, { host: normalizeBaseUrl(baseUrl), gitHead: safeGitValue(cwd, 'git rev-parse HEAD') }).forEach((line) => console.log(line));
|
|
8617
9309
|
if (toolFailed && toolFailureMessage) console.log('Tool error:', toolFailureMessage);
|
|
8618
9310
|
console.log('Call ID:', callId);
|
|
8619
9311
|
console.log('Data mode:', data.dataMode || dataMode);
|
|
@@ -8646,10 +9338,26 @@ async function toolCommand(flags) {
|
|
|
8646
9338
|
|
|
8647
9339
|
finalStatus.data.responseFile = finalStatus.responseFile;
|
|
8648
9340
|
finalStatus.data.sessionLog = sessionJsonlFile(cwd);
|
|
9341
|
+
recordWorkstreamArtifact(cwd, flags, {
|
|
9342
|
+
type: 'tool_async_final',
|
|
9343
|
+
callId,
|
|
9344
|
+
path: finalStatus.responseFile,
|
|
9345
|
+
tool: String(toolName),
|
|
9346
|
+
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) }),
|
|
9347
|
+
workspace: finalStatus.data.workspace || toolWorkspace
|
|
9348
|
+
});
|
|
8649
9349
|
evidenceData = finalStatus.data;
|
|
8650
9350
|
evidenceResponseFile = finalStatus.responseFile;
|
|
8651
9351
|
if (!jsonOutput) {
|
|
8652
9352
|
console.log('Final status:', finalStatus.data.status);
|
|
9353
|
+
if (finalStatus.data.world) {
|
|
9354
|
+
runWorldLines(finalStatus.data.world, { host: normalizeBaseUrl(baseUrl), gitHead: safeGitValue(cwd, 'git rev-parse HEAD') }).forEach((line) => console.log(line));
|
|
9355
|
+
const startWorldText = JSON.stringify(data.world || {});
|
|
9356
|
+
const finalWorldText = JSON.stringify(finalStatus.data.world || {});
|
|
9357
|
+
if (data.world && startWorldText !== finalWorldText) {
|
|
9358
|
+
console.log('World changed since start: inspect the final response file for the exact resolved execution world.');
|
|
9359
|
+
}
|
|
9360
|
+
}
|
|
8653
9361
|
printToolResponseFileLine('Tool response file', finalStatus.responseFile);
|
|
8654
9362
|
}
|
|
8655
9363
|
if (finalStatus.data.status !== 'completed') {
|
|
@@ -8663,7 +9371,20 @@ async function toolCommand(flags) {
|
|
|
8663
9371
|
success: asyncMode && !waitForAsync ? null : (!toolFailed && (!asyncMode || process.exitCode !== 1)),
|
|
8664
9372
|
pending: asyncMode && !waitForAsync,
|
|
8665
9373
|
claim: 'Tool call ' + String(toolName),
|
|
8666
|
-
world: buildCommandWorld(evidenceData, {
|
|
9374
|
+
world: buildCommandWorld(evidenceData, {
|
|
9375
|
+
accountId: evidenceData.executionAccountId || asAccountId || evidenceData.accountId || accountId,
|
|
9376
|
+
repoAccountId: accountId,
|
|
9377
|
+
checkoutAccountId: accountId,
|
|
9378
|
+
executionAccountId: evidenceData.executionAccountId || asAccountId || accountId,
|
|
9379
|
+
targetAccountId: evidenceData.targetAccountId || targetAccountId || legacyInputAccountId,
|
|
9380
|
+
dataMode: evidenceData.dataMode || dataMode,
|
|
9381
|
+
branchName: evidenceData.branchName || branchName,
|
|
9382
|
+
workspace: resolveWorkspace(cwd, flags),
|
|
9383
|
+
host: normalizeBaseUrl(baseUrl),
|
|
9384
|
+
componentSource: evidenceData.componentSource,
|
|
9385
|
+
componentSignature: evidenceData.componentSignature,
|
|
9386
|
+
sourceLayer: evidenceData.componentSource
|
|
9387
|
+
}),
|
|
8667
9388
|
revision: Object.assign(collectVerificationSource(cwd, flags), {
|
|
8668
9389
|
compileSignatures: evidenceData.componentSignature ? [evidenceData.componentSignature] : []
|
|
8669
9390
|
}),
|
|
@@ -8994,10 +9715,16 @@ async function verifyCommand(flags, subcommand) {
|
|
|
8994
9715
|
if (flags.manifest || flags.file) {
|
|
8995
9716
|
manifest = parseManifestFile(flags.manifest || flags.file);
|
|
8996
9717
|
}
|
|
8997
|
-
|
|
9718
|
+
// PROD only, and framed as risk rather than as an unmet obligation. An envelope with no contract is an
|
|
9719
|
+
// evidence log, which is the normal and complete mode; warning about it on every lane taught agents that
|
|
9720
|
+
// their own record-keeping was an exam they were failing, and they responded by starting it over.
|
|
9721
|
+
if (dataMode === 'prod' && !manifestRequiredEvidence(manifest).length && !manifestClaims(manifest).length &&
|
|
9722
|
+
!flagEnabled(flags['no-contract'])) {
|
|
8998
9723
|
console.error('');
|
|
8999
|
-
console.error('
|
|
9000
|
-
console.error('
|
|
9724
|
+
console.error('Note: this envelope records PRODUCTION-data evidence with no acceptance contract.');
|
|
9725
|
+
console.error('That is fine for investigation. Before destructive or irreversible production work, name what');
|
|
9726
|
+
console.error('must be true so the result is checkable by someone else:');
|
|
9727
|
+
console.error(' remits-cli verify claim <id> --text "what must be true" [--test "<suite>"]');
|
|
9001
9728
|
console.error('');
|
|
9002
9729
|
}
|
|
9003
9730
|
let statusResponse = null;
|
|
@@ -9027,12 +9754,17 @@ async function verifyCommand(flags, subcommand) {
|
|
|
9027
9754
|
ticketId: flags.ticket,
|
|
9028
9755
|
account: collectVerificationAccount(cwd, accountId, baseUrl, dataMode),
|
|
9029
9756
|
source,
|
|
9030
|
-
manifest
|
|
9757
|
+
manifest,
|
|
9758
|
+
noContract: flagEnabled(flags['no-contract']) || undefined
|
|
9031
9759
|
});
|
|
9032
9760
|
const envelope = response.envelope;
|
|
9033
9761
|
writeLocalVerificationEnvelope(cwd, envelope);
|
|
9034
9762
|
writeActiveVerificationEnvelope(cwd, envelope.envelopeId, activeContext);
|
|
9035
9763
|
console.log('Verification envelope started:', envelope.envelopeId);
|
|
9764
|
+
if (!manifestRequiredEvidence(manifest).length && !manifestClaims(manifest).length) {
|
|
9765
|
+
console.log('Mode: evidence log (no acceptance contract asked for). Commands attach world-stamped evidence;');
|
|
9766
|
+
console.log(' nothing is outstanding. Add `verify claim <id> --text "..."` only if someone needs a verdict.');
|
|
9767
|
+
}
|
|
9036
9768
|
printLocalCommandContext(cwd, flags, { accountId, branchName, workspace, dataMode });
|
|
9037
9769
|
printLocalStateWarnings(cwd, flags);
|
|
9038
9770
|
console.log('Target:', checkoutWorldLine + ', dataMode=' + dataMode +
|
|
@@ -9063,7 +9795,11 @@ async function verifyCommand(flags, subcommand) {
|
|
|
9063
9795
|
return response;
|
|
9064
9796
|
}
|
|
9065
9797
|
|
|
9066
|
-
const
|
|
9798
|
+
const positionalEnvelopeId = flags._ && flags._[2];
|
|
9799
|
+
const showLikeSubcommands = ['show', 'read', 'get', 'packets', 'status', 'report'];
|
|
9800
|
+
const explicitEnvelopeId = flags.envelope || flags['verify-envelope'] || flags.id ||
|
|
9801
|
+
(showLikeSubcommands.includes(sub) ? positionalEnvelopeId : null);
|
|
9802
|
+
const envelopeId = explicitEnvelopeId || readActiveVerificationEnvelope(cwd, activeContext);
|
|
9067
9803
|
if (!envelopeId) {
|
|
9068
9804
|
throw new Error('No verification envelope selected. Run `remits-cli verify start --summary "..."` or pass --envelope <id>.');
|
|
9069
9805
|
}
|
|
@@ -9108,6 +9844,40 @@ async function verifyCommand(flags, subcommand) {
|
|
|
9108
9844
|
return envelope;
|
|
9109
9845
|
}
|
|
9110
9846
|
|
|
9847
|
+
if (sub === 'claim' || sub === 'claims') {
|
|
9848
|
+
const claimId = flags.id || (flags._ && flags._[2]);
|
|
9849
|
+
const text = flags.text || flags.claim || flags.summary || flags.note;
|
|
9850
|
+
if (!claimId) {
|
|
9851
|
+
throw new Error('Usage: remits-cli verify claim <id> --text "what must be true" [--test "<suite>"] [--packet-type <type>]');
|
|
9852
|
+
}
|
|
9853
|
+
const claim = { id: String(claimId) };
|
|
9854
|
+
if (text) claim.text = String(text);
|
|
9855
|
+
// A claim may name the shape of evidence that proves it, so one line of contract can bind to a suite
|
|
9856
|
+
// instead of needing a second parallel requiredEvidence list kept in sync by hand.
|
|
9857
|
+
const suite = flags.test || flags.suite;
|
|
9858
|
+
if (suite) {
|
|
9859
|
+
claim.packetType = 'test_run';
|
|
9860
|
+
claim.suite = String(suite);
|
|
9861
|
+
if (flags.case || flags.cases) {
|
|
9862
|
+
claim.cases = [].concat(flags.case || []).concat(flags.cases || [])
|
|
9863
|
+
.flatMap((value) => String(value).split(',')).map((value) => value.trim()).filter(Boolean);
|
|
9864
|
+
}
|
|
9865
|
+
} else if (flags['packet-type'] || flags.packetType) {
|
|
9866
|
+
claim.packetType = String(flags['packet-type'] || flags.packetType);
|
|
9867
|
+
}
|
|
9868
|
+
const response = await postVerificationCommand(api, cwd, session, accountId, 'claim', { envelopeId, claim });
|
|
9869
|
+
writeLocalVerificationEnvelope(cwd, response.envelope);
|
|
9870
|
+
const acceptance = (response.envelope || {}).acceptance || {};
|
|
9871
|
+
console.log('Claim recorded on envelope ' + envelopeId + ': ' + claim.id);
|
|
9872
|
+
console.log('Contract now requires ' + (acceptance.requiredEvidence || []).length + ' item(s).');
|
|
9873
|
+
console.log('Prove it by adding --claim ' + claim.id + ' to the command that demonstrates it, e.g.');
|
|
9874
|
+
console.log(' remits-cli verify test --test "<suite>" --claim ' + claim.id);
|
|
9875
|
+
console.log(' remits-cli verify attach --claim ' + claim.id + ' --note "what you observed"');
|
|
9876
|
+
printAcceptanceWarnings(response.envelope);
|
|
9877
|
+
printEnvelopeWarnings(response.envelope);
|
|
9878
|
+
return response.envelope;
|
|
9879
|
+
}
|
|
9880
|
+
|
|
9111
9881
|
if (sub === 'attach') {
|
|
9112
9882
|
const artifacts = [];
|
|
9113
9883
|
let type = 'manual_observation';
|
|
@@ -9144,7 +9914,7 @@ async function verifyCommand(flags, subcommand) {
|
|
|
9144
9914
|
artifacts,
|
|
9145
9915
|
evidenceCategories
|
|
9146
9916
|
};
|
|
9147
|
-
const response = await appendVerificationPacket(api, cwd, session, accountId, { 'verify-envelope': envelopeId }, packet);
|
|
9917
|
+
const response = await appendVerificationPacket(api, cwd, session, accountId, Object.assign({}, flags, { 'verify-envelope': envelopeId }), packet);
|
|
9148
9918
|
console.log('Evidence attached:', (response.packet || packet).packetId);
|
|
9149
9919
|
return response;
|
|
9150
9920
|
}
|
|
@@ -9155,7 +9925,9 @@ async function verifyCommand(flags, subcommand) {
|
|
|
9155
9925
|
const packet = {
|
|
9156
9926
|
type: sub === 'browser-start' ? 'browser_session' : (sub === 'browser-snapshot' ? 'browser_snapshot' : 'browser_step'),
|
|
9157
9927
|
success: !flagEnabled(flags.failed),
|
|
9158
|
-
claim
|
|
9928
|
+
// `--claim` now names a declared acceptance claim id, so the free-text label for a browser step is
|
|
9929
|
+
// `--label`. Reading both from one flag made the id double as prose and the prose double as an id.
|
|
9930
|
+
claim: flags.label || flags.text || action,
|
|
9159
9931
|
world: buildCommandWorld(null, { accountId, dataMode, branchName, workspace, host: normalizeBaseUrl(baseUrl) }),
|
|
9160
9932
|
browser: {
|
|
9161
9933
|
action,
|
|
@@ -9168,7 +9940,7 @@ async function verifyCommand(flags, subcommand) {
|
|
|
9168
9940
|
evidenceCategories: [sub, action === 'upload' ? 'browser_session.actual_upload' : null, action === 'click' ? 'browser_session.actual_clicks' : null]
|
|
9169
9941
|
.filter(Boolean)
|
|
9170
9942
|
};
|
|
9171
|
-
const response = await appendVerificationPacket(api, cwd, session, accountId, { 'verify-envelope': envelopeId }, packet);
|
|
9943
|
+
const response = await appendVerificationPacket(api, cwd, session, accountId, Object.assign({}, flags, { 'verify-envelope': envelopeId }), packet);
|
|
9172
9944
|
console.log('Browser evidence attached:', (response.packet || packet).packetId);
|
|
9173
9945
|
return response;
|
|
9174
9946
|
}
|
|
@@ -9222,7 +9994,7 @@ async function verifyCommand(flags, subcommand) {
|
|
|
9222
9994
|
// report would list as wrong-world evidence on every status check.
|
|
9223
9995
|
const statusVerification = await verificationPreflight(api, cwd, session, accountId, { 'verify-envelope': envelopeId }, {
|
|
9224
9996
|
packetType: 'component_status',
|
|
9225
|
-
world: { accountId, dataMode, branchName, workspace }
|
|
9997
|
+
world: { repoAccountId: accountId, accountId, executionAccountId: accountId, dataMode, branchName, workspace }
|
|
9226
9998
|
}, { command: 'verify status', quiet: true, neverRefuse: true });
|
|
9227
9999
|
const componentStatus = await loggedPost(api, cwd, '/cli/components', {
|
|
9228
10000
|
token: session.token,
|
|
@@ -10591,6 +11363,7 @@ function renderDashboardHtml(snapshot) {
|
|
|
10591
11363
|
'<div class="action-row">' +
|
|
10592
11364
|
'<button class="action-btn" data-action="export-issues">Export Issue Bundle</button>' +
|
|
10593
11365
|
'<button class="action-btn" data-action="rescan-repos">Rescan Account Repos</button>' +
|
|
11366
|
+
'<button class="action-btn" data-action="sync-guides">Update Guides In All Repos</button>' +
|
|
10594
11367
|
'<button class="action-btn" data-action="reconnect-websockets">Reconnect Websockets</button>' +
|
|
10595
11368
|
'<button class="action-btn" data-action="refresh-agents">Refresh Agents</button>' +
|
|
10596
11369
|
'</div>' +
|
|
@@ -10663,7 +11436,7 @@ function renderDashboardHtml(snapshot) {
|
|
|
10663
11436
|
'summaryItem("Available Agents",String(agentList.length)),' +
|
|
10664
11437
|
'summaryItem("Routed Tickets",String(ticketCounts.routed||0))' +
|
|
10665
11438
|
'].join("");}' +
|
|
10666
|
-
'function renderLastAction(state){const target=document.getElementById("last-action");if(!state.lastAction){target.className="empty";target.textContent="No dashboard actions have been triggered yet.";return;}target.className="summary-item";target.innerHTML=\'<span class="k">Last Action</span><span class="v">\'+escapeHtml(state.lastAction.action||"unknown")+\'</span><div class="muted" style="margin-top:8px;">\'+escapeHtml(formatTime(state.lastAction.at))+\'</div>\'+(state.lastAction.bundlePath?\'<div class="muted" style="margin-top:8px;word-break:break-word;"><strong>Bundle:</strong> \'+escapeHtml(state.lastAction.bundlePath)+\'</div>\':"");}' +
|
|
11439
|
+
'function renderLastAction(state){const target=document.getElementById("last-action");if(!state.lastAction){target.className="empty";target.textContent="No dashboard actions have been triggered yet.";return;}target.className="summary-item";target.innerHTML=\'<span class="k">Last Action</span><span class="v">\'+escapeHtml(state.lastAction.action||"unknown")+\'</span><div class="muted" style="margin-top:8px;">\'+escapeHtml(formatTime(state.lastAction.at))+\'</div>\'+(state.lastAction.summary?\'<div class="muted" style="margin-top:8px;">\'+escapeHtml(state.lastAction.summary)+\'</div>\':"")+(state.lastAction.bundlePath?\'<div class="muted" style="margin-top:8px;word-break:break-word;"><strong>Bundle:</strong> \'+escapeHtml(state.lastAction.bundlePath)+\'</div>\':"");}' +
|
|
10667
11440
|
'function renderIssues(state){const target=document.getElementById("issues-section");const issues=state.issues||[];if(!issues.length){target.innerHTML=\'<div class="empty">No automatic issue signals are currently detected. If something still feels wrong, you can still click "Export Issue Bundle" to prepare a package for a Remits system admin.</div>\';return;}target.innerHTML=issues.map(function(issue,index){const tone=issue.severity==="bad"?"bad":"warn";const meta=badge(issue.severity==="bad"?"critical":"warning",tone)+\' <span class="muted">\'+escapeHtml(issue.scope+(issue.repoAccountId?(" account "+issue.repoAccountId):""))+\'</span>\';const evidence=(issue.evidence||[]).map(function(line){return "<li>"+escapeHtml(line)+"</li>";}).join("");const body=\'<div class="stack"><div class="muted">\'+escapeHtml(issue.summary||"")+\'</div><div class="mini-card"><strong>Recommendation</strong>\'+escapeHtml(issue.recommendation||"") + \'</div><div><strong style="display:block;margin-bottom:8px;">Evidence</strong><ul style="margin:0;padding-left:18px;display:grid;gap:8px;">\'+(evidence||"<li>No evidence lines captured.</li>")+\'</ul></div></div>\';return detailsBlock("issue:"+issue.id,(index+1)+". "+issue.title,meta,body,index===0);}).join("");}' +
|
|
10668
11441
|
'function renderSupportTickets(state){const target=document.getElementById("support-ticket-section");const support=state.supportTickets||{};const parts=support.compartments||[];const errors=support.errors||[];let html="";if(errors.length){html+=detailsBlock("support:errors","Queue fetch errors",badge(String(errors.length)+" error(s)","warn"),codeBlock("support-errors",JSON.stringify(errors,null,2),"log"),false);}if(!parts.length&&!errors.length){target.innerHTML=html+\'<div class="empty">No authenticated support-ticket queue is available yet.</div>\';return;}html+=parts.map(function(part,pi){const lane=(part.dataMode||"prod");const meta=badge(lane+" lane",lane==="test"?"warn":"ok")+\' \'+badge(String(part.open||0)+" open","ok")+\' <span class="muted">\'+escapeHtml(String(part.queueCount||0))+\' account queue(s)\'+(part.overlappingQueuesDropped?", "+part.overlappingQueuesDropped+" overlapping dropped":"")+\'</span>\';const caveat=part.scopeUnknown?\'<div class="mini-card"><strong>Note</strong>A queue did not report which accounts it scanned, so this total may count a ticket more than once.</div>\':"";const cards=\'<div class="mini-grid"><div class="mini-card"><strong>Open</strong>\'+escapeHtml(String(part.open||0))+\'</div><div class="mini-card"><strong>Routed To An Agent</strong>\'+escapeHtml(String(part.routed||0))+\'</div><div class="mini-card"><strong>Unrouted</strong>\'+escapeHtml(String(part.unrouted||0))+\'</div><div class="mini-card"><strong>Awaiting A Reply</strong>\'+escapeHtml(String(part.awaitingReplyOnPage||0))+\' <span class="muted">of \'+escapeHtml(String((part.tickets||[]).length))+\' shown</span></div></div>\'+caveat;const rows=(part.tickets||[]).length?(part.tickets||[]).map(function(ticket,index){const routed=!!ticket.routedAgentId;const waiting=!!ticket.awaitingResponseSince;const meta2=badge(ticket.priority||"priority",ticket.priority==="critical"||ticket.priority==="high"?"bad":"warn")+\' \'+badge(ticket.status||"status",ticket.status==="open"?"ok":"warn")+\' \'+badge(routed?("routed: "+(ticket.routedAgentLabel||ticket.routedAgentId)):"unrouted",routed?"ok":"warn")+(waiting?\' \'+badge("awaiting reply","warn"):"")+\' <span class="muted">\'+escapeHtml(ticket.accountName||ticket.accountId||"unknown account")+\'</span>\';return detailsBlock("support-ticket:"+pi+":"+ticket.ticketId,String(ticket.ticketId||"ticket")+" "+(ticket.subject||""),meta2,supportTicketDetails(state,ticket,"support-ticket:"+pi+":"+index),false);}).join(""):\'<div class="empty">No open support tickets in this compartment.</div>\';return detailsBlock("compartment:"+part.compartment,part.baseUrl||part.compartment,meta,cards+\'<div class="accordion">\'+rows+\'</div>\',pi===0);}).join("");html+=\'<div class="muted" style="margin-top:10px;">Each platform and data lane is counted separately. A localhost test-lane queue is full of suite fixtures and is not comparable to a production queue — summing them is what made this panel report 583 open tickets against a production queue of 24.</div>\';target.innerHTML=html;}' +
|
|
10669
11442
|
'function supportTicketDetails(state,ticket,key){const agents=((state.agents||{}).registered)||[];const ticketAccountId=String(ticket.accountId||"");const eligible=agents.filter(function(a){if(ticket.baseUrl&&a.baseUrl&&a.baseUrl!==ticket.baseUrl)return false;if(ticket.dataMode&&a.dataMode&&a.dataMode!==ticket.dataMode)return false;return (a.accounts||[]).some(function(acc){return String(acc.accountId)===ticketAccountId||acc.routingScope==="self_and_descendants";});});const options=[\'<option value="">- unrouted -</option>\'].concat(eligible.map(function(a){const sel=a.agentId===ticket.routedAgentId?" selected":"";return \'<option value="\'+escapeHtml(a.agentId)+\'"\'+sel+\'>\'+escapeHtml((a.label||a.userName||a.agentId)+" - "+(a.state||"idle")+(a.activity?" ("+a.activity+")":""))+\'</option>\';})).join("");const routeControl=eligible.length?(\'<div class="mini-card"><strong>Route To Agent</strong><div style="display:flex;gap:8px;margin-top:6px;"><select data-route-ticket="\'+escapeHtml(String(ticket.ticketId||""))+\'" data-route-account="\'+escapeHtml(ticketAccountId)+\'" data-route-base="\'+escapeHtml(ticket.baseUrl||"")+\'" data-route-mode="\'+escapeHtml(ticket.dataMode||"")+\'" style="flex:1;min-width:0;">\'+options+\'</select><button class="action-btn" data-action="route-ticket" data-ticket="\'+escapeHtml(String(ticket.ticketId||""))+\'">Route</button></div></div>\'):\'<div class="mini-card"><strong>Route To Agent</strong>No registered agent covers this account.</div>\';const lane=ticket.dataMode||"prod";const planning=[ticket.workstream?("Workstream "+ticket.workstream):"",ticket.plannedIn?("Planned "+ticket.plannedIn):"",ticket.boardStage?("Board "+ticket.boardStage):"",ticket.rank!==undefined&&ticket.rank!==null&&ticket.rank!==""?("Rank "+ticket.rank):"",ticket.size?("Size "+ticket.size):"",ticket.blockedBy?("Blocked by "+ticket.blockedBy):""].filter(Boolean).join(" / ");const command="remits-cli ticket read --ticket "+String(ticket.ticketId||"")+" --data-mode "+lane+"\\nremits-cli ticket accept --ticket "+String(ticket.ticketId||"")+" --data-mode "+lane;return \'<div class="stack"><div class="mini-grid"><div class="mini-card"><strong>Ticket</strong>\'+escapeHtml(ticket.ticketId||"n/a")+\'</div><div class="mini-card"><strong>Account</strong>\'+escapeHtml((ticket.accountName||"n/a")+" ("+(ticket.accountId||"n/a")+")")+\'</div><div class="mini-card"><strong>Type</strong>\'+escapeHtml(ticket.type||"n/a")+\'</div><div class="mini-card"><strong>Owned By</strong>\'+escapeHtml(ticket.assignedTo||"unassigned")+\'</div><div class="mini-card"><strong>Routed To</strong>\'+escapeHtml(ticket.routedAgentLabel||ticket.routedAgentId||"nobody")+\'</div><div class="mini-card"><strong>Implementation</strong>\'+escapeHtml((ticket.implementationAccountName||"n/a")+" ("+(ticket.implementationAccountId||"n/a")+")")+\'</div><div class="mini-card"><strong>Planning</strong>\'+escapeHtml(planning||"No planning fields")+\'</div></div>\'+routeControl+\'<div class="muted">\'+escapeHtml(ticket.affectedComponent||"No affected component recorded.")+\'</div>\'+codeBlock(key+":cmd",command)+\'</div>\';}' +
|
|
@@ -11054,6 +11827,10 @@ function startDashboardServer(preferredPort) {
|
|
|
11054
11827
|
} else if (action === 'rescan-repos') {
|
|
11055
11828
|
recordDiscoverySummary(discoverAccountRepos());
|
|
11056
11829
|
supportTicketSnapshotCache = { fetchedAtMs: 0, value: null };
|
|
11830
|
+
} else if (action === 'sync-guides') {
|
|
11831
|
+
actionResult = await syncGuidesAcrossIndexedRepos({ rescan: true });
|
|
11832
|
+
runtimeState.lastAction.summary = guideSyncSummaryLine(actionResult);
|
|
11833
|
+
recordDiscoverySummary(discoverAccountRepos());
|
|
11057
11834
|
} else if (action === 'refresh-agents') {
|
|
11058
11835
|
agentSnapshotCache = { fetchedAtMs: 0, value: null };
|
|
11059
11836
|
supportTicketSnapshotCache = { fetchedAtMs: 0, value: null };
|
|
@@ -12151,6 +12928,11 @@ function resolveAgentId(flags, options = {}) {
|
|
|
12151
12928
|
* agent is silently useless for support: it registers, appears online, and can never be routed a
|
|
12152
12929
|
* production ticket. Ask for `--data-mode test` when you want a fixture agent.
|
|
12153
12930
|
*/
|
|
12931
|
+
/**
|
|
12932
|
+
* The host and lane an agent command runs against: the flag, then this agent's own prior registration, then
|
|
12933
|
+
* the PRODUCTION platform and lane. That last default is deliberate — `agent register` joins the support
|
|
12934
|
+
* fleet, and a fleet member on a test lane appears online and can never be routed a real ticket.
|
|
12935
|
+
*/
|
|
12154
12936
|
function agentDefaults(flags, entry) {
|
|
12155
12937
|
return {
|
|
12156
12938
|
baseUrl: normalizeBaseUrl(
|
|
@@ -12163,10 +12945,29 @@ function agentDefaults(flags, entry) {
|
|
|
12163
12945
|
function agentSessionFor(flags, entry) {
|
|
12164
12946
|
const cwd = (entry && entry.cwd) || process.cwd();
|
|
12165
12947
|
const defaults = agentDefaults(flags, entry);
|
|
12166
|
-
const
|
|
12167
|
-
|
|
12168
|
-
|
|
12169
|
-
|
|
12948
|
+
const pinnedByCaller = Boolean((flags && flags['base-url']) || (entry && entry.baseUrl));
|
|
12949
|
+
let context;
|
|
12950
|
+
try {
|
|
12951
|
+
context = resolveSessionContext(cwd, Object.assign({}, flags, {
|
|
12952
|
+
'base-url': defaults.baseUrl,
|
|
12953
|
+
'data-mode': defaults.dataMode
|
|
12954
|
+
}));
|
|
12955
|
+
} catch (err) {
|
|
12956
|
+
// The prod default is intentional, and invisible: every OTHER command in this checkout follows the
|
|
12957
|
+
// active session, so "not authenticated at <some prod url>" reads as a broken session rather than as
|
|
12958
|
+
// "this command deliberately went somewhere else". Say which host it chose and why.
|
|
12959
|
+
if (!pinnedByCaller) {
|
|
12960
|
+
const local = readSession({}) || {};
|
|
12961
|
+
const refusal = new Error('Agent commands join the SUPPORT FLEET, which lives on the production ' +
|
|
12962
|
+
'platform, so this one targeted ' + defaults.baseUrl + ' — not the session this checkout uses' +
|
|
12963
|
+
(local.baseUrl ? ' (' + normalizeBaseUrl(local.baseUrl) + ')' : '') + '. ' +
|
|
12964
|
+
'Authenticate there with `remits-cli auth --base-url ' + defaults.baseUrl + '`, or point this ' +
|
|
12965
|
+
'command at another platform with `--base-url <url>`.');
|
|
12966
|
+
refusal.cause = err;
|
|
12967
|
+
throw refusal;
|
|
12968
|
+
}
|
|
12969
|
+
throw err;
|
|
12970
|
+
}
|
|
12170
12971
|
if (!context.session || !context.session.token) {
|
|
12171
12972
|
throw new Error('No authenticated session for ' + defaults.baseUrl +
|
|
12172
12973
|
'. Run: remits-cli auth --base-url ' + defaults.baseUrl);
|
|
@@ -12747,7 +13548,7 @@ async function postAgentStatus(context, agentId, update) {
|
|
|
12747
13548
|
* the same thing. Only the local supervisor can add machine-local repository hints; the worker owns the
|
|
12748
13549
|
* final grounding step, including creating or choosing a git worktree before it edits.
|
|
12749
13550
|
*/
|
|
12750
|
-
function spawnTicketWorker(context, ctx, ticket) {
|
|
13551
|
+
async function spawnTicketWorker(context, ctx, ticket) {
|
|
12751
13552
|
const { spawn } = require('child_process');
|
|
12752
13553
|
const agentId = ctx.agentId;
|
|
12753
13554
|
const ticketId = String(ticket.id || ticket.ticketId);
|
|
@@ -12766,6 +13567,32 @@ function spawnTicketWorker(context, ctx, ticket) {
|
|
|
12766
13567
|
const targetBranch = repositoryWork ? ticketTargetBranch(ticket, baseWorkingDirectory) : gitBranchNameInDirectory(baseWorkingDirectory);
|
|
12767
13568
|
const suggestedWorktree = repositoryWork && baseWorkingDirectory ? ticketWorktreePath(baseWorkingDirectory, ticketId) : null;
|
|
12768
13569
|
const workingDirectory = baseWorkingDirectory;
|
|
13570
|
+
if (workingDirectory) {
|
|
13571
|
+
try {
|
|
13572
|
+
const guideSummary = await downloadAndSyncGuides(workingDirectory, {
|
|
13573
|
+
session: context.session,
|
|
13574
|
+
baseUrl: context.baseUrl,
|
|
13575
|
+
accountId: Number(ticket.accountId) || context.accountId
|
|
13576
|
+
});
|
|
13577
|
+
if (guideSummary && ((guideSummary.updated || []).length || (guideSummary.removed || []).length)) {
|
|
13578
|
+
appendGlobalActivityLog('guides', 'worker_repo_synced', {
|
|
13579
|
+
agentId,
|
|
13580
|
+
ticketId,
|
|
13581
|
+
accountId: Number(ticket.accountId) || null,
|
|
13582
|
+
directory: workingDirectory,
|
|
13583
|
+
updated: (guideSummary.updated || []).length,
|
|
13584
|
+
removed: (guideSummary.removed || []).length
|
|
13585
|
+
});
|
|
13586
|
+
}
|
|
13587
|
+
} catch (err) {
|
|
13588
|
+
appendGlobalActivityLog('guides', 'worker_repo_sync_failed', {
|
|
13589
|
+
agentId,
|
|
13590
|
+
ticketId,
|
|
13591
|
+
directory: workingDirectory,
|
|
13592
|
+
error: describeError(err)
|
|
13593
|
+
}, 'error');
|
|
13594
|
+
}
|
|
13595
|
+
}
|
|
12769
13596
|
// The brief is rendered server-side without the local path (the platform cannot know it), so the
|
|
12770
13597
|
// resolved local repository hint is appended here. When nothing resolved, the server-rendered brief already
|
|
12771
13598
|
// tells the worker to find it rather than guess — do not paper over that with a default.
|
|
@@ -12796,6 +13623,7 @@ function spawnTicketWorker(context, ctx, ticket) {
|
|
|
12796
13623
|
'git -C ' + shellQuote(workingDirectory) + ' worktree add --force ' + shellQuote(suggestedWorktree) +
|
|
12797
13624
|
(targetBranch ? ' ' + targetBranch : ' <branch>') + '\n' +
|
|
12798
13625
|
'cd ' + shellQuote(suggestedWorktree) + '\n' +
|
|
13626
|
+
'remits-cli guides sync\n' +
|
|
12799
13627
|
(targetBranch
|
|
12800
13628
|
? 'git pull --ff-only origin ' + shellQuote(targetBranch) + '\n' +
|
|
12801
13629
|
'git rev-list --left-right --count HEAD...origin/' + shellQuote(targetBranch) + ' # must print: 0 0\n'
|
|
@@ -13169,7 +13997,7 @@ async function agentHeartbeatDaemonCommand(flags) {
|
|
|
13169
13997
|
if (!ticketId || inFlight.has(ticketId)) {
|
|
13170
13998
|
continue;
|
|
13171
13999
|
}
|
|
13172
|
-
const started = spawnTicketWorker(context, ctx, ticket);
|
|
14000
|
+
const started = await spawnTicketWorker(context, ctx, ticket);
|
|
13173
14001
|
inFlight.set(ticketId, { pid: started.pid, accountId: ticket.accountId });
|
|
13174
14002
|
started.done.then(() => {
|
|
13175
14003
|
inFlight.delete(ticketId);
|
|
@@ -14018,6 +14846,13 @@ async function activityInspectCommand(flags) {
|
|
|
14018
14846
|
dataMode: resolveDataMode(flags, session),
|
|
14019
14847
|
userId: flags['user-id'] || flags.userId || flags['cli-user-id'],
|
|
14020
14848
|
userEmail: flags['user-email'] || flags.email,
|
|
14849
|
+
workspace: flags.workspace,
|
|
14850
|
+
branchName: flags.branch || flags['branch-name'],
|
|
14851
|
+
variantBranch: flags['variant-branch'],
|
|
14852
|
+
// The local proof-campaign id, so the platform-side related view and `workstream status` can be asked
|
|
14853
|
+
// the same question. Never defaulted from the workspace here: an unasked-for filter that silently
|
|
14854
|
+
// narrows an investigation is worse than no filter.
|
|
14855
|
+
workstreamId: normalizeWorkstreamId(flags.workstream || flags['workstream-id']),
|
|
14021
14856
|
limit: flags.limit || flags.max || 20,
|
|
14022
14857
|
envelopeLimit: flags['envelope-limit'] || flags.envelopeLimit
|
|
14023
14858
|
}).then((r) => r.data);
|
|
@@ -14059,26 +14894,68 @@ function slimActivitySmell(smell) {
|
|
|
14059
14894
|
return {
|
|
14060
14895
|
severity: s.severity || 'info',
|
|
14061
14896
|
type: s.type || 'signal',
|
|
14897
|
+
title: s.title || s.envelopeSummary || s.testName || null,
|
|
14062
14898
|
summary: s.summary || null,
|
|
14899
|
+
status: s.status || s.health || null,
|
|
14900
|
+
currentFailureCount: s.currentFailureCount == null ? null : s.currentFailureCount,
|
|
14063
14901
|
accountId: s.accountId || null,
|
|
14064
14902
|
laneId: s.laneId || null,
|
|
14065
14903
|
envelopeId: s.envelopeId || null,
|
|
14066
14904
|
taskId: s.taskId || null,
|
|
14905
|
+
testName: s.testName || null,
|
|
14067
14906
|
branchName: s.branchName || null,
|
|
14068
|
-
workspace: s.workspace || null
|
|
14907
|
+
workspace: s.workspace || null,
|
|
14908
|
+
packetCount: s.packetCount == null ? null : s.packetCount,
|
|
14909
|
+
requiredCount: s.requiredCount == null ? null : s.requiredCount,
|
|
14910
|
+
openCount: s.openCount == null ? null : s.openCount,
|
|
14911
|
+
command: s.command || null,
|
|
14912
|
+
updatedAt: formatTime(s.updatedAtMs || s.updatedAt || s.at || s.createdAt)
|
|
14069
14913
|
};
|
|
14070
14914
|
}
|
|
14071
14915
|
|
|
14916
|
+
/**
|
|
14917
|
+
* The next COMMAND, not the smell restated. This list previously mapped each signal to its own summary
|
|
14918
|
+
* sentence, so "Next actions" contained no action; and it filtered on severity <= 2, which permanently
|
|
14919
|
+
* excluded `no_required_evidence` - the one signal that explains why an envelope never closes.
|
|
14920
|
+
* A signal that knows its own fix is actionable at any severity.
|
|
14921
|
+
*/
|
|
14922
|
+
function buildActivityNextActions(smells) {
|
|
14923
|
+
const seen = new Set();
|
|
14924
|
+
return (smells || [])
|
|
14925
|
+
.filter((smell) => smell && (smell.command || smellSeverityRank(smell) <= 2))
|
|
14926
|
+
.sort((left, right) => {
|
|
14927
|
+
const command = (right.command ? 1 : 0) - (left.command ? 1 : 0);
|
|
14928
|
+
if (command) return command;
|
|
14929
|
+
return smellSeverityRank(left) - smellSeverityRank(right);
|
|
14930
|
+
})
|
|
14931
|
+
.map((smell) => {
|
|
14932
|
+
const title = smell.title || smell.envelopeSummary || smell.testName ||
|
|
14933
|
+
(Array.isArray(smell.workspaces) ? smell.workspaces.join(', ') : null);
|
|
14934
|
+
return {
|
|
14935
|
+
type: smell.type || 'signal',
|
|
14936
|
+
severity: smell.severity || 'info',
|
|
14937
|
+
why: (title ? title + ' — ' : '') + (smell.summary || 'inspect this signal'),
|
|
14938
|
+
command: smell.command || null
|
|
14939
|
+
};
|
|
14940
|
+
})
|
|
14941
|
+
.filter((action) => {
|
|
14942
|
+
// Dedupe on the COMMAND when there is one: two different signals about one envelope that resolve to
|
|
14943
|
+
// the same next move are one next move.
|
|
14944
|
+
const key = action.command ? 'cmd|' + action.command : action.type + '|' + action.why;
|
|
14945
|
+
if (seen.has(key)) return false;
|
|
14946
|
+
seen.add(key);
|
|
14947
|
+
return true;
|
|
14948
|
+
})
|
|
14949
|
+
.slice(0, 6);
|
|
14950
|
+
}
|
|
14951
|
+
|
|
14072
14952
|
function compactActivityInspect(response) {
|
|
14073
14953
|
const counts = response.counts || {};
|
|
14074
14954
|
const smells = sortedActivitySmells(response.smells);
|
|
14075
14955
|
const lanes = Array.isArray(response.lanes) ? response.lanes : [];
|
|
14076
14956
|
const envelopes = Array.isArray(response.envelopes) ? response.envelopes : [];
|
|
14077
14957
|
const testRuns = Array.isArray(response.testRuns) ? response.testRuns : [];
|
|
14078
|
-
const nextActions = smells
|
|
14079
|
-
.filter((smell) => smellSeverityRank(smell) <= 2)
|
|
14080
|
-
.slice(0, 5)
|
|
14081
|
-
.map((smell) => (smell.type || 'signal') + ': ' + (smell.summary || 'inspect this signal'));
|
|
14958
|
+
const nextActions = buildActivityNextActions(smells);
|
|
14082
14959
|
|
|
14083
14960
|
return {
|
|
14084
14961
|
success: response.success,
|
|
@@ -14134,16 +15011,43 @@ function printActivityInspectSummary(response) {
|
|
|
14134
15011
|
? ' for ' + (response.subjectUser.email || response.subjectUser.name || ('user ' + response.subjectUser.id))
|
|
14135
15012
|
: '';
|
|
14136
15013
|
console.log('Activity inspection' + subject + ' — ' + response.dataMode + ' lane');
|
|
15014
|
+
// A scope of several hundred accounts printed as one line is not a scope statement, it is a wall. Say how
|
|
15015
|
+
// many, show the head, and point at --json for the rest.
|
|
15016
|
+
const scopeIds = response.accountIds || [];
|
|
15017
|
+
const scopeIdText = scopeIds.length > 12
|
|
15018
|
+
? scopeIds.slice(0, 12).join(', ') + ' … (+' + (scopeIds.length - 12) + ' more, --json for all)'
|
|
15019
|
+
: (scopeIds.join(', ') || String(response.accountId));
|
|
14137
15020
|
console.log('Scope: ' + (response.accountName || ('account ' + response.accountId)) +
|
|
14138
15021
|
' (' + response.accountId + '), ' + (response.scope || 'self') +
|
|
14139
|
-
' ·
|
|
15022
|
+
' · ' + (scopeIds.length || 1) + ' account(s): ' + scopeIdText);
|
|
15023
|
+
if (response.filters && (response.filters.workspace || response.filters.branchName || response.filters.workstreamId)) {
|
|
15024
|
+
console.log('Filters: ' + [
|
|
15025
|
+
response.filters.workspace ? 'workspace ' + response.filters.workspace : null,
|
|
15026
|
+
response.filters.branchName ? 'branch ' + response.filters.branchName : null,
|
|
15027
|
+
response.filters.workstreamId ? 'workstream ' + response.filters.workstreamId : null
|
|
15028
|
+
].filter(Boolean).join(' · ') + ' (evidence below is narrowed to it)');
|
|
15029
|
+
}
|
|
15030
|
+
if (Array.isArray(response.relatedAccounts) && response.relatedAccounts.length) {
|
|
15031
|
+
const related = response.relatedAccounts;
|
|
15032
|
+
const shown = related.slice(0, 12).map((row) =>
|
|
15033
|
+
row.id + ' ' + (row.name || '') + (row.role ? ' (' + row.role + ')' : '')).join('; ');
|
|
15034
|
+
console.log('Related accounts: ' + shown +
|
|
15035
|
+
(related.length > 12 ? '; … (+' + (related.length - 12) + ' more, --json for all)' : ''));
|
|
15036
|
+
}
|
|
14140
15037
|
const counts = response.counts || {};
|
|
14141
15038
|
console.log('Counts: ' +
|
|
14142
15039
|
(counts.agents || 0) + ' agent(s), ' +
|
|
14143
15040
|
(counts.lanes || 0) + ' lane(s), ' +
|
|
14144
15041
|
(counts.verificationEnvelopes || 0) + ' envelope(s), ' +
|
|
14145
15042
|
(counts.testRuns || 0) + ' test run(s), ' +
|
|
14146
|
-
(counts.corpusResults || 0) + ' corpus result(s)'
|
|
15043
|
+
(counts.corpusResults || 0) + ' corpus result(s)' +
|
|
15044
|
+
(counts.distinctStagedEntries != null && counts.distinctStagedEntries !== counts.stagedEntries
|
|
15045
|
+
? ', ' + counts.stagedEntries + ' staged entries across lanes (' + counts.distinctStagedEntries + ' distinct)'
|
|
15046
|
+
: (counts.stagedEntries ? ', ' + counts.stagedEntries + ' staged entries' : '')));
|
|
15047
|
+
if (response.envelopeScope === 'all-data-lanes') {
|
|
15048
|
+
console.log('Note: test runs and corpus results are narrowed to the ' + response.dataMode +
|
|
15049
|
+
' lane; envelopes are listed across BOTH lanes (stage/sync evidence is lane-independent).');
|
|
15050
|
+
}
|
|
14147
15051
|
|
|
14148
15052
|
const smells = sortedActivitySmells(response.smells);
|
|
14149
15053
|
console.log('');
|
|
@@ -14153,6 +15057,18 @@ function printActivityInspectSummary(response) {
|
|
|
14153
15057
|
console.log('Top smells (' + smells.length + '):');
|
|
14154
15058
|
smells.slice(0, 20).forEach((smell) => {
|
|
14155
15059
|
console.log(' [' + (smell.severity || 'info') + '] ' + (smell.type || 'signal') + ': ' + smell.summary);
|
|
15060
|
+
// The numbers were in the JSON and not on the line, so twenty signals read as three sentences repeated.
|
|
15061
|
+
const facts = [
|
|
15062
|
+
smell.packetCount != null ? smell.packetCount + ' packet(s)' : null,
|
|
15063
|
+
smell.currentFailureCount != null ? smell.currentFailureCount + ' current failure(s)' : null,
|
|
15064
|
+
smell.requiredCount != null ? smell.requiredCount + ' requirement(s)' : null,
|
|
15065
|
+
smell.openCount != null ? smell.openCount + ' open' : null,
|
|
15066
|
+
smell.recentRuns != null ? smell.recentRuns + ' recent run(s)' : null,
|
|
15067
|
+
smell.failedRuns != null ? smell.failedRuns + ' failed' : null,
|
|
15068
|
+
smell.stagedCount != null ? smell.stagedCount + ' staged' : null,
|
|
15069
|
+
Array.isArray(smell.workspaces) ? smell.workspaces.join(', ') : null
|
|
15070
|
+
].filter(Boolean);
|
|
15071
|
+
if (facts.length) console.log(' ' + facts.join(' · '));
|
|
14156
15072
|
const pivots = [
|
|
14157
15073
|
smell.accountId ? 'account ' + smell.accountId : null,
|
|
14158
15074
|
smell.laneId ? 'lane ' + smell.laneId : null,
|
|
@@ -14165,12 +15081,36 @@ function printActivityInspectSummary(response) {
|
|
|
14165
15081
|
if (pivots.length) console.log(' ' + pivots.join(' · '));
|
|
14166
15082
|
});
|
|
14167
15083
|
if (smells.length > 20) console.log(' ...' + (smells.length - 20) + ' more; rerun with --json');
|
|
14168
|
-
|
|
14169
|
-
|
|
14170
|
-
|
|
14171
|
-
|
|
14172
|
-
|
|
14173
|
-
|
|
15084
|
+
}
|
|
15085
|
+
const actions = buildActivityNextActions(smells);
|
|
15086
|
+
if (actions.length) {
|
|
15087
|
+
console.log('');
|
|
15088
|
+
console.log('Next actions:');
|
|
15089
|
+
actions.forEach((action) => {
|
|
15090
|
+
console.log(' - ' + action.why);
|
|
15091
|
+
if (action.command) console.log(' ' + action.command);
|
|
15092
|
+
});
|
|
15093
|
+
}
|
|
15094
|
+
|
|
15095
|
+
// "2 agent(s)" with no names is the shape of the question, not the answer: an agent asking who else is in
|
|
15096
|
+
// this repo right now could read the count and still have to go find them. The payload already carries the
|
|
15097
|
+
// directory, branch, workspace and last-seen of each.
|
|
15098
|
+
const liveAgents = response.agents || [];
|
|
15099
|
+
if (liveAgents.length) {
|
|
15100
|
+
console.log('');
|
|
15101
|
+
console.log('Live agents here:');
|
|
15102
|
+
liveAgents.slice(0, 10).forEach((agent) => {
|
|
15103
|
+
const who = agent.label || agent.userEmail || agent.agentId || 'unknown agent';
|
|
15104
|
+
const where = [agent.branch ? 'branch ' + agent.branch : null,
|
|
15105
|
+
'workspace ' + (agent.workspace || 'shared'),
|
|
15106
|
+
agent.dataMode ? 'data ' + agent.dataMode : null].filter(Boolean).join(' · ');
|
|
15107
|
+
const seen = agent.lastSeenAtMs ? ' · seen ' + formatTime(agent.lastSeenAtMs) : '';
|
|
15108
|
+
console.log(' - ' + who + ' [' + (agent.state || 'unknown') + ']' +
|
|
15109
|
+
(agent.source === 'implicit' ? ' (implicit)' : '') + seen);
|
|
15110
|
+
console.log(' ' + where);
|
|
15111
|
+
if (agent.workingDirectory) console.log(' ' + agent.workingDirectory);
|
|
15112
|
+
});
|
|
15113
|
+
if (liveAgents.length > 10) console.log(' ...' + (liveAgents.length - 10) + ' more agent(s)');
|
|
14174
15114
|
}
|
|
14175
15115
|
|
|
14176
15116
|
const lanes = response.lanes || [];
|
|
@@ -14195,8 +15135,14 @@ function printActivityInspectSummary(response) {
|
|
|
14195
15135
|
console.log('');
|
|
14196
15136
|
console.log('Recent verification envelopes:');
|
|
14197
15137
|
envelopes.slice(0, 8).forEach((env) => {
|
|
15138
|
+
const world = [env.branchName || null, env.workspace ? 'ws:' + env.workspace : null, env.dataMode || null]
|
|
15139
|
+
.filter(Boolean).join('/');
|
|
14198
15140
|
console.log(' - ' + (env.status || 'unknown') + ' · ' + (env.packetCount || 0) + ' packet(s) · ' +
|
|
15141
|
+
(world ? '[' + world + '] · ' : '') +
|
|
14199
15142
|
(env.summary || env.envelopeId) + ' · ' + formatTime(env.updatedAtMs || env.lastPacketAtMs || env.createdAt));
|
|
15143
|
+
if (env.noRequiredEvidence) {
|
|
15144
|
+
console.log(' evidence log — no verdict requested; add a claim only if someone needs one: remits-cli verify claim <id> --text "..." --envelope ' + env.envelopeId);
|
|
15145
|
+
}
|
|
14200
15146
|
if (env.currentFailureCount) console.log(' current failures: ' + env.currentFailureCount);
|
|
14201
15147
|
});
|
|
14202
15148
|
}
|
|
@@ -14237,6 +15183,133 @@ function printActivityInspectSummary(response) {
|
|
|
14237
15183
|
console.log('Full structured payload: remits-cli activity inspect --json');
|
|
14238
15184
|
}
|
|
14239
15185
|
|
|
15186
|
+
function readWorkstreamIndex(cwd, workstreamId) {
|
|
15187
|
+
const id = normalizeWorkstreamId(workstreamId);
|
|
15188
|
+
if (!id) return null;
|
|
15189
|
+
const file = workstreamFile(cwd, id);
|
|
15190
|
+
if (!fs.existsSync(file)) return null;
|
|
15191
|
+
try {
|
|
15192
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
15193
|
+
} catch (_) {
|
|
15194
|
+
return null;
|
|
15195
|
+
}
|
|
15196
|
+
}
|
|
15197
|
+
|
|
15198
|
+
function listWorkstreamIndexes(cwd) {
|
|
15199
|
+
const paths = ensureLocalState(cwd);
|
|
15200
|
+
if (!fs.existsSync(paths.workstreamsDir)) return [];
|
|
15201
|
+
return fs.readdirSync(paths.workstreamsDir, { withFileTypes: true })
|
|
15202
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
|
|
15203
|
+
.map((entry) => readWorkstreamIndex(cwd, entry.name.replace(/\.json$/, '')))
|
|
15204
|
+
.filter(Boolean)
|
|
15205
|
+
.sort((a, b) => String(b.updatedAt || '').localeCompare(String(a.updatedAt || '')));
|
|
15206
|
+
}
|
|
15207
|
+
|
|
15208
|
+
async function evidenceCommand(flags) {
|
|
15209
|
+
const cwd = process.cwd();
|
|
15210
|
+
ensureLocalState(cwd);
|
|
15211
|
+
const limit = Math.max(1, Math.min(parseInt(flags.limit || flags.max || '40', 10) || 40, 500));
|
|
15212
|
+
const entries = readEvidenceTrail(cwd, limit);
|
|
15213
|
+
if (flagEnabled(flags.json)) {
|
|
15214
|
+
console.log(JSON.stringify({ success: true, actor: resolveLocalActor().id, count: entries.length, entries }, null, 2));
|
|
15215
|
+
return entries;
|
|
15216
|
+
}
|
|
15217
|
+
const paths = localStatePaths(cwd);
|
|
15218
|
+
console.log('Evidence trail for actor ' + paths.actor.id + ' (' + paths.evidenceFile + ')');
|
|
15219
|
+
if (!entries.length) {
|
|
15220
|
+
console.log('Nothing recorded yet. Every stage, test, token, tool and sync appends one world-stamped line here,');
|
|
15221
|
+
console.log('with no envelope and nothing to start.');
|
|
15222
|
+
return entries;
|
|
15223
|
+
}
|
|
15224
|
+
// Grouped by WORLD, because "what have I proven" is only answerable per world. An agent that ran the same
|
|
15225
|
+
// suite in two lanes has two different facts, and a flat list hides exactly that.
|
|
15226
|
+
const groups = new Map();
|
|
15227
|
+
entries.forEach((entry) => {
|
|
15228
|
+
const key = describeEvidenceWorld(entry.world);
|
|
15229
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
15230
|
+
groups.get(key).push(entry);
|
|
15231
|
+
});
|
|
15232
|
+
groups.forEach((rows, world) => {
|
|
15233
|
+
console.log('');
|
|
15234
|
+
console.log(world);
|
|
15235
|
+
const byClaim = new Map();
|
|
15236
|
+
rows.forEach((row) => {
|
|
15237
|
+
const key = (row.type || 'unknown') + '|' + (row.claim || row.type || 'evidence') + '|' + evidenceOutcome(row);
|
|
15238
|
+
if (!byClaim.has(key)) byClaim.set(key, { row, count: 0 });
|
|
15239
|
+
const bucket = byClaim.get(key);
|
|
15240
|
+
bucket.count += 1;
|
|
15241
|
+
bucket.row = row;
|
|
15242
|
+
});
|
|
15243
|
+
Array.from(byClaim.values())
|
|
15244
|
+
.sort((left, right) => right.count - left.count)
|
|
15245
|
+
.slice(0, 15)
|
|
15246
|
+
.forEach((bucket) => {
|
|
15247
|
+
const row = bucket.row;
|
|
15248
|
+
const layer = row.world && row.world.sourceLayer ? ' source ' + row.world.sourceLayer : '';
|
|
15249
|
+
console.log(' ' + (bucket.count > 1 ? bucket.count + ' x ' : '') +
|
|
15250
|
+
evidenceOutcomeLabel(row) + (row.type || 'unknown') + ': ' + (row.claim || '(no claim)') +
|
|
15251
|
+
' ' + formatTime(row.ts) + layer +
|
|
15252
|
+
(Array.isArray(row.claims) && row.claims.length ? ' [claims ' + row.claims.join(', ') + ']' : '') +
|
|
15253
|
+
(row.envelopeId ? ' [envelope ' + row.envelopeId.slice(0, 8) + ']' : ''));
|
|
15254
|
+
});
|
|
15255
|
+
if (byClaim.size > 15) console.log(' ...' + (byClaim.size - 15) + ' more distinct entries; --json for all');
|
|
15256
|
+
});
|
|
15257
|
+
console.log('');
|
|
15258
|
+
console.log(entries.length + ' entry(s) shown. This is a log, not a verdict: nothing here is outstanding.');
|
|
15259
|
+
console.log('If someone needs a checkable verdict, start an envelope and name what must be true:');
|
|
15260
|
+
console.log(' remits-cli verify start --summary "..." && remits-cli verify claim <id> --text "..."');
|
|
15261
|
+
return entries;
|
|
15262
|
+
}
|
|
15263
|
+
|
|
15264
|
+
async function workstreamCommand(flags, subcommand) {
|
|
15265
|
+
const cwd = process.cwd();
|
|
15266
|
+
ensureLocalState(cwd);
|
|
15267
|
+
const action = subcommand || 'status';
|
|
15268
|
+
if (action !== 'status') throw new Error('Unknown workstream subcommand: ' + action);
|
|
15269
|
+
const requested = resolveWorkstreamId(cwd, flags, { workspace: flags.workspace });
|
|
15270
|
+
let rows = requested ? [readWorkstreamIndex(cwd, requested)].filter(Boolean) : listWorkstreamIndexes(cwd);
|
|
15271
|
+
if (flags.workspace) {
|
|
15272
|
+
const normalizedWorkspace = normalizeWorkspace(flags.workspace);
|
|
15273
|
+
rows = rows.filter((row) => {
|
|
15274
|
+
if (normalizeWorkspace(row.roles && row.roles.workspace) === normalizedWorkspace) return true;
|
|
15275
|
+
return (row.artifacts || []).some((artifact) => normalizeWorkspace(artifact.workspace || (artifact.world && artifact.world.workspace)) === normalizedWorkspace);
|
|
15276
|
+
});
|
|
15277
|
+
}
|
|
15278
|
+
if (flagEnabled(flags.json)) {
|
|
15279
|
+
console.log(JSON.stringify({ success: true, workstreams: rows }, null, 2));
|
|
15280
|
+
return;
|
|
15281
|
+
}
|
|
15282
|
+
if (!rows.length) {
|
|
15283
|
+
console.log('No local workstream index found.');
|
|
15284
|
+
console.log('Start one implicitly with a workspace, or pass --workstream <id> / REMITS_WORKSTREAM_ID while running tool or verify commands.');
|
|
15285
|
+
return;
|
|
15286
|
+
}
|
|
15287
|
+
rows.forEach((row) => {
|
|
15288
|
+
console.log('Workstream:', row.workstreamId);
|
|
15289
|
+
console.log('Updated:', row.updatedAt || 'unknown');
|
|
15290
|
+
console.log('Actors:', (row.actors || []).join(', ') || 'none recorded');
|
|
15291
|
+
const roleParts = workstreamWorldParts(row.roles);
|
|
15292
|
+
if (roleParts.length) console.log('Latest world:', roleParts.join(' · '));
|
|
15293
|
+
// A campaign that spanned several worlds says so. One blended line would describe a run that never
|
|
15294
|
+
// happened, and "which world proved this?" is the question the whole surface exists to answer.
|
|
15295
|
+
const worlds = Array.isArray(row.worlds) ? row.worlds : [];
|
|
15296
|
+
if (worlds.length > 1) {
|
|
15297
|
+
console.log('Worlds in this workstream: ' + worlds.length);
|
|
15298
|
+
worlds.slice(-6).forEach((world) => {
|
|
15299
|
+
const parts = workstreamWorldParts(world);
|
|
15300
|
+
if (parts.length) console.log(' - ' + parts.join(' · '));
|
|
15301
|
+
});
|
|
15302
|
+
}
|
|
15303
|
+
const artifacts = Array.isArray(row.artifacts) ? row.artifacts.slice(-12) : [];
|
|
15304
|
+
console.log('Artifacts:', artifacts.length ? '' : 'none recorded');
|
|
15305
|
+
artifacts.forEach((artifact) => {
|
|
15306
|
+
const label = [artifact.type, artifact.tool, artifact.callId, artifact.envelopeId, artifact.packetType].filter(Boolean).join(' ');
|
|
15307
|
+
console.log(' - ' + (artifact.recordedAt || '') + ' ' + label);
|
|
15308
|
+
if (artifact.path) console.log(' ' + artifact.path);
|
|
15309
|
+
});
|
|
15310
|
+
});
|
|
15311
|
+
}
|
|
15312
|
+
|
|
14240
15313
|
/**
|
|
14241
15314
|
* The live work map: which repositories are being edited, by whom, and who else is around.
|
|
14242
15315
|
*
|
|
@@ -15166,12 +16239,16 @@ function printCorpusHelp() {
|
|
|
15166
16239
|
}
|
|
15167
16240
|
|
|
15168
16241
|
function printToolHelp() {
|
|
15169
|
-
console.log('Usage: remits-cli tool --name <toolName> [--base-url URL] [--account-id ID] [--branch BRANCH] [--input "{...}"|--input-file file.json] [--data-mode test|prod] [--variant-branch NAME|none] [--timeout-ms 60000] [--async true --wait true]');
|
|
16242
|
+
console.log('Usage: remits-cli tool --name <toolName> [--base-url URL] [--account-id ID] [--as-account ID] [--target-account ID] [--branch BRANCH] [--input "{...}"|--input-file file.json] [--data-mode test|prod] [--variant-branch NAME|none] [--timeout-ms 60000] [--async true --wait true]');
|
|
15170
16243
|
console.log(' remits-cli tool status --call-id <callId> [--base-url URL] [--account-id ID] [--data-mode test|prod]');
|
|
15171
16244
|
console.log('');
|
|
15172
16245
|
console.log('Hierarchy scope (read/discovery tools) — you do NOT need the owning child account id:');
|
|
15173
16246
|
console.log(' --scope self|children|hierarchy expand from the repo account when searching');
|
|
15174
16247
|
console.log(' --target-account-id ID the exact account to act on (required by writes)');
|
|
16248
|
+
console.log(' --as-account ID execute as this reachable account; subscriber branch edges apply');
|
|
16249
|
+
console.log(' --target-account ID the account the tool acts ON (sent as input.accountId and');
|
|
16250
|
+
console.log(' input.targetAccountId when --input omits them). It does NOT change');
|
|
16251
|
+
console.log(' component resolution — add --as-account ID for that.');
|
|
15175
16252
|
console.log(' --account-ids 1,2,3 an explicit bounded list of owner accounts');
|
|
15176
16253
|
console.log(' --anchor-account-id ID disambiguate the path for a multi-parent account');
|
|
15177
16254
|
console.log(' These are merged into --input; a value already in --input wins.');
|
|
@@ -15179,17 +16256,68 @@ function printToolHelp() {
|
|
|
15179
16256
|
console.log('Examples:');
|
|
15180
16257
|
console.log(' remits-cli tool --name mcp_firestore_search --input-file query.json --data-mode prod');
|
|
15181
16258
|
console.log(' remits-cli tool --name mcp_firestore_search --input \'{"collection":"statements","documentId":"1234"}\' --scope children');
|
|
15182
|
-
console.log(' remits-cli tool --name mcp_run_action --input \'{"
|
|
16259
|
+
console.log(' remits-cli tool --name mcp_run_action --account-id 21 --as-account 36 --target-account 36 --input \'{"actionId":200,"executionMode":"async"}\' --data-mode prod');
|
|
15183
16260
|
console.log('');
|
|
15184
16261
|
console.log('For mcp_run_action and mcp_run_agent, prefer the tool\'s own executionMode:"async" and poll with that tool\'s controlAction:"status".');
|
|
15185
16262
|
}
|
|
15186
16263
|
|
|
15187
16264
|
function printToolsHelp() {
|
|
15188
|
-
console.log('Usage: remits-cli tools [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--variant-branch NAME|none]');
|
|
16265
|
+
console.log('Usage: remits-cli tools [--base-url URL] [--account-id ID] [--as-account ID] [--branch BRANCH] [--data-mode test|prod] [--variant-branch NAME|none]');
|
|
15189
16266
|
console.log('');
|
|
15190
16267
|
console.log('Refreshes .remits-cli/tools/tools.json for the current account/branch/data-mode context.');
|
|
15191
16268
|
}
|
|
15192
16269
|
|
|
16270
|
+
function printGuidesHelp() {
|
|
16271
|
+
console.log('Usage: remits-cli guides sync [--all] [--base-url URL] [--json]');
|
|
16272
|
+
console.log('');
|
|
16273
|
+
console.log('Refreshes front-stage guides from the authenticated platform.');
|
|
16274
|
+
console.log(' --all updates every indexed account checkout on this machine.');
|
|
16275
|
+
console.log(' without --all, run from an account repo to refresh only that checkout.');
|
|
16276
|
+
}
|
|
16277
|
+
|
|
16278
|
+
async function guidesCommand(flags, subcommand) {
|
|
16279
|
+
const mode = subcommand || 'sync';
|
|
16280
|
+
if (mode === 'help' || mode === '--help' || flagEnabled(flags.help)) {
|
|
16281
|
+
printGuidesHelp();
|
|
16282
|
+
return;
|
|
16283
|
+
}
|
|
16284
|
+
if (mode !== 'sync' && mode !== 'refresh' && mode !== 'update') {
|
|
16285
|
+
throw new Error('Unknown guides command: ' + mode + '. Use: remits-cli guides sync [--all]');
|
|
16286
|
+
}
|
|
16287
|
+
|
|
16288
|
+
if (flagEnabled(flags.all)) {
|
|
16289
|
+
const result = await syncGuidesAcrossIndexedRepos({
|
|
16290
|
+
baseUrl: flags['base-url'] || null,
|
|
16291
|
+
rescan: !flagEnabled(flags['no-rescan'])
|
|
16292
|
+
});
|
|
16293
|
+
if (flagEnabled(flags.json)) {
|
|
16294
|
+
console.log(JSON.stringify(result, null, 2));
|
|
16295
|
+
return;
|
|
16296
|
+
}
|
|
16297
|
+
console.log('Guides sync: ' + guideSyncSummaryLine(result));
|
|
16298
|
+
for (const repo of result.repos || []) {
|
|
16299
|
+
if (repo.skipped) {
|
|
16300
|
+
console.log(' skipped account ' + (repo.accountId || '?') + ': ' + repo.reason + ' ' + repo.directory);
|
|
16301
|
+
} else if ((repo.updated || []).length || (repo.removed || []).length) {
|
|
16302
|
+
console.log(' updated account ' + repo.accountId + ': ' +
|
|
16303
|
+
((repo.updated || []).length + (repo.removed || []).length) + ' file(s) ' + repo.directory);
|
|
16304
|
+
}
|
|
16305
|
+
}
|
|
16306
|
+
return;
|
|
16307
|
+
}
|
|
16308
|
+
|
|
16309
|
+
if (!loadAccountInfo(process.cwd())) {
|
|
16310
|
+
throw new Error('Run `remits-cli guides sync` from an account repo, or pass --all.');
|
|
16311
|
+
}
|
|
16312
|
+
const before = Date.now();
|
|
16313
|
+
await ensureGuidesSynced(process.cwd(), flags);
|
|
16314
|
+
if (flagEnabled(flags.json)) {
|
|
16315
|
+
console.log(JSON.stringify({ success: true, directory: process.cwd(), elapsedMs: Date.now() - before }, null, 2));
|
|
16316
|
+
return;
|
|
16317
|
+
}
|
|
16318
|
+
console.log('Guides synced for ' + process.cwd());
|
|
16319
|
+
}
|
|
16320
|
+
|
|
15193
16321
|
function printTokenHelp() {
|
|
15194
16322
|
console.log('Usage: remits-cli token [--base-url URL] [--branch BRANCH] [--path embeddable/path] [--data-mode test|prod] [--as-account ID] [--variant-branch NAME|none]');
|
|
15195
16323
|
console.log(' remits-cli token inspect --token <token|tokenKey|URL> [--base-url URL] [--account-id ID]');
|
|
@@ -15208,6 +16336,9 @@ function printVerifyHelp() {
|
|
|
15208
16336
|
console.log(' remits-cli verify use <envelopeId>');
|
|
15209
16337
|
console.log(' remits-cli verify current');
|
|
15210
16338
|
console.log(' remits-cli verify clear [--all]');
|
|
16339
|
+
console.log(' remits-cli verify claim <id> --text "what must be true" [--test "<suite>"] [--packet-type TYPE]');
|
|
16340
|
+
console.log(' # declares a contract in ONE command when someone needs a verdict; no-contract envelopes are evidence logs');
|
|
16341
|
+
console.log(' # prove it by adding --claim <id> to any verify test/token/tool/stage/attach command');
|
|
15211
16342
|
console.log(' remits-cli verify show [--envelope ID] [--summary|--compact] [--json]');
|
|
15212
16343
|
console.log(' remits-cli verify status [--envelope ID]');
|
|
15213
16344
|
console.log(' remits-cli verify report [--envelope ID]');
|
|
@@ -15276,7 +16407,7 @@ async function main() {
|
|
|
15276
16407
|
try { updateAccountRepoIndex(process.cwd()); } catch (_) { /* not in account repo */ }
|
|
15277
16408
|
// Auto-start the background service if not already running.
|
|
15278
16409
|
// Skip for lifecycle subcommands and help.
|
|
15279
|
-
const isLifecycleCmd = command === 'listen' || command === 'start' || command === 'stop' || command === 'status' || command === 'whoami' || command === 'doctor' || command === 'agent' || command === 'ticket';
|
|
16410
|
+
const isLifecycleCmd = command === 'listen' || command === 'start' || command === 'stop' || command === 'status' || command === 'whoami' || command === 'doctor' || command === 'agent' || command === 'ticket' || command === 'guides';
|
|
15280
16411
|
const isHelpCmd = !command || command === 'help' || command === '--help' || wantsHelp;
|
|
15281
16412
|
if (!wantsJson && !isHelpCmd && command === 'components') {
|
|
15282
16413
|
const commandName = 'components ' + (subcommand || 'command');
|
|
@@ -15313,22 +16444,25 @@ async function main() {
|
|
|
15313
16444
|
console.log(' remits-cli agent status [--state idle|working|paused] [--ticket ID] [--activity "..."]');
|
|
15314
16445
|
console.log(' remits-cli agent list [--account-id ID] # who else is available');
|
|
15315
16446
|
console.log(' remits-cli agent map [--account-ids 1,4] [--json] # who is EDITING what, where, right now');
|
|
15316
|
-
console.log(' remits-cli activity inspect [--account-id ID] [--scope self|children] [--user-email E] [--summary|--compact] [--json]');
|
|
16447
|
+
console.log(' remits-cli activity inspect [--account-id ID] [--scope self|children|related] [--workspace NAME] [--workstream ID] [--user-email E] [--summary|--compact] [--json]');
|
|
16448
|
+
console.log(' remits-cli workstream status [--workstream ID|--workspace NAME] [--json]');
|
|
15317
16449
|
console.log(' remits-cli agent release # stop receiving tickets now');
|
|
15318
16450
|
console.log(' remits-cli start [--foreground true] [--port 8787] # human dashboard in the browser');
|
|
15319
16451
|
console.log(' remits-cli stop');
|
|
15320
16452
|
console.log(' remits-cli status [--base-url URL] [--account-id ID] [--data-mode test|prod] [--json]');
|
|
15321
16453
|
console.log(' remits-cli doctor local-state [--json] # actor/workspace/local state hygiene');
|
|
15322
16454
|
console.log(' remits-cli whoami [--base-url URL] [--account-id ID] [--data-mode test|prod] [--json]');
|
|
16455
|
+
console.log(' remits-cli guides sync [--all] [--base-url URL] [--json] # refresh front-stage guides');
|
|
15323
16456
|
console.log(' remits-cli listen [stop|status] [--foreground true] # compatibility alias');
|
|
15324
16457
|
console.log(' remits-cli data-mode [set test|prod]');
|
|
15325
16458
|
console.log(' remits-cli install --skills [--target codex|claude|gemini|all] [--overwrite true]');
|
|
15326
|
-
console.log(' remits-cli tools [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--variant-branch NAME|none]');
|
|
15327
|
-
console.log(' remits-cli tool --name <toolName> [--base-url URL] [--branch BRANCH] [--input \"{...}\"|--input-file file.json] [--data-mode test|prod] [--scope self|children|hierarchy] [--
|
|
16459
|
+
console.log(' remits-cli tools [--base-url URL] [--account-id ID] [--as-account ID] [--branch BRANCH] [--data-mode test|prod] [--variant-branch NAME|none]');
|
|
16460
|
+
console.log(' remits-cli tool --name <toolName> [--base-url URL] [--account-id ID] [--as-account ID] [--target-account ID] [--branch BRANCH] [--input \"{...}\"|--input-file file.json] [--data-mode test|prod] [--scope self|children|hierarchy] [--account-ids 1,2,3] [--variant-branch NAME|none] [--timeout-ms 60000] [--async true --wait true]');
|
|
15328
16461
|
console.log(' remits-cli tool status --call-id <callId> [--base-url URL] [--account-id ID] [--data-mode test|prod]');
|
|
15329
16462
|
console.log(' remits-cli workspace [show|use <name>|use --auto|clear] # isolate staging when several agents share a repo');
|
|
15330
|
-
console.log(' remits-cli
|
|
15331
|
-
|
|
16463
|
+
console.log(' remits-cli evidence [--limit N] [--json] # what you have run and in which world (always recorded)');
|
|
16464
|
+
console.log(' remits-cli verify start --summary "..." [--manifest file.json] # OPTIONAL: ask for a checkable verdict');
|
|
16465
|
+
console.log(' remits-cli verify [current|list|use|clear|show|status|report|claim|attach|abandon|supersede|test|token|stage|sync|tool]');
|
|
15332
16466
|
console.log(' remits-cli components stage [--workset|--changed-only] [--base-url URL] [--account-id ID] [--branch BRANCH] [--workspace NAME] [--data-mode test|prod] [--json|--verbose]');
|
|
15333
16467
|
console.log(' remits-cli components status [--base-url URL] [--account-id ID] [--branch BRANCH] [--workspace NAME] [--component-type TYPE --component-id ID] [--json|--verbose]');
|
|
15334
16468
|
console.log(' remits-cli components lanes [--base-url URL] [--account-id ID] [--json]');
|
|
@@ -15407,6 +16541,11 @@ async function main() {
|
|
|
15407
16541
|
process.exit(0);
|
|
15408
16542
|
}
|
|
15409
16543
|
|
|
16544
|
+
if (wantsHelp && command === 'guides') {
|
|
16545
|
+
printGuidesHelp();
|
|
16546
|
+
process.exit(0);
|
|
16547
|
+
}
|
|
16548
|
+
|
|
15410
16549
|
if (wantsHelp && command === 'token') {
|
|
15411
16550
|
printTokenHelp();
|
|
15412
16551
|
process.exit(0);
|
|
@@ -15427,6 +16566,11 @@ async function main() {
|
|
|
15427
16566
|
return;
|
|
15428
16567
|
}
|
|
15429
16568
|
|
|
16569
|
+
if (command === 'guides') {
|
|
16570
|
+
await guidesCommand(args, subcommand);
|
|
16571
|
+
return;
|
|
16572
|
+
}
|
|
16573
|
+
|
|
15430
16574
|
if (command === 'components' && (subcommand === 'push' || subcommand === 'stage')) {
|
|
15431
16575
|
if (subcommand === 'push' && !args.mode) {
|
|
15432
16576
|
args.mode = 'push';
|
|
@@ -15539,11 +16683,16 @@ async function main() {
|
|
|
15539
16683
|
|
|
15540
16684
|
if (command === 'activity') {
|
|
15541
16685
|
if (wantsHelp || !subcommand) {
|
|
15542
|
-
console.log('Usage: remits-cli activity inspect [--account-id ID] [--scope self|children] [--user-id ID|--user-email EMAIL] [--limit N] [--summary|--compact] [--json]');
|
|
16686
|
+
console.log('Usage: remits-cli activity inspect [--account-id ID] [--scope self|children|related] [--workspace NAME] [--branch NAME] [--workstream ID] [--user-id ID|--user-email EMAIL] [--limit N] [--summary|--compact] [--json]');
|
|
15543
16687
|
console.log('');
|
|
15544
16688
|
console.log('Read-only workstream inspection for agent/operator review. Joins current staging lanes,');
|
|
15545
16689
|
console.log('verification envelopes, durable test runs, corpus measurements, live agent presence,');
|
|
15546
16690
|
console.log('and heuristic smell signals without changing any lane or checkout.');
|
|
16691
|
+
console.log('');
|
|
16692
|
+
console.log('--scope related also follows component owners, staging lanes and verification packet worlds');
|
|
16693
|
+
console.log('out of the hierarchy. --workspace / --branch / --workstream NARROW it: the account set becomes');
|
|
16694
|
+
console.log('the accounts with matching evidence (plus this account and its component owners), and the');
|
|
16695
|
+
console.log('lanes, envelopes, test runs and corpus results are filtered to that workstream.');
|
|
15547
16696
|
return;
|
|
15548
16697
|
}
|
|
15549
16698
|
if (subcommand === 'inspect') {
|
|
@@ -15553,6 +16702,39 @@ async function main() {
|
|
|
15553
16702
|
throw new Error('Unknown activity subcommand: ' + subcommand);
|
|
15554
16703
|
}
|
|
15555
16704
|
|
|
16705
|
+
if (command === 'evidence') {
|
|
16706
|
+
if (wantsHelp) {
|
|
16707
|
+
console.log('Usage: remits-cli evidence [--limit N] [--json]');
|
|
16708
|
+
console.log('');
|
|
16709
|
+
console.log('What you have actually run, and in which world. Every stage, test, token, tool and sync');
|
|
16710
|
+
console.log('appends one world-stamped line automatically - there is nothing to start and nothing to');
|
|
16711
|
+
console.log('satisfy. Grouped by world, because the same suite run in two lanes is two different facts.');
|
|
16712
|
+
console.log('');
|
|
16713
|
+
console.log('This is per-actor, so agents sharing a checkout never read each other\'s trail.');
|
|
16714
|
+
console.log('Use `remits-cli verify` only when someone needs a checkable VERDICT on top of this.');
|
|
16715
|
+
return;
|
|
16716
|
+
}
|
|
16717
|
+
await evidenceCommand(args);
|
|
16718
|
+
return;
|
|
16719
|
+
}
|
|
16720
|
+
|
|
16721
|
+
if (command === 'workstream') {
|
|
16722
|
+
if (wantsHelp) {
|
|
16723
|
+
console.log('Usage: remits-cli workstream status [--workstream ID|--workspace NAME] [--json]');
|
|
16724
|
+
console.log('');
|
|
16725
|
+
console.log('Shows the local logical proof campaign index across actor-scoped state. It references');
|
|
16726
|
+
console.log('tool response files, verification packets, actors and account/world roles without copying payloads.');
|
|
16727
|
+
console.log('');
|
|
16728
|
+
console.log('The id is stamped onto every verification packet world, so the same value works server-side:');
|
|
16729
|
+
console.log(' remits-cli activity inspect --scope related --workstream <id>');
|
|
16730
|
+
console.log('This is NOT a support ticket\'s --workstream, which is an account\'s own routing label matched');
|
|
16731
|
+
console.log('by `agent serve --serves`. Same flag name, unrelated namespaces.');
|
|
16732
|
+
return;
|
|
16733
|
+
}
|
|
16734
|
+
await workstreamCommand(args, subcommand);
|
|
16735
|
+
return;
|
|
16736
|
+
}
|
|
16737
|
+
|
|
15556
16738
|
if (command === 'ticket') {
|
|
15557
16739
|
if (wantsHelp || !subcommand) {
|
|
15558
16740
|
printTicketHelp();
|