@llblab/pi-actors 0.37.1 → 0.38.1
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/BACKLOG.md +1 -1
- package/CHANGELOG.md +19 -0
- package/README.md +6 -5
- package/dist/index.js +23 -3
- package/dist/lib/async-runs.d.ts +5 -0
- package/dist/lib/async-runs.js +87 -8
- package/dist/lib/command-templates.d.ts +2 -0
- package/dist/lib/execution.d.ts +4 -0
- package/dist/lib/execution.js +154 -13
- package/dist/lib/model-context.d.ts +56 -0
- package/dist/lib/model-context.js +220 -0
- package/dist/lib/observability.d.ts +2 -0
- package/dist/lib/observability.js +32 -6
- package/dist/lib/preflight-diagnostics.d.ts +27 -0
- package/dist/lib/preflight-diagnostics.js +86 -0
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +2 -2
- package/dist/lib/recipes-context.d.ts +7 -0
- package/dist/lib/recipes-context.js +20 -2
- package/dist/lib/recipes-discovery.d.ts +1 -0
- package/dist/lib/recipes-discovery.js +63 -7
- package/dist/lib/recipes-references.d.ts +2 -0
- package/dist/lib/recipes-references.js +10 -0
- package/dist/lib/tools-local.d.ts +2 -1
- package/dist/lib/tools-local.js +6 -4
- package/dist/lib/tools-response.js +22 -1
- package/dist/lib/tools-spawn.d.ts +2 -1
- package/dist/lib/tools-spawn.js +2 -1
- package/dist/recipes/lens-swarm.json +15 -2
- package/dist/recipes/pipeline-release-readiness.json +22 -3
- package/dist/recipes/pipeline-review-readiness.json +22 -3
- package/dist/recipes/subagent-judge.json +2 -1
- package/dist/recipes/subagent-merge.json +5 -2
- package/dist/recipes/subagent-normalize.json +2 -1
- package/dist/recipes/subagent-preflight.json +29 -0
- package/dist/recipes/subagent-review-coordinator.json +79 -7
- package/dist/recipes/subagent-review.json +2 -1
- package/dist/recipes/subagent-verify.json +2 -1
- package/dist/scripts/async-runner.mjs +84 -10
- package/dist/scripts/conformance.mjs +1 -0
- package/dist/skills/actors/SKILL.md +4 -4
- package/dist/skills/swarm/SKILL.md +2 -2
- package/docs/async-runs.md +3 -1
- package/docs/command-templates.md +4 -1
- package/docs/recipe-library.md +3 -2
- package/docs/template-recipes.md +2 -2
- package/index.ts +24 -3
- package/lib/async-runs.ts +157 -8
- package/lib/command-templates.ts +2 -0
- package/lib/execution.ts +218 -24
- package/lib/model-context.ts +359 -0
- package/lib/observability.ts +35 -6
- package/lib/preflight-diagnostics.ts +132 -0
- package/lib/prompts.ts +2 -2
- package/lib/recipes-context.ts +29 -2
- package/lib/recipes-discovery.ts +76 -11
- package/lib/recipes-references.ts +12 -0
- package/lib/tools-local.ts +22 -8
- package/lib/tools-response.ts +26 -1
- package/lib/tools-spawn.ts +6 -2
- package/package.json +1 -1
- package/recipes/lens-swarm.json +15 -2
- package/recipes/pipeline-release-readiness.json +22 -3
- package/recipes/pipeline-review-readiness.json +22 -3
- package/recipes/subagent-judge.json +2 -1
- package/recipes/subagent-merge.json +5 -2
- package/recipes/subagent-normalize.json +2 -1
- package/recipes/subagent-preflight.json +29 -0
- package/recipes/subagent-review-coordinator.json +79 -7
- package/recipes/subagent-review.json +2 -1
- package/recipes/subagent-verify.json +2 -1
- package/scripts/async-runner.mjs +84 -10
- package/scripts/conformance.mjs +1 -0
- package/skills/actors/SKILL.md +4 -4
- package/skills/swarm/SKILL.md +2 -2
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Current model/thinking propagation helpers
|
|
3
|
+
* Zones: pi session context, recipe inheritance
|
|
4
|
+
* Owns current model/thinking extraction from Pi tool contexts and implicit command-template values.
|
|
5
|
+
*/
|
|
6
|
+
export const CURRENT_MODEL_VALUE_KEY = "current_model";
|
|
7
|
+
export const CURRENT_THINKING_VALUE_KEY = "current_thinking";
|
|
8
|
+
export function getCurrentModelPattern(ctx) {
|
|
9
|
+
const provider = ctx?.model?.provider;
|
|
10
|
+
const id = ctx?.model?.id ?? ctx?.model?.modelId;
|
|
11
|
+
if (typeof provider !== "string" || !provider.trim())
|
|
12
|
+
return undefined;
|
|
13
|
+
if (typeof id !== "string" || !id.trim())
|
|
14
|
+
return undefined;
|
|
15
|
+
return `${provider.trim()}/${id.trim()}`;
|
|
16
|
+
}
|
|
17
|
+
function normalizeThinkingLevel(value) {
|
|
18
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
19
|
+
}
|
|
20
|
+
function getSessionThinkingLevel(ctx) {
|
|
21
|
+
const branch = ctx?.sessionManager?.getBranch?.();
|
|
22
|
+
if (!Array.isArray(branch))
|
|
23
|
+
return undefined;
|
|
24
|
+
for (const entry of [...branch].reverse()) {
|
|
25
|
+
if (!entry || typeof entry !== "object")
|
|
26
|
+
continue;
|
|
27
|
+
const record = entry;
|
|
28
|
+
if (record.type !== "thinking_level_change")
|
|
29
|
+
continue;
|
|
30
|
+
const level = normalizeThinkingLevel(record.thinkingLevel);
|
|
31
|
+
if (level)
|
|
32
|
+
return level;
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
export function getCurrentThinkingLevel(ctx) {
|
|
37
|
+
return (normalizeThinkingLevel(ctx?.currentThinking) ??
|
|
38
|
+
normalizeThinkingLevel(ctx?.thinkingLevel) ??
|
|
39
|
+
normalizeThinkingLevel(ctx?.getThinkingLevel?.()) ??
|
|
40
|
+
getSessionThinkingLevel(ctx));
|
|
41
|
+
}
|
|
42
|
+
export function withCurrentModelValues(values, ctx) {
|
|
43
|
+
const currentModel = getCurrentModelPattern(ctx);
|
|
44
|
+
const currentThinking = getCurrentThinkingLevel(ctx);
|
|
45
|
+
if (!currentModel && !currentThinking)
|
|
46
|
+
return values;
|
|
47
|
+
return {
|
|
48
|
+
...values,
|
|
49
|
+
...(currentModel ? { [CURRENT_MODEL_VALUE_KEY]: currentModel } : {}),
|
|
50
|
+
...(currentThinking
|
|
51
|
+
? { [CURRENT_THINKING_VALUE_KEY]: currentThinking }
|
|
52
|
+
: {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function isRecord(value) {
|
|
56
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
57
|
+
}
|
|
58
|
+
function stringReferencesPlaceholder(value, placeholder) {
|
|
59
|
+
return new RegExp(`\\{\\s*${placeholder}\\s*\\}`).test(value);
|
|
60
|
+
}
|
|
61
|
+
function modelPolicyKeyMatches(key) {
|
|
62
|
+
return /(^|_)models?$/.test(key);
|
|
63
|
+
}
|
|
64
|
+
function thinkingPolicyKeyMatches(key) {
|
|
65
|
+
return /(^|_)(thinking|thinking_level)$/.test(key);
|
|
66
|
+
}
|
|
67
|
+
function argName(token) {
|
|
68
|
+
if (typeof token !== "string")
|
|
69
|
+
return undefined;
|
|
70
|
+
return token.split(":")[0]?.trim() || undefined;
|
|
71
|
+
}
|
|
72
|
+
function collectPolicyDefaults(value, placeholder, path = "template", keys = new Set()) {
|
|
73
|
+
if (Array.isArray(value)) {
|
|
74
|
+
value.forEach((item, index) => collectPolicyDefaults(item, placeholder, `${path}[${index}]`, keys));
|
|
75
|
+
return keys;
|
|
76
|
+
}
|
|
77
|
+
if (!isRecord(value))
|
|
78
|
+
return keys;
|
|
79
|
+
for (const [key, child] of Object.entries(value)) {
|
|
80
|
+
if (key === "defaults" && isRecord(child)) {
|
|
81
|
+
for (const [defaultKey, defaultValue] of Object.entries(child)) {
|
|
82
|
+
if (typeof defaultValue === "string" &&
|
|
83
|
+
stringReferencesPlaceholder(defaultValue, placeholder)) {
|
|
84
|
+
keys.add(defaultKey);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
collectPolicyDefaults(child, placeholder, `${path}.${key}`, keys);
|
|
90
|
+
}
|
|
91
|
+
return keys;
|
|
92
|
+
}
|
|
93
|
+
function collectPolicyDirectRefs(value, placeholder, path = "template", refs = new Set()) {
|
|
94
|
+
if (typeof value === "string") {
|
|
95
|
+
if (stringReferencesPlaceholder(value, placeholder))
|
|
96
|
+
refs.add(path);
|
|
97
|
+
return refs;
|
|
98
|
+
}
|
|
99
|
+
if (Array.isArray(value)) {
|
|
100
|
+
value.forEach((item, index) => collectPolicyDirectRefs(item, placeholder, `${path}[${index}]`, refs));
|
|
101
|
+
return refs;
|
|
102
|
+
}
|
|
103
|
+
if (!isRecord(value))
|
|
104
|
+
return refs;
|
|
105
|
+
for (const [key, child] of Object.entries(value)) {
|
|
106
|
+
if (key === "defaults")
|
|
107
|
+
continue;
|
|
108
|
+
collectPolicyDirectRefs(child, placeholder, `${path}.${key}`, refs);
|
|
109
|
+
}
|
|
110
|
+
return refs;
|
|
111
|
+
}
|
|
112
|
+
function summarizePolicyAxis(input) {
|
|
113
|
+
const explicitKeys = Object.keys(input.explicitValues)
|
|
114
|
+
.filter((key) => key !== CURRENT_MODEL_VALUE_KEY &&
|
|
115
|
+
key !== CURRENT_THINKING_VALUE_KEY &&
|
|
116
|
+
input.explicitKeyMatches(key))
|
|
117
|
+
.sort();
|
|
118
|
+
const inheritedKeys = [...input.inheritedDefaultKeys]
|
|
119
|
+
.filter((key) => !Object.hasOwn(input.explicitValues, key))
|
|
120
|
+
.sort();
|
|
121
|
+
const directRefs = [...input.directRefs].length > 0 ? [input.directRefKey] : [];
|
|
122
|
+
const inheritedOrDirect = [...new Set([...inheritedKeys, ...directRefs])].sort();
|
|
123
|
+
const unresolvedKeys = input.currentValue ? [] : inheritedOrDirect;
|
|
124
|
+
const inheritedResolvedKeys = input.currentValue ? inheritedOrDirect : [];
|
|
125
|
+
const hasExplicit = explicitKeys.length > 0;
|
|
126
|
+
const hasInherited = inheritedResolvedKeys.length > 0;
|
|
127
|
+
const source = unresolvedKeys.length
|
|
128
|
+
? "unresolved"
|
|
129
|
+
: hasExplicit && hasInherited
|
|
130
|
+
? "mixed"
|
|
131
|
+
: hasExplicit
|
|
132
|
+
? "explicit"
|
|
133
|
+
: hasInherited
|
|
134
|
+
? "inherited"
|
|
135
|
+
: "unused";
|
|
136
|
+
const explicitValue = explicitKeys
|
|
137
|
+
.map((key) => input.explicitValues[key])
|
|
138
|
+
.find((value) => typeof value === "string" && value.trim());
|
|
139
|
+
const value = hasInherited
|
|
140
|
+
? input.currentValue
|
|
141
|
+
: typeof explicitValue === "string"
|
|
142
|
+
? explicitValue
|
|
143
|
+
: undefined;
|
|
144
|
+
return {
|
|
145
|
+
...(explicitKeys.length ? { explicit_keys: explicitKeys } : {}),
|
|
146
|
+
...(inheritedResolvedKeys.length
|
|
147
|
+
? { inherited_keys: inheritedResolvedKeys }
|
|
148
|
+
: {}),
|
|
149
|
+
source,
|
|
150
|
+
...(unresolvedKeys.length ? { unresolved_keys: unresolvedKeys } : {}),
|
|
151
|
+
...(value ? { value } : {}),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function collectPolicyDefaultsAcross(items, placeholder) {
|
|
155
|
+
return items.reduce((keys, item) => collectPolicyDefaults(item, placeholder, "template", keys), new Set());
|
|
156
|
+
}
|
|
157
|
+
function collectPolicyDirectRefsAcross(items, placeholder) {
|
|
158
|
+
return items.reduce((refs, item) => collectPolicyDirectRefs(item, placeholder, "template", refs), new Set());
|
|
159
|
+
}
|
|
160
|
+
export function describeCurrentPolicyProvenance(input) {
|
|
161
|
+
const explicitValues = input.values ?? {};
|
|
162
|
+
const defaultsEnvelope = input.defaults ? { defaults: input.defaults } : {};
|
|
163
|
+
const templateAndDefaults = [defaultsEnvelope, input.template];
|
|
164
|
+
return {
|
|
165
|
+
model: summarizePolicyAxis({
|
|
166
|
+
currentValue: typeof explicitValues[CURRENT_MODEL_VALUE_KEY] === "string"
|
|
167
|
+
? String(explicitValues[CURRENT_MODEL_VALUE_KEY])
|
|
168
|
+
: undefined,
|
|
169
|
+
directRefKey: CURRENT_MODEL_VALUE_KEY,
|
|
170
|
+
directRefs: collectPolicyDirectRefsAcross(templateAndDefaults, "current_model"),
|
|
171
|
+
explicitKeyMatches: modelPolicyKeyMatches,
|
|
172
|
+
explicitValues,
|
|
173
|
+
inheritedDefaultKeys: collectPolicyDefaultsAcross(templateAndDefaults, "current_model"),
|
|
174
|
+
}),
|
|
175
|
+
thinking: summarizePolicyAxis({
|
|
176
|
+
currentValue: typeof explicitValues[CURRENT_THINKING_VALUE_KEY] === "string"
|
|
177
|
+
? String(explicitValues[CURRENT_THINKING_VALUE_KEY])
|
|
178
|
+
: undefined,
|
|
179
|
+
directRefKey: CURRENT_THINKING_VALUE_KEY,
|
|
180
|
+
directRefs: collectPolicyDirectRefsAcross(templateAndDefaults, "current_thinking"),
|
|
181
|
+
explicitKeyMatches: thinkingPolicyKeyMatches,
|
|
182
|
+
explicitValues,
|
|
183
|
+
inheritedDefaultKeys: collectPolicyDefaultsAcross(templateAndDefaults, "current_thinking"),
|
|
184
|
+
}),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function publicPolicyArgs(args, matches) {
|
|
188
|
+
return Array.isArray(args)
|
|
189
|
+
? args
|
|
190
|
+
.map(argName)
|
|
191
|
+
.filter((key) => Boolean(key && matches(key)))
|
|
192
|
+
.sort()
|
|
193
|
+
: [];
|
|
194
|
+
}
|
|
195
|
+
function nonEmptyAxisRecipeSummary(inheritedDefaults, publicArgs) {
|
|
196
|
+
if (inheritedDefaults.length === 0 && publicArgs.length === 0)
|
|
197
|
+
return undefined;
|
|
198
|
+
return {
|
|
199
|
+
...(inheritedDefaults.length
|
|
200
|
+
? { inherited_defaults: inheritedDefaults }
|
|
201
|
+
: {}),
|
|
202
|
+
...(publicArgs.length ? { public_args: publicArgs } : {}),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
export function describeRecipeCurrentPolicy(input) {
|
|
206
|
+
const templateAndDefaults = [
|
|
207
|
+
input.defaults ? { defaults: input.defaults } : {},
|
|
208
|
+
input.template,
|
|
209
|
+
];
|
|
210
|
+
const model = nonEmptyAxisRecipeSummary([...collectPolicyDefaultsAcross(templateAndDefaults, "current_model")].sort(), publicPolicyArgs(input.args, modelPolicyKeyMatches));
|
|
211
|
+
const thinking = nonEmptyAxisRecipeSummary([
|
|
212
|
+
...collectPolicyDefaultsAcross(templateAndDefaults, "current_thinking"),
|
|
213
|
+
].sort(), publicPolicyArgs(input.args, thinkingPolicyKeyMatches));
|
|
214
|
+
if (!model && !thinking)
|
|
215
|
+
return undefined;
|
|
216
|
+
return {
|
|
217
|
+
...(model ? { model } : {}),
|
|
218
|
+
...(thinking ? { thinking } : {}),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
@@ -15,6 +15,7 @@ export interface RunObservation {
|
|
|
15
15
|
ownerId?: string;
|
|
16
16
|
artifacts?: Record<string, string>;
|
|
17
17
|
launchSource?: AsyncRuns.AsyncRunLaunchSource;
|
|
18
|
+
modelPolicy?: Record<string, unknown>;
|
|
18
19
|
recipeFile?: string;
|
|
19
20
|
terminalHandled?: boolean;
|
|
20
21
|
retireWhen?: string;
|
|
@@ -95,6 +96,7 @@ export interface RunTransition {
|
|
|
95
96
|
stateDir?: string;
|
|
96
97
|
artifacts?: Record<string, string>;
|
|
97
98
|
launchSource?: AsyncRuns.AsyncRunLaunchSource;
|
|
99
|
+
modelPolicy?: Record<string, unknown>;
|
|
98
100
|
recipeFile?: string;
|
|
99
101
|
terminalHandled?: boolean;
|
|
100
102
|
to: RunObservedStatus;
|
|
@@ -185,6 +185,11 @@ function observeRun(stateDir) {
|
|
|
185
185
|
...(status.launch_source === "spawn" || status.launch_source === "tool"
|
|
186
186
|
? { launchSource: status.launch_source }
|
|
187
187
|
: {}),
|
|
188
|
+
...(status.model_policy &&
|
|
189
|
+
typeof status.model_policy === "object" &&
|
|
190
|
+
!Array.isArray(status.model_policy)
|
|
191
|
+
? { modelPolicy: status.model_policy }
|
|
192
|
+
: {}),
|
|
188
193
|
...(typeof status.recipe_file === "string"
|
|
189
194
|
? { recipeFile: status.recipe_file }
|
|
190
195
|
: {}),
|
|
@@ -496,6 +501,7 @@ export function detectRunTransitions(previous, summary) {
|
|
|
496
501
|
...(run.stateDir ? { stateDir: run.stateDir } : {}),
|
|
497
502
|
...(run.artifacts ? { artifacts: run.artifacts } : {}),
|
|
498
503
|
...(run.launchSource ? { launchSource: run.launchSource } : {}),
|
|
504
|
+
...(run.modelPolicy ? { modelPolicy: run.modelPolicy } : {}),
|
|
499
505
|
...(run.recipeFile ? { recipeFile: run.recipeFile } : {}),
|
|
500
506
|
...(run.terminalHandled ? { terminalHandled: true } : {}),
|
|
501
507
|
to: run.status,
|
|
@@ -721,6 +727,25 @@ function formatRecipePersistenceSuggestion(transition) {
|
|
|
721
727
|
}
|
|
722
728
|
return `\nAgent note: this actor was spawned directly and completed successfully. If this pattern fits this machine's recurring workflow, ask the operator whether to save it as a durable recipe/tool under ~/.pi/agent/recipes with register_tool. Do not auto-save without confirmation.`;
|
|
723
729
|
}
|
|
730
|
+
function formatTransitionPolicy(transition) {
|
|
731
|
+
if (!transition.modelPolicy)
|
|
732
|
+
return "";
|
|
733
|
+
const axis = (key, label) => {
|
|
734
|
+
const value = transition.modelPolicy?.[key];
|
|
735
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
736
|
+
return undefined;
|
|
737
|
+
const record = value;
|
|
738
|
+
const source = typeof record.source === "string" ? record.source : "unused";
|
|
739
|
+
if (source === "unused")
|
|
740
|
+
return undefined;
|
|
741
|
+
const renderedValue = typeof record.value === "string" && record.value.trim()
|
|
742
|
+
? ` (${record.value.trim()})`
|
|
743
|
+
: "";
|
|
744
|
+
return `${label}: ${source}${renderedValue}`;
|
|
745
|
+
};
|
|
746
|
+
const lines = [axis("model", "Model"), axis("thinking", "Thinking")].filter((line) => Boolean(line));
|
|
747
|
+
return lines.length ? `\nPolicy:\n- ${lines.join("\n- ")}` : "";
|
|
748
|
+
}
|
|
724
749
|
function formatTransitionNextActions(transition) {
|
|
725
750
|
const actions = [
|
|
726
751
|
`inspect target=run:${transition.run} view=status`,
|
|
@@ -735,16 +760,17 @@ export function formatRunTransitionMessage(transition) {
|
|
|
735
760
|
const artifacts = formatNamedArtifacts(transition.artifacts);
|
|
736
761
|
const runFiles = formatRunFileList(getRunArtifacts(transition));
|
|
737
762
|
const persistenceSuggestion = formatRecipePersistenceSuggestion(transition);
|
|
763
|
+
const policy = formatTransitionPolicy(transition);
|
|
738
764
|
const nextActions = formatTransitionNextActions(transition);
|
|
739
765
|
if (transition.to === "done")
|
|
740
|
-
return `Run ${transition.run} completed successfully.${artifacts}${runFiles}${nextActions}${persistenceSuggestion}`;
|
|
766
|
+
return `Run ${transition.run} completed successfully.${policy}${artifacts}${runFiles}${nextActions}${persistenceSuggestion}`;
|
|
741
767
|
if (transition.to === "failed")
|
|
742
|
-
return `Run ${transition.run} failed.${artifacts}${runFiles}${nextActions}`;
|
|
768
|
+
return `Run ${transition.run} failed.${policy}${artifacts}${runFiles}${nextActions}`;
|
|
743
769
|
if (transition.to === "cancelled")
|
|
744
|
-
return `Run ${transition.run} was cancelled.${nextActions}`;
|
|
770
|
+
return `Run ${transition.run} was cancelled.${policy}${nextActions}`;
|
|
745
771
|
if (transition.to === "killed")
|
|
746
|
-
return `Run ${transition.run} was force-killed.${nextActions}`;
|
|
772
|
+
return `Run ${transition.run} was force-killed.${policy}${nextActions}`;
|
|
747
773
|
if (transition.to === "exited")
|
|
748
|
-
return `Run ${transition.run} exited before writing a result.${nextActions}`;
|
|
749
|
-
return `Run ${transition.run} finished with status ${transition.to}.${nextActions}`;
|
|
774
|
+
return `Run ${transition.run} exited before writing a result.${policy}${nextActions}`;
|
|
775
|
+
return `Run ${transition.run} finished with status ${transition.to}.${policy}${nextActions}`;
|
|
750
776
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review preflight diagnostics
|
|
3
|
+
* Zones: recipe diagnostics, subagent launch policy
|
|
4
|
+
* Owns compact classification of failed review-pipeline preflight commands.
|
|
5
|
+
*/
|
|
6
|
+
export type ReviewPreflightErrorClass = "auth_or_key" | "model_unavailable" | "quota_or_balance" | "rate_limited" | "timeout" | "transport" | "unknown_provider_failure";
|
|
7
|
+
export interface ReviewPreflightDiagnosticInput {
|
|
8
|
+
args: readonly string[];
|
|
9
|
+
code?: number;
|
|
10
|
+
killed?: boolean;
|
|
11
|
+
promptFile?: string;
|
|
12
|
+
promptText?: string;
|
|
13
|
+
stderr?: string;
|
|
14
|
+
stdout?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ReviewPreflightDiagnostic {
|
|
17
|
+
errorClass: ReviewPreflightErrorClass;
|
|
18
|
+
model?: string;
|
|
19
|
+
promptFile?: string;
|
|
20
|
+
stage: string;
|
|
21
|
+
suggestedOverrideArgs: string;
|
|
22
|
+
thinking?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function extractReviewPreflightStage(promptText: string | undefined): string | undefined;
|
|
25
|
+
export declare function classifyReviewPreflightError(text: string, killed?: boolean): ReviewPreflightErrorClass;
|
|
26
|
+
export declare function buildReviewPreflightDiagnostic(input: ReviewPreflightDiagnosticInput): ReviewPreflightDiagnostic | undefined;
|
|
27
|
+
export declare function formatReviewPreflightDiagnostic(diagnostic: ReviewPreflightDiagnostic): string;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review preflight diagnostics
|
|
3
|
+
* Zones: recipe diagnostics, subagent launch policy
|
|
4
|
+
* Owns compact classification of failed review-pipeline preflight commands.
|
|
5
|
+
*/
|
|
6
|
+
const PREFLIGHT_STAGE_PATTERN = /Preflight check for stage\s+([^\s.]+)\.?/i;
|
|
7
|
+
function getFlagValue(args, flag) {
|
|
8
|
+
const inlinePrefix = `${flag}=`;
|
|
9
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
10
|
+
const value = args[index];
|
|
11
|
+
if (value === flag)
|
|
12
|
+
return args[index + 1];
|
|
13
|
+
if (value?.startsWith(inlinePrefix))
|
|
14
|
+
return value.slice(inlinePrefix.length);
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
function getToolPolicy(args) {
|
|
19
|
+
if (args.includes("--no-tools"))
|
|
20
|
+
return "<tool-policy>";
|
|
21
|
+
return getFlagValue(args, "--tools") ?? "<tool-policy>";
|
|
22
|
+
}
|
|
23
|
+
export function extractReviewPreflightStage(promptText) {
|
|
24
|
+
return promptText?.match(PREFLIGHT_STAGE_PATTERN)?.[1];
|
|
25
|
+
}
|
|
26
|
+
export function classifyReviewPreflightError(text, killed = false) {
|
|
27
|
+
if (killed)
|
|
28
|
+
return "timeout";
|
|
29
|
+
const normalized = text.toLowerCase();
|
|
30
|
+
if (/insufficient[_ -]?quota|\bquota\b|balance|billing|credits?|payment/.test(normalized)) {
|
|
31
|
+
return "quota_or_balance";
|
|
32
|
+
}
|
|
33
|
+
if (/api[_ -]?key|unauthori[sz]ed|forbidden|permission|\b401\b|\b403\b|auth/.test(normalized)) {
|
|
34
|
+
return "auth_or_key";
|
|
35
|
+
}
|
|
36
|
+
if (/\b404\b|not found|unknown model|model .*not (exist|found)|invalid model|unsupported model/.test(normalized)) {
|
|
37
|
+
return "model_unavailable";
|
|
38
|
+
}
|
|
39
|
+
if (/rate limit|\b429\b|too many requests/.test(normalized)) {
|
|
40
|
+
return "rate_limited";
|
|
41
|
+
}
|
|
42
|
+
if (/network|econn|enotfound|etimedout|fetch failed|socket|\btls\b/.test(normalized)) {
|
|
43
|
+
return "transport";
|
|
44
|
+
}
|
|
45
|
+
return "unknown_provider_failure";
|
|
46
|
+
}
|
|
47
|
+
function getStageModelArg(stage) {
|
|
48
|
+
if (stage === "reviewer" ||
|
|
49
|
+
stage === "verifier" ||
|
|
50
|
+
stage === "merger" ||
|
|
51
|
+
stage === "judge") {
|
|
52
|
+
return `${stage}_model`;
|
|
53
|
+
}
|
|
54
|
+
return "model";
|
|
55
|
+
}
|
|
56
|
+
function buildSuggestedOverrideArgs(stage, args) {
|
|
57
|
+
return `${getStageModelArg(stage)}=<working-model> thinking=<supported-level> tools=${getToolPolicy(args)}`;
|
|
58
|
+
}
|
|
59
|
+
export function buildReviewPreflightDiagnostic(input) {
|
|
60
|
+
const stage = extractReviewPreflightStage(input.promptText);
|
|
61
|
+
if (!stage)
|
|
62
|
+
return undefined;
|
|
63
|
+
return {
|
|
64
|
+
errorClass: classifyReviewPreflightError([input.stderr, input.stdout].filter(Boolean).join("\n"), input.killed),
|
|
65
|
+
...(getFlagValue(input.args, "--model")
|
|
66
|
+
? { model: getFlagValue(input.args, "--model") }
|
|
67
|
+
: {}),
|
|
68
|
+
...(input.promptFile ? { promptFile: input.promptFile } : {}),
|
|
69
|
+
stage,
|
|
70
|
+
suggestedOverrideArgs: buildSuggestedOverrideArgs(stage, input.args),
|
|
71
|
+
...(getFlagValue(input.args, "--thinking")
|
|
72
|
+
? { thinking: getFlagValue(input.args, "--thinking") }
|
|
73
|
+
: {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function formatReviewPreflightDiagnostic(diagnostic) {
|
|
77
|
+
return [
|
|
78
|
+
"ACTOR_PREFLIGHT_FAILED",
|
|
79
|
+
`stage=${diagnostic.stage}`,
|
|
80
|
+
...(diagnostic.model ? [`model=${diagnostic.model}`] : []),
|
|
81
|
+
...(diagnostic.thinking ? [`thinking=${diagnostic.thinking}`] : []),
|
|
82
|
+
`error_class=${diagnostic.errorClass}`,
|
|
83
|
+
...(diagnostic.promptFile ? [`prompt_file=${diagnostic.promptFile}`] : []),
|
|
84
|
+
`suggested_override_args=\"${diagnostic.suggestedOverrideArgs}\"`,
|
|
85
|
+
].join(" ");
|
|
86
|
+
}
|
package/dist/lib/prompts.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
export declare const REGISTER_TOOL_DESCRIPTION: string;
|
|
7
7
|
export declare const REGISTER_TOOL_PROMPT_SNIPPET = "Register persistent command templates as agent-callable tools";
|
|
8
8
|
export declare const REGISTER_TOOL_GUIDELINES: string[];
|
|
9
|
-
export declare const ONBOARDING_SYSTEM_PROMPT = "pi-actors quick model:\n- Local-first actor memory: persist trusted local capabilities instead of rebuilding shell recipes.\n- Layers: task -> command template -> recipe/tool -> spawn -> run:<id>; tool:<name> wraps registered capabilities.\n- Command templates stay sync: string leaf, array sequence, object node; flags include args/defaults, parallel, when, timeout, delay, retry, failure, recover, repeat, output.\n- Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.\n- ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.\n- Recipes own template directly and may declare metadata/defaults/imports/mailbox/artifacts; files >1 MiB or import depth >32 fail closed.\n- Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.\n- Actor-mode trigger: if work may outlive this turn, need steering/follow-up/artifacts, run as a service, fan out, or be resumed/inspected later, use spawn -> message -> inspect instead of ad hoc shell backgrounding.\n- Use spawn/message/inspect for actor-level start/send/observe; short foreground checks can stay ordinary tools/templates; avoid runtime/FIFO/outbox vocabulary in public guidance.\n- Run state lives under ~/.pi/agent/tmp/pi-actors/runs; inspect status/tail/messages/mailbox/files/artifacts intentionally and avoid busy-polling.\n- Maintain ~/.pi/agent/recipes like MEMORY.md for capabilities: keep useful tools, curate stale ones, and fix/remove/disable invalid recipes flagged by registry warnings; packaged/ad hoc recipes are lower-priority components; offer to save successful recurring patterns only after confirmation.\n- Prefer maintained packaged recipes/pipelines with spawn file=<recipe> before ad hoc scripts/wrappers;
|
|
9
|
+
export declare const ONBOARDING_SYSTEM_PROMPT = "pi-actors quick model:\n- Local-first actor memory: persist trusted local capabilities instead of rebuilding shell recipes.\n- Layers: task -> command template -> recipe/tool -> spawn -> run:<id>; tool:<name> wraps registered capabilities.\n- Command templates stay sync: string leaf, array sequence, object node; flags include args/defaults, parallel, concurrency, min_successful, when, timeout, delay, retry, failure, recover, repeat, output.\n- Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.\n- ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.\n- Recipes own template directly and may declare metadata/defaults/imports/mailbox/artifacts; files >1 MiB or import depth >32 fail closed.\n- Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.\n- Actor-mode trigger: if work may outlive this turn, need steering/follow-up/artifacts, run as a service, fan out, or be resumed/inspected later, use spawn -> message -> inspect instead of ad hoc shell backgrounding.\n- Use spawn/message/inspect for actor-level start/send/observe; short foreground checks can stay ordinary tools/templates; avoid runtime/FIFO/outbox vocabulary in public guidance.\n- Run state lives under ~/.pi/agent/tmp/pi-actors/runs; inspect status/tail/messages/mailbox/files/artifacts intentionally and avoid busy-polling.\n- Maintain ~/.pi/agent/recipes like MEMORY.md for capabilities: keep useful tools, curate stale ones, and fix/remove/disable invalid recipes flagged by registry warnings; packaged/ad hoc recipes are lower-priority components; offer to save successful recurring patterns only after confirmation.\n- Prefer maintained packaged recipes/pipelines with spawn file=<recipe> before ad hoc scripts/wrappers; review swarms inherit current model/thinking, preflight before fanout, and expose quorum/concurrency/TTL knobs unless explicit args are passed.\n- For any non-trivial actor use or pi-actors change, read the bundled actors skill first; for deeper guidance, inspect installed extension sources/docs/recipes because README/docs are not automatically in context.";
|
|
10
10
|
export declare const REGISTER_TOOL_PARAM_DESCRIPTIONS: {
|
|
11
11
|
readonly name: "Tool name in snake_case (e.g., 'transcribe')";
|
|
12
12
|
readonly description: "Describe what the tool does for the LLM. Required unless deleting; omitted updates keep the old description.";
|
package/dist/lib/prompts.js
CHANGED
|
@@ -16,7 +16,7 @@ export const REGISTER_TOOL_GUIDELINES = [
|
|
|
16
16
|
export const ONBOARDING_SYSTEM_PROMPT = `pi-actors quick model:
|
|
17
17
|
- Local-first actor memory: persist trusted local capabilities instead of rebuilding shell recipes.
|
|
18
18
|
- Layers: task -> command template -> recipe/tool -> spawn -> run:<id>; tool:<name> wraps registered capabilities.
|
|
19
|
-
- Command templates stay sync: string leaf, array sequence, object node; flags include args/defaults, parallel, when, timeout, delay, retry, failure, recover, repeat, output.
|
|
19
|
+
- Command templates stay sync: string leaf, array sequence, object node; flags include args/defaults, parallel, concurrency, min_successful, when, timeout, delay, retry, failure, recover, repeat, output.
|
|
20
20
|
- Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.
|
|
21
21
|
- ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.
|
|
22
22
|
- Recipes own template directly and may declare metadata/defaults/imports/mailbox/artifacts; files >1 MiB or import depth >32 fail closed.
|
|
@@ -25,7 +25,7 @@ export const ONBOARDING_SYSTEM_PROMPT = `pi-actors quick model:
|
|
|
25
25
|
- Use spawn/message/inspect for actor-level start/send/observe; short foreground checks can stay ordinary tools/templates; avoid runtime/FIFO/outbox vocabulary in public guidance.
|
|
26
26
|
- Run state lives under ~/.pi/agent/tmp/pi-actors/runs; inspect status/tail/messages/mailbox/files/artifacts intentionally and avoid busy-polling.
|
|
27
27
|
- Maintain ~/.pi/agent/recipes like MEMORY.md for capabilities: keep useful tools, curate stale ones, and fix/remove/disable invalid recipes flagged by registry warnings; packaged/ad hoc recipes are lower-priority components; offer to save successful recurring patterns only after confirmation.
|
|
28
|
-
- Prefer maintained packaged recipes/pipelines with spawn file=<recipe> before ad hoc scripts/wrappers;
|
|
28
|
+
- Prefer maintained packaged recipes/pipelines with spawn file=<recipe> before ad hoc scripts/wrappers; review swarms inherit current model/thinking, preflight before fanout, and expose quorum/concurrency/TTL knobs unless explicit args are passed.
|
|
29
29
|
- For any non-trivial actor use or pi-actors change, read the bundled actors skill first; for deeper guidance, inspect installed extension sources/docs/recipes because README/docs are not automatically in context.`;
|
|
30
30
|
export const REGISTER_TOOL_PARAM_DESCRIPTIONS = {
|
|
31
31
|
name: "Tool name in snake_case (e.g., 'transcribe')",
|
|
@@ -9,7 +9,14 @@ export interface MarkedRecipeContextRecord extends TemplateRecipeContextRecord {
|
|
|
9
9
|
you_are_here?: true;
|
|
10
10
|
you_are_here_path?: string;
|
|
11
11
|
}
|
|
12
|
+
export declare function findPiPrintPromptIndex(args: string[]): number | undefined;
|
|
12
13
|
export declare function markRecipeContextRecords(records: TemplateRecipeContextRecord[], context?: CommandTemplateActorRecipeContext): MarkedRecipeContextRecord[];
|
|
13
14
|
export declare function formatRecipeContextJsonl(records: TemplateRecipeContextRecord[], context?: CommandTemplateActorRecipeContext): string;
|
|
14
15
|
export declare function buildRecipeContextPromptBlock(records: TemplateRecipeContextRecord[], context?: CommandTemplateActorRecipeContext): string;
|
|
15
16
|
export declare function appendRecipeContextToPiArgs(command: string, args: string[], records: TemplateRecipeContextRecord[] | undefined, context?: CommandTemplateActorRecipeContext): string[];
|
|
17
|
+
export interface MaterializedPiPrintPromptArgs {
|
|
18
|
+
args: string[];
|
|
19
|
+
promptBytes?: number;
|
|
20
|
+
promptFile?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function materializePiPrintPromptArg(command: string, args: string[], promptFile: string | (() => string)): MaterializedPiPrintPromptArgs;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Zones: async runner prompt context, recipe provenance, LLM child launches
|
|
4
4
|
* Owns compact actor recipe context records appended to child-agent prompts.
|
|
5
5
|
*/
|
|
6
|
+
import { writeFileSync } from "node:fs";
|
|
6
7
|
import { basename } from "node:path";
|
|
7
8
|
function commandName(command) {
|
|
8
9
|
return basename(command).toLowerCase();
|
|
@@ -46,7 +47,7 @@ function piOptionConsumesNextArg(arg) {
|
|
|
46
47
|
function isPiFileArgument(arg) {
|
|
47
48
|
return arg.startsWith("@") && arg.length > 1;
|
|
48
49
|
}
|
|
49
|
-
function
|
|
50
|
+
export function findPiPrintPromptIndex(args) {
|
|
50
51
|
let printMode = false;
|
|
51
52
|
let positionalOnly = false;
|
|
52
53
|
let promptIndex;
|
|
@@ -120,7 +121,7 @@ export function buildRecipeContextPromptBlock(records, context) {
|
|
|
120
121
|
export function appendRecipeContextToPiArgs(command, args, records, context) {
|
|
121
122
|
if (!records || records.length === 0 || !isPiCommand(command))
|
|
122
123
|
return args;
|
|
123
|
-
const promptIndex =
|
|
124
|
+
const promptIndex = findPiPrintPromptIndex(args);
|
|
124
125
|
if (promptIndex === undefined)
|
|
125
126
|
return args;
|
|
126
127
|
const block = buildRecipeContextPromptBlock(records, context);
|
|
@@ -130,3 +131,20 @@ export function appendRecipeContextToPiArgs(command, args, records, context) {
|
|
|
130
131
|
next[promptIndex] = `${next[promptIndex]}\n\n${block}`;
|
|
131
132
|
return next;
|
|
132
133
|
}
|
|
134
|
+
export function materializePiPrintPromptArg(command, args, promptFile) {
|
|
135
|
+
if (!isPiCommand(command))
|
|
136
|
+
return { args };
|
|
137
|
+
const promptIndex = findPiPrintPromptIndex(args);
|
|
138
|
+
if (promptIndex === undefined)
|
|
139
|
+
return { args };
|
|
140
|
+
const prompt = args[promptIndex];
|
|
141
|
+
const path = typeof promptFile === "function" ? promptFile() : promptFile;
|
|
142
|
+
writeFileSync(path, prompt, "utf8");
|
|
143
|
+
const next = [...args];
|
|
144
|
+
next[promptIndex] = `@${path}`;
|
|
145
|
+
return {
|
|
146
|
+
args: next,
|
|
147
|
+
promptBytes: Buffer.byteLength(prompt),
|
|
148
|
+
promptFile: path,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -45,6 +45,7 @@ export interface RecipesDiscoverySource {
|
|
|
45
45
|
defaultTool?: boolean;
|
|
46
46
|
mutableUsage?: boolean;
|
|
47
47
|
}
|
|
48
|
+
export declare function hasBroadWindowsWriteAcl(icaclsOutput: string): boolean;
|
|
48
49
|
export declare function discoverRecipeSources(sources: RecipesDiscoverySource[]): RecipesDiscoveryResult;
|
|
49
50
|
export declare function discoverRecipes(roots: string[]): RecipesDiscoveryResult;
|
|
50
51
|
export declare function createRecipeIntegrityManifest(result: RecipesDiscoveryResult): RecipeIntegrityManifestEntry[];
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
* Zones: recipe discovery, tool exposure, registry diagnostics
|
|
4
4
|
* Owns filename identity discovery across prioritized recipe roots
|
|
5
5
|
*/
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
6
7
|
import { createHash } from "node:crypto";
|
|
7
8
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
8
9
|
import { join } from "node:path";
|
|
9
10
|
import * as CommandTemplates from "./command-templates.js";
|
|
11
|
+
import * as ModelContext from "./model-context.js";
|
|
10
12
|
import * as RecipesReferences from "./recipes-references.js";
|
|
11
13
|
import * as Schema from "./schema.js";
|
|
12
14
|
function assertToolSafeRepeatConfig(config, argTypes, defaults) {
|
|
@@ -139,6 +141,50 @@ function filesForSource(source) {
|
|
|
139
141
|
}))
|
|
140
142
|
: [];
|
|
141
143
|
}
|
|
144
|
+
const WINDOWS_BROAD_WRITE_PRINCIPALS = [
|
|
145
|
+
"Everyone",
|
|
146
|
+
"BUILTIN\\Users",
|
|
147
|
+
"Authenticated Users",
|
|
148
|
+
"S-1-1-0",
|
|
149
|
+
"S-1-5-11",
|
|
150
|
+
"S-1-5-32-545",
|
|
151
|
+
];
|
|
152
|
+
const WINDOWS_WRITE_ACL_PATTERN = /\((?:[^)]*,)?(?:F|M|W|WD|AD|DC|GA|GW)(?:,[^)]*)?\)/i;
|
|
153
|
+
export function hasBroadWindowsWriteAcl(icaclsOutput) {
|
|
154
|
+
return icaclsOutput
|
|
155
|
+
.split(/\r?\n/)
|
|
156
|
+
.some((line) => WINDOWS_BROAD_WRITE_PRINCIPALS.some((principal) => line.includes(principal)) && WINDOWS_WRITE_ACL_PATTERN.test(line));
|
|
157
|
+
}
|
|
158
|
+
function getWindowsRecipeRootDiagnostics(root) {
|
|
159
|
+
try {
|
|
160
|
+
const output = execFileSync("icacls", [root], {
|
|
161
|
+
encoding: "utf8",
|
|
162
|
+
windowsHide: true,
|
|
163
|
+
timeout: 5000,
|
|
164
|
+
});
|
|
165
|
+
if (!hasBroadWindowsWriteAcl(output))
|
|
166
|
+
return [];
|
|
167
|
+
return [
|
|
168
|
+
`Recipe root grants write access to broad Windows principals; review ACLs: ${root}`,
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
return [
|
|
173
|
+
`Failed to inspect Windows ACL for recipe root ${root}: ${error instanceof Error ? error.message : String(error)}`,
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function getPosixRecipeRootDiagnostics(root) {
|
|
178
|
+
const diagnostics = [];
|
|
179
|
+
const stat = statSync(root);
|
|
180
|
+
if ((stat.mode & 0o002) !== 0) {
|
|
181
|
+
diagnostics.push(`Recipe root is world-writable; review permissions: ${root}`);
|
|
182
|
+
}
|
|
183
|
+
if ((stat.mode & 0o020) !== 0) {
|
|
184
|
+
diagnostics.push(`Recipe root is group-writable; review ownership and permissions: ${root}`);
|
|
185
|
+
}
|
|
186
|
+
return diagnostics;
|
|
187
|
+
}
|
|
142
188
|
function getRecipeRootDiagnostics(sources) {
|
|
143
189
|
const diagnostics = [];
|
|
144
190
|
const roots = new Set(sources
|
|
@@ -148,13 +194,9 @@ function getRecipeRootDiagnostics(sources) {
|
|
|
148
194
|
try {
|
|
149
195
|
if (!existsSync(root))
|
|
150
196
|
continue;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
if ((stat.mode & 0o020) !== 0) {
|
|
156
|
-
diagnostics.push(`Recipe root is group-writable; review ownership and permissions: ${root}`);
|
|
157
|
-
}
|
|
197
|
+
diagnostics.push(...(process.platform === "win32"
|
|
198
|
+
? getWindowsRecipeRootDiagnostics(root)
|
|
199
|
+
: getPosixRecipeRootDiagnostics(root)));
|
|
158
200
|
}
|
|
159
201
|
catch (error) {
|
|
160
202
|
diagnostics.push(`Failed to inspect recipe root ${root}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -524,6 +566,20 @@ export function summarizeDiscovery(result) {
|
|
|
524
566
|
invalid: entry.invalid,
|
|
525
567
|
shadows: entry.shadows,
|
|
526
568
|
...(entry.riskLabels.length ? { risk_labels: entry.riskLabels } : {}),
|
|
569
|
+
...(entry.config &&
|
|
570
|
+
ModelContext.describeRecipeCurrentPolicy({
|
|
571
|
+
args: entry.config.args,
|
|
572
|
+
defaults: entry.config.defaults,
|
|
573
|
+
template: entry.config.template,
|
|
574
|
+
})
|
|
575
|
+
? {
|
|
576
|
+
current_policy: ModelContext.describeRecipeCurrentPolicy({
|
|
577
|
+
args: entry.config.args,
|
|
578
|
+
defaults: entry.config.defaults,
|
|
579
|
+
template: entry.config.template,
|
|
580
|
+
}),
|
|
581
|
+
}
|
|
582
|
+
: {}),
|
|
527
583
|
...(entry.config?.imports ? { imports: entry.config.imports } : {}),
|
|
528
584
|
...(recipeUsage(entry.config)
|
|
529
585
|
? { usage: recipeUsage(entry.config) }
|
|
@@ -33,6 +33,8 @@ export interface TemplateRecipeDefinition {
|
|
|
33
33
|
args?: string[];
|
|
34
34
|
defaults?: Record<string, unknown>;
|
|
35
35
|
parallel?: boolean;
|
|
36
|
+
concurrency?: number | string;
|
|
37
|
+
min_successful?: number | string;
|
|
36
38
|
label?: string;
|
|
37
39
|
when?: boolean | string;
|
|
38
40
|
timeout?: number | string;
|
|
@@ -135,6 +135,8 @@ function getRecipeCommandTemplate(raw) {
|
|
|
135
135
|
"args",
|
|
136
136
|
"defaults",
|
|
137
137
|
"parallel",
|
|
138
|
+
"concurrency",
|
|
139
|
+
"min_successful",
|
|
138
140
|
"label",
|
|
139
141
|
"when",
|
|
140
142
|
"timeout",
|
|
@@ -711,6 +713,14 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
|
|
|
711
713
|
...(typeof substituted.parallel === "boolean"
|
|
712
714
|
? { parallel: substituted.parallel }
|
|
713
715
|
: {}),
|
|
716
|
+
...(typeof substituted.concurrency === "number" ||
|
|
717
|
+
typeof substituted.concurrency === "string"
|
|
718
|
+
? { concurrency: substituted.concurrency }
|
|
719
|
+
: {}),
|
|
720
|
+
...(typeof substituted.min_successful === "number" ||
|
|
721
|
+
typeof substituted.min_successful === "string"
|
|
722
|
+
? { min_successful: substituted.min_successful }
|
|
723
|
+
: {}),
|
|
714
724
|
...(typeof substituted.label === "string"
|
|
715
725
|
? { label: substituted.label }
|
|
716
726
|
: {}),
|