@@ -13,144 +13,144 @@ import { ProcessHarness } from "./pty-harness.js";
1313 * Create a fresh host, wait for READY, and return the harness.
1414 */
1515async function start ( ) : Promise < ProcessHarness > {
16- const h = new ProcessHarness ( ) ;
17- await h . waitForText ( "READY" ) ;
18- return h ;
16+ const h = new ProcessHarness ( ) ;
17+ await h . waitForText ( "READY" ) ;
18+ return h ;
1919}
2020
2121async function withHarness ( run : ( h : ProcessHarness ) => Promise < void > ) : Promise < void > {
22- const h = await start ( ) ;
23- try {
24- await run ( h ) ;
25- } finally {
26- try {
27- h . write ( "exit" ) ;
28- } catch {
29- // already dead
30- }
31- h . close ( ) ;
32- }
22+ const h = await start ( ) ;
23+ try {
24+ await run ( h ) ;
25+ } finally {
26+ try {
27+ h . write ( "exit" ) ;
28+ } catch {
29+ // already dead
30+ }
31+ h . close ( ) ;
32+ }
3333}
3434
3535describe ( "agenticoding E2E" , ( ) => {
36- it ( "host starts and extension registers" , async ( ) => withHarness ( async ( h ) => {
37- h . write ( "tools" ) ;
38- await h . waitForText ( "OK:" ) ;
39-
40- const snap = h . snapshot ( ) ;
41- assert . ok ( snap . includes ( "notebook_write" ) , "notebook_write tool registered" ) ;
42- assert . ok ( snap . includes ( "notebook_read" ) , "notebook_read tool registered" ) ;
43- assert . ok ( snap . includes ( "notebook_index" ) , "notebook_index tool registered" ) ;
44- assert . ok ( snap . includes ( "notebook_topic_set" ) , "notebook_topic_set tool registered" ) ;
45- assert . ok ( snap . includes ( "handoff" ) , "handoff tool registered" ) ;
46- assert . ok ( snap . includes ( "spawn" ) , "spawn tool registered" ) ;
47- } ) ) ;
48-
49- it ( "notebook write/read round-trip" , async ( ) => withHarness ( async ( h ) => {
50- h . write ( 'tool notebook_write {"name":"my-page","content":"Hello World"}' ) ;
51- await h . waitForText ( "OK:Saved notebook page" ) ;
52-
53- h . write ( 'tool notebook_read {"name":"my-page"}' ) ;
54- await h . waitForText ( "OK:--- my-page ---" ) ;
55-
56- const snap = h . snapshot ( ) ;
57- assert . ok ( snap . includes ( "Hello World" ) , "content persisted" ) ;
58- } ) ) ;
59-
60- it ( "notebook index reflects written pages" , async ( ) => withHarness ( async ( h ) => {
61- h . write ( 'tool notebook_write {"name":"page-a","content":"Page A"}' ) ;
62- await h . waitForText ( "OK:" ) ;
63-
64- h . write ( "tool notebook_index {}" ) ;
65- await h . waitForText ( "page-a" ) ;
66-
67- // Second write should appear in index
68- h . write ( 'tool notebook_write {"name":"page-b","content":"Page B"}' ) ;
69- await h . waitForText ( "OK:" ) ;
70-
71- h . write ( "tool notebook_index {}" ) ;
72- await h . waitForText ( "page-b" ) ;
73-
74- const snap = h . snapshot ( ) ;
75- assert . ok ( snap . includes ( "page-a" ) , "page-a in index" ) ;
76- assert . ok ( snap . includes ( "page-b" ) , "page-b in index" ) ;
77- } ) ) ;
78-
79- it ( "notebook_write overwrites existing page" , async ( ) => withHarness ( async ( h ) => {
80- h . write ( 'tool notebook_write {"name":"page","content":"v1"}' ) ;
81- await h . waitForText ( "OK:" ) ;
82-
83- // Clear accumulated output so we only check the second write/read
84- h . clear ( ) ;
85- h . write ( 'tool notebook_write {"name":"page","content":"v2"}' ) ;
86- await h . waitForText ( "OK:" ) ;
87-
88- h . clear ( ) ;
89- h . write ( 'tool notebook_read {"name":"page"}' ) ;
90- await h . waitForText ( "OK:--- page ---" ) ;
91-
92- const snap = h . snapshot ( ) ;
93- assert . ok ( snap . includes ( "v2" ) , "overwritten content present" ) ;
94- assert . ok ( ! snap . includes ( "v1" ) , "old content absent from fresh output" ) ;
95- } ) ) ;
96-
97- it ( "notebook topic lifecycle: set via command, agent-set blocked" , async ( ) => withHarness ( async ( h ) => {
98- // Set topic via /notebook command (human-set)
99- h . write ( "cmd notebook my-e2e-topic" ) ;
100- await h . waitForText ( "OK" ) ;
101-
102- // Agent-set should be blocked (human is authoritative)
103- h . write ( 'tool notebook_topic_set {"topic":"agent-topic"}' ) ;
104- await h . waitForText ( "ERR:" ) ;
105- const snap = h . snapshot ( ) ;
106- assert . ok (
107- snap . includes ( "authoritative" ) ,
108- "human-set topic blocks agent override" ,
109- ) ;
110- } ) ) ;
111-
112- it ( "agent-set topic works when unset" , async ( ) => withHarness ( async ( h ) => {
113- // No topic set yet -- agent can set
114- h . write ( 'tool notebook_topic_set {"topic":"fresh-agent-topic"}' ) ;
115- await h . waitForText ( "OK:Active notebook topic:" ) ;
116- const snap = h . snapshot ( ) ;
117- assert . ok ( snap . includes ( "fresh-agent-topic" ) ) ;
118- } ) ) ;
119-
120- it ( "handoff tool queues handoff state" , async ( ) => withHarness ( async ( h ) => {
121- h . write ( 'tool handoff {"task":"test handoff task","direction":"next-phase"}' ) ;
122- await h . waitForText ( "OK:Handoff started" ) ;
123- } ) ) ;
124-
125- it ( "commands are registered" , async ( ) => withHarness ( async ( h ) => {
126- h . write ( "cmds" ) ;
127- await h . waitForText ( "OK:" ) ;
128-
129- const snap = h . snapshot ( ) ;
130- assert . ok ( snap . includes ( "notebook" ) , "/notebook command registered" ) ;
131- assert . ok ( snap . includes ( "handoff" ) , "/handoff command registered" ) ;
132- } ) ) ;
133-
134- it ( "spawn tool errors gracefully without model infrastructure" , async ( ) => withHarness ( async ( h ) => {
135- // Without a real model/session manager, spawn should throw immediately.
136- h . write ( 'tool spawn {"prompt":"any task"}' ) ;
137- await h . waitForText ( "ERR:" ) ;
138-
139- const snap = h . snapshot ( ) ;
140- assert . ok ( snap . includes ( "No model" ) || snap . includes ( "ERR" ) , "spawn errors gracefully" ) ;
141- } ) ) ;
142-
143- it ( "handles errors gracefully" , async ( ) => withHarness ( async ( h ) => {
144- // Unknown tool
145- h . write ( "tool nonexistent {}" ) ;
146- await h . waitForText ( "ERR:unknown tool" ) ;
147-
148- // Invalid JSON
149- h . write ( "tool notebook_write {bad json}" ) ;
150- await h . waitForText ( "ERR:invalid json" ) ;
151-
152- // Unknown command
153- h . write ( "cmd nonexistent" ) ;
154- await h . waitForText ( "ERR:unknown command" ) ;
155- } ) ) ;
36+ it ( "host starts and extension registers" , async ( ) => withHarness ( async ( h ) => {
37+ h . write ( "tools" ) ;
38+ await h . waitForText ( "OK:" ) ;
39+
40+ const snap = h . snapshot ( ) ;
41+ assert . ok ( snap . includes ( "notebook_write" ) , "notebook_write tool registered" ) ;
42+ assert . ok ( snap . includes ( "notebook_read" ) , "notebook_read tool registered" ) ;
43+ assert . ok ( snap . includes ( "notebook_index" ) , "notebook_index tool registered" ) ;
44+ assert . ok ( snap . includes ( "notebook_topic_set" ) , "notebook_topic_set tool registered" ) ;
45+ assert . ok ( snap . includes ( "handoff" ) , "handoff tool registered" ) ;
46+ assert . ok ( snap . includes ( "spawn" ) , "spawn tool registered" ) ;
47+ } ) ) ;
48+
49+ it ( "notebook write/read round-trip" , async ( ) => withHarness ( async ( h ) => {
50+ h . write ( 'tool notebook_write {"name":"my-page","content":"Hello World"}' ) ;
51+ await h . waitForText ( "OK:Saved notebook page" ) ;
52+
53+ h . write ( 'tool notebook_read {"name":"my-page"}' ) ;
54+ await h . waitForText ( "OK:--- my-page ---" ) ;
55+
56+ const snap = h . snapshot ( ) ;
57+ assert . ok ( snap . includes ( "Hello World" ) , "content persisted" ) ;
58+ } ) ) ;
59+
60+ it ( "notebook index reflects written pages" , async ( ) => withHarness ( async ( h ) => {
61+ h . write ( 'tool notebook_write {"name":"page-a","content":"Page A"}' ) ;
62+ await h . waitForText ( "OK:" ) ;
63+
64+ h . write ( "tool notebook_index {}" ) ;
65+ await h . waitForText ( "page-a" ) ;
66+
67+ // Second write should appear in index
68+ h . write ( 'tool notebook_write {"name":"page-b","content":"Page B"}' ) ;
69+ await h . waitForText ( "OK:" ) ;
70+
71+ h . write ( "tool notebook_index {}" ) ;
72+ await h . waitForText ( "page-b" ) ;
73+
74+ const snap = h . snapshot ( ) ;
75+ assert . ok ( snap . includes ( "page-a" ) , "page-a in index" ) ;
76+ assert . ok ( snap . includes ( "page-b" ) , "page-b in index" ) ;
77+ } ) ) ;
78+
79+ it ( "notebook_write overwrites existing page" , async ( ) => withHarness ( async ( h ) => {
80+ h . write ( 'tool notebook_write {"name":"page","content":"v1"}' ) ;
81+ await h . waitForText ( "OK:" ) ;
82+
83+ // Clear accumulated output so we only check the second write/read
84+ h . clear ( ) ;
85+ h . write ( 'tool notebook_write {"name":"page","content":"v2"}' ) ;
86+ await h . waitForText ( "OK:" ) ;
87+
88+ h . clear ( ) ;
89+ h . write ( 'tool notebook_read {"name":"page"}' ) ;
90+ await h . waitForText ( "OK:--- page ---" ) ;
91+
92+ const snap = h . snapshot ( ) ;
93+ assert . ok ( snap . includes ( "v2" ) , "overwritten content present" ) ;
94+ assert . ok ( ! snap . includes ( "v1" ) , "old content absent from fresh output" ) ;
95+ } ) ) ;
96+
97+ it ( "notebook topic lifecycle: set via command, agent-set blocked" , async ( ) => withHarness ( async ( h ) => {
98+ // Set topic via /notebook command (human-set)
99+ h . write ( "cmd notebook my-e2e-topic" ) ;
100+ await h . waitForText ( "OK" ) ;
101+
102+ // Agent-set should be blocked (human is authoritative)
103+ h . write ( 'tool notebook_topic_set {"topic":"agent-topic"}' ) ;
104+ await h . waitForText ( "ERR:" ) ;
105+ const snap = h . snapshot ( ) ;
106+ assert . ok (
107+ snap . includes ( "authoritative" ) ,
108+ "human-set topic blocks agent override" ,
109+ ) ;
110+ } ) ) ;
111+
112+ it ( "agent-set topic works when unset" , async ( ) => withHarness ( async ( h ) => {
113+ // No topic set yet -- agent can set
114+ h . write ( 'tool notebook_topic_set {"topic":"fresh-agent-topic"}' ) ;
115+ await h . waitForText ( "OK:Active notebook topic:" ) ;
116+ const snap = h . snapshot ( ) ;
117+ assert . ok ( snap . includes ( "fresh-agent-topic" ) ) ;
118+ } ) ) ;
119+
120+ it ( "handoff tool queues handoff state" , async ( ) => withHarness ( async ( h ) => {
121+ h . write ( 'tool handoff {"task":"test handoff task","direction":"next-phase"}' ) ;
122+ await h . waitForText ( "OK:Handoff started" ) ;
123+ } ) ) ;
124+
125+ it ( "commands are registered" , async ( ) => withHarness ( async ( h ) => {
126+ h . write ( "cmds" ) ;
127+ await h . waitForText ( "OK:" ) ;
128+
129+ const snap = h . snapshot ( ) ;
130+ assert . ok ( snap . includes ( "notebook" ) , "/notebook command registered" ) ;
131+ assert . ok ( snap . includes ( "handoff" ) , "/handoff command registered" ) ;
132+ } ) ) ;
133+
134+ it ( "spawn tool errors gracefully without model infrastructure" , async ( ) => withHarness ( async ( h ) => {
135+ // Without a real model/session manager, spawn should throw immediately.
136+ h . write ( 'tool spawn {"prompt":"any task"}' ) ;
137+ await h . waitForText ( "ERR:" ) ;
138+
139+ const snap = h . snapshot ( ) ;
140+ assert . ok ( snap . includes ( "No model" ) || snap . includes ( "ERR" ) , "spawn errors gracefully" ) ;
141+ } ) ) ;
142+
143+ it ( "handles errors gracefully" , async ( ) => withHarness ( async ( h ) => {
144+ // Unknown tool
145+ h . write ( "tool nonexistent {}" ) ;
146+ await h . waitForText ( "ERR:unknown tool" ) ;
147+
148+ // Invalid JSON
149+ h . write ( "tool notebook_write {bad json}" ) ;
150+ await h . waitForText ( "ERR:invalid json" ) ;
151+
152+ // Unknown command
153+ h . write ( "cmd nonexistent" ) ;
154+ await h . waitForText ( "ERR:unknown command" ) ;
155+ } ) ) ;
156156} ) ;
0 commit comments