@geraldmaron/construct 1.0.14 → 1.0.15
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 -0
- package/bin/construct +3 -3
- package/commands/build/feature.md +0 -6
- package/commands/build/fix.md +0 -6
- package/commands/design/access.md +0 -6
- package/commands/design/flow.md +0 -6
- package/commands/design/ui.md +0 -6
- package/commands/measure/experiment.md +0 -6
- package/commands/measure/metrics.md +0 -6
- package/commands/measure/results.md +0 -6
- package/commands/plan/api.md +0 -6
- package/commands/plan/challenge.md +0 -6
- package/commands/plan/decide.md +0 -6
- package/commands/plan/feature.md +0 -7
- package/commands/plan/requirements.md +0 -6
- package/commands/remember/context.md +0 -6
- package/commands/remember/handoff.md +0 -6
- package/commands/remember/runbook.md +0 -6
- package/commands/review/code.md +0 -6
- package/commands/review/quality.md +0 -6
- package/commands/review/security.md +0 -6
- package/commands/ship/ready.md +0 -6
- package/commands/ship/release.md +0 -6
- package/commands/ship/status.md +0 -6
- package/commands/understand/docs.md +0 -6
- package/commands/understand/research.md +0 -6
- package/commands/understand/this.md +0 -6
- package/commands/understand/why.md +0 -6
- package/commands/work/clean.md +0 -6
- package/commands/work/drive.md +0 -6
- package/commands/work/optimize-prompts.md +0 -6
- package/commands/work/parallel-review.md +0 -6
- package/lib/beads-client.mjs +19 -1
- package/lib/comment-lint.mjs +5 -1
- package/lib/context-state.mjs +15 -3
- package/lib/contracts/validate.mjs +54 -10
- package/lib/contracts/violation-log.mjs +154 -0
- package/lib/document-extract/docling-client.mjs +114 -0
- package/lib/document-extract/docling-sidecar.py +122 -0
- package/lib/document-extract/whisper-client.mjs +79 -0
- package/lib/document-extract.mjs +73 -8
- package/lib/document-ingest.mjs +26 -4
- package/lib/embed/daemon.mjs +6 -1
- package/lib/flavors/loader.mjs +1 -1
- package/lib/handoffs/contract.mjs +2 -2
- package/lib/hooks/session-start.mjs +1 -1
- package/lib/ingest/chunker.mjs +94 -0
- package/lib/ingest/pipeline.mjs +53 -0
- package/lib/ingest/store.mjs +82 -0
- package/lib/knowledge/search.mjs +12 -0
- package/lib/mcp/server.mjs +142 -1
- package/lib/mcp/tools/skills.mjs +6 -3
- package/lib/mcp/tools/workflow.mjs +6 -2
- package/lib/observation-store.mjs +14 -5
- package/lib/ollama-manager.mjs +32 -28
- package/lib/prompt-composer.js +11 -1
- package/lib/runtime/uv-bootstrap.mjs +129 -0
- package/lib/runtime/whisper-bootstrap.mjs +102 -0
- package/lib/server/index.mjs +22 -1
- package/lib/server/static/index.html +1 -1
- package/lib/specialists/postconditions.mjs +1 -1
- package/lib/sync/skill-frontmatter.mjs +113 -21
- package/lib/tracking-surfaces.mjs +2 -0
- package/lib/validators/skills.mjs +86 -54
- package/package.json +5 -5
- package/personas/construct.md +4 -7
- package/platforms/claude/settings.template.json +1 -1
- package/rules/common/beads-hygiene.md +3 -9
- package/rules/common/code-review.md +3 -6
- package/rules/common/coding-style.md +3 -6
- package/rules/common/comments.md +3 -7
- package/rules/common/commit-approval.md +3 -6
- package/rules/common/cx-agent-routing.md +3 -7
- package/rules/common/cx-skill-routing.md +3 -3
- package/rules/common/doc-ownership.md +3 -8
- package/rules/common/efficiency.md +3 -8
- package/rules/common/framing.md +3 -8
- package/rules/common/git-workflow.md +3 -5
- package/rules/common/no-fabrication.md +4 -12
- package/rules/common/patterns.md +3 -5
- package/rules/common/release-gates.md +3 -8
- package/rules/common/research.md +3 -8
- package/rules/common/review-before-change.md +3 -9
- package/rules/common/security.md +3 -6
- package/rules/common/skill-composition.md +3 -7
- package/rules/common/testing.md +3 -6
- package/rules/golang/coding-style.md +1 -5
- package/rules/golang/hooks.md +1 -5
- package/rules/golang/patterns.md +1 -5
- package/rules/golang/security.md +1 -5
- package/rules/golang/testing.md +1 -5
- package/rules/python/coding-style.md +1 -5
- package/rules/python/hooks.md +1 -5
- package/rules/python/patterns.md +1 -5
- package/rules/python/security.md +1 -5
- package/rules/python/testing.md +1 -5
- package/rules/swift/coding-style.md +1 -5
- package/rules/swift/hooks.md +1 -5
- package/rules/swift/patterns.md +1 -5
- package/rules/swift/security.md +1 -5
- package/rules/swift/testing.md +1 -5
- package/rules/typescript/coding-style.md +1 -5
- package/rules/typescript/hooks.md +1 -5
- package/rules/typescript/patterns.md +1 -5
- package/rules/typescript/security.md +1 -5
- package/rules/typescript/testing.md +1 -5
- package/rules/web/coding-style.md +3 -5
- package/rules/web/design-quality.md +3 -5
- package/rules/web/hooks.md +3 -5
- package/rules/web/patterns.md +3 -5
- package/rules/web/performance.md +3 -5
- package/rules/web/security.md +3 -5
- package/rules/web/testing.md +3 -5
- package/skills/ai/agent-dev.md +4 -5
- package/skills/ai/llm-security.md +4 -5
- package/skills/ai/ml-ops.md +4 -5
- package/skills/ai/orchestration-workflow.md +4 -5
- package/skills/ai/prompt-and-eval.md +4 -5
- package/skills/ai/prompt-optimizer.md +4 -6
- package/skills/ai/rag-system.md +4 -5
- package/skills/architecture/api-design.md +4 -5
- package/skills/architecture/caching.md +4 -5
- package/skills/architecture/cloud-native.md +4 -5
- package/skills/architecture/message-queue.md +4 -5
- package/skills/architecture/security-arch.md +4 -5
- package/skills/compliance/ai-disclosure.md +4 -5
- package/skills/compliance/data-privacy.md +4 -5
- package/skills/compliance/license-audit.md +4 -5
- package/skills/compliance/regulatory-review.md +4 -5
- package/skills/development/cpp.md +4 -5
- package/skills/development/go.md +4 -5
- package/skills/development/java.md +4 -5
- package/skills/development/kotlin.md +4 -5
- package/skills/development/mobile-crossplatform.md +4 -5
- package/skills/development/python.md +4 -5
- package/skills/development/rust.md +4 -5
- package/skills/development/shell.md +4 -5
- package/skills/development/swift.md +4 -5
- package/skills/development/typescript.md +4 -5
- package/skills/devops/ci-cd.md +4 -5
- package/skills/devops/containerization.md +4 -5
- package/skills/devops/cost-optimization.md +4 -5
- package/skills/devops/data-engineering.md +4 -5
- package/skills/devops/database.md +4 -5
- package/skills/devops/dependency-management.md +4 -5
- package/skills/devops/devsecops.md +4 -5
- package/skills/devops/git-workflow.md +4 -5
- package/skills/devops/incident-response.md +4 -5
- package/skills/devops/monorepo.md +4 -5
- package/skills/devops/observability.md +4 -5
- package/skills/devops/performance.md +4 -5
- package/skills/devops/testing.md +4 -5
- package/skills/docs/adr-workflow.md +4 -7
- package/skills/docs/backlog-proposal-workflow.md +4 -3
- package/skills/docs/customer-profile-workflow.md +4 -3
- package/skills/docs/document-ingest-workflow.md +4 -3
- package/skills/docs/evidence-ingest-workflow.md +4 -3
- package/skills/docs/init-docs.md +4 -5
- package/skills/docs/init-project.md +4 -5
- package/skills/docs/prd-workflow.md +4 -7
- package/skills/docs/prfaq-workflow.md +4 -3
- package/skills/docs/product-intelligence-review.md +4 -3
- package/skills/docs/product-intelligence-workflow.md +4 -6
- package/skills/docs/product-signal-workflow.md +4 -5
- package/skills/docs/research-workflow.md +4 -5
- package/skills/docs/runbook-workflow.md +4 -5
- package/skills/docs/strategy-workflow.md +4 -5
- package/skills/exploration/dependency-graph-reading.md +4 -7
- package/skills/exploration/repo-map.md +4 -5
- package/skills/exploration/tracer-bullet-method.md +4 -7
- package/skills/exploration/unknown-codebase-onboarding.md +4 -7
- package/skills/frameworks/django.md +4 -5
- package/skills/frameworks/nextjs.md +4 -5
- package/skills/frameworks/react.md +4 -5
- package/skills/frameworks/spring-boot.md +4 -5
- package/skills/frontend-design/accessibility.md +4 -5
- package/skills/frontend-design/component-patterns.md +4 -5
- package/skills/frontend-design/engineering.md +4 -5
- package/skills/frontend-design/state-management.md +4 -5
- package/skills/frontend-design/ui-aesthetics.md +4 -5
- package/skills/frontend-design/ux-principles.md +4 -5
- package/skills/operating/change-management.md +4 -8
- package/skills/operating/incident-response.md +4 -8
- package/skills/operating/oncall-rotation.md +4 -7
- package/skills/operating/orchestration-reference.md +4 -10
- package/skills/quality-gates/review-work.md +4 -5
- package/skills/quality-gates/verify-change.md +4 -5
- package/skills/quality-gates/verify-module.md +4 -5
- package/skills/quality-gates/verify-quality.md +4 -5
- package/skills/quality-gates/verify-security.md +4 -5
- package/skills/roles/architect.ai-systems.md +6 -9
- package/skills/roles/architect.data.md +6 -9
- package/skills/roles/architect.enterprise.md +6 -9
- package/skills/roles/architect.integration.md +6 -9
- package/skills/roles/architect.md +7 -14
- package/skills/roles/architect.platform.md +6 -9
- package/skills/roles/data-analyst.experiment.md +6 -9
- package/skills/roles/data-analyst.md +6 -9
- package/skills/roles/data-analyst.product-intelligence.md +7 -9
- package/skills/roles/data-analyst.product.md +6 -9
- package/skills/roles/data-analyst.telemetry.md +7 -9
- package/skills/roles/data-engineer.pipeline.md +6 -9
- package/skills/roles/data-engineer.vector-retrieval.md +7 -9
- package/skills/roles/data-engineer.warehouse.md +6 -9
- package/skills/roles/debugger.md +6 -9
- package/skills/roles/designer.accessibility.md +6 -9
- package/skills/roles/designer.md +7 -9
- package/skills/roles/engineer.ai.md +6 -9
- package/skills/roles/engineer.data.md +6 -9
- package/skills/roles/engineer.md +9 -9
- package/skills/roles/engineer.platform.md +6 -9
- package/skills/roles/operator.docs.md +6 -9
- package/skills/roles/operator.md +9 -9
- package/skills/roles/operator.release.md +6 -9
- package/skills/roles/operator.sre.md +6 -9
- package/skills/roles/orchestrator.md +6 -9
- package/skills/roles/product-manager.ai-product.md +6 -9
- package/skills/roles/product-manager.business-strategy.md +6 -9
- package/skills/roles/product-manager.enterprise.md +6 -9
- package/skills/roles/product-manager.growth.md +7 -9
- package/skills/roles/product-manager.md +7 -9
- package/skills/roles/product-manager.platform.md +6 -9
- package/skills/roles/product-manager.product.md +6 -9
- package/skills/roles/qa.ai-eval.md +8 -9
- package/skills/roles/qa.api-contract.md +7 -9
- package/skills/roles/qa.data-pipeline.md +7 -9
- package/skills/roles/qa.md +7 -9
- package/skills/roles/qa.test-automation.md +6 -9
- package/skills/roles/qa.web-ui.md +7 -9
- package/skills/roles/researcher.explorer.md +6 -9
- package/skills/roles/researcher.md +8 -9
- package/skills/roles/researcher.ux.md +6 -9
- package/skills/roles/reviewer.devil-advocate.md +6 -9
- package/skills/roles/reviewer.evaluator.md +6 -9
- package/skills/roles/reviewer.md +9 -9
- package/skills/roles/reviewer.trace.md +6 -9
- package/skills/roles/security.ai.md +7 -9
- package/skills/roles/security.appsec.md +6 -9
- package/skills/roles/security.cloud.md +6 -9
- package/skills/roles/security.legal-compliance.md +6 -9
- package/skills/roles/security.md +7 -9
- package/skills/roles/security.privacy.md +7 -9
- package/skills/roles/security.supply-chain.md +7 -9
- package/skills/security/blue-team.md +4 -5
- package/skills/security/code-audit.md +4 -5
- package/skills/security/pentest.md +4 -5
- package/skills/security/red-team.md +4 -5
- package/skills/security/threat-intel.md +4 -5
- package/skills/security/vuln-research.md +4 -5
- package/skills/strategy/competitive-landscape.md +4 -8
- package/skills/strategy/market-research-methods.md +4 -8
- package/skills/strategy/narrative-arc.md +4 -8
- package/skills/strategy/pricing-positioning.md +4 -8
- package/skills/utility/clean-code.md +4 -5
- package/specialists/policy-inventory.json +14 -0
- package/lib/specialist-contracts-enforce.mjs +0 -172
- package/rules/common/agents.md +0 -28
- package/rules/common/development-workflow.md +0 -32
- package/rules/common/performance.md +0 -55
package/lib/ollama-manager.mjs
CHANGED
|
@@ -6,10 +6,26 @@
|
|
|
6
6
|
* pulling new models, and configuring Ollama as a provider.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { execFileSync } from 'node:child_process';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import { homedir } from 'node:os';
|
|
12
12
|
|
|
13
|
+
// argv-array form for every shell-out: no shell interpolation, model names and
|
|
14
|
+
// JSON bodies are passed as discrete arguments / stdin so user-supplied values
|
|
15
|
+
// cannot break out of the command.
|
|
16
|
+
|
|
17
|
+
function curlGet(url, { timeout } = {}) {
|
|
18
|
+
return execFileSync('curl', ['-s', url], { encoding: 'utf8', timeout });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function curlPost(url, body, { timeout } = {}) {
|
|
22
|
+
return execFileSync('curl', ['-s', url, '--data-binary', '@-'], {
|
|
23
|
+
encoding: 'utf8',
|
|
24
|
+
timeout,
|
|
25
|
+
input: typeof body === 'string' ? body : JSON.stringify(body),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
13
29
|
const OLLAMA_BASE_URL = process.env.OLLAMA_BASE_URL || process.env.OLLAMA_HOST || 'http://localhost:11434';
|
|
14
30
|
|
|
15
31
|
/**
|
|
@@ -17,14 +33,11 @@ const OLLAMA_BASE_URL = process.env.OLLAMA_BASE_URL || process.env.OLLAMA_HOST |
|
|
|
17
33
|
*/
|
|
18
34
|
export function checkOllamaStatus() {
|
|
19
35
|
try {
|
|
20
|
-
|
|
36
|
+
execFileSync('which', ['ollama'], { stdio: 'pipe' });
|
|
21
37
|
const installed = true;
|
|
22
|
-
|
|
38
|
+
|
|
23
39
|
try {
|
|
24
|
-
const response =
|
|
25
|
-
encoding: 'utf8',
|
|
26
|
-
timeout: 3000
|
|
27
|
-
});
|
|
40
|
+
const response = curlGet(`${OLLAMA_BASE_URL}/api/tags`, { timeout: 3000 });
|
|
28
41
|
const data = JSON.parse(response);
|
|
29
42
|
return {
|
|
30
43
|
installed,
|
|
@@ -54,10 +67,7 @@ export function checkOllamaStatus() {
|
|
|
54
67
|
*/
|
|
55
68
|
export function listModels() {
|
|
56
69
|
try {
|
|
57
|
-
const response =
|
|
58
|
-
encoding: 'utf8',
|
|
59
|
-
timeout: 5000
|
|
60
|
-
});
|
|
70
|
+
const response = curlGet(`${OLLAMA_BASE_URL}/api/tags`, { timeout: 5000 });
|
|
61
71
|
const data = JSON.parse(response);
|
|
62
72
|
return data.models || [];
|
|
63
73
|
} catch (e) {
|
|
@@ -71,7 +81,7 @@ export function listModels() {
|
|
|
71
81
|
export function pullModel(modelName, { verbose = false } = {}) {
|
|
72
82
|
console.log(`Pulling ${modelName}...`);
|
|
73
83
|
try {
|
|
74
|
-
const output =
|
|
84
|
+
const output = execFileSync('ollama', ['pull', modelName], {
|
|
75
85
|
encoding: 'utf8',
|
|
76
86
|
stdio: verbose ? 'inherit' : 'pipe'
|
|
77
87
|
});
|
|
@@ -86,7 +96,7 @@ export function pullModel(modelName, { verbose = false } = {}) {
|
|
|
86
96
|
*/
|
|
87
97
|
export function removeModel(modelName) {
|
|
88
98
|
try {
|
|
89
|
-
|
|
99
|
+
execFileSync('ollama', ['rm', modelName], { encoding: 'utf8' });
|
|
90
100
|
return { success: true };
|
|
91
101
|
} catch (e) {
|
|
92
102
|
return { success: false, error: e.message };
|
|
@@ -98,10 +108,7 @@ export function removeModel(modelName) {
|
|
|
98
108
|
*/
|
|
99
109
|
export function showModel(modelName) {
|
|
100
110
|
try {
|
|
101
|
-
const response =
|
|
102
|
-
encoding: 'utf8',
|
|
103
|
-
timeout: 5000
|
|
104
|
-
});
|
|
111
|
+
const response = curlPost(`${OLLAMA_BASE_URL}/api/show`, { name: modelName }, { timeout: 5000 });
|
|
105
112
|
return JSON.parse(response);
|
|
106
113
|
} catch (e) {
|
|
107
114
|
throw new Error(`Failed to fetch model details: ${e.message}`);
|
|
@@ -113,14 +120,11 @@ export function showModel(modelName) {
|
|
|
113
120
|
*/
|
|
114
121
|
export function testModel(modelName, prompt = 'Say hello in one sentence.') {
|
|
115
122
|
try {
|
|
116
|
-
const response =
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
encoding: 'utf8',
|
|
122
|
-
timeout: 30000
|
|
123
|
-
});
|
|
123
|
+
const response = curlPost(
|
|
124
|
+
`${OLLAMA_BASE_URL}/api/generate`,
|
|
125
|
+
{ model: modelName, prompt, stream: false },
|
|
126
|
+
{ timeout: 30000 },
|
|
127
|
+
);
|
|
124
128
|
const data = JSON.parse(response);
|
|
125
129
|
return {
|
|
126
130
|
success: true,
|
|
@@ -371,7 +375,7 @@ async function cmdOllamaSetup(args) {
|
|
|
371
375
|
if (autoYes || process.platform === 'darwin') {
|
|
372
376
|
console.log('Installing via Homebrew...');
|
|
373
377
|
try {
|
|
374
|
-
|
|
378
|
+
execFileSync('brew', ['install', 'ollama'], { stdio: 'inherit' });
|
|
375
379
|
console.log('✅ Ollama installed');
|
|
376
380
|
} catch (e) {
|
|
377
381
|
console.error('❌ Installation failed. Please install manually: https://ollama.com');
|
|
@@ -390,11 +394,11 @@ async function cmdOllamaSetup(args) {
|
|
|
390
394
|
console.log('⚠️ Ollama is not running');
|
|
391
395
|
console.log('Starting Ollama service...');
|
|
392
396
|
try {
|
|
393
|
-
|
|
397
|
+
execFileSync('brew', ['services', 'start', 'ollama'], { stdio: 'pipe' });
|
|
394
398
|
console.log('✅ Ollama service started');
|
|
395
399
|
// Wait for it to be ready
|
|
396
400
|
console.log('Waiting for Ollama to be ready...');
|
|
397
|
-
|
|
401
|
+
execFileSync('sleep', ['3']);
|
|
398
402
|
} catch (e) {
|
|
399
403
|
console.log('You can start it manually with: ollama serve');
|
|
400
404
|
}
|
package/lib/prompt-composer.js
CHANGED
|
@@ -94,7 +94,17 @@ function compactTokens(text, tokenLimit = 300, { modelId = 'default' } = {}) {
|
|
|
94
94
|
function readPromptBody(promptFile, rootDir) {
|
|
95
95
|
const filePath = path.join(rootDir, promptFile);
|
|
96
96
|
if (!fs.existsSync(filePath)) return '';
|
|
97
|
-
|
|
97
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
98
|
+
return stripLeadingYamlFrontmatter(raw).trim();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function stripLeadingYamlFrontmatter(content) {
|
|
102
|
+
if (!content.startsWith('---\n') && !content.startsWith('---\r\n')) return content;
|
|
103
|
+
const closeIdx = content.indexOf('\n---', 4);
|
|
104
|
+
if (closeIdx === -1) return content;
|
|
105
|
+
const afterClose = content.indexOf('\n', closeIdx + 1);
|
|
106
|
+
if (afterClose === -1) return '';
|
|
107
|
+
return content.slice(afterClose + 1);
|
|
98
108
|
}
|
|
99
109
|
|
|
100
110
|
function buildLearnedPatternsBlock(agentName, {
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/runtime/uv-bootstrap.mjs — idempotent uv + docling venv provisioner.
|
|
3
|
+
*
|
|
4
|
+
* On first use: downloads uv via the official Astral installer, creates
|
|
5
|
+
* `<rootDir>/.cx/runtime/docling/.venv`, installs the pinned docling release.
|
|
6
|
+
* Subsequent calls are no-ops if the venv is already present.
|
|
7
|
+
*
|
|
8
|
+
* Best-practice notes (2026-06):
|
|
9
|
+
* - uv (Astral) is the consensus Python tooling, ~10× faster than pip, single
|
|
10
|
+
* binary, lockfile-based. OpenAI acquired Astral on 2026-03-19; the project
|
|
11
|
+
* remains Apache-2.0 and actively maintained, but single-vendor governance
|
|
12
|
+
* is a documented concern — flagged for re-evaluation if licensing shifts.
|
|
13
|
+
* - docling (IBM, MIT, donated to LF AI & Data early 2026) is the consensus
|
|
14
|
+
* multi-format doc parser for layout-aware MD/JSON output.
|
|
15
|
+
*
|
|
16
|
+
* Returns the path to the venv's python binary so callers can spawn the
|
|
17
|
+
* sidecar without relying on PATH state.
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
20
|
+
import { spawnSync } from 'node:child_process';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import os from 'node:os';
|
|
23
|
+
|
|
24
|
+
const DOCLING_PIN = '2.45.0';
|
|
25
|
+
const UV_INSTALL_URL = 'https://astral.sh/uv/install.sh';
|
|
26
|
+
const UV_TIMEOUT_MS = 120_000;
|
|
27
|
+
const VENV_TIMEOUT_MS = 240_000;
|
|
28
|
+
const INSTALL_TIMEOUT_MS = 600_000;
|
|
29
|
+
|
|
30
|
+
export function defaultRuntimeDir(cwd = process.cwd()) {
|
|
31
|
+
return path.join(cwd, '.cx', 'runtime', 'docling');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function which(bin) {
|
|
35
|
+
const result = spawnSync(process.platform === 'win32' ? 'where' : 'which', [bin], { encoding: 'utf8' });
|
|
36
|
+
if (result.status !== 0) return null;
|
|
37
|
+
return result.stdout.trim().split('\n')[0] || null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function ensureUv(installDir) {
|
|
41
|
+
const fromPath = which('uv');
|
|
42
|
+
if (fromPath) return fromPath;
|
|
43
|
+
const cachedUv = path.join(installDir, 'bin', 'uv');
|
|
44
|
+
if (existsSync(cachedUv)) return cachedUv;
|
|
45
|
+
mkdirSync(installDir, { recursive: true });
|
|
46
|
+
const env = { ...process.env, UV_INSTALL_DIR: path.join(installDir, 'bin'), UV_NO_MODIFY_PATH: '1' };
|
|
47
|
+
const sh = spawnSync('sh', ['-c', `curl -LsSf ${UV_INSTALL_URL} | sh`], {
|
|
48
|
+
env,
|
|
49
|
+
timeout: UV_TIMEOUT_MS,
|
|
50
|
+
encoding: 'utf8',
|
|
51
|
+
});
|
|
52
|
+
if (sh.status !== 0) {
|
|
53
|
+
throw new Error(`uv install failed (${sh.status}): ${sh.stderr || sh.stdout}`);
|
|
54
|
+
}
|
|
55
|
+
if (!existsSync(cachedUv)) {
|
|
56
|
+
throw new Error(`uv install reported success but binary not found at ${cachedUv}`);
|
|
57
|
+
}
|
|
58
|
+
return cachedUv;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function pythonBinFor(venvDir) {
|
|
62
|
+
const candidates = process.platform === 'win32'
|
|
63
|
+
? [path.join(venvDir, 'Scripts', 'python.exe')]
|
|
64
|
+
: [path.join(venvDir, 'bin', 'python'), path.join(venvDir, 'bin', 'python3')];
|
|
65
|
+
return candidates.find((p) => existsSync(p));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function readMarker(markerPath) {
|
|
69
|
+
try { return JSON.parse(readFileSync(markerPath, 'utf8')); }
|
|
70
|
+
catch { return null; }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function writeMarker(markerPath, payload) {
|
|
74
|
+
mkdirSync(path.dirname(markerPath), { recursive: true });
|
|
75
|
+
writeFileSync(markerPath, JSON.stringify(payload, null, 2) + '\n', 'utf8');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function ensureDoclingVenv({ runtimeDir = defaultRuntimeDir(), force = false } = {}) {
|
|
79
|
+
mkdirSync(runtimeDir, { recursive: true });
|
|
80
|
+
const venvDir = path.join(runtimeDir, '.venv');
|
|
81
|
+
const markerPath = path.join(runtimeDir, '.install-marker.json');
|
|
82
|
+
const existingMarker = readMarker(markerPath);
|
|
83
|
+
const existingPython = pythonBinFor(venvDir);
|
|
84
|
+
|
|
85
|
+
if (!force && existingPython && existingMarker?.doclingVersion === DOCLING_PIN) {
|
|
86
|
+
return { pythonBin: existingPython, venvDir, runtimeDir, fresh: false };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const uv = ensureUv(runtimeDir);
|
|
90
|
+
|
|
91
|
+
const venvResult = spawnSync(uv, ['venv', venvDir, '--python', '3.11'], {
|
|
92
|
+
timeout: VENV_TIMEOUT_MS,
|
|
93
|
+
encoding: 'utf8',
|
|
94
|
+
});
|
|
95
|
+
if (venvResult.status !== 0) {
|
|
96
|
+
throw new Error(`uv venv failed (${venvResult.status}): ${venvResult.stderr}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const installResult = spawnSync(uv, ['pip', 'install', '--python', pythonBinFor(venvDir), `docling==${DOCLING_PIN}`], {
|
|
100
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
101
|
+
encoding: 'utf8',
|
|
102
|
+
});
|
|
103
|
+
if (installResult.status !== 0) {
|
|
104
|
+
throw new Error(`docling install failed (${installResult.status}): ${installResult.stderr}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const pythonBin = pythonBinFor(venvDir);
|
|
108
|
+
writeMarker(markerPath, {
|
|
109
|
+
doclingVersion: DOCLING_PIN,
|
|
110
|
+
installedAt: new Date().toISOString(),
|
|
111
|
+
pythonBin,
|
|
112
|
+
uvVersion: spawnSync(uv, ['--version'], { encoding: 'utf8' }).stdout.trim() || null,
|
|
113
|
+
platform: `${process.platform}-${process.arch}`,
|
|
114
|
+
node: process.version,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return { pythonBin, venvDir, runtimeDir, fresh: true };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function describeDoclingRuntime({ runtimeDir = defaultRuntimeDir() } = {}) {
|
|
121
|
+
const venvDir = path.join(runtimeDir, '.venv');
|
|
122
|
+
return {
|
|
123
|
+
runtimeDir,
|
|
124
|
+
venvDir,
|
|
125
|
+
pythonBin: pythonBinFor(venvDir),
|
|
126
|
+
marker: readMarker(path.join(runtimeDir, '.install-marker.json')),
|
|
127
|
+
available: !!pythonBinFor(venvDir),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/runtime/whisper-bootstrap.mjs — provision whisper.cpp binary + ASR model.
|
|
3
|
+
*
|
|
4
|
+
* Strategy (2026-06):
|
|
5
|
+
* 1. Prefer system-installed whisper-cli (or legacy `main`) on PATH —
|
|
6
|
+
* `brew install whisper-cpp` (macOS, Metal-accelerated) or distro
|
|
7
|
+
* package on Linux.
|
|
8
|
+
* 2. Fall back to bundled `whisper-cli` cached at <runtimeDir>/bin/ if a
|
|
9
|
+
* previous bootstrap fetched one.
|
|
10
|
+
* 3. If neither, return a structured "not-available" descriptor — caller
|
|
11
|
+
* surfaces a user-actionable install hint rather than crashing.
|
|
12
|
+
*
|
|
13
|
+
* Model files are downloaded directly from Hugging Face the first time the
|
|
14
|
+
* default `base.en` (or env-overridden) model is requested. Models cache at
|
|
15
|
+
* <runtimeDir>/models/. base.en is ~150MB; smaller than the small.en or
|
|
16
|
+
* medium.en model that callers can opt into via CONSTRUCT_WHISPER_MODEL.
|
|
17
|
+
*
|
|
18
|
+
* No bundled distribution: whisper.cpp upstream does not publish stable
|
|
19
|
+
* pre-built CLI binaries across platforms, and building from source via
|
|
20
|
+
* cmake adds a heavyweight first-run path. Defer that to a future opt-in.
|
|
21
|
+
*/
|
|
22
|
+
import { existsSync, mkdirSync, statSync, createWriteStream } from 'node:fs';
|
|
23
|
+
import { spawnSync } from 'node:child_process';
|
|
24
|
+
import { pipeline } from 'node:stream/promises';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import os from 'node:os';
|
|
27
|
+
|
|
28
|
+
const HF_MODEL_BASE_URL = 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main';
|
|
29
|
+
const DEFAULT_MODEL = process.env.CONSTRUCT_WHISPER_MODEL || 'base.en';
|
|
30
|
+
const KNOWN_MODELS = new Set(['tiny', 'tiny.en', 'base', 'base.en', 'small', 'small.en', 'medium', 'medium.en', 'large-v3', 'large-v3-turbo']);
|
|
31
|
+
|
|
32
|
+
export function defaultRuntimeDir(cwd = process.cwd()) {
|
|
33
|
+
return path.join(cwd, '.cx', 'runtime', 'whisper');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function which(bin) {
|
|
37
|
+
const result = spawnSync(process.platform === 'win32' ? 'where' : 'which', [bin], { encoding: 'utf8' });
|
|
38
|
+
if (result.status !== 0) return null;
|
|
39
|
+
return result.stdout.trim().split('\n')[0] || null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function locateWhisperBinary({ runtimeDir = defaultRuntimeDir() } = {}) {
|
|
43
|
+
for (const candidate of ['whisper-cli', 'whisper-cpp', 'main']) {
|
|
44
|
+
const found = which(candidate);
|
|
45
|
+
if (found) return { binary: found, source: 'system', name: candidate };
|
|
46
|
+
}
|
|
47
|
+
const cached = path.join(runtimeDir, 'bin', 'whisper-cli');
|
|
48
|
+
if (existsSync(cached)) return { binary: cached, source: 'cached', name: 'whisper-cli' };
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function describeWhisperRuntime({ runtimeDir = defaultRuntimeDir() } = {}) {
|
|
53
|
+
const located = locateWhisperBinary({ runtimeDir });
|
|
54
|
+
const modelPath = modelPathFor(DEFAULT_MODEL, runtimeDir);
|
|
55
|
+
return {
|
|
56
|
+
runtimeDir,
|
|
57
|
+
binary: located,
|
|
58
|
+
defaultModel: DEFAULT_MODEL,
|
|
59
|
+
modelPath,
|
|
60
|
+
modelInstalled: existsSync(modelPath),
|
|
61
|
+
available: !!located,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function installHint() {
|
|
66
|
+
if (process.platform === 'darwin') return 'brew install whisper-cpp';
|
|
67
|
+
if (process.platform === 'linux') return 'See https://github.com/ggml-org/whisper.cpp#quick-start (build from source via cmake or install via your distro package manager).';
|
|
68
|
+
return 'See https://github.com/ggml-org/whisper.cpp#quick-start';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function modelPathFor(modelName, runtimeDir) {
|
|
72
|
+
return path.join(runtimeDir, 'models', `ggml-${modelName}.bin`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function ensureWhisperModel({ runtimeDir = defaultRuntimeDir(), model = DEFAULT_MODEL } = {}) {
|
|
76
|
+
if (!KNOWN_MODELS.has(model)) {
|
|
77
|
+
throw new Error(`unknown whisper model: ${model} (allowed: ${[...KNOWN_MODELS].join(', ')})`);
|
|
78
|
+
}
|
|
79
|
+
const target = modelPathFor(model, runtimeDir);
|
|
80
|
+
if (existsSync(target) && statSync(target).size > 1024) return target;
|
|
81
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
82
|
+
const url = `${HF_MODEL_BASE_URL}/ggml-${model}.bin`;
|
|
83
|
+
const response = await fetch(url, { redirect: 'follow' });
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
throw new Error(`failed to download whisper model ${model}: ${response.status} ${response.statusText} (${url})`);
|
|
86
|
+
}
|
|
87
|
+
await pipeline(response.body, createWriteStream(target));
|
|
88
|
+
return target;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function ensureWhisperRuntime({ runtimeDir = defaultRuntimeDir(), model = DEFAULT_MODEL } = {}) {
|
|
92
|
+
mkdirSync(runtimeDir, { recursive: true });
|
|
93
|
+
const located = locateWhisperBinary({ runtimeDir });
|
|
94
|
+
if (!located) {
|
|
95
|
+
const err = new Error(`whisper-cli not found; install via: ${installHint()}`);
|
|
96
|
+
err.code = 'WHISPER_BINARY_MISSING';
|
|
97
|
+
err.installHint = installHint();
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
const modelPath = await ensureWhisperModel({ runtimeDir, model });
|
|
101
|
+
return { binary: located.binary, source: located.source, modelPath, model };
|
|
102
|
+
}
|
package/lib/server/index.mjs
CHANGED
|
@@ -33,7 +33,27 @@ const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
|
33
33
|
const ROOT_DIR = join(__dirname, '..', '..');
|
|
34
34
|
const HOME = homedir();
|
|
35
35
|
const PORT = parseInt(process.env.PORT ?? '4242', 10);
|
|
36
|
-
|
|
36
|
+
|
|
37
|
+
// Default bind is loopback regardless of NODE_ENV. Exposing the dashboard to
|
|
38
|
+
// non-loopback interfaces is opt-in via BIND_HOST and gated on configured
|
|
39
|
+
// auth (see assertSafeBind below).
|
|
40
|
+
|
|
41
|
+
const BIND_HOST = process.env.BIND_HOST ?? '127.0.0.1';
|
|
42
|
+
|
|
43
|
+
function isLoopbackHost(host) {
|
|
44
|
+
return host === '127.0.0.1' || host === 'localhost' || host === '::1';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function assertSafeBind() {
|
|
48
|
+
if (isLoopbackHost(BIND_HOST)) return;
|
|
49
|
+
if (!isAuthConfigured()) {
|
|
50
|
+
process.stderr.write(
|
|
51
|
+
`[dashboard] refuses to bind to non-loopback host ${BIND_HOST} without auth configured. ` +
|
|
52
|
+
`Set CONSTRUCT_DASHBOARD_TOKEN (or run \`construct dashboard auth init\`) before exposing the dashboard.\n`,
|
|
53
|
+
);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
37
57
|
|
|
38
58
|
const STATIC_DIR = join(__dirname, 'static');
|
|
39
59
|
const REGISTRY_FILE = join(ROOT_DIR, 'specialists', 'registry.json');
|
|
@@ -2237,6 +2257,7 @@ if (process.env.CX_AUTO_EMBED === '1') {
|
|
|
2237
2257
|
setInterval(runEmbedSync, intervalMs).unref();
|
|
2238
2258
|
console.log(`Embed scheduler active — syncing every ${intervalMs / 60_000} min`);
|
|
2239
2259
|
}
|
|
2260
|
+
assertSafeBind();
|
|
2240
2261
|
server.listen(PORT, BIND_HOST, () => {
|
|
2241
2262
|
console.log(`Construct dashboard running at http://${BIND_HOST}:${PORT}`);
|
|
2242
2263
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><!--02hC8wDNvTNKwi4iN6D_t--><html lang="en" data-theme="dark" data-density="comfortable" data-motion="normal" class="__variable_629dad __variable_246ccd __variable_3c557b"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/22a5144ee8d83bca-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="preload" href="/_next/static/media/bb3ef058b751a6ad-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/css/47a506ef57811fb9.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-8e0503f8a36190b3.js"/><script src="/_next/static/chunks/87c73c54-24122e7b92478d00.js" async=""></script><script src="/_next/static/chunks/18-1e25b5692ec1d26d.js" async=""></script><script src="/_next/static/chunks/main-app-07d82ec6c92d7402.js" async=""></script><script src="/_next/static/chunks/9664-af80478aa73ba424.js" async=""></script><script src="/_next/static/chunks/8144-da56a4a291c0b21a.js" async=""></script><script src="/_next/static/chunks/app/layout-1a2d2674aa220229.js" async=""></script><script src="/_next/static/chunks/app/page-2568fbdd2b0e1eca.js" async=""></script><meta name="next-size-adjust" content=""/><title>Construct — Dashboard</title><meta name="description" content="Local operations dashboard for Construct — approvals, health, knowledge, models, providers."/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div class="shell"><header class="topbar"><a class="brand" href="/"><div class="mark"></div><div class="name">Construct<em>dashboard</em></div></a><div class="search-wrap"><label class="search"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"></circle><line x1="21" y1="21" x2="16.5" y2="16.5"></line></svg><input readOnly="" placeholder="Search dashboard…"/><span class="kbd">⌘K</span></label></div><div class="top-actions"><button class="icon-btn" title="Toggle theme" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"></circle><line x1="12" y1="2" x2="12" y2="5"></line><line x1="12" y1="19" x2="12" y2="22"></line><line x1="2" y1="12" x2="5" y2="12"></line><line x1="19" y1="12" x2="22" y2="12"></line><line x1="4.5" y1="4.5" x2="6.5" y2="6.5"></line><line x1="17.5" y1="17.5" x2="19.5" y2="19.5"></line><line x1="4.5" y1="19.5" x2="6.5" y2="17.5"></line><line x1="17.5" y1="6.5" x2="19.5" y2="4.5"></line></svg></button><button class="icon-btn" title="Toggle density" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="7" x2="21" y2="7"></line><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="17" x2="21" y2="17"></line></svg></button><a class="icon-btn outlined" href="https://github.com/geraldmaron/construct" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M12 .5C5.65.5.5 5.66.5 12.02c0 5.09 3.29 9.4 7.86 10.92.58.1.79-.25.79-.55v-2.13c-3.2.7-3.87-1.37-3.87-1.37-.52-1.32-1.27-1.67-1.27-1.67-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.76 2.69 1.25 3.35.96.1-.74.4-1.25.73-1.54-2.55-.29-5.24-1.28-5.24-5.7 0-1.26.45-2.29 1.18-3.1-.12-.29-.51-1.46.11-3.04 0 0 .96-.31 3.16 1.18a10.9 10.9 0 0 1 5.74 0c2.2-1.49 3.16-1.18 3.16-1.18.62 1.58.23 2.75.12 3.04.74.81 1.18 1.84 1.18 3.1 0 4.43-2.69 5.41-5.25 5.69.41.36.78 1.05.78 2.13v3.16c0 .31.21.66.79.55A11.52 11.52 0 0 0 23.5 12.02C23.5 5.66 18.35.5 12 .5Z"></path></svg> <span style="font-size:11px">repo</span></a></div><div class="progress"></div></header><div class="body-grid"><aside class="sidebar"><div class="side-group"><div class="side-label">Overview</div><a class="side-link active" href="/"><span class="num">01</span><span>Home</span></a><a class="side-link" href="/doctor/"><span class="num">02</span><span>Doctor</span></a><a class="side-link" href="/resources/"><span class="num">03</span><span>Services</span></a></div><div class="side-group"><div class="side-label">Activity</div><a class="side-link" href="/audit/"><span class="num">01</span><span>Audit trail</span></a><a class="side-link" href="/snapshots/"><span class="num">02</span><span>Snapshots</span></a><a class="side-link" href="/performance/"><span class="num">03</span><span>Performance</span></a></div><div class="side-group"><div class="side-label">Work</div><a class="side-link" href="/approvals/"><span class="num">01</span><span>Approvals</span></a><a class="side-link" href="/workflow/"><span class="num">02</span><span>Workflow</span></a><a class="side-link" href="/beads/"><span class="num">03</span><span>Beads</span></a><a class="side-link" href="/artifacts/"><span class="num">04</span><span>Artifacts</span></a><a class="side-link" href="/intake/"><span class="num">05</span><span>Intake</span></a></div><div class="side-group"><div class="side-label">Specialists</div><a class="side-link" href="/agents/"><span class="num">01</span><span>Specialists</span></a><a class="side-link" href="/skills/"><span class="num">02</span><span>Skills</span></a><a class="side-link" href="/commands/"><span class="num">03</span><span>Slash commands</span></a><a class="side-link" href="/hooks/"><span class="num">04</span><span>Hooks</span></a><a class="side-link" href="/plugins/"><span class="num">05</span><span>Plugins</span></a></div><div class="side-group"><div class="side-label">Models & Providers</div><a class="side-link" href="/models/"><span class="num">01</span><span>Models</span></a><a class="side-link" href="/providers/"><span class="num">02</span><span>Providers</span></a><a class="side-link" href="/mcp/"><span class="num">03</span><span>MCP servers</span></a></div><div class="side-group"><div class="side-label">Knowledge</div><a class="side-link" href="/knowledge/"><span class="num">01</span><span>Knowledge</span></a><a class="side-link" href="/editor/"><span class="num">02</span><span>Editor</span></a></div><div class="side-group"><div class="side-label">System</div><a class="side-link" href="/config/"><span class="num">01</span><span>Config</span></a><a class="side-link" href="/infrastructure/"><span class="num">02</span><span>Infrastructure</span></a></div><div class="side-foot">Local dashboard for Construct. Theme + density preferences persist.</div></aside><main class="main"><div class="page"><div class="eyebrow"><span class="dot"></span><span>overview · construct dashboard</span></div><h1 class="page-title">System state</h1><p class="page-lede">The runtime in one place. Approvals, services, model providers, knowledge corpus, and intake — each section deep-links to its dedicated surface.</p><div class="meta-strip"><span class="pill">mode: <!-- -->…</span><span>instance: <!-- -->…</span><span class="sep">·</span><span>embed: <!-- -->stopped</span></div><div class="body"><div class="hero-stats" style="margin-top:0;margin-bottom:24px"><div class="hero-stat"><span class="k">Services</span><span class="v">…</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">running</span></div><div class="hero-stat"><span class="k">Approvals</span><span class="v">…</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">pending</span></div><div class="hero-stat"><span class="k">Skills</span><span class="v">0</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">registered</span></div><div class="hero-stat"><span class="k">Commands</span><span class="v">0</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">slash</span></div></div><section class="section open"><button class="section-head" aria-expanded="true" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">01</span><span class="section-title">Pending approvals</span></span><span class="section-aside"><span>empty</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Items waiting on a human gate. High-risk mutations (work item creation, merge, doc publish, config changes) land here.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div style="padding:24px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">Loading…</div></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">02</span><span class="section-title">Services</span></span><span class="section-aside"><span>0/0 running</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Local processes that back the runtime: Postgres, memory bridge, OpenCode bridge, dashboard, embed daemon.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div style="padding:24px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">Loading…</div><p style="margin-top:12px"><a class="link" href="/resources/">Open services dashboard →</a></p></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">03</span><span class="section-title">Credentials</span></span><span class="section-aside"><span>0 tracked</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Provider API keys, billing posture, and 1Password references. Set them in /providers.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><p style="margin-top:12px"><a class="link" href="/providers/">Manage providers →</a></p></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">04</span><span class="section-title">Quick links</span></span><span class="section-aside"></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Deep links into every dashboard surface.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div class="callout"><span class="clt-label">Where to next</span><div><p><a class="link" href="/doctor/">Doctor</a>,<!-- --> <a class="link" href="/knowledge/">Knowledge</a>,<!-- --> <a class="link" href="/intake/">Intake</a>,<!-- --> <a class="link" href="/workflow/">Workflow</a>,<!-- --> <a class="link" href="/agents/">Specialists</a>,<!-- --> <a class="link" href="/models/">Models</a>,<!-- --> <a class="link" href="/audit/">Audit</a>.</p></div></div></div></div></div></div></section></div></div><!--$--><!--/$--></main></div></div><script src="/_next/static/chunks/webpack-8e0503f8a36190b3.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[105,[\"9664\",\"static/chunks/9664-af80478aa73ba424.js\",\"8144\",\"static/chunks/8144-da56a4a291c0b21a.js\",\"7177\",\"static/chunks/app/layout-1a2d2674aa220229.js\"],\"AppShellClient\"]\n3:I[5341,[],\"\"]\n4:I[25,[],\"\"]\n5:I[1012,[],\"ClientPageRoot\"]\n6:I[9008,[\"9664\",\"static/chunks/9664-af80478aa73ba424.js\",\"8144\",\"static/chunks/8144-da56a4a291c0b21a.js\",\"8974\",\"static/chunks/app/page-2568fbdd2b0e1eca.js\"],\"default\"]\n9:I[5104,[],\"OutletBoundary\"]\nb:I[7158,[],\"AsyncMetadataOutlet\"]\nd:I[5104,[],\"ViewportBoundary\"]\nf:I[5104,[],\"MetadataBoundary\"]\n10:\"$Sreact.suspense\"\n12:I[4431,[],\"\"]\n:HL[\"/_next/static/media/22a5144ee8d83bca-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/media/bb3ef058b751a6ad-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/47a506ef57811fb9.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"02hC8wDNvTNKwi4iN6D_t\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/47a506ef57811fb9.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"data-theme\":\"dark\",\"data-density\":\"comfortable\",\"data-motion\":\"normal\",\"suppressHydrationWarning\":true,\"className\":\"__variable_629dad __variable_246ccd __variable_3c557b\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$\",\"$L2\",null,{\"nav\":[{\"label\":\"Overview\",\"items\":[{\"id\":\"home\",\"title\":\"Home\",\"href\":\"/\"},{\"id\":\"doctor\",\"title\":\"Doctor\",\"href\":\"/doctor\"},{\"id\":\"resources\",\"title\":\"Services\",\"href\":\"/resources\"}]},{\"label\":\"Activity\",\"items\":[{\"id\":\"audit\",\"title\":\"Audit trail\",\"href\":\"/audit\"},{\"id\":\"snapshots\",\"title\":\"Snapshots\",\"href\":\"/snapshots\"},{\"id\":\"performance\",\"title\":\"Performance\",\"href\":\"/performance\"}]},{\"label\":\"Work\",\"items\":[{\"id\":\"approvals\",\"title\":\"Approvals\",\"href\":\"/approvals\"},{\"id\":\"workflow\",\"title\":\"Workflow\",\"href\":\"/workflow\"},{\"id\":\"beads\",\"title\":\"Beads\",\"href\":\"/beads\"},{\"id\":\"artifacts\",\"title\":\"Artifacts\",\"href\":\"/artifacts\"},{\"id\":\"intake\",\"title\":\"Intake\",\"href\":\"/intake\"}]},{\"label\":\"Specialists\",\"items\":[{\"id\":\"agents\",\"title\":\"Specialists\",\"href\":\"/agents\"},{\"id\":\"skills\",\"title\":\"Skills\",\"href\":\"/skills\"},{\"id\":\"commands\",\"title\":\"Slash commands\",\"href\":\"/commands\"},{\"id\":\"hooks\",\"title\":\"Hooks\",\"href\":\"/hooks\"},{\"id\":\"plugins\",\"title\":\"Plugins\",\"href\":\"/plugins\"}]},{\"label\":\"Models \u0026 Providers\",\"items\":[{\"id\":\"models\",\"title\":\"Models\",\"href\":\"/models\"},{\"id\":\"providers\",\"title\":\"Providers\",\"href\":\"/providers\"},{\"id\":\"mcp\",\"title\":\"MCP servers\",\"href\":\"/mcp\"}]},{\"label\":\"Knowledge\",\"items\":[{\"id\":\"knowledge\",\"title\":\"Knowledge\",\"href\":\"/knowledge\"},{\"id\":\"editor\",\"title\":\"Editor\",\"href\":\"/editor\"}]},{\"label\":\"System\",\"items\":[{\"id\":\"config\",\"title\":\"Config\",\"href\":\"/config\"},{\"id\":\"infrastructure\",\"title\":\"Infrastructure\",\"href\":\"/infrastructure\"}]}],\"children\":[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"$L5\",null,{\"Component\":\"$6\",\"searchParams\":{},\"params\":{},\"promises\":[\"$@7\",\"$@8\"]}],null,[\"$\",\"$L9\",null,{\"children\":[\"$La\",[\"$\",\"$Lb\",null,{\"promise\":\"$@c\"}]]}]]}],{},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[null,[[\"$\",\"$Ld\",null,{\"children\":\"$Le\"}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]],[\"$\",\"$Lf\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$10\",null,{\"fallback\":null,\"children\":\"$L11\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$12\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"7:{}\n8:\"$0:f:0:1:2:children:1:props:children:0:props:params\"\n"])</script><script>self.__next_f.push([1,"e:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\na:null\n"])</script><script>self.__next_f.push([1,"c:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Construct — Dashboard\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Local operations dashboard for Construct — approvals, health, knowledge, models, providers.\"}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"11:\"$c:metadata\"\n"])</script></body></html>
|
|
1
|
+
<!DOCTYPE html><!--qA_CTDVxjnG_0_bArvNYn--><html lang="en" data-theme="dark" data-density="comfortable" data-motion="normal" class="__variable_629dad __variable_246ccd __variable_3c557b"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/22a5144ee8d83bca-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="preload" href="/_next/static/media/bb3ef058b751a6ad-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/css/47a506ef57811fb9.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-8e0503f8a36190b3.js"/><script src="/_next/static/chunks/87c73c54-24122e7b92478d00.js" async=""></script><script src="/_next/static/chunks/18-1e25b5692ec1d26d.js" async=""></script><script src="/_next/static/chunks/main-app-07d82ec6c92d7402.js" async=""></script><script src="/_next/static/chunks/9664-af80478aa73ba424.js" async=""></script><script src="/_next/static/chunks/8144-da56a4a291c0b21a.js" async=""></script><script src="/_next/static/chunks/app/layout-1a2d2674aa220229.js" async=""></script><script src="/_next/static/chunks/app/page-2568fbdd2b0e1eca.js" async=""></script><meta name="next-size-adjust" content=""/><title>Construct — Dashboard</title><meta name="description" content="Local operations dashboard for Construct — approvals, health, knowledge, models, providers."/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div class="shell"><header class="topbar"><a class="brand" href="/"><div class="mark"></div><div class="name">Construct<em>dashboard</em></div></a><div class="search-wrap"><label class="search"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"></circle><line x1="21" y1="21" x2="16.5" y2="16.5"></line></svg><input readOnly="" placeholder="Search dashboard…"/><span class="kbd">⌘K</span></label></div><div class="top-actions"><button class="icon-btn" title="Toggle theme" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"></circle><line x1="12" y1="2" x2="12" y2="5"></line><line x1="12" y1="19" x2="12" y2="22"></line><line x1="2" y1="12" x2="5" y2="12"></line><line x1="19" y1="12" x2="22" y2="12"></line><line x1="4.5" y1="4.5" x2="6.5" y2="6.5"></line><line x1="17.5" y1="17.5" x2="19.5" y2="19.5"></line><line x1="4.5" y1="19.5" x2="6.5" y2="17.5"></line><line x1="17.5" y1="6.5" x2="19.5" y2="4.5"></line></svg></button><button class="icon-btn" title="Toggle density" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="7" x2="21" y2="7"></line><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="17" x2="21" y2="17"></line></svg></button><a class="icon-btn outlined" href="https://github.com/geraldmaron/construct" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M12 .5C5.65.5.5 5.66.5 12.02c0 5.09 3.29 9.4 7.86 10.92.58.1.79-.25.79-.55v-2.13c-3.2.7-3.87-1.37-3.87-1.37-.52-1.32-1.27-1.67-1.27-1.67-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.76 2.69 1.25 3.35.96.1-.74.4-1.25.73-1.54-2.55-.29-5.24-1.28-5.24-5.7 0-1.26.45-2.29 1.18-3.1-.12-.29-.51-1.46.11-3.04 0 0 .96-.31 3.16 1.18a10.9 10.9 0 0 1 5.74 0c2.2-1.49 3.16-1.18 3.16-1.18.62 1.58.23 2.75.12 3.04.74.81 1.18 1.84 1.18 3.1 0 4.43-2.69 5.41-5.25 5.69.41.36.78 1.05.78 2.13v3.16c0 .31.21.66.79.55A11.52 11.52 0 0 0 23.5 12.02C23.5 5.66 18.35.5 12 .5Z"></path></svg> <span style="font-size:11px">repo</span></a></div><div class="progress"></div></header><div class="body-grid"><aside class="sidebar"><div class="side-group"><div class="side-label">Overview</div><a class="side-link active" href="/"><span class="num">01</span><span>Home</span></a><a class="side-link" href="/doctor/"><span class="num">02</span><span>Doctor</span></a><a class="side-link" href="/resources/"><span class="num">03</span><span>Services</span></a></div><div class="side-group"><div class="side-label">Activity</div><a class="side-link" href="/audit/"><span class="num">01</span><span>Audit trail</span></a><a class="side-link" href="/snapshots/"><span class="num">02</span><span>Snapshots</span></a><a class="side-link" href="/performance/"><span class="num">03</span><span>Performance</span></a></div><div class="side-group"><div class="side-label">Work</div><a class="side-link" href="/approvals/"><span class="num">01</span><span>Approvals</span></a><a class="side-link" href="/workflow/"><span class="num">02</span><span>Workflow</span></a><a class="side-link" href="/beads/"><span class="num">03</span><span>Beads</span></a><a class="side-link" href="/artifacts/"><span class="num">04</span><span>Artifacts</span></a><a class="side-link" href="/intake/"><span class="num">05</span><span>Intake</span></a></div><div class="side-group"><div class="side-label">Specialists</div><a class="side-link" href="/agents/"><span class="num">01</span><span>Specialists</span></a><a class="side-link" href="/skills/"><span class="num">02</span><span>Skills</span></a><a class="side-link" href="/commands/"><span class="num">03</span><span>Slash commands</span></a><a class="side-link" href="/hooks/"><span class="num">04</span><span>Hooks</span></a><a class="side-link" href="/plugins/"><span class="num">05</span><span>Plugins</span></a></div><div class="side-group"><div class="side-label">Models & Providers</div><a class="side-link" href="/models/"><span class="num">01</span><span>Models</span></a><a class="side-link" href="/providers/"><span class="num">02</span><span>Providers</span></a><a class="side-link" href="/mcp/"><span class="num">03</span><span>MCP servers</span></a></div><div class="side-group"><div class="side-label">Knowledge</div><a class="side-link" href="/knowledge/"><span class="num">01</span><span>Knowledge</span></a><a class="side-link" href="/editor/"><span class="num">02</span><span>Editor</span></a></div><div class="side-group"><div class="side-label">System</div><a class="side-link" href="/config/"><span class="num">01</span><span>Config</span></a><a class="side-link" href="/infrastructure/"><span class="num">02</span><span>Infrastructure</span></a></div><div class="side-foot">Local dashboard for Construct. Theme + density preferences persist.</div></aside><main class="main"><div class="page"><div class="eyebrow"><span class="dot"></span><span>overview · construct dashboard</span></div><h1 class="page-title">System state</h1><p class="page-lede">The runtime in one place. Approvals, services, model providers, knowledge corpus, and intake — each section deep-links to its dedicated surface.</p><div class="meta-strip"><span class="pill">mode: <!-- -->…</span><span>instance: <!-- -->…</span><span class="sep">·</span><span>embed: <!-- -->stopped</span></div><div class="body"><div class="hero-stats" style="margin-top:0;margin-bottom:24px"><div class="hero-stat"><span class="k">Services</span><span class="v">…</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">running</span></div><div class="hero-stat"><span class="k">Approvals</span><span class="v">…</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">pending</span></div><div class="hero-stat"><span class="k">Skills</span><span class="v">0</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">registered</span></div><div class="hero-stat"><span class="k">Commands</span><span class="v">0</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">slash</span></div></div><section class="section open"><button class="section-head" aria-expanded="true" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">01</span><span class="section-title">Pending approvals</span></span><span class="section-aside"><span>empty</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Items waiting on a human gate. High-risk mutations (work item creation, merge, doc publish, config changes) land here.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div style="padding:24px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">Loading…</div></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">02</span><span class="section-title">Services</span></span><span class="section-aside"><span>0/0 running</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Local processes that back the runtime: Postgres, memory bridge, OpenCode bridge, dashboard, embed daemon.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div style="padding:24px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">Loading…</div><p style="margin-top:12px"><a class="link" href="/resources/">Open services dashboard →</a></p></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">03</span><span class="section-title">Credentials</span></span><span class="section-aside"><span>0 tracked</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Provider API keys, billing posture, and 1Password references. Set them in /providers.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><p style="margin-top:12px"><a class="link" href="/providers/">Manage providers →</a></p></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">04</span><span class="section-title">Quick links</span></span><span class="section-aside"></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Deep links into every dashboard surface.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div class="callout"><span class="clt-label">Where to next</span><div><p><a class="link" href="/doctor/">Doctor</a>,<!-- --> <a class="link" href="/knowledge/">Knowledge</a>,<!-- --> <a class="link" href="/intake/">Intake</a>,<!-- --> <a class="link" href="/workflow/">Workflow</a>,<!-- --> <a class="link" href="/agents/">Specialists</a>,<!-- --> <a class="link" href="/models/">Models</a>,<!-- --> <a class="link" href="/audit/">Audit</a>.</p></div></div></div></div></div></div></section></div></div><!--$--><!--/$--></main></div></div><script src="/_next/static/chunks/webpack-8e0503f8a36190b3.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[105,[\"9664\",\"static/chunks/9664-af80478aa73ba424.js\",\"8144\",\"static/chunks/8144-da56a4a291c0b21a.js\",\"7177\",\"static/chunks/app/layout-1a2d2674aa220229.js\"],\"AppShellClient\"]\n3:I[5341,[],\"\"]\n4:I[25,[],\"\"]\n5:I[1012,[],\"ClientPageRoot\"]\n6:I[9008,[\"9664\",\"static/chunks/9664-af80478aa73ba424.js\",\"8144\",\"static/chunks/8144-da56a4a291c0b21a.js\",\"8974\",\"static/chunks/app/page-2568fbdd2b0e1eca.js\"],\"default\"]\n9:I[5104,[],\"OutletBoundary\"]\nb:I[7158,[],\"AsyncMetadataOutlet\"]\nd:I[5104,[],\"ViewportBoundary\"]\nf:I[5104,[],\"MetadataBoundary\"]\n10:\"$Sreact.suspense\"\n12:I[4431,[],\"\"]\n:HL[\"/_next/static/media/22a5144ee8d83bca-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/media/bb3ef058b751a6ad-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/47a506ef57811fb9.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"qA_CTDVxjnG_0_bArvNYn\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/47a506ef57811fb9.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"data-theme\":\"dark\",\"data-density\":\"comfortable\",\"data-motion\":\"normal\",\"suppressHydrationWarning\":true,\"className\":\"__variable_629dad __variable_246ccd __variable_3c557b\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$\",\"$L2\",null,{\"nav\":[{\"label\":\"Overview\",\"items\":[{\"id\":\"home\",\"title\":\"Home\",\"href\":\"/\"},{\"id\":\"doctor\",\"title\":\"Doctor\",\"href\":\"/doctor\"},{\"id\":\"resources\",\"title\":\"Services\",\"href\":\"/resources\"}]},{\"label\":\"Activity\",\"items\":[{\"id\":\"audit\",\"title\":\"Audit trail\",\"href\":\"/audit\"},{\"id\":\"snapshots\",\"title\":\"Snapshots\",\"href\":\"/snapshots\"},{\"id\":\"performance\",\"title\":\"Performance\",\"href\":\"/performance\"}]},{\"label\":\"Work\",\"items\":[{\"id\":\"approvals\",\"title\":\"Approvals\",\"href\":\"/approvals\"},{\"id\":\"workflow\",\"title\":\"Workflow\",\"href\":\"/workflow\"},{\"id\":\"beads\",\"title\":\"Beads\",\"href\":\"/beads\"},{\"id\":\"artifacts\",\"title\":\"Artifacts\",\"href\":\"/artifacts\"},{\"id\":\"intake\",\"title\":\"Intake\",\"href\":\"/intake\"}]},{\"label\":\"Specialists\",\"items\":[{\"id\":\"agents\",\"title\":\"Specialists\",\"href\":\"/agents\"},{\"id\":\"skills\",\"title\":\"Skills\",\"href\":\"/skills\"},{\"id\":\"commands\",\"title\":\"Slash commands\",\"href\":\"/commands\"},{\"id\":\"hooks\",\"title\":\"Hooks\",\"href\":\"/hooks\"},{\"id\":\"plugins\",\"title\":\"Plugins\",\"href\":\"/plugins\"}]},{\"label\":\"Models \u0026 Providers\",\"items\":[{\"id\":\"models\",\"title\":\"Models\",\"href\":\"/models\"},{\"id\":\"providers\",\"title\":\"Providers\",\"href\":\"/providers\"},{\"id\":\"mcp\",\"title\":\"MCP servers\",\"href\":\"/mcp\"}]},{\"label\":\"Knowledge\",\"items\":[{\"id\":\"knowledge\",\"title\":\"Knowledge\",\"href\":\"/knowledge\"},{\"id\":\"editor\",\"title\":\"Editor\",\"href\":\"/editor\"}]},{\"label\":\"System\",\"items\":[{\"id\":\"config\",\"title\":\"Config\",\"href\":\"/config\"},{\"id\":\"infrastructure\",\"title\":\"Infrastructure\",\"href\":\"/infrastructure\"}]}],\"children\":[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"$L5\",null,{\"Component\":\"$6\",\"searchParams\":{},\"params\":{},\"promises\":[\"$@7\",\"$@8\"]}],null,[\"$\",\"$L9\",null,{\"children\":[\"$La\",[\"$\",\"$Lb\",null,{\"promise\":\"$@c\"}]]}]]}],{},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[null,[[\"$\",\"$Ld\",null,{\"children\":\"$Le\"}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]],[\"$\",\"$Lf\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$10\",null,{\"fallback\":null,\"children\":\"$L11\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$12\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"7:{}\n8:\"$0:f:0:1:2:children:1:props:children:0:props:params\"\n"])</script><script>self.__next_f.push([1,"e:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\na:null\n"])</script><script>self.__next_f.push([1,"c:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Construct — Dashboard\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Local operations dashboard for Construct — approvals, health, knowledge, models, providers.\"}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"11:\"$c:metadata\"\n"])</script></body></html>
|
|
@@ -100,7 +100,7 @@ export const POSTCONDITIONS = {
|
|
|
100
100
|
* @param {object} packet — the output packet about to hand off
|
|
101
101
|
* @returns {{ ok: boolean, producer: string, failures: Array<{id, reason}> }}
|
|
102
102
|
*/
|
|
103
|
-
export function
|
|
103
|
+
export function validateBinaryPostconditions(producer, packet) {
|
|
104
104
|
const rules = POSTCONDITIONS[producer] || [];
|
|
105
105
|
if (rules.length === 0) return { ok: true, producer, failures: [] };
|
|
106
106
|
const failures = [];
|