@@ -2,6 +2,7 @@ import { describe, expect, it } from "@effect/vitest";
22
33import {
44 PRIME_AGENT_PLAN_PROTOCOL ,
5+ PRIME_AGENT_PLAN_TOOL_DEFINITION ,
56 PRIME_AGENT_PLAN_TOOL_NAME ,
67 makePrimeAgentManagedExtensionSource ,
78 projectPrimeAgentManagedPermissionRequest ,
@@ -14,6 +15,19 @@ const project = (input: {
1415 readonly message ?: string ;
1516} ) => projectPrimeAgentManagedPermissionRequest ( input , token ) ;
1617
18+ describe ( "PRIME_AGENT_PLAN_TOOL_DEFINITION" , ( ) => {
19+ it ( "advertises the same discriminated waiting contract enforced at runtime" , ( ) => {
20+ const [ nonWaiting , waiting ] =
21+ PRIME_AGENT_PLAN_TOOL_DEFINITION . parameters . properties . plan . items . oneOf ;
22+
23+ expect ( nonWaiting . properties . status . enum ) . toEqual ( [ "pending" , "inProgress" , "completed" ] ) ;
24+ expect ( nonWaiting . properties ) . not . toHaveProperty ( "waitingOn" ) ;
25+ expect ( waiting . properties . status . const ) . toBe ( "waiting" ) ;
26+ expect ( waiting . properties . waitingOn . enum ) . toEqual ( [ "user" , "delegates" , "external" ] ) ;
27+ expect ( waiting . required ) . toContain ( "waitingOn" ) ;
28+ } ) ;
29+ } ) ;
30+
1731describe ( "projectPrimeAgentManagedPermissionRequest" , ( ) => {
1832 it ( "projects only the versioned, session-authorized managed confirmation format" , ( ) => {
1933 expect (
@@ -189,6 +203,70 @@ describe("makePrimeAgentManagedExtensionSource", () => {
189203 ) . rejects . toThrow ( "root Pylon session" ) ;
190204 } ) ;
191205
206+ it ( "preserves honest waits and rejects ambiguous managed plans" , async ( ) => {
207+ const extension = await loadExtension ( extensionSource ( ) ) ;
208+ const execute = extension . tools . get ( PRIME_AGENT_PLAN_TOOL_NAME ) ?. execute ;
209+ if ( ! execute ) throw new Error ( "expected managed plan tool" ) ;
210+
211+ for ( const waitingOn of [ "user" , "delegates" , "external" ] as const ) {
212+ await expect (
213+ execute (
214+ `call-wait-${ waitingOn } ` ,
215+ { plan : [ { step : "Await dependency" , status : "waiting" , waitingOn } ] } ,
216+ undefined ,
217+ undefined ,
218+ extensionContext ( true ) ,
219+ ) ,
220+ ) . resolves . toMatchObject ( {
221+ details : {
222+ plan : [ { step : "Await dependency" , status : "waiting" , waitingOn } ] ,
223+ } ,
224+ } ) ;
225+ }
226+
227+ await expect (
228+ execute (
229+ "call-wait-missing-owner" ,
230+ { plan : [ { step : "Await dependency" , status : "waiting" } ] } ,
231+ undefined ,
232+ undefined ,
233+ extensionContext ( true ) ,
234+ ) ,
235+ ) . rejects . toThrow ( "Waiting plan steps require waitingOn" ) ;
236+ await expect (
237+ execute (
238+ "call-owner-without-wait" ,
239+ { plan : [ { step : "Keep working" , status : "inProgress" , waitingOn : "user" } ] } ,
240+ undefined ,
241+ undefined ,
242+ extensionContext ( true ) ,
243+ ) ,
244+ ) . rejects . toThrow ( "waitingOn is valid only for waiting plan steps" ) ;
245+ await expect (
246+ execute (
247+ "call-ambiguous-active" ,
248+ {
249+ plan : [
250+ { step : "Implement" , status : "inProgress" } ,
251+ { step : "Await agents" , status : "waiting" , waitingOn : "delegates" } ,
252+ ] ,
253+ } ,
254+ undefined ,
255+ undefined ,
256+ extensionContext ( true ) ,
257+ ) ,
258+ ) . rejects . toThrow ( "at most one in-progress or waiting step" ) ;
259+ await expect (
260+ execute (
261+ "call-all-complete" ,
262+ { plan : [ { step : "Done" , status : "completed" } ] } ,
263+ undefined ,
264+ undefined ,
265+ extensionContext ( true ) ,
266+ ) ,
267+ ) . resolves . toMatchObject ( { details : { plan : [ { step : "Done" , status : "completed" } ] } } ) ;
268+ } ) ;
269+
192270 it ( "combines plan updates with the supervised gate and allows only that side-effect-free tool" , async ( ) => {
193271 const source = extensionSource ( token ) ;
194272 expect ( source ) . toContain ( 'pi.on("tool_call"' ) ;
0 commit comments