@aion0/forge 0.9.12 → 0.9.14
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/RELEASE_NOTES.md +8 -6
- package/app/api/prompts/[name]/route.ts +37 -0
- package/app/api/prompts/route.ts +35 -0
- package/app/api/schedules/extract/route.ts +184 -0
- package/app/api/schedules/route.ts +14 -37
- package/components/ScheduleCreateModal.tsx +237 -537
- package/components/ScheduleQuickCreate.tsx +404 -0
- package/components/SchedulesView.tsx +18 -6
- package/lib/chat/agent-loop.ts +24 -0
- package/lib/chat/llm/anthropic.ts +128 -78
- package/lib/chat/llm/openai.ts +75 -180
- package/lib/chat/tool-dispatcher.ts +221 -0
- package/lib/forge-mcp-server.ts +84 -0
- package/lib/init.ts +7 -0
- package/lib/pipeline.ts +19 -0
- package/lib/projects.ts +67 -2
- package/lib/prompts/store.ts +142 -0
- package/lib/prompts/types.ts +53 -0
- package/lib/schedules/scheduler.ts +51 -143
- package/lib/schedules/store.ts +6 -15
- package/lib/schedules/types.ts +10 -14
- package/package.json +1 -1
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
* No sequential / on_failure / dedup / retry knobs. All body-internal.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { randomUUID } from 'node:crypto';
|
|
16
15
|
import { CronExpressionParser } from 'cron-parser';
|
|
17
16
|
import { startPipeline, getWorkflow } from '@/lib/pipeline';
|
|
18
17
|
import {
|
|
@@ -21,9 +20,9 @@ import {
|
|
|
21
20
|
onTaskEvent,
|
|
22
21
|
taskAppendSystemPromptOverrides,
|
|
23
22
|
} from '@/lib/task-manager';
|
|
24
|
-
import { getProjectInfo } from '@/lib/projects';
|
|
25
|
-
import { dispatchTool } from '@/lib/chat/tool-dispatcher';
|
|
23
|
+
import { getProjectInfo, SCRATCH_PROJECT_NAME } from '@/lib/projects';
|
|
26
24
|
import { ensureInstalledInProject } from '@/lib/skills';
|
|
25
|
+
import { getPrompt } from '@/lib/prompts/store';
|
|
27
26
|
import { getDb } from '@/src/core/db/database';
|
|
28
27
|
import { getDbPath } from '@/src/config';
|
|
29
28
|
import {
|
|
@@ -50,7 +49,7 @@ export function startSchedulesScheduler(): void {
|
|
|
50
49
|
if (g[schedulerKey]) return;
|
|
51
50
|
g[schedulerKey] = true;
|
|
52
51
|
ensureSchema();
|
|
53
|
-
|
|
52
|
+
installTaskBackedRunListener();
|
|
54
53
|
console.log('[schedules-scheduler] started');
|
|
55
54
|
void tick();
|
|
56
55
|
setInterval(() => { void tick(); }, TICK_INTERVAL_MS).unref?.();
|
|
@@ -135,9 +134,8 @@ export function advanceSchedule(s: Schedule): void {
|
|
|
135
134
|
* the manual fire API. Returns the target_id (the dispatched body's id).
|
|
136
135
|
*/
|
|
137
136
|
export async function executeSchedule(s: Schedule, trigger: ScheduleRunTrigger): Promise<string> {
|
|
138
|
-
if (s.body_kind === 'pipeline')
|
|
139
|
-
if (s.body_kind === '
|
|
140
|
-
if (s.body_kind === 'connector_tool') return executeConnectorToolBody(s, trigger);
|
|
137
|
+
if (s.body_kind === 'pipeline') return executePipelineBody(s, trigger);
|
|
138
|
+
if (s.body_kind === 'prompt') return executePromptBody(s, trigger);
|
|
141
139
|
throw new Error(`unknown body_kind: ${s.body_kind}`);
|
|
142
140
|
}
|
|
143
141
|
|
|
@@ -202,38 +200,40 @@ async function preinstallSkillsForPipeline(
|
|
|
202
200
|
}
|
|
203
201
|
}
|
|
204
202
|
|
|
205
|
-
// ───
|
|
203
|
+
// ─── prompt body (V3) ────────────────────────────────────
|
|
206
204
|
|
|
207
205
|
/**
|
|
208
|
-
*
|
|
209
|
-
* with the
|
|
210
|
-
*
|
|
211
|
-
* task.
|
|
206
|
+
* Prompt body: load prompts/<body_ref>.yaml, dispatch a one-shot Claude
|
|
207
|
+
* task with the YAML's prompt text + executor.agent + executor.skills.
|
|
208
|
+
* Schedule.input.project chooses where the task runs. The schedule_run
|
|
209
|
+
* carries task.id as target_id; the task event listener (below) settles
|
|
210
|
+
* status + body_output when the task reaches terminal state.
|
|
212
211
|
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* Optional input fields:
|
|
217
|
-
* - user_prompt (string, the actual question to ask Claude)
|
|
218
|
-
* defaults to "Run skill /<body_ref>"
|
|
219
|
-
* - <any other key/value> — passed in the user message verbatim
|
|
220
|
-
* (advanced; phase 2 doesn't do template expansion)
|
|
212
|
+
* The user's prompt text is sent VERBATIM — no LLM rewrites, no template
|
|
213
|
+
* expansion. Schedule.input is currently ignored (project is the only
|
|
214
|
+
* required field); future versions may surface input.* as template vars.
|
|
221
215
|
*/
|
|
222
|
-
async function
|
|
223
|
-
const
|
|
224
|
-
if (!
|
|
225
|
-
|
|
226
|
-
|
|
216
|
+
async function executePromptBody(s: Schedule, trigger: ScheduleRunTrigger): Promise<string> {
|
|
217
|
+
const def = getPrompt(s.body_ref);
|
|
218
|
+
if (!def) throw new Error(`prompt definition not found: prompts/${s.body_ref}.yaml`);
|
|
219
|
+
|
|
220
|
+
// input.project is optional for prompt body — empty / unset falls back
|
|
221
|
+
// to the synthetic 'scratch' project (<dataDir>/scratch) materialized
|
|
222
|
+
// at init time. Schedules created before the optional-project change
|
|
223
|
+
// also work: empty string → scratch.
|
|
224
|
+
const projectName = strField(s.input.project) || SCRATCH_PROJECT_NAME;
|
|
227
225
|
const project = getProjectInfo(projectName);
|
|
228
|
-
if (!project) {
|
|
229
|
-
|
|
230
|
-
|
|
226
|
+
if (!project) throw new Error(`project not found: "${projectName}"`);
|
|
227
|
+
|
|
228
|
+
// Merge prompt-yaml skills with any extra skills the schedule row carries.
|
|
229
|
+
// YAML is the primary source (designed per-prompt); schedule.skills is a
|
|
230
|
+
// legacy/override hook that we keep for parity with pipeline/skill bodies.
|
|
231
|
+
const skills = [
|
|
232
|
+
...(def.executor.skills || []),
|
|
233
|
+
...(s.skills || []),
|
|
234
|
+
].filter((v, i, arr) => v && arr.indexOf(v) === i);
|
|
231
235
|
|
|
232
|
-
|
|
233
|
-
// body_ref was validated as installed at POST time but only on SOME
|
|
234
|
-
// project (or globally) — make sure it's actually on disk for THIS one.
|
|
235
|
-
const skillsToInstall = [s.body_ref, ...(s.skills || [])].filter((v, i, arr) => v && arr.indexOf(v) === i);
|
|
236
|
-
for (const skill of skillsToInstall) {
|
|
236
|
+
for (const skill of skills) {
|
|
237
237
|
try {
|
|
238
238
|
const r = await ensureInstalledInProject(skill, project.path);
|
|
239
239
|
if (!r.installed) console.warn(`[schedules] skill "${skill}" not installable in ${project.path}: ${r.reason}`);
|
|
@@ -242,31 +242,15 @@ async function executeSkillBody(s: Schedule, trigger: ScheduleRunTrigger): Promi
|
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
const userPrompt = strField(s.input.user_prompt) || `Run skill /${s.body_ref}`;
|
|
246
|
-
// Pass other input keys as a JSON block appended after user_prompt
|
|
247
|
-
// so the skill can extract them.
|
|
248
|
-
const extras: Record<string, string> = {};
|
|
249
|
-
for (const [k, v] of Object.entries(s.input || {})) {
|
|
250
|
-
if (k === 'project' || k === 'user_prompt') continue;
|
|
251
|
-
extras[k] = strField(v);
|
|
252
|
-
}
|
|
253
|
-
const extrasBlock = Object.keys(extras).length
|
|
254
|
-
? `\n\n--- Schedule input ---\n${JSON.stringify(extras, null, 2)}\n`
|
|
255
|
-
: '';
|
|
256
|
-
|
|
257
245
|
const task = createTask({
|
|
258
246
|
projectName: project.name,
|
|
259
247
|
projectPath: project.path,
|
|
260
|
-
prompt:
|
|
248
|
+
prompt: def.prompt,
|
|
261
249
|
conversationId: '',
|
|
250
|
+
agent: def.executor.agent || undefined,
|
|
262
251
|
});
|
|
263
252
|
|
|
264
|
-
|
|
265
|
-
// the task up. Same mechanism Pipelines use (lib/pipeline.ts:1448+).
|
|
266
|
-
// body_ref is the primary skill; extra s.skills are merged for tasks
|
|
267
|
-
// that need additional skills alongside (e.g. /forge-mr-triage + /git-helpers).
|
|
268
|
-
const skillSet = [s.body_ref, ...(s.skills || [])].filter((v, i, arr) => v && arr.indexOf(v) === i);
|
|
269
|
-
const append = renderSkillsAppendPrompt(skillSet);
|
|
253
|
+
const append = renderSkillsAppendPrompt(skills);
|
|
270
254
|
if (append) taskAppendSystemPromptOverrides.set(task.id, append);
|
|
271
255
|
|
|
272
256
|
insertScheduleRun({ schedule_id: s.id, target_id: task.id, trigger });
|
|
@@ -274,82 +258,6 @@ async function executeSkillBody(s: Schedule, trigger: ScheduleRunTrigger): Promi
|
|
|
274
258
|
return task.id;
|
|
275
259
|
}
|
|
276
260
|
|
|
277
|
-
// ─── connector_tool body ──────────────────────────────────
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Connector tool body: a single synchronous HTTP-style invocation
|
|
281
|
-
* (run through tool-dispatcher; no Claude, no pipeline). body_ref is
|
|
282
|
-
* "<plugin>.<tool>" (e.g. "mantis.search_bugs"). input is passed
|
|
283
|
-
* verbatim as tool input.
|
|
284
|
-
*
|
|
285
|
-
* Synthetic target_id (uuid prefix "ct_") since there's no backing
|
|
286
|
-
* task/pipeline row. The schedule_run carries the full lifecycle.
|
|
287
|
-
*
|
|
288
|
-
* Dispatch is fire-and-forget — we return the target_id immediately
|
|
289
|
-
* so the caller (tick loop / manual fire API) doesn't block on slow
|
|
290
|
-
* connector tools. The async helper settles body_output + status
|
|
291
|
-
* when the call resolves.
|
|
292
|
-
*/
|
|
293
|
-
function executeConnectorToolBody(s: Schedule, trigger: ScheduleRunTrigger): string {
|
|
294
|
-
const targetId = `ct_${randomUUID().slice(0, 12).replace(/-/g, '')}`;
|
|
295
|
-
insertScheduleRun({ schedule_id: s.id, target_id: targetId, trigger });
|
|
296
|
-
setLastRunAt(s.id, toSqlIso(new Date()));
|
|
297
|
-
void invokeConnectorTool(s, targetId).catch((err) => {
|
|
298
|
-
console.error(`[schedules] connector_tool body for ${s.id} crashed`, err);
|
|
299
|
-
updateScheduleRunStatus({
|
|
300
|
-
target_id: targetId,
|
|
301
|
-
status: 'failed',
|
|
302
|
-
error: (err as Error)?.message || String(err),
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
return targetId;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
async function invokeConnectorTool(s: Schedule, targetId: string): Promise<void> {
|
|
309
|
-
// body_ref is "<plugin_id>.<tool_name>". Some tools could contain
|
|
310
|
-
// dots, but our internal naming is always plugin.tool and the
|
|
311
|
-
// dispatcher splits on the FIRST dot.
|
|
312
|
-
const dot = s.body_ref.indexOf('.');
|
|
313
|
-
if (dot <= 0 || dot === s.body_ref.length - 1) {
|
|
314
|
-
updateScheduleRunStatus({
|
|
315
|
-
target_id: targetId,
|
|
316
|
-
status: 'failed',
|
|
317
|
-
error: `invalid body_ref: expected "<plugin>.<tool>", got "${s.body_ref}"`,
|
|
318
|
-
});
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
const fullName = s.body_ref;
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
const result = await dispatchTool({
|
|
325
|
-
id: `schedule-${targetId}`,
|
|
326
|
-
name: fullName,
|
|
327
|
-
input: s.input || {},
|
|
328
|
-
});
|
|
329
|
-
const output = typeof result.content === 'string' ? result.content : JSON.stringify(result.content ?? '');
|
|
330
|
-
setScheduleRunBodyOutputByTarget(targetId, output);
|
|
331
|
-
if (result.is_error) {
|
|
332
|
-
updateScheduleRunStatus({
|
|
333
|
-
target_id: targetId,
|
|
334
|
-
status: 'failed',
|
|
335
|
-
error: output.slice(0, 500) || 'connector tool returned is_error',
|
|
336
|
-
});
|
|
337
|
-
} else {
|
|
338
|
-
updateScheduleRunStatus({ target_id: targetId, status: 'done' });
|
|
339
|
-
}
|
|
340
|
-
} catch (err) {
|
|
341
|
-
updateScheduleRunStatus({
|
|
342
|
-
target_id: targetId,
|
|
343
|
-
status: 'failed',
|
|
344
|
-
error: (err as Error)?.message || String(err),
|
|
345
|
-
});
|
|
346
|
-
} finally {
|
|
347
|
-
void runActionForTarget(targetId).catch((e) => {
|
|
348
|
-
console.error(`[schedules-scheduler] action for connector_tool target ${targetId} crashed`, e);
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
261
|
function strField(v: unknown): string {
|
|
354
262
|
if (v == null) return '';
|
|
355
263
|
return typeof v === 'string' ? v : (typeof v === 'object' ? JSON.stringify(v) : String(v));
|
|
@@ -367,31 +275,31 @@ function renderSkillsAppendPrompt(skills: string[] | undefined): string {
|
|
|
367
275
|
].join('\n');
|
|
368
276
|
}
|
|
369
277
|
|
|
370
|
-
// ───
|
|
278
|
+
// ─── Task-backed run settler ──────────────────────────────
|
|
371
279
|
|
|
372
280
|
/**
|
|
373
281
|
* Global task event listener that settles schedule_runs whose target_id
|
|
374
|
-
* is a Claude task (
|
|
375
|
-
*
|
|
282
|
+
* is a Claude task (body_kind='prompt' dispatches one-shot tasks and stores
|
|
283
|
+
* task.id as target_id).
|
|
284
|
+
*
|
|
285
|
+
* We only act on terminal status events; reads getTask to capture
|
|
286
|
+
* resultSummary as body_output.
|
|
376
287
|
*
|
|
377
|
-
* Idempotent: updateScheduleRunStatus only touches rows with status='started'
|
|
378
|
-
* so a duplicate event is a no-op.
|
|
288
|
+
* Idempotent: updateScheduleRunStatus only touches rows with status='started'
|
|
289
|
+
* AND target_id=taskId, so a duplicate event (or a non-schedule task) is a no-op.
|
|
379
290
|
*/
|
|
380
|
-
let
|
|
381
|
-
function
|
|
382
|
-
if (
|
|
383
|
-
|
|
291
|
+
let taskBackedListenerInstalled = false;
|
|
292
|
+
function installTaskBackedRunListener(): void {
|
|
293
|
+
if (taskBackedListenerInstalled) return;
|
|
294
|
+
taskBackedListenerInstalled = true;
|
|
384
295
|
onTaskEvent((taskId, event, data) => {
|
|
385
296
|
if (event !== 'status') return;
|
|
386
297
|
if (data !== 'done' && data !== 'failed' && data !== 'cancelled') return;
|
|
387
|
-
|
|
298
|
+
settleTaskBackedRun(taskId, data as ScheduleRunStatus);
|
|
388
299
|
});
|
|
389
300
|
}
|
|
390
301
|
|
|
391
|
-
function
|
|
392
|
-
// We don't know body kind without a join; just attempt the update.
|
|
393
|
-
// updateScheduleRunStatus only touches rows with status='started'
|
|
394
|
-
// AND target_id=taskId, so it's a no-op for non-schedule tasks.
|
|
302
|
+
function settleTaskBackedRun(taskId: string, status: ScheduleRunStatus): void {
|
|
395
303
|
const t = getTask(taskId);
|
|
396
304
|
if (!t) {
|
|
397
305
|
updateScheduleRunStatus({ target_id: taskId, status, error: 'task row gone' });
|
|
@@ -404,7 +312,7 @@ function settleSkillRunIfMatch(taskId: string, status: ScheduleRunStatus): void
|
|
|
404
312
|
error: status === 'failed' ? (t.error || null) : null,
|
|
405
313
|
});
|
|
406
314
|
void runActionForTarget(taskId).catch((e) => {
|
|
407
|
-
console.error(`[schedules-scheduler] action for
|
|
315
|
+
console.error(`[schedules-scheduler] action for task target ${taskId} crashed`, e);
|
|
408
316
|
});
|
|
409
317
|
}
|
|
410
318
|
|
package/lib/schedules/store.ts
CHANGED
|
@@ -485,10 +485,9 @@ export function setScheduleRunAction(args: {
|
|
|
485
485
|
|
|
486
486
|
// ─── Zombie reconciliation ────────────────────────────────
|
|
487
487
|
|
|
488
|
-
/**
|
|
489
|
-
*
|
|
490
|
-
*
|
|
491
|
-
* body_kind=skill (read tasks table). connector_tool lands in phase 3. */
|
|
488
|
+
/** Any started+30s+ schedule_run whose target is gone OR in terminal
|
|
489
|
+
* state → fix the DB row. Handles body_kind=pipeline (read JSON file)
|
|
490
|
+
* and body_kind=prompt (read tasks table). */
|
|
492
491
|
export function reconcileStaleScheduleRuns(): void {
|
|
493
492
|
try {
|
|
494
493
|
const stale = db().prepare(`
|
|
@@ -513,16 +512,8 @@ export function reconcileStaleScheduleRuns(): void {
|
|
|
513
512
|
let settled = false;
|
|
514
513
|
if (row.body_kind === 'pipeline') {
|
|
515
514
|
settled = reconcilePipelineRun(row.id, row.target_id, pipelineDir);
|
|
516
|
-
} else if (row.body_kind === '
|
|
517
|
-
settled =
|
|
518
|
-
} else if (row.body_kind === 'connector_tool') {
|
|
519
|
-
// connector_tool dispatch is in-memory async. If it's still
|
|
520
|
-
// 'started' 30s later it means forge crashed mid-call, or the
|
|
521
|
-
// tool itself is hanging. Either way: fail the run so future
|
|
522
|
-
// ticks aren't blocked by isScheduleBusy.
|
|
523
|
-
markRunStatus(row.id, 'failed');
|
|
524
|
-
console.warn(`[schedules] reconciled stale connector_tool schedule_run ${row.id} → failed (no in-memory progress after 30s)`);
|
|
525
|
-
settled = true;
|
|
515
|
+
} else if (row.body_kind === 'prompt') {
|
|
516
|
+
settled = reconcileTaskBackedRun(row.id, row.target_id);
|
|
526
517
|
} else {
|
|
527
518
|
markRunStatus(row.id, 'failed');
|
|
528
519
|
console.warn(`[schedules] reconciled schedule_run ${row.id} → failed (unsupported body_kind '${row.body_kind}')`);
|
|
@@ -590,7 +581,7 @@ function reconcilePipelineRun(
|
|
|
590
581
|
|
|
591
582
|
/** Same contract as reconcilePipelineRun: true if settled, false if
|
|
592
583
|
* task still in flight (caller must NOT fire action yet). */
|
|
593
|
-
function
|
|
584
|
+
function reconcileTaskBackedRun(runId: string, taskId: string): boolean {
|
|
594
585
|
try {
|
|
595
586
|
const t = db().prepare(
|
|
596
587
|
`SELECT status, result_summary FROM tasks WHERE id = ?`,
|
package/lib/schedules/types.ts
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Forge Schedules — types (
|
|
2
|
+
* Forge Schedules — types (V3)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* connector_tool (skill + connector_tool land in later phases).
|
|
7
|
-
* action is one of none / chat / email / telegram (chat/email/telegram
|
|
8
|
-
* land in later phases).
|
|
9
|
-
*
|
|
10
|
-
* Phase 1 only renames + extends fields; runtime behavior unchanged
|
|
11
|
-
* (body_kind defaults to 'pipeline', action_kind defaults to 'none').
|
|
4
|
+
* trigger + body + action. body is one of pipeline / prompt.
|
|
5
|
+
* action is one of none / chat / email / telegram.
|
|
12
6
|
*/
|
|
13
7
|
|
|
14
8
|
export type ScheduleKind = 'period' | 'once' | 'cron' | 'manual';
|
|
15
9
|
export type ScheduleRunStatus = 'started' | 'done' | 'failed' | 'cancelled';
|
|
16
10
|
export type ScheduleRunTrigger = 'schedule' | 'manual';
|
|
17
11
|
|
|
18
|
-
export type ScheduleBodyKind = 'pipeline' | '
|
|
12
|
+
export type ScheduleBodyKind = 'pipeline' | 'prompt';
|
|
19
13
|
export type ScheduleActionKind = 'none' | 'chat' | 'email' | 'telegram';
|
|
20
14
|
export type ScheduleActionStatus = 'pending' | 'done' | 'failed' | 'skipped';
|
|
21
15
|
|
|
@@ -27,15 +21,17 @@ export interface Schedule {
|
|
|
27
21
|
|
|
28
22
|
// Body — what to run when this schedule fires.
|
|
29
23
|
body_kind: ScheduleBodyKind;
|
|
30
|
-
/** Reference depends on body_kind:
|
|
24
|
+
/** Reference depends on body_kind:
|
|
25
|
+
* pipeline → pipeline name (flows/<name>.yaml)
|
|
26
|
+
* prompt → prompt name (prompts/<name>.yaml) */
|
|
31
27
|
body_ref: string;
|
|
32
28
|
/** Input params; shape depends on body_kind. */
|
|
33
29
|
input: Record<string, unknown>;
|
|
34
30
|
|
|
35
31
|
/** Extra Forge skills attached to the dispatched body. For body=pipeline
|
|
36
32
|
* these are forwarded to startPipeline({skills}) → every task gets the
|
|
37
|
-
* --append-system-prompt block. For body=
|
|
38
|
-
*
|
|
33
|
+
* --append-system-prompt block. For body=prompt they're merged with the
|
|
34
|
+
* prompt yaml's executor.skills and installed before dispatch. */
|
|
39
35
|
skills: string[];
|
|
40
36
|
|
|
41
37
|
// Action — what to do with body's output. action_config JSON shape
|
|
@@ -72,7 +68,7 @@ export interface ScheduleRun {
|
|
|
72
68
|
id: string;
|
|
73
69
|
schedule_id: string;
|
|
74
70
|
/** ID of the dispatched body — pipeline_id for body=pipeline,
|
|
75
|
-
* task_id for body=
|
|
71
|
+
* task_id for body=prompt. */
|
|
76
72
|
target_id: string;
|
|
77
73
|
trigger: ScheduleRunTrigger;
|
|
78
74
|
status: ScheduleRunStatus;
|
package/package.json
CHANGED