pi-harness-runtime 0.9.25 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -7
- package/README.md +173 -51
- package/harness/auto-compact.ts +1 -1
- package/harness/autonomous-review.ts +88 -0
- package/harness/context-compact-orchestrator.ts +23 -23
- package/harness/context-window-manager.ts +5 -5
- package/harness/continue-prompt.ts +3 -3
- package/harness/e2e/minimax-quota-scraper.ts +5 -5
- package/harness/e2e/openai-quota-scraper.ts +13 -8
- package/harness/forked-summarizer.ts +4 -4
- package/harness/herdr-agents.ts +91 -0
- package/harness/index.ts +5 -5
- package/harness/loop-code-agent.ts +284 -0
- package/harness/loop-controller.ts +201 -0
- package/harness/loop-review-agent.ts +295 -0
- package/harness/loop-runtime.ts +10 -10
- package/harness/loop-types.ts +118 -0
- package/harness/notification-events.ts +3 -3
- package/harness/output-limit-handler.ts +1 -1
- package/harness/partial-recovery.ts +3 -3
- package/harness/session-memory.ts +5 -5
- package/index.ts +139 -34
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/a2a-adapter/src/types.ts +5 -5
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/dist/inbox.d.ts +3 -3
- package/packages/autonomous-runtime/dist/inbox.js +12 -12
- package/packages/autonomous-runtime/dist/lease.js +14 -14
- package/packages/autonomous-runtime/dist/types.js +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/autonomous-runtime/src/inbox.ts +12 -12
- package/packages/autonomous-runtime/src/lease.ts +15 -15
- package/packages/autonomous-runtime/src/types.ts +13 -13
- package/packages/autonomous-runtime/tsconfig.tsbuildinfo +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/dist/types.js +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/capability-registry/src/types.ts +5 -5
- package/packages/checkpoint/README.md +6 -6
- package/packages/checkpoint/dist/differ.js +2 -2
- package/packages/checkpoint/dist/engine.js +3 -3
- package/packages/checkpoint/dist/index.js +5 -5
- package/packages/checkpoint/dist/storage.js +3 -3
- package/packages/checkpoint/dist/types.js +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/checkpoint/src/differ.ts +2 -2
- package/packages/checkpoint/src/engine.ts +3 -3
- package/packages/checkpoint/src/index.ts +5 -5
- package/packages/checkpoint/src/storage.ts +3 -3
- package/packages/checkpoint/src/types.ts +10 -10
- package/packages/checkpoint/tsconfig.tsbuildinfo +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/dist/src/clipboard.d.ts.map +1 -1
- package/packages/clipboard/dist/src/clipboard.js +15 -7
- package/packages/clipboard/dist/src/clipboard.js.map +1 -1
- package/packages/clipboard/dist/src/copy-sync.d.ts +27 -0
- package/packages/clipboard/dist/src/copy-sync.d.ts.map +1 -0
- package/packages/clipboard/dist/src/copy-sync.js +170 -0
- package/packages/clipboard/dist/src/copy-sync.js.map +1 -0
- package/packages/clipboard/dist/src/gist-relay.d.ts +48 -0
- package/packages/clipboard/dist/src/gist-relay.d.ts.map +1 -0
- package/packages/clipboard/dist/src/gist-relay.js +186 -0
- package/packages/clipboard/dist/src/gist-relay.js.map +1 -0
- package/packages/clipboard/dist/src/github-login.d.ts +16 -0
- package/packages/clipboard/dist/src/github-login.d.ts.map +1 -0
- package/packages/clipboard/dist/src/github-login.js +69 -0
- package/packages/clipboard/dist/src/github-login.js.map +1 -0
- package/packages/clipboard/dist/src/index.d.ts +2 -0
- package/packages/clipboard/dist/src/index.d.ts.map +1 -1
- package/packages/clipboard/dist/src/index.js +2 -1
- package/packages/clipboard/dist/src/index.js.map +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard/src/clipboard.ts +16 -9
- package/packages/clipboard/src/copy-sync.ts +201 -0
- package/packages/clipboard/src/gist-relay.ts +240 -0
- package/packages/clipboard/src/github-login.ts +107 -0
- package/packages/clipboard/src/index.ts +2 -0
- package/packages/clipboard/test/mojibake.test.mjs +143 -0
- package/packages/code-generation/README.md +13 -13
- package/packages/code-generation/dist/faker.js +4 -4
- package/packages/code-generation/dist/generator.js +3 -3
- package/packages/code-generation/dist/index.js +3 -3
- package/packages/code-generation/dist/templates/registry.js +1 -1
- package/packages/code-generation/dist/types.js +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-generation/src/faker.ts +5 -5
- package/packages/code-generation/src/generator.ts +4 -4
- package/packages/code-generation/src/index.ts +3 -3
- package/packages/code-generation/src/templates/registry.ts +1 -1
- package/packages/code-generation/src/types.ts +10 -10
- package/packages/code-generation/tsconfig.tsbuildinfo +1 -1
- package/packages/code-review/README.md +13 -13
- package/packages/code-review/dist/engine.js +3 -3
- package/packages/code-review/dist/index.js +3 -3
- package/packages/code-review/dist/types.js +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/code-review/src/engine.ts +3 -3
- package/packages/code-review/src/index.ts +3 -3
- package/packages/code-review/src/types.ts +10 -10
- package/packages/code-review/tsconfig.tsbuildinfo +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/context-compiler/dist/compiler.js +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-compiler/src/compiler.ts +1 -1
- package/packages/context-compiler/src/types.ts +11 -11
- package/packages/context-compiler/test/context-compiler.test.ts +11 -11
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/README.md +27 -27
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/cost-optimizer/src/types.ts +5 -5
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/dist/analyzer.js +7 -7
- package/packages/django-plugin/package.json +1 -1
- package/packages/django-plugin/src/analyzer.ts +7 -7
- package/packages/doc-generator/package.json +1 -1
- package/packages/evaluation-engine/dist/engine.js +9 -9
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-engine/src/engine.ts +9 -9
- package/packages/evaluation-engine/src/types.ts +3 -3
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/dist/src/herdr-bus.d.ts +121 -0
- package/packages/event-bus/dist/src/herdr-bus.d.ts.map +1 -0
- package/packages/event-bus/dist/src/herdr-bus.js +322 -0
- package/packages/event-bus/dist/src/herdr-bus.js.map +1 -0
- package/packages/event-bus/dist/src/index.d.ts +2 -0
- package/packages/event-bus/dist/src/index.d.ts.map +1 -1
- package/packages/event-bus/dist/src/index.js +1 -0
- package/packages/event-bus/dist/src/index.js.map +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-bus/src/herdr-bus.ts +525 -0
- package/packages/event-bus/src/index.ts +29 -0
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/dist/replay.js +7 -7
- package/packages/experience-replay/package.json +1 -1
- package/packages/experience-replay/src/replay.ts +7 -7
- package/packages/experience-replay/src/types.ts +5 -5
- package/packages/feedback-collector/package.json +1 -1
- package/packages/framework-detector/README.md +18 -18
- package/packages/framework-detector/dist/detector.js +3 -3
- package/packages/framework-detector/dist/index.js +3 -3
- package/packages/framework-detector/dist/signatures/registry.js +24 -24
- package/packages/framework-detector/dist/types.js +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-detector/src/detector.ts +3 -3
- package/packages/framework-detector/src/index.ts +3 -3
- package/packages/framework-detector/src/signatures/registry.ts +24 -24
- package/packages/framework-detector/src/types.ts +7 -7
- package/packages/framework-detector/tsconfig.tsbuildinfo +1 -1
- package/packages/framework-plugin-sdk/README.md +23 -23
- package/packages/framework-plugin-sdk/dist/index.js +2 -2
- package/packages/framework-plugin-sdk/dist/manager.js +3 -3
- package/packages/framework-plugin-sdk/dist/types.js +2 -2
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/framework-plugin-sdk/src/index.ts +2 -2
- package/packages/framework-plugin-sdk/src/manager.ts +3 -3
- package/packages/framework-plugin-sdk/src/types.ts +10 -10
- package/packages/framework-plugin-sdk/tsconfig.tsbuildinfo +1 -1
- package/packages/frappe-plugin/dist/analyzer.js +8 -8
- package/packages/frappe-plugin/dist/types.js +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/frappe-plugin/src/analyzer.ts +8 -8
- package/packages/frappe-plugin/src/types.ts +10 -10
- package/packages/generic-web-plugin/dist/analyzer.js +5 -5
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/generic-web-plugin/src/analyzer.ts +5 -5
- package/packages/health-monitor/package.json +1 -1
- package/packages/health-monitor/src/monitor.ts +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/laravel-plugin/dist/analyzer.js +11 -11
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/laravel-plugin/src/analyzer.ts +11 -11
- package/packages/learning-engine/dist/engine.js +5 -5
- package/packages/learning-engine/dist/types.js +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/learning-engine/src/engine.ts +5 -5
- package/packages/learning-engine/src/types.ts +6 -6
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/dist/engine.js +8 -8
- package/packages/memory-engine/dist/types.js +2 -2
- package/packages/memory-engine/package.json +1 -1
- package/packages/memory-engine/src/engine.ts +8 -8
- package/packages/memory-engine/src/types.ts +9 -9
- package/packages/model-registry/package.json +1 -1
- package/packages/model-registry/src/types.ts +4 -4
- package/packages/nextjs-plugin/dist/analyzer.js +9 -9
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/nextjs-plugin/src/analyzer.ts +9 -9
- package/packages/notification/dist/notification-center.js +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/notification/src/notification-center.ts +1 -1
- package/packages/observability/dist/alerts.js +2 -2
- package/packages/observability/dist/health.js +4 -4
- package/packages/observability/dist/index.js +6 -6
- package/packages/observability/dist/logger.js +7 -7
- package/packages/observability/dist/metrics.js +11 -11
- package/packages/observability/dist/tracer.js +3 -3
- package/packages/observability/dist/types.js +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/observability/src/alerts.ts +2 -2
- package/packages/observability/src/health.ts +4 -4
- package/packages/observability/src/index.ts +6 -6
- package/packages/observability/src/logger.ts +7 -7
- package/packages/observability/src/metrics.ts +11 -11
- package/packages/observability/src/tracer.ts +3 -3
- package/packages/observability/src/types.ts +7 -7
- package/packages/observability/test/observability.test.ts +4 -4
- package/packages/observability/tsconfig.tsbuildinfo +1 -1
- package/packages/performance-optimizer/dist/optimizer.js +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/performance-optimizer/src/optimizer.ts +1 -1
- package/packages/performance-optimizer/src/types.ts +4 -4
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/dist/analyzer.js +4 -4
- package/packages/project-analyzer/dist/index.js +8 -8
- package/packages/project-analyzer/dist/signals.js +3 -3
- package/packages/project-analyzer/dist/types.js +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-analyzer/src/analyzer.ts +4 -4
- package/packages/project-analyzer/src/index.ts +9 -9
- package/packages/project-analyzer/src/signals.ts +4 -4
- package/packages/project-analyzer/src/types.ts +15 -15
- package/packages/project-analyzer/test/index.test.ts +17 -17
- package/packages/project-analyzer/tsconfig.tsbuildinfo +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/dist/section-builder.js +1 -1
- package/packages/prompt-compiler/dist/types.js +3 -3
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-compiler/src/section-builder.ts +1 -1
- package/packages/prompt-compiler/src/types.ts +12 -12
- package/packages/prompt-compiler/test/prompt-compiler.test.ts +12 -12
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/dist/builder.js +1 -1
- package/packages/provider-adapter-sdk/dist/index.js +5 -5
- package/packages/provider-adapter-sdk/dist/registry.js +1 -1
- package/packages/provider-adapter-sdk/dist/tester.js +1 -1
- package/packages/provider-adapter-sdk/dist/types.js +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-adapter-sdk/src/builder.ts +1 -1
- package/packages/provider-adapter-sdk/src/index.ts +6 -6
- package/packages/provider-adapter-sdk/src/registry.ts +1 -1
- package/packages/provider-adapter-sdk/src/tester.ts +1 -1
- package/packages/provider-adapter-sdk/src/types.ts +5 -5
- package/packages/provider-adapter-sdk/tsconfig.tsbuildinfo +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-router/src/types.ts +4 -4
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/dist/deepagents-adapter.js +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/providers/src/deepagents-adapter.ts +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/dist/analyzer.js +7 -7
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/react-vite-plugin/src/analyzer.ts +7 -7
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/dist/compiler.js +17 -17
- package/packages/requirement-compiler/dist/types.js +2 -2
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/requirement-compiler/src/compiler.ts +17 -17
- package/packages/requirement-compiler/src/types.ts +21 -21
- package/packages/requirement-compiler/test/requirement-compiler.test.ts +17 -17
- package/packages/runtime/dist/command-executor.js +1 -1
- package/packages/runtime/dist/local-runtime-agent.js +1 -1
- package/packages/runtime/dist/policy-engine.js +1 -1
- package/packages/runtime/dist/runtime-api.js +1 -1
- package/packages/runtime/dist/workspace-manager.js +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/runtime/src/command-executor.ts +1 -1
- package/packages/runtime/src/local-runtime-agent.ts +1 -1
- package/packages/runtime/src/policy-engine.ts +1 -1
- package/packages/runtime/src/runtime-api.ts +1 -1
- package/packages/runtime/src/workspace-manager.ts +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/README.md +18 -18
- package/packages/session/dist/history.js +3 -3
- package/packages/session/dist/index.js +5 -5
- package/packages/session/dist/manager.js +3 -3
- package/packages/session/dist/policy.js +2 -2
- package/packages/session/dist/store.js +2 -2
- package/packages/session/dist/types.js +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session/src/history.ts +3 -3
- package/packages/session/src/index.ts +5 -5
- package/packages/session/src/manager.ts +3 -3
- package/packages/session/src/policy.ts +4 -4
- package/packages/session/src/store.ts +3 -3
- package/packages/session/src/types.ts +10 -10
- package/packages/session/tsconfig.tsbuildinfo +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skill-registry/src/types.ts +4 -4
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/dist/compiler.js +7 -7
- package/packages/task-compiler/dist/decompose.js +11 -11
- package/packages/task-compiler/dist/file-scope.js +6 -6
- package/packages/task-compiler/dist/graph.js +6 -6
- package/packages/task-compiler/dist/index.js +2 -2
- package/packages/task-compiler/dist/types.js +5 -5
- package/packages/task-compiler/dist/validate.js +2 -2
- package/packages/task-compiler/dist/verification.js +2 -2
- package/packages/task-compiler/package.json +1 -1
- package/packages/task-compiler/src/command-policy.ts +1 -1
- package/packages/task-compiler/src/compiler.ts +7 -7
- package/packages/task-compiler/src/decompose.ts +11 -11
- package/packages/task-compiler/src/file-scope.ts +6 -6
- package/packages/task-compiler/src/graph.ts +6 -6
- package/packages/task-compiler/src/index.ts +3 -3
- package/packages/task-compiler/src/types.ts +14 -14
- package/packages/task-compiler/src/validate.ts +2 -2
- package/packages/task-compiler/src/verification.ts +2 -2
- package/packages/task-compiler/test/task-compiler.test.ts +10 -10
- package/packages/test-data-generator/README.md +13 -13
- package/packages/test-data-generator/dist/faker-adapter.js +4 -4
- package/packages/test-data-generator/dist/generator.js +3 -3
- package/packages/test-data-generator/dist/index.js +3 -3
- package/packages/test-data-generator/dist/types.js +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-data-generator/src/faker-adapter.ts +4 -4
- package/packages/test-data-generator/src/generator.ts +3 -3
- package/packages/test-data-generator/src/index.ts +3 -3
- package/packages/test-data-generator/src/types.ts +9 -9
- package/packages/test-data-generator/tsconfig.tsbuildinfo +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +22 -0
- package/packages/todo-bd-sync/src/detector.ts +150 -0
- package/packages/todo-bd-sync/src/extension.ts +101 -0
- package/packages/todo-bd-sync/src/index.ts +401 -0
- package/packages/todo-bd-sync/src/sync.ts +322 -0
- package/packages/todo-bd-sync/src/todo-reminder.ts +418 -0
- package/packages/todo-bd-sync/src/types.ts +137 -0
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/types/src/agent-worker-types.ts +5 -5
- package/packages/types/src/runtime-types.ts +12 -12
- package/packages/types/tsconfig.tsbuildinfo +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/renderer.ts +7 -7
- package/skills/harness-runtime/SKILL.md +15 -15
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loop Controller — blackboard-coordinated write-review loops.
|
|
3
|
+
*
|
|
4
|
+
* Human types /review-loop <write_count> <review_count> --prompt="..."
|
|
5
|
+
* Controller creates blackboard, sets first nextAction, monitors progress.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { writeFileSync } from "fs";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
import { randomUUID } from "crypto";
|
|
11
|
+
import {
|
|
12
|
+
createHerdrBus,
|
|
13
|
+
ensureHerdrWorkspace,
|
|
14
|
+
publishLoopStarted,
|
|
15
|
+
type LoopConfig,
|
|
16
|
+
} from "../packages/event-bus/src/index.js";
|
|
17
|
+
import { createBlackboard } from "./blackboard.js";
|
|
18
|
+
import { buildLoopTaskGraph } from "./loop-types.js";
|
|
19
|
+
import type { SharedBlackboard } from "./blackboard.js";
|
|
20
|
+
|
|
21
|
+
function encodeNextAction(la: {
|
|
22
|
+
taskId: string;
|
|
23
|
+
agentType: string;
|
|
24
|
+
iteration: number;
|
|
25
|
+
prompt?: string;
|
|
26
|
+
codeFiles?: string[];
|
|
27
|
+
}) {
|
|
28
|
+
return {
|
|
29
|
+
taskId: la.taskId,
|
|
30
|
+
instruction: "LOOP:" + JSON.stringify(la),
|
|
31
|
+
priority: "high" as const,
|
|
32
|
+
createdAt: new Date().toISOString(),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function sleep(ms: number): Promise<void> {
|
|
37
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Monitor ────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
async function monitorLoop(
|
|
43
|
+
blackboard: SharedBlackboard,
|
|
44
|
+
loopId: string,
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
const paths = ensureHerdrWorkspace();
|
|
47
|
+
|
|
48
|
+
while (true) {
|
|
49
|
+
blackboard.load();
|
|
50
|
+
const record = blackboard.getRecord();
|
|
51
|
+
if (!record) {
|
|
52
|
+
await sleep(500);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check if report is done
|
|
57
|
+
const reportNode = record.tasks.nodes["report"];
|
|
58
|
+
if (reportNode?.status === "done") {
|
|
59
|
+
const reviews = Object.values(record.tasks.nodes).filter(
|
|
60
|
+
(n) => n.id.startsWith("review-") && n.status === "done",
|
|
61
|
+
);
|
|
62
|
+
const lastReview = reviews.sort((a, b) =>
|
|
63
|
+
(b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""),
|
|
64
|
+
)[0];
|
|
65
|
+
const verdict = (lastReview as { result?: string }).result ?? "unknown";
|
|
66
|
+
const summary =
|
|
67
|
+
verdict !== "unknown" ? `Verdict: ${verdict}` : "Loop completed";
|
|
68
|
+
|
|
69
|
+
const reportPath = join(
|
|
70
|
+
paths.reviews,
|
|
71
|
+
`loop-${loopId.slice(0, 8)}-final.md`,
|
|
72
|
+
);
|
|
73
|
+
writeFileSync(
|
|
74
|
+
reportPath,
|
|
75
|
+
`# Loop Result — ${loopId.slice(0, 8)}
|
|
76
|
+
|
|
77
|
+
## Result
|
|
78
|
+
${summary}
|
|
79
|
+
|
|
80
|
+
## Verdict: **${verdict.toUpperCase()}**
|
|
81
|
+
`,
|
|
82
|
+
);
|
|
83
|
+
console.log(`[controller] VERDICT: ${verdict.toUpperCase()}`);
|
|
84
|
+
console.log(`[controller] Report: ${reportPath}`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check for blocked
|
|
89
|
+
const blocked = Object.values(record.tasks.nodes).find(
|
|
90
|
+
(n) => n.status === "blocked",
|
|
91
|
+
);
|
|
92
|
+
if (blocked) {
|
|
93
|
+
const reportPath = join(
|
|
94
|
+
paths.reviews,
|
|
95
|
+
`loop-${loopId.slice(0, 8)}-final.md`,
|
|
96
|
+
);
|
|
97
|
+
writeFileSync(
|
|
98
|
+
reportPath,
|
|
99
|
+
`# Loop Result — ${loopId.slice(0, 8)}
|
|
100
|
+
|
|
101
|
+
## Result
|
|
102
|
+
Blocked at: ${blocked.id}
|
|
103
|
+
|
|
104
|
+
## Verdict: **BLOCKED**
|
|
105
|
+
`,
|
|
106
|
+
);
|
|
107
|
+
console.log(`[controller] VERDICT: BLOCKED (${blocked.id})`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
await sleep(500);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─── Run ──────────────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
async function runLoop(args: string[]): Promise<void> {
|
|
118
|
+
const writeCount = parseInt(args[0] ?? "3", 10);
|
|
119
|
+
const reviewCount = parseInt(args[1] ?? "2", 10);
|
|
120
|
+
const prompt =
|
|
121
|
+
(args.find((a) => a.startsWith("--prompt=")) ?? "")
|
|
122
|
+
.slice("--prompt=".length)
|
|
123
|
+
.replace(/^["']|["']$/g, "") || "No prompt";
|
|
124
|
+
|
|
125
|
+
if (isNaN(writeCount) || writeCount < 1 || writeCount > 20) {
|
|
126
|
+
console.error("write_count must be 1-20");
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
if (isNaN(reviewCount) || reviewCount < 0 || reviewCount > 20) {
|
|
130
|
+
console.error("review_count must be 0-20");
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const loopId = randomUUID();
|
|
135
|
+
const paths = ensureHerdrWorkspace();
|
|
136
|
+
const taskGraph = buildLoopTaskGraph(writeCount, reviewCount, loopId);
|
|
137
|
+
|
|
138
|
+
console.log("=".repeat(60));
|
|
139
|
+
console.log(`LOOP STARTED — ${loopId.slice(0, 8)}`);
|
|
140
|
+
console.log(` Writes: ${writeCount} Reviews: ${reviewCount}`);
|
|
141
|
+
console.log(` Prompt: ${prompt}`);
|
|
142
|
+
console.log("=".repeat(60));
|
|
143
|
+
|
|
144
|
+
// Create blackboard
|
|
145
|
+
const blackboard = createBlackboard(loopId, paths.root, taskGraph);
|
|
146
|
+
console.log(`[controller] Blackboard: ${blackboard.getPath()}`);
|
|
147
|
+
|
|
148
|
+
// Event bus for cross-process signaling
|
|
149
|
+
const bus = createHerdrBus(`ctrl-${loopId.slice(0, 8)}`);
|
|
150
|
+
|
|
151
|
+
// Save config
|
|
152
|
+
// Clean old loop configs
|
|
153
|
+
for (const f of (await import("fs")).readdirSync(paths.root)) {
|
|
154
|
+
if (f.startsWith("loop-") && f.endsWith(".config.json")) {
|
|
155
|
+
(await import("fs")).unlinkSync(join(paths.root, f));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const config: LoopConfig = {
|
|
160
|
+
loopId,
|
|
161
|
+
writeCount,
|
|
162
|
+
reviewCount,
|
|
163
|
+
prompt,
|
|
164
|
+
nextReviewAfter: Math.max(1, Math.floor(writeCount / (reviewCount || 1))),
|
|
165
|
+
createdAt: new Date().toISOString(),
|
|
166
|
+
};
|
|
167
|
+
writeFileSync(
|
|
168
|
+
join(paths.root, `loop-${loopId}.config.json`),
|
|
169
|
+
JSON.stringify(config, null, 2),
|
|
170
|
+
);
|
|
171
|
+
publishLoopStarted(bus, config);
|
|
172
|
+
|
|
173
|
+
// Set first action: code agent writes iteration 1
|
|
174
|
+
blackboard.setNextAction(
|
|
175
|
+
encodeNextAction({
|
|
176
|
+
taskId: "write-1",
|
|
177
|
+
agentType: "code",
|
|
178
|
+
iteration: 1,
|
|
179
|
+
prompt,
|
|
180
|
+
}),
|
|
181
|
+
);
|
|
182
|
+
console.log("[controller] nextAction: code write-1");
|
|
183
|
+
|
|
184
|
+
// Monitor until done
|
|
185
|
+
await monitorLoop(blackboard, loopId);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ─── CLI ──────────────────────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
const args = process.argv.slice(2);
|
|
191
|
+
if (!args.length || args[0] === "--help" || args[0] === "-h") {
|
|
192
|
+
console.log(
|
|
193
|
+
'Usage: bun harness/loop-controller.ts <write_count> <review_count> --prompt="..."\nExample: bun harness/loop-controller.ts 3 2 --prompt="build REST API"',
|
|
194
|
+
);
|
|
195
|
+
process.exit(0);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
runLoop(args).catch((err) => {
|
|
199
|
+
console.error("[controller] Fatal:", err);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
});
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loop Review Agent (GPT) — coordinates via blackboard.
|
|
3
|
+
*
|
|
4
|
+
* Polls blackboard for nextAction where agentType="review".
|
|
5
|
+
* Reviews code, writes verdict, updates node status.
|
|
6
|
+
*
|
|
7
|
+
* Usage: bun harness/loop-review-agent.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
import {
|
|
13
|
+
ensureHerdrWorkspace,
|
|
14
|
+
parseVerdict,
|
|
15
|
+
} from "../packages/event-bus/src/herdr-bus.js";
|
|
16
|
+
import { SharedBlackboard } from "./blackboard.js";
|
|
17
|
+
import type {
|
|
18
|
+
LoopNextAction,
|
|
19
|
+
LoopAgentReport,
|
|
20
|
+
LoopVerdict,
|
|
21
|
+
} from "./loop-types.js";
|
|
22
|
+
|
|
23
|
+
const AGENT_ID = "review-agent";
|
|
24
|
+
const AGENT_TYPE = "review";
|
|
25
|
+
const POLL_MS = 1000;
|
|
26
|
+
const REVIEW_TIMEOUT_MS = 5 * 60 * 1000;
|
|
27
|
+
|
|
28
|
+
async function main(): Promise<void> {
|
|
29
|
+
console.log(`[${AGENT_ID}] Starting...`);
|
|
30
|
+
const paths = ensureHerdrWorkspace();
|
|
31
|
+
|
|
32
|
+
// Find active loop config
|
|
33
|
+
const loopId = await findActiveLoop(paths.root);
|
|
34
|
+
if (!loopId) console.log(`[${AGENT_ID}] No active loop found. Waiting...`);
|
|
35
|
+
|
|
36
|
+
const blackboard = new SharedBlackboard(loopId ?? "no-loop", paths.root);
|
|
37
|
+
blackboard.load();
|
|
38
|
+
blackboard.registerAgent(AGENT_ID, "Review Agent", "openai", "gpt-4o");
|
|
39
|
+
blackboard.updateAgentStatus(AGENT_ID, "idle");
|
|
40
|
+
console.log(`[${AGENT_ID}] Registered. Blackboard: ${blackboard.getPath()}`);
|
|
41
|
+
|
|
42
|
+
while (true) {
|
|
43
|
+
await sleep(POLL_MS);
|
|
44
|
+
|
|
45
|
+
blackboard.load();
|
|
46
|
+
const record = blackboard.getRecord();
|
|
47
|
+
if (!record) continue;
|
|
48
|
+
if (record.jobId !== loopId) continue;
|
|
49
|
+
|
|
50
|
+
// Check for early exit
|
|
51
|
+
const earlyExit = checkEarlyExit(record);
|
|
52
|
+
if (earlyExit) {
|
|
53
|
+
console.log(`[${AGENT_ID}] Loop ended: ${earlyExit}`);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check nextAction for review agent
|
|
58
|
+
const action = parseNextAction(record.nextAction);
|
|
59
|
+
if (!action || action.agentType !== AGENT_TYPE) continue;
|
|
60
|
+
|
|
61
|
+
// Claim the task
|
|
62
|
+
const locked = blackboard.acquireLock(action.taskId, AGENT_ID);
|
|
63
|
+
if (!locked) continue;
|
|
64
|
+
|
|
65
|
+
blackboard.updateAgentStatus(AGENT_ID, "working", action.taskId);
|
|
66
|
+
console.log(`[${AGENT_ID}] Claimed: ${action.taskId}`);
|
|
67
|
+
|
|
68
|
+
// Handle report task
|
|
69
|
+
if (action.taskId === "report") {
|
|
70
|
+
await finishReport(blackboard, record);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Do the review
|
|
75
|
+
const verdict = await doReview(action, paths, record.jobId);
|
|
76
|
+
|
|
77
|
+
// Update task status
|
|
78
|
+
updateTaskStatus(blackboard, record, action.taskId, verdict);
|
|
79
|
+
|
|
80
|
+
// Release and report
|
|
81
|
+
blackboard.releaseLock(action.taskId, AGENT_ID);
|
|
82
|
+
blackboard.writeReport({
|
|
83
|
+
agentId: AGENT_ID,
|
|
84
|
+
taskId: action.taskId,
|
|
85
|
+
status: "success",
|
|
86
|
+
message: verdict,
|
|
87
|
+
} as unknown as Parameters<typeof blackboard.writeReport>[0]);
|
|
88
|
+
blackboard.updateAgentStatus(AGENT_ID, "idle");
|
|
89
|
+
|
|
90
|
+
// Decide next action based on verdict
|
|
91
|
+
if (verdict === "approved" || verdict === "blocked") {
|
|
92
|
+
// Early exit — go to report
|
|
93
|
+
blackboard.setNextAction(
|
|
94
|
+
encodeNextAction({
|
|
95
|
+
taskId: "report",
|
|
96
|
+
agentType: "review",
|
|
97
|
+
iteration: 0,
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
100
|
+
console.log(
|
|
101
|
+
`[${AGENT_ID}] ${verdict.toUpperCase()} — nextAction: report`,
|
|
102
|
+
);
|
|
103
|
+
} else {
|
|
104
|
+
// Changes requested — continue to next write
|
|
105
|
+
const nextWrite = getNextWriteTask(record, action.iteration);
|
|
106
|
+
if (nextWrite) {
|
|
107
|
+
blackboard.setNextAction(
|
|
108
|
+
encodeNextAction({
|
|
109
|
+
taskId: nextWrite,
|
|
110
|
+
agentType: "code",
|
|
111
|
+
iteration: getWriteIteration(nextWrite),
|
|
112
|
+
prompt: action.prompt,
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
console.log(`[${AGENT_ID}] CHANGES — nextAction: code ${nextWrite}`);
|
|
116
|
+
} else {
|
|
117
|
+
blackboard.setNextAction({
|
|
118
|
+
taskId: "report",
|
|
119
|
+
agentType: "review",
|
|
120
|
+
iteration: 0,
|
|
121
|
+
} as unknown as ReturnType<typeof encodeNextAction>);
|
|
122
|
+
console.log(`[${AGENT_ID}] CHANGES — nextAction: report`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ─── Review Logic ────────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
async function doReview(
|
|
131
|
+
action: LoopNextAction,
|
|
132
|
+
paths: { reviews: string },
|
|
133
|
+
loopId: string,
|
|
134
|
+
): Promise<LoopVerdict> {
|
|
135
|
+
const reviewDir = join(paths.reviews, loopId);
|
|
136
|
+
mkdirSync(reviewDir, { recursive: true });
|
|
137
|
+
|
|
138
|
+
const reviewFile = join(reviewDir, `review-${action.iteration}.md`);
|
|
139
|
+
|
|
140
|
+
// Read code files
|
|
141
|
+
const codeBlocks = await Promise.all(
|
|
142
|
+
(action.codeFiles ?? []).map(async (file) => {
|
|
143
|
+
const content = existsSync(file)
|
|
144
|
+
? readFileSync(file, "utf-8").slice(0, 3000)
|
|
145
|
+
: `[Not found: ${file}]`;
|
|
146
|
+
return `## ${file}\n\n\`\`\`\n${content}\n\`\`\`\n`;
|
|
147
|
+
}),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// Write review stub
|
|
151
|
+
const review = `# Review — Iteration ${action.iteration}
|
|
152
|
+
|
|
153
|
+
> GPT tab: Edit this file and add your verdict at the bottom.
|
|
154
|
+
> Use: \`## Verdict: APPROVED\`, \`## Verdict: CHANGES_REQUESTED\`, or \`## Verdict: BLOCKED\`
|
|
155
|
+
> GPT tab has ${REVIEW_TIMEOUT_MS / 1000 / 60} minutes to complete.
|
|
156
|
+
|
|
157
|
+
${codeBlocks.join("\n")}
|
|
158
|
+
`;
|
|
159
|
+
writeFileSync(reviewFile, review);
|
|
160
|
+
console.log(`[${AGENT_ID}] Review stub: ${reviewFile}`);
|
|
161
|
+
console.log(`[${AGENT_ID}] Waiting for GPT verdict...`);
|
|
162
|
+
|
|
163
|
+
// Poll for verdict
|
|
164
|
+
return await pollVerdict(reviewFile);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function pollVerdict(reviewFile: string): Promise<LoopVerdict> {
|
|
168
|
+
const deadline = Date.now() + REVIEW_TIMEOUT_MS;
|
|
169
|
+
while (Date.now() < deadline) {
|
|
170
|
+
if (existsSync(reviewFile)) {
|
|
171
|
+
const content = readFileSync(reviewFile, "utf-8");
|
|
172
|
+
const verdict = parseVerdict(content);
|
|
173
|
+
if (verdict) return verdict;
|
|
174
|
+
}
|
|
175
|
+
await sleep(3000);
|
|
176
|
+
}
|
|
177
|
+
console.log(
|
|
178
|
+
`[${AGENT_ID}] Verdict timeout — defaulting to CHANGES_REQUESTED`,
|
|
179
|
+
);
|
|
180
|
+
return "changes_requested";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function finishReport(
|
|
184
|
+
blackboard: SharedBlackboard,
|
|
185
|
+
record: { tasks: { nodes: Record<string, { status: string }> } },
|
|
186
|
+
): Promise<void> {
|
|
187
|
+
const reportNode = record.tasks.nodes["report"] as { status: string };
|
|
188
|
+
reportNode.status = "done";
|
|
189
|
+
blackboard.save();
|
|
190
|
+
console.log(`[${AGENT_ID}] Report complete. Loop finished.`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
async function findActiveLoop(rootDir: string): Promise<string | null> {
|
|
196
|
+
const { readdirSync } = await import("fs");
|
|
197
|
+
try {
|
|
198
|
+
const files = readdirSync(rootDir).filter(
|
|
199
|
+
(f) => f.startsWith("loop-") && f.endsWith(".config.json"),
|
|
200
|
+
);
|
|
201
|
+
if (files.length > 0) {
|
|
202
|
+
const config = JSON.parse(readFileSync(join(rootDir, files[0]), "utf-8"));
|
|
203
|
+
return config.loopId;
|
|
204
|
+
}
|
|
205
|
+
} catch {
|
|
206
|
+
// no loop yet
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function checkEarlyExit(record: {
|
|
212
|
+
tasks: { nodes: Record<string, { status: string }> };
|
|
213
|
+
jobId: string;
|
|
214
|
+
}): string | null {
|
|
215
|
+
const reportNode = record.tasks.nodes["report"];
|
|
216
|
+
if (
|
|
217
|
+
reportNode &&
|
|
218
|
+
(reportNode.status === "done" || reportNode.status === "blocked")
|
|
219
|
+
) {
|
|
220
|
+
return reportNode.status === "done" ? "finished" : "blocked";
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function updateTaskStatus(
|
|
226
|
+
blackboard: SharedBlackboard,
|
|
227
|
+
record: {
|
|
228
|
+
tasks: { nodes: Record<string, { status: string; result?: string }> };
|
|
229
|
+
},
|
|
230
|
+
taskId: string,
|
|
231
|
+
verdict: LoopVerdict,
|
|
232
|
+
): void {
|
|
233
|
+
const node = record.tasks.nodes[taskId] as
|
|
234
|
+
| { status: string; result?: string }
|
|
235
|
+
| undefined;
|
|
236
|
+
if (node) {
|
|
237
|
+
node.status = verdict === "blocked" ? "blocked" : "done";
|
|
238
|
+
node.result = verdict;
|
|
239
|
+
node.updatedAt = new Date().toISOString();
|
|
240
|
+
blackboard.save();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function encodeNextAction(action: Partial<LoopNextAction>) {
|
|
245
|
+
return {
|
|
246
|
+
taskId: action.taskId,
|
|
247
|
+
instruction: "LOOP:" + JSON.stringify(action),
|
|
248
|
+
priority: "high" as const,
|
|
249
|
+
createdAt: new Date().toISOString(),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function parseNextAction(nextAction: unknown): LoopNextAction | null {
|
|
254
|
+
if (!nextAction) return null;
|
|
255
|
+
const a = nextAction as Record<string, unknown>;
|
|
256
|
+
if (!a.taskId) return null;
|
|
257
|
+
if (typeof a.instruction === "string" && a.instruction.startsWith("LOOP:")) {
|
|
258
|
+
try {
|
|
259
|
+
return JSON.parse(a.instruction.slice(5)) as LoopNextAction;
|
|
260
|
+
} catch {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function getNextWriteTask(
|
|
268
|
+
record: { tasks: { nodes: Record<string, { status: string }> } },
|
|
269
|
+
currentReviewIteration: number,
|
|
270
|
+
): string | null {
|
|
271
|
+
const writeCount = Object.keys(record.tasks.nodes).filter((id) =>
|
|
272
|
+
id.startsWith("write-"),
|
|
273
|
+
).length;
|
|
274
|
+
// After review N, next write is N+1
|
|
275
|
+
const nextWrite = currentReviewIteration + 1;
|
|
276
|
+
const taskId = `write-${nextWrite}`;
|
|
277
|
+
if (
|
|
278
|
+
record.tasks.nodes[taskId] &&
|
|
279
|
+
record.tasks.nodes[taskId].status === "pending"
|
|
280
|
+
) {
|
|
281
|
+
return taskId;
|
|
282
|
+
}
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function getWriteIteration(taskId: string): number {
|
|
287
|
+
const m = taskId.match(/^write-(\d+)$/);
|
|
288
|
+
return m ? parseInt(m[1], 10) : 0;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function sleep(ms: number): Promise<void> {
|
|
292
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
main().catch(console.error);
|
package/harness/loop-runtime.ts
CHANGED
|
@@ -46,12 +46,12 @@ import {
|
|
|
46
46
|
createForkedSummarizer,
|
|
47
47
|
} from "./forked-summarizer.js";
|
|
48
48
|
|
|
49
|
-
//
|
|
49
|
+
// --- Constants ----------------------------------------------------------------
|
|
50
50
|
|
|
51
51
|
const DEFAULT_MAX_COMPACT_RETRIES = 3;
|
|
52
52
|
const DEFAULT_MAX_OUTPUT_TOKENS = 4096;
|
|
53
53
|
|
|
54
|
-
//
|
|
54
|
+
// --- Lifecycle Callbacks ----------------------------------------------------
|
|
55
55
|
|
|
56
56
|
export interface LoopCallbacks {
|
|
57
57
|
/** Fetch next task from queue */
|
|
@@ -97,7 +97,7 @@ export interface LoopCallbacks {
|
|
|
97
97
|
onCompaction?: (result: CompactResult) => void;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
//
|
|
100
|
+
// --- Loop Result -----------------------------------------------------------
|
|
101
101
|
|
|
102
102
|
export interface LoopResult {
|
|
103
103
|
success: boolean;
|
|
@@ -109,7 +109,7 @@ export interface LoopResult {
|
|
|
109
109
|
totalCompactions?: number;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
//
|
|
112
|
+
// --- Loop Runtime -----------------------------------------------------------
|
|
113
113
|
|
|
114
114
|
export class LoopRuntime {
|
|
115
115
|
private readonly config: Required<LoopConfig>;
|
|
@@ -221,12 +221,12 @@ export class LoopRuntime {
|
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
//
|
|
224
|
+
// --- Public API ---------------------------------------------------------
|
|
225
225
|
|
|
226
226
|
async run(): Promise<LoopResult> {
|
|
227
227
|
this.running = true;
|
|
228
228
|
|
|
229
|
-
//
|
|
229
|
+
// --- Auto-resume from quota pause ----------------------------------
|
|
230
230
|
const checkpoint = await this.callbacks.onGetCheckpoint?.();
|
|
231
231
|
if (checkpoint?.status === "paused_quota" && checkpoint.resumeAt) {
|
|
232
232
|
const resumeMs = new Date(checkpoint.resumeAt).getTime();
|
|
@@ -348,7 +348,7 @@ export class LoopRuntime {
|
|
|
348
348
|
return this.sessionMemory;
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
//
|
|
351
|
+
// --- Task Execution with Compact ---------------------------------------
|
|
352
352
|
|
|
353
353
|
private async executeTaskWithCompact(task: RuntimeTask): Promise<void> {
|
|
354
354
|
// 1. Pick model
|
|
@@ -690,7 +690,7 @@ export class LoopRuntime {
|
|
|
690
690
|
return parts.join("\n");
|
|
691
691
|
}
|
|
692
692
|
|
|
693
|
-
//
|
|
693
|
+
// --- Quota Handling ----------------------------------------------------
|
|
694
694
|
|
|
695
695
|
private async handleQuotaPause(): Promise<void> {
|
|
696
696
|
this.paused = true;
|
|
@@ -717,7 +717,7 @@ export class LoopRuntime {
|
|
|
717
717
|
await this.saveCheckpoint();
|
|
718
718
|
}
|
|
719
719
|
|
|
720
|
-
//
|
|
720
|
+
// --- Persistence --------------------------------------------------------
|
|
721
721
|
|
|
722
722
|
private async saveCheckpoint(): Promise<void> {
|
|
723
723
|
const now = new Date().toISOString();
|
|
@@ -736,7 +736,7 @@ export class LoopRuntime {
|
|
|
736
736
|
await this.callbacks.onCheckpoint?.(checkpoint);
|
|
737
737
|
}
|
|
738
738
|
|
|
739
|
-
//
|
|
739
|
+
// --- Resume from Checkpoint --------------------------------------------
|
|
740
740
|
|
|
741
741
|
async resumeFromCheckpoint(
|
|
742
742
|
checkpoint: RuntimeCheckpoint,
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for loop coordination via blackboard.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
TaskGraph,
|
|
7
|
+
TaskNode,
|
|
8
|
+
} from "../packages/types/src/runtime-types.ts";
|
|
9
|
+
|
|
10
|
+
export type LoopVerdict = "approved" | "changes_requested" | "blocked";
|
|
11
|
+
export type LoopNodeType = "write" | "review" | "decide" | "report";
|
|
12
|
+
export type LoopNodeStatus = "pending" | "running" | "done" | "blocked";
|
|
13
|
+
|
|
14
|
+
export interface LoopNextAction {
|
|
15
|
+
taskId: string;
|
|
16
|
+
agentType: "code" | "review";
|
|
17
|
+
iteration: number;
|
|
18
|
+
prompt?: string;
|
|
19
|
+
codeFiles?: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface LoopAgentReport {
|
|
23
|
+
agentId: string;
|
|
24
|
+
taskId: string;
|
|
25
|
+
status: "running" | "done" | "failed";
|
|
26
|
+
message?: string;
|
|
27
|
+
files?: string[];
|
|
28
|
+
verdict?: LoopVerdict;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface LoopTaskGraph {
|
|
32
|
+
nodes: Record<string, TaskNode>;
|
|
33
|
+
topologicalOrder: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function buildLoopTaskGraph(
|
|
37
|
+
writeCount: number,
|
|
38
|
+
reviewCount: number,
|
|
39
|
+
loopId: string,
|
|
40
|
+
): TaskGraph {
|
|
41
|
+
const now = new Date().toISOString();
|
|
42
|
+
const nodes: Record<string, TaskNode> = {};
|
|
43
|
+
const order: string[] = [];
|
|
44
|
+
|
|
45
|
+
// Write tasks
|
|
46
|
+
for (let i = 1; i <= writeCount; i++) {
|
|
47
|
+
const id = `write-${i}`;
|
|
48
|
+
nodes[id] = {
|
|
49
|
+
id,
|
|
50
|
+
title: `Code Write ${i}`,
|
|
51
|
+
description: `Write iteration ${i}`,
|
|
52
|
+
status: "pending",
|
|
53
|
+
dependencies: [],
|
|
54
|
+
dependents: [],
|
|
55
|
+
createdAt: now,
|
|
56
|
+
updatedAt: now,
|
|
57
|
+
};
|
|
58
|
+
order.push(id);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Review tasks
|
|
62
|
+
const step = Math.max(1, Math.floor(writeCount / (reviewCount || 1)));
|
|
63
|
+
for (let i = 1; i <= reviewCount; i++) {
|
|
64
|
+
const id = `review-${i}`;
|
|
65
|
+
const writeIdx = i * step;
|
|
66
|
+
nodes[id] = {
|
|
67
|
+
id,
|
|
68
|
+
title: `Code Review ${i}`,
|
|
69
|
+
description: `Review iteration ${i}`,
|
|
70
|
+
status: "pending",
|
|
71
|
+
dependencies: writeIdx <= writeCount ? [`write-${writeIdx}`] : [],
|
|
72
|
+
dependents: [],
|
|
73
|
+
createdAt: now,
|
|
74
|
+
updatedAt: now,
|
|
75
|
+
};
|
|
76
|
+
// Wire dependencies
|
|
77
|
+
if (writeIdx <= writeCount) {
|
|
78
|
+
nodes[`write-${writeIdx}`].dependents.push(id);
|
|
79
|
+
}
|
|
80
|
+
order.push(id);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Report task
|
|
84
|
+
const reportId = "report";
|
|
85
|
+
nodes[reportId] = {
|
|
86
|
+
id: reportId,
|
|
87
|
+
title: "Loop Report",
|
|
88
|
+
description: "Final summary",
|
|
89
|
+
status: "pending",
|
|
90
|
+
dependencies: [],
|
|
91
|
+
dependents: [],
|
|
92
|
+
createdAt: now,
|
|
93
|
+
updatedAt: now,
|
|
94
|
+
};
|
|
95
|
+
order.push(reportId);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
jobId: loopId,
|
|
99
|
+
nodes,
|
|
100
|
+
topologicalOrder: order,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function getNodeStatus(
|
|
105
|
+
graph: TaskGraph,
|
|
106
|
+
nodeId: string,
|
|
107
|
+
): LoopNodeStatus | undefined {
|
|
108
|
+
return graph.nodes[nodeId]?.status as LoopNodeStatus | undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function getReviewVerdict(
|
|
112
|
+
graph: TaskGraph,
|
|
113
|
+
reviewId: string,
|
|
114
|
+
): LoopVerdict | undefined {
|
|
115
|
+
const node = graph.nodes[reviewId];
|
|
116
|
+
if (!node) return undefined;
|
|
117
|
+
return (node as unknown as { result?: LoopVerdict }).result;
|
|
118
|
+
}
|