codepiper 0.1.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/.env.example +28 -0
- package/CHANGELOG.md +10 -0
- package/LEGAL_NOTICE.md +39 -0
- package/LICENSE +21 -0
- package/README.md +524 -0
- package/package.json +90 -0
- package/packages/cli/package.json +13 -0
- package/packages/cli/src/commands/analytics.ts +157 -0
- package/packages/cli/src/commands/attach.ts +299 -0
- package/packages/cli/src/commands/audit.ts +50 -0
- package/packages/cli/src/commands/auth.ts +261 -0
- package/packages/cli/src/commands/daemon.ts +162 -0
- package/packages/cli/src/commands/doctor.ts +303 -0
- package/packages/cli/src/commands/env-set.ts +162 -0
- package/packages/cli/src/commands/hook-forward.ts +268 -0
- package/packages/cli/src/commands/keys.ts +77 -0
- package/packages/cli/src/commands/kill.ts +19 -0
- package/packages/cli/src/commands/logs.ts +419 -0
- package/packages/cli/src/commands/model.ts +172 -0
- package/packages/cli/src/commands/policy-set.ts +185 -0
- package/packages/cli/src/commands/policy.ts +227 -0
- package/packages/cli/src/commands/providers.ts +114 -0
- package/packages/cli/src/commands/resize.ts +34 -0
- package/packages/cli/src/commands/send.ts +184 -0
- package/packages/cli/src/commands/sessions.ts +202 -0
- package/packages/cli/src/commands/slash.ts +92 -0
- package/packages/cli/src/commands/start.ts +243 -0
- package/packages/cli/src/commands/stop.ts +19 -0
- package/packages/cli/src/commands/tail.ts +137 -0
- package/packages/cli/src/commands/workflow.ts +786 -0
- package/packages/cli/src/commands/workspace.ts +127 -0
- package/packages/cli/src/lib/api.ts +78 -0
- package/packages/cli/src/lib/args.ts +72 -0
- package/packages/cli/src/lib/format.ts +93 -0
- package/packages/cli/src/main.ts +563 -0
- package/packages/core/package.json +7 -0
- package/packages/core/src/config.ts +30 -0
- package/packages/core/src/errors.ts +38 -0
- package/packages/core/src/eventBus.ts +56 -0
- package/packages/core/src/eventBusAdapter.ts +143 -0
- package/packages/core/src/index.ts +10 -0
- package/packages/core/src/sqliteEventBus.ts +336 -0
- package/packages/core/src/types.ts +63 -0
- package/packages/daemon/package.json +11 -0
- package/packages/daemon/src/api/analyticsRoutes.ts +343 -0
- package/packages/daemon/src/api/authRoutes.ts +344 -0
- package/packages/daemon/src/api/bodyLimit.ts +133 -0
- package/packages/daemon/src/api/envSetRoutes.ts +170 -0
- package/packages/daemon/src/api/gitRoutes.ts +409 -0
- package/packages/daemon/src/api/hooks.ts +588 -0
- package/packages/daemon/src/api/inputPolicy.ts +249 -0
- package/packages/daemon/src/api/notificationRoutes.ts +532 -0
- package/packages/daemon/src/api/policyRoutes.ts +234 -0
- package/packages/daemon/src/api/policySetRoutes.ts +445 -0
- package/packages/daemon/src/api/routeUtils.ts +28 -0
- package/packages/daemon/src/api/routes.ts +1004 -0
- package/packages/daemon/src/api/server.ts +1388 -0
- package/packages/daemon/src/api/settingsRoutes.ts +367 -0
- package/packages/daemon/src/api/sqliteErrors.ts +47 -0
- package/packages/daemon/src/api/stt.ts +143 -0
- package/packages/daemon/src/api/terminalRoutes.ts +200 -0
- package/packages/daemon/src/api/validation.ts +287 -0
- package/packages/daemon/src/api/validationRoutes.ts +174 -0
- package/packages/daemon/src/api/workflowRoutes.ts +567 -0
- package/packages/daemon/src/api/workspaceRoutes.ts +151 -0
- package/packages/daemon/src/api/ws.ts +1588 -0
- package/packages/daemon/src/auth/apiRateLimiter.ts +73 -0
- package/packages/daemon/src/auth/authMiddleware.ts +305 -0
- package/packages/daemon/src/auth/authService.ts +496 -0
- package/packages/daemon/src/auth/rateLimiter.ts +137 -0
- package/packages/daemon/src/config/pricing.ts +79 -0
- package/packages/daemon/src/crypto/encryption.ts +196 -0
- package/packages/daemon/src/db/db.ts +2745 -0
- package/packages/daemon/src/db/index.ts +16 -0
- package/packages/daemon/src/db/migrations.ts +182 -0
- package/packages/daemon/src/db/policyDb.ts +349 -0
- package/packages/daemon/src/db/schema.sql +408 -0
- package/packages/daemon/src/db/workflowDb.ts +464 -0
- package/packages/daemon/src/git/gitUtils.ts +544 -0
- package/packages/daemon/src/index.ts +6 -0
- package/packages/daemon/src/main.ts +525 -0
- package/packages/daemon/src/notifications/pushNotifier.ts +369 -0
- package/packages/daemon/src/providers/codexAppServerScaffold.ts +49 -0
- package/packages/daemon/src/providers/registry.ts +111 -0
- package/packages/daemon/src/providers/types.ts +82 -0
- package/packages/daemon/src/sessions/auditLogger.ts +103 -0
- package/packages/daemon/src/sessions/policyEngine.ts +165 -0
- package/packages/daemon/src/sessions/policyMatcher.ts +114 -0
- package/packages/daemon/src/sessions/policyTypes.ts +94 -0
- package/packages/daemon/src/sessions/ptyProcess.ts +141 -0
- package/packages/daemon/src/sessions/sessionManager.ts +1770 -0
- package/packages/daemon/src/sessions/tmuxSession.ts +1073 -0
- package/packages/daemon/src/sessions/transcriptManager.ts +110 -0
- package/packages/daemon/src/sessions/transcriptParser.ts +149 -0
- package/packages/daemon/src/sessions/transcriptTailer.ts +214 -0
- package/packages/daemon/src/tracking/tokenTracker.ts +168 -0
- package/packages/daemon/src/workflows/contextManager.ts +83 -0
- package/packages/daemon/src/workflows/index.ts +31 -0
- package/packages/daemon/src/workflows/resultExtractor.ts +118 -0
- package/packages/daemon/src/workflows/waitConditionPoller.ts +131 -0
- package/packages/daemon/src/workflows/workflowParser.ts +217 -0
- package/packages/daemon/src/workflows/workflowRunner.ts +969 -0
- package/packages/daemon/src/workflows/workflowTypes.ts +188 -0
- package/packages/daemon/src/workflows/workflowValidator.ts +533 -0
- package/packages/providers/claude-code/package.json +11 -0
- package/packages/providers/claude-code/src/index.ts +7 -0
- package/packages/providers/claude-code/src/overlaySettings.ts +198 -0
- package/packages/providers/claude-code/src/provider.ts +311 -0
- package/packages/web/dist/android-chrome-192x192.png +0 -0
- package/packages/web/dist/android-chrome-512x512.png +0 -0
- package/packages/web/dist/apple-touch-icon.png +0 -0
- package/packages/web/dist/assets/AnalyticsPage-BIopKWRf.js +17 -0
- package/packages/web/dist/assets/PoliciesPage-CjdLN3dl.js +11 -0
- package/packages/web/dist/assets/SessionDetailPage-BtSA0V0M.js +179 -0
- package/packages/web/dist/assets/SettingsPage-Dbbz4Ca5.js +37 -0
- package/packages/web/dist/assets/WorkflowsPage-Dv6f3GgU.js +1 -0
- package/packages/web/dist/assets/chart-vendor-DlOHLaCG.js +49 -0
- package/packages/web/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/packages/web/dist/assets/css.worker-BvV5MPou.js +93 -0
- package/packages/web/dist/assets/editor.worker-CKy7Pnvo.js +26 -0
- package/packages/web/dist/assets/html.worker-BLJhxQJQ.js +470 -0
- package/packages/web/dist/assets/index-BbdhRfr2.css +1 -0
- package/packages/web/dist/assets/index-hgphORiw.js +204 -0
- package/packages/web/dist/assets/json.worker-usMZ-FED.js +58 -0
- package/packages/web/dist/assets/monaco-core-B_19GPAS.css +1 -0
- package/packages/web/dist/assets/monaco-core-DQ5Mk8AK.js +1234 -0
- package/packages/web/dist/assets/monaco-react-DfZNWvtW.js +11 -0
- package/packages/web/dist/assets/monacoSetup-DvBj52bT.js +1 -0
- package/packages/web/dist/assets/pencil-Dbczxz59.js +11 -0
- package/packages/web/dist/assets/react-vendor-B5MgMUHH.js +136 -0
- package/packages/web/dist/assets/refresh-cw-B0MGsYPL.js +6 -0
- package/packages/web/dist/assets/tabs-C8LsWiR5.js +1 -0
- package/packages/web/dist/assets/terminal-vendor-Cs8KPbV3.js +9 -0
- package/packages/web/dist/assets/terminal-vendor-LcAfv9l9.css +32 -0
- package/packages/web/dist/assets/trash-2-Btlg0d4l.js +6 -0
- package/packages/web/dist/assets/ts.worker-DGHjMaqB.js +67731 -0
- package/packages/web/dist/favicon.ico +0 -0
- package/packages/web/dist/icon.svg +1 -0
- package/packages/web/dist/index.html +29 -0
- package/packages/web/dist/manifest.json +29 -0
- package/packages/web/dist/og-image.png +0 -0
- package/packages/web/dist/originals/android-chrome-192x192.png +0 -0
- package/packages/web/dist/originals/android-chrome-512x512.png +0 -0
- package/packages/web/dist/originals/apple-touch-icon.png +0 -0
- package/packages/web/dist/originals/favicon.ico +0 -0
- package/packages/web/dist/piper.svg +1 -0
- package/packages/web/dist/sounds/codepiper-soft-chime.wav +0 -0
- package/packages/web/dist/sw.js +257 -0
- package/scripts/postinstall-link-workspaces.mjs +58 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextManager - manages workflow context and variable substitution
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class ContextManager {
|
|
6
|
+
private context: Record<string, any>;
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
this.context = {};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Set a context value
|
|
14
|
+
* @param key - Context key
|
|
15
|
+
* @param value - Value to store
|
|
16
|
+
*/
|
|
17
|
+
set(key: string, value: any): void {
|
|
18
|
+
this.context[key] = value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get a context value, supporting nested paths
|
|
23
|
+
* @param key - Context key or path (e.g., "user.name")
|
|
24
|
+
* @returns Value or undefined if not found
|
|
25
|
+
*/
|
|
26
|
+
get(key: string): any {
|
|
27
|
+
const parts = key.split(".");
|
|
28
|
+
let current = this.context;
|
|
29
|
+
|
|
30
|
+
for (const part of parts) {
|
|
31
|
+
if (current === null || current === undefined) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (typeof current === "object" && part in current) {
|
|
36
|
+
current = current[part];
|
|
37
|
+
} else {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return current;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Substitute variables in a string using ${variable} syntax
|
|
47
|
+
* @param text - Text with variables to substitute
|
|
48
|
+
* @returns Text with variables replaced
|
|
49
|
+
*/
|
|
50
|
+
substitute(text: string): string {
|
|
51
|
+
// Match ${variable} or ${nested.path}
|
|
52
|
+
return text.replace(/\$\{([^}]+)\}/g, (match, key) => {
|
|
53
|
+
const value = this.get(key.trim());
|
|
54
|
+
|
|
55
|
+
if (value === undefined) {
|
|
56
|
+
// Leave unresolved variables as-is
|
|
57
|
+
return match;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Convert objects/arrays to JSON
|
|
61
|
+
if (typeof value === "object" && value !== null) {
|
|
62
|
+
return JSON.stringify(value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return String(value);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get all context as a plain object
|
|
71
|
+
* @returns Copy of entire context
|
|
72
|
+
*/
|
|
73
|
+
getAllContext(): Record<string, any> {
|
|
74
|
+
return { ...this.context };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Clear all context
|
|
79
|
+
*/
|
|
80
|
+
clear(): void {
|
|
81
|
+
this.context = {};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow DSL and Parser
|
|
3
|
+
*
|
|
4
|
+
* Exports for workflow parsing, validation, and type definitions
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { parseWorkflow, parseWorkflowFromYaml, substituteVariables } from "./workflowParser";
|
|
8
|
+
export type {
|
|
9
|
+
BaseStep,
|
|
10
|
+
ConditionalStep,
|
|
11
|
+
ErrorStrategy,
|
|
12
|
+
ExtractConfig,
|
|
13
|
+
ExtractType,
|
|
14
|
+
LogStep,
|
|
15
|
+
LoopStep,
|
|
16
|
+
ParallelStep,
|
|
17
|
+
RetryConfig,
|
|
18
|
+
SessionStep,
|
|
19
|
+
StepResult,
|
|
20
|
+
StepStatus,
|
|
21
|
+
StepType,
|
|
22
|
+
WaitCondition,
|
|
23
|
+
WaitConditionType,
|
|
24
|
+
WorkflowContext,
|
|
25
|
+
WorkflowDefinition,
|
|
26
|
+
WorkflowExecution,
|
|
27
|
+
WorkflowStatus,
|
|
28
|
+
WorkflowStep,
|
|
29
|
+
WorkflowValidationError,
|
|
30
|
+
} from "./workflowTypes";
|
|
31
|
+
export { validateWorkflow } from "./workflowValidator";
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ResultExtractor - extracts results from transcript events using regex or JSONPath
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { JSONPath } from "jsonpath-plus";
|
|
6
|
+
import type { Database } from "../db/db.ts";
|
|
7
|
+
import type { ExtractConfig } from "./workflowTypes.ts";
|
|
8
|
+
|
|
9
|
+
export class ResultExtractor {
|
|
10
|
+
private db: Database;
|
|
11
|
+
|
|
12
|
+
constructor(db: Database) {
|
|
13
|
+
this.db = db;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extract result from transcript events
|
|
18
|
+
* @param sessionId - Session to extract from
|
|
19
|
+
* @param config - Extraction configuration
|
|
20
|
+
* @returns Extracted value or null if not found
|
|
21
|
+
*/
|
|
22
|
+
async extract(sessionId: string, config: ExtractConfig): Promise<any> {
|
|
23
|
+
// Validate extract type first
|
|
24
|
+
const configType = config.type as string;
|
|
25
|
+
if (configType !== "regex" && configType !== "jsonpath") {
|
|
26
|
+
throw new Error(`Unknown extract type: ${configType}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Get all transcript events for session
|
|
30
|
+
const events = this.db.getEventsBySessionId(sessionId, {
|
|
31
|
+
source: "transcript",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Concatenate all content
|
|
35
|
+
const allContent = events
|
|
36
|
+
.map((e) => {
|
|
37
|
+
const payload = e.payload as any;
|
|
38
|
+
return payload?.content;
|
|
39
|
+
})
|
|
40
|
+
.filter((content) => content !== null && content !== undefined)
|
|
41
|
+
.join("");
|
|
42
|
+
|
|
43
|
+
if (!allContent) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
switch (configType) {
|
|
48
|
+
case "regex":
|
|
49
|
+
return this.extractRegex(allContent, config);
|
|
50
|
+
|
|
51
|
+
case "jsonpath":
|
|
52
|
+
return this.extractJsonPath(allContent, config);
|
|
53
|
+
|
|
54
|
+
default:
|
|
55
|
+
throw new Error(`Unknown extract type: ${configType}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Extract using regex pattern
|
|
61
|
+
*/
|
|
62
|
+
private extractRegex(content: string, config: ExtractConfig): any {
|
|
63
|
+
if (!config.pattern) {
|
|
64
|
+
throw new Error("pattern is required for regex extraction");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const match = content.match(new RegExp(config.pattern));
|
|
68
|
+
if (!match) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// If there are capture groups, return them
|
|
73
|
+
if (match.length > 2) {
|
|
74
|
+
// Multiple capture groups: return array excluding full match
|
|
75
|
+
return match.slice(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (match.length === 2) {
|
|
79
|
+
// Single capture group: return just the captured value
|
|
80
|
+
return match[1];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// No capture groups: return full match
|
|
84
|
+
return match[0];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Extract using JSONPath
|
|
89
|
+
*/
|
|
90
|
+
private extractJsonPath(content: string, config: ExtractConfig): any {
|
|
91
|
+
if (!config.path) {
|
|
92
|
+
throw new Error("path is required for jsonpath extraction");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
// Parse content as JSON
|
|
97
|
+
const parsed = JSON.parse(content);
|
|
98
|
+
|
|
99
|
+
// Apply JSONPath query
|
|
100
|
+
const results = JSONPath({ path: config.path, json: parsed });
|
|
101
|
+
|
|
102
|
+
if (results.length === 0) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Return single result if only one match
|
|
107
|
+
if (results.length === 1) {
|
|
108
|
+
return results[0];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Return array if multiple matches
|
|
112
|
+
return results;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
115
|
+
throw new Error(`Failed to parse JSON for JSONPath extraction: ${errorMsg}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WaitConditionPoller - polls database for wait condition satisfaction
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Database } from "../db/db.ts";
|
|
6
|
+
import type { WaitCondition } from "./workflowTypes.ts";
|
|
7
|
+
|
|
8
|
+
export class WaitConditionPoller {
|
|
9
|
+
private db: Database;
|
|
10
|
+
private pollInterval: number;
|
|
11
|
+
|
|
12
|
+
constructor(db: Database, pollInterval: number = 100) {
|
|
13
|
+
this.db = db;
|
|
14
|
+
this.pollInterval = pollInterval;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Wait for any of the given conditions to be satisfied
|
|
19
|
+
* @param sessionId - Session to monitor
|
|
20
|
+
* @param conditions - Array of conditions to check
|
|
21
|
+
* @throws Error if timeout occurs or conditions array is empty
|
|
22
|
+
*/
|
|
23
|
+
async wait(sessionId: string, conditions: WaitCondition[]): Promise<void> {
|
|
24
|
+
if (conditions.length === 0) {
|
|
25
|
+
throw new Error("At least one wait condition is required");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const startTime = Date.now();
|
|
29
|
+
const minTimeout = this.getMinTimeout(conditions);
|
|
30
|
+
|
|
31
|
+
while (true) {
|
|
32
|
+
// Check timeout
|
|
33
|
+
if (minTimeout !== Infinity && Date.now() - startTime > minTimeout) {
|
|
34
|
+
throw new Error(`Wait condition timeout after ${minTimeout}ms for session ${sessionId}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check each condition
|
|
38
|
+
for (const condition of conditions) {
|
|
39
|
+
const satisfied = await this.checkCondition(sessionId, condition);
|
|
40
|
+
if (satisfied) {
|
|
41
|
+
return; // Condition met, resolve
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Poll interval delay
|
|
46
|
+
await new Promise((resolve) => setTimeout(resolve, this.pollInterval));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get minimum timeout from all conditions
|
|
52
|
+
*/
|
|
53
|
+
private getMinTimeout(conditions: WaitCondition[]): number {
|
|
54
|
+
const timeouts = conditions.map((c) => c.timeout ?? Infinity).filter((t) => t !== undefined);
|
|
55
|
+
|
|
56
|
+
return timeouts.length > 0 ? Math.min(...timeouts) : Infinity;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if a specific condition is satisfied
|
|
61
|
+
*/
|
|
62
|
+
private async checkCondition(sessionId: string, condition: WaitCondition): Promise<boolean> {
|
|
63
|
+
switch (condition.type) {
|
|
64
|
+
case "idle_prompt":
|
|
65
|
+
return this.checkNotification(sessionId, "idle_prompt");
|
|
66
|
+
|
|
67
|
+
case "permission_prompt":
|
|
68
|
+
return this.checkNotification(sessionId, "permission_prompt");
|
|
69
|
+
|
|
70
|
+
case "stop":
|
|
71
|
+
return this.checkStopEvent(sessionId);
|
|
72
|
+
|
|
73
|
+
case "event":
|
|
74
|
+
if (!condition.eventType) {
|
|
75
|
+
throw new Error("eventType is required for event wait condition");
|
|
76
|
+
}
|
|
77
|
+
return this.checkCustomEvent(sessionId, condition.eventType);
|
|
78
|
+
|
|
79
|
+
case "timeout":
|
|
80
|
+
// Timeout is handled separately in the main loop
|
|
81
|
+
return false;
|
|
82
|
+
|
|
83
|
+
default:
|
|
84
|
+
throw new Error(`Unknown wait condition type: ${(condition as any).type}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check if a notification of given type exists
|
|
90
|
+
*/
|
|
91
|
+
private checkNotification(sessionId: string, notificationType: string): boolean {
|
|
92
|
+
const events = this.db.getEventsBySessionId(sessionId, {
|
|
93
|
+
type: "Notification",
|
|
94
|
+
limit: 10, // Check recent events
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Check if any event has matching notification type
|
|
98
|
+
for (const event of events) {
|
|
99
|
+
const payload = event.payload as any;
|
|
100
|
+
if (payload?.type === notificationType) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check if Stop event exists
|
|
110
|
+
*/
|
|
111
|
+
private checkStopEvent(sessionId: string): boolean {
|
|
112
|
+
const events = this.db.getEventsBySessionId(sessionId, {
|
|
113
|
+
type: "Stop",
|
|
114
|
+
limit: 1,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return events.length > 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Check if custom event exists
|
|
122
|
+
*/
|
|
123
|
+
private checkCustomEvent(sessionId: string, eventType: string): boolean {
|
|
124
|
+
const events = this.db.getEventsBySessionId(sessionId, {
|
|
125
|
+
type: eventType,
|
|
126
|
+
limit: 1,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return events.length > 0;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses workflow definitions from YAML/JSON and provides variable substitution
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as yaml from "js-yaml";
|
|
8
|
+
import type { WorkflowContext, WorkflowDefinition } from "./workflowTypes";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parse a workflow definition from JSON string
|
|
12
|
+
*
|
|
13
|
+
* @param json - JSON string containing workflow definition
|
|
14
|
+
* @returns Parsed workflow or null if invalid
|
|
15
|
+
*/
|
|
16
|
+
export function parseWorkflow(json: string): WorkflowDefinition | null {
|
|
17
|
+
try {
|
|
18
|
+
const data = JSON.parse(json);
|
|
19
|
+
return validateAndNormalizeWorkflow(data);
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parse a workflow definition from YAML string
|
|
27
|
+
*
|
|
28
|
+
* @param yamlString - YAML string containing workflow definition
|
|
29
|
+
* @returns Parsed workflow or null if invalid
|
|
30
|
+
*/
|
|
31
|
+
export function parseWorkflowFromYaml(yamlString: string): WorkflowDefinition | null {
|
|
32
|
+
try {
|
|
33
|
+
const data = yaml.load(yamlString);
|
|
34
|
+
return validateAndNormalizeWorkflow(data);
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Validate and normalize workflow data
|
|
42
|
+
*
|
|
43
|
+
* Ensures the parsed data conforms to WorkflowDefinition interface
|
|
44
|
+
*/
|
|
45
|
+
function validateAndNormalizeWorkflow(data: unknown): WorkflowDefinition | null {
|
|
46
|
+
if (!data || typeof data !== "object") {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const obj = data as Record<string, unknown>;
|
|
51
|
+
|
|
52
|
+
// Required fields
|
|
53
|
+
if (typeof obj.name !== "string" || !obj.name) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!Array.isArray(obj.steps)) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Build workflow definition
|
|
62
|
+
const workflow: WorkflowDefinition = {
|
|
63
|
+
name: obj.name,
|
|
64
|
+
steps: normalizeWorkflowSteps(obj.steps),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Optional fields
|
|
68
|
+
if (typeof obj.description === "string") {
|
|
69
|
+
workflow.description = obj.description;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (obj.env && typeof obj.env === "object") {
|
|
73
|
+
workflow.env = obj.env as Record<string, string>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (typeof obj.cwd === "string") {
|
|
77
|
+
workflow.cwd = obj.cwd;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return workflow;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeWorkflowSteps(steps: unknown[]): any[] {
|
|
84
|
+
return steps.map((step) => normalizeWorkflowStep(step));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizeWorkflowStep(step: unknown): any {
|
|
88
|
+
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
89
|
+
return step;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const normalized: Record<string, any> = { ...(step as Record<string, unknown>) };
|
|
93
|
+
|
|
94
|
+
// Accept snake_case alias used by built-in YAML examples.
|
|
95
|
+
if (normalized.waitFor === undefined && normalized.wait_for !== undefined) {
|
|
96
|
+
normalized.waitFor = normalized.wait_for;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (normalized.type === "parallel" && Array.isArray(normalized.steps)) {
|
|
100
|
+
normalized.steps = normalizeWorkflowSteps(normalized.steps);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (normalized.type === "if") {
|
|
104
|
+
if (Array.isArray(normalized.then)) {
|
|
105
|
+
// biome-ignore lint/suspicious/noThenProperty: Workflow DSL uses "then" for conditional branches.
|
|
106
|
+
normalized.then = normalizeWorkflowSteps(normalized.then);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (Array.isArray(normalized.else)) {
|
|
110
|
+
normalized.else = normalizeWorkflowSteps(normalized.else);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (normalized.type === "foreach" && normalized.step) {
|
|
115
|
+
normalized.step = normalizeWorkflowStep(normalized.step);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return normalized;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Substitute variables in a string using workflow context
|
|
123
|
+
*
|
|
124
|
+
* Supports ${variable} and ${steps.stepName.field} syntax
|
|
125
|
+
*
|
|
126
|
+
* @param template - String containing variable placeholders
|
|
127
|
+
* @param context - Workflow context with variables and step results
|
|
128
|
+
* @returns String with variables substituted
|
|
129
|
+
*/
|
|
130
|
+
export function substituteVariables(template: string, context: WorkflowContext): string {
|
|
131
|
+
// Match ${...} patterns (but not \${...})
|
|
132
|
+
const variablePattern = /(?<!\\)\$\{([^}]+)\}/g;
|
|
133
|
+
|
|
134
|
+
return template.replace(variablePattern, (match, path) => {
|
|
135
|
+
const value = resolveVariablePath(path.trim(), context);
|
|
136
|
+
|
|
137
|
+
if (value === undefined) {
|
|
138
|
+
// Keep original if not found
|
|
139
|
+
return match;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return valueToString(value);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Resolve a variable path in the context
|
|
148
|
+
*
|
|
149
|
+
* Supports:
|
|
150
|
+
* - Direct variables: "name" -> context.variables.name
|
|
151
|
+
* - Step results: "steps.stepName.field" -> context.steps.stepName.field
|
|
152
|
+
* - Nested paths: "steps.stepName.extractedData.key"
|
|
153
|
+
*
|
|
154
|
+
* @param path - Dot-separated path to variable
|
|
155
|
+
* @param context - Workflow context
|
|
156
|
+
* @returns Resolved value or undefined
|
|
157
|
+
*/
|
|
158
|
+
function resolveVariablePath(path: string, context: WorkflowContext): unknown {
|
|
159
|
+
const parts = path.split(".");
|
|
160
|
+
|
|
161
|
+
// Check if it starts with "steps"
|
|
162
|
+
if (parts[0] === "steps") {
|
|
163
|
+
// Navigate through context.steps
|
|
164
|
+
let current: unknown = context.steps;
|
|
165
|
+
|
|
166
|
+
for (let i = 1; i < parts.length; i++) {
|
|
167
|
+
if (!current || typeof current !== "object") {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
const part = parts[i];
|
|
171
|
+
if (part === undefined) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
current = (current as Record<string, unknown>)[part];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return current;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Otherwise, look in variables
|
|
181
|
+
let current: unknown = context.variables;
|
|
182
|
+
|
|
183
|
+
for (const part of parts) {
|
|
184
|
+
if (!current || typeof current !== "object") {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
current = (current as Record<string, unknown>)[part];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return current;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Convert a value to string for substitution
|
|
195
|
+
*
|
|
196
|
+
* - Primitives: toString()
|
|
197
|
+
* - Objects/Arrays: JSON.stringify()
|
|
198
|
+
*
|
|
199
|
+
* @param value - Value to convert
|
|
200
|
+
* @returns String representation
|
|
201
|
+
*/
|
|
202
|
+
function valueToString(value: unknown): string {
|
|
203
|
+
if (value === null || value === undefined) {
|
|
204
|
+
return String(value);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (typeof value === "string") {
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
212
|
+
return String(value);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Objects and arrays -> JSON
|
|
216
|
+
return JSON.stringify(value);
|
|
217
|
+
}
|