owflow 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +448 -261
- package/dist/agents/bottleneck-analyzer.md +332 -329
- package/dist/agents/code-quality-pragmatist.md +353 -354
- package/dist/agents/code-reviewer.md +238 -239
- package/dist/agents/codebase-analysis-reporter.md +245 -246
- package/dist/agents/docs-operator.md +22 -23
- package/dist/agents/e2e-test-verifier.md +447 -448
- package/dist/agents/gap-analyzer.md +501 -502
- package/dist/agents/implementation-completeness-checker.md +219 -220
- package/dist/agents/implementation-planner.md +308 -309
- package/dist/agents/information-gatherer.md +690 -691
- package/dist/agents/production-readiness-checker.md +278 -279
- package/dist/agents/project-analyzer.md +403 -403
- package/dist/agents/reality-assessor.md +365 -366
- package/dist/agents/research-planner.md +449 -450
- package/dist/agents/research-synthesizer.md +402 -403
- package/dist/agents/solution-brainstormer.md +256 -257
- package/dist/agents/solution-designer.md +371 -372
- package/dist/agents/spec-auditor.md +297 -298
- package/dist/agents/specification-creator.md +333 -334
- package/dist/agents/task-classifier.md +449 -450
- package/dist/agents/task-group-implementer.md +283 -284
- package/dist/agents/test-suite-runner.md +194 -195
- package/dist/agents/user-docs-generator.md +508 -509
- package/dist/commands/reviews-code.md +90 -90
- package/dist/commands/reviews-pragmatic.md +99 -99
- package/dist/commands/reviews-production-readiness.md +113 -113
- package/dist/commands/reviews-reality-check.md +110 -110
- package/dist/commands/reviews-spec-audit.md +114 -114
- package/dist/commands/work.md +280 -280
- package/dist/configuration/agents-config.d.ts +2 -0
- package/dist/configuration/agents-config.js +8 -44
- package/dist/configuration/commands-config.d.ts +23 -0
- package/dist/configuration/commands-config.js +17 -17
- package/dist/configuration/skills-config.d.ts +2 -0
- package/dist/configuration/skills-config.js +3 -3
- package/dist/hooks/hooks-diagnostics.d.ts +3 -0
- package/dist/hooks/hooks-diagnostics.js +21 -0
- package/dist/hooks/hooks-persistence.d.ts +17 -0
- package/dist/hooks/hooks-persistence.js +207 -0
- package/dist/hooks/hooks-registry.d.ts +6 -0
- package/dist/hooks/hooks-registry.js +104 -0
- package/dist/hooks/hooks-wrapper.d.ts +25 -0
- package/dist/hooks/hooks-wrapper.js +318 -0
- package/dist/hooks/internal/before-tool.d.ts +6 -0
- package/dist/hooks/internal/before-tool.js +13 -0
- package/dist/hooks/internal/session-compaction.d.ts +9 -0
- package/dist/hooks/{session-compaction.js → internal/session-compaction.js} +9 -7
- package/dist/hooks/runtime/circuit-breaker.d.ts +23 -0
- package/dist/hooks/runtime/circuit-breaker.js +39 -0
- package/dist/hooks/runtime/failure-policy.d.ts +15 -0
- package/dist/hooks/runtime/failure-policy.js +49 -0
- package/dist/hooks/runtime/process-executor.d.ts +33 -0
- package/dist/hooks/runtime/process-executor.js +147 -0
- package/dist/hooks/runtime/status-recorder.d.ts +27 -0
- package/dist/hooks/runtime/status-recorder.js +111 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +111 -40
- package/dist/skills/agents-md-generator/SKILL.md +223 -223
- package/dist/skills/codebase-analyzer/SKILL.md +162 -162
- package/dist/skills/codebase-analyzer/references/code-analysis.md +63 -63
- package/dist/skills/codebase-analyzer/references/combined.md +31 -31
- package/dist/skills/codebase-analyzer/references/context-discovery.md +63 -63
- package/dist/skills/codebase-analyzer/references/file-discovery.md +51 -51
- package/dist/skills/codebase-analyzer/references/migration-target.md +23 -23
- package/dist/skills/codebase-analyzer/references/pattern-mining.md +22 -22
- package/dist/skills/dev-analyze/SKILL.md +115 -115
- package/dist/skills/dev-bugfix/SKILL.md +452 -452
- package/dist/skills/dev-finalize/SKILL.md +118 -118
- package/dist/skills/dev-implement/SKILL.md +167 -167
- package/dist/skills/dev-plan/SKILL.md +147 -147
- package/dist/skills/dev-spec/SKILL.md +182 -182
- package/dist/skills/dev-tdd-red/SKILL.md +94 -94
- package/dist/skills/dev-verify/SKILL.md +153 -153
- package/dist/skills/development/SKILL.md +260 -260
- package/dist/skills/diagrams-mermaid/SKILL.md +183 -159
- package/dist/skills/diagrams-mermaid/references/reference.md +220 -148
- package/dist/skills/docs-manager/SKILL.md +379 -379
- package/dist/skills/docs-manager/docs/INDEX.md +199 -199
- package/dist/skills/docs-manager/docs/standards/backend/api.md +25 -25
- package/dist/skills/docs-manager/docs/standards/backend/migrations.md +22 -22
- package/dist/skills/docs-manager/docs/standards/backend/models.md +25 -25
- package/dist/skills/docs-manager/docs/standards/backend/queries.md +22 -22
- package/dist/skills/docs-manager/docs/standards/frontend/accessibility.md +25 -25
- package/dist/skills/docs-manager/docs/standards/frontend/components.md +28 -28
- package/dist/skills/docs-manager/docs/standards/frontend/css.md +16 -16
- package/dist/skills/docs-manager/docs/standards/frontend/responsive.md +28 -28
- package/dist/skills/docs-manager/docs/standards/global/coding-style.md +25 -25
- package/dist/skills/docs-manager/docs/standards/global/commenting.md +10 -10
- package/dist/skills/docs-manager/docs/standards/global/conventions.md +31 -31
- package/dist/skills/docs-manager/docs/standards/global/error-handling.md +22 -22
- package/dist/skills/docs-manager/docs/standards/global/minimal-implementation.md +22 -22
- package/dist/skills/docs-manager/docs/standards/global/validation.md +28 -28
- package/dist/skills/docs-manager/docs/standards/testing/test-writing.md +25 -25
- package/dist/skills/docs-manager/references/agents-md-template.md +29 -29
- package/dist/skills/docs-manager/references/index-md-template.md +73 -73
- package/dist/skills/flow-init/SKILL.md +234 -234
- package/dist/skills/flow-init/references/architecture-template.md +45 -45
- package/dist/skills/flow-init/references/roadmap-templates.md +93 -93
- package/dist/skills/flow-init/references/tech-stack-template.md +70 -70
- package/dist/skills/flow-init/references/vision-templates.md +75 -75
- package/dist/skills/goal-development/SKILL.md +96 -96
- package/dist/skills/goal-migration/SKILL.md +114 -114
- package/dist/skills/goal-performance/SKILL.md +126 -0
- package/dist/skills/goal-research/SKILL.md +118 -118
- package/dist/skills/html-renderer/SKILL.md +173 -173
- package/dist/skills/html-renderer/references/index.html +318 -318
- package/dist/skills/html-renderer/references/styles.css +388 -388
- package/dist/skills/html-renderer/references/tokens.json +104 -104
- package/dist/skills/implementation-plan-executor/SKILL.md +378 -378
- package/dist/skills/implementation-verifier/SKILL.md +307 -307
- package/dist/skills/migration/SKILL.md +268 -268
- package/dist/skills/migration-analyze/SKILL.md +126 -126
- package/dist/skills/migration-finalize/SKILL.md +161 -161
- package/dist/skills/migration-fix/SKILL.md +155 -155
- package/dist/skills/migration-implement/SKILL.md +131 -131
- package/dist/skills/migration-plan/SKILL.md +131 -131
- package/dist/skills/migration-spec/SKILL.md +135 -135
- package/dist/skills/migration-spec/references/migration-strategies.md +430 -430
- package/dist/skills/migration-target/SKILL.md +134 -134
- package/dist/skills/migration-target/references/migration-types.md +489 -489
- package/dist/skills/migration-verify/SKILL.md +184 -184
- package/dist/skills/orchestrator-framework/SKILL.md +65 -65
- package/dist/skills/orchestrator-framework/references/command-namespacing.md +7 -7
- package/dist/skills/orchestrator-framework/references/confirm-or-revise-exception.md +20 -20
- package/dist/skills/orchestrator-framework/references/delegation-rules.md +54 -54
- package/dist/skills/orchestrator-framework/references/dispatcher-handoff.md +27 -27
- package/dist/skills/orchestrator-framework/references/fix-loop-contract.md +65 -65
- package/dist/skills/orchestrator-framework/references/gate-contract.md +122 -117
- package/dist/skills/orchestrator-framework/references/orchestrator-creation-checklist.md +51 -51
- package/dist/skills/orchestrator-framework/references/orchestrator-patterns.md +235 -235
- package/dist/skills/performance/SKILL.md +267 -437
- package/dist/skills/performance-analyze/SKILL.md +129 -0
- package/dist/skills/{performance → performance-analyze}/references/performance-optimization-guide.md +271 -365
- package/dist/skills/performance-finalize/SKILL.md +117 -0
- package/dist/skills/performance-implement/SKILL.md +114 -0
- package/dist/skills/performance-plan/SKILL.md +112 -0
- package/dist/skills/performance-spec/SKILL.md +145 -0
- package/dist/skills/performance-verify/SKILL.md +164 -0
- package/dist/skills/research/SKILL.md +249 -249
- package/dist/skills/research-brainstorm/SKILL.md +120 -120
- package/dist/skills/research-brainstorm/references/brainstorming-techniques.md +84 -84
- package/dist/skills/research-converge/SKILL.md +130 -130
- package/dist/skills/research-design/SKILL.md +146 -146
- package/dist/skills/research-design/references/design-techniques.md +40 -40
- package/dist/skills/research-finalize/SKILL.md +125 -125
- package/dist/skills/research-fork/SKILL.md +190 -190
- package/dist/skills/research-gather/SKILL.md +133 -133
- package/dist/skills/research-plan/SKILL.md +141 -141
- package/dist/skills/research-plan/references/research-methodologies.md +710 -710
- package/dist/skills/research-quick/SKILL.md +139 -139
- package/dist/skills/research-synthesize/SKILL.md +127 -127
- package/dist/skills/rule-reviewer/SKILL.md +334 -334
- package/dist/skills/standards-discover/SKILL.md +260 -260
- package/dist/skills/standards-discover/references/aggregation-strategy.md +76 -76
- package/dist/skills/standards-discover/references/code-pattern-prompt.md +68 -68
- package/dist/skills/standards-discover/references/config-analyzer-prompt.md +67 -67
- package/dist/skills/standards-discover/references/docs-extractor-prompt.md +65 -65
- package/dist/skills/standards-discover/references/external-analyzer-prompt.md +75 -75
- package/dist/skills/standards-update/SKILL.md +207 -207
- package/dist/templates/orchestrator-state-base.yml +17 -17
- package/dist/templates/orchestrator-state-development.yml +95 -95
- package/dist/templates/orchestrator-state-migration.yml +95 -95
- package/dist/templates/orchestrator-state-performance.yml +64 -53
- package/dist/templates/orchestrator-state-research.yml +67 -67
- package/dist/tools/fork_task.d.ts +17 -0
- package/dist/tools/fork_task.js +21 -11
- package/dist/tools/verify_template.d.ts +11 -0
- package/dist/tools/verify_template.js +32 -17
- package/dist/tui/hooks-dialogs.d.ts +20 -0
- package/dist/tui/hooks-dialogs.js +181 -0
- package/dist/tui/hooks-presenter.d.ts +15 -0
- package/dist/tui/hooks-presenter.js +70 -0
- package/dist/tui/hooks-sidebar.d.ts +10 -0
- package/dist/tui/hooks-sidebar.js +104 -0
- package/dist/tui/primitives.d.ts +21 -0
- package/dist/tui/primitives.js +47 -0
- package/dist/tui.d.ts +12 -0
- package/dist/tui.js +129 -0
- package/dist/types/hook-types.d.ts +125 -0
- package/dist/types/hook-types.js +76 -0
- package/dist/types/opencode-types.d.ts +13 -0
- package/dist/utils/file-util.d.ts +2 -0
- package/dist/utils/file-util.js +6 -6
- package/dist/utils/fork-task.d.ts +27 -0
- package/dist/utils/fork-task.js +18 -23
- package/dist/utils/plugin-info.d.ts +3 -0
- package/dist/utils/plugin-info.js +8 -10
- package/dist/utils/workflow-paths.d.ts +17 -0
- package/dist/utils/workflow-paths.js +18 -0
- package/dist/utils/yaml-structure.d.ts +7 -0
- package/dist/utils/yaml-structure.js +7 -11
- package/package.json +68 -48
- package/dist/hooks/before-tool.js +0 -19
package/dist/tools/fork_task.js
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
|
-
import { tool } from
|
|
2
|
-
import path from
|
|
3
|
-
import { forkTask } from
|
|
1
|
+
import { tool } from '@opencode-ai/plugin';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { forkTask } from '../utils/fork-task.js';
|
|
4
|
+
import { RESEARCH_DIRECTORY_NAME, TASKS_DIRECTORY_NAME, workflowRelativePath } from '../utils/workflow-paths.js';
|
|
4
5
|
export const fork_task = tool({
|
|
5
|
-
description:
|
|
6
|
+
description: 'Fork a research task: copy the whole source task directory into a new date-prefixed directory named from the slug, then trim the copied state so the fork continues from the chosen completed step (fork point) onwards. The source task is never modified.',
|
|
6
7
|
args: {
|
|
7
|
-
taskRoot: tool.schema.string().describe(
|
|
8
|
-
source: tool.schema
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
taskRoot: tool.schema.string().describe(`Project-relative root of all tasks (typically '${workflowRelativePath(TASKS_DIRECTORY_NAME)}')`),
|
|
9
|
+
source: tool.schema
|
|
10
|
+
.string()
|
|
11
|
+
.describe(`Source task path or identifier under ${workflowRelativePath(TASKS_DIRECTORY_NAME, RESEARCH_DIRECTORY_NAME)}/`),
|
|
12
|
+
slug: tool.schema
|
|
13
|
+
.string()
|
|
14
|
+
.describe('Lowercase-kebab-case slug for the fork name, 3-5 words, WITHOUT the date prefix (derived by the agent from a short descriptive name)'),
|
|
15
|
+
from: tool.schema
|
|
16
|
+
.string()
|
|
17
|
+
.describe("Fork point: a completed step slug from the source task's completed_phases (e.g. 'synthesis-complete'); the fork continues from the step after it"),
|
|
18
|
+
intent: tool.schema
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Optional one-line description of what the fork should explore; stamped into the fork's orchestrator.options.fork_information.intent"),
|
|
12
22
|
},
|
|
13
23
|
async execute(args, context) {
|
|
14
24
|
try {
|
|
@@ -23,10 +33,10 @@ export const fork_task = tool({
|
|
|
23
33
|
return {
|
|
24
34
|
output: [
|
|
25
35
|
`FORK CREATED`,
|
|
26
|
-
`Fork path: ${path.relative(context.directory, result.forkPath).split(path.sep).join(
|
|
36
|
+
`Fork path: ${path.relative(context.directory, result.forkPath).split(path.sep).join('/')}`,
|
|
27
37
|
`Fork point: ${result.forkPoint}`,
|
|
28
38
|
...(args.intent ? [`Intent: ${args.intent}`] : []),
|
|
29
|
-
].join(
|
|
39
|
+
].join('\n'),
|
|
30
40
|
};
|
|
31
41
|
}
|
|
32
42
|
catch (error) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const verify_template: {
|
|
2
|
+
description: string;
|
|
3
|
+
args: {
|
|
4
|
+
filePath: import("zod").ZodString;
|
|
5
|
+
templateName: import("zod").ZodString;
|
|
6
|
+
};
|
|
7
|
+
execute(args: {
|
|
8
|
+
filePath: string;
|
|
9
|
+
templateName: string;
|
|
10
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
|
|
11
|
+
};
|
|
@@ -1,40 +1,55 @@
|
|
|
1
|
-
import { tool } from
|
|
2
|
-
import fs from
|
|
3
|
-
import path from
|
|
4
|
-
import { fileURLToPath } from
|
|
5
|
-
import { findStructuralErrors, parseYamlFile
|
|
1
|
+
import { tool } from '@opencode-ai/plugin';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { findStructuralErrors, parseYamlFile } from '../utils/yaml-structure.js';
|
|
6
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
7
|
export const verify_template = tool({
|
|
8
|
-
description:
|
|
8
|
+
description: 'Check if file in the given path exists and follows correct YAML structure by checking against the template file from /templates.',
|
|
9
9
|
args: {
|
|
10
|
-
filePath: tool.schema.string().describe(
|
|
11
|
-
templateName: tool.schema.string().describe(
|
|
10
|
+
filePath: tool.schema.string().describe('Path to the file to check'),
|
|
11
|
+
templateName: tool.schema.string().describe('Name of the template from /templates (e.g. orchestrator-state-development.yml)'),
|
|
12
12
|
},
|
|
13
13
|
async execute({ filePath, templateName }, context) {
|
|
14
14
|
const absolutePath = path.resolve(context.directory, filePath);
|
|
15
15
|
if (!fs.existsSync(absolutePath)) {
|
|
16
|
-
return {
|
|
16
|
+
return {
|
|
17
|
+
output: `File not found at ${filePath}. Hint: Double-check the path, create the file if it's missing, or verify you are in the correct directory.`,
|
|
18
|
+
};
|
|
17
19
|
}
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
const templatesDir = path.resolve(process.env.OWFLOW_TEMPLATES_DIR ?? path.join(__dirname, '../templates'));
|
|
21
|
+
const safeTemplateName = path.basename(templateName);
|
|
22
|
+
const templatePath = path.resolve(templatesDir, safeTemplateName);
|
|
23
|
+
if (templateName !== safeTemplateName ||
|
|
24
|
+
templateName.includes('..') ||
|
|
25
|
+
!templatePath.startsWith(templatesDir + path.sep) ||
|
|
26
|
+
!fs.existsSync(templatePath)) {
|
|
27
|
+
return {
|
|
28
|
+
output: `Template '${templateName}' not found. Hint: Verify the template name is correct and exists in the /templates folder (e.g., 'orchestrator-state-development.yml').`,
|
|
29
|
+
};
|
|
21
30
|
}
|
|
22
31
|
let targetYaml;
|
|
23
32
|
const parsedTarget = parseYamlFile(absolutePath);
|
|
24
33
|
if (!parsedTarget.ok) {
|
|
25
|
-
return {
|
|
34
|
+
return {
|
|
35
|
+
output: `YAML Syntax Error in ${filePath}: ${parsedTarget.error}. Hint: Check the file content for invalid YAML formatting, such as incorrect indentation or unescaped strings, and fix them.`,
|
|
36
|
+
};
|
|
26
37
|
}
|
|
27
38
|
targetYaml = parsedTarget.data;
|
|
28
39
|
let templateYaml;
|
|
29
40
|
const parsedTemplate = parseYamlFile(templatePath);
|
|
30
41
|
if (!parsedTemplate.ok) {
|
|
31
|
-
return {
|
|
42
|
+
return {
|
|
43
|
+
output: `Internal Error: Failed to parse reference template YAML '${templateName}': ${parsedTemplate.error}. Hint: The template file itself contains invalid YAML syntax.`,
|
|
44
|
+
};
|
|
32
45
|
}
|
|
33
46
|
templateYaml = parsedTemplate.data;
|
|
34
47
|
const errors = findStructuralErrors(templateYaml, targetYaml);
|
|
35
48
|
if (errors.length > 0) {
|
|
36
|
-
return {
|
|
49
|
+
return {
|
|
50
|
+
output: `YAML Structure Validation Failed. Your file is missing the following required keys:\n- ${errors.join('\n- ')}\n\nHint: Update the file at ${filePath} to include these missing keys so it matches the structure of '${templateName}'.`,
|
|
51
|
+
};
|
|
37
52
|
}
|
|
38
|
-
return { output:
|
|
39
|
-
}
|
|
53
|
+
return { output: 'File exists and follows the correct YAML structure.' };
|
|
54
|
+
},
|
|
40
55
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { TuiPluginApi } from '@opencode-ai/plugin/tui';
|
|
2
|
+
import { type HookEventName, type HookRunLogRecord, type HookRuntimeEntry } from '../types/hook-types.js';
|
|
3
|
+
import { type ActiveHookRow } from './hooks-presenter.js';
|
|
4
|
+
import { type SidebarTheme, type SolidRuntime } from './primitives.js';
|
|
5
|
+
export interface ActiveDialogHandle {
|
|
6
|
+
readonly event: HookEventName;
|
|
7
|
+
readonly row: ActiveHookRow;
|
|
8
|
+
readonly notBefore: number;
|
|
9
|
+
readonly sessionID?: string;
|
|
10
|
+
readonly dispose: () => void;
|
|
11
|
+
}
|
|
12
|
+
export declare const getActiveDialogHandle: () => ActiveDialogHandle | undefined;
|
|
13
|
+
export declare const buildHookLogsBody: (record: HookRunLogRecord, theme: SidebarTheme, solid: SolidRuntime, onClose: () => void, maxHeight: number, _renderer?: {
|
|
14
|
+
requestRender: () => void;
|
|
15
|
+
}) => unknown;
|
|
16
|
+
export declare const buildHookRunDetailBody: (record: HookRunLogRecord, logFile: string, theme: SidebarTheme, solid: SolidRuntime, onShowLogs: () => void, onClose: () => void, totalRunsInSession?: number, _renderer?: {
|
|
17
|
+
requestRender: () => void;
|
|
18
|
+
}) => unknown;
|
|
19
|
+
export declare const resolveSessionRecords: (logFile: string, row: ActiveHookRow, notBefore: number, sessionID?: string, statusFile?: string, fallbackEntry?: HookRuntimeEntry) => readonly HookRunLogRecord[];
|
|
20
|
+
export declare const openHookLogPopup: (api: TuiPluginApi, solid: SolidRuntime, logFile: string, row: ActiveHookRow, theme: SidebarTheme, notBefore: number, sessionID?: string, statusFile?: string, fallbackEntry?: HookRuntimeEntry) => void;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { HookEvent, HookRunState } from '../types/hook-types.js';
|
|
2
|
+
import { formatHookLogsMessage, formatHookRunSummary } from './hooks-presenter.js';
|
|
3
|
+
import { el, txt } from './primitives.js';
|
|
4
|
+
import { readHookStatusSnapshot, readSessionHookLogs } from '../hooks/hooks-persistence.js';
|
|
5
|
+
const SHOW_LOGS_LABEL = 'View logs';
|
|
6
|
+
const DETAIL_DIALOG_SIZE = 'large';
|
|
7
|
+
let currentDialogHandle;
|
|
8
|
+
export const getActiveDialogHandle = () => currentDialogHandle;
|
|
9
|
+
const buildHeader = (solid, titleText, theme, onClose) => el(solid, 'box', { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', width: '100%', gap: 1 }, [
|
|
10
|
+
txt(solid, titleText, theme.text, { flexGrow: 1, textAlign: 'center' }),
|
|
11
|
+
txt(solid, 'esc', theme.textMuted, { flexShrink: 0, onMouseUp: onClose }),
|
|
12
|
+
]);
|
|
13
|
+
export const buildHookLogsBody = (record, theme, solid, onClose, maxHeight, _renderer) => {
|
|
14
|
+
const fullText = formatHookLogsMessage(record);
|
|
15
|
+
const bodyNode = txt(solid, fullText, theme.text, { width: '100%', textAlign: 'left' });
|
|
16
|
+
const scrollbox = el(solid, 'scrollbox', { width: '100%', height: maxHeight, focused: true }, [bodyNode]);
|
|
17
|
+
return el(solid, 'box', {
|
|
18
|
+
flexDirection: 'column',
|
|
19
|
+
alignItems: 'center',
|
|
20
|
+
justifyContent: 'center',
|
|
21
|
+
width: '100%',
|
|
22
|
+
paddingLeft: 2,
|
|
23
|
+
paddingRight: 2,
|
|
24
|
+
paddingTop: 1,
|
|
25
|
+
paddingBottom: 1,
|
|
26
|
+
gap: 1,
|
|
27
|
+
}, [buildHeader(solid, `Hook logs: ${record.name}`, theme, onClose), scrollbox]);
|
|
28
|
+
};
|
|
29
|
+
export const buildHookRunDetailBody = (record, logFile, theme, solid, onShowLogs, onClose, totalRunsInSession = 0, _renderer) => {
|
|
30
|
+
const buttonLabel = txt(solid, SHOW_LOGS_LABEL, theme.selectedListItemText, { flexShrink: 0 });
|
|
31
|
+
const button = el(solid, 'box', {
|
|
32
|
+
backgroundColor: theme.primary,
|
|
33
|
+
paddingLeft: 3,
|
|
34
|
+
paddingRight: 3,
|
|
35
|
+
alignItems: 'center',
|
|
36
|
+
justifyContent: 'center',
|
|
37
|
+
onMouseUp: onShowLogs,
|
|
38
|
+
onMouseOver: () => {
|
|
39
|
+
solid.setProp(button, 'backgroundColor', theme.backgroundElement);
|
|
40
|
+
solid.setProp(buttonLabel, 'fg', theme.primary);
|
|
41
|
+
},
|
|
42
|
+
onMouseOut: () => {
|
|
43
|
+
solid.setProp(button, 'backgroundColor', theme.primary);
|
|
44
|
+
solid.setProp(buttonLabel, 'fg', theme.selectedListItemText);
|
|
45
|
+
},
|
|
46
|
+
}, [buttonLabel]);
|
|
47
|
+
const formatDetails = (rec, totalRuns) => [formatHookRunSummary(rec), `Runs in session: ${totalRuns}`, `Log file: ${logFile}`].join('\n');
|
|
48
|
+
const detailsText = formatDetails(record, totalRunsInSession);
|
|
49
|
+
const detailsNode = txt(solid, detailsText, theme.textMuted, { width: '100%', textAlign: 'center' });
|
|
50
|
+
return el(solid, 'box', {
|
|
51
|
+
flexDirection: 'column',
|
|
52
|
+
alignItems: 'center',
|
|
53
|
+
justifyContent: 'center',
|
|
54
|
+
width: '100%',
|
|
55
|
+
paddingLeft: 2,
|
|
56
|
+
paddingRight: 2,
|
|
57
|
+
paddingTop: 1,
|
|
58
|
+
paddingBottom: 1,
|
|
59
|
+
gap: 1,
|
|
60
|
+
}, [
|
|
61
|
+
buildHeader(solid, `Hook run: ${record.name}`, theme, onClose),
|
|
62
|
+
detailsNode,
|
|
63
|
+
el(solid, 'box', { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', width: '100%' }, [button]),
|
|
64
|
+
]);
|
|
65
|
+
};
|
|
66
|
+
const createIdleRecord = (row, sessionID) => ({
|
|
67
|
+
version: 1,
|
|
68
|
+
runId: `${row.event}#idle`,
|
|
69
|
+
pid: process.pid,
|
|
70
|
+
workspace: '',
|
|
71
|
+
event: row.event,
|
|
72
|
+
hook: row.name,
|
|
73
|
+
name: row.name,
|
|
74
|
+
...(sessionID !== undefined ? { sessionID } : {}),
|
|
75
|
+
startedAt: 0,
|
|
76
|
+
finishedAt: 0,
|
|
77
|
+
durationMs: 0,
|
|
78
|
+
status: HookRunState.IDLE,
|
|
79
|
+
});
|
|
80
|
+
export const resolveSessionRecords = (logFile, row, notBefore, sessionID, statusFile, fallbackEntry) => {
|
|
81
|
+
let records = [];
|
|
82
|
+
try {
|
|
83
|
+
records = readSessionHookLogs(logFile, row.event, notBefore, sessionID);
|
|
84
|
+
}
|
|
85
|
+
catch { }
|
|
86
|
+
let entry = fallbackEntry;
|
|
87
|
+
if (statusFile) {
|
|
88
|
+
try {
|
|
89
|
+
const snap = readHookStatusSnapshot(statusFile);
|
|
90
|
+
if (snap.kind === 'ok' && snap.snapshot.hooks[row.event]) {
|
|
91
|
+
entry = snap.snapshot.hooks[row.event];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch { }
|
|
95
|
+
}
|
|
96
|
+
if (entry && entry.status !== HookRunState.IDLE) {
|
|
97
|
+
const belongs = sessionID && entry.sessionID ? entry.sessionID === sessionID : true;
|
|
98
|
+
const lastRecord = records[records.length - 1];
|
|
99
|
+
const shouldAppend = entry.status === HookRunState.PENDING
|
|
100
|
+
? lastRecord?.status !== HookRunState.PENDING
|
|
101
|
+
: lastRecord === undefined || lastRecord.startedAt < entry.startedAt;
|
|
102
|
+
if (belongs && shouldAppend) {
|
|
103
|
+
const now = Date.now();
|
|
104
|
+
const exitCode = entry.exitCode;
|
|
105
|
+
const runtimeRecord = {
|
|
106
|
+
version: 1,
|
|
107
|
+
runId: `${row.event}#${entry.status}`,
|
|
108
|
+
pid: process.pid,
|
|
109
|
+
workspace: '',
|
|
110
|
+
event: row.event,
|
|
111
|
+
hook: row.name,
|
|
112
|
+
name: row.name,
|
|
113
|
+
...(entry.sessionID !== undefined ? { sessionID: entry.sessionID } : sessionID !== undefined ? { sessionID } : {}),
|
|
114
|
+
startedAt: entry.startedAt,
|
|
115
|
+
finishedAt: entry.finishedAt ?? now,
|
|
116
|
+
durationMs: entry.durationMs ?? Math.max(0, (entry.finishedAt ?? now) - entry.startedAt),
|
|
117
|
+
status: entry.status,
|
|
118
|
+
...(exitCode !== undefined ? { exitCode } : {}),
|
|
119
|
+
...(entry.reason !== undefined ? { reason: entry.reason } : {}),
|
|
120
|
+
};
|
|
121
|
+
records = [...records, runtimeRecord];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return records;
|
|
125
|
+
};
|
|
126
|
+
export const openHookLogPopup = (api, solid, logFile, row, theme, notBefore, sessionID, statusFile, fallbackEntry) => {
|
|
127
|
+
currentDialogHandle?.dispose();
|
|
128
|
+
const records = resolveSessionRecords(logFile, row, notBefore, sessionID, statusFile, fallbackEntry);
|
|
129
|
+
try {
|
|
130
|
+
const targetRecord = records[records.length - 1] ?? createIdleRecord(row, sessionID);
|
|
131
|
+
const h = api.renderer.height;
|
|
132
|
+
const maxHeight = Math.max(4, Math.floor(((h > 0 ? h : 24) * 3) / 4) - 8);
|
|
133
|
+
const onClose = () => {
|
|
134
|
+
currentDialogHandle?.dispose();
|
|
135
|
+
api.ui.dialog.clear();
|
|
136
|
+
};
|
|
137
|
+
const showLogs = () => {
|
|
138
|
+
try {
|
|
139
|
+
currentDialogHandle?.dispose();
|
|
140
|
+
const latestRecords = resolveSessionRecords(logFile, row, notBefore, sessionID, statusFile, fallbackEntry);
|
|
141
|
+
const effective = latestRecords[latestRecords.length - 1] ?? targetRecord;
|
|
142
|
+
const logsHandle = {
|
|
143
|
+
event: row.event,
|
|
144
|
+
row,
|
|
145
|
+
notBefore,
|
|
146
|
+
...(sessionID !== undefined ? { sessionID } : {}),
|
|
147
|
+
dispose: () => {
|
|
148
|
+
if (currentDialogHandle === logsHandle) {
|
|
149
|
+
currentDialogHandle = undefined;
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
currentDialogHandle = logsHandle;
|
|
154
|
+
api.ui.dialog.replace(() => {
|
|
155
|
+
const logsBody = buildHookLogsBody(effective, theme, solid, onClose, maxHeight, api.renderer);
|
|
156
|
+
return logsBody;
|
|
157
|
+
}, logsHandle.dispose);
|
|
158
|
+
api.ui.dialog.setSize(DETAIL_DIALOG_SIZE);
|
|
159
|
+
}
|
|
160
|
+
catch { }
|
|
161
|
+
};
|
|
162
|
+
const detailHandle = {
|
|
163
|
+
event: row.event,
|
|
164
|
+
row,
|
|
165
|
+
notBefore,
|
|
166
|
+
...(sessionID !== undefined ? { sessionID } : {}),
|
|
167
|
+
dispose: () => {
|
|
168
|
+
if (currentDialogHandle === detailHandle) {
|
|
169
|
+
currentDialogHandle = undefined;
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
currentDialogHandle = detailHandle;
|
|
174
|
+
api.ui.dialog.replace(() => {
|
|
175
|
+
const detailBody = buildHookRunDetailBody(targetRecord, logFile, theme, solid, showLogs, onClose, records.length, api.renderer);
|
|
176
|
+
return detailBody;
|
|
177
|
+
}, detailHandle.dispose);
|
|
178
|
+
api.ui.dialog.setSize(DETAIL_DIALOG_SIZE);
|
|
179
|
+
}
|
|
180
|
+
catch { }
|
|
181
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { HookRunState, type HookEventName, type HookRunLogRecord } from '../types/hook-types.js';
|
|
2
|
+
import type { SidebarTheme } from './primitives.js';
|
|
3
|
+
export interface ActiveHookRow {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly event: HookEventName;
|
|
6
|
+
}
|
|
7
|
+
export interface ActiveHooksView {
|
|
8
|
+
readonly title: string;
|
|
9
|
+
readonly rows: readonly ActiveHookRow[];
|
|
10
|
+
}
|
|
11
|
+
export declare const buildActiveHooksView: (directory: string) => ActiveHooksView;
|
|
12
|
+
export declare const statusLabelFor: (status: HookRunState | undefined) => string;
|
|
13
|
+
export declare const colorForStatus: (status: HookRunState | undefined, theme: SidebarTheme) => unknown;
|
|
14
|
+
export declare const formatHookRunSummary: (record: HookRunLogRecord, maxChars?: number) => string;
|
|
15
|
+
export declare const formatHookLogsMessage: (record: HookRunLogRecord) => string;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { scanHooks } from '../hooks/hooks-registry.js';
|
|
3
|
+
import { HookDigestMode, HookRunState } from '../types/hook-types.js';
|
|
4
|
+
import { hooksDirectory } from '../utils/workflow-paths.js';
|
|
5
|
+
const ACTIVE_HOOKS_TITLE = 'Active hooks:';
|
|
6
|
+
const RUN_SUMMARY_MAX_CHARS = 4_000;
|
|
7
|
+
export const buildActiveHooksView = (directory) => {
|
|
8
|
+
const scan = scanHooks(hooksDirectory(directory), { mode: HookDigestMode.Report });
|
|
9
|
+
return {
|
|
10
|
+
title: ACTIVE_HOOKS_TITLE,
|
|
11
|
+
rows: scan.entries.map((entry) => ({
|
|
12
|
+
name: path.basename(entry.path, path.extname(entry.path)),
|
|
13
|
+
event: entry.event,
|
|
14
|
+
})),
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export const statusLabelFor = (status) => {
|
|
18
|
+
switch (status) {
|
|
19
|
+
case HookRunState.PENDING:
|
|
20
|
+
return 'In progress';
|
|
21
|
+
case HookRunState.SUCCESS:
|
|
22
|
+
return 'Success';
|
|
23
|
+
case HookRunState.FAILURE:
|
|
24
|
+
return 'Error';
|
|
25
|
+
default:
|
|
26
|
+
return 'Idle';
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
export const colorForStatus = (status, theme) => {
|
|
30
|
+
switch (status) {
|
|
31
|
+
case HookRunState.PENDING:
|
|
32
|
+
return theme.warning;
|
|
33
|
+
case HookRunState.SUCCESS:
|
|
34
|
+
return theme.success;
|
|
35
|
+
case HookRunState.FAILURE:
|
|
36
|
+
return theme.error;
|
|
37
|
+
default:
|
|
38
|
+
return theme.textMuted;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const truncateMessage = (message, maxChars) => {
|
|
42
|
+
if (message.length <= maxChars)
|
|
43
|
+
return message;
|
|
44
|
+
const suffix = '\n[message truncated]';
|
|
45
|
+
return message.slice(0, Math.max(0, maxChars - suffix.length)) + suffix;
|
|
46
|
+
};
|
|
47
|
+
export const formatHookRunSummary = (record, maxChars = RUN_SUMMARY_MAX_CHARS) => {
|
|
48
|
+
const timestamp = record.finishedAt || record.startedAt;
|
|
49
|
+
const timeLabel = timestamp > 0 ? new Date(timestamp).toISOString() : 'none';
|
|
50
|
+
const lines = [
|
|
51
|
+
`Event: ${record.event}`,
|
|
52
|
+
`Status: ${statusLabelFor(record.status)} — ${record.durationMs} ms`,
|
|
53
|
+
`Time: ${timeLabel}`,
|
|
54
|
+
`Session: ${record.sessionID ?? 'none'}`,
|
|
55
|
+
`Exit: ${record.exitCode ?? 'none'}`,
|
|
56
|
+
];
|
|
57
|
+
return truncateMessage(lines.join('\n'), maxChars);
|
|
58
|
+
};
|
|
59
|
+
export const formatHookLogsMessage = (record) => {
|
|
60
|
+
const sections = [];
|
|
61
|
+
if (record.stderr !== undefined && record.stderr.trim().length > 0)
|
|
62
|
+
sections.push(`--- stderr ---\n${record.stderr.trimEnd()}`);
|
|
63
|
+
if (record.stdout !== undefined && record.stdout.trim().length > 0)
|
|
64
|
+
sections.push(`--- stdout ---\n${record.stdout.trimEnd()}`);
|
|
65
|
+
if (sections.length === 0 && record.reason !== undefined && record.reason.trim().length > 0)
|
|
66
|
+
sections.push(record.reason.trimEnd());
|
|
67
|
+
if (sections.length === 0)
|
|
68
|
+
sections.push('No output captured for this run.');
|
|
69
|
+
return sections.join('\n\n');
|
|
70
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { HookRunState, HookRuntimeEntry } from '../types/hook-types.js';
|
|
2
|
+
import { type ActiveHookRow, type ActiveHooksView } from './hooks-presenter.js';
|
|
3
|
+
import { type SidebarTheme, type SolidRuntime } from './primitives.js';
|
|
4
|
+
export interface SidebarRenderHandle {
|
|
5
|
+
readonly root: unknown;
|
|
6
|
+
readonly applyStatuses: (snapshotEntries: ReadonlyMap<string, HookRuntimeEntry>) => boolean;
|
|
7
|
+
readonly disposed: () => boolean;
|
|
8
|
+
readonly dispose: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare const materializeSidebar: (view: ActiveHooksView, theme: SidebarTheme, solid: SolidRuntime, onRowClick: (row: ActiveHookRow, theme: SidebarTheme) => void, onRowHover: () => void, interactive: boolean, notBefore: number, sessionID?: string, initialStates?: ReadonlyMap<string, HookRunState>) => SidebarRenderHandle;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { colorForStatus, statusLabelFor } from './hooks-presenter.js';
|
|
2
|
+
import { el, txt } from './primitives.js';
|
|
3
|
+
const EMPTY_HOOKS_NAME = '(none)';
|
|
4
|
+
const OPEN_HINT = '›';
|
|
5
|
+
export const materializeSidebar = (view, theme, solid, onRowClick, onRowHover, interactive, notBefore, sessionID, initialStates) => {
|
|
6
|
+
const rows = new Map();
|
|
7
|
+
const colors = new Map();
|
|
8
|
+
const sessionStates = new Map(initialStates);
|
|
9
|
+
const root = el(solid, 'box', { flexDirection: 'column' }, [el(solid, 'text', { fg: theme.text }, [view.title])]);
|
|
10
|
+
let isDisposed = false;
|
|
11
|
+
const dispose = () => {
|
|
12
|
+
if (isDisposed)
|
|
13
|
+
return;
|
|
14
|
+
isDisposed = true;
|
|
15
|
+
rows.clear();
|
|
16
|
+
colors.clear();
|
|
17
|
+
sessionStates.clear();
|
|
18
|
+
};
|
|
19
|
+
if (typeof solid.onCleanup === 'function') {
|
|
20
|
+
try {
|
|
21
|
+
solid.onCleanup(dispose);
|
|
22
|
+
}
|
|
23
|
+
catch { }
|
|
24
|
+
}
|
|
25
|
+
if (view.rows.length === 0) {
|
|
26
|
+
solid.insert(root, txt(solid, EMPTY_HOOKS_NAME, theme.textMuted));
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
for (const row of view.rows) {
|
|
30
|
+
const initialStatus = sessionStates.get(row.event);
|
|
31
|
+
const initialKey = initialStatus ?? 'idle';
|
|
32
|
+
const orb = txt(solid, '•', colorForStatus(initialStatus, theme), { flexShrink: 0 });
|
|
33
|
+
const name = txt(solid, row.name, theme.text);
|
|
34
|
+
const label = txt(solid, statusLabelFor(initialStatus), theme.textMuted);
|
|
35
|
+
const hint = interactive ? txt(solid, OPEN_HINT, theme.textMuted, { flexShrink: 0 }) : undefined;
|
|
36
|
+
const line = el(solid, 'box', {
|
|
37
|
+
flexDirection: 'row',
|
|
38
|
+
gap: 1,
|
|
39
|
+
...(interactive
|
|
40
|
+
? {
|
|
41
|
+
onMouseUp: () => {
|
|
42
|
+
if (!isDisposed)
|
|
43
|
+
onRowClick(row, theme);
|
|
44
|
+
},
|
|
45
|
+
onMouseOver: () => {
|
|
46
|
+
if (isDisposed)
|
|
47
|
+
return;
|
|
48
|
+
solid.setProp(line, 'backgroundColor', theme.backgroundElement);
|
|
49
|
+
solid.setProp(name, 'fg', theme.primary);
|
|
50
|
+
solid.setProp(hint, 'fg', theme.primary);
|
|
51
|
+
onRowHover();
|
|
52
|
+
},
|
|
53
|
+
onMouseOut: () => {
|
|
54
|
+
if (isDisposed)
|
|
55
|
+
return;
|
|
56
|
+
solid.setProp(line, 'backgroundColor', undefined);
|
|
57
|
+
solid.setProp(name, 'fg', theme.text);
|
|
58
|
+
solid.setProp(hint, 'fg', theme.textMuted);
|
|
59
|
+
onRowHover();
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
: {}),
|
|
63
|
+
}, [orb, name, label, hint]);
|
|
64
|
+
solid.insert(root, line);
|
|
65
|
+
rows.set(row.event, { orb, label });
|
|
66
|
+
colors.set(row.event, initialKey);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const applyStatuses = (snapshotEntries) => {
|
|
70
|
+
if (isDisposed)
|
|
71
|
+
return false;
|
|
72
|
+
try {
|
|
73
|
+
let changed = false;
|
|
74
|
+
for (const [event, entry] of snapshotEntries) {
|
|
75
|
+
const belongs = sessionID !== undefined && sessionID.length > 0
|
|
76
|
+
? entry.sessionID === sessionID || (entry.sessionID === undefined && entry.startedAt >= notBefore)
|
|
77
|
+
: entry.startedAt >= notBefore;
|
|
78
|
+
if (belongs) {
|
|
79
|
+
sessionStates.set(event, entry.status);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const [event, nodes] of rows) {
|
|
83
|
+
const status = sessionStates.get(event);
|
|
84
|
+
const key = status ?? 'idle';
|
|
85
|
+
if (colors.get(event) === key)
|
|
86
|
+
continue;
|
|
87
|
+
solid.setProp(nodes.orb, 'fg', colorForStatus(status, theme));
|
|
88
|
+
solid.setProp(nodes.label, 'content', statusLabelFor(status));
|
|
89
|
+
colors.set(event, key);
|
|
90
|
+
changed = true;
|
|
91
|
+
}
|
|
92
|
+
return changed;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
root,
|
|
100
|
+
applyStatuses,
|
|
101
|
+
disposed: () => isDisposed || Boolean(root?.isDestroyed),
|
|
102
|
+
dispose,
|
|
103
|
+
};
|
|
104
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { TuiPluginApi } from '@opencode-ai/plugin/tui';
|
|
2
|
+
export interface SolidRuntime {
|
|
3
|
+
readonly createElement: (tag: string) => unknown;
|
|
4
|
+
readonly insert: (parent: unknown, child: unknown) => unknown;
|
|
5
|
+
readonly setProp: (node: unknown, name: string, value: unknown) => unknown;
|
|
6
|
+
readonly onCleanup?: (fn: () => void) => void;
|
|
7
|
+
}
|
|
8
|
+
export interface SidebarTheme {
|
|
9
|
+
readonly text: unknown;
|
|
10
|
+
readonly textMuted: unknown;
|
|
11
|
+
readonly primary: unknown;
|
|
12
|
+
readonly selectedListItemText: unknown;
|
|
13
|
+
readonly backgroundElement: unknown;
|
|
14
|
+
readonly warning: unknown;
|
|
15
|
+
readonly success: unknown;
|
|
16
|
+
readonly error: unknown;
|
|
17
|
+
}
|
|
18
|
+
export type SolidElement = ReturnType<TuiPluginApi['ui']['Dialog']>;
|
|
19
|
+
export declare const el: (solid: SolidRuntime, tag: string, props: Record<string, unknown>, children?: readonly unknown[]) => unknown;
|
|
20
|
+
export declare const txt: (solid: SolidRuntime, content: string, fg: unknown, extra?: Record<string, unknown>) => unknown;
|
|
21
|
+
export declare const loadSolidRuntime: (importSolid?: () => Promise<unknown>, importSolidJs?: () => Promise<unknown>) => Promise<SolidRuntime | null>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const el = (solid, tag, props, children = []) => {
|
|
2
|
+
const node = solid.createElement(tag);
|
|
3
|
+
for (const [k, v] of Object.entries(props)) {
|
|
4
|
+
if (v !== undefined) {
|
|
5
|
+
try {
|
|
6
|
+
solid.setProp(node, k, v);
|
|
7
|
+
}
|
|
8
|
+
catch { }
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
for (const child of children) {
|
|
12
|
+
if (child !== undefined)
|
|
13
|
+
solid.insert(node, child);
|
|
14
|
+
}
|
|
15
|
+
return node;
|
|
16
|
+
};
|
|
17
|
+
export const txt = (solid, content, fg, extra = {}) => el(solid, 'text', { fg, content, ...extra });
|
|
18
|
+
export const loadSolidRuntime = async (importSolid = () => import('@opentui/solid'), importSolidJs = () => import('solid-js')) => {
|
|
19
|
+
try {
|
|
20
|
+
const runtime = (await importSolid());
|
|
21
|
+
let onCleanup;
|
|
22
|
+
if (typeof runtime.onCleanup === 'function') {
|
|
23
|
+
onCleanup = runtime.onCleanup;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
try {
|
|
27
|
+
const solidJs = (await importSolidJs());
|
|
28
|
+
if (typeof solidJs.onCleanup === 'function') {
|
|
29
|
+
onCleanup = solidJs.onCleanup;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch { }
|
|
33
|
+
}
|
|
34
|
+
return typeof runtime.createElement === 'function' && typeof runtime.insert === 'function' && typeof runtime.setProp === 'function'
|
|
35
|
+
? {
|
|
36
|
+
createElement: runtime.createElement,
|
|
37
|
+
insert: runtime.insert,
|
|
38
|
+
setProp: runtime.setProp,
|
|
39
|
+
...(onCleanup !== undefined ? { onCleanup } : {}),
|
|
40
|
+
}
|
|
41
|
+
: null;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.warn('[owflow] Failed to import @opentui/solid', error);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
};
|
package/dist/tui.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TuiPluginApi } from '@opencode-ai/plugin/tui';
|
|
2
|
+
import { buildHookLogsBody, buildHookRunDetailBody, getActiveDialogHandle, type ActiveDialogHandle } from './tui/hooks-dialogs.js';
|
|
3
|
+
import { buildActiveHooksView, colorForStatus, formatHookLogsMessage, formatHookRunSummary, statusLabelFor, type ActiveHookRow, type ActiveHooksView } from './tui/hooks-presenter.js';
|
|
4
|
+
import { type SidebarRenderHandle } from './tui/hooks-sidebar.js';
|
|
5
|
+
import { type SidebarTheme, type SolidRuntime } from './tui/primitives.js';
|
|
6
|
+
export { buildActiveHooksView, buildHookLogsBody, buildHookRunDetailBody, colorForStatus, formatHookLogsMessage, formatHookRunSummary, getActiveDialogHandle, statusLabelFor, type ActiveDialogHandle, type ActiveHookRow, type ActiveHooksView, type SidebarRenderHandle, type SidebarTheme, type SolidRuntime, };
|
|
7
|
+
export declare const registerActiveHooksSidebar: (api: TuiPluginApi, directory: string, loadSolid?: () => Promise<SolidRuntime | null>) => Promise<boolean>;
|
|
8
|
+
declare const _default: {
|
|
9
|
+
id: string;
|
|
10
|
+
tui: (api: TuiPluginApi) => Promise<void>;
|
|
11
|
+
};
|
|
12
|
+
export default _default;
|