nolo-cli 0.1.21 → 0.1.22
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/ai/skills/skillDocProtocol.ts +95 -3
- package/client/agentRun.test.ts +50 -2
- package/client/agentRun.ts +56 -0
- package/package.json +1 -1
|
@@ -47,12 +47,30 @@ export interface SkillEvalConfig {
|
|
|
47
47
|
cases: SkillEvalCase[];
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
export interface WorkflowReferenceConfig {
|
|
51
|
+
version: "0.1";
|
|
52
|
+
kind: "workflow";
|
|
53
|
+
id?: string;
|
|
54
|
+
name: string;
|
|
55
|
+
description: string;
|
|
56
|
+
defaultAgent?: string;
|
|
57
|
+
inputs?: string[];
|
|
58
|
+
recommendedTools?: string[];
|
|
59
|
+
requiredTools?: string[];
|
|
60
|
+
requiredOutputs?: string[];
|
|
61
|
+
gates?: string[];
|
|
62
|
+
budgetTier?: SkillBudgetTier;
|
|
63
|
+
contextStrategy?: string;
|
|
64
|
+
failureProtocol?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
50
67
|
export interface PageSkillMetadata {
|
|
51
68
|
kind?: SkillDocKind;
|
|
52
69
|
requiredSkills?: string[];
|
|
53
70
|
recommendedSkills?: string[];
|
|
54
71
|
skillConfig?: SkillDocConfig;
|
|
55
72
|
evalConfig?: SkillEvalConfig;
|
|
73
|
+
workflowConfig?: WorkflowReferenceConfig;
|
|
56
74
|
}
|
|
57
75
|
|
|
58
76
|
export interface ParsedSkillDocProtocol {
|
|
@@ -71,6 +89,7 @@ export interface ParsedExternalSkillMarkdown {
|
|
|
71
89
|
|
|
72
90
|
const SKILL_CONFIG_BLOCK = "skill-config";
|
|
73
91
|
const EVAL_CONFIG_BLOCK = "eval-config";
|
|
92
|
+
const WORKFLOW_CONFIG_BLOCK = "workflow-config";
|
|
74
93
|
|
|
75
94
|
const normalizeStringArray = (value: unknown): string[] | undefined => {
|
|
76
95
|
if (!Array.isArray(value)) return undefined;
|
|
@@ -200,6 +219,49 @@ const normalizeEvalConfig = (value: unknown): SkillEvalConfig | undefined => {
|
|
|
200
219
|
: undefined;
|
|
201
220
|
};
|
|
202
221
|
|
|
222
|
+
const normalizeWorkflowConfig = (value: unknown): WorkflowReferenceConfig | undefined => {
|
|
223
|
+
if (!value || typeof value !== "object") return undefined;
|
|
224
|
+
const record = value as Record<string, unknown>;
|
|
225
|
+
const name =
|
|
226
|
+
typeof record.name === "string" && record.name.trim()
|
|
227
|
+
? record.name.trim()
|
|
228
|
+
: "";
|
|
229
|
+
const description =
|
|
230
|
+
typeof record.description === "string" && record.description.trim()
|
|
231
|
+
? record.description.trim()
|
|
232
|
+
: "";
|
|
233
|
+
if (!name || !description) return undefined;
|
|
234
|
+
const budgetTier = normalizeSkillEnumValue("budgetTier", record.budgetTier);
|
|
235
|
+
return {
|
|
236
|
+
version: "0.1",
|
|
237
|
+
kind: "workflow",
|
|
238
|
+
id:
|
|
239
|
+
typeof record.id === "string" && record.id.trim()
|
|
240
|
+
? record.id.trim()
|
|
241
|
+
: undefined,
|
|
242
|
+
name,
|
|
243
|
+
description,
|
|
244
|
+
defaultAgent:
|
|
245
|
+
typeof record.defaultAgent === "string" && record.defaultAgent.trim()
|
|
246
|
+
? record.defaultAgent.trim()
|
|
247
|
+
: undefined,
|
|
248
|
+
inputs: normalizeStringArray(record.inputs),
|
|
249
|
+
recommendedTools: normalizeStringArray(record.recommendedTools),
|
|
250
|
+
requiredTools: normalizeStringArray(record.requiredTools),
|
|
251
|
+
requiredOutputs: normalizeStringArray(record.requiredOutputs),
|
|
252
|
+
gates: normalizeStringArray(record.gates),
|
|
253
|
+
budgetTier,
|
|
254
|
+
contextStrategy:
|
|
255
|
+
typeof record.contextStrategy === "string" && record.contextStrategy.trim()
|
|
256
|
+
? record.contextStrategy.trim()
|
|
257
|
+
: undefined,
|
|
258
|
+
failureProtocol:
|
|
259
|
+
typeof record.failureProtocol === "string" && record.failureProtocol.trim()
|
|
260
|
+
? record.failureProtocol.trim()
|
|
261
|
+
: undefined,
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
|
|
203
265
|
const normalizePageSkillMetadata = (
|
|
204
266
|
value: unknown,
|
|
205
267
|
fallbackTools?: string[]
|
|
@@ -214,17 +276,22 @@ const normalizePageSkillMetadata = (
|
|
|
214
276
|
recommendedSkills: normalizeStringArray(record.recommendedSkills),
|
|
215
277
|
skillConfig: normalizeSkillConfig(record.skillConfig, fallbackTools),
|
|
216
278
|
evalConfig: normalizeEvalConfig(record.evalConfig),
|
|
279
|
+
workflowConfig: normalizeWorkflowConfig(record.workflowConfig),
|
|
217
280
|
};
|
|
218
281
|
|
|
219
282
|
if (meta.skillConfig && !meta.kind) {
|
|
220
283
|
meta.kind = "skill";
|
|
221
284
|
}
|
|
285
|
+
if (meta.workflowConfig && !meta.kind) {
|
|
286
|
+
meta.kind = "instruction";
|
|
287
|
+
}
|
|
222
288
|
|
|
223
289
|
return meta.kind ||
|
|
224
290
|
meta.requiredSkills ||
|
|
225
291
|
meta.recommendedSkills ||
|
|
226
292
|
meta.skillConfig ||
|
|
227
|
-
meta.evalConfig
|
|
293
|
+
meta.evalConfig ||
|
|
294
|
+
meta.workflowConfig
|
|
228
295
|
? meta
|
|
229
296
|
: undefined;
|
|
230
297
|
};
|
|
@@ -268,9 +335,13 @@ export const parseSkillDocProtocol = (
|
|
|
268
335
|
const source = typeof markdown === "string" ? markdown : "";
|
|
269
336
|
const skillBlock = parseYamlObject(extractCommentBlock(source, SKILL_CONFIG_BLOCK));
|
|
270
337
|
const evalBlock = parseYamlObject(extractCommentBlock(source, EVAL_CONFIG_BLOCK));
|
|
338
|
+
const workflowBlock = parseYamlObject(extractCommentBlock(source, WORKFLOW_CONFIG_BLOCK));
|
|
271
339
|
const cleanedContent = removeCommentBlock(
|
|
272
|
-
removeCommentBlock(
|
|
273
|
-
|
|
340
|
+
removeCommentBlock(
|
|
341
|
+
removeCommentBlock(source, SKILL_CONFIG_BLOCK),
|
|
342
|
+
EVAL_CONFIG_BLOCK
|
|
343
|
+
),
|
|
344
|
+
WORKFLOW_CONFIG_BLOCK
|
|
274
345
|
)
|
|
275
346
|
.replace(/\n{3,}/g, "\n\n")
|
|
276
347
|
.trim();
|
|
@@ -292,6 +363,7 @@ export const parseSkillDocProtocol = (
|
|
|
292
363
|
}
|
|
293
364
|
: {}),
|
|
294
365
|
...(evalBlock ? { evalConfig: evalBlock } : {}),
|
|
366
|
+
...(workflowBlock ? { workflowConfig: workflowBlock } : {}),
|
|
295
367
|
},
|
|
296
368
|
fallbackTools
|
|
297
369
|
);
|
|
@@ -364,15 +436,35 @@ export const buildEvalConfigComment = (config: SkillEvalConfig): string =>
|
|
|
364
436
|
cases: config.cases,
|
|
365
437
|
})}\n-->`;
|
|
366
438
|
|
|
439
|
+
export const buildWorkflowConfigComment = (config: WorkflowReferenceConfig): string =>
|
|
440
|
+
`<!-- ${WORKFLOW_CONFIG_BLOCK}\n${yamlBlock({
|
|
441
|
+
version: config.version,
|
|
442
|
+
kind: config.kind,
|
|
443
|
+
...(config.id ? { id: config.id } : {}),
|
|
444
|
+
name: config.name,
|
|
445
|
+
description: config.description,
|
|
446
|
+
...(config.defaultAgent ? { defaultAgent: config.defaultAgent } : {}),
|
|
447
|
+
...(config.inputs?.length ? { inputs: config.inputs } : {}),
|
|
448
|
+
...(config.recommendedTools?.length ? { recommendedTools: config.recommendedTools } : {}),
|
|
449
|
+
...(config.requiredTools?.length ? { requiredTools: config.requiredTools } : {}),
|
|
450
|
+
...(config.requiredOutputs?.length ? { requiredOutputs: config.requiredOutputs } : {}),
|
|
451
|
+
...(config.gates?.length ? { gates: config.gates } : {}),
|
|
452
|
+
...(config.budgetTier ? { budgetTier: config.budgetTier } : {}),
|
|
453
|
+
...(config.contextStrategy ? { contextStrategy: config.contextStrategy } : {}),
|
|
454
|
+
...(config.failureProtocol ? { failureProtocol: config.failureProtocol } : {}),
|
|
455
|
+
})}\n-->`;
|
|
456
|
+
|
|
367
457
|
export const buildSkillDocMarkdown = (options: {
|
|
368
458
|
body?: string;
|
|
369
459
|
skillConfig: SkillDocConfig;
|
|
370
460
|
evalConfig?: SkillEvalConfig;
|
|
461
|
+
workflowConfig?: WorkflowReferenceConfig;
|
|
371
462
|
}): string => {
|
|
372
463
|
const sections = [
|
|
373
464
|
options.body?.trim() || "",
|
|
374
465
|
buildSkillConfigComment(options.skillConfig),
|
|
375
466
|
options.evalConfig ? buildEvalConfigComment(options.evalConfig) : "",
|
|
467
|
+
options.workflowConfig ? buildWorkflowConfigComment(options.workflowConfig) : "",
|
|
376
468
|
].filter(Boolean);
|
|
377
469
|
return sections.join("\n\n").trim();
|
|
378
470
|
};
|
package/client/agentRun.test.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
runAgentTurn,
|
|
8
8
|
shouldUseScriptBridge,
|
|
9
9
|
} from "./agentRun";
|
|
10
|
+
import { NOLO_PROJECT_MANAGER_AGENT_KEY } from "../agentAliases";
|
|
10
11
|
|
|
11
12
|
class CaptureOutput extends Writable {
|
|
12
13
|
chunks: string[] = [];
|
|
@@ -571,7 +572,7 @@ describe("cli agent run client", () => {
|
|
|
571
572
|
|
|
572
573
|
const result = await runAgentTurn({
|
|
573
574
|
agentName: "pm",
|
|
574
|
-
agentKey: "
|
|
575
|
+
agentKey: "agent-custom-platform-tools",
|
|
575
576
|
serverUrl: "https://nolo.chat",
|
|
576
577
|
message: "write task rows",
|
|
577
578
|
scriptDir: "C:/missing/scripts",
|
|
@@ -608,13 +609,60 @@ describe("cli agent run client", () => {
|
|
|
608
609
|
|
|
609
610
|
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-server" });
|
|
610
611
|
expect(httpCalls).toHaveLength(1);
|
|
611
|
-
expect(httpCalls[0]?.body.agentKey).toBe("
|
|
612
|
+
expect(httpCalls[0]?.body.agentKey).toBe("agent-custom-platform-tools");
|
|
612
613
|
expect(output.text()).toContain("auto runtime: skipping local runtime");
|
|
613
614
|
expect(output.text()).toContain("queryTableRows, addTableRow, updateTableRow");
|
|
614
615
|
expect(output.text()).toContain("pm -> working");
|
|
615
616
|
expect(output.text()).toContain("pm > server ok");
|
|
616
617
|
});
|
|
617
618
|
|
|
619
|
+
test("auto mode skips known platform agents when local config cannot be read", async () => {
|
|
620
|
+
const output = new CaptureOutput();
|
|
621
|
+
const httpCalls: Array<{ url: string; body: any }> = [];
|
|
622
|
+
|
|
623
|
+
const result = await runAgentTurn({
|
|
624
|
+
agentName: "nolo-project-manager",
|
|
625
|
+
agentKey: NOLO_PROJECT_MANAGER_AGENT_KEY,
|
|
626
|
+
serverUrl: "https://us.nolo.chat",
|
|
627
|
+
message: "write task rows",
|
|
628
|
+
scriptDir: "C:/missing/scripts",
|
|
629
|
+
env: { AUTH_TOKEN: "token-123", OPENAI_API_KEY: "local-provider-present" },
|
|
630
|
+
output,
|
|
631
|
+
runtimeMode: "auto",
|
|
632
|
+
localRuntimeAdapter: {
|
|
633
|
+
host: "cli",
|
|
634
|
+
capabilities: ["leveldb-agent-config", "local-provider", "local-tools"],
|
|
635
|
+
loadAgentConfig: async () => {
|
|
636
|
+
throw new Error("Database failed to open: LOCK");
|
|
637
|
+
},
|
|
638
|
+
loadDialogHistory: async () => {
|
|
639
|
+
throw new Error("local runtime should be skipped");
|
|
640
|
+
},
|
|
641
|
+
saveTurn: async () => {
|
|
642
|
+
throw new Error("local runtime should be skipped");
|
|
643
|
+
},
|
|
644
|
+
resolveProvider: async () => {
|
|
645
|
+
throw new Error("local provider should be skipped");
|
|
646
|
+
},
|
|
647
|
+
executeTool: async () => {
|
|
648
|
+
throw new Error("local tools should be skipped");
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
scriptPathExists: () => false,
|
|
652
|
+
fetchImpl: async (url, init) => {
|
|
653
|
+
httpCalls.push({ url: String(url), body: JSON.parse(String(init?.body)) });
|
|
654
|
+
return Response.json({ content: "server ok", dialogId: "dialog-server" });
|
|
655
|
+
},
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-server" });
|
|
659
|
+
expect(httpCalls).toHaveLength(1);
|
|
660
|
+
expect(httpCalls[0]?.body.agentKey).toBe(NOLO_PROJECT_MANAGER_AGENT_KEY);
|
|
661
|
+
expect(output.text()).toContain("known platform agent");
|
|
662
|
+
expect(output.text()).toContain("nolo-project-manager -> working");
|
|
663
|
+
expect(output.text()).toContain("nolo-project-manager > server ok");
|
|
664
|
+
});
|
|
665
|
+
|
|
618
666
|
test("builds the default local adapter when env requests local mode", async () => {
|
|
619
667
|
const output = new CaptureOutput();
|
|
620
668
|
const builtModes: string[] = [];
|
package/client/agentRun.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { runLocalAgentTurn } from "../agentRuntimeLocal";
|
|
4
|
+
import {
|
|
5
|
+
FRONTEND_IMPLEMENTER_AGENT_KEY,
|
|
6
|
+
NOLO_FULLSTACK_AGENT_KEY,
|
|
7
|
+
NOLO_PROJECT_MANAGER_AGENT_KEY,
|
|
8
|
+
NOLO_REVIEW_AGENT_KEY,
|
|
9
|
+
NOLO_SENIOR_FULLSTACK_AGENT_KEY,
|
|
10
|
+
} from "../agentAliases";
|
|
4
11
|
import type { LocalAgentToolEvent } from "../agent-runtime/localLoop";
|
|
5
12
|
import type { AgentRuntimeHostAdapter, AgentRuntimeRequestedMode } from "../agentRuntimeLocal";
|
|
6
13
|
import { createCliLocalRuntimeAdapter } from "./localRuntimeAdapter";
|
|
@@ -84,6 +91,38 @@ const SERVER_PLATFORM_TOOL_NAMES = new Set([
|
|
|
84
91
|
"updateTableRows",
|
|
85
92
|
]);
|
|
86
93
|
|
|
94
|
+
const KNOWN_SERVER_PLATFORM_AGENT_KEYS = new Set([
|
|
95
|
+
FRONTEND_IMPLEMENTER_AGENT_KEY,
|
|
96
|
+
NOLO_FULLSTACK_AGENT_KEY,
|
|
97
|
+
NOLO_PROJECT_MANAGER_AGENT_KEY,
|
|
98
|
+
NOLO_REVIEW_AGENT_KEY,
|
|
99
|
+
NOLO_SENIOR_FULLSTACK_AGENT_KEY,
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
const KNOWN_SERVER_PLATFORM_AGENT_ALIASES = new Set([
|
|
103
|
+
"code-review",
|
|
104
|
+
"frontend",
|
|
105
|
+
"frontend-agent",
|
|
106
|
+
"frontend-implementer",
|
|
107
|
+
"full-stack",
|
|
108
|
+
"fullstack",
|
|
109
|
+
"nolo code review",
|
|
110
|
+
"nolo frontend",
|
|
111
|
+
"nolo fullstack",
|
|
112
|
+
"nolo project manager",
|
|
113
|
+
"nolo reviewer",
|
|
114
|
+
"nolo-code-review",
|
|
115
|
+
"nolo-frontend",
|
|
116
|
+
"nolo-fullstack",
|
|
117
|
+
"nolo-pm",
|
|
118
|
+
"nolo-project-manager",
|
|
119
|
+
"nolo-reviewer",
|
|
120
|
+
"pm",
|
|
121
|
+
"project-manager",
|
|
122
|
+
"review",
|
|
123
|
+
"reviewer",
|
|
124
|
+
]);
|
|
125
|
+
|
|
87
126
|
export function shouldUseScriptBridge(decision: ScriptBridgeDecision) {
|
|
88
127
|
return !decision.hasAuthToken && decision.scriptPathExists;
|
|
89
128
|
}
|
|
@@ -92,6 +131,16 @@ export function findServerPlatformTools(toolNames?: string[]) {
|
|
|
92
131
|
if (!Array.isArray(toolNames)) return [];
|
|
93
132
|
return toolNames.filter((toolName) => SERVER_PLATFORM_TOOL_NAMES.has(toolName));
|
|
94
133
|
}
|
|
134
|
+
|
|
135
|
+
function normalizeAgentRef(ref?: string) {
|
|
136
|
+
return ref?.trim().toLowerCase().replace(/\s+/g, " ");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function isKnownServerPlatformAgent(options: RunAgentTurnOptions) {
|
|
140
|
+
if (KNOWN_SERVER_PLATFORM_AGENT_KEYS.has(options.agentKey)) return true;
|
|
141
|
+
const normalizedKey = normalizeAgentRef(options.agentKey);
|
|
142
|
+
return Boolean(normalizedKey && KNOWN_SERVER_PLATFORM_AGENT_ALIASES.has(normalizedKey));
|
|
143
|
+
}
|
|
95
144
|
|
|
96
145
|
function resolveAuthToken(env: EnvLike) {
|
|
97
146
|
return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
|
|
@@ -126,6 +175,13 @@ function resolveLocalRuntimeAdapter(options: RunAgentTurnOptions) {
|
|
|
126
175
|
}
|
|
127
176
|
|
|
128
177
|
async function shouldSkipAutoLocalForServerPlatformTools(options: RunAgentTurnOptions) {
|
|
178
|
+
if (isKnownServerPlatformAgent(options)) {
|
|
179
|
+
options.output.write(
|
|
180
|
+
`[nolo] auto runtime: skipping local runtime because ${options.agentKey} is a known platform agent. ` +
|
|
181
|
+
"Use --local explicitly to force local workspace tools.\n"
|
|
182
|
+
);
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
129
185
|
const adapter = resolveLocalRuntimeAdapter(options);
|
|
130
186
|
if (!adapter) return false;
|
|
131
187
|
let agentConfig;
|