@remits/remits-cli 0.1.167 → 0.1.168
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 +1 -0
- package/index.js +145 -21
- package/lib/local-ai.js +2 -2
- package/package.json +1 -1
- package/skills/remits-cli/references/agent-sessions.md +20 -3
- package/skills/remits-cli/references/investigation.md +21 -0
- package/skills/remits-cli/references/support-tickets.md +20 -0
- package/skills/remits-cli/references/tool-reference.md +30 -1
package/README.md
CHANGED
|
@@ -90,6 +90,7 @@ remits-cli install --skills --overwrite true
|
|
|
90
90
|
- `components clear` clears staged entries. Scope it with `--component-type` and/or `--component-id`. Component ids are type-local, so an id alone clears that one component when the id is staged in only one family; if the same id is staged across multiple families it returns an ambiguity error asking you to add `--component-type`. With no filter it clears every staged entry for the current branch; pass `--all` to force the full-branch wipe explicitly.
|
|
91
91
|
- `components stage`, `components status`, `components lanes`, `components entries`, and `components clear` print concise summaries by default. Add `--json` or `--verbose` to print the full server response.
|
|
92
92
|
- `activity inspect` is a read-only workstream view for operator/agent review. It joins staging lanes, verification envelopes, Test runs, corpus measurements, live agent presence, and heuristic smell signals. Use `--scope children` from a platform/root account and `--user-email` or `--user-id` to focus on one CLI user.
|
|
93
|
+
- System → Activity captures CLI requests under the authenticated user. Declare an interactive agent with `REMITS_AGENT_ID` or `--agent-id`; supervised workers already carry agent/run/ticket identity. The page distinguishes declared agents, browser activity, and CLI calls without declared agent identity. `--local-agent` only chooses a local log folder. Request labels describe intent, not a background job's completion, and local edits between platform calls are not observed.
|
|
93
94
|
- `components stage` and `components status` report three separate numbers, and they answer three different questions: the **workset** (components git reports this working tree changed), what was **submitted**, and the materialized **overlay** the lane now holds — which is what a run resolves. A number that could not be established prints as `unknown`, never as `0`.
|
|
94
95
|
- `components sync` performs a server-side sync from the git remote into the Remits platform for the selected branch. It does not run local git commands. After a successful non-dry-run sync, the platform clears the branch/user staging scope so staged aliases cannot keep shadowing the newly synced DB rows.
|
|
95
96
|
- On trunk, `components sync` performs the full repo-to-DB reconcile. On a non-trunk branch, it writes `ComponentVariant` overlays only and may refresh branch-local `account-info.json`, `account-hierarchy.json`, and `account-configurations.json` for the subscribing account that initiated the sync. `--dry-run` is accepted only for non-trunk variant syncs and reports overrides/additions/tombstones without writing variants, caching the sync SHA, updating metadata, or clearing staging. Add `--summary` to dry-run output when you only need counts, removals/tombstones, errors, skipped items, and warnings. `--force-tombstones` is accepted only for non-trunk variant syncs and should be used only when missing trunk component files are intentional tombstone overrides.
|
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
|
+
- L128 Sessions, Account Resolution, And Production Guards
|
|
8
|
+
- L634 Local State, Workspaces, And Verification Evidence
|
|
9
|
+
- L2672 Account Repos, Guide Sync, And Platform Repo
|
|
10
|
+
- L3571 Component Discovery And HTTP Logging
|
|
11
|
+
- L4350 Skill Delivery And TOC Resolution
|
|
12
|
+
- L4665 Auth And Component Staging
|
|
13
|
+
- L5301 Component Summaries, Status, And Sync Gates
|
|
14
|
+
- L7625 Branches, Promotion, Commit, And Test Runs
|
|
15
|
+
- L9889 Tokens, Tools, Verification, And Config
|
|
16
|
+
- L11406 Service Dashboard And WebSocket Listener
|
|
17
|
+
- L13916 Agent And Ticket Workflows
|
|
18
|
+
- L18429 Help, Auto Update, And Command Dispatch
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
/*
|
|
@@ -34,6 +34,8 @@ const { Client } = require('@stomp/stompjs');
|
|
|
34
34
|
const WebSocket = require('ws');
|
|
35
35
|
const { installWebsocketRetry } = require('./lib/websocket-retry');
|
|
36
36
|
const YAML = require('yaml');
|
|
37
|
+
// Invocation identity is separate from the local log-folder actor and staging workspace.
|
|
38
|
+
let requestIdentityFlags = {};
|
|
37
39
|
|
|
38
40
|
Object.assign(global, { WebSocket });
|
|
39
41
|
const openBrowser = (openModule && typeof openModule === 'function')
|
|
@@ -4044,7 +4046,7 @@ function stageTimeoutMs(components, flags = {}) {
|
|
|
4044
4046
|
return Math.min(600000, Math.max(60000, Math.round(60000 + (megabytes * 60000))));
|
|
4045
4047
|
}
|
|
4046
4048
|
|
|
4047
|
-
function
|
|
4049
|
+
function cliRequestHeaders(token) {
|
|
4048
4050
|
const headers = token ? { Authorization: 'Bearer ' + token } : {};
|
|
4049
4051
|
// The CLI version on EVERY request, from the one place every request is built. It used to travel only on
|
|
4050
4052
|
// `agent register`, so the platform knew the version of the sessions that registered - and the agents that
|
|
@@ -4055,10 +4057,19 @@ function buildAxios(baseUrl, token, timeoutMs = 60000) {
|
|
|
4055
4057
|
// A worker's run identity rides on every call, so a verb whose payload does not repeat it (stage, commit,
|
|
4056
4058
|
// a tool) is still attributed to the run. The ROLE is deliberately not sent: the platform reads it from
|
|
4057
4059
|
// the session's registration, where the user's permission to declare it was checked.
|
|
4058
|
-
|
|
4060
|
+
// ticket route historically accepts --agent-id as the TARGET; it is not the caller.
|
|
4061
|
+
const targetAgent = requestIdentityFlags._?.[0] === 'ticket' && ['route', 'unroute'].includes(requestIdentityFlags._?.[1]);
|
|
4062
|
+
const rawAgentFlag = requestIdentityFlags['agent-id'] || requestIdentityFlags.agentId;
|
|
4063
|
+
const flagAgent = typeof rawAgentFlag === 'string' ? rawAgentFlag.trim() : '';
|
|
4064
|
+
const agentId = (!targetAgent && flagAgent) || process.env.REMITS_AGENT_ID;
|
|
4065
|
+
if (typeof agentId === 'string' && agentId.trim()) headers['X-Remits-Agent-Id'] = agentId.trim();
|
|
4059
4066
|
if (process.env.REMITS_WORKER_RUN_ID) headers['X-Remits-Run-Id'] = process.env.REMITS_WORKER_RUN_ID;
|
|
4060
4067
|
if (process.env.REMITS_SUPPORT_TICKET_ID) headers['X-Remits-Ticket-Id'] = process.env.REMITS_SUPPORT_TICKET_ID;
|
|
4061
|
-
return
|
|
4068
|
+
return headers;
|
|
4069
|
+
}
|
|
4070
|
+
|
|
4071
|
+
function buildAxios(baseUrl, token, timeoutMs = 60000) {
|
|
4072
|
+
return axios.create({ baseURL: baseUrl, timeout: parsePositiveInt(timeoutMs, 60000), headers: cliRequestHeaders(token) });
|
|
4062
4073
|
}
|
|
4063
4074
|
|
|
4064
4075
|
function describeError(err) {
|
|
@@ -4988,6 +4999,10 @@ async function pushComponentsCommand(flags) {
|
|
|
4988
4999
|
...runContextPayload(flags),
|
|
4989
5000
|
agentId: flags['agent-id'] || process.env.REMITS_AGENT_ID || undefined,
|
|
4990
5001
|
acknowledge: flags.acknowledge || undefined,
|
|
5002
|
+
// `commit` when this stage is the pre-push check of `components commit`: the platform then judges it as a
|
|
5003
|
+
// LANDING (the account's committed `commit`-point rules, component rules included), so a refusal
|
|
5004
|
+
// arrives before anything is pushed instead of after.
|
|
5005
|
+
intent: flags.intent || undefined,
|
|
4991
5006
|
// What this stage IS, stated rather than implied. The server derives the lane behavior from the mode
|
|
4992
5007
|
// (CliComponentStaging.stageRequest) instead of from `replace`, which was overloaded: a partial
|
|
4993
5008
|
// manifest with `replace: true` would make the reconcile pass delete nearly the whole lane, so
|
|
@@ -8073,7 +8088,7 @@ async function commitComponentsCommand(flags) {
|
|
|
8073
8088
|
};
|
|
8074
8089
|
try {
|
|
8075
8090
|
if (!skipGit) {
|
|
8076
|
-
emit('Phase 1/4: staged compile validation');
|
|
8091
|
+
emit('Phase 1/4: staged compile validation and landing policy check');
|
|
8077
8092
|
// MERGE semantics on purpose (`--changed-only`, never `--workset`). A workset stage RECONCILES the
|
|
8078
8093
|
// lane — it deletes every entry outside the git changed set — and the lane is frequently shared. A
|
|
8079
8094
|
// validation pass must not be able to delete another agent's staged work as a side effect of somebody
|
|
@@ -8085,7 +8100,8 @@ async function commitComponentsCommand(flags) {
|
|
|
8085
8100
|
workset: false,
|
|
8086
8101
|
'replace-lane': false,
|
|
8087
8102
|
replaceLane: false,
|
|
8088
|
-
'changed-only': true
|
|
8103
|
+
'changed-only': true,
|
|
8104
|
+
intent: 'commit'
|
|
8089
8105
|
})));
|
|
8090
8106
|
|
|
8091
8107
|
outcome.phase = 'git';
|
|
@@ -15234,6 +15250,84 @@ async function postWorkerSummary(context, agentId, ticket, summaryFile, options
|
|
|
15234
15250
|
}
|
|
15235
15251
|
}
|
|
15236
15252
|
|
|
15253
|
+
/**
|
|
15254
|
+
* What a worker run cost, read from its own JSONL transcript — the only place a local model's spend is
|
|
15255
|
+
* visible. A Claude run's `result` event carries `total_cost_usd` and token usage; a Codex run's
|
|
15256
|
+
* `turn.completed` events carry token usage and no price. Returns null when the transcript says nothing,
|
|
15257
|
+
* so an unknown cost is reported as unknown rather than as zero.
|
|
15258
|
+
*/
|
|
15259
|
+
function workerRunUsage(text) {
|
|
15260
|
+
let usd = null;
|
|
15261
|
+
let inputTokens = 0;
|
|
15262
|
+
let outputTokens = 0;
|
|
15263
|
+
let sawTokens = false;
|
|
15264
|
+
String(text || '').split('\n').forEach((line) => {
|
|
15265
|
+
let event;
|
|
15266
|
+
try { event = JSON.parse(line.trim()); } catch (_) { return; }
|
|
15267
|
+
if (!event || typeof event !== 'object') return;
|
|
15268
|
+
if (event.type === 'result') {
|
|
15269
|
+
if (typeof event.total_cost_usd === 'number') usd = event.total_cost_usd;
|
|
15270
|
+
if (event.usage) {
|
|
15271
|
+
inputTokens = Number(event.usage.input_tokens || 0) + Number(event.usage.cache_read_input_tokens || 0) +
|
|
15272
|
+
Number(event.usage.cache_creation_input_tokens || 0);
|
|
15273
|
+
outputTokens = Number(event.usage.output_tokens || 0);
|
|
15274
|
+
sawTokens = true;
|
|
15275
|
+
}
|
|
15276
|
+
} else if (event.type === 'turn.completed' && event.usage) {
|
|
15277
|
+
inputTokens += Number(event.usage.input_tokens || 0);
|
|
15278
|
+
outputTokens += Number(event.usage.output_tokens || 0);
|
|
15279
|
+
sawTokens = true;
|
|
15280
|
+
}
|
|
15281
|
+
});
|
|
15282
|
+
if (usd === null && !sawTokens) return null;
|
|
15283
|
+
return { usd, inputTokens: sawTokens ? inputTokens : null, outputTokens: sawTokens ? outputTokens : null };
|
|
15284
|
+
}
|
|
15285
|
+
|
|
15286
|
+
/**
|
|
15287
|
+
* The identity a worker attempt's cost is reported under. The platform keeps ONE report per identity (a
|
|
15288
|
+
* re-sent report replaces itself), so it must be unique per ATTEMPT, not per supervisor: the supervisor's
|
|
15289
|
+
* run id is constant for its whole life, and reusing it made each retry overwrite the previous attempt's
|
|
15290
|
+
* spend. The attempt's transcript file is unique per spawn and stable for that attempt, which is exactly
|
|
15291
|
+
* the identity a retried report must keep.
|
|
15292
|
+
*/
|
|
15293
|
+
function workerCostReportId(runId, logFile) {
|
|
15294
|
+
return String(runId || 'run') + ':' + path.basename(String(logFile || 'attempt'), '.log');
|
|
15295
|
+
}
|
|
15296
|
+
|
|
15297
|
+
/** Report a finished run's cost onto its ticket. Best-effort: a lost report under-counts, never fails a run. */
|
|
15298
|
+
async function reportWorkerCost(context, agentId, ticket, logFile, runId, agentType) {
|
|
15299
|
+
let usage = null;
|
|
15300
|
+
try {
|
|
15301
|
+
usage = workerRunUsage(fs.readFileSync(logFile, 'utf8'));
|
|
15302
|
+
} catch (_) {
|
|
15303
|
+
return false;
|
|
15304
|
+
}
|
|
15305
|
+
if (!usage || !runId) return false;
|
|
15306
|
+
try {
|
|
15307
|
+
await buildAxios(context.baseUrl, context.session.token, 20000)
|
|
15308
|
+
.post('/cli/ticket', {
|
|
15309
|
+
action: 'cost',
|
|
15310
|
+
ticketId: ticket.ticketId || ticket.id,
|
|
15311
|
+
accountId: Number(ticket.accountId) || undefined,
|
|
15312
|
+
dataMode: context.dataMode,
|
|
15313
|
+
agentId,
|
|
15314
|
+
runId,
|
|
15315
|
+
costRunId: workerCostReportId(runId, logFile),
|
|
15316
|
+
usd: usage.usd,
|
|
15317
|
+
inputTokens: usage.inputTokens,
|
|
15318
|
+
outputTokens: usage.outputTokens,
|
|
15319
|
+
provider: agentType || undefined,
|
|
15320
|
+
costSource: 'remits-cli-worker'
|
|
15321
|
+
});
|
|
15322
|
+
return true;
|
|
15323
|
+
} catch (err) {
|
|
15324
|
+
appendGlobalActivityLog('agent', 'worker.cost_post_failed', {
|
|
15325
|
+
agentId, ticketId: ticket.ticketId || ticket.id, error: describeError(err)
|
|
15326
|
+
}, 'error');
|
|
15327
|
+
return false;
|
|
15328
|
+
}
|
|
15329
|
+
}
|
|
15330
|
+
|
|
15237
15331
|
/** Drop a claim on the platform. Best-effort: the claim expires on its own if this never lands. */
|
|
15238
15332
|
async function releaseTicketClaim(context, agentId, ticket, options = {}) {
|
|
15239
15333
|
try {
|
|
@@ -15517,6 +15611,8 @@ async function spawnTicketWorker(context, ctx, ticket) {
|
|
|
15517
15611
|
// zero having done nothing. So the TICKET decides — unclaim always, and only give up (release
|
|
15518
15612
|
// it back to the queue) once it has genuinely failed enough times and is still not terminal.
|
|
15519
15613
|
const giveUp = !ok && attempt >= AGENT_WORKER_MAX_ATTEMPTS;
|
|
15614
|
+
// Spend first, while this run still holds the claim it is reported against.
|
|
15615
|
+
await reportWorkerCost(context, agentId, { ticketId, accountId: ticket.accountId }, logFile, ctx.runId, agentType);
|
|
15520
15616
|
const result = await releaseTicketClaim(context, agentId, { ticketId, accountId: ticket.accountId }, {
|
|
15521
15617
|
runId: ctx.runId,
|
|
15522
15618
|
outcome: ok ? 'completed' : (signal ? 'killed' : 'failed'),
|
|
@@ -16063,7 +16159,24 @@ async function ticketCommand(flags, subcommand) {
|
|
|
16063
16159
|
approvers: flags.approvers ? String(flags.approvers).split(',').map((v) => v.trim()).filter(Boolean) : undefined,
|
|
16064
16160
|
reviewers: flags.reviewers ? String(flags.reviewers).split(',').map((v) => v.trim()).filter(Boolean) : undefined,
|
|
16065
16161
|
expiresAt: flags['expires-at'] || undefined,
|
|
16162
|
+
// N-of-M: how many distinct people must approve before the request is approved.
|
|
16163
|
+
minApprovals: flags['min-approvals'] ? Number(flags['min-approvals']) : undefined,
|
|
16066
16164
|
delivery: flags.delivery || undefined,
|
|
16165
|
+
// `wake`: a durable timer. `--at` an instant, `--in` minutes, `--cancel` to clear it.
|
|
16166
|
+
wakeAt: action === 'wake' ? (flags.at || undefined) : undefined,
|
|
16167
|
+
wakeInMinutes: action === 'wake' && flags.in ? Number(flags.in) : undefined,
|
|
16168
|
+
cancel: action === 'wake' && flagEnabled(flags.cancel) ? true : undefined,
|
|
16169
|
+
// `depends-on`: the tickets this one waits on. The flag with no value clears them.
|
|
16170
|
+
dependsOn: action === 'depends-on'
|
|
16171
|
+
? (flags.tickets === true || flags.tickets === '' ? [] : (flags.tickets ? String(flags.tickets).split(',').map((v) => v.trim()).filter(Boolean) : undefined))
|
|
16172
|
+
: undefined,
|
|
16173
|
+
// `cost`: what a run cost, reported by whoever can see it (a launcher, a person).
|
|
16174
|
+
usd: action === 'cost' && flags.usd !== undefined ? Number(flags.usd) : undefined,
|
|
16175
|
+
inputTokens: action === 'cost' && flags['input-tokens'] ? Number(flags['input-tokens']) : undefined,
|
|
16176
|
+
outputTokens: action === 'cost' && flags['output-tokens'] ? Number(flags['output-tokens']) : undefined,
|
|
16177
|
+
model: action === 'cost' ? (flags.model || undefined) : undefined,
|
|
16178
|
+
provider: action === 'cost' ? (flags.provider || undefined) : undefined,
|
|
16179
|
+
costRunId: action === 'cost' ? (flags['cost-run-id'] || undefined) : undefined,
|
|
16067
16180
|
// The revision a call acts on or a decision looked at: a patch digest, a staged snapshot, a version.
|
|
16068
16181
|
revision: flags.revision || undefined,
|
|
16069
16182
|
note: flags.note,
|
|
@@ -16780,7 +16893,7 @@ function printTicketHelp() {
|
|
|
16780
16893
|
console.log(' ask --question "..." [--context "..."] [--to WHO] [--visibility internal]');
|
|
16781
16894
|
console.log(' Put a question to a human and park. Use it when the CHOICE is not yours. Recorded as');
|
|
16782
16895
|
console.log(' request R<n>; answering it resumes the work even if no worker is online right now.');
|
|
16783
|
-
console.log(' request-approval --for ACTION [--revision R | --delivery ID] [--approvers a@x,b@y] [--summary "..."]');
|
|
16896
|
+
console.log(' request-approval --for ACTION [--revision R | --delivery ID] [--approvers a@x,b@y] [--min-approvals N] [--summary "..."]');
|
|
16784
16897
|
console.log(' Ask a PERSON to authorize one action on one revision (promote a patch, send a message).');
|
|
16785
16898
|
console.log(' An answer is not an approval: only an authenticated person can decide it, never an');
|
|
16786
16899
|
console.log(' agent run, and a changed revision needs a new approval.');
|
|
@@ -16794,7 +16907,15 @@ function printTicketHelp() {
|
|
|
16794
16907
|
console.log(' Hand the next step to another session (queued if nobody is online). --exclude keeps');
|
|
16795
16908
|
console.log(' YOU off it — use it for independent review. Your exit will not erase this handoff.');
|
|
16796
16909
|
console.log(' continue [--reason requested] Queue the ticket for the automation to work again.');
|
|
16797
|
-
console.log(' reset-budget OPERATOR: restore a ticket\'s run budget after looking at it.');
|
|
16910
|
+
console.log(' reset-budget OPERATOR: restore a ticket\'s run and spend budget after looking at it.');
|
|
16911
|
+
console.log(' wake --in MINUTES | --at ISO-TIME [--reason "..."] | wake --cancel');
|
|
16912
|
+
console.log(' A durable timer: when it is due the ticket is runnable work again (reason `timer`) and');
|
|
16913
|
+
console.log(' the run that takes it is told why. Use it for "look again after X", never sleep-and-poll.');
|
|
16914
|
+
console.log(' depends-on --tickets 101,102 This ticket waits on those; the sweep will not start it until');
|
|
16915
|
+
console.log(' every one is resolved or closed. `--tickets` with no value clears it.');
|
|
16916
|
+
console.log(' cost --usd 1.25 [--input-tokens N] [--output-tokens N] [--model M] [--cost-run-id R]');
|
|
16917
|
+
console.log(' Record what a run cost. One report per run (a second replaces the first); the account\'s');
|
|
16918
|
+
console.log(' supportRouting.maxTicketSpendUsd then stops automatic starts.');
|
|
16798
16919
|
console.log(' answer --message "..." [--route false] (alias: reply)');
|
|
16799
16920
|
console.log(' Answer the open question a previous run asked. By default this re-routes the ticket,');
|
|
16800
16921
|
console.log(' so a fresh worker resumes with your answer already in its brief. This is NOT how you');
|
|
@@ -18836,6 +18957,7 @@ async function main() {
|
|
|
18836
18957
|
migrateSessionIfNeeded();
|
|
18837
18958
|
const originalArgv = process.argv.slice(2);
|
|
18838
18959
|
const args = parseArgs(originalArgv);
|
|
18960
|
+
requestIdentityFlags = args;
|
|
18839
18961
|
configureLocalActor(args);
|
|
18840
18962
|
const [command, subcommand] = args._;
|
|
18841
18963
|
const wantsHelp = args.help === true || subcommand === 'help' || subcommand === '--help' || args._.includes('--help');
|
|
@@ -18851,7 +18973,7 @@ async function main() {
|
|
|
18851
18973
|
requireAccountRepoContext(process.cwd(), flags, 'ai ' + subcommand, context);
|
|
18852
18974
|
await localAi.command({
|
|
18853
18975
|
subcommand, flags, context, root: SESSION_DIR, axios, Client, WebSocket,
|
|
18854
|
-
defaultAgent: getPreferredAgent()
|
|
18976
|
+
defaultAgent: getPreferredAgent(), requestHeaders: cliRequestHeaders
|
|
18855
18977
|
});
|
|
18856
18978
|
return;
|
|
18857
18979
|
}
|
|
@@ -18910,7 +19032,9 @@ async function main() {
|
|
|
18910
19032
|
|
|
18911
19033
|
if (!command || command === 'help' || command === '--help') {
|
|
18912
19034
|
console.log('Usage: remits-cli <command>');
|
|
18913
|
-
console.log('Global options: --no-auto-update, --local-agent NAME');
|
|
19035
|
+
console.log('Global options: --no-auto-update, --local-agent NAME, --agent-id ID');
|
|
19036
|
+
console.log(' --agent-id / REMITS_AGENT_ID declares the agent acting for the authenticated user on CLI requests.');
|
|
19037
|
+
console.log(' --local-agent names only the local log folder; it does not declare an agent to the platform.');
|
|
18914
19038
|
console.log(' remits-cli auth [--base-url URL] [--account-id ID] [--port 8765] [--data-mode test|prod]');
|
|
18915
19039
|
console.log(' remits-cli sessions [list|remove] [--account-id ID] [--base-url URL] [--data-mode test|prod]');
|
|
18916
19040
|
console.log(' remits-cli config [set] [--agent claude|codex|gemini]');
|
package/lib/local-ai.js
CHANGED
|
@@ -525,7 +525,7 @@ function help() {
|
|
|
525
525
|
console.log('Decisions: {"content":"reply","toolCalls":[]} or {"content":null,"toolCalls":[{"name":"tool","arguments":{}}]}');
|
|
526
526
|
}
|
|
527
527
|
|
|
528
|
-
async function command({ subcommand, flags, context, root, axios, Client, WebSocket, defaultAgent = 'claude' }) {
|
|
528
|
+
async function command({ subcommand, flags, context, root, axios, Client, WebSocket, defaultAgent = 'claude', requestHeaders }) {
|
|
529
529
|
if (!subcommand || flags.help || subcommand === 'help') return help();
|
|
530
530
|
if (!['serve', 'workers', 'work', 'request', 'respond', 'status', 'sessions', 'release'].includes(subcommand)) throw new Error('Unknown ai subcommand');
|
|
531
531
|
const agent = flags.agent || defaultAgent;
|
|
@@ -545,7 +545,7 @@ async function command({ subcommand, flags, context, root, axios, Client, WebSoc
|
|
|
545
545
|
try {
|
|
546
546
|
const result = await axios.post(baseUrl.replace(/\/+$/, '') + '/cli/ai',
|
|
547
547
|
{ ...payload, command: action, workerId: worker.workerId, accountId, dataMode },
|
|
548
|
-
{ headers: { Authorization: 'Bearer ' + session.token }, timeout: 15000, maxContentLength: MAX_BYTES, maxBodyLength: MAX_BYTES });
|
|
548
|
+
{ headers: requestHeaders ? requestHeaders(session.token) : { Authorization: 'Bearer ' + session.token }, timeout: 15000, maxContentLength: MAX_BYTES, maxBodyLength: MAX_BYTES });
|
|
549
549
|
if (!result.data?.success) throw new Error(result.data?.message || 'Local AI command failed');
|
|
550
550
|
return result.data;
|
|
551
551
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -106,9 +106,26 @@ review.
|
|
|
106
106
|
|
|
107
107
|
### Resolving which agent a command means
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
For agent lifecycle commands, explicit `--agent-id` or `REMITS_AGENT_ID` wins; otherwise the CLI looks
|
|
110
|
+
for the registration anchored to this terminal's owning process. It never borrows another terminal's
|
|
111
|
+
registration just because it shares the checkout.
|
|
112
|
+
|
|
113
|
+
For platform request attribution, set `REMITS_AGENT_ID` for the agent's shell or pass `--agent-id` on
|
|
114
|
+
each command. Both reach the shared HTTP client, including status, tools and local-AI requests.
|
|
115
|
+
Supervised ticket workers receive `REMITS_AGENT_ID`, `REMITS_WORKER_RUN_ID` and
|
|
116
|
+
`REMITS_SUPPORT_TICKET_ID` automatically. A manually named interactive agent does not need to register
|
|
117
|
+
for ticket routing just to declare its identity. Registered roles and labels are accepted only from a
|
|
118
|
+
registration owned by the authenticated user. `--local-agent` / `REMITS_LOCAL_AGENT_ID` remains a local
|
|
119
|
+
folder choice and does not declare platform agent identity.
|
|
120
|
+
|
|
121
|
+
System → Activity groups these requests under the authenticated user, with separate agent/run sessions
|
|
122
|
+
and source filters. Undeclared CLI requests are labeled **CLI · no declared agent**, never assumed to be
|
|
123
|
+
human. Browser sessions can also be automated. Only platform requests are observed, not local editing
|
|
124
|
+
or reasoning between commands. Creating a preview token does not mark every subsequent browser user
|
|
125
|
+
of that token as an agent.
|
|
126
|
+
|
|
127
|
+
`ticket route --agent <target>` names the destination. Its legacy `--agent-id` alias also names the
|
|
128
|
+
destination, not the caller; use `REMITS_AGENT_ID` to identify the agent making a routing request.
|
|
112
129
|
|
|
113
130
|
### Which agent gets a ticket
|
|
114
131
|
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
- [Investigation Strategy](#investigation-strategy)
|
|
21
21
|
- [Presenting Findings](#presenting-findings)
|
|
22
22
|
- [Verifying a Production Issue Fix](#verifying-a-production-issue-fix)
|
|
23
|
+
- [Missing or deleted business documents](#missing-or-deleted-business-documents)
|
|
23
24
|
|
|
24
25
|
## The Investigation Model
|
|
25
26
|
|
|
@@ -281,3 +282,23 @@ When a bug is reported from production, use this pattern:
|
|
|
281
282
|
6. If useful, create or update a dedicated embeddable "playground" in test mode to reproduce the scenario in a controlled way.
|
|
282
283
|
|
|
283
284
|
Do not move production customer data into another account's test collection as a routine verification strategy. If you cannot verify with a Test component, Playwright flow, or controlled test-mode embeddable, explain the gap clearly instead of improvising with live production validation.
|
|
285
|
+
|
|
286
|
+
## Missing or deleted business documents
|
|
287
|
+
|
|
288
|
+
`PROCESSED` is the MySQL object's processing outcome, not a guarantee that its Firestore document
|
|
289
|
+
still exists. Read `documentEvidence` from `mcp_object_activity` or a current object `mcp_record_view`:
|
|
290
|
+
it includes the resolved path, observed data lane, presence, recent revision metadata, and the latest
|
|
291
|
+
Object `Document Delete` receipt when available. `state` is `present`, `missing`, `deleted`, `unknown`
|
|
292
|
+
(read failed), or `out_of_scope`. `deleted` requires retained deletion evidence; absence alone is not proof.
|
|
293
|
+
A `laneMismatch` means the requested read lane differs from the object's persisted lane.
|
|
294
|
+
|
|
295
|
+
`Document Delete` ObjectLog entries expose structured `documentOperation` (outcome, document path,
|
|
296
|
+
user/agent, component/action, node, data lane, scope, run and trace ids). The operation's
|
|
297
|
+
`threadGroupingId` can differ from `request.traceId`; both are retained for correlation. Failed deletes
|
|
298
|
+
never generate success revisions or downstream deletion messages. The processing status remains intact.
|
|
299
|
+
|
|
300
|
+
To read retained history without a live parent document, call `mcp_firestore_search` with one
|
|
301
|
+
`targetAccountId`, `collection`, `documentId`, and `includeHistory: true`. Use `historyLimit` (default 10,
|
|
302
|
+
maximum 100), `includeRevisionSnapshots: true` for the pre-delete business snapshot, or `revisionId`
|
|
303
|
+
for one exact revision. History is explicitly truncated when more rows exist. Legacy revisions may
|
|
304
|
+
identify the user and time without the newer component/request provenance; do not invent missing facts.
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
- [Moving a ticket through its lifecycle](#moving-a-ticket-through-its-lifecycle)
|
|
27
27
|
- [When a worker needs a decision from a human](#when-a-worker-needs-a-decision-from-a-human)
|
|
28
28
|
- [Approvals, reviews and handing work on](#approvals-reviews-and-handing-work-on)
|
|
29
|
+
- [Coming back later, waiting on other tickets, and cost](#coming-back-later-waiting-on-other-tickets-and-cost)
|
|
29
30
|
|
|
30
31
|
## Support Ticket Mental Model
|
|
31
32
|
|
|
@@ -592,6 +593,25 @@ The handoff is queued even if nobody is online, `--exclude` keeps you off it (in
|
|
|
592
593
|
that), and your own exit will not undo it. A reviewer decides with `ticket decide --request R3 --decision
|
|
593
594
|
approve|changes|reject --revision <rev>`; "changes" sends the work back with the reviewer's note.
|
|
594
595
|
|
|
596
|
+
An approval or review can need several people: `request-approval ... --min-approvals 2` stays open until two
|
|
597
|
+
different eligible deciders approve; one rejection settles it.
|
|
598
|
+
|
|
599
|
+
### Coming back later, waiting on other tickets, and cost
|
|
600
|
+
|
|
601
|
+
```bash
|
|
602
|
+
remits-cli ticket wake --in 1440 --reason "requester promised logs tomorrow" # or --at <ISO time>; --cancel
|
|
603
|
+
remits-cli ticket depends-on --tickets 19463,19470 # no value clears
|
|
604
|
+
remits-cli ticket cost --usd 1.25 # usually reported for you
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
- **`wake` instead of waiting.** Never sleep or poll inside a run. Record what you are waiting for, set a
|
|
608
|
+
wake-up, and finish the run; when the time comes a fresh worker gets the ticket with `resumed for: timer`
|
|
609
|
+
and your reason in its brief.
|
|
610
|
+
- **`depends-on`** when this ticket genuinely cannot start until others are resolved. The sweep holds it
|
|
611
|
+
until they are; nothing else has to remember to release it.
|
|
612
|
+
- **Cost** is reported automatically by `agent serve` from each worker's transcript. An account can cap a
|
|
613
|
+
ticket's spend; when it is reached, starts are refused until a person resets the budget.
|
|
614
|
+
|
|
595
615
|
**`mcp_support_ticket` is an account convention, not a platform guarantee.** It belongs to one
|
|
596
616
|
particular System Account: it may not exist on the platform you are on, it scopes to its own
|
|
597
617
|
account, and it will refuse a ticket owned by a different one. Use it only when you are working in
|
|
@@ -141,6 +141,28 @@ The account-graph and user surface: the middle ground between `account-info.json
|
|
|
141
141
|
compactly, because it is read into your context every session) and `mcp_account_view` (everything about one
|
|
142
142
|
account).
|
|
143
143
|
|
|
144
|
+
**Recurring schedules:** `action_schedules` lists the target account's Action schedules;
|
|
145
|
+
`action_schedule_update` upserts by `targetAccountId` + `actionId` (or an account-scoped `scheduleId`);
|
|
146
|
+
`action_schedule_delete` removes that schedule and its cloud job. Updates accept `cron`, `timeZone`,
|
|
147
|
+
`enabled` and `dryRun`. `enabled:false` pauses; `enabled:true` resumes while preserving omitted settings.
|
|
148
|
+
`regression_schedule` reads the per-account regression settings and latest report;
|
|
149
|
+
`regression_schedule_update` accepts the same scheduling fields plus `resultAction`, `suiteConcurrency`
|
|
150
|
+
(1–16) and explicit `ownerUserId`. Existing owners are preserved. Only admins may assign another owner;
|
|
151
|
+
an eligible caller may take ownership themselves.
|
|
152
|
+
|
|
153
|
+
Both use five-field cron and IANA time zones, default UTC. Existing Action schedules retain UTC.
|
|
154
|
+
All writes require production mode; `dryRun:true` validates from either lane without writing or creating
|
|
155
|
+
a cloud job. Action jobs resolve committed enabled `job:true` Actions on the target account; regression
|
|
156
|
+
suites run committed source in test data. Optional `includeSchedulerStatus:true` on reads reports cloud
|
|
157
|
+
registration/next delivery/last transport status; this does not prove Action or regression success.
|
|
158
|
+
An empty regression update does not repair registration: explicitly pass cron/timeZone/enabled.
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
remits-cli tool --name mcp_account_user_admin --data-mode test --input '{"action":"action_schedule_update","targetAccountId":4655,"actionId":21,"cron":"0 1 * * *","timeZone":"America/New_York","enabled":true,"dryRun":true}'
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Inspect the preview, then omit `dryRun` and use `--data-mode prod` when authorized to save the schedule.
|
|
165
|
+
|
|
144
166
|
- `action: 'hierarchy'` (default) — the descendant tree trimmed to `depth` (1-10, default 2; nodes cut off
|
|
145
167
|
are marked `truncated` and still report their child count) and/or the anchored ancestor chain plus every
|
|
146
168
|
edge (`direction: 'down'|'up'|'both'`). Nodes include `testAccount`. **This is how you get the deep tree
|
|
@@ -299,6 +321,11 @@ evidence at all, is in `investigation.md` → *HTTP audits*.
|
|
|
299
321
|
remits-cli tool --name "mcp_firestore_search" --input '{"accountId": 37, "collection": "http-audits/http-audits-2026-05/entries", "filters": [{"field": "direction", "operation": "EQUALS", "value": "OUTBOUND"}, {"field": "request.path", "operation": "EQUALS", "value": "/api/orders"}, {"field": "success", "operation": "EQUALS", "value": false}], "sort": [{"field": "timestamp", "direction": "DESC"}], "pagination": {"limit": 10}}' --data-mode prod
|
|
300
322
|
```
|
|
301
323
|
|
|
324
|
+
Exact-account history reads also accept `includeHistory` (boolean), `historyLimit` (1–100, default 10),
|
|
325
|
+
`includeRevisionSnapshots` (boolean, default false), and `revisionId` (exact Firestore revision id).
|
|
326
|
+
These require one `targetAccountId`, a collection name and `documentId`. They return `documentEvidence`
|
|
327
|
+
even when the live document is deleted. A normal exact-id miss also returns bounded evidence.
|
|
328
|
+
|
|
302
329
|
### `mcp_firestore_patch`
|
|
303
330
|
Guarded exact-document Firestore patch tool for bounded repairs. It defaults to `dryRun:true` and refuses broad
|
|
304
331
|
updates, wildcard collections, delete/remove operations, protected identity fields, and `_lastModified*` audit
|
|
@@ -329,7 +356,9 @@ Response fields include `dataMode`, `accountId`, `collection`, `documentId`, `dr
|
|
|
329
356
|
`diff`. Switch to `"dryRun":false` only after the diff and preconditions are exactly what you intended.
|
|
330
357
|
|
|
331
358
|
### `mcp_object_activity`
|
|
332
|
-
Object lifecycle timeline — metadata + recent activity.
|
|
359
|
+
Object lifecycle timeline — metadata + recent activity. Object metadata includes `documentEvidence`
|
|
360
|
+
with path, current presence/data lane, recent revisions and latest deletion receipt. Document Delete
|
|
361
|
+
activity entries include structured `documentOperation` evidence; PROCESSED is independent of document presence.
|
|
333
362
|
|
|
334
363
|
| Parameter | Required | Description |
|
|
335
364
|
|-----------|----------|-------------|
|