@remits/remits-cli 0.1.163 → 0.1.165
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/README.md +4 -0
- package/index.js +52 -20
- package/lib/local-ai.js +4 -0
- package/lib/websocket-retry.js +67 -0
- package/package.json +2 -1
- package/skills/remits-cli/SKILL.md +14 -4
- package/skills/remits-cli/references/cli-state.md +10 -4
- package/skills/remits-cli/references/command-reference.md +6 -4
- package/skills/remits-cli/references/development-loop.md +3 -2
package/README.md
CHANGED
|
@@ -139,6 +139,10 @@ remits-cli install --skills --overwrite true
|
|
|
139
139
|
- The service groups sessions by `baseUrl` so one websocket connection can service multiple authenticated accounts on the same Remits environment.
|
|
140
140
|
- Websocket subscriptions are deduplicated by user topic. If multiple authenticated accounts share the same websocket topic, the listener subscribes once and maps that topic back to all related accounts.
|
|
141
141
|
- The daemon keeps STOMP heartbeats enabled and runs a maintenance watchdog that recreates unhealthy websocket connections.
|
|
142
|
+
- Terminal server authentication/authorization errors suspend WebSocket retries and show
|
|
143
|
+
`authentication_required`. Reauthenticate to replace the managed client's credentials; the watchdog
|
|
144
|
+
does not revive rejected credentials. Transient failures use jittered exponential backoff up to
|
|
145
|
+
60 seconds. Test watchers keep HTTP polling; restart local-AI workers after reauthentication.
|
|
142
146
|
- If the machine sleeps, the network drops, or auth sessions change while the daemon is already running, the service now attempts to reconnect and resubscribe automatically once connectivity returns.
|
|
143
147
|
- Support tickets are the primary unit of dispatched work. A serving agent launches one fresh worker process per routed ticket.
|
|
144
148
|
- Agent sessions register themselves with `remits-cli agent register` or `remits-cli agent serve`; `serve` also starts the local supervisor.
|
package/index.js
CHANGED
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
## Table of Contents
|
|
5
5
|
|
|
6
6
|
- L22 Runtime Bootstrap And Shared State
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
7
|
+
- L125 Sessions, Account Resolution, And Production Guards
|
|
8
|
+
- L631 Local State, Workspaces, And Verification Evidence
|
|
9
|
+
- L2669 Account Repos, Guide Sync, And Platform Repo
|
|
10
|
+
- L3509 Component Discovery And HTTP Logging
|
|
11
|
+
- L4279 Skill Delivery And TOC Resolution
|
|
12
|
+
- L4594 Auth And Component Staging
|
|
13
|
+
- L5226 Component Summaries, Status, And Sync Gates
|
|
14
|
+
- L7550 Branches, Promotion, Commit, And Test Runs
|
|
15
|
+
- L9813 Tokens, Tools, Verification, And Config
|
|
16
|
+
- L11330 Service Dashboard And WebSocket Listener
|
|
17
|
+
- L13840 Agent And Ticket Workflows
|
|
18
|
+
- L18101 Help, Auto Update, And Command Dispatch
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
/*
|
|
@@ -32,6 +32,7 @@ const axios = require('axios');
|
|
|
32
32
|
const openModule = require('open');
|
|
33
33
|
const { Client } = require('@stomp/stompjs');
|
|
34
34
|
const WebSocket = require('ws');
|
|
35
|
+
const { installWebsocketRetry } = require('./lib/websocket-retry');
|
|
35
36
|
const YAML = require('yaml');
|
|
36
37
|
|
|
37
38
|
Object.assign(global, { WebSocket });
|
|
@@ -898,7 +899,7 @@ function localStateWarnings(cwd, flags = {}) {
|
|
|
898
899
|
.sort((a, b) => Date.parse(b.updatedAt || 0) - Date.parse(a.updatedAt || 0));
|
|
899
900
|
const shown = recent.slice(0, 3).map((a) => a.actor);
|
|
900
901
|
const stale = report.foreignActors.length - recent.length;
|
|
901
|
-
const setOne = 'Name this one with --
|
|
902
|
+
const setOne = 'Name this one with --local-agent <name> (or REMITS_LOCAL_AGENT_ID) if another agent shares this checkout; REMITS_AGENT_ID also declares agent identity to the platform.';
|
|
902
903
|
const pruneHint = stale
|
|
903
904
|
? stale + ' spent actor dir(s) (idle, or left behind by finished processes) can be cleared with `remits-cli doctor local-state --prune-actors`.'
|
|
904
905
|
: '';
|
|
@@ -3668,7 +3669,9 @@ function collectComponents(cwd) {
|
|
|
3668
3669
|
component.html = content;
|
|
3669
3670
|
} else if (type === 'htmltemplate' && ext === 'json') {
|
|
3670
3671
|
component.previewData = content;
|
|
3671
|
-
} else if (type === 'tool' && ext === 'json') {
|
|
3672
|
+
} else if ((type === 'tool' || type === 'action') && ext === 'json') {
|
|
3673
|
+
// An Action's .json sidecar is its input schema (manual-run form and x-remits-operations), exactly as
|
|
3674
|
+
// repository sync maps it; staging used to drop it, so a staged declaration silently did not apply.
|
|
3672
3675
|
component.inputSchema = content;
|
|
3673
3676
|
} else if (type === 'i18n' && ext === 'json') {
|
|
3674
3677
|
component.messages = content;
|
|
@@ -3849,7 +3852,7 @@ function componentPathInfo(cwd, filePath) {
|
|
|
3849
3852
|
else if (type === 'embeddable' && ext === 'js') field = 'javascript';
|
|
3850
3853
|
else if (type === 'htmltemplate' && ext === 'html') field = 'html';
|
|
3851
3854
|
else if (type === 'htmltemplate' && ext === 'json') field = 'previewData';
|
|
3852
|
-
else if (type === 'tool' && ext === 'json') field = 'inputSchema';
|
|
3855
|
+
else if ((type === 'tool' || type === 'action') && ext === 'json') field = 'inputSchema';
|
|
3853
3856
|
else if (type === 'i18n' && ext === 'json') field = 'messages';
|
|
3854
3857
|
else if (type === 'schema' && ext === 'json') field = 'schema';
|
|
3855
3858
|
|
|
@@ -9129,10 +9132,18 @@ async function waitForToolStatus(api, cwd, options) {
|
|
|
9129
9132
|
|
|
9130
9133
|
// One compact progress line for a streamed TestSuite frame: a finished case, or the suite roll-up.
|
|
9131
9134
|
function testSuiteProgressLine(payload = {}) {
|
|
9135
|
+
if (payload.event === 'case_started') {
|
|
9136
|
+
return '[ws] running ' + (payload.activeCases || []).map((c) =>
|
|
9137
|
+
c.name + (c.traceId ? ' [trace ' + c.traceId + ']' : '')).join(', ');
|
|
9138
|
+
}
|
|
9132
9139
|
if (payload.total != null || Array.isArray(payload.tests)) {
|
|
9133
9140
|
const total = payload.total != null ? payload.total : (payload.tests || []).length;
|
|
9134
9141
|
const passed = payload.passed != null ? payload.passed : (payload.tests || []).filter((t) => t && t.passed).length;
|
|
9135
|
-
|
|
9142
|
+
const active = (payload.activeCases || []).map((c) => c.name + (c.traceId ? ' [trace ' + c.traceId + ']' : ''));
|
|
9143
|
+
const queued = (payload.shards || []).filter((s) => s.status === 'queued').length;
|
|
9144
|
+
return '[ws] ' + (payload.testName || 'suite') + ': ' + passed + '/' + total + ' passed' +
|
|
9145
|
+
(payload.status ? ' (' + payload.status + ')' : '') +
|
|
9146
|
+
(active.length ? '; running: ' + active.join(', ') : '') + (queued ? '; ' + queued + ' workers queued' : '');
|
|
9136
9147
|
}
|
|
9137
9148
|
const mark = payload.passed === false ? 'FAIL' : 'pass';
|
|
9138
9149
|
const detail = payload.outcome && payload.outcome !== 'passed' ? ' [' + payload.outcome + ']' : '';
|
|
@@ -9144,7 +9155,16 @@ function watchWebsocket(baseUrl, topicId, taskId, options = {}) {
|
|
|
9144
9155
|
const emit = options.stderr ? console.error : console.log;
|
|
9145
9156
|
const client = new Client({
|
|
9146
9157
|
brokerURL: webSocketUrl(baseUrl),
|
|
9147
|
-
connectHeaders: {
|
|
9158
|
+
connectHeaders: {
|
|
9159
|
+
'X-Auth-Token': options.token || '',
|
|
9160
|
+
'x-remits-ws-label': 'cli-test',
|
|
9161
|
+
'x-remits-client-kind': 'cli',
|
|
9162
|
+
'x-remits-tab-id': 'cli:' + process.pid + ':test:' + crypto.randomUUID(),
|
|
9163
|
+
'x-remits-origin': 'remits-cli://test',
|
|
9164
|
+
'x-remits-path': 'test/watch',
|
|
9165
|
+
'x-remits-location': normalizeBaseUrl(baseUrl),
|
|
9166
|
+
'x-remits-broker': webSocketUrl(baseUrl)
|
|
9167
|
+
},
|
|
9148
9168
|
reconnectDelay: 3000,
|
|
9149
9169
|
heartbeatIncoming: 10000,
|
|
9150
9170
|
heartbeatOutgoing: 10000,
|
|
@@ -9162,7 +9182,7 @@ function watchWebsocket(baseUrl, topicId, taskId, options = {}) {
|
|
|
9162
9182
|
if (payload.taskId && followed.length && !followed.includes(payload.taskId)) {
|
|
9163
9183
|
return;
|
|
9164
9184
|
}
|
|
9165
|
-
if (payload.tests || payload.name || payload.total != null) {
|
|
9185
|
+
if (payload.tests || payload.name || payload.total != null || payload.event === 'case_started') {
|
|
9166
9186
|
// Progress, not the payload. This used to print the whole suite result — every case, every
|
|
9167
9187
|
// stacktrace — as one enormous line immediately before the readable summary said the same thing,
|
|
9168
9188
|
// which buried the run world, the outcomes and the pivots an agent actually reads. --verbose still
|
|
@@ -9176,6 +9196,7 @@ function watchWebsocket(baseUrl, topicId, taskId, options = {}) {
|
|
|
9176
9196
|
});
|
|
9177
9197
|
};
|
|
9178
9198
|
|
|
9199
|
+
installWebsocketRetry(client, { onBlocked: code => emit('[ws] Authentication required (' + code + '); HTTP polling continues.') });
|
|
9179
9200
|
client.activate();
|
|
9180
9201
|
return () => {
|
|
9181
9202
|
client.deactivate();
|
|
@@ -9334,6 +9355,7 @@ async function testCommand(flags) {
|
|
|
9334
9355
|
workspace,
|
|
9335
9356
|
dataMode,
|
|
9336
9357
|
dataModeSource,
|
|
9358
|
+
caseConcurrency: flags.parallel !== undefined && runOptions.parallel === 1 ? 1 : undefined,
|
|
9337
9359
|
testId: /^\d+$/.test(String(testRef)) ? Number(testRef) : undefined,
|
|
9338
9360
|
testName: /^\d+$/.test(String(testRef)) ? undefined : String(testRef),
|
|
9339
9361
|
tests: names
|
|
@@ -13538,6 +13560,7 @@ function createManagedWebsocketClient(baseUrl, urlSessions) {
|
|
|
13538
13560
|
discardWebsocketOnCommFailure: true,
|
|
13539
13561
|
connectHeaders: {
|
|
13540
13562
|
'x-remits-ws-label': 'remits-cli',
|
|
13563
|
+
'x-remits-client-kind': 'cli',
|
|
13541
13564
|
'x-remits-ws-scope': headerScope,
|
|
13542
13565
|
'x-remits-user-id': headerUserIds.length === 1 ? headerUserIds[0] : '',
|
|
13543
13566
|
'x-remits-account-id': headerAccountIds.length === 1 ? headerAccountIds[0] : '',
|
|
@@ -13643,7 +13666,7 @@ function createManagedWebsocketClient(baseUrl, urlSessions) {
|
|
|
13643
13666
|
ensureRuntimeWebsocketEntry(normalizedBaseUrl);
|
|
13644
13667
|
runtimeState.websocket[normalizedBaseUrl] = {
|
|
13645
13668
|
...runtimeState.websocket[normalizedBaseUrl],
|
|
13646
|
-
status: 'disconnected',
|
|
13669
|
+
status: client.remitsAuthBlocked ? 'authentication_required' : 'disconnected',
|
|
13647
13670
|
disconnectedAt: new Date().toISOString()
|
|
13648
13671
|
};
|
|
13649
13672
|
console.log('[listener] Disconnected from', normalizedBaseUrl, '- will reconnect');
|
|
@@ -13673,7 +13696,7 @@ function createManagedWebsocketClient(baseUrl, urlSessions) {
|
|
|
13673
13696
|
ensureRuntimeWebsocketEntry(normalizedBaseUrl);
|
|
13674
13697
|
runtimeState.websocket[normalizedBaseUrl] = {
|
|
13675
13698
|
...runtimeState.websocket[normalizedBaseUrl],
|
|
13676
|
-
status: 'disconnected',
|
|
13699
|
+
status: client.remitsAuthBlocked ? 'authentication_required' : 'disconnected',
|
|
13677
13700
|
disconnectedAt: new Date().toISOString(),
|
|
13678
13701
|
lastError: detail
|
|
13679
13702
|
};
|
|
@@ -13700,6 +13723,12 @@ function createManagedWebsocketClient(baseUrl, urlSessions) {
|
|
|
13700
13723
|
};
|
|
13701
13724
|
|
|
13702
13725
|
managedClient.client = client;
|
|
13726
|
+
installWebsocketRetry(client, { onBlocked: code => {
|
|
13727
|
+
runtimeState.websocket[normalizedBaseUrl] = {
|
|
13728
|
+
...runtimeState.websocket[normalizedBaseUrl], status: 'authentication_required', lastError: code
|
|
13729
|
+
};
|
|
13730
|
+
updateRuntimeServiceState();
|
|
13731
|
+
} });
|
|
13703
13732
|
return managedClient;
|
|
13704
13733
|
}
|
|
13705
13734
|
|
|
@@ -13745,6 +13774,9 @@ async function runManagedWebsocketWatchdog() {
|
|
|
13745
13774
|
if (!managedClient || !managedClient.client) {
|
|
13746
13775
|
continue;
|
|
13747
13776
|
}
|
|
13777
|
+
// A session-file change replaces the client. The watchdog must not revive rejected credentials
|
|
13778
|
+
// or defeat the normal transport backoff while it is waiting to reconnect.
|
|
13779
|
+
if (managedClient.client.remitsAuthBlocked || managedClient.client.remitsRetryPending) continue;
|
|
13748
13780
|
const state = ensureRuntimeWebsocketEntry(managedClient.baseUrl);
|
|
13749
13781
|
const socket = managedClient.client.webSocket;
|
|
13750
13782
|
const readyState = socket && typeof socket.readyState === 'number' ? socket.readyState : null;
|
package/lib/local-ai.js
CHANGED
|
@@ -6,6 +6,7 @@ const fs = require('node:fs');
|
|
|
6
6
|
const path = require('node:path');
|
|
7
7
|
const crypto = require('node:crypto');
|
|
8
8
|
const { spawn } = require('node:child_process');
|
|
9
|
+
const { installWebsocketRetry } = require('./websocket-retry');
|
|
9
10
|
|
|
10
11
|
const MAX_BYTES = 8 * 1024 * 1024;
|
|
11
12
|
const AGENTS = ['manual', 'codex', 'claude', 'gemini'];
|
|
@@ -623,6 +624,9 @@ async function command({ subcommand, flags, context, root, axios, Client, WebSoc
|
|
|
623
624
|
console.error('Local AI WebSocket unavailable; authenticated polling continues.');
|
|
624
625
|
}
|
|
625
626
|
});
|
|
627
|
+
installWebsocketRetry(socket, { onBlocked: code => {
|
|
628
|
+
console.error(`Local AI WebSocket authentication required (${code}); reauthenticate and restart the worker.`);
|
|
629
|
+
} });
|
|
626
630
|
socket.activate();
|
|
627
631
|
}
|
|
628
632
|
await supervisor.tick();
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The server sends stable, credential-free codes. A transport close code alone (including 1002)
|
|
4
|
+
// cannot distinguish expired credentials from a transient broker failure.
|
|
5
|
+
const TERMINAL_CODES = new Set(['credentials_missing', 'credentials_rejected', 'credentials_malformed',
|
|
6
|
+
'session_required', 'topic_denied', 'destination_denied', 'recipient_denied']);
|
|
7
|
+
|
|
8
|
+
function terminalCode(frame) {
|
|
9
|
+
const headers = frame?.headers || {};
|
|
10
|
+
const code = headers['x-remits-error-code'];
|
|
11
|
+
return TERMINAL_CODES.has(code) ? code : null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function installWebsocketRetry(client, { onBlocked = () => {}, random = Math.random,
|
|
15
|
+
setTimer = setTimeout, clearTimer = clearTimeout } = {}) {
|
|
16
|
+
const base = Math.max(1, Number(client.reconnectDelay) || 5000);
|
|
17
|
+
let failures = 0, wait = 0, timer, release;
|
|
18
|
+
const cancelWait = () => {
|
|
19
|
+
if (timer) clearTimer(timer);
|
|
20
|
+
timer = null;
|
|
21
|
+
if (release) release();
|
|
22
|
+
release = null;
|
|
23
|
+
client.remitsRetryPending = false;
|
|
24
|
+
};
|
|
25
|
+
const before = client.beforeConnect;
|
|
26
|
+
client.beforeConnect = async () => {
|
|
27
|
+
if (client.remitsAuthBlocked) { await client.deactivate(); return; }
|
|
28
|
+
if (wait > 0) {
|
|
29
|
+
client.remitsRetryPending = true;
|
|
30
|
+
await new Promise(resolve => { release = resolve; timer = setTimer(cancelWait, wait); });
|
|
31
|
+
// A stop/replacement may have resolved the wait; never read or send credentials afterwards.
|
|
32
|
+
if (!client.active || client.remitsAuthBlocked) return;
|
|
33
|
+
}
|
|
34
|
+
client.remitsRetryPending = false;
|
|
35
|
+
if (before) await before();
|
|
36
|
+
};
|
|
37
|
+
const connected = client.onConnect;
|
|
38
|
+
client.onConnect = frame => {
|
|
39
|
+
failures = 0; wait = 0; client.remitsRetryPending = false;
|
|
40
|
+
if (connected) connected(frame);
|
|
41
|
+
};
|
|
42
|
+
const closed = client.onWebSocketClose;
|
|
43
|
+
client.onWebSocketClose = event => {
|
|
44
|
+
const ceiling = Math.min(60000, base * Math.pow(2, Math.min(++failures, 5)));
|
|
45
|
+
// STOMP already waits base ms. Add jitter/backoff before its next CONNECT using public APIs.
|
|
46
|
+
wait = Math.max(0, Math.floor(ceiling / 2 + random() * ceiling / 2) - base);
|
|
47
|
+
client.remitsRetryPending = !client.remitsAuthBlocked;
|
|
48
|
+
if (closed) closed(event);
|
|
49
|
+
};
|
|
50
|
+
const error = client.onStompError;
|
|
51
|
+
client.onStompError = frame => {
|
|
52
|
+
const code = terminalCode(frame);
|
|
53
|
+
if (code) {
|
|
54
|
+
client.remitsAuthBlocked = code;
|
|
55
|
+
cancelWait();
|
|
56
|
+
// deactivate changes active synchronously, before STOMP schedules the close reconnect.
|
|
57
|
+
Promise.resolve(client.deactivate({ force: true })).catch(() => {});
|
|
58
|
+
}
|
|
59
|
+
if (error) error(frame);
|
|
60
|
+
if (code) onBlocked(code);
|
|
61
|
+
};
|
|
62
|
+
const deactivate = client.deactivate.bind(client);
|
|
63
|
+
client.deactivate = options => { cancelWait(); return deactivate(options); };
|
|
64
|
+
return client;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { installWebsocketRetry, terminalCode };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remits/remits-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.165",
|
|
4
4
|
"description": "Local CLI for auth, component sync, and live test execution against Remits",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"index.js",
|
|
14
14
|
"lib/sync-operation.js",
|
|
15
15
|
"lib/local-ai.js",
|
|
16
|
+
"lib/websocket-retry.js",
|
|
16
17
|
"README.md",
|
|
17
18
|
"scripts/prepare-index-toc.js",
|
|
18
19
|
"skills/remits-cli/SKILL.md",
|
|
@@ -181,10 +181,15 @@ reference named after it.
|
|
|
181
181
|
from `./.remits-cli/actors/<local-agent>/tool-responses/<callId>.json` (or the legacy flat fallback
|
|
182
182
|
if `doctor local-state` says the response is there) before reporting the work as done.
|
|
183
183
|
(`tool-reference.md`)
|
|
184
|
-
- **Workspace
|
|
185
|
-
|
|
186
|
-
mirrors
|
|
187
|
-
`remits-cli doctor local-state` when inheriting one.
|
|
184
|
+
- **Workspace, local actor and agent identity are three different things.** `workspace` is the
|
|
185
|
+
server-side staging lane. The **local actor** is only the repo-local folder for session logs, tool
|
|
186
|
+
responses and verification mirrors: name it with `--local-agent <name>` or `REMITS_LOCAL_AGENT_ID` when
|
|
187
|
+
agents share a checkout, and run `remits-cli doctor local-state` when inheriting one. **Agent identity**
|
|
188
|
+
(`--agent-id` / `REMITS_AGENT_ID`) is what the PLATFORM sees: it is sent on every call, it makes the
|
|
189
|
+
call an `agent` to the account's policy rules, and it decides ticket ownership. `REMITS_AGENT_ID` also
|
|
190
|
+
names the local folder when no local actor is set, which is why setting it "just for the folder" changes
|
|
191
|
+
how the platform treats you. If you are an AI agent, setting it is correct: say who you are, and let the
|
|
192
|
+
account's agent rules apply to you. (`cli-state.md`, `agent-sessions.md`)
|
|
188
193
|
- **Writing the code is not finishing the job.** A change is complete when it is verified — a Test
|
|
189
194
|
component (preferred, because it becomes regression protection) or a browser flow through
|
|
190
195
|
`remits-cli token`. "Just do it" and "that's fine, commit it" are not evidence. If you genuinely
|
|
@@ -232,6 +237,11 @@ reference named after it.
|
|
|
232
237
|
- **Reading and investigating are parallel-safe; editing one account's repository is exclusive**, via a
|
|
233
238
|
lease. A refusal is not an error — carry on read-only and hand off with `ticket progress --next-step`.
|
|
234
239
|
Never wait or poll for a lease. (`support-tickets.md`)
|
|
240
|
+
- **`Refused by this account's '<rule>' rule` is a POLICY decision, not a missing capability.** The
|
|
241
|
+
sentence says who the call presented as (`[This call presents as agent (agentId=…)]`). Find the rule in
|
|
242
|
+
the account's `OPERATIONS.md` `remits-policy` block, report its id, its `when`/`forbid`, and why your call
|
|
243
|
+
matched, then ask a person. Never generalize it ("syncs are impossible here"), and never drop your agent
|
|
244
|
+
identity to get past it. (`support-tickets.md`)
|
|
235
245
|
- **A brief's `## Rules you will be held to` section is ENFORCED, not requested.** The platform refuses
|
|
236
246
|
the verb and the message says what to do instead — act on that sentence rather than retrying or
|
|
237
247
|
looking for another door. A `[require]` rule needs a person; hand off. (`support-tickets.md`)
|
|
@@ -76,13 +76,19 @@ Think about remits-cli as two cooperating layers:
|
|
|
76
76
|
- where a large tool response was written
|
|
77
77
|
- which tool schemas were most recently cached here
|
|
78
78
|
|
|
79
|
-
Per-repo state has two independent namespaces:
|
|
79
|
+
Per-repo state has two independent namespaces, and one identity sits beside them:
|
|
80
80
|
|
|
81
81
|
- **Workspace** (`--workspace`, `REMITS_WORKSPACE`, `.remits-cli/workspace`) chooses the server-side
|
|
82
82
|
Redis staging lane. It changes what staged entries a run resolves.
|
|
83
|
-
- **Local actor** (`--local-agent`, `REMITS_AGENT_ID`,
|
|
84
|
-
namespace under `.remits-cli/actors/<local-agent>/`. It
|
|
85
|
-
verification mirrors, and diagnostics are written.
|
|
83
|
+
- **Local actor** (`--local-agent` / `--actor`, then `REMITS_AGENT_ID`, then `REMITS_LOCAL_AGENT_ID`, then a
|
|
84
|
+
generated fallback) chooses the local filesystem namespace under `.remits-cli/actors/<local-agent>/`. It
|
|
85
|
+
changes where session logs, tool responses, verification mirrors, and diagnostics are written. To name
|
|
86
|
+
only the folder, use `--local-agent` or `REMITS_LOCAL_AGENT_ID`.
|
|
87
|
+
- **Agent identity** (`--agent-id`, then `REMITS_AGENT_ID`) is not local state. The CLI sends it to the
|
|
88
|
+
platform on every call (`X-Remits-Agent-Id` and the payload `agentId`), so the call is an `agent` to the
|
|
89
|
+
account's `remits-policy` rules, owns the tickets it claims, and is attributed to that agent. Without it
|
|
90
|
+
the call is the signed-in person. `REMITS_AGENT_ID` therefore does two jobs, folder and identity; an AI
|
|
91
|
+
agent should set it deliberately, as identity.
|
|
86
92
|
|
|
87
93
|
Run `remits-cli doctor local-state` to print the active actor, state directory, workspace source,
|
|
88
94
|
foreign actors, git-tracked `.remits-cli` files, legacy flat state, and large/suspicious logs.
|
|
@@ -284,10 +284,12 @@ remits-cli corpus retire --corpus <name> --case <caseKey> [--case ...] [--restor
|
|
|
284
284
|
`test run` starts a server-side task immediately. By default the CLI process polls until the task is
|
|
285
285
|
terminal; `--watch false` disables websocket progress streaming but still waits. Pass `--wait false` to
|
|
286
286
|
return after launch with the task id. A single run with `--names "a|b|c"` selects cases into one suite task,
|
|
287
|
-
and those cases execute sequentially
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
287
|
+
and those cases execute sequentially by default. Test metadata `caseConcurrency` (1–16, default 1)
|
|
288
|
+
enables native server workers. Explicit `--parallel N` overrides it; `--parallel 1` forces serial.
|
|
289
|
+
Parent/group IDs work with `test status` and corpus commands; case results retain child task and trace IDs.
|
|
290
|
+
Scheduled batches additionally accept `suiteConcurrency` (1–16, default 1) and overlap only suites marked
|
|
291
|
+
`parallelSafe: true`; other suites run as exclusive barriers. Avoid concurrency for shared mutable
|
|
292
|
+
fixtures or order-dependent setup. See the Test component guide's Configuring Parallel Execution section.
|
|
291
293
|
|
|
292
294
|
### Reading a failing run
|
|
293
295
|
|
|
@@ -430,8 +430,9 @@ id + content hash, platform sync vs local HEAD. If it is not the world you meant
|
|
|
430
430
|
about a different world.
|
|
431
431
|
|
|
432
432
|
The platform launch is asynchronous. By default `remits-cli test run` waits for its task to finish, and any
|
|
433
|
-
cases selected by `--names "a|b"` execute sequentially
|
|
434
|
-
|
|
433
|
+
cases selected by `--names "a|b"` execute sequentially by default. A Test sidecar can set
|
|
434
|
+
`caseConcurrency: 2` for native server workers, including scheduled runs. Explicit `--parallel N`
|
|
435
|
+
overrides that setting (`--parallel 1` forces serial). It splits the selected cases across N shards that run side by side, and the run
|
|
435
436
|
keeps ONE id (`grp-…`) for `test status` and every corpus command. An instance runs only a few suites at once
|
|
436
437
|
(two by default), so shards beyond that wait for a slot. Ask for 2–4. Starting separate
|
|
437
438
|
`test run --names "<case>" --wait false` commands also works for one-off proofs, but every such run is a separate
|