killeros 2.0.8 → 2.0.10

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.
@@ -19,7 +19,10 @@ export interface WorkflowPolicy {
19
19
 
20
20
  export interface WorkflowAdapter {
21
21
  id: string;
22
- activation: string;
22
+ /** The existing single explicit skill activation API. */
23
+ activation?: string;
24
+ /** Additional explicit skill activations that share this adapter policy. */
25
+ activations?: readonly string[];
23
26
  question: QuestionParamsValue;
24
27
  policies: readonly WorkflowPolicy[];
25
28
  selectPolicy: (details: QuestionDetails) => WorkflowPolicy | undefined;
@@ -48,11 +51,14 @@ type InternalState =
48
51
  | {
49
52
  kind: "pending_decision";
50
53
  adapter: WorkflowAdapter;
54
+ activation: string;
51
55
  abortController: AbortController;
52
56
  token: symbol;
53
57
  }
54
- | { kind: "active"; adapter: WorkflowAdapter; policy: WorkflowPolicy; token: symbol }
55
- | { kind: "terminal_cleanup"; adapter: WorkflowAdapter; reason: WorkflowTerminalReason; token: symbol };
58
+ | { kind: "active"; adapter: WorkflowAdapter; activation: string; policy: WorkflowPolicy; token: symbol }
59
+ | { kind: "terminal_cleanup"; adapter: WorkflowAdapter; activation: string; reason: WorkflowTerminalReason; token: symbol };
60
+
61
+ const ACTIVATION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/u;
56
62
 
57
63
  function explicitSkillActivation(text: string): string | undefined {
58
64
  if (!text.startsWith("/skill:")) return;
@@ -76,21 +82,21 @@ function publicState(state: InternalState): WorkflowGateState {
76
82
  return {
77
83
  kind: state.kind,
78
84
  adapterId: state.adapter.id,
79
- activation: state.adapter.activation,
85
+ activation: state.activation,
80
86
  };
81
87
  }
82
88
  if (state.kind === "terminal_cleanup") {
83
89
  return {
84
90
  kind: state.kind,
85
91
  adapterId: state.adapter.id,
86
- activation: state.adapter.activation,
92
+ activation: state.activation,
87
93
  reason: state.reason,
88
94
  };
89
95
  }
90
96
  return {
91
97
  kind: state.kind,
92
98
  adapterId: state.adapter.id,
93
- activation: state.adapter.activation,
99
+ activation: state.activation,
94
100
  policyId: state.policy.id,
95
101
  };
96
102
  }
@@ -141,14 +147,26 @@ export function registerWorkflowGate(
141
147
  const adaptersByActivation = new Map<string, WorkflowAdapter>();
142
148
  for (const adapter of adapters) {
143
149
  if (!adapter.id.trim()) throw new Error("Decision-gated workflow adapters require an id");
144
- if (!/^[-A-Za-z0-9._]+$/u.test(adapter.activation)) {
145
- throw new Error(`Invalid decision-gated workflow activation: ${adapter.activation}`);
150
+ if (adapter.activations !== undefined && !Array.isArray(adapter.activations)) {
151
+ throw new Error(`Workflow adapter ${adapter.id} activations must be an array`);
146
152
  }
147
- if (adaptersByActivation.has(adapter.activation)) {
148
- throw new Error(`Duplicate decision-gated workflow activation: ${adapter.activation}`);
153
+ const activations = [
154
+ ...(adapter.activation === undefined ? [] : [adapter.activation]),
155
+ ...(adapter.activations ?? []),
156
+ ];
157
+ if (activations.length === 0) {
158
+ throw new Error(`Workflow adapter ${adapter.id} must register at least one explicit skill activation`);
159
+ }
160
+ for (const activation of activations) {
161
+ if (typeof activation !== "string" || !ACTIVATION_PATTERN.test(activation)) {
162
+ throw new Error(`Invalid decision-gated workflow activation: ${activation}`);
163
+ }
164
+ if (adaptersByActivation.has(activation)) {
165
+ throw new Error(`Duplicate decision-gated workflow activation: ${activation}`);
166
+ }
167
+ adaptersByActivation.set(activation, adapter);
149
168
  }
150
169
  if (adapter.policies.length === 0) throw new Error(`Workflow adapter ${adapter.id} has no policies`);
151
- adaptersByActivation.set(adapter.activation, adapter);
152
170
  }
153
171
 
154
172
  let state: InternalState = { kind: "inactive" };
@@ -160,6 +178,7 @@ export function registerWorkflowGate(
160
178
  state = {
161
179
  kind: "terminal_cleanup",
162
180
  adapter: current.adapter,
181
+ activation: current.activation,
163
182
  reason,
164
183
  token: current.token,
165
184
  };
@@ -204,13 +223,12 @@ export function registerWorkflowGate(
204
223
  };
205
224
 
206
225
  pi.on("input", async (event, ctx) => {
207
- const activation = explicitSkillActivation(event.text);
208
- if (!activation) return;
209
-
210
- if (state.kind === "pending_decision" || state.kind === "terminal_cleanup") {
226
+ if ((state.kind === "pending_decision" || state.kind === "terminal_cleanup") && event.text.startsWith("/skill:")) {
211
227
  notify(ctx, "A decision-gated workflow is waiting for its structured question; skill routing is blocked", "warning");
212
228
  return { action: "handled" };
213
229
  }
230
+ const activation = explicitSkillActivation(event.text);
231
+ if (!activation) return;
214
232
 
215
233
  const adapter = adaptersByActivation.get(activation);
216
234
  if (!adapter) return;
@@ -230,7 +248,7 @@ export function registerWorkflowGate(
230
248
 
231
249
  const abortController = new AbortController();
232
250
  const token = Symbol();
233
- state = { kind: "pending_decision", adapter, abortController, token };
251
+ state = { kind: "pending_decision", adapter, activation, abortController, token };
234
252
  let details: QuestionDetails;
235
253
  try {
236
254
  details = await questionRunner.ask(adapter.question, abortController.signal, ctx);
@@ -275,7 +293,7 @@ export function registerWorkflowGate(
275
293
  if (state.kind !== "pending_decision" || state.adapter !== adapter || state.token !== token) {
276
294
  return { action: "handled" };
277
295
  }
278
- state = { kind: "active", adapter, policy, token };
296
+ state = { kind: "active", adapter, activation, policy, token };
279
297
  return { action: "continue" };
280
298
  });
281
299
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "killeros",
3
- "version": "2.0.8",
3
+ "version": "2.0.10",
4
4
  "description": "TUI, goals, and workflow automation for the Pi coding agent",
5
5
  "type": "module",
6
6
  "keywords": [