@poncho-ai/cli 0.36.8 → 0.37.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @poncho-ai/cli@0.36.8 build /home/runner/work/poncho-ai/poncho-ai/packages/cli
2
+ > @poncho-ai/cli@0.37.0 build /home/runner/work/poncho-ai/poncho-ai/packages/cli
3
3
  > tsup src/index.ts src/cli.ts --format esm --dts
4
4
 
5
5
  CLI Building entry: src/cli.ts, src/index.ts
@@ -8,11 +8,11 @@
8
8
  CLI Target: es2022
9
9
  ESM Build start
10
10
  ESM dist/cli.js 94.00 B
11
- ESM dist/run-interactive-ink-3OIMW3XL.js 56.86 KB
12
11
  ESM dist/index.js 917.00 B
13
- ESM dist/chunk-GG6IZSWT.js 571.87 KB
14
- ESM ⚡️ Build success in 75ms
12
+ ESM dist/run-interactive-ink-75GKYSEC.js 56.86 KB
13
+ ESM dist/chunk-GUGBKAIM.js 572.60 KB
14
+ ESM ⚡️ Build success in 68ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 4433ms
16
+ DTS ⚡️ Build success in 4119ms
17
17
  DTS dist/cli.d.ts 20.00 B
18
18
  DTS dist/index.d.ts 7.07 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @poncho-ai/cli
2
2
 
3
+ ## 0.37.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`620a0c8`](https://github.com/cesr/poncho-ai/commit/620a0c89efaafce28968fca5cbde2e2b19bd1595) Thanks [@cesr](https://github.com/cesr)! - feat: add recurrent reminders (daily, weekly, monthly, cron)
8
+
9
+ The `set_reminder` tool now accepts an optional `recurrence` parameter that makes reminders repeat on a schedule instead of firing once. Supports daily, weekly (with specific days-of-week), monthly, and cron expressions. Recurring reminders are rescheduled after each firing and can be bounded by `maxOccurrences` or `endsAt`. Cancel a recurring reminder to stop all future occurrences.
10
+
11
+ ### Patch Changes
12
+
13
+ - [`6486de2`](https://github.com/cesr/poncho-ai/commit/6486de2242a2976068e4bd09f7c0f2d978c35c96) Thanks [@cesr](https://github.com/cesr)! - fix: persist subagent `parentConversationId` atomically so children never appear top-level in the sidebar.
14
+
15
+ `SubagentManager.spawn` previously did a two-step write: `conversationStore.create(...)` followed by `conversationStore.update(...)` to attach `parentConversationId`, `subagentMeta`, and the initial user message. If the follow-up update was interrupted (serverless timeout, transient DB error), the child row was left in the database with `parent_conversation_id = NULL`, so it slipped past the `!c.parentConversationId` filter on `/api/conversations` and showed up as a top-level conversation. This was especially visible with cron-driven research subagents.
16
+
17
+ `ConversationStore.create` now accepts an optional `init` bag (`parentConversationId`, `subagentMeta`, `messages`, `channelMeta`) that is written in the single INSERT — both into the `data` blob and into the dedicated `parent_conversation_id` column. `spawn` passes those fields through and drops the redundant update, eliminating the orphan window. All existing `create(ownerId, title, tenantId)` callers keep working since `init` is optional.
18
+
19
+ - Updated dependencies [[`6486de2`](https://github.com/cesr/poncho-ai/commit/6486de2242a2976068e4bd09f7c0f2d978c35c96), [`0d0578f`](https://github.com/cesr/poncho-ai/commit/0d0578fbc97a3d2644c4e22cab14ff02a79f805f), [`620a0c8`](https://github.com/cesr/poncho-ai/commit/620a0c89efaafce28968fca5cbde2e2b19bd1595)]:
20
+ - @poncho-ai/harness@0.38.0
21
+
22
+ ## 0.36.9
23
+
24
+ ### Patch Changes
25
+
26
+ - [`af5b449`](https://github.com/cesr/poncho-ai/commit/af5b449b46c9994b5b7335c5d64e4c66d5d8f3d8) Thanks [@cesr](https://github.com/cesr)! - perf(web-ui): parallelize conversation and todos fetches when selecting a conversation.
27
+
28
+ Selecting a conversation in the sidebar previously issued `/api/conversations/:id` and `/api/conversations/:id/todos` sequentially, so the todos round-trip was paid on top of the (usually larger) conversation round-trip. Todos only needs the conversation id, so both requests now fire in parallel and the todos response is awaited just before the todo panel renders. The result is roughly one RTT shaved off every sidebar click, which is very noticeable on non-local connections.
29
+
3
30
  ## 0.36.8
4
31
 
5
32
  ### Patch Changes
@@ -22,7 +22,8 @@ import {
22
22
  parseAgentMarkdown,
23
23
  resolveStateConfig,
24
24
  verifyTenantToken,
25
- createSecretsStore
25
+ createSecretsStore,
26
+ computeNextOccurrence
26
27
  } from "@poncho-ai/harness";
27
28
  import { getTextContent } from "@poncho-ai/sdk";
28
29
  import {
@@ -3505,7 +3506,12 @@ var getWebUiClientScript = (markedSource2) => `
3505
3506
 
3506
3507
  const loadConversation = async (conversationId) => {
3507
3508
  if (window._resetBrowserPanel) window._resetBrowserPanel();
3508
- const payload = await api("/api/conversations/" + encodeURIComponent(conversationId));
3509
+ // Kick off conversation + todos fetches in parallel \u2014 todos only needs
3510
+ // the id, so there's no reason to wait for the conversation response.
3511
+ const conversationPromise = api("/api/conversations/" + encodeURIComponent(conversationId));
3512
+ const todosPromise = api("/api/conversations/" + encodeURIComponent(conversationId) + "/todos")
3513
+ .catch(() => ({ todos: [] }));
3514
+ const payload = await conversationPromise;
3509
3515
  elements.chatTitle.textContent = payload.conversation.title;
3510
3516
  // Merge own pending approvals + subagent pending approvals
3511
3517
  var allPendingApprovals = [].concat(
@@ -3553,12 +3559,8 @@ var getWebUiClientScript = (markedSource2) => `
3553
3559
  loadSubagents(subagentParentId);
3554
3560
  }
3555
3561
 
3556
- try {
3557
- const todosPayload = await api("/api/conversations/" + encodeURIComponent(conversationId) + "/todos");
3558
- state.todos = todosPayload.todos || [];
3559
- } catch (_e) {
3560
- state.todos = [];
3561
- }
3562
+ const todosPayload = await todosPromise;
3563
+ state.todos = todosPayload.todos || [];
3562
3564
  _autoCollapseTodos();
3563
3565
  renderTodoPanel();
3564
3566
 
@@ -10611,12 +10613,16 @@ ${resultBody}`,
10611
10613
  if (getRunningSubagentCountForParent(opts.parentConversationId) >= MAX_CONCURRENT_SUBAGENTS) {
10612
10614
  throw new Error(`Maximum concurrent subagents (${MAX_CONCURRENT_SUBAGENTS}) per parent reached. Wait for running subagents to complete or stop some first.`);
10613
10615
  }
10614
- const conversation = await conversationStore.create(opts.ownerId, opts.task.slice(0, 80), opts.tenantId ?? null);
10615
- conversation.parentConversationId = opts.parentConversationId;
10616
- conversation.subagentMeta = { task: opts.task, status: "running" };
10617
- conversation.messages = [{ role: "user", content: opts.task }];
10618
- conversation.updatedAt = Date.now();
10619
- await conversationStore.update(conversation);
10616
+ const conversation = await conversationStore.create(
10617
+ opts.ownerId,
10618
+ opts.task.slice(0, 80),
10619
+ opts.tenantId ?? null,
10620
+ {
10621
+ parentConversationId: opts.parentConversationId,
10622
+ subagentMeta: { task: opts.task, status: "running" },
10623
+ messages: [{ role: "user", content: opts.task }]
10624
+ }
10625
+ );
10620
10626
  recentlySpawnedParents.set(
10621
10627
  opts.parentConversationId,
10622
10628
  (recentlySpawnedParents.get(opts.parentConversationId) ?? 0) + 1
@@ -13593,13 +13599,24 @@ ${cronJob.task}`;
13593
13599
  const due = reminders.filter((r) => r.status === "pending" && r.scheduledAt <= cutoff);
13594
13600
  for (const reminder of due) {
13595
13601
  try {
13596
- await reminderStore.delete(reminder.id);
13602
+ const nextScheduledAt = computeNextOccurrence(reminder);
13603
+ if (nextScheduledAt) {
13604
+ await reminderStore.update(reminder.id, {
13605
+ scheduledAt: nextScheduledAt,
13606
+ occurrenceCount: (reminder.occurrenceCount ?? 0) + 1
13607
+ });
13608
+ } else {
13609
+ await reminderStore.delete(reminder.id);
13610
+ }
13597
13611
  const originConv = reminder.conversationId ? await conversationStore.get(reminder.conversationId) : void 0;
13598
13612
  const channelMeta = originConv?.channelMeta;
13613
+ const isRecurring = !!reminder.recurrence;
13614
+ const recurrenceNote = isRecurring && nextScheduledAt ? `
13615
+ Next occurrence: ${new Date(nextScheduledAt).toISOString()}` : isRecurring ? "\nThis was the final occurrence." : "";
13599
13616
  const framedMessage = `[Reminder] A reminder you previously set has fired.
13600
13617
  Task: "${reminder.task}"
13601
13618
  Originally set at: ${new Date(reminder.createdAt).toISOString()}
13602
- Scheduled for: ${new Date(reminder.scheduledAt).toISOString()}`;
13619
+ Scheduled for: ${new Date(reminder.scheduledAt).toISOString()}` + recurrenceNote;
13603
13620
  if (channelMeta) {
13604
13621
  const adapter = messagingAdapters.get(channelMeta.platform);
13605
13622
  if (adapter && originConv) {
@@ -14047,7 +14064,7 @@ var runInteractive = async (workingDir, params) => {
14047
14064
  await harness.initialize();
14048
14065
  const identity = await ensureAgentIdentity2(workingDir);
14049
14066
  try {
14050
- const { runInteractiveInk } = await import("./run-interactive-ink-3OIMW3XL.js");
14067
+ const { runInteractiveInk } = await import("./run-interactive-ink-75GKYSEC.js");
14051
14068
  await runInteractiveInk({
14052
14069
  harness,
14053
14070
  params,
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  main
4
- } from "./chunk-GG6IZSWT.js";
4
+ } from "./chunk-GUGBKAIM.js";
5
5
 
6
6
  // src/cli.ts
7
7
  void main();
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  runTests,
25
25
  startDevServer,
26
26
  updateAgentGuidance
27
- } from "./chunk-GG6IZSWT.js";
27
+ } from "./chunk-GUGBKAIM.js";
28
28
  export {
29
29
  __internalRunOrchestration,
30
30
  addSkill,
@@ -2,7 +2,7 @@ import {
2
2
  consumeFirstRunIntro,
3
3
  inferConversationTitle,
4
4
  resolveHarnessEnvironment
5
- } from "./chunk-GG6IZSWT.js";
5
+ } from "./chunk-GUGBKAIM.js";
6
6
 
7
7
  // src/run-interactive-ink.ts
8
8
  import * as readline from "readline";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/cli",
3
- "version": "0.36.8",
3
+ "version": "0.37.0",
4
4
  "description": "CLI for building and deploying AI agents",
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,7 +28,7 @@
28
28
  "react": "^19.2.4",
29
29
  "react-devtools-core": "^6.1.5",
30
30
  "yaml": "^2.8.1",
31
- "@poncho-ai/harness": "0.37.2",
31
+ "@poncho-ai/harness": "0.38.0",
32
32
  "@poncho-ai/messaging": "0.8.3",
33
33
  "@poncho-ai/sdk": "1.8.1"
34
34
  },
package/src/index.ts CHANGED
@@ -35,6 +35,7 @@ import {
35
35
  type UploadStore,
36
36
  verifyTenantToken,
37
37
  createSecretsStore,
38
+ computeNextOccurrence,
38
39
  } from "@poncho-ai/harness";
39
40
  import type { AgentEvent, FileInput, Message, RunInput } from "@poncho-ai/sdk";
40
41
  import { getTextContent } from "@poncho-ai/sdk";
@@ -3110,12 +3111,19 @@ export const createRequestHandler = async (options?: {
3110
3111
  throw new Error(`Maximum concurrent subagents (${MAX_CONCURRENT_SUBAGENTS}) per parent reached. Wait for running subagents to complete or stop some first.`);
3111
3112
  }
3112
3113
 
3113
- const conversation = await conversationStore.create(opts.ownerId, opts.task.slice(0, 80), opts.tenantId ?? null);
3114
- conversation.parentConversationId = opts.parentConversationId;
3115
- conversation.subagentMeta = { task: opts.task, status: "running" };
3116
- conversation.messages = [{ role: "user", content: opts.task }];
3117
- conversation.updatedAt = Date.now();
3118
- await conversationStore.update(conversation);
3114
+ // Atomic create: parent_conversation_id and initial state are persisted
3115
+ // in the single INSERT. This prevents orphaned top-level-looking
3116
+ // subagents if a follow-up update is interrupted.
3117
+ const conversation = await conversationStore.create(
3118
+ opts.ownerId,
3119
+ opts.task.slice(0, 80),
3120
+ opts.tenantId ?? null,
3121
+ {
3122
+ parentConversationId: opts.parentConversationId,
3123
+ subagentMeta: { task: opts.task, status: "running" },
3124
+ messages: [{ role: "user", content: opts.task }],
3125
+ },
3126
+ );
3119
3127
 
3120
3128
  // Register this parent as having a pending spawn BEFORE the async
3121
3129
  // runSubagent starts, so hasPendingSubagentWorkForParent sees it
@@ -6486,18 +6494,36 @@ export const createRequestHandler = async (options?: {
6486
6494
 
6487
6495
  for (const reminder of due) {
6488
6496
  try {
6489
- await reminderStore.delete(reminder.id);
6497
+ // For recurring reminders, compute the next occurrence before any
6498
+ // state changes so we can reschedule. For one-off reminders, delete.
6499
+ const nextScheduledAt = computeNextOccurrence(reminder);
6500
+ if (nextScheduledAt) {
6501
+ await reminderStore.update(reminder.id, {
6502
+ scheduledAt: nextScheduledAt,
6503
+ occurrenceCount: (reminder.occurrenceCount ?? 0) + 1,
6504
+ });
6505
+ } else {
6506
+ await reminderStore.delete(reminder.id);
6507
+ }
6490
6508
 
6491
6509
  const originConv = reminder.conversationId
6492
6510
  ? await conversationStore.get(reminder.conversationId)
6493
6511
  : undefined;
6494
6512
  const channelMeta = originConv?.channelMeta;
6495
6513
 
6514
+ const isRecurring = !!reminder.recurrence;
6515
+ const recurrenceNote = isRecurring && nextScheduledAt
6516
+ ? `\nNext occurrence: ${new Date(nextScheduledAt).toISOString()}`
6517
+ : isRecurring
6518
+ ? "\nThis was the final occurrence."
6519
+ : "";
6520
+
6496
6521
  const framedMessage =
6497
6522
  `[Reminder] A reminder you previously set has fired.\n` +
6498
6523
  `Task: "${reminder.task}"\n` +
6499
6524
  `Originally set at: ${new Date(reminder.createdAt).toISOString()}\n` +
6500
- `Scheduled for: ${new Date(reminder.scheduledAt).toISOString()}`;
6525
+ `Scheduled for: ${new Date(reminder.scheduledAt).toISOString()}` +
6526
+ recurrenceNote;
6501
6527
 
6502
6528
  if (channelMeta) {
6503
6529
  const adapter = messagingAdapters.get(channelMeta.platform);
@@ -1459,7 +1459,12 @@ export const getWebUiClientScript = (markedSource: string): string => `
1459
1459
 
1460
1460
  const loadConversation = async (conversationId) => {
1461
1461
  if (window._resetBrowserPanel) window._resetBrowserPanel();
1462
- const payload = await api("/api/conversations/" + encodeURIComponent(conversationId));
1462
+ // Kick off conversation + todos fetches in parallel — todos only needs
1463
+ // the id, so there's no reason to wait for the conversation response.
1464
+ const conversationPromise = api("/api/conversations/" + encodeURIComponent(conversationId));
1465
+ const todosPromise = api("/api/conversations/" + encodeURIComponent(conversationId) + "/todos")
1466
+ .catch(() => ({ todos: [] }));
1467
+ const payload = await conversationPromise;
1463
1468
  elements.chatTitle.textContent = payload.conversation.title;
1464
1469
  // Merge own pending approvals + subagent pending approvals
1465
1470
  var allPendingApprovals = [].concat(
@@ -1507,12 +1512,8 @@ export const getWebUiClientScript = (markedSource: string): string => `
1507
1512
  loadSubagents(subagentParentId);
1508
1513
  }
1509
1514
 
1510
- try {
1511
- const todosPayload = await api("/api/conversations/" + encodeURIComponent(conversationId) + "/todos");
1512
- state.todos = todosPayload.todos || [];
1513
- } catch (_e) {
1514
- state.todos = [];
1515
- }
1515
+ const todosPayload = await todosPromise;
1516
+ state.todos = todosPayload.todos || [];
1516
1517
  _autoCollapseTodos();
1517
1518
  renderTodoPanel();
1518
1519