pi-factory-gate 1.0.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/AGENTS.md +78 -0
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/package.json +51 -0
- package/src/config.ts +32 -0
- package/src/helpers.ts +114 -0
- package/src/index.ts +45 -0
- package/src/tools/agents.ts +119 -0
- package/src/tools/environments.ts +161 -0
- package/src/tools/events.ts +104 -0
- package/src/tools/jobs.ts +330 -0
- package/src/tools/workers.ts +62 -0
- package/src/tools/workflows.ts +130 -0
- package/src/types.ts +120 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { loadConfig } from "../config";
|
|
4
|
+
import { factoryApi } from "../helpers";
|
|
5
|
+
import type { WorkflowInfo } from "../types";
|
|
6
|
+
|
|
7
|
+
// ─── List Workflows ────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export const listWorkflowsTool = {
|
|
10
|
+
name: "factory_list_workflows" as const,
|
|
11
|
+
label: "List Workflow Templates",
|
|
12
|
+
description:
|
|
13
|
+
"List available workflow templates in the factory. Workflows are predefined chains of agent jobs.",
|
|
14
|
+
parameters: Type.Object({}),
|
|
15
|
+
async execute(_id: string, _p: any, _s: any, _u: any, ctx: ExtensionContext) {
|
|
16
|
+
const config = loadConfig(ctx.cwd);
|
|
17
|
+
|
|
18
|
+
// Try the workflow-templates endpoint first (DB-backed), then fall back to discovery
|
|
19
|
+
const r = await factoryApi<{ templates?: Array<{ name: string; title: string; description?: string }> }>(
|
|
20
|
+
config,
|
|
21
|
+
"/workflows/templates",
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
if (r.ok && r.data?.templates && r.data.templates.length > 0) {
|
|
25
|
+
const templates = r.data.templates;
|
|
26
|
+
const lines = [
|
|
27
|
+
`📋 Workflow Templates (${templates.length})`,
|
|
28
|
+
"",
|
|
29
|
+
];
|
|
30
|
+
for (const t of templates) {
|
|
31
|
+
lines.push(` 📝 ${t.title || t.name}`);
|
|
32
|
+
lines.push(` id: ${t.name}`);
|
|
33
|
+
if (t.description) lines.push(` ${t.description.slice(0, 120)}`);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
37
|
+
details: { count: templates.length, source: "templates" },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Fall back to workflow discovery (disk-based .md files)
|
|
42
|
+
const r2 = await factoryApi<{ workflows: WorkflowInfo[] }>(config, "/workflows");
|
|
43
|
+
|
|
44
|
+
if (!r2.ok || !r2.data) {
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: "text", text: `❌ Failed to list workflows: ${r2.error || "unknown"}` }],
|
|
47
|
+
isError: true,
|
|
48
|
+
details: {},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const workflows = r2.data.workflows || [];
|
|
53
|
+
if (workflows.length === 0) {
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text: "No workflow templates found." }],
|
|
56
|
+
details: { count: 0 },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const lines = [`📋 Workflow Templates (${workflows.length})`, ""];
|
|
61
|
+
for (const w of workflows) {
|
|
62
|
+
lines.push(` 📝 ${w.name}`);
|
|
63
|
+
lines.push(` path: ${w.path}`);
|
|
64
|
+
if (w.description) lines.push(` ${w.description.slice(0, 120)}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
69
|
+
details: { count: workflows.length, source: "discovery" },
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// ─── Run Workflow ──────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
export const runWorkflowTool = {
|
|
77
|
+
name: "factory_run_workflow" as const,
|
|
78
|
+
label: "Run Workflow Template",
|
|
79
|
+
description:
|
|
80
|
+
"Execute a workflow template on the factory. This creates a job that runs the workflow steps.",
|
|
81
|
+
parameters: Type.Object({
|
|
82
|
+
template: Type.String({ description: "Workflow template name or ID to run" }),
|
|
83
|
+
environment: Type.Optional(Type.String({ description: "Named environment to run in" })),
|
|
84
|
+
repo: Type.Optional(Type.String({ description: "Direct repo URL (overrides environment)" })),
|
|
85
|
+
branch: Type.Optional(Type.String({ description: "Branch to run on (default: main)" })),
|
|
86
|
+
confirm: Type.Optional(Type.Boolean({ description: "Must be true to confirm execution" })),
|
|
87
|
+
}),
|
|
88
|
+
async execute(_id: string, params: any, _s: any, _u: any, ctx: ExtensionContext) {
|
|
89
|
+
const config = loadConfig(ctx.cwd);
|
|
90
|
+
|
|
91
|
+
// Try the new workflow-scheduler endpoint first: POST /workflows/run with template+params
|
|
92
|
+
const body: Record<string, unknown> = { template: params.template };
|
|
93
|
+
if (params.environment) body.environment = params.environment;
|
|
94
|
+
if (params.repo) body.repo = params.repo;
|
|
95
|
+
if (params.branch) body.branch = params.branch;
|
|
96
|
+
|
|
97
|
+
const r = await factoryApi<{ jobId: string; workflow?: string; status: string }>(
|
|
98
|
+
config,
|
|
99
|
+
"/workflows/run",
|
|
100
|
+
"POST",
|
|
101
|
+
body,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
if (!r.ok) {
|
|
105
|
+
return {
|
|
106
|
+
content: [{ type: "text", text: `❌ Failed to run workflow: ${r.error || "unknown"}` }],
|
|
107
|
+
isError: true,
|
|
108
|
+
details: {},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const { jobId, status: jobStatus } = r.data!;
|
|
113
|
+
return {
|
|
114
|
+
content: [
|
|
115
|
+
{
|
|
116
|
+
type: "text",
|
|
117
|
+
text: [
|
|
118
|
+
`🔄 Workflow "${params.template}" started`,
|
|
119
|
+
` Job ID: ${jobId}`,
|
|
120
|
+
` Status: ${jobStatus}`,
|
|
121
|
+
"",
|
|
122
|
+
`Track progress: factory_get_job(id="${jobId}")`,
|
|
123
|
+
`Stream output: factory_stream_job(id="${jobId}")`,
|
|
124
|
+
].join("\n"),
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
details: { jobId, workflow: params.template, status: jobStatus },
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-factory-gate — Types
|
|
3
|
+
*
|
|
4
|
+
* Shared types for factory-gate configuration and API responses.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ── Factory Config ────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export interface FactoryConfig {
|
|
10
|
+
/** Base URL of the wrok.in orchestrator (e.g. http://orchestrator:3001) */
|
|
11
|
+
orchestratorUrl: string;
|
|
12
|
+
/** Default environment name for job dispatch when none specified */
|
|
13
|
+
defaultEnvironment?: string;
|
|
14
|
+
/** Default page limit for list operations */
|
|
15
|
+
defaultLimit: number;
|
|
16
|
+
/** Timeout in ms for API calls */
|
|
17
|
+
requestTimeout: number;
|
|
18
|
+
/** Max lines for job log output */
|
|
19
|
+
maxLogLines: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const DEFAULT_CONFIG: FactoryConfig = {
|
|
23
|
+
orchestratorUrl: "http://localhost:3001",
|
|
24
|
+
defaultLimit: 20,
|
|
25
|
+
requestTimeout: 15000,
|
|
26
|
+
maxLogLines: 500,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ── API Response Types ────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
export interface AgentInfo {
|
|
32
|
+
name: string;
|
|
33
|
+
description: string;
|
|
34
|
+
tools: string[];
|
|
35
|
+
model: string;
|
|
36
|
+
systemPrompt?: string;
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
source: "user" | "project" | "env";
|
|
39
|
+
resolvedFrom?: string;
|
|
40
|
+
isOverride?: boolean;
|
|
41
|
+
extends?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface AgentDetail extends AgentInfo {
|
|
45
|
+
resolvedFrom: string;
|
|
46
|
+
isOverride: boolean;
|
|
47
|
+
extends?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface JobInfo {
|
|
51
|
+
id: string;
|
|
52
|
+
task: {
|
|
53
|
+
mode: "single" | "parallel" | "chain";
|
|
54
|
+
agent?: string;
|
|
55
|
+
task?: string;
|
|
56
|
+
steps?: Array<{ agent: string; task: string }>;
|
|
57
|
+
tasks?: Array<{ agent: string; task: string }>;
|
|
58
|
+
};
|
|
59
|
+
status: "pending" | "running" | "completed" | "failed" | "partial";
|
|
60
|
+
createdAt: number;
|
|
61
|
+
startedAt?: number;
|
|
62
|
+
completedAt?: number;
|
|
63
|
+
result?: unknown;
|
|
64
|
+
workerId?: string;
|
|
65
|
+
environment?: string;
|
|
66
|
+
workflowName?: string;
|
|
67
|
+
repo?: string;
|
|
68
|
+
branch?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface EnvironmentInfo {
|
|
72
|
+
name: string;
|
|
73
|
+
repo: string;
|
|
74
|
+
baseBranch: string;
|
|
75
|
+
currentBranch?: string;
|
|
76
|
+
featureBranch?: string;
|
|
77
|
+
status: "empty" | "cloning" | "ready" | "error";
|
|
78
|
+
lastCommit?: string;
|
|
79
|
+
error?: string;
|
|
80
|
+
description?: string;
|
|
81
|
+
createdAt: number;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface WorkerInfo {
|
|
85
|
+
id: string;
|
|
86
|
+
host: string;
|
|
87
|
+
port: number;
|
|
88
|
+
status: "idle" | "busy" | "offline";
|
|
89
|
+
currentJobId?: string;
|
|
90
|
+
completedJobs: number;
|
|
91
|
+
lastHeartbeat: number;
|
|
92
|
+
environment?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface WorkflowInfo {
|
|
96
|
+
name: string;
|
|
97
|
+
path: string;
|
|
98
|
+
description?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface SystemEvent {
|
|
102
|
+
id: string;
|
|
103
|
+
ts: number;
|
|
104
|
+
type: string;
|
|
105
|
+
message: string;
|
|
106
|
+
details?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface FactoryStatus {
|
|
110
|
+
service: string;
|
|
111
|
+
id: string;
|
|
112
|
+
status: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── Job Streaming ─────────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
export interface StreamEvent {
|
|
118
|
+
job?: JobInfo;
|
|
119
|
+
error?: string;
|
|
120
|
+
}
|