diff --git a/packages/paperclip-runner/docs/capability-contract.md b/packages/paperclip-runner/docs/capability-contract.md index 6d9400b4d8f..355ba6484fa 100644 --- a/packages/paperclip-runner/docs/capability-contract.md +++ b/packages/paperclip-runner/docs/capability-contract.md @@ -47,34 +47,34 @@ The skill/reference inventory and eval cases are the only normative behavior sou | skill:skills/paperclip/SKILL.md:conversation-tasks:32 | optional_agent_tool | skills/paperclip/SKILL.md:32 | | skill:skills/paperclip/SKILL.md:server-verified-external-chat-turns:49 | control_plane_owned | skills/paperclip/SKILL.md:49 | | skill:skills/paperclip/SKILL.md:the-heartbeat-procedure:89 | optional_agent_tool | skills/paperclip/SKILL.md:89 | -| skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:200 | always_agent_tool | skills/paperclip/SKILL.md:200 | -| skill:skills/paperclip/SKILL.md:status-quick-guide:248 | control_plane_owned | skills/paperclip/SKILL.md:248 | -| skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:258 | optional_agent_tool | skills/paperclip/SKILL.md:258 | -| skill:skills/paperclip/SKILL.md:delegating-review-tasks:271 | always_agent_tool | skills/paperclip/SKILL.md:271 | -| skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:282 | control_plane_owned | skills/paperclip/SKILL.md:282 | -| skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:290 | control_plane_owned | skills/paperclip/SKILL.md:290 | -| skill:skills/paperclip/SKILL.md:requesting-board-approval:315 | optional_agent_tool | skills/paperclip/SKILL.md:315 | -| skill:skills/paperclip/SKILL.md:issue-thread-interactions:336 | optional_agent_tool | skills/paperclip/SKILL.md:336 | -| skill:skills/paperclip/SKILL.md:standalone-decisions:366 | optional_agent_tool | skills/paperclip/SKILL.md:366 | -| skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:470 | optional_agent_tool | skills/paperclip/SKILL.md:470 | -| skill:skills/paperclip/SKILL.md:niche-workflow-pointers:512 | optional_agent_tool | skills/paperclip/SKILL.md:512 | -| skill:skills/paperclip/SKILL.md:cases:522 | optional_agent_tool | skills/paperclip/SKILL.md:522 | -| skill:skills/paperclip/SKILL.md:company-skills-workflow:527 | optional_agent_tool | skills/paperclip/SKILL.md:527 | -| skill:skills/paperclip/SKILL.md:routines:538 | optional_agent_tool | skills/paperclip/SKILL.md:538 | -| skill:skills/paperclip/SKILL.md:task-files-and-workspace-choices:549 | optional_agent_tool | skills/paperclip/SKILL.md:549 | -| skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:555 | optional_agent_tool | skills/paperclip/SKILL.md:555 | -| skill:skills/paperclip/SKILL.md:proposing-credentials-safely:562 | optional_agent_tool | skills/paperclip/SKILL.md:562 | -| skill:skills/paperclip/SKILL.md:reading-granted-secrets:569 | optional_agent_tool | skills/paperclip/SKILL.md:569 | -| skill:skills/paperclip/SKILL.md:critical-rules:595 | optional_agent_tool | skills/paperclip/SKILL.md:595 | -| skill:skills/paperclip/SKILL.md:comment-style-required:616 | always_agent_tool | skills/paperclip/SKILL.md:616 | -| skill:skills/paperclip/SKILL.md:update:648 | optional_agent_tool | skills/paperclip/SKILL.md:648 | -| skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:658 | always_agent_tool | skills/paperclip/SKILL.md:658 | -| skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:672 | optional_agent_tool | skills/paperclip/SKILL.md:672 | -| skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:703 | optional_agent_tool | skills/paperclip/SKILL.md:703 | -| skill:skills/paperclip/SKILL.md:searching-issues:733 | optional_agent_tool | skills/paperclip/SKILL.md:733 | -| skill:skills/paperclip/SKILL.md:full-reference:743 | optional_agent_tool | skills/paperclip/SKILL.md:743 | -| skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:747 | always_agent_tool | skills/paperclip/SKILL.md:747 | -| skill:skills/paperclip/SKILL.md:incidental-feedback:764 | optional_agent_tool | skills/paperclip/SKILL.md:764 | +| skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:201 | always_agent_tool | skills/paperclip/SKILL.md:201 | +| skill:skills/paperclip/SKILL.md:status-quick-guide:249 | control_plane_owned | skills/paperclip/SKILL.md:249 | +| skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:259 | optional_agent_tool | skills/paperclip/SKILL.md:259 | +| skill:skills/paperclip/SKILL.md:delegating-review-tasks:272 | always_agent_tool | skills/paperclip/SKILL.md:272 | +| skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:283 | control_plane_owned | skills/paperclip/SKILL.md:283 | +| skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:291 | control_plane_owned | skills/paperclip/SKILL.md:291 | +| skill:skills/paperclip/SKILL.md:requesting-board-approval:316 | optional_agent_tool | skills/paperclip/SKILL.md:316 | +| skill:skills/paperclip/SKILL.md:issue-thread-interactions:337 | optional_agent_tool | skills/paperclip/SKILL.md:337 | +| skill:skills/paperclip/SKILL.md:standalone-decisions:367 | optional_agent_tool | skills/paperclip/SKILL.md:367 | +| skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:471 | optional_agent_tool | skills/paperclip/SKILL.md:471 | +| skill:skills/paperclip/SKILL.md:niche-workflow-pointers:513 | optional_agent_tool | skills/paperclip/SKILL.md:513 | +| skill:skills/paperclip/SKILL.md:cases:523 | optional_agent_tool | skills/paperclip/SKILL.md:523 | +| skill:skills/paperclip/SKILL.md:company-skills-workflow:528 | optional_agent_tool | skills/paperclip/SKILL.md:528 | +| skill:skills/paperclip/SKILL.md:routines:539 | optional_agent_tool | skills/paperclip/SKILL.md:539 | +| skill:skills/paperclip/SKILL.md:task-files-and-workspace-choices:550 | optional_agent_tool | skills/paperclip/SKILL.md:550 | +| skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:558 | optional_agent_tool | skills/paperclip/SKILL.md:558 | +| skill:skills/paperclip/SKILL.md:proposing-credentials-safely:565 | optional_agent_tool | skills/paperclip/SKILL.md:565 | +| skill:skills/paperclip/SKILL.md:reading-granted-secrets:572 | optional_agent_tool | skills/paperclip/SKILL.md:572 | +| skill:skills/paperclip/SKILL.md:critical-rules:598 | optional_agent_tool | skills/paperclip/SKILL.md:598 | +| skill:skills/paperclip/SKILL.md:comment-style-required:619 | always_agent_tool | skills/paperclip/SKILL.md:619 | +| skill:skills/paperclip/SKILL.md:update:651 | optional_agent_tool | skills/paperclip/SKILL.md:651 | +| skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:661 | always_agent_tool | skills/paperclip/SKILL.md:661 | +| skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:675 | optional_agent_tool | skills/paperclip/SKILL.md:675 | +| skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:706 | optional_agent_tool | skills/paperclip/SKILL.md:706 | +| skill:skills/paperclip/SKILL.md:searching-issues:736 | optional_agent_tool | skills/paperclip/SKILL.md:736 | +| skill:skills/paperclip/SKILL.md:full-reference:746 | optional_agent_tool | skills/paperclip/SKILL.md:746 | +| skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:750 | always_agent_tool | skills/paperclip/SKILL.md:750 | +| skill:skills/paperclip/SKILL.md:incidental-feedback:767 | optional_agent_tool | skills/paperclip/SKILL.md:767 | | skill:skills/paperclip/references/api-reference.md:paperclip-api-reference:1 | optional_agent_tool | skills/paperclip/references/api-reference.md:1 | | skill:skills/paperclip/references/api-reference.md:response-schemas:9 | optional_agent_tool | skills/paperclip/references/api-reference.md:9 | | skill:skills/paperclip/references/api-reference.md:agent-record-get-api-agents-me-or-get-api-agents-agentid:11 | optional_agent_tool | skills/paperclip/references/api-reference.md:11 | diff --git a/packages/paperclip-runner/generated/capability/capabilities.yaml b/packages/paperclip-runner/generated/capability/capabilities.yaml index 3cd9b643901..c1692d311b4 100644 --- a/packages/paperclip-runner/generated/capability/capabilities.yaml +++ b/packages/paperclip-runner/generated/capability/capabilities.yaml @@ -56,252 +56,252 @@ "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:200", + "id": "skill:skills/paperclip/SKILL.md:201", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L200:generated-artifacts-and-work-products", + "sourceAnchor": "skills/paperclip/SKILL.md#L201:generated-artifacts-and-work-products", "heading": "Generated Artifacts and Work Products", "primaryDisposition": "always_agent_tool", "semanticOperation": "register_deliverable", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:248", + "id": "skill:skills/paperclip/SKILL.md:249", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L248:status-quick-guide", + "sourceAnchor": "skills/paperclip/SKILL.md#L249:status-quick-guide", "heading": "Status Quick Guide", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:258", + "id": "skill:skills/paperclip/SKILL.md:259", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L258:monitors-and-watchers-say-only-what-you-actually-scheduled", + "sourceAnchor": "skills/paperclip/SKILL.md#L259:monitors-and-watchers-say-only-what-you-actually-scheduled", "heading": "Monitors and Watchers (say only what you actually scheduled)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:271", + "id": "skill:skills/paperclip/SKILL.md:272", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L271:delegating-review-tasks", + "sourceAnchor": "skills/paperclip/SKILL.md#L272:delegating-review-tasks", "heading": "Delegating review tasks", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:282", + "id": "skill:skills/paperclip/SKILL.md:283", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L282:managing-a-user-s-inbox", + "sourceAnchor": "skills/paperclip/SKILL.md#L283:managing-a-user-s-inbox", "heading": "Managing A User's Inbox", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:290", + "id": "skill:skills/paperclip/SKILL.md:291", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L290:issue-dependencies-blockers", + "sourceAnchor": "skills/paperclip/SKILL.md#L291:issue-dependencies-blockers", "heading": "Issue Dependencies (Blockers)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:315", + "id": "skill:skills/paperclip/SKILL.md:316", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L315:requesting-board-approval", + "sourceAnchor": "skills/paperclip/SKILL.md#L316:requesting-board-approval", "heading": "Requesting Board Approval", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:336", + "id": "skill:skills/paperclip/SKILL.md:337", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L336:issue-thread-interactions", + "sourceAnchor": "skills/paperclip/SKILL.md#L337:issue-thread-interactions", "heading": "Issue-Thread Interactions", "primaryDisposition": "always_agent_tool", "semanticOperation": "request_human_input", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:366", + "id": "skill:skills/paperclip/SKILL.md:367", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L366:standalone-decisions", + "sourceAnchor": "skills/paperclip/SKILL.md#L367:standalone-decisions", "heading": "Standalone Decisions", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:470", + "id": "skill:skills/paperclip/SKILL.md:471", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L470:mcp-tool-approval-gates", + "sourceAnchor": "skills/paperclip/SKILL.md#L471:mcp-tool-approval-gates", "heading": "MCP Tool Approval Gates", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:512", + "id": "skill:skills/paperclip/SKILL.md:513", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L512:niche-workflow-pointers", + "sourceAnchor": "skills/paperclip/SKILL.md#L513:niche-workflow-pointers", "heading": "Niche Workflow Pointers", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:522", + "id": "skill:skills/paperclip/SKILL.md:523", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L522:cases", + "sourceAnchor": "skills/paperclip/SKILL.md#L523:cases", "heading": "Cases", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:527", + "id": "skill:skills/paperclip/SKILL.md:528", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L527:company-skills-workflow", + "sourceAnchor": "skills/paperclip/SKILL.md#L528:company-skills-workflow", "heading": "Company Skills Workflow", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:538", + "id": "skill:skills/paperclip/SKILL.md:539", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L538:routines", + "sourceAnchor": "skills/paperclip/SKILL.md#L539:routines", "heading": "Routines", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:549", + "id": "skill:skills/paperclip/SKILL.md:550", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L549:task-files-and-workspace-choices", + "sourceAnchor": "skills/paperclip/SKILL.md#L550:task-files-and-workspace-choices", "heading": "Task Files and Workspace Choices", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:555", + "id": "skill:skills/paperclip/SKILL.md:558", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L555:issue-workspace-runtime-controls", + "sourceAnchor": "skills/paperclip/SKILL.md#L558:issue-workspace-runtime-controls", "heading": "Issue Workspace Runtime Controls", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:562", + "id": "skill:skills/paperclip/SKILL.md:565", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L562:proposing-credentials-safely", + "sourceAnchor": "skills/paperclip/SKILL.md#L565:proposing-credentials-safely", "heading": "Proposing Credentials Safely", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:569", + "id": "skill:skills/paperclip/SKILL.md:572", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L569:reading-granted-secrets", + "sourceAnchor": "skills/paperclip/SKILL.md#L572:reading-granted-secrets", "heading": "Reading Granted Secrets", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:595", + "id": "skill:skills/paperclip/SKILL.md:598", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L595:critical-rules", + "sourceAnchor": "skills/paperclip/SKILL.md#L598:critical-rules", "heading": "Critical Rules", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:616", + "id": "skill:skills/paperclip/SKILL.md:619", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L616:comment-style-required", + "sourceAnchor": "skills/paperclip/SKILL.md#L619:comment-style-required", "heading": "Comment Style (Required)", "primaryDisposition": "always_agent_tool", "semanticOperation": "report_progress", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:648", + "id": "skill:skills/paperclip/SKILL.md:651", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L648:update", + "sourceAnchor": "skills/paperclip/SKILL.md#L651:update", "heading": "Update", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:658", + "id": "skill:skills/paperclip/SKILL.md:661", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L658:task-documents-and-deliverables", + "sourceAnchor": "skills/paperclip/SKILL.md#L661:task-documents-and-deliverables", "heading": "Task Documents and Deliverables", "primaryDisposition": "always_agent_tool", "semanticOperation": "write_document", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:672", + "id": "skill:skills/paperclip/SKILL.md:675", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L672:planning-required-when-planning-requested", + "sourceAnchor": "skills/paperclip/SKILL.md#L675:planning-required-when-planning-requested", "heading": "Planning (Required when planning requested)", "primaryDisposition": "always_agent_tool", "semanticOperation": "write_document", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:703", + "id": "skill:skills/paperclip/SKILL.md:706", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L703:key-endpoints-hot-routes", + "sourceAnchor": "skills/paperclip/SKILL.md#L706:key-endpoints-hot-routes", "heading": "Key Endpoints (Hot Routes)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:733", + "id": "skill:skills/paperclip/SKILL.md:736", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L733:searching-issues", + "sourceAnchor": "skills/paperclip/SKILL.md#L736:searching-issues", "heading": "Searching Issues", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:743", + "id": "skill:skills/paperclip/SKILL.md:746", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L743:full-reference", + "sourceAnchor": "skills/paperclip/SKILL.md#L746:full-reference", "heading": "Full Reference", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:747", + "id": "skill:skills/paperclip/SKILL.md:750", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L747:conversational-confirmation-answers", + "sourceAnchor": "skills/paperclip/SKILL.md#L750:conversational-confirmation-answers", "heading": "Conversational confirmation answers", "primaryDisposition": "always_agent_tool", "semanticOperation": "call_api", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:764", + "id": "skill:skills/paperclip/SKILL.md:767", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L764:incidental-feedback", + "sourceAnchor": "skills/paperclip/SKILL.md#L767:incidental-feedback", "heading": "Incidental feedback", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", diff --git a/packages/paperclip-runner/generated/capability/capability-contract.md b/packages/paperclip-runner/generated/capability/capability-contract.md index 59e7170afcd..e84663504aa 100644 --- a/packages/paperclip-runner/generated/capability/capability-contract.md +++ b/packages/paperclip-runner/generated/capability/capability-contract.md @@ -5,6 +5,6 @@ Generated by `scripts/generate-capability-contract.mjs`; do not edit generated f - Skill/reference headings: 167 - Legacy MCP tools: 42 - Eval cases: 106 across 16 groups -- Deterministic content SHA-256: `56f834a342a1e75e535d1f1a23f483882331d1fb364aa6bd17de7d9a2da39c4b` +- Deterministic content SHA-256: `05f5724a98ae5e6045a550d969599c3e5f40d4c97069ae2453cc6e7e60d2ba44` Every row has exactly one primary disposition, a source anchor, a semantic operation, and a mock-state expectation. diff --git a/packages/paperclip-runner/spec/capability/capabilities.yaml b/packages/paperclip-runner/spec/capability/capabilities.yaml index 72ad2664f46..1c43458abcd 100644 --- a/packages/paperclip-runner/spec/capability/capabilities.yaml +++ b/packages/paperclip-runner/spec/capability/capabilities.yaml @@ -105,9 +105,9 @@ ] }, { - "id": "skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:200", + "id": "skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:201", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:200", + "sourceAnchor": "skills/paperclip/SKILL.md:201", "title": "Generated Artifacts and Work Products", "expectedSemantics": "Skill guidance headed “Generated Artifacts and Work Products”.", "primaryDisposition": "always_agent_tool", @@ -116,13 +116,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:200" + "skill:skills/paperclip/SKILL.md:201" ] }, { - "id": "skill:skills/paperclip/SKILL.md:status-quick-guide:248", + "id": "skill:skills/paperclip/SKILL.md:status-quick-guide:249", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:248", + "sourceAnchor": "skills/paperclip/SKILL.md:249", "title": "Status Quick Guide", "expectedSemantics": "Skill guidance headed “Status Quick Guide”.", "primaryDisposition": "control_plane_owned", @@ -131,13 +131,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:248" + "skill:skills/paperclip/SKILL.md:249" ] }, { - "id": "skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:258", + "id": "skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:259", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:258", + "sourceAnchor": "skills/paperclip/SKILL.md:259", "title": "Monitors and Watchers (say only what you actually scheduled)", "expectedSemantics": "Skill guidance headed “Monitors and Watchers (say only what you actually scheduled)”.", "primaryDisposition": "optional_agent_tool", @@ -146,13 +146,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:258" + "skill:skills/paperclip/SKILL.md:259" ] }, { - "id": "skill:skills/paperclip/SKILL.md:delegating-review-tasks:271", + "id": "skill:skills/paperclip/SKILL.md:delegating-review-tasks:272", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:271", + "sourceAnchor": "skills/paperclip/SKILL.md:272", "title": "Delegating review tasks", "expectedSemantics": "Skill guidance headed “Delegating review tasks”.", "primaryDisposition": "always_agent_tool", @@ -161,13 +161,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:271" + "skill:skills/paperclip/SKILL.md:272" ] }, { - "id": "skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:282", + "id": "skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:283", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:282", + "sourceAnchor": "skills/paperclip/SKILL.md:283", "title": "Managing A User's Inbox", "expectedSemantics": "Skill guidance headed “Managing A User's Inbox”.", "primaryDisposition": "control_plane_owned", @@ -176,13 +176,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:282" + "skill:skills/paperclip/SKILL.md:283" ] }, { - "id": "skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:290", + "id": "skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:291", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:290", + "sourceAnchor": "skills/paperclip/SKILL.md:291", "title": "Issue Dependencies (Blockers)", "expectedSemantics": "Skill guidance headed “Issue Dependencies (Blockers)”.", "primaryDisposition": "control_plane_owned", @@ -191,13 +191,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:290" + "skill:skills/paperclip/SKILL.md:291" ] }, { - "id": "skill:skills/paperclip/SKILL.md:requesting-board-approval:315", + "id": "skill:skills/paperclip/SKILL.md:requesting-board-approval:316", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:315", + "sourceAnchor": "skills/paperclip/SKILL.md:316", "title": "Requesting Board Approval", "expectedSemantics": "Skill guidance headed “Requesting Board Approval”.", "primaryDisposition": "optional_agent_tool", @@ -206,13 +206,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:315" + "skill:skills/paperclip/SKILL.md:316" ] }, { - "id": "skill:skills/paperclip/SKILL.md:issue-thread-interactions:336", + "id": "skill:skills/paperclip/SKILL.md:issue-thread-interactions:337", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:336", + "sourceAnchor": "skills/paperclip/SKILL.md:337", "title": "Issue-Thread Interactions", "expectedSemantics": "Skill guidance headed “Issue-Thread Interactions”.", "primaryDisposition": "optional_agent_tool", @@ -221,13 +221,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:336" + "skill:skills/paperclip/SKILL.md:337" ] }, { - "id": "skill:skills/paperclip/SKILL.md:standalone-decisions:366", + "id": "skill:skills/paperclip/SKILL.md:standalone-decisions:367", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:366", + "sourceAnchor": "skills/paperclip/SKILL.md:367", "title": "Standalone Decisions", "expectedSemantics": "Skill guidance headed “Standalone Decisions”.", "primaryDisposition": "optional_agent_tool", @@ -236,13 +236,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:366" + "skill:skills/paperclip/SKILL.md:367" ] }, { - "id": "skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:470", + "id": "skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:471", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:470", + "sourceAnchor": "skills/paperclip/SKILL.md:471", "title": "MCP Tool Approval Gates", "expectedSemantics": "Skill guidance headed “MCP Tool Approval Gates”.", "primaryDisposition": "optional_agent_tool", @@ -251,13 +251,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:470" + "skill:skills/paperclip/SKILL.md:471" ] }, { - "id": "skill:skills/paperclip/SKILL.md:niche-workflow-pointers:512", + "id": "skill:skills/paperclip/SKILL.md:niche-workflow-pointers:513", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:512", + "sourceAnchor": "skills/paperclip/SKILL.md:513", "title": "Niche Workflow Pointers", "expectedSemantics": "Skill guidance headed “Niche Workflow Pointers”.", "primaryDisposition": "optional_agent_tool", @@ -266,13 +266,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:512" + "skill:skills/paperclip/SKILL.md:513" ] }, { - "id": "skill:skills/paperclip/SKILL.md:cases:522", + "id": "skill:skills/paperclip/SKILL.md:cases:523", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:522", + "sourceAnchor": "skills/paperclip/SKILL.md:523", "title": "Cases", "expectedSemantics": "Skill guidance headed “Cases”.", "primaryDisposition": "optional_agent_tool", @@ -281,13 +281,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:522" + "skill:skills/paperclip/SKILL.md:523" ] }, { - "id": "skill:skills/paperclip/SKILL.md:company-skills-workflow:527", + "id": "skill:skills/paperclip/SKILL.md:company-skills-workflow:528", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:527", + "sourceAnchor": "skills/paperclip/SKILL.md:528", "title": "Company Skills Workflow", "expectedSemantics": "Skill guidance headed “Company Skills Workflow”.", "primaryDisposition": "optional_agent_tool", @@ -296,13 +296,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:527" + "skill:skills/paperclip/SKILL.md:528" ] }, { - "id": "skill:skills/paperclip/SKILL.md:routines:538", + "id": "skill:skills/paperclip/SKILL.md:routines:539", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:538", + "sourceAnchor": "skills/paperclip/SKILL.md:539", "title": "Routines", "expectedSemantics": "Skill guidance headed “Routines”.", "primaryDisposition": "optional_agent_tool", @@ -311,13 +311,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:538" + "skill:skills/paperclip/SKILL.md:539" ] }, { - "id": "skill:skills/paperclip/SKILL.md:task-files-and-workspace-choices:549", + "id": "skill:skills/paperclip/SKILL.md:task-files-and-workspace-choices:550", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:549", + "sourceAnchor": "skills/paperclip/SKILL.md:550", "title": "Task Files and Workspace Choices", "expectedSemantics": "Skill guidance headed “Task Files and Workspace Choices”.", "primaryDisposition": "optional_agent_tool", @@ -326,13 +326,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:549" + "skill:skills/paperclip/SKILL.md:550" ] }, { - "id": "skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:555", + "id": "skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:558", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:555", + "sourceAnchor": "skills/paperclip/SKILL.md:558", "title": "Issue Workspace Runtime Controls", "expectedSemantics": "Skill guidance headed “Issue Workspace Runtime Controls”.", "primaryDisposition": "optional_agent_tool", @@ -341,13 +341,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:555" + "skill:skills/paperclip/SKILL.md:558" ] }, { - "id": "skill:skills/paperclip/SKILL.md:proposing-credentials-safely:562", + "id": "skill:skills/paperclip/SKILL.md:proposing-credentials-safely:565", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:562", + "sourceAnchor": "skills/paperclip/SKILL.md:565", "title": "Proposing Credentials Safely", "expectedSemantics": "Skill guidance headed “Proposing Credentials Safely”.", "primaryDisposition": "optional_agent_tool", @@ -356,13 +356,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:562" + "skill:skills/paperclip/SKILL.md:565" ] }, { - "id": "skill:skills/paperclip/SKILL.md:reading-granted-secrets:569", + "id": "skill:skills/paperclip/SKILL.md:reading-granted-secrets:572", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:569", + "sourceAnchor": "skills/paperclip/SKILL.md:572", "title": "Reading Granted Secrets", "expectedSemantics": "Skill guidance headed “Reading Granted Secrets”.", "primaryDisposition": "optional_agent_tool", @@ -371,13 +371,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:569" + "skill:skills/paperclip/SKILL.md:572" ] }, { - "id": "skill:skills/paperclip/SKILL.md:critical-rules:595", + "id": "skill:skills/paperclip/SKILL.md:critical-rules:598", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:595", + "sourceAnchor": "skills/paperclip/SKILL.md:598", "title": "Critical Rules", "expectedSemantics": "Skill guidance headed “Critical Rules”.", "primaryDisposition": "optional_agent_tool", @@ -386,13 +386,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:595" + "skill:skills/paperclip/SKILL.md:598" ] }, { - "id": "skill:skills/paperclip/SKILL.md:comment-style-required:616", + "id": "skill:skills/paperclip/SKILL.md:comment-style-required:619", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:616", + "sourceAnchor": "skills/paperclip/SKILL.md:619", "title": "Comment Style (Required)", "expectedSemantics": "Skill guidance headed “Comment Style (Required)”.", "primaryDisposition": "always_agent_tool", @@ -401,13 +401,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:616" + "skill:skills/paperclip/SKILL.md:619" ] }, { - "id": "skill:skills/paperclip/SKILL.md:update:648", + "id": "skill:skills/paperclip/SKILL.md:update:651", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:648", + "sourceAnchor": "skills/paperclip/SKILL.md:651", "title": "Update", "expectedSemantics": "Skill guidance headed “Update”.", "primaryDisposition": "optional_agent_tool", @@ -416,13 +416,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:648" + "skill:skills/paperclip/SKILL.md:651" ] }, { - "id": "skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:658", + "id": "skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:661", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:658", + "sourceAnchor": "skills/paperclip/SKILL.md:661", "title": "Task Documents and Deliverables", "expectedSemantics": "Skill guidance headed “Task Documents and Deliverables”.", "primaryDisposition": "always_agent_tool", @@ -431,13 +431,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:658" + "skill:skills/paperclip/SKILL.md:661" ] }, { - "id": "skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:672", + "id": "skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:675", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:672", + "sourceAnchor": "skills/paperclip/SKILL.md:675", "title": "Planning (Required when planning requested)", "expectedSemantics": "Skill guidance headed “Planning (Required when planning requested)”.", "primaryDisposition": "optional_agent_tool", @@ -446,13 +446,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:672" + "skill:skills/paperclip/SKILL.md:675" ] }, { - "id": "skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:703", + "id": "skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:706", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:703", + "sourceAnchor": "skills/paperclip/SKILL.md:706", "title": "Key Endpoints (Hot Routes)", "expectedSemantics": "Skill guidance headed “Key Endpoints (Hot Routes)”.", "primaryDisposition": "optional_agent_tool", @@ -461,13 +461,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:703" + "skill:skills/paperclip/SKILL.md:706" ] }, { - "id": "skill:skills/paperclip/SKILL.md:searching-issues:733", + "id": "skill:skills/paperclip/SKILL.md:searching-issues:736", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:733", + "sourceAnchor": "skills/paperclip/SKILL.md:736", "title": "Searching Issues", "expectedSemantics": "Skill guidance headed “Searching Issues”.", "primaryDisposition": "optional_agent_tool", @@ -476,13 +476,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:733" + "skill:skills/paperclip/SKILL.md:736" ] }, { - "id": "skill:skills/paperclip/SKILL.md:full-reference:743", + "id": "skill:skills/paperclip/SKILL.md:full-reference:746", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:743", + "sourceAnchor": "skills/paperclip/SKILL.md:746", "title": "Full Reference", "expectedSemantics": "Skill guidance headed “Full Reference”.", "primaryDisposition": "optional_agent_tool", @@ -491,13 +491,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:743" + "skill:skills/paperclip/SKILL.md:746" ] }, { - "id": "skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:747", + "id": "skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:750", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:747", + "sourceAnchor": "skills/paperclip/SKILL.md:750", "title": "Conversational confirmation answers", "expectedSemantics": "Skill guidance headed “Conversational confirmation answers”.", "primaryDisposition": "always_agent_tool", @@ -506,13 +506,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:747" + "skill:skills/paperclip/SKILL.md:750" ] }, { - "id": "skill:skills/paperclip/SKILL.md:incidental-feedback:764", + "id": "skill:skills/paperclip/SKILL.md:incidental-feedback:767", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:764", + "sourceAnchor": "skills/paperclip/SKILL.md:767", "title": "Incidental feedback", "expectedSemantics": "Skill guidance headed “Incidental feedback”.", "primaryDisposition": "optional_agent_tool", @@ -521,7 +521,7 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:764" + "skill:skills/paperclip/SKILL.md:767" ] }, { diff --git a/server/src/__tests__/helpers/heartbeat-queue.ts b/server/src/__tests__/helpers/heartbeat-queue.ts new file mode 100644 index 00000000000..c5c4ecd9af8 --- /dev/null +++ b/server/src/__tests__/helpers/heartbeat-queue.ts @@ -0,0 +1,72 @@ +import type { Db } from "@paperclipai/db"; +import { heartbeatRuns } from "@paperclipai/db"; +import { eq } from "drizzle-orm"; +import { vi, type Mock, type Mocked } from "vitest"; +import { createRunDispatch } from "../../modules/run-dispatch/index.js"; +import type { createWakeQueue } from "../../modules/wake-queue/index.js"; +import { createHeartbeatRunState } from "../../services/heartbeat/run-state.js"; +import { createHeartbeatRunPreparation } from "../../services/heartbeat/run-preparation.js"; +import type { HeartbeatQueueDependencies } from "../../services/heartbeat/queue.js"; + +type Run = typeof heartbeatRuns.$inferSelect; +type MockedCallback = "getAgent" | "setRunStatus" | "setWakeupStatus" | "appendRunEvent" | "finalizeAgentStatus" | "getAgentInvokability" | "getWorktreeExecutionCutoff" | "applyRunDispatchPostCommitEffects" | "getSchedulingSuppression" | "canStartNewExternalBotWork" | "executeRun" | "applyWakeQueuePostCommitEffects" | "releaseIssueExecutionAndPromote" | "publishRunLifecyclePluginEvent" | "cancelRunInternal" | "cancelActiveForAgentInternal" | "resumeExecutionWaitComments" | "resumeQueuedCommentInterrupt" | "resumeSavedLegacyComments" | "sweepPendingCleanupLeases"; +type QueueTestDependencies = HeartbeatQueueDependencies & { [K in MockedCallback]: Mock } & { + issuesSvc: Mocked; + treeControlSvc: Mocked; + budgets: Mocked; + instanceSettings: Mocked; + wakeQueue: Mocked; + liveRunExecutions: Mocked; +}; + +export function createHeartbeatQueueTestDependencies(db: Db): QueueTestDependencies { + const state = createHeartbeatRunState(db); + const preparation = createHeartbeatRunPreparation(db); + return { + ...preparation, + ...state, + getAgent: vi.fn(async () => { throw new Error("Unexpected agent lookup"); }), + options: {}, + setRunStatus: vi.fn(async (id, status, patch) => + db.update(heartbeatRuns).set({ ...patch, status }).where(eq(heartbeatRuns.id, id)).returning().then(rows => rows[0] ?? null)), + setWakeupStatus: vi.fn(async () => undefined), + appendRunEvent: vi.fn(async () => undefined), + finalizeAgentStatus: vi.fn(async () => undefined), + getAgentInvokability: vi.fn(async () => ({ invokable: true })), + getWorktreeExecutionCutoff: vi.fn(async () => null), + applyRunDispatchPostCommitEffects: vi.fn(), + issuesSvc: { + getDependencyReadiness: vi.fn(), + listDependencyReadiness: vi.fn(async () => new Map()), + }, + treeControlSvc: { getActivePauseHoldGate: vi.fn(async () => null) }, + budgets: { getInvocationBlock: vi.fn(async () => null) }, + instanceSettings: { getExperimental: vi.fn() }, + runDispatch: createRunDispatch(db), + wakeQueue: { + createAdmissionTransactionScope: vi.fn["createAdmissionTransactionScope"]>(), + admitWakeBehindIssueExecution: vi.fn["admitWakeBehindIssueExecution"]>(), + }, + activeRunExecutions: new Set(), + activeRunExecutionPromises: new Set>(), + activeWakeupPromises: new Set>(), + liveRunExecutions: { has: vi.fn(() => false) }, + isHeartbeatRunTerminalStatus: (status: string | null | undefined) => ["succeeded", "failed", "cancelled", "timed_out"].includes(status ?? ""), + isSameTaskScope: (left: string | null, right: string | null) => left === right, + runTaskKey: (run: Run) => typeof run.contextSnapshot?.issueId === "string" ? run.contextSnapshot.issueId : null, + filterZombieCoalesceTarget: (run: T | null) => run, + getSchedulingSuppression: vi.fn(async () => ({ suppressed: false, reason: null } as Awaited>)), + canStartNewExternalBotWork: vi.fn(async () => true), + executeRun: vi.fn(async () => undefined), + applyWakeQueuePostCommitEffects: vi.fn(async () => undefined), + releaseIssueExecutionAndPromote: vi.fn(async () => undefined), + publishRunLifecyclePluginEvent: vi.fn(), + cancelRunInternal: vi.fn(async () => undefined), + cancelActiveForAgentInternal: vi.fn(async () => undefined), + resumeExecutionWaitComments: vi.fn(async () => undefined), + resumeQueuedCommentInterrupt: vi.fn(async () => undefined), + resumeSavedLegacyComments: vi.fn(async () => undefined), + formatIssueIdentifierLink: (_identifier: string | null, fallback: string) => fallback, + sweepPendingCleanupLeases: vi.fn(), + } satisfies HeartbeatQueueDependencies; +} diff --git a/server/src/services/chat-monitor-continuation.test.ts b/server/src/services/chat-monitor-continuation.test.ts index cdd09f1c630..973daefb885 100644 --- a/server/src/services/chat-monitor-continuation.test.ts +++ b/server/src/services/chat-monitor-continuation.test.ts @@ -3,7 +3,7 @@ import { and, eq } from "drizzle-orm"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { agents, agentWakeupRequests, authUsers, chatConversations, chatEndpoints, chatMessageLinks, - chatPublications, companies, completionContracts, createDb, heartbeatRuns, issueComments, issues, nativeRunFinalizations, nativeRunResults, + chatPublications, companies, companyMemberships, completionContracts, createDb, heartbeatRuns, issueComments, issues, nativeRunFinalizations, nativeRunResults, statusDecisions, statusDecisionEffects, toolApplications, toolConnections, } from "@paperclipai/db"; import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "../__tests__/helpers/embedded-postgres.js"; @@ -12,6 +12,9 @@ import { CHAT_RUN_PRESENTATION_AUTHORIZATION_REASON } from "./heartbeat-run-summ import { resolveChatOriginPublicationBindings } from "./issues.js"; import { NativeRunCoordinatorStore } from "./native-runtime/native-run-coordinator-store.js"; import { finalizeNativeRun, repairCommittedNativeChatResponse } from "./native-runtime/native-run-finalizer.js"; +import { createHeartbeatQueueTestDependencies } from "../__tests__/helpers/heartbeat-queue.js"; +import { createHeartbeatQueue } from "./heartbeat/queue.js"; +import { instanceSettingsService } from "./instance-settings.js"; const support = await getEmbeddedPostgresTestSupport(); if (!support.supported) console.warn(`Monitor publication database unavailable: ${support.reason}`); @@ -27,8 +30,9 @@ if (!support.supported) console.warn(`Monitor publication database unavailable: async function fixture() { const [company] = await db.insert(companies).values({ name: "Continuation", issuePrefix: randomUUID().slice(0, 8) }).returning(); + await db.insert(companyMemberships).values({ companyId: company.id, principalType: "user", principalId: "requester", status: "active", membershipRole: "owner" }); const [agent] = await db.insert(agents).values({ companyId: company.id, name: "Worker", adapterType: "paperclip_runner" }).returning(); - const [issue] = await db.insert(issues).values({ companyId: company.id, title: "Repository QA", status: "in_progress", assigneeAgentId: agent.id }).returning(); + const [issue] = await db.insert(issues).values({ companyId: company.id, title: "Repository QA", status: "in_progress", assigneeAgentId: agent.id, responsibleUserId: "requester" }).returning(); const [application] = await db.insert(toolApplications).values({ companyId: company.id, applicationKey: randomUUID(), name: "Slack", type: "chat" }).returning(); const [connection] = await db.insert(toolConnections).values({ companyId: company.id, applicationId: application.id, name: "Slack", uid: randomUUID(), connectionPurpose: "channel", transport: "chat_sdk" }).returning(); const [endpoint] = await db.insert(chatEndpoints).values({ companyId: company.id, connectionId: connection.id, provider: "slack", publicId: randomUUID(), assignedAgentId: agent.id, status: "active" }).returning(); @@ -161,11 +165,21 @@ if (!support.supported) console.warn(`Monitor publication database unavailable: await finalizeNativeRun({ db, runId: f.source.id, workspaceFinalizeStatus: "succeeded", projectRunStatus: true }); const [wake] = await db.select().from(agentWakeupRequests).where(and(eq(agentWakeupRequests.companyId, f.company.id), eq(agentWakeupRequests.reason, "issue_status_changed"))); expect(wake).toBeDefined(); - await db.update(agentWakeupRequests).set({ status: "claimed", runId: f.successor.id }).where(eq(agentWakeupRequests.id, wake.id)); - await db.update(heartbeatRuns).set({ status: "running", finishedAt: null, - contextSnapshot: { source: "native_status_decision", issueId: f.issue.id }, - }).where(eq(heartbeatRuns.id, f.successor.id)); - return { ...f, continuationWake: wake, summary }; + const deps = createHeartbeatQueueTestDependencies(db); + deps.getAgent.mockImplementation(async id => (await db.select().from(agents).where(eq(agents.id, id)))[0] ?? null); + deps.instanceSettings.getExperimental.mockImplementation(() => instanceSettingsService(db).getExperimental()); + await db.update(agents).set({ runtimeConfig: { heartbeat: { wakeOnDemand: true } } }).where(eq(agents.id, f.agent.id)); + const queue = createHeartbeatQueue(db, deps); + expect(await queue.dispatchPendingNativeStatusWakeups({ companyId: f.company.id })).toMatchObject({ dispatched: 1 }); + await queue.startNextQueuedRunForAgent(f.agent.id); + await Promise.all([...deps.activeRunExecutionPromises]); + const [delivered] = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.id, wake.id)); + expect(delivered).toMatchObject({ status: "coalesced" }); + expect(delivered.runId).toBeTruthy(); + const [successor] = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, delivered.runId!)); + expect(successor.responsibleUserId).toBe("requester"); + return { ...f, successor, continuationWake: delivered, summary, + bindings: (runId = successor.id) => resolveChatOriginPublicationBindings(db, f.company.id, f.issue.id, runId) }; } it("delivers the saved setup explanation and retains chat through the committed automatic continuation", async () => { @@ -182,11 +196,13 @@ if (!support.supported) console.warn(`Monitor publication database unavailable: expect(await db.select().from(nativeRunResults).where(eq(nativeRunResults.runId, f.source.id))).toHaveLength(1); }); - it.each(["effect", "decision", "wake", "requester", "agent"])("rejects automatic continuation when %s authority is no longer valid", async fault => { + it.each(["effect", "decision", "wake", "dispatch", "requester", "agent"])("rejects automatic continuation when %s authority is no longer valid", async fault => { const f = await continuedTaskFixture(); if (fault === "effect") await db.update(statusDecisionEffects).set({ targetId: randomUUID() }).where(eq(statusDecisionEffects.companyId, f.company.id)); if (fault === "decision") await db.update(statusDecisions).set({ applicationState: "proposed" }).where(eq(statusDecisions.companyId, f.company.id)); if (fault === "wake") await db.update(agentWakeupRequests).set({ idempotencyKey: randomUUID() }).where(eq(agentWakeupRequests.id, f.continuationWake.id)); + if (fault === "dispatch") await db.update(agentWakeupRequests).set({ requestedByActorId: "unrelated-dispatch" }).where(and( + eq(agentWakeupRequests.companyId, f.company.id), eq(agentWakeupRequests.requestedByActorId, `native-status-wake-dispatch:${f.continuationWake.id}`))); if (fault === "requester") await db.update(heartbeatRuns).set({ responsibleUserId: "another-person" }).where(eq(heartbeatRuns.id, f.successor.id)); if (fault === "agent") { const [other] = await db.insert(agents).values({ companyId: f.company.id, name: "Other", adapterType: "paperclip_runner" }).returning(); diff --git a/server/src/services/chat-run-publications.ts b/server/src/services/chat-run-publications.ts index 84e3cc6a28e..74eb2ca3901 100644 --- a/server/src/services/chat-run-publications.ts +++ b/server/src/services/chat-run-publications.ts @@ -757,7 +757,7 @@ export async function enqueueChatRunMilestones( and continuation_wake.run_id = ${heartbeatRuns.id} and continuation_wake.agent_id = ${heartbeatRuns.agentId} and continuation_wake.reason = 'issue_status_changed' - and continuation_wake.status in ('claimed', 'completed') + and continuation_wake.status in ('queued', 'claimed', 'completed', 'coalesced') and continuation_wake.payload->>'issueId' = ${issueIdFromContext} ) )`; diff --git a/server/src/services/heartbeat/queue.test.ts b/server/src/services/heartbeat/queue.test.ts index 7952c0da7d0..9af4a5ac956 100644 --- a/server/src/services/heartbeat/queue.test.ts +++ b/server/src/services/heartbeat/queue.test.ts @@ -4,66 +4,12 @@ import { agents, agentWakeupRequests, caseHandoffs, cases, companies, createDb, import { eq, sql } from "drizzle-orm"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "../../__tests__/helpers/embedded-postgres.js"; -import { createRunDispatch } from "../../modules/run-dispatch/index.js"; -import type { createWakeQueue } from "../../modules/wake-queue/index.js"; import { createPostgresWakeQueueAdapter } from "../../modules/wake-queue/adapters/postgres.js"; import { createReleaseIssueExecution } from "../../modules/wake-queue/application/use-cases.js"; -import { createHeartbeatRunState } from "./run-state.js"; -import { createHeartbeatRunPreparation } from "./run-preparation.js"; import { createHeartbeatQueue, type HeartbeatQueueDependencies } from "./queue.js"; -type Run = typeof heartbeatRuns.$inferSelect; -function callbacks(db: Db) { - const state = createHeartbeatRunState(db); - const preparation = createHeartbeatRunPreparation(db); - return { - ...preparation, - ...state, - getAgent: vi.fn(async () => { throw new Error("Unexpected agent lookup"); }), - options: {}, - setRunStatus: vi.fn(async (id, status, patch) => - db.update(heartbeatRuns).set({ ...patch, status }).where(eq(heartbeatRuns.id, id)).returning().then(rows => rows[0] ?? null)), - setWakeupStatus: vi.fn(async () => undefined), - appendRunEvent: vi.fn(async () => undefined), - finalizeAgentStatus: vi.fn(async () => undefined), - getAgentInvokability: vi.fn(async () => ({ invokable: true })), - getWorktreeExecutionCutoff: vi.fn(async () => null), - applyRunDispatchPostCommitEffects: vi.fn(), - issuesSvc: { - getDependencyReadiness: vi.fn(), - listDependencyReadiness: vi.fn(async () => new Map()), - }, - treeControlSvc: { getActivePauseHoldGate: vi.fn(async () => null) }, - budgets: { getInvocationBlock: vi.fn(async () => null) }, - instanceSettings: { getExperimental: vi.fn() }, - runDispatch: createRunDispatch(db), - wakeQueue: { - createAdmissionTransactionScope: vi.fn["createAdmissionTransactionScope"]>(), - admitWakeBehindIssueExecution: vi.fn["admitWakeBehindIssueExecution"]>(), - }, - activeRunExecutions: new Set(), - activeRunExecutionPromises: new Set>(), - activeWakeupPromises: new Set>(), - liveRunExecutions: { has: vi.fn(() => false) }, - isHeartbeatRunTerminalStatus: (status: string | null | undefined) => ["succeeded", "failed", "cancelled", "timed_out"].includes(status ?? ""), - isSameTaskScope: (left: string | null, right: string | null) => left === right, - runTaskKey: (run: Run) => typeof run.contextSnapshot?.issueId === "string" ? run.contextSnapshot.issueId : null, - filterZombieCoalesceTarget: (run: T | null) => run, - getSchedulingSuppression: vi.fn(async () => ({ suppressed: false, reason: null } as Awaited>)), - canStartNewExternalBotWork: vi.fn(async () => true), - executeRun: vi.fn(async () => undefined), - applyWakeQueuePostCommitEffects: vi.fn(async () => undefined), - releaseIssueExecutionAndPromote: vi.fn(async () => undefined), - publishRunLifecyclePluginEvent: vi.fn(), - cancelRunInternal: vi.fn(async () => undefined), - cancelActiveForAgentInternal: vi.fn(async () => undefined), - resumeExecutionWaitComments: vi.fn(async () => undefined), - resumeQueuedCommentInterrupt: vi.fn(async () => undefined), - resumeSavedLegacyComments: vi.fn(async () => undefined), - formatIssueIdentifierLink: (_identifier: string | null, fallback: string) => fallback, - sweepPendingCleanupLeases: vi.fn(), - } satisfies HeartbeatQueueDependencies; -} +import { createHeartbeatQueueTestDependencies as callbacks } from "../../__tests__/helpers/heartbeat-queue.js"; + function guardedDatabase() { const access = vi.fn(() => { throw new Error("Unexpected queue database access"); }); return { db: new Proxy({}, { get: access }) as Db, access }; diff --git a/server/src/services/native-runtime/native-continuation-delivery.ts b/server/src/services/native-runtime/native-continuation-delivery.ts index 8b94204419e..72595082530 100644 --- a/server/src/services/native-runtime/native-continuation-delivery.ts +++ b/server/src/services/native-runtime/native-continuation-delivery.ts @@ -10,14 +10,27 @@ export async function readNativeContinuationSource( binding: { companyId: string; issueId: string; runId: string }, ): Promise { const source = alias(heartbeatRuns, "continuation_source"); + const dispatch = alias(agentWakeupRequests, "continuation_dispatch"); const sources = await db.select({ runId: source.id }).from(heartbeatRuns) .innerJoin(agentWakeupRequests, and( eq(agentWakeupRequests.companyId, binding.companyId), eq(agentWakeupRequests.runId, heartbeatRuns.id), eq(agentWakeupRequests.agentId, heartbeatRuns.agentId), eq(agentWakeupRequests.reason, "issue_status_changed"), - inArray(agentWakeupRequests.status, ["claimed", "completed"]), + eq(agentWakeupRequests.status, "coalesced"), + eq(agentWakeupRequests.requestedByActorType, "system"), + eq(agentWakeupRequests.requestedByActorId, "native-status-committer"), sql`${agentWakeupRequests.payload}->>'issueId' = ${binding.issueId}`, + sql`${heartbeatRuns.contextSnapshot}->>'nativeStatusWakeIntentId' = ${agentWakeupRequests.id}::text`, + )) + .innerJoin(dispatch, and( + eq(dispatch.companyId, binding.companyId), eq(dispatch.agentId, heartbeatRuns.agentId), + eq(dispatch.runId, heartbeatRuns.id), eq(dispatch.reason, "issue_status_changed"), + eq(dispatch.requestedByActorType, "system"), + sql`${dispatch.requestedByActorId} = 'native-status-wake-dispatch:' || ${agentWakeupRequests.id}::text`, + eq(dispatch.idempotencyKey, agentWakeupRequests.idempotencyKey), + inArray(dispatch.status, ["queued", "claimed", "completed", "coalesced"]), + sql`${dispatch.payload}->>'issueId' = ${binding.issueId}`, )) .innerJoin(statusDecisionEffects, and( eq(statusDecisionEffects.companyId, binding.companyId), eq(statusDecisionEffects.issueId, binding.issueId),