panopticon-cli 0.5.8 → 0.5.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.
- package/README.md +29 -83
- package/dist/{agents-I6RAEGL5.js → agents-MOMDECON.js} +8 -6
- package/dist/{archive-planning-U3AZAKWI.js → archive-planning-54J6EP6A.js} +3 -3
- package/dist/{chunk-UKSGE6RH.js → chunk-3KYTNMSE.js} +1 -2
- package/dist/{chunk-UKSGE6RH.js.map → chunk-3KYTNMSE.js.map} +1 -1
- package/dist/{chunk-M6ZVVKZ3.js → chunk-4OQ4SXQZ.js} +219 -107
- package/dist/chunk-4OQ4SXQZ.js.map +1 -0
- package/dist/{chunk-ZMJFEHGF.js → chunk-7ZB5D46Y.js} +2 -2
- package/dist/{chunk-ZMJFEHGF.js.map → chunk-7ZB5D46Y.js.map} +1 -1
- package/dist/{chunk-BYWVPPAZ.js → chunk-BHRMW7BY.js} +31 -4
- package/dist/chunk-BHRMW7BY.js.map +1 -0
- package/dist/{chunk-WEQW3EAT.js → chunk-F4XS2FQN.js} +3 -2
- package/dist/chunk-F4XS2FQN.js.map +1 -0
- package/dist/{chunk-OJF4QS3S.js → chunk-GIW2TUWI.js} +2 -2
- package/dist/{chunk-SUM2WVPF.js → chunk-H7T35QDO.js} +30 -12
- package/dist/chunk-H7T35QDO.js.map +1 -0
- package/dist/{chunk-MJXYTGK5.js → chunk-JZWCL5S5.js} +2 -2
- package/dist/{chunk-ZN5RHWGR.js → chunk-PFA5XE2V.js} +5 -41
- package/dist/chunk-PFA5XE2V.js.map +1 -0
- package/dist/{chunk-6OYUJ4AJ.js → chunk-R47UJWF6.js} +2 -2
- package/dist/{chunk-NYOGHGIW.js → chunk-RCYJK3ZC.js} +10 -9
- package/dist/chunk-RCYJK3ZC.js.map +1 -0
- package/dist/{chunk-R4KPLLRB.js → chunk-SFX3BG6N.js} +1 -1
- package/dist/chunk-SFX3BG6N.js.map +1 -0
- package/dist/{chunk-IZIXJYXZ.js → chunk-TA5X4QYQ.js} +6 -2
- package/dist/{chunk-IZIXJYXZ.js.map → chunk-TA5X4QYQ.js.map} +1 -1
- package/dist/{chunk-43F4LDZ4.js → chunk-VVTAPQOI.js} +2 -2
- package/dist/{chunk-YAAT66RT.js → chunk-WP6ZLWU3.js} +28 -3
- package/dist/chunk-WP6ZLWU3.js.map +1 -0
- package/dist/clean-planning-V4SSVU26.js +9 -0
- package/dist/cli/index.js +1654 -1056
- package/dist/cli/index.js.map +1 -1
- package/dist/close-issue-5OMOP2FU.js +9 -0
- package/dist/compact-beads-YQDVF6FQ.js +9 -0
- package/dist/dashboard/prompts/inspect-agent.md +157 -0
- package/dist/dashboard/prompts/merge-agent.md +11 -0
- package/dist/dashboard/prompts/review-agent.md +9 -0
- package/dist/dashboard/prompts/test-agent.md +9 -0
- package/dist/dashboard/prompts/uat-agent.md +215 -0
- package/dist/dashboard/prompts/work-agent.md +53 -5
- package/dist/dashboard/public/assets/index-5hYjhhGn.js +826 -0
- package/dist/dashboard/public/assets/index-DIFh3T1V.css +32 -0
- package/dist/dashboard/public/index.html +3 -6
- package/dist/dashboard/server.js +3338 -2033
- package/dist/factory-KKT7324R.js +20 -0
- package/dist/{feedback-writer-T2WCT6EZ.js → feedback-writer-IPPIUPDX.js} +2 -2
- package/dist/feedback-writer-IPPIUPDX.js.map +1 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.js +19 -19
- package/dist/{label-cleanup-4HJVX6NP.js → label-cleanup-4IVZIPGK.js} +2 -2
- package/dist/{merge-agent-ZITLVF2B.js → merge-agent-6YOMGQMX.js} +16 -16
- package/dist/{projects-3CRF57ZU.js → projects-BPGM6IFB.js} +2 -2
- package/dist/{remote-workspace-M4IULGFZ.js → remote-workspace-LKRDGYEB.js} +2 -2
- package/dist/{review-status-J2YJGL3E.js → review-status-E77PZZWG.js} +2 -2
- package/dist/{specialist-context-W25PPWM4.js → specialist-context-GVF4DV3M.js} +5 -5
- package/dist/{specialist-logs-KPC45SZN.js → specialist-logs-W47SAAIU.js} +5 -5
- package/dist/{specialists-H4LGYR7R.js → specialists-SIXRWCZ3.js} +5 -5
- package/dist/{traefik-QXLZ4PO2.js → traefik-X2IWTUHO.js} +3 -3
- package/dist/{workspace-manager-G6TTBPC3.js → workspace-manager-Z57ROWBQ.js} +2 -2
- package/dist/workspace-manager-Z57ROWBQ.js.map +1 -0
- package/package.json +1 -1
- package/scripts/inspect-on-bead-close +73 -0
- package/scripts/stop-hook +17 -0
- package/skills/pan-new-project/SKILL.md +1 -1
- package/skills/pan-oversee/SKILL.md +45 -10
- package/skills/plan/SKILL.md +336 -0
- package/dist/chunk-BYWVPPAZ.js.map +0 -1
- package/dist/chunk-M6ZVVKZ3.js.map +0 -1
- package/dist/chunk-NYOGHGIW.js.map +0 -1
- package/dist/chunk-R4KPLLRB.js.map +0 -1
- package/dist/chunk-SUM2WVPF.js.map +0 -1
- package/dist/chunk-WEQW3EAT.js.map +0 -1
- package/dist/chunk-YAAT66RT.js.map +0 -1
- package/dist/chunk-ZN5RHWGR.js.map +0 -1
- package/dist/clean-planning-7Z5YY64X.js +0 -9
- package/dist/close-issue-CTZK777I.js +0 -9
- package/dist/compact-beads-72SHALOL.js +0 -9
- package/dist/dashboard/public/assets/index-Bx4NCn9A.css +0 -32
- package/dist/dashboard/public/assets/index-C7hJ5-o1.js +0 -756
- package/dist/feedback-writer-T2WCT6EZ.js.map +0 -1
- package/skills/opus-plan/SKILL.md +0 -400
- /package/dist/{agents-I6RAEGL5.js.map → agents-MOMDECON.js.map} +0 -0
- /package/dist/{archive-planning-U3AZAKWI.js.map → archive-planning-54J6EP6A.js.map} +0 -0
- /package/dist/{chunk-OJF4QS3S.js.map → chunk-GIW2TUWI.js.map} +0 -0
- /package/dist/{chunk-MJXYTGK5.js.map → chunk-JZWCL5S5.js.map} +0 -0
- /package/dist/{chunk-6OYUJ4AJ.js.map → chunk-R47UJWF6.js.map} +0 -0
- /package/dist/{chunk-43F4LDZ4.js.map → chunk-VVTAPQOI.js.map} +0 -0
- /package/dist/{clean-planning-7Z5YY64X.js.map → clean-planning-V4SSVU26.js.map} +0 -0
- /package/dist/{close-issue-CTZK777I.js.map → close-issue-5OMOP2FU.js.map} +0 -0
- /package/dist/{compact-beads-72SHALOL.js.map → compact-beads-YQDVF6FQ.js.map} +0 -0
- /package/dist/{projects-3CRF57ZU.js.map → factory-KKT7324R.js.map} +0 -0
- /package/dist/{label-cleanup-4HJVX6NP.js.map → label-cleanup-4IVZIPGK.js.map} +0 -0
- /package/dist/{merge-agent-ZITLVF2B.js.map → merge-agent-6YOMGQMX.js.map} +0 -0
- /package/dist/{review-status-J2YJGL3E.js.map → projects-BPGM6IFB.js.map} +0 -0
- /package/dist/{remote-workspace-M4IULGFZ.js.map → remote-workspace-LKRDGYEB.js.map} +0 -0
- /package/dist/{specialist-logs-KPC45SZN.js.map → review-status-E77PZZWG.js.map} +0 -0
- /package/dist/{specialist-context-W25PPWM4.js.map → specialist-context-GVF4DV3M.js.map} +0 -0
- /package/dist/{specialists-H4LGYR7R.js.map → specialist-logs-W47SAAIU.js.map} +0 -0
- /package/dist/{traefik-QXLZ4PO2.js.map → specialists-SIXRWCZ3.js.map} +0 -0
- /package/dist/{workspace-manager-G6TTBPC3.js.map → traefik-X2IWTUHO.js.map} +0 -0
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
init_work_type_router,
|
|
10
10
|
popFromHook,
|
|
11
11
|
pushToHook
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-PFA5XE2V.js";
|
|
13
13
|
import {
|
|
14
14
|
clearCredentialFileAuth,
|
|
15
15
|
getProviderEnv,
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
getProject,
|
|
28
28
|
init_projects,
|
|
29
29
|
projects_exports
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-7ZB5D46Y.js";
|
|
31
31
|
import {
|
|
32
32
|
capturePaneAsync,
|
|
33
33
|
confirmDelivery,
|
|
@@ -552,6 +552,80 @@ var init_specialist_handoff_logger = __esm({
|
|
|
552
552
|
}
|
|
553
553
|
});
|
|
554
554
|
|
|
555
|
+
// src/lib/vbrief/io.ts
|
|
556
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
557
|
+
import { join as join4 } from "path";
|
|
558
|
+
function findPlan(workspacePath) {
|
|
559
|
+
const planPath = join4(workspacePath, ".planning", PLAN_FILENAME);
|
|
560
|
+
return existsSync4(planPath) ? planPath : null;
|
|
561
|
+
}
|
|
562
|
+
function readPlan(planPath) {
|
|
563
|
+
const raw = readFileSync4(planPath, "utf-8");
|
|
564
|
+
return JSON.parse(raw);
|
|
565
|
+
}
|
|
566
|
+
function readWorkspacePlan(workspacePath) {
|
|
567
|
+
const planPath = findPlan(workspacePath);
|
|
568
|
+
if (!planPath) return null;
|
|
569
|
+
return readPlan(planPath);
|
|
570
|
+
}
|
|
571
|
+
function updateItemStatus(workspacePath, itemId, status) {
|
|
572
|
+
const planPath = findPlan(workspacePath);
|
|
573
|
+
if (!planPath) return;
|
|
574
|
+
const doc = readPlan(planPath);
|
|
575
|
+
const item = doc.plan.items.find((i) => i.id === itemId);
|
|
576
|
+
if (!item) return;
|
|
577
|
+
item.status = status;
|
|
578
|
+
const tempPath = planPath + ".tmp";
|
|
579
|
+
writeFileSync2(tempPath, JSON.stringify(doc, null, 2), "utf-8");
|
|
580
|
+
renameSync(tempPath, planPath);
|
|
581
|
+
}
|
|
582
|
+
function updateSubItemStatus(workspacePath, itemId, subItemId, status) {
|
|
583
|
+
const planPath = findPlan(workspacePath);
|
|
584
|
+
if (!planPath) return;
|
|
585
|
+
const doc = readPlan(planPath);
|
|
586
|
+
const item = doc.plan.items.find((i) => i.id === itemId);
|
|
587
|
+
if (!item?.subItems) return;
|
|
588
|
+
const subItem = item.subItems.find((s) => s.id === subItemId);
|
|
589
|
+
if (!subItem) return;
|
|
590
|
+
subItem.status = status;
|
|
591
|
+
const tempPath = planPath + ".tmp";
|
|
592
|
+
writeFileSync2(tempPath, JSON.stringify(doc, null, 2), "utf-8");
|
|
593
|
+
renameSync(tempPath, planPath);
|
|
594
|
+
}
|
|
595
|
+
var PLAN_FILENAME;
|
|
596
|
+
var init_io = __esm({
|
|
597
|
+
"src/lib/vbrief/io.ts"() {
|
|
598
|
+
"use strict";
|
|
599
|
+
init_esm_shims();
|
|
600
|
+
PLAN_FILENAME = "plan.vbrief.json";
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
// src/lib/cloister/task-readiness.ts
|
|
605
|
+
function isTaskReady(itemId, workspacePath) {
|
|
606
|
+
const doc = readWorkspacePlan(workspacePath);
|
|
607
|
+
if (!doc) return true;
|
|
608
|
+
const itemExists = doc.plan.items.some((i) => i.id === itemId);
|
|
609
|
+
if (!itemExists) return true;
|
|
610
|
+
const blockerIds = doc.plan.edges.filter((e) => e.type === "blocks" && e.to === itemId).map((e) => e.from);
|
|
611
|
+
if (blockerIds.length === 0) return true;
|
|
612
|
+
const itemById = new Map(doc.plan.items.map((i) => [i.id, i]));
|
|
613
|
+
return blockerIds.every((blockerId) => {
|
|
614
|
+
const blocker = itemById.get(blockerId);
|
|
615
|
+
if (!blocker) return true;
|
|
616
|
+
return TERMINAL_STATUSES.includes(blocker.status);
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
var TERMINAL_STATUSES;
|
|
620
|
+
var init_task_readiness = __esm({
|
|
621
|
+
"src/lib/cloister/task-readiness.ts"() {
|
|
622
|
+
"use strict";
|
|
623
|
+
init_esm_shims();
|
|
624
|
+
init_io();
|
|
625
|
+
TERMINAL_STATUSES = ["completed", "cancelled"];
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
|
|
555
629
|
// src/lib/cloister/specialists.ts
|
|
556
630
|
var specialists_exports = {};
|
|
557
631
|
__export(specialists_exports, {
|
|
@@ -611,8 +685,8 @@ __export(specialists_exports, {
|
|
|
611
685
|
wakeSpecialistOrQueue: () => wakeSpecialistOrQueue,
|
|
612
686
|
wakeSpecialistWithTask: () => wakeSpecialistWithTask
|
|
613
687
|
});
|
|
614
|
-
import { readFileSync as
|
|
615
|
-
import { join as
|
|
688
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync as readdirSync3, unlinkSync, appendFileSync as appendFileSync3 } from "fs";
|
|
689
|
+
import { join as join5, basename as basename2 } from "path";
|
|
616
690
|
import { homedir as homedir2 } from "os";
|
|
617
691
|
import { exec } from "child_process";
|
|
618
692
|
import { promisify } from "util";
|
|
@@ -623,14 +697,14 @@ async function resolveWorkspaceGitInfo(workspace, taskBranch) {
|
|
|
623
697
|
if (!workspace || workspace === "unknown") {
|
|
624
698
|
return { gitDirs, branch, isPolyrepo: false };
|
|
625
699
|
}
|
|
626
|
-
if (
|
|
700
|
+
if (existsSync5(join5(workspace, ".git"))) {
|
|
627
701
|
gitDirs.push(workspace);
|
|
628
702
|
} else {
|
|
629
703
|
try {
|
|
630
704
|
const entries = readdirSync3(workspace, { withFileTypes: true });
|
|
631
705
|
for (const entry of entries) {
|
|
632
|
-
if (entry.isDirectory() &&
|
|
633
|
-
gitDirs.push(
|
|
706
|
+
if (entry.isDirectory() && existsSync5(join5(workspace, entry.name, ".git"))) {
|
|
707
|
+
gitDirs.push(join5(workspace, entry.name));
|
|
634
708
|
}
|
|
635
709
|
}
|
|
636
710
|
} catch {
|
|
@@ -670,10 +744,10 @@ function buildTmuxEnvFlags(env) {
|
|
|
670
744
|
return flags;
|
|
671
745
|
}
|
|
672
746
|
function initSpecialistsDirectory() {
|
|
673
|
-
if (!
|
|
747
|
+
if (!existsSync5(SPECIALISTS_DIR)) {
|
|
674
748
|
mkdirSync3(SPECIALISTS_DIR, { recursive: true });
|
|
675
749
|
}
|
|
676
|
-
if (!
|
|
750
|
+
if (!existsSync5(REGISTRY_FILE)) {
|
|
677
751
|
const registry = {
|
|
678
752
|
version: "2.0",
|
|
679
753
|
// Updated for per-project structure
|
|
@@ -697,7 +771,7 @@ function initSpecialistsDirectory() {
|
|
|
697
771
|
}
|
|
698
772
|
function migrateRegistryIfNeeded() {
|
|
699
773
|
try {
|
|
700
|
-
const content =
|
|
774
|
+
const content = readFileSync5(REGISTRY_FILE, "utf-8");
|
|
701
775
|
const registry = JSON.parse(content);
|
|
702
776
|
if (registry.version === "2.0" || registry.projects) {
|
|
703
777
|
return;
|
|
@@ -727,7 +801,7 @@ function migrateRegistryIfNeeded() {
|
|
|
727
801
|
function loadRegistry() {
|
|
728
802
|
initSpecialistsDirectory();
|
|
729
803
|
try {
|
|
730
|
-
const content =
|
|
804
|
+
const content = readFileSync5(REGISTRY_FILE, "utf-8");
|
|
731
805
|
return JSON.parse(content);
|
|
732
806
|
} catch (error) {
|
|
733
807
|
console.error("Failed to load specialist registry:", error);
|
|
@@ -745,13 +819,13 @@ function loadRegistry() {
|
|
|
745
819
|
}
|
|
746
820
|
}
|
|
747
821
|
function saveRegistry(registry) {
|
|
748
|
-
if (!
|
|
822
|
+
if (!existsSync5(SPECIALISTS_DIR)) {
|
|
749
823
|
mkdirSync3(SPECIALISTS_DIR, { recursive: true });
|
|
750
824
|
}
|
|
751
825
|
registry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
752
826
|
try {
|
|
753
827
|
const content = JSON.stringify(registry, null, 2);
|
|
754
|
-
|
|
828
|
+
writeFileSync3(REGISTRY_FILE, content, "utf-8");
|
|
755
829
|
} catch (error) {
|
|
756
830
|
console.error("Failed to save specialist registry:", error);
|
|
757
831
|
throw error;
|
|
@@ -763,17 +837,17 @@ function deterministicUUID(input) {
|
|
|
763
837
|
}
|
|
764
838
|
function getSessionFilePath(name, projectKey) {
|
|
765
839
|
if (projectKey) {
|
|
766
|
-
return
|
|
840
|
+
return join5(SPECIALISTS_DIR, "projects", projectKey, `${name}.session`);
|
|
767
841
|
}
|
|
768
|
-
return
|
|
842
|
+
return join5(SPECIALISTS_DIR, `${name}.session`);
|
|
769
843
|
}
|
|
770
844
|
function getSessionId(name, projectKey) {
|
|
771
845
|
const sessionFile = getSessionFilePath(name, projectKey);
|
|
772
|
-
if (!
|
|
846
|
+
if (!existsSync5(sessionFile)) {
|
|
773
847
|
return null;
|
|
774
848
|
}
|
|
775
849
|
try {
|
|
776
|
-
const sessionId =
|
|
850
|
+
const sessionId = readFileSync5(sessionFile, "utf-8").trim();
|
|
777
851
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
778
852
|
if (!uuidRegex.test(sessionId)) {
|
|
779
853
|
console.warn(`[specialist] Invalid session ID format for ${name} (${projectKey ?? "global"}): ${sessionId} \u2014 discarding`);
|
|
@@ -788,12 +862,12 @@ function getSessionId(name, projectKey) {
|
|
|
788
862
|
}
|
|
789
863
|
function setSessionId(name, sessionId, projectKey) {
|
|
790
864
|
const sessionFile = getSessionFilePath(name, projectKey);
|
|
791
|
-
const dir = projectKey ?
|
|
792
|
-
if (!
|
|
865
|
+
const dir = projectKey ? join5(SPECIALISTS_DIR, "projects", projectKey) : SPECIALISTS_DIR;
|
|
866
|
+
if (!existsSync5(dir)) {
|
|
793
867
|
mkdirSync3(dir, { recursive: true });
|
|
794
868
|
}
|
|
795
869
|
try {
|
|
796
|
-
|
|
870
|
+
writeFileSync3(sessionFile, sessionId.trim(), "utf-8");
|
|
797
871
|
} catch (error) {
|
|
798
872
|
console.error(`Failed to write session file for ${name} (${projectKey ?? "global"}):`, error);
|
|
799
873
|
throw error;
|
|
@@ -801,7 +875,7 @@ function setSessionId(name, sessionId, projectKey) {
|
|
|
801
875
|
}
|
|
802
876
|
function clearSessionId(name, projectKey) {
|
|
803
877
|
const sessionFile = getSessionFilePath(name, projectKey);
|
|
804
|
-
if (!
|
|
878
|
+
if (!existsSync5(sessionFile)) {
|
|
805
879
|
return false;
|
|
806
880
|
}
|
|
807
881
|
try {
|
|
@@ -859,9 +933,9 @@ function recordWake(name, sessionId) {
|
|
|
859
933
|
}
|
|
860
934
|
async function spawnEphemeralSpecialist(projectKey, specialistType, task) {
|
|
861
935
|
ensureProjectSpecialistDir(projectKey, specialistType);
|
|
862
|
-
const { loadContextDigest } = await import("./specialist-context-
|
|
936
|
+
const { loadContextDigest } = await import("./specialist-context-GVF4DV3M.js");
|
|
863
937
|
const contextDigest = loadContextDigest(projectKey, specialistType);
|
|
864
|
-
const { createRunLog: createRunLog2 } = await import("./specialist-logs-
|
|
938
|
+
const { createRunLog: createRunLog2 } = await import("./specialist-logs-W47SAAIU.js");
|
|
865
939
|
const { runId, filePath: logFilePath } = createRunLog2(
|
|
866
940
|
projectKey,
|
|
867
941
|
specialistType,
|
|
@@ -881,7 +955,7 @@ ${basePrompt}`;
|
|
|
881
955
|
const project = getProject(projectKey);
|
|
882
956
|
const cwd = project?.path || getDevrootPath() || homedir2();
|
|
883
957
|
try {
|
|
884
|
-
const { preTrustDirectory } = await import("./workspace-manager-
|
|
958
|
+
const { preTrustDirectory } = await import("./workspace-manager-Z57ROWBQ.js");
|
|
885
959
|
preTrustDirectory(cwd);
|
|
886
960
|
} catch {
|
|
887
961
|
}
|
|
@@ -889,7 +963,7 @@ ${basePrompt}`;
|
|
|
889
963
|
try {
|
|
890
964
|
const { stdout: sessions } = await execAsync('tmux list-sessions -F "#{session_name}" 2>/dev/null || echo ""', { encoding: "utf-8" });
|
|
891
965
|
if (sessions.split("\n").map((s) => s.trim()).includes(tmuxSession)) {
|
|
892
|
-
const { getAgentRuntimeState } = await import("./agents-
|
|
966
|
+
const { getAgentRuntimeState } = await import("./agents-MOMDECON.js");
|
|
893
967
|
const existingState = getAgentRuntimeState(tmuxSession);
|
|
894
968
|
if (existingState?.state === "active") {
|
|
895
969
|
return {
|
|
@@ -926,17 +1000,17 @@ ${basePrompt}`;
|
|
|
926
1000
|
clearCredentialFileAuth(cwd);
|
|
927
1001
|
}
|
|
928
1002
|
const permissionFlags = specialistType === "merge-agent" ? "--dangerously-skip-permissions --permission-mode bypassPermissions" : "--dangerously-skip-permissions";
|
|
929
|
-
const agentDir =
|
|
1003
|
+
const agentDir = join5(homedir2(), ".panopticon", "agents", tmuxSession);
|
|
930
1004
|
await execAsync(`mkdir -p "${agentDir}"`, { encoding: "utf-8" });
|
|
931
|
-
const promptFile =
|
|
932
|
-
|
|
1005
|
+
const promptFile = join5(agentDir, "task-prompt.md");
|
|
1006
|
+
writeFileSync3(promptFile, taskPrompt);
|
|
933
1007
|
const sessionName = `specialist-${projectKey}-${specialistType}`;
|
|
934
1008
|
const sessionId = deterministicUUID(sessionName);
|
|
935
1009
|
setSessionId(specialistType, sessionId, projectKey);
|
|
936
1010
|
console.log(`[specialist] Dispatching ${specialistType} for ${projectKey}/${task.issueId} (session: ${sessionId.slice(0, 8)}...)`);
|
|
937
|
-
const launcherScript =
|
|
938
|
-
const innerScript =
|
|
939
|
-
|
|
1011
|
+
const launcherScript = join5(agentDir, "launcher.sh");
|
|
1012
|
+
const innerScript = join5(agentDir, "run-claude.sh");
|
|
1013
|
+
writeFileSync3(innerScript, `#!/bin/bash
|
|
940
1014
|
set -o pipefail
|
|
941
1015
|
cd "${cwd}"
|
|
942
1016
|
export PANOPTICON_AGENT_ID="${tmuxSession}"
|
|
@@ -958,14 +1032,14 @@ fi
|
|
|
958
1032
|
echo ""
|
|
959
1033
|
echo "## Specialist completed task"
|
|
960
1034
|
`, { mode: 493 });
|
|
961
|
-
|
|
1035
|
+
writeFileSync3(launcherScript, `#!/bin/bash
|
|
962
1036
|
script -qfec "bash '${innerScript}'" /dev/null 2>&1 | tee -a "${logFilePath}"
|
|
963
1037
|
`, { mode: 493 });
|
|
964
1038
|
await execAsync(
|
|
965
|
-
`tmux new-session -d -s "${tmuxSession}"${envFlags} "bash '${launcherScript}'"`,
|
|
1039
|
+
`tmux new-session -d -s "${tmuxSession}" -c "${cwd}"${envFlags} "bash '${launcherScript}'"`,
|
|
966
1040
|
{ encoding: "utf-8" }
|
|
967
1041
|
);
|
|
968
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
1042
|
+
const { saveAgentRuntimeState } = await import("./agents-MOMDECON.js");
|
|
969
1043
|
saveAgentRuntimeState(tmuxSession, {
|
|
970
1044
|
state: "active",
|
|
971
1045
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -989,7 +1063,7 @@ script -qfec "bash '${innerScript}'" /dev/null 2>&1 | tee -a "${logFilePath}"
|
|
|
989
1063
|
}
|
|
990
1064
|
}
|
|
991
1065
|
async function buildTaskPrompt(projectKey, specialistType, task, contextDigest) {
|
|
992
|
-
const { getSpecialistPromptOverride } = await import("./projects-
|
|
1066
|
+
const { getSpecialistPromptOverride } = await import("./projects-BPGM6IFB.js");
|
|
993
1067
|
const customPrompt = getSpecialistPromptOverride(projectKey, specialistType);
|
|
994
1068
|
let prompt = `# ${specialistType} Task - ${task.issueId}
|
|
995
1069
|
|
|
@@ -1039,17 +1113,8 @@ Update status via API:
|
|
|
1039
1113
|
- If issues found: POST to /api/workspaces/${task.issueId}/review-status with {"reviewStatus":"blocked","reviewNotes":"..."}
|
|
1040
1114
|
- If review passes: POST with {"reviewStatus":"passed"} then queue test-agent`;
|
|
1041
1115
|
break;
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
1. Run the full test suite
|
|
1045
|
-
2. Analyze any failures in detail
|
|
1046
|
-
3. Identify root causes
|
|
1047
|
-
4. Update status via API when done
|
|
1048
|
-
|
|
1049
|
-
Update status via API:
|
|
1050
|
-
- If tests pass: POST to /api/workspaces/${task.issueId}/review-status with {"testStatus":"passed"}
|
|
1051
|
-
- If tests fail: POST with {"testStatus":"failed","testNotes":"..."}`;
|
|
1052
|
-
break;
|
|
1116
|
+
// test-agent: detailed prompt is in the second switch block below (line ~2173)
|
|
1117
|
+
// Do NOT add an inline stub here — it would shadow the detailed template.
|
|
1053
1118
|
case "merge-agent": {
|
|
1054
1119
|
const bInfo = await resolveWorkspaceGitInfo(task.workspace, task.branch);
|
|
1055
1120
|
if (bInfo.isPolyrepo) {
|
|
@@ -1159,7 +1224,7 @@ async function terminateSpecialist(projectKey, specialistType) {
|
|
|
1159
1224
|
console.error(`[specialist] Failed to kill tmux session ${tmuxSession}:`, error);
|
|
1160
1225
|
}
|
|
1161
1226
|
if (metadata.currentRun) {
|
|
1162
|
-
const { finalizeRunLog: finalizeRunLog2 } = await import("./specialist-logs-
|
|
1227
|
+
const { finalizeRunLog: finalizeRunLog2 } = await import("./specialist-logs-W47SAAIU.js");
|
|
1163
1228
|
try {
|
|
1164
1229
|
finalizeRunLog2(projectKey, specialistType, metadata.currentRun, {
|
|
1165
1230
|
status: metadata.lastRunStatus || "incomplete",
|
|
@@ -1172,20 +1237,20 @@ async function terminateSpecialist(projectKey, specialistType) {
|
|
|
1172
1237
|
}
|
|
1173
1238
|
const key = `${projectKey}-${specialistType}`;
|
|
1174
1239
|
gracePeriodStates.delete(key);
|
|
1175
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
1240
|
+
const { saveAgentRuntimeState } = await import("./agents-MOMDECON.js");
|
|
1176
1241
|
saveAgentRuntimeState(tmuxSession, {
|
|
1177
1242
|
state: "suspended",
|
|
1178
1243
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString()
|
|
1179
1244
|
});
|
|
1180
|
-
const { scheduleDigestGeneration } = await import("./specialist-context-
|
|
1245
|
+
const { scheduleDigestGeneration } = await import("./specialist-context-GVF4DV3M.js");
|
|
1181
1246
|
scheduleDigestGeneration(projectKey, specialistType);
|
|
1182
1247
|
scheduleLogCleanup(projectKey, specialistType);
|
|
1183
1248
|
}
|
|
1184
1249
|
function scheduleLogCleanup(projectKey, specialistType) {
|
|
1185
1250
|
Promise.resolve().then(async () => {
|
|
1186
1251
|
try {
|
|
1187
|
-
const { cleanupOldLogs: cleanupOldLogs2 } = await import("./specialist-logs-
|
|
1188
|
-
const { getSpecialistRetention } = await import("./projects-
|
|
1252
|
+
const { cleanupOldLogs: cleanupOldLogs2 } = await import("./specialist-logs-W47SAAIU.js");
|
|
1253
|
+
const { getSpecialistRetention } = await import("./projects-BPGM6IFB.js");
|
|
1189
1254
|
const retention = getSpecialistRetention(projectKey);
|
|
1190
1255
|
const deleted = cleanupOldLogs2(projectKey, specialistType, { maxDays: retention.max_days, maxRuns: retention.max_runs });
|
|
1191
1256
|
if (deleted > 0) {
|
|
@@ -1197,16 +1262,16 @@ function scheduleLogCleanup(projectKey, specialistType) {
|
|
|
1197
1262
|
});
|
|
1198
1263
|
}
|
|
1199
1264
|
function getProjectSpecialistDir(projectKey, specialistType) {
|
|
1200
|
-
return
|
|
1265
|
+
return join5(SPECIALISTS_DIR, projectKey, specialistType);
|
|
1201
1266
|
}
|
|
1202
1267
|
function ensureProjectSpecialistDir(projectKey, specialistType) {
|
|
1203
1268
|
const specialistDir = getProjectSpecialistDir(projectKey, specialistType);
|
|
1204
|
-
const runsDir =
|
|
1205
|
-
const contextDir =
|
|
1206
|
-
if (!
|
|
1269
|
+
const runsDir = join5(specialistDir, "runs");
|
|
1270
|
+
const contextDir = join5(specialistDir, "context");
|
|
1271
|
+
if (!existsSync5(runsDir)) {
|
|
1207
1272
|
mkdirSync3(runsDir, { recursive: true });
|
|
1208
1273
|
}
|
|
1209
|
-
if (!
|
|
1274
|
+
if (!existsSync5(contextDir)) {
|
|
1210
1275
|
mkdirSync3(contextDir, { recursive: true });
|
|
1211
1276
|
}
|
|
1212
1277
|
}
|
|
@@ -1373,7 +1438,7 @@ async function getSpecialistStatus(name, projectKey) {
|
|
|
1373
1438
|
const sessionId = getSessionId(name, projectKey);
|
|
1374
1439
|
const running = await isRunning(name, projectKey);
|
|
1375
1440
|
const contextTokens = countContextTokens(name);
|
|
1376
|
-
const { getAgentRuntimeState } = await import("./agents-
|
|
1441
|
+
const { getAgentRuntimeState } = await import("./agents-MOMDECON.js");
|
|
1377
1442
|
const tmuxSession = getTmuxSessionName(name, projectKey);
|
|
1378
1443
|
const runtimeState = getAgentRuntimeState(tmuxSession);
|
|
1379
1444
|
let state;
|
|
@@ -1454,20 +1519,20 @@ Say: "I am the ${name} specialist, ready and waiting for tasks."`;
|
|
|
1454
1519
|
} else {
|
|
1455
1520
|
clearCredentialFileAuth(cwd);
|
|
1456
1521
|
}
|
|
1457
|
-
const agentDir =
|
|
1522
|
+
const agentDir = join5(homedir2(), ".panopticon", "agents", tmuxSession);
|
|
1458
1523
|
await execAsync(`mkdir -p "${agentDir}"`, { encoding: "utf-8" });
|
|
1459
|
-
const promptFile =
|
|
1460
|
-
const launcherScript =
|
|
1461
|
-
|
|
1524
|
+
const promptFile = join5(agentDir, "identity-prompt.md");
|
|
1525
|
+
const launcherScript = join5(agentDir, "launcher.sh");
|
|
1526
|
+
writeFileSync3(promptFile, identityPrompt);
|
|
1462
1527
|
const newSessionId = randomUUID();
|
|
1463
|
-
|
|
1528
|
+
writeFileSync3(launcherScript, `#!/bin/bash
|
|
1464
1529
|
cd "${cwd}"
|
|
1465
1530
|
prompt=$(cat "${promptFile}")
|
|
1466
1531
|
exec claude --dangerously-skip-permissions --session-id "${newSessionId}" --model ${model} "$prompt"
|
|
1467
1532
|
`, { mode: 493 });
|
|
1468
1533
|
setSessionId(name, newSessionId);
|
|
1469
1534
|
await execAsync(
|
|
1470
|
-
`tmux new-session -d -s "${tmuxSession}"${envFlags} "bash '${launcherScript}'"`,
|
|
1535
|
+
`tmux new-session -d -s "${tmuxSession}" -c "${cwd}"${envFlags} "bash '${launcherScript}'"`,
|
|
1471
1536
|
{ encoding: "utf-8" }
|
|
1472
1537
|
);
|
|
1473
1538
|
recordWake(name);
|
|
@@ -1528,7 +1593,7 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
|
|
|
1528
1593
|
const sessionId = getSessionId(name);
|
|
1529
1594
|
const wasAlreadyRunning = await isRunning(name);
|
|
1530
1595
|
if (wasAlreadyRunning && !options.skipBusyGuard) {
|
|
1531
|
-
const { getAgentRuntimeState } = await import("./agents-
|
|
1596
|
+
const { getAgentRuntimeState } = await import("./agents-MOMDECON.js");
|
|
1532
1597
|
const runtimeState = getAgentRuntimeState(tmuxSession);
|
|
1533
1598
|
if (runtimeState?.state === "active") {
|
|
1534
1599
|
console.warn(`[specialist] ${name} is busy (working on ${runtimeState.currentIssue}), refusing to interrupt`);
|
|
@@ -1550,9 +1615,9 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
|
|
|
1550
1615
|
error: "not_running"
|
|
1551
1616
|
};
|
|
1552
1617
|
}
|
|
1553
|
-
const cwd = getDevrootPath() ||
|
|
1618
|
+
const cwd = getDevrootPath() || join5(process.env.HOME || "/home/eltmon", "Projects");
|
|
1554
1619
|
try {
|
|
1555
|
-
const { preTrustDirectory } = await import("./workspace-manager-
|
|
1620
|
+
const { preTrustDirectory } = await import("./workspace-manager-Z57ROWBQ.js");
|
|
1556
1621
|
preTrustDirectory(cwd);
|
|
1557
1622
|
} catch {
|
|
1558
1623
|
}
|
|
@@ -1619,11 +1684,11 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
|
|
|
1619
1684
|
const isLargePrompt = taskPrompt.length > 500 || taskPrompt.includes("\n");
|
|
1620
1685
|
let messageToSend;
|
|
1621
1686
|
if (isLargePrompt) {
|
|
1622
|
-
if (!
|
|
1687
|
+
if (!existsSync5(TASKS_DIR)) {
|
|
1623
1688
|
mkdirSync3(TASKS_DIR, { recursive: true });
|
|
1624
1689
|
}
|
|
1625
|
-
const taskFile =
|
|
1626
|
-
|
|
1690
|
+
const taskFile = join5(TASKS_DIR, `${name}-${Date.now()}.md`);
|
|
1691
|
+
writeFileSync3(taskFile, taskPrompt, "utf-8");
|
|
1627
1692
|
messageToSend = `Read and execute the task in: ${taskFile}`;
|
|
1628
1693
|
} else {
|
|
1629
1694
|
messageToSend = taskPrompt;
|
|
@@ -1647,7 +1712,7 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
|
|
|
1647
1712
|
}
|
|
1648
1713
|
}
|
|
1649
1714
|
recordWake(name, sessionId || void 0);
|
|
1650
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
1715
|
+
const { saveAgentRuntimeState } = await import("./agents-MOMDECON.js");
|
|
1651
1716
|
saveAgentRuntimeState(tmuxSession, {
|
|
1652
1717
|
state: "active",
|
|
1653
1718
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1705,7 +1770,7 @@ PHASE 1 \u2014 SYNC & BASELINE (before merge):
|
|
|
1705
1770
|
If rebase conflicts: abort and report failure.
|
|
1706
1771
|
5. Run tests on main to establish a baseline. Record BASELINE_PASS and BASELINE_FAIL.
|
|
1707
1772
|
|
|
1708
|
-
PHASE 2 \u2014 MERGE:
|
|
1773
|
+
PHASE 2 \u2014 MERGE (dry run):
|
|
1709
1774
|
6. git merge ${mergeBranch} --no-edit
|
|
1710
1775
|
7. If conflicts: resolve them intelligently, then git add and git commit
|
|
1711
1776
|
8. If clean merge: the merge commit is auto-created (or fast-forward)
|
|
@@ -1715,10 +1780,13 @@ PHASE 3 \u2014 VERIFY:
|
|
|
1715
1780
|
|
|
1716
1781
|
PHASE 4 \u2014 DECIDE:
|
|
1717
1782
|
10. Compare results:
|
|
1718
|
-
- If MERGE_FAIL > BASELINE_FAIL (NEW test failures): ROLLBACK with git reset --hard ORIG_HEAD
|
|
1719
|
-
- If MERGE_FAIL <= BASELINE_FAIL (no new failures):
|
|
1783
|
+
- If MERGE_FAIL > BASELINE_FAIL (NEW test failures): ROLLBACK with git reset --hard ORIG_HEAD and report FAILED
|
|
1784
|
+
- If MERGE_FAIL <= BASELINE_FAIL (no new failures): Report PASSED (merge is validated)
|
|
1720
1785
|
- Pre-existing failures on main are NOT a reason to rollback
|
|
1721
1786
|
|
|
1787
|
+
CRITICAL: Do NOT push to main. Do NOT run git push origin main.
|
|
1788
|
+
The merge validation stays LOCAL. A human will click Merge in the dashboard to push.
|
|
1789
|
+
|
|
1722
1790
|
PHASE 5 \u2014 REPORT:
|
|
1723
1791
|
11. Call the Panopticon API to report results:
|
|
1724
1792
|
curl -s -X POST ${apiUrl}/api/specialists/done \\
|
|
@@ -1726,6 +1794,7 @@ PHASE 5 \u2014 REPORT:
|
|
|
1726
1794
|
-d '{"specialist":"merge","issueId":"${task.issueId}","status":"passed|failed","notes":"<summary>"}'
|
|
1727
1795
|
|
|
1728
1796
|
CRITICAL: You MUST call the /api/specialists/done endpoint whether you succeed or fail.
|
|
1797
|
+
CRITICAL: NEVER push to main \u2014 only humans merge. Your job is to VALIDATE the merge, not execute it.
|
|
1729
1798
|
CRITICAL: NEVER use git push --force.
|
|
1730
1799
|
CRITICAL: Do NOT delete the feature branch.`;
|
|
1731
1800
|
break;
|
|
@@ -1749,14 +1818,14 @@ CRITICAL: Do NOT delete the feature branch.`;
|
|
|
1749
1818
|
if (totalChangedFiles === 0) {
|
|
1750
1819
|
staleBranch = true;
|
|
1751
1820
|
console.log(`[specialist] review-agent: stale branch detected for ${task.issueId} \u2014 0 files changed vs main`);
|
|
1752
|
-
const { setReviewStatus } = await import("./review-status-
|
|
1821
|
+
const { setReviewStatus } = await import("./review-status-E77PZZWG.js");
|
|
1753
1822
|
setReviewStatus(task.issueId.toUpperCase(), {
|
|
1754
1823
|
reviewStatus: "passed",
|
|
1755
1824
|
reviewNotes: "No changes to review \u2014 branch identical to main (already merged or stale)"
|
|
1756
1825
|
});
|
|
1757
1826
|
console.log(`[specialist] review-agent: auto-passed ${task.issueId} (stale branch)`);
|
|
1758
1827
|
const tmuxSession = getTmuxSessionName("review-agent");
|
|
1759
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
1828
|
+
const { saveAgentRuntimeState } = await import("./agents-MOMDECON.js");
|
|
1760
1829
|
saveAgentRuntimeState(tmuxSession, {
|
|
1761
1830
|
state: "idle",
|
|
1762
1831
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1861,7 +1930,7 @@ curl -s -X POST ${apiUrl}/api/specialists/test-agent/queue -H "Content-Type: app
|
|
|
1861
1930
|
const testWorkspace = task.workspace || "unknown";
|
|
1862
1931
|
const testGitInfo = await resolveWorkspaceGitInfo(task.workspace, task.branch);
|
|
1863
1932
|
const testIsPolyrepo = testGitInfo.isPolyrepo;
|
|
1864
|
-
const { extractTeamPrefix, findProjectByTeam } = await import("./projects-
|
|
1933
|
+
const { extractTeamPrefix, findProjectByTeam } = await import("./projects-BPGM6IFB.js");
|
|
1865
1934
|
const testTeamPrefix = extractTeamPrefix(task.issueId);
|
|
1866
1935
|
const testProjectConfig = testTeamPrefix ? findProjectByTeam(testTeamPrefix) : null;
|
|
1867
1936
|
const testConfigs = testProjectConfig?.tests;
|
|
@@ -1989,6 +2058,29 @@ Then use send-feedback-to-agent skill to notify issue agent of NEW failures only
|
|
|
1989
2058
|
|
|
1990
2059
|
**NEVER run test commands without redirecting to a file.** This is not optional.
|
|
1991
2060
|
|
|
2061
|
+
## REQUIRED: Container Smoke Test
|
|
2062
|
+
|
|
2063
|
+
After unit tests pass, verify the Docker workspace frontend is accessible.
|
|
2064
|
+
This is NOT optional \u2014 UI changes that pass unit tests but break in containers must be caught.
|
|
2065
|
+
|
|
2066
|
+
\`\`\`bash
|
|
2067
|
+
# Check if containers are running for this workspace
|
|
2068
|
+
docker ps --filter "name=${featureName}" --format "{{.Names}} {{.Status}}" 2>/dev/null
|
|
2069
|
+
\`\`\`
|
|
2070
|
+
|
|
2071
|
+
If containers are running, test these URLs:
|
|
2072
|
+
- **Frontend**: \`curl -sk https://feature-${featureName}.${testProjectConfig?.workspace?.dns?.domain || "pan.localhost"}/ | head -5\`
|
|
2073
|
+
- **API proxy**: \`curl -sk https://feature-${featureName}.${testProjectConfig?.workspace?.dns?.domain || "pan.localhost"}/api/health\`
|
|
2074
|
+
- **API issues**: \`curl -sk https://feature-${featureName}.${testProjectConfig?.workspace?.dns?.domain || "pan.localhost"}/api/issues | head -100\`
|
|
2075
|
+
|
|
2076
|
+
**Pass criteria:**
|
|
2077
|
+
1. Frontend returns HTML containing \`<div id="root">\`
|
|
2078
|
+
2. \`/api/health\` returns JSON with \`"status":"ok"\`
|
|
2079
|
+
3. \`/api/issues\` returns JSON array (not an error)
|
|
2080
|
+
|
|
2081
|
+
**If ANY of these fail, the test FAILS** \u2014 report via the API with details about which check failed.
|
|
2082
|
+
If containers are NOT running, note it but don't fail (containers may not be configured for this project).
|
|
2083
|
+
|
|
1992
2084
|
IMPORTANT: Do NOT hand off to merge-agent. Human clicks Merge button when ready.`;
|
|
1993
2085
|
break;
|
|
1994
2086
|
}
|
|
@@ -1999,8 +2091,23 @@ IMPORTANT: Do NOT hand off to merge-agent. Human clicks Merge button when ready.
|
|
|
1999
2091
|
}
|
|
2000
2092
|
async function wakeSpecialistOrQueue(name, task, options = {}) {
|
|
2001
2093
|
const { priority = "normal", source = "handoff" } = options;
|
|
2094
|
+
const vbriefItemId = task.context?.vbriefItemId;
|
|
2095
|
+
const workspacePath = task.workspace || task.context?.workspace;
|
|
2096
|
+
if (vbriefItemId && workspacePath) {
|
|
2097
|
+
try {
|
|
2098
|
+
if (!isTaskReady(vbriefItemId, workspacePath)) {
|
|
2099
|
+
return {
|
|
2100
|
+
success: false,
|
|
2101
|
+
queued: false,
|
|
2102
|
+
message: `Task "${vbriefItemId}" has incomplete blocking dependencies \u2014 not ready to schedule`
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
} catch (readinessErr) {
|
|
2106
|
+
console.warn(`[specialist] Task readiness check failed for ${vbriefItemId}: ${readinessErr.message}`);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2002
2109
|
const running = await isRunning(name);
|
|
2003
|
-
const { getAgentRuntimeState } = await import("./agents-
|
|
2110
|
+
const { getAgentRuntimeState } = await import("./agents-MOMDECON.js");
|
|
2004
2111
|
const tmuxSession = getTmuxSessionName(name);
|
|
2005
2112
|
const runtimeState = getAgentRuntimeState(tmuxSession);
|
|
2006
2113
|
const idle = runtimeState?.state === "idle" || runtimeState?.state === "suspended";
|
|
@@ -2031,7 +2138,7 @@ async function wakeSpecialistOrQueue(name, task, options = {}) {
|
|
|
2031
2138
|
};
|
|
2032
2139
|
}
|
|
2033
2140
|
}
|
|
2034
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
2141
|
+
const { saveAgentRuntimeState } = await import("./agents-MOMDECON.js");
|
|
2035
2142
|
saveAgentRuntimeState(tmuxSession, {
|
|
2036
2143
|
state: "active",
|
|
2037
2144
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2115,7 +2222,7 @@ function getNextSpecialistTask(specialistName) {
|
|
|
2115
2222
|
}
|
|
2116
2223
|
async function sendFeedbackToAgent(feedback) {
|
|
2117
2224
|
const { fromSpecialist, toIssueId, summary, details } = feedback;
|
|
2118
|
-
if (!
|
|
2225
|
+
if (!existsSync5(FEEDBACK_DIR)) {
|
|
2119
2226
|
mkdirSync3(FEEDBACK_DIR, { recursive: true });
|
|
2120
2227
|
}
|
|
2121
2228
|
const fullFeedback = {
|
|
@@ -2131,7 +2238,7 @@ async function sendFeedbackToAgent(feedback) {
|
|
|
2131
2238
|
}
|
|
2132
2239
|
const agentSession = `agent-${toIssueId.toLowerCase()}`;
|
|
2133
2240
|
const feedbackMessage = formatFeedbackForAgent(fullFeedback);
|
|
2134
|
-
const { writeFeedbackFile } = await import("./feedback-writer-
|
|
2241
|
+
const { writeFeedbackFile } = await import("./feedback-writer-IPPIUPDX.js");
|
|
2135
2242
|
const specialistMap = {
|
|
2136
2243
|
"review-agent": "review-agent",
|
|
2137
2244
|
"test-agent": "test-agent",
|
|
@@ -2151,7 +2258,7 @@ async function sendFeedbackToAgent(feedback) {
|
|
|
2151
2258
|
return false;
|
|
2152
2259
|
}
|
|
2153
2260
|
try {
|
|
2154
|
-
const { messageAgent } = await import("./agents-
|
|
2261
|
+
const { messageAgent } = await import("./agents-MOMDECON.js");
|
|
2155
2262
|
const msg = `SPECIALIST FEEDBACK: ${fromSpecialist} reported ${feedback.feedbackType.toUpperCase()} for ${toIssueId}.
|
|
2156
2263
|
Read and address: ${fileResult.relativePath}`;
|
|
2157
2264
|
await messageAgent(agentSession, msg);
|
|
@@ -2210,11 +2317,11 @@ ${details}
|
|
|
2210
2317
|
return message;
|
|
2211
2318
|
}
|
|
2212
2319
|
function getPendingFeedback(issueId) {
|
|
2213
|
-
if (!
|
|
2320
|
+
if (!existsSync5(FEEDBACK_LOG)) {
|
|
2214
2321
|
return [];
|
|
2215
2322
|
}
|
|
2216
2323
|
try {
|
|
2217
|
-
const content =
|
|
2324
|
+
const content = readFileSync5(FEEDBACK_LOG, "utf-8");
|
|
2218
2325
|
const lines = content.trim().split("\n").filter((l) => l.length > 0);
|
|
2219
2326
|
const allFeedback = lines.map((line) => JSON.parse(line));
|
|
2220
2327
|
return allFeedback.filter((f) => f.toIssueId.toLowerCase() === issueId.toLowerCase());
|
|
@@ -2233,11 +2340,11 @@ function getFeedbackStats() {
|
|
|
2233
2340
|
byType: {},
|
|
2234
2341
|
total: 0
|
|
2235
2342
|
};
|
|
2236
|
-
if (!
|
|
2343
|
+
if (!existsSync5(FEEDBACK_LOG)) {
|
|
2237
2344
|
return stats;
|
|
2238
2345
|
}
|
|
2239
2346
|
try {
|
|
2240
|
-
const content =
|
|
2347
|
+
const content = readFileSync5(FEEDBACK_LOG, "utf-8");
|
|
2241
2348
|
const lines = content.trim().split("\n").filter((l) => l.length > 0);
|
|
2242
2349
|
for (const line of lines) {
|
|
2243
2350
|
const feedback = JSON.parse(line);
|
|
@@ -2264,11 +2371,12 @@ var init_specialists = __esm({
|
|
|
2264
2371
|
init_providers();
|
|
2265
2372
|
init_tmux();
|
|
2266
2373
|
init_pipeline_notifier();
|
|
2374
|
+
init_task_readiness();
|
|
2267
2375
|
init_hooks();
|
|
2268
2376
|
execAsync = promisify(exec);
|
|
2269
|
-
SPECIALISTS_DIR =
|
|
2270
|
-
REGISTRY_FILE =
|
|
2271
|
-
TASKS_DIR =
|
|
2377
|
+
SPECIALISTS_DIR = join5(PANOPTICON_HOME, "specialists");
|
|
2378
|
+
REGISTRY_FILE = join5(SPECIALISTS_DIR, "registry.json");
|
|
2379
|
+
TASKS_DIR = join5(SPECIALISTS_DIR, "tasks");
|
|
2272
2380
|
DEFAULT_SPECIALISTS = [
|
|
2273
2381
|
{
|
|
2274
2382
|
name: "merge-agent",
|
|
@@ -2293,8 +2401,8 @@ var init_specialists = __esm({
|
|
|
2293
2401
|
}
|
|
2294
2402
|
];
|
|
2295
2403
|
gracePeriodStates = /* @__PURE__ */ new Map();
|
|
2296
|
-
FEEDBACK_DIR =
|
|
2297
|
-
FEEDBACK_LOG =
|
|
2404
|
+
FEEDBACK_DIR = join5(PANOPTICON_HOME, "specialists", "feedback");
|
|
2405
|
+
FEEDBACK_LOG = join5(FEEDBACK_DIR, "feedback.jsonl");
|
|
2298
2406
|
}
|
|
2299
2407
|
});
|
|
2300
2408
|
|
|
@@ -2318,13 +2426,13 @@ __export(specialist_logs_exports, {
|
|
|
2318
2426
|
listRunLogs: () => listRunLogs,
|
|
2319
2427
|
parseLogMetadata: () => parseLogMetadata
|
|
2320
2428
|
});
|
|
2321
|
-
import { existsSync as
|
|
2322
|
-
import { join as
|
|
2429
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, appendFileSync as appendFileSync4, readFileSync as readFileSync6, readdirSync as readdirSync4, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
2430
|
+
import { join as join6, basename as basename3 } from "path";
|
|
2323
2431
|
function getSpecialistsDir() {
|
|
2324
|
-
return
|
|
2432
|
+
return join6(getPanopticonHome(), "specialists");
|
|
2325
2433
|
}
|
|
2326
2434
|
function getRunsDirectory(projectKey, specialistType) {
|
|
2327
|
-
return
|
|
2435
|
+
return join6(getSpecialistsDir(), projectKey, specialistType, "runs");
|
|
2328
2436
|
}
|
|
2329
2437
|
function generateRunId(issueId) {
|
|
2330
2438
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
|
|
@@ -2332,11 +2440,11 @@ function generateRunId(issueId) {
|
|
|
2332
2440
|
}
|
|
2333
2441
|
function getRunLogPath(projectKey, specialistType, runId) {
|
|
2334
2442
|
const runsDir = getRunsDirectory(projectKey, specialistType);
|
|
2335
|
-
return
|
|
2443
|
+
return join6(runsDir, `${runId}.log`);
|
|
2336
2444
|
}
|
|
2337
2445
|
function ensureRunsDirectory(projectKey, specialistType) {
|
|
2338
2446
|
const runsDir = getRunsDirectory(projectKey, specialistType);
|
|
2339
|
-
if (!
|
|
2447
|
+
if (!existsSync6(runsDir)) {
|
|
2340
2448
|
mkdirSync4(runsDir, { recursive: true });
|
|
2341
2449
|
}
|
|
2342
2450
|
}
|
|
@@ -2356,22 +2464,22 @@ ${contextSeed ? contextSeed : "[No context digest available]"}
|
|
|
2356
2464
|
|
|
2357
2465
|
## Session Transcript
|
|
2358
2466
|
`;
|
|
2359
|
-
|
|
2467
|
+
writeFileSync4(filePath, header, "utf-8");
|
|
2360
2468
|
return { runId, filePath };
|
|
2361
2469
|
}
|
|
2362
2470
|
function appendToRunLog(projectKey, specialistType, runId, content) {
|
|
2363
2471
|
const filePath = getRunLogPath(projectKey, specialistType, runId);
|
|
2364
|
-
if (!
|
|
2472
|
+
if (!existsSync6(filePath)) {
|
|
2365
2473
|
throw new Error(`Run log not found: ${filePath}`);
|
|
2366
2474
|
}
|
|
2367
2475
|
appendFileSync4(filePath, content, "utf-8");
|
|
2368
2476
|
}
|
|
2369
2477
|
function finalizeRunLog(projectKey, specialistType, runId, result) {
|
|
2370
2478
|
const filePath = getRunLogPath(projectKey, specialistType, runId);
|
|
2371
|
-
if (!
|
|
2479
|
+
if (!existsSync6(filePath)) {
|
|
2372
2480
|
throw new Error(`Run log not found: ${filePath}`);
|
|
2373
2481
|
}
|
|
2374
|
-
const content =
|
|
2482
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
2375
2483
|
const startMatch = content.match(/^Started: (.+)$/m);
|
|
2376
2484
|
const startedAt = startMatch ? new Date(startMatch[1]) : /* @__PURE__ */ new Date();
|
|
2377
2485
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
@@ -2392,11 +2500,11 @@ Finished: ${finishedAt.toISOString()}
|
|
|
2392
2500
|
}
|
|
2393
2501
|
function getRunLog(projectKey, specialistType, runId) {
|
|
2394
2502
|
const filePath = getRunLogPath(projectKey, specialistType, runId);
|
|
2395
|
-
if (!
|
|
2503
|
+
if (!existsSync6(filePath)) {
|
|
2396
2504
|
return null;
|
|
2397
2505
|
}
|
|
2398
2506
|
try {
|
|
2399
|
-
return
|
|
2507
|
+
return readFileSync6(filePath, "utf-8");
|
|
2400
2508
|
} catch (error) {
|
|
2401
2509
|
console.error(`Failed to read run log ${runId}:`, error);
|
|
2402
2510
|
return null;
|
|
@@ -2431,15 +2539,15 @@ function parseLogMetadata(logContent) {
|
|
|
2431
2539
|
}
|
|
2432
2540
|
function listRunLogs(projectKey, specialistType, options = {}) {
|
|
2433
2541
|
const runsDir = getRunsDirectory(projectKey, specialistType);
|
|
2434
|
-
if (!
|
|
2542
|
+
if (!existsSync6(runsDir)) {
|
|
2435
2543
|
return [];
|
|
2436
2544
|
}
|
|
2437
2545
|
try {
|
|
2438
2546
|
const files = readdirSync4(runsDir).filter((f) => f.endsWith(".log")).map((f) => {
|
|
2439
|
-
const filePath =
|
|
2547
|
+
const filePath = join6(runsDir, f);
|
|
2440
2548
|
const stats = statSync2(filePath);
|
|
2441
2549
|
const runId = basename3(f, ".log");
|
|
2442
|
-
const content =
|
|
2550
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
2443
2551
|
const metadata = parseLogMetadata(content);
|
|
2444
2552
|
return {
|
|
2445
2553
|
runId,
|
|
@@ -2512,7 +2620,7 @@ function isRunLogActive(projectKey, specialistType, runId) {
|
|
|
2512
2620
|
}
|
|
2513
2621
|
function getRunLogSize(projectKey, specialistType, runId) {
|
|
2514
2622
|
const filePath = getRunLogPath(projectKey, specialistType, runId);
|
|
2515
|
-
if (!
|
|
2623
|
+
if (!existsSync6(filePath)) {
|
|
2516
2624
|
return null;
|
|
2517
2625
|
}
|
|
2518
2626
|
try {
|
|
@@ -2569,6 +2677,10 @@ var init_specialist_logs = __esm({
|
|
|
2569
2677
|
});
|
|
2570
2678
|
|
|
2571
2679
|
export {
|
|
2680
|
+
readWorkspacePlan,
|
|
2681
|
+
updateItemStatus,
|
|
2682
|
+
updateSubItemStatus,
|
|
2683
|
+
init_io,
|
|
2572
2684
|
readTodayCosts,
|
|
2573
2685
|
readIssueCosts,
|
|
2574
2686
|
summarizeCosts,
|
|
@@ -2660,4 +2772,4 @@ export {
|
|
|
2660
2772
|
getFeedbackStats,
|
|
2661
2773
|
init_specialists
|
|
2662
2774
|
};
|
|
2663
|
-
//# sourceMappingURL=chunk-
|
|
2775
|
+
//# sourceMappingURL=chunk-4OQ4SXQZ.js.map
|