Setting the file. One moment.
Task State · Wix Replatform · wix/skills · Skills Docs
ContentsBack to the top of the page function waitForInput
— line 65
This file
Number 45.156
Position 156 of 182
Type JavaScript
Size 7 KB
Lines 130 lib/ task-state.js
JavaScript · 130 lines · 7 KB
SCHEMA_VERSION
=
1
;
10 const taskStatePath = ( projectDir ) => path. join (projectDir, 'state' , 'runner' , 'task-state.json' );
11 const now = () => new Date (). toISOString ();
12 const terminal = new Set ([ 'completed' , 'blocked' , 'failed' , 'canceled' , 'interrupted' ]);
13 const taskStates = new Set ([ 'planned' , 'running' , 'waiting_for_input' , ... terminal]);
14 // `starting` is distinct from `running`: a vendor can acknowledge a worker request
15 // before the child has initialized. The supervisor uses that distinction for its bounded
16 // startup watchdog, and the console renders it as an active-but-not-yet-started worker.
17 const agentStates = new Set ([ 'starting' , 'running' , ... terminal]);
18
19 async function readTaskState ( projectDir ) {
20 try {
21 const value = JSON . parse ( await fs. readFile ( taskStatePath (projectDir), 'utf8' ));
22 if (value?.schemaVersion !== SCHEMA_VERSION || ! Array. isArray (value.tasks) || ! Array. isArray (value.agents)) return null ;
23 return value;
24 } catch (error) {
25 if (error.code === 'ENOENT' ) return null ;
26 throw error;
27 }
28 }
29
30 async function writeTaskState ( projectDir , state ) {
31 const next = { ... state, schemaVersion: SCHEMA_VERSION , updatedAt: now () };
32 await fs. mkdir (path. dirname ( taskStatePath (projectDir)), { recursive: true });
33 await writeJsonAtomic ( taskStatePath (projectDir), next);
34 return next;
35 }
36
37 function createTaskState ( runId ) { return { schemaVersion: SCHEMA_VERSION , runId, updatedAt: now (), tasks: [], agents: [] }; }
38 function routeTitle ( route ) { return route.nextResource ? `Run ${ route . nextResource }` : `Resolve ${ String ( route . nextState || 'migration' ). replaceAll ( '_' , ' ' ) }` ; }
39 function routeValue ( route ) { return { state: route.nextState || 'unknown' , reason: route.reason || 'unknown' , resource: route.nextResource || null }; }
40 function currentTask ( state ) { return state.tasks. find (( task ) => [ 'running' , 'waiting_for_input' , 'planned' ]. includes (task.state)) || null ; }
41
42 function finishTask ( state , task , outcome , endedAt = now ()) {
43 if ( ! task || ! taskStates. has (outcome) || terminal. has (task.state)) return ;
44 task.state = outcome; task.endedAt = endedAt;
45 for ( const agent of state.agents. filter (( item ) => item.taskId === task.taskId && [ 'starting' , 'running' ]. includes (item.state))) {
46 agent.state = outcome === 'completed' ? 'completed' : outcome;
47 agent.endedAt = endedAt;
48 }
49 }
50
51 function selectRoute ( state , route ) {
52 const value = routeValue (route); const active = currentTask (state);
53 if (active && active.kind === 'route' && active.route.state === value.state && active.route.reason === value.reason && active.route.resource === value.resource) return active;
54 if (active) finishTask (state, active, 'completed' );
55 const task = { taskId: `route:${ state . tasks . filter (( item ) => item . kind === 'route' ). length + 1 }` , parentTaskId: null , title: routeTitle (route), kind: 'route' , state: 'planned' , route: value, createdAt: now (), startedAt: null , endedAt: null };
56 state.tasks. push (task); return task;
57 }
58
59 function startRoute ( state , route ) {
60 const task = selectRoute (state, route);
61 task.state = 'running' ; task.startedAt ||= now ();
62 return task;
63 }
64
65 function waitForInput ( state , request ) {
66 let task = currentTask (state);
67 if ( ! task) {
68 task = { taskId: `route:${ state . tasks . filter (( item ) => item . kind === 'route' ). length + 1 }` , parentTaskId: null , title: request?.title || 'Await user action' , kind: 'route' , state: 'waiting_for_input' , route: { state: 'awaiting_user_action' , reason: 'pending_user_action' , resource: null }, createdAt: now (), startedAt: null , endedAt: null };
69 state.tasks. push (task);
70 }
71 task.state = 'waiting_for_input' ;
72 return task;
73 }
74
75 function startDirectAgent ( state , task , adapter , pid ) {
76 const agentId = `agent:${ adapter }:${ pid || adapter }` ;
77 let agent = state.agents. find (( item ) => item.agentId === agentId);
78 if ( ! agent) {
79 agent = { agentId, taskId: task.taskId, parentAgentId: null , adapter, label: `${ adapter } agent` , state: 'running' , pid: Number. isInteger (pid) ? pid : null , startedAt: now (), endedAt: null };
80 state.agents. push (agent);
81 } else { agent.state = 'running' ; agent.endedAt = null ; }
82 return agent;
83 }
84
85 function finishDirectAgent ( state , agentId , outcome ) {
86 const agent = state.agents. find (( item ) => item.agentId === agentId);
87 if (agent && agent.state === 'running' ) { agent.state = outcome; agent.endedAt = now (); }
88 }
89
90 function finishWorkers ( state , taskId , outcome = 'failed' ) {
91 if ( ! terminal. has (outcome)) return ;
92 const endedAt = now ();
93 for ( const agent of state.agents. filter (( item ) => item.taskId === taskId && item.agentId. includes ( ':worker:' ) && ! terminal. has (item.state))) {
94 agent.state = outcome;
95 agent.endedAt = endedAt;
96 }
97 }
98
99 function recordWorker ( state , taskId , adapter , report ) {
100 if ( ! report || typeof report !== 'object' || ! / ^ [A-Za-z0-9._:-] {1,160}$ / . test (report.workerId || '' ) || ! agentStates. has (report.state) || typeof report.label !== 'string' || ! report.label. trim ()) return false ;
101 const parentAgentId = report.parentWorkerId ? `agent:${ adapter }:worker:${ report . parentWorkerId }` : null ;
102 if (parentAgentId && ! state.agents. some (( item ) => item.agentId === parentAgentId)) return false ;
103 const agentId = `agent:${ adapter }:worker:${ report . workerId }` ;
104 let agent = state.agents. find (( item ) => item.agentId === agentId);
105 if ( ! agent) { agent = { agentId, taskId, parentAgentId, adapter, label: report.label. trim (), state: report.state, pid: null , startedAt: report.startedAt || now (), endedAt: terminal. has (report.state) ? (report.endedAt || now ()) : null }; state.agents. push (agent); }
106 else {
107 // wait/close events may not carry the original prompt. Preserve the useful spawn
108 // label when the adapter has to fall back to an ID-based label later.
109 if (report.label. trim () !== `Codex worker ${ report . workerId }` ) agent.label = report.label. trim ();
110 agent.state = report.state; agent.endedAt = terminal. has (report.state) ? (report.endedAt || now ()) : null ;
111 }
112 if ( typeof report.activity === 'string' && report.activity. trim ()) agent.activity = report.activity. trim ();
113 if ( typeof report.handoffPath === 'string' && report.handoffPath. trim ()) agent.handoffPath = report.handoffPath. trim ();
114 if (Number. isFinite (report.lastActivityAt)) agent.lastActivityAt = report.lastActivityAt;
115 return true ;
116 }
117
118 function finishCurrent ( state , outcome ) { finishTask (state, currentTask (state), outcome); }
119
120 async function reconcileTaskState ( projectDir , state ) {
121 let changed = false ;
122 for ( const agent of state.agents. filter (( item ) => item.state === 'running' && Number. isInteger (item.pid))) {
123 try { process. kill (agent.pid, 0 ); } catch { agent.state = 'interrupted' ; agent.endedAt = now (); changed = true ; }
124 }
125 const activeTask = currentTask (state);
126 if (activeTask && state.agents. some (( item ) => item.taskId === activeTask.taskId && item.state === 'interrupted' )) { activeTask.state = 'interrupted' ; activeTask.endedAt = now (); changed = true ; }
127 return changed ? writeTaskState (projectDir, state) : state;
128 }
129
130 module . exports = { SCHEMA_VERSION, taskStatePath, readTaskState, writeTaskState, createTaskState, selectRoute, startRoute, waitForInput, startDirectAgent, finishDirectAgent, finishWorkers, recordWorker, finishCurrent, reconcileTaskState };