taskplane 0.6.1 → 0.7.1
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 +6 -5
- package/dashboard/public/app.js +227 -0
- package/dashboard/public/index.html +19 -0
- package/dashboard/public/style.css +319 -0
- package/dashboard/server.cjs +219 -1
- package/extensions/taskplane/config-loader.ts +7 -1
- package/extensions/taskplane/config-schema.ts +19 -2
- package/extensions/taskplane/config.ts +23 -1
- package/extensions/taskplane/engine.ts +913 -46
- package/extensions/taskplane/execution.ts +1 -0
- package/extensions/taskplane/extension.ts +975 -54
- package/extensions/taskplane/formatting.ts +713 -712
- package/extensions/taskplane/index.ts +1 -0
- package/extensions/taskplane/merge.ts +236 -48
- package/extensions/taskplane/messages.ts +10 -0
- package/extensions/taskplane/persistence.ts +183 -3
- package/extensions/taskplane/resume.ts +243 -24
- package/extensions/taskplane/settings-tui.ts +10 -3
- package/extensions/taskplane/supervisor-primer.md +626 -0
- package/extensions/taskplane/supervisor.ts +3659 -0
- package/extensions/taskplane/types.ts +330 -3
- package/package.json +1 -1
package/dashboard/server.cjs
CHANGED
|
@@ -507,6 +507,222 @@ function loadTelemetryData(batchState) {
|
|
|
507
507
|
return result;
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
+
// ─── Supervisor Data Loading ────────────────────────────────────────────────
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Module-level tail state for supervisor JSONL files (actions.jsonl, events.jsonl).
|
|
514
|
+
* Reuses the same incremental tailing pattern as telemetry.
|
|
515
|
+
* Key: absolute file path → { offset, partial, entries }
|
|
516
|
+
*/
|
|
517
|
+
const supervisorTailStates = {
|
|
518
|
+
actions: { offset: 0, partial: "", entries: [] },
|
|
519
|
+
events: { offset: 0, partial: "", entries: [] },
|
|
520
|
+
conversation: { offset: 0, partial: "", entries: [] },
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* The last known batchId — used to detect batch changes and reset accumulators.
|
|
525
|
+
*/
|
|
526
|
+
let supervisorLastBatchId = "";
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Incrementally tail a JSONL file, accumulating parsed entries.
|
|
530
|
+
* Filters entries by batchId when provided.
|
|
531
|
+
*
|
|
532
|
+
* @param {string} filePath - Absolute path to the JSONL file
|
|
533
|
+
* @param {object} tailState - Mutable tail state { offset, partial, entries }
|
|
534
|
+
* @param {string} batchId - Batch ID to filter by (empty = no filter)
|
|
535
|
+
* @returns {object[]} The accumulated entries array (same reference as tailState.entries)
|
|
536
|
+
*/
|
|
537
|
+
function tailSupervisorJsonl(filePath, tailState, batchId) {
|
|
538
|
+
// Check file size
|
|
539
|
+
let fileSize;
|
|
540
|
+
try {
|
|
541
|
+
fileSize = fs.statSync(filePath).size;
|
|
542
|
+
} catch {
|
|
543
|
+
return tailState.entries; // File doesn't exist yet — return accumulated
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Handle file truncation/recreation
|
|
547
|
+
if (fileSize < tailState.offset) {
|
|
548
|
+
tailState.offset = 0;
|
|
549
|
+
tailState.partial = "";
|
|
550
|
+
tailState.entries = [];
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (fileSize <= tailState.offset) {
|
|
554
|
+
return tailState.entries; // No new data
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Read new bytes from offset
|
|
558
|
+
const bytesToRead = fileSize - tailState.offset;
|
|
559
|
+
const buf = Buffer.alloc(bytesToRead);
|
|
560
|
+
let fd;
|
|
561
|
+
try {
|
|
562
|
+
fd = fs.openSync(filePath, "r");
|
|
563
|
+
} catch {
|
|
564
|
+
return tailState.entries;
|
|
565
|
+
}
|
|
566
|
+
try {
|
|
567
|
+
fs.readSync(fd, buf, 0, bytesToRead, tailState.offset);
|
|
568
|
+
} catch {
|
|
569
|
+
fs.closeSync(fd);
|
|
570
|
+
return tailState.entries;
|
|
571
|
+
}
|
|
572
|
+
fs.closeSync(fd);
|
|
573
|
+
tailState.offset = fileSize;
|
|
574
|
+
|
|
575
|
+
// Split into lines, preserving partial trailing line
|
|
576
|
+
const chunk = tailState.partial + buf.toString("utf-8");
|
|
577
|
+
const lines = chunk.split("\n");
|
|
578
|
+
tailState.partial = lines.pop() || "";
|
|
579
|
+
|
|
580
|
+
for (const line of lines) {
|
|
581
|
+
const trimmed = line.trim();
|
|
582
|
+
if (!trimmed) continue;
|
|
583
|
+
try {
|
|
584
|
+
const entry = JSON.parse(trimmed);
|
|
585
|
+
// Filter by batchId if provided
|
|
586
|
+
if (batchId && entry.batchId && entry.batchId !== batchId) continue;
|
|
587
|
+
tailState.entries.push(entry);
|
|
588
|
+
} catch {
|
|
589
|
+
// Malformed JSON — skip
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Cap accumulated entries to prevent unbounded growth (keep last 500)
|
|
594
|
+
if (tailState.entries.length > 500) {
|
|
595
|
+
tailState.entries = tailState.entries.slice(-500);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return tailState.entries;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Read supervisor autonomy level from project config.
|
|
603
|
+
*
|
|
604
|
+
* Checks `.pi/taskplane-config.json` for `orchestrator.supervisor.autonomy`.
|
|
605
|
+
* Falls back to "supervised" (the default) if config is missing or malformed.
|
|
606
|
+
* This is needed because the lockfile does not contain the autonomy level.
|
|
607
|
+
*
|
|
608
|
+
* @returns {string} Autonomy level: "interactive" | "supervised" | "autonomous"
|
|
609
|
+
*/
|
|
610
|
+
function loadSupervisorAutonomy() {
|
|
611
|
+
try {
|
|
612
|
+
const configPath = path.join(REPO_ROOT, ".pi", "taskplane-config.json");
|
|
613
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
614
|
+
const config = JSON.parse(raw);
|
|
615
|
+
const autonomy = config?.orchestrator?.supervisor?.autonomy;
|
|
616
|
+
if (autonomy === "interactive" || autonomy === "supervised" || autonomy === "autonomous") {
|
|
617
|
+
return autonomy;
|
|
618
|
+
}
|
|
619
|
+
} catch {
|
|
620
|
+
// Config missing or malformed — use default
|
|
621
|
+
}
|
|
622
|
+
return "supervised"; // Default per DEFAULT_SUPERVISOR_CONFIG
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Load supervisor data for the dashboard.
|
|
627
|
+
*
|
|
628
|
+
* Reads (all from .pi/supervisor/):
|
|
629
|
+
* - lock.json: supervisor active/stale status, heartbeat, autonomy (from config)
|
|
630
|
+
* - actions.jsonl: recovery action audit trail (batch-scoped, incremental)
|
|
631
|
+
* - events.jsonl: engine + tier 0 events (batch-scoped, incremental)
|
|
632
|
+
* - conversation.jsonl: operator ↔ supervisor interaction log (spec §9.1)
|
|
633
|
+
* - summary.md: human-readable batch summary (generated on completion)
|
|
634
|
+
*
|
|
635
|
+
* Returns null when no supervisor files exist (pre-supervisor batches).
|
|
636
|
+
*
|
|
637
|
+
* @param {object|null} batchState - The batch state from batch-state.json
|
|
638
|
+
* @returns {object|null} Supervisor data object or null
|
|
639
|
+
*/
|
|
640
|
+
function loadSupervisorData(batchState) {
|
|
641
|
+
const supervisorDir = path.join(REPO_ROOT, ".pi", "supervisor");
|
|
642
|
+
const batchId = batchState ? (batchState.batchId || "") : "";
|
|
643
|
+
|
|
644
|
+
// Detect batch change — reset tail state accumulators
|
|
645
|
+
if (batchId && batchId !== supervisorLastBatchId) {
|
|
646
|
+
supervisorLastBatchId = batchId;
|
|
647
|
+
supervisorTailStates.actions = { offset: 0, partial: "", entries: [] };
|
|
648
|
+
supervisorTailStates.events = { offset: 0, partial: "", entries: [] };
|
|
649
|
+
supervisorTailStates.conversation = { offset: 0, partial: "", entries: [] };
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// ── Lockfile: supervisor status ──
|
|
653
|
+
// The lockfile contains pid, sessionId, batchId, startedAt, heartbeat.
|
|
654
|
+
// It does NOT contain autonomy — that comes from project config.
|
|
655
|
+
let lock = null;
|
|
656
|
+
try {
|
|
657
|
+
const lockPath = path.join(supervisorDir, "lock.json");
|
|
658
|
+
const raw = fs.readFileSync(lockPath, "utf-8");
|
|
659
|
+
const parsed = JSON.parse(raw);
|
|
660
|
+
if (parsed && parsed.pid && parsed.sessionId) {
|
|
661
|
+
// Determine if lock is stale (heartbeat older than 90s)
|
|
662
|
+
const heartbeatAge = parsed.heartbeat
|
|
663
|
+
? Date.now() - new Date(parsed.heartbeat).getTime()
|
|
664
|
+
: Infinity;
|
|
665
|
+
const isStale = heartbeatAge > 90_000;
|
|
666
|
+
|
|
667
|
+
lock = {
|
|
668
|
+
active: !isStale,
|
|
669
|
+
pid: parsed.pid,
|
|
670
|
+
sessionId: parsed.sessionId,
|
|
671
|
+
batchId: parsed.batchId || "",
|
|
672
|
+
startedAt: parsed.startedAt || "",
|
|
673
|
+
heartbeat: parsed.heartbeat || "",
|
|
674
|
+
// Autonomy is NOT in the lockfile — derive from project config
|
|
675
|
+
autonomy: loadSupervisorAutonomy(),
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
} catch {
|
|
679
|
+
// No lockfile or malformed — supervisor is inactive
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// ── Actions JSONL: recovery audit trail (batch-scoped, incremental) ──
|
|
683
|
+
const actionsPath = path.join(supervisorDir, "actions.jsonl");
|
|
684
|
+
const actions = tailSupervisorJsonl(actionsPath, supervisorTailStates.actions, batchId);
|
|
685
|
+
|
|
686
|
+
// ── Events JSONL: engine events (batch-scoped, incremental) ──
|
|
687
|
+
const eventsPath = path.join(supervisorDir, "events.jsonl");
|
|
688
|
+
const events = tailSupervisorJsonl(eventsPath, supervisorTailStates.events, batchId);
|
|
689
|
+
|
|
690
|
+
// ── Conversation JSONL: operator interaction log (spec §9.1) ──
|
|
691
|
+
// The supervisor writes operator↔supervisor messages to conversation.jsonl.
|
|
692
|
+
// Not yet implemented in all supervisor versions — degrade gracefully.
|
|
693
|
+
const conversationPath = path.join(supervisorDir, "conversation.jsonl");
|
|
694
|
+
const conversation = tailSupervisorJsonl(conversationPath, supervisorTailStates.conversation, batchId);
|
|
695
|
+
|
|
696
|
+
// ── Summary: human-readable batch summary (generated on completion) ──
|
|
697
|
+
// Per spec §9.1, the supervisor writes .pi/supervisor/summary.md when the
|
|
698
|
+
// batch completes or is abandoned. Read the file if it exists.
|
|
699
|
+
let summary = null;
|
|
700
|
+
try {
|
|
701
|
+
const summaryPath = path.join(supervisorDir, "summary.md");
|
|
702
|
+
summary = fs.readFileSync(summaryPath, "utf-8");
|
|
703
|
+
} catch {
|
|
704
|
+
// No summary yet — batch may still be running, or pre-supervisor batch
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// If nothing exists at all, return null (pre-supervisor batch)
|
|
708
|
+
if (!lock && actions.length === 0 && events.length === 0 && conversation.length === 0 && !summary) {
|
|
709
|
+
// Check if the supervisor directory even exists
|
|
710
|
+
try {
|
|
711
|
+
fs.statSync(supervisorDir);
|
|
712
|
+
} catch {
|
|
713
|
+
return null; // No supervisor dir → pre-supervisor batch
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
return {
|
|
718
|
+
lock,
|
|
719
|
+
actions,
|
|
720
|
+
events,
|
|
721
|
+
conversation,
|
|
722
|
+
summary,
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
|
|
510
726
|
/**
|
|
511
727
|
* Compute batch total cost from lane states (primary) and telemetry (supplementary).
|
|
512
728
|
* Lane states are authoritative — telemetry provides additional data only for lanes
|
|
@@ -541,9 +757,10 @@ function buildDashboardState() {
|
|
|
541
757
|
const laneStates = loadLaneStates();
|
|
542
758
|
const telemetry = loadTelemetryData(state);
|
|
543
759
|
const batchTotalCost = computeBatchTotalCost(laneStates, telemetry);
|
|
760
|
+
const supervisor = loadSupervisorData(state);
|
|
544
761
|
|
|
545
762
|
if (!state) {
|
|
546
|
-
return { batch: null, tmuxSessions, laneStates: {}, telemetry: {}, batchTotalCost: 0, timestamp: Date.now() };
|
|
763
|
+
return { batch: null, tmuxSessions, laneStates: {}, telemetry: {}, batchTotalCost: 0, supervisor: null, timestamp: Date.now() };
|
|
547
764
|
}
|
|
548
765
|
|
|
549
766
|
const tasks = (state.tasks || []).map((task) => {
|
|
@@ -562,6 +779,7 @@ function buildDashboardState() {
|
|
|
562
779
|
laneStates,
|
|
563
780
|
telemetry,
|
|
564
781
|
batchTotalCost,
|
|
782
|
+
supervisor,
|
|
565
783
|
batch: {
|
|
566
784
|
batchId: state.batchId,
|
|
567
785
|
phase: state.phase,
|
|
@@ -261,6 +261,9 @@ function mapOrchestratorYaml(raw: any): Partial<OrchestratorSection> {
|
|
|
261
261
|
// verification: all keys are structural (TP-032)
|
|
262
262
|
if (raw.verification) result.verification = convertStructuralKeys(raw.verification);
|
|
263
263
|
|
|
264
|
+
// supervisor: all keys are structural (TP-041)
|
|
265
|
+
if (raw.supervisor) result.supervisor = convertStructuralKeys(raw.supervisor);
|
|
266
|
+
|
|
264
267
|
return result;
|
|
265
268
|
}
|
|
266
269
|
|
|
@@ -515,6 +518,7 @@ function extractAllowlistedPreferences(raw: Record<string, any>): UserPreference
|
|
|
515
518
|
if (typeof raw.workerModel === "string") prefs.workerModel = raw.workerModel;
|
|
516
519
|
if (typeof raw.reviewerModel === "string") prefs.reviewerModel = raw.reviewerModel;
|
|
517
520
|
if (typeof raw.mergeModel === "string") prefs.mergeModel = raw.mergeModel;
|
|
521
|
+
if (typeof raw.supervisorModel === "string") prefs.supervisorModel = raw.supervisorModel;
|
|
518
522
|
if (typeof raw.dashboardPort === "number" && Number.isFinite(raw.dashboardPort)) {
|
|
519
523
|
prefs.dashboardPort = raw.dashboardPort;
|
|
520
524
|
}
|
|
@@ -541,6 +545,7 @@ function extractAllowlistedPreferences(raw: Record<string, any>): UserPreference
|
|
|
541
545
|
* prefs.workerModel → config.taskRunner.worker.model
|
|
542
546
|
* prefs.reviewerModel → config.taskRunner.reviewer.model
|
|
543
547
|
* prefs.mergeModel → config.orchestrator.merge.model
|
|
548
|
+
* prefs.supervisorModel → config.orchestrator.supervisor.model
|
|
544
549
|
* prefs.dashboardPort → (no config target yet — stored only)
|
|
545
550
|
*/
|
|
546
551
|
export function applyUserPreferences(config: TaskplaneConfig, prefs: UserPreferences): TaskplaneConfig {
|
|
@@ -554,6 +559,7 @@ export function applyUserPreferences(config: TaskplaneConfig, prefs: UserPrefere
|
|
|
554
559
|
applyStr(prefs.workerModel, (v) => { config.taskRunner.worker.model = v; });
|
|
555
560
|
applyStr(prefs.reviewerModel, (v) => { config.taskRunner.reviewer.model = v; });
|
|
556
561
|
applyStr(prefs.mergeModel, (v) => { config.orchestrator.merge.model = v; });
|
|
562
|
+
applyStr(prefs.supervisorModel, (v) => { config.orchestrator.supervisor.model = v; });
|
|
557
563
|
|
|
558
564
|
// spawnMode: enum — apply if defined (not a string-empty check)
|
|
559
565
|
if (prefs.spawnMode !== undefined) {
|
|
@@ -578,7 +584,7 @@ export function applyUserPreferences(config: TaskplaneConfig, prefs: UserPrefere
|
|
|
578
584
|
* `<configRepo>/.taskplane/`) where files are scaffolded directly
|
|
579
585
|
* without a `.pi/` subdirectory.
|
|
580
586
|
*/
|
|
581
|
-
function hasConfigFiles(root: string): boolean {
|
|
587
|
+
export function hasConfigFiles(root: string): boolean {
|
|
582
588
|
const files = [PROJECT_CONFIG_FILENAME, "task-runner.yaml", "task-orchestrator.yaml"];
|
|
583
589
|
for (const f of files) {
|
|
584
590
|
if (existsSync(join(root, ".pi", f)) || existsSync(join(root, f))) return true;
|
|
@@ -241,8 +241,8 @@ export interface OrchestratorCoreConfig {
|
|
|
241
241
|
tmuxPrefix: string;
|
|
242
242
|
/** Operator identifier. Auto-detected from OS username if empty */
|
|
243
243
|
operatorId: string;
|
|
244
|
-
/** How completed batches are integrated. manual = user runs /orch-integrate. auto =
|
|
245
|
-
integration: "manual" | "auto";
|
|
244
|
+
/** How completed batches are integrated. manual = user runs /orch-integrate. supervised = supervisor proposes plan, asks confirmation. auto = supervisor executes without asking. */
|
|
245
|
+
integration: "manual" | "supervised" | "auto";
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
/** Dependency resolution settings */
|
|
@@ -360,6 +360,14 @@ export interface VerificationConfig {
|
|
|
360
360
|
/**
|
|
361
361
|
* All orchestrator settings, previously from `.pi/task-orchestrator.yaml`.
|
|
362
362
|
*/
|
|
363
|
+
/** Supervisor agent settings (TP-041). */
|
|
364
|
+
export interface SupervisorSectionConfig {
|
|
365
|
+
/** Supervisor model (empty = inherit active session model) */
|
|
366
|
+
model: string;
|
|
367
|
+
/** Autonomy level for recovery actions */
|
|
368
|
+
autonomy: "interactive" | "supervised" | "autonomous";
|
|
369
|
+
}
|
|
370
|
+
|
|
363
371
|
export interface OrchestratorSection {
|
|
364
372
|
/** Core orchestrator settings */
|
|
365
373
|
orchestrator: OrchestratorCoreConfig;
|
|
@@ -377,6 +385,8 @@ export interface OrchestratorSection {
|
|
|
377
385
|
monitoring: MonitoringConfig;
|
|
378
386
|
/** Verification baseline fingerprinting (TP-032) */
|
|
379
387
|
verification: VerificationConfig;
|
|
388
|
+
/** Supervisor agent (TP-041) */
|
|
389
|
+
supervisor: SupervisorSectionConfig;
|
|
380
390
|
}
|
|
381
391
|
|
|
382
392
|
|
|
@@ -435,6 +445,7 @@ export interface TaskplaneConfig {
|
|
|
435
445
|
* | workerModel | taskRunner.worker.model | string |
|
|
436
446
|
* | reviewerModel | taskRunner.reviewer.model | string |
|
|
437
447
|
* | mergeModel | orchestrator.merge.model | string |
|
|
448
|
+
* | supervisorModel | orchestrator.supervisor.model | string |
|
|
438
449
|
* | dashboardPort | (preferences-only; not yet in schema)| number |
|
|
439
450
|
*/
|
|
440
451
|
export interface UserPreferences {
|
|
@@ -450,6 +461,8 @@ export interface UserPreferences {
|
|
|
450
461
|
reviewerModel?: string;
|
|
451
462
|
/** Merge model override (overrides orchestrator.merge.model) */
|
|
452
463
|
mergeModel?: string;
|
|
464
|
+
/** Supervisor model override (overrides orchestrator.supervisor.model) (TP-041) */
|
|
465
|
+
supervisorModel?: string;
|
|
453
466
|
/** Dashboard port (preferences-only; not yet wired into config schema) */
|
|
454
467
|
dashboardPort?: number;
|
|
455
468
|
}
|
|
@@ -549,6 +562,10 @@ export const DEFAULT_ORCHESTRATOR_SECTION: OrchestratorSection = {
|
|
|
549
562
|
mode: "permissive",
|
|
550
563
|
flakyReruns: 1,
|
|
551
564
|
},
|
|
565
|
+
supervisor: {
|
|
566
|
+
model: "",
|
|
567
|
+
autonomy: "supervised",
|
|
568
|
+
},
|
|
552
569
|
};
|
|
553
570
|
|
|
554
571
|
/** Default unified config */
|
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
* @module orch/config
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { loadProjectConfig, toOrchestratorConfig, toTaskRunnerConfig } from "./config-loader.ts";
|
|
14
|
+
import { loadProjectConfig, toOrchestratorConfig, toTaskRunnerConfig, hasConfigFiles } from "./config-loader.ts";
|
|
15
|
+
export { hasConfigFiles, resolveConfigRoot } from "./config-loader.ts";
|
|
15
16
|
import type { OrchestratorConfig, TaskRunnerConfig } from "./types.ts";
|
|
17
|
+
import type { SupervisorConfig } from "./supervisor.ts";
|
|
18
|
+
import { DEFAULT_SUPERVISOR_CONFIG } from "./supervisor.ts";
|
|
16
19
|
|
|
17
20
|
// ── Config Loading ───────────────────────────────────────────────────
|
|
18
21
|
|
|
@@ -49,3 +52,22 @@ export function loadTaskRunnerConfig(cwd: string, pointerConfigRoot?: string): T
|
|
|
49
52
|
const unified = loadProjectConfig(cwd, pointerConfigRoot);
|
|
50
53
|
return toTaskRunnerConfig(unified);
|
|
51
54
|
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Load supervisor config from unified project config.
|
|
58
|
+
*
|
|
59
|
+
* Extracts the `orchestrator.supervisor` section from the unified config.
|
|
60
|
+
* Falls back to defaults if the section is missing (backward compatibility
|
|
61
|
+
* with configs created before TP-041).
|
|
62
|
+
*
|
|
63
|
+
* @since TP-041
|
|
64
|
+
*/
|
|
65
|
+
export function loadSupervisorConfig(cwd: string, pointerConfigRoot?: string): SupervisorConfig {
|
|
66
|
+
const unified = loadProjectConfig(cwd, pointerConfigRoot);
|
|
67
|
+
const section = unified.orchestrator.supervisor;
|
|
68
|
+
if (!section) return { ...DEFAULT_SUPERVISOR_CONFIG };
|
|
69
|
+
return {
|
|
70
|
+
model: section.model ?? DEFAULT_SUPERVISOR_CONFIG.model,
|
|
71
|
+
autonomy: section.autonomy ?? DEFAULT_SUPERVISOR_CONFIG.autonomy,
|
|
72
|
+
};
|
|
73
|
+
}
|