pi-crew 0.9.68 → 0.10.2
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/CHANGELOG.md +222 -0
- package/NOTICE.md +21 -0
- package/README.md +44 -2
- package/agents/analyst.md +1 -1
- package/agents/cold-verifier.md +3 -1
- package/agents/critic.md +1 -1
- package/agents/executor.md +1 -1
- package/agents/explorer.md +1 -1
- package/agents/planner.md +1 -1
- package/agents/reviewer.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/test-engineer.md +1 -1
- package/agents/verifier.md +1 -1
- package/agents/writer.md +1 -1
- package/dist/index.mjs +68113 -60774
- package/docs/README.md +2 -0
- package/docs/actions-reference.md +31 -0
- package/docs/commands-reference.md +17 -6
- package/docs/resource-formats.md +13 -0
- package/package.json +4 -2
- package/schema.json +503 -91
- package/scripts/resource-sampler.mjs +36 -2
- package/skills/requirements-to-task-packet/SKILL.md +26 -0
- package/skills/widget-rendering/SKILL.md +7 -7
- package/src/agents/agent-config.ts +2 -1
- package/src/agents/discover-agents.ts +23 -14
- package/src/config/config-merge.ts +183 -0
- package/src/config/config-validation.ts +687 -0
- package/src/config/config.ts +22 -864
- package/src/config/defaults.ts +43 -2
- package/src/config/drift-detector.ts +1 -1
- package/src/config/env-vars.ts +691 -0
- package/src/config/role-tools.ts +11 -9
- package/src/config/sanitize-project-config.ts +172 -0
- package/src/config/types.ts +49 -1
- package/src/extension/async-notifier.ts +25 -2
- package/src/extension/crew-cleanup.ts +13 -0
- package/src/extension/crew-vibes/config.ts +2 -1
- package/src/extension/crew-vibes/footer.ts +19 -0
- package/src/extension/crew-vibes/index.ts +11 -1
- package/src/extension/plan-orchestrate.ts +132 -0
- package/src/extension/register.ts +8 -0
- package/src/extension/registration/command-registration.ts +1 -0
- package/src/extension/registration/commands/dashboard.ts +158 -0
- package/src/extension/registration/commands/index.ts +35 -0
- package/src/extension/registration/commands/manage.ts +303 -0
- package/src/extension/registration/commands/run.ts +228 -0
- package/src/extension/registration/commands/shared.ts +639 -0
- package/src/extension/registration/commands/status.ts +60 -0
- package/src/extension/registration/commands.ts +13 -1224
- package/src/extension/registration/foreground-run-controller.ts +10 -2
- package/src/extension/registration/lifecycle-handlers.ts +178 -17
- package/src/extension/registration/runtime-cleanup.ts +23 -5
- package/src/extension/registration/subagent-tools.ts +218 -9
- package/src/extension/registration/team-tool.ts +5 -1
- package/src/extension/registration/ui.ts +5 -4
- package/src/extension/rpc-hmac.ts +5 -3
- package/src/extension/team-tool/api/heartbeat.ts +47 -10
- package/src/extension/team-tool/api/plan-approval.ts +9 -0
- package/src/extension/team-tool/api/task-claims.ts +109 -40
- package/src/extension/team-tool/cancel.ts +84 -50
- package/src/extension/team-tool/dispatch/index.ts +1 -0
- package/src/extension/team-tool/dispatch/run.ts +4 -1
- package/src/extension/team-tool/doctor.ts +103 -1
- package/src/extension/team-tool/orchestrate.ts +66 -1
- package/src/extension/team-tool/plans.ts +192 -0
- package/src/extension/team-tool/respond.ts +197 -65
- package/src/extension/team-tool/run-deadline.ts +35 -3
- package/src/extension/team-tool/run-intent.ts +63 -0
- package/src/extension/team-tool/run.ts +74 -20
- package/src/extension/team-tool/status.ts +84 -26
- package/src/extension/team-tool.ts +11 -2
- package/src/hooks/registry.ts +1 -6
- package/src/i18n.ts +9 -0
- package/src/prompt/prompt-runtime.ts +521 -2
- package/src/prompt/worker-events-channel.ts +173 -0
- package/src/runtime/README.md +8 -8
- package/src/runtime/async-runner.ts +7 -3
- package/src/runtime/background-runner.ts +42 -14
- package/src/runtime/broker/broker-issuer.ts +9 -2
- package/src/runtime/broker/crew-broker-tokens.ts +43 -6
- package/src/runtime/broker/crew-broker.ts +838 -10
- package/src/runtime/broker/wait-status-cache.ts +157 -0
- package/src/runtime/budget-enforcement.ts +281 -0
- package/src/runtime/child-pi/child-pi-constants.ts +8 -0
- package/src/runtime/child-pi/child-pi-spawn.ts +60 -14
- package/src/runtime/child-pi/child-pi-streams.ts +21 -1
- package/src/runtime/child-pi/child-pi-timers.ts +324 -0
- package/src/runtime/child-pi/child-pi.ts +97 -201
- package/src/runtime/child-pi/mock-fixtures.ts +16 -2
- package/src/runtime/crew-agent-records.ts +259 -14
- package/src/runtime/delegate-spawn.ts +148 -0
- package/src/runtime/detached-run-results.ts +90 -0
- package/src/runtime/deterministic-ast.ts +2 -1
- package/src/runtime/dispatch-batch.ts +945 -0
- package/src/runtime/finalize-run.ts +557 -0
- package/src/runtime/goal-workflow/adaptive-plan.ts +116 -15
- package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +8 -3
- package/src/runtime/goal-workflow/goal-state-store.ts +1 -1
- package/src/runtime/group-join.ts +11 -125
- package/src/runtime/live-session/live-session-runtime.ts +26 -1
- package/src/runtime/merge-gate.ts +32 -10
- package/src/runtime/merge-loop.ts +130 -0
- package/src/runtime/model/model-budget-summary.ts +53 -0
- package/src/runtime/model/model-fallback.ts +36 -2
- package/src/runtime/model/pi-args.ts +10 -0
- package/src/runtime/model/provider-extensions.ts +10 -0
- package/src/runtime/orphan-worker-registry.ts +1 -1
- package/src/runtime/output/output-validator.ts +45 -0
- package/src/runtime/parent-guard.ts +3 -1
- package/src/runtime/peer-dep.ts +2 -1
- package/src/runtime/per-write-validator.ts +0 -5
- package/src/runtime/pi-spawn.ts +61 -15
- package/src/runtime/plan-approval.ts +125 -0
- package/src/runtime/plan-replan.ts +151 -0
- package/src/runtime/process-status.ts +16 -1
- package/src/runtime/recovery/checkpoint.ts +0 -18
- package/src/runtime/recovery/crash-recovery.ts +111 -46
- package/src/runtime/run-tracker.ts +77 -10
- package/src/runtime/scheduler-context.ts +98 -0
- package/src/runtime/scheduling/coalesce-tasks.ts +5 -0
- package/src/runtime/scheduling/global-worker-cap.ts +2 -1
- package/src/runtime/scheduling/nested-slots.ts +70 -0
- package/src/runtime/scheduling/run-coalesced-task-group.ts +64 -13
- package/src/runtime/scheduling/task-graph-scheduler.ts +0 -10
- package/src/runtime/settings-store.ts +219 -0
- package/src/runtime/spawn-policy.ts +217 -0
- package/src/runtime/stale-reconciler.ts +87 -6
- package/src/runtime/subagent-manager.ts +25 -1
- package/src/runtime/task-output-context.ts +230 -9
- package/src/runtime/task-packet.ts +23 -1
- package/src/runtime/task-runner/child-executor.ts +106 -7
- package/src/runtime/task-runner/post-execution.ts +125 -1
- package/src/runtime/task-runner/pre-execution.ts +39 -1
- package/src/runtime/task-runner/prompt-builder.ts +51 -1
- package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
- package/src/runtime/task-runner/spec-evidence.ts +403 -0
- package/src/runtime/task-runner/state-helpers.ts +26 -24
- package/src/runtime/task-runner.ts +11 -0
- package/src/runtime/team-runner.ts +132 -1673
- package/src/runtime/verification/spec-sandbox.ts +255 -0
- package/src/runtime/verification/verification-gates.ts +3 -2
- package/src/runtime/verification/verification-worktree.ts +2 -1
- package/src/runtime/workflow-phase-advance.ts +100 -0
- package/src/runtime/workspace-tree.ts +9 -0
- package/src/schema/config-schema.ts +66 -26
- package/src/schema/sensitive-config-paths.ts +64 -0
- package/src/schema/team-tool-schema.ts +13 -3
- package/src/state/README.md +4 -10
- package/src/state/atomic-write.ts +20 -3
- package/src/state/contracts.ts +38 -0
- package/src/state/coordination/mailbox.ts +12 -2
- package/src/state/event-log/cursor.ts +223 -0
- package/src/state/event-log/event-log-rotation.ts +12 -4
- package/src/state/event-log/event-log.ts +152 -369
- package/src/state/event-log/sequence-cache.ts +373 -0
- package/src/state/event-log/worker-atomic-writer.ts +2 -1
- package/src/state/stores/active-run-registry.ts +3 -2
- package/src/state/stores/manifest-io.ts +237 -0
- package/src/state/stores/ownership-map.ts +162 -0
- package/src/state/stores/plan-store.ts +241 -0
- package/src/state/stores/run-cache.ts +0 -90
- package/src/state/stores/spec-store.ts +189 -0
- package/src/state/stores/state-store.ts +139 -232
- package/src/state/types.ts +199 -0
- package/src/ui/dashboard-panes/plan-pane.ts +136 -0
- package/src/ui/dashboard-panes/progress-pane.ts +6 -0
- package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
- package/src/ui/dock-footer.ts +49 -0
- package/src/ui/heartbeat-aggregator.ts +9 -1
- package/src/ui/inline-panel/agent-pane.ts +375 -0
- package/src/ui/inline-panel/agent-transcript.ts +338 -0
- package/src/ui/inline-panel/agent-view-overlay.ts +225 -0
- package/src/ui/inline-panel/crew-editor.ts +192 -0
- package/src/ui/inline-panel/index.ts +290 -0
- package/src/ui/inline-panel/panel-rows.ts +37 -0
- package/src/ui/inline-panel/panel-selection.ts +157 -0
- package/src/ui/inline-panel/panel-store.ts +111 -0
- package/src/ui/inline-panel/view-session-store.ts +36 -0
- package/src/ui/keybinding-map.ts +54 -13
- package/src/ui/pi-ui-compat.ts +9 -0
- package/src/ui/powerbar-publisher.ts +52 -1
- package/src/ui/run-dashboard.ts +31 -5
- package/src/ui/run-snapshot-cache.ts +57 -30
- package/src/ui/snapshot-types.ts +6 -1
- package/src/ui/widget/index.ts +176 -22
- package/src/ui/widget/task-list.ts +198 -0
- package/src/ui/widget/widget-formatters.ts +240 -4
- package/src/ui/widget/widget-renderer.ts +243 -38
- package/src/ui/widget/widget-types.ts +11 -0
- package/src/utils/child-process-shield.ts +106 -0
- package/src/utils/file-coalescer.ts +0 -4
- package/src/utils/fs-errno.ts +66 -0
- package/src/utils/fs-watch.ts +1 -1
- package/src/utils/internal-error.ts +3 -1
- package/src/utils/paths.ts +11 -3
- package/src/utils/redaction.ts +7 -0
- package/src/utils/safe-abort.ts +45 -0
- package/src/utils/task-name-generator.ts +1 -8
- package/src/workflows/discover-workflows.ts +20 -2
- package/src/workflows/validate-workflow.ts +7 -1
- package/src/workflows/workflow-config.ts +17 -0
- package/src/workflows/workflow-serializer.ts +3 -0
- package/src/worktree/worktree-manager.ts +22 -0
- package/workflows/default.workflow.md +36 -26
- package/workflows/strict-fast-fix.workflow.md +26 -0
- package/src/agents/agent-search.ts +0 -98
- package/src/benchmark/benchmark-runner.ts +0 -313
- package/src/benchmark/feedback-loop.ts +0 -73
- package/src/config/resilient-parser.ts +0 -117
- package/src/extension/crew-vibes/cat-frames.ts +0 -18
- package/src/extension/result-watcher.ts +0 -139
- package/src/observability/exporters/prometheus-exporter.ts +0 -54
- package/src/observability/metric-retention.ts +0 -64
- package/src/runtime/compaction/compaction-summary.ts +0 -278
- package/src/runtime/errors/crew-errors.ts +0 -162
- package/src/runtime/live-session/intercom-bridge.ts +0 -187
- package/src/runtime/loop-gates.ts +0 -128
- package/src/runtime/metric-parser.ts +0 -36
- package/src/runtime/output/stream-preview.ts +0 -184
- package/src/runtime/output/tool-progress.ts +0 -278
- package/src/runtime/phase-tracker.ts +0 -385
- package/src/runtime/pipeline-runner.ts +0 -523
- package/src/runtime/process/process-lifecycle.ts +0 -491
- package/src/runtime/recovery/retry-runner.ts +0 -330
- package/src/runtime/run-drift.ts +0 -219
- package/src/runtime/task-quality.ts +0 -199
- package/src/runtime/task-runner/run-projection.ts +0 -128
- package/src/runtime/verification/post-checks.ts +0 -142
- package/src/state/coordination/schedule.ts +0 -166
- package/src/state/event-log/jsonl-writer.ts +0 -115
- package/src/state/hook-instinct-bridge.ts +0 -94
- package/src/state/hook-integrations.ts +0 -51
- package/src/state/session-state-map.ts +0 -51
- package/src/state/stores/blob-store.ts +0 -308
- package/src/state/stores/instinct-store.ts +0 -275
- package/src/state/stores/observation-store.ts +0 -176
- package/src/state/tiered-eval.ts +0 -480
- package/src/state/types-eval.ts +0 -58
- package/src/tools/safe-bash-extension.ts +0 -54
- package/src/tools/safe-bash.ts +0 -505
- package/src/ui/agent-management-overlay.ts +0 -160
- package/src/ui/crew-footer.ts +0 -102
- package/src/ui/crew-select-list.ts +0 -114
- package/src/ui/dashboard-panes/capability-pane.ts +0 -77
- package/src/ui/transcript-entries.ts +0 -256
- package/src/utils/conflict-detect.ts +0 -721
- package/src/utils/fingerprint.ts +0 -180
- package/src/utils/gh-protocol.ts +0 -556
- package/src/utils/project-detector.ts +0 -160
- package/src/utils/sse-parser.ts +0 -131
- package/src/workflows/cost-estimator.ts +0 -34
- package/src/workflows/intermediate-store.ts +0 -166
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Task quality scoring — simple additive heuristic for evaluating task
|
|
3
|
-
* completion quality based on diagnostics, metrics, artifacts, and duration.
|
|
4
|
-
*
|
|
5
|
-
* Distilled from pi-autoresearch's quality scoring pattern.
|
|
6
|
-
*/
|
|
7
|
-
import * as fs from "node:fs";
|
|
8
|
-
import * as path from "node:path";
|
|
9
|
-
import type { TeamTaskState } from "../state/types.ts";
|
|
10
|
-
|
|
11
|
-
/** Letter grade for task quality. */
|
|
12
|
-
export type QualityGrade = "A" | "B" | "C" | "D";
|
|
13
|
-
|
|
14
|
-
/** Breakdown of individual quality criteria. */
|
|
15
|
-
export interface QualityBreakdown {
|
|
16
|
-
/** Task has a non-empty diagnostics object. */
|
|
17
|
-
hasDiagnostics: boolean;
|
|
18
|
-
/** Task has a non-empty metrics object. */
|
|
19
|
-
hasMetrics: boolean;
|
|
20
|
-
/** Task produced files in the artifacts directory. */
|
|
21
|
-
producedArtifacts: boolean;
|
|
22
|
-
/** Task has a non-empty result/description. */
|
|
23
|
-
hasDescription: boolean;
|
|
24
|
-
/** Task duration is reasonable (> 0 and < 1 hour). */
|
|
25
|
-
durationReasonable: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Scored quality result for a task. */
|
|
29
|
-
export interface TaskQualityScore {
|
|
30
|
-
/** Numeric score (0–5). */
|
|
31
|
-
score: number;
|
|
32
|
-
/** Individual criterion breakdown. */
|
|
33
|
-
breakdown: QualityBreakdown;
|
|
34
|
-
/** Letter grade based on score thresholds. */
|
|
35
|
-
grade: QualityGrade;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** One hour in milliseconds. */
|
|
39
|
-
const ONE_HOUR_MS = 3_600_000;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Determine the letter grade for a given numeric score.
|
|
43
|
-
*
|
|
44
|
-
* A: 4–5, B: 3, C: 2, D: 0–1
|
|
45
|
-
*/
|
|
46
|
-
function scoreToGrade(score: number): QualityGrade {
|
|
47
|
-
if (score >= 4) return "A";
|
|
48
|
-
if (score === 3) return "B";
|
|
49
|
-
if (score === 2) return "C";
|
|
50
|
-
return "D";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Check whether the artifacts directory contains files for the given task.
|
|
55
|
-
*
|
|
56
|
-
* Looks for a subdirectory named after the task ID, or files containing
|
|
57
|
-
* the task ID prefix in the artifacts directory.
|
|
58
|
-
*/
|
|
59
|
-
function hasTaskArtifacts(taskId: string, artifactsDir: string): boolean {
|
|
60
|
-
try {
|
|
61
|
-
if (!fs.existsSync(artifactsDir)) return false;
|
|
62
|
-
|
|
63
|
-
// Check for a task-specific subdirectory
|
|
64
|
-
const taskDir = path.join(artifactsDir, taskId);
|
|
65
|
-
if (fs.existsSync(taskDir)) {
|
|
66
|
-
const stat = fs.statSync(taskDir);
|
|
67
|
-
if (stat.isDirectory()) {
|
|
68
|
-
const entries = fs.readdirSync(taskDir);
|
|
69
|
-
return entries.length > 0;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Check for files containing the task ID prefix
|
|
74
|
-
const entries = fs.readdirSync(artifactsDir);
|
|
75
|
-
const safePrefix = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
76
|
-
return entries.some((entry) => entry.includes(safePrefix));
|
|
77
|
-
} catch {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Check if a task result string is a non-empty description.
|
|
84
|
-
*
|
|
85
|
-
* A result is considered descriptive if any of these sources have non-empty content:
|
|
86
|
-
* - task.resultArtifact exists with a path
|
|
87
|
-
* - task.error is a non-empty string (workers often set this with result info)
|
|
88
|
-
* - task.verification.satisfied is true
|
|
89
|
-
* - task.diagnostics contains a 'result' string
|
|
90
|
-
*/
|
|
91
|
-
function isResultDescriptive(task: TeamTaskState): boolean {
|
|
92
|
-
// Check resultArtifact — presence of a result artifact indicates output was produced
|
|
93
|
-
if (task.resultArtifact?.path) return true;
|
|
94
|
-
|
|
95
|
-
// Check error field — workers often put result info here
|
|
96
|
-
if (typeof task.error === "string" && task.error.trim().length > 0) return true;
|
|
97
|
-
|
|
98
|
-
// Check verification — satisfied verification indicates meaningful output
|
|
99
|
-
if (task.verification?.satisfied) return true;
|
|
100
|
-
|
|
101
|
-
// Check diagnostics for an explicit result string
|
|
102
|
-
if (
|
|
103
|
-
task.diagnostics &&
|
|
104
|
-
typeof task.diagnostics === "object" &&
|
|
105
|
-
typeof task.diagnostics.result === "string" &&
|
|
106
|
-
(task.diagnostics.result as string).trim().length > 0
|
|
107
|
-
)
|
|
108
|
-
return true;
|
|
109
|
-
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Check if the task duration is reasonable (started, finished, > 0, < 1 hour).
|
|
115
|
-
*/
|
|
116
|
-
function isDurationReasonable(task: TeamTaskState): boolean {
|
|
117
|
-
if (!task.startedAt || !task.finishedAt) return false;
|
|
118
|
-
|
|
119
|
-
const started = new Date(task.startedAt).getTime();
|
|
120
|
-
const finished = new Date(task.finishedAt).getTime();
|
|
121
|
-
|
|
122
|
-
if (Number.isNaN(started) || Number.isNaN(finished)) return false;
|
|
123
|
-
|
|
124
|
-
const duration = finished - started;
|
|
125
|
-
return duration > 0 && duration < ONE_HOUR_MS;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Compute the quality score for a completed task.
|
|
130
|
-
*
|
|
131
|
-
* Uses simple additive scoring across 5 criteria:
|
|
132
|
-
* - hasDiagnostics: +1 if task.diagnostics exists and has keys
|
|
133
|
-
* - hasMetrics: +1 if task.metrics exists and has keys
|
|
134
|
-
* - producedArtifacts: +1 if artifactsDir has files for this task
|
|
135
|
-
* - hasDescription: +1 if task has a non-empty result/description
|
|
136
|
-
* - durationReasonable: +1 if task has both startedAt and finishedAt, duration > 0 and < 1 hour
|
|
137
|
-
*
|
|
138
|
-
* @param task - The task state to evaluate
|
|
139
|
-
* @param artifactsDir - Optional path to the run artifacts directory
|
|
140
|
-
* @returns TaskQualityScore with numeric score, breakdown, and letter grade
|
|
141
|
-
*/
|
|
142
|
-
export function computeTaskQuality(task: TeamTaskState, artifactsDir?: string): TaskQualityScore {
|
|
143
|
-
const hasDiagnostics =
|
|
144
|
-
task.diagnostics !== undefined && typeof task.diagnostics === "object" && Object.keys(task.diagnostics).length > 0;
|
|
145
|
-
|
|
146
|
-
const hasMetrics = task.metrics !== undefined && typeof task.metrics === "object" && Object.keys(task.metrics).length > 0;
|
|
147
|
-
|
|
148
|
-
const producedArtifacts = artifactsDir !== undefined && hasTaskArtifacts(task.id, artifactsDir);
|
|
149
|
-
|
|
150
|
-
const hasDescription = isResultDescriptive(task);
|
|
151
|
-
|
|
152
|
-
const durationReasonable = isDurationReasonable(task);
|
|
153
|
-
|
|
154
|
-
const breakdown: QualityBreakdown = {
|
|
155
|
-
hasDiagnostics,
|
|
156
|
-
hasMetrics,
|
|
157
|
-
producedArtifacts,
|
|
158
|
-
hasDescription,
|
|
159
|
-
durationReasonable,
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const score =
|
|
163
|
-
(hasDiagnostics ? 1 : 0) +
|
|
164
|
-
(hasMetrics ? 1 : 0) +
|
|
165
|
-
(producedArtifacts ? 1 : 0) +
|
|
166
|
-
(hasDescription ? 1 : 0) +
|
|
167
|
-
(durationReasonable ? 1 : 0);
|
|
168
|
-
|
|
169
|
-
return {
|
|
170
|
-
score,
|
|
171
|
-
breakdown,
|
|
172
|
-
grade: scoreToGrade(score),
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/** Human-readable labels for each quality criterion. */
|
|
177
|
-
const CRITERION_LABELS: Record<keyof QualityBreakdown, string> = {
|
|
178
|
-
hasDiagnostics: "diagnostics",
|
|
179
|
-
hasMetrics: "metrics",
|
|
180
|
-
producedArtifacts: "artifacts",
|
|
181
|
-
hasDescription: "description",
|
|
182
|
-
durationReasonable: "duration",
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Format a quality score as a human-readable one-line string.
|
|
187
|
-
*
|
|
188
|
-
* Format: "Quality: B (3/5: diagnostics, metrics, description)"
|
|
189
|
-
*
|
|
190
|
-
* @param score - The quality score to format
|
|
191
|
-
* @returns Formatted string
|
|
192
|
-
*/
|
|
193
|
-
export function formatQualityScore(score: TaskQualityScore): string {
|
|
194
|
-
const metCriteria = Object.entries(score.breakdown)
|
|
195
|
-
.filter(([, met]) => met)
|
|
196
|
-
.map(([key]) => CRITERION_LABELS[key as keyof QualityBreakdown]);
|
|
197
|
-
|
|
198
|
-
return `Quality: ${score.grade} (${score.score}/5${metCriteria.length > 0 ? `: ${metCriteria.join(", ")}` : ""})`;
|
|
199
|
-
}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import type { MailboxMessage } from "../../state/coordination/mailbox.ts";
|
|
2
|
-
import type { ArtifactDescriptor, TeamRunManifest, TeamTaskState } from "../../state/types.ts";
|
|
3
|
-
import { sanitizeTaskText } from "../task-packet.ts";
|
|
4
|
-
|
|
5
|
-
export interface RunProjectionSource {
|
|
6
|
-
kind: "events" | "mailbox" | "artifacts" | "ui_metadata" | "runtime_metadata";
|
|
7
|
-
bounded: boolean;
|
|
8
|
-
reference?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface RunProjectionResult {
|
|
12
|
-
sources: RunProjectionSource[];
|
|
13
|
-
summary: string;
|
|
14
|
-
injectedAsContext: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Transform run context before a worker starts.
|
|
19
|
-
* Builds a bounded projection of durable history that will be available
|
|
20
|
-
* to the worker as reference context, not as instructions.
|
|
21
|
-
*
|
|
22
|
-
* Rules:
|
|
23
|
-
* - Durable history retains events, mailbox, artifacts, UI/runtime metadata.
|
|
24
|
-
* - Worker prompt gets a bounded projection (truncated/summarized).
|
|
25
|
-
* - UI/runtime events are not prompt text unless explicitly selected.
|
|
26
|
-
*/
|
|
27
|
-
export function transformRunContextBeforeWorkerStart(input: {
|
|
28
|
-
manifest: TeamRunManifest;
|
|
29
|
-
tasks: TeamTaskState[];
|
|
30
|
-
pendingMailbox: MailboxMessage[];
|
|
31
|
-
artifacts: ArtifactDescriptor[];
|
|
32
|
-
maxEvents?: number;
|
|
33
|
-
maxMailboxMessages?: number;
|
|
34
|
-
maxArtifactRefs?: number;
|
|
35
|
-
}): RunProjectionResult {
|
|
36
|
-
const maxEvents = input.maxEvents ?? 20;
|
|
37
|
-
const maxMailbox = input.maxMailboxMessages ?? 10;
|
|
38
|
-
const maxArtifacts = input.maxArtifactRefs ?? 15;
|
|
39
|
-
|
|
40
|
-
const sources: RunProjectionSource[] = [];
|
|
41
|
-
const lines: string[] = [];
|
|
42
|
-
|
|
43
|
-
// Project a bounded slice of task history
|
|
44
|
-
const completedTasks = input.tasks.filter((t) => t.status === "completed" || t.status === "failed");
|
|
45
|
-
if (completedTasks.length > 0) {
|
|
46
|
-
const tasks = completedTasks.slice(0, maxEvents);
|
|
47
|
-
sources.push({
|
|
48
|
-
kind: "events",
|
|
49
|
-
bounded: true,
|
|
50
|
-
reference: `tasks:${tasks.length}/${completedTasks.length}`,
|
|
51
|
-
});
|
|
52
|
-
lines.push(`Previous tasks (${tasks.length}/${completedTasks.length}):`);
|
|
53
|
-
for (const task of tasks) {
|
|
54
|
-
lines.push(`- ${task.id}: ${task.status}${task.error ? ` (${task.error})` : ""}`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Project pending mailbox that is relevant to this worker
|
|
59
|
-
if (input.pendingMailbox.length > 0) {
|
|
60
|
-
const messages = input.pendingMailbox.slice(0, maxMailbox);
|
|
61
|
-
sources.push({
|
|
62
|
-
kind: "mailbox",
|
|
63
|
-
bounded: true,
|
|
64
|
-
reference: `mailbox:${messages.length}/${input.pendingMailbox.length}`,
|
|
65
|
-
});
|
|
66
|
-
lines.push(`Pending messages (${messages.length}/${input.pendingMailbox.length}):`);
|
|
67
|
-
for (const msg of messages) {
|
|
68
|
-
const safeBody = sanitizeTaskText(msg.body.slice(0, 100));
|
|
69
|
-
// VULN-3: mailbox bodies are cross-worker controllable — give the model a
|
|
70
|
-
// structural signal that this is untrusted content (sanitizeTaskText catches
|
|
71
|
-
// directive injection; the demarcation frames natural-language injection).
|
|
72
|
-
lines.push(`- ${msg.kind ?? "message"}: <untrusted_data source="mailbox">${safeBody}</untrusted_data>`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Project artifact references (not content)
|
|
77
|
-
if (input.artifacts.length > 0) {
|
|
78
|
-
const artifacts = input.artifacts.slice(0, maxArtifacts);
|
|
79
|
-
sources.push({
|
|
80
|
-
kind: "artifacts",
|
|
81
|
-
bounded: true,
|
|
82
|
-
reference: `artifacts:${artifacts.length}/${input.artifacts.length}`,
|
|
83
|
-
});
|
|
84
|
-
lines.push(`Available artifacts (${artifacts.length}/${input.artifacts.length}):`);
|
|
85
|
-
for (const art of artifacts) {
|
|
86
|
-
lines.push(`- ${art.kind} (${art.producer})`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Metadata markers — not injected as prompt instructions
|
|
91
|
-
sources.push({
|
|
92
|
-
kind: "ui_metadata",
|
|
93
|
-
bounded: false,
|
|
94
|
-
reference: "excluded_from_prompt",
|
|
95
|
-
});
|
|
96
|
-
sources.push({
|
|
97
|
-
kind: "runtime_metadata",
|
|
98
|
-
bounded: false,
|
|
99
|
-
reference: "excluded_from_prompt",
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
sources,
|
|
104
|
-
summary: lines.join("\n"),
|
|
105
|
-
injectedAsContext: true,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Convert run history to a bounded worker prompt section.
|
|
111
|
-
* Same logic as transformRunContextBeforeWorkerStart but returns
|
|
112
|
-
* the prompt text directly for embedding in the worker prompt.
|
|
113
|
-
*/
|
|
114
|
-
export function convertRunHistoryToWorkerPrompt(input: {
|
|
115
|
-
manifest: TeamRunManifest;
|
|
116
|
-
tasks: TeamTaskState[];
|
|
117
|
-
pendingMailbox: MailboxMessage[];
|
|
118
|
-
artifacts: ArtifactDescriptor[];
|
|
119
|
-
}): string {
|
|
120
|
-
const projection = transformRunContextBeforeWorkerStart(input);
|
|
121
|
-
if (!projection.summary) return "";
|
|
122
|
-
return [
|
|
123
|
-
"## Run Context (bounded projection)",
|
|
124
|
-
projection.summary,
|
|
125
|
-
"",
|
|
126
|
-
`Projection sources: ${projection.sources.map((s) => s.kind).join(", ")}`,
|
|
127
|
-
].join("\n");
|
|
128
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Post-task backpressure checks — runs a configurable shell script after
|
|
3
|
-
* task completion to verify workspace health before proceeding.
|
|
4
|
-
*
|
|
5
|
-
* Distilled from pi-autoresearch's post-check / backpressure pattern.
|
|
6
|
-
*/
|
|
7
|
-
import { execFileSync } from "node:child_process";
|
|
8
|
-
import { WINDOWS_ESSENTIAL_ENV_VARS } from "../../utils/env-allowlist.ts";
|
|
9
|
-
import { sanitizeEnvSecrets } from "../../utils/env-filter.ts";
|
|
10
|
-
import { resolveShellForScript } from "../../utils/resolve-shell.ts";
|
|
11
|
-
import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
|
|
12
|
-
|
|
13
|
-
/** Default timeout for post-check scripts (5 minutes). */
|
|
14
|
-
const DEFAULT_TIMEOUT_MS = 300_000;
|
|
15
|
-
|
|
16
|
-
/** Environment variable name for the post-check script path. */
|
|
17
|
-
const POST_CHECK_SCRIPT_ENV = "PI_CREW_POST_CHECK_SCRIPT";
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Configuration for a post-task check.
|
|
21
|
-
*/
|
|
22
|
-
export interface PostCheckConfig {
|
|
23
|
-
/** Path to the shell script to execute. */
|
|
24
|
-
scriptPath: string;
|
|
25
|
-
/** Timeout in milliseconds. Defaults to 300000 (5 minutes). */
|
|
26
|
-
timeoutMs: number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Result of running a post-task check.
|
|
31
|
-
*/
|
|
32
|
-
export interface PostCheckResult {
|
|
33
|
-
/** Whether the check passed (exit code 0). */
|
|
34
|
-
passed: boolean;
|
|
35
|
-
/** Combined stdout + stderr from the script. */
|
|
36
|
-
output: string;
|
|
37
|
-
/** Wall-clock duration of the check in milliseconds. */
|
|
38
|
-
durationMs: number;
|
|
39
|
-
/** Whether the check timed out. */
|
|
40
|
-
timedOut: boolean;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Resolve the effective post-check script path.
|
|
45
|
-
* Prefers config.scriptPath; falls back to the PI_CREW_POST_CHECK_SCRIPT env var.
|
|
46
|
-
*/
|
|
47
|
-
function resolveScriptPath(config: PostCheckConfig): string | undefined {
|
|
48
|
-
if (config.scriptPath && config.scriptPath.length > 0) {
|
|
49
|
-
return config.scriptPath;
|
|
50
|
-
}
|
|
51
|
-
return process.env[POST_CHECK_SCRIPT_ENV];
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Run a post-task backpressure check script.
|
|
56
|
-
*
|
|
57
|
-
* Executes the configured bash script and returns a structured result.
|
|
58
|
-
* If no script path is available (neither config nor env var), the check
|
|
59
|
-
* passes by default with a note.
|
|
60
|
-
*
|
|
61
|
-
* **Security note:** The script path is validated to stay within `cwd`.
|
|
62
|
-
* Scripts that escape the working directory are rejected.
|
|
63
|
-
*
|
|
64
|
-
* @param config - Post-check configuration (script path and timeout)
|
|
65
|
-
* @param cwd - Working directory for script execution
|
|
66
|
-
* @returns PostCheckResult with pass/fail status, output, and timing
|
|
67
|
-
*/
|
|
68
|
-
export async function runPostCheck(config: PostCheckConfig, cwd: string): Promise<PostCheckResult> {
|
|
69
|
-
const scriptPath = resolveScriptPath(config);
|
|
70
|
-
const timeoutMs = config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
71
|
-
|
|
72
|
-
if (!scriptPath) {
|
|
73
|
-
return {
|
|
74
|
-
passed: true,
|
|
75
|
-
output: "No post-check script configured; skipping.",
|
|
76
|
-
durationMs: 0,
|
|
77
|
-
timedOut: false,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// M-1 fix (code-review 2026-06-23): use the project's safe-path primitive instead
|
|
82
|
-
// of a hand-rolled path.resolve + startsWith check. The lexical check passed a
|
|
83
|
-
// symlinked cwd/scripts -> /usr/local/bin, but execFileSync followed the symlink
|
|
84
|
-
// and executed a script outside the working directory. resolveRealContainedPath
|
|
85
|
-
// walks ancestors with O_NOFOLLOW and re-validates after realpath, defeating
|
|
86
|
-
// symlink-traversal attacks. Throws if the resolved path escapes cwd.
|
|
87
|
-
resolveRealContainedPath(cwd, scriptPath);
|
|
88
|
-
|
|
89
|
-
const startTime = Date.now();
|
|
90
|
-
|
|
91
|
-
return new Promise<PostCheckResult>((resolve) => {
|
|
92
|
-
try {
|
|
93
|
-
const { command, args } = resolveShellForScript(scriptPath);
|
|
94
|
-
const output = execFileSync(command, args, {
|
|
95
|
-
cwd,
|
|
96
|
-
timeout: timeoutMs,
|
|
97
|
-
encoding: "utf-8",
|
|
98
|
-
maxBuffer: 10 * 1024 * 1024, // 10 MB
|
|
99
|
-
env: {
|
|
100
|
-
...sanitizeEnvSecrets(process.env, {
|
|
101
|
-
allowList: ["PATH", "HOME", "USER", ...WINDOWS_ESSENTIAL_ENV_VARS, "TMPDIR", "LANG", "LC_ALL", "PI_CREW_*"],
|
|
102
|
-
}),
|
|
103
|
-
PI_CREW_POST_CHECK: "1",
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const durationMs = Date.now() - startTime;
|
|
108
|
-
resolve({
|
|
109
|
-
passed: true,
|
|
110
|
-
output: output.trim(),
|
|
111
|
-
durationMs,
|
|
112
|
-
timedOut: false,
|
|
113
|
-
});
|
|
114
|
-
} catch (error: unknown) {
|
|
115
|
-
const durationMs = Date.now() - startTime;
|
|
116
|
-
|
|
117
|
-
// Determine if this was a timeout
|
|
118
|
-
// execFileSync throws with code 'ETIMEDOUT' or sets killed:true on timeout
|
|
119
|
-
const isTimedOut =
|
|
120
|
-
Boolean((error as NodeJS.ErrnoException & { killed?: boolean }).killed) ||
|
|
121
|
-
(error as NodeJS.ErrnoException).code === "ETIMEDOUT";
|
|
122
|
-
|
|
123
|
-
let output = "";
|
|
124
|
-
if (error instanceof Error) {
|
|
125
|
-
const execError = error as NodeJS.ErrnoException & {
|
|
126
|
-
stdout?: string;
|
|
127
|
-
stderr?: string;
|
|
128
|
-
};
|
|
129
|
-
output = [execError.stdout ?? "", execError.stderr ?? "", execError.message ?? ""].join("\n").trim();
|
|
130
|
-
} else {
|
|
131
|
-
output = String(error);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
resolve({
|
|
135
|
-
passed: false,
|
|
136
|
-
output,
|
|
137
|
-
durationMs,
|
|
138
|
-
timedOut: isTimedOut,
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* schedule.ts — Schedule detection and parsing utilities.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors pi-subagents3's SubagentScheduler static methods:
|
|
5
|
-
* - detectSchedule(): sniff cron / interval / one-shot from string
|
|
6
|
-
* - validateCronExpression(): 6-field cron validation
|
|
7
|
-
* - parseRelativeTime(): "+10m" → ISO timestamp
|
|
8
|
-
* - parseInterval(): "5m" → milliseconds
|
|
9
|
-
*/
|
|
10
|
-
import * as fs from "node:fs";
|
|
11
|
-
import * as path from "node:path";
|
|
12
|
-
import { logInternalError } from "../../utils/internal-error.ts";
|
|
13
|
-
import { atomicWriteJson } from "../atomic-write.ts";
|
|
14
|
-
|
|
15
|
-
import type { ScheduledTask, ScheduleStoreData } from "../types.ts";
|
|
16
|
-
|
|
17
|
-
export type DetectedSchedule =
|
|
18
|
-
| { type: "cron"; normalized: string }
|
|
19
|
-
| { type: "interval"; intervalMs: number; normalized: string }
|
|
20
|
-
| { type: "once"; normalized: string };
|
|
21
|
-
|
|
22
|
-
/** "+10s"/"+5m"/"+1h"/"+2d" → ISO timestamp or null if not a relative time. */
|
|
23
|
-
export function parseRelativeTime(s: string): string | null {
|
|
24
|
-
const m = s.trim().match(/^\+(\d+)(s|m|h|d)$/);
|
|
25
|
-
if (!m) return null;
|
|
26
|
-
const ms = parseInt(m[1], 10) * { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[m[2] as "s" | "m" | "h" | "d"];
|
|
27
|
-
return new Date(Date.now() + ms).toISOString();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** "10s"/"5m"/"1h"/"2d" → milliseconds or null if not an interval. */
|
|
31
|
-
export function parseInterval(s: string): number | null {
|
|
32
|
-
const m = s.trim().match(/^(\d+)(s|m|h|d)$/);
|
|
33
|
-
if (!m) return null;
|
|
34
|
-
return parseInt(m[1], 10) * { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[m[2] as "s" | "m" | "h" | "d"];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** 6-field cron validation ("second minute hour dom month dow"). */
|
|
38
|
-
export function validateCronExpression(expr: string): {
|
|
39
|
-
valid: boolean;
|
|
40
|
-
error?: string;
|
|
41
|
-
} {
|
|
42
|
-
const fields = expr.trim().split(/\s+/);
|
|
43
|
-
if (fields.length !== 6) {
|
|
44
|
-
return {
|
|
45
|
-
valid: false,
|
|
46
|
-
error: `Cron must have 6 fields (second minute hour dom month dow), got ${fields.length}. Example: "0 0 9 * * 1" for 9am every Monday.`,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
// Basic format check: all fields must be non-empty
|
|
50
|
-
if (!fields.every((f) => f.length > 0)) {
|
|
51
|
-
return {
|
|
52
|
-
valid: false,
|
|
53
|
-
error: "Cron expression contains empty fields.",
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
// Accept any cron pattern — fail silently for malformed expressions
|
|
57
|
-
// (the croner library will reject at execution time)
|
|
58
|
-
return { valid: true };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Sniff a schedule string and tag its type. Throws on invalid input.
|
|
63
|
-
* Order matters: relative ("+10m") and interval ("5m") both match digit+unit;
|
|
64
|
-
* relative requires the leading "+" to disambiguate.
|
|
65
|
-
*/
|
|
66
|
-
export function detectSchedule(s: string): DetectedSchedule {
|
|
67
|
-
const trimmed = s.trim();
|
|
68
|
-
// "+10m" — relative one-shot
|
|
69
|
-
const rel = parseRelativeTime(trimmed);
|
|
70
|
-
if (rel !== null) return { type: "once", normalized: rel };
|
|
71
|
-
// "5m" — interval
|
|
72
|
-
const ivl = parseInterval(trimmed);
|
|
73
|
-
if (ivl !== null) return { type: "interval", intervalMs: ivl, normalized: trimmed };
|
|
74
|
-
// ISO timestamp — one-shot. Reject past timestamps.
|
|
75
|
-
if (/^\d{4}-\d{2}-\d{2}T/.test(trimmed)) {
|
|
76
|
-
const d = new Date(trimmed);
|
|
77
|
-
if (!Number.isNaN(d.getTime())) {
|
|
78
|
-
if (d.getTime() <= Date.now()) {
|
|
79
|
-
throw new Error(`Scheduled time ${d.toISOString()} is in the past.`);
|
|
80
|
-
}
|
|
81
|
-
return { type: "once", normalized: d.toISOString() };
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
// Cron — 6-field
|
|
85
|
-
const cronCheck = validateCronExpression(trimmed);
|
|
86
|
-
if (cronCheck.valid) return { type: "cron", normalized: trimmed };
|
|
87
|
-
throw new Error(
|
|
88
|
-
`Invalid schedule "${s}". Use 6-field cron (e.g. "0 0 9 * * 1" — 9am every Monday), interval ("5m"/"1h"), or one-shot ("+10m" / ISO).`,
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** ScheduleStore: PID-locked, session-scoped, atomic JSON persistence. */
|
|
93
|
-
export class ScheduleStore {
|
|
94
|
-
private readonly path: string;
|
|
95
|
-
private data: ScheduleStoreData;
|
|
96
|
-
|
|
97
|
-
constructor(path: string) {
|
|
98
|
-
this.path = path;
|
|
99
|
-
this.data = { version: 1, jobs: [] };
|
|
100
|
-
try {
|
|
101
|
-
if (fs.existsSync(path)) {
|
|
102
|
-
const content = fs.readFileSync(path, "utf-8");
|
|
103
|
-
const parsed = JSON.parse(content);
|
|
104
|
-
if (parsed && typeof parsed === "object" && "version" in parsed && "jobs" in parsed) {
|
|
105
|
-
this.data = parsed as ScheduleStoreData;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
} catch {
|
|
109
|
-
// Corrupt or missing file — start fresh
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
private save(): void {
|
|
114
|
-
try {
|
|
115
|
-
fs.mkdirSync(path.dirname(this.path), { recursive: true });
|
|
116
|
-
// Atomic (temp + rename + fsync) — matches the class docstring's "atomic JSON
|
|
117
|
-
// persistence" claim. A raw writeFileSync could truncate schedules.json on
|
|
118
|
-
// crash and the constructor silently resets to {jobs:[]} on parse failure.
|
|
119
|
-
// NOTE: this class is currently not instantiated in production (scheduling
|
|
120
|
-
// persists via settings-store updateCrewSettings); kept consistent for safety.
|
|
121
|
-
atomicWriteJson(this.path, this.data);
|
|
122
|
-
} catch (error) {
|
|
123
|
-
// FIX (Round 21, L1): Use logInternalError for consistency with
|
|
124
|
-
// the rest of the codebase. Previously console.warn may not be
|
|
125
|
-
// visible in all environments (e.g. JSON-RPC mode, redirected
|
|
126
|
-
// stderr). Also import the dependency properly at the top of
|
|
127
|
-
// the file (this method used the legacy require() pattern).
|
|
128
|
-
logInternalError("schedule.save", error, `path=${this.path}`);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
list(): ScheduledTask[] {
|
|
133
|
-
return [...this.data.jobs];
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
hasName(name: string): boolean {
|
|
137
|
-
return this.data.jobs.some((j) => j.name === name);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
get(id: string): ScheduledTask | undefined {
|
|
141
|
-
return this.data.jobs.find((j) => j.id === id);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
add(job: ScheduledTask): void {
|
|
145
|
-
this.data.jobs.push(job);
|
|
146
|
-
this.save();
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
update(id: string, patch: Partial<ScheduledTask>): ScheduledTask | undefined {
|
|
150
|
-
const idx = this.data.jobs.findIndex((j) => j.id === id);
|
|
151
|
-
if (idx === -1) return undefined;
|
|
152
|
-
this.data.jobs[idx] = { ...this.data.jobs[idx], ...patch };
|
|
153
|
-
this.save();
|
|
154
|
-
return this.data.jobs[idx];
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
remove(id: string): boolean {
|
|
158
|
-
const before = this.data.jobs.length;
|
|
159
|
-
this.data.jobs = this.data.jobs.filter((j) => j.id !== id);
|
|
160
|
-
if (this.data.jobs.length !== before) {
|
|
161
|
-
this.save();
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
}
|