@easynet/agent-runtime 1.0.3 → 1.0.4

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 (205) hide show
  1. package/.github/workflows/ci.yml +9 -24
  2. package/.github/workflows/release.yml +14 -35
  3. package/agent-runtime/.github/workflows/ci.yml +69 -0
  4. package/agent-runtime/.github/workflows/release.yml +118 -0
  5. package/agent-runtime/.releaserc.cjs +26 -0
  6. package/agent-runtime/config/agent.deep.yaml +25 -0
  7. package/agent-runtime/config/agent.react.yaml +24 -0
  8. package/agent-runtime/example/basic-usage.ts +49 -0
  9. package/agent-runtime/package-lock.json +7740 -0
  10. package/agent-runtime/package.json +49 -0
  11. package/agent-runtime/pnpm-lock.yaml +3712 -0
  12. package/agent-runtime/scripts/resolve-deps.js +54 -0
  13. package/agent-runtime/src/agents/deep-agent.ts +165 -0
  14. package/agent-runtime/src/agents/react-agent.helpers.ts +227 -0
  15. package/agent-runtime/src/agents/react-agent.ts +584 -0
  16. package/{src → agent-runtime/src/agents}/sub-agent.ts +2 -2
  17. package/agent-runtime/src/cli/args.ts +15 -0
  18. package/agent-runtime/src/cli/event-listener.ts +162 -0
  19. package/agent-runtime/src/cli/interactive.ts +144 -0
  20. package/agent-runtime/src/cli/runtime.ts +31 -0
  21. package/agent-runtime/src/cli/spinner.ts +23 -0
  22. package/agent-runtime/src/cli/terminal-render.ts +322 -0
  23. package/agent-runtime/src/cli/types.ts +33 -0
  24. package/agent-runtime/src/cli.ts +134 -0
  25. package/agent-runtime/src/config/helpers.ts +179 -0
  26. package/agent-runtime/src/config/index.ts +245 -0
  27. package/agent-runtime/src/config/types.ts +62 -0
  28. package/agent-runtime/src/core/context.ts +266 -0
  29. package/agent-runtime/src/index.ts +55 -0
  30. package/agent-runtime/tsconfig.json +18 -0
  31. package/apps/imessagebot/README.md +38 -0
  32. package/apps/imessagebot/config/.agent/cache/easynet/agent-tool-buildin/0.0.45/README.md +33 -0
  33. package/apps/imessagebot/config/.agent/cache/easynet/agent-tool-buildin/0.0.45/package-lock.json +15257 -0
  34. package/apps/imessagebot/config/.agent/cache/easynet/agent-tool-buildin/0.0.45/package.json +55 -0
  35. package/apps/imessagebot/config/agents/deep/agent.yaml +31 -0
  36. package/apps/imessagebot/config/agents/react/agent.yaml +58 -0
  37. package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.43/README.md +33 -0
  38. package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.43/package-lock.json +15457 -0
  39. package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.43/package.json +55 -0
  40. package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.46/README.md +33 -0
  41. package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.46/package-lock.json +15257 -0
  42. package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.46/package.json +62 -0
  43. package/apps/imessagebot/config/agents/shared/memory.yaml +31 -0
  44. package/apps/imessagebot/config/agents/shared/model.yaml +23 -0
  45. package/apps/imessagebot/config/agents/shared/tool.yaml +13 -0
  46. package/apps/imessagebot/config/app.yaml +14 -0
  47. package/apps/imessagebot/package-lock.json +53695 -0
  48. package/apps/imessagebot/package.json +41 -0
  49. package/apps/imessagebot/pnpm-lock.yaml +1589 -0
  50. package/apps/imessagebot/scripts/resolve-deps.js +41 -0
  51. package/apps/imessagebot/scripts/test-llm.mjs +27 -0
  52. package/apps/imessagebot/scripts/validate-tools-config.mjs +174 -0
  53. package/apps/imessagebot/src/config.ts +76 -0
  54. package/apps/imessagebot/src/context.ts +35 -0
  55. package/apps/imessagebot/src/index.ts +17 -0
  56. package/apps/imessagebot/tsconfig.json +18 -0
  57. package/apps/itermbot/.github/workflows/ci.yml +61 -0
  58. package/apps/itermbot/.github/workflows/release.yml +80 -0
  59. package/apps/itermbot/.releaserc.cjs +26 -0
  60. package/apps/itermbot/README.md +82 -0
  61. package/apps/itermbot/config/app.yaml +29 -0
  62. package/apps/itermbot/config/tsconfig.json +18 -0
  63. package/apps/itermbot/macos_disk_usage_agent_plan.md +244 -0
  64. package/apps/itermbot/package-lock.json +53697 -0
  65. package/apps/itermbot/package.json +57 -0
  66. package/apps/itermbot/pnpm-lock.yaml +3966 -0
  67. package/apps/itermbot/scripts/patch-buildin-cache.sh +25 -0
  68. package/apps/itermbot/scripts/resolve-deps.js +41 -0
  69. package/apps/itermbot/scripts/test-llm.mjs +32 -0
  70. package/apps/itermbot/skills/command-explain-and-guard/SKILL.md +39 -0
  71. package/apps/itermbot/skills/command-explain-and-guard/handler.js +86 -0
  72. package/apps/itermbot/skills/disk-usage-investigate/SKILL.md +44 -0
  73. package/apps/itermbot/skills/disk-usage-investigate/handler.js +12 -0
  74. package/apps/itermbot/skills/gpu-ssh-monitor/SKILL.md +64 -0
  75. package/apps/itermbot/skills/repo-triage/SKILL.md +40 -0
  76. package/apps/itermbot/skills/repo-triage/handler.js +56 -0
  77. package/apps/itermbot/skills/test-failure-diagnose/SKILL.md +43 -0
  78. package/apps/itermbot/skills/test-failure-diagnose/handler.js +107 -0
  79. package/apps/itermbot/src/config.ts +95 -0
  80. package/apps/itermbot/src/context.ts +35 -0
  81. package/apps/itermbot/src/index.ts +223 -0
  82. package/apps/itermbot/src/iterm/session-hint.ts +40 -0
  83. package/apps/itermbot/src/iterm/target-routing.ts +419 -0
  84. package/apps/itermbot/src/startup/colors.ts +317 -0
  85. package/apps/itermbot/src/startup/diagnostics.ts +97 -0
  86. package/apps/itermbot/src/startup/ui.ts +141 -0
  87. package/config/agent.deep.yaml +25 -0
  88. package/config/agent.react.yaml +24 -0
  89. package/dist/agents/deep-agent.d.ts +37 -0
  90. package/dist/agents/deep-agent.d.ts.map +1 -0
  91. package/dist/agents/deep-agent.js +115 -0
  92. package/dist/agents/deep-agent.js.map +1 -0
  93. package/dist/agents/react-agent.d.ts +40 -0
  94. package/dist/agents/react-agent.d.ts.map +1 -0
  95. package/dist/agents/react-agent.helpers.d.ts +40 -0
  96. package/dist/agents/react-agent.helpers.d.ts.map +1 -0
  97. package/dist/agents/react-agent.helpers.js +196 -0
  98. package/dist/agents/react-agent.helpers.js.map +1 -0
  99. package/dist/agents/react-agent.js +400 -0
  100. package/dist/agents/react-agent.js.map +1 -0
  101. package/dist/agents/sub-agent.d.ts +34 -0
  102. package/dist/agents/sub-agent.d.ts.map +1 -0
  103. package/dist/agents/sub-agent.js +53 -0
  104. package/dist/agents/sub-agent.js.map +1 -0
  105. package/dist/cli/args.d.ts +8 -0
  106. package/dist/cli/args.d.ts.map +1 -0
  107. package/dist/cli/args.js +9 -0
  108. package/dist/cli/args.js.map +1 -0
  109. package/dist/cli/event-listener.d.ts +3 -0
  110. package/dist/cli/event-listener.d.ts.map +1 -0
  111. package/dist/cli/event-listener.js +131 -0
  112. package/dist/cli/event-listener.js.map +1 -0
  113. package/dist/cli/interactive.d.ts +4 -0
  114. package/dist/cli/interactive.d.ts.map +1 -0
  115. package/dist/cli/interactive.js +118 -0
  116. package/dist/cli/interactive.js.map +1 -0
  117. package/dist/cli/runtime.d.ts +8 -0
  118. package/dist/cli/runtime.d.ts.map +1 -0
  119. package/dist/cli/runtime.js +27 -0
  120. package/dist/cli/runtime.js.map +1 -0
  121. package/dist/cli/spinner.d.ts +2 -0
  122. package/dist/cli/spinner.d.ts.map +1 -0
  123. package/dist/cli/spinner.js +22 -0
  124. package/dist/cli/spinner.js.map +1 -0
  125. package/dist/cli/terminal-render.d.ts +7 -0
  126. package/dist/cli/terminal-render.d.ts.map +1 -0
  127. package/dist/cli/terminal-render.js +282 -0
  128. package/dist/cli/terminal-render.js.map +1 -0
  129. package/dist/cli/types.d.ts +29 -0
  130. package/dist/cli/types.d.ts.map +1 -0
  131. package/dist/cli/types.js +3 -0
  132. package/dist/cli/types.js.map +1 -0
  133. package/dist/cli.d.ts +4 -41
  134. package/dist/cli.d.ts.map +1 -1
  135. package/dist/cli.js +84 -588
  136. package/dist/cli.js.map +1 -1
  137. package/dist/config/helpers.d.ts +6 -0
  138. package/dist/config/helpers.d.ts.map +1 -0
  139. package/dist/config/helpers.js +164 -0
  140. package/dist/config/helpers.js.map +1 -0
  141. package/dist/config/index.d.ts +15 -0
  142. package/dist/config/index.d.ts.map +1 -0
  143. package/dist/config/index.js +160 -0
  144. package/dist/config/index.js.map +1 -0
  145. package/dist/config/types.d.ts +57 -0
  146. package/dist/config/types.d.ts.map +1 -0
  147. package/dist/config/types.js +2 -0
  148. package/dist/config/types.js.map +1 -0
  149. package/dist/context.d.ts +8 -69
  150. package/dist/context.d.ts.map +1 -1
  151. package/dist/context.js +44 -24
  152. package/dist/context.js.map +1 -1
  153. package/dist/core/context.d.ts +66 -0
  154. package/dist/core/context.d.ts.map +1 -0
  155. package/dist/core/context.js +149 -0
  156. package/dist/core/context.js.map +1 -0
  157. package/dist/deep-agent.d.ts +5 -2
  158. package/dist/deep-agent.d.ts.map +1 -1
  159. package/dist/deep-agent.js +44 -11
  160. package/dist/deep-agent.js.map +1 -1
  161. package/dist/index.d.ts +6 -6
  162. package/dist/index.d.ts.map +1 -1
  163. package/dist/index.js +6 -6
  164. package/dist/index.js.map +1 -1
  165. package/dist/middleware/malformed-tool-call-middleware.d.ts +8 -0
  166. package/dist/middleware/malformed-tool-call-middleware.d.ts.map +1 -0
  167. package/dist/middleware/malformed-tool-call-middleware.js +191 -0
  168. package/dist/middleware/malformed-tool-call-middleware.js.map +1 -0
  169. package/dist/react-agent.d.ts +2 -2
  170. package/dist/react-agent.d.ts.map +1 -1
  171. package/dist/react-agent.js +28 -9
  172. package/dist/react-agent.js.map +1 -1
  173. package/package.json +1 -1
  174. package/scripts/resolve-deps.js +54 -0
  175. package/src/agents/deep-agent.ts +165 -0
  176. package/src/agents/react-agent.helpers.ts +227 -0
  177. package/src/agents/react-agent.ts +584 -0
  178. package/src/agents/sub-agent.ts +82 -0
  179. package/src/cli/args.ts +15 -0
  180. package/src/cli/event-listener.ts +162 -0
  181. package/src/cli/interactive.ts +144 -0
  182. package/src/cli/runtime.ts +31 -0
  183. package/src/cli/spinner.ts +23 -0
  184. package/src/cli/terminal-render.ts +322 -0
  185. package/src/cli/types.ts +33 -0
  186. package/src/cli.ts +91 -702
  187. package/src/config/helpers.ts +179 -0
  188. package/src/config/index.ts +245 -0
  189. package/src/config/types.ts +62 -0
  190. package/src/core/context.ts +266 -0
  191. package/src/index.ts +13 -11
  192. package/src/middleware/malformed-tool-call-middleware.ts +239 -0
  193. package/src/types/markdown-it-terminal.d.ts +4 -0
  194. package/src/types/marked-terminal.d.ts +16 -0
  195. package/dist/config.d.ts +0 -86
  196. package/dist/config.d.ts.map +0 -1
  197. package/dist/config.js +0 -84
  198. package/dist/config.js.map +0 -1
  199. package/src/config.ts +0 -177
  200. package/src/context.ts +0 -247
  201. package/src/deep-agent.ts +0 -104
  202. package/src/react-agent.ts +0 -576
  203. /package/{src → agent-runtime/src/middleware}/malformed-tool-call-middleware.ts +0 -0
  204. /package/{src → agent-runtime/src/types}/markdown-it-terminal.d.ts +0 -0
  205. /package/{src → agent-runtime/src/types}/marked-terminal.d.ts +0 -0
@@ -0,0 +1,33 @@
1
+ import type { AgentEventListener } from "@easynet/agent-common";
2
+ import type { BotContext } from "../core/context.js";
3
+
4
+ export const REACT = "react" as const;
5
+ export const DEEP = "deep" as const;
6
+
7
+ export type AgentKind = typeof REACT | typeof DEEP;
8
+
9
+ export interface AppCliUiOptions {
10
+ userLabel?: string;
11
+ assistantLabel?: string;
12
+ useColor?: boolean;
13
+ renderMarkdown?: boolean;
14
+ echoUserQuestion?: boolean;
15
+ loadingText?: string | false;
16
+ loadingSpinner?: boolean;
17
+ readyText?: string | false;
18
+ interactiveIntro?: string | false;
19
+ processingSpinner?: boolean;
20
+ processingText?: string | false;
21
+ }
22
+
23
+ export interface AppCliOptions {
24
+ appName: string;
25
+ createBotContext: () => Promise<BotContext>;
26
+ interactiveCommands?: Record<string, InteractiveCommandHandler>;
27
+ eventListener?: AgentEventListener | AgentEventListener[];
28
+ onReady?: (ctx: BotContext) => void | Promise<void>;
29
+ onShutdown?: (ctx: BotContext) => void;
30
+ ui?: AppCliUiOptions;
31
+ }
32
+
33
+ export type InteractiveCommandHandler = (ctx: BotContext) => void | Promise<void>;
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Shared CLI runner for agent apps.
3
+ * Provides: arg parsing, single-shot mode, interactive REPL, event logging.
4
+ */
5
+ import { createConsoleAgentEventListener } from "@easynet/agent-common";
6
+ import { parseArgs } from "./cli/args.js";
7
+ import { createStructuredRunEventListener } from "./cli/event-listener.js";
8
+ import { interactive } from "./cli/interactive.js";
9
+ import { readDefaultAgentKindFromConfig, runOne } from "./cli/runtime.js";
10
+ import { startLoadingSpinner } from "./cli/spinner.js";
11
+ import { renderForTerminal } from "./cli/terminal-render.js";
12
+ import { REACT, type AgentKind, type AppCliOptions, type AppCliUiOptions } from "./cli/types.js";
13
+
14
+ export type { AppCliOptions, AppCliUiOptions };
15
+ export { createStructuredRunEventListener };
16
+
17
+ function createExitHandler(
18
+ appName: string,
19
+ onShutdown: AppCliOptions["onShutdown"],
20
+ ctx: Awaited<ReturnType<AppCliOptions["createBotContext"]>>,
21
+ ): (code: number) => never {
22
+ let didCleanup = false;
23
+
24
+ const cleanup = () => {
25
+ if (didCleanup) return;
26
+ didCleanup = true;
27
+ try {
28
+ onShutdown?.(ctx);
29
+ } catch (err) {
30
+ console.error(`${appName}: onShutdown hook failed:`, err instanceof Error ? err.message : err);
31
+ }
32
+ };
33
+
34
+ process.once("exit", cleanup);
35
+ const exitApp = (code: number): never => {
36
+ cleanup();
37
+ process.exit(code);
38
+ };
39
+ process.once("SIGINT", () => exitApp(130));
40
+ process.once("SIGTERM", () => exitApp(143));
41
+ return exitApp;
42
+ }
43
+
44
+ function subscribeEventListeners(options: AppCliOptions, ctx: Awaited<ReturnType<AppCliOptions["createBotContext"]>>): void {
45
+ if (process.env.AGENT_EVENT_STDERR === "1") {
46
+ ctx.events.subscribe(createConsoleAgentEventListener());
47
+ }
48
+
49
+ const listeners = Array.isArray(options.eventListener)
50
+ ? options.eventListener
51
+ : options.eventListener
52
+ ? [options.eventListener]
53
+ : [];
54
+ for (const listener of listeners) {
55
+ ctx.events.subscribe(listener);
56
+ }
57
+ }
58
+
59
+ async function runOnReady(options: AppCliOptions, appName: string, ctx: Awaited<ReturnType<AppCliOptions["createBotContext"]>>): Promise<void> {
60
+ if (!options.onReady) return;
61
+ try {
62
+ await options.onReady(ctx);
63
+ } catch (err) {
64
+ console.error(`${appName}: onReady hook failed:`, err instanceof Error ? err.message : err);
65
+ }
66
+ }
67
+
68
+ async function createContextWithLoading(options: AppCliOptions): Promise<Awaited<ReturnType<AppCliOptions["createBotContext"]>>> {
69
+ const loadingText = options.ui?.loadingText ?? `${options.appName}: loading config, LLM, memory, tools...`;
70
+ const useLoadingSpinner = options.ui?.loadingSpinner ?? false;
71
+ let stopLoadingSpinner: (() => void) | null = null;
72
+
73
+ if (loadingText !== false) {
74
+ if (useLoadingSpinner && Boolean(process.stderr.isTTY)) {
75
+ stopLoadingSpinner = startLoadingSpinner(loadingText);
76
+ } else {
77
+ console.error(loadingText);
78
+ }
79
+ }
80
+
81
+ const ctx = await options.createBotContext();
82
+ stopLoadingSpinner?.();
83
+ return ctx;
84
+ }
85
+
86
+ function printReady(options: AppCliOptions, kind: AgentKind): void {
87
+ const agentLabel = kind === REACT ? "ReAct (LangChain)" : "Deep (DeepAgents)";
88
+ const readyText = options.ui?.readyText ?? `Ready. Agent: ${agentLabel}`;
89
+ if (readyText !== false) {
90
+ console.error(readyText);
91
+ }
92
+ }
93
+
94
+ function resolveAgentKind(parsed: ReturnType<typeof parseArgs>, ctx: Awaited<ReturnType<AppCliOptions["createBotContext"]>>): AgentKind {
95
+ if (parsed.explicitKind) return parsed.kind;
96
+ return readDefaultAgentKindFromConfig(ctx) ?? parsed.kind;
97
+ }
98
+
99
+ async function runSingleQuery(
100
+ options: AppCliOptions,
101
+ ctx: Awaited<ReturnType<AppCliOptions["createBotContext"]>>,
102
+ kind: AgentKind,
103
+ query: string,
104
+ ): Promise<void> {
105
+ const text = await runOne(ctx, kind, query);
106
+ const useColor = options.ui?.useColor ?? (Boolean(process.stdout.isTTY) && !process.env.NO_COLOR);
107
+ const renderMarkdown = options.ui?.renderMarkdown ?? true;
108
+ console.log(renderForTerminal(text, { renderMarkdown, useColor }));
109
+ }
110
+
111
+ export function runAppCli(options: AppCliOptions): void {
112
+ const main = async () => {
113
+ const parsed = parseArgs();
114
+ const ctx = await createContextWithLoading(options);
115
+ const kind = resolveAgentKind(parsed, ctx);
116
+ const exitApp = createExitHandler(options.appName, options.onShutdown, ctx);
117
+
118
+ subscribeEventListeners(options, ctx);
119
+ await runOnReady(options, options.appName, ctx);
120
+ printReady(options, kind);
121
+
122
+ if (parsed.query) {
123
+ await runSingleQuery(options, ctx, kind, parsed.query);
124
+ exitApp(0);
125
+ }
126
+
127
+ await interactive(ctx, kind, options, exitApp);
128
+ };
129
+
130
+ main().catch((err) => {
131
+ console.error(err);
132
+ process.exit(1);
133
+ });
134
+ }
@@ -0,0 +1,179 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { deepMerge, hydrateNamedScopedDefaults, loadModuleConfig } from "@easynet/agent-common";
5
+ import { normalizeSkillConfig, resolveSkillRuntimeDefaults as resolveSkillRuntimeDefaultsFromModule } from "@easynet/agent-skill";
6
+ import { normalizeAgentToolConfigRef } from "@easynet/agent-tool";
7
+ import type { AgentProfileConfig, AgentMemoryProfileRef, AgentMemoryRuntimeRef } from "./types.js";
8
+
9
+ const RUNTIME_CONFIG_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "../../config");
10
+
11
+ let defaultProfilesPromise: Promise<Record<string, AgentProfileConfig>> | null = null;
12
+
13
+ function asMemoryObject(input: unknown): Record<string, unknown> | undefined {
14
+ return typeof input === "object" && input !== null ? (input as Record<string, unknown>) : undefined;
15
+ }
16
+
17
+ function normalizeAgentMemoryRuntimeRef(raw: unknown): Partial<AgentMemoryRuntimeRef> {
18
+ const obj = asMemoryObject(raw);
19
+ if (!obj) return {};
20
+ const out: Partial<AgentMemoryRuntimeRef> = {};
21
+ if (obj.namespace_mode === "thread" || obj.namespace_mode === "fixed") out.namespace_mode = obj.namespace_mode;
22
+ if (typeof obj.namespace === "string") out.namespace = obj.namespace;
23
+ if (typeof obj.top_k === "number") out.top_k = obj.top_k;
24
+ if (typeof obj.budget_tokens === "number") out.budget_tokens = obj.budget_tokens;
25
+ if (typeof obj.auto_write === "boolean") out.auto_write = obj.auto_write;
26
+ if (obj.auto_write_scope === "thread" || obj.auto_write_scope === "cross_thread" || obj.auto_write_scope === "knowledge" || obj.auto_write_scope === "auto") out.auto_write_scope = obj.auto_write_scope;
27
+ if (typeof obj.auto_write_max_chars === "number") out.auto_write_max_chars = obj.auto_write_max_chars;
28
+ if (typeof obj.auto_write_user === "boolean") out.auto_write_user = obj.auto_write_user;
29
+ if (typeof obj.auto_write_assistant === "boolean") out.auto_write_assistant = obj.auto_write_assistant;
30
+ return out;
31
+ }
32
+
33
+ function normalizeAgentMemoryProfileRef(raw: unknown): Partial<AgentMemoryProfileRef> {
34
+ if (typeof raw === "string" && raw.trim().length > 0) return { ref: raw.trim() };
35
+ const obj = asMemoryObject(raw);
36
+ if (!obj) return {};
37
+ const runtime = normalizeAgentMemoryRuntimeRef(obj);
38
+ const out: Partial<AgentMemoryProfileRef> = { ...runtime };
39
+ if (typeof obj.ref === "string" && obj.ref.trim().length > 0) out.ref = obj.ref.trim();
40
+ return out;
41
+ }
42
+
43
+ async function resolveAgentMemoryRuntimeDefaultsFromModule(
44
+ memoryRef: string,
45
+ appRoot: string,
46
+ ): Promise<Partial<AgentMemoryRuntimeRef>> {
47
+ const common = await import("@easynet/agent-common");
48
+ const loadModuleConfigFn = (common as {
49
+ loadModuleConfig?: <TConfig>(options: { path: string; appRoot: string; unwrapSpec: boolean; fallback: TConfig }) => Promise<TConfig>;
50
+ }).loadModuleConfig;
51
+ const raw: Record<string, unknown> = typeof loadModuleConfigFn === "function"
52
+ ? await loadModuleConfigFn<Record<string, unknown>>({ path: memoryRef, appRoot, unwrapSpec: true, fallback: {} }) as Record<string, unknown>
53
+ : {};
54
+ const rootRuntime = normalizeAgentMemoryRuntimeRef(asMemoryObject(raw.runtime));
55
+ const memoryRuntime = normalizeAgentMemoryRuntimeRef(asMemoryObject(asMemoryObject(raw.memory)?.runtime));
56
+ return { ...rootRuntime, ...memoryRuntime };
57
+ }
58
+
59
+ function normalizeAgentModelConfigRef(raw: unknown): { ref?: string } {
60
+ if (typeof raw === "string" && raw.trim().length > 0) return { ref: raw.trim() };
61
+ if (typeof raw !== "object" || raw === null) return {};
62
+ const ref = (raw as { ref?: unknown }).ref;
63
+ if (typeof ref === "string" && ref.trim().length > 0) return { ref: ref.trim() };
64
+ return {};
65
+ }
66
+
67
+ async function discoverDefaultAgentProfileFiles(): Promise<Record<string, string>> {
68
+ try {
69
+ const entries = await readdir(RUNTIME_CONFIG_DIR, { withFileTypes: true });
70
+ return Object.fromEntries(
71
+ entries
72
+ .filter((entry) => entry.isFile())
73
+ .map((entry) => {
74
+ const match = /^agent\.(.+)\.ya?ml$/i.exec(entry.name);
75
+ if (!match) return null;
76
+ return [match[1], resolve(RUNTIME_CONFIG_DIR, entry.name)] as const;
77
+ })
78
+ .filter((item): item is readonly [string, string] => item !== null),
79
+ );
80
+ } catch {
81
+ return {};
82
+ }
83
+ }
84
+
85
+ export function normalizeProfileConfig(
86
+ raw: Record<string, unknown> | undefined,
87
+ base?: AgentProfileConfig
88
+ ): AgentProfileConfig {
89
+ const merged: AgentProfileConfig = deepMerge({} as AgentProfileConfig, base ?? {});
90
+ if (!raw) return merged;
91
+
92
+ const models = normalizeAgentModelConfigRef(raw.models);
93
+ if (Object.keys(models).length > 0) merged.models = { ...(merged.models ?? {}), ...models };
94
+
95
+ const memory = normalizeAgentMemoryProfileRef(raw.memory);
96
+ if (Object.keys(memory).length > 0) merged.memory = { ...(merged.memory ?? {}), ...memory };
97
+
98
+ const tools = normalizeAgentToolConfigRef(raw.tools);
99
+ if (Object.keys(tools).length > 0) merged.tools = { ...(merged.tools ?? {}), ...tools };
100
+
101
+ const skills = normalizeSkillConfig(raw.skills);
102
+ if (Object.keys(skills).length > 0) merged.skills = { ...(merged.skills ?? {}), ...skills };
103
+
104
+ if (typeof raw.systemPrompt === "string") merged.systemPrompt = raw.systemPrompt;
105
+ if (typeof raw.maxSteps === "number") merged.maxSteps = raw.maxSteps;
106
+ if (typeof raw.printSteps === "boolean") merged.printSteps = raw.printSteps;
107
+ if (typeof raw.fallbackText === "string") merged.fallbackText = raw.fallbackText;
108
+ if (typeof raw.commandWindowLabel === "string") merged.commandWindowLabel = raw.commandWindowLabel;
109
+ if (typeof raw.recursionLimit === "number") merged.recursionLimit = raw.recursionLimit;
110
+ if (typeof raw.memoriesPath === "string") merged.memoriesPath = raw.memoriesPath;
111
+
112
+ return merged;
113
+ }
114
+
115
+ export async function hydrateAgentMemoryRuntimeDefaults(
116
+ agents: Record<string, AgentProfileConfig>,
117
+ appRoot: string
118
+ ): Promise<Record<string, AgentProfileConfig>> {
119
+ return hydrateNamedScopedDefaults<
120
+ AgentProfileConfig,
121
+ NonNullable<AgentProfileConfig["memory"]>,
122
+ Partial<NonNullable<AgentProfileConfig["memory"]>>
123
+ >(agents, {
124
+ appRoot,
125
+ getScope: (agent) => agent.memory as NonNullable<AgentProfileConfig["memory"]> | undefined,
126
+ setScope: (agent, memory) => ({ ...agent, memory }),
127
+ resolveDefaults: resolveAgentMemoryRuntimeDefaultsFromModule as (ref: string, appRoot: string) => Promise<Partial<NonNullable<AgentProfileConfig["memory"]>>>,
128
+ });
129
+ }
130
+
131
+ export async function hydrateAgentSkillsRuntimeDefaults(
132
+ agents: Record<string, AgentProfileConfig>,
133
+ appRoot: string
134
+ ): Promise<Record<string, AgentProfileConfig>> {
135
+ return hydrateNamedScopedDefaults<
136
+ AgentProfileConfig,
137
+ NonNullable<AgentProfileConfig["skills"]>,
138
+ Partial<NonNullable<AgentProfileConfig["skills"]>>
139
+ >(agents, {
140
+ appRoot,
141
+ getScope: (agent) => agent.skills as NonNullable<AgentProfileConfig["skills"]> | undefined,
142
+ setScope: (agent, skills) => ({ ...agent, skills }),
143
+ resolveDefaults: resolveSkillRuntimeDefaultsFromModule,
144
+ });
145
+ }
146
+
147
+ async function loadDefaultAgentProfiles(): Promise<Record<string, AgentProfileConfig>> {
148
+ if (defaultProfilesPromise) return defaultProfilesPromise;
149
+ defaultProfilesPromise = (async () => {
150
+ const defaultProfileFiles = await discoverDefaultAgentProfileFiles();
151
+ const entries = await Promise.all(
152
+ Object.entries(defaultProfileFiles).map(async ([name, filePath]) => {
153
+ try {
154
+ const raw = await loadModuleConfig<Record<string, unknown>>({
155
+ path: filePath,
156
+ unwrapSpec: true,
157
+ fallback: {},
158
+ });
159
+ return [name, normalizeProfileConfig(raw)] as const;
160
+ } catch {
161
+ return [name, {} as AgentProfileConfig] as const;
162
+ }
163
+ })
164
+ );
165
+ return Object.fromEntries(entries);
166
+ })();
167
+ return defaultProfilesPromise;
168
+ }
169
+
170
+ export async function applyDefaultAgentProfiles(
171
+ agents: Record<string, AgentProfileConfig>
172
+ ): Promise<Record<string, AgentProfileConfig>> {
173
+ const defaultsMap = await loadDefaultAgentProfiles();
174
+ const out: Record<string, AgentProfileConfig> = { ...agents };
175
+ for (const [name, defaults] of Object.entries(defaultsMap)) {
176
+ out[name] = deepMerge({} as AgentProfileConfig, defaults, out[name] ?? {});
177
+ }
178
+ return out;
179
+ }
@@ -0,0 +1,245 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, resolve as resolvePath } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { asObject, deepMerge, resolveConfigPath, resolveKindResourceFile } from "@easynet/agent-common";
5
+ import { createSkillConfig, resolveSkillRuntimeDefaults } from "@easynet/agent-skill";
6
+ import type {
7
+ ConfigApi,
8
+ AgentProfileConfig,
9
+ AgentRuntimeConfig,
10
+ AgentConfigDefaults,
11
+ SkillConfig,
12
+ } from "./types.js";
13
+ import {
14
+ applyDefaultAgentProfiles,
15
+ hydrateAgentMemoryRuntimeDefaults,
16
+ hydrateAgentSkillsRuntimeDefaults,
17
+ normalizeProfileConfig,
18
+ } from "./helpers.js";
19
+
20
+ export type { AgentProfileConfig, AgentRuntimeConfig, AgentConfigDefaults, SkillConfig } from "./types.js";
21
+
22
+ export interface CreateRuntimeConfigOptions {
23
+ configPath?: string;
24
+ overrides?: Partial<AgentRuntimeConfig>;
25
+ }
26
+
27
+ const APP_ROOT = process.cwd();
28
+ const RUNTIME_DIR = dirname(fileURLToPath(import.meta.url));
29
+ const RUNTIME_PACKAGE_ROOT = resolvePath(RUNTIME_DIR, "../../");
30
+
31
+ type ModuleDefaultConfigPaths = {
32
+ modelsPath?: string;
33
+ memoryPath?: string;
34
+ toolPath?: string;
35
+ skillPath?: string;
36
+ };
37
+
38
+ function resolveModuleConfigPath(pkgName: string, configRelativePath: string): string | undefined {
39
+ const candidates = [
40
+ resolvePath(RUNTIME_PACKAGE_ROOT, "node_modules", pkgName, configRelativePath),
41
+ resolvePath(APP_ROOT, "node_modules", pkgName, configRelativePath),
42
+ ];
43
+ return candidates.find((candidate) => existsSync(candidate));
44
+ }
45
+
46
+ const MODULE_DEFAULTS: ModuleDefaultConfigPaths = {
47
+ modelsPath: resolveModuleConfigPath("@easynet/agent-model", "config/model.yaml"),
48
+ memoryPath: resolveModuleConfigPath("@easynet/agent-memory", "config/memory.yaml"),
49
+ toolPath: resolveModuleConfigPath("@easynet/agent-tool", "config/tool.yaml"),
50
+ skillPath: resolveModuleConfigPath("@easynet/agent-skill", "config/skill.yaml"),
51
+ };
52
+
53
+ type SkillsDefaults = Pick<
54
+ NonNullable<AgentProfileConfig["skills"]>,
55
+ "mode" | "inject_metadata" | "embedding_threshold" | "keyword_threshold"
56
+ >;
57
+
58
+ type AppSpec = {
59
+ agents?: {
60
+ list?: Record<string, unknown>;
61
+ };
62
+ };
63
+
64
+ function toAgentConfigMap(config: AgentRuntimeConfig | undefined): Record<string, AgentProfileConfig> {
65
+ return config?.app?.agent ?? {};
66
+ }
67
+
68
+ function getFirstAgentName(config: AgentRuntimeConfig | undefined): string | undefined {
69
+ return Object.keys(toAgentConfigMap(config))[0];
70
+ }
71
+
72
+ function pickAgentConfig(
73
+ config: AgentRuntimeConfig | undefined,
74
+ agentName?: string
75
+ ): AgentProfileConfig | undefined {
76
+ const map = toAgentConfigMap(config);
77
+ if (agentName && map[agentName]) return map[agentName];
78
+ const firstName = getFirstAgentName(config);
79
+ return firstName ? map[firstName] : undefined;
80
+ }
81
+
82
+ function requirePath(pathValue: string | undefined, label: string): string {
83
+ if (!pathValue || pathValue.trim().length === 0) {
84
+ throw new Error(`Missing ${label} config path. Set it in agent profile or provide defaults.`);
85
+ }
86
+ return pathValue;
87
+ }
88
+
89
+ function toRuntimeApp(agents: Record<string, AgentProfileConfig>): AgentRuntimeConfig["app"] {
90
+ return {
91
+ agent: agents,
92
+ } as AgentRuntimeConfig["app"];
93
+ }
94
+
95
+ export async function loadAgentConfig(configPath?: string): Promise<AgentRuntimeConfig> {
96
+ const appResource = await resolveKindResourceFile<AppSpec>(configPath ?? "config/app.yaml", {
97
+ baseDir: APP_ROOT,
98
+ expectedApiVersion: "easynet.world/v1",
99
+ expectedKind: "AppConfig",
100
+ });
101
+ const spec = asObject(appResource.spec) as AppSpec | undefined;
102
+ const list = spec?.agents?.list ?? {};
103
+
104
+ const mergedAgents = Object.fromEntries(
105
+ Object.entries(list)
106
+ .map(([name, value]) => {
107
+ const entry = asObject(value);
108
+ if (!entry) return null;
109
+ const profileRaw = asObject((entry as { spec?: unknown }).spec) ?? entry;
110
+ const normalized = normalizeProfileConfig(profileRaw);
111
+ return [name, { ...normalized, profile: name } as AgentProfileConfig] as const;
112
+ })
113
+ .filter((item): item is readonly [string, AgentProfileConfig] => item !== null)
114
+ );
115
+
116
+ const withDefaults = await applyDefaultAgentProfiles(mergedAgents);
117
+ const memoryHydrated = await hydrateAgentMemoryRuntimeDefaults(withDefaults, APP_ROOT);
118
+ const skillsHydrated = await hydrateAgentSkillsRuntimeDefaults(memoryHydrated, APP_ROOT);
119
+
120
+ return {
121
+ app: toRuntimeApp(skillsHydrated),
122
+ };
123
+ }
124
+
125
+ export async function createRuntimeConfig(options: CreateRuntimeConfigOptions = {}): Promise<AgentRuntimeConfig> {
126
+ const base = await loadAgentConfig(options.configPath);
127
+ if (!options.overrides) return base;
128
+ return deepMerge({} as AgentRuntimeConfig, base, options.overrides);
129
+ }
130
+
131
+ export function getModelsConfigPath(
132
+ config?: AgentRuntimeConfig,
133
+ agentName?: string,
134
+ defaults?: AgentConfigDefaults
135
+ ): string {
136
+ const agent = pickAgentConfig(config, agentName);
137
+ const p = requirePath(agent?.models?.ref ?? defaults?.modelsPath ?? MODULE_DEFAULTS.modelsPath, "models");
138
+ return resolveConfigPath(p, APP_ROOT);
139
+ }
140
+
141
+ export function getMemoryConfigPath(
142
+ config?: AgentRuntimeConfig,
143
+ agentName?: string,
144
+ defaults?: AgentConfigDefaults
145
+ ): string {
146
+ const p = requirePath(
147
+ pickAgentConfig(config, agentName)?.memory?.ref ?? defaults?.memoryPath ?? MODULE_DEFAULTS.memoryPath,
148
+ "memory",
149
+ );
150
+ return resolveConfigPath(p, APP_ROOT);
151
+ }
152
+
153
+ export function getToolConfigPath(
154
+ config: AgentRuntimeConfig | undefined,
155
+ agentName: string | undefined,
156
+ defaults?: AgentConfigDefaults
157
+ ): string {
158
+ const name = agentName ?? getFirstAgentName(config);
159
+ const envFromProcess = (process.env.APP_ENV ?? process.env.NODE_ENV ?? "").trim().toLowerCase();
160
+ const env = envFromProcess || "development";
161
+ const isProd = env === "production" || env === "prod";
162
+ const tools = pickAgentConfig(config, name)?.tools;
163
+
164
+ const p = isProd
165
+ ? requirePath(
166
+ tools?.production ?? tools?.ref ?? defaults?.toolProdPath ?? defaults?.toolPath ?? MODULE_DEFAULTS.toolPath,
167
+ "tool(production)",
168
+ )
169
+ : requirePath(
170
+ tools?.development ?? tools?.ref ?? defaults?.toolDevPath ?? defaults?.toolPath ?? MODULE_DEFAULTS.toolPath,
171
+ "tool(development)",
172
+ );
173
+ return resolveConfigPath(p, APP_ROOT);
174
+ }
175
+
176
+ export function resolveDefaultAgentName(
177
+ config: AgentRuntimeConfig | undefined,
178
+ preferred?: string
179
+ ): string {
180
+ const agents = toAgentConfigMap(config);
181
+ const names = Object.keys(agents);
182
+ if (preferred && agents[preferred]) return preferred;
183
+ const configured = (config?.app as { defaultAgent?: unknown } | undefined)?.defaultAgent;
184
+ if (typeof configured === "string" && agents[configured]) return configured;
185
+ return names[0] ?? preferred ?? "";
186
+ }
187
+
188
+ export function createConfigApi(defaults?: AgentConfigDefaults): ConfigApi<AgentRuntimeConfig> {
189
+ return {
190
+ loadAgentConfig,
191
+ getModelsConfigPath: (config, agentName) => getModelsConfigPath(config, agentName, defaults),
192
+ getMemoryConfigPath: (config, agentName) => getMemoryConfigPath(config, agentName, defaults),
193
+ getToolConfigPath: (config, agentName) => getToolConfigPath(config, agentName, defaults),
194
+ };
195
+ }
196
+
197
+ export async function getSkillsConfig(
198
+ config: AgentRuntimeConfig | undefined,
199
+ agentName?: string,
200
+ ): Promise<SkillConfig | undefined> {
201
+ const raw = pickAgentConfig(config, agentName)?.skills;
202
+ const defaultSkillRef = MODULE_DEFAULTS.skillPath;
203
+ const appLocalSkillsDir = resolvePath(APP_ROOT, "skills");
204
+ const defaultsFromYaml = defaultSkillRef
205
+ ? await createSkillConfig({ configPath: resolveConfigPath(defaultSkillRef, APP_ROOT) })
206
+ : null;
207
+ const defaults: Partial<SkillsDefaults> = defaultsFromYaml
208
+ ? defaultsFromYaml
209
+ : (defaultSkillRef ? await resolveSkillRuntimeDefaults(defaultSkillRef, APP_ROOT).catch(() => ({})) : {});
210
+
211
+ if (!raw?.ref && existsSync(appLocalSkillsDir)) {
212
+ return {
213
+ path: appLocalSkillsDir,
214
+ mode: raw?.mode ?? defaults.mode,
215
+ inject_metadata: raw?.inject_metadata ?? defaults.inject_metadata,
216
+ embedding_threshold: raw?.embedding_threshold ?? defaults.embedding_threshold,
217
+ keyword_threshold: raw?.keyword_threshold ?? defaults.keyword_threshold,
218
+ };
219
+ }
220
+
221
+ const skillRef = raw?.ref ?? defaultSkillRef;
222
+ if (!skillRef) return undefined;
223
+
224
+ const resolvedRef = resolveConfigPath(skillRef, APP_ROOT);
225
+ const fromConfig = /\.ya?ml$/i.test(resolvedRef)
226
+ ? await createSkillConfig({
227
+ configPath: resolvedRef,
228
+ overrides: {
229
+ mode: raw?.mode,
230
+ inject_metadata: raw?.inject_metadata,
231
+ embedding_threshold: raw?.embedding_threshold,
232
+ keyword_threshold: raw?.keyword_threshold,
233
+ },
234
+ })
235
+ : null;
236
+ if (fromConfig) return fromConfig;
237
+
238
+ return {
239
+ path: resolvedRef,
240
+ mode: raw?.mode ?? defaults.mode,
241
+ inject_metadata: raw?.inject_metadata ?? defaults.inject_metadata,
242
+ embedding_threshold: raw?.embedding_threshold ?? defaults.embedding_threshold,
243
+ keyword_threshold: raw?.keyword_threshold ?? defaults.keyword_threshold,
244
+ };
245
+ }
@@ -0,0 +1,62 @@
1
+ import type { AgentToolConfigRef } from "@easynet/agent-tool";
2
+ import type { SkillConfig, SkillConfigRef } from "@easynet/agent-skill";
3
+
4
+ export type { SkillConfig, SkillConfigRef } from "@easynet/agent-skill";
5
+ export type { AgentToolConfigRef } from "@easynet/agent-tool";
6
+
7
+ export interface AgentMemoryRuntimeRef {
8
+ namespace_mode?: "thread" | "fixed";
9
+ namespace?: string;
10
+ top_k?: number;
11
+ budget_tokens?: number;
12
+ auto_write?: boolean;
13
+ auto_write_scope?: "thread" | "cross_thread" | "knowledge" | "auto";
14
+ auto_write_max_chars?: number;
15
+ auto_write_user?: boolean;
16
+ auto_write_assistant?: boolean;
17
+ }
18
+
19
+ export type AgentMemoryProfileRef = { ref?: string } & AgentMemoryRuntimeRef;
20
+
21
+ export interface AgentModelConfigRef {
22
+ ref?: string;
23
+ }
24
+
25
+ export type AgentProfileConfig = {
26
+ profile?: string;
27
+ models?: AgentModelConfigRef;
28
+ memory?: AgentMemoryProfileRef;
29
+ tools?: AgentToolConfigRef;
30
+ skills?: SkillConfigRef;
31
+ systemPrompt?: string;
32
+ maxSteps?: number;
33
+ printSteps?: boolean;
34
+ fallbackText?: string;
35
+ commandWindowLabel?: string;
36
+ recursionLimit?: number;
37
+ memoriesPath?: string;
38
+ };
39
+
40
+ export interface BaseAgentConfig {
41
+ app?: {
42
+ agent?: Record<string, AgentProfileConfig>;
43
+ [key: string]: unknown;
44
+ };
45
+ }
46
+
47
+ export interface ConfigApi<TConfig> {
48
+ loadAgentConfig: (configPath?: string) => Promise<TConfig>;
49
+ getModelsConfigPath: (config: TConfig, agentName?: string) => string;
50
+ getMemoryConfigPath: (config: TConfig, agentName?: string) => string;
51
+ getToolConfigPath: (config: TConfig, agentName?: string) => string;
52
+ }
53
+
54
+ export interface AgentRuntimeConfig extends BaseAgentConfig {}
55
+
56
+ export interface AgentConfigDefaults {
57
+ modelsPath?: string;
58
+ memoryPath?: string;
59
+ toolPath?: string;
60
+ toolDevPath?: string;
61
+ toolProdPath?: string;
62
+ }