@tt-a1i/hive 2.1.9 → 2.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/src/cli/hive-mcp.d.ts +62 -0
  3. package/dist/src/cli/hive-mcp.js +253 -0
  4. package/dist/src/cli/hive.js +8 -0
  5. package/dist/src/cli/team.d.ts +8 -0
  6. package/dist/src/cli/team.js +100 -0
  7. package/dist/src/server/agent-runtime-contract.d.ts +1 -0
  8. package/dist/src/server/agent-runtime.js +4 -0
  9. package/dist/src/server/agent-startup-instructions.js +1 -1
  10. package/dist/src/server/agent-stdin-dispatcher.js +1 -0
  11. package/dist/src/server/diagnostics-support-bundle.d.ts +1 -1
  12. package/dist/src/server/external-goal-auth.d.ts +1 -0
  13. package/dist/src/server/external-goal-auth.js +1 -0
  14. package/dist/src/server/external-goal-bridge.d.ts +103 -0
  15. package/dist/src/server/external-goal-bridge.js +299 -0
  16. package/dist/src/server/external-goal-store.d.ts +59 -0
  17. package/dist/src/server/external-goal-store.js +165 -0
  18. package/dist/src/server/hive-team-guidance.js +1 -0
  19. package/dist/src/server/post-start-input-writer.js +52 -6
  20. package/dist/src/server/route-types.d.ts +30 -0
  21. package/dist/src/server/routes-external-goals.d.ts +2 -0
  22. package/dist/src/server/routes-external-goals.js +184 -0
  23. package/dist/src/server/routes-runtime.js +6 -1
  24. package/dist/src/server/routes-scenarios.d.ts +12 -17
  25. package/dist/src/server/routes-scenarios.js +41 -17
  26. package/dist/src/server/routes-team-goals.d.ts +2 -0
  27. package/dist/src/server/routes-team-goals.js +60 -0
  28. package/dist/src/server/routes.js +4 -0
  29. package/dist/src/server/runtime-store-contract.d.ts +11 -0
  30. package/dist/src/server/runtime-store-external-goals.d.ts +47 -0
  31. package/dist/src/server/runtime-store-external-goals.js +24 -0
  32. package/dist/src/server/runtime-store-helpers.d.ts +3 -0
  33. package/dist/src/server/runtime-store-helpers.js +11 -1
  34. package/dist/src/server/runtime-store.js +6 -0
  35. package/dist/src/server/scenario-presets.d.ts +7 -4
  36. package/dist/src/server/scenario-presets.js +16 -6
  37. package/dist/src/server/sqlite-schema-v37.d.ts +2 -0
  38. package/dist/src/server/sqlite-schema-v37.js +38 -0
  39. package/dist/src/server/sqlite-schema.d.ts +1 -1
  40. package/dist/src/server/sqlite-schema.js +41 -1
  41. package/dist/src/server/team-authz.d.ts +1 -1
  42. package/dist/src/server/team-authz.js +1 -0
  43. package/dist/src/server/ui-auth.d.ts +2 -0
  44. package/dist/src/server/ui-auth.js +7 -0
  45. package/dist/src/shared/random-worker-name.d.ts +9 -0
  46. package/dist/src/shared/random-worker-name.js +382 -0
  47. package/package.json +1 -1
  48. package/web/dist/assets/{AddWorkerDialog-QBN0m0eH.js → AddWorkerDialog-CMQdmZJQ.js} +2 -2
  49. package/web/dist/assets/{AddWorkspaceFlow-BJy-W2XI.js → AddWorkspaceFlow-arGy-LVX.js} +1 -1
  50. package/web/dist/assets/{FirstRunWizard-V-idMQB7.js → FirstRunWizard-PRHGFYry.js} +1 -1
  51. package/web/dist/assets/{MarketplaceDrawer-D4ldgvSM.js → MarketplaceDrawer-Dm-Z-YL8.js} +1 -1
  52. package/web/dist/assets/{TaskGraphDrawer-BSxRbr1h.js → TaskGraphDrawer-HaGRlpar.js} +1 -1
  53. package/web/dist/assets/{WhatsNewDialog-C-hpR8y4.js → WhatsNewDialog-9upxbs9b.js} +1 -1
  54. package/web/dist/assets/{WorkerModal-EuNxsCtr.js → WorkerModal-DAC0fORb.js} +1 -1
  55. package/web/dist/assets/{WorkflowsDrawer-CoT6kWcM.js → WorkflowsDrawer-Dqv4vNZW.js} +1 -1
  56. package/web/dist/assets/{WorkspaceMemoryDrawer-BXx4Z0kx.js → WorkspaceMemoryDrawer-QvVUM6qp.js} +1 -1
  57. package/web/dist/assets/{WorkspaceTaskDrawer-DKRRttxQ.js → WorkspaceTaskDrawer-CdiraIBo.js} +1 -1
  58. package/web/dist/assets/index-B1vvo0-M.js +84 -0
  59. package/web/dist/assets/{search-KbSLfFB8.js → search-DgMNPsfM.js} +1 -1
  60. package/web/dist/assets/{square-terminal-C89RIZhu.js → square-terminal-CiDDFh5Z.js} +1 -1
  61. package/web/dist/index.html +1 -1
  62. package/web/dist/sw.js +1 -1
  63. package/web/dist/assets/index-D43vHIy2.js +0 -84
@@ -0,0 +1,103 @@
1
+ import type { AgentSummary, TeamListItem, WorkspaceSummary } from '../shared/types.js';
2
+ import type { ExternalGoalEvent, ExternalGoalReportStatus, ExternalGoalSession, ExternalGoalStore } from './external-goal-store.js';
3
+ import { PtyInactiveError } from './http-errors.js';
4
+ interface ExternalGoalBridgePorts {
5
+ deliverToOrchestrator: (workspaceId: string, text: string) => Promise<void>;
6
+ getActiveRunByAgentId: (workspaceId: string, agentId: string) => unknown | undefined;
7
+ getAgent: (workspaceId: string, agentId: string) => AgentSummary;
8
+ getWorkspaceSnapshot: (workspaceId: string) => {
9
+ agents: AgentSummary[];
10
+ summary: WorkspaceSummary;
11
+ };
12
+ goalStore: ExternalGoalStore;
13
+ listWorkers: (workspaceId: string) => TeamListItem[];
14
+ listWorkspaces: () => WorkspaceSummary[];
15
+ }
16
+ export interface ExternalGoalStartInput {
17
+ context?: unknown;
18
+ goal: string;
19
+ source: string;
20
+ timeoutHintMs?: number;
21
+ workspaceId: string;
22
+ }
23
+ export interface ExternalGoalContinueInput {
24
+ context?: unknown;
25
+ goalId: string;
26
+ message: string;
27
+ }
28
+ export interface ExternalGoalReportInput {
29
+ artifacts?: string[];
30
+ body: string;
31
+ fromAgentId: string;
32
+ goalId: string;
33
+ status: ExternalGoalReportStatus;
34
+ workspaceId: string;
35
+ }
36
+ export interface ExternalGoalWaitInput {
37
+ cursor?: number;
38
+ goalId: string;
39
+ timeoutMs?: number;
40
+ }
41
+ export interface ExternalGoalCancelInput {
42
+ goalId: string;
43
+ reason: string;
44
+ }
45
+ export declare class ExternalGoalDeliveryError extends PtyInactiveError {
46
+ readonly cursor: number;
47
+ readonly goalId: string;
48
+ readonly status: ExternalGoalSession['status'];
49
+ constructor(input: {
50
+ cursor: number;
51
+ goalId: string;
52
+ message: string;
53
+ status: 'cancelled' | 'failed';
54
+ });
55
+ }
56
+ export declare const createExternalGoalBridge: ({ deliverToOrchestrator, getActiveRunByAgentId, getAgent, getWorkspaceSnapshot, goalStore, listWorkers, listWorkspaces, }: ExternalGoalBridgePorts) => {
57
+ listWorkspaces: () => WorkspaceSummary[];
58
+ inspectWorkspace(input: {
59
+ workspaceId: string;
60
+ }): {
61
+ workspace: WorkspaceSummary;
62
+ orchestrator: {
63
+ id: string;
64
+ name: string;
65
+ status: "idle" | "working" | "stopped";
66
+ active_run: boolean;
67
+ };
68
+ members: import("../shared/types.js").TeamListItemPayload[];
69
+ };
70
+ startGoal(input: ExternalGoalStartInput): Promise<{
71
+ cursor: number;
72
+ events: ExternalGoalEvent[];
73
+ goalId: string;
74
+ session: ExternalGoalSession;
75
+ status: "open" | "cancelled" | "in_progress" | "blocked" | "done" | "failed";
76
+ }>;
77
+ continueGoal(input: ExternalGoalContinueInput): Promise<{
78
+ cursor: number;
79
+ event: ExternalGoalEvent;
80
+ session: ExternalGoalSession;
81
+ status: "open" | "cancelled" | "in_progress" | "blocked" | "done" | "failed";
82
+ }>;
83
+ reportGoal(input: ExternalGoalReportInput): {
84
+ cursor: number;
85
+ event: ExternalGoalEvent;
86
+ session: ExternalGoalSession;
87
+ status: "open" | "cancelled" | "in_progress" | "blocked" | "done" | "failed";
88
+ };
89
+ waitGoal(input: ExternalGoalWaitInput): Promise<{
90
+ cursor: number;
91
+ events: ExternalGoalEvent[];
92
+ goalId: string;
93
+ status: "open" | "cancelled" | "in_progress" | "blocked" | "done" | "failed";
94
+ }>;
95
+ cancelGoal(input: ExternalGoalCancelInput): Promise<{
96
+ cursor: number;
97
+ event: ExternalGoalEvent;
98
+ session: ExternalGoalSession;
99
+ status: "open" | "cancelled" | "in_progress" | "blocked" | "done" | "failed";
100
+ }>;
101
+ };
102
+ export type ExternalGoalBridge = ReturnType<typeof createExternalGoalBridge>;
103
+ export {};
@@ -0,0 +1,299 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { escapeHiveEnvelopeAttribute, escapeHiveEnvelopeText } from './hive-envelope-escape.js';
3
+ import { BadRequestError, ConflictError, ForbiddenError, HttpError, PtyInactiveError, } from './http-errors.js';
4
+ import { serializeTeamListItem } from './team-list-serializer.js';
5
+ import { getOrchestratorId } from './workspace-store-support.js';
6
+ const DEFAULT_WAIT_TIMEOUT_MS = 30_000;
7
+ const MAX_WAIT_TIMEOUT_MS = 120_000;
8
+ export class ExternalGoalDeliveryError extends PtyInactiveError {
9
+ cursor;
10
+ goalId;
11
+ status;
12
+ constructor(input) {
13
+ super(input.message);
14
+ this.name = 'ExternalGoalDeliveryError';
15
+ this.cursor = input.cursor;
16
+ this.goalId = input.goalId;
17
+ this.status = input.status;
18
+ }
19
+ }
20
+ const reportEventKind = (status) => {
21
+ if (status === 'progress')
22
+ return 'progress_reported';
23
+ if (status === 'done')
24
+ return 'goal_done';
25
+ if (status === 'blocked')
26
+ return 'goal_blocked';
27
+ return 'goal_failed';
28
+ };
29
+ const reportSessionStatus = (status) => {
30
+ if (status === 'progress')
31
+ return 'in_progress';
32
+ return status;
33
+ };
34
+ const closedForReport = new Set([
35
+ 'blocked',
36
+ 'done',
37
+ 'failed',
38
+ 'cancelled',
39
+ ]);
40
+ const boundedTimeout = (value) => {
41
+ if (value === undefined)
42
+ return DEFAULT_WAIT_TIMEOUT_MS;
43
+ if (!Number.isFinite(value) || value < 0) {
44
+ throw new BadRequestError('timeout_ms must be a non-negative number');
45
+ }
46
+ return Math.min(Math.floor(value), MAX_WAIT_TIMEOUT_MS);
47
+ };
48
+ const normalizedCursor = (value) => {
49
+ if (value === undefined)
50
+ return 0;
51
+ if (!Number.isInteger(value) || value < 0) {
52
+ throw new BadRequestError('cursor must be a non-negative integer');
53
+ }
54
+ return value;
55
+ };
56
+ const requireSession = (goalStore, goalId) => {
57
+ const session = goalStore.getSession(goalId);
58
+ if (!session)
59
+ throw new HttpError(404, `External goal not found: ${goalId}`);
60
+ return session;
61
+ };
62
+ const contextLines = (context) => {
63
+ if (context === undefined || context === null)
64
+ return [];
65
+ const body = typeof context === 'string' ? context : JSON.stringify(context, null, 2);
66
+ if (!body.trim())
67
+ return [];
68
+ return ['', 'Context:', escapeHiveEnvelopeText(body)];
69
+ };
70
+ const buildExternalGoalPayload = (session) => [
71
+ `<hive-message kind="external-goal" source="${escapeHiveEnvelopeAttribute(session.source)}" goal_id="${escapeHiveEnvelopeAttribute(session.id)}">`,
72
+ '',
73
+ 'You received an external Supervisor goal.',
74
+ '',
75
+ 'Rules:',
76
+ '- You are still the Hive Orchestrator for this workspace.',
77
+ '- Use current Hive members through `team list` and `team send`.',
78
+ '- Do not report final completion to the user directly; report to the external Supervisor with:',
79
+ ` team goal report --goal ${session.id} --status done --stdin`,
80
+ '- Use status `progress` for meaningful phase updates.',
81
+ '- Use status `blocked` when you need an external decision.',
82
+ '',
83
+ 'Goal:',
84
+ escapeHiveEnvelopeText(session.goal),
85
+ ...contextLines(session.context),
86
+ '</hive-message>',
87
+ '',
88
+ ].join('\n');
89
+ const buildExternalGoalContinuePayload = (session, message, context) => [
90
+ `<hive-message kind="external-goal-continue" goal_id="${escapeHiveEnvelopeAttribute(session.id)}">`,
91
+ '',
92
+ 'The external Supervisor added context for this goal.',
93
+ '',
94
+ 'Continue coordinating through Hive members as needed. Report meaningful updates with:',
95
+ ` team goal report --goal ${session.id} --status progress --stdin`,
96
+ '',
97
+ 'Message:',
98
+ escapeHiveEnvelopeText(message),
99
+ ...contextLines(context),
100
+ '</hive-message>',
101
+ '',
102
+ ].join('\n');
103
+ const buildExternalGoalCancelPayload = (session, reason) => [
104
+ `<hive-message kind="external-goal-cancel" goal_id="${escapeHiveEnvelopeAttribute(session.id)}">`,
105
+ '',
106
+ 'Stop coordinating this external goal. Do not send more goal reports for it.',
107
+ '',
108
+ 'MVP note: Hive did not automatically cancel member dispatches. Decide whether any open dispatches should be cancelled with `team cancel`.',
109
+ '',
110
+ 'Reason:',
111
+ escapeHiveEnvelopeText(reason),
112
+ '</hive-message>',
113
+ '',
114
+ ].join('\n');
115
+ export const createExternalGoalBridge = ({ deliverToOrchestrator, getActiveRunByAgentId, getAgent, getWorkspaceSnapshot, goalStore, listWorkers, listWorkspaces, }) => {
116
+ const events = new EventEmitter();
117
+ events.setMaxListeners(200);
118
+ const notify = (goalId) => events.emit(goalId);
119
+ const waitForNewEvent = (goalId, timeoutMs) => {
120
+ if (timeoutMs <= 0)
121
+ return Promise.resolve();
122
+ return new Promise((resolve) => {
123
+ const onEvent = () => {
124
+ clearTimeout(timer);
125
+ resolve();
126
+ };
127
+ const timer = setTimeout(() => {
128
+ events.off(goalId, onEvent);
129
+ resolve();
130
+ }, timeoutMs);
131
+ timer.unref?.();
132
+ events.once(goalId, onEvent);
133
+ });
134
+ };
135
+ const appendAndNotify = (input) => {
136
+ const event = goalStore.appendEvent(input);
137
+ notify(input.goalId);
138
+ return event;
139
+ };
140
+ const deliverOrMarkFailed = async (session, text) => {
141
+ try {
142
+ await deliverToOrchestrator(session.workspaceId, text);
143
+ }
144
+ catch (error) {
145
+ const event = appendAndNotify({
146
+ body: error instanceof Error ? error.message : String(error),
147
+ goalId: session.id,
148
+ kind: 'delivery_failed',
149
+ sessionStatus: 'failed',
150
+ status: 'failed',
151
+ });
152
+ throw new ExternalGoalDeliveryError({
153
+ cursor: event.sequence,
154
+ goalId: session.id,
155
+ message: `orchestrator_not_running: could not deliver external goal ${session.id}`,
156
+ status: 'failed',
157
+ });
158
+ }
159
+ };
160
+ return {
161
+ listWorkspaces,
162
+ inspectWorkspace(input) {
163
+ const snapshot = getWorkspaceSnapshot(input.workspaceId);
164
+ const orchestratorId = getOrchestratorId(input.workspaceId);
165
+ const orchestrator = getAgent(input.workspaceId, orchestratorId);
166
+ return {
167
+ workspace: snapshot.summary,
168
+ orchestrator: {
169
+ id: orchestrator.id,
170
+ name: orchestrator.name,
171
+ status: orchestrator.status,
172
+ active_run: Boolean(getActiveRunByAgentId(input.workspaceId, orchestrator.id)),
173
+ },
174
+ members: listWorkers(input.workspaceId).map((worker) => serializeTeamListItem(worker)),
175
+ };
176
+ },
177
+ async startGoal(input) {
178
+ getWorkspaceSnapshot(input.workspaceId);
179
+ const { session } = goalStore.createSession({
180
+ context: input.context,
181
+ goal: input.goal,
182
+ source: input.source,
183
+ workspaceId: input.workspaceId,
184
+ });
185
+ notify(session.id);
186
+ await deliverOrMarkFailed(session, buildExternalGoalPayload(session));
187
+ const delivered = appendAndNotify({
188
+ body: 'External goal delivered to Orchestrator.',
189
+ goalId: session.id,
190
+ kind: 'goal_delivered',
191
+ sessionStatus: 'in_progress',
192
+ status: 'in_progress',
193
+ });
194
+ const current = requireSession(goalStore, session.id);
195
+ return {
196
+ cursor: delivered.sequence,
197
+ events: goalStore.listEventsAfter(session.id, 0),
198
+ goalId: session.id,
199
+ session: current,
200
+ status: current.status,
201
+ };
202
+ },
203
+ async continueGoal(input) {
204
+ const session = requireSession(goalStore, input.goalId);
205
+ if (session.status === 'cancelled') {
206
+ throw new ConflictError(`External goal is cancelled: ${input.goalId}`);
207
+ }
208
+ const event = appendAndNotify({
209
+ body: input.message,
210
+ goalId: session.id,
211
+ kind: 'goal_continued',
212
+ sessionStatus: 'in_progress',
213
+ status: 'in_progress',
214
+ });
215
+ await deliverOrMarkFailed({ ...session, status: 'in_progress' }, buildExternalGoalContinuePayload(session, input.message, input.context));
216
+ const current = requireSession(goalStore, session.id);
217
+ return { cursor: event.sequence, event, session: current, status: current.status };
218
+ },
219
+ reportGoal(input) {
220
+ const session = requireSession(goalStore, input.goalId);
221
+ if (session.workspaceId !== input.workspaceId) {
222
+ throw new HttpError(404, `External goal not found in workspace: ${input.goalId}`);
223
+ }
224
+ if (input.fromAgentId !== getOrchestratorId(input.workspaceId)) {
225
+ throw new ForbiddenError('Only the workspace Orchestrator can report external goals');
226
+ }
227
+ if (closedForReport.has(session.status)) {
228
+ throw new ConflictError(`External goal is not accepting reports: ${input.goalId}`);
229
+ }
230
+ const event = appendAndNotify({
231
+ body: input.body,
232
+ goalId: session.id,
233
+ kind: reportEventKind(input.status),
234
+ sessionStatus: reportSessionStatus(input.status),
235
+ status: input.status,
236
+ ...(input.artifacts !== undefined ? { artifacts: input.artifacts } : {}),
237
+ });
238
+ const current = requireSession(goalStore, session.id);
239
+ return { cursor: event.sequence, event, session: current, status: current.status };
240
+ },
241
+ async waitGoal(input) {
242
+ const session = requireSession(goalStore, input.goalId);
243
+ const cursor = normalizedCursor(input.cursor);
244
+ const existing = goalStore.listEventsAfter(session.id, cursor);
245
+ if (existing.length > 0) {
246
+ const current = requireSession(goalStore, session.id);
247
+ return {
248
+ cursor: existing.at(-1)?.sequence ?? cursor,
249
+ events: existing,
250
+ goalId: session.id,
251
+ status: current.status,
252
+ };
253
+ }
254
+ await waitForNewEvent(session.id, boundedTimeout(input.timeoutMs));
255
+ const eventsAfterWait = goalStore.listEventsAfter(session.id, cursor);
256
+ const current = requireSession(goalStore, session.id);
257
+ return {
258
+ cursor: eventsAfterWait.at(-1)?.sequence ??
259
+ Math.max(cursor, goalStore.getLatestSequence(session.id)),
260
+ events: eventsAfterWait,
261
+ goalId: session.id,
262
+ status: current.status,
263
+ };
264
+ },
265
+ async cancelGoal(input) {
266
+ const session = requireSession(goalStore, input.goalId);
267
+ if (closedForReport.has(session.status) && session.status !== 'blocked') {
268
+ throw new ConflictError(`External goal cannot be cancelled from ${session.status}: ${input.goalId}`);
269
+ }
270
+ const event = appendAndNotify({
271
+ body: input.reason,
272
+ goalId: session.id,
273
+ kind: 'goal_cancelled',
274
+ sessionStatus: 'cancelled',
275
+ status: 'cancelled',
276
+ });
277
+ try {
278
+ await deliverToOrchestrator(session.workspaceId, buildExternalGoalCancelPayload(session, input.reason));
279
+ }
280
+ catch (error) {
281
+ const deliveryFailure = appendAndNotify({
282
+ body: error instanceof Error ? error.message : String(error),
283
+ goalId: session.id,
284
+ kind: 'delivery_failed',
285
+ sessionStatus: 'cancelled',
286
+ status: 'cancelled',
287
+ });
288
+ throw new ExternalGoalDeliveryError({
289
+ cursor: deliveryFailure.sequence,
290
+ goalId: session.id,
291
+ message: `orchestrator_not_running: cancelled external goal ${session.id}, but could not deliver the cancellation notice`,
292
+ status: 'cancelled',
293
+ });
294
+ }
295
+ const current = requireSession(goalStore, session.id);
296
+ return { cursor: event.sequence, event, session: current, status: current.status };
297
+ },
298
+ };
299
+ };
@@ -0,0 +1,59 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ export declare const EXTERNAL_GOAL_STATUSES: readonly ["open", "in_progress", "blocked", "done", "failed", "cancelled"];
3
+ export type ExternalGoalStatus = (typeof EXTERNAL_GOAL_STATUSES)[number];
4
+ export declare const EXTERNAL_GOAL_REPORT_STATUSES: readonly ["progress", "done", "blocked", "failed"];
5
+ export type ExternalGoalReportStatus = (typeof EXTERNAL_GOAL_REPORT_STATUSES)[number];
6
+ export declare const EXTERNAL_GOAL_EVENT_KINDS: readonly ["goal_started", "goal_continued", "goal_delivered", "progress_reported", "goal_done", "goal_blocked", "goal_failed", "goal_cancelled", "delivery_failed"];
7
+ export type ExternalGoalEventKind = (typeof EXTERNAL_GOAL_EVENT_KINDS)[number];
8
+ export interface ExternalGoalSession {
9
+ closedAt: number | null;
10
+ context: unknown;
11
+ createdAt: number;
12
+ goal: string;
13
+ id: string;
14
+ source: string;
15
+ status: ExternalGoalStatus;
16
+ summary: string | null;
17
+ title: string | null;
18
+ updatedAt: number;
19
+ workspaceId: string;
20
+ }
21
+ export interface ExternalGoalEvent {
22
+ artifacts: string[];
23
+ body: string;
24
+ createdAt: number;
25
+ goalId: string;
26
+ id: string;
27
+ kind: ExternalGoalEventKind;
28
+ sequence: number;
29
+ status: ExternalGoalReportStatus | ExternalGoalStatus | null;
30
+ workspaceId: string;
31
+ }
32
+ interface CreateExternalGoalInput {
33
+ context?: unknown;
34
+ goal: string;
35
+ source: string;
36
+ workspaceId: string;
37
+ }
38
+ interface AppendExternalGoalEventInput {
39
+ artifacts?: string[];
40
+ body: string;
41
+ goalId: string;
42
+ kind: ExternalGoalEventKind;
43
+ sessionStatus?: ExternalGoalStatus;
44
+ status?: ExternalGoalEvent['status'];
45
+ }
46
+ export declare const isExternalGoalReportStatus: (value: unknown) => value is ExternalGoalReportStatus;
47
+ export declare const createExternalGoalStore: (db: Database) => {
48
+ appendEvent: (input: AppendExternalGoalEventInput) => ExternalGoalEvent;
49
+ createSession: (input: CreateExternalGoalInput) => {
50
+ event: ExternalGoalEvent;
51
+ session: ExternalGoalSession;
52
+ };
53
+ deleteWorkspaceGoals: (workspaceId: string) => void;
54
+ getLatestSequence: (goalId: string) => number;
55
+ getSession: (goalId: string) => ExternalGoalSession | undefined;
56
+ listEventsAfter: (goalId: string, cursor?: number) => ExternalGoalEvent[];
57
+ };
58
+ export type ExternalGoalStore = ReturnType<typeof createExternalGoalStore>;
59
+ export {};
@@ -0,0 +1,165 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ export const EXTERNAL_GOAL_STATUSES = [
3
+ 'open',
4
+ 'in_progress',
5
+ 'blocked',
6
+ 'done',
7
+ 'failed',
8
+ 'cancelled',
9
+ ];
10
+ export const EXTERNAL_GOAL_REPORT_STATUSES = ['progress', 'done', 'blocked', 'failed'];
11
+ export const EXTERNAL_GOAL_EVENT_KINDS = [
12
+ 'goal_started',
13
+ 'goal_continued',
14
+ 'goal_delivered',
15
+ 'progress_reported',
16
+ 'goal_done',
17
+ 'goal_blocked',
18
+ 'goal_failed',
19
+ 'goal_cancelled',
20
+ 'delivery_failed',
21
+ ];
22
+ const CLOSED_STATUSES = new Set(['blocked', 'done', 'failed', 'cancelled']);
23
+ export const isExternalGoalReportStatus = (value) => typeof value === 'string' && EXTERNAL_GOAL_REPORT_STATUSES.includes(value);
24
+ const toSessionRecord = (row) => ({
25
+ closedAt: row.closed_at,
26
+ context: JSON.parse(row.context_json),
27
+ createdAt: row.created_at,
28
+ goal: row.goal,
29
+ id: row.id,
30
+ source: row.source,
31
+ status: row.status,
32
+ summary: row.summary,
33
+ title: row.title,
34
+ updatedAt: row.updated_at,
35
+ workspaceId: row.workspace_id,
36
+ });
37
+ const toEventRecord = (row) => ({
38
+ artifacts: JSON.parse(row.artifacts_json),
39
+ body: row.body,
40
+ createdAt: row.created_at,
41
+ goalId: row.goal_id,
42
+ id: row.id,
43
+ kind: row.kind,
44
+ sequence: row.sequence,
45
+ status: row.status,
46
+ workspaceId: row.workspace_id,
47
+ });
48
+ const deriveTitle = (goal) => {
49
+ const firstLine = goal
50
+ .split(/\r?\n/u)
51
+ .map((line) => line.trim())
52
+ .find(Boolean);
53
+ return (firstLine ?? 'External goal').slice(0, 120);
54
+ };
55
+ export const createExternalGoalStore = (db) => {
56
+ const getSession = (goalId) => {
57
+ const row = db.prepare('SELECT * FROM external_goal_sessions WHERE id = ?').get(goalId);
58
+ return row ? toSessionRecord(row) : undefined;
59
+ };
60
+ const listEventsAfter = (goalId, cursor = 0) => db
61
+ .prepare(`SELECT *
62
+ FROM external_goal_events
63
+ WHERE goal_id = ? AND sequence > ?
64
+ ORDER BY sequence ASC`)
65
+ .all(goalId, cursor).map(toEventRecord);
66
+ const getLatestSequence = (goalId) => {
67
+ const row = db
68
+ .prepare('SELECT COALESCE(MAX(sequence), 0) AS sequence FROM external_goal_events WHERE goal_id = ?')
69
+ .get(goalId);
70
+ return row?.sequence ?? 0;
71
+ };
72
+ const insertEvent = (session, input) => {
73
+ const sequence = getLatestSequence(input.goalId) + 1;
74
+ const now = Date.now();
75
+ const event = {
76
+ artifacts: input.artifacts ?? [],
77
+ body: input.body,
78
+ createdAt: now,
79
+ goalId: session.id,
80
+ id: randomUUID(),
81
+ kind: input.kind,
82
+ sequence,
83
+ status: input.status ?? null,
84
+ workspaceId: session.workspaceId,
85
+ };
86
+ db.prepare(`INSERT INTO external_goal_events (
87
+ id,
88
+ goal_id,
89
+ workspace_id,
90
+ sequence,
91
+ kind,
92
+ status,
93
+ body,
94
+ artifacts_json,
95
+ created_at
96
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(event.id, event.goalId, event.workspaceId, event.sequence, event.kind, event.status, event.body, JSON.stringify(event.artifacts), event.createdAt);
97
+ if (input.sessionStatus) {
98
+ db.prepare(`UPDATE external_goal_sessions
99
+ SET status = ?, updated_at = ?, closed_at = ?
100
+ WHERE id = ?`).run(input.sessionStatus, now, CLOSED_STATUSES.has(input.sessionStatus) ? now : null, session.id);
101
+ }
102
+ else {
103
+ db.prepare('UPDATE external_goal_sessions SET updated_at = ? WHERE id = ?').run(now, session.id);
104
+ }
105
+ return event;
106
+ };
107
+ const appendEvent = (input) => {
108
+ return db.transaction(() => {
109
+ const session = getSession(input.goalId);
110
+ if (!session)
111
+ throw new Error(`External goal not found: ${input.goalId}`);
112
+ return insertEvent(session, input);
113
+ })();
114
+ };
115
+ const createSession = (input) => {
116
+ return db.transaction(() => {
117
+ const now = Date.now();
118
+ const session = {
119
+ closedAt: null,
120
+ context: input.context ?? null,
121
+ createdAt: now,
122
+ goal: input.goal,
123
+ id: `goal_${randomUUID()}`,
124
+ source: input.source,
125
+ status: 'open',
126
+ summary: null,
127
+ title: deriveTitle(input.goal),
128
+ updatedAt: now,
129
+ workspaceId: input.workspaceId,
130
+ };
131
+ db.prepare(`INSERT INTO external_goal_sessions (
132
+ id,
133
+ workspace_id,
134
+ source,
135
+ status,
136
+ goal,
137
+ context_json,
138
+ title,
139
+ summary,
140
+ created_at,
141
+ updated_at,
142
+ closed_at
143
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(session.id, session.workspaceId, session.source, session.status, session.goal, JSON.stringify(session.context), session.title, session.summary, session.createdAt, session.updatedAt, session.closedAt);
144
+ const event = insertEvent(session, {
145
+ body: input.goal,
146
+ goalId: session.id,
147
+ kind: 'goal_started',
148
+ status: 'open',
149
+ });
150
+ return { event, session };
151
+ })();
152
+ };
153
+ const deleteWorkspaceGoals = (workspaceId) => {
154
+ db.prepare('DELETE FROM external_goal_events WHERE workspace_id = ?').run(workspaceId);
155
+ db.prepare('DELETE FROM external_goal_sessions WHERE workspace_id = ?').run(workspaceId);
156
+ };
157
+ return {
158
+ appendEvent,
159
+ createSession,
160
+ deleteWorkspaceGoals,
161
+ getLatestSequence,
162
+ getSession,
163
+ listEventsAfter,
164
+ };
165
+ };
@@ -313,6 +313,7 @@ export const buildProtocolGuide = (topic, cliPolicy = DEFAULT_WORKFLOW_CLI_POLIC
313
313
  '- `team list` — show current members/status and open dispatches.',
314
314
  '- `team send "<member-name>" "<task>"` — dispatch by member name, never id.',
315
315
  '- `team cancel --dispatch <id> "<reason>"` — cancel an obsolete open dispatch.',
316
+ '- `team goal report --goal <goal-id> --status progress|done|blocked|failed --stdin` — report external Supervisor goal events when an external-goal envelope asks for it.',
316
317
  `- \`team spawn <role> [--name <name>] [--cli <${BUILTIN_COMMAND_PRESET_CLI_LIST}>] [--ephemeral]\` — create a member only when authorized.`,
317
318
  '- `team dismiss <member-name>` — remove a member you are allowed to remove.',
318
319
  '',