@skrr-ai/cli 0.1.108 → 0.1.109
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 +10 -0
- package/dist/base-command.d.ts +59 -0
- package/dist/base-command.js +118 -7
- package/dist/commands/agents/update.d.ts +14 -0
- package/dist/commands/agents/update.js +40 -1
- package/dist/commands/commitments/accounting.js +7 -2
- package/dist/commands/commitments/cost.d.ts +11 -0
- package/dist/commands/commitments/cost.js +26 -3
- package/dist/commands/commitments/create.js +2 -1
- package/dist/commands/commitments/operating-review.d.ts +15 -0
- package/dist/commands/commitments/operating-review.js +103 -50
- package/dist/commands/commitments/update.js +12 -5
- package/dist/commands/doctor.js +9 -0
- package/dist/commands/goals/list.js +6 -1
- package/dist/commands/grants/rotate.js +4 -0
- package/dist/commands/login.js +42 -14
- package/dist/commands/machines/list.js +28 -6
- package/dist/commands/projects/show.js +1 -1
- package/dist/commands/revoke-daemon.js +2 -1
- package/dist/commands/spaces/workflows/archive.d.ts +1 -0
- package/dist/commands/spaces/workflows/archive.js +1 -0
- package/dist/commands/spaces/workflows/artifact.d.ts +1 -0
- package/dist/commands/spaces/workflows/artifact.js +1 -0
- package/dist/commands/spaces/workflows/cancel-node.d.ts +1 -0
- package/dist/commands/spaces/workflows/cancel-node.js +1 -0
- package/dist/commands/spaces/workflows/cancel.d.ts +1 -0
- package/dist/commands/spaces/workflows/cancel.js +6 -2
- package/dist/commands/spaces/workflows/create.d.ts +1 -0
- package/dist/commands/spaces/workflows/create.js +7 -1
- package/dist/commands/spaces/workflows/events.d.ts +1 -0
- package/dist/commands/spaces/workflows/events.js +1 -0
- package/dist/commands/spaces/workflows/fixtures/create.d.ts +1 -0
- package/dist/commands/spaces/workflows/fixtures/create.js +1 -0
- package/dist/commands/spaces/workflows/fixtures/list.d.ts +1 -0
- package/dist/commands/spaces/workflows/fixtures/list.js +1 -0
- package/dist/commands/spaces/workflows/lifecycle-diagnostics.d.ts +1 -0
- package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +1 -0
- package/dist/commands/spaces/workflows/list.d.ts +1 -0
- package/dist/commands/spaces/workflows/list.js +1 -0
- package/dist/commands/spaces/workflows/node-types.d.ts +2 -0
- package/dist/commands/spaces/workflows/node-types.js +24 -6
- package/dist/commands/spaces/workflows/pause.d.ts +1 -0
- package/dist/commands/spaces/workflows/pause.js +1 -0
- package/dist/commands/spaces/workflows/plan/show.d.ts +1 -0
- package/dist/commands/spaces/workflows/plan/show.js +1 -0
- package/dist/commands/spaces/workflows/plan/update.d.ts +1 -0
- package/dist/commands/spaces/workflows/plan/update.js +1 -0
- package/dist/commands/spaces/workflows/proposals/apply.d.ts +1 -0
- package/dist/commands/spaces/workflows/proposals/apply.js +1 -0
- package/dist/commands/spaces/workflows/proposals/confirm.d.ts +1 -0
- package/dist/commands/spaces/workflows/proposals/confirm.js +1 -0
- package/dist/commands/spaces/workflows/proposals/create.d.ts +1 -0
- package/dist/commands/spaces/workflows/proposals/create.js +1 -0
- package/dist/commands/spaces/workflows/proposals/dismiss.d.ts +1 -0
- package/dist/commands/spaces/workflows/proposals/dismiss.js +1 -0
- package/dist/commands/spaces/workflows/proposals/list.d.ts +1 -0
- package/dist/commands/spaces/workflows/proposals/list.js +1 -0
- package/dist/commands/spaces/workflows/publish.d.ts +1 -0
- package/dist/commands/spaces/workflows/publish.js +9 -0
- package/dist/commands/spaces/workflows/reconcile.d.ts +1 -0
- package/dist/commands/spaces/workflows/reconcile.js +1 -0
- package/dist/commands/spaces/workflows/resume.d.ts +1 -0
- package/dist/commands/spaces/workflows/resume.js +1 -0
- package/dist/commands/spaces/workflows/retry.d.ts +1 -0
- package/dist/commands/spaces/workflows/retry.js +1 -0
- package/dist/commands/spaces/workflows/run.d.ts +1 -0
- package/dist/commands/spaces/workflows/run.js +14 -7
- package/dist/commands/spaces/workflows/runs.d.ts +1 -0
- package/dist/commands/spaces/workflows/runs.js +1 -0
- package/dist/commands/spaces/workflows/show.d.ts +25 -0
- package/dist/commands/spaces/workflows/show.js +42 -4
- package/dist/commands/spaces/workflows/signal.d.ts +12 -0
- package/dist/commands/spaces/workflows/signal.js +40 -2
- package/dist/commands/spaces/workflows/start.d.ts +1 -0
- package/dist/commands/spaces/workflows/start.js +1 -0
- package/dist/commands/spaces/workflows/task-execution/create.d.ts +1 -0
- package/dist/commands/spaces/workflows/task-execution/create.js +1 -0
- package/dist/commands/spaces/workflows/test.d.ts +1 -0
- package/dist/commands/spaces/workflows/test.js +1 -0
- package/dist/commands/spaces/workflows/triggers/create.d.ts +1 -0
- package/dist/commands/spaces/workflows/triggers/create.js +1 -0
- package/dist/commands/spaces/workflows/triggers/fire.d.ts +1 -0
- package/dist/commands/spaces/workflows/triggers/fire.js +1 -0
- package/dist/commands/spaces/workflows/triggers/list.d.ts +1 -0
- package/dist/commands/spaces/workflows/triggers/list.js +1 -0
- package/dist/commands/spaces/workflows/triggers/update.d.ts +1 -0
- package/dist/commands/spaces/workflows/triggers/update.js +1 -0
- package/dist/commands/spaces/workflows/update.d.ts +1 -0
- package/dist/commands/spaces/workflows/update.js +4 -0
- package/dist/commands/spaces/workflows/validate.d.ts +1 -0
- package/dist/commands/spaces/workflows/validate.js +15 -0
- package/dist/commands/tasks/activity.js +6 -2
- package/dist/commands/tasks/bulk-update.d.ts +1 -0
- package/dist/commands/tasks/bulk-update.js +7 -0
- package/dist/commands/tasks/complete.js +2 -1
- package/dist/commands/tasks/create-tree.d.ts +69 -0
- package/dist/commands/tasks/create-tree.js +413 -47
- package/dist/commands/tasks/list.d.ts +2 -0
- package/dist/commands/tasks/list.js +19 -1
- package/dist/commands/tasks/rearm-recurrence.d.ts +22 -0
- package/dist/commands/tasks/rearm-recurrence.js +68 -0
- package/dist/commands/tasks/show.d.ts +1 -0
- package/dist/commands/tasks/show.js +17 -3
- package/dist/commands/tasks/timeline.js +4 -0
- package/dist/commands/tasks/update.d.ts +1 -0
- package/dist/commands/tasks/update.js +6 -1
- package/dist/commands/triggers/create.js +8 -2
- package/dist/commands/triggers/parse-intent.d.ts +7 -0
- package/dist/commands/triggers/parse-intent.js +31 -9
- package/dist/commands/workspaces/grants.js +23 -3
- package/dist/commands/workspaces/members.d.ts +20 -0
- package/dist/commands/workspaces/members.js +64 -0
- package/dist/commands/workspaces/stewardship.js +14 -2
- package/dist/help.js +12 -0
- package/dist/hooks/command-not-found.js +27 -1
- package/dist/lib/command-miss.d.ts +52 -2
- package/dist/lib/command-miss.js +187 -18
- package/dist/lib/commitment-loop-status.d.ts +8 -0
- package/dist/lib/commitment-loop-status.js +45 -0
- package/dist/lib/commitments.d.ts +16 -2
- package/dist/lib/commitments.js +48 -8
- package/dist/lib/daemon-command-hint.d.ts +15 -0
- package/dist/lib/daemon-command-hint.js +21 -0
- package/dist/lib/daemon-setup.js +10 -1
- package/dist/lib/daemonBrokerRefusal.js +3 -2
- package/dist/lib/daemonHandoff.js +2 -1
- package/dist/lib/deployed-build.d.ts +43 -0
- package/dist/lib/deployed-build.js +66 -0
- package/dist/lib/grants.d.ts +27 -1
- package/dist/lib/grants.js +39 -21
- package/dist/lib/json-mode.d.ts +11 -0
- package/dist/lib/json-mode.js +19 -0
- package/dist/lib/json-output.d.ts +25 -0
- package/dist/lib/json-output.js +30 -0
- package/dist/lib/list-output.d.ts +9 -0
- package/dist/lib/list-output.js +20 -1
- package/dist/lib/local-daemon-readiness.d.ts +90 -0
- package/dist/lib/local-daemon-readiness.js +143 -0
- package/dist/lib/machines.d.ts +117 -0
- package/dist/lib/machines.js +139 -0
- package/dist/lib/managed-instruction-local.d.ts +2 -0
- package/dist/lib/managed-instruction-local.js +2 -0
- package/dist/lib/profile-machine-identity-guard.d.ts +18 -0
- package/dist/lib/profile-machine-identity-guard.js +29 -0
- package/dist/lib/request-field-flags.d.ts +20 -0
- package/dist/lib/request-field-flags.js +34 -0
- package/dist/lib/stewardship-output.d.ts +1 -1
- package/dist/lib/stewardship-output.js +26 -30
- package/dist/lib/task-batch.d.ts +61 -4
- package/dist/lib/task-batch.js +316 -80
- package/dist/lib/task-economics.d.ts +5 -0
- package/dist/lib/task-economics.js +15 -0
- package/dist/lib/task-recurrence-outcome.d.ts +6 -6
- package/dist/lib/task-recurrence-outcome.js +27 -3
- package/dist/lib/tasks.d.ts +39 -0
- package/dist/lib/tasks.js +132 -0
- package/dist/lib/triggers.d.ts +31 -1
- package/dist/lib/triggers.js +55 -1
- package/dist/lib/workflow-issues.d.ts +14 -0
- package/dist/lib/workflow-issues.js +32 -0
- package/dist/lib/workflow-run-view.d.ts +55 -0
- package/dist/lib/workflow-run-view.js +105 -0
- package/dist/lib/workspaces.d.ts +31 -4
- package/dist/lib/workspaces.js +43 -0
- package/dist/lib/write-target.d.ts +29 -0
- package/dist/lib/write-target.js +72 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/workspaceIsolationProbeWire.d.ts +51 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/workspaceIsolationProbeWire.js +50 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/workspaceIsolationProbeWire.d.ts +51 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/workspaceIsolationProbeWire.js +46 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +6718 -6370
- package/oclif.manifest.json +376 -52
- package/package.json +19 -1
package/dist/lib/task-batch.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.batchRetryPolicy = void 0;
|
|
3
4
|
exports.describeApiFailure = describeApiFailure;
|
|
4
5
|
exports.isBatchRouteMissing = isBatchRouteMissing;
|
|
5
6
|
exports.chunkItems = chunkItems;
|
|
@@ -81,31 +82,51 @@ async function reconcileSubmittedNodes(nodes, mapping, error, deadlineAt = Date.
|
|
|
81
82
|
let timer;
|
|
82
83
|
try {
|
|
83
84
|
const read = (0, tasks_1.findTaskByExternalKey)({
|
|
84
|
-
externalKey,
|
|
85
|
+
externalKey,
|
|
86
|
+
spaceId: node.payload.spaceId,
|
|
87
|
+
goalId: node.payload.goalId,
|
|
85
88
|
});
|
|
86
|
-
const existing = await Promise.race([
|
|
89
|
+
const existing = await Promise.race([
|
|
90
|
+
read,
|
|
91
|
+
new Promise((resolve) => {
|
|
87
92
|
timer = setTimeout(() => resolve(null), Math.max(1, deadlineAt - Date.now()));
|
|
88
|
-
})
|
|
93
|
+
}),
|
|
94
|
+
]);
|
|
89
95
|
if (existing?.task?.externalKey === externalKey && (0, tasks_1.taskId)(existing.task)) {
|
|
90
96
|
const id = (0, tasks_1.taskId)(existing.task);
|
|
91
97
|
mapping[node.key] = id;
|
|
92
|
-
rows.push({
|
|
93
|
-
|
|
98
|
+
rows.push({
|
|
99
|
+
key: node.key,
|
|
100
|
+
id,
|
|
101
|
+
status: 'exists',
|
|
102
|
+
externalKey,
|
|
103
|
+
...(typeof existing.task.identifier === 'string'
|
|
104
|
+
? { identifier: existing.task.identifier }
|
|
105
|
+
: {}),
|
|
94
106
|
});
|
|
95
107
|
return;
|
|
96
108
|
}
|
|
97
109
|
}
|
|
98
|
-
catch {
|
|
110
|
+
catch {
|
|
111
|
+
/* A failed read does not prove the write failed. */
|
|
112
|
+
}
|
|
99
113
|
finally {
|
|
100
114
|
clearTimeout(timer);
|
|
101
115
|
}
|
|
102
116
|
}
|
|
103
|
-
failures.push({
|
|
117
|
+
failures.push({
|
|
118
|
+
key: node.key,
|
|
119
|
+
skipped: false,
|
|
120
|
+
outcome: 'unknown',
|
|
104
121
|
...(externalKey ? { externalKey } : {}),
|
|
105
|
-
error: {
|
|
122
|
+
error: {
|
|
123
|
+
...error,
|
|
124
|
+
code: 'BATCH_OUTCOME_UNKNOWN',
|
|
125
|
+
retryable: Boolean(externalKey),
|
|
106
126
|
message: `The result for "${node.key}" is unknown. ` +
|
|
107
|
-
(externalKey
|
|
108
|
-
|
|
127
|
+
(externalKey
|
|
128
|
+
? `No committed task was confirmed by external key "${externalKey}"; the original write may still finish. Re-run the same keyed tree to reconcile and resume.`
|
|
129
|
+
: 'This node has no stable external key. Inspect the Space before another write; do not re-run --fresh blindly.'),
|
|
109
130
|
},
|
|
110
131
|
});
|
|
111
132
|
}));
|
|
@@ -116,47 +137,181 @@ async function reconcileSubmittedNodes(nodes, mapping, error, deadlineAt = Date.
|
|
|
116
137
|
return { rows, failures };
|
|
117
138
|
}
|
|
118
139
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
140
|
+
* How a create that is not finishing is retried. A seam rather than constants so
|
|
141
|
+
* a test can run the whole policy without waiting through it.
|
|
142
|
+
*
|
|
143
|
+
* Each round that confirms nothing waits the next interval and counts toward the
|
|
144
|
+
* limit; a round that confirms anything resets it. The total wait is bounded
|
|
145
|
+
* (about 15 s), on purpose: this is a courtesy to a server that is briefly slow
|
|
146
|
+
* or rate-limited, not a promise to outlast an outage — past it the command says
|
|
147
|
+
* what is unknown and how to continue.
|
|
148
|
+
*/
|
|
149
|
+
exports.batchRetryPolicy = {
|
|
150
|
+
backoffMs: [1000, 2000, 4000, 8000],
|
|
151
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
152
|
+
};
|
|
153
|
+
const SERVER_DEADLINE_CODES = new Set(['BATCH_DEADLINE_EXCEEDED']);
|
|
154
|
+
/** A row the server skipped because something it depends on never came to exist. */
|
|
155
|
+
const UPSTREAM_FAILED_CODES = new Set(['BATCH_PARENT_NOT_CREATED', 'BATCH_BLOCKER_NOT_CREATED']);
|
|
156
|
+
function isUncertainTransport(error) {
|
|
157
|
+
return (!error.status ||
|
|
158
|
+
error.status === 408 ||
|
|
159
|
+
error.status === 425 ||
|
|
160
|
+
error.status === 429 ||
|
|
161
|
+
error.status >= 500 ||
|
|
162
|
+
error.code === 'IDEMPOTENCY_IN_PROGRESS');
|
|
163
|
+
}
|
|
164
|
+
function stableKeyOf(node) {
|
|
165
|
+
const key = node.payload.externalKey;
|
|
166
|
+
return typeof key === 'string' && key.trim() ? key : undefined;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Create `nodes` — parents and blockers BEFORE what depends on them — until every
|
|
170
|
+
* one exists, or say precisely which do not and why.
|
|
171
|
+
*
|
|
172
|
+
* One request is a bounded unit of work, not the whole job: the server answers
|
|
173
|
+
* within a response budget, creating what it can and reporting the rest as not
|
|
174
|
+
* started (`BATCH_DEADLINE_EXCEEDED`) or still in flight (`pending`). That is a
|
|
175
|
+
* normal, successful answer, so this loop is what turns it into a finished tree:
|
|
176
|
+
* submit, record what is confirmed, submit what is left. It used to stop at the
|
|
177
|
+
* first row the server had not confirmed and hand the person a `--resume` to
|
|
178
|
+
* type — once per row or two, against a server that confirms a row or two per
|
|
179
|
+
* request (OSK-S-440 #1, #3, #4).
|
|
180
|
+
*
|
|
181
|
+
* The one rule that makes resubmitting safe is the one the server enforces: a
|
|
182
|
+
* node with a stable `externalKey` can be created once. A submitted write whose
|
|
183
|
+
* outcome is unknown is therefore READ first (`reconcileSubmittedNodes`), and only
|
|
184
|
+
* if the key is still absent is the node sent again — where, if the first write
|
|
185
|
+
* has landed in the meantime, the unique index turns it into `exists`. A node with
|
|
186
|
+
* no stable key is never resubmitted after an uncertain outcome, because nothing
|
|
187
|
+
* could tell the second write from a duplicate; that case stops and says so, as
|
|
188
|
+
* before.
|
|
189
|
+
*
|
|
190
|
+
* `mapping` holds key -> id for nodes that already exist and is extended in place.
|
|
122
191
|
*/
|
|
123
|
-
async function createNodesViaBatch(nodes, mapping) {
|
|
192
|
+
async function createNodesViaBatch(nodes, mapping, options = {}) {
|
|
193
|
+
const startedAt = Date.now();
|
|
194
|
+
const position = new Map(nodes.map((node, index) => [node.key, index]));
|
|
124
195
|
const rows = [];
|
|
125
196
|
const failures = [];
|
|
126
|
-
|
|
197
|
+
const chunkSize = data_provider_1.TASK_BATCH_MAX_ITEMS ?? 100;
|
|
198
|
+
let remaining = nodes.filter((node) => !(node.key in mapping));
|
|
199
|
+
/** Nodes whose last submission ended without a verdict: they may exist. */
|
|
200
|
+
const unknown = new Set();
|
|
201
|
+
let stalled = 0;
|
|
202
|
+
let round = 0;
|
|
203
|
+
let lastError;
|
|
204
|
+
const report = (waiting) => options.onProgress?.({
|
|
205
|
+
confirmed: rows.length,
|
|
206
|
+
total: nodes.length,
|
|
207
|
+
round,
|
|
208
|
+
elapsedMs: Date.now() - startedAt,
|
|
209
|
+
...(waiting ? { waiting } : {}),
|
|
210
|
+
});
|
|
211
|
+
/** Everything still owed becomes a failure that says whether it may exist. */
|
|
212
|
+
const giveUp = (reason) => {
|
|
213
|
+
for (const node of remaining) {
|
|
214
|
+
const externalKey = stableKeyOf(node);
|
|
215
|
+
const maybeThere = unknown.has(node.key);
|
|
216
|
+
failures.push({
|
|
217
|
+
key: node.key,
|
|
218
|
+
skipped: !maybeThere,
|
|
219
|
+
outcome: maybeThere ? 'unknown' : 'not_attempted',
|
|
220
|
+
...(externalKey ? { externalKey } : {}),
|
|
221
|
+
error: {
|
|
222
|
+
...(maybeThere && lastError ? lastError : {}),
|
|
223
|
+
message: maybeThere
|
|
224
|
+
? `The result for "${node.key}" is unknown: ${reason}` +
|
|
225
|
+
(externalKey
|
|
226
|
+
? ' Re-run the same keyed tree with --resume to confirm it and continue.'
|
|
227
|
+
: ' This node has no stable external key. Inspect the Space before another write; do not re-run --fresh blindly.')
|
|
228
|
+
: `"${node.key}" was not started: ${reason}`,
|
|
229
|
+
code: maybeThere ? 'BATCH_OUTCOME_UNKNOWN' : 'BATCH_NOT_ATTEMPTED',
|
|
230
|
+
retryable: maybeThere ? Boolean(externalKey) : true,
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
remaining = [];
|
|
235
|
+
};
|
|
236
|
+
while (remaining.length > 0) {
|
|
237
|
+
round += 1;
|
|
238
|
+
const chunk = remaining.slice(0, chunkSize);
|
|
127
239
|
const inChunk = new Map(chunk.map((node, i) => [node.key, `n${i}`]));
|
|
128
240
|
const items = [];
|
|
129
241
|
const sent = [];
|
|
242
|
+
const dropped = new Set();
|
|
130
243
|
for (const [i, node] of chunk.entries()) {
|
|
131
244
|
const { parentId: _ignored, ...payload } = node.payload;
|
|
132
245
|
const item = { ...payload, tempId: `n${i}` };
|
|
246
|
+
let unresolved;
|
|
133
247
|
if (node.parentKey) {
|
|
134
248
|
const sameChunk = inChunk.get(node.parentKey);
|
|
135
249
|
if (sameChunk)
|
|
136
250
|
item.parentTempId = sameChunk;
|
|
137
251
|
else if (mapping[node.parentKey])
|
|
138
252
|
item.parentId = mapping[node.parentKey];
|
|
139
|
-
else
|
|
140
|
-
|
|
141
|
-
key: node.key,
|
|
142
|
-
skipped: true,
|
|
143
|
-
error: {
|
|
144
|
-
message: `Parent "${node.parentKey}" was not created, so "${node.key}" was not attempted`,
|
|
145
|
-
code: 'BATCH_PARENT_NOT_CREATED',
|
|
146
|
-
retryable: false,
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
253
|
+
else
|
|
254
|
+
unresolved = `Parent "${node.parentKey}"`;
|
|
151
255
|
}
|
|
152
256
|
else if (node.payload.parentId) {
|
|
153
257
|
item.parentId = node.payload.parentId;
|
|
154
258
|
}
|
|
259
|
+
if (!unresolved && node.blockerKeys?.length) {
|
|
260
|
+
const tempIds = [];
|
|
261
|
+
const ids = [];
|
|
262
|
+
for (const blockerKey of node.blockerKeys) {
|
|
263
|
+
const sameChunk = inChunk.get(blockerKey);
|
|
264
|
+
if (sameChunk)
|
|
265
|
+
tempIds.push(sameChunk);
|
|
266
|
+
else if (mapping[blockerKey])
|
|
267
|
+
ids.push(mapping[blockerKey]);
|
|
268
|
+
else {
|
|
269
|
+
unresolved = `Blocker "${blockerKey}"`;
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (!unresolved) {
|
|
274
|
+
if (tempIds.length)
|
|
275
|
+
item.blockedByTempIds = tempIds;
|
|
276
|
+
if (ids.length) {
|
|
277
|
+
item.blockedByIds = [
|
|
278
|
+
...new Set([...(payload.blockedByIds ?? []), ...ids]),
|
|
279
|
+
];
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (unresolved) {
|
|
284
|
+
dropped.add(node.key);
|
|
285
|
+
failures.push({
|
|
286
|
+
key: node.key,
|
|
287
|
+
skipped: true,
|
|
288
|
+
outcome: 'not_attempted',
|
|
289
|
+
error: {
|
|
290
|
+
message: `${unresolved} was not created, so "${node.key}" was not attempted`,
|
|
291
|
+
code: node.blockerKeys?.length
|
|
292
|
+
? 'BATCH_BLOCKER_NOT_CREATED'
|
|
293
|
+
: 'BATCH_PARENT_NOT_CREATED',
|
|
294
|
+
retryable: false,
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
155
299
|
items.push(item);
|
|
156
300
|
sent.push(node);
|
|
157
301
|
}
|
|
158
|
-
|
|
302
|
+
// A dropped node is a failure already recorded; it leaves the queue now, or
|
|
303
|
+
// it would be examined, and recorded, again every round.
|
|
304
|
+
remaining = remaining.filter((node) => !dropped.has(node.key));
|
|
305
|
+
const upstreamBroken = dropped.size > 0;
|
|
306
|
+
if (items.length === 0) {
|
|
307
|
+
// Everything in this chunk depended on something that is gone, so nothing
|
|
308
|
+
// further can be built on it.
|
|
309
|
+
giveUp('a node it depends on was not created.');
|
|
159
310
|
break;
|
|
311
|
+
}
|
|
312
|
+
let confirmedThisRound = 0;
|
|
313
|
+
let hardFailure = false;
|
|
314
|
+
let waitReason = 'the server confirmed nothing';
|
|
160
315
|
let response;
|
|
161
316
|
try {
|
|
162
317
|
response = await data_provider_1.dataService.createTasksBatch(items, {
|
|
@@ -164,63 +319,124 @@ async function createNodesViaBatch(nodes, mapping) {
|
|
|
164
319
|
});
|
|
165
320
|
}
|
|
166
321
|
catch (err) {
|
|
167
|
-
if (
|
|
322
|
+
if (round === 1 && isBatchRouteMissing(err))
|
|
168
323
|
return { supported: false };
|
|
169
324
|
const error = describeApiFailure(err);
|
|
170
|
-
|
|
171
|
-
if (
|
|
325
|
+
lastError = error;
|
|
326
|
+
if (isUncertainTransport(error)) {
|
|
327
|
+
// Read before writing again: a lost response is not a lost write.
|
|
172
328
|
const recovered = await reconcileSubmittedNodes(sent, mapping, error);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
329
|
+
for (const row of recovered.rows) {
|
|
330
|
+
rows.push(row);
|
|
331
|
+
unknown.delete(row.key);
|
|
332
|
+
confirmedThisRound += 1;
|
|
333
|
+
}
|
|
334
|
+
const stillUnknown = recovered.failures.map((f) => f.key);
|
|
335
|
+
for (const key of stillUnknown)
|
|
336
|
+
unknown.add(key);
|
|
337
|
+
const unkeyed = sent.filter((n) => stillUnknown.includes(n.key) && !stableKeyOf(n));
|
|
338
|
+
remaining = remaining.filter((node) => !(node.key in mapping));
|
|
339
|
+
if (unkeyed.length > 0) {
|
|
340
|
+
// No stable key: a second write could not be told from a duplicate.
|
|
341
|
+
// Say what is unknown and stop, exactly as before.
|
|
342
|
+
for (const f of recovered.failures)
|
|
343
|
+
failures.push(f);
|
|
344
|
+
remaining = remaining.filter((node) => !recovered.failures.some((f) => f.key === node.key));
|
|
345
|
+
giveUp('the request was lost before the server answered.');
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
waitReason = `the request did not complete (${error.status ?? 'no response'})`;
|
|
182
349
|
}
|
|
183
350
|
else {
|
|
184
|
-
|
|
351
|
+
// The whole request was refused (400/401/403/413/422...): a decision, not a glitch.
|
|
352
|
+
for (const node of sent) {
|
|
185
353
|
failures.push({ key: node.key, skipped: false, outcome: 'failed', error });
|
|
186
|
-
for (const node of nodes.slice((chunkIndex + 1) * (data_provider_1.TASK_BATCH_MAX_ITEMS ?? 100))) {
|
|
187
|
-
failures.push({ key: node.key, skipped: true, outcome: 'not_attempted',
|
|
188
|
-
error: { message: 'Not started after a batch refusal.', code: 'BATCH_NOT_ATTEMPTED', retryable: true } });
|
|
189
354
|
}
|
|
355
|
+
remaining = remaining.filter((node) => !sent.includes(node));
|
|
356
|
+
giveUp('the batch request was refused.');
|
|
357
|
+
break;
|
|
190
358
|
}
|
|
191
|
-
break;
|
|
192
359
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
360
|
+
if (response) {
|
|
361
|
+
const results = new Map((response.results ?? []).map((r) => [r.index, r]));
|
|
362
|
+
const retryLater = [];
|
|
363
|
+
const reconcileDeadlineAt = Date.now() + 5000;
|
|
364
|
+
for (const [i, node] of sent.entries()) {
|
|
365
|
+
const result = results.get(i);
|
|
366
|
+
const code = result?.error?.code;
|
|
367
|
+
const status = result?.error?.status ?? 0;
|
|
368
|
+
const confirmed = result?.ok && result.id;
|
|
369
|
+
if (confirmed) {
|
|
370
|
+
mapping[node.key] = result.id;
|
|
371
|
+
rows.push({
|
|
372
|
+
key: node.key,
|
|
373
|
+
id: result.id,
|
|
374
|
+
...(result.identifier ? { identifier: result.identifier } : {}),
|
|
375
|
+
status: result.status === 'exists' ? 'exists' : 'created',
|
|
376
|
+
...(result.externalKey ? { externalKey: result.externalKey } : {}),
|
|
377
|
+
});
|
|
378
|
+
unknown.delete(node.key);
|
|
379
|
+
confirmedThisRound += 1;
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
// Submitted, outcome not established: pending, absent from a 200, an ok
|
|
383
|
+
// row with no id, or a 5xx the item handler returned. Read, don't guess.
|
|
384
|
+
if (!result || result.status === 'pending' || result.ok || status >= 500) {
|
|
385
|
+
const recovered = await reconcileSubmittedNodes([
|
|
386
|
+
{
|
|
387
|
+
...node,
|
|
388
|
+
payload: {
|
|
389
|
+
...node.payload,
|
|
390
|
+
...(result?.externalKey ? { externalKey: result.externalKey } : {}),
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
], mapping, {
|
|
394
|
+
message: 'Batch did not confirm this submitted write',
|
|
395
|
+
code: 'BATCH_OUTCOME_UNKNOWN',
|
|
396
|
+
retryable: true,
|
|
397
|
+
}, reconcileDeadlineAt);
|
|
398
|
+
if (recovered.rows.length > 0) {
|
|
399
|
+
for (const row of recovered.rows)
|
|
400
|
+
rows.push(row);
|
|
401
|
+
unknown.delete(node.key);
|
|
402
|
+
confirmedThisRound += 1;
|
|
403
|
+
}
|
|
404
|
+
else if (stableKeyOf(node) || result?.externalKey) {
|
|
405
|
+
unknown.add(node.key);
|
|
406
|
+
retryLater.push(node);
|
|
407
|
+
lastError = recovered.failures[0]?.error ?? lastError;
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
failures.push(...recovered.failures);
|
|
411
|
+
hardFailure = true;
|
|
412
|
+
}
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
// Never started: the response budget ran out, or the rate window did.
|
|
416
|
+
if (result.status === 'skipped' &&
|
|
417
|
+
(SERVER_DEADLINE_CODES.has(code ?? '') ||
|
|
418
|
+
code === 'BATCH_RATE_LIMITED' ||
|
|
419
|
+
status === 429 ||
|
|
420
|
+
status === 408)) {
|
|
421
|
+
retryLater.push(node);
|
|
422
|
+
if (code === 'BATCH_RATE_LIMITED' || status === 429)
|
|
423
|
+
waitReason = 'the create rate limit is spent for this minute';
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
// Something it depends on failed, or the row itself was refused for good.
|
|
427
|
+
const retryable = Boolean(result.error?.retryable) && result.status !== 'skipped';
|
|
428
|
+
if (retryable) {
|
|
429
|
+
retryLater.push(node);
|
|
430
|
+
lastError = describeApiFailure(result.error);
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
hardFailure = true;
|
|
220
434
|
failures.push({
|
|
221
435
|
key: node.key,
|
|
222
436
|
skipped: result.status === 'skipped',
|
|
223
|
-
outcome: result.status === 'skipped'
|
|
437
|
+
outcome: result.status === 'skipped' || UPSTREAM_FAILED_CODES.has(code ?? '')
|
|
438
|
+
? 'not_attempted'
|
|
439
|
+
: 'failed',
|
|
224
440
|
...(result.externalKey ? { externalKey: result.externalKey } : {}),
|
|
225
441
|
error: result.error
|
|
226
442
|
? {
|
|
@@ -232,17 +448,37 @@ async function createNodesViaBatch(nodes, mapping) {
|
|
|
232
448
|
: { message: 'Task was not created', retryable: false },
|
|
233
449
|
});
|
|
234
450
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
error: { message: 'Not started after an incomplete batch; resume the same keyed tree.', code: 'BATCH_NOT_ATTEMPTED', retryable: true }, });
|
|
451
|
+
const retrySet = new Set(retryLater.map((n) => n.key));
|
|
452
|
+
remaining = remaining.filter((node) => !(node.key in mapping) && (retrySet.has(node.key) || !sent.includes(node)));
|
|
453
|
+
// Rows refused for good leave the queue; the loop stops below.
|
|
454
|
+
if (hardFailure) {
|
|
455
|
+
remaining = remaining.filter((node) => !failures.some((f) => f.key === node.key));
|
|
241
456
|
}
|
|
457
|
+
}
|
|
458
|
+
report();
|
|
459
|
+
if (hardFailure || upstreamBroken) {
|
|
460
|
+
giveUp('an earlier node was refused; nothing that depends on it can be created.');
|
|
242
461
|
break;
|
|
243
462
|
}
|
|
463
|
+
if (remaining.length === 0)
|
|
464
|
+
break;
|
|
465
|
+
if (confirmedThisRound > 0) {
|
|
466
|
+
stalled = 0;
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
stalled += 1;
|
|
470
|
+
if (stalled >= exports.batchRetryPolicy.backoffMs.length) {
|
|
471
|
+
giveUp(`no progress after ${stalled} attempts (${waitReason}${lastError?.code ? `; last: ${lastError.code}` : ''}).`);
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
const ms = exports.batchRetryPolicy.backoffMs[stalled - 1];
|
|
475
|
+
report({ ms, reason: waitReason });
|
|
476
|
+
await exports.batchRetryPolicy.sleep(ms);
|
|
477
|
+
}
|
|
244
478
|
}
|
|
245
|
-
|
|
479
|
+
rows.sort((a, b) => (position.get(a.key) ?? 0) - (position.get(b.key) ?? 0));
|
|
480
|
+
failures.sort((a, b) => (position.get(a.key) ?? 0) - (position.get(b.key) ?? 0));
|
|
481
|
+
return { supported: true, rows, failures, rounds: round };
|
|
246
482
|
}
|
|
247
483
|
/**
|
|
248
484
|
* Apply `updates` in chunks; `results[i]` answers `updates[i]`. A chunk the
|
|
@@ -29,3 +29,8 @@ export declare function describeEstimate(economics: unknown): string;
|
|
|
29
29
|
* scored and sorts after every row that is.
|
|
30
30
|
*/
|
|
31
31
|
export declare function economicsScoreText(economics: unknown): string;
|
|
32
|
+
/**
|
|
33
|
+
* One line for a `task.economics.*` Activity event (OSK-15365): the revision, the claim after the
|
|
34
|
+
* change and the claim it replaced, so `tasks activity` shows from-what-to-what without `--json`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function describeEconomicsChange(data: unknown): string;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.economicsFromFlags = economicsFromFlags;
|
|
4
4
|
exports.describeEstimate = describeEstimate;
|
|
5
5
|
exports.economicsScoreText = economicsScoreText;
|
|
6
|
+
exports.describeEconomicsChange = describeEconomicsChange;
|
|
6
7
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
7
8
|
/**
|
|
8
9
|
* A flag's amount as a number, or an error that names the flag and the value (OSK-15366). A raw
|
|
@@ -85,3 +86,17 @@ function economicsScoreText(economics) {
|
|
|
85
86
|
const score = (0, data_provider_1.proposalEconomicsRank)(economics).score;
|
|
86
87
|
return score === null || !Number.isFinite(score) ? '—' : String(Math.round(score));
|
|
87
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* One line for a `task.economics.*` Activity event (OSK-15365): the revision, the claim after the
|
|
91
|
+
* change and the claim it replaced, so `tasks activity` shows from-what-to-what without `--json`.
|
|
92
|
+
*/
|
|
93
|
+
function describeEconomicsChange(data) {
|
|
94
|
+
const value = (data && typeof data === 'object' ? data : {});
|
|
95
|
+
const now = value.change === 'retracted' || !value.after ? 'no estimate' : describeEstimate(value.after);
|
|
96
|
+
let was = 'no estimate';
|
|
97
|
+
if (value.before)
|
|
98
|
+
was = describeEstimate(value.before);
|
|
99
|
+
else if (value.beforeUnavailable)
|
|
100
|
+
was = 'not recorded';
|
|
101
|
+
return `r${value.revision ?? '?'} ${value.change ?? 'changed'}: ${now} (was: ${was})`;
|
|
102
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* reports `recurrence` on the completion; this words it.
|
|
2
|
+
* The ONE way back for a series whose next occurrence was never armed (OSK-15338), named by every
|
|
3
|
+
* surface that reports the failure. `tasks schedule` refuses the finished occurrence that carries
|
|
4
|
+
* the failure, so naming it (as `tasks show` did) sent people to a command that could never work.
|
|
5
|
+
* An `invalid_pattern` failure has no cure without a new pattern, so the hint carries `--cron`.
|
|
7
6
|
*/
|
|
7
|
+
export declare function recurrenceRearmHint(code: unknown, taskRef?: string, bin?: string): string;
|
|
8
8
|
export type RecurrenceOutcome = {
|
|
9
9
|
state?: string;
|
|
10
10
|
reason?: string;
|
|
@@ -16,4 +16,4 @@ export type RecurrenceOutcome = {
|
|
|
16
16
|
armFailure?: unknown;
|
|
17
17
|
};
|
|
18
18
|
/** One line, or null when the Task does not recur (or an older server did not say). */
|
|
19
|
-
export declare function recurrenceOutcomeLine(outcome: RecurrenceOutcome | null | undefined): string | null;
|
|
19
|
+
export declare function recurrenceOutcomeLine(outcome: RecurrenceOutcome | null | undefined, taskRef?: string, bin?: string): string | null;
|
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.recurrenceRearmHint = recurrenceRearmHint;
|
|
3
4
|
exports.recurrenceOutcomeLine = recurrenceOutcomeLine;
|
|
5
|
+
/**
|
|
6
|
+
* What completing a recurring occurrence did to its series, as a sentence (OSK-15483).
|
|
7
|
+
*
|
|
8
|
+
* `tasks complete` armed the next occurrence (or deliberately did not) and printed nothing either way, so a
|
|
9
|
+
* person could not tell that the series continued, or that the third of three runs had ended it. The server
|
|
10
|
+
* reports `recurrence` on the completion; this words it.
|
|
11
|
+
*/
|
|
12
|
+
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
13
|
+
/**
|
|
14
|
+
* The ONE way back for a series whose next occurrence was never armed (OSK-15338), named by every
|
|
15
|
+
* surface that reports the failure. `tasks schedule` refuses the finished occurrence that carries
|
|
16
|
+
* the failure, so naming it (as `tasks show` did) sent people to a command that could never work.
|
|
17
|
+
* An `invalid_pattern` failure has no cure without a new pattern, so the hint carries `--cron`.
|
|
18
|
+
*/
|
|
19
|
+
function recurrenceRearmHint(code, taskRef = '<task-id>', bin = 'skrr') {
|
|
20
|
+
const fix = code === 'invalid_pattern' ? ' --cron "<minute hour day month weekday>"' : '';
|
|
21
|
+
return `Re-arm the series with \`${bin} tasks rearm-recurrence ${taskRef}${fix}\`.`;
|
|
22
|
+
}
|
|
4
23
|
const ENDED_BECAUSE = {
|
|
5
24
|
max_runs: 'it reached its maximum number of runs',
|
|
6
25
|
end_date: 'its end date has passed',
|
|
7
26
|
no_next_occurrence: 'its pattern has no further occurrence',
|
|
8
27
|
};
|
|
9
28
|
/** One line, or null when the Task does not recur (or an older server did not say). */
|
|
10
|
-
function recurrenceOutcomeLine(outcome) {
|
|
29
|
+
function recurrenceOutcomeLine(outcome, taskRef, bin = 'skrr') {
|
|
11
30
|
if (!outcome || typeof outcome !== 'object')
|
|
12
31
|
return null;
|
|
13
32
|
switch (outcome.state) {
|
|
@@ -22,8 +41,13 @@ function recurrenceOutcomeLine(outcome) {
|
|
|
22
41
|
return 'Recurrence: the series is paused, so no next occurrence was scheduled.';
|
|
23
42
|
case 'routine_managed':
|
|
24
43
|
return 'Recurrence: a Routine owns this series, so no next occurrence was scheduled here.';
|
|
25
|
-
case 'not_armed':
|
|
26
|
-
|
|
44
|
+
case 'not_armed': {
|
|
45
|
+
const code = outcome.armFailure?.code;
|
|
46
|
+
const why = typeof code === 'string' && code
|
|
47
|
+
? `${(0, data_provider_1.describeRecurrenceArmFailure)(code)} (${code})`
|
|
48
|
+
: 'The series is live but no next occurrence was scheduled.';
|
|
49
|
+
return `Recurrence: ${why} ${recurrenceRearmHint(code, taskRef, bin)}`;
|
|
50
|
+
}
|
|
27
51
|
default:
|
|
28
52
|
return null;
|
|
29
53
|
}
|
package/dist/lib/tasks.d.ts
CHANGED
|
@@ -241,6 +241,45 @@ export declare function findTaskByExternalKey({ externalKey, spaceId, goalId, pa
|
|
|
241
241
|
task: TaskRecord | null;
|
|
242
242
|
duplicates: TaskRecord[];
|
|
243
243
|
}>;
|
|
244
|
+
export type ExternalKeyLookup = {
|
|
245
|
+
externalKey: string;
|
|
246
|
+
spaceId?: unknown;
|
|
247
|
+
goalId?: unknown;
|
|
248
|
+
};
|
|
249
|
+
export type ExternalKeyHit = {
|
|
250
|
+
task: TaskRecord | null;
|
|
251
|
+
duplicates: TaskRecord[];
|
|
252
|
+
/** Set when the lookup itself failed: "could not look" is not "nothing there". */
|
|
253
|
+
error?: Error;
|
|
254
|
+
};
|
|
255
|
+
/** The identity of one lookup: a key means a task only within its scope. */
|
|
256
|
+
export declare function externalKeyLookupId(lookup: ExternalKeyLookup): string;
|
|
257
|
+
/**
|
|
258
|
+
* Which of these tasks already exist? Answers for many keys at once.
|
|
259
|
+
*
|
|
260
|
+
* `create-tree`, `--resume` and `--verify` all start by asking that of every node
|
|
261
|
+
* of a tree, and asked it once per node, one after another: 94 round trips
|
|
262
|
+
* before the first write (OSK-S-440 #3). Two things bring that down without
|
|
263
|
+
* changing a single answer:
|
|
264
|
+
*
|
|
265
|
+
* - Keys that share a prefix (`aap/c1`, `aap/c2`, ... — a tree is written
|
|
266
|
+
* under one) are answered by ONE paged read of the prefix, per scope. The
|
|
267
|
+
* read can return rows that are not this tree's; they are ignored, since a
|
|
268
|
+
* key is matched whole. If the prefix is broad enough that the read hits its
|
|
269
|
+
* page cap, whatever it did not find is looked up directly, because absence
|
|
270
|
+
* from a truncated read proves nothing.
|
|
271
|
+
* - Whatever is left is looked up by exact key, a few at a time rather than
|
|
272
|
+
* one at a time.
|
|
273
|
+
*
|
|
274
|
+
* A server that predates `externalKeyPrefix` ignores it and returns the scope's
|
|
275
|
+
* tasks: still correct (matched whole), merely a wider read, and bounded by the
|
|
276
|
+
* same cap.
|
|
277
|
+
*/
|
|
278
|
+
export declare function findTasksByExternalKeys(lookups: readonly ExternalKeyLookup[], opts?: {
|
|
279
|
+
concurrency?: number;
|
|
280
|
+
minKeysForPrefix?: number;
|
|
281
|
+
maxPages?: number;
|
|
282
|
+
}): Promise<Map<string, ExternalKeyHit>>;
|
|
244
283
|
/**
|
|
245
284
|
* What the CLI reports under `defaultAgent` in `--json` output.
|
|
246
285
|
*
|