@skrr-ai/cli 0.1.14 → 0.1.17
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 +3 -0
- package/dist/base-command.d.ts +14 -0
- package/dist/base-command.js +60 -1
- package/dist/commands/agents/create.d.ts +1 -0
- package/dist/commands/agents/create.js +6 -1
- package/dist/commands/agents/instructions/add.d.ts +21 -0
- package/dist/commands/agents/instructions/add.js +115 -0
- package/dist/commands/agents/instructions/disable.d.ts +13 -0
- package/dist/commands/agents/instructions/disable.js +50 -0
- package/dist/commands/agents/instructions/edit.d.ts +16 -0
- package/dist/commands/agents/instructions/edit.js +79 -0
- package/dist/commands/agents/instructions/enable.d.ts +13 -0
- package/dist/commands/agents/instructions/enable.js +50 -0
- package/dist/commands/agents/instructions/move.d.ts +15 -0
- package/dist/commands/agents/instructions/move.js +61 -0
- package/dist/commands/agents/instructions/preview.d.ts +13 -0
- package/dist/commands/agents/instructions/preview.js +59 -0
- package/dist/commands/agents/instructions/remove.d.ts +13 -0
- package/dist/commands/agents/instructions/remove.js +49 -0
- package/dist/commands/agents/instructions/set.d.ts +17 -0
- package/dist/commands/agents/instructions/set.js +79 -0
- package/dist/commands/agents/instructions/show.d.ts +15 -0
- package/dist/commands/agents/instructions/show.js +76 -0
- package/dist/commands/agents/instructions/upgrade.d.ts +16 -0
- package/dist/commands/agents/instructions/upgrade.js +77 -0
- package/dist/commands/agents/show.js +5 -0
- package/dist/commands/agents/triggers/policy.js +8 -2
- package/dist/commands/agents/update.d.ts +1 -0
- package/dist/commands/agents/update.js +8 -2
- package/dist/commands/commitments/autonomy.js +9 -1
- package/dist/commands/commitments/create.d.ts +8 -5
- package/dist/commands/commitments/create.js +38 -8
- package/dist/commands/commitments/doctor.d.ts +19 -0
- package/dist/commands/commitments/doctor.js +72 -0
- package/dist/commands/commitments/preflight.d.ts +3 -1
- package/dist/commands/commitments/preflight.js +25 -3
- package/dist/commands/commitments/reports.js +15 -2
- package/dist/commands/commitments/update.d.ts +9 -2
- package/dist/commands/commitments/update.js +36 -2
- package/dist/commands/daemon/index.js +2 -0
- package/dist/commands/daemon/recover-offline-queue.d.ts +9 -0
- package/dist/commands/daemon/recover-offline-queue.js +24 -0
- package/dist/commands/daemon/start.js +29 -1
- package/dist/commands/instruction-blocks/create.d.ts +19 -0
- package/dist/commands/instruction-blocks/create.js +94 -0
- package/dist/commands/instruction-blocks/delete.d.ts +13 -0
- package/dist/commands/instruction-blocks/delete.js +35 -0
- package/dist/commands/instruction-blocks/list.d.ts +13 -0
- package/dist/commands/instruction-blocks/list.js +71 -0
- package/dist/commands/instruction-blocks/show.d.ts +12 -0
- package/dist/commands/instruction-blocks/show.js +35 -0
- package/dist/commands/instruction-blocks/update.d.ts +23 -0
- package/dist/commands/instruction-blocks/update.js +94 -0
- package/dist/commands/spaces/auto-executor/preview.d.ts +1 -0
- package/dist/commands/spaces/auto-executor/preview.js +62 -3
- package/dist/commands/spaces/auto-executor/update.d.ts +4 -0
- package/dist/commands/spaces/auto-executor/update.js +78 -7
- package/dist/commands/spaces/capacity.js +14 -2
- package/dist/commands/spaces/update.d.ts +4 -0
- package/dist/commands/spaces/update.js +49 -2
- package/dist/commands/tasks/complete.d.ts +12 -5
- package/dist/commands/tasks/complete.js +107 -28
- package/dist/commands/tasks/continue-ready.js +9 -1
- package/dist/commands/tasks/create.d.ts +1 -0
- package/dist/commands/tasks/create.js +27 -2
- package/dist/commands/tasks/deliverable/add.d.ts +23 -0
- package/dist/commands/tasks/deliverable/add.js +70 -0
- package/dist/commands/tasks/deliverable/confirm.d.ts +35 -0
- package/dist/commands/tasks/deliverable/confirm.js +77 -0
- package/dist/commands/tasks/events/append.d.ts +1 -1
- package/dist/commands/tasks/events/append.js +4 -4
- package/dist/commands/tasks/expectations.d.ts +12 -0
- package/dist/commands/tasks/expectations.js +111 -0
- package/dist/commands/tasks/judge.d.ts +29 -0
- package/dist/commands/tasks/judge.js +68 -0
- package/dist/commands/tasks/report.d.ts +1 -1
- package/dist/commands/tasks/report.js +27 -3
- package/dist/commands/tasks/trust.d.ts +35 -0
- package/dist/commands/tasks/trust.js +75 -0
- package/dist/commands/tasks/update.d.ts +1 -0
- package/dist/commands/tasks/update.js +34 -4
- package/dist/commands/tasks/waive.d.ts +25 -0
- package/dist/commands/tasks/waive.js +58 -0
- package/dist/lib/agent-config.js +5 -0
- package/dist/lib/agent-instructions.d.ts +15 -0
- package/dist/lib/agent-instructions.js +55 -0
- package/dist/lib/command-hygiene.d.ts +6 -1
- package/dist/lib/command-hygiene.js +49 -29
- package/dist/lib/commitments.d.ts +28 -3
- package/dist/lib/commitments.js +20 -11
- package/dist/lib/exec-runtime-binary.d.ts +13 -0
- package/dist/lib/exec-runtime-binary.js +45 -0
- package/dist/lib/git-task.d.ts +37 -0
- package/dist/lib/git-task.js +151 -0
- package/dist/lib/instruction-blocks.d.ts +29 -0
- package/dist/lib/instruction-blocks.js +159 -0
- package/dist/lib/runtime-start-verification.d.ts +49 -0
- package/dist/lib/runtime-start-verification.js +108 -0
- package/dist/lib/task-execution.d.ts +18 -2
- package/dist/lib/task-execution.js +19 -14
- package/dist/lib/task-transcript.js +1 -1
- package/dist/lib/tasks.d.ts +50 -0
- package/dist/lib/tasks.js +93 -2
- package/dist/node_modules/@skrr-ai/data-provider/index.js +2393 -2197
- package/dist/node_modules/@skrr-ai/inference-broker/package.json +1 -1
- package/oclif.manifest.json +17725 -15563
- package/package.json +4 -1
package/dist/lib/git-task.js
CHANGED
|
@@ -34,11 +34,162 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.GitTaskError = void 0;
|
|
37
|
+
exports.commitArtifacts = commitArtifacts;
|
|
38
|
+
exports.inspectTaskDeliveryArtifacts = inspectTaskDeliveryArtifacts;
|
|
37
39
|
exports.currentGitBranch = currentGitBranch;
|
|
38
40
|
exports.currentGitTaskBranch = currentGitTaskBranch;
|
|
39
41
|
exports.checkoutTaskBranch = checkoutTaskBranch;
|
|
40
42
|
const node_child_process_1 = require("node:child_process");
|
|
41
43
|
const taskReferences = __importStar(require("@skrr-ai/data-provider"));
|
|
44
|
+
const COMMIT_ARTIFACT_RE = /(?:^|[\s,;:(/])(?:commit\s+)?([0-9a-f]{7,40})(?=$|[\s,;:)?#])/gi;
|
|
45
|
+
const GITHUB_PR_ARTIFACT_RE = /https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/pull\/\d+/i;
|
|
46
|
+
function runCommand(command, args, cwd) {
|
|
47
|
+
const result = (0, node_child_process_1.spawnSync)(command, args, {
|
|
48
|
+
cwd,
|
|
49
|
+
encoding: 'utf8',
|
|
50
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
51
|
+
timeout: 15_000,
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
status: result.status,
|
|
55
|
+
stdout: typeof result.stdout === 'string' ? result.stdout.trim() : '',
|
|
56
|
+
stderr: typeof result.stderr === 'string' ? result.stderr.trim() : '',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function commitArtifacts(deliverables) {
|
|
60
|
+
const commits = [];
|
|
61
|
+
for (const match of deliverables.matchAll(COMMIT_ARTIFACT_RE)) {
|
|
62
|
+
const sha = match[1].toLowerCase();
|
|
63
|
+
if (!commits.includes(sha))
|
|
64
|
+
commits.push(sha);
|
|
65
|
+
}
|
|
66
|
+
return commits;
|
|
67
|
+
}
|
|
68
|
+
function safeTaskBranchRef(taskRef, sha) {
|
|
69
|
+
const match = taskRef.match(/[A-Za-z][A-Za-z0-9]{1,31}-\d{1,12}/);
|
|
70
|
+
const label = match?.[0].toUpperCase() || 'recovery';
|
|
71
|
+
return `oversky/task/${label}/recovery-${sha.slice(0, 12)}`;
|
|
72
|
+
}
|
|
73
|
+
function resolveRemoteDefault(cwd, remote, runner) {
|
|
74
|
+
const live = runner('git', ['ls-remote', '--symref', remote, 'HEAD'], cwd);
|
|
75
|
+
if (live.status === 0) {
|
|
76
|
+
const match = live.stdout.match(/^ref:\s+refs\/heads\/(\S+)\s+HEAD$/m);
|
|
77
|
+
if (match)
|
|
78
|
+
return match[1];
|
|
79
|
+
}
|
|
80
|
+
const symbolic = runner('git', ['symbolic-ref', '--quiet', '--short', `refs/remotes/${remote}/HEAD`], cwd);
|
|
81
|
+
if (symbolic.status === 0 && symbolic.stdout.startsWith(`${remote}/`)) {
|
|
82
|
+
return symbolic.stdout.slice(remote.length + 1);
|
|
83
|
+
}
|
|
84
|
+
for (const fallback of ['main', 'master']) {
|
|
85
|
+
if (runner('git', ['show-ref', '--verify', '--quiet', `refs/remotes/${remote}/${fallback}`], cwd)
|
|
86
|
+
.status === 0) {
|
|
87
|
+
return fallback;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Build a receipt for commit/PR deliverables before `tasks complete` writes
|
|
94
|
+
* anything. Commit reachability is checked against a freshly fetched remote
|
|
95
|
+
* default branch. A locally dangling commit is first anchored under refs/heads
|
|
96
|
+
* so even a held completion cannot leave it exposed to `git gc`.
|
|
97
|
+
*/
|
|
98
|
+
function inspectTaskDeliveryArtifacts(opts) {
|
|
99
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
100
|
+
const runner = opts.runner ?? runCommand;
|
|
101
|
+
const commits = commitArtifacts(opts.deliverables);
|
|
102
|
+
const verifiedCommits = [];
|
|
103
|
+
let containingBranch;
|
|
104
|
+
for (const claimed of commits) {
|
|
105
|
+
const resolved = runner('git', ['rev-parse', '--verify', `${claimed}^{commit}`], cwd);
|
|
106
|
+
if (resolved.status !== 0 || !/^[0-9a-f]{40}$/i.test(resolved.stdout))
|
|
107
|
+
continue;
|
|
108
|
+
const sha = resolved.stdout.toLowerCase();
|
|
109
|
+
verifiedCommits.push(sha);
|
|
110
|
+
const branches = runner('git', ['for-each-ref', '--format=%(refname:short)', '--contains', sha, 'refs/heads'], cwd);
|
|
111
|
+
const firstBranch = branches.stdout.split('\n').find(Boolean);
|
|
112
|
+
if (firstBranch) {
|
|
113
|
+
containingBranch ||= firstBranch;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const recoveryBranch = safeTaskBranchRef(opts.taskRef, sha);
|
|
117
|
+
const existing = runner('git', ['show-ref', '--verify', '--quiet', `refs/heads/${recoveryBranch}`], cwd);
|
|
118
|
+
if (existing.status !== 0) {
|
|
119
|
+
const anchored = runner('git', ['update-ref', `refs/heads/${recoveryBranch}`, sha], cwd);
|
|
120
|
+
if (anchored.status !== 0) {
|
|
121
|
+
throw new Error(`Commit ${sha} is not reachable from a named local branch, and ${recoveryBranch} ` +
|
|
122
|
+
'could not be created. Completion was refused so the commit is not reported as safe.');
|
|
123
|
+
}
|
|
124
|
+
containingBranch ||= recoveryBranch;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
containingBranch ||= recoveryBranch;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const prUrl = opts.deliverables.match(GITHUB_PR_ARTIFACT_RE)?.[0];
|
|
131
|
+
if (prUrl) {
|
|
132
|
+
const viewed = runner('gh', [
|
|
133
|
+
'pr',
|
|
134
|
+
'view',
|
|
135
|
+
prUrl,
|
|
136
|
+
'--json',
|
|
137
|
+
'mergedAt,headRefName,headRefOid,baseRefName,mergeCommit,url',
|
|
138
|
+
], cwd);
|
|
139
|
+
if (viewed.status === 0) {
|
|
140
|
+
try {
|
|
141
|
+
const pr = JSON.parse(viewed.stdout);
|
|
142
|
+
const headSha = typeof pr.headRefOid === 'string' ? pr.headRefOid : undefined;
|
|
143
|
+
const mergeCommit = pr.mergeCommit && typeof pr.mergeCommit === 'object'
|
|
144
|
+
? pr.mergeCommit.oid
|
|
145
|
+
: undefined;
|
|
146
|
+
return {
|
|
147
|
+
kind: 'pull_request',
|
|
148
|
+
state: pr.mergedAt ? 'integrated' : 'pr_open',
|
|
149
|
+
prUrl,
|
|
150
|
+
...(typeof pr.headRefName === 'string' ? { branch: pr.headRefName } : {}),
|
|
151
|
+
...(headSha && /^[0-9a-f]{7,40}$/i.test(headSha) ? { headSha } : {}),
|
|
152
|
+
...(typeof pr.baseRefName === 'string' ? { integrationBranch: pr.baseRefName } : {}),
|
|
153
|
+
...(typeof mergeCommit === 'string' && /^[0-9a-f]{7,40}$/i.test(mergeCommit)
|
|
154
|
+
? { mergeCommit }
|
|
155
|
+
: {}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Invalid/unavailable gh evidence is not proof of integration.
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return { kind: 'pull_request', state: 'pr_open', prUrl };
|
|
163
|
+
}
|
|
164
|
+
if (!commits.length || verifiedCommits.length !== commits.length)
|
|
165
|
+
return null;
|
|
166
|
+
const remotes = runner('git', ['remote'], cwd).stdout.split('\n').filter(Boolean);
|
|
167
|
+
const remote = remotes.includes('origin') ? 'origin' : remotes[0];
|
|
168
|
+
const integrationBranch = remote ? resolveRemoteDefault(cwd, remote, runner) : null;
|
|
169
|
+
let integrated = false;
|
|
170
|
+
if (remote && integrationBranch) {
|
|
171
|
+
const fetched = runner('git', [
|
|
172
|
+
'fetch',
|
|
173
|
+
'--quiet',
|
|
174
|
+
'--no-tags',
|
|
175
|
+
remote,
|
|
176
|
+
`+refs/heads/${integrationBranch}:refs/remotes/${remote}/${integrationBranch}`,
|
|
177
|
+
], cwd);
|
|
178
|
+
integrated =
|
|
179
|
+
fetched.status === 0 &&
|
|
180
|
+
verifiedCommits.every((sha) => runner('git', ['merge-base', '--is-ancestor', sha, `refs/remotes/${remote}/${integrationBranch}`], cwd).status === 0);
|
|
181
|
+
}
|
|
182
|
+
const headSha = verifiedCommits[verifiedCommits.length - 1];
|
|
183
|
+
return {
|
|
184
|
+
kind: 'commit',
|
|
185
|
+
state: integrated ? 'integrated' : 'branch_ready',
|
|
186
|
+
commitShas: verifiedCommits,
|
|
187
|
+
headSha,
|
|
188
|
+
...(containingBranch ? { branch: containingBranch } : {}),
|
|
189
|
+
...(integrationBranch ? { integrationBranch } : {}),
|
|
190
|
+
...(integrated ? { mergeCommit: headSha } : {}),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
42
193
|
class GitTaskError extends Error {
|
|
43
194
|
code;
|
|
44
195
|
constructor(message, code) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AgentPromptBlockEntry, AgentPromptComposition, InstructionBlock, InstructionBlockCatalogResponse } from '@skrr-ai/data-provider';
|
|
2
|
+
/**
|
|
3
|
+
* Helpers shared by the reusable Block Library commands and the per-Agent
|
|
4
|
+
* composition commands. Keep this deliberately about data shape, not HTTP:
|
|
5
|
+
* the server remains the authority that resolves versions, bindings and
|
|
6
|
+
* rendered text before an Agent is saved.
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseJsonFlag<T>(value: string | undefined, flag: string): T | undefined;
|
|
9
|
+
export declare function parseStringArrayFlag(value: string | undefined, flag: string): string[] | undefined;
|
|
10
|
+
/** Parse repeated `--binding key=value` flags without ever guessing a key. */
|
|
11
|
+
export declare function parseBindings(values: string[] | undefined): Record<string, string> | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Read the structured source when it exists; otherwise preserve the legacy
|
|
14
|
+
* prompt byte-for-byte as one inline entry before changing anything. This is
|
|
15
|
+
* the same compatibility move as the server-side resolver, so `add` never
|
|
16
|
+
* drops an Agent's existing instructions merely because it predates Blocks.
|
|
17
|
+
*/
|
|
18
|
+
export declare function compositionForAgent(agent: {
|
|
19
|
+
promptComposition?: unknown;
|
|
20
|
+
instructions?: unknown;
|
|
21
|
+
}): AgentPromptComposition;
|
|
22
|
+
export declare function nextInstanceId(kind?: 'inline' | 'library'): string;
|
|
23
|
+
export declare function entryByInstance(composition: AgentPromptComposition, instanceId: string): AgentPromptBlockEntry;
|
|
24
|
+
export declare function replaceEntry(composition: AgentPromptComposition, instanceId: string, replacement: AgentPromptBlockEntry): AgentPromptComposition;
|
|
25
|
+
export declare function removeEntry(composition: AgentPromptComposition, instanceId: string): AgentPromptComposition;
|
|
26
|
+
/** Move one instance immediately before or after a different existing instance. */
|
|
27
|
+
export declare function moveEntry(composition: AgentPromptComposition, instanceId: string, anchorId: string, placement: 'before' | 'after'): AgentPromptComposition;
|
|
28
|
+
export declare function findBlock(catalog: InstructionBlockCatalogResponse, reference: string): InstructionBlock | undefined;
|
|
29
|
+
export declare function renderInstructionBlockDetail(block: InstructionBlock): string[];
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseJsonFlag = parseJsonFlag;
|
|
4
|
+
exports.parseStringArrayFlag = parseStringArrayFlag;
|
|
5
|
+
exports.parseBindings = parseBindings;
|
|
6
|
+
exports.compositionForAgent = compositionForAgent;
|
|
7
|
+
exports.nextInstanceId = nextInstanceId;
|
|
8
|
+
exports.entryByInstance = entryByInstance;
|
|
9
|
+
exports.replaceEntry = replaceEntry;
|
|
10
|
+
exports.removeEntry = removeEntry;
|
|
11
|
+
exports.moveEntry = moveEntry;
|
|
12
|
+
exports.findBlock = findBlock;
|
|
13
|
+
exports.renderInstructionBlockDetail = renderInstructionBlockDetail;
|
|
14
|
+
const node_crypto_1 = require("node:crypto");
|
|
15
|
+
/**
|
|
16
|
+
* Helpers shared by the reusable Block Library commands and the per-Agent
|
|
17
|
+
* composition commands. Keep this deliberately about data shape, not HTTP:
|
|
18
|
+
* the server remains the authority that resolves versions, bindings and
|
|
19
|
+
* rendered text before an Agent is saved.
|
|
20
|
+
*/
|
|
21
|
+
function parseJsonFlag(value, flag) {
|
|
22
|
+
if (value === undefined)
|
|
23
|
+
return undefined;
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(value);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
throw new Error(`${flag} must be valid JSON`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function parseStringArrayFlag(value, flag) {
|
|
32
|
+
const parsed = parseJsonFlag(value, flag);
|
|
33
|
+
if (parsed === undefined)
|
|
34
|
+
return undefined;
|
|
35
|
+
if (!Array.isArray(parsed) || parsed.some((entry) => typeof entry !== 'string')) {
|
|
36
|
+
throw new Error(`${flag} must be a JSON array of strings`);
|
|
37
|
+
}
|
|
38
|
+
return parsed;
|
|
39
|
+
}
|
|
40
|
+
/** Parse repeated `--binding key=value` flags without ever guessing a key. */
|
|
41
|
+
function parseBindings(values) {
|
|
42
|
+
if (!values?.length)
|
|
43
|
+
return undefined;
|
|
44
|
+
const bindings = {};
|
|
45
|
+
for (const value of values) {
|
|
46
|
+
const separator = value.indexOf('=');
|
|
47
|
+
if (separator < 1) {
|
|
48
|
+
throw new Error(`Invalid binding "${value}". Use --binding key=value.`);
|
|
49
|
+
}
|
|
50
|
+
const key = value.slice(0, separator).trim();
|
|
51
|
+
if (!/^[a-z][a-z0-9_]{0,63}$/.test(key)) {
|
|
52
|
+
throw new Error(`Invalid binding key "${key}".`);
|
|
53
|
+
}
|
|
54
|
+
if (Object.prototype.hasOwnProperty.call(bindings, key)) {
|
|
55
|
+
throw new Error(`Binding "${key}" was supplied more than once.`);
|
|
56
|
+
}
|
|
57
|
+
bindings[key] = value.slice(separator + 1).trim();
|
|
58
|
+
}
|
|
59
|
+
return bindings;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Read the structured source when it exists; otherwise preserve the legacy
|
|
63
|
+
* prompt byte-for-byte as one inline entry before changing anything. This is
|
|
64
|
+
* the same compatibility move as the server-side resolver, so `add` never
|
|
65
|
+
* drops an Agent's existing instructions merely because it predates Blocks.
|
|
66
|
+
*/
|
|
67
|
+
function compositionForAgent(agent) {
|
|
68
|
+
const composition = agent.promptComposition;
|
|
69
|
+
if (composition &&
|
|
70
|
+
typeof composition === 'object' &&
|
|
71
|
+
!Array.isArray(composition) &&
|
|
72
|
+
composition.schemaVersion === 1 &&
|
|
73
|
+
Array.isArray(composition.entries)) {
|
|
74
|
+
return composition;
|
|
75
|
+
}
|
|
76
|
+
const instructions = agent.instructions;
|
|
77
|
+
if (typeof instructions === 'string' && instructions.length) {
|
|
78
|
+
return {
|
|
79
|
+
schemaVersion: 1,
|
|
80
|
+
entries: [
|
|
81
|
+
{
|
|
82
|
+
instanceId: nextInstanceId('inline'),
|
|
83
|
+
enabled: true,
|
|
84
|
+
source: { kind: 'inline', title: 'Core instructions', body: instructions },
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return { schemaVersion: 1, entries: [] };
|
|
90
|
+
}
|
|
91
|
+
function nextInstanceId(kind = 'inline') {
|
|
92
|
+
return `${kind}_${(0, node_crypto_1.randomUUID)()}`;
|
|
93
|
+
}
|
|
94
|
+
function entryByInstance(composition, instanceId) {
|
|
95
|
+
const entry = composition.entries.find((candidate) => candidate.instanceId === instanceId);
|
|
96
|
+
if (!entry) {
|
|
97
|
+
throw new Error(`No instruction block instance "${instanceId}". Run \`skrr agents instructions show\` to list this Agent's instances.`);
|
|
98
|
+
}
|
|
99
|
+
return entry;
|
|
100
|
+
}
|
|
101
|
+
function replaceEntry(composition, instanceId, replacement) {
|
|
102
|
+
entryByInstance(composition, instanceId);
|
|
103
|
+
return {
|
|
104
|
+
schemaVersion: 1,
|
|
105
|
+
entries: composition.entries.map((entry) => entry.instanceId === instanceId ? replacement : entry),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function removeEntry(composition, instanceId) {
|
|
109
|
+
entryByInstance(composition, instanceId);
|
|
110
|
+
return {
|
|
111
|
+
schemaVersion: 1,
|
|
112
|
+
entries: composition.entries.filter((entry) => entry.instanceId !== instanceId),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** Move one instance immediately before or after a different existing instance. */
|
|
116
|
+
function moveEntry(composition, instanceId, anchorId, placement) {
|
|
117
|
+
if (instanceId === anchorId)
|
|
118
|
+
throw new Error('A block cannot be moved relative to itself.');
|
|
119
|
+
const entry = entryByInstance(composition, instanceId);
|
|
120
|
+
entryByInstance(composition, anchorId);
|
|
121
|
+
const remaining = composition.entries.filter((candidate) => candidate.instanceId !== instanceId);
|
|
122
|
+
const anchor = remaining.findIndex((candidate) => candidate.instanceId === anchorId);
|
|
123
|
+
const insertAt = placement === 'before' ? anchor : anchor + 1;
|
|
124
|
+
return {
|
|
125
|
+
schemaVersion: 1,
|
|
126
|
+
entries: [...remaining.slice(0, insertAt), entry, ...remaining.slice(insertAt)],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function findBlock(catalog, reference) {
|
|
130
|
+
return [...catalog.official, ...catalog.library].find((block) => block.id === reference);
|
|
131
|
+
}
|
|
132
|
+
function renderInstructionBlockDetail(block) {
|
|
133
|
+
const current = block.versions.find((version) => version.version === block.currentVersion);
|
|
134
|
+
const lines = [
|
|
135
|
+
`${block.name} (${block.id})`,
|
|
136
|
+
`Source: ${block.source}`,
|
|
137
|
+
`Visibility: ${block.visibility}`,
|
|
138
|
+
`Status: ${block.status}`,
|
|
139
|
+
`Version: v${block.currentVersion}`,
|
|
140
|
+
];
|
|
141
|
+
if (block.summary)
|
|
142
|
+
lines.push(`Summary: ${block.summary}`);
|
|
143
|
+
if (block.tags.length)
|
|
144
|
+
lines.push(`Tags: ${block.tags.join(', ')}`);
|
|
145
|
+
if (current) {
|
|
146
|
+
if (current.requirements.length) {
|
|
147
|
+
lines.push(`Needs: ${current.requirements
|
|
148
|
+
.map((requirement) => `${requirement.kind}:${requirement.key}${requirement.access ? ` (${requirement.access})` : ''}`)
|
|
149
|
+
.join(', ')} — declarative only; this does not grant access`);
|
|
150
|
+
}
|
|
151
|
+
if (current.parameterSchema.length) {
|
|
152
|
+
lines.push(`Parameters: ${current.parameterSchema
|
|
153
|
+
.map((parameter) => `${parameter.key}${parameter.required ? ' (required)' : ''}`)
|
|
154
|
+
.join(', ')}`);
|
|
155
|
+
}
|
|
156
|
+
lines.push('', 'Instructions:', ...current.bodyTemplate.split('\n').map((line) => ` ${line}`));
|
|
157
|
+
}
|
|
158
|
+
return lines;
|
|
159
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Matches the runtime's EX_CONFIG result for a permanent local configuration failure. */
|
|
2
|
+
export declare const DAEMON_START_REAUTH_EXIT = 78;
|
|
3
|
+
/** Matches the runtime's EX_TEMPFAIL result when a service cannot remain running. */
|
|
4
|
+
export declare const DAEMON_START_UNVERIFIED_EXIT = 75;
|
|
5
|
+
export type RuntimeStartStatus = {
|
|
6
|
+
profile: string;
|
|
7
|
+
serverUrl: string | null;
|
|
8
|
+
cwd: string | null;
|
|
9
|
+
reauth: {
|
|
10
|
+
needed: boolean;
|
|
11
|
+
reason: string | null;
|
|
12
|
+
};
|
|
13
|
+
running: boolean;
|
|
14
|
+
runtimeStatus: string | null;
|
|
15
|
+
};
|
|
16
|
+
export type RuntimeStartVerification = {
|
|
17
|
+
status: 'running';
|
|
18
|
+
} | {
|
|
19
|
+
status: 'reauth_required';
|
|
20
|
+
profile: string;
|
|
21
|
+
reason: string | null;
|
|
22
|
+
recoveryCommand: string;
|
|
23
|
+
} | {
|
|
24
|
+
status: 'not_running';
|
|
25
|
+
profile: string;
|
|
26
|
+
runtimeStatus: string | null;
|
|
27
|
+
} | {
|
|
28
|
+
status: 'unreadable_status';
|
|
29
|
+
profile: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The status probe must observe the same profile as the start request. A
|
|
33
|
+
* runtime's `--profile` flag is global, so it is deliberately returned after
|
|
34
|
+
* `status` here and normalized by execRuntimeBinary before spawning.
|
|
35
|
+
*/
|
|
36
|
+
export declare function statusArgsForStart(argv: readonly string[]): string[];
|
|
37
|
+
export declare function parseRuntimeStartStatus(value: unknown, fallbackProfile: string): RuntimeStartStatus | null;
|
|
38
|
+
/**
|
|
39
|
+
* `skrr daemon login` is the public wrapper for daemon-scope login. Explicit
|
|
40
|
+
* server, profile, and cwd make this remedy target the stopped daemon rather
|
|
41
|
+
* than whichever CLI profile or working directory happens to be current.
|
|
42
|
+
*/
|
|
43
|
+
export declare function daemonLoginRecoveryCommand(status: RuntimeStartStatus): string;
|
|
44
|
+
/**
|
|
45
|
+
* A runtime binary may predate a CLI update. Treat its successful process exit
|
|
46
|
+
* as an unverified supervisor request until `status --json` proves it either
|
|
47
|
+
* stayed alive or named the permanent re-auth latch it wrote.
|
|
48
|
+
*/
|
|
49
|
+
export declare function verifyRuntimeStart(statusOutput: string, statusExitCode: number, argv: readonly string[]): RuntimeStartVerification;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DAEMON_START_UNVERIFIED_EXIT = exports.DAEMON_START_REAUTH_EXIT = void 0;
|
|
4
|
+
exports.statusArgsForStart = statusArgsForStart;
|
|
5
|
+
exports.parseRuntimeStartStatus = parseRuntimeStartStatus;
|
|
6
|
+
exports.daemonLoginRecoveryCommand = daemonLoginRecoveryCommand;
|
|
7
|
+
exports.verifyRuntimeStart = verifyRuntimeStart;
|
|
8
|
+
const keychain_1 = require("./keychain");
|
|
9
|
+
/** Matches the runtime's EX_CONFIG result for a permanent local configuration failure. */
|
|
10
|
+
exports.DAEMON_START_REAUTH_EXIT = 78;
|
|
11
|
+
/** Matches the runtime's EX_TEMPFAIL result when a service cannot remain running. */
|
|
12
|
+
exports.DAEMON_START_UNVERIFIED_EXIT = 75;
|
|
13
|
+
function asRecord(value) {
|
|
14
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
15
|
+
? value
|
|
16
|
+
: null;
|
|
17
|
+
}
|
|
18
|
+
function asString(value) {
|
|
19
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
20
|
+
}
|
|
21
|
+
function profileFromArgv(argv) {
|
|
22
|
+
return ((0, keychain_1.resolveProfileFromArgv)(argv) ??
|
|
23
|
+
process.env.OVERSKY_PROFILE ??
|
|
24
|
+
(0, keychain_1.getActiveProfile)() ??
|
|
25
|
+
keychain_1.DEFAULT_PROFILE);
|
|
26
|
+
}
|
|
27
|
+
function quoteShellArg(value) {
|
|
28
|
+
return /^[A-Za-z0-9_./:@+=-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The status probe must observe the same profile as the start request. A
|
|
32
|
+
* runtime's `--profile` flag is global, so it is deliberately returned after
|
|
33
|
+
* `status` here and normalized by execRuntimeBinary before spawning.
|
|
34
|
+
*/
|
|
35
|
+
function statusArgsForStart(argv) {
|
|
36
|
+
const requested = (0, keychain_1.resolveProfileFromArgv)(argv);
|
|
37
|
+
return requested ? ['status', '--json', '--profile', requested] : ['status', '--json'];
|
|
38
|
+
}
|
|
39
|
+
function parseRuntimeStartStatus(value, fallbackProfile) {
|
|
40
|
+
const root = asRecord(value);
|
|
41
|
+
if (!root)
|
|
42
|
+
return null;
|
|
43
|
+
const auth = asRecord(root.auth);
|
|
44
|
+
const reauth = asRecord(auth?.reauth);
|
|
45
|
+
const profile = asRecord(root.profile);
|
|
46
|
+
const liveDaemon = asRecord(root.liveDaemon);
|
|
47
|
+
const service = asRecord(root.service);
|
|
48
|
+
const runtime = asRecord(service?.runtime);
|
|
49
|
+
return {
|
|
50
|
+
profile: asString(profile?.name) ?? fallbackProfile,
|
|
51
|
+
serverUrl: asString(root.serverUrl),
|
|
52
|
+
cwd: asString(root.cwd),
|
|
53
|
+
reauth: {
|
|
54
|
+
needed: reauth?.needed === true,
|
|
55
|
+
reason: asString(reauth?.reason),
|
|
56
|
+
},
|
|
57
|
+
running: liveDaemon?.detected === true || runtime?.status === 'running',
|
|
58
|
+
runtimeStatus: asString(runtime?.status),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* `skrr daemon login` is the public wrapper for daemon-scope login. Explicit
|
|
63
|
+
* server, profile, and cwd make this remedy target the stopped daemon rather
|
|
64
|
+
* than whichever CLI profile or working directory happens to be current.
|
|
65
|
+
*/
|
|
66
|
+
function daemonLoginRecoveryCommand(status) {
|
|
67
|
+
const args = ['skrr', 'daemon', 'login', '--profile', quoteShellArg(status.profile)];
|
|
68
|
+
if (status.serverUrl)
|
|
69
|
+
args.push('--server', quoteShellArg(status.serverUrl));
|
|
70
|
+
if (status.cwd)
|
|
71
|
+
args.push('--cwd', quoteShellArg(status.cwd));
|
|
72
|
+
return args.join(' ');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A runtime binary may predate a CLI update. Treat its successful process exit
|
|
76
|
+
* as an unverified supervisor request until `status --json` proves it either
|
|
77
|
+
* stayed alive or named the permanent re-auth latch it wrote.
|
|
78
|
+
*/
|
|
79
|
+
function verifyRuntimeStart(statusOutput, statusExitCode, argv) {
|
|
80
|
+
const fallbackProfile = profileFromArgv(argv);
|
|
81
|
+
if (statusExitCode !== 0)
|
|
82
|
+
return { status: 'unreadable_status', profile: fallbackProfile };
|
|
83
|
+
let parsed;
|
|
84
|
+
try {
|
|
85
|
+
parsed = JSON.parse(statusOutput);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return { status: 'unreadable_status', profile: fallbackProfile };
|
|
89
|
+
}
|
|
90
|
+
const status = parseRuntimeStartStatus(parsed, fallbackProfile);
|
|
91
|
+
if (!status)
|
|
92
|
+
return { status: 'unreadable_status', profile: fallbackProfile };
|
|
93
|
+
if (status.reauth.needed) {
|
|
94
|
+
return {
|
|
95
|
+
status: 'reauth_required',
|
|
96
|
+
profile: status.profile,
|
|
97
|
+
reason: status.reauth.reason,
|
|
98
|
+
recoveryCommand: daemonLoginRecoveryCommand(status),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (status.running)
|
|
102
|
+
return { status: 'running' };
|
|
103
|
+
return {
|
|
104
|
+
status: 'not_running',
|
|
105
|
+
profile: status.profile,
|
|
106
|
+
runtimeStatus: status.runtimeStatus,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -46,12 +46,28 @@ export type TaskSnapshot = {
|
|
|
46
46
|
export type SpaceCapacity = {
|
|
47
47
|
object: 'space_task_capacity';
|
|
48
48
|
space: TaskRecord;
|
|
49
|
-
|
|
49
|
+
/**
|
|
50
|
+
* `null` means UNLIMITED, matching the orchestration payload. Declaring
|
|
51
|
+
* these `number` was not merely a type lie — it broke the CLI build, and
|
|
52
|
+
* before that it reached users as `Capacity: 0/null active`, because
|
|
53
|
+
* `autoExecuteMaxActiveTasks` defaults to `0` (= unlimited) and so the
|
|
54
|
+
* unlimited case is the COMMON path, not an edge case (OSK-7087).
|
|
55
|
+
*/
|
|
56
|
+
maxActiveTasks: number | null;
|
|
50
57
|
activeCount: number;
|
|
51
|
-
availableSlots: number;
|
|
58
|
+
availableSlots: number | null;
|
|
59
|
+
/** Live start-budget consumption, or `null` when no round is open. */
|
|
60
|
+
budget: SpaceCapacityBudget | null;
|
|
52
61
|
activeTasks: TaskRecord[];
|
|
53
62
|
sampledTaskCount: number;
|
|
54
63
|
};
|
|
64
|
+
export type SpaceCapacityBudget = {
|
|
65
|
+
generation: number;
|
|
66
|
+
maxStarts: number;
|
|
67
|
+
consumedStarts: number;
|
|
68
|
+
remaining: number | null;
|
|
69
|
+
unlimited: boolean;
|
|
70
|
+
};
|
|
55
71
|
export declare function statusOf(task: TaskRecord | null | undefined): string;
|
|
56
72
|
export declare function isTerminalTask(task: TaskRecord): boolean;
|
|
57
73
|
/**
|
|
@@ -79,10 +79,15 @@ function satisfiesWaitTarget(task, until) {
|
|
|
79
79
|
return until === 'terminal' ? exports.TERMINAL_STATUSES.has(status) : status === until;
|
|
80
80
|
}
|
|
81
81
|
function isActiveTask(task) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
if (isTerminalTask(task))
|
|
83
|
+
return false;
|
|
84
|
+
const reasonCode = task.actionability?.reasonCode;
|
|
85
|
+
if (typeof reasonCode === 'string')
|
|
86
|
+
return reasonCode === 'execution_running';
|
|
87
|
+
// Compatibility for older servers that do not attach actionability. A
|
|
88
|
+
// workflow status or leftover session is not compute ownership; only the
|
|
89
|
+
// checkout is a conservative fallback.
|
|
90
|
+
return isNonEmptyString(task.checkoutRunId);
|
|
86
91
|
}
|
|
87
92
|
function isRecoveredExecutionTask(task) {
|
|
88
93
|
if (!isStartableTask(task))
|
|
@@ -178,19 +183,25 @@ async function fetchSpaceTasks(spaceId, limit = 500) {
|
|
|
178
183
|
return items;
|
|
179
184
|
}
|
|
180
185
|
async function getSpaceCapacity(spaceId, limit = 1000) {
|
|
181
|
-
const [space, tasks] = await Promise.all([
|
|
186
|
+
const [space, tasks, preview] = await Promise.all([
|
|
182
187
|
data_provider_1.dataService.getSpace(spaceId),
|
|
183
188
|
fetchSpaceTasks(spaceId, limit),
|
|
189
|
+
data_provider_1.dataService.previewSpaceAutoExecutor(spaceId),
|
|
184
190
|
]);
|
|
185
191
|
const activeTasks = tasks.filter(isActiveTask);
|
|
186
|
-
const
|
|
187
|
-
|
|
192
|
+
const canonicalCapacity = preview?.plan?.capacity;
|
|
193
|
+
if (!canonicalCapacity) {
|
|
194
|
+
throw new Error('Space auto-executor preview did not return canonical capacity');
|
|
195
|
+
}
|
|
196
|
+
const maxActiveTasks = canonicalCapacity.maxActiveTasks;
|
|
197
|
+
const activeCount = canonicalCapacity.activeCount;
|
|
188
198
|
return {
|
|
189
199
|
object: 'space_task_capacity',
|
|
190
200
|
space,
|
|
191
201
|
maxActiveTasks,
|
|
192
202
|
activeCount,
|
|
193
|
-
availableSlots:
|
|
203
|
+
availableSlots: canonicalCapacity.availableSlots,
|
|
204
|
+
budget: canonicalCapacity.budget ?? null,
|
|
194
205
|
activeTasks,
|
|
195
206
|
sampledTaskCount: tasks.length,
|
|
196
207
|
};
|
|
@@ -314,9 +325,3 @@ function stringValue(value) {
|
|
|
314
325
|
function isNonEmptyString(value) {
|
|
315
326
|
return typeof value === 'string' && value.trim().length > 0;
|
|
316
327
|
}
|
|
317
|
-
function normalizePositiveInt(value, fallback) {
|
|
318
|
-
const n = Number(value);
|
|
319
|
-
if (!Number.isFinite(n) || n <= 0)
|
|
320
|
-
return fallback;
|
|
321
|
-
return Math.floor(n);
|
|
322
|
-
}
|
package/dist/lib/tasks.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ParsedDeliverableCoordinate, ParsedExpectationSpec } from '@skrr-ai/data-provider';
|
|
1
2
|
export declare const TASK_PRIORITIES: readonly ["low", "medium", "high", "critical"];
|
|
2
3
|
/**
|
|
3
4
|
* The kinds `tasks events append --kind` will accept.
|
|
@@ -50,6 +51,14 @@ export type TaskCreateFlagValues = {
|
|
|
50
51
|
'assignee-agent'?: string[];
|
|
51
52
|
'assignee-user'?: string[];
|
|
52
53
|
'execution-mode'?: string;
|
|
54
|
+
expectations?: Array<{
|
|
55
|
+
shape?: string;
|
|
56
|
+
kind?: string;
|
|
57
|
+
format?: string;
|
|
58
|
+
target?: string;
|
|
59
|
+
description?: string;
|
|
60
|
+
presetKey?: string;
|
|
61
|
+
}>;
|
|
53
62
|
'start-date'?: string;
|
|
54
63
|
'due-date'?: string;
|
|
55
64
|
estimate?: number;
|
|
@@ -62,6 +71,47 @@ export type TaskCreateFlagValues = {
|
|
|
62
71
|
* true ⇒ 'ephemeral', false ⇒ 'shared', undefined ⇒ undefined (inherit/leave).
|
|
63
72
|
*/
|
|
64
73
|
export declare function executionIsolationFromWorktreeFlag(worktree: boolean | undefined): 'shared' | 'ephemeral' | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* Parse an expectation spec.
|
|
76
|
+
*
|
|
77
|
+
* DELEGATES to `@skrr-ai/data-provider`, which owns the grammar and is read by
|
|
78
|
+
* the API and the web client too. The copy this replaces validated correctly
|
|
79
|
+
* and then described a syntax it refused — its messages named three different
|
|
80
|
+
* flags (`--requires`, `--output`, `--accept`) and used `git_ref:origin/main`
|
|
81
|
+
* as the example, a value the same function rejects. For an agent the refusal
|
|
82
|
+
* IS the documentation: it hits the error and corrects, so an error naming a
|
|
83
|
+
* rejected example costs a whole turn and can loop.
|
|
84
|
+
*/
|
|
85
|
+
export declare function parseExpectation(spec: string): {
|
|
86
|
+
ok: true;
|
|
87
|
+
value: ParsedExpectationSpec;
|
|
88
|
+
} | {
|
|
89
|
+
ok: false;
|
|
90
|
+
error: string;
|
|
91
|
+
};
|
|
92
|
+
/** Parse several, failing on the first that cannot be read. */
|
|
93
|
+
export declare function parseExpectations(specs: string[] | undefined): {
|
|
94
|
+
ok: true;
|
|
95
|
+
value: ParsedExpectationSpec[];
|
|
96
|
+
} | {
|
|
97
|
+
ok: false;
|
|
98
|
+
error: string;
|
|
99
|
+
};
|
|
100
|
+
export declare const EXPECT_FLAG_HELP: string;
|
|
101
|
+
/**
|
|
102
|
+
* Parse a deliverable coordinate. Also delegated — `:` and `@` and now `/` all
|
|
103
|
+
* mean different things, and a second implementation of that grammar is how the
|
|
104
|
+
* declaring side and the recording side stop agreeing.
|
|
105
|
+
*/
|
|
106
|
+
export declare function parseDeliverableCoordinate(spec: string): {
|
|
107
|
+
ok: true;
|
|
108
|
+
value: ParsedDeliverableCoordinate;
|
|
109
|
+
} | {
|
|
110
|
+
ok: false;
|
|
111
|
+
error: string;
|
|
112
|
+
};
|
|
113
|
+
/** Lifecycle values a caller may assert, for flag validation. */
|
|
114
|
+
export declare const DELIVERABLE_LIFECYCLE_VALUES: readonly ["local", "in_review", "durable", "blocked", "superseded"];
|
|
65
115
|
export declare function buildTaskCreatePayload(body: TaskRecord, flags?: TaskCreateFlagValues): TaskRecord;
|
|
66
116
|
/**
|
|
67
117
|
* The Agent this CLI process is running AS, if any.
|