@remits/remits-cli 0.1.150 → 0.1.152

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -8,14 +8,14 @@
8
8
  - L621 Local State, Workspaces, And Verification Evidence
9
9
  - L2659 Account Repos, Guide Sync, And Platform Repo
10
10
  - L3499 Component Discovery And HTTP Logging
11
- - L4225 Skill Delivery And TOC Resolution
12
- - L4540 Auth And Component Staging
13
- - L5172 Component Summaries, Status, And Sync Gates
14
- - L7473 Branches, Promotion, Commit, And Test Runs
15
- - L9562 Tokens, Tools, Verification, And Config
16
- - L10989 Service Dashboard And WebSocket Listener
17
- - L13488 Agent And Ticket Workflows
18
- - L17623 Help, Auto Update, And Command Dispatch
11
+ - L4267 Skill Delivery And TOC Resolution
12
+ - L4582 Auth And Component Staging
13
+ - L5214 Component Summaries, Status, And Sync Gates
14
+ - L7515 Branches, Promotion, Commit, And Test Runs
15
+ - L9631 Tokens, Tools, Verification, And Config
16
+ - L11058 Service Dashboard And WebSocket Listener
17
+ - L13557 Agent And Ticket Workflows
18
+ - L17692 Help, Auto Update, And Command Dispatch
19
19
  */
20
20
 
21
21
  /*
@@ -4142,6 +4142,48 @@ function describeToolsSnapshotWorld(world) {
4142
4142
  'data ' + (world.dataMode || '?')].filter(Boolean).join(', ');
4143
4143
  }
4144
4144
 
4145
+ // How many times a `WORKLOAD_BUSY` refusal is retried before it is handed to the caller. About 90 seconds
4146
+ // in total: long enough to ride out another agent's stage or sync, short enough that a gate filled by
4147
+ // long-running suites still answers as "busy" rather than as a hang.
4148
+ const WORKLOAD_BUSY_MAX_RETRIES = 6;
4149
+
4150
+ /**
4151
+ * How long to wait before retrying a request the platform refused as `WORKLOAD_BUSY`, or null when the
4152
+ * failure is anything else (or the retries are spent).
4153
+ *
4154
+ * The platform admits only a few heavyweight requests per instance and answers the rest with HTTP 429
4155
+ * BEFORE doing any work, so repeating the identical request is safe — which is not true of any other
4156
+ * failure, and is why this matches on the code rather than on the status alone. The wait starts at the
4157
+ * server's Retry-After and grows, with jitter so several refused agents do not come back in step.
4158
+ */
4159
+ function workloadBusyRetryDelayMs(err, attempt, maxRetries, random) {
4160
+ const response = err && err.response;
4161
+ const body = response && response.data;
4162
+ if (!response || response.status !== 429 || !body || body.code !== 'WORKLOAD_BUSY') return null;
4163
+ if (attempt >= maxRetries) return null;
4164
+ const headers = response.headers || {};
4165
+ const advised = Number(body.retryAfterSeconds || headers['retry-after']);
4166
+ const baseSeconds = Number.isFinite(advised) && advised > 0 ? Math.min(advised, 60) : 5;
4167
+ const grown = Math.min(baseSeconds * Math.pow(1.5, attempt), 30);
4168
+ return Math.round(grown * 1000 * (0.75 + 0.5 * random()));
4169
+ }
4170
+
4171
+ async function postRidingOutBusy(api, endpoint, payload) {
4172
+ const configured = Number(process.env.REMITS_CLI_BUSY_RETRIES);
4173
+ const maxRetries = Number.isInteger(configured) && configured >= 0 ? configured : WORKLOAD_BUSY_MAX_RETRIES;
4174
+ for (let attempt = 0; ; attempt += 1) {
4175
+ try {
4176
+ return await api.post(endpoint, payload);
4177
+ } catch (err) {
4178
+ const delayMs = workloadBusyRetryDelayMs(err, attempt, maxRetries, Math.random);
4179
+ if (delayMs == null) throw err;
4180
+ console.error('Platform instance is busy with ' + (err.response.data.workload || 'other work') +
4181
+ '; retrying in ' + Math.round(delayMs / 1000) + 's (' + (attempt + 1) + '/' + maxRetries + ').');
4182
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
4183
+ }
4184
+ }
4185
+ }
4186
+
4145
4187
  async function loggedPost(api, cwd, endpoint, payload, options = {}) {
4146
4188
  const requestId = options.requestId || crypto.randomUUID();
4147
4189
  const started = Date.now();
@@ -4155,7 +4197,7 @@ async function loggedPost(api, cwd, endpoint, payload, options = {}) {
4155
4197
  }
4156
4198
 
4157
4199
  try {
4158
- responseData = await api.post(endpoint, payload).then((r) => r.data);
4200
+ responseData = await postRidingOutBusy(api, endpoint, payload).then((r) => r.data);
4159
4201
  if (options.toolResponseFile) {
4160
4202
  const callId = options.toolCallId || (responseData && responseData.callId) || requestId;
4161
4203
  responseFile = writeToolResponseFile(cwd, callId, responseData);
@@ -8537,6 +8579,32 @@ function selectPivotCases(tests, limit = 6) {
8537
8579
  return { shown: representatives.slice(0, limit), failedCount: failed.length, rootCount: representatives.length };
8538
8580
  }
8539
8581
 
8582
+ // What a PASSING case reported: report(...) / diagnostic(...) values and captured println output. Failed
8583
+ // cases print theirs in the pivot block below. Without this, the only way to read a value out of a run
8584
+ // was to end the case in a deliberately failing assertion. Bounded, because this output is an agent's
8585
+ // context: a few cases, a few hundred characters each, and a count for the rest.
8586
+ const PASSING_DIAGNOSTIC_CASES = 6;
8587
+ const PASSING_DIAGNOSTIC_CHARS = 700;
8588
+ function passingCaseDiagnostics(tests) {
8589
+ return (Array.isArray(tests) ? tests : []).filter((test) => {
8590
+ const passed = test && (test.passed === true || test.outcome === 'passed') && !test.error;
8591
+ const diagnostics = test && test.diagnostics && typeof test.diagnostics === 'object' ? test.diagnostics : null;
8592
+ return passed && diagnostics && Object.keys(diagnostics).length > 0;
8593
+ });
8594
+ }
8595
+ function printPassingCaseDiagnostics(tests) {
8596
+ const reporting = passingCaseDiagnostics(tests);
8597
+ if (!reporting.length) return;
8598
+ reporting.slice(0, PASSING_DIAGNOSTIC_CASES).forEach((test) => {
8599
+ const text = JSON.stringify(test.diagnostics);
8600
+ console.log('Reported by passing case:', (test.name || '(unnamed)'));
8601
+ console.log(' Diagnostics:', text.length > PASSING_DIAGNOSTIC_CHARS ? text.slice(0, PASSING_DIAGNOSTIC_CHARS) + '... (' + text.length + ' chars; --json for all)' : text);
8602
+ });
8603
+ if (reporting.length > PASSING_DIAGNOSTIC_CASES) {
8604
+ console.log(' ' + (reporting.length - PASSING_DIAGNOSTIC_CASES) + ' more passing case(s) reported diagnostics; use --json to read them.');
8605
+ }
8606
+ }
8607
+
8540
8608
  function printTestRunPivots(status = {}) {
8541
8609
  const tests = status.result && Array.isArray(status.result.tests) ? status.result.tests : [];
8542
8610
  if (!tests.length) return;
@@ -8544,6 +8612,7 @@ function printTestRunPivots(status = {}) {
8544
8612
  if (slowest && slowest.duration != null) {
8545
8613
  console.log('Slowest case:', (slowest.name || '(unnamed)') + ' in ' + formatDurationMs(slowest.duration));
8546
8614
  }
8615
+ printPassingCaseDiagnostics(tests);
8547
8616
  const selection = selectPivotCases(tests);
8548
8617
  selection.shown.forEach((test) => {
8549
8618
  console.log('Pivots for failed case:', test.name || '(unnamed)');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remits/remits-cli",
3
- "version": "0.1.150",
3
+ "version": "0.1.152",
4
4
  "description": "Local CLI for auth, component sync, and live test execution against Remits",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -736,9 +736,10 @@ Read `classification` first:
736
736
  - `ORPHANED_*` / `RECOVERED_*` — the attempt was abandoned; read `abandonmentCause`.
737
737
  - `REQUEST_TIMEOUT_LIKELY` — it used its whole deadline (`timing.deadlineUsed` near `1.0`). The unit of
738
738
  work is too big for one event; the fix is resumable batches, not component logic.
739
- - `PROCESS_TERMINATED_LIKELY` — it stopped well inside its deadline (`timing.deadlineUsed` near `0`), so
740
- the worker was killed (memory pressure, restart). Not a logic bug; inspect JVM/node health and
741
- container lifecycle logs.
739
+ - `PROCESS_TERMINATED_LIKELY` — its last recorded sign of life came well inside its deadline
740
+ (`timing.deadlineUsed` near `0`). A killed worker (memory pressure, restart) is one explanation; a
741
+ stalled heartbeat or a missed completion write leaves the same evidence. Not a logic bug; check the
742
+ correlated worker logs, then JVM/node health and container lifecycle logs.
742
743
  - `UNKNOWN_NO_DEADLINE_EVIDENCE` — no deadline was recorded, so the cause is genuinely unknown. Use the
743
744
  returned `logQuery` filters; do not assume. Events predating delivery-envelope capture always look
744
745
  like this.
@@ -996,14 +997,17 @@ every command, so you navigate iteratively.
996
997
  | `ticketId` / `artifactType` / `artifactLabel` / `artifactNotes` | no | Attach a screenshot/pdf/trace/video to a support ticket |
997
998
 
998
999
  ### `mcp_jvm_spike_triage`
999
- Production-aware triage bundle for a Cloud Run service showing a latency/memory spike. Safe by construction:
1000
- optional class-histogram sampling, **no heap dump and no JFR**.
1000
+ Single-instance triage bundle for a Cloud Run service showing a latency/memory spike.
1001
+ Summary, optional histogram and stacks come from one request with an explicit process UUID.
1002
+ Requires the bundle-capable platform; deploy it before promoting the updated System Account tool.
1003
+ Preserves container/heap/non-heap memory, classes, buffers, all-thread family totals and HTTP pool counters.
1004
+ Optional logs are service-region scope and can concern other JVMs. No heap dump or JFR.
1001
1005
 
1002
1006
  | Parameter | Required | Description |
1003
1007
  |-----------|----------|-------------|
1004
1008
  | `serviceName` | yes | e.g. `remits`, `remits-actions` |
1005
1009
  | `region` | yes | e.g. `us-east5`, `us-east1` |
1006
- | `sampleHeap` | no | Default true — one class histogram + heap composition sample (brief stop-the-world) |
1010
+ | `sampleHeap` | no | Default false — explicitly request a live-object histogram; it can trigger GC and pause the JVM |
1007
1011
  | `top` | no | Top classes to return. Default 20, max 100. |
1008
1012
  | `includeThreadDump` / `threadLimit` | no | Fuller thread dump beyond the built-in top-thread preview |
1009
1013
  | `includeLogs` / `logLookbackMinutes` / `logLimit` | no | Recent WARNING+ log signals for the same service |
@@ -25,6 +25,7 @@
25
25
  | Need to learn command behavior | Read the `remits-cli` skill and its references, repo docs, or CLI source. Do not run unsupported mutating subcommand variants such as `remits-cli components commit --help`; if a command unexpectedly mutates or commits, run `remits-cli doctor local-state` and inspect the active actor's repo-local session log before doing anything else. |
26
26
  | Sync response reports unexpected deletes, creates, renames, uniqueness errors, or component ID drift | Stop immediately. Do not rerun sync, do not create replacement components to paper over the mismatch, and do not promote more changes. Preserve session logs/tool responses, compare `account-info.json`, local filenames, live inventory, and git history, then prepare a repair/escalation summary. |
27
27
  | 500 / `Internal Server Error` from Remits | Stop normal task work. Capture the exact command, error, and response artifact, then escalate to a Remits system admin. Do not invent a workaround. |
28
+ | `Platform instance is busy with ...; retrying` on stderr, or a final `WORKLOAD_BUSY` (HTTP 429) | The platform runs only a few stage/sync/commit requests and Test suites at once per instance, and refused yours before doing any work. The CLI already retried for about 90 seconds. Nothing is broken and nothing was half-applied by the refused request: wait for the other work and run the same command again. If the refusal came from the last step of `components commit`, read the outcome it printed: a branch that was pushed but not synced needs `remits-cli components sync`, not another commit. |
28
29
  | Tool parameters rejected | Tool schemas may be cached. Run `remits-cli tools` to refresh `.remits-cli/shared/tools/tools.json` with latest schemas. |
29
30
  | Tool response missing | Run `remits-cli doctor local-state`, then check `./.remits-cli/actors/<local-agent>/tool-responses/` and any legacy flat `./.remits-cli/tool-responses/` fallback it reports. |
30
31
  | Long-running tool times out | For `mcp_run_action`/`mcp_run_agent`, use the tool's own `executionMode:"async"` and poll with a `controlAction:"status"` call (see `command-reference.md` → *Tool Execution Lifecycle*). For other long tools without their own async, use `remits-cli tool --async true` and poll `remits-cli tool status --call-id <callId>`. `--timeout-ms` only adjusts the per-request HTTP timeout; it is not a substitute for async. |