@skrr-ai/cli 0.1.126 → 0.1.128
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/dist/base-command.d.ts +23 -0
- package/dist/base-command.js +72 -0
- package/dist/commands/agents/chat.js +7 -0
- package/dist/commands/agents/goals/list.d.ts +2 -2
- package/dist/commands/agents/goals/list.js +2 -2
- package/dist/commands/commitments/action-history.js +1 -1
- package/dist/commands/commitments/cases.js +3 -3
- package/dist/commands/commitments/check-now.d.ts +10 -2
- package/dist/commands/commitments/check-now.js +53 -3
- package/dist/commands/commitments/create.d.ts +6 -14
- package/dist/commands/commitments/create.js +61 -6
- package/dist/commands/commitments/doctor.js +3 -1
- package/dist/commands/commitments/effective-policy.d.ts +11 -1
- package/dist/commands/commitments/effective-policy.js +84 -4
- package/dist/commands/commitments/hypotheses.js +12 -12
- package/dist/commands/commitments/next-tick.js +5 -0
- package/dist/commands/commitments/observations.js +18 -17
- package/dist/commands/commitments/pack/list.js +2 -1
- package/dist/commands/commitments/pack/plan.d.ts +6 -0
- package/dist/commands/commitments/pack/plan.js +19 -0
- package/dist/commands/commitments/preflight.d.ts +19 -0
- package/dist/commands/commitments/preflight.js +108 -10
- package/dist/commands/commitments/trigger-status.js +8 -0
- package/dist/commands/commitments/update.d.ts +1 -0
- package/dist/commands/commitments/update.js +45 -4
- package/dist/commands/commitments/wake-capabilities.js +13 -0
- package/dist/commands/compass/archive.js +4 -0
- package/dist/commands/compass/commitments.js +15 -1
- package/dist/commands/compass/create.js +11 -3
- package/dist/commands/compass/effective-policy.d.ts +1 -0
- package/dist/commands/compass/effective-policy.js +8 -7
- package/dist/commands/compass/revert.d.ts +8 -0
- package/dist/commands/compass/revert.js +25 -1
- package/dist/commands/compass/revisions.d.ts +2 -0
- package/dist/commands/compass/revisions.js +18 -2
- package/dist/commands/compass/tactic.d.ts +5 -3
- package/dist/commands/compass/tactic.js +8 -7
- package/dist/commands/compass/update.js +19 -3
- package/dist/commands/followups/act.js +1 -1
- package/dist/commands/followups/cancel.js +13 -0
- package/dist/commands/followups/observability.js +20 -1
- package/dist/commands/followups/plans/create.js +12 -1
- package/dist/commands/followups/show.js +13 -0
- package/dist/commands/followups/watch.js +1 -1
- package/dist/commands/goals/create.d.ts +1 -0
- package/dist/commands/goals/create.js +4 -1
- package/dist/commands/goals/planner-config.d.ts +11 -0
- package/dist/commands/goals/planner-config.js +16 -0
- package/dist/commands/goals/revisions.js +3 -19
- package/dist/commands/machines/update.d.ts +5 -3
- package/dist/commands/machines/update.js +70 -9
- package/dist/commands/northstars/create.js +3 -1
- package/dist/commands/northstars/governance/configure.d.ts +4 -0
- package/dist/commands/northstars/governance/configure.js +75 -9
- package/dist/commands/northstars/intents/decide.d.ts +6 -0
- package/dist/commands/northstars/intents/decide.js +69 -15
- package/dist/commands/northstars/intents/list.d.ts +2 -1
- package/dist/commands/northstars/intents/list.js +15 -5
- package/dist/commands/northstars/intents.d.ts +26 -0
- package/dist/commands/northstars/intents.js +49 -0
- package/dist/commands/northstars/list.d.ts +1 -0
- package/dist/commands/northstars/list.js +24 -3
- package/dist/commands/northstars/recommendations/list.d.ts +1 -1
- package/dist/commands/northstars/recommendations/list.js +3 -3
- package/dist/commands/northstars/recommendations.d.ts +19 -0
- package/dist/commands/northstars/recommendations.js +42 -0
- package/dist/commands/northstars/review-now.d.ts +1 -0
- package/dist/commands/northstars/review-now.js +10 -4
- package/dist/commands/northstars/reviews.d.ts +1 -0
- package/dist/commands/northstars/reviews.js +19 -3
- package/dist/commands/northstars/update.js +8 -1
- package/dist/commands/proposals/list.js +11 -2
- package/dist/commands/proposals/show.js +9 -1
- package/dist/commands/spaces/auto-executor/preview.d.ts +14 -1
- package/dist/commands/spaces/auto-executor/preview.js +35 -1
- package/dist/commands/spaces/auto-executor/run.d.ts +17 -0
- package/dist/commands/spaces/auto-executor/run.js +60 -2
- package/dist/commands/spaces/files/upload.js +1 -2
- package/dist/commands/spaces/index.js +23 -0
- package/dist/commands/spaces/revisions.js +3 -2
- package/dist/commands/tasks/revisions.js +3 -2
- package/dist/commands/triggers/fire-detail.js +2 -0
- package/dist/generated/agent-guides.json +69 -69
- package/dist/lib/agentic-stream.d.ts +9 -0
- package/dist/lib/agentic-stream.js +14 -0
- package/dist/lib/command-miss.js +11 -2
- package/dist/lib/commitment-attention-copy.d.ts +96 -0
- package/dist/lib/commitment-attention-copy.js +209 -0
- package/dist/lib/commitment-loop-status.d.ts +6 -0
- package/dist/lib/commitment-loop-status.js +16 -6
- package/dist/lib/commitments.d.ts +88 -0
- package/dist/lib/commitments.js +188 -0
- package/dist/lib/compass-body-help.d.ts +20 -0
- package/dist/lib/compass-body-help.js +61 -0
- package/dist/lib/compass-refusals.d.ts +35 -0
- package/dist/lib/compass-refusals.js +50 -0
- package/dist/lib/compasses.d.ts +80 -4
- package/dist/lib/compasses.js +232 -12
- package/dist/lib/followups.d.ts +22 -1
- package/dist/lib/followups.js +21 -1
- package/dist/lib/format.d.ts +11 -0
- package/dist/lib/format.js +29 -0
- package/dist/lib/initiatives.d.ts +9 -2
- package/dist/lib/initiatives.js +13 -2
- package/dist/lib/machines.d.ts +14 -1
- package/dist/lib/machines.js +24 -0
- package/dist/lib/northstars.d.ts +158 -24
- package/dist/lib/northstars.js +538 -26
- package/dist/lib/proposal-decision-command.js +21 -1
- package/dist/lib/proposals.d.ts +26 -1
- package/dist/lib/proposals.js +163 -0
- package/dist/lib/rate-limit-retry.d.ts +5 -0
- package/dist/lib/rate-limit-retry.js +19 -0
- package/dist/lib/revision-actor.d.ts +2 -0
- package/dist/lib/revision-actor.js +16 -0
- package/dist/lib/roles.d.ts +1 -1
- package/dist/lib/space-charter.js +9 -0
- package/dist/lib/task-asset-upload.js +1 -2
- package/dist/lib/triggers.d.ts +33 -1
- package/dist/lib/triggers.js +77 -3
- package/dist/node_modules/@skrr-ai/data-provider/index.js +7537 -7474
- package/oclif.manifest.json +357 -34
- package/package.json +4 -4
|
@@ -82,7 +82,27 @@ class ProposalDecisionCommand extends base_command_1.BaseCommand {
|
|
|
82
82
|
}
|
|
83
83
|
this.handleApiError(error);
|
|
84
84
|
}
|
|
85
|
-
|
|
85
|
+
// A retry whose first response was lost lands on the decision that already took effect; the
|
|
86
|
+
// server marks it, so it is not worded like a first success (OSK-21191).
|
|
87
|
+
if (flags.json) {
|
|
88
|
+
this.log(JSON.stringify(result, null, 2));
|
|
89
|
+
}
|
|
90
|
+
else if (result.replayed) {
|
|
91
|
+
// The receipt repeats the decision that was ASKED for; what the intent's outcome actually is
|
|
92
|
+
// (applied, still waiting to be applied, rejected) is on the proposal, so read it (OSK-21250).
|
|
93
|
+
// Best effort: when that read fails the message names the decision, as before.
|
|
94
|
+
let view;
|
|
95
|
+
try {
|
|
96
|
+
view = await proposals_1.proposalApi.show(ref.kind, ref.id);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
view = undefined;
|
|
100
|
+
}
|
|
101
|
+
this.log((0, proposals_1.describeReplay)(decision, result, view));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
this.log(`Proposal ${decision} decision recorded.`);
|
|
105
|
+
}
|
|
86
106
|
}
|
|
87
107
|
}
|
|
88
108
|
exports.ProposalDecisionCommand = ProposalDecisionCommand;
|
package/dist/lib/proposals.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProposalDecisionParams, ProposalDecisionResponse, ProposalKind, ProposalListParams, ProposalListResponse, ProposalView } from '@skrr-ai/data-provider';
|
|
1
|
+
import type { ProposalDecision, ProposalDecisionParams, ProposalDecisionResponse, ProposalKind, ProposalListParams, ProposalListResponse, ProposalView } from '@skrr-ai/data-provider';
|
|
2
2
|
/** The unified, read-time proposal inbox. Decisions are routed by the server to
|
|
3
3
|
* each family's own writer; the CLI never writes a source row. */
|
|
4
4
|
export declare const proposalApi: {
|
|
@@ -23,3 +23,28 @@ export declare function resolveProposalRef(kindArg: string | undefined, idArg: s
|
|
|
23
23
|
};
|
|
24
24
|
/** A 404 here means no such proposal under THIS kind: the id is looked up under the kind given. */
|
|
25
25
|
export declare function describeProposalNotFound(kind: string, id: string, bin?: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* The intent's title in words: what it would DO, to what. Any other kind of proposal, and an
|
|
28
|
+
* intent the server sent no context for, keeps the view's own title.
|
|
29
|
+
*/
|
|
30
|
+
export declare function proposalTitleFor(view: ProposalView): string;
|
|
31
|
+
/**
|
|
32
|
+
* The inbox files a rejection nobody on the owner's side made under `discarded`. For a Northstar
|
|
33
|
+
* intent the verdict, and who made it, is said in the NOTE column, so a policy refusal is not
|
|
34
|
+
* read as something the person threw away.
|
|
35
|
+
*/
|
|
36
|
+
export declare function northstarIntentNote(view: ProposalView): string;
|
|
37
|
+
/**
|
|
38
|
+
* `proposals show` for a Northstar intent, as lines a person can read. Null for every other kind,
|
|
39
|
+
* which keeps printing the full JSON as before (a script reading it does not change).
|
|
40
|
+
*/
|
|
41
|
+
export declare function renderNorthstarIntentView(view: ProposalView): string[] | null;
|
|
42
|
+
/**
|
|
43
|
+
* What a replayed decision says. The replay receipt names the decision that was asked for ("keep"), but a
|
|
44
|
+
* Northstar intent that was kept has gone on: it may be applied, or approved and not applied yet, and a
|
|
45
|
+
* discard lands as `rejected`. "Already kept" for an intent that was in fact applied understated what
|
|
46
|
+
* had happened (OSK-21250), so the intent's own verdict is named when the proposal could be read.
|
|
47
|
+
*/
|
|
48
|
+
export declare function describeReplay(decision: ProposalDecision, receipt: {
|
|
49
|
+
decidedAt?: string;
|
|
50
|
+
}, view?: ProposalView): string;
|
package/dist/lib/proposals.js
CHANGED
|
@@ -3,8 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.proposalApi = void 0;
|
|
4
4
|
exports.resolveProposalRef = resolveProposalRef;
|
|
5
5
|
exports.describeProposalNotFound = describeProposalNotFound;
|
|
6
|
+
exports.proposalTitleFor = proposalTitleFor;
|
|
7
|
+
exports.northstarIntentNote = northstarIntentNote;
|
|
8
|
+
exports.renderNorthstarIntentView = renderNorthstarIntentView;
|
|
9
|
+
exports.describeReplay = describeReplay;
|
|
6
10
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
7
11
|
const data_provider_2 = require("@skrr-ai/data-provider");
|
|
12
|
+
const northstars_1 = require("./northstars");
|
|
8
13
|
const triggers_1 = require("./triggers");
|
|
9
14
|
const MOUNT = '/api/proposals';
|
|
10
15
|
/** The unified, read-time proposal inbox. Decisions are routed by the server to
|
|
@@ -61,3 +66,161 @@ function describeProposalNotFound(kind, id, bin = 'skrr') {
|
|
|
61
66
|
return (`No ${kind} proposal ${id} is visible to you. A proposal is looked up under its kind, so an id ` +
|
|
62
67
|
`that exists under another kind is not found here; \`${bin} proposals list\` prints each as KIND:ID.`);
|
|
63
68
|
}
|
|
69
|
+
/* --------------------------------------------------------------------------
|
|
70
|
+
* A Northstar intent, in words
|
|
71
|
+
*
|
|
72
|
+
* `proposals list` showed the intent's TITLE as its kind code (`pause_commitment`),
|
|
73
|
+
* `proposals show` printed the raw JSON, `rejected` read as `discarded` (the inbox's
|
|
74
|
+
* own vocabulary, which says a person discarded something), and the sentence the
|
|
75
|
+
* governor wrote lived only inside the JSON. Everything below renders fields the
|
|
76
|
+
* view already carries; no new data is needed.
|
|
77
|
+
* ------------------------------------------------------------------------ */
|
|
78
|
+
/** What each kind of intent does, as a phrase a target can follow. A new kind fails the build. */
|
|
79
|
+
const INTENT_PHRASE = {
|
|
80
|
+
pause_commitment: 'Pause',
|
|
81
|
+
resume_commitment: 'Resume',
|
|
82
|
+
reprioritize_commitment: 'Reprioritize',
|
|
83
|
+
request_commitment_check: 'Check now:',
|
|
84
|
+
set_execution_bounds: 'Change the run bounds of',
|
|
85
|
+
create_commitment_proposal: 'Start a new commitment',
|
|
86
|
+
reassign_commitment_proposal: 'Reassign',
|
|
87
|
+
};
|
|
88
|
+
/** The intent's own verdict, which the inbox flattens into proposed/kept/discarded/expired. */
|
|
89
|
+
const VERDICT_WORDS = {
|
|
90
|
+
advisory: 'advisory: recorded for you to read, nothing to decide',
|
|
91
|
+
awaiting_approval: 'waiting for your decision',
|
|
92
|
+
approved: 'approved, not applied yet',
|
|
93
|
+
applying: 'being applied',
|
|
94
|
+
applied: 'applied',
|
|
95
|
+
rejected: 'rejected',
|
|
96
|
+
invalidated: 'invalidated: it went stale before it was applied',
|
|
97
|
+
};
|
|
98
|
+
function isNorthstarIntentView(view) {
|
|
99
|
+
return view.kind === 'northstar_intent' && Boolean(view.decisionContext?.snapshot);
|
|
100
|
+
}
|
|
101
|
+
function intentTarget(view) {
|
|
102
|
+
const id = view.decisionContext?.snapshot.targetCommitmentId ?? view.commitmentId;
|
|
103
|
+
if (view.commitmentTitle)
|
|
104
|
+
return id ? `${view.commitmentTitle} (${id})` : view.commitmentTitle;
|
|
105
|
+
return id ?? 'a commitment';
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The intent's title in words: what it would DO, to what. Any other kind of proposal, and an
|
|
109
|
+
* intent the server sent no context for, keeps the view's own title.
|
|
110
|
+
*/
|
|
111
|
+
function proposalTitleFor(view) {
|
|
112
|
+
if (!isNorthstarIntentView(view))
|
|
113
|
+
return view.title;
|
|
114
|
+
const snapshot = view.decisionContext.snapshot;
|
|
115
|
+
const phrase = INTENT_PHRASE[snapshot.kind] ?? snapshot.kind;
|
|
116
|
+
if (snapshot.kind === 'create_commitment_proposal') {
|
|
117
|
+
const title = snapshot.payload?.title;
|
|
118
|
+
return typeof title === 'string' && title.trim() ? `${phrase}: ${title.trim()}` : phrase;
|
|
119
|
+
}
|
|
120
|
+
if (snapshot.kind === 'reassign_commitment_proposal') {
|
|
121
|
+
const agent = snapshot.payload?.agentId;
|
|
122
|
+
const to = typeof agent === 'string' && agent ? ` to agent ${agent}` : '';
|
|
123
|
+
return `${phrase} ${intentTarget(view)}${to}`;
|
|
124
|
+
}
|
|
125
|
+
return `${phrase} ${intentTarget(view)}`;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The inbox files a rejection nobody on the owner's side made under `discarded`. For a Northstar
|
|
129
|
+
* intent the verdict, and who made it, is said in the NOTE column, so a policy refusal is not
|
|
130
|
+
* read as something the person threw away.
|
|
131
|
+
*/
|
|
132
|
+
function northstarIntentNote(view) {
|
|
133
|
+
if (!isNorthstarIntentView(view))
|
|
134
|
+
return '';
|
|
135
|
+
const verdict = view.sourceStatus;
|
|
136
|
+
const reason = view.decision?.reason ? `: ${view.decision.reason}` : '';
|
|
137
|
+
if (verdict === 'rejected') {
|
|
138
|
+
return view.decision?.by ? `rejected by you${reason}` : `rejected by policy${reason}`;
|
|
139
|
+
}
|
|
140
|
+
if (verdict === 'invalidated') {
|
|
141
|
+
return `invalidated: it went stale, nothing was discarded${reason}`;
|
|
142
|
+
}
|
|
143
|
+
if (verdict === 'advisory')
|
|
144
|
+
return 'advisory: nothing to decide';
|
|
145
|
+
return '';
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* `proposals show` for a Northstar intent, as lines a person can read. Null for every other kind,
|
|
149
|
+
* which keeps printing the full JSON as before (a script reading it does not change).
|
|
150
|
+
*/
|
|
151
|
+
function renderNorthstarIntentView(view) {
|
|
152
|
+
if (!isNorthstarIntentView(view))
|
|
153
|
+
return null;
|
|
154
|
+
const context = view.decisionContext;
|
|
155
|
+
const verdict = view.sourceStatus;
|
|
156
|
+
const risk = context.riskLevel ?? context.snapshot.riskLevel ?? 'unknown';
|
|
157
|
+
const hasTarget = Boolean(context.snapshot.targetCommitmentId || view.commitmentId);
|
|
158
|
+
const lines = [proposalTitleFor(view), ''];
|
|
159
|
+
lines.push(`Proposal ${view.id}`);
|
|
160
|
+
lines.push(`Kind ${context.snapshot.kind} · risk ${risk}`);
|
|
161
|
+
lines.push(`Target ${hasTarget ? intentTarget(view) : 'none (a new commitment)'}`);
|
|
162
|
+
if (context.reason)
|
|
163
|
+
lines.push(`Why ${context.reason}`);
|
|
164
|
+
lines.push(`Status ${VERDICT_WORDS[verdict] ?? verdict} (the inbox files it under "${view.disposition}")`);
|
|
165
|
+
const decision = view.decision;
|
|
166
|
+
if (verdict === 'awaiting_approval' || verdict === 'advisory') {
|
|
167
|
+
// Nobody has decided: `decision.reason` here is the policy that HELD it (or kept it advisory),
|
|
168
|
+
// and printing it as "Decided … by policy" read as a recorded decision (OSK-21251).
|
|
169
|
+
if (decision?.reason) {
|
|
170
|
+
const words = (0, northstars_1.describeNorthstarReason)(decision.reason);
|
|
171
|
+
lines.push(verdict === 'awaiting_approval' ? `Waiting for you: ${words}` : `Not decided: ${words}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else if (decision && (decision.at || decision.by || decision.reason)) {
|
|
175
|
+
const at = decision.at ? `${(0, northstars_1.shortDate)(decision.at)} UTC` : 'at an unrecorded time';
|
|
176
|
+
const by = decision.by ? ` by user ${decision.by}` : ' by policy';
|
|
177
|
+
const via = decision.via ? ` via ${decision.via}` : '';
|
|
178
|
+
const reason = decision.reason ? `: ${decision.reason}` : '';
|
|
179
|
+
lines.push(`Decided ${at}${by}${via}${reason}`);
|
|
180
|
+
}
|
|
181
|
+
if (view.canDecide) {
|
|
182
|
+
lines.push(`Can decide ${view.availableDecisions.join(', ')}`);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
const why = view.undecidableReason
|
|
186
|
+
? data_provider_2.PROPOSAL_UNDECIDABLE_TEXT[view.undecidableReason]
|
|
187
|
+
: 'not now';
|
|
188
|
+
lines.push(`Can decide no: ${why}`);
|
|
189
|
+
}
|
|
190
|
+
const continuation = view.continuation;
|
|
191
|
+
if (continuation) {
|
|
192
|
+
const bits = [continuation.status, continuation.commitmentId, continuation.reason].filter(Boolean);
|
|
193
|
+
lines.push(`Continued ${bits.join(' · ')}`);
|
|
194
|
+
}
|
|
195
|
+
lines.push(`Revision ${JSON.stringify(view.revision)}`);
|
|
196
|
+
// The hint is for a proposal that can still be decided: beside "Can decide no" it contradicts
|
|
197
|
+
// the line above it (OSK-21255).
|
|
198
|
+
if (view.canDecide) {
|
|
199
|
+
lines.push(' (pass this exact JSON as --expected to keep, discard or snooze)');
|
|
200
|
+
}
|
|
201
|
+
lines.push(`Web ${view.links.web}`);
|
|
202
|
+
return lines;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* What a replayed decision says. The replay receipt names the decision that was asked for ("keep"), but a
|
|
206
|
+
* Northstar intent that was kept has gone on: it may be applied, or approved and not applied yet, and a
|
|
207
|
+
* discard lands as `rejected`. "Already kept" for an intent that was in fact applied understated what
|
|
208
|
+
* had happened (OSK-21250), so the intent's own verdict is named when the proposal could be read.
|
|
209
|
+
*/
|
|
210
|
+
function describeReplay(decision, receipt, view) {
|
|
211
|
+
let outcome = { keep: 'kept', discard: 'discarded', snooze: 'snoozed' }[decision];
|
|
212
|
+
if (view && isNorthstarIntentView(view)) {
|
|
213
|
+
const settled = INTENT_REPLAY_WORDS[view.sourceStatus];
|
|
214
|
+
if (settled)
|
|
215
|
+
outcome = settled;
|
|
216
|
+
}
|
|
217
|
+
const when = receipt.decidedAt ? ` at ${(0, northstars_1.shortDate)(receipt.decidedAt)} UTC` : '';
|
|
218
|
+
return `Already ${outcome}${when} — nothing changed.`;
|
|
219
|
+
}
|
|
220
|
+
/** The verdicts a replayed keep or discard can find an intent in. */
|
|
221
|
+
const INTENT_REPLAY_WORDS = {
|
|
222
|
+
applied: 'applied',
|
|
223
|
+
approved: 'approved (not applied yet)',
|
|
224
|
+
applying: 'approved and being applied',
|
|
225
|
+
rejected: 'rejected',
|
|
226
|
+
};
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
* `X-Idempotency-Key` (every `request.ts` / adapter write does). A 429 means the
|
|
11
11
|
* request was refused before it ran, so the key is belt and braces.
|
|
12
12
|
*
|
|
13
|
+
* Never retried when the body says it cannot help: a 429 that declares
|
|
14
|
+
* `retryable: false` (a follow-up budget refusal, which says the day resets at
|
|
15
|
+
* 00:00 UTC) is a refusal to keep, not a limiter to wait out. Retrying it sent the
|
|
16
|
+
* same refused request three times and told the server about three intents (OSK-21266).
|
|
17
|
+
*
|
|
13
18
|
* Bounded twice: at most `maxRetries` extra attempts and a per-wait ceiling, so
|
|
14
19
|
* a stuck limiter costs a script seconds, not minutes. `SKRR_RATE_LIMIT_RETRIES=0`
|
|
15
20
|
* turns it off.
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
* `X-Idempotency-Key` (every `request.ts` / adapter write does). A 429 means the
|
|
12
12
|
* request was refused before it ran, so the key is belt and braces.
|
|
13
13
|
*
|
|
14
|
+
* Never retried when the body says it cannot help: a 429 that declares
|
|
15
|
+
* `retryable: false` (a follow-up budget refusal, which says the day resets at
|
|
16
|
+
* 00:00 UTC) is a refusal to keep, not a limiter to wait out. Retrying it sent the
|
|
17
|
+
* same refused request three times and told the server about three intents (OSK-21266).
|
|
18
|
+
*
|
|
14
19
|
* Bounded twice: at most `maxRetries` extra attempts and a per-wait ceiling, so
|
|
15
20
|
* a stuck limiter costs a script seconds, not minutes. `SKRR_RATE_LIMIT_RETRIES=0`
|
|
16
21
|
* turns it off.
|
|
@@ -65,6 +70,18 @@ function retryAfterOf(response) {
|
|
|
65
70
|
? parseRetryAfterMs(response.headers.get('retry-after'))
|
|
66
71
|
: undefined;
|
|
67
72
|
}
|
|
73
|
+
/** A 429 whose body declares `retryable: false`. Reads a clone, so the caller still gets the body. */
|
|
74
|
+
async function declaresNotRetryable(response) {
|
|
75
|
+
if (response.status !== 429)
|
|
76
|
+
return false;
|
|
77
|
+
try {
|
|
78
|
+
const body = (await response.clone().json());
|
|
79
|
+
return body?.retryable === false;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
68
85
|
/**
|
|
69
86
|
* Run `attemptFetch` and, on a 429 for a replay-safe request, wait and run it
|
|
70
87
|
* again. Returns the last response, with `retryAfterMs` of the final 429 (so the
|
|
@@ -80,6 +97,8 @@ async function fetchWithRateLimitRetry(attemptFetch, options) {
|
|
|
80
97
|
for (let attempt = 0; attempt < max && response.status === 429; attempt++) {
|
|
81
98
|
if (options.signal?.aborted)
|
|
82
99
|
break;
|
|
100
|
+
if (await declaresNotRetryable(response))
|
|
101
|
+
break;
|
|
83
102
|
await sleep(rateLimitWaitMs(attempt, retryAfterMs, options.random));
|
|
84
103
|
response = await attemptFetch();
|
|
85
104
|
retryAfterMs = retryAfterOf(response);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.revisionActorLabel = revisionActorLabel;
|
|
4
|
+
/** Render the actual actor without presenting a system or Agent as a user. */
|
|
5
|
+
function revisionActorLabel(revision) {
|
|
6
|
+
if (revision.userId != null)
|
|
7
|
+
return String(revision.userId);
|
|
8
|
+
const actor = revision.actor;
|
|
9
|
+
if (actor && typeof actor === 'object' && !Array.isArray(actor)) {
|
|
10
|
+
const { kind, id } = actor;
|
|
11
|
+
if ((kind === 'system' || kind === 'agent') && typeof id === 'string' && id) {
|
|
12
|
+
return `${kind}: ${id}`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return '-';
|
|
16
|
+
}
|
package/dist/lib/roles.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export declare const roleApi: {
|
|
|
53
53
|
config?: Record<string, unknown> | undefined;
|
|
54
54
|
}[];
|
|
55
55
|
sources: {
|
|
56
|
-
source: "space" | "tasks" | "memory" | "daemon" | "email" | "goal" | "files" | "local" | "browser" | "conversations" | "slack" | "github" | "messages" | "calendar" | "
|
|
56
|
+
source: "space" | "tasks" | "memory" | "daemon" | "email" | "goal" | "files" | "local" | "browser" | "conversations" | "slack" | "github" | "messages" | "calendar" | "docs" | "metrics" | "agent_run" | "agent_initiatives" | "documents" | "product_metrics" | "business_metrics" | "state_monitor" | "monitor";
|
|
57
57
|
enabled: boolean;
|
|
58
58
|
config?: Record<string, unknown> | undefined;
|
|
59
59
|
}[];
|
|
@@ -51,6 +51,15 @@ function charterLines(charter, { binName, spaceId, full = false }) {
|
|
|
51
51
|
if (lead) {
|
|
52
52
|
for (const line of lead.split('\n'))
|
|
53
53
|
lines.push(` ${line}`);
|
|
54
|
+
// Agents are handed about the first `leadContextChars` characters of the lead, then a pointer
|
|
55
|
+
// to the rest. The page printed all of it under "read by every agent", so a long lead looked
|
|
56
|
+
// fully delivered (the web card warns; the CLI said nothing). Measured like the server does.
|
|
57
|
+
const cap = charter.leadContextChars;
|
|
58
|
+
if (typeof cap === 'number' && cap > 0 && lead.length > cap) {
|
|
59
|
+
lines.push('');
|
|
60
|
+
lines.push(` Over the cap: this lead is ${lead.length} characters and agents get about the first ${cap}, ` +
|
|
61
|
+
'then a pointer to the rest. Put the essentials first, or move detail below a ## heading.');
|
|
62
|
+
}
|
|
54
63
|
}
|
|
55
64
|
else {
|
|
56
65
|
lines.push(' (empty: the page opens with a ## heading, so agents receive no charter lead. ' +
|
|
@@ -68,7 +68,6 @@ const fs = __importStar(require("node:fs"));
|
|
|
68
68
|
const http = __importStar(require("node:http"));
|
|
69
69
|
const https = __importStar(require("node:https"));
|
|
70
70
|
const path = __importStar(require("node:path"));
|
|
71
|
-
const node_buffer_1 = require("node:buffer");
|
|
72
71
|
const node_crypto_1 = require("node:crypto");
|
|
73
72
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
74
73
|
const base_command_1 = require("../base-command");
|
|
@@ -223,7 +222,7 @@ function taskAssetMarkdownRef(asset) {
|
|
|
223
222
|
*/
|
|
224
223
|
function fileFormData(filePath, filename, mimeType, role, ledger) {
|
|
225
224
|
const fd = new FormData();
|
|
226
|
-
fd.append('file', new
|
|
225
|
+
fd.append('file', new Blob([fs.readFileSync(filePath)], { type: mimeType }), filename);
|
|
227
226
|
if (role)
|
|
228
227
|
fd.append('role', role);
|
|
229
228
|
if (ledger?.identity) {
|
package/dist/lib/triggers.d.ts
CHANGED
|
@@ -158,6 +158,15 @@ export interface TriggerFireRecord {
|
|
|
158
158
|
replayBlockedReason?: string;
|
|
159
159
|
replayRequirements?: string[];
|
|
160
160
|
replayContext?: unknown;
|
|
161
|
+
/** Tool calls refused during a turn that was delivered (OSK-20776). */
|
|
162
|
+
permissionRefusals?: {
|
|
163
|
+
count?: number;
|
|
164
|
+
tools?: string[];
|
|
165
|
+
unattended?: boolean;
|
|
166
|
+
layer?: string;
|
|
167
|
+
};
|
|
168
|
+
/** The refusal record could not be read for this fire, which is not "none were refused". */
|
|
169
|
+
permissionRefusalsUnread?: boolean;
|
|
161
170
|
};
|
|
162
171
|
}
|
|
163
172
|
export interface TriggerListResponse {
|
|
@@ -499,7 +508,7 @@ export declare function formatDuplicateSubscriptionWarning(trigger: unknown): {
|
|
|
499
508
|
* "armed" when it will fire and "inert" until enabled — so a freshly-created
|
|
500
509
|
* trigger did not plainly confirm it is live.
|
|
501
510
|
*/
|
|
502
|
-
export declare function armedStateLabel(enabled: boolean | undefined): string;
|
|
511
|
+
export declare function armedStateLabel(enabled: boolean | undefined, lastResult?: string | null): string;
|
|
503
512
|
/**
|
|
504
513
|
* Human phrasing for an effect trigger's write mode (OSK-4886).
|
|
505
514
|
*
|
|
@@ -516,6 +525,29 @@ export declare function writeModeLabel(writeMode: string): string;
|
|
|
516
525
|
* the configured base URL rather than guessed.
|
|
517
526
|
*/
|
|
518
527
|
export declare function webhookIngestUrl(baseURL: string, triggerId: string): string;
|
|
528
|
+
/**
|
|
529
|
+
* What `triggers show` adds when the last fire DELIVERED but had tool calls
|
|
530
|
+
* refused (OSK-20776). The outcome of such a fire is `succeeded`, so the
|
|
531
|
+
* `Fires:` line reads as success; this says what the string after `last` means
|
|
532
|
+
* and what a person can do about it.
|
|
533
|
+
*
|
|
534
|
+
* It states the mechanism and not this fire's cause, because `lastResult` names
|
|
535
|
+
* the refused kinds only. Whether a fire was unattended, and which layer
|
|
536
|
+
* refused, is on the fire record (`fire-detail`), and this points there.
|
|
537
|
+
*
|
|
538
|
+
* It does not recommend a mode. `bypassPermissions` is the only mode under which
|
|
539
|
+
* an unattended shell call runs, and it runs EVERY call without asking, which is
|
|
540
|
+
* a decision about what this agent may do on its own (a release job included),
|
|
541
|
+
* not a hint to paste.
|
|
542
|
+
*/
|
|
543
|
+
export declare function toolsRefusedHintLines(trigger: TriggerDefinition, kinds: readonly string[], bin?: string): string[];
|
|
544
|
+
/**
|
|
545
|
+
* The refusal facts a fire record holds, as `fire-detail` lines. Nothing when
|
|
546
|
+
* the fire recorded none. A fire whose record could not be read says so: that
|
|
547
|
+
* is not the same as no refusal, and a reader must not be able to mistake it
|
|
548
|
+
* for one.
|
|
549
|
+
*/
|
|
550
|
+
export declare function fireRefusalLines(metadata: unknown): string[];
|
|
519
551
|
export declare function renderTriggerSummary(trigger: TriggerDefinition, log: (line: string) => void, options?: {
|
|
520
552
|
baseURL?: string;
|
|
521
553
|
}): void;
|
package/dist/lib/triggers.js
CHANGED
|
@@ -48,6 +48,8 @@ exports.formatDuplicateSubscriptionWarning = formatDuplicateSubscriptionWarning;
|
|
|
48
48
|
exports.armedStateLabel = armedStateLabel;
|
|
49
49
|
exports.writeModeLabel = writeModeLabel;
|
|
50
50
|
exports.webhookIngestUrl = webhookIngestUrl;
|
|
51
|
+
exports.toolsRefusedHintLines = toolsRefusedHintLines;
|
|
52
|
+
exports.fireRefusalLines = fireRefusalLines;
|
|
51
53
|
exports.renderTriggerSummary = renderTriggerSummary;
|
|
52
54
|
exports.renderLegacyRuns = renderLegacyRuns;
|
|
53
55
|
exports.renderFireAudit = renderFireAudit;
|
|
@@ -1239,8 +1241,16 @@ function formatDuplicateSubscriptionWarning(trigger) {
|
|
|
1239
1241
|
* "armed" when it will fire and "inert" until enabled — so a freshly-created
|
|
1240
1242
|
* trigger did not plainly confirm it is live.
|
|
1241
1243
|
*/
|
|
1242
|
-
function armedStateLabel(enabled) {
|
|
1243
|
-
|
|
1244
|
+
function armedStateLabel(enabled, lastResult) {
|
|
1245
|
+
if (enabled === false)
|
|
1246
|
+
return 'no — inert until enabled';
|
|
1247
|
+
// "Will fire" is the row's intent. A platform-managed row whose owner is fenced (Always-On off) ticks on
|
|
1248
|
+
// schedule and is skipped every time, and the same screen's `Fires:` line says so; the first line a reader
|
|
1249
|
+
// meets must not say the opposite (dogfood df-261031-commitment-authoring, OSK-21103).
|
|
1250
|
+
const skipped = typeof lastResult === 'string' ? /^skipped:(.+)$/.exec(lastResult) : null;
|
|
1251
|
+
return skipped
|
|
1252
|
+
? `yes — but its last tick was skipped (${skipped[1]}), so it did not run`
|
|
1253
|
+
: 'yes — will fire';
|
|
1244
1254
|
}
|
|
1245
1255
|
/**
|
|
1246
1256
|
* Human phrasing for an effect trigger's write mode (OSK-4886).
|
|
@@ -1276,10 +1286,69 @@ const WEBHOOK_INGEST_KINDS = new Set(['on_demand', 'form', 'chat_message']);
|
|
|
1276
1286
|
function webhookIngestUrl(baseURL, triggerId) {
|
|
1277
1287
|
return `${String(baseURL).replace(/\/+$/, '')}/api/triggers/webhook/${encodeURIComponent(triggerId)}`;
|
|
1278
1288
|
}
|
|
1289
|
+
/** An id or token that is short and plain, so it is safe in a command a person will paste. */
|
|
1290
|
+
const PLAIN_TOKEN = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
1291
|
+
/**
|
|
1292
|
+
* What `triggers show` adds when the last fire DELIVERED but had tool calls
|
|
1293
|
+
* refused (OSK-20776). The outcome of such a fire is `succeeded`, so the
|
|
1294
|
+
* `Fires:` line reads as success; this says what the string after `last` means
|
|
1295
|
+
* and what a person can do about it.
|
|
1296
|
+
*
|
|
1297
|
+
* It states the mechanism and not this fire's cause, because `lastResult` names
|
|
1298
|
+
* the refused kinds only. Whether a fire was unattended, and which layer
|
|
1299
|
+
* refused, is on the fire record (`fire-detail`), and this points there.
|
|
1300
|
+
*
|
|
1301
|
+
* It does not recommend a mode. `bypassPermissions` is the only mode under which
|
|
1302
|
+
* an unattended shell call runs, and it runs EVERY call without asking, which is
|
|
1303
|
+
* a decision about what this agent may do on its own (a release job included),
|
|
1304
|
+
* not a hint to paste.
|
|
1305
|
+
*/
|
|
1306
|
+
function toolsRefusedHintLines(trigger, kinds, bin = 'skrr') {
|
|
1307
|
+
const scopedAgent = trigger.scope?.kind === 'agent' ? trigger.scope.refs?.agentId : undefined;
|
|
1308
|
+
const agent = typeof scopedAgent === 'string' && PLAIN_TOKEN.test(scopedAgent) ? scopedAgent : '<agent>';
|
|
1309
|
+
const triggerId = typeof trigger.id === 'string' && PLAIN_TOKEN.test(trigger.id) ? trigger.id : '<trigger-id>';
|
|
1310
|
+
const what = kinds.length > 0 ? kinds.join(', ') : 'some tool calls';
|
|
1311
|
+
return [
|
|
1312
|
+
`Refused: ${what} - the last fire delivered, but these tool calls were refused`,
|
|
1313
|
+
' A scheduled fire runs unattended: a call that would need approval is refused, because no one is there to give it.',
|
|
1314
|
+
" To allow it, change the agent's permission mode (bypassPermissions runs every tool call without asking, so decide that on purpose):",
|
|
1315
|
+
` ${bin} agents update ${agent} --default-permission-mode <mode> (modes: ${bin} agents update --help)`,
|
|
1316
|
+
` What was refused, per fire: ${bin} triggers audit ${triggerId}, then ${bin} triggers fire-detail ${triggerId} <fire-id>`,
|
|
1317
|
+
];
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* The refusal facts a fire record holds, as `fire-detail` lines. Nothing when
|
|
1321
|
+
* the fire recorded none. A fire whose record could not be read says so: that
|
|
1322
|
+
* is not the same as no refusal, and a reader must not be able to mistake it
|
|
1323
|
+
* for one.
|
|
1324
|
+
*/
|
|
1325
|
+
function fireRefusalLines(metadata) {
|
|
1326
|
+
if (!metadata || typeof metadata !== 'object')
|
|
1327
|
+
return [];
|
|
1328
|
+
const { permissionRefusals, permissionRefusalsUnread } = metadata;
|
|
1329
|
+
const lines = [];
|
|
1330
|
+
if (permissionRefusals && typeof permissionRefusals === 'object') {
|
|
1331
|
+
const { count, tools, unattended, layer } = permissionRefusals;
|
|
1332
|
+
const kinds = (Array.isArray(tools) ? tools : []).filter((tool) => typeof tool === 'string' && PLAIN_TOKEN.test(tool));
|
|
1333
|
+
const calls = typeof count === 'number' && Number.isFinite(count) && count > 0
|
|
1334
|
+
? `${count} tool call${count === 1 ? '' : 's'}`
|
|
1335
|
+
: 'tool calls';
|
|
1336
|
+
const parts = [`${calls} refused${kinds.length > 0 ? ` (${kinds.join(', ')})` : ''}`];
|
|
1337
|
+
if (unattended === true)
|
|
1338
|
+
parts.push('unattended, so no one could grant them');
|
|
1339
|
+
if (typeof layer === 'string' && PLAIN_TOKEN.test(layer))
|
|
1340
|
+
parts.push(`refused by ${layer}`);
|
|
1341
|
+
lines.push(`Refused: ${parts.join('; ')}`);
|
|
1342
|
+
}
|
|
1343
|
+
if (permissionRefusalsUnread === true) {
|
|
1344
|
+
lines.push('Refused: unknown - the refusal record could not be read for this fire; that is not the same as none');
|
|
1345
|
+
}
|
|
1346
|
+
return lines;
|
|
1347
|
+
}
|
|
1279
1348
|
function renderTriggerSummary(trigger, log, options = {}) {
|
|
1280
1349
|
log(`ID: ${trigger.id ?? '-'}`);
|
|
1281
1350
|
log(`Kind: ${trigger.kind ?? '-'}`);
|
|
1282
|
-
log(`Armed: ${armedStateLabel(trigger.enabled)}`);
|
|
1351
|
+
log(`Armed: ${armedStateLabel(trigger.enabled, trigger.audit?.lastResult)}`);
|
|
1283
1352
|
log(`Scope: ${scopeLabel(trigger)}`);
|
|
1284
1353
|
/*
|
|
1285
1354
|
* Say when the row is not yours to change.
|
|
@@ -1389,6 +1458,11 @@ function renderTriggerSummary(trigger, log, options = {}) {
|
|
|
1389
1458
|
parts.push(`last ${audit.lastResult}`);
|
|
1390
1459
|
log(`Fires: ${parts.join(', ')}`);
|
|
1391
1460
|
}
|
|
1461
|
+
// `last tools_refused:...` above is a success that held a refusal. Say what that means.
|
|
1462
|
+
const refused = (0, data_provider_1.parseToolsRefusedResult)(audit.lastResult);
|
|
1463
|
+
if (refused)
|
|
1464
|
+
for (const line of toolsRefusedHintLines(trigger, refused))
|
|
1465
|
+
log(line);
|
|
1392
1466
|
// The reason, at the surface a reader reaches for — not only in `--json`.
|
|
1393
1467
|
if (audit.lastError)
|
|
1394
1468
|
log(`Last error: ${audit.lastError}`);
|