@chorus-aidlc/chorus-openclaw-plugin 0.16.2 → 0.17.1

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.
@@ -14,10 +14,12 @@
14
14
  // The two files are kept in lock-step by the spec's "single source of truth for
15
15
  // the payload shapes" requirement; a drift would be caught by T5 (live e2e).
16
16
  //
17
- // The five operations and their EXACT server payload shapes (verified against
18
- // cli/daemon-rest-client.mjs + src/app/api/daemon/*/route.ts — server unchanged):
17
+ // The operations and payload fields this client supports (kept aligned with
18
+ // cli/daemon-rest-client.mjs and accepted by src/app/api/daemon/*/route.ts):
19
19
  // turnAdvance → POST /api/daemon/turn-advance
20
- // { connectionUuid, sessionId, status, entityType?, entityUuid? }
20
+ // { connectionUuid, sessionId, status, turnUuid?,
21
+ // entityType?, entityUuid?, interruptedReason?,
22
+ // transcriptRelayError?, usage? }
21
23
  // transcript → POST /api/daemon/transcript
22
24
  // { sessionId, messages: [{ role, text }] }
23
25
  // executionState → POST /api/daemon/execution-state
@@ -27,6 +29,8 @@
27
29
  // startedAt|null }] }
28
30
  // reportInterrupt → POST /api/daemon/report-interrupt
29
31
  // { connectionUuid, entityType, entityUuid, reason }
32
+ // heartbeat → POST /api/daemon/connection-heartbeat
33
+ // { connectionUuid, connectedAt }
30
34
  // readPendingTurns → GET /api/daemon/pending-turns?connectionUuid=…
31
35
  // → { turns: [{ turnUuid, sessionId, directIdeaUuid, trigger,
32
36
  // promptText }] }
@@ -129,6 +133,7 @@ export interface CreateDaemonRestClientOptions {
129
133
  export interface DaemonRestClient {
130
134
  turnAdvance(p: {
131
135
  sessionId: string;
136
+ turnUuid?: string | null;
132
137
  status: "running" | "ended" | "interrupted";
133
138
  entityType?: string | null;
134
139
  entityUuid?: string | null;
@@ -136,7 +141,7 @@ export interface DaemonRestClient {
136
141
  transcriptRelayError?: string | null;
137
142
  // Per-turn token usage (daemon-token-usage); sent only on a terminal edge.
138
143
  usage?: TokenUsage | null;
139
- }): Promise<DaemonRestResult>;
144
+ }): Promise<DaemonRestResult<{ turnUuid: string }>>;
140
145
  transcript(p: {
141
146
  sessionId: string;
142
147
  messages: DaemonTranscriptMessage[];
@@ -172,13 +177,14 @@ export function createDaemonRestClient(opts: CreateDaemonRestClientOptions): Dae
172
177
  * IDENTICAL across all four POST endpoints; only the `op` label and the path
173
178
  * differ. Never throws — returns a structured {@link DaemonRestResult}.
174
179
  */
175
- async function post(
180
+ async function post<TData = unknown>(
176
181
  op: string,
177
182
  path: string,
178
183
  body: unknown,
179
184
  successLog?: string,
180
185
  context = "",
181
- ): Promise<DaemonRestResult> {
186
+ readData = false,
187
+ ): Promise<DaemonRestResult<TData>> {
182
188
  let response: Response;
183
189
  try {
184
190
  response = await fetchImpl(`${url}${path}`, {
@@ -198,8 +204,17 @@ export function createDaemonRestClient(opts: CreateDaemonRestClientOptions): Dae
198
204
  logger.warn(`[Chorus] ${error}`);
199
205
  return { ok: false, status: response.status, error };
200
206
  }
207
+ let data: TData | undefined;
208
+ if (readData) {
209
+ try {
210
+ const parsed = (await response.json()) as { data?: TData } | null;
211
+ data = parsed && typeof parsed === "object" ? parsed.data : undefined;
212
+ } catch (err) {
213
+ logger.warn(`[Chorus] ${op} response correlation unavailable: ${err}`);
214
+ }
215
+ }
201
216
  if (successLog) logger.info(`[Chorus] ${successLog}`);
202
- return { ok: true, status: response.status };
217
+ return { ok: true, status: response.status, ...(data !== undefined ? { data } : {}) };
203
218
  }
204
219
 
205
220
  return {
@@ -211,7 +226,7 @@ export function createDaemonRestClient(opts: CreateDaemonRestClientOptions): Dae
211
226
  * normalized `usage` (daemon-token-usage) — byte-for-byte the CLI client's shape.
212
227
  * Requires the connectionUuid.
213
228
  */
214
- async turnAdvance({ sessionId, status, entityType, entityUuid, interruptedReason, transcriptRelayError, usage }) {
229
+ async turnAdvance({ sessionId, turnUuid, status, entityType, entityUuid, interruptedReason, transcriptRelayError, usage }) {
215
230
  const connectionUuid = getConnectionUuid();
216
231
  if (!connectionUuid) {
217
232
  const error = `cannot advance turn for session ${sessionId} → ${status} — no connection uuid yet`;
@@ -225,6 +240,7 @@ export function createDaemonRestClient(opts: CreateDaemonRestClientOptions): Dae
225
240
  connectionUuid,
226
241
  sessionId,
227
242
  status,
243
+ ...(turnUuid ? { turnUuid } : {}),
228
244
  // Only sent when BOTH are present, so the server never gets a partial linkage.
229
245
  ...(entityType && entityUuid ? { entityType, entityUuid } : {}),
230
246
  // Only meaningful alongside status=interrupted; never sent otherwise.
@@ -234,12 +250,24 @@ export function createDaemonRestClient(opts: CreateDaemonRestClientOptions): Dae
234
250
  // The whole normalized TokenUsage object, nested under `usage`, only on a terminal edge.
235
251
  ...(usage && isTerminal ? { usage } : {}),
236
252
  };
237
- return post(
253
+ const result = await post<{ turn?: { uuid?: unknown } }>(
238
254
  "turn-advance",
239
255
  "/api/daemon/turn-advance",
240
256
  body,
241
257
  `advanced turn for session ${sessionId} → ${status}`,
258
+ "",
259
+ status === "running",
242
260
  );
261
+ const resolvedTurnUuid =
262
+ typeof result.data?.turn?.uuid === "string" ? result.data.turn.uuid : null;
263
+ if (status !== "running") {
264
+ const { data: _rawData, ...baseResult } = result;
265
+ return baseResult;
266
+ }
267
+ const { data: _rawData, ...baseResult } = result;
268
+ return resolvedTurnUuid
269
+ ? { ...baseResult, data: { turnUuid: resolvedTurnUuid } }
270
+ : baseResult;
243
271
  },
244
272
 
245
273
  /**
@@ -332,12 +332,23 @@ export class ChorusEventRouter {
332
332
  );
333
333
  }
334
334
 
335
+ /**
336
+ * An idea was assigned to this agent. The assignment ALREADY set the assignee, so there is
337
+ * nothing to claim — chorus_claim_idea would throw (AlreadyClaimedError, and a hard "Cannot
338
+ * claim an elaborated Idea" for the elaborated-backfill case). Do NOT emit a claim
339
+ * instruction; tell the already-assigned agent to review and advance from the idea's CURRENT
340
+ * stage, stopping at the human proposal/verify gates, and name the assigner via
341
+ * n.actorName / n.actorType. Mirrors the daemon's cli/prompts.mjs `idea_claimed` case
342
+ * (plugin voice; no headless preamble) — KEEP THE TWO WORDINGS IN SYNC.
343
+ */
335
344
  private handleIdeaClaimed(n: NotificationDetail, attr: WakeAttribution): void {
336
345
  const mentionGuidance = this.buildMentionGuidance(n, "idea");
337
346
 
338
347
  this.wakeWithHandoff(
339
- `[Chorus] Idea '${n.entityTitle}' has been assigned to you (ideaUuid: ${n.entityUuid}, projectUuid: ${n.projectUuid}). ` +
340
- `Use chorus_get_idea to review the idea, then chorus_claim_idea to start elaboration.\n` +
348
+ `[Chorus] Idea '${n.entityTitle}' was assigned to you by ${n.actorName} (${n.actorType}) (ideaUuid: ${n.entityUuid}, projectUuid: ${n.projectUuid}). ` +
349
+ `You are now the assignee — no need to claim it. Use chorus_get_idea to review it, then advance it from its CURRENT stage: ` +
350
+ `if it is still elaborating, run or continue elaboration; if it is already elaborated, author the proposal. ` +
351
+ `Stop at the human proposal/verify gates and never merge automatically.\n` +
341
352
  mentionGuidance,
342
353
  this.contextKeyFor("idea_claimed", n.entityUuid),
343
354
  n,