deepclause-pi 0.3.0 → 0.5.0
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 +4 -4
- package/dist/config.d.ts +22 -0
- package/dist/config.js +60 -0
- package/dist/diagram/extract.d.ts +2 -0
- package/dist/diagram/extract.js +223 -3
- package/dist/diagram/grade.d.ts +1 -1
- package/dist/diagram/grade.js +4 -4
- package/dist/index.d.ts +10 -0
- package/dist/index.js +204 -86
- package/dist/runtime.d.ts +3 -1
- package/dist/runtime.js +23 -1
- package/docs/SPECKIT.md +10 -0
- package/package.json +2 -2
- package/skills/handbook-dml/SKILL.md +174 -46
- package/src/assets/AGENTS.md +43 -7
- package/src/assets/apply.dml +2 -2
- package/src/assets/specs.dml +30 -26
- package/src/config.ts +91 -0
- package/src/diagram/extract.ts +225 -3
- package/src/diagram/grade.ts +4 -4
- package/src/index.ts +224 -97
- package/src/runtime.ts +30 -2
package/src/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { StringEnum } from "@earendil-works/pi-ai";
|
|
|
6
6
|
import type { DMLEvent } from "deepclause-sdk";
|
|
7
7
|
import { Type } from "typebox";
|
|
8
8
|
import { buildInitialMessages } from "./context.js";
|
|
9
|
-
import { loadConfig, setModelToolEnabled, type ContextMode } from "./config.js";
|
|
9
|
+
import { loadConfig, setJudgeConfig, setModelToolEnabled, type ContextMode, type DeepClauseConfig } from "./config.js";
|
|
10
10
|
import { renderDml, renderSequence } from "./diagram/extract.js";
|
|
11
11
|
import { polishDiagram, resolveGrade, type DiagramGrade } from "./diagram/grade.js";
|
|
12
12
|
import { findChrome, validateMermaid, type MermaidView } from "./diagram/validate.js";
|
|
@@ -57,6 +57,7 @@ export interface ParsedRun {
|
|
|
57
57
|
contextMode?: ContextMode;
|
|
58
58
|
verbose: boolean;
|
|
59
59
|
debug: boolean;
|
|
60
|
+
judge?: string;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
interface ParsedPlan {
|
|
@@ -125,11 +126,13 @@ export function parseRun(input: string): ParsedRun {
|
|
|
125
126
|
let contextMode: ContextMode | undefined;
|
|
126
127
|
let verbose = false;
|
|
127
128
|
let debug = false;
|
|
129
|
+
let judge: string | undefined;
|
|
128
130
|
const args: string[] = [];
|
|
129
131
|
|
|
130
132
|
for (let index = 0; index < tokens.length; index++) {
|
|
131
133
|
const token = tokens[index]!;
|
|
132
134
|
const contextValue = token.startsWith("--context=") ? token.slice("--context=".length) : undefined;
|
|
135
|
+
const judgeValue = token.startsWith("--judge=") ? token.slice("--judge=".length) : undefined;
|
|
133
136
|
if (token === "--verbose" || token === "-v") {
|
|
134
137
|
verbose = true;
|
|
135
138
|
} else if (token === "--debug" || token === "-d") {
|
|
@@ -146,11 +149,18 @@ export function parseRun(input: string): ParsedRun {
|
|
|
146
149
|
throw new Error("--context must be turn, branch, or isolated");
|
|
147
150
|
}
|
|
148
151
|
contextMode = value;
|
|
152
|
+
} else if (judgeValue !== undefined) {
|
|
153
|
+
if (!judgeValue.trim()) throw new Error("--judge requires a non-empty backend name");
|
|
154
|
+
judge = judgeValue.trim();
|
|
155
|
+
} else if (token === "--judge") {
|
|
156
|
+
const value = tokens[++index];
|
|
157
|
+
if (!value || !value.trim()) throw new Error("--judge requires a non-empty backend name");
|
|
158
|
+
judge = value.trim();
|
|
149
159
|
} else {
|
|
150
160
|
args.push(token);
|
|
151
161
|
}
|
|
152
162
|
}
|
|
153
|
-
return { target, args, contextMode, verbose, debug };
|
|
163
|
+
return { target, args, contextMode, verbose, debug, judge };
|
|
154
164
|
}
|
|
155
165
|
|
|
156
166
|
export function parsePlan(input: string): ParsedPlan {
|
|
@@ -288,6 +298,18 @@ function modelLabel(ctx: ExtensionCommandContext): string {
|
|
|
288
298
|
return ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "none selected";
|
|
289
299
|
}
|
|
290
300
|
|
|
301
|
+
function judgeBackendsEnabled(config: DeepClauseConfig): string {
|
|
302
|
+
return config.judgment.jev.enabled ? ", jev" : "";
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function jevStatus(config: DeepClauseConfig): string {
|
|
306
|
+
const jev = config.judgment.jev;
|
|
307
|
+
if (!jev.enabled) return "disabled";
|
|
308
|
+
return process.env[jev.apiKeyEnv]
|
|
309
|
+
? `enabled (${jev.model})`
|
|
310
|
+
: `enabled but ${jev.apiKeyEnv} is not set`;
|
|
311
|
+
}
|
|
312
|
+
|
|
291
313
|
async function bundledViewerTemplate(): Promise<string> {
|
|
292
314
|
return readFile(fileURLToPath(new URL("./assets/viewer.template.html", import.meta.url)), "utf8");
|
|
293
315
|
}
|
|
@@ -300,6 +322,24 @@ function publishResult(pi: ExtensionAPI, content: string, details: Record<string
|
|
|
300
322
|
pi.sendMessage({ customType: "deepclause-result", content, display: true, details });
|
|
301
323
|
}
|
|
302
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Ask the user a DeepClause question.
|
|
327
|
+
*
|
|
328
|
+
* Pi's interactive text-input dialog renders only its title: the placeholder
|
|
329
|
+
* argument is ignored by `ExtensionInputComponent`. Passing the question as the
|
|
330
|
+
* placeholder (as the extension used to) made it invisible, so put the whole
|
|
331
|
+
* question in the title and label which run is asking.
|
|
332
|
+
*/
|
|
333
|
+
export function requestDeepClauseInput(
|
|
334
|
+
ctx: Pick<ExtensionContext, "ui">,
|
|
335
|
+
label: string,
|
|
336
|
+
prompt: string,
|
|
337
|
+
signal: AbortSignal,
|
|
338
|
+
): Promise<string | undefined> {
|
|
339
|
+
const heading = label ? `DeepClause input — ${label}` : "DeepClause input";
|
|
340
|
+
return ctx.ui.input(`${heading}\n\n${prompt}`, undefined, { signal });
|
|
341
|
+
}
|
|
342
|
+
|
|
303
343
|
export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
304
344
|
let activeController: AbortController | undefined;
|
|
305
345
|
let activeDescription: string | undefined;
|
|
@@ -310,6 +350,27 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
310
350
|
let planningTransaction: PlanningTransaction | undefined;
|
|
311
351
|
let pendingAgentStep: PendingAgentStep | undefined;
|
|
312
352
|
|
|
353
|
+
/**
|
|
354
|
+
* Claim the single DeepClause execution slot synchronously, before any await.
|
|
355
|
+
* Without this, parallel `dc_run` calls all pass the `activeController` check
|
|
356
|
+
* while the first one is still awaiting its setup, then run concurrently and
|
|
357
|
+
* fight over the one-slot pi input dialog (leaving earlier runs hung).
|
|
358
|
+
*/
|
|
359
|
+
const claimExecution = (description: string): AbortController | undefined => {
|
|
360
|
+
if (activeController) return undefined;
|
|
361
|
+
const controller = new AbortController();
|
|
362
|
+
activeController = controller;
|
|
363
|
+
activeDescription = description;
|
|
364
|
+
return controller;
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const releaseExecution = (controller: AbortController): void => {
|
|
368
|
+
if (activeController === controller) {
|
|
369
|
+
activeController = undefined;
|
|
370
|
+
activeDescription = undefined;
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
|
|
313
374
|
const setPlanCommitActive = (enabled: boolean) => {
|
|
314
375
|
if (enabled && !planCommitRegistered) {
|
|
315
376
|
pi.registerTool({
|
|
@@ -506,6 +567,7 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
506
567
|
promptGuidelines: [
|
|
507
568
|
"Use dc_run only for existing DML programs when their deterministic logic, constraints, or specialized orchestration is useful; do not use dc_run to compile natural language or create a skill.",
|
|
508
569
|
"Do not call dc_run while another DeepClause execution is active, and do not claim success unless dc_run returns an answer without errors.",
|
|
570
|
+
"If dc_run reports execution_already_active, wait for the active run to finish and then retry this skill instead of reporting failure.",
|
|
509
571
|
],
|
|
510
572
|
parameters: Type.Object({
|
|
511
573
|
skill: Type.String({ description: "Skill name such as example, or a DML path relative to .pi/deepclause/." }),
|
|
@@ -515,46 +577,45 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
515
577
|
})),
|
|
516
578
|
}),
|
|
517
579
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
518
|
-
|
|
580
|
+
const controller = claimExecution("dc_run");
|
|
581
|
+
if (!controller) {
|
|
519
582
|
return {
|
|
520
|
-
content: [{ type: "text", text: "DeepClause execution rejected: another execution is
|
|
583
|
+
content: [{ type: "text", text: "DeepClause execution rejected: another execution is active. Wait for it to finish, then call dc_run again for this skill." }],
|
|
521
584
|
details: { success: false, error: "execution_already_active" },
|
|
522
585
|
};
|
|
523
586
|
}
|
|
524
|
-
|
|
525
|
-
const paths = await initializeWorkspace(ctx.cwd);
|
|
526
|
-
const config = await loadConfig(paths.config);
|
|
527
|
-
if (!config.modelToolEnabled || !pi.getActiveTools().includes(DC_RUN_TOOL)) {
|
|
528
|
-
return {
|
|
529
|
-
content: [{ type: "text", text: "The dc_run tool is disabled. The user can enable it with /dc-tool enable." }],
|
|
530
|
-
details: { success: false, error: "tool_disabled" },
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
const mode = params.context ?? config.contextMode;
|
|
535
|
-
const filePath = await resolveDmlPath(paths, params.skill);
|
|
536
|
-
if (await isContextualPlan(filePath)) {
|
|
537
|
-
return {
|
|
538
|
-
content: [{ type: "text", text: "Contextual DML plans must be started by the user with /dc-run; they cannot start a nested pi agent turn from dc_run." }],
|
|
539
|
-
details: { success: false, error: "interactive_plan_requires_user_run" },
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
const skillName = path.relative(paths.root, filePath);
|
|
543
|
-
const initialMessages = buildInitialMessages(
|
|
544
|
-
ctx.sessionManager.getBranch(),
|
|
545
|
-
mode,
|
|
546
|
-
config.branchMessageLimit,
|
|
547
|
-
);
|
|
548
|
-
const controller = new AbortController();
|
|
549
587
|
const cancel = () => controller.abort(signal?.reason ?? new Error("dc_run cancelled"));
|
|
550
588
|
if (signal?.aborted) cancel();
|
|
551
589
|
else signal?.addEventListener("abort", cancel, { once: true });
|
|
552
|
-
activeController = controller;
|
|
553
|
-
activeDescription = `model tool running ${skillName}`;
|
|
554
|
-
const startedAt = Date.now();
|
|
555
|
-
const progress: string[] = [];
|
|
556
590
|
|
|
557
591
|
try {
|
|
592
|
+
const paths = await initializeWorkspace(ctx.cwd);
|
|
593
|
+
const config = await loadConfig(paths.config);
|
|
594
|
+
if (!config.modelToolEnabled || !pi.getActiveTools().includes(DC_RUN_TOOL)) {
|
|
595
|
+
return {
|
|
596
|
+
content: [{ type: "text", text: "The dc_run tool is disabled. The user can enable it with /dc-tool enable." }],
|
|
597
|
+
details: { success: false, error: "tool_disabled" },
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const mode = params.context ?? config.contextMode;
|
|
602
|
+
const filePath = await resolveDmlPath(paths, params.skill);
|
|
603
|
+
if (await isContextualPlan(filePath)) {
|
|
604
|
+
return {
|
|
605
|
+
content: [{ type: "text", text: "Contextual DML plans must be started by the user with /dc-run; they cannot start a nested pi agent turn from dc_run." }],
|
|
606
|
+
details: { success: false, error: "interactive_plan_requires_user_run" },
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
const skillName = path.relative(paths.root, filePath);
|
|
610
|
+
activeDescription = `model tool running ${skillName}`;
|
|
611
|
+
const initialMessages = buildInitialMessages(
|
|
612
|
+
ctx.sessionManager.getBranch(),
|
|
613
|
+
mode,
|
|
614
|
+
config.branchMessageLimit,
|
|
615
|
+
);
|
|
616
|
+
const startedAt = Date.now();
|
|
617
|
+
const progress: string[] = [];
|
|
618
|
+
|
|
558
619
|
const result = await executeDml(
|
|
559
620
|
filePath,
|
|
560
621
|
params.args ?? [],
|
|
@@ -578,7 +639,7 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
578
639
|
onDiagnostic: () => {},
|
|
579
640
|
onInput: async (prompt, inputSignal) => {
|
|
580
641
|
if (!ctx.hasUI) throw new Error("dc_run cannot request user input without interactive UI");
|
|
581
|
-
const answer = await ctx
|
|
642
|
+
const answer = await requestDeepClauseInput(ctx, skillName, prompt, inputSignal);
|
|
582
643
|
if (answer === undefined) throw new Error("Input cancelled");
|
|
583
644
|
return answer;
|
|
584
645
|
},
|
|
@@ -604,12 +665,11 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
604
665
|
const message = error instanceof Error ? error.message : String(error);
|
|
605
666
|
return {
|
|
606
667
|
content: [{ type: "text", text: `DeepClause execution failed: ${message}` }],
|
|
607
|
-
details: { success: false,
|
|
668
|
+
details: { success: false, error: message },
|
|
608
669
|
};
|
|
609
670
|
} finally {
|
|
610
671
|
signal?.removeEventListener("abort", cancel);
|
|
611
|
-
|
|
612
|
-
activeDescription = undefined;
|
|
672
|
+
releaseExecution(controller);
|
|
613
673
|
}
|
|
614
674
|
},
|
|
615
675
|
});
|
|
@@ -630,11 +690,11 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
630
690
|
pi.registerTool({
|
|
631
691
|
name: DC_DIAGRAM_TOOL,
|
|
632
692
|
label: "Create DeepClause Diagram",
|
|
633
|
-
description: "Create a presentation-grade or specification-grade Mermaid diagram from any .dml file, write a self-contained offline viewer under .pi/deepclause/diagrams/, and open it.",
|
|
693
|
+
description: "Create a presentation-grade or specification-grade Mermaid diagram from any .dml file, write a self-contained offline viewer under .pi/deepclause/diagrams/, and open it. The specification grade preserves the core decision logic and rule facts.",
|
|
634
694
|
promptSnippet: "Create a presentation- or specification-grade diagram from a DML file",
|
|
635
695
|
promptGuidelines: [
|
|
636
696
|
"Use dc_diagram whenever the user asks for a diagram, flowchart, or visual of a .dml file; pass the exact path the user named.",
|
|
637
|
-
"Choose grade=presentation for slides and overviews and grade=specification for engineering detail; use grade=both only when the user asks for both.",
|
|
697
|
+
"Choose grade=presentation for slides and overviews and grade=specification for engineering detail (decision predicates, thresholds, rule facts); use grade=both only when the user asks for both.",
|
|
638
698
|
"Do not hand-write Mermaid or run diagram tools yourself; call dc_diagram and report the viewer result.",
|
|
639
699
|
],
|
|
640
700
|
parameters: Type.Object({
|
|
@@ -643,55 +703,51 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
643
703
|
view: Type.Optional(StringEnum(["flow", "sequence"] as const, { description: "Base layout used to seed the grade; default flow." })),
|
|
644
704
|
}),
|
|
645
705
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
646
|
-
|
|
706
|
+
const controller = claimExecution("dc_diagram");
|
|
707
|
+
if (!controller) {
|
|
647
708
|
return {
|
|
648
709
|
content: [{ type: "text", text: "Another DeepClause operation is already active; wait for it to finish." }],
|
|
649
710
|
details: { success: false, error: "execution_already_active" },
|
|
650
711
|
};
|
|
651
712
|
}
|
|
652
|
-
|
|
653
|
-
const requested = resolveGrade(String(params.grade ?? "")) ?? "presentation";
|
|
654
|
-
const grades: DiagramGrade[] = requested === "both" ? ["presentation", "specification"] : [requested];
|
|
655
|
-
const view: MermaidView = params.view === "sequence" ? "sequence" : "flow";
|
|
656
|
-
|
|
657
|
-
let sourcePath: string;
|
|
658
|
-
try {
|
|
659
|
-
sourcePath = await resolveDiagramSource(ctx.cwd, params.dml);
|
|
660
|
-
} catch (error) {
|
|
661
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
662
|
-
return { content: [{ type: "text", text: `dc_diagram failed: ${message}` }], details: { success: false, error: message } };
|
|
663
|
-
}
|
|
664
|
-
if (!ctx.model) {
|
|
665
|
-
return {
|
|
666
|
-
content: [{ type: "text", text: "dc_diagram requires an active pi model. Select one and try again." }],
|
|
667
|
-
details: { success: false, error: "no_model" },
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
const config = await loadConfig(getPaths(ctx.cwd).config);
|
|
672
|
-
const source = await readFile(sourcePath, "utf8");
|
|
673
|
-
const display = displayPath(ctx.cwd, sourcePath);
|
|
674
|
-
const seed = view === "sequence"
|
|
675
|
-
? renderSequence(display, source)
|
|
676
|
-
: renderDml(display, source, { hideOutput: true });
|
|
677
|
-
const targets = await collectDiagramTargets(ctx.cwd, [sourcePath]);
|
|
678
|
-
const name = diagramNameFor(sourcePath, targets, ctx.cwd);
|
|
679
|
-
const { diagrams, vendor } = await ensureDiagramDir(ctx.cwd, viewerVendorAssetPath());
|
|
680
|
-
const templateText = await bundledViewerTemplate();
|
|
681
|
-
|
|
682
|
-
const controller = new AbortController();
|
|
683
713
|
const cancel = () => controller.abort(signal?.reason ?? new Error("dc_diagram cancelled"));
|
|
684
714
|
if (signal?.aborted) cancel();
|
|
685
715
|
else signal?.addEventListener("abort", cancel, { once: true });
|
|
686
|
-
activeController = controller;
|
|
687
|
-
activeDescription = `diagram ${name} (${grades.join("+")})`;
|
|
688
|
-
|
|
689
|
-
const run = (command: string, args: string[], options?: { timeout?: number }) => pi.exec(command, args, options);
|
|
690
|
-
let chrome: string | undefined;
|
|
691
|
-
let chromeResolved = false;
|
|
692
716
|
|
|
693
717
|
try {
|
|
718
|
+
const requested = resolveGrade(String(params.grade ?? "")) ?? "presentation";
|
|
719
|
+
const grades: DiagramGrade[] = requested === "both" ? ["presentation", "specification"] : [requested];
|
|
720
|
+
const view: MermaidView = params.view === "sequence" ? "sequence" : "flow";
|
|
721
|
+
|
|
722
|
+
const sourcePath = await resolveDiagramSource(ctx.cwd, params.dml);
|
|
723
|
+
if (!ctx.model) {
|
|
724
|
+
return {
|
|
725
|
+
content: [{ type: "text", text: "dc_diagram requires an active pi model. Select one and try again." }],
|
|
726
|
+
details: { success: false, error: "no_model" },
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const config = await loadConfig(getPaths(ctx.cwd).config);
|
|
731
|
+
const source = await readFile(sourcePath, "utf8");
|
|
732
|
+
const display = displayPath(ctx.cwd, sourcePath);
|
|
733
|
+
const targets = await collectDiagramTargets(ctx.cwd, [sourcePath]);
|
|
734
|
+
const name = diagramNameFor(sourcePath, targets, ctx.cwd);
|
|
735
|
+
const { diagrams, vendor } = await ensureDiagramDir(ctx.cwd, viewerVendorAssetPath());
|
|
736
|
+
const templateText = await bundledViewerTemplate();
|
|
737
|
+
|
|
738
|
+
activeDescription = `diagram ${name} (${grades.join("+")})`;
|
|
739
|
+
|
|
740
|
+
const run = (command: string, args: string[], options?: { timeout?: number }) => pi.exec(command, args, options);
|
|
741
|
+
let chrome: string | undefined;
|
|
742
|
+
let chromeResolved = false;
|
|
743
|
+
|
|
694
744
|
for (const grade of grades) {
|
|
745
|
+
// The specification seed carries the core decision logic and rule
|
|
746
|
+
// facts; the presentation seed stays small so the model can keep
|
|
747
|
+
// it to a general-audience overview.
|
|
748
|
+
const seed = view === "sequence"
|
|
749
|
+
? renderSequence(display, source)
|
|
750
|
+
: renderDml(display, source, { hideOutput: true, includeLogic: grade === "specification" });
|
|
695
751
|
const result = await polishDiagram({
|
|
696
752
|
grade,
|
|
697
753
|
view,
|
|
@@ -736,8 +792,7 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
736
792
|
return { content: [{ type: "text", text: `dc_diagram failed: ${message}` }], details: { success: false, error: message } };
|
|
737
793
|
} finally {
|
|
738
794
|
signal?.removeEventListener("abort", cancel);
|
|
739
|
-
|
|
740
|
-
activeDescription = undefined;
|
|
795
|
+
releaseExecution(controller);
|
|
741
796
|
}
|
|
742
797
|
},
|
|
743
798
|
});
|
|
@@ -789,7 +844,7 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
789
844
|
const viewer = displayPath(ctx.cwd, build.viewerPath);
|
|
790
845
|
return {
|
|
791
846
|
content: [{ type: "text", text: `Created spec graph (${view}). Viewer: ${viewer}${opened ? " (opened in your browser)" : ""}` }],
|
|
792
|
-
details: { success: true, view, viewer, opened },
|
|
847
|
+
details: { success: true, view, viewer, viewerPath: build.viewerPath, opened },
|
|
793
848
|
};
|
|
794
849
|
} catch (error) {
|
|
795
850
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -811,13 +866,12 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
811
866
|
args: string[] = [],
|
|
812
867
|
options: { verifyCommands?: string[]; piAgentStep?: boolean; changeJsonPath?: string } = {},
|
|
813
868
|
): Promise<string> => {
|
|
814
|
-
const
|
|
815
|
-
|
|
816
|
-
const filePath = await resolveDmlPath(paths, skill);
|
|
817
|
-
const controller = new AbortController();
|
|
818
|
-
activeController = controller;
|
|
819
|
-
activeDescription = `running ${skill}`;
|
|
869
|
+
const controller = claimExecution(`running ${skill}`);
|
|
870
|
+
if (!controller) throw new Error("Another DeepClause execution is already active");
|
|
820
871
|
try {
|
|
872
|
+
const paths = await initializeWorkspace(ctx.cwd);
|
|
873
|
+
const config = await loadConfig(paths.config);
|
|
874
|
+
const filePath = await resolveDmlPath(paths, skill);
|
|
821
875
|
const result = await executeDml(
|
|
822
876
|
filePath,
|
|
823
877
|
args,
|
|
@@ -837,8 +891,7 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
837
891
|
if (result.errors.length) throw new Error(result.errors.join("\n"));
|
|
838
892
|
return result.answer ?? "(no answer)";
|
|
839
893
|
} finally {
|
|
840
|
-
|
|
841
|
-
activeDescription = undefined;
|
|
894
|
+
releaseExecution(controller);
|
|
842
895
|
}
|
|
843
896
|
};
|
|
844
897
|
|
|
@@ -926,6 +979,8 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
926
979
|
`Skills: ${path.relative(ctx.cwd, paths.skills)}`,
|
|
927
980
|
`Plans: ${path.relative(ctx.cwd, paths.plans)}`,
|
|
928
981
|
`Context: ${config.contextMode} (verbose default: ${config.verbose})`,
|
|
982
|
+
`Judgment: ${config.judgment.default} (backends: llm${judgeBackendsEnabled(config)})`,
|
|
983
|
+
`Jev: ${jevStatus(config)}`,
|
|
929
984
|
`Model tool (${DC_RUN_TOOL}): ${config.modelToolEnabled && pi.getActiveTools().includes(DC_RUN_TOOL) ? "enabled" : "disabled"}`,
|
|
930
985
|
`Model tool (${DC_DIAGRAM_TOOL}): ${pi.getActiveTools().includes(DC_DIAGRAM_TOOL) ? "enabled" : "disabled"}`,
|
|
931
986
|
"Ask pi for a presentation-grade or specification-grade diagram of any .dml file;",
|
|
@@ -936,10 +991,13 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
936
991
|
" /dc-check <change|spec> validate specs and deltas deterministically",
|
|
937
992
|
" /dc-archive <change> merge a change delta into specs/ and archive it",
|
|
938
993
|
" /dc-apply <change> [--abort] execute tasks.dml; --abort discards an interrupted apply",
|
|
939
|
-
" /dc-run <skill|path> [args] [--context=turn|branch|isolated]",
|
|
994
|
+
" /dc-run <skill|path> [args] [--context=turn|branch|isolated] [--judge=llm|jev]",
|
|
940
995
|
" /dc-run <skill|path> --verbose show lifecycle events",
|
|
941
996
|
" /dc-run <skill|path> --debug show full event payloads and SDK diagnostics",
|
|
942
997
|
" /dc-tool enable|disable|status control the model-callable dc_run tool",
|
|
998
|
+
" /dc-judge [enable|disable|status] select the judgment backend (llm|jev)",
|
|
999
|
+
" /dc-judge default llm|jev set the default judgment backend",
|
|
1000
|
+
" /dc-judge model <name> | key-env <ENV_VAR>",
|
|
943
1001
|
" /dc-cancel",
|
|
944
1002
|
].join("\n");
|
|
945
1003
|
ctx.ui.notify(message, "info");
|
|
@@ -1032,6 +1090,78 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
1032
1090
|
},
|
|
1033
1091
|
});
|
|
1034
1092
|
|
|
1093
|
+
pi.registerCommand("dc-judge", {
|
|
1094
|
+
description: "Enable, disable, or select the semantic judgment backend (llm|jev)",
|
|
1095
|
+
handler: async (rawArgs, ctx) => {
|
|
1096
|
+
const tokens = rawArgs.trim().split(/\s+/).filter(Boolean);
|
|
1097
|
+
const action = (tokens[0] ?? "status").toLowerCase();
|
|
1098
|
+
const paths = await initializeWorkspace(ctx.cwd);
|
|
1099
|
+
|
|
1100
|
+
const describe = (config: DeepClauseConfig): string => {
|
|
1101
|
+
const jev = config.judgment.jev;
|
|
1102
|
+
const keyPresent = Boolean(process.env[jev.apiKeyEnv]);
|
|
1103
|
+
return [
|
|
1104
|
+
`judgment backend: ${config.judgment.default}`,
|
|
1105
|
+
`registered backends: llm${jev.enabled ? ", jev" : ""}`,
|
|
1106
|
+
`jev: ${jev.enabled ? "enabled" : "disabled"} | model=${jev.model} | ${jev.apiKeyEnv}=${keyPresent ? "set" : "not set"}`,
|
|
1107
|
+
].join("\n");
|
|
1108
|
+
};
|
|
1109
|
+
|
|
1110
|
+
try {
|
|
1111
|
+
if (action === "status") {
|
|
1112
|
+
ctx.ui.notify(describe(await loadConfig(paths.config)), "info");
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
if (action === "enable" || action === "on" || action === "disable" || action === "off") {
|
|
1116
|
+
const enabled = action === "enable" || action === "on";
|
|
1117
|
+
const config = await setJudgeConfig(paths.config, { jev: { enabled } });
|
|
1118
|
+
const jev = config.judgment.jev;
|
|
1119
|
+
const note = enabled && !process.env[jev.apiKeyEnv]
|
|
1120
|
+
? `\n${jev.apiKeyEnv} is not set; export it before using --judge=jev.`
|
|
1121
|
+
: "";
|
|
1122
|
+
ctx.ui.notify(`Jev backend ${enabled ? "enabled" : "disabled"} for this workspace.${note}`, enabled ? "warning" : "info");
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
if (action === "default") {
|
|
1126
|
+
const name = tokens[1];
|
|
1127
|
+
if (name !== "llm" && name !== "jev") {
|
|
1128
|
+
ctx.ui.notify("Usage: /dc-judge default llm|jev", "warning");
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
await setJudgeConfig(paths.config, { default: name });
|
|
1132
|
+
ctx.ui.notify(`Default judgment backend set to '${name}'`, "info");
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
if (action === "model") {
|
|
1136
|
+
const model = tokens[1];
|
|
1137
|
+
if (!model) {
|
|
1138
|
+
ctx.ui.notify("Usage: /dc-judge model <name>", "warning");
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
await setJudgeConfig(paths.config, { jev: { model } });
|
|
1142
|
+
ctx.ui.notify(`Jev model set to '${model}'`, "info");
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
if (action === "key-env") {
|
|
1146
|
+
const envName = tokens[1];
|
|
1147
|
+
if (!envName) {
|
|
1148
|
+
ctx.ui.notify("Usage: /dc-judge key-env <ENV_VAR>", "warning");
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
await setJudgeConfig(paths.config, { jev: { apiKeyEnv: envName } });
|
|
1152
|
+
ctx.ui.notify(`Jev API key environment variable set to '${envName}'`, "info");
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
ctx.ui.notify(
|
|
1156
|
+
"Usage: /dc-judge [enable|disable|status|default llm|jev|model <name>|key-env <ENV_VAR>]",
|
|
1157
|
+
"warning",
|
|
1158
|
+
);
|
|
1159
|
+
} catch (error) {
|
|
1160
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
1161
|
+
}
|
|
1162
|
+
},
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1035
1165
|
pi.registerCommand("dc-list", {
|
|
1036
1166
|
description: "List DeepClause DML skills and generated plans",
|
|
1037
1167
|
handler: async (_args, ctx) => {
|
|
@@ -1188,8 +1318,8 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
1188
1318
|
pi.registerCommand("dc-run", {
|
|
1189
1319
|
description: "Run a DML skill with pi's active model",
|
|
1190
1320
|
handler: async (rawArgs, ctx) => {
|
|
1191
|
-
|
|
1192
|
-
if (
|
|
1321
|
+
const controller = claimExecution("dc-run");
|
|
1322
|
+
if (!controller) {
|
|
1193
1323
|
ctx.ui.notify("A DeepClause execution is already active", "warning");
|
|
1194
1324
|
return;
|
|
1195
1325
|
}
|
|
@@ -1230,8 +1360,6 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
1230
1360
|
mode,
|
|
1231
1361
|
config.branchMessageLimit,
|
|
1232
1362
|
);
|
|
1233
|
-
const controller = new AbortController();
|
|
1234
|
-
activeController = controller;
|
|
1235
1363
|
const outputLines: string[] = [];
|
|
1236
1364
|
const recentEvents: string[] = [];
|
|
1237
1365
|
const events: Array<Record<string, unknown>> = [];
|
|
@@ -1302,7 +1430,7 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
1302
1430
|
filePath,
|
|
1303
1431
|
parsed.args,
|
|
1304
1432
|
initialMessages,
|
|
1305
|
-
{ ...config, verbose: config.verbose || parsed.debug },
|
|
1433
|
+
{ ...config, verbose: config.verbose || parsed.debug, judgeBackend: parsed.judge },
|
|
1306
1434
|
pi,
|
|
1307
1435
|
ctx,
|
|
1308
1436
|
controller,
|
|
@@ -1310,7 +1438,7 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
1310
1438
|
onEvent,
|
|
1311
1439
|
onDiagnostic,
|
|
1312
1440
|
onInput: async (prompt, signal) => {
|
|
1313
|
-
const answer = await ctx
|
|
1441
|
+
const answer = await requestDeepClauseInput(ctx, skillName, prompt, signal);
|
|
1314
1442
|
if (answer === undefined) throw new Error("Input cancelled");
|
|
1315
1443
|
return answer;
|
|
1316
1444
|
},
|
|
@@ -1338,8 +1466,7 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
1338
1466
|
publishResult(pi, `DeepClause execution failed: ${message}`, { error: message });
|
|
1339
1467
|
ctx.ui.notify(message, activeController?.signal.aborted ? "warning" : "error");
|
|
1340
1468
|
} finally {
|
|
1341
|
-
|
|
1342
|
-
activeDescription = undefined;
|
|
1469
|
+
releaseExecution(controller);
|
|
1343
1470
|
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
1344
1471
|
ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
1345
1472
|
}
|
package/src/runtime.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { realpath, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { createDeepClause } from "deepclause-sdk";
|
|
3
|
+
import { createDeepClause, createJevJudgeBackend, createLLMJudgeBackend } from "deepclause-sdk";
|
|
4
4
|
import type {
|
|
5
5
|
DMLEvent,
|
|
6
|
+
JudgeBackend,
|
|
6
7
|
LLMBackend,
|
|
7
8
|
LLMBackendMessage,
|
|
8
9
|
LLMUsage,
|
|
@@ -327,7 +328,7 @@ export async function executeDml(
|
|
|
327
328
|
filePath: string,
|
|
328
329
|
args: string[],
|
|
329
330
|
initialMessages: MemoryMessage[],
|
|
330
|
-
config: DeepClauseConfig,
|
|
331
|
+
config: DeepClauseConfig & { judgeBackend?: string },
|
|
331
332
|
pi: ExtensionAPI,
|
|
332
333
|
ctx: ExtensionContext,
|
|
333
334
|
controller: AbortController,
|
|
@@ -339,12 +340,38 @@ export async function executeDml(
|
|
|
339
340
|
const model = ctx.model;
|
|
340
341
|
if (!model) throw new Error("Select a pi model before running DeepClause");
|
|
341
342
|
const backend = createPiBackend(ctx, config.maxTokens, callbacks.onDiagnostic ?? (() => {}));
|
|
343
|
+
|
|
344
|
+
// Judgment backends. `llm` reuses pi's active model and credentials; `jev`
|
|
345
|
+
// is opt-in and only registered when enabled and its key is present.
|
|
346
|
+
const judgeBackends: Record<string, JudgeBackend> = {
|
|
347
|
+
llm: createLLMJudgeBackend({ llmBackend: backend, model: model.id }),
|
|
348
|
+
};
|
|
349
|
+
const jev = config.judgment.jev;
|
|
350
|
+
if (jev.enabled) {
|
|
351
|
+
const apiKey = process.env[jev.apiKeyEnv];
|
|
352
|
+
if (apiKey) {
|
|
353
|
+
judgeBackends.jev = createJevJudgeBackend({ apiKey, model: jev.model });
|
|
354
|
+
} else {
|
|
355
|
+
callbacks.onDiagnostic?.(
|
|
356
|
+
`judgment backend 'jev' is enabled but ${jev.apiKeyEnv} is not set; falling back to 'llm'`,
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const requestedJudge = config.judgeBackend ?? config.judgment.default;
|
|
361
|
+
if (!judgeBackends[requestedJudge]) {
|
|
362
|
+
throw new Error(
|
|
363
|
+
`Judgment backend '${requestedJudge}' is not available. Enable it in .pi/deepclause/config.json or use --judge=llm.`,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
342
367
|
const sdk = await createDeepClause({
|
|
343
368
|
model: model.id,
|
|
344
369
|
maxTokens: config.maxTokens,
|
|
345
370
|
streaming: true,
|
|
346
371
|
debug: config.verbose,
|
|
347
372
|
llmBackend: backend,
|
|
373
|
+
judgeBackends,
|
|
374
|
+
defaultJudge: requestedJudge,
|
|
348
375
|
});
|
|
349
376
|
registerPiRuntimeTools(
|
|
350
377
|
sdk,
|
|
@@ -458,6 +485,7 @@ export async function executeDml(
|
|
|
458
485
|
gasLimit: config.gasLimit,
|
|
459
486
|
signal: controller.signal,
|
|
460
487
|
initialMessages,
|
|
488
|
+
judgeBackend: config.judgeBackend,
|
|
461
489
|
onUserInput: (prompt) => callbacks.onInput(prompt, controller.signal),
|
|
462
490
|
})) {
|
|
463
491
|
callbacks.onEvent(event);
|