pi-crew 0.2.3 → 0.2.5
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 +57 -32
- package/CHANGELOG.md +466 -448
- package/LICENSE +21 -21
- package/NOTICE.md +16 -16
- package/README.md +323 -323
- package/docs/FEATURE_INTAKE.md +126 -0
- package/docs/HARNESS.md +86 -0
- package/docs/HARNESS_BACKLOG.md +41 -0
- package/docs/TEST_MATRIX.md +49 -0
- package/docs/actions-reference.md +595 -595
- package/docs/architecture.md +180 -180
- package/docs/code-review-2026-05-11.md +592 -592
- package/docs/commands-reference.md +347 -347
- package/docs/comparison-pi-subagents-vs-pi-crew.md +303 -0
- package/docs/decisions/0001-durable-state.md +41 -0
- package/docs/decisions/0002-child-process-for-async.md +42 -0
- package/docs/decisions/0003-depth-guard.md +36 -0
- package/docs/decisions/0004-execfile-over-exec.md +34 -0
- package/docs/decisions/0005-no-parameter-properties.md +49 -0
- package/docs/decisions/0006-publish-bundled-esm.md +63 -0
- package/docs/decisions/0007-active-run-binary-index.md +54 -0
- package/docs/decisions/0008-child-pi-warm-pool.md +61 -0
- package/docs/decisions/README.md +23 -0
- package/docs/followup-review-round4-2026-05-13.md +107 -0
- package/docs/implementation-plan-top3.md +333 -0
- package/docs/live-mailbox-runtime.md +36 -36
- package/docs/next-upgrade-roadmap.md +808 -808
- package/docs/oh-my-pi-research.md +509 -0
- package/docs/perf/baseline-2026-05.md +113 -0
- package/docs/perf/final-report-2026-05.md +206 -0
- package/docs/perf/sprint-1-report.md +71 -0
- package/docs/perf/sprint-2-report.md +81 -0
- package/docs/perf/sprint-2.5-report.md +53 -0
- package/docs/perf/sprint-3-report.md +36 -0
- package/docs/perf/sprint-4-report.md +47 -0
- package/docs/perf/sprint-5-report.md +51 -0
- package/docs/perf/sprint-6-report.md +94 -0
- package/docs/perf/sprint-7-report.md +74 -0
- package/docs/perf/upgrade-plan-2026-05.md +147 -0
- package/docs/pi-subagents3-deep-analysis.md +508 -0
- package/docs/product/README.md +31 -0
- package/docs/product/platform.md +27 -0
- package/docs/product/runtime-safety.md +37 -0
- package/docs/product/team-run.md +39 -0
- package/docs/product/team-tool.md +37 -0
- package/docs/publishing.md +65 -65
- package/docs/resource-formats.md +134 -134
- package/docs/runtime-analysis-child-vs-live.md +171 -0
- package/docs/runtime-flow.md +148 -148
- package/docs/runtime-migration-in-process-analysis.md +250 -0
- package/docs/stories/README.md +30 -0
- package/docs/stories/backlog.md +36 -0
- package/docs/templates/decision.md +27 -0
- package/docs/templates/story.md +44 -0
- package/docs/templates/validation-report.md +32 -0
- package/docs/usage.md +238 -238
- package/index.ts +7 -6
- package/install.mjs +65 -65
- package/package.json +107 -100
- package/schema.json +222 -222
- package/skills/child-pi-spawning/SKILL.md +213 -0
- package/skills/context-artifact-hygiene/SKILL.md +32 -0
- package/skills/event-log-tracing/SKILL.md +299 -0
- package/skills/git-master/SKILL.md +225 -24
- package/skills/live-agent-lifecycle/SKILL.md +192 -0
- package/skills/mailbox-interactive/SKILL.md +300 -19
- package/skills/model-routing-context/SKILL.md +94 -0
- package/skills/multi-perspective-review/SKILL.md +88 -0
- package/skills/read-only-explorer/SKILL.md +250 -26
- package/skills/safe-bash/SKILL.md +307 -21
- package/skills/verification-before-done/SKILL.md +11 -2
- package/skills/widget-rendering/SKILL.md +258 -0
- package/skills/workspace-isolation/SKILL.md +202 -0
- package/skills/worktree-isolation/SKILL.md +202 -18
- package/src/adapters/claude-adapter.ts +25 -25
- package/src/adapters/codex-adapter.ts +21 -21
- package/src/adapters/cursor-adapter.ts +17 -17
- package/src/adapters/export-util.ts +137 -137
- package/src/adapters/index.ts +15 -15
- package/src/adapters/registry.ts +18 -18
- package/src/adapters/types.ts +23 -23
- package/src/agents/agent-config.ts +38 -38
- package/src/agents/agent-serializer.ts +38 -38
- package/src/agents/discover-agents.ts +121 -118
- package/src/config/config.ts +740 -858
- package/src/config/defaults.ts +96 -96
- package/src/config/drift-detector.ts +211 -211
- package/src/config/markers.ts +327 -327
- package/src/config/resilient-parser.ts +109 -108
- package/src/config/suggestions.ts +74 -74
- package/src/config/types.ts +199 -0
- package/src/extension/async-notifier.ts +123 -89
- package/src/extension/autonomous-policy.ts +169 -169
- package/src/extension/cross-extension-rpc.ts +104 -104
- package/src/extension/help.ts +47 -47
- package/src/extension/import-index.ts +69 -69
- package/src/extension/management.ts +395 -382
- package/src/extension/notification-router.ts +116 -116
- package/src/extension/notification-sink.ts +51 -51
- package/src/extension/project-init.ts +168 -168
- package/src/extension/register.ts +859 -668
- package/src/extension/registration/artifact-cleanup.ts +15 -15
- package/src/extension/registration/command-utils.ts +54 -54
- package/src/extension/registration/commands.ts +559 -452
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/subagent-helpers.ts +102 -102
- package/src/extension/registration/subagent-tools.ts +220 -159
- package/src/extension/registration/team-tool.ts +159 -99
- package/src/extension/registration/viewers.ts +29 -0
- package/src/extension/result-watcher.ts +128 -128
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-export.ts +73 -73
- package/src/extension/run-import.ts +84 -84
- package/src/extension/run-index.ts +94 -94
- package/src/extension/run-maintenance.ts +142 -142
- package/src/extension/session-summary.ts +8 -8
- package/src/extension/team-manager-command.ts +96 -96
- package/src/extension/team-recommendation.ts +188 -188
- package/src/extension/team-tool/api.ts +5 -2
- package/src/extension/team-tool/cancel.ts +224 -209
- package/src/extension/team-tool/config-patch.ts +36 -36
- package/src/extension/team-tool/context.ts +60 -60
- package/src/extension/team-tool/doctor.ts +242 -242
- package/src/extension/team-tool/handle-settings.ts +421 -195
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/lifecycle-actions.ts +139 -139
- package/src/extension/team-tool/parallel-dispatch.ts +156 -156
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +112 -111
- package/src/extension/team-tool/run.ts +246 -229
- package/src/extension/team-tool/status.ts +110 -110
- package/src/extension/team-tool-types.ts +13 -13
- package/src/extension/team-tool.ts +344 -344
- package/src/extension/tool-result.ts +16 -16
- package/src/extension/validate-resources.ts +77 -77
- package/src/hooks/registry.ts +61 -61
- package/src/hooks/types.ts +40 -40
- package/src/i18n.ts +184 -184
- package/src/observability/correlation.ts +35 -35
- package/src/observability/event-to-metric.ts +68 -68
- package/src/observability/exporters/adapter.ts +30 -30
- package/src/observability/exporters/otlp-exporter.ts +106 -92
- package/src/observability/exporters/prometheus-exporter.ts +54 -54
- package/src/observability/metric-registry.ts +87 -87
- package/src/observability/metric-retention.ts +54 -54
- package/src/observability/metric-sink.ts +81 -56
- package/src/observability/metrics-primitives.ts +167 -167
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/adaptive-plan.ts +338 -0
- package/src/runtime/agent-control.ts +169 -169
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -114
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/async-runner.ts +153 -153
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/auto-resume.ts +100 -100
- package/src/runtime/background-runner.ts +122 -89
- package/src/runtime/cancellation.ts +61 -61
- package/src/runtime/capability-inventory.ts +116 -116
- package/src/runtime/child-pi-pool.ts +68 -0
- package/src/runtime/child-pi.ts +541 -461
- package/src/runtime/code-summary.ts +247 -247
- package/src/runtime/compaction-summary.ts +271 -271
- package/src/runtime/concurrency.ts +58 -58
- package/src/runtime/crash-recovery.ts +317 -301
- package/src/runtime/crew-agent-records.ts +379 -281
- package/src/runtime/crew-agent-runtime.ts +60 -60
- package/src/runtime/cross-extension-rpc.ts +72 -0
- package/src/runtime/custom-tools/irc-tool.ts +201 -201
- package/src/runtime/custom-tools/submit-result-tool.ts +90 -90
- package/src/runtime/deadletter.ts +47 -47
- package/src/runtime/delivery-coordinator.ts +176 -176
- package/src/runtime/delta-conflict.ts +360 -360
- package/src/runtime/diagnostic-export.ts +102 -102
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/effectiveness.ts +82 -81
- package/src/runtime/errors/crew-errors.ts +166 -0
- package/src/runtime/event-stream-bridge.ts +92 -92
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +234 -106
- package/src/runtime/heartbeat-watcher.ts +145 -124
- package/src/runtime/iteration-hooks.ts +267 -267
- package/src/runtime/live-agent-control.ts +88 -88
- package/src/runtime/live-agent-manager.ts +377 -179
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-session-runtime.ts +676 -600
- package/src/runtime/loop-gates.ts +129 -129
- package/src/runtime/manifest-cache.ts +263 -263
- package/src/runtime/mcp-proxy.ts +113 -113
- package/src/runtime/metric-parser.ts +40 -40
- package/src/runtime/model-fallback.ts +282 -274
- package/src/runtime/model-resolver.ts +118 -0
- package/src/runtime/output-validator.ts +187 -187
- package/src/runtime/overflow-recovery.ts +175 -175
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/parallel-utils.ts +156 -156
- package/src/runtime/parent-guard.ts +80 -80
- package/src/runtime/phase-progress.ts +217 -217
- package/src/runtime/pi-args.ts +165 -165
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/pi-spawn.ts +167 -167
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/post-checks.ts +125 -125
- package/src/runtime/post-exit-stdio-guard.ts +86 -86
- package/src/runtime/process-status.ts +97 -73
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/retry-executor.ts +81 -81
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/run-tracker.ts +99 -0
- package/src/runtime/runtime-policy.ts +21 -0
- package/src/runtime/runtime-resolver.ts +94 -91
- package/src/runtime/scheduler.ts +294 -0
- package/src/runtime/semaphore.ts +131 -131
- package/src/runtime/sensitive-paths.ts +92 -92
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/settings-store.ts +103 -0
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/skill-instructions.ts +222 -222
- package/src/runtime/stale-reconciler.ts +198 -189
- package/src/runtime/streaming-output.ts +47 -0
- package/src/runtime/subagent-manager.ts +404 -400
- package/src/runtime/subprocess-tool-registry.ts +67 -67
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-graph-scheduler.ts +122 -122
- package/src/runtime/task-graph.ts +207 -207
- package/src/runtime/task-output-context.ts +177 -177
- package/src/runtime/task-packet.ts +93 -93
- package/src/runtime/task-quality.ts +207 -207
- package/src/runtime/task-runner/capabilities.ts +78 -78
- package/src/runtime/task-runner/live-executor.ts +131 -113
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/prompt-builder.ts +139 -139
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/run-projection.ts +103 -103
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/task-runner.ts +469 -459
- package/src/runtime/team-runner.ts +693 -945
- package/src/runtime/usage-tracker.ts +71 -0
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/runtime/workflow-state.ts +187 -187
- package/src/runtime/yield-handler.ts +190 -190
- package/src/schema/config-schema.ts +172 -168
- package/src/schema/team-tool-schema.ts +126 -126
- package/src/schema/validation-types.ts +151 -148
- package/src/skills/discover-skills.ts +67 -67
- package/src/skills/skill-templates.ts +374 -374
- package/src/state/active-run-registry.ts +227 -191
- package/src/state/artifact-store.ts +130 -129
- package/src/state/atomic-write.ts +262 -195
- package/src/state/blob-store.ts +116 -116
- package/src/state/contracts.ts +111 -111
- package/src/state/event-log-rotation.ts +161 -158
- package/src/state/event-log.ts +383 -303
- package/src/state/event-reconstructor.ts +217 -217
- package/src/state/jsonl-writer.ts +82 -82
- package/src/state/locks.ts +146 -146
- package/src/state/mailbox.ts +446 -405
- package/src/state/state-store.ts +364 -351
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +285 -285
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/discover-teams.ts +116 -116
- package/src/teams/team-config.ts +27 -27
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/agent-management-overlay.ts +144 -144
- package/src/ui/crew-widget.ts +487 -370
- package/src/ui/dashboard-panes/agents-pane.ts +109 -28
- package/src/ui/dashboard-panes/cancellation-pane.ts +42 -42
- package/src/ui/dashboard-panes/capability-pane.ts +59 -59
- package/src/ui/dashboard-panes/health-pane.ts +30 -30
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -35
- package/src/ui/dashboard-panes/progress-pane.ts +30 -30
- package/src/ui/dashboard-panes/transcript-pane.ts +10 -10
- package/src/ui/heartbeat-aggregator.ts +63 -63
- package/src/ui/keybinding-map.ts +97 -94
- package/src/ui/live-conversation-overlay.ts +152 -0
- package/src/ui/live-run-sidebar.ts +180 -180
- package/src/ui/mascot.ts +442 -442
- package/src/ui/overlays/agent-picker-overlay.ts +57 -57
- package/src/ui/overlays/confirm-overlay.ts +58 -58
- package/src/ui/overlays/mailbox-compose-overlay.ts +144 -144
- package/src/ui/overlays/mailbox-compose-preview.ts +63 -63
- package/src/ui/overlays/mailbox-detail-overlay.ts +122 -122
- package/src/ui/pi-ui-compat.ts +57 -57
- package/src/ui/powerbar-publisher.ts +221 -197
- package/src/ui/render-scheduler.ts +216 -143
- package/src/ui/run-action-dispatcher.ts +118 -118
- package/src/ui/run-dashboard.ts +526 -464
- package/src/ui/run-event-bus.ts +208 -208
- package/src/ui/run-snapshot-cache.ts +826 -777
- package/src/ui/settings-overlay.ts +721 -0
- package/src/ui/snapshot-types.ts +86 -70
- package/src/ui/theme-adapter.ts +190 -190
- package/src/ui/tool-progress-formatter.ts +89 -0
- package/src/ui/transcript-cache.ts +94 -94
- package/src/ui/transcript-viewer.ts +335 -335
- package/src/utils/conflict-detect.ts +662 -0
- package/src/utils/file-coalescer.ts +86 -86
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/fs-watch.ts +88 -31
- package/src/utils/gh-protocol.ts +479 -0
- package/src/utils/ids.ts +17 -17
- package/src/utils/incremental-reader.ts +104 -104
- package/src/utils/internal-error.ts +6 -6
- package/src/utils/names.ts +27 -27
- package/src/utils/paths.ts +102 -63
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/scan-cache.ts +136 -136
- package/src/utils/sse-parser.ts +134 -134
- package/src/utils/task-name-generator.ts +337 -337
- package/src/utils/timings.ts +33 -33
- package/src/utils/visual.ts +243 -198
- package/src/workflows/discover-workflows.ts +139 -139
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/workflows/workflow-config.ts +26 -26
- package/src/workflows/workflow-serializer.ts +32 -32
- package/src/worktree/branch-freshness.ts +45 -45
- package/src/worktree/cleanup.ts +75 -75
- package/src/worktree/worktree-manager.ts +188 -188
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/tsconfig.json +19 -19
- package/workflows/default.workflow.md +30 -30
- package/workflows/fast-fix.workflow.md +23 -23
- package/workflows/implementation.workflow.md +43 -43
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
- package/skills/task-packet/SKILL.md +0 -28
- package/skills/verify-evidence/SKILL.md +0 -27
|
@@ -1,91 +1,94 @@
|
|
|
1
|
-
import type { PiTeamsConfig } from "../config/config.ts";
|
|
2
|
-
import type { RuntimeResolutionState } from "../state/types.ts";
|
|
3
|
-
import type { CrewRuntimeKind } from "./crew-agent-runtime.ts";
|
|
4
|
-
|
|
5
|
-
export type CrewRuntimeMode = "auto" | "scaffold" | "child-process" | "live-session";
|
|
6
|
-
|
|
7
|
-
export type CrewRuntimeSafety = "trusted" | "explicit_dry_run" | "blocked";
|
|
8
|
-
|
|
9
|
-
export interface CrewRuntimeCapabilities {
|
|
10
|
-
kind: CrewRuntimeKind;
|
|
11
|
-
requestedMode: CrewRuntimeMode;
|
|
12
|
-
available: boolean;
|
|
13
|
-
fallback?: CrewRuntimeKind;
|
|
14
|
-
steer: boolean;
|
|
15
|
-
resume: boolean;
|
|
16
|
-
liveToolActivity: boolean;
|
|
17
|
-
transcript: boolean;
|
|
18
|
-
reason?: string;
|
|
19
|
-
safety: CrewRuntimeSafety;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function runtimeResolutionState(runtime: CrewRuntimeCapabilities, resolvedAt = new Date().toISOString()): RuntimeResolutionState {
|
|
23
|
-
return {
|
|
24
|
-
kind: runtime.kind,
|
|
25
|
-
requestedMode: runtime.requestedMode,
|
|
26
|
-
safety: runtime.safety,
|
|
27
|
-
available: runtime.available,
|
|
28
|
-
...(runtime.fallback ? { fallback: runtime.fallback } : {}),
|
|
29
|
-
...(runtime.reason ? { reason: runtime.reason } : {}),
|
|
30
|
-
resolvedAt,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function isLiveSessionRuntimeAvailable(timeoutMs = 1500, env: NodeJS.ProcessEnv = process.env): Promise<{ available: boolean; reason?: string }> {
|
|
35
|
-
if (env.PI_CREW_MOCK_LIVE_SESSION === "success") {
|
|
36
|
-
return { available: true, reason: "Mock live-session runtime is enabled." };
|
|
37
|
-
}
|
|
38
|
-
const probe = async (): Promise<{ available: boolean; reason?: string }> => {
|
|
39
|
-
try {
|
|
40
|
-
// LAZY: optional peer dependency — probe at runtime to avoid hard dependency.
|
|
41
|
-
const mod = await import("@mariozechner/pi-coding-agent");
|
|
42
|
-
const api = mod as Record<string, unknown>;
|
|
43
|
-
const required = ["createAgentSession", "DefaultResourceLoader", "SessionManager", "SettingsManager"];
|
|
44
|
-
const missing = required.filter((name) => typeof api[name] === "undefined");
|
|
45
|
-
if (missing.length) return { available: false, reason: `Pi SDK live-session exports missing: ${missing.join(", ")}.` };
|
|
46
|
-
return { available: true };
|
|
47
|
-
} catch (error) {
|
|
48
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
49
|
-
return { available: false, reason: `Could not load optional Pi SDK live-session runtime: ${message}` };
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
let timer: NodeJS.Timeout | undefined;
|
|
53
|
-
try {
|
|
54
|
-
return await Promise.race([
|
|
55
|
-
probe(),
|
|
56
|
-
new Promise<{ available: boolean; reason: string }>((resolve) => {
|
|
57
|
-
timer = setTimeout(() => resolve({ available: false, reason: `Timed out probing optional Pi SDK live-session runtime after ${timeoutMs}ms.` }), timeoutMs);
|
|
58
|
-
timer.unref();
|
|
59
|
-
}),
|
|
60
|
-
]);
|
|
61
|
-
} finally {
|
|
62
|
-
if (timer) clearTimeout(timer);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export async function resolveCrewRuntime(config: PiTeamsConfig, env: NodeJS.ProcessEnv = process.env): Promise<CrewRuntimeCapabilities> {
|
|
67
|
-
const requestedMode = config.runtime?.mode ?? "auto";
|
|
68
|
-
const workersDisabled = config.executeWorkers === false || env.PI_CREW_EXECUTE_WORKERS === "0" || env.PI_TEAMS_EXECUTE_WORKERS === "0";
|
|
69
|
-
if (requestedMode === "scaffold") return scaffoldCaps(requestedMode, undefined, "explicit_dry_run");
|
|
70
|
-
if (workersDisabled) return scaffoldCaps(requestedMode, "Child worker execution disabled by config/env. Set runtime.mode=scaffold or executeWorkers=false only for dry runs.", "blocked");
|
|
71
|
-
if (requestedMode === "child-process") return childCaps(requestedMode);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
1
|
+
import type { PiTeamsConfig } from "../config/config.ts";
|
|
2
|
+
import type { RuntimeResolutionState } from "../state/types.ts";
|
|
3
|
+
import type { CrewRuntimeKind } from "./crew-agent-runtime.ts";
|
|
4
|
+
|
|
5
|
+
export type CrewRuntimeMode = "auto" | "scaffold" | "child-process" | "live-session";
|
|
6
|
+
|
|
7
|
+
export type CrewRuntimeSafety = "trusted" | "explicit_dry_run" | "blocked";
|
|
8
|
+
|
|
9
|
+
export interface CrewRuntimeCapabilities {
|
|
10
|
+
kind: CrewRuntimeKind;
|
|
11
|
+
requestedMode: CrewRuntimeMode;
|
|
12
|
+
available: boolean;
|
|
13
|
+
fallback?: CrewRuntimeKind;
|
|
14
|
+
steer: boolean;
|
|
15
|
+
resume: boolean;
|
|
16
|
+
liveToolActivity: boolean;
|
|
17
|
+
transcript: boolean;
|
|
18
|
+
reason?: string;
|
|
19
|
+
safety: CrewRuntimeSafety;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function runtimeResolutionState(runtime: CrewRuntimeCapabilities, resolvedAt = new Date().toISOString()): RuntimeResolutionState {
|
|
23
|
+
return {
|
|
24
|
+
kind: runtime.kind,
|
|
25
|
+
requestedMode: runtime.requestedMode,
|
|
26
|
+
safety: runtime.safety,
|
|
27
|
+
available: runtime.available,
|
|
28
|
+
...(runtime.fallback ? { fallback: runtime.fallback } : {}),
|
|
29
|
+
...(runtime.reason ? { reason: runtime.reason } : {}),
|
|
30
|
+
resolvedAt,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function isLiveSessionRuntimeAvailable(timeoutMs = 1500, env: NodeJS.ProcessEnv = process.env): Promise<{ available: boolean; reason?: string }> {
|
|
35
|
+
if (env.PI_CREW_MOCK_LIVE_SESSION === "success") {
|
|
36
|
+
return { available: true, reason: "Mock live-session runtime is enabled." };
|
|
37
|
+
}
|
|
38
|
+
const probe = async (): Promise<{ available: boolean; reason?: string }> => {
|
|
39
|
+
try {
|
|
40
|
+
// LAZY: optional peer dependency — probe at runtime to avoid hard dependency.
|
|
41
|
+
const mod = await import("@mariozechner/pi-coding-agent");
|
|
42
|
+
const api = mod as Record<string, unknown>;
|
|
43
|
+
const required = ["createAgentSession", "DefaultResourceLoader", "SessionManager", "SettingsManager"];
|
|
44
|
+
const missing = required.filter((name) => typeof api[name] === "undefined");
|
|
45
|
+
if (missing.length) return { available: false, reason: `Pi SDK live-session exports missing: ${missing.join(", ")}.` };
|
|
46
|
+
return { available: true };
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
49
|
+
return { available: false, reason: `Could not load optional Pi SDK live-session runtime: ${message}` };
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
let timer: NodeJS.Timeout | undefined;
|
|
53
|
+
try {
|
|
54
|
+
return await Promise.race([
|
|
55
|
+
probe(),
|
|
56
|
+
new Promise<{ available: boolean; reason: string }>((resolve) => {
|
|
57
|
+
timer = setTimeout(() => resolve({ available: false, reason: `Timed out probing optional Pi SDK live-session runtime after ${timeoutMs}ms.` }), timeoutMs);
|
|
58
|
+
timer.unref();
|
|
59
|
+
}),
|
|
60
|
+
]);
|
|
61
|
+
} finally {
|
|
62
|
+
if (timer) clearTimeout(timer);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function resolveCrewRuntime(config: PiTeamsConfig, env: NodeJS.ProcessEnv = process.env): Promise<CrewRuntimeCapabilities> {
|
|
67
|
+
const requestedMode = config.runtime?.mode ?? "auto";
|
|
68
|
+
const workersDisabled = config.executeWorkers === false || env.PI_CREW_EXECUTE_WORKERS === "0" || env.PI_TEAMS_EXECUTE_WORKERS === "0";
|
|
69
|
+
if (requestedMode === "scaffold") return scaffoldCaps(requestedMode, undefined, "explicit_dry_run");
|
|
70
|
+
if (workersDisabled) return scaffoldCaps(requestedMode, "Child worker execution disabled by config/env. Set runtime.mode=scaffold or executeWorkers=false only for dry runs.", "blocked");
|
|
71
|
+
if (requestedMode === "child-process") return childCaps(requestedMode);
|
|
72
|
+
// When a child-process mock is active (tests), force auto-mode to child-process where the mock is active.
|
|
73
|
+
if (requestedMode === "auto" && env.PI_TEAMS_MOCK_CHILD_PI) return childCaps(requestedMode, "PI_TEAMS_MOCK_CHILD_PI mock forces child-process runtime in auto mode.");
|
|
74
|
+
if (requestedMode === "live-session" || requestedMode === "auto") {
|
|
75
|
+
const live = await isLiveSessionRuntimeAvailable(1500, env);
|
|
76
|
+
if (live.available) return liveCaps(requestedMode);
|
|
77
|
+
if (requestedMode === "live-session" && config.runtime?.allowChildProcessFallback === false)
|
|
78
|
+
return scaffoldCaps(requestedMode, live.reason, "blocked");
|
|
79
|
+
return { ...childCaps(requestedMode), fallback: "child-process", reason: live.reason };
|
|
80
|
+
}
|
|
81
|
+
return childCaps(requestedMode);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function scaffoldCaps(requestedMode: CrewRuntimeMode, reason?: string, safety: CrewRuntimeSafety = "explicit_dry_run"): CrewRuntimeCapabilities {
|
|
85
|
+
return { kind: "scaffold", requestedMode, available: safety !== "blocked", steer: false, resume: false, liveToolActivity: false, transcript: false, safety, ...(reason ? { reason } : {}) };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function childCaps(requestedMode: CrewRuntimeMode, reason?: string): CrewRuntimeCapabilities {
|
|
89
|
+
return { kind: "child-process", requestedMode, available: true, steer: false, resume: false, liveToolActivity: false, transcript: true, safety: "trusted", ...(reason ? { reason } : {}) };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function liveCaps(requestedMode: CrewRuntimeMode): CrewRuntimeCapabilities {
|
|
93
|
+
return { kind: "live-session", requestedMode, available: true, steer: true, resume: true, liveToolActivity: true, transcript: true, safety: "trusted" };
|
|
94
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
export type ScheduleType = "cron" | "once" | "interval";
|
|
2
|
+
|
|
3
|
+
export interface ScheduledJob {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
schedule: string;
|
|
8
|
+
scheduleType: ScheduleType;
|
|
9
|
+
intervalMs?: number;
|
|
10
|
+
subagentType: string;
|
|
11
|
+
prompt: string;
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
lastRun?: string;
|
|
15
|
+
lastStatus?: "success" | "error" | "running";
|
|
16
|
+
nextRun?: string;
|
|
17
|
+
runCount: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ScheduleChangeEvent =
|
|
21
|
+
| { type: "added"; job: ScheduledJob }
|
|
22
|
+
| { type: "removed"; jobId: string }
|
|
23
|
+
| { type: "updated"; job: ScheduledJob }
|
|
24
|
+
| { type: "fired"; jobId: string; agentId: string; name: string }
|
|
25
|
+
| { type: "error"; jobId: string; error: string };
|
|
26
|
+
|
|
27
|
+
export class CrewScheduler {
|
|
28
|
+
private jobs = new Map<string, ScheduledJob>();
|
|
29
|
+
private timers = new Map<string, ReturnType<typeof setInterval | typeof setTimeout>>();
|
|
30
|
+
private emit?: (event: ScheduleChangeEvent) => void;
|
|
31
|
+
private executor?: (job: ScheduledJob) => string;
|
|
32
|
+
private finalizer?: (jobId: string, agentId: string) => void;
|
|
33
|
+
|
|
34
|
+
start(
|
|
35
|
+
options: {
|
|
36
|
+
emit: (event: ScheduleChangeEvent) => void;
|
|
37
|
+
executor: (job: ScheduledJob) => string;
|
|
38
|
+
finalizer: (jobId: string, agentId: string) => void;
|
|
39
|
+
},
|
|
40
|
+
): void {
|
|
41
|
+
this.emit = options.emit;
|
|
42
|
+
this.executor = options.executor;
|
|
43
|
+
this.finalizer = options.finalizer;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
stop(): void {
|
|
47
|
+
for (const t of this.timers.values()) {
|
|
48
|
+
clearInterval(t as ReturnType<typeof setInterval>);
|
|
49
|
+
clearTimeout(t as ReturnType<typeof setTimeout>);
|
|
50
|
+
}
|
|
51
|
+
this.timers.clear();
|
|
52
|
+
this.emit = undefined;
|
|
53
|
+
this.executor = undefined;
|
|
54
|
+
this.finalizer = undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
add(job: ScheduledJob): void {
|
|
58
|
+
this.jobs.set(job.id, job);
|
|
59
|
+
if (job.enabled) this.arm(job);
|
|
60
|
+
this.emit?.({ type: "added", job });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
remove(id: string): boolean {
|
|
64
|
+
this.disarm(id);
|
|
65
|
+
const ok = this.jobs.delete(id);
|
|
66
|
+
if (ok) this.emit?.({ type: "removed", jobId: id });
|
|
67
|
+
return ok;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
update(id: string, patch: Partial<ScheduledJob>): ScheduledJob | undefined {
|
|
71
|
+
const existing = this.jobs.get(id);
|
|
72
|
+
if (!existing) return undefined;
|
|
73
|
+
this.disarm(id);
|
|
74
|
+
const updated = { ...existing, ...patch };
|
|
75
|
+
this.jobs.set(id, updated);
|
|
76
|
+
if (updated.enabled) this.arm(updated);
|
|
77
|
+
this.emit?.({ type: "updated", job: updated });
|
|
78
|
+
return updated;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
list(): ScheduledJob[] {
|
|
82
|
+
return [...this.jobs.values()];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private arm(job: ScheduledJob): void {
|
|
86
|
+
if (this.timers.has(job.id)) return;
|
|
87
|
+
if (job.scheduleType === "interval" && job.intervalMs) {
|
|
88
|
+
const t = setInterval(() => this.fire(job.id), job.intervalMs);
|
|
89
|
+
this.timers.set(job.id, t);
|
|
90
|
+
} else if (job.scheduleType === "once") {
|
|
91
|
+
const target = new Date(job.schedule).getTime();
|
|
92
|
+
const delay = target - Date.now();
|
|
93
|
+
if (delay > 0) {
|
|
94
|
+
const t = setTimeout(() => {
|
|
95
|
+
this.fire(job.id);
|
|
96
|
+
this.update(job.id, { enabled: false });
|
|
97
|
+
}, delay);
|
|
98
|
+
this.timers.set(job.id, t);
|
|
99
|
+
} else {
|
|
100
|
+
this.update(job.id, { enabled: false, lastStatus: "error" });
|
|
101
|
+
this.emit?.({ type: "error", jobId: job.id, error: `Scheduled time ${job.schedule} is in the past` });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private disarm(id: string): void {
|
|
107
|
+
const t = this.timers.get(id);
|
|
108
|
+
if (t) {
|
|
109
|
+
clearInterval(t as ReturnType<typeof setInterval>);
|
|
110
|
+
clearTimeout(t as ReturnType<typeof setTimeout>);
|
|
111
|
+
this.timers.delete(id);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private fire(id: string): void {
|
|
116
|
+
const job = this.jobs.get(id);
|
|
117
|
+
if (!job?.enabled || !this.executor) return;
|
|
118
|
+
this.update(id, { lastStatus: "running" });
|
|
119
|
+
let agentId: string;
|
|
120
|
+
try {
|
|
121
|
+
agentId = this.executor(job);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
124
|
+
this.update(id, { lastRun: new Date().toISOString(), lastStatus: "error" });
|
|
125
|
+
this.emit?.({ type: "error", jobId: id, error });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.emit?.({ type: "fired", jobId: id, agentId, name: job.name });
|
|
129
|
+
this.finalizer?.(id, agentId);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
static detectSchedule(s: string): { type: ScheduleType; intervalMs?: number; normalized: string } {
|
|
133
|
+
const trimmed = s.trim();
|
|
134
|
+
// Relative: +10m
|
|
135
|
+
const rel = trimmed.match(/^\+(\d+)(s|m|h|d)$/);
|
|
136
|
+
if (rel) {
|
|
137
|
+
const ms = parseInt(rel[1], 10) * { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[rel[2] as "s" | "m" | "h" | "d"];
|
|
138
|
+
return { type: "once", normalized: new Date(Date.now() + ms).toISOString() };
|
|
139
|
+
}
|
|
140
|
+
// Interval: 5m
|
|
141
|
+
const ivl = trimmed.match(/^(\d+)(s|m|h|d)$/);
|
|
142
|
+
if (ivl) {
|
|
143
|
+
const ms = parseInt(ivl[1], 10) * { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[ivl[2] as "s" | "m" | "h" | "d"];
|
|
144
|
+
return { type: "interval", intervalMs: ms, normalized: trimmed };
|
|
145
|
+
}
|
|
146
|
+
// ISO timestamp
|
|
147
|
+
if (/^\d{4}-\d{2}-\d{2}T/.test(trimmed)) {
|
|
148
|
+
const d = new Date(trimmed);
|
|
149
|
+
if (!Number.isNaN(d.getTime())) {
|
|
150
|
+
if (d.getTime() <= Date.now()) throw new Error(`Scheduled time ${d.toISOString()} is in the past.`);
|
|
151
|
+
return { type: "once", normalized: d.toISOString() };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Simple cron-like (5 fields)
|
|
155
|
+
const cronFields = trimmed.split(/\s+/);
|
|
156
|
+
if (cronFields.length >= 5) {
|
|
157
|
+
return { type: "cron", normalized: trimmed };
|
|
158
|
+
}
|
|
159
|
+
throw new Error(`Invalid schedule "${s}". Use "5m", "+10m", ISO timestamp, or cron expression.`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface ScheduleSpec {
|
|
164
|
+
kind: "once" | "interval" | "cron";
|
|
165
|
+
spec: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function parseIntervalMs(s: string): number | undefined {
|
|
169
|
+
let ms = 0;
|
|
170
|
+
let remaining = s;
|
|
171
|
+
const unitMs: Record<string, number> = { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 };
|
|
172
|
+
while (remaining.length > 0) {
|
|
173
|
+
const m = remaining.match(/^(\d+)(s|m|h|d)/);
|
|
174
|
+
if (!m) return undefined;
|
|
175
|
+
ms += parseInt(m[1], 10) * unitMs[m[2]];
|
|
176
|
+
remaining = remaining.slice(m[0].length);
|
|
177
|
+
}
|
|
178
|
+
return ms;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function nextCronDate(spec: string, from: Date): Date | { error: string } | null {
|
|
182
|
+
const parts = spec.split(/\s+/);
|
|
183
|
+
if (parts.length < 5) return { error: "Invalid cron expression" };
|
|
184
|
+
const [minStr, hourStr, domStr, monthStr, dowStr] = parts;
|
|
185
|
+
|
|
186
|
+
function matchField(value: number, str: string, min: number, max: number): boolean {
|
|
187
|
+
if (str === "*") return true;
|
|
188
|
+
const n = parseInt(str, 10);
|
|
189
|
+
if (!Number.isNaN(n) && n >= min && n <= max && n === value) return true;
|
|
190
|
+
if (/^\d+-\d+$/.test(str)) {
|
|
191
|
+
const [a, b] = str.split("-").map(Number);
|
|
192
|
+
return value >= a && value <= b;
|
|
193
|
+
}
|
|
194
|
+
if (str.includes(",")) {
|
|
195
|
+
return str.split(",").some((part) => matchField(value, part.trim(), min, max));
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let cursor = new Date(from.getTime());
|
|
201
|
+
cursor.setSeconds(0, 0);
|
|
202
|
+
cursor = new Date(cursor.getTime() + 60_000);
|
|
203
|
+
|
|
204
|
+
const maxIterations = 366 * 24 * 60;
|
|
205
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
206
|
+
const min = cursor.getUTCMinutes();
|
|
207
|
+
const hour = cursor.getUTCHours();
|
|
208
|
+
const dom = cursor.getUTCDate();
|
|
209
|
+
const month = cursor.getUTCMonth() + 1;
|
|
210
|
+
const dow = cursor.getUTCDay();
|
|
211
|
+
|
|
212
|
+
if (
|
|
213
|
+
matchField(min, minStr, 0, 59) &&
|
|
214
|
+
matchField(hour, hourStr, 0, 23) &&
|
|
215
|
+
matchField(dom, domStr, 1, 31) &&
|
|
216
|
+
matchField(month, monthStr, 1, 12) &&
|
|
217
|
+
matchField(dow, dowStr, 0, 6)
|
|
218
|
+
) {
|
|
219
|
+
return cursor;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
cursor = new Date(cursor.getTime() + 60_000);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return { error: "No next cron occurrence found within search window" };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function parseSchedule(spec: string): ScheduleSpec | { error: string } {
|
|
229
|
+
const trimmed = spec.trim();
|
|
230
|
+
if (/^\d{4}-\d{2}-\d{2}T/.test(trimmed)) {
|
|
231
|
+
const d = new Date(trimmed);
|
|
232
|
+
if (!Number.isNaN(d.getTime())) {
|
|
233
|
+
return { kind: "once", spec: trimmed };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (/^\+\d+(s|m|h|d)$/.test(trimmed)) {
|
|
237
|
+
return { kind: "once", spec: trimmed };
|
|
238
|
+
}
|
|
239
|
+
const ivlMs = parseIntervalMs(trimmed);
|
|
240
|
+
if (ivlMs !== undefined && ivlMs > 0) {
|
|
241
|
+
return { kind: "interval", spec: trimmed };
|
|
242
|
+
}
|
|
243
|
+
const fields = trimmed.split(/\s+/);
|
|
244
|
+
if (fields.length >= 5) {
|
|
245
|
+
return { kind: "cron", spec: trimmed };
|
|
246
|
+
}
|
|
247
|
+
return { error: `Invalid schedule "${spec}". Use "5m", "+10m", ISO timestamp, or cron expression.` };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function nextRunTime(spec: ScheduleSpec, from: Date = new Date()): Date | { error: string } {
|
|
251
|
+
if (spec.kind === "once") {
|
|
252
|
+
const rel = spec.spec.match(/^\+(\d+)(s|m|h|d)$/);
|
|
253
|
+
if (rel) {
|
|
254
|
+
const ms = parseInt(rel[1], 10) * { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[rel[2] as "s" | "m" | "h" | "d"];
|
|
255
|
+
return new Date(from.getTime() + ms);
|
|
256
|
+
}
|
|
257
|
+
const d = new Date(spec.spec);
|
|
258
|
+
if (Number.isNaN(d.getTime())) {
|
|
259
|
+
return { error: `Invalid once schedule: ${spec.spec}` };
|
|
260
|
+
}
|
|
261
|
+
return d;
|
|
262
|
+
}
|
|
263
|
+
if (spec.kind === "interval") {
|
|
264
|
+
const ms = parseIntervalMs(spec.spec);
|
|
265
|
+
if (ms === undefined || ms <= 0) {
|
|
266
|
+
return { error: `Invalid interval: ${spec.spec}` };
|
|
267
|
+
}
|
|
268
|
+
return new Date(from.getTime() + ms);
|
|
269
|
+
}
|
|
270
|
+
if (spec.kind === "cron") {
|
|
271
|
+
const next = nextCronDate(spec.spec, from);
|
|
272
|
+
if (!next || !(next instanceof Date)) {
|
|
273
|
+
return (next as { error: string } | null) ?? { error: "Invalid cron expression" };
|
|
274
|
+
}
|
|
275
|
+
return next;
|
|
276
|
+
}
|
|
277
|
+
return { error: `Unknown schedule kind: ${(spec as unknown as Record<string, unknown>).kind}` };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function humanizeSchedule(spec: ScheduleSpec): string {
|
|
281
|
+
if (spec.kind === "once") {
|
|
282
|
+
if (/^\+\d+(s|m|h|d)$/.test(spec.spec)) {
|
|
283
|
+
return `once in ${spec.spec.slice(1)}`;
|
|
284
|
+
}
|
|
285
|
+
return `once at ${spec.spec}`;
|
|
286
|
+
}
|
|
287
|
+
if (spec.kind === "interval") {
|
|
288
|
+
return `every ${spec.spec}`;
|
|
289
|
+
}
|
|
290
|
+
if (spec.kind === "cron") {
|
|
291
|
+
return `cron ${spec.spec}`;
|
|
292
|
+
}
|
|
293
|
+
return "unknown schedule";
|
|
294
|
+
}
|