@nanhara/hara 0.122.3 → 0.122.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 (60) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +15 -2
  3. package/dist/agent/limits.js +51 -0
  4. package/dist/agent/loop.js +367 -112
  5. package/dist/agent/repeat-guard.js +49 -12
  6. package/dist/checkpoints.js +9 -0
  7. package/dist/cli.js +2 -1
  8. package/dist/config.js +10 -2
  9. package/dist/context/agents-md.js +21 -2
  10. package/dist/context/mentions.js +84 -1
  11. package/dist/context/workspace-scope.js +49 -0
  12. package/dist/cron/deliver.js +61 -38
  13. package/dist/cron/runner.js +423 -65
  14. package/dist/cron/schedule.js +23 -7
  15. package/dist/cron/store.js +709 -43
  16. package/dist/fs-walk.js +279 -7
  17. package/dist/fs-write.js +9 -0
  18. package/dist/gateway/feishu.js +351 -64
  19. package/dist/gateway/flows-pending.js +28 -14
  20. package/dist/gateway/flows.js +306 -31
  21. package/dist/gateway/outbound-files.js +20 -6
  22. package/dist/gateway/runtime-state.js +1485 -0
  23. package/dist/gateway/serve.js +550 -242
  24. package/dist/gateway/telegram.js +279 -29
  25. package/dist/gateway/tts.js +182 -38
  26. package/dist/hooks.js +22 -22
  27. package/dist/index.js +466 -158
  28. package/dist/memory/store.js +8 -5
  29. package/dist/org/planner.js +11 -6
  30. package/dist/process-identity.js +52 -0
  31. package/dist/providers/bounded-turn.js +42 -0
  32. package/dist/recall.js +69 -1
  33. package/dist/runtime.js +24 -0
  34. package/dist/sandbox.js +54 -4
  35. package/dist/search/embed.js +13 -2
  36. package/dist/search/hybrid.js +6 -3
  37. package/dist/search/semindex.js +87 -5
  38. package/dist/security/guardian.js +3 -15
  39. package/dist/security/permissions.js +11 -0
  40. package/dist/serve/server.js +70 -42
  41. package/dist/serve/sessions.js +4 -3
  42. package/dist/tools/agent.js +1 -1
  43. package/dist/tools/ask_user.js +5 -1
  44. package/dist/tools/builtin.js +5 -2
  45. package/dist/tools/codebase.js +67 -18
  46. package/dist/tools/computer.js +149 -68
  47. package/dist/tools/cron.js +72 -18
  48. package/dist/tools/edit.js +3 -2
  49. package/dist/tools/external_agent.js +66 -12
  50. package/dist/tools/memory.js +25 -7
  51. package/dist/tools/patch.js +11 -2
  52. package/dist/tools/registry.js +16 -1
  53. package/dist/tools/search.js +68 -9
  54. package/dist/tools/send.js +1 -1
  55. package/dist/tools/skill.js +1 -1
  56. package/dist/tools/web.js +43 -13
  57. package/dist/tui/App.js +93 -25
  58. package/dist/vision.js +5 -6
  59. package/package.json +2 -2
  60. package/runtime-bootstrap.cjs +22 -0
@@ -144,6 +144,8 @@ export function parseSchedule(input, nowMs) {
144
144
  const t = Date.parse(s);
145
145
  if (Number.isNaN(t))
146
146
  return { error: `bad timestamp: ${s}` };
147
+ if (t <= nowMs)
148
+ return { error: `timestamp is in the past: ${s}` };
147
149
  return { kind: "once", runAt: t, display: `once, at ${s}` };
148
150
  }
149
151
  if (parseCron(s))
@@ -158,9 +160,17 @@ export function describeSchedule(sched) {
158
160
  export function isDue(job, nowMs) {
159
161
  const s = job.schedule;
160
162
  if (s.kind === "cron") {
161
- if (!cronMatches(s.expr, new Date(nowMs), job.tz))
162
- return false;
163
- return job.lastRunAt === undefined || Math.floor(job.lastRunAt / 60_000) < Math.floor(nowMs / 60_000);
163
+ // This marker is persisted only for an enabled job created during a matching minute. It survives the
164
+ // 09:00:00 -> 09:00:37 creation race, but unlike inferring from `createdAt` it can be cleared on disable
165
+ // and therefore cannot make a job execute a months-old occurrence after it is re-enabled.
166
+ if (job.pendingDueAt !== undefined
167
+ && job.pendingDueAt <= nowMs
168
+ && (job.lastRunAt === undefined || job.lastRunAt < job.pendingDueAt))
169
+ return true;
170
+ if (cronMatches(s.expr, new Date(nowMs), job.tz)) {
171
+ return job.lastRunAt === undefined || Math.floor(job.lastRunAt / 60_000) < Math.floor(nowMs / 60_000);
172
+ }
173
+ return false;
164
174
  }
165
175
  // interval: fire once per grid slot of width everyMs — a tick landing slightly early still counts the
166
176
  // slot (a plain `now >= last+everyMs` deadline loses ~half the fires of `every 1m` at 60s tick granularity).
@@ -168,17 +178,23 @@ export function isDue(job, nowMs) {
168
178
  return Math.floor(nowMs / s.everyMs) > Math.floor((job.lastRunAt ?? job.createdAt) / s.everyMs);
169
179
  return job.lastRunAt === undefined && nowMs >= s.runAt; // once
170
180
  }
171
- /** Next fire time at/after `fromMs` (for display). Cron scans minute-by-minute up to a year; null if none
172
- * (e.g. a one-shot already past). */
181
+ /** Next actionable fire time at/after `fromMs` (for display). A currently-due cron or overdue persisted
182
+ * one-shot returns `fromMs` instead of lying with tomorrow/a past timestamp. Cron scans minute-by-minute
183
+ * up to a year; null means the job has already completed or has no match in the scan window. */
173
184
  export function nextRun(job, fromMs) {
174
185
  const s = job.schedule;
175
186
  if (s.kind === "every")
176
187
  return (Math.floor(fromMs / s.everyMs) + 1) * s.everyMs; // next grid boundary (always > fromMs)
177
- if (s.kind === "once")
178
- return job.lastRunAt === undefined ? s.runAt : null;
188
+ if (s.kind === "once") {
189
+ if (job.lastRunAt !== undefined)
190
+ return null;
191
+ return s.runAt <= fromMs ? fromMs : s.runAt;
192
+ }
179
193
  const p = parseCron(s.expr);
180
194
  if (!p)
181
195
  return null;
196
+ if (isDue(job, fromMs))
197
+ return fromMs;
182
198
  const start = Math.floor(fromMs / 60_000) * 60_000 + 60_000; // next minute boundary
183
199
  for (let t = start, i = 0; i < 366 * 24 * 60; t += 60_000, i++) {
184
200
  if (cronMatches(s.expr, new Date(t), job.tz))