task-loop-handlers.mjs
214 lines 5.8 KB
Raw
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor ⚠ breaking 10 days ago
1 /**
2 * Shared Task Loop list/get handlers — CLI = MCP = Hub REST parity (Phase 2G-c-b).
3 *
4 * @see docs/TASK-LOOP-STORE-CONTRACT-2G-c.md §4
5 */
6
7 import {
8 listTaskLoops,
9 getTaskLoop,
10 getOrchestratorGraph,
11 taskLoopForClient,
12 orchestratorGraphForClient,
13 taskLoopGetEffectiveScope,
14 LOOP_ID_RE,
15 GRAPH_ID_RE,
16 MAX_TASK_LOOP_SUMMARIES,
17 LOOP_STATUSES,
18 } from './task-loop-store.mjs';
19 import { TASK_KINDS } from './task-store.mjs';
20 import { resolveFlowScopeQuery } from '../flow/flow-scope.mjs';
21 import { resolveHandlerVisibleScopes } from '../flow/flow-handlers.mjs';
22
23 /**
24 * @typedef {import('../flow/flow-scope.mjs').FlowScope} FlowScope
25 */
26
27 /**
28 * @param {{
29 * dataDir: string,
30 * vaultId: string,
31 * userId?: string,
32 * role?: string,
33 * cliScopes?: FlowScope[],
34 * visibleScopes?: Set<FlowScope>,
35 * ambiguous?: boolean,
36 * scope?: string,
37 * workspace_id?: string,
38 * workspaceId?: string,
39 * status?: string,
40 * kind?: string,
41 * limit?: number,
42 * starterDir?: string,
43 * graphsDir?: string,
44 * instancesDir?: string,
45 * }} input
46 */
47 export function handleTaskLoopListRequest(input) {
48 const resolved = resolveHandlerVisibleScopes(input);
49 if (resolved.ambiguous) {
50 return {
51 ok: false,
52 status: 400,
53 error: 'Ambiguous task loop scope',
54 code: 'TASK_LOOP_SCOPE_AMBIGUOUS',
55 };
56 }
57
58 const scopeQuery = resolveFlowScopeQuery(resolved.visibleScopes, input.scope);
59 if (!scopeQuery.ok) {
60 const code =
61 scopeQuery.code === 'FLOW_SCOPE_DENIED' ? 'TASK_LOOP_SCOPE_DENIED' : scopeQuery.code;
62 const error =
63 scopeQuery.code === 'FLOW_SCOPE_DENIED'
64 ? 'Task loop scope not authorized'
65 : scopeQuery.error;
66 return { ok: false, status: scopeQuery.status, error, code };
67 }
68
69 const status = typeof input.status === 'string' ? input.status.trim() : '';
70 if (status && !LOOP_STATUSES.includes(/** @type {typeof LOOP_STATUSES[number]} */ (status))) {
71 return { ok: false, status: 400, error: 'Invalid status', code: 'BAD_REQUEST' };
72 }
73
74 const kind = typeof input.kind === 'string' ? input.kind.trim() : '';
75 if (kind && !TASK_KINDS.includes(/** @type {typeof TASK_KINDS[number]} */ (kind))) {
76 return { ok: false, status: 400, error: 'Invalid kind', code: 'BAD_REQUEST' };
77 }
78
79 let limit = typeof input.limit === 'number' ? input.limit : MAX_TASK_LOOP_SUMMARIES;
80 if (!Number.isInteger(limit) || limit < 1) limit = MAX_TASK_LOOP_SUMMARIES;
81
82 const workspaceId =
83 typeof input.workspaceId === 'string'
84 ? input.workspaceId
85 : typeof input.workspace_id === 'string'
86 ? input.workspace_id
87 : undefined;
88
89 const payload = listTaskLoops(input.dataDir, input.vaultId, {
90 visibleScopes: resolved.visibleScopes,
91 filterScopes: scopeQuery.filterScopes,
92 effectiveScope: taskLoopGetEffectiveScope(resolved.visibleScopes),
93 workspaceId,
94 status: status || undefined,
95 kind: kind || undefined,
96 limit,
97 starterDir: input.starterDir,
98 graphsDir: input.graphsDir,
99 instancesDir: input.instancesDir,
100 });
101
102 return { ok: true, payload };
103 }
104
105 /**
106 * @param {{
107 * dataDir: string,
108 * vaultId: string,
109 * loopId: string,
110 * userId?: string,
111 * role?: string,
112 * cliScopes?: FlowScope[],
113 * visibleScopes?: Set<FlowScope>,
114 * ambiguous?: boolean,
115 * starterDir?: string,
116 * graphsDir?: string,
117 * instancesDir?: string,
118 * }} input
119 */
120 export function handleTaskLoopGetRequest(input) {
121 const resolved = resolveHandlerVisibleScopes(input);
122 if (resolved.ambiguous) {
123 return {
124 ok: false,
125 status: 400,
126 error: 'Ambiguous task loop scope',
127 code: 'TASK_LOOP_SCOPE_AMBIGUOUS',
128 };
129 }
130
131 const loopId = typeof input.loopId === 'string' ? input.loopId.trim() : '';
132 if (!LOOP_ID_RE.test(loopId)) {
133 return { ok: false, status: 400, error: 'Invalid loop id', code: 'BAD_REQUEST' };
134 }
135
136 const row = getTaskLoop(input.dataDir, input.vaultId, loopId, {
137 visibleScopes: resolved.visibleScopes,
138 starterDir: input.starterDir,
139 graphsDir: input.graphsDir,
140 instancesDir: input.instancesDir,
141 });
142
143 if (!row) {
144 return { ok: false, status: 404, error: 'unknown_task_loop', code: 'unknown_task_loop' };
145 }
146
147 return {
148 ok: true,
149 payload: {
150 schema: 'knowtation.task_loop_get/v0',
151 vault_id: input.vaultId,
152 effective_scope: taskLoopGetEffectiveScope(resolved.visibleScopes),
153 loop: taskLoopForClient(row),
154 },
155 };
156 }
157
158 /**
159 * @param {{
160 * dataDir: string,
161 * vaultId: string,
162 * graphId: string,
163 * userId?: string,
164 * role?: string,
165 * cliScopes?: FlowScope[],
166 * visibleScopes?: Set<FlowScope>,
167 * ambiguous?: boolean,
168 * starterDir?: string,
169 * graphsDir?: string,
170 * instancesDir?: string,
171 * }} input
172 */
173 export function handleOrchestratorGraphGetRequest(input) {
174 const resolved = resolveHandlerVisibleScopes(input);
175 if (resolved.ambiguous) {
176 return {
177 ok: false,
178 status: 400,
179 error: 'Ambiguous orchestrator graph scope',
180 code: 'ORCHESTRATOR_GRAPH_SCOPE_AMBIGUOUS',
181 };
182 }
183
184 const graphId = typeof input.graphId === 'string' ? input.graphId.trim() : '';
185 if (!GRAPH_ID_RE.test(graphId)) {
186 return { ok: false, status: 400, error: 'Invalid graph id', code: 'BAD_REQUEST' };
187 }
188
189 const row = getOrchestratorGraph(input.dataDir, input.vaultId, graphId, {
190 visibleScopes: resolved.visibleScopes,
191 starterDir: input.starterDir,
192 graphsDir: input.graphsDir,
193 instancesDir: input.instancesDir,
194 });
195
196 if (!row) {
197 return {
198 ok: false,
199 status: 404,
200 error: 'unknown_orchestrator_graph',
201 code: 'unknown_orchestrator_graph',
202 };
203 }
204
205 return {
206 ok: true,
207 payload: {
208 schema: 'knowtation.orchestrator_graph_get/v0',
209 vault_id: input.vaultId,
210 effective_scope: taskLoopGetEffectiveScope(resolved.visibleScopes),
211 graph: orchestratorGraphForClient(row),
212 },
213 };
214 }
File History 1 commit
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor 10 days ago