feat(runner): support project handoff tools and durable chat turns
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
2083bf6f9a
commit
63a69d0414
|
|
@ -8,9 +8,9 @@ The skill/reference inventory and eval cases are the only normative behavior sou
|
|||
|
||||
## Baseline Counts
|
||||
|
||||
- Skill/reference headings: 153
|
||||
- Skill/reference headings: 154
|
||||
- Eval cases: 106 across 16 groups
|
||||
- Total normative rows: 259
|
||||
- Total normative rows: 260
|
||||
- Legacy MCP aliases folded into normative rows: 42
|
||||
|
||||
| Eval group | Cases |
|
||||
|
|
@ -44,32 +44,33 @@ The skill/reference inventory and eval cases are the only normative behavior sou
|
|||
| skill:skills/paperclip/SKILL.md:paperclip-skill:10 | optional_agent_tool | skills/paperclip/SKILL.md:10 |
|
||||
| skill:skills/paperclip/SKILL.md:terminology:14 | optional_agent_tool | skills/paperclip/SKILL.md:14 |
|
||||
| skill:skills/paperclip/SKILL.md:authentication:18 | control_plane_owned | skills/paperclip/SKILL.md:18 |
|
||||
| skill:skills/paperclip/SKILL.md:server-verified-external-chat-turns:30 | control_plane_owned | skills/paperclip/SKILL.md:30 |
|
||||
| skill:skills/paperclip/SKILL.md:the-heartbeat-procedure:70 | optional_agent_tool | skills/paperclip/SKILL.md:70 |
|
||||
| skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:142 | always_agent_tool | skills/paperclip/SKILL.md:142 |
|
||||
| skill:skills/paperclip/SKILL.md:status-quick-guide:190 | control_plane_owned | skills/paperclip/SKILL.md:190 |
|
||||
| skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:200 | optional_agent_tool | skills/paperclip/SKILL.md:200 |
|
||||
| skill:skills/paperclip/SKILL.md:delegating-review-tasks:213 | always_agent_tool | skills/paperclip/SKILL.md:213 |
|
||||
| skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:224 | control_plane_owned | skills/paperclip/SKILL.md:224 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:232 | control_plane_owned | skills/paperclip/SKILL.md:232 |
|
||||
| skill:skills/paperclip/SKILL.md:requesting-board-approval:257 | optional_agent_tool | skills/paperclip/SKILL.md:257 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-thread-interactions:278 | optional_agent_tool | skills/paperclip/SKILL.md:278 |
|
||||
| skill:skills/paperclip/SKILL.md:standalone-decisions:307 | optional_agent_tool | skills/paperclip/SKILL.md:307 |
|
||||
| skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:411 | optional_agent_tool | skills/paperclip/SKILL.md:411 |
|
||||
| skill:skills/paperclip/SKILL.md:niche-workflow-pointers:453 | optional_agent_tool | skills/paperclip/SKILL.md:453 |
|
||||
| skill:skills/paperclip/SKILL.md:cases:463 | optional_agent_tool | skills/paperclip/SKILL.md:463 |
|
||||
| skill:skills/paperclip/SKILL.md:company-skills-workflow:468 | optional_agent_tool | skills/paperclip/SKILL.md:468 |
|
||||
| skill:skills/paperclip/SKILL.md:routines:479 | optional_agent_tool | skills/paperclip/SKILL.md:479 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:490 | optional_agent_tool | skills/paperclip/SKILL.md:490 |
|
||||
| skill:skills/paperclip/SKILL.md:proposing-credentials-safely:497 | optional_agent_tool | skills/paperclip/SKILL.md:497 |
|
||||
| skill:skills/paperclip/SKILL.md:reading-granted-secrets:504 | optional_agent_tool | skills/paperclip/SKILL.md:504 |
|
||||
| skill:skills/paperclip/SKILL.md:critical-rules:530 | optional_agent_tool | skills/paperclip/SKILL.md:530 |
|
||||
| skill:skills/paperclip/SKILL.md:comment-style-required:554 | always_agent_tool | skills/paperclip/SKILL.md:554 |
|
||||
| skill:skills/paperclip/SKILL.md:update:586 | optional_agent_tool | skills/paperclip/SKILL.md:586 |
|
||||
| skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:596 | optional_agent_tool | skills/paperclip/SKILL.md:596 |
|
||||
| skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:629 | optional_agent_tool | skills/paperclip/SKILL.md:629 |
|
||||
| skill:skills/paperclip/SKILL.md:searching-issues:658 | optional_agent_tool | skills/paperclip/SKILL.md:658 |
|
||||
| skill:skills/paperclip/SKILL.md:full-reference:668 | optional_agent_tool | skills/paperclip/SKILL.md:668 |
|
||||
| skill:skills/paperclip/SKILL.md:conversation-tasks:30 | optional_agent_tool | skills/paperclip/SKILL.md:30 |
|
||||
| skill:skills/paperclip/SKILL.md:server-verified-external-chat-turns:47 | control_plane_owned | skills/paperclip/SKILL.md:47 |
|
||||
| skill:skills/paperclip/SKILL.md:the-heartbeat-procedure:87 | optional_agent_tool | skills/paperclip/SKILL.md:87 |
|
||||
| skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:159 | always_agent_tool | skills/paperclip/SKILL.md:159 |
|
||||
| skill:skills/paperclip/SKILL.md:status-quick-guide:207 | control_plane_owned | skills/paperclip/SKILL.md:207 |
|
||||
| skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:217 | optional_agent_tool | skills/paperclip/SKILL.md:217 |
|
||||
| skill:skills/paperclip/SKILL.md:delegating-review-tasks:230 | always_agent_tool | skills/paperclip/SKILL.md:230 |
|
||||
| skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:241 | control_plane_owned | skills/paperclip/SKILL.md:241 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:249 | control_plane_owned | skills/paperclip/SKILL.md:249 |
|
||||
| skill:skills/paperclip/SKILL.md:requesting-board-approval:274 | optional_agent_tool | skills/paperclip/SKILL.md:274 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-thread-interactions:295 | optional_agent_tool | skills/paperclip/SKILL.md:295 |
|
||||
| skill:skills/paperclip/SKILL.md:standalone-decisions:324 | optional_agent_tool | skills/paperclip/SKILL.md:324 |
|
||||
| skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:428 | optional_agent_tool | skills/paperclip/SKILL.md:428 |
|
||||
| skill:skills/paperclip/SKILL.md:niche-workflow-pointers:470 | optional_agent_tool | skills/paperclip/SKILL.md:470 |
|
||||
| skill:skills/paperclip/SKILL.md:cases:480 | optional_agent_tool | skills/paperclip/SKILL.md:480 |
|
||||
| skill:skills/paperclip/SKILL.md:company-skills-workflow:485 | optional_agent_tool | skills/paperclip/SKILL.md:485 |
|
||||
| skill:skills/paperclip/SKILL.md:routines:496 | optional_agent_tool | skills/paperclip/SKILL.md:496 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:507 | optional_agent_tool | skills/paperclip/SKILL.md:507 |
|
||||
| skill:skills/paperclip/SKILL.md:proposing-credentials-safely:514 | optional_agent_tool | skills/paperclip/SKILL.md:514 |
|
||||
| skill:skills/paperclip/SKILL.md:reading-granted-secrets:521 | optional_agent_tool | skills/paperclip/SKILL.md:521 |
|
||||
| skill:skills/paperclip/SKILL.md:critical-rules:547 | optional_agent_tool | skills/paperclip/SKILL.md:547 |
|
||||
| skill:skills/paperclip/SKILL.md:comment-style-required:571 | always_agent_tool | skills/paperclip/SKILL.md:571 |
|
||||
| skill:skills/paperclip/SKILL.md:update:603 | optional_agent_tool | skills/paperclip/SKILL.md:603 |
|
||||
| skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:613 | optional_agent_tool | skills/paperclip/SKILL.md:613 |
|
||||
| skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:646 | optional_agent_tool | skills/paperclip/SKILL.md:646 |
|
||||
| skill:skills/paperclip/SKILL.md:searching-issues:675 | optional_agent_tool | skills/paperclip/SKILL.md:675 |
|
||||
| skill:skills/paperclip/SKILL.md:full-reference:685 | optional_agent_tool | skills/paperclip/SKILL.md:685 |
|
||||
| skill:skills/paperclip/references/artifacts.md:generated-artifacts-and-work-products:1 | always_agent_tool | skills/paperclip/references/artifacts.md:1 |
|
||||
| skill:skills/paperclip/references/artifacts.md:workspace-only-file-references:15 | optional_agent_tool | skills/paperclip/references/artifacts.md:15 |
|
||||
| skill:skills/paperclip/references/cases.md:cases:1 | optional_agent_tool | skills/paperclip/references/cases.md:1 |
|
||||
|
|
@ -172,28 +173,28 @@ The skill/reference inventory and eval cases are the only normative behavior sou
|
|||
| skill:skills/paperclip/references/api-reference.md:openclaw-invite-prompt-ceo:731 | optional_agent_tool | skills/paperclip/references/api-reference.md:731 |
|
||||
| skill:skills/paperclip/references/api-reference.md:setting-agent-instructions-path:750 | optional_agent_tool | skills/paperclip/references/api-reference.md:750 |
|
||||
| skill:skills/paperclip/references/api-reference.md:project-setup-create-workspace:783 | optional_agent_tool | skills/paperclip/references/api-reference.md:783 |
|
||||
| skill:skills/paperclip/references/api-reference.md:option-a-one-call-create-with-workspace:787 | optional_agent_tool | skills/paperclip/references/api-reference.md:787 |
|
||||
| skill:skills/paperclip/references/api-reference.md:option-b-two-calls-project-first-then-workspace:806 | optional_agent_tool | skills/paperclip/references/api-reference.md:806 |
|
||||
| skill:skills/paperclip/references/api-reference.md:governance-and-approvals:835 | optional_agent_tool | skills/paperclip/references/api-reference.md:835 |
|
||||
| skill:skills/paperclip/references/api-reference.md:requesting-a-hire-management-only:839 | optional_agent_tool | skills/paperclip/references/api-reference.md:839 |
|
||||
| skill:skills/paperclip/references/api-reference.md:ceo-strategy-approval:859 | optional_agent_tool | skills/paperclip/references/api-reference.md:859 |
|
||||
| skill:skills/paperclip/references/api-reference.md:issue-thread-confirmations:868 | always_agent_tool | skills/paperclip/references/api-reference.md:868 |
|
||||
| skill:skills/paperclip/references/api-reference.md:checkbox-confirmations:926 | always_agent_tool | skills/paperclip/references/api-reference.md:926 |
|
||||
| skill:skills/paperclip/references/api-reference.md:item-verdict-requests:1041 | optional_agent_tool | skills/paperclip/references/api-reference.md:1041 |
|
||||
| skill:skills/paperclip/references/api-reference.md:checking-approval-status:1151 | optional_agent_tool | skills/paperclip/references/api-reference.md:1151 |
|
||||
| skill:skills/paperclip/references/api-reference.md:approval-follow-up-requesting-agent:1157 | always_agent_tool | skills/paperclip/references/api-reference.md:1157 |
|
||||
| skill:skills/paperclip/references/api-reference.md:issue-lifecycle:1175 | always_agent_tool | skills/paperclip/references/api-reference.md:1175 |
|
||||
| skill:skills/paperclip/references/api-reference.md:error-handling:1205 | control_plane_owned | skills/paperclip/references/api-reference.md:1205 |
|
||||
| skill:skills/paperclip/references/api-reference.md:full-api-reference:1219 | optional_agent_tool | skills/paperclip/references/api-reference.md:1219 |
|
||||
| skill:skills/paperclip/references/api-reference.md:agents:1221 | optional_agent_tool | skills/paperclip/references/api-reference.md:1221 |
|
||||
| skill:skills/paperclip/references/api-reference.md:issues-tasks:1242 | optional_agent_tool | skills/paperclip/references/api-reference.md:1242 |
|
||||
| skill:skills/paperclip/references/api-reference.md:companies-projects-goals:1282 | optional_agent_tool | skills/paperclip/references/api-reference.md:1282 |
|
||||
| skill:skills/paperclip/references/api-reference.md:routines:1306 | optional_agent_tool | skills/paperclip/references/api-reference.md:1306 |
|
||||
| skill:skills/paperclip/references/api-reference.md:approvals-costs-activity-dashboard:1322 | optional_agent_tool | skills/paperclip/references/api-reference.md:1322 |
|
||||
| skill:skills/paperclip/references/api-reference.md:secrets:1344 | optional_agent_tool | skills/paperclip/references/api-reference.md:1344 |
|
||||
| skill:skills/paperclip/references/api-reference.md:agent-secret-proposals:1357 | optional_agent_tool | skills/paperclip/references/api-reference.md:1357 |
|
||||
| skill:skills/paperclip/references/api-reference.md:agent-secret-access:1457 | optional_agent_tool | skills/paperclip/references/api-reference.md:1457 |
|
||||
| skill:skills/paperclip/references/api-reference.md:common-mistakes:1497 | optional_agent_tool | skills/paperclip/references/api-reference.md:1497 |
|
||||
| skill:skills/paperclip/references/api-reference.md:option-a-one-call-create-with-workspace:807 | optional_agent_tool | skills/paperclip/references/api-reference.md:807 |
|
||||
| skill:skills/paperclip/references/api-reference.md:option-b-two-calls-project-first-then-workspace:826 | optional_agent_tool | skills/paperclip/references/api-reference.md:826 |
|
||||
| skill:skills/paperclip/references/api-reference.md:governance-and-approvals:855 | optional_agent_tool | skills/paperclip/references/api-reference.md:855 |
|
||||
| skill:skills/paperclip/references/api-reference.md:requesting-a-hire-management-only:859 | optional_agent_tool | skills/paperclip/references/api-reference.md:859 |
|
||||
| skill:skills/paperclip/references/api-reference.md:ceo-strategy-approval:879 | optional_agent_tool | skills/paperclip/references/api-reference.md:879 |
|
||||
| skill:skills/paperclip/references/api-reference.md:issue-thread-confirmations:888 | always_agent_tool | skills/paperclip/references/api-reference.md:888 |
|
||||
| skill:skills/paperclip/references/api-reference.md:checkbox-confirmations:946 | always_agent_tool | skills/paperclip/references/api-reference.md:946 |
|
||||
| skill:skills/paperclip/references/api-reference.md:item-verdict-requests:1061 | optional_agent_tool | skills/paperclip/references/api-reference.md:1061 |
|
||||
| skill:skills/paperclip/references/api-reference.md:checking-approval-status:1171 | optional_agent_tool | skills/paperclip/references/api-reference.md:1171 |
|
||||
| skill:skills/paperclip/references/api-reference.md:approval-follow-up-requesting-agent:1177 | always_agent_tool | skills/paperclip/references/api-reference.md:1177 |
|
||||
| skill:skills/paperclip/references/api-reference.md:issue-lifecycle:1195 | always_agent_tool | skills/paperclip/references/api-reference.md:1195 |
|
||||
| skill:skills/paperclip/references/api-reference.md:error-handling:1225 | control_plane_owned | skills/paperclip/references/api-reference.md:1225 |
|
||||
| skill:skills/paperclip/references/api-reference.md:full-api-reference:1239 | optional_agent_tool | skills/paperclip/references/api-reference.md:1239 |
|
||||
| skill:skills/paperclip/references/api-reference.md:agents:1241 | optional_agent_tool | skills/paperclip/references/api-reference.md:1241 |
|
||||
| skill:skills/paperclip/references/api-reference.md:issues-tasks:1262 | optional_agent_tool | skills/paperclip/references/api-reference.md:1262 |
|
||||
| skill:skills/paperclip/references/api-reference.md:companies-projects-goals:1302 | optional_agent_tool | skills/paperclip/references/api-reference.md:1302 |
|
||||
| skill:skills/paperclip/references/api-reference.md:routines:1326 | optional_agent_tool | skills/paperclip/references/api-reference.md:1326 |
|
||||
| skill:skills/paperclip/references/api-reference.md:approvals-costs-activity-dashboard:1342 | optional_agent_tool | skills/paperclip/references/api-reference.md:1342 |
|
||||
| skill:skills/paperclip/references/api-reference.md:secrets:1364 | optional_agent_tool | skills/paperclip/references/api-reference.md:1364 |
|
||||
| skill:skills/paperclip/references/api-reference.md:agent-secret-proposals:1377 | optional_agent_tool | skills/paperclip/references/api-reference.md:1377 |
|
||||
| skill:skills/paperclip/references/api-reference.md:agent-secret-access:1477 | optional_agent_tool | skills/paperclip/references/api-reference.md:1477 |
|
||||
| skill:skills/paperclip/references/api-reference.md:common-mistakes:1517 | optional_agent_tool | skills/paperclip/references/api-reference.md:1517 |
|
||||
|
||||
## Legacy MCP Alias Index
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ the authoritative rows.
|
|||
Only two sources are normative:
|
||||
|
||||
1. The Paperclip skill and its seven references (`SKILL.md` plus
|
||||
`references/*.md`), contributing **152 headings**.
|
||||
`references/*.md`), contributing **153 headings**.
|
||||
2. The Paperclip Evals corpus, contributing **106 cases across 16 groups**.
|
||||
|
||||
Together these produce **258 normative rows**. The legacy Paperclip MCP tool
|
||||
Together these produce **259 normative rows**. The legacy Paperclip MCP tool
|
||||
surface (**41 tools**) is not a production capability surface; each MCP name is
|
||||
folded one-to-one into a normative eval row as a traceability alias and inherits
|
||||
that row's disposition. The contract prints the alias index only so the
|
||||
|
|
|
|||
|
|
@ -31,232 +31,241 @@
|
|||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:30",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L30:server-verified-external-chat-turns",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L30:conversation-tasks",
|
||||
"heading": "Conversation tasks",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"semanticOperation": "runtime_reconciliation",
|
||||
"expectedMockState": "runtime_decision_record"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:47",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L47:server-verified-external-chat-turns",
|
||||
"heading": "Server-Verified External Chat Turns",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"semanticOperation": "runtime_reconciliation",
|
||||
"expectedMockState": "runtime_decision_record"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:70",
|
||||
"id": "skill:skills/paperclip/SKILL.md:87",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L70:the-heartbeat-procedure",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L87:the-heartbeat-procedure",
|
||||
"heading": "The Heartbeat Procedure",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"semanticOperation": "runtime_reconciliation",
|
||||
"expectedMockState": "runtime_decision_record"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:142",
|
||||
"id": "skill:skills/paperclip/SKILL.md:159",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L142:generated-artifacts-and-work-products",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L159: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:190",
|
||||
"id": "skill:skills/paperclip/SKILL.md:207",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L190:status-quick-guide",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L207:status-quick-guide",
|
||||
"heading": "Status Quick Guide",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"semanticOperation": "runtime_reconciliation",
|
||||
"expectedMockState": "runtime_decision_record"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:200",
|
||||
"id": "skill:skills/paperclip/SKILL.md:217",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L200:monitors-and-watchers-say-only-what-you-actually-scheduled",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L217: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:213",
|
||||
"id": "skill:skills/paperclip/SKILL.md:230",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L213:delegating-review-tasks",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L230:delegating-review-tasks",
|
||||
"heading": "Delegating review tasks",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"semanticOperation": "runtime_reconciliation",
|
||||
"expectedMockState": "runtime_decision_record"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:224",
|
||||
"id": "skill:skills/paperclip/SKILL.md:241",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L224:managing-a-user-s-inbox",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L241: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:232",
|
||||
"id": "skill:skills/paperclip/SKILL.md:249",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L232:issue-dependencies-blockers",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L249:issue-dependencies-blockers",
|
||||
"heading": "Issue Dependencies (Blockers)",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"semanticOperation": "runtime_reconciliation",
|
||||
"expectedMockState": "runtime_decision_record"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:257",
|
||||
"id": "skill:skills/paperclip/SKILL.md:274",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L257:requesting-board-approval",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L274:requesting-board-approval",
|
||||
"heading": "Requesting Board Approval",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:278",
|
||||
"id": "skill:skills/paperclip/SKILL.md:295",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L278:issue-thread-interactions",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L295:issue-thread-interactions",
|
||||
"heading": "Issue-Thread Interactions",
|
||||
"primaryDisposition": "always_agent_tool",
|
||||
"semanticOperation": "request_human_input",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:307",
|
||||
"id": "skill:skills/paperclip/SKILL.md:324",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L307:standalone-decisions",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L324:standalone-decisions",
|
||||
"heading": "Standalone Decisions",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"semanticOperation": "runtime_reconciliation",
|
||||
"expectedMockState": "runtime_decision_record"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:411",
|
||||
"id": "skill:skills/paperclip/SKILL.md:428",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L411:mcp-tool-approval-gates",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L428: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:453",
|
||||
"id": "skill:skills/paperclip/SKILL.md:470",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L453:niche-workflow-pointers",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L470:niche-workflow-pointers",
|
||||
"heading": "Niche Workflow Pointers",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"semanticOperation": "runtime_reconciliation",
|
||||
"expectedMockState": "runtime_decision_record"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:463",
|
||||
"id": "skill:skills/paperclip/SKILL.md:480",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L463:cases",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L480:cases",
|
||||
"heading": "Cases",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:468",
|
||||
"id": "skill:skills/paperclip/SKILL.md:485",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L468:company-skills-workflow",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L485:company-skills-workflow",
|
||||
"heading": "Company Skills Workflow",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:479",
|
||||
"id": "skill:skills/paperclip/SKILL.md:496",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L479:routines",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L496:routines",
|
||||
"heading": "Routines",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:490",
|
||||
"id": "skill:skills/paperclip/SKILL.md:507",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L490:issue-workspace-runtime-controls",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L507: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:497",
|
||||
"id": "skill:skills/paperclip/SKILL.md:514",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L497:proposing-credentials-safely",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L514:proposing-credentials-safely",
|
||||
"heading": "Proposing Credentials Safely",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"semanticOperation": "runtime_reconciliation",
|
||||
"expectedMockState": "runtime_decision_record"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:504",
|
||||
"id": "skill:skills/paperclip/SKILL.md:521",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L504:reading-granted-secrets",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L521:reading-granted-secrets",
|
||||
"heading": "Reading Granted Secrets",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:530",
|
||||
"id": "skill:skills/paperclip/SKILL.md:547",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L530:critical-rules",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L547:critical-rules",
|
||||
"heading": "Critical Rules",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"semanticOperation": "runtime_reconciliation",
|
||||
"expectedMockState": "runtime_decision_record"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:554",
|
||||
"id": "skill:skills/paperclip/SKILL.md:571",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L554:comment-style-required",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L571:comment-style-required",
|
||||
"heading": "Comment Style (Required)",
|
||||
"primaryDisposition": "always_agent_tool",
|
||||
"semanticOperation": "report_progress",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:586",
|
||||
"id": "skill:skills/paperclip/SKILL.md:603",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L586:update",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L603:update",
|
||||
"heading": "Update",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"semanticOperation": "runtime_reconciliation",
|
||||
"expectedMockState": "runtime_decision_record"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:596",
|
||||
"id": "skill:skills/paperclip/SKILL.md:613",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L596:planning-required-when-planning-requested",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L613: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:629",
|
||||
"id": "skill:skills/paperclip/SKILL.md:646",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L629:key-endpoints-hot-routes",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L646: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:658",
|
||||
"id": "skill:skills/paperclip/SKILL.md:675",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L658:searching-issues",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L675:searching-issues",
|
||||
"heading": "Searching Issues",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"semanticOperation": "runtime_reconciliation",
|
||||
"expectedMockState": "runtime_decision_record"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:668",
|
||||
"id": "skill:skills/paperclip/SKILL.md:685",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L668:full-reference",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md#L685:full-reference",
|
||||
"heading": "Full Reference",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"semanticOperation": "runtime_reconciliation",
|
||||
|
|
@ -677,207 +686,207 @@
|
|||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:787",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:807",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L787:option-a-one-call-create-with-workspace",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L807:option-a-one-call-create-with-workspace",
|
||||
"heading": "Option A: One-call create with workspace",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:806",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:826",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L806:option-b-two-calls-project-first-then-workspace",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L826:option-b-two-calls-project-first-then-workspace",
|
||||
"heading": "Option B: Two calls (project first, then workspace)",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:835",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:855",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L835:governance-and-approvals",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L855:governance-and-approvals",
|
||||
"heading": "Governance and Approvals",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:839",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L839:requesting-a-hire-management-only",
|
||||
"heading": "Requesting a hire (management only)",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:859",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L859:ceo-strategy-approval",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L859:requesting-a-hire-management-only",
|
||||
"heading": "Requesting a hire (management only)",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:879",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L879:ceo-strategy-approval",
|
||||
"heading": "CEO strategy approval",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:868",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:888",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L868:issue-thread-confirmations",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L888:issue-thread-confirmations",
|
||||
"heading": "Issue-thread confirmations",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:926",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:946",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L926:checkbox-confirmations",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L946:checkbox-confirmations",
|
||||
"heading": "Checkbox confirmations",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1041",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1061",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1041:item-verdict-requests",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1061:item-verdict-requests",
|
||||
"heading": "Item verdict requests",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1151",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1171",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1151:checking-approval-status",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1171:checking-approval-status",
|
||||
"heading": "Checking approval status",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1157",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1177",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1157:approval-follow-up-requesting-agent",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1177:approval-follow-up-requesting-agent",
|
||||
"heading": "Approval follow-up (requesting agent)",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"semanticOperation": "runtime_reconciliation",
|
||||
"expectedMockState": "runtime_decision_record"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1175",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1195",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1175:issue-lifecycle",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1195:issue-lifecycle",
|
||||
"heading": "Issue Lifecycle",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1205",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1225",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1205:error-handling",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1225:error-handling",
|
||||
"heading": "Error Handling",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"semanticOperation": "runtime_reconciliation",
|
||||
"expectedMockState": "runtime_decision_record"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1219",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1239",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1219:full-api-reference",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1239:full-api-reference",
|
||||
"heading": "Full API Reference",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1221",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1241",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1221:agents",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1241:agents",
|
||||
"heading": "Agents",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1242",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1262",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1242:issues-tasks",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1262:issues-tasks",
|
||||
"heading": "Issues (Tasks)",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1282",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1302",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1282:companies-projects-goals",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1302:companies-projects-goals",
|
||||
"heading": "Companies, Projects, Goals",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1306",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1326",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1306:routines",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1326:routines",
|
||||
"heading": "Routines",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1322",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1342",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1322:approvals-costs-activity-dashboard",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1342:approvals-costs-activity-dashboard",
|
||||
"heading": "Approvals, Costs, Activity, Dashboard",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"semanticOperation": "runtime_reconciliation",
|
||||
"expectedMockState": "runtime_decision_record"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1344",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1364",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1344:secrets",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1364:secrets",
|
||||
"heading": "Secrets",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1357",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1377",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1357:agent-secret-proposals",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1377:agent-secret-proposals",
|
||||
"heading": "Agent secret proposals",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1410",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1430",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1410:re-bind-an-existing-secret-under-a-new-path-no-secret-id",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1430:re-bind-an-existing-secret-under-a-new-path-no-secret-id",
|
||||
"heading": "Re-bind an existing secret under a new path (no secret ID)",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1457",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1477",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1457:agent-secret-access",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1477:agent-secret-access",
|
||||
"heading": "Agent secret access",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
"expectedMockState": "operation_result"
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1497",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:1517",
|
||||
"kind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1497:common-mistakes",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md#L1517:common-mistakes",
|
||||
"heading": "Common Mistakes",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"semanticOperation": "scoped_discovery",
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
Generated by `scripts/generate-capability-contract.mjs`; do not edit generated files.
|
||||
|
||||
- Skill/reference headings: 154
|
||||
- Skill/reference headings: 155
|
||||
- Legacy MCP tools: 42
|
||||
- Eval cases: 106 across 16 groups
|
||||
- Deterministic content SHA-256: `7d89b580b41830403a625dc44644e5faf9b5eb83a27706bc2d624d9da464d331`
|
||||
- Deterministic content SHA-256: `f83b043e95387a56679241e89e330600e198b890e83e1b21012cf2f4b9210a00`
|
||||
|
||||
Every row has exactly one primary disposition, a source anchor, a semantic operation, and a mock-state expectation.
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1556,9 +1556,210 @@
|
|||
{
|
||||
"allowedModes": [
|
||||
"standard",
|
||||
"ask",
|
||||
"planning",
|
||||
"skill_test"
|
||||
],
|
||||
"description": "Create one child task under the active task.",
|
||||
"description": "Inspect available company projects before selecting a project for new work.",
|
||||
"effect": "read",
|
||||
"inputSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {},
|
||||
"required": [],
|
||||
"type": "object"
|
||||
},
|
||||
"operationId": "list_projects",
|
||||
"outputSchema": {
|
||||
"additionalProperties": true,
|
||||
"type": "object"
|
||||
},
|
||||
"placement": "optional",
|
||||
"requiredClaims": [
|
||||
"discovery:projects:read"
|
||||
],
|
||||
"schema": "paperclip.semantic-action.v1",
|
||||
"title": "List projects",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"allowedModes": [
|
||||
"standard",
|
||||
"ask",
|
||||
"planning",
|
||||
"skill_test"
|
||||
],
|
||||
"description": "List authorized repositories with stable IDs and names. Consider appropriate repositories before creating a project; never invent IDs.",
|
||||
"effect": "read",
|
||||
"inputSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {},
|
||||
"required": [],
|
||||
"type": "object"
|
||||
},
|
||||
"operationId": "list_project_repositories",
|
||||
"outputSchema": {
|
||||
"additionalProperties": true,
|
||||
"type": "object"
|
||||
},
|
||||
"placement": "optional",
|
||||
"requiredClaims": [],
|
||||
"schema": "paperclip.semantic-action.v1",
|
||||
"title": "List available repositories",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"allowedModes": [
|
||||
"standard",
|
||||
"skill_test"
|
||||
],
|
||||
"description": "Create a project after considering existing projects and available repositories. repositoryIds and repositoryUrls accept multiple existing repositories. Use HTTPS GitHub repositoryUrls when an accessible repo is not in the catalog; this registers project repositories, not remote GitHub repositories. Non-code projects may omit repositories. Cannot combine repositoryIds/repositoryUrls with workspace. Reuse the idempotency key on retries.",
|
||||
"effect": "write",
|
||||
"inputSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"archivedAt": {
|
||||
"description": "Archive timestamp.",
|
||||
"maxLength": 20000,
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"color": {
|
||||
"description": "Project color.",
|
||||
"maxLength": 20000,
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"description": "Project outcome and context.",
|
||||
"maxLength": 20000,
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"env": {
|
||||
"additionalProperties": true,
|
||||
"type": "object"
|
||||
},
|
||||
"executionWorkspacePolicy": {
|
||||
"additionalProperties": true,
|
||||
"type": "object"
|
||||
},
|
||||
"goalId": {
|
||||
"description": "Goal ID.",
|
||||
"maxLength": 20000,
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"goalIds": {
|
||||
"description": "Goal IDs.",
|
||||
"items": {
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"maxItems": 200,
|
||||
"type": "array",
|
||||
"uniqueItems": true
|
||||
},
|
||||
"icon": {
|
||||
"description": "Project icon.",
|
||||
"maxLength": 20000,
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"idempotencyKey": {
|
||||
"description": "Caller-stable retry key.",
|
||||
"maxLength": 240,
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"leadAgentId": {
|
||||
"description": "Lead agent ID.",
|
||||
"maxLength": 20000,
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"description": "Project name.",
|
||||
"maxLength": 500,
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"repositoryIds": {
|
||||
"description": "Authorized repository IDs from list_project_repositories; may contain multiple repositories.",
|
||||
"items": {
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"maxItems": 200,
|
||||
"type": "array",
|
||||
"uniqueItems": true
|
||||
},
|
||||
"repositoryUrls": {
|
||||
"description": "Existing HTTPS GitHub repository URLs, including repos absent from the catalog.",
|
||||
"items": {
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"maxItems": 200,
|
||||
"type": "array",
|
||||
"uniqueItems": true
|
||||
},
|
||||
"status": {
|
||||
"enum": [
|
||||
"backlog",
|
||||
"planned",
|
||||
"in_progress",
|
||||
"completed",
|
||||
"cancelled"
|
||||
]
|
||||
},
|
||||
"targetDate": {
|
||||
"description": "Target date.",
|
||||
"maxLength": 20000,
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"workspace": {
|
||||
"additionalProperties": true,
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"idempotencyKey",
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"operationId": "create_project",
|
||||
"outputSchema": {
|
||||
"additionalProperties": true,
|
||||
"type": "object"
|
||||
},
|
||||
"placement": "optional",
|
||||
"requiredClaims": [],
|
||||
"schema": "paperclip.semantic-action.v1",
|
||||
"title": "Create project",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"allowedModes": [
|
||||
"standard",
|
||||
"skill_test"
|
||||
],
|
||||
"description": "Create an assigned task. In a conversation, create a project task with no parent; otherwise create a child of the active task. Include initialPlan to persist its plan before execution.",
|
||||
"effect": "write",
|
||||
"inputSchema": {
|
||||
"additionalProperties": false,
|
||||
|
|
@ -1595,6 +1796,14 @@
|
|||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"initialPlan": {
|
||||
"description": "Relevant markdown plan to persist on the new task before it starts.",
|
||||
"maxLength": 20000,
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"priority": {
|
||||
"enum": [
|
||||
"critical",
|
||||
|
|
@ -1603,8 +1812,16 @@
|
|||
"low"
|
||||
]
|
||||
},
|
||||
"projectId": {
|
||||
"description": "Project identifier for the new task.",
|
||||
"maxLength": 20000,
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"description": "Child task title.",
|
||||
"description": "Task title.",
|
||||
"maxLength": 500,
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
|
|
@ -1671,7 +1888,7 @@
|
|||
"delegation:tasks:create"
|
||||
],
|
||||
"schema": "paperclip.semantic-action.v1",
|
||||
"title": "Create child task",
|
||||
"title": "Create task",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use serde_json::Value;
|
|||
|
||||
use crate::acpx_event_scope::AcpxEventScope;
|
||||
use crate::acpx_sidecar_transport::AcpxSidecarEvent;
|
||||
use crate::durable::{redact_text, sanitize_value};
|
||||
use crate::durable::{redact_text, sanitize_semantic_tool_input, sanitize_value};
|
||||
use crate::generated_acpx_sidecar_contract::{
|
||||
classify_generated_acpx_tool_operation, GeneratedAcpxSidecarEventType,
|
||||
};
|
||||
|
|
@ -133,11 +133,19 @@ pub fn decode_acpx_event(
|
|||
"ACPX tool call input must be an object",
|
||||
));
|
||||
}
|
||||
let operation_id = required_id(&event.payload, "operationId", "tool operation")?;
|
||||
// This input is dispatched as a mutation, not merely displayed in
|
||||
// the event feed. Use the same declared-prose policy as native
|
||||
// semantic_tool.input before any generic diagnostic scrub can
|
||||
// irreversibly change the task's requirements.
|
||||
let safe_input = sanitize_semantic_tool_input(&operation_id, &input)
|
||||
.map_err(|error| LocalRunnerError::invalid(error.to_string()))?;
|
||||
Ok(AcpxEventPayload::ToolCalled {
|
||||
call_id: required_id(&event.payload, "callId", "tool call")?,
|
||||
operation_id: required_id(&event.payload, "operationId", "tool operation")?,
|
||||
operation_id,
|
||||
// Keep the original digest for the sidecar's result binding.
|
||||
input_digest: semantic_value_digest(&input),
|
||||
input: sanitize_value(&input),
|
||||
input: safe_input,
|
||||
})
|
||||
}
|
||||
GeneratedAcpxSidecarEventType::RuntimeTurnTerminal => {
|
||||
|
|
|
|||
|
|
@ -632,7 +632,9 @@ impl AcpxCommandExecutor {
|
|||
event_type: "run.terminal".to_owned(),
|
||||
priority: EventPriority::P0,
|
||||
payload: json!({
|
||||
"schema": "paperclip.prp.terminal.v1",
|
||||
"status": "failed",
|
||||
"turnTerminalState": "failed",
|
||||
"runTerminalState": "failed",
|
||||
"reportedWorkDisposition": "unknown",
|
||||
"provider": "acpx",
|
||||
|
|
@ -1393,11 +1395,11 @@ impl AcpxCommandExecutor {
|
|||
{
|
||||
continue;
|
||||
}
|
||||
let status = match event_type.as_str() {
|
||||
"turn.completed" => "succeeded",
|
||||
"turn.cancelled" => "cancelled",
|
||||
"turn.interrupted" => "interrupted",
|
||||
_ => "failed",
|
||||
let (turn_terminal_state, status) = match event_type.as_str() {
|
||||
"turn.completed" => ("completed", "succeeded"),
|
||||
"turn.cancelled" => ("cancelled", "cancelled"),
|
||||
"turn.interrupted" => ("interrupted", "cancelled"),
|
||||
_ => ("failed", "failed"),
|
||||
};
|
||||
let disposition = goal_terminal_disposition(
|
||||
state
|
||||
|
|
@ -1415,7 +1417,9 @@ impl AcpxCommandExecutor {
|
|||
event_type: "run.terminal".to_owned(),
|
||||
priority: EventPriority::P0,
|
||||
payload: json!({
|
||||
"schema": "paperclip.prp.terminal.v1",
|
||||
"status": status,
|
||||
"turnTerminalState": turn_terminal_state,
|
||||
"runTerminalState": status,
|
||||
"reportedWorkDisposition": disposition,
|
||||
"provider": "acpx",
|
||||
|
|
@ -1527,6 +1531,13 @@ impl CommandExecutor for AcpxCommandExecutor {
|
|||
return Ok(Vec::new());
|
||||
}
|
||||
self.poll_provider()?;
|
||||
self.retained_events()
|
||||
}
|
||||
|
||||
fn retained_events(&mut self) -> Result<Vec<PolledEvent>, DurableRunnerError> {
|
||||
// Explicit drain runs while control traffic suppresses provider polling.
|
||||
// Expose the already-retained suffix so runnerd can commit and ACK it
|
||||
// before suspension, without restoring or advancing the provider.
|
||||
Ok(self
|
||||
.state
|
||||
.as_ref()
|
||||
|
|
@ -1805,6 +1816,57 @@ mod tests {
|
|||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retained_events_exposes_terminal_suffix_without_restoring_provider() {
|
||||
let directory = temporary_directory("retained-terminal-suffix");
|
||||
let config = test_config(&directory, None);
|
||||
let mut executor = AcpxCommandExecutor::with_runner_config(&directory, &config);
|
||||
// Invalid on-disk state would fail restoration. Retained-only reads
|
||||
// must neither restore a provider nor inspect a different state owner.
|
||||
fs::write(executor.state_path(), b"not provider state").unwrap();
|
||||
assert!(executor.retained_events().unwrap().is_empty());
|
||||
|
||||
let operations = Vec::new();
|
||||
let tool_set = AuthorizedToolSet {
|
||||
schema: TOOL_SET_SCHEMA.to_owned(),
|
||||
schema_version: 1,
|
||||
catalog_digest: authorized_tool_catalog_digest(&operations).unwrap(),
|
||||
operations,
|
||||
};
|
||||
let mut state = AcpxDurableState::new(
|
||||
serde_json::from_value(descriptor("claude")).unwrap(),
|
||||
tool_set,
|
||||
"retained-only-test".to_owned(),
|
||||
);
|
||||
state.lifecycle = "session_open".to_owned();
|
||||
for event_type in ["turn.completed", "run.usage", "run.completed"] {
|
||||
state
|
||||
.push(NormalizedProviderEvent {
|
||||
event_type: event_type.to_owned(),
|
||||
priority: EventPriority::P0,
|
||||
payload: json!({}),
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
executor.state = Some(state);
|
||||
let suffix = executor.retained_events().unwrap();
|
||||
assert_eq!(
|
||||
suffix
|
||||
.iter()
|
||||
.map(|event| event.event_type.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
vec!["turn.completed", "run.usage", "run.completed"],
|
||||
);
|
||||
// Reading is not acknowledgement: a retry sees the exact same FIFO.
|
||||
assert_eq!(executor.retained_events().unwrap(), suffix);
|
||||
assert!(executor.session.is_none());
|
||||
assert_eq!(
|
||||
fs::read(executor.state_path()).unwrap(),
|
||||
b"not provider state"
|
||||
);
|
||||
fs::remove_dir_all(directory).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admits_only_exact_qualified_claude_and_codex_descriptors() {
|
||||
for agent in ["claude", "codex"] {
|
||||
|
|
@ -2169,6 +2231,8 @@ mod tests {
|
|||
assert_eq!(events[0].event_type, "turn.failed");
|
||||
assert_eq!(events[0].payload["providerShutdownFailed"], true);
|
||||
assert_eq!(events[1].event_type, "run.terminal");
|
||||
assert_eq!(events[1].payload["schema"], "paperclip.prp.terminal.v1");
|
||||
assert_eq!(events[1].payload["turnTerminalState"], "failed");
|
||||
let cleanup_error = recovered
|
||||
.shutdown()
|
||||
.expect_err("cleanup must not succeed while the original lifetime remains active");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::collections::{BTreeSet, VecDeque};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc::RecvTimeoutError;
|
||||
use std::time::{Duration, Instant};
|
||||
|
|
@ -82,6 +82,7 @@ pub struct AcpxSidecarTransport {
|
|||
last_event_sequence: u64,
|
||||
buffered_events: VecDeque<AcpxSidecarEvent>,
|
||||
stderr_tail: BoundedLogBuffer,
|
||||
stderr_categories: BTreeSet<&'static str>,
|
||||
poisoned: bool,
|
||||
}
|
||||
|
||||
|
|
@ -154,6 +155,7 @@ impl AcpxSidecarTransport {
|
|||
last_event_sequence: 0,
|
||||
buffered_events: VecDeque::new(),
|
||||
stderr_tail: BoundedLogBuffer::new(32, 8 * 1024),
|
||||
stderr_categories: BTreeSet::new(),
|
||||
poisoned: false,
|
||||
})
|
||||
}
|
||||
|
|
@ -323,7 +325,7 @@ impl AcpxSidecarTransport {
|
|||
match self.process.recv_timeout(remaining) {
|
||||
Ok(ProcessOutput::Stdout(line)) => return Ok(Some(line)),
|
||||
Ok(ProcessOutput::Stderr(line)) => {
|
||||
self.stderr_tail.push(redact_diagnostic(&line));
|
||||
self.record_stderr(&line);
|
||||
}
|
||||
Ok(ProcessOutput::StdoutError(message)) => {
|
||||
return Err(LocalRunnerError::invalid(format!(
|
||||
|
|
@ -412,7 +414,7 @@ impl AcpxSidecarTransport {
|
|||
};
|
||||
match output {
|
||||
Some(ProcessOutput::Stderr(line)) => {
|
||||
self.stderr_tail.push(redact_diagnostic(&line));
|
||||
self.record_stderr(&line);
|
||||
}
|
||||
Some(ProcessOutput::StderrClosed) | None => break,
|
||||
Some(ProcessOutput::Stdout(_))
|
||||
|
|
@ -424,13 +426,33 @@ impl AcpxSidecarTransport {
|
|||
|
||||
fn diagnostic_suffix(&self) -> String {
|
||||
let diagnostics = self.stderr_tail.snapshot().lines.join("\n");
|
||||
if diagnostics.is_empty() {
|
||||
let categories = if self.stderr_categories.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" stderrTail={diagnostics:?}")
|
||||
format!(
|
||||
" stderrCategories={}",
|
||||
self.stderr_categories
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<Vec<_>>()
|
||||
.join(",")
|
||||
)
|
||||
};
|
||||
if diagnostics.is_empty() {
|
||||
categories
|
||||
} else {
|
||||
format!("{categories} stderrTail={diagnostics:?}")
|
||||
}
|
||||
}
|
||||
|
||||
fn record_stderr(&mut self, line: &str) {
|
||||
// Only fixed categories cross this boundary. Raw errors, stack paths,
|
||||
// identifiers, and credential-bearing strings remain fully redacted.
|
||||
self.stderr_categories
|
||||
.extend(stderr_diagnostic_categories(line));
|
||||
self.stderr_tail.push(redact_diagnostic(line));
|
||||
}
|
||||
|
||||
fn poison(&mut self) {
|
||||
if self.poisoned {
|
||||
return;
|
||||
|
|
@ -598,6 +620,53 @@ fn redact_diagnostic(value: &str) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
fn stderr_diagnostic_categories(value: &str) -> BTreeSet<&'static str> {
|
||||
const CATEGORIES: &[(&str, &str)] = &[
|
||||
("TypeError", "javascript_type_error"),
|
||||
("ReferenceError", "javascript_reference_error"),
|
||||
("SyntaxError", "javascript_syntax_error"),
|
||||
("RangeError", "javascript_range_error"),
|
||||
("AssertionError", "javascript_assertion_error"),
|
||||
("UnhandledPromiseRejection", "unhandled_rejection"),
|
||||
("ERR_UNHANDLED_REJECTION", "unhandled_rejection"),
|
||||
("ERR_UNHANDLED_ERROR", "unhandled_event_error"),
|
||||
("ERR_INVALID_ARG_TYPE", "invalid_argument_type"),
|
||||
("ERR_INVALID_ARG_VALUE", "invalid_argument_value"),
|
||||
("ERR_STREAM_WRITE_AFTER_END", "stream_write_after_end"),
|
||||
("ERR_STREAM_DESTROYED", "stream_destroyed"),
|
||||
("ERR_IPC_CHANNEL_CLOSED", "ipc_channel_closed"),
|
||||
("ERR_SOCKET_CLOSED", "socket_closed"),
|
||||
("ERR_MODULE_NOT_FOUND", "module_not_found"),
|
||||
("MODULE_NOT_FOUND", "module_not_found"),
|
||||
("EPIPE", "broken_pipe"),
|
||||
("ECONNRESET", "connection_reset"),
|
||||
("EADDRINUSE", "address_in_use"),
|
||||
("ENOENT", "file_not_found"),
|
||||
("EACCES", "permission_denied"),
|
||||
("EPERM", "permission_denied"),
|
||||
(
|
||||
"ACPX_PERSISTED_SESSION_IDENTITY_MISMATCH",
|
||||
"persisted_session_identity_mismatch",
|
||||
),
|
||||
("SESSION_RESUME_REQUIRED", "session_resume_required"),
|
||||
];
|
||||
let mut categories: BTreeSet<&'static str> = value
|
||||
.split(|character: char| !character.is_ascii_alphanumeric() && character != '_')
|
||||
.filter_map(|token| {
|
||||
CATEGORIES
|
||||
.iter()
|
||||
.find_map(|(known, category)| (token == *known).then_some(*category))
|
||||
})
|
||||
.collect();
|
||||
if value.contains("triggerUncaughtException") && value.contains("fromPromise") {
|
||||
categories.insert("unhandled_rejection");
|
||||
}
|
||||
if value.contains("ACPX provider spawned after ownership admission was sealed") {
|
||||
categories.insert("provider_spawn_after_ownership_seal");
|
||||
}
|
||||
categories
|
||||
}
|
||||
|
||||
fn response_error_classification(error: &ResponseError) -> &'static str {
|
||||
match error.code.as_str() {
|
||||
"ACP_MODEL_UNSUPPORTED" => return "requested_model_unsupported",
|
||||
|
|
@ -642,6 +711,17 @@ fn response_error_classification(error: &ResponseError) -> &'static str {
|
|||
_ => {}
|
||||
}
|
||||
match error.message.as_str() {
|
||||
"ACPX provider spawned after ownership admission was sealed" => {
|
||||
"provider_spawn_after_ownership_seal"
|
||||
}
|
||||
"ACPX recovery identity conflicts with the immutable session configuration" => {
|
||||
"recovery_configuration_mismatch"
|
||||
}
|
||||
"ACPX recovery identity does not match the persisted runtime record" => {
|
||||
"recovery_identity_mismatch"
|
||||
}
|
||||
"ACPX provider lifetime lease is unavailable" => "provider_lifetime_unavailable",
|
||||
"Managed Codex credential home already has an active lease" => "provider_lifetime_owned",
|
||||
"ACPX session handshake exceeded its admission deadline" => "session_handshake_timeout",
|
||||
"ACPX provider lifetime guardian exited before ownership transfer" => {
|
||||
"provider_guardian_exit"
|
||||
|
|
@ -734,6 +814,39 @@ mod tests {
|
|||
)),
|
||||
"session_handshake_timeout"
|
||||
);
|
||||
for (message, classification) in [
|
||||
(
|
||||
"ACPX recovery identity conflicts with the immutable session configuration",
|
||||
"recovery_configuration_mismatch",
|
||||
),
|
||||
(
|
||||
"ACPX recovery identity does not match the persisted runtime record",
|
||||
"recovery_identity_mismatch",
|
||||
),
|
||||
(
|
||||
"ACPX provider lifetime lease is unavailable",
|
||||
"provider_lifetime_unavailable",
|
||||
),
|
||||
] {
|
||||
assert_eq!(
|
||||
response_error_classification(&error("acpx_sidecar_command_failed", message)),
|
||||
classification
|
||||
);
|
||||
assert_eq!(
|
||||
response_error_classification(&error(
|
||||
"acpx_sidecar_command_failed",
|
||||
&format!("{message}: private-provider-detail")
|
||||
)),
|
||||
"unclassified"
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
response_error_classification(&error(
|
||||
"acpx_sidecar_command_failed",
|
||||
"Managed Codex credential home already has an active lease"
|
||||
)),
|
||||
"provider_lifetime_owned"
|
||||
);
|
||||
let admission_failures = [
|
||||
(
|
||||
"ACPX_RUNTIME_ADMISSION_VERIFICATION_TIMEOUT",
|
||||
|
|
|
|||
|
|
@ -2670,6 +2670,9 @@ mod tests {
|
|||
}
|
||||
|
||||
fn read_http_request(socket: &mut TcpStream) -> Result<CapturedRequest, std::io::Error> {
|
||||
// Darwin inherits the listener's nonblocking flag on accept. Wait for
|
||||
// request bytes within the timeout instead of dropping an early accept.
|
||||
socket.set_nonblocking(false)?;
|
||||
socket.set_read_timeout(Some(Duration::from_secs(2)))?;
|
||||
let mut bytes = Vec::new();
|
||||
let mut buffer = [0_u8; 4096];
|
||||
|
|
@ -2722,6 +2725,34 @@ mod tests {
|
|||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fake_service_waits_for_request_bytes_on_an_accepted_nonblocking_socket() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let mut client = TcpStream::connect(listener.local_addr().unwrap()).unwrap();
|
||||
let (mut accepted, _) = listener.accept().unwrap();
|
||||
// Reproduce Darwin's inherited listener flag on every test platform.
|
||||
accepted.set_nonblocking(true).unwrap();
|
||||
let (result_tx, result_rx) = mpsc::channel();
|
||||
let reader = thread::spawn(move || {
|
||||
result_tx.send(read_http_request(&mut accepted)).unwrap();
|
||||
});
|
||||
assert!(matches!(
|
||||
result_rx.recv_timeout(Duration::from_millis(25)),
|
||||
Err(mpsc::RecvTimeoutError::Timeout)
|
||||
));
|
||||
client
|
||||
.write_all(b"POST /delayed HTTP/1.1\r\nContent-Length: 2\r\n\r\n{}")
|
||||
.unwrap();
|
||||
let request = result_rx
|
||||
.recv_timeout(Duration::from_secs(3))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
reader.join().unwrap();
|
||||
assert_eq!(request.method, "POST");
|
||||
assert_eq!(request.path, "/delayed");
|
||||
assert_eq!(request.body, "{}");
|
||||
}
|
||||
|
||||
fn send_json_response(socket: &mut TcpStream, status: &str, value: &Value) {
|
||||
let body = serde_json::to_string(value).unwrap();
|
||||
let _ = write!(socket, "HTTP/1.1 {status}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", body.len());
|
||||
|
|
|
|||
|
|
@ -1597,6 +1597,28 @@ pub(crate) fn sanitize_semantic_tool_input(
|
|||
input: &Value,
|
||||
) -> Result<Value, DurableRunnerError> {
|
||||
let mut sanitized = sanitize_value(input);
|
||||
// Mutation prose is the user's intended work, not a diagnostic. Preserve
|
||||
// ordinary references to a token in these declared text fields; credential
|
||||
// syntax and high-confidence secret values are still scrubbed. All other
|
||||
// fields and operations retain the strict diagnostic policy.
|
||||
let prose_fields: &[&str] = match operation_id {
|
||||
"create_task" => &["title", "description", "initialPlan"],
|
||||
"create_project" => &["name", "description"],
|
||||
"write_document" => &["title", "body", "changeSummary"],
|
||||
_ => &[],
|
||||
};
|
||||
if let Some(sanitized_input) = sanitized.as_object_mut() {
|
||||
for field in prose_fields {
|
||||
if let Some(text) = input.get(*field).and_then(Value::as_str) {
|
||||
sanitized_input.insert(
|
||||
(*field).to_owned(),
|
||||
// The tool/API schema bounds business content. A diagnostic
|
||||
// preview limit must never truncate a plan or document.
|
||||
Value::String(redact_sensitive_text_values_with_context(text, true)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !matches!(operation_id, "paperclip_finish" | "paperclip_block") {
|
||||
return Ok(sanitized);
|
||||
}
|
||||
|
|
@ -1720,6 +1742,10 @@ pub(crate) fn redact_text(input: &str) -> String {
|
|||
}
|
||||
|
||||
fn redact_sensitive_text_values(input: &str) -> String {
|
||||
redact_sensitive_text_values_with_context(input, false)
|
||||
}
|
||||
|
||||
fn redact_sensitive_text_values_with_context(input: &str, semantic_prose: bool) -> String {
|
||||
let normalized = input.to_ascii_lowercase();
|
||||
let bytes = normalized.as_bytes();
|
||||
let mut ranges: Vec<(usize, usize)> = Vec::new();
|
||||
|
|
@ -1893,6 +1919,7 @@ fn redact_sensitive_text_values(input: &str) -> String {
|
|||
("ghu_", 20),
|
||||
("ghs_", 20),
|
||||
("ghr_", 20),
|
||||
("github_pat_", 20),
|
||||
] {
|
||||
for (start, _) in normalized.match_indices(prefix) {
|
||||
if start > 0 && is_name_byte(bytes[start - 1]) {
|
||||
|
|
@ -2088,6 +2115,35 @@ fn redact_sensitive_text_values(input: &str) -> String {
|
|||
.any(|delimiter| before.ends_with(delimiter))
|
||||
};
|
||||
let has_hyphenated_count_lead = token_phrase_has_lead("one-");
|
||||
// A bare token reference in declared mutation prose can be an output
|
||||
// requirement. Auth/access/session context, explicit assignment,
|
||||
// quoted credentials and CLI/compound names remain credential pairs.
|
||||
// Known key/JWT/Bearer values are independently scrubbed above.
|
||||
let is_semantic_token_reference = semantic_prose
|
||||
&& key == "token"
|
||||
&& !key_is_compound
|
||||
&& whitespace_start == start + key.len()
|
||||
&& separator > whitespace_start
|
||||
&& !has_assignment_separator
|
||||
&& bytes[whitespace_start..separator]
|
||||
.iter()
|
||||
.all(|value| matches!(value, b' ' | b'\t'))
|
||||
&& quoted_value_start(separator).1.is_none()
|
||||
&& ![
|
||||
"auth ",
|
||||
"authentication ",
|
||||
"authorization ",
|
||||
"access ",
|
||||
"refresh ",
|
||||
"session ",
|
||||
"api ",
|
||||
"security ",
|
||||
"secret ",
|
||||
"credential ",
|
||||
"bearer ",
|
||||
]
|
||||
.iter()
|
||||
.any(|lead| token_phrase_has_lead(lead));
|
||||
let is_benign_token_noun_phrase = key == "token"
|
||||
&& (!key_is_compound || has_hyphenated_count_lead)
|
||||
&& whitespace_start == start + key.len()
|
||||
|
|
@ -2150,7 +2206,8 @@ fn redact_sensitive_text_values(input: &str) -> String {
|
|||
|| (token_phrase_has_tail("can equal") && token_phrase_has_lead("one ")));
|
||||
let has_whitespace_separator = separator > whitespace_start
|
||||
&& (key != "authorization" || key_is_compound || has_authorization_scheme)
|
||||
&& !is_benign_token_noun_phrase;
|
||||
&& !is_benign_token_noun_phrase
|
||||
&& !is_semantic_token_reference;
|
||||
if !has_assignment_separator && !has_whitespace_separator {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -3159,6 +3216,148 @@ mod tests {
|
|||
assert_eq!(sanitized["accessToken"], json!("[REDACTED]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic_handoff_preserves_acceptance_identifiers_in_declared_prose() {
|
||||
let description =
|
||||
"The document body must contain the token CHAT250ed7e4dc071. No code changes needed.";
|
||||
let plan = format!(
|
||||
"## Plan\n{}\n- The token CHAT250ed7e4dc071 included somewhere in the body.\n- Save the output document.",
|
||||
"Relevant task context. ".repeat(300),
|
||||
);
|
||||
assert!(plan.len() > 4096);
|
||||
let input = json!({
|
||||
"title": "Write project description",
|
||||
"description": description,
|
||||
"initialPlan": plan,
|
||||
"idempotencyKey": "write-description-1",
|
||||
});
|
||||
assert_eq!(
|
||||
sanitize_semantic_tool_input("create_task", &input).unwrap(),
|
||||
input
|
||||
);
|
||||
for text in [
|
||||
description,
|
||||
plan.as_str(),
|
||||
"Must include the literal token `CHAT66e7813a4f9d1` somewhere in the text.",
|
||||
"Include the exact token ACCEPTANCE-42 in the final output.",
|
||||
] {
|
||||
assert_eq!(
|
||||
sanitize_semantic_tool_input("write_document", &json!({"body": text})).unwrap(),
|
||||
json!({"body": text})
|
||||
);
|
||||
assert_ne!(
|
||||
redact_text(text),
|
||||
text,
|
||||
"diagnostics keep their strict policy"
|
||||
);
|
||||
}
|
||||
let config = config(PathBuf::from("unused"));
|
||||
let mut state = DurableState::new(&config);
|
||||
state
|
||||
.enqueue_executor_event(
|
||||
&config,
|
||||
"provider-create-task".to_owned(),
|
||||
"semantic_tool.input".to_owned(),
|
||||
EventPriority::P0,
|
||||
json!({"semantic_tool": {
|
||||
"schema": "paperclip.prp.semantic_tool.v1",
|
||||
"schemaVersion": 1,
|
||||
"phase": "input",
|
||||
"operationId": "create_task",
|
||||
"content": {"digest": semantic_value_digest(&input)},
|
||||
"input": input,
|
||||
}}),
|
||||
)
|
||||
.unwrap();
|
||||
let transmitted = state.outbox[0]
|
||||
.envelope
|
||||
.pointer("/payload/payload/semantic_tool/input")
|
||||
.unwrap();
|
||||
assert_eq!(transmitted, &input);
|
||||
assert_eq!(
|
||||
state.outbox[0]
|
||||
.envelope
|
||||
.pointer("/payload/payload/semantic_tool/content/digest"),
|
||||
Some(&json!(semantic_value_digest(transmitted))),
|
||||
);
|
||||
let document = format!(
|
||||
"{}\nAuthorization: Bearer late-credential\nFINAL-ACCEPTANCE-42",
|
||||
"Document content. ".repeat(400)
|
||||
);
|
||||
let safe =
|
||||
sanitize_semantic_tool_input("write_document", &json!({"body": document})).unwrap();
|
||||
let body = safe["body"].as_str().unwrap();
|
||||
assert!(body.len() > 4096);
|
||||
assert!(body.ends_with("FINAL-ACCEPTANCE-42"));
|
||||
assert!(!body.contains("late-credential"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic_prose_does_not_exempt_credential_syntax_or_shapes() {
|
||||
for text in [
|
||||
"auth token opaque-credential",
|
||||
"access token opaque-credential",
|
||||
"session token opaque-credential",
|
||||
"refresh token opaque-credential",
|
||||
"authentication token opaque-credential",
|
||||
"literal token=opaque-credential",
|
||||
"literal token:opaque-credential",
|
||||
"literal --token opaque-credential",
|
||||
"literal access_token opaque-credential",
|
||||
"literal \"token\" opaque-credential",
|
||||
"literal token \"opaque-credential\"",
|
||||
] {
|
||||
assert!(!redact_text(text).contains("opaque-credential"), "{text}");
|
||||
let input = json!({"description": text, "initialPlan": text});
|
||||
assert!(
|
||||
!sanitize_semantic_tool_input("create_task", &input)
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.contains("opaque-credential"),
|
||||
"{text}"
|
||||
);
|
||||
}
|
||||
for secret in [
|
||||
"sk-proj-secretvalue123456",
|
||||
"ghp_secretvalue12345678901234567890",
|
||||
"github_pat_secretvalue12345678901234567890",
|
||||
"eyJhbGciOiJIUzI1NiJ9.c2VjcmV0LWNsYWlt.signaturesecret",
|
||||
] {
|
||||
let text = format!("Include the literal token {secret} in the document.");
|
||||
assert!(!redact_text(&text).contains(secret), "{text}");
|
||||
assert!(
|
||||
!sanitize_semantic_tool_input("write_document", &json!({"body": text}))
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.contains(secret)
|
||||
);
|
||||
}
|
||||
let input = json!({
|
||||
"description": "Include the literal token ACCEPTANCE-42. Authorization: Bearer opaque-credential",
|
||||
"token": "opaque-credential",
|
||||
});
|
||||
let safe = sanitize_semantic_tool_input("create_task", &input).unwrap();
|
||||
assert!(safe["description"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.contains("ACCEPTANCE-42"));
|
||||
assert!(!safe.to_string().contains("opaque-credential"));
|
||||
let diagnostic = json!({"description": "the token opaque-credential"});
|
||||
assert!(
|
||||
!sanitize_semantic_tool_input("get_task_context", &diagnostic)
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.contains("opaque-credential")
|
||||
);
|
||||
assert!(!sanitize_semantic_tool_input(
|
||||
"create_task",
|
||||
&json!({"diagnostic": "token opaque-credential"})
|
||||
)
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.contains("opaque-credential"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic_redaction_preserves_benign_token_system_prose() {
|
||||
let prose = "Offer a simple token system so guests can exchange items even when their contributions differ in quantity.";
|
||||
|
|
|
|||
|
|
@ -495,3 +495,116 @@ fn terminal_events_clear_pending_requests_and_reject_late_turn_events() {
|
|||
))
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mutation_prose_survives_sidecar_decode_pending_state_and_semantic_projection() {
|
||||
let mut state = AcpxProviderState::new("run-1").unwrap();
|
||||
state.begin_turn("turn-1").unwrap();
|
||||
let plan = format!(
|
||||
"{}\nThe token CHAT8322bda781b81 must be included in the document.",
|
||||
"Relevant context. ".repeat(400)
|
||||
);
|
||||
let input = json!({
|
||||
"title": "Write project description",
|
||||
"description": "The document must contain the token CHAT8322bda781b81.",
|
||||
"initialPlan": plan,
|
||||
"idempotencyKey": "CHAT8322bda781b81-task",
|
||||
"apiToken": "actual-credential",
|
||||
});
|
||||
let mut expected = input.clone();
|
||||
expected["apiToken"] = json!("[REDACTED]");
|
||||
let emitted = state
|
||||
.accept_event(&event(
|
||||
1,
|
||||
GeneratedAcpxSidecarEventType::RuntimeToolCalled,
|
||||
Some("turn-1"),
|
||||
json!({"callId": "call-1", "operationId": "create_task", "input": input}),
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(state.pending_tool("call-1").unwrap().input, expected);
|
||||
let projected = project_acpx_state_event(
|
||||
&AcpxEventProjectionContext {
|
||||
run_id: "run-1".to_owned(),
|
||||
normalized_session_id: "session-1".to_owned(),
|
||||
turn_id: "turn-1".to_owned(),
|
||||
provider_turn_id: None,
|
||||
item_id: "call-1".to_owned(),
|
||||
},
|
||||
&emitted[0],
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(projected[0].event_type, "semantic_tool.input");
|
||||
assert_eq!(projected[0].payload["semantic_tool"]["input"], expected);
|
||||
assert_eq!(
|
||||
projected[0].payload["semantic_tool"]["content"]["digest"],
|
||||
json!(paperclip_runner_core::provider_bridge::semantic_value_digest(&expected))
|
||||
);
|
||||
|
||||
for (operation, field, prose, preserved) in [
|
||||
(
|
||||
"write_document",
|
||||
"body",
|
||||
"Include the token CHAT8322bda781b81.",
|
||||
true,
|
||||
),
|
||||
(
|
||||
"create_project",
|
||||
"description",
|
||||
"Include the token CHAT8322bda781b81.",
|
||||
true,
|
||||
),
|
||||
(
|
||||
"get_task_context",
|
||||
"description",
|
||||
"Include the token CHAT8322bda781b81.",
|
||||
false,
|
||||
),
|
||||
(
|
||||
"mcp__untrusted__create_task",
|
||||
"description",
|
||||
"Include the token CHAT8322bda781b81.",
|
||||
false,
|
||||
),
|
||||
(
|
||||
"create_task",
|
||||
"description",
|
||||
"Authorization: Bearer actual-credential",
|
||||
false,
|
||||
),
|
||||
(
|
||||
"create_task",
|
||||
"initialPlan",
|
||||
"access token actual-credential",
|
||||
false,
|
||||
),
|
||||
] {
|
||||
state
|
||||
.complete_tool(
|
||||
"call-1",
|
||||
state
|
||||
.pending_tool("call-1")
|
||||
.unwrap()
|
||||
.operation_id
|
||||
.clone()
|
||||
.as_str(),
|
||||
)
|
||||
.unwrap();
|
||||
let emitted = state
|
||||
.accept_event(&event(
|
||||
2,
|
||||
GeneratedAcpxSidecarEventType::RuntimeToolCalled,
|
||||
Some("turn-1"),
|
||||
json!({"callId": "call-1", "operationId": operation, "input": {field: prose}}),
|
||||
))
|
||||
.unwrap();
|
||||
let AcpxProviderStateEvent::ToolCall { input, .. } = &emitted[0] else {
|
||||
panic!("expected tool call");
|
||||
};
|
||||
assert_eq!(
|
||||
input[field] == json!(prose),
|
||||
preserved,
|
||||
"{operation}: {prose}"
|
||||
);
|
||||
assert!(!input.to_string().contains("actual-credential"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,3 +180,37 @@ fn redacts_sidecar_stderr_when_the_process_exits() {
|
|||
assert!(message.contains("[REDACTED]"));
|
||||
assert!(!message.contains("amber-signal-7305"));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn preserves_only_allowlisted_stderr_categories_when_the_process_exits() {
|
||||
let mut transport = AcpxSidecarTransport::start(&AcpxSidecarTransportConfig {
|
||||
command: PathBuf::from("/bin/sh"),
|
||||
args: vec![
|
||||
"-c".to_owned(),
|
||||
"printf '%s\n' 'TypeError [ERR_INVALID_ARG_TYPE]: token=amber-signal-7305' ' at /private/secret-project/session-123.js:42' 'triggerUncaughtException(err, true /* fromPromise */);' 'Error: ACPX provider spawned after ownership admission was sealed' 'code: EPIPE' 'UnknownProviderError: private-value' 'prefixECONNRESETsuffix' >&2; exit 1".to_owned(),
|
||||
],
|
||||
verified_launch: None,
|
||||
request_timeout: Duration::from_secs(1),
|
||||
shutdown_grace: Duration::from_millis(50),
|
||||
})
|
||||
.expect("diagnostic fixture should start");
|
||||
let error = transport
|
||||
.poll_event(Duration::from_secs(1))
|
||||
.expect_err("exited sidecar must fail");
|
||||
let message = error.to_string();
|
||||
assert!(message.contains("stderrCategories=broken_pipe,invalid_argument_type,javascript_type_error,provider_spawn_after_ownership_seal,unhandled_rejection"));
|
||||
assert!(message.contains("stderrTail="));
|
||||
assert!(message.contains("[REDACTED]"));
|
||||
for sensitive in [
|
||||
"amber-signal-7305",
|
||||
"secret-project",
|
||||
"session-123",
|
||||
"private-value",
|
||||
"UnknownProviderError",
|
||||
"connection_reset",
|
||||
"TypeError",
|
||||
] {
|
||||
assert!(!message.contains(sensitive));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,6 +124,30 @@ fn opencode_call_count(state_dir: &Path, method: &str) -> usize {
|
|||
.count()
|
||||
}
|
||||
|
||||
fn assert_valid_terminal(payload: &Value) {
|
||||
let schema: Value = serde_json::from_str(include_str!(
|
||||
"../../../../protocol/schemas/terminal.schema.json"
|
||||
))
|
||||
.unwrap();
|
||||
let stop_reason: Value = serde_json::from_str(include_str!(
|
||||
"../../../../protocol/schemas/stop-reason.schema.json"
|
||||
))
|
||||
.unwrap();
|
||||
let registry = jsonschema::Registry::new()
|
||||
.add(
|
||||
"https://paperclip.dev/schemas/prp/v1/stop-reason.schema.json",
|
||||
stop_reason,
|
||||
)
|
||||
.unwrap()
|
||||
.prepare()
|
||||
.unwrap();
|
||||
let validator = jsonschema::options()
|
||||
.with_registry(®istry)
|
||||
.build(&schema)
|
||||
.unwrap();
|
||||
validator.validate(payload).unwrap();
|
||||
}
|
||||
|
||||
fn command(sequence: u64, command_type: &str, payload: Value) -> Command {
|
||||
Command {
|
||||
schema: "paperclip.prp.command.v1".to_owned(),
|
||||
|
|
@ -216,6 +240,7 @@ fn preserves_acpx_semantic_disposition_in_the_run_terminal() {
|
|||
.iter()
|
||||
.find(|event| event.event_type == "run.terminal")
|
||||
.expect("ACPX blocked result must become terminal");
|
||||
assert_valid_terminal(&terminal.payload);
|
||||
assert_eq!(terminal.payload["runTerminalState"], "succeeded");
|
||||
assert_eq!(terminal.payload["reportedWorkDisposition"], "blocked");
|
||||
|
||||
|
|
@ -360,7 +385,19 @@ fn executes_a_qualified_acpx_profile_through_the_native_selector() {
|
|||
assert!(events
|
||||
.iter()
|
||||
.any(|event| event.event_type == "run.terminal"));
|
||||
assert_valid_terminal(
|
||||
&events
|
||||
.iter()
|
||||
.find(|event| event.event_type == "run.terminal")
|
||||
.unwrap()
|
||||
.payload,
|
||||
);
|
||||
// runner.drain must see this exact terminal suffix without polling the
|
||||
// provider again. An empty default implementation strands the suffix and
|
||||
// makes shared native transport closure fail after a successful reply.
|
||||
assert_eq!(executor.retained_events().unwrap(), events);
|
||||
executor.acknowledge_events(events.len()).unwrap();
|
||||
assert!(executor.retained_events().unwrap().is_empty());
|
||||
executor
|
||||
.execute(&command(4, "session.close", json!({})))
|
||||
.unwrap();
|
||||
|
|
@ -368,6 +405,57 @@ fn executes_a_qualified_acpx_profile_through_the_native_selector() {
|
|||
fs::remove_dir_all(directory).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resumes_an_idle_acpx_session_in_a_cold_replacement_runner() {
|
||||
let directory = temporary_directory("acpx-cold-idle-recovery");
|
||||
let config = acpx_config(&directory, "turns-reserved-result-terminal");
|
||||
let mut executor = NativeProviderCommandExecutor::with_runner_config(&directory, &config);
|
||||
executor
|
||||
.execute(&command(
|
||||
1,
|
||||
"run.prepare",
|
||||
prepare_payload(&directory, "codex"),
|
||||
))
|
||||
.unwrap();
|
||||
let original = executor
|
||||
.execute(&command(2, "session.open", json!({})))
|
||||
.unwrap();
|
||||
executor
|
||||
.execute(&command(
|
||||
3,
|
||||
"turn.start",
|
||||
json!({"text":"Acknowledge.", "turnId":"provider-turn-first"}),
|
||||
))
|
||||
.unwrap();
|
||||
let events = executor.poll_events().unwrap();
|
||||
executor.acknowledge_events(events.len()).unwrap();
|
||||
executor
|
||||
.execute(&command(4, "runner.suspend", json!({})))
|
||||
.unwrap();
|
||||
executor.shutdown().unwrap();
|
||||
drop(executor);
|
||||
|
||||
let mut replacement_config = config.clone();
|
||||
replacement_config.run_id = "run-2".to_owned();
|
||||
replacement_config.turn_id = "turn-2".to_owned();
|
||||
let mut replacement =
|
||||
NativeProviderCommandExecutor::with_runner_config(&directory, &replacement_config);
|
||||
let mut payload = prepare_payload(&directory, "codex");
|
||||
payload["provider"]["runId"] = json!("run-2");
|
||||
let resumed = replacement
|
||||
.execute(&command(1, "run.attach", payload))
|
||||
.unwrap();
|
||||
assert_eq!(resumed.result["status"], "resumed");
|
||||
assert_eq!(
|
||||
resumed.result["providerSessionId"],
|
||||
original.result["providerSessionId"]
|
||||
);
|
||||
// Admission itself must preserve the provider identity before any new
|
||||
// model turn. The fixture's scripted terminal events belong to run-1.
|
||||
replacement.shutdown().unwrap();
|
||||
fs::remove_dir_all(directory).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_native_acpx_semantic_events_on_the_durable_controller_turn() {
|
||||
let directory = temporary_directory("acpx-durable-turn-correlation");
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ function validInventories() {
|
|||
schemaVersion: 2,
|
||||
inventoryRole: "normative",
|
||||
generatedFrom: ["skills/paperclip/SKILL.md"],
|
||||
rows: Array.from({ length: 153 }, (_, index) => row(`capability-${index}`)),
|
||||
rows: Array.from({ length: 154 }, (_, index) => row(`capability-${index}`)),
|
||||
},
|
||||
evaluations: {
|
||||
schemaVersion: 2,
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ export async function buildMcpInventory(repoRoot) {
|
|||
|
||||
export function validateInventories(inventories) {
|
||||
const errors = [];
|
||||
const expectedCounts = { capabilities: 153, evaluations: 106, legacyMcpAliases: 42 };
|
||||
const expectedCounts = { capabilities: 154, evaluations: 106, legacyMcpAliases: 42 };
|
||||
const normativeNames = ["capabilities", "evaluations"];
|
||||
const normativeRows = new Map();
|
||||
const globalNormativeIds = new Set();
|
||||
|
|
|
|||
|
|
@ -59,12 +59,12 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:server-verified-external-chat-turns:30",
|
||||
"id": "skill:skills/paperclip/SKILL.md:conversation-tasks:30",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:30",
|
||||
"title": "Server-Verified External Chat Turns",
|
||||
"expectedSemantics": "Skill guidance headed “Server-Verified External Chat Turns”.",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"title": "Conversation tasks",
|
||||
"expectedSemantics": "Skill guidance headed “Conversation tasks”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"requiredGrants": [],
|
||||
"assertionClasses": [
|
||||
"control_plane_invariant"
|
||||
|
|
@ -74,9 +74,24 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:the-heartbeat-procedure:70",
|
||||
"id": "skill:skills/paperclip/SKILL.md:server-verified-external-chat-turns:47",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:70",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:47",
|
||||
"title": "Server-Verified External Chat Turns",
|
||||
"expectedSemantics": "Skill guidance headed “Server-Verified External Chat Turns”.",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
"requiredGrants": [],
|
||||
"assertionClasses": [
|
||||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:47"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:the-heartbeat-procedure:87",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:87",
|
||||
"title": "The Heartbeat Procedure",
|
||||
"expectedSemantics": "Skill guidance headed “The Heartbeat Procedure”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -85,13 +100,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:70"
|
||||
"skill:skills/paperclip/SKILL.md:87"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:142",
|
||||
"id": "skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:159",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:142",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:159",
|
||||
"title": "Generated Artifacts and Work Products",
|
||||
"expectedSemantics": "Skill guidance headed “Generated Artifacts and Work Products”.",
|
||||
"primaryDisposition": "always_agent_tool",
|
||||
|
|
@ -100,13 +115,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:142"
|
||||
"skill:skills/paperclip/SKILL.md:159"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:status-quick-guide:190",
|
||||
"id": "skill:skills/paperclip/SKILL.md:status-quick-guide:207",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:190",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:207",
|
||||
"title": "Status Quick Guide",
|
||||
"expectedSemantics": "Skill guidance headed “Status Quick Guide”.",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
|
|
@ -115,13 +130,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:190"
|
||||
"skill:skills/paperclip/SKILL.md:207"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:200",
|
||||
"id": "skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:217",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:200",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:217",
|
||||
"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",
|
||||
|
|
@ -130,13 +145,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:200"
|
||||
"skill:skills/paperclip/SKILL.md:217"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:delegating-review-tasks:213",
|
||||
"id": "skill:skills/paperclip/SKILL.md:delegating-review-tasks:230",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:213",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:230",
|
||||
"title": "Delegating review tasks",
|
||||
"expectedSemantics": "Skill guidance headed “Delegating review tasks”.",
|
||||
"primaryDisposition": "always_agent_tool",
|
||||
|
|
@ -145,13 +160,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:213"
|
||||
"skill:skills/paperclip/SKILL.md:230"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:224",
|
||||
"id": "skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:241",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:224",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:241",
|
||||
"title": "Managing A User's Inbox",
|
||||
"expectedSemantics": "Skill guidance headed “Managing A User's Inbox”.",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
|
|
@ -160,13 +175,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:224"
|
||||
"skill:skills/paperclip/SKILL.md:241"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:232",
|
||||
"id": "skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:249",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:232",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:249",
|
||||
"title": "Issue Dependencies (Blockers)",
|
||||
"expectedSemantics": "Skill guidance headed “Issue Dependencies (Blockers)”.",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
|
|
@ -175,13 +190,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:232"
|
||||
"skill:skills/paperclip/SKILL.md:249"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:requesting-board-approval:257",
|
||||
"id": "skill:skills/paperclip/SKILL.md:requesting-board-approval:274",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:257",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:274",
|
||||
"title": "Requesting Board Approval",
|
||||
"expectedSemantics": "Skill guidance headed “Requesting Board Approval”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -190,13 +205,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:257"
|
||||
"skill:skills/paperclip/SKILL.md:274"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:issue-thread-interactions:278",
|
||||
"id": "skill:skills/paperclip/SKILL.md:issue-thread-interactions:295",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:278",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:295",
|
||||
"title": "Issue-Thread Interactions",
|
||||
"expectedSemantics": "Skill guidance headed “Issue-Thread Interactions”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -205,13 +220,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:278"
|
||||
"skill:skills/paperclip/SKILL.md:295"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:standalone-decisions:307",
|
||||
"id": "skill:skills/paperclip/SKILL.md:standalone-decisions:324",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:307",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:324",
|
||||
"title": "Standalone Decisions",
|
||||
"expectedSemantics": "Skill guidance headed “Standalone Decisions”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -220,13 +235,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:307"
|
||||
"skill:skills/paperclip/SKILL.md:324"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:411",
|
||||
"id": "skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:428",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:411",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:428",
|
||||
"title": "MCP Tool Approval Gates",
|
||||
"expectedSemantics": "Skill guidance headed “MCP Tool Approval Gates”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -235,13 +250,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:411"
|
||||
"skill:skills/paperclip/SKILL.md:428"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:niche-workflow-pointers:453",
|
||||
"id": "skill:skills/paperclip/SKILL.md:niche-workflow-pointers:470",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:453",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:470",
|
||||
"title": "Niche Workflow Pointers",
|
||||
"expectedSemantics": "Skill guidance headed “Niche Workflow Pointers”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -250,13 +265,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:453"
|
||||
"skill:skills/paperclip/SKILL.md:470"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:cases:463",
|
||||
"id": "skill:skills/paperclip/SKILL.md:cases:480",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:463",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:480",
|
||||
"title": "Cases",
|
||||
"expectedSemantics": "Skill guidance headed “Cases”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -265,13 +280,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:463"
|
||||
"skill:skills/paperclip/SKILL.md:480"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:company-skills-workflow:468",
|
||||
"id": "skill:skills/paperclip/SKILL.md:company-skills-workflow:485",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:468",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:485",
|
||||
"title": "Company Skills Workflow",
|
||||
"expectedSemantics": "Skill guidance headed “Company Skills Workflow”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -280,13 +295,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:468"
|
||||
"skill:skills/paperclip/SKILL.md:485"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:routines:479",
|
||||
"id": "skill:skills/paperclip/SKILL.md:routines:496",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:479",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:496",
|
||||
"title": "Routines",
|
||||
"expectedSemantics": "Skill guidance headed “Routines”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -295,13 +310,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:479"
|
||||
"skill:skills/paperclip/SKILL.md:496"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:490",
|
||||
"id": "skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:507",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:490",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:507",
|
||||
"title": "Issue Workspace Runtime Controls",
|
||||
"expectedSemantics": "Skill guidance headed “Issue Workspace Runtime Controls”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -310,13 +325,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:490"
|
||||
"skill:skills/paperclip/SKILL.md:507"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:proposing-credentials-safely:497",
|
||||
"id": "skill:skills/paperclip/SKILL.md:proposing-credentials-safely:514",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:497",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:514",
|
||||
"title": "Proposing Credentials Safely",
|
||||
"expectedSemantics": "Skill guidance headed “Proposing Credentials Safely”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -325,13 +340,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:497"
|
||||
"skill:skills/paperclip/SKILL.md:514"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:reading-granted-secrets:504",
|
||||
"id": "skill:skills/paperclip/SKILL.md:reading-granted-secrets:521",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:504",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:521",
|
||||
"title": "Reading Granted Secrets",
|
||||
"expectedSemantics": "Skill guidance headed “Reading Granted Secrets”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -340,13 +355,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:504"
|
||||
"skill:skills/paperclip/SKILL.md:521"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:critical-rules:530",
|
||||
"id": "skill:skills/paperclip/SKILL.md:critical-rules:547",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:530",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:547",
|
||||
"title": "Critical Rules",
|
||||
"expectedSemantics": "Skill guidance headed “Critical Rules”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -355,13 +370,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:530"
|
||||
"skill:skills/paperclip/SKILL.md:547"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:comment-style-required:554",
|
||||
"id": "skill:skills/paperclip/SKILL.md:comment-style-required:571",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:554",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:571",
|
||||
"title": "Comment Style (Required)",
|
||||
"expectedSemantics": "Skill guidance headed “Comment Style (Required)”.",
|
||||
"primaryDisposition": "always_agent_tool",
|
||||
|
|
@ -370,13 +385,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:554"
|
||||
"skill:skills/paperclip/SKILL.md:571"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:update:586",
|
||||
"id": "skill:skills/paperclip/SKILL.md:update:603",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:586",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:603",
|
||||
"title": "Update",
|
||||
"expectedSemantics": "Skill guidance headed “Update”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -385,13 +400,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:586"
|
||||
"skill:skills/paperclip/SKILL.md:603"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:596",
|
||||
"id": "skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:613",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:596",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:613",
|
||||
"title": "Planning (Required when planning requested)",
|
||||
"expectedSemantics": "Skill guidance headed “Planning (Required when planning requested)”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -400,13 +415,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:596"
|
||||
"skill:skills/paperclip/SKILL.md:613"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:629",
|
||||
"id": "skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:646",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:629",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:646",
|
||||
"title": "Key Endpoints (Hot Routes)",
|
||||
"expectedSemantics": "Skill guidance headed “Key Endpoints (Hot Routes)”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -415,13 +430,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:629"
|
||||
"skill:skills/paperclip/SKILL.md:646"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:searching-issues:658",
|
||||
"id": "skill:skills/paperclip/SKILL.md:searching-issues:675",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:658",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:675",
|
||||
"title": "Searching Issues",
|
||||
"expectedSemantics": "Skill guidance headed “Searching Issues”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -430,13 +445,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:658"
|
||||
"skill:skills/paperclip/SKILL.md:675"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/SKILL.md:full-reference:668",
|
||||
"id": "skill:skills/paperclip/SKILL.md:full-reference:685",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:668",
|
||||
"sourceAnchor": "skills/paperclip/SKILL.md:685",
|
||||
"title": "Full Reference",
|
||||
"expectedSemantics": "Skill guidance headed “Full Reference”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -445,7 +460,7 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/SKILL.md:668"
|
||||
"skill:skills/paperclip/SKILL.md:685"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -1979,9 +1994,9 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:option-a-one-call-create-with-workspace:787",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:option-a-one-call-create-with-workspace:807",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:787",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:807",
|
||||
"title": "Option A: One-call create with workspace",
|
||||
"expectedSemantics": "Skill guidance headed “Option A: One-call create with workspace”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -1990,13 +2005,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:787"
|
||||
"skill:skills/paperclip/references/api-reference.md:807"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:option-b-two-calls-project-first-then-workspace:806",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:option-b-two-calls-project-first-then-workspace:826",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:806",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:826",
|
||||
"title": "Option B: Two calls (project first, then workspace)",
|
||||
"expectedSemantics": "Skill guidance headed “Option B: Two calls (project first, then workspace)”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -2005,13 +2020,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:806"
|
||||
"skill:skills/paperclip/references/api-reference.md:826"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:governance-and-approvals:835",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:governance-and-approvals:855",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:835",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:855",
|
||||
"title": "Governance and Approvals",
|
||||
"expectedSemantics": "Skill guidance headed “Governance and Approvals”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -2020,30 +2035,15 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:835"
|
||||
"skill:skills/paperclip/references/api-reference.md:855"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:requesting-a-hire-management-only:839",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:839",
|
||||
"title": "Requesting a hire (management only)",
|
||||
"expectedSemantics": "Skill guidance headed “Requesting a hire (management only)”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"requiredGrants": [],
|
||||
"assertionClasses": [
|
||||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:839"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:ceo-strategy-approval:859",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:requesting-a-hire-management-only:859",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:859",
|
||||
"title": "CEO strategy approval",
|
||||
"expectedSemantics": "Skill guidance headed “CEO strategy approval”.",
|
||||
"title": "Requesting a hire (management only)",
|
||||
"expectedSemantics": "Skill guidance headed “Requesting a hire (management only)”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"requiredGrants": [],
|
||||
"assertionClasses": [
|
||||
|
|
@ -2054,9 +2054,24 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:issue-thread-confirmations:868",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:ceo-strategy-approval:879",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:868",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:879",
|
||||
"title": "CEO strategy approval",
|
||||
"expectedSemantics": "Skill guidance headed “CEO strategy approval”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
"requiredGrants": [],
|
||||
"assertionClasses": [
|
||||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:879"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:issue-thread-confirmations:888",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:888",
|
||||
"title": "Issue-thread confirmations",
|
||||
"expectedSemantics": "Skill guidance headed “Issue-thread confirmations”.",
|
||||
"primaryDisposition": "always_agent_tool",
|
||||
|
|
@ -2065,13 +2080,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:868"
|
||||
"skill:skills/paperclip/references/api-reference.md:888"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:checkbox-confirmations:926",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:checkbox-confirmations:946",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:926",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:946",
|
||||
"title": "Checkbox confirmations",
|
||||
"expectedSemantics": "Skill guidance headed “Checkbox confirmations”.",
|
||||
"primaryDisposition": "always_agent_tool",
|
||||
|
|
@ -2080,13 +2095,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:926"
|
||||
"skill:skills/paperclip/references/api-reference.md:946"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:item-verdict-requests:1041",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:item-verdict-requests:1061",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1041",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1061",
|
||||
"title": "Item verdict requests",
|
||||
"expectedSemantics": "Skill guidance headed “Item verdict requests”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -2095,13 +2110,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:1041"
|
||||
"skill:skills/paperclip/references/api-reference.md:1061"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:checking-approval-status:1151",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:checking-approval-status:1171",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1151",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1171",
|
||||
"title": "Checking approval status",
|
||||
"expectedSemantics": "Skill guidance headed “Checking approval status”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -2110,13 +2125,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:1151"
|
||||
"skill:skills/paperclip/references/api-reference.md:1171"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:approval-follow-up-requesting-agent:1157",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:approval-follow-up-requesting-agent:1177",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1157",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1177",
|
||||
"title": "Approval follow-up (requesting agent)",
|
||||
"expectedSemantics": "Skill guidance headed “Approval follow-up (requesting agent)”.",
|
||||
"primaryDisposition": "always_agent_tool",
|
||||
|
|
@ -2125,13 +2140,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:1157"
|
||||
"skill:skills/paperclip/references/api-reference.md:1177"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:issue-lifecycle:1175",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:issue-lifecycle:1195",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1175",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1195",
|
||||
"title": "Issue Lifecycle",
|
||||
"expectedSemantics": "Skill guidance headed “Issue Lifecycle”.",
|
||||
"primaryDisposition": "always_agent_tool",
|
||||
|
|
@ -2140,13 +2155,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:1175"
|
||||
"skill:skills/paperclip/references/api-reference.md:1195"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:error-handling:1205",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:error-handling:1225",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1205",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1225",
|
||||
"title": "Error Handling",
|
||||
"expectedSemantics": "Skill guidance headed “Error Handling”.",
|
||||
"primaryDisposition": "control_plane_owned",
|
||||
|
|
@ -2155,13 +2170,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:1205"
|
||||
"skill:skills/paperclip/references/api-reference.md:1225"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:full-api-reference:1219",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:full-api-reference:1239",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1219",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1239",
|
||||
"title": "Full API Reference",
|
||||
"expectedSemantics": "Skill guidance headed “Full API Reference”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -2170,13 +2185,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:1219"
|
||||
"skill:skills/paperclip/references/api-reference.md:1239"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:agents:1221",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:agents:1241",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1221",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1241",
|
||||
"title": "Agents",
|
||||
"expectedSemantics": "Skill guidance headed “Agents”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -2185,13 +2200,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:1221"
|
||||
"skill:skills/paperclip/references/api-reference.md:1241"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:issues-tasks:1242",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:issues-tasks:1262",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1242",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1262",
|
||||
"title": "Issues (Tasks)",
|
||||
"expectedSemantics": "Skill guidance headed “Issues (Tasks)”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -2200,13 +2215,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:1242"
|
||||
"skill:skills/paperclip/references/api-reference.md:1262"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:companies-projects-goals:1282",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:companies-projects-goals:1302",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1282",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1302",
|
||||
"title": "Companies, Projects, Goals",
|
||||
"expectedSemantics": "Skill guidance headed “Companies, Projects, Goals”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -2215,13 +2230,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:1282"
|
||||
"skill:skills/paperclip/references/api-reference.md:1302"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:routines:1306",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:routines:1326",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1306",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1326",
|
||||
"title": "Routines",
|
||||
"expectedSemantics": "Skill guidance headed “Routines”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -2230,13 +2245,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:1306"
|
||||
"skill:skills/paperclip/references/api-reference.md:1326"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:approvals-costs-activity-dashboard:1322",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:approvals-costs-activity-dashboard:1342",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1322",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1342",
|
||||
"title": "Approvals, Costs, Activity, Dashboard",
|
||||
"expectedSemantics": "Skill guidance headed “Approvals, Costs, Activity, Dashboard”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -2245,13 +2260,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:1322"
|
||||
"skill:skills/paperclip/references/api-reference.md:1342"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:secrets:1344",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:secrets:1364",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1344",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1364",
|
||||
"title": "Secrets",
|
||||
"expectedSemantics": "Skill guidance headed “Secrets”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -2260,13 +2275,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:1344"
|
||||
"skill:skills/paperclip/references/api-reference.md:1364"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:agent-secret-proposals:1357",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:agent-secret-proposals:1377",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1357",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1377",
|
||||
"title": "Agent secret proposals",
|
||||
"expectedSemantics": "Skill guidance headed “Agent secret proposals”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -2275,13 +2290,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:1357"
|
||||
"skill:skills/paperclip/references/api-reference.md:1377"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:agent-secret-access:1457",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:agent-secret-access:1477",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1457",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1477",
|
||||
"title": "Agent secret access",
|
||||
"expectedSemantics": "Skill guidance headed “Agent secret access”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -2290,13 +2305,13 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:1457"
|
||||
"skill:skills/paperclip/references/api-reference.md:1477"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:common-mistakes:1497",
|
||||
"id": "skill:skills/paperclip/references/api-reference.md:common-mistakes:1517",
|
||||
"sourceKind": "skill_heading",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1497",
|
||||
"sourceAnchor": "skills/paperclip/references/api-reference.md:1517",
|
||||
"title": "Common Mistakes",
|
||||
"expectedSemantics": "Skill guidance headed “Common Mistakes”.",
|
||||
"primaryDisposition": "optional_agent_tool",
|
||||
|
|
@ -2305,7 +2320,7 @@
|
|||
"control_plane_invariant"
|
||||
],
|
||||
"evidenceIds": [
|
||||
"skill:skills/paperclip/references/api-reference.md:1497"
|
||||
"skill:skills/paperclip/references/api-reference.md:1517"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -7,10 +7,48 @@
|
|||
"src/scenarios/scenario-plan.ts"
|
||||
],
|
||||
"counts": {
|
||||
"actions": 43,
|
||||
"actions": 45,
|
||||
"legacyRequirements": 106
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"id": "create_project",
|
||||
"ownership": "optional_agent_tool",
|
||||
"surfaces": [
|
||||
"live"
|
||||
],
|
||||
"legacyAliases": [],
|
||||
"contractCase": "protocol-action:create_project",
|
||||
"contractOwner": "src/catalog/protocol-action-contracts.test.ts::create_project has a schema-valid canonical example and every declared projection",
|
||||
"legacyBehavioralCases": [],
|
||||
"deterministicCases": [
|
||||
"protocol-action:create_project"
|
||||
],
|
||||
"legacyRequirementCases": [],
|
||||
"deterministicOwners": [
|
||||
"src/catalog/protocol-action-contracts.test.ts::create_project has a schema-valid canonical example and every declared projection",
|
||||
"src/scenarios/scenario-explorer.test.ts::renders every scenario with exposure, control plane, authorization, diff, and parity"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "list_project_repositories",
|
||||
"ownership": "optional_agent_tool",
|
||||
"surfaces": [
|
||||
"live"
|
||||
],
|
||||
"legacyAliases": [],
|
||||
"contractCase": "protocol-action:list_project_repositories",
|
||||
"contractOwner": "src/catalog/protocol-action-contracts.test.ts::list_project_repositories has a schema-valid canonical example and every declared projection",
|
||||
"legacyBehavioralCases": [],
|
||||
"deterministicCases": [
|
||||
"protocol-action:list_project_repositories"
|
||||
],
|
||||
"legacyRequirementCases": [],
|
||||
"deterministicOwners": [
|
||||
"src/catalog/protocol-action-contracts.test.ts::list_project_repositories has a schema-valid canonical example and every declared projection",
|
||||
"src/scenarios/scenario-explorer.test.ts::renders every scenario with exposure, control plane, authorization, diff, and parity"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "search_api",
|
||||
"ownership": "optional_agent_tool",
|
||||
|
|
@ -913,7 +951,8 @@
|
|||
"id": "list_projects",
|
||||
"ownership": "optional_agent_tool",
|
||||
"surfaces": [
|
||||
"scenario"
|
||||
"scenario",
|
||||
"live"
|
||||
],
|
||||
"legacyAliases": [],
|
||||
"contractCase": "protocol-action:list_projects",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export const CAPABILITY_CANONICAL_OPERATIONS: readonly CapabilityCanonicalOperat
|
|||
.sort((left, right) => left.operationId.localeCompare(right.operationId)),
|
||||
);
|
||||
const byId = new Map(CAPABILITY_CANONICAL_OPERATIONS.map((operation) => [operation.operationId, operation]));
|
||||
if (byId.size !== 43) throw new Error(`expected 43 canonical semantic operations, found ${byId.size}`);
|
||||
if (byId.size !== PAPERCLIP_PROTOCOL_ACTIONS.length) throw new Error("Duplicate canonical semantic operation ID");
|
||||
export function capabilityCanonicalOperation(operationId: string): CapabilityCanonicalOperation | undefined { return byId.get(operationId); }
|
||||
export function capabilityCanonicalOperationsForSurface(surface: CapabilityCatalogSurface): readonly CapabilityCanonicalOperation[] { return CAPABILITY_CANONICAL_OPERATIONS.filter((operation) => operation.surfaces.includes(surface)); }
|
||||
export function capabilityCanonicalOperationIds(): readonly string[] { return CAPABILITY_CANONICAL_OPERATIONS.map((operation) => operation.operationId); }
|
||||
|
|
|
|||
|
|
@ -20,16 +20,18 @@ describe("canonical semantic-catalog reconciliation authority", () => {
|
|||
it("pins the reconciled op-set relationship between the two catalogs", () => {
|
||||
const summary = capabilityCatalogReconciliation();
|
||||
expect(summary.scenarioCount).toBe(37);
|
||||
expect(summary.liveCount).toBe(30);
|
||||
expect(summary.sharedCount).toBe(24);
|
||||
expect(summary.unionCount).toBe(43);
|
||||
expect(summary.liveCount).toBe(33);
|
||||
expect(summary.sharedCount).toBe(25);
|
||||
expect(summary.unionCount).toBe(45);
|
||||
// Any operation added to or removed from either catalog without a
|
||||
// reconciliation decision changes these exact sets and fails the gate.
|
||||
expect(summary.liveOnly).toEqual([
|
||||
"call_api",
|
||||
"create_project",
|
||||
"get_agent",
|
||||
"get_approval",
|
||||
"get_approval_context",
|
||||
"list_project_repositories",
|
||||
"schedule_wake",
|
||||
"search_api",
|
||||
]);
|
||||
|
|
@ -40,7 +42,6 @@ describe("canonical semantic-catalog reconciliation authority", () => {
|
|||
"list_cases",
|
||||
"list_company_skills",
|
||||
"list_goals",
|
||||
"list_projects",
|
||||
"list_routines",
|
||||
"list_secret_metadata",
|
||||
"manage_routine",
|
||||
|
|
@ -51,7 +52,7 @@ describe("canonical semantic-catalog reconciliation authority", () => {
|
|||
});
|
||||
|
||||
it("is the single source both catalogs derive their operation set from", () => {
|
||||
expect(CAPABILITY_CANONICAL_OPERATIONS).toHaveLength(43);
|
||||
expect(CAPABILITY_CANONICAL_OPERATIONS).toHaveLength(45);
|
||||
const canonicalIds = new Set(CAPABILITY_CANONICAL_OPERATIONS.map((operation) => operation.operationId));
|
||||
// Neither catalog may contain an operation absent from the canonical source.
|
||||
for (const tool of SCENARIO_CATALOG) expect(canonicalIds.has(tool.operationId)).toBe(true);
|
||||
|
|
@ -85,7 +86,7 @@ describe("canonical semantic-catalog reconciliation authority", () => {
|
|||
});
|
||||
|
||||
it("names placement, claims, task modes, side-effect class, idempotency, redaction, mock mapping, real binding status, and PRP evidence for every operation", () => {
|
||||
expect(CAPABILITY_CANONICAL_CATALOG).toHaveLength(43);
|
||||
expect(CAPABILITY_CANONICAL_CATALOG).toHaveLength(45);
|
||||
for (const operation of CAPABILITY_CANONICAL_CATALOG) {
|
||||
expect(operation.placement).toMatch(/^(always|optional)_agent_tool$/);
|
||||
expect(Array.isArray(operation.requiredClaims)).toBe(true);
|
||||
|
|
@ -111,8 +112,8 @@ describe("canonical semantic-catalog reconciliation authority", () => {
|
|||
it("classifies real binding status so generic_api_request is never product coverage", () => {
|
||||
const summary = capabilityCatalogReconciliation();
|
||||
expect(summary.byRealBindingStatus).toEqual({
|
||||
live_codex: 29,
|
||||
scenario_mock: 13,
|
||||
live_codex: 32,
|
||||
scenario_mock: 12,
|
||||
test_only: 1,
|
||||
});
|
||||
expect(capabilityCanonicalOperation("generic_api_request")?.realBindingStatus).toBe("test_only");
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url";
|
|||
|
||||
import Ajv2020 from "ajv/dist/2020.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createTaskAction } from "../protocol-actions/create-task.js";
|
||||
|
||||
import {
|
||||
PAPERCLIP_SEMANTIC_ACTION_CATALOG,
|
||||
|
|
@ -18,12 +19,27 @@ const packageRoot = resolve(
|
|||
);
|
||||
|
||||
describe("semantic action catalog", () => {
|
||||
it("accepts project handoff receipts and preserves ordinary child task receipts", () => {
|
||||
const ajv = new Ajv2020({ allErrors: true, allowUnionTypes: true, strict: true });
|
||||
const validate = ajv.compile(createTaskAction.live.descriptor.outputSchema);
|
||||
const receipt = {
|
||||
commandId: "create-task-1", disposition: "applied", stateRevision: 1,
|
||||
entityRefs: ["task-1"], scheduledWakeIds: ["wake-1"],
|
||||
task: { id: "task-1", identifier: "CHAT-1", parentId: null, projectId: "project-1", status: "todo", assigneeActorId: "agent-1" },
|
||||
};
|
||||
expect(validate(receipt), JSON.stringify(validate.errors)).toBe(true);
|
||||
const { projectId: _projectId, ...childTask } = receipt.task;
|
||||
expect(validate({ ...receipt, task: { ...childTask, parentId: "parent-1" } })).toBe(true);
|
||||
expect(validate({ ...receipt, task: { ...receipt.task, projectId: 42 } })).toBe(false);
|
||||
expect(validate({ ...receipt, task: { ...receipt.task, parentId: "" } })).toBe(false);
|
||||
});
|
||||
|
||||
it("defines one immutable v1 declaration for each Codex-spine action", () => {
|
||||
const operationIds = PAPERCLIP_SEMANTIC_ACTION_CATALOG.map(
|
||||
(action) => action.operationId,
|
||||
);
|
||||
|
||||
expect(operationIds).toHaveLength(29);
|
||||
expect(operationIds).toHaveLength(32);
|
||||
expect(new Set(operationIds).size).toBe(operationIds.length);
|
||||
expect(operationIds).not.toContain("generic_api_request");
|
||||
expect(Object.isFrozen(PAPERCLIP_SEMANTIC_ACTION_CATALOG)).toBe(true);
|
||||
|
|
|
|||
|
|
@ -428,10 +428,41 @@ const descriptors: readonly PaperclipSemanticActionDescriptor[] = [
|
|||
),
|
||||
outputSchema: operationReceipt,
|
||||
}),
|
||||
descriptor({
|
||||
operationId: "list_projects",
|
||||
title: "List projects",
|
||||
requiredClaims: ["discovery:projects:read"],
|
||||
description: "Inspect available company projects before selecting a project for new work.",
|
||||
placement: "optional",
|
||||
inputSchema: object({}),
|
||||
}),
|
||||
descriptor({
|
||||
operationId: "list_project_repositories",
|
||||
title: "List available repositories",
|
||||
description: "List authorized repositories with stable IDs and names. Consider appropriate repositories before creating a project; never invent IDs.",
|
||||
placement: "optional",
|
||||
inputSchema: object({}),
|
||||
}),
|
||||
descriptor({
|
||||
operationId: "create_project",
|
||||
title: "Create project",
|
||||
description: "Create a project after considering existing projects and available repositories. repositoryIds and repositoryUrls accept multiple existing repositories. Use HTTPS GitHub repositoryUrls when an accessible repo is not in the catalog; this registers project repositories, not remote GitHub repositories. Non-code projects may omit repositories. Cannot combine repositoryIds/repositoryUrls with workspace. Reuse the idempotency key on retries.",
|
||||
placement: "optional", effect: "write", allowedModes: STANDARD_MODE,
|
||||
inputSchema: object({
|
||||
...idempotency, name: text("Project name.", 500), description: nullableText("Project outcome and context."),
|
||||
repositoryIds: stringArray("Authorized repository IDs from list_project_repositories; may contain multiple repositories."),
|
||||
repositoryUrls: stringArray("Existing HTTPS GitHub repository URLs, including repos absent from the catalog."),
|
||||
workspace: openObject, status: { enum: ["backlog", "planned", "in_progress", "completed", "cancelled"] },
|
||||
goalId: nullableText("Goal ID."), goalIds: stringArray("Goal IDs."), leadAgentId: nullableText("Lead agent ID."),
|
||||
targetDate: nullableText("Target date."), color: nullableText("Project color."), icon: nullableText("Project icon."),
|
||||
env: openObject, executionWorkspacePolicy: openObject, archivedAt: nullableText("Archive timestamp."),
|
||||
}, ["idempotencyKey", "name"]),
|
||||
outputSchema: openObject,
|
||||
}),
|
||||
descriptor({
|
||||
operationId: "create_task",
|
||||
title: "Create child task",
|
||||
description: "Create one child task under the active task.",
|
||||
title: "Create task",
|
||||
description: "Create an assigned task. In a conversation, create a project task with no parent; otherwise create a child of the active task. Include initialPlan to persist its plan before execution.",
|
||||
placement: "optional",
|
||||
effect: "write",
|
||||
requiredClaims: ["delegation:tasks:create"],
|
||||
|
|
@ -439,7 +470,9 @@ const descriptors: readonly PaperclipSemanticActionDescriptor[] = [
|
|||
inputSchema: object(
|
||||
{
|
||||
...idempotency,
|
||||
title: text("Child task title.", 500),
|
||||
title: text("Task title.", 500),
|
||||
projectId: nullableText("Project identifier for the new task."),
|
||||
initialPlan: nullableText("Relevant markdown plan to persist on the new task before it starts."),
|
||||
description: nullableText("Child task description."),
|
||||
assigneeActorId: nullableText("Optional actor assignee.", 200),
|
||||
priority: { enum: ["critical", "high", "medium", "low"] },
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ export type PaperclipSemanticActionId =
|
|||
| "get_workspace_runtime"
|
||||
| "control_workspace_service"
|
||||
| "set_dependencies"
|
||||
| "create_project"
|
||||
| "list_project_repositories"
|
||||
| "list_projects"
|
||||
| "create_task"
|
||||
| "request_approval"
|
||||
| "decide_approval"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { describe, expect, it, vi } from "vitest";
|
|||
|
||||
import type { VerifiedAcpxCommandLease } from "./installation-integrity.js";
|
||||
import { openCodexAcpxRuntime } from "./codex-runtime-adapter.js";
|
||||
import { createAcpxCommandLeaseOwner } from "./command-lease-owner.js";
|
||||
import { resolveQualifiedAcpxProfile } from "./qualified-profiles.js";
|
||||
import type { AcpxRuntimePortOpenOptions } from "./runtime-host.js";
|
||||
|
||||
|
|
@ -1311,6 +1312,116 @@ describe("Codex ACPX runtime adapter", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("observes prompt admission rejection when the sidecar consumes only events and the result", async () => {
|
||||
const runtime = fakeRuntime();
|
||||
const failure = new Error("Recovered provider could not start the prompt");
|
||||
vi.mocked(runtime.startTurn).mockImplementation(() => ({
|
||||
requestId: "turn-recovered-failure",
|
||||
promptStarted: Promise.reject(failure),
|
||||
events: { async *[Symbol.asyncIterator]() { throw failure; } },
|
||||
result: Promise.reject(failure),
|
||||
cancel: vi.fn(),
|
||||
closeStream: vi.fn(),
|
||||
}));
|
||||
const port = await openCodexAcpxRuntime(openOptions(fakeCommand()), {
|
||||
createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime,
|
||||
});
|
||||
const turn = port.startTurn({ text: "Resume", requestId: "turn-recovered-failure" });
|
||||
const eventDrain = (async () => { for await (const _event of turn.events) { /* drain */ } })();
|
||||
await expect(eventDrain).rejects.toBe(failure);
|
||||
// The sidecar does not await promptStarted. Leave it unconsumed across a
|
||||
// full event-loop turn so an unobserved derived rejection fails this test.
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
// Observing internally must not replace failure with successful admission.
|
||||
await expect(turn.result).rejects.toBe(failure);
|
||||
await expect(turn.promptStarted).rejects.toBe(failure);
|
||||
await port.close({ reason: "test complete" });
|
||||
});
|
||||
|
||||
it("verifies a lazy recovered provider spawned by model selection before returning", async () => {
|
||||
const runtime = fakeRuntime();
|
||||
const command = fakeCommand();
|
||||
vi.mocked(command.spawn).mockReturnValue(fakeChild());
|
||||
let runtimeOptions: AcpRuntimeOptions | undefined;
|
||||
let acknowledgeOwnership!: () => void;
|
||||
const ownership = new Promise<void>((resolve) => { acknowledgeOwnership = resolve; });
|
||||
vi.mocked(runtime.setConfigOption!).mockImplementation(async () => {
|
||||
await Promise.resolve();
|
||||
runtimeOptions?.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {} });
|
||||
});
|
||||
const port = await openCodexAcpxRuntime(openOptions(command), {
|
||||
createRegistry: () => registry(), createStore: () => store(),
|
||||
awaitProviderOwnership: () => ownership,
|
||||
awaitProviderExit: providerOwnershipEstablished,
|
||||
createRuntime: (options) => { runtimeOptions = options; return runtime; },
|
||||
});
|
||||
let admitted = false;
|
||||
const selection = port.setModel!("gpt-5.6-sol").then(() => { admitted = true; });
|
||||
void selection.catch(() => undefined);
|
||||
await vi.waitFor(() => expect(command.spawn).toHaveBeenCalledOnce());
|
||||
expect(admitted).toBe(false);
|
||||
acknowledgeOwnership();
|
||||
await selection;
|
||||
expect(admitted).toBe(true);
|
||||
expect(() => runtimeOptions?.spawnAgent?.({ command: "ignored", args: [], options: {} }))
|
||||
.toThrow("provider spawned after ownership admission was sealed");
|
||||
await port.close({ reason: "test complete" });
|
||||
});
|
||||
|
||||
it("uses a fresh single-use command after a cold model control consumes its launch", async () => {
|
||||
const runtime = fakeRuntime();
|
||||
const freshCommand = () => {
|
||||
const command = fakeCommand();
|
||||
vi.mocked(command.spawn).mockReturnValueOnce(fakeChild()).mockImplementation(() => {
|
||||
throw new Error("Verified ACPX command lease is closed");
|
||||
});
|
||||
return command;
|
||||
};
|
||||
const first = freshCommand();
|
||||
const second = freshCommand();
|
||||
const openCommand = vi.fn(async () => second);
|
||||
const owner = createAcpxCommandLeaseOwner(first, openCommand);
|
||||
let runtimeOptions: AcpRuntimeOptions;
|
||||
vi.mocked(runtime.setConfigOption!).mockImplementation(async () => {
|
||||
runtimeOptions.spawnAgent!({ command: "ignored", args: [], options: {} });
|
||||
});
|
||||
vi.mocked(runtime.startTurn).mockImplementation(() => {
|
||||
runtimeOptions.spawnAgent!({ command: "ignored", args: [], options: {} });
|
||||
return {
|
||||
requestId: "cold-turn",
|
||||
promptStarted: Promise.resolve(),
|
||||
events: { async *[Symbol.asyncIterator]() {} },
|
||||
result: Promise.resolve({ status: "completed" }),
|
||||
cancel: vi.fn(),
|
||||
closeStream: vi.fn(),
|
||||
};
|
||||
});
|
||||
const port = await openCodexAcpxRuntime(
|
||||
{
|
||||
...openOptions(owner.command),
|
||||
refreshConsumedCommand: owner.refreshConsumedCommand,
|
||||
},
|
||||
{
|
||||
createRegistry: () => registry(),
|
||||
createStore: () => store(),
|
||||
awaitProviderOwnership: providerOwnershipEstablished,
|
||||
awaitProviderExit: providerOwnershipEstablished,
|
||||
createRuntime: (options) => {
|
||||
runtimeOptions = options;
|
||||
return runtime;
|
||||
},
|
||||
},
|
||||
);
|
||||
await port.setModel!("gpt-5.6-sol");
|
||||
expect(openCommand).toHaveBeenCalledOnce();
|
||||
const turn = port.startTurn({ text: "Resume", requestId: "cold-turn" });
|
||||
await expect(turn.result).resolves.toMatchObject({ status: "completed" });
|
||||
expect(first.spawn).toHaveBeenCalledOnce();
|
||||
expect(second.spawn).toHaveBeenCalledOnce();
|
||||
await port.close({ reason: "test complete" });
|
||||
await owner.command.close();
|
||||
});
|
||||
|
||||
it("admits a verified provider that starts with the first recovered turn", async () => {
|
||||
const runtime = fakeRuntime();
|
||||
const child = fakeChild();
|
||||
|
|
|
|||
|
|
@ -265,6 +265,7 @@ export async function openQualifiedAcpxRuntime(
|
|||
update.goal === null ? null : structuredClone(update.goal),
|
||||
);
|
||||
};
|
||||
const commandLaunches = { count: 0, refreshConsumedCommand: options.refreshConsumedCommand };
|
||||
const runtimeOptions: GoalAwareAcpRuntimeOptions = {
|
||||
cwd: options.cwd,
|
||||
sessionStore,
|
||||
|
|
@ -327,6 +328,7 @@ export async function openQualifiedAcpxRuntime(
|
|||
// handshake cannot create a provider process after authority is gone.
|
||||
options.signal?.throwIfAborted();
|
||||
options.assertWorkspaceHeld?.();
|
||||
commandLaunches.count += 1;
|
||||
return children.add(
|
||||
options.command.spawn(input.args, input.options, {
|
||||
credentialFenceFds,
|
||||
|
|
@ -431,6 +433,7 @@ export async function openQualifiedAcpxRuntime(
|
|||
children,
|
||||
runtimeCloseTimeoutMs,
|
||||
goalState,
|
||||
commandLaunches,
|
||||
);
|
||||
} catch (error) {
|
||||
const cleanupReason = "ACPX runtime identity validation failed";
|
||||
|
|
@ -857,6 +860,7 @@ function runtimePort(
|
|||
children: SpawnedChildSet,
|
||||
runtimeCloseTimeoutMs: number,
|
||||
goalState: AcpxRuntimeGoalState,
|
||||
commandLaunches: { count: number; refreshConsumedCommand?: () => Promise<void> },
|
||||
): AcpxRuntimePort {
|
||||
type RuntimeCloseAttempt = {
|
||||
readonly outcome: Promise<unknown | null>;
|
||||
|
|
@ -1156,11 +1160,26 @@ function runtimePort(
|
|||
...(runtime.setConfigOption
|
||||
? {
|
||||
async setModel(model: string) {
|
||||
await runtime.setConfigOption?.({
|
||||
handle,
|
||||
key: "model",
|
||||
value: model,
|
||||
});
|
||||
// A restored handle can be lazy: selecting the pinned model may
|
||||
// launch its first provider before any prompt. Admit that spawn
|
||||
// only for this control call, and verify ownership before return.
|
||||
const finishOwnershipAdmission =
|
||||
children.beginLifetimeOwnershipAdmission();
|
||||
const spawnsBeforeControl = commandLaunches.count;
|
||||
try {
|
||||
await runtime.setConfigOption?.({
|
||||
handle,
|
||||
key: "model",
|
||||
value: model,
|
||||
});
|
||||
} finally {
|
||||
await finishOwnershipAdmission();
|
||||
}
|
||||
// Cold ACP config calls open and close a temporary connection.
|
||||
// A later prompt needs a newly verified single-use launch snapshot.
|
||||
if (commandLaunches.count > spawnsBeforeControl) {
|
||||
await commandLaunches.refreshConsumedCommand?.();
|
||||
}
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
|
|
@ -1212,11 +1231,20 @@ function turnWithVerifiedLifetimeOwnership(
|
|||
finishOwnershipAdmission(),
|
||||
);
|
||||
void ownershipVerified.catch(() => undefined);
|
||||
const promptStarted = ownershipVerified.then(() => turn.promptStarted);
|
||||
const result = ownershipVerified.then(() => turn.result);
|
||||
// Some consumers (including the sidecar) drain events and await the result
|
||||
// without awaiting this optional admission signal. Observe its rejection
|
||||
// immediately so a failed cold start cannot terminate the host process as an
|
||||
// unhandled rejection. Keep the original rejected promise for consumers.
|
||||
void promptStarted.catch(() => undefined);
|
||||
// Event drains can fail before their caller reaches the result promise.
|
||||
void result.catch(() => undefined);
|
||||
return {
|
||||
requestId: turn.requestId,
|
||||
promptStarted: ownershipVerified.then(() => turn.promptStarted),
|
||||
promptStarted,
|
||||
events: eventsAfterLifetimeOwnership(turn.events, ownershipVerified),
|
||||
result: ownershipVerified.then(() => turn.result),
|
||||
result,
|
||||
cancel: (input) => turn.cancel(input),
|
||||
closeStream: (input) => turn.closeStream(input),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
import type { ChildProcess } from "node:child_process";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createAcpxCommandLeaseOwner } from "./command-lease-owner.js";
|
||||
import type { VerifiedAcpxCommandLease } from "./installation-integrity.js";
|
||||
|
||||
function lease() {
|
||||
let consumed = false;
|
||||
return {
|
||||
spawn: vi.fn(() => {
|
||||
if (consumed) throw new Error("single-use command already consumed");
|
||||
consumed = true;
|
||||
return {} as ChildProcess;
|
||||
}),
|
||||
close: vi.fn(async () => {
|
||||
consumed = true;
|
||||
}),
|
||||
} satisfies VerifiedAcpxCommandLease;
|
||||
}
|
||||
|
||||
describe("ACPX verified command lease owner", () => {
|
||||
it("refreshes only consumed snapshots and preserves single-use spawn enforcement", async () => {
|
||||
const first = lease();
|
||||
const second = lease();
|
||||
const open = vi.fn(async () => second);
|
||||
const owner = createAcpxCommandLeaseOwner(first, open);
|
||||
await owner.refreshConsumedCommand();
|
||||
expect(open).not.toHaveBeenCalled();
|
||||
owner.command.spawn();
|
||||
expect(() => owner.command.spawn()).toThrow("already consumed");
|
||||
await Promise.all([owner.refreshConsumedCommand(), owner.refreshConsumedCommand()]);
|
||||
expect(open).toHaveBeenCalledOnce();
|
||||
owner.command.spawn();
|
||||
expect(second.spawn).toHaveBeenCalledOnce();
|
||||
expect(() => owner.command.spawn()).toThrow("already consumed");
|
||||
await owner.command.close();
|
||||
expect(first.close).toHaveBeenCalledOnce();
|
||||
expect(second.close).toHaveBeenCalledOnce();
|
||||
expect(() => owner.command.spawn()).toThrow("closing");
|
||||
await expect(owner.refreshConsumedCommand()).rejects.toThrow("closing");
|
||||
});
|
||||
|
||||
it("retains a replacement acquired during shutdown and retries its failed cleanup", async () => {
|
||||
const first = lease();
|
||||
const replacement = lease();
|
||||
replacement.close.mockRejectedValueOnce(new Error("close failed"));
|
||||
let acquired!: (value: VerifiedAcpxCommandLease) => void;
|
||||
const owner = createAcpxCommandLeaseOwner(
|
||||
first,
|
||||
() => new Promise((resolve) => {
|
||||
acquired = resolve;
|
||||
}),
|
||||
);
|
||||
owner.command.spawn();
|
||||
const refresh = owner.refreshConsumedCommand();
|
||||
const rejectedRefresh = expect(refresh).rejects.toThrow("closed during refresh");
|
||||
await Promise.resolve();
|
||||
const close = owner.command.close();
|
||||
const rejectedClose = expect(close).rejects.toThrow("leases did not close");
|
||||
acquired(replacement);
|
||||
await rejectedRefresh;
|
||||
await rejectedClose;
|
||||
expect(replacement.spawn).not.toHaveBeenCalled();
|
||||
await owner.command.close();
|
||||
expect(replacement.close).toHaveBeenCalledTimes(2);
|
||||
expect(first.close).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("fails closed when fresh command verification fails", async () => {
|
||||
const initial = lease();
|
||||
const owner = createAcpxCommandLeaseOwner(initial, async () => {
|
||||
throw new Error("installation changed");
|
||||
});
|
||||
owner.command.spawn();
|
||||
await expect(owner.refreshConsumedCommand()).rejects.toThrow("installation changed");
|
||||
expect(() => owner.command.spawn()).toThrow("already consumed");
|
||||
await owner.command.close();
|
||||
expect(initial.close).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import type { VerifiedAcpxCommandLease } from "./installation-integrity.js";
|
||||
|
||||
/** Keep each launch single-use while owning replacements for transient ACP controls. */
|
||||
export function createAcpxCommandLeaseOwner(
|
||||
initial: VerifiedAcpxCommandLease,
|
||||
openCommand: () => Promise<VerifiedAcpxCommandLease>,
|
||||
) {
|
||||
const leases = new Set([initial]);
|
||||
let current = initial;
|
||||
let consumed = false;
|
||||
let closing = false;
|
||||
let refresh: Promise<void> | null = null;
|
||||
const command: VerifiedAcpxCommandLease = {
|
||||
spawn(...args) {
|
||||
if (closing) throw new Error("Verified ACPX command owner is closing");
|
||||
consumed = true;
|
||||
return current.spawn(...args);
|
||||
},
|
||||
async close() {
|
||||
closing = true;
|
||||
// Late acquisitions remain owned. Retry every lease whose close fails.
|
||||
await refresh?.catch(() => undefined);
|
||||
const failures: unknown[] = [];
|
||||
for (const lease of leases) {
|
||||
try {
|
||||
await lease.close();
|
||||
leases.delete(lease);
|
||||
} catch (error) {
|
||||
failures.push(error);
|
||||
}
|
||||
}
|
||||
if (failures.length) throw new AggregateError(failures, "ACPX command leases did not close");
|
||||
},
|
||||
};
|
||||
return {
|
||||
command,
|
||||
async refreshConsumedCommand(): Promise<void> {
|
||||
if (closing) throw new Error("Verified ACPX command owner is closing");
|
||||
if (!consumed) return;
|
||||
if (!refresh) {
|
||||
refresh = Promise.resolve()
|
||||
.then(openCommand)
|
||||
.then((replacement) => {
|
||||
leases.add(replacement);
|
||||
if (closing) throw new Error("Verified ACPX command owner closed during refresh");
|
||||
current = replacement;
|
||||
consumed = false;
|
||||
});
|
||||
}
|
||||
const pending = refresh;
|
||||
try {
|
||||
await pending;
|
||||
} finally {
|
||||
if (refresh === pending) refresh = null;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import {
|
|||
type VerifiedAcpxCommandLease,
|
||||
type VerifiedAcpxInstallation,
|
||||
} from "./installation-integrity.js";
|
||||
import { createAcpxCommandLeaseOwner } from "./command-lease-owner.js";
|
||||
import {
|
||||
requireVerifiedAcpxModel,
|
||||
type AcpxModelStatus,
|
||||
|
|
@ -117,6 +118,8 @@ export interface AcpxRuntimePort {
|
|||
|
||||
export interface AcpxRuntimePortOpenOptions {
|
||||
command: VerifiedAcpxCommandLease;
|
||||
/** Replace a consumed launch snapshot after an ephemeral control session. */
|
||||
refreshConsumedCommand?: () => Promise<void>;
|
||||
profile: QualifiedAcpxProfile;
|
||||
cwd: string;
|
||||
stateDirectory: string;
|
||||
|
|
@ -401,6 +404,11 @@ export class AcpxRuntimeHost {
|
|||
reportFailure: (failure) =>
|
||||
dependencies.reportRetainedCleanupFailure(failure),
|
||||
});
|
||||
const commandOwner = createAcpxCommandLeaseOwner(
|
||||
command,
|
||||
() => installation.openCommand(),
|
||||
);
|
||||
command = commandOwner.command;
|
||||
toolBridge = options.semanticTools
|
||||
? await acquireAbortableAdmissionResource({
|
||||
signal: options.signal,
|
||||
|
|
@ -421,6 +429,7 @@ export class AcpxRuntimeHost {
|
|||
options.assertWorkspaceHeld?.();
|
||||
return dependencies.openRuntime({
|
||||
command: command!,
|
||||
refreshConsumedCommand: commandOwner.refreshConsumedCommand,
|
||||
profile,
|
||||
cwd: binding.workspacePath,
|
||||
stateDirectory: sandbox.stateDirectory,
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ export type CapabilityPrimaryDisposition =
|
|||
| "optional_agent_tool";
|
||||
|
||||
export const capabilityInventoryCounts = {
|
||||
"skillReferenceCapabilities": 153,
|
||||
"skillReferenceCapabilities": 154,
|
||||
"evalCases": 106,
|
||||
"normativeRows": 259,
|
||||
"normativeRows": 260,
|
||||
"legacyMcpAliases": 42
|
||||
} as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -4032,8 +4032,18 @@ class DurablePrpCodexTransport implements CodexAppServerTransport {
|
|||
// A failed drain still proceeds through bounded suspension/containment,
|
||||
// but can never authorize a reusable checkpoint or deletion of evidence.
|
||||
}
|
||||
const lastDrain = [...core.store.state.commands]
|
||||
.reverse()
|
||||
.find((command) => command.type === "runner.drain");
|
||||
this.#diagnostic(
|
||||
"provider suffix did not prove durable drain before bounded runner suspension",
|
||||
"provider suffix did not prove durable drain before bounded runner suspension: " +
|
||||
JSON.stringify({
|
||||
providerState: this.#providerDrainState(),
|
||||
semanticResultsSettled: core.semanticToolResultsSettled(),
|
||||
drainStatus: lastDrain?.status ?? null,
|
||||
retainedEventsDrained:
|
||||
record(record(lastDrain?.result).result).retainedEventsDrained ?? null,
|
||||
}),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,198 @@
|
|||
/** Canonical project tool definition. */
|
||||
export const createProjectAction = {
|
||||
"id": "create_project",
|
||||
"canonical": {
|
||||
"operationId": "create_project",
|
||||
"surfaces": [
|
||||
"live"
|
||||
],
|
||||
"placement": "optional_agent_tool",
|
||||
"optionalGroup": "discovery",
|
||||
"requiredClaims": [],
|
||||
"taskModes": [
|
||||
"standard",
|
||||
"skill_test"
|
||||
],
|
||||
"sideEffectClass": "company_write",
|
||||
"idempotency": "required",
|
||||
"disabledByDefault": false,
|
||||
"realBindingStatus": "live_codex",
|
||||
"realServiceBinding": "PaperclipRunnerToolAuthority",
|
||||
"prpEvidence": "Authenticated project tools, persisted projects and repository workspaces, and run-bound activity.",
|
||||
"prpBindingStatus": "bound",
|
||||
"legacyAliases": []
|
||||
},
|
||||
"documentation": {
|
||||
"title": "Create project",
|
||||
"description": "Create a project after considering existing projects and available repositories. repositoryIds and repositoryUrls accept multiple existing repositories. Use HTTPS GitHub repositoryUrls when an accessible repo is not in the catalog; this registers project repositories, not remote GitHub repositories. Non-code projects may omit repositories. Cannot combine repositoryIds/repositoryUrls with workspace. Reuse the idempotency key on retries.",
|
||||
"note": null
|
||||
},
|
||||
"examples": {
|
||||
"call": {
|
||||
"operationId": "create_project",
|
||||
"input": {
|
||||
"name": "Example",
|
||||
"repositoryIds": [
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"idempotencyKey": "example"
|
||||
}
|
||||
},
|
||||
"success": {
|
||||
"ok": true,
|
||||
"operationId": "create_project",
|
||||
"result": {}
|
||||
}
|
||||
},
|
||||
"live": {
|
||||
"order": 43,
|
||||
"descriptor": {
|
||||
"schema": "paperclip.semantic-tool.v1",
|
||||
"operationId": "create_project",
|
||||
"version": 1,
|
||||
"title": "Create project",
|
||||
"description": "Create a project after considering existing projects and available repositories. repositoryIds and repositoryUrls accept multiple existing repositories. Use HTTPS GitHub repositoryUrls when an accessible repo is not in the catalog; this registers project repositories, not remote GitHub repositories. Non-code projects may omit repositories. Cannot combine repositoryIds/repositoryUrls with workspace. Reuse the idempotency key on retries.",
|
||||
"effect": "write",
|
||||
"requiredClaims": [],
|
||||
"allowedModes": [
|
||||
"standard",
|
||||
"skill_test"
|
||||
],
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"idempotencyKey": {
|
||||
"type": "string",
|
||||
"description": "Caller-stable retry key.",
|
||||
"minLength": 1,
|
||||
"maxLength": 240
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Project name.",
|
||||
"minLength": 1,
|
||||
"maxLength": 500
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "Project outcome and context.",
|
||||
"maxLength": 20000
|
||||
},
|
||||
"repositoryIds": {
|
||||
"type": "array",
|
||||
"description": "Authorized repository IDs from list_project_repositories; may contain multiple repositories.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"maxItems": 200,
|
||||
"uniqueItems": true
|
||||
},
|
||||
"workspace": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"status": {
|
||||
"enum": [
|
||||
"backlog",
|
||||
"planned",
|
||||
"in_progress",
|
||||
"completed",
|
||||
"cancelled"
|
||||
]
|
||||
},
|
||||
"goalId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "Goal ID.",
|
||||
"maxLength": 20000
|
||||
},
|
||||
"goalIds": {
|
||||
"type": "array",
|
||||
"description": "Goal IDs.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"maxItems": 200,
|
||||
"uniqueItems": true
|
||||
},
|
||||
"leadAgentId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "Lead agent ID.",
|
||||
"maxLength": 20000
|
||||
},
|
||||
"targetDate": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "Target date.",
|
||||
"maxLength": 20000
|
||||
},
|
||||
"color": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "Project color.",
|
||||
"maxLength": 20000
|
||||
},
|
||||
"icon": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "Project icon.",
|
||||
"maxLength": 20000
|
||||
},
|
||||
"env": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"executionWorkspacePolicy": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"archivedAt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "Archive timestamp.",
|
||||
"maxLength": 20000
|
||||
},
|
||||
"repositoryUrls": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"maxItems": 100,
|
||||
"description": "Existing HTTPS GitHub repository URLs, including repos absent from the catalog."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"idempotencyKey",
|
||||
"name"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"outputSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"exposure": "optional"
|
||||
}
|
||||
},
|
||||
"scenario": null
|
||||
} as const;
|
||||
|
|
@ -20,7 +20,7 @@ export const createTaskAction = {
|
|||
"idempotency": "required",
|
||||
"disabledByDefault": false,
|
||||
"realBindingStatus": "live_codex",
|
||||
"realServiceBinding": "issues.createChild",
|
||||
"realServiceBinding": "issues.create / issues.createChild",
|
||||
"prpEvidence": "semantic-operation item event plus company-entity state diff and audit record",
|
||||
"prpBindingStatus": "bound",
|
||||
"legacyAliases": [
|
||||
|
|
@ -28,8 +28,8 @@ export const createTaskAction = {
|
|||
]
|
||||
},
|
||||
"documentation": {
|
||||
"title": "Create child task",
|
||||
"description": "Create one durable standard child under the active task.",
|
||||
"title": "Create task",
|
||||
"description": "Create a project task from a conversation, or a child from an ordinary task. Persist initialPlan before execution.",
|
||||
"note": null
|
||||
},
|
||||
"examples": {
|
||||
|
|
@ -76,8 +76,8 @@ export const createTaskAction = {
|
|||
"schema": "paperclip.semantic-tool.v1",
|
||||
"operationId": "create_task",
|
||||
"version": 1,
|
||||
"title": "Create child task",
|
||||
"description": "Create one durable standard child under the active task. Use only when a real ownership, parallelism, dependency, review, or lifecycle boundary justifies delegation.",
|
||||
"title": "Create task",
|
||||
"description": "Create a project task from a conversation, or a child from an ordinary task. Persist initialPlan before execution.",
|
||||
"exposure": "optional",
|
||||
"requiredClaims": [
|
||||
"delegation:tasks:create"
|
||||
|
|
@ -134,6 +134,21 @@ export const createTaskAction = {
|
|||
},
|
||||
"maxItems": 200,
|
||||
"uniqueItems": true
|
||||
},
|
||||
"projectId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "Project ID for the task."
|
||||
},
|
||||
"initialPlan": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"maxLength": 200000,
|
||||
"description": "Relevant markdown plan saved on the new task before execution starts."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -184,13 +199,42 @@ export const createTaskAction = {
|
|||
"task": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string", "minLength": 1 },
|
||||
"identifier": { "type": ["string", "null"] },
|
||||
"parentId": { "type": "string", "minLength": 1 },
|
||||
"status": { "type": "string", "minLength": 1 },
|
||||
"assigneeActorId": { "type": ["string", "null"] }
|
||||
"id": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"identifier": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"parentId": {
|
||||
"type": ["string", "null"],
|
||||
"minLength": 1
|
||||
},
|
||||
"projectId": {
|
||||
"type": ["string", "null"],
|
||||
"minLength": 1
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"assigneeActorId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["id", "identifier", "parentId", "status", "assigneeActorId"],
|
||||
"required": [
|
||||
"id",
|
||||
"identifier",
|
||||
"parentId",
|
||||
"status",
|
||||
"assigneeActorId"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { createProjectAction } from "./create-project.js";
|
||||
import { listProjectRepositoriesAction } from "./list-project-repositories.js";
|
||||
import { searchApiAction } from "./search-api.js";
|
||||
import { callApiAction } from "./call-api.js";
|
||||
import { administerCompanyAction } from "./administer-company.js";
|
||||
|
|
@ -44,6 +46,8 @@ import { writeDocumentAction } from "./write-document.js";
|
|||
import { deepFreezeProtocolAction } from "./freeze.js";
|
||||
|
||||
export const PAPERCLIP_PROTOCOL_ACTIONS = deepFreezeProtocolAction([
|
||||
createProjectAction,
|
||||
listProjectRepositoriesAction,
|
||||
searchApiAction,
|
||||
callApiAction,
|
||||
administerCompanyAction,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
/** Canonical project tool definition. */
|
||||
export const listProjectRepositoriesAction = {
|
||||
"id": "list_project_repositories",
|
||||
"canonical": {
|
||||
"operationId": "list_project_repositories",
|
||||
"surfaces": [
|
||||
"live"
|
||||
],
|
||||
"placement": "optional_agent_tool",
|
||||
"optionalGroup": "discovery",
|
||||
"requiredClaims": [],
|
||||
"taskModes": [
|
||||
"standard",
|
||||
"ask",
|
||||
"planning",
|
||||
"skill_test"
|
||||
],
|
||||
"sideEffectClass": "read",
|
||||
"idempotency": "none",
|
||||
"disabledByDefault": false,
|
||||
"realBindingStatus": "live_codex",
|
||||
"realServiceBinding": "PaperclipRunnerToolAuthority",
|
||||
"prpEvidence": "Authenticated project tools, persisted projects and repository workspaces, and run-bound activity.",
|
||||
"prpBindingStatus": "bound",
|
||||
"legacyAliases": []
|
||||
},
|
||||
"documentation": {
|
||||
"title": "List available repositories",
|
||||
"description": "List authorized repositories with stable IDs and names. Consider appropriate repositories before creating a project; never invent IDs.",
|
||||
"note": null
|
||||
},
|
||||
"examples": {
|
||||
"call": {
|
||||
"operationId": "list_project_repositories",
|
||||
"input": {}
|
||||
},
|
||||
"success": {
|
||||
"ok": true,
|
||||
"operationId": "list_project_repositories",
|
||||
"result": {}
|
||||
}
|
||||
},
|
||||
"live": {
|
||||
"order": 44,
|
||||
"descriptor": {
|
||||
"schema": "paperclip.semantic-tool.v1",
|
||||
"operationId": "list_project_repositories",
|
||||
"version": 1,
|
||||
"title": "List available repositories",
|
||||
"description": "List authorized repositories with stable IDs and names. Consider appropriate repositories before creating a project; never invent IDs.",
|
||||
"effect": "read",
|
||||
"requiredClaims": [],
|
||||
"allowedModes": [
|
||||
"standard",
|
||||
"ask",
|
||||
"planning",
|
||||
"skill_test"
|
||||
],
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": [],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"outputSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"exposure": "optional"
|
||||
}
|
||||
},
|
||||
"scenario": null
|
||||
} as const;
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
/** Canonical definition and documentation for `list_projects`. */
|
||||
/** Canonical project discovery definition. */
|
||||
export const listProjectsAction = {
|
||||
"id": "list_projects",
|
||||
"canonical": {
|
||||
"operationId": "list_projects",
|
||||
"surfaces": [
|
||||
"scenario"
|
||||
"scenario",
|
||||
"live"
|
||||
],
|
||||
"placement": "optional_agent_tool",
|
||||
"optionalGroup": "discovery",
|
||||
|
|
@ -13,22 +14,23 @@ export const listProjectsAction = {
|
|||
],
|
||||
"taskModes": [
|
||||
"standard",
|
||||
"ask",
|
||||
"planning",
|
||||
"skill_test"
|
||||
],
|
||||
"sideEffectClass": "read",
|
||||
"idempotency": "none",
|
||||
"disabledByDefault": false,
|
||||
"realBindingStatus": "scenario_mock",
|
||||
"realServiceBinding": "unbound",
|
||||
"prpEvidence": "read projection surfaced via a tool-result item event; no control-plane state diff",
|
||||
"prpBindingStatus": "audit_pending",
|
||||
"legacyAliases": [],
|
||||
"note": "Scenario/eval-only discovery via mock extension; no live dispatcher binding yet."
|
||||
"realBindingStatus": "live_codex",
|
||||
"realServiceBinding": "PaperclipRunnerToolAuthority",
|
||||
"prpEvidence": "Authenticated project tools, persisted projects and repository workspaces, and run-bound activity.",
|
||||
"prpBindingStatus": "bound",
|
||||
"legacyAliases": []
|
||||
},
|
||||
"documentation": {
|
||||
"title": "List Projects",
|
||||
"description": "List Projects through the Capability discovery capability set.",
|
||||
"note": "Scenario/eval-only discovery via mock extension; no live dispatcher binding yet."
|
||||
"title": "List projects",
|
||||
"description": "Inspect available company projects before selecting a project for new work.",
|
||||
"note": null
|
||||
},
|
||||
"examples": {
|
||||
"call": {
|
||||
|
|
@ -38,18 +40,40 @@ export const listProjectsAction = {
|
|||
"success": {
|
||||
"ok": true,
|
||||
"operationId": "list_projects",
|
||||
"result": {
|
||||
"schema": "paperclip.capability.tool-result.v1",
|
||||
"ok": true,
|
||||
"operationId": "list_projects",
|
||||
"operationResultId": "example",
|
||||
"value": "example",
|
||||
"commandResult": "example",
|
||||
"authorization": "example"
|
||||
}
|
||||
"result": {}
|
||||
}
|
||||
},
|
||||
"live": {
|
||||
"order": 45,
|
||||
"descriptor": {
|
||||
"schema": "paperclip.semantic-tool.v1",
|
||||
"operationId": "list_projects",
|
||||
"version": 1,
|
||||
"title": "List projects",
|
||||
"description": "Inspect available company projects before selecting a project for new work.",
|
||||
"effect": "read",
|
||||
"requiredClaims": [
|
||||
"discovery:projects:read"
|
||||
],
|
||||
"allowedModes": [
|
||||
"standard",
|
||||
"ask",
|
||||
"planning",
|
||||
"skill_test"
|
||||
],
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": [],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"outputSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"exposure": "optional"
|
||||
}
|
||||
},
|
||||
"live": null,
|
||||
"scenario": {
|
||||
"order": 16,
|
||||
"descriptor": {
|
||||
|
|
@ -104,6 +128,8 @@ export const listProjectsAction = {
|
|||
],
|
||||
"taskModes": [
|
||||
"standard",
|
||||
"ask",
|
||||
"planning",
|
||||
"skill_test"
|
||||
],
|
||||
"sideEffectClass": "read",
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const NAMESPACE: Readonly<Record<CapabilitySemanticOperationId, string>> = Objec
|
|||
report_progress: "active_task", answer_status_question: "active_task", write_document: "documents",
|
||||
request_human_input: "documents", register_deliverable: "documents", finish_task: "active_task",
|
||||
block_task: "active_task", request_review: "active_task", search_tasks: "discovery",
|
||||
list_agents: "discovery", get_agent: "discovery", create_task: "delegation",
|
||||
list_agents: "discovery", get_agent: "discovery", create_task: "delegation", create_project: "projects", list_project_repositories: "projects", list_projects: "projects",
|
||||
set_dependencies: "delegation", list_approvals: "governance", get_approval: "governance",
|
||||
get_approval_context: "governance", request_approval: "governance",
|
||||
decide_approval: "governance", comment_on_approval: "governance",
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ const NAMESPACE: Readonly<Record<PaperclipSemanticActionId, string>> =
|
|||
get_workspace_runtime: "workspace",
|
||||
control_workspace_service: "workspace",
|
||||
set_dependencies: "delegation",
|
||||
create_task: "delegation",
|
||||
create_task: "delegation", create_project: "projects", list_project_repositories: "projects", list_projects: "projects",
|
||||
request_approval: "governance",
|
||||
decide_approval: "governance",
|
||||
comment_on_approval: "governance",
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ export type CapabilitySemanticOperationId =
|
|||
| "get_workspace_runtime"
|
||||
| "control_workspace_service"
|
||||
| "set_dependencies"
|
||||
| "create_project"
|
||||
| "list_project_repositories"
|
||||
| "list_projects"
|
||||
| "create_task"
|
||||
| "request_approval"
|
||||
| "decide_approval"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ test("generated Capability inventory has full source coverage", async () => {
|
|||
readRows("eval-traceability.yaml"),
|
||||
]);
|
||||
|
||||
assert.equal(capabilities.length, 152);
|
||||
assert.equal(capabilities.length, 155);
|
||||
assert.equal(tools.length, 42);
|
||||
assert.equal(evals.length, 106);
|
||||
assert.equal(new Set(evals.map((row) => row.group)).size, 16);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,23 @@ Manual local CLI mode (outside heartbeat runs): use `paperclipai agent local-cli
|
|||
|
||||
**Run audit trail:** You MUST include `-H 'X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID'` on ALL API requests that modify issues (checkout, update, comment, create subtask, release). This links your actions to the current heartbeat run for traceability.
|
||||
|
||||
## Conversation tasks
|
||||
|
||||
When the task context says **Chat mode** (the issue has `conversationAgentId`),
|
||||
follow that directive for the conversation lifecycle. Research, clarify, and
|
||||
revise the conversation's `plan` document here. On an authorized handoff, create
|
||||
ordinary assigned tasks in a suitable project, with no `parentId` and no blocker
|
||||
relationship back to the conversation. Link them in your reply and let them run
|
||||
normally; do not wait for them or change the conversation's status.
|
||||
|
||||
Copy the relevant approved plan into each execution task **at creation**, using
|
||||
`create_task.initialPlan` or the HTTP issue-creation body's `initialPlan` field.
|
||||
Include an `idempotencyKey`. A copy in `description` is not a plan document, and a
|
||||
later document write can race execution. Verify the created task's `plan`
|
||||
document before claiming handoff. Preserve the source plan in this conversation.
|
||||
The ordinary completion, child-task, and blocker instructions below apply to
|
||||
execution tasks; they do not override chat mode.
|
||||
|
||||
## Server-Verified External Chat Turns
|
||||
|
||||
Paperclip may identify an ordinary external-chat turn as already checked out and
|
||||
|
|
@ -208,7 +225,7 @@ Because of that, follow these rules:
|
|||
- **Never imply a live watcher on a task you are marking `done`.** `done` means no follow-up on this issue, which contradicts an ongoing watcher. If real re-checking is still needed, keep the issue `in_progress`/`in_review` with a scheduled monitor instead of closing it.
|
||||
- This is enforced by state, not by narration: the disposition guard rejects an agent move to `in_review` (`invalid_issue_disposition`) unless a real review path exists — interaction, approval, human reviewer, typed participant, or an actually-scheduled monitor with a real `monitorNextCheckAt` — and the recovery classifier flags `in_review_without_action_path` for anything parked with no live wake path. Keep your comments consistent with that real state.
|
||||
|
||||
**Step 9 — Delegate if needed.** Create subtasks with `POST /api/companies/{companyId}/issues`. Always set `parentId` and `goalId`. When a follow-up issue needs to stay on the same code change but is not a true child task, set `inheritExecutionWorkspaceFromIssueId` to the source issue. Set `billingCode` for cross-team work.
|
||||
**Step 9 — Delegate if needed.** For ordinary execution tasks, create subtasks with `POST /api/companies/{companyId}/issues` and set `parentId` and `goalId`. For conversation tasks, use the project handoff above instead. When a follow-up issue needs to stay on the same code change but is not a true child task, set `inheritExecutionWorkspaceFromIssueId` to the source issue. Set `billingCode` for cross-team work.
|
||||
|
||||
### Delegating review tasks
|
||||
|
||||
|
|
@ -624,7 +641,7 @@ PUT /api/issues/{issueId}/documents/plan
|
|||
}
|
||||
```
|
||||
|
||||
If `plan` already exists, fetch the current document first and send its latest `baseRevisionId` when you update it.
|
||||
If `plan` already exists, first `GET /api/issues/{issueId}/documents/plan` and read its current body and `latestRevisionId`. Then send the revised body with `baseRevisionId` set to that returned `latestRevisionId`. The GET field is `latestRevisionId`; the PUT field is `baseRevisionId`. Omitting it on an update returns `409`. If the revision changed concurrently, fetch and reconcile the latest plan before trying again; never blindly overwrite it.
|
||||
|
||||
## Key Endpoints (Hot Routes)
|
||||
|
||||
|
|
|
|||
|
|
@ -784,6 +784,26 @@ PATCH /api/agents/{agentId}/instructions-path
|
|||
|
||||
When a CEO/manager task asks you to "set up a new project" and wire local + GitHub context, use this sequence.
|
||||
|
||||
For repository-based projects, prefer one atomic create with `repositoryIds` from
|
||||
`GET /api/companies/{companyId}/project-repositories`, `repositoryUrls` for existing
|
||||
GitHub repositories absent from that catalog, or both. These arrays support
|
||||
multiple repositories. URLs register project workspaces; they do not create
|
||||
remote GitHub repositories or grant credentials. Use HTTPS URLs without credentials.
|
||||
Do not combine either array with an explicit `workspace`. Reuse the same
|
||||
`idempotencyKey` and body when retrying a creation.
|
||||
|
||||
```
|
||||
POST /api/companies/{companyId}/projects
|
||||
{
|
||||
"name": "Web and API",
|
||||
"repositoryUrls": ["https://github.com/acme/web", "https://github.com/acme/api"],
|
||||
"idempotencyKey": "web-api-project"
|
||||
}
|
||||
```
|
||||
|
||||
Omit repository inputs for non-code work. The explicit workspace alternatives
|
||||
below remain available when local workspace configuration is needed.
|
||||
|
||||
### Option A: One-call create with workspace
|
||||
|
||||
```
|
||||
|
|
@ -1291,7 +1311,7 @@ Terminal states: `done`, `cancelled`
|
|||
| POST | `/api/companies/:companyId/archive` | Archive company |
|
||||
| GET | `/api/companies/:companyId/projects` | List projects |
|
||||
| GET | `/api/projects/:projectId` | Project details |
|
||||
| POST | `/api/companies/:companyId/projects` | Create project (optional inline `workspace`) |
|
||||
| POST | `/api/companies/:companyId/projects` | Create project (`repositoryIds`/`repositoryUrls` arrays or inline `workspace`; optional `idempotencyKey`) |
|
||||
| PATCH | `/api/projects/:projectId` | Update project |
|
||||
| GET | `/api/projects/:projectId/workspaces` | List project workspaces |
|
||||
| POST | `/api/projects/:projectId/workspaces` | Create project workspace |
|
||||
|
|
|
|||
|
|
@ -46,7 +46,9 @@ describe("public repository paid workflow security", () => {
|
|||
.split(/\n(?= {6}- )/u)
|
||||
.filter((step) => step.includes("uses: pnpm/action-setup@"));
|
||||
|
||||
expect(pnpmSetupSteps, workflowName).toHaveLength(7);
|
||||
expect(pnpmSetupSteps, workflowName).toHaveLength(
|
||||
workflowName === "pr-trusted.yml" ? 8 : 7,
|
||||
);
|
||||
for (const step of pnpmSetupSteps) {
|
||||
expect(step, workflowName).toContain('NPM_CONFIG_AUDIT: "false"');
|
||||
expect(step, workflowName).toContain('NPM_CONFIG_FUND: "false"');
|
||||
|
|
@ -75,7 +77,7 @@ describe("public repository paid workflow security", () => {
|
|||
},
|
||||
{
|
||||
name: "pr-trusted.yml",
|
||||
expectedCachedSetupNodeSteps: 7,
|
||||
expectedCachedSetupNodeSteps: 8,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -89,7 +91,9 @@ describe("public repository paid workflow security", () => {
|
|||
step.includes("uses: pnpm/action-setup@") ? [index] : [],
|
||||
);
|
||||
|
||||
expect(pnpmSetupStepIndexes, name).toHaveLength(7);
|
||||
expect(pnpmSetupStepIndexes, name).toHaveLength(
|
||||
name === "pr-trusted.yml" ? 8 : 7,
|
||||
);
|
||||
for (const pnpmSetupStepIndex of pnpmSetupStepIndexes) {
|
||||
const pnpmSetupStep = steps[pnpmSetupStepIndex]!;
|
||||
const nodeBootstrapStep = steps[pnpmSetupStepIndex - 1]!;
|
||||
|
|
|
|||
Loading…
Reference in New Issue