canopy-ui 0.15.2 → 0.15.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canopy-ui",
3
- "version": "0.15.2",
3
+ "version": "0.15.3",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -186,7 +186,7 @@ export interface SessionMenu {
186
186
  * any of it and simply showed an empty panel.
187
187
  */
188
188
  export interface TurnStatus {
189
- /** `picking_up` | `waiting_runner` | `unrouted` | `working` | `blocked`
189
+ /** `picking_up` | `queued_behind` | `waiting_runner` | `unrouted` | `working` | `blocked`
190
190
  * | `paused` | `done` | `cancelled` | `missed` | `failed` | `lost`.
191
191
  *
192
192
  * Deliberately a string rather than a union: an older client meeting a
@@ -212,6 +212,10 @@ export interface TurnStatus {
212
212
  menu_pending: boolean;
213
213
  /** Why a `failed` turn failed, in the runner's words. Null for every other
214
214
  * state. Absent on an older server. */
215
+ /** For `queued_behind`: what it waits behind — `session` (the previous
216
+ * message in this conversation) or `agent` (another turn of the same
217
+ * agent). Absent on an older server. */
218
+ behind?: "session" | "agent" | null;
215
219
  detail?: string | null;
216
220
  /** When a settled turn finished — lets the kit drop a failure the
217
221
  * conversation has since moved past. Absent on an older server. */
@@ -72,6 +72,16 @@ describe("pendingLabel", () => {
72
72
  expect(pendingLabel({ status: status({ state: "working" }) })).toBe("Thinking on jj-mbp…");
73
73
  });
74
74
 
75
+ it("says a follow-up is queued behind the turn ahead, not being picked up (#1147)", () => {
76
+ const s = status({ state: "queued_behind", behind: "session", claimed_by: null });
77
+ expect(agentHasFloor({ status: s })).toBe(true);
78
+ expect(pendingLabel({ status: s })).toBe("Queued behind the previous message on jj-mbp…");
79
+ expect(pendingLabel({ status: { ...s, behind: "agent" } })).toBe(
80
+ "Queued behind the agent's current turn on jj-mbp…",
81
+ );
82
+ expect(turnNotice(s)).toBeNull();
83
+ });
84
+
75
85
  it("names a pinned runner as a decision, not a queue position", () => {
76
86
  const s = status({ state: "picking_up", pinned: true, runners: ["cloud-1"] });
77
87
  expect(pendingLabel({ status: s })).toBe("Sent to cloud-1…");
@@ -41,8 +41,8 @@ export function agentHasFloor({
41
41
  if (activity === "blocked") return false;
42
42
  if (status) {
43
43
  if (status.settled || status.stuck) return false;
44
- // `picking_up` and `working` are the two live states, and both mean a
45
- // reply is genuinely coming.
44
+ // `picking_up`, `queued_behind` and `working` are the live states, and
45
+ // each means a reply is genuinely coming.
46
46
  return true;
47
47
  }
48
48
  return Boolean(awaitingReply) || activity === "working";
@@ -65,6 +65,13 @@ export function pendingLabel({
65
65
  if (status?.state === "working") {
66
66
  return status.claimed_by ? `Thinking on ${status.claimed_by}…` : "Thinking…";
67
67
  }
68
+ if (status?.state === "queued_behind") {
69
+ // A live runner will take it, but only once the turn ahead ends — saying
70
+ // "picking this up" made a follow-up sent mid-turn look lost (#1147).
71
+ const [first] = status.runners;
72
+ const ahead = status.behind === "session" ? "the previous message" : "the agent's current turn";
73
+ return first ? `Queued behind ${ahead} on ${first}…` : `Queued behind ${ahead}…`;
74
+ }
68
75
  if (status?.state === "picking_up") {
69
76
  const [first] = status.runners;
70
77
  if (status.pinned && first) return `Sent to ${first}…`;