lastlight-workflow-engine 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/LICENSE +21 -0
- package/dist/core/dag.d.ts +37 -0
- package/dist/core/dag.js +118 -0
- package/dist/core/dag.js.map +1 -0
- package/dist/core/loop-eval.d.ts +28 -0
- package/dist/core/loop-eval.js +74 -0
- package/dist/core/loop-eval.js.map +1 -0
- package/dist/core/phase-executor.d.ts +152 -0
- package/dist/core/phase-executor.js +922 -0
- package/dist/core/phase-executor.js.map +1 -0
- package/dist/core/phase-ref.d.ts +50 -0
- package/dist/core/phase-ref.js +91 -0
- package/dist/core/phase-ref.js.map +1 -0
- package/dist/core/scheduler.d.ts +45 -0
- package/dist/core/scheduler.js +196 -0
- package/dist/core/scheduler.js.map +1 -0
- package/dist/core/schema.d.ts +344 -0
- package/dist/core/schema.js +460 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/core/templates.d.ts +88 -0
- package/dist/core/templates.js +127 -0
- package/dist/core/templates.js.map +1 -0
- package/dist/core/types.d.ts +261 -0
- package/dist/core/types.js +15 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/verdict.d.ts +19 -0
- package/dist/core/verdict.js +30 -0
- package/dist/core/verdict.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/ports/ports.d.ts +233 -0
- package/dist/ports/ports.js +11 -0
- package/dist/ports/ports.js.map +1 -0
- package/dist/test-support/fakes.d.ts +86 -0
- package/dist/test-support/fakes.js +0 -0
- package/dist/test-support/fakes.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal template engine for workflow prompt files.
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* {{varName}} — simple variable substitution
|
|
6
|
+
* {{slugify varName}} — slugify helper applied to a variable
|
|
7
|
+
* {{branchUrl file}} — generate a GitHub branch URL for a file in issueDir
|
|
8
|
+
* {{#if varName}}...{{/if}} — conditional blocks (no nesting, truthy check)
|
|
9
|
+
*/
|
|
10
|
+
export function slugify(text) {
|
|
11
|
+
return text
|
|
12
|
+
.toLowerCase()
|
|
13
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
14
|
+
.replace(/^-|-$/g, "")
|
|
15
|
+
.slice(0, 40);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Render a template string with the given context.
|
|
19
|
+
* Processes: {{#if}}, {{slugify}}, {{branchUrl}}, {{varName}}.
|
|
20
|
+
*/
|
|
21
|
+
export function renderTemplate(template, ctx) {
|
|
22
|
+
let result = template;
|
|
23
|
+
// 0. Phase output substitution: ${phaseName.output} → phaseOutputs[phaseName]
|
|
24
|
+
if (ctx.phaseOutputs) {
|
|
25
|
+
const phaseOutputs = ctx.phaseOutputs;
|
|
26
|
+
result = result.replace(/\$\{(\w+)\.output\}/g, (_match, phaseName) => {
|
|
27
|
+
const val = phaseOutputs[phaseName];
|
|
28
|
+
if (val === undefined || val === null)
|
|
29
|
+
return "";
|
|
30
|
+
return typeof val === "string" ? val : String(val?.output ?? JSON.stringify(val));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// Walk a dotted key like `scratch.socratic.ready` through ctx, falling
|
|
34
|
+
// back to ctx.phaseOutputs for the first segment. Returns undefined on
|
|
35
|
+
// any missing intermediate.
|
|
36
|
+
const walkKey = (key) => {
|
|
37
|
+
const parts = key.split(".");
|
|
38
|
+
if (parts.length === 1) {
|
|
39
|
+
const val = ctx[key];
|
|
40
|
+
if (val !== undefined && val !== null)
|
|
41
|
+
return val;
|
|
42
|
+
return ctx.phaseOutputs?.[key];
|
|
43
|
+
}
|
|
44
|
+
let cur = ctx[parts[0]];
|
|
45
|
+
if (cur === undefined || cur === null)
|
|
46
|
+
cur = ctx.phaseOutputs?.[parts[0]];
|
|
47
|
+
for (let i = 1; i < parts.length; i++) {
|
|
48
|
+
if (cur === null || typeof cur !== "object")
|
|
49
|
+
return undefined;
|
|
50
|
+
cur = cur[parts[i]];
|
|
51
|
+
}
|
|
52
|
+
return cur;
|
|
53
|
+
};
|
|
54
|
+
// 1. Conditional blocks: {{#if varName}}...{{/if}} (supports dot notation).
|
|
55
|
+
// Key segments allow hyphens so phase/model keys like `pr-fix` resolve
|
|
56
|
+
// (e.g. {{models.pr-fix}}); \w alone would leave them unrendered.
|
|
57
|
+
result = result.replace(/\{\{#if\s+(!?)([\w-]+(?:\.[\w-]+)*)\}\}([\s\S]*?)\{\{\/if\}\}/g, (_match, negate, varName, body) => {
|
|
58
|
+
const val = walkKey(varName);
|
|
59
|
+
// Truthy: non-empty string, non-zero number, non-empty array, true boolean
|
|
60
|
+
const truthy = val !== undefined &&
|
|
61
|
+
val !== null &&
|
|
62
|
+
val !== "" &&
|
|
63
|
+
val !== false &&
|
|
64
|
+
val !== 0 &&
|
|
65
|
+
!(Array.isArray(val) && val.length === 0);
|
|
66
|
+
return (negate ? !truthy : truthy) ? body : "";
|
|
67
|
+
});
|
|
68
|
+
// 2. Slugify helper: {{slugify varName}}
|
|
69
|
+
result = result.replace(/\{\{slugify\s+(\w+)\}\}/g, (_match, varName) => {
|
|
70
|
+
const val = ctx[varName];
|
|
71
|
+
if (val === undefined || val === null)
|
|
72
|
+
return "";
|
|
73
|
+
return slugify(String(val));
|
|
74
|
+
});
|
|
75
|
+
// 3. Branch URL helper: {{branchUrl filename}}
|
|
76
|
+
// Generates: https://github.com/{owner}/{repo}/blob/{branch}/.lastlight/issue-{N}/{file}
|
|
77
|
+
result = result.replace(/\{\{branchUrl\s+(\S+)\}\}/g, (_match, file) => {
|
|
78
|
+
const encoded = encodeURIComponent(ctx.branch);
|
|
79
|
+
return `https://github.com/${ctx.owner}/${ctx.repo}/blob/${encoded}/${ctx.issueDir}/${file}`;
|
|
80
|
+
});
|
|
81
|
+
// 3b. Artifact URL helper: {{artifactUrl filename}} — mode-aware link to a
|
|
82
|
+
// handoff doc. In repo mode it's identical to {{branchUrl}} (the doc is
|
|
83
|
+
// committed on the branch). In server mode the doc lives on the Last Light
|
|
84
|
+
// host, so the link points at the admin dashboard's Artifacts view. Falls
|
|
85
|
+
// back to the branch URL when no publicUrl is configured so a server-mode
|
|
86
|
+
// deploy without PUBLIC_URL still produces a usable (if GitHub-less) link.
|
|
87
|
+
result = result.replace(/\{\{artifactUrl\s+(\S+)\}\}/g, (_match, file) => {
|
|
88
|
+
const branchHref = `https://github.com/${ctx.owner}/${ctx.repo}/blob/${encodeURIComponent(ctx.branch)}/${ctx.issueDir}/${file}`;
|
|
89
|
+
if (!ctx.externalizeArtifacts)
|
|
90
|
+
return branchHref;
|
|
91
|
+
if (!ctx.publicUrl)
|
|
92
|
+
return branchHref;
|
|
93
|
+
const issueKey = ctx.issueDir.replace(/^\.lastlight\//, "");
|
|
94
|
+
const base = String(ctx.publicUrl).replace(/\/+$/, "");
|
|
95
|
+
const q = `repo=${encodeURIComponent(`${ctx.owner}/${ctx.repo}`)}` +
|
|
96
|
+
`&key=${encodeURIComponent(issueKey)}` +
|
|
97
|
+
`&doc=${encodeURIComponent(file)}`;
|
|
98
|
+
return `${base}/admin/?tab=artifacts&${q}`;
|
|
99
|
+
});
|
|
100
|
+
// 3c. Approval URL helper: {{approvalUrl}} — deep link to the focused
|
|
101
|
+
// approval view for the gate currently being rendered. `approvalId` is
|
|
102
|
+
// injected into the message context by PhaseExecutor.pauseForApproval, and
|
|
103
|
+
// `publicUrl` is the dashboard base. Renders empty when either is absent
|
|
104
|
+
// (e.g. no PUBLIC_URL configured) so the rest of the gate message still
|
|
105
|
+
// posts cleanly.
|
|
106
|
+
result = result.replace(/\{\{approvalUrl\}\}/g, () => {
|
|
107
|
+
if (!ctx.publicUrl || !ctx.approvalId)
|
|
108
|
+
return "";
|
|
109
|
+
const base = String(ctx.publicUrl).replace(/\/+$/, "");
|
|
110
|
+
return `${base}/admin/?approval=${encodeURIComponent(String(ctx.approvalId))}`;
|
|
111
|
+
});
|
|
112
|
+
// 4. Simple variable substitution: {{varName}} and {{a.b.c...}}.
|
|
113
|
+
// Dotted access first checks top-level ctx, then falls back to
|
|
114
|
+
// ctx.phaseOutputs[parent] for the first segment so YAML phases can
|
|
115
|
+
// emit structured output via `output_var` and downstream prompts can
|
|
116
|
+
// read it directly — and the socratic explore loop can read
|
|
117
|
+
// {{scratch.socratic.qa}}.
|
|
118
|
+
// Key segments allow hyphens (e.g. {{models.pr-fix}}) — see #1.
|
|
119
|
+
result = result.replace(/\{\{([\w-]+(?:\.[\w-]+)*)\}\}/g, (_match, key) => {
|
|
120
|
+
const val = walkKey(key);
|
|
121
|
+
if (val === undefined || val === null)
|
|
122
|
+
return "";
|
|
123
|
+
return typeof val === "object" ? JSON.stringify(val) : String(val);
|
|
124
|
+
});
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/core/templates.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared vocabulary for the workflow engine — the concrete data types that flow
|
|
3
|
+
* through phase execution: the agent config/result, the git-sandbox access
|
|
4
|
+
* descriptor, and the deterministic command spec.
|
|
5
|
+
*
|
|
6
|
+
* These moved here (from `src/engine/github/profiles.ts`,
|
|
7
|
+
* `src/config/config.ts`, and `src/engine/executors/orchestrator.ts`) so the
|
|
8
|
+
* engine owns its own vocabulary and no longer type-depends on the app layer.
|
|
9
|
+
* The app-side modules re-export them from here for import-path stability.
|
|
10
|
+
*
|
|
11
|
+
* Engine-internal: this module imports nothing from `../engine`, `../state`,
|
|
12
|
+
* `../config`, etc. — the dependency-cruiser boundary gate enforces that.
|
|
13
|
+
*/
|
|
14
|
+
/** Workflow sandbox backend. */
|
|
15
|
+
export type SandboxBackend = "gondolin" | "docker" | "smol" | "none";
|
|
16
|
+
/** Where build handoff docs live for a run. */
|
|
17
|
+
export type BuildAssetsLocation = "repo" | "server";
|
|
18
|
+
/** OTEL forwarding + redaction config threaded into a phase run. */
|
|
19
|
+
export interface OtelConfig {
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
serviceName: string;
|
|
22
|
+
includeContent: boolean;
|
|
23
|
+
forwardToSandbox: boolean;
|
|
24
|
+
strict: boolean;
|
|
25
|
+
collectorHosts: string[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Configuration for an agent execution.
|
|
29
|
+
*/
|
|
30
|
+
export interface ExecutorConfig {
|
|
31
|
+
/** Working directory for the agent (used by the no-sandbox path). */
|
|
32
|
+
cwd?: string;
|
|
33
|
+
/** Maximum conversation turns. Unused by agentic-pi; kept for API stability. */
|
|
34
|
+
maxTurns?: number;
|
|
35
|
+
/** Model id (e.g. "anthropic/claude-sonnet-4-6"). */
|
|
36
|
+
model?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Pi thinking level: `off | minimal | low | medium | high | xhigh`.
|
|
39
|
+
* Forwarded to agentic-pi as the `thinking` option. When omitted,
|
|
40
|
+
* agentic-pi uses the model's default.
|
|
41
|
+
*/
|
|
42
|
+
variant?: string;
|
|
43
|
+
/** Path to agent context directory. */
|
|
44
|
+
agentContextDir?: string;
|
|
45
|
+
/** Directory for persistent state. */
|
|
46
|
+
stateDir?: string;
|
|
47
|
+
/** Directory for agent sandboxes (cloned repos). */
|
|
48
|
+
sandboxDir?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Run the agent in a `<workspace>/<repoSubdir>/` SUBDIRECTORY instead of the
|
|
51
|
+
* workspace root, WITHOUT going through the token-gated `prePopulate`/clone
|
|
52
|
+
* path. For callers that pre-seed the repo themselves (e.g. the evals harness
|
|
53
|
+
* in static-token mode): it reproduces production's nested layout — the repo
|
|
54
|
+
* checkout is a child dir, while `AGENTS.md`/`.lastlight-skills/` stay at the
|
|
55
|
+
* workspace root, siblings outside the repo's git tree. Ignored when a real
|
|
56
|
+
* `prePopulate` clone runs (that already nests into `<repo>/`). Use backend
|
|
57
|
+
* `none` so skills stage at the root rather than under the repo.
|
|
58
|
+
*/
|
|
59
|
+
repoSubdir?: string;
|
|
60
|
+
/** Where the shim writes dashboard envelope jsonl. */
|
|
61
|
+
sessionsDir?: string;
|
|
62
|
+
/** Workflow sandbox backend (overrides config-level default). */
|
|
63
|
+
sandbox?: SandboxBackend;
|
|
64
|
+
/**
|
|
65
|
+
* Which docker sandbox image this phase runs in: `"qa"` selects the
|
|
66
|
+
* browser-QA image (Playwright + Chromium), anything else the lean default.
|
|
67
|
+
* Overlaid per-phase from `sandbox_image:` by `phaseConfigFor`; only acted on
|
|
68
|
+
* by the docker path.
|
|
69
|
+
*/
|
|
70
|
+
sandboxImage?: "default" | "qa";
|
|
71
|
+
/**
|
|
72
|
+
* Where build handoff docs live for this run:
|
|
73
|
+
* - "repo" (default): the agent writes/commits `.lastlight/<issueKey>/`
|
|
74
|
+
* into the target repo branch; the stage-in/harvest seam is skipped.
|
|
75
|
+
* - "server": docs are staged in from / harvested back to the server store
|
|
76
|
+
* under `buildAssetsDir`, never committed. Set from the runtime config.
|
|
77
|
+
*/
|
|
78
|
+
buildAssets?: BuildAssetsLocation;
|
|
79
|
+
/** Filesystem root for the server-mode build-assets store (when buildAssets === "server"). */
|
|
80
|
+
buildAssetsDir?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Server-mode artifact identity for this run. Used by the stage-in/harvest
|
|
83
|
+
* seam to locate the run's docs in the store at
|
|
84
|
+
* `<buildAssetsDir>/<owner>/<repo>/<issueKey>/`. Set per run from simple.ts.
|
|
85
|
+
*/
|
|
86
|
+
buildAssetsKey?: {
|
|
87
|
+
owner: string;
|
|
88
|
+
repo: string;
|
|
89
|
+
issueKey: string;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Bypass the HTTP egress allowlist for this phase. When true:
|
|
93
|
+
* - gondolin: `allowedHttpHosts: ["*"]` is passed to agentic-pi.
|
|
94
|
+
* - docker: the sandbox container uses the open CoreDNS/nginx
|
|
95
|
+
* egress-firewall pair instead of the strict one.
|
|
96
|
+
*
|
|
97
|
+
* Default false. Set via the `unrestricted_egress` field on a workflow
|
|
98
|
+
* phase — used for phases that need broad web access (e.g. an explore
|
|
99
|
+
* phase that searches third-party documentation).
|
|
100
|
+
*/
|
|
101
|
+
unrestrictedEgress?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Enable agentic-pi's web-search extension (`web_search` / `web_fetch`
|
|
104
|
+
* tools). Default false. agentic-pi auto-enables web search whenever a
|
|
105
|
+
* provider env var (`TAVILY_API_KEY`, `BRAVE_SEARCH_API_KEY`,
|
|
106
|
+
* `EXA_API_KEY`) is present, so we pass an explicit `false` downstream
|
|
107
|
+
* to suppress auto-enable for non-explore workflows.
|
|
108
|
+
*/
|
|
109
|
+
webSearch?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Force a specific web-search provider. When unset, agentic-pi picks
|
|
112
|
+
* one based on which API key env var is present (Tavily > Exa > Brave).
|
|
113
|
+
*/
|
|
114
|
+
webSearchProvider?: "tavily" | "brave" | "exa";
|
|
115
|
+
/**
|
|
116
|
+
* Override the GitHub REST API base URL for agentic-pi's built-in
|
|
117
|
+
* `github_*` tools (Octokit `baseUrl`). Test/eval escape hatch only:
|
|
118
|
+
* the eval harness (`evals/`) points this at a local fake GitHub server so
|
|
119
|
+
* a real workflow runs unchanged with its GitHub calls mocked. Production
|
|
120
|
+
* leaves it unset → `https://api.github.com`. Only honoured by the
|
|
121
|
+
* in-process (`none`/`gondolin`) path.
|
|
122
|
+
*/
|
|
123
|
+
githubApiBaseUrl?: string;
|
|
124
|
+
/** Controls OTEL env forwarding and PI event redaction for workflow phase runs. */
|
|
125
|
+
otel?: OtelConfig;
|
|
126
|
+
telemetry?: {
|
|
127
|
+
workflowName?: string;
|
|
128
|
+
phaseName?: string;
|
|
129
|
+
triggerId?: string;
|
|
130
|
+
workflowRunId?: string;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Absolute host paths to skill directories (each containing SKILL.md
|
|
134
|
+
* plus optional scripts/, references/, assets/). Staged into a per-phase
|
|
135
|
+
* bundle at `.lastlight-skills/<phaseName>/<basename>/` before the agent
|
|
136
|
+
* runs — gondolin/none via symlink, docker via copy — and passed to pi
|
|
137
|
+
* explicitly (`--skill` in docker, `skillPaths` in-process). cwd stays the
|
|
138
|
+
* repo; the bundle is a workspace-root sibling of the repo (never committed)
|
|
139
|
+
* on docker/none, and under the repo + local `.git/info/exclude` on gondolin
|
|
140
|
+
* (which mounts only cwd). Keyed per phase so parallel phases stay isolated.
|
|
141
|
+
* Resolved by `phaseConfigFor` in the workflow runner from the phase's
|
|
142
|
+
* `skill:`/`skills:` field.
|
|
143
|
+
*/
|
|
144
|
+
skillPaths?: string[];
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Normalized status of one agentic-pi extension for a single run.
|
|
148
|
+
*/
|
|
149
|
+
export interface ExtensionStatus {
|
|
150
|
+
/** "configured" when the extension loaded, "skipped" otherwise. */
|
|
151
|
+
status: string;
|
|
152
|
+
/** file-search only: "override" | "tools-only" | "tools-and-ui". */
|
|
153
|
+
mode?: string;
|
|
154
|
+
/** web-search only: the resolved search provider. */
|
|
155
|
+
provider?: string;
|
|
156
|
+
/** Number of tools the extension registered. */
|
|
157
|
+
toolCount?: number;
|
|
158
|
+
/** Why it was skipped (e.g. "no-credentials", "resolve-failed"). */
|
|
159
|
+
reason?: string;
|
|
160
|
+
}
|
|
161
|
+
export type ExtensionStatusMap = Record<string, ExtensionStatus>;
|
|
162
|
+
/**
|
|
163
|
+
* One skill agentic-pi discovered for a run, flattened from the
|
|
164
|
+
* `skills_status` event's `skills[]`. Mirrors agentic-pi's `SkillSummary`.
|
|
165
|
+
*/
|
|
166
|
+
export interface SkillSummary {
|
|
167
|
+
/** Skill name (from SKILL.md frontmatter). */
|
|
168
|
+
name: string;
|
|
169
|
+
/** Absolute path to the skill's SKILL.md. */
|
|
170
|
+
source: string;
|
|
171
|
+
/**
|
|
172
|
+
* Whether the model can auto-invoke it. False when the skill set
|
|
173
|
+
* `disable-model-invocation: true`.
|
|
174
|
+
*/
|
|
175
|
+
modelInvocable: boolean;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Normalized status of agentic-pi's skill discovery for a single run.
|
|
179
|
+
*/
|
|
180
|
+
export interface SkillsStatus {
|
|
181
|
+
status: string;
|
|
182
|
+
discovered: number;
|
|
183
|
+
skills: SkillSummary[];
|
|
184
|
+
mappedPaths: string[];
|
|
185
|
+
noSkills: boolean;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Result from an agent execution.
|
|
189
|
+
*/
|
|
190
|
+
export interface ExecutionResult {
|
|
191
|
+
success: boolean;
|
|
192
|
+
output: string;
|
|
193
|
+
turns: number;
|
|
194
|
+
error?: string;
|
|
195
|
+
durationMs: number;
|
|
196
|
+
/** Session id captured from the runtime's stream. */
|
|
197
|
+
sessionId?: string;
|
|
198
|
+
/** Total USD cost reported by the runtime. Zero under OAuth/subscription auth. */
|
|
199
|
+
costUsd?: number;
|
|
200
|
+
/** Tokens billed as fresh input (no cache hit). */
|
|
201
|
+
inputTokens?: number;
|
|
202
|
+
/** Tokens that triggered a cache write. */
|
|
203
|
+
cacheCreationInputTokens?: number;
|
|
204
|
+
/** Tokens served from prompt cache (heavily discounted). */
|
|
205
|
+
cacheReadInputTokens?: number;
|
|
206
|
+
/** Output tokens generated by the model. */
|
|
207
|
+
outputTokens?: number;
|
|
208
|
+
/** API-side duration (excludes orchestrator overhead). */
|
|
209
|
+
apiDurationMs?: number;
|
|
210
|
+
/** Mapped stop reason ("success" / "error_*" / etc.). */
|
|
211
|
+
stopReason?: string;
|
|
212
|
+
/** Which agentic-pi extensions were active for this run, keyed by name. */
|
|
213
|
+
extensions?: ExtensionStatusMap;
|
|
214
|
+
/** Skill-loading status captured from agentic-pi's `skills_status` event. */
|
|
215
|
+
skills?: SkillsStatus;
|
|
216
|
+
}
|
|
217
|
+
export type GitAccessProfile = "read" | "issues-write" | "review-write" | "repo-write";
|
|
218
|
+
export interface GitSandboxAccess {
|
|
219
|
+
owner: string;
|
|
220
|
+
repo: string;
|
|
221
|
+
profile: GitAccessProfile;
|
|
222
|
+
/**
|
|
223
|
+
* When true, sandbox MCP can mint/refresh tokens using the app PEM.
|
|
224
|
+
* Leave false for lower-trust runs to keep private key material inaccessible.
|
|
225
|
+
*/
|
|
226
|
+
allowMcpAppAuth?: boolean;
|
|
227
|
+
/**
|
|
228
|
+
* When set, the harness pre-clones the repo at this branch into the
|
|
229
|
+
* task's workspace before the sandbox container starts.
|
|
230
|
+
*/
|
|
231
|
+
prePopulateBranch?: string;
|
|
232
|
+
/**
|
|
233
|
+
* The owning workflow run id. Stamped into a `<workDir>/.lastlight-run`
|
|
234
|
+
* marker by the pre-clone so a reused per-PR workspace can tell "next phase
|
|
235
|
+
* of the same run" from "a fresh run reusing an old PR dir".
|
|
236
|
+
*/
|
|
237
|
+
runId?: string;
|
|
238
|
+
/**
|
|
239
|
+
* Clone shallowly (`--depth 1 --single-branch`). Set for read-only
|
|
240
|
+
* workflows that never need history.
|
|
241
|
+
*/
|
|
242
|
+
shallow?: boolean;
|
|
243
|
+
/**
|
|
244
|
+
* Recreate the workspace from the **default branch** on a fresh run instead
|
|
245
|
+
* of reusing/refreshing an existing feature-branch checkout (issue #153).
|
|
246
|
+
*/
|
|
247
|
+
recreateFromBase?: boolean;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* The spec for a deterministic `type: bash` / `type: script` phase — a shell
|
|
251
|
+
* command or an inline script with a runtime.
|
|
252
|
+
*/
|
|
253
|
+
export type CommandSpec = {
|
|
254
|
+
kind: "bash";
|
|
255
|
+
command: string;
|
|
256
|
+
} | {
|
|
257
|
+
kind: "script";
|
|
258
|
+
script: string;
|
|
259
|
+
runtime: "js" | "ts" | "python";
|
|
260
|
+
name: string;
|
|
261
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared vocabulary for the workflow engine — the concrete data types that flow
|
|
3
|
+
* through phase execution: the agent config/result, the git-sandbox access
|
|
4
|
+
* descriptor, and the deterministic command spec.
|
|
5
|
+
*
|
|
6
|
+
* These moved here (from `src/engine/github/profiles.ts`,
|
|
7
|
+
* `src/config/config.ts`, and `src/engine/executors/orchestrator.ts`) so the
|
|
8
|
+
* engine owns its own vocabulary and no longer type-depends on the app layer.
|
|
9
|
+
* The app-side modules re-export them from here for import-path stability.
|
|
10
|
+
*
|
|
11
|
+
* Engine-internal: this module imports nothing from `../engine`, `../state`,
|
|
12
|
+
* `../config`, etc. — the dependency-cruiser boundary gate enforces that.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single parser for a reviewer phase's verdict.
|
|
3
|
+
*
|
|
4
|
+
* A reviewer agent is asked to end its output with a `VERDICT:` marker line —
|
|
5
|
+
* either `VERDICT: APPROVED` or `VERDICT: REQUEST_CHANGES`. When that marker is
|
|
6
|
+
* present we trust it. When it's absent we fall back to a fragile heuristic
|
|
7
|
+
* (preserved here exactly as both runner sites had it): treat the output as
|
|
8
|
+
* approved only when it does NOT mention `REQUEST_CHANGES` and DOES start with
|
|
9
|
+
* `APPROVED`. `viaFallback` lets callers warn when the marker was missing.
|
|
10
|
+
*
|
|
11
|
+
* Hardening the fallback is deliberately out of scope — this parser is
|
|
12
|
+
* behaviour-neutral on verdict semantics.
|
|
13
|
+
*/
|
|
14
|
+
export type ReviewerVerdict = "APPROVED" | "REQUEST_CHANGES";
|
|
15
|
+
export interface ParsedVerdict {
|
|
16
|
+
verdict: ReviewerVerdict;
|
|
17
|
+
viaFallback: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function parseReviewerVerdict(output: string): ParsedVerdict;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single parser for a reviewer phase's verdict.
|
|
3
|
+
*
|
|
4
|
+
* A reviewer agent is asked to end its output with a `VERDICT:` marker line —
|
|
5
|
+
* either `VERDICT: APPROVED` or `VERDICT: REQUEST_CHANGES`. When that marker is
|
|
6
|
+
* present we trust it. When it's absent we fall back to a fragile heuristic
|
|
7
|
+
* (preserved here exactly as both runner sites had it): treat the output as
|
|
8
|
+
* approved only when it does NOT mention `REQUEST_CHANGES` and DOES start with
|
|
9
|
+
* `APPROVED`. `viaFallback` lets callers warn when the marker was missing.
|
|
10
|
+
*
|
|
11
|
+
* Hardening the fallback is deliberately out of scope — this parser is
|
|
12
|
+
* behaviour-neutral on verdict semantics.
|
|
13
|
+
*/
|
|
14
|
+
export function parseReviewerVerdict(output) {
|
|
15
|
+
const marker = output.match(/^\s*VERDICT:\s*(APPROVED|REQUEST_CHANGES)\s*$/im);
|
|
16
|
+
if (marker) {
|
|
17
|
+
return {
|
|
18
|
+
verdict: marker[1].toUpperCase() === "APPROVED" ? "APPROVED" : "REQUEST_CHANGES",
|
|
19
|
+
viaFallback: false,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const upper = output.toUpperCase();
|
|
23
|
+
const hasRequestChanges = /\bREQUEST_CHANGES\b/.test(upper);
|
|
24
|
+
const isApproved = !hasRequestChanges && /^APPROVED\b/.test(upper);
|
|
25
|
+
return {
|
|
26
|
+
verdict: isApproved ? "APPROVED" : "REQUEST_CHANGES",
|
|
27
|
+
viaFallback: true,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=verdict.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verdict.js","sourceRoot":"","sources":["../../src/core/verdict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH,MAAM,UAAU,oBAAoB,CAAC,MAAc;IACjD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;IAC/E,IAAI,MAAM,EAAE,CAAC;QACX,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,iBAAiB;YAChF,WAAW,EAAE,KAAK;SACnB,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IACnC,MAAM,iBAAiB,GAAG,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5D,MAAM,UAAU,GAAG,CAAC,iBAAiB,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnE,OAAO;QACL,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,iBAAiB;QACpD,WAAW,EAAE,IAAI;KAClB,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `lastlight-workflow-engine` — the port-driven workflow engine extracted from
|
|
3
|
+
* Last Light. Domain-agnostic (zod + node built-ins only); the embedding app
|
|
4
|
+
* supplies the agent/asset/liveness/telemetry seams via {@link EnginePorts}.
|
|
5
|
+
*
|
|
6
|
+
* The `test-support` subpath (`lastlight-workflow-engine/test-support`) ships
|
|
7
|
+
* in-memory fakes; it stays a separate entry so the fakes never enter a
|
|
8
|
+
* consumer's production import graph.
|
|
9
|
+
*/
|
|
10
|
+
export * from "./core/types.js";
|
|
11
|
+
export * from "./core/schema.js";
|
|
12
|
+
export * from "./core/templates.js";
|
|
13
|
+
export * from "./core/dag.js";
|
|
14
|
+
export * from "./core/verdict.js";
|
|
15
|
+
export * from "./core/phase-ref.js";
|
|
16
|
+
export * from "./core/loop-eval.js";
|
|
17
|
+
export * from "./ports/ports.js";
|
|
18
|
+
export { PhaseExecutor, isTerminated, isSoftOutcome, buildPhasePrompt, phaseConfigFor, runPhase, runCommandPhase, } from "./core/phase-executor.js";
|
|
19
|
+
export type { PhaseRunContext } from "./core/phase-executor.js";
|
|
20
|
+
export { runWorkflowCore } from "./core/scheduler.js";
|
|
21
|
+
export type { SchedulerDeps, HostCapabilities } from "./core/scheduler.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `lastlight-workflow-engine` — the port-driven workflow engine extracted from
|
|
3
|
+
* Last Light. Domain-agnostic (zod + node built-ins only); the embedding app
|
|
4
|
+
* supplies the agent/asset/liveness/telemetry seams via {@link EnginePorts}.
|
|
5
|
+
*
|
|
6
|
+
* The `test-support` subpath (`lastlight-workflow-engine/test-support`) ships
|
|
7
|
+
* in-memory fakes; it stays a separate entry so the fakes never enter a
|
|
8
|
+
* consumer's production import graph.
|
|
9
|
+
*/
|
|
10
|
+
// Core leaves + vocabulary (no cross-overlap → safe star exports).
|
|
11
|
+
export * from "./core/types.js";
|
|
12
|
+
export * from "./core/schema.js";
|
|
13
|
+
export * from "./core/templates.js";
|
|
14
|
+
export * from "./core/dag.js";
|
|
15
|
+
export * from "./core/verdict.js";
|
|
16
|
+
export * from "./core/phase-ref.js";
|
|
17
|
+
export * from "./core/loop-eval.js";
|
|
18
|
+
// Ports (interfaces + engine result vocabulary).
|
|
19
|
+
export * from "./ports/ports.js";
|
|
20
|
+
// The executor + scheduler — explicit named exports so their re-exported port
|
|
21
|
+
// types don't collide with the `./ports/ports.js` star export above.
|
|
22
|
+
export { PhaseExecutor, isTerminated, isSoftOutcome, buildPhasePrompt, phaseConfigFor, runPhase, runCommandPhase, } from "./core/phase-executor.js";
|
|
23
|
+
export { runWorkflowCore } from "./core/scheduler.js";
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,mEAAmE;AACnE,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AAEpC,iDAAiD;AACjD,cAAc,kBAAkB,CAAC;AAEjC,8EAA8E;AAC9E,qEAAqE;AACrE,OAAO,EACL,aAAa,EACb,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,QAAQ,EACR,eAAe,GAChB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC"}
|