@parall/agent-core 1.44.0 → 1.46.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.
Files changed (62) hide show
  1. package/dist/channel-capability.d.ts +2 -0
  2. package/dist/channel-capability.d.ts.map +1 -1
  3. package/dist/channel-capability.js +15 -0
  4. package/dist/dispatch-adapter.d.ts +9 -0
  5. package/dist/dispatch-adapter.d.ts.map +1 -1
  6. package/dist/event-format.d.ts.map +1 -1
  7. package/dist/event-format.js +27 -7
  8. package/dist/fork-session-finalizer.d.ts +65 -0
  9. package/dist/fork-session-finalizer.d.ts.map +1 -0
  10. package/dist/fork-session-finalizer.js +70 -0
  11. package/dist/gateway-base.d.ts +55 -0
  12. package/dist/gateway-base.d.ts.map +1 -1
  13. package/dist/gateway-base.js +640 -263
  14. package/dist/gateway-lane-flow.d.ts +75 -5
  15. package/dist/gateway-lane-flow.d.ts.map +1 -1
  16. package/dist/gateway-lane-flow.js +240 -18
  17. package/dist/http-keepalive.d.ts +4 -0
  18. package/dist/http-keepalive.d.ts.map +1 -0
  19. package/dist/http-keepalive.js +33 -0
  20. package/dist/index.d.ts +2 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +7 -1
  23. package/dist/lane-ledger.d.ts +8 -0
  24. package/dist/lane-ledger.d.ts.map +1 -1
  25. package/dist/lane-ledger.js +14 -0
  26. package/dist/session-lifecycle.d.ts +198 -0
  27. package/dist/session-lifecycle.d.ts.map +1 -0
  28. package/dist/session-lifecycle.js +446 -0
  29. package/dist/skills/parall-clips.d.ts +1 -1
  30. package/dist/skills/parall-clips.d.ts.map +1 -1
  31. package/dist/skills/parall-clips.js +3 -0
  32. package/dist/skills/parall-schedules.d.ts +1 -1
  33. package/dist/skills/parall-schedules.d.ts.map +1 -1
  34. package/dist/skills/parall-schedules.js +1 -1
  35. package/dist/skills/parall-tasks.d.ts +1 -1
  36. package/dist/skills/parall-tasks.d.ts.map +1 -1
  37. package/dist/skills/parall-tasks.js +21 -5
  38. package/dist/step-persister.d.ts +66 -0
  39. package/dist/step-persister.d.ts.map +1 -0
  40. package/dist/step-persister.js +116 -0
  41. package/dist/step-retry-queue.d.ts +91 -0
  42. package/dist/step-retry-queue.d.ts.map +1 -0
  43. package/dist/step-retry-queue.js +259 -0
  44. package/dist/types.d.ts +1 -1
  45. package/dist/types.d.ts.map +1 -1
  46. package/package.json +3 -2
  47. package/src/channel-capability.ts +16 -0
  48. package/src/dispatch-adapter.ts +10 -0
  49. package/src/event-format.ts +27 -7
  50. package/src/fork-session-finalizer.ts +122 -0
  51. package/src/gateway-base.ts +747 -331
  52. package/src/gateway-lane-flow.ts +275 -18
  53. package/src/http-keepalive.ts +36 -0
  54. package/src/index.ts +7 -1
  55. package/src/lane-ledger.ts +14 -0
  56. package/src/session-lifecycle.ts +552 -0
  57. package/src/skills/parall-clips.ts +3 -0
  58. package/src/skills/parall-schedules.ts +1 -1
  59. package/src/skills/parall-tasks.ts +21 -5
  60. package/src/step-persister.ts +161 -0
  61. package/src/step-retry-queue.ts +296 -0
  62. package/src/types.ts +2 -1
@@ -1,2 +1,2 @@
1
- export declare const PARALL_SCHEDULES_SKILL = "# Parall Schedules\n\nA **Schedule** is a platform time trigger. At fire time the platform delivers the schedule's `description` to a target \u2014 that's it. How you respond is up to you: send a message, create a task, update a wiki page, or do nothing. Use schedules for recurring reminders (\"standup every weekday 10am\"), delayed prompts (\"in 1 hour, check CI\"), or fire-and-forget cron work.\n\nThree spec types \u2014 pick exactly one:\n\n- `cron` \u2014 5-field expression (min granularity: 1 minute)\n- `interval` \u2014 every N seconds (minimum 60)\n- `one_shot` \u2014 fire once at a specific time\n\n## Creating schedules\n\n```bash\n# Recurring cron (weekdays 10am New York)\nparall schedules create \\\n --name \"Daily standup\" \\\n --description \"Ask the team for their plan today; see prll://wik_xxx for the standup template\" \\\n --target-ids prll://usr_xxx \\\n --cron-expr \"0 10 * * 1-5\" \\\n --timezone America/New_York \\\n --attached-to-uri prll://cht_xxx\n\n# Every 30 minutes\nparall schedules create \\\n --name \"CI watch\" \\\n --description \"Check the deploy status and flag failures\" \\\n --target-ids prll://usr_xxx \\\n --interval-seconds 1800\n\n# One-shot at a future RFC3339 time\nparall schedules create \\\n --name \"Followup\" \\\n --description \"Remind the user about the PR review if still pending\" \\\n --target-ids prll://usr_xxx \\\n --run-at <FUTURE_RFC3339_TIME>\n```\n\n`--target-ids` is who receives the fire (usually yourself when you're self-scheduling; another agent or human when delegating). `--attached-to-uri` optionally anchors the schedule to a task / chat / project / wiki page \u2014 when that resource is archived or deleted, the schedule auto-cancels (`cancel_reason=attached_gone`).\n\n### Reminders for someone else\n\nWhen someone asks you to remind them (or a third person), put that person in\n`--target-ids` \u2014 the fire is delivered to its targets, so a reminder\ntargeting only yourself never reaches them. The schedule record stays yours as\ncreator (there is no owner transfer); add yourself as an additional target\nonly if you also need to act at fire time.\n\n## Listing / inspecting\n\n```bash\nparall schedules list --status active,paused\nparall schedules list --attached-to prll://tsk_xxx\nparall schedules list --attendee-id prll://usr_xxx\nparall schedules get prll://sch_xxx\nparall schedules runs prll://sch_xxx # fire history\nparall schedules run prll://srn_xxx # single run incl. fire-time snapshot\n```\n\n## Lifecycle\n\n```bash\nparall schedules update prll://sch_xxx --description \"New prompt\"\nparall schedules pause prll://sch_xxx # reversible\nparall schedules resume prll://sch_xxx # does NOT catch up missed slots\nparall schedules cancel prll://sch_xxx # terminal; row + runs preserved, prll://sch_ ref stays valid\nparall schedules delete prll://sch_xxx # hard-delete; requires status=cancelled AND run_count=0. Once a schedule has fired, it is permanently undeletable (409 SCHEDULE_HAS_RUNS) \u2014 cancel it and leave the audit trail. Delete is for never-fired test/accidental schedules only.\n```\n\n`spec_type` cannot be changed via update \u2014 if you need to switch between cron / interval / one_shot, cancel the old one and create a new schedule.\n\n## Responding to schedule fires\n\nWhen you receive `[Event: schedule.fired]`, the platform has fired a schedule targeting you.\n\nThe runtime (agent-core) has already done the heavy lifting: it fetched the schedule run and inlined the fire-time `description` (a frozen snapshot \u2014 later edits to the schedule don't change past fires) into your prompt, alongside `[Schedule: prll://sch_xxx]` and `[Run: prll://srn_xxx]` headers. You do **not** need to call `schedules run prll://srn_xxx` yourself \u2014 the description is already in the prompt body.\n\nYour job is to interpret the description and act:\n\n1. Read the description and any `prll://` refs it contains\n2. Do whatever the prompt asks (send a message, create a task, update a wiki, etc.) \u2014 there is no canonical response format\n3. Optional: if the fire is genuinely a no-op and you don't want to produce any artifact, use `no-reply` (from parall-platform skill) to stay silent for this turn\n\nDo not treat schedule fires as \"tasks assigned to you\" \u2014 there's no status to transition, no acknowledgment required. If the work warrants a task (multi-step, needs tracking), create one from within the response.\n\n**Fetching the run explicitly** (optional): `schedules run prll://srn_xxx` returns the same snapshot plus delivery records (reverse-lookable via `source_id=srn_xxx`) for audit. If you call it and get 404 (because the schedule was cancelled or its target/attachment changed after the fire), drop the request and continue \u2014 don't retry.\n\nCLI command results are JSON on stdout; mutation commands may emit auxiliary hints on stderr (for example, `Created: prll://sch_xxx`).\n";
1
+ export declare const PARALL_SCHEDULES_SKILL = "# Parall Schedules\n\nA **Schedule** is a platform time trigger. At fire time the platform delivers the schedule's `description` to a target \u2014 that's it. How you respond is up to you: send a message, create a task, update a wiki page, or do nothing. Use schedules for recurring reminders (\"standup every weekday 10am\"), delayed prompts (\"in 1 hour, check CI\"), or fire-and-forget cron work.\n\nThree spec types \u2014 pick exactly one:\n\n- `cron` \u2014 5-field expression (min granularity: 1 minute)\n- `interval` \u2014 every N seconds (minimum 60)\n- `one_shot` \u2014 fire once at a specific time\n\n## Creating schedules\n\n```bash\n# Recurring cron (weekdays 10am New York)\nparall schedules create \\\n --name \"Daily standup\" \\\n --description \"Ask the team for their plan today; see prll://wik_xxx for the standup template\" \\\n --target-ids prll://usr_xxx \\\n --cron-expr \"0 10 * * 1-5\" \\\n --timezone America/New_York \\\n --attached-to-uri prll://cht_xxx\n\n# Every 30 minutes\nparall schedules create \\\n --name \"CI watch\" \\\n --description \"Check the deploy status and flag failures\" \\\n --target-ids prll://usr_xxx \\\n --interval-seconds 1800\n\n# One-shot at a future RFC3339 time\nparall schedules create \\\n --name \"Followup\" \\\n --description \"Remind the user about the PR review if still pending\" \\\n --target-ids prll://usr_xxx \\\n --run-at <FUTURE_RFC3339_TIME>\n```\n\n`--target-ids` is who receives the fire (usually yourself when you're self-scheduling; another agent or human when delegating). `--attached-to-uri` optionally anchors the schedule to a task / chat / project / wiki page \u2014 when that resource is archived or deleted, the schedule auto-cancels (`status_reason=attached_gone`). A schedule whose agent targets all sit on a terminated machine is auto-paused by the platform (`status_reason=attendee_unreachable`) instead of firing into a void; resuming a recurring schedule while the machine is still terminated just pauses it again on the next slot (a one-shot resumed past its catch-up window instead follows the normal missed semantics and completes).\n\n### Reminders for someone else\n\nWhen someone asks you to remind them (or a third person), put that person in\n`--target-ids` \u2014 the fire is delivered to its targets, so a reminder\ntargeting only yourself never reaches them. The schedule record stays yours as\ncreator (there is no owner transfer); add yourself as an additional target\nonly if you also need to act at fire time.\n\n## Listing / inspecting\n\n```bash\nparall schedules list --status active,paused\nparall schedules list --attached-to prll://tsk_xxx\nparall schedules list --attendee-id prll://usr_xxx\nparall schedules get prll://sch_xxx\nparall schedules runs prll://sch_xxx # fire history\nparall schedules run prll://srn_xxx # single run incl. fire-time snapshot\n```\n\n## Lifecycle\n\n```bash\nparall schedules update prll://sch_xxx --description \"New prompt\"\nparall schedules pause prll://sch_xxx # reversible\nparall schedules resume prll://sch_xxx # does NOT catch up missed slots\nparall schedules cancel prll://sch_xxx # terminal; row + runs preserved, prll://sch_ ref stays valid\nparall schedules delete prll://sch_xxx # hard-delete; requires status=cancelled AND run_count=0. Once a schedule has fired, it is permanently undeletable (409 SCHEDULE_HAS_RUNS) \u2014 cancel it and leave the audit trail. Delete is for never-fired test/accidental schedules only.\n```\n\n`spec_type` cannot be changed via update \u2014 if you need to switch between cron / interval / one_shot, cancel the old one and create a new schedule.\n\n## Responding to schedule fires\n\nWhen you receive `[Event: schedule.fired]`, the platform has fired a schedule targeting you.\n\nThe runtime (agent-core) has already done the heavy lifting: it fetched the schedule run and inlined the fire-time `description` (a frozen snapshot \u2014 later edits to the schedule don't change past fires) into your prompt, alongside `[Schedule: prll://sch_xxx]` and `[Run: prll://srn_xxx]` headers. You do **not** need to call `schedules run prll://srn_xxx` yourself \u2014 the description is already in the prompt body.\n\nYour job is to interpret the description and act:\n\n1. Read the description and any `prll://` refs it contains\n2. Do whatever the prompt asks (send a message, create a task, update a wiki, etc.) \u2014 there is no canonical response format\n3. Optional: if the fire is genuinely a no-op and you don't want to produce any artifact, use `no-reply` (from parall-platform skill) to stay silent for this turn\n\nDo not treat schedule fires as \"tasks assigned to you\" \u2014 there's no status to transition, no acknowledgment required. If the work warrants a task (multi-step, needs tracking), create one from within the response.\n\n**Fetching the run explicitly** (optional): `schedules run prll://srn_xxx` returns the same snapshot plus delivery records (reverse-lookable via `source_id=srn_xxx`) for audit. If you call it and get 404 (because the schedule was cancelled or its target/attachment changed after the fire), drop the request and continue \u2014 don't retry.\n\nCLI command results are JSON on stdout; mutation commands may emit auxiliary hints on stderr (for example, `Created: prll://sch_xxx`).\n";
2
2
  //# sourceMappingURL=parall-schedules.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parall-schedules.d.ts","sourceRoot":"","sources":["../../src/skills/parall-schedules.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,44JAuFlC,CAAC"}
1
+ {"version":3,"file":"parall-schedules.d.ts","sourceRoot":"","sources":["../../src/skills/parall-schedules.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,mwKAuFlC,CAAC"}
@@ -35,7 +35,7 @@ parall schedules create \\
35
35
  --run-at <FUTURE_RFC3339_TIME>
36
36
  \`\`\`
37
37
 
38
- \`--target-ids\` is who receives the fire (usually yourself when you're self-scheduling; another agent or human when delegating). \`--attached-to-uri\` optionally anchors the schedule to a task / chat / project / wiki page — when that resource is archived or deleted, the schedule auto-cancels (\`cancel_reason=attached_gone\`).
38
+ \`--target-ids\` is who receives the fire (usually yourself when you're self-scheduling; another agent or human when delegating). \`--attached-to-uri\` optionally anchors the schedule to a task / chat / project / wiki page — when that resource is archived or deleted, the schedule auto-cancels (\`status_reason=attached_gone\`). A schedule whose agent targets all sit on a terminated machine is auto-paused by the platform (\`status_reason=attendee_unreachable\`) instead of firing into a void; resuming a recurring schedule while the machine is still terminated just pauses it again on the next slot (a one-shot resumed past its catch-up window instead follows the normal missed semantics and completes).
39
39
 
40
40
  ### Reminders for someone else
41
41
 
@@ -1,2 +1,2 @@
1
- export declare const PARALL_TASKS_SKILL = "# Parall Tasks\n\nManage tasks and projects via the Parall CLI. Auth and runtime context are pre-configured.\n\n## Finding What's on Someone's Plate (incl. subtasks)\n\nTo answer \"what do I still have to do\", \"what's <person> working on\", or any\n\"open work assigned to X\" question, use `tasks assigned`:\n\n```bash\n# Pending tasks (todo + in_progress) assigned to a member \u2014 INCLUDES subtasks.\nparall tasks assigned prll://usr_xxx # a specific person (e.g. the human who asked)\nparall tasks assigned # yourself (defaults to the authenticated user)\n```\n\nThis is the authoritative \"open work for a person\" query. It returns every\npending task assigned to that member **including subtasks** \u2014 even when the\nsubtask's parent task belongs to someone else. Decomposed work usually lives in\nsubtasks, so do NOT answer this kind of question from `tasks list` alone:\nthat is org-wide, page-capped, and not scoped to a person, so a person's\nsubtasks are easily missed.\n\nResolve a person's `prll://usr_` id from the message context, the members\nlist, or ref search; your own id comes from `parall whoami`.\n\n## Task Commands\n\n```bash\n# List tasks (org-wide; filter by status, assignee, or parent)\nparall tasks list\nparall tasks list --status todo\nparall tasks list --status in_progress\nparall tasks list --assignee-id prll://usr_xxx # first page only (default 20) \u2014 for a person's FULL backlog use 'tasks assigned' above\nparall tasks subtasks prll://tsk_xxx # children of a single parent task\n\n# Create a task (add --parent-id to make it a SUBTASK of another task)\nparall tasks create --title \"Task title\" [--assignee-id prll://usr_xxx] [--parent-id prll://tsk_xxx] [--project-id prll://prj_xxx]\n\n# Update task status\nparall tasks update prll://tsk_xxx --status in_progress\nparall tasks update prll://tsk_xxx --status done\n\n# Add a comment\nparall tasks comments add prll://tsk_xxx --body \"Progress update...\"\n```\n\nSubtasks are just tasks with a parent: create one with `tasks create --parent-id`,\nre-parent with `tasks update --parent-id`, list a parent's children with\n`tasks subtasks`. `tasks list` without `--parent-id` already returns both\ntop-level tasks and subtasks; per-person open work is best fetched with\n`tasks assigned` (above).\n\n## Project Commands\n\n```bash\nparall projects list\n```\n\n## Watching Tasks\n\nWatchers receive dispatch events for a task's new comments. Acting on a task\nauto-subscribes you \u2014 creating it, being assigned, commenting, being\n@mentioned, or substantively editing it (description / assignee). Handle or\ndismiss those comment events deliberately.\n\n```bash\nparall tasks watch prll://tsk_xxx # follow a task without acting on it\nparall tasks unwatch prll://tsk_xxx # opt out of a task's comment events\nparall tasks watchers prll://tsk_xxx # list who is watching\n```\n\nCreators and assignees are locked subscribers \u2014 `unwatch` returns 409 for\nthem until the role changes (e.g. reassignment); it works for every other\nwatcher.\n\n## Responding to Task Assignments\n\nWhen you receive `[Event: task.assigned]`:\n\n1. Acknowledge with a comment: `tasks comments add prll://tsk_xxx --body \"On it\"`\n2. Update status: `tasks update prll://tsk_xxx --status in_progress`\n3. Do the work\n4. Report results in a comment. If a gate remains \u2014 review, merge, deploy,\n requester acceptance \u2014 set `in_review` and name the gate; set `done`\n only once the work has actually landed\n\n## Responding to Task Comments\n\nWhen you receive `[Event: task.comment.created]`, someone commented on a task you are watching. Read the comment body and respond if action is needed:\n\n1. Review the comment content and task context\n2. Reply via comment: `tasks comments add prll://tsk_xxx --body \"Response...\"`\n3. If the comment requests status changes, update accordingly\n\nCLI success output is JSON; errors print a JSON line plus, on a `PERMISSION_DENIED`, an optional plain-text `Request approval:` line \u2014 read both.\n";
1
+ export declare const PARALL_TASKS_SKILL = "# Parall Tasks\n\nManage tasks and projects via the Parall CLI. Auth and runtime context are pre-configured.\n\n## Finding What's on Someone's Plate (incl. subtasks)\n\nTo answer \"what do I still have to do\", \"what's <person> working on\", or any\n\"open work assigned to X\" question, use `tasks assigned`:\n\n```bash\n# Pending tasks (todo + in_progress) assigned to a member \u2014 INCLUDES subtasks.\nparall tasks assigned prll://usr_xxx # a specific person (e.g. the human who asked)\nparall tasks assigned # yourself (defaults to the authenticated user)\n```\n\nThis is the authoritative \"open work for a person\" query. It returns every\npending task assigned to that member **including subtasks** \u2014 even when the\nsubtask's parent task belongs to someone else. Decomposed work usually lives in\nsubtasks, so do NOT answer this kind of question from `tasks list` alone:\nthat is org-wide, page-capped, and not scoped to a person, so a person's\nsubtasks are easily missed.\n\nResolve a person's `prll://usr_` id from the message context, the members\nlist, or ref search; your own id comes from `parall whoami`.\n\n## Task Commands\n\n```bash\n# List tasks (org-wide; filter by status, assignee, or parent)\nparall tasks list\nparall tasks list --status todo\nparall tasks list --status in_progress\nparall tasks list --assignee-id prll://usr_xxx # first page only (default 20) \u2014 for a person's FULL backlog use 'tasks assigned' above\nparall tasks subtasks prll://tsk_xxx # children of a single parent task\n\n# Create a task (add --parent-id to make it a SUBTASK of another task)\nparall tasks create --title \"Task title\" [--assignee-id prll://usr_xxx] [--parent-id prll://tsk_xxx] [--project-id prll://prj_xxx] [--due-date 2026-08-01]\n\n# Update task status \u2014 add --placement end so the task lands at the end of\n# its NEW status column (a bare --status keeps the old column's sort_order)\nparall tasks update prll://tsk_xxx --status in_progress --placement end\nparall tasks update prll://tsk_xxx --status done --placement end\n\n# Due date \u2014 a plain YYYY-MM-DD date (no timestamps); \"none\" clears it\nparall tasks update prll://tsk_xxx --due-date 2026-08-01\nparall tasks update prll://tsk_xxx --due-date none\n\n# Move a task to the end of its status column\nparall tasks update prll://tsk_xxx --placement end\n\n# Add a comment\nparall tasks comments add prll://tsk_xxx --body \"Progress update...\"\n```\n\nOrdering: to append a task to the end of a status column, always use\n`--placement end` \u2014 the server resolves the position atomically. This\nincludes status changes: a bare `--status` keeps the task's old\n`sort_order`, which may collide inside the new column. Do NOT compute a\n`sort_order` value yourself from listed tasks (your view may be stale or\npartial). `--sort-order` is only for pinpoint insertion between two cards\nyou just listed, and it cannot be combined with `--placement`.\n\nSubtasks are just tasks with a parent: create one with `tasks create --parent-id`,\nre-parent with `tasks update --parent-id`, list a parent's children with\n`tasks subtasks`. `tasks list` without `--parent-id` already returns both\ntop-level tasks and subtasks; per-person open work is best fetched with\n`tasks assigned` (above).\n\n## Project Commands\n\n```bash\nparall projects list\n```\n\n## Watching Tasks\n\nWatchers receive dispatch events for a task's new comments. Acting on a task\nauto-subscribes you \u2014 creating it, being assigned, commenting, being\n@mentioned, or substantively editing it (description / assignee). Handle or\ndismiss those comment events deliberately.\n\n```bash\nparall tasks watch prll://tsk_xxx # follow a task without acting on it\nparall tasks unwatch prll://tsk_xxx # opt out of a task's comment events\nparall tasks watchers prll://tsk_xxx # list who is watching\n```\n\nCreators and assignees are locked subscribers \u2014 `unwatch` returns 409 for\nthem until the role changes (e.g. reassignment); it works for every other\nwatcher.\n\n## Responding to Task Assignments\n\nWhen you receive `[Event: task.assigned]`:\n\n1. Acknowledge with a comment: `tasks comments add prll://tsk_xxx --body \"On it\"`\n2. Update status: `tasks update prll://tsk_xxx --status in_progress --placement end`\n3. Do the work\n4. Report results in a comment. If a gate remains \u2014 review, merge, deploy,\n requester acceptance \u2014 set `in_review` and name the gate; set `done`\n only once the work has actually landed\n\n## Responding to Task Comments\n\nWhen you receive `[Event: task.comment.created]`, someone commented on a task you are watching. Read the comment body and respond if action is needed:\n\n1. Review the comment content and task context\n2. Reply via comment: `tasks comments add prll://tsk_xxx --body \"Response...\"`\n3. If the comment requests status changes, update accordingly\n\nCLI success output is JSON; errors print a JSON line plus, on a `PERMISSION_DENIED`, an optional plain-text `Request approval:` line \u2014 read both.\n";
2
2
  //# sourceMappingURL=parall-tasks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parall-tasks.d.ts","sourceRoot":"","sources":["../../src/skills/parall-tasks.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,2/HA+F9B,CAAC"}
1
+ {"version":3,"file":"parall-tasks.d.ts","sourceRoot":"","sources":["../../src/skills/parall-tasks.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,k+JA+G9B,CAAC"}
@@ -34,16 +34,32 @@ parall tasks list --assignee-id prll://usr_xxx # first page only (default 20)
34
34
  parall tasks subtasks prll://tsk_xxx # children of a single parent task
35
35
 
36
36
  # Create a task (add --parent-id to make it a SUBTASK of another task)
37
- parall tasks create --title "Task title" [--assignee-id prll://usr_xxx] [--parent-id prll://tsk_xxx] [--project-id prll://prj_xxx]
37
+ parall tasks create --title "Task title" [--assignee-id prll://usr_xxx] [--parent-id prll://tsk_xxx] [--project-id prll://prj_xxx] [--due-date 2026-08-01]
38
38
 
39
- # Update task status
40
- parall tasks update prll://tsk_xxx --status in_progress
41
- parall tasks update prll://tsk_xxx --status done
39
+ # Update task status — add --placement end so the task lands at the end of
40
+ # its NEW status column (a bare --status keeps the old column's sort_order)
41
+ parall tasks update prll://tsk_xxx --status in_progress --placement end
42
+ parall tasks update prll://tsk_xxx --status done --placement end
43
+
44
+ # Due date — a plain YYYY-MM-DD date (no timestamps); "none" clears it
45
+ parall tasks update prll://tsk_xxx --due-date 2026-08-01
46
+ parall tasks update prll://tsk_xxx --due-date none
47
+
48
+ # Move a task to the end of its status column
49
+ parall tasks update prll://tsk_xxx --placement end
42
50
 
43
51
  # Add a comment
44
52
  parall tasks comments add prll://tsk_xxx --body "Progress update..."
45
53
  \`\`\`
46
54
 
55
+ Ordering: to append a task to the end of a status column, always use
56
+ \`--placement end\` — the server resolves the position atomically. This
57
+ includes status changes: a bare \`--status\` keeps the task's old
58
+ \`sort_order\`, which may collide inside the new column. Do NOT compute a
59
+ \`sort_order\` value yourself from listed tasks (your view may be stale or
60
+ partial). \`--sort-order\` is only for pinpoint insertion between two cards
61
+ you just listed, and it cannot be combined with \`--placement\`.
62
+
47
63
  Subtasks are just tasks with a parent: create one with \`tasks create --parent-id\`,
48
64
  re-parent with \`tasks update --parent-id\`, list a parent's children with
49
65
  \`tasks subtasks\`. \`tasks list\` without \`--parent-id\` already returns both
@@ -78,7 +94,7 @@ watcher.
78
94
  When you receive \`[Event: task.assigned]\`:
79
95
 
80
96
  1. Acknowledge with a comment: \`tasks comments add prll://tsk_xxx --body "On it"\`
81
- 2. Update status: \`tasks update prll://tsk_xxx --status in_progress\`
97
+ 2. Update status: \`tasks update prll://tsk_xxx --status in_progress --placement end\`
82
98
  3. Do the work
83
99
  4. Report results in a comment. If a gate remains — review, merge, deploy,
84
100
  requester acceptance — set \`in_review\` and name the gate; set \`done\`
@@ -0,0 +1,66 @@
1
+ import type { AgentStep, CreateAgentStepRequest, ParallClient } from '@parall/sdk';
2
+ import { type StepDrainOutcome } from './step-retry-queue.js';
3
+ /**
4
+ * AgentStep write path: one place decides inline-write vs park-for-retry.
5
+ *
6
+ * Extracted from the gateway so step persistence is a nameable collaborator
7
+ * instead of more growth in gateway-base.ts. Behavior contract (see
8
+ * AGENTS.md § Step write resilience): every request carries an
9
+ * idempotency_key so retries are replay-safe; transient failures park in the
10
+ * per-session FIFO StepRetryQueue; while a session has parked writes, new
11
+ * writes queue behind them to preserve ledger order; session-stale errors
12
+ * propagate to the caller (the gateway's existing recovery contract).
13
+ */
14
+ export type StepPersisterOpts = {
15
+ client: ParallClient;
16
+ orgId: string;
17
+ agentUserId: string;
18
+ log?: {
19
+ warn: (msg: string) => void;
20
+ };
21
+ /** Session is dead (SESSION_NOT_LIVE / INVALID_TRANSITION 409). */
22
+ isSessionStale: (err: unknown) => boolean;
23
+ /** Retry backoff override (tests). Default: StepRetryQueue's schedule. */
24
+ retryDelaysMs?: number[];
25
+ };
26
+ export declare function isRetryableStepError(err: unknown): boolean;
27
+ export declare class StepPersister {
28
+ private readonly opts;
29
+ private readonly queue;
30
+ private readonly sealed;
31
+ private disposed;
32
+ constructor(opts: StepPersisterOpts);
33
+ /**
34
+ * Write one AgentStep, riding the retry queue on transient failure.
35
+ * Returns the created step, or null when the write was queued or dropped.
36
+ */
37
+ persist(sessionId: string, label: string, req: CreateAgentStepRequest): Promise<AgentStep | null>;
38
+ /**
39
+ * Close the producer end for a session (finalization began): subsequent
40
+ * persist() calls are refused with a warn. Parked/in-flight writes keep
41
+ * retrying — seal gates NEW writes only.
42
+ */
43
+ seal(sessionId: string): void;
44
+ /**
45
+ * Reclaim a seal once its finalization released the session (the seal set
46
+ * must not grow forever on a daemon churning fork sessions). After the
47
+ * session is closed server-side, any late write is rejected there (409).
48
+ */
49
+ unseal(sessionId: string): void;
50
+ /**
51
+ * Resolves when the session's step queue reaches quiescence — see
52
+ * StepRetryQueue.awaitSessionDrained. Non-destructive: parked writes keep
53
+ * retrying on their normal schedule while awaited.
54
+ */
55
+ drainSession(sessionId: string): Promise<StepDrainOutcome>;
56
+ /** DESTRUCTIVE: discards the session's parked writes (server-terminal only). */
57
+ dropSession(sessionId: string): void;
58
+ /** Every session is dead (server closed them all — new_session). */
59
+ dropAllSessions(): void;
60
+ /** Best-effort single pass over parked writes; returns items still parked. */
61
+ flush(deadlineMs: number): Promise<number>;
62
+ pendingTotal(): number;
63
+ dispose(): void;
64
+ private enqueue;
65
+ }
66
+ //# sourceMappingURL=step-persister.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"step-persister.d.ts","sourceRoot":"","sources":["../src/step-persister.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEnF,OAAO,EAAE,KAAK,gBAAgB,EAAkB,MAAM,uBAAuB,CAAC;AAE9E;;;;;;;;;;GAUG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;IACtC,mEAAmE;IACnE,cAAc,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IAC1C,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAI1D;AAED,qBAAa,aAAa;IAOZ,OAAO,CAAC,QAAQ,CAAC,IAAI;IANjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IAGvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAS;gBAEI,IAAI,EAAE,iBAAiB;IAcpD;;;OAGG;IACG,OAAO,CACX,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,sBAAsB,GAC1B,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAuC5B;;;;OAIG;IACH,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAI7B;;;;OAIG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAI/B;;;;OAIG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAI1D,gFAAgF;IAChF,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKpC,oEAAoE;IACpE,eAAe,IAAI,IAAI;IAKvB,8EAA8E;IAC9E,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI1C,YAAY,IAAI,MAAM;IAItB,OAAO,IAAI,IAAI;IAKf,OAAO,CAAC,OAAO;CAOhB"}
@@ -0,0 +1,116 @@
1
+ import { ApiError } from '@parall/sdk';
2
+ import { StepRetryQueue } from './step-retry-queue.js';
3
+ export function isRetryableStepError(err) {
4
+ // ApiError status 0 = fetch-level failure (timeout / network); 5xx =
5
+ // server-side transient. 4xx are contract errors a retry cannot fix.
6
+ return err instanceof ApiError && (err.status === 0 || err.status >= 500);
7
+ }
8
+ export class StepPersister {
9
+ opts;
10
+ queue;
11
+ // Sessions whose producer end is closed (finalization in progress) — new
12
+ // writes are refused instead of racing the drain/close sequence.
13
+ sealed = new Set();
14
+ disposed = false;
15
+ constructor(opts) {
16
+ this.opts = opts;
17
+ this.queue = new StepRetryQueue({
18
+ log: opts.log,
19
+ isRetryable: isRetryableStepError,
20
+ isSessionStale: opts.isSessionStale,
21
+ retryDelaysMs: opts.retryDelaysMs,
22
+ // Inline step writes already drive full stale recovery; a background
23
+ // retry discovering it just drops the dead session's queue — the next
24
+ // dispatch hits the same 409 inline and heals.
25
+ onSessionStale: (sessionId, err) => opts.log?.warn(`queued step hit stale session ${sessionId}: ${String(err)}`),
26
+ });
27
+ }
28
+ /**
29
+ * Write one AgentStep, riding the retry queue on transient failure.
30
+ * Returns the created step, or null when the write was queued or dropped.
31
+ */
32
+ async persist(sessionId, label, req) {
33
+ if (this.disposed) {
34
+ // Absorbing, like the queue and the lifecycle coordinator: a dispatch
35
+ // still draining past the shutdown deadline must not race the exiting
36
+ // process with fresh inline writes — its WorkItem is un-acked, so the
37
+ // re-drive on the replacement pod rewrites these steps idempotently.
38
+ this.opts.log?.warn(`refusing ${label} step after dispose (shutting down)`);
39
+ return null;
40
+ }
41
+ if (this.sealed.has(sessionId)) {
42
+ this.opts.log?.warn(`refusing ${label} step for sealed session ${sessionId} (finalization in progress)`);
43
+ return null;
44
+ }
45
+ if (this.queue.hasPending(sessionId)) {
46
+ // Preserve per-session FIFO while degraded.
47
+ this.enqueue(sessionId, label, req);
48
+ return null;
49
+ }
50
+ try {
51
+ return await this.opts.client.createAgentStep(this.opts.orgId, this.opts.agentUserId, sessionId, req);
52
+ }
53
+ catch (err) {
54
+ if (this.opts.isSessionStale(err))
55
+ throw err;
56
+ if (isRetryableStepError(err)) {
57
+ this.opts.log?.warn(`failed to create ${label} step (queued for retry): ${String(err)}`);
58
+ this.enqueue(sessionId, label, req);
59
+ }
60
+ else {
61
+ this.opts.log?.warn(`failed to create ${label} step: ${String(err)}`);
62
+ }
63
+ return null;
64
+ }
65
+ }
66
+ /**
67
+ * Close the producer end for a session (finalization began): subsequent
68
+ * persist() calls are refused with a warn. Parked/in-flight writes keep
69
+ * retrying — seal gates NEW writes only.
70
+ */
71
+ seal(sessionId) {
72
+ this.sealed.add(sessionId);
73
+ }
74
+ /**
75
+ * Reclaim a seal once its finalization released the session (the seal set
76
+ * must not grow forever on a daemon churning fork sessions). After the
77
+ * session is closed server-side, any late write is rejected there (409).
78
+ */
79
+ unseal(sessionId) {
80
+ this.sealed.delete(sessionId);
81
+ }
82
+ /**
83
+ * Resolves when the session's step queue reaches quiescence — see
84
+ * StepRetryQueue.awaitSessionDrained. Non-destructive: parked writes keep
85
+ * retrying on their normal schedule while awaited.
86
+ */
87
+ drainSession(sessionId) {
88
+ return this.queue.awaitSessionDrained(sessionId);
89
+ }
90
+ /** DESTRUCTIVE: discards the session's parked writes (server-terminal only). */
91
+ dropSession(sessionId) {
92
+ this.sealed.delete(sessionId);
93
+ this.queue.dropSession(sessionId);
94
+ }
95
+ /** Every session is dead (server closed them all — new_session). */
96
+ dropAllSessions() {
97
+ this.sealed.clear();
98
+ this.queue.dropAllSessions();
99
+ }
100
+ /** Best-effort single pass over parked writes; returns items still parked. */
101
+ flush(deadlineMs) {
102
+ return this.queue.flush(deadlineMs);
103
+ }
104
+ pendingTotal() {
105
+ return this.queue.pendingTotal();
106
+ }
107
+ dispose() {
108
+ this.disposed = true;
109
+ this.queue.dispose();
110
+ }
111
+ enqueue(sessionId, label, req) {
112
+ this.queue.enqueue(sessionId, label, () => this.opts.client
113
+ .createAgentStep(this.opts.orgId, this.opts.agentUserId, sessionId, req)
114
+ .then(() => undefined));
115
+ }
116
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Per-session FIFO retry queue for AgentStep writes.
3
+ *
4
+ * Step creation runs serially inside the dispatch drain loop, so a blocking
5
+ * retry there would stall healthy runtime work behind a degraded api link
6
+ * (the 2026-07-10 window: model calls fine, every fresh parall HTTPS
7
+ * handshake timing out). Instead, a failed write is parked here and retried
8
+ * in the background with backoff; while a session has parked items, new
9
+ * writes for that session are appended behind them so ledger order is
10
+ * preserved. All step requests carry an `idempotency_key`, which makes
11
+ * retries safe even when the original attempt landed server-side but its
12
+ * response was lost.
13
+ *
14
+ * In-memory only — a process restart drops parked items (the ledger hole is
15
+ * then no worse than the pre-queue behavior). Items are dropped, loudly, when
16
+ * they exceed `maxAgeMs` or the per-session cap.
17
+ */
18
+ export type StepRetryQueueOpts = {
19
+ log?: {
20
+ warn: (msg: string) => void;
21
+ };
22
+ /** Transient failures worth retrying (timeouts, network, 5xx). */
23
+ isRetryable: (err: unknown) => boolean;
24
+ /** Session is dead (SESSION_NOT_LIVE / INVALID_TRANSITION) — drop its queue. */
25
+ isSessionStale: (err: unknown) => boolean;
26
+ /** Invoked once when a retry discovers a stale session. */
27
+ onSessionStale?: (sessionId: string, err: unknown) => void;
28
+ /** Drop an item once it has been parked this long. Default 10 minutes. */
29
+ maxAgeMs?: number;
30
+ /** Per-session cap; enqueue beyond it drops the NEW item. Default 200. */
31
+ maxQueueLength?: number;
32
+ /** Backoff schedule override (tests). Default 5s→10s→20s→40s→60s. */
33
+ retryDelaysMs?: number[];
34
+ };
35
+ /**
36
+ * How a session's queue reached quiescence: `drained` — every parked item
37
+ * settled (written, or dropped loudly by age/cap/non-retryable policy);
38
+ * `dropped` — the queue was discarded because the server considers the
39
+ * session terminal (stale 409, `agent.new_session`); `disposed` — the queue
40
+ * was shut down mid-wait.
41
+ */
42
+ export type StepDrainOutcome = 'drained' | 'dropped' | 'disposed';
43
+ export declare class StepRetryQueue {
44
+ private readonly opts;
45
+ private readonly queues;
46
+ private readonly timers;
47
+ private readonly drains;
48
+ private readonly drainWaiters;
49
+ private readonly retryDelays;
50
+ private disposed;
51
+ constructor(opts: StepRetryQueueOpts);
52
+ hasPending(sessionId: string): boolean;
53
+ pendingCount(sessionId: string): number;
54
+ enqueue(sessionId: string, label: string, exec: () => Promise<void>): void;
55
+ dropSession(sessionId: string): void;
56
+ /**
57
+ * Resolves when this session's queue reaches quiescence: every parked item
58
+ * settled (`drained` — including items dropped loudly by the age/cap/
59
+ * non-retryable policy), the queue was discarded because the server
60
+ * considers the session terminal (`dropped`), or the queue was disposed
61
+ * (`disposed`). Retries keep riding the normal backoff schedule while a
62
+ * caller waits — this only OBSERVES the queue, it never accelerates or
63
+ * abandons it.
64
+ */
65
+ awaitSessionDrained(sessionId: string): Promise<StepDrainOutcome>;
66
+ pendingTotal(): number;
67
+ /**
68
+ * One best-effort pass over every parked item — no backoff waits. Used at
69
+ * graceful shutdown: parked items are process-local (their WorkItems
70
+ * already resolved, so restart catch-up will NOT re-drive them), and the
71
+ * common shutdown (idle-stop, deploy) happens on a healthy network where
72
+ * the writes just succeed. A session whose head still fails retryably is
73
+ * skipped (its remaining items would fail the same way). A drain already in
74
+ * flight when flush arrives is JOINED (drain() returns the shared promise),
75
+ * so flush waits for its settle within the deadline instead of misreading
76
+ * "no progress" and giving up early. `deadlineMs` is a HARD cap: a write
77
+ * still in flight at the deadline is abandoned to the background (it is
78
+ * idempotent; dispose() right after clears any re-arm) and flush returns.
79
+ * Returns the number of items still parked.
80
+ */
81
+ flush(deadlineMs: number): Promise<number>;
82
+ /** Every tracked session is dead (server closed them all — new_session). */
83
+ dropAllSessions(): void;
84
+ dispose(): void;
85
+ private schedule;
86
+ private drain;
87
+ private drainOnce;
88
+ private rearm;
89
+ private resolveDrainWaiters;
90
+ }
91
+ //# sourceMappingURL=step-retry-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"step-retry-queue.d.ts","sourceRoot":"","sources":["../src/step-retry-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;IACtC,kEAAkE;IAClE,WAAW,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IACvC,gFAAgF;IAChF,cAAc,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IAC1C,2DAA2D;IAC3D,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3D,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B,CAAC;AASF;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAgBlE,qBAAa,cAAc;IAgBb,OAAO,CAAC,QAAQ,CAAC,IAAI;IAfjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAC1D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqC;IAQ5D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;IAE3D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiE;IAC9F,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAW;IACvC,OAAO,CAAC,QAAQ,CAAS;gBAEI,IAAI,EAAE,kBAAkB;IAIrD,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAItC,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAIvC,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAe1E,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAYpC;;;;;;;;OAQG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAYjE,YAAY,IAAI,MAAM;IAMtB;;;;;;;;;;;;;OAaG;IACG,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAuBhD,4EAA4E;IAC5E,eAAe,IAAI,IAAI;IAIvB,OAAO,IAAI,IAAI;IAUf,OAAO,CAAC,QAAQ;IAchB,OAAO,CAAC,KAAK;YAeC,SAAS;IA4CvB,OAAO,CAAC,KAAK;IAkBb,OAAO,CAAC,mBAAmB;CAM5B"}