deepclause-pi 0.4.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/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 +9 -0
- package/dist/index.js +103 -81
- package/package.json +1 -1
- package/skills/handbook-dml/SKILL.md +174 -46
- package/src/assets/AGENTS.md +43 -7
- package/src/diagram/extract.ts +225 -3
- package/src/diagram/grade.ts +4 -4
- package/src/index.ts +120 -92
package/src/index.ts
CHANGED
|
@@ -322,6 +322,24 @@ function publishResult(pi: ExtensionAPI, content: string, details: Record<string
|
|
|
322
322
|
pi.sendMessage({ customType: "deepclause-result", content, display: true, details });
|
|
323
323
|
}
|
|
324
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
|
+
|
|
325
343
|
export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
326
344
|
let activeController: AbortController | undefined;
|
|
327
345
|
let activeDescription: string | undefined;
|
|
@@ -332,6 +350,27 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
332
350
|
let planningTransaction: PlanningTransaction | undefined;
|
|
333
351
|
let pendingAgentStep: PendingAgentStep | undefined;
|
|
334
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
|
+
|
|
335
374
|
const setPlanCommitActive = (enabled: boolean) => {
|
|
336
375
|
if (enabled && !planCommitRegistered) {
|
|
337
376
|
pi.registerTool({
|
|
@@ -528,6 +567,7 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
528
567
|
promptGuidelines: [
|
|
529
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.",
|
|
530
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.",
|
|
531
571
|
],
|
|
532
572
|
parameters: Type.Object({
|
|
533
573
|
skill: Type.String({ description: "Skill name such as example, or a DML path relative to .pi/deepclause/." }),
|
|
@@ -537,46 +577,45 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
537
577
|
})),
|
|
538
578
|
}),
|
|
539
579
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
540
|
-
|
|
580
|
+
const controller = claimExecution("dc_run");
|
|
581
|
+
if (!controller) {
|
|
541
582
|
return {
|
|
542
|
-
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." }],
|
|
543
584
|
details: { success: false, error: "execution_already_active" },
|
|
544
585
|
};
|
|
545
586
|
}
|
|
546
|
-
|
|
547
|
-
const paths = await initializeWorkspace(ctx.cwd);
|
|
548
|
-
const config = await loadConfig(paths.config);
|
|
549
|
-
if (!config.modelToolEnabled || !pi.getActiveTools().includes(DC_RUN_TOOL)) {
|
|
550
|
-
return {
|
|
551
|
-
content: [{ type: "text", text: "The dc_run tool is disabled. The user can enable it with /dc-tool enable." }],
|
|
552
|
-
details: { success: false, error: "tool_disabled" },
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
const mode = params.context ?? config.contextMode;
|
|
557
|
-
const filePath = await resolveDmlPath(paths, params.skill);
|
|
558
|
-
if (await isContextualPlan(filePath)) {
|
|
559
|
-
return {
|
|
560
|
-
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." }],
|
|
561
|
-
details: { success: false, error: "interactive_plan_requires_user_run" },
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
|
-
const skillName = path.relative(paths.root, filePath);
|
|
565
|
-
const initialMessages = buildInitialMessages(
|
|
566
|
-
ctx.sessionManager.getBranch(),
|
|
567
|
-
mode,
|
|
568
|
-
config.branchMessageLimit,
|
|
569
|
-
);
|
|
570
|
-
const controller = new AbortController();
|
|
571
587
|
const cancel = () => controller.abort(signal?.reason ?? new Error("dc_run cancelled"));
|
|
572
588
|
if (signal?.aborted) cancel();
|
|
573
589
|
else signal?.addEventListener("abort", cancel, { once: true });
|
|
574
|
-
activeController = controller;
|
|
575
|
-
activeDescription = `model tool running ${skillName}`;
|
|
576
|
-
const startedAt = Date.now();
|
|
577
|
-
const progress: string[] = [];
|
|
578
590
|
|
|
579
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
|
+
|
|
580
619
|
const result = await executeDml(
|
|
581
620
|
filePath,
|
|
582
621
|
params.args ?? [],
|
|
@@ -600,7 +639,7 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
600
639
|
onDiagnostic: () => {},
|
|
601
640
|
onInput: async (prompt, inputSignal) => {
|
|
602
641
|
if (!ctx.hasUI) throw new Error("dc_run cannot request user input without interactive UI");
|
|
603
|
-
const answer = await ctx
|
|
642
|
+
const answer = await requestDeepClauseInput(ctx, skillName, prompt, inputSignal);
|
|
604
643
|
if (answer === undefined) throw new Error("Input cancelled");
|
|
605
644
|
return answer;
|
|
606
645
|
},
|
|
@@ -626,12 +665,11 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
626
665
|
const message = error instanceof Error ? error.message : String(error);
|
|
627
666
|
return {
|
|
628
667
|
content: [{ type: "text", text: `DeepClause execution failed: ${message}` }],
|
|
629
|
-
details: { success: false,
|
|
668
|
+
details: { success: false, error: message },
|
|
630
669
|
};
|
|
631
670
|
} finally {
|
|
632
671
|
signal?.removeEventListener("abort", cancel);
|
|
633
|
-
|
|
634
|
-
activeDescription = undefined;
|
|
672
|
+
releaseExecution(controller);
|
|
635
673
|
}
|
|
636
674
|
},
|
|
637
675
|
});
|
|
@@ -652,11 +690,11 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
652
690
|
pi.registerTool({
|
|
653
691
|
name: DC_DIAGRAM_TOOL,
|
|
654
692
|
label: "Create DeepClause Diagram",
|
|
655
|
-
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.",
|
|
656
694
|
promptSnippet: "Create a presentation- or specification-grade diagram from a DML file",
|
|
657
695
|
promptGuidelines: [
|
|
658
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.",
|
|
659
|
-
"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.",
|
|
660
698
|
"Do not hand-write Mermaid or run diagram tools yourself; call dc_diagram and report the viewer result.",
|
|
661
699
|
],
|
|
662
700
|
parameters: Type.Object({
|
|
@@ -665,55 +703,51 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
665
703
|
view: Type.Optional(StringEnum(["flow", "sequence"] as const, { description: "Base layout used to seed the grade; default flow." })),
|
|
666
704
|
}),
|
|
667
705
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
668
|
-
|
|
706
|
+
const controller = claimExecution("dc_diagram");
|
|
707
|
+
if (!controller) {
|
|
669
708
|
return {
|
|
670
709
|
content: [{ type: "text", text: "Another DeepClause operation is already active; wait for it to finish." }],
|
|
671
710
|
details: { success: false, error: "execution_already_active" },
|
|
672
711
|
};
|
|
673
712
|
}
|
|
674
|
-
|
|
675
|
-
const requested = resolveGrade(String(params.grade ?? "")) ?? "presentation";
|
|
676
|
-
const grades: DiagramGrade[] = requested === "both" ? ["presentation", "specification"] : [requested];
|
|
677
|
-
const view: MermaidView = params.view === "sequence" ? "sequence" : "flow";
|
|
678
|
-
|
|
679
|
-
let sourcePath: string;
|
|
680
|
-
try {
|
|
681
|
-
sourcePath = await resolveDiagramSource(ctx.cwd, params.dml);
|
|
682
|
-
} catch (error) {
|
|
683
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
684
|
-
return { content: [{ type: "text", text: `dc_diagram failed: ${message}` }], details: { success: false, error: message } };
|
|
685
|
-
}
|
|
686
|
-
if (!ctx.model) {
|
|
687
|
-
return {
|
|
688
|
-
content: [{ type: "text", text: "dc_diagram requires an active pi model. Select one and try again." }],
|
|
689
|
-
details: { success: false, error: "no_model" },
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
const config = await loadConfig(getPaths(ctx.cwd).config);
|
|
694
|
-
const source = await readFile(sourcePath, "utf8");
|
|
695
|
-
const display = displayPath(ctx.cwd, sourcePath);
|
|
696
|
-
const seed = view === "sequence"
|
|
697
|
-
? renderSequence(display, source)
|
|
698
|
-
: renderDml(display, source, { hideOutput: true });
|
|
699
|
-
const targets = await collectDiagramTargets(ctx.cwd, [sourcePath]);
|
|
700
|
-
const name = diagramNameFor(sourcePath, targets, ctx.cwd);
|
|
701
|
-
const { diagrams, vendor } = await ensureDiagramDir(ctx.cwd, viewerVendorAssetPath());
|
|
702
|
-
const templateText = await bundledViewerTemplate();
|
|
703
|
-
|
|
704
|
-
const controller = new AbortController();
|
|
705
713
|
const cancel = () => controller.abort(signal?.reason ?? new Error("dc_diagram cancelled"));
|
|
706
714
|
if (signal?.aborted) cancel();
|
|
707
715
|
else signal?.addEventListener("abort", cancel, { once: true });
|
|
708
|
-
activeController = controller;
|
|
709
|
-
activeDescription = `diagram ${name} (${grades.join("+")})`;
|
|
710
|
-
|
|
711
|
-
const run = (command: string, args: string[], options?: { timeout?: number }) => pi.exec(command, args, options);
|
|
712
|
-
let chrome: string | undefined;
|
|
713
|
-
let chromeResolved = false;
|
|
714
716
|
|
|
715
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
|
+
|
|
716
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" });
|
|
717
751
|
const result = await polishDiagram({
|
|
718
752
|
grade,
|
|
719
753
|
view,
|
|
@@ -758,8 +792,7 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
758
792
|
return { content: [{ type: "text", text: `dc_diagram failed: ${message}` }], details: { success: false, error: message } };
|
|
759
793
|
} finally {
|
|
760
794
|
signal?.removeEventListener("abort", cancel);
|
|
761
|
-
|
|
762
|
-
activeDescription = undefined;
|
|
795
|
+
releaseExecution(controller);
|
|
763
796
|
}
|
|
764
797
|
},
|
|
765
798
|
});
|
|
@@ -833,13 +866,12 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
833
866
|
args: string[] = [],
|
|
834
867
|
options: { verifyCommands?: string[]; piAgentStep?: boolean; changeJsonPath?: string } = {},
|
|
835
868
|
): Promise<string> => {
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
const filePath = await resolveDmlPath(paths, skill);
|
|
839
|
-
const controller = new AbortController();
|
|
840
|
-
activeController = controller;
|
|
841
|
-
activeDescription = `running ${skill}`;
|
|
869
|
+
const controller = claimExecution(`running ${skill}`);
|
|
870
|
+
if (!controller) throw new Error("Another DeepClause execution is already active");
|
|
842
871
|
try {
|
|
872
|
+
const paths = await initializeWorkspace(ctx.cwd);
|
|
873
|
+
const config = await loadConfig(paths.config);
|
|
874
|
+
const filePath = await resolveDmlPath(paths, skill);
|
|
843
875
|
const result = await executeDml(
|
|
844
876
|
filePath,
|
|
845
877
|
args,
|
|
@@ -859,8 +891,7 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
859
891
|
if (result.errors.length) throw new Error(result.errors.join("\n"));
|
|
860
892
|
return result.answer ?? "(no answer)";
|
|
861
893
|
} finally {
|
|
862
|
-
|
|
863
|
-
activeDescription = undefined;
|
|
894
|
+
releaseExecution(controller);
|
|
864
895
|
}
|
|
865
896
|
};
|
|
866
897
|
|
|
@@ -1287,8 +1318,8 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
1287
1318
|
pi.registerCommand("dc-run", {
|
|
1288
1319
|
description: "Run a DML skill with pi's active model",
|
|
1289
1320
|
handler: async (rawArgs, ctx) => {
|
|
1290
|
-
|
|
1291
|
-
if (
|
|
1321
|
+
const controller = claimExecution("dc-run");
|
|
1322
|
+
if (!controller) {
|
|
1292
1323
|
ctx.ui.notify("A DeepClause execution is already active", "warning");
|
|
1293
1324
|
return;
|
|
1294
1325
|
}
|
|
@@ -1329,8 +1360,6 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
1329
1360
|
mode,
|
|
1330
1361
|
config.branchMessageLimit,
|
|
1331
1362
|
);
|
|
1332
|
-
const controller = new AbortController();
|
|
1333
|
-
activeController = controller;
|
|
1334
1363
|
const outputLines: string[] = [];
|
|
1335
1364
|
const recentEvents: string[] = [];
|
|
1336
1365
|
const events: Array<Record<string, unknown>> = [];
|
|
@@ -1409,7 +1438,7 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
1409
1438
|
onEvent,
|
|
1410
1439
|
onDiagnostic,
|
|
1411
1440
|
onInput: async (prompt, signal) => {
|
|
1412
|
-
const answer = await ctx
|
|
1441
|
+
const answer = await requestDeepClauseInput(ctx, skillName, prompt, signal);
|
|
1413
1442
|
if (answer === undefined) throw new Error("Input cancelled");
|
|
1414
1443
|
return answer;
|
|
1415
1444
|
},
|
|
@@ -1437,8 +1466,7 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
|
|
|
1437
1466
|
publishResult(pi, `DeepClause execution failed: ${message}`, { error: message });
|
|
1438
1467
|
ctx.ui.notify(message, activeController?.signal.aborted ? "warning" : "error");
|
|
1439
1468
|
} finally {
|
|
1440
|
-
|
|
1441
|
-
activeDescription = undefined;
|
|
1469
|
+
releaseExecution(controller);
|
|
1442
1470
|
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
1443
1471
|
ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
1444
1472
|
}
|