@evo-dev/core 0.0.1-alpha.1 → 0.0.1-alpha.2

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.
@@ -1,5 +1,7 @@
1
+ import { readFile } from "node:fs/promises";
1
2
  import { type HookSettings, createDefaultHookSettings, parseHookSettings } from "../hooks/index.ts";
2
3
  import { EvoDevConfigError, describeType } from "./errors.ts";
4
+ import { resolveEvoDevPaths } from "./paths.ts";
3
5
 
4
6
  export interface PluginSettings {
5
7
  enabled: boolean;
@@ -7,6 +9,13 @@ export interface PluginSettings {
7
9
  autoSyncAgents?: boolean;
8
10
  }
9
11
 
12
+ export interface MemorySettings {
13
+ autoAccept: boolean;
14
+ runtimeInjection: boolean;
15
+ staleReview: boolean;
16
+ lexicalIndex: boolean;
17
+ }
18
+
10
19
  export interface EvoDevSettings {
11
20
  version: 1;
12
21
  platform: {
@@ -29,6 +38,7 @@ export interface EvoDevSettings {
29
38
  };
30
39
  hooks: HookSettings;
31
40
  teamRuntime: TeamRuntimeSettings;
41
+ memory: MemorySettings;
32
42
  }
33
43
 
34
44
  export type SettingsInput = Partial<{
@@ -45,6 +55,7 @@ export type SettingsInput = Partial<{
45
55
  doctor: Partial<EvoDevSettings["doctor"]>;
46
56
  hooks: unknown;
47
57
  teamRuntime: Partial<TeamRuntimeSettings>;
58
+ memory: Partial<MemorySettings>;
48
59
  }>;
49
60
 
50
61
  export interface TeamRuntimeSettings {
@@ -52,6 +63,7 @@ export interface TeamRuntimeSettings {
52
63
  defaultModel: string | null;
53
64
  defaultThinkingLevel: string | null;
54
65
  recordTranscript: boolean;
66
+ displayMode: "normal" | "development";
55
67
  }
56
68
 
57
69
  export function createDefaultSettings(os: string = process.platform): EvoDevSettings {
@@ -83,6 +95,7 @@ export function createDefaultSettings(os: string = process.platform): EvoDevSett
83
95
  },
84
96
  hooks: createDefaultHookSettings(),
85
97
  teamRuntime: createDefaultTeamRuntimeSettings(),
98
+ memory: createDefaultMemorySettings(),
86
99
  };
87
100
  }
88
101
 
@@ -92,6 +105,16 @@ export function createDefaultTeamRuntimeSettings(): TeamRuntimeSettings {
92
105
  defaultModel: null,
93
106
  defaultThinkingLevel: null,
94
107
  recordTranscript: false,
108
+ displayMode: "normal",
109
+ };
110
+ }
111
+
112
+ export function createDefaultMemorySettings(): MemorySettings {
113
+ return {
114
+ autoAccept: true,
115
+ runtimeInjection: true,
116
+ staleReview: true,
117
+ lexicalIndex: true,
95
118
  };
96
119
  }
97
120
 
@@ -135,11 +158,25 @@ export function mergeSettings(
135
158
  ...defaults.teamRuntime,
136
159
  ...existing.teamRuntime,
137
160
  },
161
+ memory: {
162
+ ...defaults.memory,
163
+ ...existing.memory,
164
+ },
138
165
  };
139
166
 
140
167
  return parseSettings(merged);
141
168
  }
142
169
 
170
+ export async function readRuntimeInjectionSettings(homeDir?: string): Promise<MemorySettings> {
171
+ const paths = resolveEvoDevPaths(homeDir);
172
+ try {
173
+ return parseSettings(JSON.parse(await readFile(paths.settingsPath, "utf8"))).memory;
174
+ } catch (error) {
175
+ if (isNotFoundError(error)) return createDefaultMemorySettings();
176
+ throw error;
177
+ }
178
+ }
179
+
143
180
  export function parseSettings(value: unknown): EvoDevSettings {
144
181
  const root = expectRecord(value, "settings");
145
182
  const version = root.version;
@@ -184,11 +221,35 @@ export function parseSettings(value: unknown): EvoDevSettings {
184
221
  root.teamRuntime ?? createDefaultTeamRuntimeSettings(),
185
222
  "settings.teamRuntime",
186
223
  ),
224
+ memory: parseMemorySettings(root.memory ?? createDefaultMemorySettings(), "settings.memory"),
187
225
  };
188
226
 
189
227
  return parsed;
190
228
  }
191
229
 
230
+ function parseMemorySettings(value: unknown, path: string): MemorySettings {
231
+ const input = expectRecord(value, path);
232
+ const defaults = createDefaultMemorySettings();
233
+ return {
234
+ autoAccept:
235
+ input.autoAccept === undefined
236
+ ? defaults.autoAccept
237
+ : expectBoolean(input.autoAccept, `${path}.autoAccept`),
238
+ runtimeInjection:
239
+ input.runtimeInjection === undefined
240
+ ? defaults.runtimeInjection
241
+ : expectBoolean(input.runtimeInjection, `${path}.runtimeInjection`),
242
+ staleReview:
243
+ input.staleReview === undefined
244
+ ? defaults.staleReview
245
+ : expectBoolean(input.staleReview, `${path}.staleReview`),
246
+ lexicalIndex:
247
+ input.lexicalIndex === undefined
248
+ ? defaults.lexicalIndex
249
+ : expectBoolean(input.lexicalIndex, `${path}.lexicalIndex`),
250
+ };
251
+ }
252
+
192
253
  function parseTeamRuntimeSettings(value: unknown, path: string): TeamRuntimeSettings {
193
254
  const input = expectRecord(value, path);
194
255
  const defaults = createDefaultTeamRuntimeSettings();
@@ -211,9 +272,20 @@ function parseTeamRuntimeSettings(value: unknown, path: string): TeamRuntimeSett
211
272
  input.recordTranscript === undefined
212
273
  ? defaults.recordTranscript
213
274
  : expectBoolean(input.recordTranscript, `${path}.recordTranscript`),
275
+ displayMode: parseTeamRuntimeDisplayMode(input.displayMode, defaults.displayMode, path),
214
276
  };
215
277
  }
216
278
 
279
+ function parseTeamRuntimeDisplayMode(
280
+ value: unknown,
281
+ fallback: TeamRuntimeSettings["displayMode"],
282
+ path: string,
283
+ ): TeamRuntimeSettings["displayMode"] {
284
+ if (value === undefined) return fallback;
285
+ if (value === "normal" || value === "development") return value;
286
+ throw new EvoDevConfigError(`Invalid ${path}.displayMode; expected normal or development`);
287
+ }
288
+
217
289
  function parsePluginSettings(value: unknown, path: string): PluginSettings {
218
290
  const input = expectRecord(value, path);
219
291
  const parsed: PluginSettings = {
@@ -239,6 +311,12 @@ function expectRecord(value: unknown, path: string): Record<string, unknown> {
239
311
  return value as Record<string, unknown>;
240
312
  }
241
313
 
314
+ function isNotFoundError(error: unknown): boolean {
315
+ return (
316
+ error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "ENOENT"
317
+ );
318
+ }
319
+
242
320
  function expectString(value: unknown, path: string): string {
243
321
  if (typeof value !== "string" || value.length === 0) {
244
322
  throw new EvoDevConfigError(`Invalid ${path}; expected non-empty string`);
@@ -1,5 +1,6 @@
1
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { dirname } from "node:path";
3
+ import { ensureOkfKnowledgeBase } from "../knowledge/index.ts";
3
4
  import { EvoDevConfigError } from "./errors.ts";
4
5
  import { type EvoDevPaths, resolveEvoDevPaths } from "./paths.ts";
5
6
  import { type EvoDevRegistry, createDefaultRegistry, parseRegistry } from "./registry.ts";
@@ -102,6 +103,7 @@ export async function initializeCoreConfig(homeDir?: string): Promise<CoreConfig
102
103
  export async function ensureKnowledgeBaseFiles(paths: EvoDevPaths): Promise<void> {
103
104
  await mkdir(paths.knowledgeDir, { recursive: true });
104
105
  await mkdir(paths.evosCasesDir, { recursive: true });
106
+ await ensureOkfKnowledgeBase(paths.homeDir);
105
107
  await writeTextIfMissing(
106
108
  `${paths.knowledgeDir}/README.md`,
107
109
  [
@@ -6,12 +6,19 @@ import { listEvolutionTriggers, processEvolutionTriggers } from "../evolution/in
6
6
  import { listObservabilityEvents } from "../observability/index.ts";
7
7
  import {
8
8
  type TeamRuntimeAdapter,
9
+ isActiveTeamAgentStatus,
10
+ isIdleTeamAgentStatus,
11
+ isMidTurnTeamAgentStatus,
9
12
  listTeamRuns,
13
+ markTeamMessagesDelivered,
14
+ readPendingTeamMessagesForRole,
10
15
  reconcileTeamRun,
11
16
  resumeTeamRun,
17
+ schedulePendingTeamMessageDelivery,
12
18
  sendTeamMessage,
13
19
  spawnTeamRole,
14
20
  stopTeamRole,
21
+ updateTeamAgentHookState,
15
22
  } from "../team/index.ts";
16
23
 
17
24
  export interface DaemonPaths {
@@ -182,12 +189,42 @@ export async function handleDaemonRequest(
182
189
  const removed = await cleanupDaemonState(input.homeDir, input.token ?? "");
183
190
  return ok({ stopped: true, removed }, warnings);
184
191
  }
185
- if (input.path === "/teams/send" && input.method === "POST") {
192
+ if (
193
+ (input.path === "/team/message/enqueue" || input.path === "/teams/send") &&
194
+ input.method === "POST"
195
+ ) {
186
196
  return dashboardMutation(
187
197
  await sendDashboardTeamMessage(input.homeDir, input.body, input.runtimeAdapter),
188
198
  warnings,
189
199
  );
190
200
  }
201
+ if (input.path === "/team/agent/state" && input.method === "POST") {
202
+ return dashboardMutation(
203
+ await updateDashboardTeamAgentState(input.homeDir, input.body),
204
+ warnings,
205
+ );
206
+ }
207
+ if (input.path === "/team/message/claim" && input.method === "POST") {
208
+ return ok(await claimDashboardTeamMessages(input.homeDir, input.body), warnings);
209
+ }
210
+ if (input.path === "/team/message/delivered" && input.method === "POST") {
211
+ return dashboardMutation(
212
+ await markDashboardTeamMessagesDelivered(input.homeDir, input.body),
213
+ warnings,
214
+ );
215
+ }
216
+ if (
217
+ (input.path === "/team/reconcile" || input.path === "/teams/reconcile") &&
218
+ input.method === "POST"
219
+ ) {
220
+ await schedulePendingTeamMessageDelivery({
221
+ homeDir: input.homeDir,
222
+ runtimeAdapter: input.runtimeAdapter,
223
+ }).catch((error) =>
224
+ warnings.push(`Team delivery scheduling unavailable: ${describeError(error)}`),
225
+ );
226
+ return ok(await collectTeamStatus(input.homeDir, warnings, input.runtimeAdapter), warnings);
227
+ }
191
228
  if (input.path === "/teams/spawn" && input.method === "POST") {
192
229
  return dashboardMutation(
193
230
  await spawnDashboardTeamRole(input.homeDir, input.body, input.runtimeAdapter),
@@ -206,9 +243,6 @@ export async function handleDaemonRequest(
206
243
  warnings,
207
244
  );
208
245
  }
209
- if (input.path === "/teams/reconcile" && input.method === "POST") {
210
- return ok(await collectTeamStatus(input.homeDir, warnings, input.runtimeAdapter), warnings);
211
- }
212
246
  if (input.path === "/evolution/process" && input.method === "POST") {
213
247
  return dashboardMutation(
214
248
  await processDashboardEvolutionTriggers(input.homeDir, input.body),
@@ -541,10 +575,9 @@ async function collectTeamStatus(
541
575
  thinkingLevel: agent.thinkingLevel,
542
576
  status: agent.status,
543
577
  paneId: agent.tmux.paneId,
544
- canReceiveMessages:
545
- agent.status === "running" ||
546
- agent.status === "recovering" ||
547
- agent.status === "recreated",
578
+ canReceiveMessages: isActiveTeamAgentStatus(agent.status),
579
+ isIdle: isIdleTeamAgentStatus(agent.status),
580
+ isMidTurn: isMidTurnTeamAgentStatus(agent.status),
548
581
  nativeSessionRecorded: agent.nativeSession.sessionId !== null,
549
582
  updatedAt: agent.updatedAt,
550
583
  })),
@@ -552,7 +585,8 @@ async function collectTeamStatus(
552
585
  notifications: status.notifications.map((notification) => ({
553
586
  ok: notification.ok,
554
587
  error: notification.error ?? null,
555
- deliveredTo: notification.deliveredTo ?? null,
588
+ delivery: notification.delivery ?? null,
589
+ queuedFor: notification.queuedFor ?? null,
556
590
  })),
557
591
  }) as DaemonTeamStatusSummary;
558
592
  } catch (error) {
@@ -577,6 +611,61 @@ async function sendDashboardTeamMessage(
577
611
  });
578
612
  }
579
613
 
614
+ async function updateDashboardTeamAgentState(homeDir: string, body: unknown): Promise<unknown> {
615
+ const input = expectRequestBody(body);
616
+ const result = await updateTeamAgentHookState({
617
+ homeDir,
618
+ runId: expectBodyString(input, "runId"),
619
+ roleId: expectBodyString(input, "roleId"),
620
+ hookEvent: expectBodyString(input, "hookEvent"),
621
+ });
622
+ return {
623
+ ok: true,
624
+ roleId: result.agent.roleId,
625
+ status: result.agent.status,
626
+ statusPath: result.statusPath,
627
+ };
628
+ }
629
+
630
+ async function claimDashboardTeamMessages(homeDir: string, body: unknown): Promise<unknown> {
631
+ const input = expectRequestBody(body);
632
+ const runId = expectBodyString(input, "runId");
633
+ const roleId = expectBodyString(input, "roleId");
634
+ const messages = await readPendingTeamMessagesForRole({
635
+ homeDir,
636
+ runId,
637
+ roleId,
638
+ limit: optionalBodyNumber(input.limit),
639
+ });
640
+ return {
641
+ runId,
642
+ roleId,
643
+ messages,
644
+ };
645
+ }
646
+
647
+ async function markDashboardTeamMessagesDelivered(
648
+ homeDir: string,
649
+ body: unknown,
650
+ ): Promise<unknown> {
651
+ const input = expectRequestBody(body);
652
+ const messageIdsValue = input.messageIds;
653
+ if (!Array.isArray(messageIdsValue)) throw new Error("Expected body.messageIds array.");
654
+ const messageIds = messageIdsValue.map((value) => {
655
+ if (typeof value !== "string" || value.length === 0) {
656
+ throw new Error("Expected body.messageIds to contain non-empty strings.");
657
+ }
658
+ return value;
659
+ });
660
+ await markTeamMessagesDelivered({
661
+ homeDir,
662
+ runId: expectBodyString(input, "runId"),
663
+ roleId: expectBodyString(input, "roleId"),
664
+ messageIds,
665
+ });
666
+ return { ok: true, delivered: messageIds };
667
+ }
668
+
580
669
  async function spawnDashboardTeamRole(
581
670
  homeDir: string,
582
671
  body: unknown,