cccc-sdk 0.4.4 → 0.4.40

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 (58) hide show
  1. package/README.md +252 -16
  2. package/dist/client.d.ts +87 -163
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +663 -536
  5. package/dist/client.js.map +1 -1
  6. package/dist/client_0430_admin_ops.d.ts +56 -0
  7. package/dist/client_0430_admin_ops.d.ts.map +1 -0
  8. package/dist/client_0430_admin_ops.js +143 -0
  9. package/dist/client_0430_admin_ops.js.map +1 -0
  10. package/dist/client_0430_assistant_ops.d.ts +106 -0
  11. package/dist/client_0430_assistant_ops.d.ts.map +1 -0
  12. package/dist/client_0430_assistant_ops.js +122 -0
  13. package/dist/client_0430_assistant_ops.js.map +1 -0
  14. package/dist/client_0430_memory_ops.d.ts +14 -0
  15. package/dist/client_0430_memory_ops.d.ts.map +1 -0
  16. package/dist/client_0430_memory_ops.js +84 -0
  17. package/dist/client_0430_memory_ops.js.map +1 -0
  18. package/dist/client_0430_ops.d.ts +8 -0
  19. package/dist/client_0430_ops.d.ts.map +1 -0
  20. package/dist/client_0430_ops.js +9 -0
  21. package/dist/client_0430_ops.js.map +1 -0
  22. package/dist/client_0430_shared.d.ts +15 -0
  23. package/dist/client_0430_shared.d.ts.map +1 -0
  24. package/dist/client_0430_shared.js +4 -0
  25. package/dist/client_0430_shared.js.map +1 -0
  26. package/dist/client_0434_ops.d.ts +10 -0
  27. package/dist/client_0434_ops.d.ts.map +1 -0
  28. package/dist/client_0434_ops.js +43 -0
  29. package/dist/client_0434_ops.js.map +1 -0
  30. package/dist/client_chat_ops.d.ts +28 -0
  31. package/dist/client_chat_ops.d.ts.map +1 -0
  32. package/dist/client_chat_ops.js +213 -0
  33. package/dist/client_chat_ops.js.map +1 -0
  34. package/dist/client_connect_ops.d.ts +9 -0
  35. package/dist/client_connect_ops.d.ts.map +1 -0
  36. package/dist/client_connect_ops.js +44 -0
  37. package/dist/client_connect_ops.js.map +1 -0
  38. package/dist/client_group_space_ops.d.ts +24 -0
  39. package/dist/client_group_space_ops.d.ts.map +1 -0
  40. package/dist/client_group_space_ops.js +167 -0
  41. package/dist/client_group_space_ops.js.map +1 -0
  42. package/dist/errors.d.ts +17 -1
  43. package/dist/errors.d.ts.map +1 -1
  44. package/dist/errors.js +26 -1
  45. package/dist/errors.js.map +1 -1
  46. package/dist/index.d.ts +4 -4
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +3 -3
  49. package/dist/index.js.map +1 -1
  50. package/dist/transport.d.ts +4 -3
  51. package/dist/transport.d.ts.map +1 -1
  52. package/dist/transport.js +236 -120
  53. package/dist/transport.js.map +1 -1
  54. package/dist/types.d.ts +752 -89
  55. package/dist/types.d.ts.map +1 -1
  56. package/dist/types.js +7 -3
  57. package/dist/types.js.map +1 -1
  58. package/package.json +3 -3
package/dist/client.js CHANGED
@@ -1,9 +1,13 @@
1
1
  /**
2
2
  * CCCC SDK client
3
3
  */
4
- import { DaemonAPIError, IncompatibleDaemonError, ErrorCodes, } from './errors.js';
5
- import { isStreamEvent } from './types.js';
4
+ import { installConnectOps } from './client_connect_ops.js';
5
+ import { DaemonAPIError, DaemonConnectionError, IncompatibleDaemonError, } from './errors.js';
6
6
  import { discoverEndpoint, callDaemon, openEventsStream, readLines, } from './transport.js';
7
+ import { installCCCC0430Ops } from './client_0430_ops.js';
8
+ import { installCCCC0434Ops } from './client_0434_ops.js';
9
+ import { installGroupSpaceOps } from './client_group_space_ops.js';
10
+ import { installChatOps } from './client_chat_ops.js';
7
11
  function compactRecord(input) {
8
12
  return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
9
13
  }
@@ -17,9 +21,11 @@ function compactRecord(input) {
17
21
  * ```
18
22
  */
19
23
  export class CCCCClient {
20
- constructor(endpoint, timeoutMs) {
24
+ constructor(endpoint, timeoutMs, ccccHome, endpointExplicit) {
21
25
  this._endpoint = endpoint;
22
26
  this._timeoutMs = timeoutMs;
27
+ this._ccccHome = ccccHome;
28
+ this._endpointExplicit = endpointExplicit;
23
29
  }
24
30
  /**
25
31
  * Create a new client instance, auto-discovering the daemon endpoint.
@@ -30,7 +36,7 @@ export class CCCCClient {
30
36
  static async create(options = {}) {
31
37
  const endpoint = options.endpoint ?? await discoverEndpoint(options.ccccHome);
32
38
  const timeoutMs = options.timeoutMs ?? 30000;
33
- return new CCCCClient(endpoint, timeoutMs);
39
+ return new CCCCClient(endpoint, timeoutMs, options.ccccHome, options.endpoint !== undefined);
34
40
  }
35
41
  /** The resolved daemon endpoint this client connects to. */
36
42
  get endpoint() {
@@ -53,24 +59,38 @@ export class CCCCClient {
53
59
  op,
54
60
  args: args ?? {},
55
61
  };
56
- const response = await callDaemon(this._endpoint, request, this._timeoutMs);
62
+ let response;
63
+ try {
64
+ response = await callDaemon(this._endpoint, request, this._timeoutMs);
65
+ }
66
+ catch (error) {
67
+ if (!(error instanceof DaemonConnectionError) || this._endpointExplicit) {
68
+ throw error;
69
+ }
70
+ // Rediscovery is safe only because DaemonConnectionError means that no
71
+ // request bytes were written. Exchange failures are never replayed.
72
+ this._endpoint = await discoverEndpoint(this._ccccHome);
73
+ response = await callDaemon(this._endpoint, request, this._timeoutMs);
74
+ }
75
+ if (response.v !== undefined && response.v !== 1) {
76
+ throw new IncompatibleDaemonError(`Daemon response uses unsupported IPC version: ${response.v}`);
77
+ }
57
78
  if (!response.ok) {
58
- throw new DaemonAPIError(response.error?.code ?? 'error', response.error?.message ?? 'daemon error', response.error?.details ?? {}, response);
79
+ throw new DaemonAPIError(response.error?.code ?? 'error', response.error?.message ?? 'daemon returned ok=false without an error', response.error?.details ?? {}, response);
59
80
  }
60
81
  return response;
61
82
  }
62
83
  /**
63
84
  * Send an IPC request and return only the result payload.
64
- * @typeParam T - Expected result type (defaults to `Record<string, unknown>`).
65
85
  * @param op - The IPC operation name.
66
86
  * @param args - Operation arguments.
67
- * @returns The `result` field from the daemon response, cast to `T`.
87
+ * @returns The `result` field from the daemon response (empty object if absent).
68
88
  * @throws {DaemonAPIError} If the daemon returns an error.
69
89
  * @throws {DaemonUnavailableError} If the connection fails.
70
90
  */
71
91
  async call(op, args) {
72
92
  const response = await this.callRaw(op, args);
73
- return (response.result ?? {});
93
+ return response.result ?? {};
74
94
  }
75
95
  /**
76
96
  * Assert that the connected daemon meets the caller's compatibility requirements.
@@ -82,8 +102,12 @@ export class CCCCClient {
82
102
  */
83
103
  async assertCompatible(options = {}) {
84
104
  const pingResult = await this.ping();
85
- const ipcV = pingResult.ipc_v ?? 0;
86
- const capabilities = pingResult.capabilities ?? {};
105
+ const rawIpcV = pingResult['ipc_v'];
106
+ const ipcV = typeof rawIpcV === 'number' ? rawIpcV : 0;
107
+ const rawCaps = pingResult['capabilities'];
108
+ const capabilities = (rawCaps !== null && typeof rawCaps === 'object' && !Array.isArray(rawCaps))
109
+ ? rawCaps
110
+ : {};
87
111
  // Check IPC version
88
112
  const requiredV = options.requireIpcV ?? 1;
89
113
  if (ipcV < requiredV) {
@@ -95,19 +119,83 @@ export class CCCCClient {
95
119
  throw new IncompatibleDaemonError(`Missing capability: ${cap}`);
96
120
  }
97
121
  }
98
- // Check operation support by probing
99
- const reservedOps = new Set(['ping', 'shutdown', 'events_stream', 'term_attach']);
100
- for (const op of options.requireOps ?? []) {
101
- if (reservedOps.has(op))
122
+ // Probe only audited operations with harmless empty arguments.
123
+ const safeEmptyProbes = new Set([
124
+ 'groups',
125
+ 'group_show',
126
+ 'group_preamble_get',
127
+ 'group_preamble_set',
128
+ 'group_preamble_reset',
129
+ 'send',
130
+ 'tracked_send',
131
+ 'send_files',
132
+ 'reply',
133
+ 'inbox_peek',
134
+ 'inbox_read',
135
+ 'context_get',
136
+ 'context_sync',
137
+ 'message_deliver',
138
+ 'message_history',
139
+ 'reply_request_cancel',
140
+ 'send_cross_group',
141
+ 'memory_search',
142
+ 'memory_get',
143
+ 'memory_write',
144
+ 'memory_profile_get',
145
+ 'memory_health',
146
+ 'actor_new_session',
147
+ 'group_reset',
148
+ 'group_copy_export_file',
149
+ 'terminal_history',
150
+ 'terminal_since',
151
+ 'terminal_snapshot',
152
+ 'term_resize',
153
+ 'web_model_delivery_preferences_get',
154
+ 'web_model_delivery_preferences_update',
155
+ 'web_model_runtime_recover_turn',
156
+ 'events_stream',
157
+ 'connect_catalog',
158
+ 'connect_send',
159
+ 'connect_send_files',
160
+ ]);
161
+ for (const requestedOp of options.requireOps ?? []) {
162
+ const op = requestedOp.trim();
163
+ if (op === 'ping')
102
164
  continue;
165
+ if (capabilities[op] === false) {
166
+ throw new IncompatibleDaemonError(`Operation not supported: ${op}`);
167
+ }
168
+ if (!safeEmptyProbes.has(op)) {
169
+ if (capabilities[op] === true)
170
+ continue;
171
+ throw new IncompatibleDaemonError(`Cannot safely verify operation: ${op}; no advertised capability or safe probe`);
172
+ }
103
173
  try {
104
174
  await this.callRaw(op, {});
105
175
  }
106
176
  catch (e) {
107
- if (e instanceof DaemonAPIError && e.code === ErrorCodes.UNKNOWN_OP) {
177
+ if (e instanceof DaemonAPIError && e.code === 'unknown_op') {
178
+ if (op === 'term_resize') {
179
+ try {
180
+ await this.callRaw('terminal_resize', {});
181
+ continue;
182
+ }
183
+ catch (aliasError) {
184
+ if (aliasError instanceof DaemonAPIError) {
185
+ if (aliasError.code !== 'unknown_op') {
186
+ continue;
187
+ }
188
+ }
189
+ else {
190
+ throw aliasError;
191
+ }
192
+ }
193
+ }
108
194
  throw new IncompatibleDaemonError(`Operation not supported: ${op}`);
109
195
  }
110
- // Other errors (e.g. missing_group_id) imply the operation exists.
196
+ if (!(e instanceof DaemonAPIError))
197
+ throw e;
198
+ // A structured daemon rejection (e.g. missing_group_id) proves recognition.
111
199
  }
112
200
  }
113
201
  return pingResult;
@@ -164,6 +252,17 @@ export class CCCCClient {
164
252
  async groupDelete(groupId, by = 'user') {
165
253
  return this.call('group_delete', { group_id: groupId, by });
166
254
  }
255
+ /** Replace a group with a clean group while preserving selected configuration. */
256
+ async groupReset(options) {
257
+ if (options.confirmGroupId !== options.groupId) {
258
+ throw new DaemonAPIError('invalid_args', 'groupReset requires confirmGroupId to equal groupId', {});
259
+ }
260
+ return this.call('group_reset', {
261
+ group_id: options.groupId,
262
+ confirm: options.confirmGroupId,
263
+ by: options.by ?? 'user',
264
+ });
265
+ }
167
266
  /**
168
267
  * Use group (set active scope)
169
268
  */
@@ -208,7 +307,7 @@ export class CCCCClient {
208
307
  async groupAutomationManage(options) {
209
308
  const actions = options.actions;
210
309
  if (actions.length === 0) {
211
- throw new DaemonAPIError(ErrorCodes.INVALID_ARGS, 'groupAutomationManage requires a non-empty actions array', {});
310
+ throw new DaemonAPIError('invalid_args', 'groupAutomationManage requires a non-empty actions array', {});
212
311
  }
213
312
  const args = {
214
313
  group_id: options.groupId,
@@ -264,7 +363,7 @@ export class CCCCClient {
264
363
  }
265
364
  /**
266
365
  * Add an actor to a group.
267
- * @param options - Actor configuration (id, runtime, runner, etc.).
366
+ * @param options - Actor configuration (id, runtime, command, etc.).
268
367
  * @returns The daemon result (includes assigned actor id).
269
368
  * @throws {DaemonAPIError} On invalid group or duplicate actor id.
270
369
  */
@@ -273,12 +372,14 @@ export class CCCCClient {
273
372
  actor_id: options.actorId,
274
373
  title: options.title,
275
374
  runtime: options.runtime,
276
- runner: options.runner,
277
375
  command: options.command,
278
376
  env: options.env,
279
377
  env_private: options.envPrivate,
280
378
  capability_autoload: options.capabilityAutoload,
379
+ capability_hidden: options.capabilityHidden,
281
380
  profile_id: options.profileId,
381
+ profile_scope: options.profileScope,
382
+ profile_owner: options.profileOwner,
282
383
  default_scope_key: options.defaultScopeKey,
283
384
  submit: options.submit,
284
385
  };
@@ -315,25 +416,10 @@ export class CCCCClient {
315
416
  return this.call('actor_remove', { group_id: groupId, actor_id: actorId, by });
316
417
  }
317
418
  /**
318
- * Start actor.
319
- * @param groupId - Group ID.
320
- * @param actorId - Actor ID.
321
- * @param options - Optional settings. Pass `{ idempotent: true }` to suppress errors when the actor is already running.
322
- * @returns The daemon result.
323
- * @throws {DaemonAPIError} On error (unless idempotent and actor already running).
419
+ * Start actor
324
420
  */
325
- async actorStart(groupId, actorId, options) {
326
- const by = typeof options === 'string' ? options : (options?.by ?? 'user');
327
- const idempotent = typeof options === 'object' && options?.idempotent === true;
328
- try {
329
- return await this.call('actor_start', { group_id: groupId, actor_id: actorId, by });
330
- }
331
- catch (e) {
332
- if (idempotent && e instanceof DaemonAPIError && e.code === ErrorCodes.CONFLICT) {
333
- return {};
334
- }
335
- throw e;
336
- }
421
+ async actorStart(groupId, actorId, by = 'user') {
422
+ return this.call('actor_start', { group_id: groupId, actor_id: actorId, by });
337
423
  }
338
424
  /**
339
425
  * Stop actor
@@ -347,6 +433,23 @@ export class CCCCClient {
347
433
  async actorRestart(groupId, actorId, by = 'user') {
348
434
  return this.call('actor_restart', { group_id: groupId, actor_id: actorId, by });
349
435
  }
436
+ async runtimeHermesStatus() {
437
+ return this.call('runtime_hermes_status', {});
438
+ }
439
+ async runtimeHermesPrepare(options = {}) {
440
+ return this.call('runtime_hermes_prepare', compactRecord({
441
+ cwd: options.cwd,
442
+ auto_enable_tools: options.autoEnableTools,
443
+ force_mcp: options.forceMcp,
444
+ }));
445
+ }
446
+ async runtimeHermesMcpTest(options = {}) {
447
+ return this.call('runtime_hermes_mcp_test', compactRecord({
448
+ cwd: options.cwd,
449
+ group_id: options.groupId,
450
+ actor_id: options.actorId,
451
+ }));
452
+ }
350
453
  /**
351
454
  * List actor private env keys (without values)
352
455
  */
@@ -482,35 +585,48 @@ export class CCCCClient {
482
585
  /**
483
586
  * Search the capability registry for one group/caller scope.
484
587
  */
485
- async capabilitySearch(options = {}) {
486
- return this.call('capability_search', compactRecord({
588
+ async capabilitySearch(options) {
589
+ const args = {
487
590
  group_id: options.groupId,
488
- by: options.by,
489
- actor_id: options.actorId,
490
- query: options.query,
491
- kind: options.kind,
492
- source_id: options.sourceId,
493
- trust_tier: options.trustTier,
494
- qualification_status: options.qualificationStatus,
495
- include_external: options.includeExternal,
496
- limit: options.limit,
497
- }));
591
+ by: options.by ?? 'user',
592
+ };
593
+ if (options.actorId)
594
+ args['actor_id'] = options.actorId;
595
+ if (options.query)
596
+ args['query'] = options.query;
597
+ if (options.kind !== undefined)
598
+ args['kind'] = options.kind;
599
+ if (options.sourceId)
600
+ args['source_id'] = options.sourceId;
601
+ if (options.trustTier)
602
+ args['trust_tier'] = options.trustTier;
603
+ if (options.qualificationStatus !== undefined)
604
+ args['qualification_status'] = options.qualificationStatus;
605
+ if (options.includeExternal !== undefined)
606
+ args['include_external'] = options.includeExternal;
607
+ if (options.limit !== undefined)
608
+ args['limit'] = options.limit;
609
+ return this.call('capability_search', args);
498
610
  }
499
611
  /**
500
612
  * Enable or disable a capability.
501
613
  */
502
614
  async capabilityEnable(options) {
503
- return this.call('capability_enable', compactRecord({
615
+ const args = {
504
616
  group_id: options.groupId,
505
617
  capability_id: options.capabilityId,
506
- scope: options.scope,
507
- enabled: options.enabled,
508
- cleanup: options.cleanup,
509
- by: options.by,
510
- reason: options.reason,
511
- ttl_seconds: options.ttlSeconds,
512
- actor_id: options.actorId,
513
- }));
618
+ scope: options.scope ?? 'session',
619
+ enabled: options.enabled ?? true,
620
+ cleanup: options.cleanup ?? false,
621
+ by: options.by ?? 'user',
622
+ };
623
+ if (options.reason)
624
+ args['reason'] = options.reason;
625
+ if (options.ttlSeconds !== undefined)
626
+ args['ttl_seconds'] = options.ttlSeconds;
627
+ if (options.actorId)
628
+ args['actor_id'] = options.actorId;
629
+ return this.call('capability_enable', args);
514
630
  }
515
631
  /**
516
632
  * Block or unblock a capability.
@@ -534,15 +650,14 @@ export class CCCCClient {
534
650
  /**
535
651
  * Read effective capability exposure for one caller scope.
536
652
  */
537
- async capabilityState(options = {}, actorId) {
538
- const args = typeof options === 'string'
539
- ? { group_id: options, actor_id: actorId }
540
- : {
541
- group_id: options.groupId,
542
- by: options.by,
543
- actor_id: options.actorId,
544
- };
545
- return this.call('capability_state', compactRecord(args));
653
+ async capabilityState(options) {
654
+ const args = {
655
+ group_id: options.groupId,
656
+ by: options.by ?? 'user',
657
+ };
658
+ if (options.actorId)
659
+ args['actor_id'] = options.actorId;
660
+ return this.call('capability_state', args);
546
661
  }
547
662
  /**
548
663
  * Read capability allowlist default, overlay, and effective snapshots.
@@ -643,250 +758,68 @@ export class CCCCClient {
643
758
  args['actor_id'] = options.actorId;
644
759
  return this.call('capability_tool_call', args);
645
760
  }
646
- // ============================================================
647
- // Convenience methods: messaging
648
- // ============================================================
649
- /**
650
- * Send a chat message to a group.
651
- * @param options - Message content, recipients, and priority.
652
- * @returns The created event and optional ack event.
653
- * @throws {DaemonAPIError} On invalid group, missing permissions, etc.
654
- */
655
- async send(options) {
656
- const args = {
657
- group_id: options.groupId,
658
- text: options.text,
659
- by: options.by ?? 'user',
660
- priority: options.priority ?? 'normal',
661
- reply_required: options.replyRequired ?? false,
662
- };
663
- if (options.to)
664
- args['to'] = options.to;
665
- if (options.path)
666
- args['path'] = options.path;
667
- return this.call('send', args);
668
- }
669
- /**
670
- * Send message across groups.
671
- * @returns The created event and optional ack event.
672
- */
673
- async sendCrossGroup(options) {
674
- const args = {
675
- group_id: options.groupId,
676
- dst_group_id: options.dstGroupId,
677
- text: options.text,
678
- by: options.by ?? 'user',
679
- priority: options.priority ?? 'normal',
680
- reply_required: options.replyRequired ?? false,
681
- };
682
- if (options.to)
683
- args['to'] = options.to;
684
- return this.call('send_cross_group', args);
685
- }
686
- /**
687
- * Create a durable task and send one linked visible message.
688
- */
689
- async trackedSend(options) {
690
- const args = compactRecord({
761
+ async capabilityUse(options) {
762
+ const enableResult = await this.capabilityEnable(options);
763
+ if (!options.toolName)
764
+ return enableResult;
765
+ return this.call('capability_tool_call', compactRecord({
691
766
  group_id: options.groupId,
692
- title: options.title,
693
- text: options.text,
694
- outcome: options.outcome,
695
- assignee: options.assignee,
696
- handoff_to: options.handoffTo,
697
- waiting_on: options.waitingOn,
698
- checklist: options.checklist,
699
- notes: options.notes,
700
- by: options.by ?? 'user',
701
- to: options.to,
702
- priority: options.priority ?? 'normal',
703
- reply_required: options.replyRequired ?? true,
704
- path: options.path,
705
- });
706
- return this.call('tracked_send', args);
767
+ actor_id: options.actorId,
768
+ by: options.by,
769
+ tool_name: options.toolName,
770
+ arguments: options.toolArguments ?? {},
771
+ }));
707
772
  }
708
- /**
709
- * Reply to a message.
710
- * @returns The created event and optional ack event.
711
- */
712
- async reply(options) {
713
- const args = {
773
+ async memorySearch(options) {
774
+ return this.call('memory_search', compactRecord({
714
775
  group_id: options.groupId,
715
- reply_to: options.replyTo,
716
- text: options.text,
717
- by: options.by ?? 'user',
718
- priority: options.priority ?? 'normal',
719
- reply_required: options.replyRequired ?? false,
720
- };
721
- if (options.to)
722
- args['to'] = options.to;
723
- return this.call('reply', args);
724
- }
725
- /**
726
- * Send a message and wait for a reply to it.
727
- * Opens the event stream **before** sending to avoid race conditions,
728
- * then yields control to the stream until a matching reply arrives.
729
- *
730
- * @param options - Send options plus `listenAs` (the actor to listen as) and optional `waitTimeoutMs`.
731
- * @returns The reply event (a `chat.message` whose `reply_to` matches the sent event ID).
732
- * @throws {DaemonAPIError} On send/stream errors.
733
- * @throws {Error} If the timeout elapses or the signal is aborted before a reply arrives.
734
- */
735
- async sendAndWaitForReply(options) {
736
- const waitTimeout = options.waitTimeoutMs ?? 60000;
737
- const deadline = Date.now() + waitTimeout;
738
- // Open stream FIRST (with sinceTs = now) to avoid missing replies
739
- // that arrive between send() returning and stream connecting.
740
- const stream = this.eventsStream({
741
- groupId: options.groupId,
742
- by: options.listenAs,
743
- kinds: ['chat.message'],
744
- sinceTs: new Date().toISOString(),
745
- signal: options.signal,
746
- });
747
- let nextItem = stream.next();
748
- // Now send the message
749
- const sendResult = await this.send(options);
750
- const sentEventId = sendResult.event.id;
751
- try {
752
- while (true) {
753
- if (options.signal?.aborted) {
754
- throw new Error('sendAndWaitForReply aborted');
755
- }
756
- if (Date.now() > deadline) {
757
- throw new Error(`sendAndWaitForReply timed out after ${waitTimeout}ms`);
758
- }
759
- const { value: item, done } = await nextItem;
760
- if (done)
761
- break;
762
- nextItem = stream.next();
763
- if (isStreamEvent(item)) {
764
- const evt = item.event;
765
- if (evt.kind === 'chat.message') {
766
- const data = evt.data;
767
- if (data['reply_to'] === sentEventId) {
768
- return evt;
769
- }
770
- }
771
- }
772
- }
773
- }
774
- finally {
775
- await stream.return(undefined);
776
- }
777
- throw new Error('sendAndWaitForReply: stream ended without reply');
778
- }
779
- /**
780
- * Acknowledge chat message
781
- */
782
- async chatAck(groupId, actorId, eventId, by) {
783
- return this.call('chat_ack', {
784
- group_id: groupId,
785
- actor_id: actorId,
786
- event_id: eventId,
787
- by: by ?? actorId,
788
- });
776
+ actor_id: options.actorId,
777
+ query: options.query,
778
+ limit: options.limit,
779
+ max_results: options.maxResults,
780
+ vector_weight: options.vectorWeight,
781
+ candidate_multiplier: options.candidateMultiplier,
782
+ min_score: options.minScore,
783
+ tags: options.tags,
784
+ target: options.target,
785
+ }));
789
786
  }
790
- /**
791
- * Send a file as a chat attachment.
792
- * @param options - File path (relative to scope root), optional caption and recipients.
793
- * @returns The created event and optional ack event.
794
- */
795
- async fileSend(options) {
796
- const args = {
787
+ async memoryGet(options) {
788
+ return this.call('memory_get', compactRecord({
797
789
  group_id: options.groupId,
790
+ actor_id: options.actorId,
798
791
  path: options.path,
799
- by: options.by ?? 'user',
800
- priority: options.priority ?? 'normal',
801
- reply_required: options.replyRequired ?? false,
802
- };
803
- if (options.text)
804
- args['text'] = options.text;
805
- if (options.to)
806
- args['to'] = options.to;
807
- return this.call('file_send', args);
792
+ target: options.target,
793
+ date: options.date,
794
+ offset: options.offset,
795
+ limit: options.limit,
796
+ }));
808
797
  }
809
- /**
810
- * Get recent chat messages from the ledger.
811
- * @param options - Group ID, limit, and max chars.
812
- * @returns The ledger tail result.
813
- */
814
- async ledgerTail(options) {
815
- const args = {
798
+ async memoryWrite(options) {
799
+ return this.call('memory_write', compactRecord({
816
800
  group_id: options.groupId,
817
- by: options.by ?? 'user',
818
- };
819
- if (options.limit !== undefined)
820
- args['limit'] = options.limit;
821
- if (options.maxChars !== undefined)
822
- args['max_chars'] = options.maxChars;
823
- return this.call('ledger_tail', args);
801
+ actor_id: options.actorId,
802
+ target: options.target,
803
+ content: options.content,
804
+ tags: options.tags,
805
+ source_refs: options.sourceRefs,
806
+ idempotency_key: options.idempotencyKey,
807
+ dedup_intent: options.dedupIntent,
808
+ dedup_query: options.dedupQuery,
809
+ }));
824
810
  }
825
- /**
826
- * Get recent terminal output from an actor.
827
- * @param options - Group ID, actor ID, and line count.
828
- * @returns The terminal tail result.
829
- */
830
- async terminalTail(options) {
831
- const args = {
811
+ async memoryHealth(options) {
812
+ return this.call('memory_health', compactRecord({
832
813
  group_id: options.groupId,
833
- actor_id: options.actorId,
834
- by: options.by ?? 'user',
835
- };
836
- if (options.lines !== undefined)
837
- args['lines'] = options.lines;
838
- return this.call('terminal_tail', args);
814
+ }));
839
815
  }
840
- // ============================================================
841
- // Convenience methods: inbox
842
- // ============================================================
843
- /**
844
- * List inbox
845
- */
846
- async inboxList(options) {
847
- return this.call('inbox_list', {
816
+ async memoryProfileGet(options) {
817
+ return this.call('memory_profile_get', compactRecord({
848
818
  group_id: options.groupId,
849
819
  actor_id: options.actorId,
850
- by: options.by ?? 'user',
851
- limit: options.limit ?? 50,
852
- kind_filter: options.kindFilter ?? 'all',
853
- });
854
- }
855
- /**
856
- * Mark message as read
857
- */
858
- async inboxMarkRead(groupId, actorId, eventId, by = 'user') {
859
- return this.call('inbox_mark_read', {
860
- group_id: groupId,
861
- actor_id: actorId,
862
- event_id: eventId,
863
- by,
864
- });
865
- }
866
- /**
867
- * Mark all messages as read
868
- */
869
- async inboxMarkAllRead(groupId, actorId, by = 'user', kindFilter = 'all') {
870
- return this.call('inbox_mark_all_read', {
871
- group_id: groupId,
872
- actor_id: actorId,
873
- by,
874
- kind_filter: kindFilter,
875
- });
876
- }
877
- // ============================================================
878
- // Convenience methods: notifications
879
- // ============================================================
880
- /**
881
- * Acknowledge notification
882
- */
883
- async notifyAck(groupId, actorId, notifyEventId, by) {
884
- return this.call('notify_ack', {
885
- group_id: groupId,
886
- actor_id: actorId,
887
- notify_event_id: notifyEventId,
888
- by: by ?? actorId,
889
- });
820
+ user_id: options.userId,
821
+ tags: options.tags,
822
+ }));
890
823
  }
891
824
  // ============================================================
892
825
  // Convenience methods: context
@@ -894,8 +827,8 @@ export class CCCCClient {
894
827
  /**
895
828
  * Get group context
896
829
  */
897
- async contextGet(groupId) {
898
- return this.call('context_get', { group_id: groupId });
830
+ async contextGet(groupId, detail = 'full') {
831
+ return this.call('context_get', { group_id: groupId, detail });
899
832
  }
900
833
  /**
901
834
  * Sync context
@@ -906,12 +839,12 @@ export class CCCCClient {
906
839
  ops: options.ops,
907
840
  by: options.by ?? 'system',
908
841
  dry_run: options.dryRun ?? false,
842
+ ...(options.ifVersion !== undefined ? { if_version: options.ifVersion } : {}),
909
843
  });
910
844
  }
911
845
  contextOp(groupId, op, by = 'system', dryRun = false) {
912
846
  return this.contextSync({ groupId, by, dryRun, ops: [op] });
913
847
  }
914
- /** Update the shared coordination brief (Context Ops v3). */
915
848
  async coordinationBriefUpdate(options) {
916
849
  return this.contextOp(options.groupId, compactRecord({
917
850
  op: 'coordination.brief.update',
@@ -922,7 +855,6 @@ export class CCCCClient {
922
855
  project_brief_stale: options.projectBriefStale,
923
856
  }), options.by, options.dryRun);
924
857
  }
925
- /** Add a compact coordination decision or handoff note. */
926
858
  async coordinationNoteAdd(options) {
927
859
  return this.contextOp(options.groupId, compactRecord({
928
860
  op: 'coordination.note.add',
@@ -931,7 +863,6 @@ export class CCCCClient {
931
863
  task_id: options.taskId,
932
864
  }), options.by, options.dryRun);
933
865
  }
934
- /** Create a Context Ops v3 task. */
935
866
  async taskCreate(options) {
936
867
  return this.contextOp(options.groupId, compactRecord({
937
868
  op: 'task.create',
@@ -949,7 +880,6 @@ export class CCCCClient {
949
880
  checklist: options.checklist,
950
881
  }), options.by, options.dryRun);
951
882
  }
952
- /** Update a Context Ops v3 task. */
953
883
  async taskUpdate(options) {
954
884
  return this.contextOp(options.groupId, compactRecord({
955
885
  op: 'task.update',
@@ -966,7 +896,6 @@ export class CCCCClient {
966
896
  checklist: options.checklist,
967
897
  }), options.by, options.dryRun);
968
898
  }
969
- /** Move a task through the canonical lifecycle operation. */
970
899
  async taskMove(options) {
971
900
  return this.contextOp(options.groupId, {
972
901
  op: 'task.move',
@@ -974,14 +903,18 @@ export class CCCCClient {
974
903
  status: options.status,
975
904
  }, options.by, options.dryRun);
976
905
  }
977
- /** Restore an archived task. */
978
906
  async taskRestore(options) {
979
907
  return this.contextOp(options.groupId, {
980
908
  op: 'task.restore',
981
909
  task_id: options.taskId,
982
910
  }, options.by, options.dryRun);
983
911
  }
984
- /** Update per-actor working memory. */
912
+ async taskDelete(options) {
913
+ return this.contextOp(options.groupId, {
914
+ op: 'task.delete',
915
+ task_id: options.taskId,
916
+ }, options.by, options.dryRun);
917
+ }
985
918
  async agentStateUpdate(options) {
986
919
  return this.contextOp(options.groupId, compactRecord({
987
920
  op: 'agent_state.update',
@@ -996,17 +929,14 @@ export class CCCCClient {
996
929
  environment_summary: options.environmentSummary,
997
930
  user_model: options.userModel,
998
931
  persona_notes: options.personaNotes,
999
- resume_hint: options.resumeHint,
1000
932
  }), options.by, options.dryRun);
1001
933
  }
1002
- /** Clear per-actor working memory. */
1003
934
  async agentStateClear(options) {
1004
935
  return this.contextOp(options.groupId, {
1005
936
  op: 'agent_state.clear',
1006
937
  actor_id: options.actorId,
1007
938
  }, options.by, options.dryRun);
1008
939
  }
1009
- /** Merge restricted projection metadata. */
1010
940
  async metaMerge(options) {
1011
941
  return this.contextOp(options.groupId, {
1012
942
  op: 'meta.merge',
@@ -1014,268 +944,449 @@ export class CCCCClient {
1014
944
  }, options.by, options.dryRun);
1015
945
  }
1016
946
  // ============================================================
1017
- // Convenience methods: capability use and memory
947
+ // Convenience methods: tracked delegation
1018
948
  // ============================================================
1019
- async capabilityUse(options) {
1020
- const enableResult = await this.capabilityEnable(options);
1021
- if (!options.toolName)
1022
- return enableResult;
1023
- return this.call('capability_tool_call', compactRecord({
1024
- group_id: options.groupId,
1025
- actor_id: options.actorId,
1026
- by: options.by,
1027
- tool_name: options.toolName,
1028
- arguments: options.toolArguments ?? {},
1029
- }));
1030
- }
1031
- async memorySearch(options) {
1032
- return this.call('memory_search', compactRecord({
949
+ /**
950
+ * Atomically create a tracked task and send the linked chat message. Daemon
951
+ * handles task.create + send in one transaction. Use `idempotencyKey` to make
952
+ * retries safe (the daemon replays the previous result on duplicate keys).
953
+ */
954
+ async trackedSend(options) {
955
+ const args = {
1033
956
  group_id: options.groupId,
1034
- actor_id: options.actorId,
1035
- query: options.query,
1036
- limit: options.limit,
1037
- tags: options.tags,
1038
- target: options.target,
1039
- }));
957
+ text: options.text,
958
+ by: options.by ?? 'user',
959
+ };
960
+ if (options.title)
961
+ args['title'] = options.title;
962
+ if (options.insight)
963
+ args['insight'] = options.insight;
964
+ if (options.to)
965
+ args['to'] = options.to;
966
+ if (options.path)
967
+ args['path'] = options.path;
968
+ if (options.taskPriority)
969
+ args['task_priority'] = options.taskPriority;
970
+ if (options.idempotencyKey)
971
+ args['idempotency_key'] = options.idempotencyKey;
972
+ if (options.outcome)
973
+ args['outcome'] = options.outcome;
974
+ if (options.status)
975
+ args['status'] = options.status;
976
+ if (options.waitingOn)
977
+ args['waiting_on'] = options.waitingOn;
978
+ if (options.taskType)
979
+ args['task_type'] = options.taskType;
980
+ if (options.checklist)
981
+ args['checklist'] = options.checklist;
982
+ if (options.notes)
983
+ args['notes'] = options.notes;
984
+ if (options.blockedBy)
985
+ args['blocked_by'] = options.blockedBy;
986
+ if (options.handoffTo)
987
+ args['handoff_to'] = options.handoffTo;
988
+ if (options.assignee)
989
+ args['assignee'] = options.assignee;
990
+ if (options.refs)
991
+ args['refs'] = options.refs;
992
+ if (options.insight)
993
+ args['insight'] = options.insight;
994
+ if (options.requirePeerInsight !== undefined)
995
+ args['require_peer_insight'] = options.requirePeerInsight;
996
+ return this.call('tracked_send', args);
1040
997
  }
1041
- async memoryGet(options) {
1042
- return this.call('memory_get', compactRecord({
1043
- group_id: options.groupId,
1044
- actor_id: options.actorId,
1045
- path: options.path,
1046
- target: options.target,
1047
- date: options.date,
1048
- offset: options.offset,
1049
- limit: options.limit,
1050
- }));
998
+ /**
999
+ * List all tasks in a group, or fetch a single task (with children) by id.
1000
+ */
1001
+ async taskList(options) {
1002
+ if (options.taskId && options.taskIds?.length) {
1003
+ throw new DaemonAPIError('invalid_args', 'taskId and taskIds are mutually exclusive', {});
1004
+ }
1005
+ if (options.status && options.statuses?.length) {
1006
+ throw new DaemonAPIError('invalid_args', 'status and statuses are mutually exclusive', {});
1007
+ }
1008
+ if (options.offset !== undefined && options.limit === undefined) {
1009
+ throw new DaemonAPIError('invalid_args', 'offset requires limit', {});
1010
+ }
1011
+ if (options.limit !== undefined && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 100)) {
1012
+ throw new DaemonAPIError('invalid_args', 'limit must be an integer from 1 through 100', {});
1013
+ }
1014
+ const taskIds = options.taskIds?.map((value) => value.trim()) ?? [];
1015
+ if (taskIds.some((value) => value.length === 0) || taskIds.length > 100) {
1016
+ throw new DaemonAPIError('invalid_args', 'taskIds must contain at most 100 non-empty ids', {});
1017
+ }
1018
+ const args = { group_id: options.groupId };
1019
+ if (options.taskId)
1020
+ args['task_id'] = options.taskId;
1021
+ if (taskIds.length)
1022
+ args['task_ids'] = taskIds.join(',');
1023
+ if (options.status)
1024
+ args['status'] = options.status;
1025
+ if (options.statuses?.length)
1026
+ args['statuses'] = options.statuses.join(',');
1027
+ if (options.query)
1028
+ args['query'] = options.query;
1029
+ if (options.assignee)
1030
+ args['assignee'] = options.assignee;
1031
+ if (options.attention)
1032
+ args['attention'] = options.attention;
1033
+ if (options.offset !== undefined)
1034
+ args['offset'] = options.offset;
1035
+ if (options.limit !== undefined)
1036
+ args['limit'] = options.limit;
1037
+ if (options.includeIndex !== undefined)
1038
+ args['include_index'] = options.includeIndex;
1039
+ return this.call('task_list', args);
1051
1040
  }
1052
- async memoryWrite(options) {
1053
- return this.call('memory_write', compactRecord({
1041
+ // ============================================================
1042
+ // Convenience methods: headless runtime control
1043
+ // ============================================================
1044
+ async headlessStatus(options) {
1045
+ return this.call('headless_status', {
1054
1046
  group_id: options.groupId,
1055
1047
  actor_id: options.actorId,
1056
- target: options.target,
1057
- content: options.content,
1058
- tags: options.tags,
1059
- source_refs: options.sourceRefs,
1060
- idempotency_key: options.idempotencyKey,
1061
- dedup_intent: options.dedupIntent,
1062
- dedup_query: options.dedupQuery,
1063
- }));
1064
- }
1065
- async memoryHealth(options = {}) {
1066
- return this.call('memory_health', compactRecord({
1067
- group_id: options.groupId,
1068
- }));
1048
+ });
1069
1049
  }
1070
- async memoryProfileGet(options) {
1071
- return this.call('memory_profile_get', compactRecord({
1050
+ async headlessSetStatus(options) {
1051
+ const args = {
1072
1052
  group_id: options.groupId,
1073
1053
  actor_id: options.actorId,
1074
- user_id: options.userId,
1075
- tags: options.tags,
1076
- }));
1054
+ status: options.status,
1055
+ };
1056
+ if (options.taskId)
1057
+ args['task_id'] = options.taskId;
1058
+ return this.call('headless_set_status', args);
1077
1059
  }
1078
1060
  // ============================================================
1079
- // Convenience methods: Group Space
1061
+ // Convenience methods: group copy (export/import)
1080
1062
  // ============================================================
1081
- /**
1082
- * Read Group Space provider and binding status.
1083
- */
1084
- async groupSpaceStatus(options) {
1085
- return this.call('group_space_status', {
1063
+ async groupCopyExport(options) {
1064
+ return this.call('group_copy_export', {
1086
1065
  group_id: options.groupId,
1087
- provider: options.provider ?? 'notebooklm',
1066
+ by: options.by ?? 'user',
1088
1067
  });
1089
1068
  }
1090
- /**
1091
- * List available remote spaces for binding.
1092
- */
1093
- async groupSpaceSpaces(options) {
1094
- return this.call('group_space_spaces', {
1095
- group_id: options.groupId,
1096
- provider: options.provider ?? 'notebooklm',
1097
- });
1069
+ async groupCopyPreviewImport(options) {
1070
+ const packageB64 = options.packageB64;
1071
+ const packagePath = options.packagePath;
1072
+ if (Boolean(packageB64) === Boolean(packagePath)) {
1073
+ throw new DaemonAPIError('invalid_args', 'exactly one of packageB64 or packagePath is required', {});
1074
+ }
1075
+ return this.call('group_copy_preview_import', compactRecord({
1076
+ package_b64: packageB64,
1077
+ package_path: packagePath,
1078
+ }));
1098
1079
  }
1099
- /**
1100
- * Read the provider capability matrix for a group.
1101
- */
1102
- async groupSpaceCapabilities(options) {
1103
- return this.call('group_space_capabilities', {
1104
- group_id: options.groupId,
1105
- provider: options.provider ?? 'notebooklm',
1080
+ async groupCopyImport(options) {
1081
+ const packageB64 = options.packageB64;
1082
+ const packagePath = options.packagePath;
1083
+ if (Boolean(packageB64) === Boolean(packagePath)) {
1084
+ throw new DaemonAPIError('invalid_args', 'exactly one of packageB64 or packagePath is required', {});
1085
+ }
1086
+ const args = compactRecord({
1087
+ package_b64: packageB64,
1088
+ package_path: packagePath,
1106
1089
  });
1090
+ if (options.workspaceRoot)
1091
+ args['workspace_root'] = options.workspaceRoot;
1092
+ if (options.title)
1093
+ args['title'] = options.title;
1094
+ return this.call('group_copy_import', args);
1107
1095
  }
1108
- /**
1109
- * Bind or unbind one Group Space lane.
1110
- */
1111
- async groupSpaceBind(options) {
1096
+ // ============================================================
1097
+ // Convenience methods: capability extensions
1098
+ // ============================================================
1099
+ async capabilityVisibility(options) {
1112
1100
  const args = {
1113
1101
  group_id: options.groupId,
1114
- provider: options.provider ?? 'notebooklm',
1115
- lane: options.lane,
1116
- action: options.action ?? 'bind',
1102
+ capability_id: options.capabilityId,
1103
+ hidden: options.hidden ?? true,
1117
1104
  by: options.by ?? 'user',
1118
1105
  };
1119
- if (options.remoteSpaceId)
1120
- args['remote_space_id'] = options.remoteSpaceId;
1121
- return this.call('group_space_bind', args);
1106
+ if (options.actorId)
1107
+ args['actor_id'] = options.actorId;
1108
+ if (options.reason)
1109
+ args['reason'] = options.reason;
1110
+ return this.call('capability_visibility', args);
1122
1111
  }
1123
- /**
1124
- * Enqueue one Group Space ingest action.
1125
- */
1126
- async groupSpaceIngest(options) {
1112
+ async capabilityInstallTarget(options) {
1127
1113
  const args = {
1128
1114
  group_id: options.groupId,
1129
- provider: options.provider ?? 'notebooklm',
1130
- lane: options.lane,
1131
- kind: options.kind ?? 'context_sync',
1115
+ target: options.target,
1116
+ scope: options.scope ?? 'actor',
1132
1117
  by: options.by ?? 'user',
1133
1118
  };
1134
- if (options.payload)
1135
- args['payload'] = options.payload;
1136
- if (options.idempotencyKey)
1137
- args['idempotency_key'] = options.idempotencyKey;
1138
- return this.call('group_space_ingest', args);
1119
+ if (options.actorId)
1120
+ args['actor_id'] = options.actorId;
1121
+ if (options.ttlSeconds !== undefined)
1122
+ args['ttl_seconds'] = options.ttlSeconds;
1123
+ if (options.reason)
1124
+ args['reason'] = options.reason;
1125
+ return this.call('capability_install_target', args);
1139
1126
  }
1140
- /**
1141
- * Query Group Space knowledge for one lane.
1142
- */
1143
- async groupSpaceQuery(options) {
1127
+ async capabilitySourceDelete(options) {
1144
1128
  const args = {
1145
1129
  group_id: options.groupId,
1146
- provider: options.provider ?? 'notebooklm',
1147
- lane: options.lane,
1148
- query: options.query,
1130
+ source_id: options.sourceId,
1131
+ by: options.by ?? 'user',
1149
1132
  };
1150
- if (options.options)
1151
- args['options'] = options.options;
1152
- return this.call('group_space_query', args);
1133
+ if (options.reason)
1134
+ args['reason'] = options.reason;
1135
+ if (options.actorId)
1136
+ args['actor_id'] = options.actorId;
1137
+ return this.call('capability_source_delete', args);
1153
1138
  }
1154
- /**
1155
- * Manage remote sources in the bound Group Space lane.
1156
- */
1157
- async groupSpaceSources(options) {
1139
+ // ============================================================
1140
+ // Convenience methods: presentation workspace
1141
+ // ============================================================
1142
+ async presentationGet(options) {
1143
+ return this.call('presentation_get', { group_id: options.groupId });
1144
+ }
1145
+ async presentationPublish(options) {
1158
1146
  const args = {
1159
1147
  group_id: options.groupId,
1160
- provider: options.provider ?? 'notebooklm',
1161
- lane: options.lane,
1162
- action: options.action ?? 'list',
1163
1148
  by: options.by ?? 'user',
1164
1149
  };
1165
- if (options.sourceId)
1166
- args['source_id'] = options.sourceId;
1167
- if (options.newTitle)
1168
- args['new_title'] = options.newTitle;
1169
- return this.call('group_space_sources', args);
1170
- }
1171
- /**
1172
- * List, generate, or download Group Space artifacts.
1173
- */
1174
- async groupSpaceArtifact(options) {
1150
+ if (options.slot)
1151
+ args['slot'] = options.slot;
1152
+ if (options.title)
1153
+ args['title'] = options.title;
1154
+ if (options.summary)
1155
+ args['summary'] = options.summary;
1156
+ if (options.sourceLabel)
1157
+ args['source_label'] = options.sourceLabel;
1158
+ if (options.sourceRef)
1159
+ args['source_ref'] = options.sourceRef;
1160
+ if (options.cardType)
1161
+ args['card_type'] = options.cardType;
1162
+ if (options.content)
1163
+ args['content'] = options.content;
1164
+ if (options.path)
1165
+ args['path'] = options.path;
1166
+ if (options.url)
1167
+ args['url'] = options.url;
1168
+ if (options.blobRelPath)
1169
+ args['blob_rel_path'] = options.blobRelPath;
1170
+ if (options.table)
1171
+ args['table'] = options.table;
1172
+ return this.call('presentation_publish', args);
1173
+ }
1174
+ async presentationClear(options) {
1175
1175
  const args = {
1176
1176
  group_id: options.groupId,
1177
- provider: options.provider ?? 'notebooklm',
1178
- lane: options.lane,
1179
- action: options.action ?? 'list',
1180
1177
  by: options.by ?? 'user',
1181
1178
  };
1182
- if (options.kind)
1183
- args['kind'] = options.kind;
1184
- if (options.options)
1185
- args['options'] = options.options;
1186
- if (options.wait !== undefined)
1187
- args['wait'] = options.wait;
1188
- if (options.saveToSpace !== undefined)
1189
- args['save_to_space'] = options.saveToSpace;
1190
- if (options.outputPath)
1191
- args['output_path'] = options.outputPath;
1192
- if (options.outputFormat)
1193
- args['output_format'] = options.outputFormat;
1194
- if (options.artifactId)
1195
- args['artifact_id'] = options.artifactId;
1196
- if (options.timeoutSeconds !== undefined)
1197
- args['timeout_seconds'] = options.timeoutSeconds;
1198
- if (options.initialInterval !== undefined)
1199
- args['initial_interval'] = options.initialInterval;
1200
- if (options.maxInterval !== undefined)
1201
- args['max_interval'] = options.maxInterval;
1202
- return this.call('group_space_artifact', args);
1179
+ if (options.slot)
1180
+ args['slot'] = options.slot;
1181
+ return this.call('presentation_clear', args);
1203
1182
  }
1204
- /**
1205
- * List or manage Group Space jobs.
1206
- */
1207
- async groupSpaceJobs(options) {
1208
- const args = {
1183
+ /** @deprecated This browser surface is served by CCCC Web, not daemon IPC. */
1184
+ async presentationBrowserOpen(options) {
1185
+ void options;
1186
+ throw new IncompatibleDaemonError("Presentation browsers are no longer served by daemon IPC; use the CCCC Web Presentation browser surface");
1187
+ }
1188
+ /** @deprecated This browser surface is served by CCCC Web, not daemon IPC. */
1189
+ async presentationBrowserInfo(options) {
1190
+ void options;
1191
+ throw new IncompatibleDaemonError("Presentation browsers are no longer served by daemon IPC; use the CCCC Web Presentation browser surface");
1192
+ }
1193
+ /** @deprecated This browser surface is served by CCCC Web, not daemon IPC. */
1194
+ async presentationBrowserClose(options) {
1195
+ void options;
1196
+ throw new IncompatibleDaemonError("Presentation browsers are no longer served by daemon IPC; use the CCCC Web Presentation browser surface");
1197
+ }
1198
+ // ============================================================
1199
+ // Convenience methods: built-in assistants (PET / Voice Secretary)
1200
+ // ============================================================
1201
+ async assistantState(options) {
1202
+ const args = { group_id: options.groupId };
1203
+ if (options.assistantId)
1204
+ args['assistant_id'] = options.assistantId;
1205
+ if (options.promptRequestId)
1206
+ args['prompt_request_id'] = options.promptRequestId;
1207
+ return this.call('assistant_state', args);
1208
+ }
1209
+ async assistantVoiceRecordingLease(options) {
1210
+ return this.call('assistant_voice_recording_lease', compactRecord({
1209
1211
  group_id: options.groupId,
1210
- provider: options.provider ?? 'notebooklm',
1211
- lane: options.lane,
1212
- action: options.action ?? 'list',
1212
+ action: options.action,
1213
1213
  by: options.by ?? 'user',
1214
- };
1215
- if (options.jobId)
1216
- args['job_id'] = options.jobId;
1217
- if (options.state)
1218
- args['state'] = options.state;
1219
- if (options.limit !== undefined)
1220
- args['limit'] = options.limit;
1221
- return this.call('group_space_jobs', args);
1214
+ owner_id: options.ownerId,
1215
+ lease_id: options.leaseId,
1216
+ ttl_seconds: options.ttlSeconds,
1217
+ capture_mode: options.captureMode,
1218
+ recognition_backend: options.recognitionBackend,
1219
+ }));
1222
1220
  }
1223
- /**
1224
- * Read or run Group Space synchronization for one lane.
1225
- */
1226
- async groupSpaceSync(options) {
1227
- return this.call('group_space_sync', {
1221
+ async assistantSettingsUpdate(options) {
1222
+ return this.call('assistant_settings_update', {
1228
1223
  group_id: options.groupId,
1229
- provider: options.provider ?? 'notebooklm',
1230
- lane: options.lane,
1231
- action: options.action ?? 'status',
1232
- force: options.force ?? false,
1224
+ assistant_id: options.assistantId,
1225
+ patch: options.patch,
1233
1226
  by: options.by ?? 'user',
1234
1227
  });
1235
1228
  }
1236
- /**
1237
- * Read provider credential status.
1238
- */
1239
- async groupSpaceProviderCredentialStatus(options = {}) {
1240
- return this.call('group_space_provider_credential_status', {
1241
- provider: options.provider ?? 'notebooklm',
1229
+ async assistantStatusUpdate(options) {
1230
+ const args = {
1231
+ group_id: options.groupId,
1232
+ assistant_id: options.assistantId,
1233
+ lifecycle: options.lifecycle,
1234
+ };
1235
+ if (options.health)
1236
+ args['health'] = options.health;
1237
+ if (options.by)
1238
+ args['by'] = options.by;
1239
+ return this.call('assistant_status_update', args);
1240
+ }
1241
+ // ============================================================
1242
+ // Convenience methods: daemon core
1243
+ // ============================================================
1244
+ /** Trigger graceful daemon shutdown (no-args). */
1245
+ async shutdown() {
1246
+ return this.call('shutdown', {});
1247
+ }
1248
+ async observabilityGet() {
1249
+ return this.call('observability_get', {});
1250
+ }
1251
+ async observabilityUpdate(options) {
1252
+ return this.call('observability_update', {
1253
+ patch: options.patch,
1242
1254
  by: options.by ?? 'user',
1243
1255
  });
1244
1256
  }
1245
- /**
1246
- * Update provider credentials.
1247
- */
1248
- async groupSpaceProviderCredentialUpdate(options = {}) {
1257
+ async brandingGet() {
1258
+ return this.call('branding_get', {});
1259
+ }
1260
+ async brandingUpdate(options) {
1261
+ return this.call('branding_update', {
1262
+ patch: options.patch,
1263
+ by: options.by ?? 'user',
1264
+ });
1265
+ }
1266
+ // ============================================================
1267
+ // Convenience methods: diagnostics
1268
+ // ============================================================
1269
+ async debugSnapshot(options) {
1270
+ return this.call('debug_snapshot', {
1271
+ group_id: options.groupId,
1272
+ by: options.by ?? 'user',
1273
+ });
1274
+ }
1275
+ async debugTailLogs(options) {
1249
1276
  const args = {
1250
- provider: options.provider ?? 'notebooklm',
1277
+ component: options.component,
1251
1278
  by: options.by ?? 'user',
1252
- clear: options.clear ?? false,
1279
+ lines: options.lines ?? 200,
1253
1280
  };
1254
- if (options.authJson)
1255
- args['auth_json'] = options.authJson;
1256
- return this.call('group_space_provider_credential_update', args);
1281
+ if (options.groupId)
1282
+ args['group_id'] = options.groupId;
1283
+ return this.call('debug_tail_logs', args);
1257
1284
  }
1258
- /**
1259
- * Run provider health check.
1260
- */
1261
- async groupSpaceProviderHealthCheck(options = {}) {
1262
- return this.call('group_space_provider_health_check', {
1263
- provider: options.provider ?? 'notebooklm',
1285
+ async debugClearLogs(options) {
1286
+ const args = {
1287
+ component: options.component,
1288
+ by: options.by ?? 'user',
1289
+ };
1290
+ if (options.groupId)
1291
+ args['group_id'] = options.groupId;
1292
+ return this.call('debug_clear_logs', args);
1293
+ }
1294
+ async terminalTail(options) {
1295
+ return this.call('terminal_tail', {
1296
+ group_id: options.groupId,
1297
+ actor_id: options.actorId,
1298
+ max_chars: options.maxChars ?? 8000,
1299
+ strip_ansi: options.stripAnsi ?? true,
1300
+ compact: options.compact ?? true,
1301
+ by: options.by ?? 'user',
1302
+ });
1303
+ }
1304
+ async terminalClear(options) {
1305
+ return this.call('terminal_clear', {
1306
+ group_id: options.groupId,
1307
+ actor_id: options.actorId,
1308
+ by: options.by ?? 'user',
1309
+ });
1310
+ }
1311
+ // ============================================================
1312
+ // Convenience methods: maintenance (ledger)
1313
+ // ============================================================
1314
+ async ledgerSnapshot(options) {
1315
+ return this.call('ledger_snapshot', {
1316
+ group_id: options.groupId,
1264
1317
  by: options.by ?? 'user',
1318
+ reason: options.reason ?? 'manual',
1265
1319
  });
1266
1320
  }
1321
+ async ledgerCompact(options) {
1322
+ return this.call('ledger_compact', {
1323
+ group_id: options.groupId,
1324
+ by: options.by ?? 'user',
1325
+ reason: options.reason ?? 'auto',
1326
+ force: options.force ?? false,
1327
+ });
1328
+ }
1329
+ // ============================================================
1330
+ // Convenience methods: stream / system notify (low-level)
1331
+ // ============================================================
1267
1332
  /**
1268
- * Control provider auth flow.
1333
+ * Emit a chat.stream event (`op` = 'start' | 'update' | 'end').
1334
+ * For 'start', a new stream_id is generated and returned. For
1335
+ * 'update'/'end', supply the stream_id you got from 'start'.
1269
1336
  */
1270
- async groupSpaceProviderAuth(options = {}) {
1337
+ async streamEmit(options) {
1271
1338
  const args = {
1272
- provider: options.provider ?? 'notebooklm',
1273
- action: options.action ?? 'status',
1274
- by: options.by ?? 'user',
1339
+ group_id: options.groupId,
1340
+ by: options.by,
1341
+ op: options.op,
1342
+ format: options.format ?? 'plain',
1343
+ seq: options.seq ?? 0,
1344
+ };
1345
+ if (options.streamId)
1346
+ args['stream_id'] = options.streamId;
1347
+ if (options.text !== undefined)
1348
+ args['text'] = options.text;
1349
+ if (options.to)
1350
+ args['to'] = options.to;
1351
+ if (options.replyTo)
1352
+ args['reply_to'] = options.replyTo;
1353
+ if (options.clientId)
1354
+ args['client_id'] = options.clientId;
1355
+ return this.call('stream_emit', args);
1356
+ }
1357
+ async systemNotify(options) {
1358
+ const args = {
1359
+ group_id: options.groupId,
1360
+ by: options.by ?? 'system',
1361
+ kind: options.kind ?? 'info',
1362
+ priority: options.priority ?? 'normal',
1275
1363
  };
1276
- if (options.timeoutSeconds !== undefined)
1277
- args['timeout_seconds'] = options.timeoutSeconds;
1278
- return this.call('group_space_provider_auth', args);
1364
+ if (options.message)
1365
+ args['message'] = options.message;
1366
+ if (options.title)
1367
+ args['title'] = options.title;
1368
+ if (options.targetActorId)
1369
+ args['target_actor_id'] = options.targetActorId;
1370
+ if (options.imVisibility)
1371
+ args['im_visibility'] = options.imVisibility;
1372
+ if (options.context)
1373
+ args['context'] = options.context;
1374
+ return this.call('system_notify', args);
1375
+ }
1376
+ // ============================================================
1377
+ // Convenience methods: registry / group admin
1378
+ // ============================================================
1379
+ async registryReconcile(options = {}) {
1380
+ return this.call('registry_reconcile', {
1381
+ remove_missing: options.removeMissing ?? false,
1382
+ });
1383
+ }
1384
+ async groupDetachScope(options) {
1385
+ return this.call('group_detach_scope', {
1386
+ group_id: options.groupId,
1387
+ scope_key: options.scopeKey,
1388
+ by: options.by ?? 'user',
1389
+ });
1279
1390
  }
1280
1391
  // ============================================================
1281
1392
  // Event stream
@@ -1284,12 +1395,11 @@ export class CCCCClient {
1284
1395
  * Subscribe to the group event stream (Server-Sent Events style, long-lived connection).
1285
1396
  * Yields {@link EventStreamItem} objects as they arrive. The socket is destroyed
1286
1397
  * when the generator is returned or thrown.
1287
- * @param options - Group ID, event filters, optional since cursor, and optional AbortSignal.
1398
+ * @param options - Group ID, event filters, and optional since cursor.
1288
1399
  * @yields {EventStreamItem} Each event or heartbeat from the stream.
1289
1400
  * @throws {DaemonAPIError} If the handshake fails.
1290
1401
  */
1291
1402
  async *eventsStream(options) {
1292
- // Check abort before connecting
1293
1403
  if (options.signal?.aborted)
1294
1404
  return;
1295
1405
  const args = {
@@ -1312,44 +1422,61 @@ export class CCCCClient {
1312
1422
  op: 'events_stream',
1313
1423
  args,
1314
1424
  };
1315
- const { socket, handshake, initialBuffer } = await openEventsStream(this._endpoint, request, options.timeoutMs ?? this._timeoutMs);
1425
+ const streamTimeoutMs = options.timeoutMs ?? this._timeoutMs;
1426
+ let connection;
1427
+ try {
1428
+ connection = await openEventsStream(this._endpoint, request, streamTimeoutMs, options.signal);
1429
+ }
1430
+ catch (error) {
1431
+ if (!(error instanceof DaemonConnectionError) || this._endpointExplicit) {
1432
+ throw error;
1433
+ }
1434
+ this._endpoint = await discoverEndpoint(this._ccccHome);
1435
+ connection = await openEventsStream(this._endpoint, request, streamTimeoutMs, options.signal);
1436
+ }
1437
+ const { socket, handshake, initialBuffer } = connection;
1438
+ if (options.signal?.aborted) {
1439
+ socket.destroy();
1440
+ return;
1441
+ }
1442
+ if (handshake.v !== undefined && handshake.v !== 1) {
1443
+ socket.destroy();
1444
+ throw new IncompatibleDaemonError(`Daemon stream handshake uses unsupported IPC version: ${handshake.v}`);
1445
+ }
1316
1446
  if (!handshake.ok) {
1317
1447
  socket.destroy();
1318
1448
  throw new DaemonAPIError(handshake.error?.code ?? 'unknown', handshake.error?.message ?? 'Handshake failed', handshake.error?.details, handshake);
1319
1449
  }
1320
- // Wire up AbortSignal to destroy the socket
1321
- const onAbort = () => { socket.destroy(); };
1322
- options.signal?.addEventListener('abort', onAbort, { once: true });
1450
+ const abortStream = () => socket.destroy();
1451
+ options.signal?.addEventListener('abort', abortStream, { once: true });
1323
1452
  try {
1324
1453
  for await (const line of readLines(socket, initialBuffer)) {
1325
- if (options.signal?.aborted)
1326
- return;
1327
- let parsed;
1328
1454
  try {
1329
- parsed = JSON.parse(line);
1330
- }
1331
- catch {
1332
- // Non-JSON line from daemon; surface as an error so callers know
1333
- // something unexpected happened instead of silently losing data.
1334
- throw new DaemonAPIError('invalid_stream_data', `Unparseable event stream line: ${line.slice(0, 200)}`, {});
1335
- }
1336
- if (parsed !== null && typeof parsed === 'object') {
1337
- const obj = parsed;
1338
- // Daemon may send error objects in the stream (ok: false).
1339
- if ('ok' in obj && obj['ok'] === false) {
1340
- const err = obj['error'];
1341
- throw new DaemonAPIError(err?.['code'] ?? 'stream_error', err?.['message'] ?? 'Daemon sent error in event stream', err?.['details'] ?? {}, obj);
1342
- }
1343
- if ('t' in obj) {
1455
+ const parsed = JSON.parse(line);
1456
+ if (parsed !== null && typeof parsed === 'object' && 't' in parsed) {
1344
1457
  yield parsed;
1345
1458
  }
1346
1459
  }
1460
+ catch {
1461
+ // Skip invalid JSON lines.
1462
+ }
1347
1463
  }
1348
1464
  }
1465
+ catch (error) {
1466
+ // Cancelling an established subscription is normal completion. Preserve
1467
+ // unexpected transport/decoding errors when the caller did not cancel.
1468
+ if (!options.signal?.aborted)
1469
+ throw error;
1470
+ }
1349
1471
  finally {
1350
- options.signal?.removeEventListener('abort', onAbort);
1472
+ options.signal?.removeEventListener('abort', abortStream);
1351
1473
  socket.destroy();
1352
1474
  }
1353
1475
  }
1354
1476
  }
1477
+ installCCCC0430Ops(CCCCClient.prototype);
1478
+ installCCCC0434Ops(CCCCClient.prototype);
1479
+ installGroupSpaceOps(CCCCClient.prototype);
1480
+ installChatOps(CCCCClient.prototype);
1481
+ installConnectOps(CCCCClient.prototype);
1355
1482
  //# sourceMappingURL=client.js.map