@epoch-agent/core 0.3.2 → 0.5.0

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.
package/dist/index.d.ts CHANGED
@@ -7563,6 +7563,13 @@ interface AgentConfig {
7563
7563
  * 和 `LogFn` 同一条规矩:库不替宿主决定往哪儿发数据,真 SDK 的装配在 runtime。
7564
7564
  */
7565
7565
  telemetry?: Telemetry;
7566
+ /**
7567
+ * 一条用户消息底下最多几次模型请求。**`0` = 不设上限**,也是缺省
7568
+ * (判据全文在 infra 的 `DEFAULT_MAX_TURNS` / `UNLIMITED_TURNS` 上)。
7569
+ *
7570
+ * `undefined` 和 `0` 在这一格上**同义**(都落到 `DEFAULT_MAX_TURNS`),
7571
+ * 两者的区别只在 `RunInput.maxTurns` 那一格上才成立 —— 见那儿。
7572
+ */
7566
7573
  maxTurns?: number;
7567
7574
  /**
7568
7575
  * 工作目录。
@@ -7909,6 +7916,11 @@ interface RunInput {
7909
7916
  *
7910
7917
  * 是 per-call 而不是改配置:长驻宿主(`--input-format stream-json`)一个进程
7911
7918
  * 服务很多次调用,把「这一次最多跑几轮」写进进程级配置就没法逐次收紧了。
7919
+ *
7920
+ * ⚠️ **这一格上 `undefined` 和 `0` 是两个意思**(2026-09-06):`undefined` =
7921
+ * 「这次没给,跟配置走」,`0` = 「这次不限轮次」(`--max-turns 0`,压过一个
7922
+ * 配置里设了上限的会话)。所以消费它的那一行必须是 `??` 而不是 `||` ——
7923
+ * 判据在 `loop.ts` 的 `runTurns()` 上。
7912
7924
  */
7913
7925
  maxTurns?: number;
7914
7926
  /**
@@ -9817,6 +9829,28 @@ declare const EpochConfigSchema: z.ZodObject<{
9817
9829
  label: z.ZodString;
9818
9830
  icon: z.ZodOptional<z.ZodString>;
9819
9831
  }, z.core.$strip>>>;
9832
+ blankCards: z.ZodOptional<z.ZodArray<z.ZodObject<{
9833
+ id: z.ZodString;
9834
+ title: z.ZodString;
9835
+ subtitle: z.ZodOptional<z.ZodString>;
9836
+ prompt: z.ZodString;
9837
+ icon: z.ZodOptional<z.ZodString>;
9838
+ }, z.core.$strip>>>;
9839
+ blankGreeting: z.ZodOptional<z.ZodObject<{
9840
+ eyebrow: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
9841
+ zh: "zh";
9842
+ en: "en";
9843
+ }> & z.core.$partial, z.ZodString>>;
9844
+ title: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
9845
+ zh: "zh";
9846
+ en: "en";
9847
+ }> & z.core.$partial, z.ZodString>>;
9848
+ subtitle: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
9849
+ zh: "zh";
9850
+ en: "en";
9851
+ }> & z.core.$partial, z.ZodString>>;
9852
+ hidden: z.ZodOptional<z.ZodBoolean>;
9853
+ }, z.core.$strip>>;
9820
9854
  }, z.core.$strip>;
9821
9855
  /**
9822
9856
  * 全默认配置 —— 从 schema 派生,不是第二份字面量。
@@ -12961,8 +12995,27 @@ type UpdateScheduleInput = Partial<Omit<CreateScheduleInput, 'maxBudgetUsd'>> &
12961
12995
  * `maxBudgetUsd` **不在这里**,那是刻意的 —— 它没有缺省值。
12962
12996
  */
12963
12997
  declare const SCHEDULE_DEFAULTS: {
12964
- /** 交互缺省是 50。无人看着的一轮不该比有人看着的更长 */
12965
- readonly maxTurns: 20;
12998
+ /**
12999
+ * 和交互缺省**同一个常量**(2026-09-04,原来这儿是自己的一个 `20`)。
13000
+ *
13001
+ * 那个 20 的理由写的是「无人看着的一轮不该比有人看着的更长」。它不成立了 ——
13002
+ * 它当初拦的是「跑飞了没人按 Esc」,而这一层真正拦住那件事的是 `maxBudgetUsd`:
13003
+ * 它在这套功能里**必填**(所以它不在这份缺省里),而钱是比轮次准的尺子。
13004
+ * 用轮次替它把无人值守的活压到 20,拦掉的绝大多数不是跑飞,是「这个任务本来
13005
+ * 就要 60 轮」—— 而那种失败要等到第二天 9 点才看得见(同下面
13006
+ * `SCHEDULE_DEFAULT_ALLOWLIST` 那节的判据)。
13007
+ *
13008
+ * ⚠️ 这一份是**没有配置在手时的兜底**。真正拿着用户那格 `maxTurns` 的地方是
13009
+ * `server/src/schedule/handlers.ts` 的 `defaultsOf()` —— 表单预填走它,
13010
+ * 所以用户把设置里那个数改成 500,新建任务那一格就预填 500。
13011
+ *
13012
+ * ⚠️ **2026-09-06 起这个缺省是「不设上限」**(`UNLIMITED_TURNS`),因为上面那条
13013
+ * 判据跟着交互缺省一起走到了头:既然轮次拦掉的绝大多数不是跑飞,那把它当无人
13014
+ * 值守的兜底闸门就是**拿错了尺子**。这条路上兜住跑飞的仍然是 `maxBudgetUsd`
13015
+ * (必填,所以它不在这份缺省里)和 `timeoutMs`。要一道轮次闸门的人在新建任务
13016
+ * 那一格自己填一个数。
13017
+ */
13018
+ readonly maxTurns: 0;
12966
13019
  /** 15 分钟。唯一能拦住「一条命令挂住了」的那把尺子 */
12967
13020
  readonly timeoutMs: number;
12968
13021
  };
@@ -14367,6 +14420,32 @@ declare class HookManager implements HookManager$1 {
14367
14420
  } | null>;
14368
14421
  trigger(type: HookType, event: HookEvent, ctx: HookContext): Promise<void>;
14369
14422
  triggerTransform<T>(type: HookType, event: HookEvent, ctx: HookContext): Promise<T | null>;
14423
+ /**
14424
+ * ## ⚠️ 这个方法**必须**同时读 `commandHooks`(2026-09-04 补的那一半)
14425
+ *
14426
+ * 它原来是三个分发方法里唯一不读配置文件那一半的:
14427
+ *
14428
+ * | 方法 | 读 `this.hooks` | 读 `this.commandHooks` |
14429
+ * | ---- | :--: | :--: |
14430
+ * | {@link trigger} | ✅ | ✅ |
14431
+ * | {@link triggerCommandHooks} | ❌ | ✅ |
14432
+ * | 这一个(改之前) | ✅ | **❌** |
14433
+ *
14434
+ * 而 `verify:stop` **只**走这一条路。两条路当时都是死的:配置文件那一半没人读,
14435
+ * 进程内注册那一半对嵌入宿主也够不着(`BuildRuntimeOptions` 没有 hooks 字段、
14436
+ * `EpochRuntime` 上没有 `hookManager`)。于是「模型说完就停」那唯一一道闸
14437
+ * 对所有人都是关着的,而且**零诊断**:配置合法、`parseHooksConfig` 收下了它、
14438
+ * `issues` 是空的、`commandHooks` map 里真的有它 —— 只是没有任何一处会去读。
14439
+ * 写了它的人拿不到任何反馈,只会以为「配了但模型不听」。
14440
+ *
14441
+ * ## 这里是 `await`,不是 `trigger` 那种 fire-and-forget
14442
+ *
14443
+ * 表决要参与判断(`AgentLoop` 据此决定要不要再跑一轮),所以必须等。
14444
+ * 超时用的是**每条 hook 自己的** `config.timeout`(`runCommandHook` 里缺省
14445
+ * 30 秒),不是上面那个给 JS hook 的 {@link HOOK_TIMEOUT_MS} 5 秒 ——
14446
+ * 那 5 秒对一条要跑测试或跑 lint 的 `verify:stop` 明显偏紧,
14447
+ * 而 `HookEntrySchema` 里本来就有 `timeout` 这一格。
14448
+ */
14370
14449
  triggerVerify(type: HookType, event: HookEvent, ctx: HookContext): Promise<VerifyResult[]>;
14371
14450
  private getSorted;
14372
14451
  }
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync, rmSync
2
2
  import { homedir, platform, tmpdir, hostname } from 'os';
3
3
  import { join, dirname, isAbsolute, resolve, relative, sep, extname, basename } from 'path';
4
4
  import { execFile, execSync, execFileSync, spawn } from 'child_process';
5
- import { createLogger, dbPath, resolveHomeDir, t, unknownKeyIssues, parseLenient, issueDetails, splitWords, checkpointsDir, trustedImportsPath, budgetStatePath, artifactsDir, configPath, managedSettingsPath, projectSettingsPath, projectLocalSettingsPath, getSecretValues, listStoredSecretNames, maskApiKey, setSectionField, isInWorkspace, workspacesPath, openDatabase, migrate, releaseDatabase, automationLogsDir, projectPoliciesDir, projectComplianceDir, projectHooksPath, projectClaudeSettingsPath, projectClaudeLocalSettingsPath, projectSkillsDir, trustPath, detectBackend, EXEC_PATH_AS_NODE_ENV, isolate, createStreamDecoder, writeArtifact, parseShellCommand, envPath, checkDangerousCommand, checkObfuscation, countCodePoints, keepHeadAndTail, normalizeForMatch, isReadOnlyCommand, CODE_EXEC_ROOTS } from '@epoch-agent/infra';
5
+ import { createLogger, dbPath, resolveHomeDir, DEFAULT_MAX_TURNS, t, unknownKeyIssues, parseLenient, issueDetails, splitWords, checkpointsDir, trustedImportsPath, budgetStatePath, artifactsDir, UNLIMITED_TURNS, turnLimitOf, configPath, managedSettingsPath, projectSettingsPath, projectLocalSettingsPath, getSecretValues, listStoredSecretNames, maskApiKey, setSectionField, isInWorkspace, workspacesPath, openDatabase, migrate, releaseDatabase, automationLogsDir, projectPoliciesDir, projectComplianceDir, projectHooksPath, projectClaudeSettingsPath, projectClaudeLocalSettingsPath, projectSkillsDir, trustPath, detectBackend, EXEC_PATH_AS_NODE_ENV, isolate, createStreamDecoder, writeArtifact, parseShellCommand, envPath, checkDangerousCommand, checkObfuscation, countCodePoints, keepHeadAndTail, normalizeForMatch, isReadOnlyCommand, CODE_EXEC_ROOTS } from '@epoch-agent/infra';
6
6
  export { IS_WINDOWS, WINDOWS_HIDE_FLAGS, buildBwrapArgs, buildProfile, checkDangerousCommand, checkObfuscation, createLogger, detectBackend, getDefaultShell, getPythonCommand, isInWorkspace, isReadOnlyCommand, isolate, maskApiKey, probeBubblewrap, probeSeatbelt, complianceDir as resolveComplianceDir, policiesDir as resolvePoliciesDir, setLogLevel } from '@epoch-agent/infra';
7
7
  import { PROVIDER_TYPES, OPERATION_TYPES, COMPLIANCE_CATEGORIES, COMPLIANCE_ACTIONS, SEARCH_PROVIDER_TYPES, LANGS, PERMISSION_LEVELS, SHELL_KINDS, ToolExposure, MAX_OPTIONS, MIN_OPTIONS, MAX_HEADER_CHARS, MAX_QUESTIONS, isValidAgentRoleName, DEFAULT_AGENT_ROLE, isValidCommandName, RESERVED_COMMAND_NAMES, parseModelRef, systemNote, totalUsageTokens, NOOP_TELEMETRY, METRIC, contentToText, GEN_AI, promptTokens, apiKeyEnvVar, PROVIDER_INFOS, isProviderType, API_KEY_ENV_VARS, isPermissionLevel, KEY_ACTIONS, tableOf, perAction, isKeyAction, parseSequence, RESERVED_CHORDS, chordId, sequenceId, ACTION_CONTEXTS, SEQUENCE_CONTEXTS, assertNeverPart, SCHEDULE_INTERVAL_MINUTES, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_SESSION_REFS, utf8ByteLength, attachSessionSurface, isContextNearlyFull, isOperationType, normalizeApproval, MAX_ATTACH_BYTES, SIMULTANEOUS_CONTEXTS, SESSION_REFERENCE_ERROR_CODES, filePartSummary, sessionPartSummary } from '@epoch-agent/protocol';
8
8
  export { API_KEY_ENV_VARS, COMPLIANCE_ACTIONS, COMPLIANCE_CATEGORIES, MAX_ATTACH_BYTES, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_SESSION_ATTACH_BYTES, MAX_SESSION_REFS, OPERATION_TYPES, PERMISSION_LEVELS, PLAN_OUTCOMES, PROVIDER_INFOS, PROVIDER_TYPES, ToolExposure, apiKeyEnvVar, extractAllMentions, extractMentions, extractSessionMentions, getProviderInfo, isOperationType, isPermissionLevel, isPlanOutcome, isProviderType, normalizeApproval } from '@epoch-agent/protocol';
@@ -3571,7 +3571,7 @@ var ROLE_FRONTMATTER_SCHEMA = z.object({
3571
3571
  description: z.string().min(1),
3572
3572
  tools: z.array(z.string().min(1)).optional(),
3573
3573
  skills: z.array(z.string().min(1)).optional(),
3574
- maxTurns: z.int().positive().max(200).optional()
3574
+ maxTurns: z.int().nonnegative().optional()
3575
3575
  });
3576
3576
  var FrontmatterSchema = ROLE_FRONTMATTER_SCHEMA;
3577
3577
  var FRONTMATTER_KEYS2 = Object.keys(FrontmatterSchema.shape);
@@ -3646,7 +3646,7 @@ function parseRoleDefinition(raw, file, source) {
3646
3646
  ...prompt ? { prompt } : {},
3647
3647
  ...fm.tools ? { tools: fm.tools } : {},
3648
3648
  ...fm.skills ? { skills: fm.skills } : {},
3649
- ...fm.maxTurns ? { maxTurns: fm.maxTurns } : {},
3649
+ ...fm.maxTurns !== void 0 ? { maxTurns: fm.maxTurns } : {},
3650
3650
  source
3651
3651
  },
3652
3652
  issues
@@ -3734,7 +3734,6 @@ var BUILTIN_ROLES = [
3734
3734
  description: "\u53EA\u8BFB\u68C0\u7D22\u3002\u627E\u6587\u4EF6\u3001\u627E\u7B26\u53F7\u3001\u56DE\u7B54\u300CX \u5728\u54EA\u5B9E\u73B0\u7684\u300D\u3002\u5B83\u6539\u4E0D\u4E86\u4EFB\u4F55\u4E1C\u897F\uFF0C\u56DE\u4F20\u7684\u662F\u6587\u4EF6\u8DEF\u5F84 + \u884C\u53F7\u800C\u4E0D\u662F\u4EE3\u7801\u539F\u6587\uFF0C\u9002\u5408\u9700\u8981\u7FFB\u5F88\u591A\u6587\u4EF6\u4F46\u7ED3\u8BBA\u5F88\u77ED\u7684\u4EFB\u52A1",
3735
3735
  prompt: EXPLORE_PROMPT,
3736
3736
  tools: [...READ_ONLY_TOOLS],
3737
- maxTurns: 8,
3738
3737
  source: "builtin"
3739
3738
  },
3740
3739
  {
@@ -3742,7 +3741,6 @@ var BUILTIN_ROLES = [
3742
3741
  description: "\u4EE3\u7801\u5BA1\u67E5\u3002\u53EA\u8BFB\uFF0C\u8F93\u51FA\u300C\u6587\u4EF6:\u884C \xB7 \u4E25\u91CD\u5EA6 \xB7 \u4E00\u53E5\u8BDD\u300D\u7684\u95EE\u9898\u6E05\u5355\u3002\u9002\u5408\u6539\u5B8C\u4E00\u6279\u4EE3\u7801\u4E4B\u540E\u627E\u95EE\u9898\uFF0C\u4E0D\u9002\u5408\u8BA9\u5B83\u987A\u624B\u628A\u95EE\u9898\u4FEE\u6389\uFF08\u5B83\u6CA1\u6709\u5199\u5DE5\u5177\uFF09",
3743
3742
  prompt: REVIEW_PROMPT,
3744
3743
  tools: [...READ_ONLY_TOOLS],
3745
- maxTurns: 10,
3746
3744
  source: "builtin"
3747
3745
  }
3748
3746
  ];
@@ -3774,7 +3772,7 @@ function mergeRoles(overrides) {
3774
3772
  ...existing,
3775
3773
  ...incoming.description ? { description: incoming.description } : {},
3776
3774
  ...incoming.prompt ? { prompt: incoming.prompt } : {},
3777
- ...incoming.maxTurns ? { maxTurns: incoming.maxTurns } : {},
3775
+ ...incoming.maxTurns !== void 0 ? { maxTurns: incoming.maxTurns } : {},
3778
3776
  ...tools ? { tools } : {},
3779
3777
  ...skills ? { skills } : {},
3780
3778
  source: incoming.source
@@ -8510,6 +8508,15 @@ function wrapUntrusted(source, text) {
8510
8508
  ${escaped}
8511
8509
  </${OUTPUT_TAG}>`;
8512
8510
  }
8511
+ function outputText(output) {
8512
+ if (output === void 0 || output === null) return "";
8513
+ return typeof output === "string" ? output : JSON.stringify(output) ?? "";
8514
+ }
8515
+ function failureText(r) {
8516
+ const detail = r.error?.message?.trim() || outputText(r.output).trim();
8517
+ if (!detail) return "\u5DE5\u5177\u6267\u884C\u5931\u8D25\uFF08\u5DE5\u5177\u672A\u63D0\u4F9B\u539F\u56E0\uFF09";
8518
+ return r.error?.code === void 0 ? `\u5DE5\u5177\u6267\u884C\u5931\u8D25\uFF1A${detail}` : `\u5DE5\u5177\u6267\u884C\u5931\u8D25 [${r.error.code}]: ${detail}`;
8519
+ }
8513
8520
  var ToolExecutor = class {
8514
8521
  constructor(deps) {
8515
8522
  this.deps = deps;
@@ -8603,7 +8610,7 @@ var ToolExecutor = class {
8603
8610
  toolName: et.name,
8604
8611
  toolResult: {
8605
8612
  success: r.success,
8606
- output: typeof r.output === "string" ? r.output : JSON.stringify(r.output),
8613
+ output: outputText(r.output),
8607
8614
  error: r.error
8608
8615
  }
8609
8616
  },
@@ -8611,7 +8618,7 @@ var ToolExecutor = class {
8611
8618
  ).catch(() => {
8612
8619
  });
8613
8620
  if (r.success) {
8614
- const text = typeof r.output === "string" ? r.output : JSON.stringify(r.output);
8621
+ const text = outputText(r.output);
8615
8622
  const artifacts = r.artifacts ? admitArtifacts(r.artifacts, this.policy(sessionId)) : [];
8616
8623
  const diffs = before ? await collectDiffs(before, this.deps.workDir()) : [];
8617
8624
  return finish({
@@ -8632,7 +8639,10 @@ var ToolExecutor = class {
8632
8639
  return finish({
8633
8640
  ...base,
8634
8641
  success: false,
8635
- output: `\u5DE5\u5177\u6267\u884C\u5931\u8D25 [${r.error?.code}]: ${r.error?.message}`
8642
+ output: this.renderOutput(
8643
+ et,
8644
+ truncate(failureText(r), depth > 0 ? MAX_SUB_CALL_OUTPUT : MAX_TOOL_OUTPUT)
8645
+ )
8636
8646
  });
8637
8647
  } catch (err) {
8638
8648
  span.recordError(err);
@@ -8900,12 +8910,12 @@ var AgentLoop = class {
8900
8910
  learnedSkills = [];
8901
8911
  constructor(config) {
8902
8912
  this.config = {
8903
- maxTurns: 50,
8913
+ maxTurns: DEFAULT_MAX_TURNS,
8904
8914
  workDir: process.cwd(),
8905
8915
  contextLength: DEFAULT_CONTEXT_LENGTH,
8906
8916
  ...config
8907
8917
  };
8908
- if (this.config.maxTurns < 1) this.config.maxTurns = 1;
8918
+ if (this.config.maxTurns < 0) this.config.maxTurns = UNLIMITED_TURNS;
8909
8919
  if (this.config.site) {
8910
8920
  this.site = this.config.site;
8911
8921
  } else {
@@ -9032,7 +9042,7 @@ var AgentLoop = class {
9032
9042
  yield* this.runTurns(input, turns);
9033
9043
  }
9034
9044
  async *runTurns(input, turns) {
9035
- const maxTurns = input.maxTurns ?? this.config.maxTurns;
9045
+ const maxTurns = turnLimitOf(input.maxTurns ?? this.config.maxTurns);
9036
9046
  this.stallDetector.resetOnSuccess();
9037
9047
  const rawUserText = contentToText(input.userMessage, { imagesCarriedAsParts: true });
9038
9048
  const images = imageParts(input.userMessage);
@@ -9267,6 +9277,10 @@ var AgentLoop = class {
9267
9277
  return;
9268
9278
  }
9269
9279
  const toolCalls = result.toolCalls ?? [];
9280
+ const truncated = result.finishReason === "length" && toolCalls.length === 0;
9281
+ if (truncated) {
9282
+ yield { type: "notice", code: "response-truncated", text: t("agent_run.truncated") };
9283
+ }
9270
9284
  if (toolCalls.length === 0) {
9271
9285
  turns.push({
9272
9286
  index: turn,
@@ -9311,7 +9325,7 @@ ${finalText}` };
9311
9325
  turns
9312
9326
  });
9313
9327
  this.maybeLearn(userText, finalText);
9314
- yield finish("stop");
9328
+ yield finish(truncated ? "length" : "stop");
9315
9329
  return;
9316
9330
  }
9317
9331
  messages.push({
@@ -9613,6 +9627,24 @@ var SidebarMenuItemSchema = z.object({
9613
9627
  label: z.string().min(1),
9614
9628
  icon: z.string().min(1).optional()
9615
9629
  });
9630
+ var BlankCardSchema = z.object({
9631
+ id: z.string().min(1),
9632
+ title: z.string().min(1),
9633
+ subtitle: z.string().min(1).optional(),
9634
+ prompt: z.string().min(1),
9635
+ icon: z.string().min(1).optional()
9636
+ });
9637
+ var HostTextSchema = z.partialRecord(z.enum(LANGS), z.string().min(1)).refine((text) => Object.values(text).some((value) => value !== void 0), {
9638
+ message: "at least one language must be given"
9639
+ });
9640
+ var BlankGreetingSchema = z.object({
9641
+ eyebrow: HostTextSchema.optional(),
9642
+ title: HostTextSchema.optional(),
9643
+ subtitle: HostTextSchema.optional(),
9644
+ hidden: z.boolean().optional()
9645
+ }).refine((greeting) => Object.values(greeting).some((value) => value !== void 0), {
9646
+ message: "blankGreeting: must set at least one of eyebrow / title / subtitle / hidden"
9647
+ });
9616
9648
  var ArtifactsSchema = z.object({
9617
9649
  maxAgeDays: z.int().positive().max(3650).default(ARTIFACT_RETENTION_DEFAULTS.maxAgeDays),
9618
9650
  maxTotalBytes: z.int().positive().default(ARTIFACT_RETENTION_DEFAULTS.maxTotalBytes)
@@ -9628,7 +9660,7 @@ var EpochConfigSchema = z.object({
9628
9660
  model: z.string().min(1).default("gpt-4o-mini"),
9629
9661
  models: z.object({ utility: z.string().min(1).optional() }).optional(),
9630
9662
  shell: z.enum(SHELL_KINDS).optional(),
9631
- maxTurns: z.int().positive().default(50),
9663
+ maxTurns: z.int().nonnegative().default(DEFAULT_MAX_TURNS),
9632
9664
  contextLength: z.int().positive().default(2e5),
9633
9665
  homeDir: z.string().min(1).default(() => resolveHomeDir()),
9634
9666
  dbPath: z.string().min(1).default(() => dbPath(resolveHomeDir())),
@@ -9663,7 +9695,11 @@ var EpochConfigSchema = z.object({
9663
9695
  brand: z.string().min(1).optional(),
9664
9696
  sidebarMenu: z.array(SidebarMenuItemSchema).min(1).refine((items) => new Set(items.map((item) => item.id)).size === items.length, {
9665
9697
  message: "sidebarMenu: id must be unique"
9666
- }).optional()
9698
+ }).optional(),
9699
+ blankCards: z.array(BlankCardSchema).min(1).refine((items) => new Set(items.map((item) => item.id)).size === items.length, {
9700
+ message: "blankCards: id must be unique"
9701
+ }).optional(),
9702
+ blankGreeting: BlankGreetingSchema.optional()
9667
9703
  });
9668
9704
  function defaultConfig() {
9669
9705
  return EpochConfigSchema.parse({});
@@ -10501,7 +10537,7 @@ function applyEnvOverrides(config, issues, track) {
10501
10537
  }
10502
10538
  if (env.EPOCH_MAX_TURNS) {
10503
10539
  const turns = Number(env.EPOCH_MAX_TURNS);
10504
- if (Number.isInteger(turns) && turns > 0) {
10540
+ if (Number.isInteger(turns) && turns >= 0) {
10505
10541
  config.maxTurns = turns;
10506
10542
  track.env.add("maxTurns");
10507
10543
  } else {
@@ -12040,7 +12076,7 @@ var DESCRIPTIONS = {
12040
12076
  "permissions.ask": "\u6BCF\u6B21\u90FD\u95EE\u4E00\u53E5\uFF0C\u5373\u4F7F\u6743\u9650\u7EA7\u522B\u672C\u6765\u4E0D\u95EE\u3002\u5199\u6CD5\u540C allow\u3002",
12041
12077
  "permissions.deny": "\u4E00\u5F8B\u62D2\u7EDD\uFF0C\u538B\u8FC7 allow / ask \u548C\u5BA1\u6279\u7F13\u5B58\u3002\u5199\u6CD5\u540C allow\uFF1B\u672A\u4FE1\u4EFB\u7684\u4ED3\u5E93\u91CC\u53EA\u6709\u8FD9\u4E00\u5F20\u5217\u8868\u8FD8\u751F\u6548\u3002",
12042
12078
  model: "\u672C\u5C42\u4F7F\u7528\u7684\u6A21\u578B ID\uFF0C\u8986\u76D6\u4F4E\u5C42\u7EA7\u7684\u8BBE\u7F6E\u3002",
12043
- maxTurns: "\u5355\u6B21\u4F1A\u8BDD\u6700\u591A\u51E0\u8F6E\uFF08\u4E00\u8F6E = \u4E00\u6B21\u6A21\u578B\u8C03\u7528 + \u5B83\u89E6\u53D1\u7684\u5DE5\u5177\u6267\u884C\uFF09\u3002\u6B63\u6574\u6570\u3002",
12079
+ maxTurns: "\u5355\u6B21\u4F1A\u8BDD\u6700\u591A\u51E0\u8F6E\uFF08\u4E00\u8F6E = \u4E00\u6B21\u6A21\u578B\u8C03\u7528 + \u5B83\u89E6\u53D1\u7684\u5DE5\u5177\u6267\u884C\uFF09\u30020 \u6216\u6B63\u6574\u6570\uFF0C**0 = \u4E0D\u8BBE\u4E0A\u9650**\uFF08\u7F3A\u7701\uFF09\u3002",
12044
12080
  allowManagedPermissionRulesOnly: "\u6253\u5F00\u540E\uFF0C\u5176\u4F59\u5404\u5C42\uFF08\u7528\u6237\u7EA7 / \u9879\u76EE\u7EA7 / \u9879\u76EE\u672C\u5730 / --settings\uFF09\u7684 allow / ask / deny **\u6574\u4EFD\u5FFD\u7565**\uFF0C\u53EA\u6709\u8FD9\u4EFD\u6258\u7BA1\u6587\u4EF6\u91CC\u7684\u89C4\u5219\u7B97\u6570\u3002\u8FD9\u662F\u5168\u6D41\u7A0B\u552F\u4E00\u4E00\u5904\u66FF\u6362\u8BED\u4E49\uFF0C\u4E0D\u662F\u5E76\u96C6\u3002\u9ED8\u8BA4 false\u3002",
12045
12081
  allowManagedHooksOnly: "\u6253\u5F00\u540E\uFF0C\u7528\u6237\u7EA7 hook \u4E00\u6761\u90FD\u4E0D\u52A0\u8F7D\uFF08hook \u80FD spawn \u4EFB\u610F\u5B50\u8FDB\u7A0B\uFF09\u3002\u9ED8\u8BA4 false\u3002",
12046
12082
  disableBypassPermissionsMode: "\u6253\u5F00\u540E\uFF0C`permission: bypass` **\u62D2\u7EDD\u542F\u52A8**\uFF08\u9000\u51FA\u7801 4\uFF09\uFF0C\u8FD0\u884C\u671F\u5207\u5230 bypass \u4E5F\u4F1A\u88AB\u6321\u4E0B\u3002\u9ED8\u8BA4 false\u3002"
@@ -15665,7 +15701,6 @@ function suggestRuleFromApproval(request) {
15665
15701
  ].join("\n")
15666
15702
  };
15667
15703
  }
15668
- // src/schedule/types.ts
15669
15704
  var SCHEDULE_ISSUE_CODES = [
15670
15705
  "name-empty",
15671
15706
  "prompt-empty",
@@ -15689,7 +15724,7 @@ var SCHEDULE_ISSUE_CODES = [
15689
15724
  "bypass-not-confirmed"
15690
15725
  ];
15691
15726
  var SCHEDULE_DEFAULTS = {
15692
- maxTurns: 20,
15727
+ maxTurns: DEFAULT_MAX_TURNS,
15693
15728
  timeoutMs: 15 * 60 * 1e3
15694
15729
  };
15695
15730
  var SCHEDULE_DEFAULT_ALLOWLIST = {
@@ -16277,7 +16312,6 @@ function onceInstant(trigger) {
16277
16312
  if (!date || !clock) return null;
16278
16313
  return new Date(date.year, date.month - 1, date.day, clock.hour, clock.minute, 0, 0).getTime();
16279
16314
  }
16280
- var MAX_TURNS_CEILING = 1e3;
16281
16315
  var MAX_TIMEOUT_MS = 24 * 60 * 60 * 1e3;
16282
16316
  function validateSchedule(input) {
16283
16317
  const issues = [];
@@ -16293,7 +16327,7 @@ function validateSchedule(input) {
16293
16327
  if (!Number.isFinite(input.maxBudgetUsd) || input.maxBudgetUsd <= 0) {
16294
16328
  issues.push({ code: "budget-missing", detail: String(input.maxBudgetUsd) });
16295
16329
  }
16296
- if (!Number.isInteger(input.maxTurns) || input.maxTurns <= 0 || input.maxTurns > MAX_TURNS_CEILING) {
16330
+ if (!Number.isInteger(input.maxTurns) || input.maxTurns < 0) {
16297
16331
  issues.push({ code: "max-turns-invalid", detail: String(input.maxTurns) });
16298
16332
  }
16299
16333
  if (!Number.isFinite(input.timeoutMs) || input.timeoutMs <= 0 || input.timeoutMs > MAX_TIMEOUT_MS) {
@@ -16320,7 +16354,7 @@ function bypassIssues(input) {
16320
16354
  } else if (isTooBroad(input.workDir, input.homeDir)) {
16321
16355
  issues.push({ code: "bypass-workdir-too-broad", detail: input.workDir });
16322
16356
  }
16323
- const missingRuler = !(input.maxBudgetUsd > 0) || !(input.maxTurns > 0) || !(input.timeoutMs > 0);
16357
+ const missingRuler = !(input.maxBudgetUsd > 0) || !(input.timeoutMs > 0);
16324
16358
  if (missingRuler) issues.push({ code: "bypass-needs-limits" });
16325
16359
  if (input.bypassAcknowledged !== true) issues.push({ code: "bypass-not-confirmed" });
16326
16360
  return issues;
@@ -17893,6 +17927,25 @@ var HookManager = class {
17893
17927
  });
17894
17928
  }
17895
17929
  }
17930
+ const cmdEntries = this.commandHooks.get(type);
17931
+ if (cmdEntries) {
17932
+ for (const entry of cmdEntries) {
17933
+ if (!matchesHook(entry.matcher, event)) continue;
17934
+ for (const config of entry.configs) {
17935
+ try {
17936
+ const result = await runCommandHook(config, event, ctx.workDir);
17937
+ reportIfNeverRan(this.log, type, config, result);
17938
+ const verdict = verdictOf2(result);
17939
+ if (verdict) results.push(verdict);
17940
+ } catch (err) {
17941
+ this.log("warn", `[HookManager] ${type} \u7684 command hook \u6267\u884C\u5F02\u5E38: ${describe7(err)}`, {
17942
+ hookType: type,
17943
+ command: config.command
17944
+ });
17945
+ }
17946
+ }
17947
+ }
17948
+ }
17896
17949
  return results;
17897
17950
  }
17898
17951
  getSorted(type) {
@@ -17904,6 +17957,17 @@ var HookManager = class {
17904
17957
  function describe7(err) {
17905
17958
  return err instanceof Error ? err.message : String(err);
17906
17959
  }
17960
+ function verdictOf2(result) {
17961
+ const action = result.output?.["action"];
17962
+ if (action === "continue" || action === "stop") {
17963
+ const message = typeof result.output?.["message"] === "string" ? result.output["message"] : "";
17964
+ return { action, ...message ? { message } : {} };
17965
+ }
17966
+ if (result.blocked) {
17967
+ return { action: "continue", ...result.rejectReason ? { message: result.rejectReason } : {} };
17968
+ }
17969
+ return void 0;
17970
+ }
17907
17971
  function reportIfNeverRan(log5, type, config, result) {
17908
17972
  if (result.exitCode !== -1) return;
17909
17973
  log5(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epoch-agent/core",
3
- "version": "0.3.2",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "description": "epoch-agent 核心引擎:ReAct 循环、Provider 路由、工具调度、记忆管理",
6
6
  "repository": {
@@ -27,8 +27,8 @@
27
27
  "gpt-tokenizer": "^3.4.0",
28
28
  "js-yaml": "^5.2.3",
29
29
  "zod": "^4.4.3",
30
- "@epoch-agent/infra": "0.3.2",
31
- "@epoch-agent/protocol": "0.3.2"
30
+ "@epoch-agent/infra": "0.5.0",
31
+ "@epoch-agent/protocol": "0.5.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@ai-sdk/amazon-bedrock": "^5.0.40",