cccc-sdk 0.1.4 → 0.4.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/README.md +68 -0
- package/dist/client.d.ts +154 -58
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +523 -162
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +1 -31
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -31
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +3 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -6
- package/dist/index.js.map +1 -1
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +0 -3
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +282 -192
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -16
- package/dist/types.js.map +1 -1
- package/package.json +3 -3
package/dist/client.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CCCC SDK client
|
|
3
3
|
*/
|
|
4
|
-
import { DaemonAPIError, IncompatibleDaemonError,
|
|
5
|
-
import { isStreamEvent } from './types.js';
|
|
4
|
+
import { DaemonAPIError, IncompatibleDaemonError, } from './errors.js';
|
|
6
5
|
import { discoverEndpoint, callDaemon, openEventsStream, readLines, } from './transport.js';
|
|
7
6
|
/**
|
|
8
7
|
* Client for communicating with the CCCC daemon over IPC (Unix socket or TCP).
|
|
@@ -51,23 +50,22 @@ export class CCCCClient {
|
|
|
51
50
|
args: args ?? {},
|
|
52
51
|
};
|
|
53
52
|
const response = await callDaemon(this._endpoint, request, this._timeoutMs);
|
|
54
|
-
if (!response.ok) {
|
|
55
|
-
throw new DaemonAPIError(response.error
|
|
53
|
+
if (!response.ok && response.error) {
|
|
54
|
+
throw new DaemonAPIError(response.error.code ?? 'error', response.error.message ?? 'daemon error', response.error.details ?? {}, response);
|
|
56
55
|
}
|
|
57
56
|
return response;
|
|
58
57
|
}
|
|
59
58
|
/**
|
|
60
59
|
* Send an IPC request and return only the result payload.
|
|
61
|
-
* @typeParam T - Expected result type (defaults to `Record<string, unknown>`).
|
|
62
60
|
* @param op - The IPC operation name.
|
|
63
61
|
* @param args - Operation arguments.
|
|
64
|
-
* @returns The `result` field from the daemon response
|
|
62
|
+
* @returns The `result` field from the daemon response (empty object if absent).
|
|
65
63
|
* @throws {DaemonAPIError} If the daemon returns an error.
|
|
66
64
|
* @throws {DaemonUnavailableError} If the connection fails.
|
|
67
65
|
*/
|
|
68
66
|
async call(op, args) {
|
|
69
67
|
const response = await this.callRaw(op, args);
|
|
70
|
-
return
|
|
68
|
+
return response.result ?? {};
|
|
71
69
|
}
|
|
72
70
|
/**
|
|
73
71
|
* Assert that the connected daemon meets the caller's compatibility requirements.
|
|
@@ -79,8 +77,12 @@ export class CCCCClient {
|
|
|
79
77
|
*/
|
|
80
78
|
async assertCompatible(options = {}) {
|
|
81
79
|
const pingResult = await this.ping();
|
|
82
|
-
const
|
|
83
|
-
const
|
|
80
|
+
const rawIpcV = pingResult['ipc_v'];
|
|
81
|
+
const ipcV = typeof rawIpcV === 'number' ? rawIpcV : 0;
|
|
82
|
+
const rawCaps = pingResult['capabilities'];
|
|
83
|
+
const capabilities = (rawCaps !== null && typeof rawCaps === 'object' && !Array.isArray(rawCaps))
|
|
84
|
+
? rawCaps
|
|
85
|
+
: {};
|
|
84
86
|
// Check IPC version
|
|
85
87
|
const requiredV = options.requireIpcV ?? 1;
|
|
86
88
|
if (ipcV < requiredV) {
|
|
@@ -101,7 +103,7 @@ export class CCCCClient {
|
|
|
101
103
|
await this.callRaw(op, {});
|
|
102
104
|
}
|
|
103
105
|
catch (e) {
|
|
104
|
-
if (e instanceof DaemonAPIError && e.code ===
|
|
106
|
+
if (e instanceof DaemonAPIError && e.code === 'unknown_op') {
|
|
105
107
|
throw new IncompatibleDaemonError(`Operation not supported: ${op}`);
|
|
106
108
|
}
|
|
107
109
|
// Other errors (e.g. missing_group_id) imply the operation exists.
|
|
@@ -205,7 +207,7 @@ export class CCCCClient {
|
|
|
205
207
|
async groupAutomationManage(options) {
|
|
206
208
|
const actions = options.actions;
|
|
207
209
|
if (actions.length === 0) {
|
|
208
|
-
throw new DaemonAPIError(
|
|
210
|
+
throw new DaemonAPIError('invalid_args', 'groupAutomationManage requires a non-empty actions array', {});
|
|
209
211
|
}
|
|
210
212
|
const args = {
|
|
211
213
|
group_id: options.groupId,
|
|
@@ -274,6 +276,8 @@ export class CCCCClient {
|
|
|
274
276
|
command: options.command,
|
|
275
277
|
env: options.env,
|
|
276
278
|
env_private: options.envPrivate,
|
|
279
|
+
capability_autoload: options.capabilityAutoload,
|
|
280
|
+
profile_id: options.profileId,
|
|
277
281
|
default_scope_key: options.defaultScopeKey,
|
|
278
282
|
submit: options.submit,
|
|
279
283
|
};
|
|
@@ -291,12 +295,17 @@ export class CCCCClient {
|
|
|
291
295
|
* Update actor
|
|
292
296
|
*/
|
|
293
297
|
async actorUpdate(options) {
|
|
294
|
-
|
|
298
|
+
const args = {
|
|
295
299
|
group_id: options.groupId,
|
|
296
300
|
actor_id: options.actorId,
|
|
297
|
-
patch: options.patch,
|
|
301
|
+
patch: options.patch ?? {},
|
|
298
302
|
by: options.by ?? 'user',
|
|
299
|
-
}
|
|
303
|
+
};
|
|
304
|
+
if (options.profileId != null)
|
|
305
|
+
args['profile_id'] = options.profileId;
|
|
306
|
+
if (options.profileAction != null)
|
|
307
|
+
args['profile_action'] = options.profileAction;
|
|
308
|
+
return this.call('actor_update', args);
|
|
300
309
|
}
|
|
301
310
|
/**
|
|
302
311
|
* Remove actor
|
|
@@ -305,25 +314,10 @@ export class CCCCClient {
|
|
|
305
314
|
return this.call('actor_remove', { group_id: groupId, actor_id: actorId, by });
|
|
306
315
|
}
|
|
307
316
|
/**
|
|
308
|
-
* Start actor
|
|
309
|
-
* @param groupId - Group ID.
|
|
310
|
-
* @param actorId - Actor ID.
|
|
311
|
-
* @param options - Optional settings. Pass `{ idempotent: true }` to suppress errors when the actor is already running.
|
|
312
|
-
* @returns The daemon result.
|
|
313
|
-
* @throws {DaemonAPIError} On error (unless idempotent and actor already running).
|
|
317
|
+
* Start actor
|
|
314
318
|
*/
|
|
315
|
-
async actorStart(groupId, actorId,
|
|
316
|
-
|
|
317
|
-
const idempotent = typeof options === 'object' && options?.idempotent === true;
|
|
318
|
-
try {
|
|
319
|
-
return await this.call('actor_start', { group_id: groupId, actor_id: actorId, by });
|
|
320
|
-
}
|
|
321
|
-
catch (e) {
|
|
322
|
-
if (idempotent && e instanceof DaemonAPIError && e.code === ErrorCodes.CONFLICT) {
|
|
323
|
-
return {};
|
|
324
|
-
}
|
|
325
|
-
throw e;
|
|
326
|
-
}
|
|
319
|
+
async actorStart(groupId, actorId, by = 'user') {
|
|
320
|
+
return this.call('actor_start', { group_id: groupId, actor_id: actorId, by });
|
|
327
321
|
}
|
|
328
322
|
/**
|
|
329
323
|
* Stop actor
|
|
@@ -359,13 +353,299 @@ export class CCCCClient {
|
|
|
359
353
|
args['unset'] = options.unset;
|
|
360
354
|
return this.call('actor_env_private_update', args);
|
|
361
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* List global actor profiles.
|
|
358
|
+
*/
|
|
359
|
+
async actorProfileList(by = 'user') {
|
|
360
|
+
return this.call('actor_profile_list', { by });
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Get one actor profile and current usage.
|
|
364
|
+
*/
|
|
365
|
+
async actorProfileGet(profileId, by = 'user') {
|
|
366
|
+
return this.call('actor_profile_get', { profile_id: profileId, by });
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Create/update one actor profile.
|
|
370
|
+
*/
|
|
371
|
+
async actorProfileUpsert(options) {
|
|
372
|
+
const profile = { ...options.profile };
|
|
373
|
+
if ('capabilityDefaults' in profile) {
|
|
374
|
+
const rawDefaults = profile['capabilityDefaults'];
|
|
375
|
+
delete profile['capabilityDefaults'];
|
|
376
|
+
if (rawDefaults != null && typeof rawDefaults === 'object' && !Array.isArray(rawDefaults)) {
|
|
377
|
+
const defaults = rawDefaults;
|
|
378
|
+
profile['capability_defaults'] = {
|
|
379
|
+
...(defaults['autoloadCapabilities'] !== undefined
|
|
380
|
+
? { autoload_capabilities: defaults['autoloadCapabilities'] }
|
|
381
|
+
: {}),
|
|
382
|
+
...(defaults['defaultScope'] !== undefined
|
|
383
|
+
? { default_scope: defaults['defaultScope'] }
|
|
384
|
+
: {}),
|
|
385
|
+
...(defaults['sessionTtlSeconds'] !== undefined
|
|
386
|
+
? { session_ttl_seconds: defaults['sessionTtlSeconds'] }
|
|
387
|
+
: {}),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
profile['capability_defaults'] = rawDefaults;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const args = {
|
|
395
|
+
profile,
|
|
396
|
+
by: options.by ?? 'user',
|
|
397
|
+
};
|
|
398
|
+
if (options.expectedRevision !== undefined)
|
|
399
|
+
args['expected_revision'] = options.expectedRevision;
|
|
400
|
+
return this.call('actor_profile_upsert', args);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Delete one actor profile (rejected when still in use).
|
|
404
|
+
*/
|
|
405
|
+
async actorProfileDelete(profileId, by = 'user', forceDetach = false) {
|
|
406
|
+
return this.call('actor_profile_delete', { profile_id: profileId, by, force_detach: forceDetach });
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* List profile-level secret keys and masked previews.
|
|
410
|
+
*/
|
|
411
|
+
async actorProfileSecretKeys(profileId, by = 'user') {
|
|
412
|
+
return this.call('actor_profile_secret_keys', { profile_id: profileId, by });
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Update profile-level private env.
|
|
416
|
+
*/
|
|
417
|
+
async actorProfileSecretUpdate(options) {
|
|
418
|
+
const args = {
|
|
419
|
+
profile_id: options.profileId,
|
|
420
|
+
by: options.by ?? 'user',
|
|
421
|
+
clear: options.clear ?? false,
|
|
422
|
+
};
|
|
423
|
+
if (options.set)
|
|
424
|
+
args['set'] = options.set;
|
|
425
|
+
if (options.unset)
|
|
426
|
+
args['unset'] = options.unset;
|
|
427
|
+
return this.call('actor_profile_secret_update', args);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Copy one actor's runtime env (public + private) into a profile's private env.
|
|
431
|
+
*/
|
|
432
|
+
async actorProfileSecretCopyFromActor(options) {
|
|
433
|
+
return this.call('actor_profile_secret_copy_from_actor', {
|
|
434
|
+
profile_id: options.profileId,
|
|
435
|
+
group_id: options.groupId,
|
|
436
|
+
actor_id: options.actorId,
|
|
437
|
+
by: options.by ?? 'user',
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Copy one profile's secrets into another profile.
|
|
442
|
+
*/
|
|
443
|
+
async actorProfileSecretCopyFromProfile(options) {
|
|
444
|
+
return this.call('actor_profile_secret_copy_from_profile', {
|
|
445
|
+
profile_id: options.profileId,
|
|
446
|
+
source_profile_id: options.sourceProfileId,
|
|
447
|
+
by: options.by ?? 'user',
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
// ============================================================
|
|
451
|
+
// Convenience methods: capabilities
|
|
452
|
+
// ============================================================
|
|
453
|
+
/**
|
|
454
|
+
* Read the global capability overview snapshot.
|
|
455
|
+
*/
|
|
456
|
+
async capabilityOverview(options = {}) {
|
|
457
|
+
const args = {};
|
|
458
|
+
if (options.query)
|
|
459
|
+
args['query'] = options.query;
|
|
460
|
+
if (options.limit !== undefined)
|
|
461
|
+
args['limit'] = options.limit;
|
|
462
|
+
if (options.includeIndexed !== undefined)
|
|
463
|
+
args['include_indexed'] = options.includeIndexed;
|
|
464
|
+
return this.call('capability_overview', args);
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Search the capability registry for one group/caller scope.
|
|
468
|
+
*/
|
|
469
|
+
async capabilitySearch(options) {
|
|
470
|
+
const args = {
|
|
471
|
+
group_id: options.groupId,
|
|
472
|
+
by: options.by ?? 'user',
|
|
473
|
+
};
|
|
474
|
+
if (options.actorId)
|
|
475
|
+
args['actor_id'] = options.actorId;
|
|
476
|
+
if (options.query)
|
|
477
|
+
args['query'] = options.query;
|
|
478
|
+
if (options.kind !== undefined)
|
|
479
|
+
args['kind'] = options.kind;
|
|
480
|
+
if (options.sourceId)
|
|
481
|
+
args['source_id'] = options.sourceId;
|
|
482
|
+
if (options.trustTier)
|
|
483
|
+
args['trust_tier'] = options.trustTier;
|
|
484
|
+
if (options.qualificationStatus !== undefined)
|
|
485
|
+
args['qualification_status'] = options.qualificationStatus;
|
|
486
|
+
if (options.includeExternal !== undefined)
|
|
487
|
+
args['include_external'] = options.includeExternal;
|
|
488
|
+
if (options.limit !== undefined)
|
|
489
|
+
args['limit'] = options.limit;
|
|
490
|
+
return this.call('capability_search', args);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Enable or disable a capability.
|
|
494
|
+
*/
|
|
495
|
+
async capabilityEnable(options) {
|
|
496
|
+
const args = {
|
|
497
|
+
group_id: options.groupId,
|
|
498
|
+
capability_id: options.capabilityId,
|
|
499
|
+
scope: options.scope ?? 'session',
|
|
500
|
+
enabled: options.enabled ?? true,
|
|
501
|
+
cleanup: options.cleanup ?? false,
|
|
502
|
+
by: options.by ?? 'user',
|
|
503
|
+
};
|
|
504
|
+
if (options.reason)
|
|
505
|
+
args['reason'] = options.reason;
|
|
506
|
+
if (options.ttlSeconds !== undefined)
|
|
507
|
+
args['ttl_seconds'] = options.ttlSeconds;
|
|
508
|
+
if (options.actorId)
|
|
509
|
+
args['actor_id'] = options.actorId;
|
|
510
|
+
return this.call('capability_enable', args);
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Block or unblock a capability.
|
|
514
|
+
*/
|
|
515
|
+
async capabilityBlock(options) {
|
|
516
|
+
const args = {
|
|
517
|
+
group_id: options.groupId,
|
|
518
|
+
capability_id: options.capabilityId,
|
|
519
|
+
scope: options.scope ?? 'group',
|
|
520
|
+
blocked: options.blocked ?? true,
|
|
521
|
+
by: options.by ?? 'user',
|
|
522
|
+
};
|
|
523
|
+
if (options.ttlSeconds !== undefined)
|
|
524
|
+
args['ttl_seconds'] = options.ttlSeconds;
|
|
525
|
+
if (options.reason)
|
|
526
|
+
args['reason'] = options.reason;
|
|
527
|
+
if (options.actorId)
|
|
528
|
+
args['actor_id'] = options.actorId;
|
|
529
|
+
return this.call('capability_block', args);
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Read effective capability exposure for one caller scope.
|
|
533
|
+
*/
|
|
534
|
+
async capabilityState(options) {
|
|
535
|
+
const args = {
|
|
536
|
+
group_id: options.groupId,
|
|
537
|
+
by: options.by ?? 'user',
|
|
538
|
+
};
|
|
539
|
+
if (options.actorId)
|
|
540
|
+
args['actor_id'] = options.actorId;
|
|
541
|
+
return this.call('capability_state', args);
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Read capability allowlist default, overlay, and effective snapshots.
|
|
545
|
+
*/
|
|
546
|
+
async capabilityAllowlistGet(options = {}) {
|
|
547
|
+
return this.call('capability_allowlist_get', {
|
|
548
|
+
by: options.by ?? 'user',
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Dry-run capability allowlist overlay validation without persistence.
|
|
553
|
+
*/
|
|
554
|
+
async capabilityAllowlistValidate(options = {}) {
|
|
555
|
+
const args = {
|
|
556
|
+
mode: options.mode ?? 'patch',
|
|
557
|
+
};
|
|
558
|
+
if (options.patch !== undefined)
|
|
559
|
+
args['patch'] = options.patch;
|
|
560
|
+
if (options.overlay !== undefined)
|
|
561
|
+
args['overlay'] = options.overlay;
|
|
562
|
+
return this.call('capability_allowlist_validate', args);
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Persist capability allowlist overlay with optional optimistic concurrency.
|
|
566
|
+
*/
|
|
567
|
+
async capabilityAllowlistUpdate(options = {}) {
|
|
568
|
+
const args = {
|
|
569
|
+
by: options.by ?? 'user',
|
|
570
|
+
mode: options.mode ?? 'patch',
|
|
571
|
+
};
|
|
572
|
+
if (options.expectedRevision)
|
|
573
|
+
args['expected_revision'] = options.expectedRevision;
|
|
574
|
+
if (options.patch !== undefined)
|
|
575
|
+
args['patch'] = options.patch;
|
|
576
|
+
if (options.overlay !== undefined)
|
|
577
|
+
args['overlay'] = options.overlay;
|
|
578
|
+
return this.call('capability_allowlist_update', args);
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Reset capability allowlist overlay to empty/default state.
|
|
582
|
+
*/
|
|
583
|
+
async capabilityAllowlistReset(options = {}) {
|
|
584
|
+
return this.call('capability_allowlist_reset', {
|
|
585
|
+
by: options.by ?? 'user',
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Import one structured capability record, with optional readiness probe.
|
|
590
|
+
*/
|
|
591
|
+
async capabilityImport(options) {
|
|
592
|
+
const args = {
|
|
593
|
+
group_id: options.groupId,
|
|
594
|
+
record: options.record,
|
|
595
|
+
by: options.by ?? 'user',
|
|
596
|
+
dry_run: options.dryRun ?? false,
|
|
597
|
+
};
|
|
598
|
+
if (options.actorId)
|
|
599
|
+
args['actor_id'] = options.actorId;
|
|
600
|
+
if (options.probe !== undefined)
|
|
601
|
+
args['probe'] = options.probe;
|
|
602
|
+
if (options.enableAfterImport !== undefined)
|
|
603
|
+
args['enable_after_import'] = options.enableAfterImport;
|
|
604
|
+
if (options.scope)
|
|
605
|
+
args['scope'] = options.scope;
|
|
606
|
+
if (options.ttlSeconds !== undefined)
|
|
607
|
+
args['ttl_seconds'] = options.ttlSeconds;
|
|
608
|
+
if (options.reason)
|
|
609
|
+
args['reason'] = options.reason;
|
|
610
|
+
return this.call('capability_import', args);
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Uninstall a capability from the target group scope.
|
|
614
|
+
*/
|
|
615
|
+
async capabilityUninstall(options) {
|
|
616
|
+
const args = {
|
|
617
|
+
group_id: options.groupId,
|
|
618
|
+
capability_id: options.capabilityId,
|
|
619
|
+
by: options.by ?? 'user',
|
|
620
|
+
};
|
|
621
|
+
if (options.reason)
|
|
622
|
+
args['reason'] = options.reason;
|
|
623
|
+
if (options.actorId)
|
|
624
|
+
args['actor_id'] = options.actorId;
|
|
625
|
+
return this.call('capability_uninstall', args);
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Call one enabled dynamic capability tool through daemon IPC.
|
|
629
|
+
*/
|
|
630
|
+
async capabilityToolCall(options) {
|
|
631
|
+
const args = {
|
|
632
|
+
group_id: options.groupId,
|
|
633
|
+
tool_name: options.toolName,
|
|
634
|
+
by: options.by ?? 'user',
|
|
635
|
+
};
|
|
636
|
+
if (options.arguments)
|
|
637
|
+
args['arguments'] = options.arguments;
|
|
638
|
+
if (options.actorId)
|
|
639
|
+
args['actor_id'] = options.actorId;
|
|
640
|
+
return this.call('capability_tool_call', args);
|
|
641
|
+
}
|
|
362
642
|
// ============================================================
|
|
363
643
|
// Convenience methods: messaging
|
|
364
644
|
// ============================================================
|
|
365
645
|
/**
|
|
366
646
|
* Send a chat message to a group.
|
|
367
647
|
* @param options - Message content, recipients, and priority.
|
|
368
|
-
* @returns The
|
|
648
|
+
* @returns The daemon result (includes event id).
|
|
369
649
|
* @throws {DaemonAPIError} On invalid group, missing permissions, etc.
|
|
370
650
|
*/
|
|
371
651
|
async send(options) {
|
|
@@ -383,8 +663,7 @@ export class CCCCClient {
|
|
|
383
663
|
return this.call('send', args);
|
|
384
664
|
}
|
|
385
665
|
/**
|
|
386
|
-
* Send message across groups
|
|
387
|
-
* @returns The created event and optional ack event.
|
|
666
|
+
* Send message across groups
|
|
388
667
|
*/
|
|
389
668
|
async sendCrossGroup(options) {
|
|
390
669
|
const args = {
|
|
@@ -400,8 +679,7 @@ export class CCCCClient {
|
|
|
400
679
|
return this.call('send_cross_group', args);
|
|
401
680
|
}
|
|
402
681
|
/**
|
|
403
|
-
* Reply
|
|
404
|
-
* @returns The created event and optional ack event.
|
|
682
|
+
* Reply message
|
|
405
683
|
*/
|
|
406
684
|
async reply(options) {
|
|
407
685
|
const args = {
|
|
@@ -416,55 +694,6 @@ export class CCCCClient {
|
|
|
416
694
|
args['to'] = options.to;
|
|
417
695
|
return this.call('reply', args);
|
|
418
696
|
}
|
|
419
|
-
/**
|
|
420
|
-
* Send a message and wait for a reply to it.
|
|
421
|
-
* Opens the event stream **before** sending to avoid race conditions,
|
|
422
|
-
* then yields control to the stream until a matching reply arrives.
|
|
423
|
-
*
|
|
424
|
-
* @param options - Send options plus `listenAs` (the actor to listen as) and optional `waitTimeoutMs`.
|
|
425
|
-
* @returns The reply event (a `chat.message` whose `reply_to` matches the sent event ID).
|
|
426
|
-
* @throws {DaemonAPIError} On send/stream errors.
|
|
427
|
-
* @throws {Error} If the timeout elapses or the signal is aborted before a reply arrives.
|
|
428
|
-
*/
|
|
429
|
-
async sendAndWaitForReply(options) {
|
|
430
|
-
const waitTimeout = options.waitTimeoutMs ?? 60000;
|
|
431
|
-
const deadline = Date.now() + waitTimeout;
|
|
432
|
-
// Open stream FIRST (with sinceTs = now) to avoid missing replies
|
|
433
|
-
// that arrive between send() returning and stream connecting.
|
|
434
|
-
const stream = this.eventsStream({
|
|
435
|
-
groupId: options.groupId,
|
|
436
|
-
by: options.listenAs,
|
|
437
|
-
kinds: ['chat.message'],
|
|
438
|
-
sinceTs: new Date().toISOString(),
|
|
439
|
-
signal: options.signal,
|
|
440
|
-
});
|
|
441
|
-
// Now send the message
|
|
442
|
-
const sendResult = await this.send(options);
|
|
443
|
-
const sentEventId = sendResult.event.id;
|
|
444
|
-
try {
|
|
445
|
-
for await (const item of stream) {
|
|
446
|
-
if (options.signal?.aborted) {
|
|
447
|
-
throw new Error('sendAndWaitForReply aborted');
|
|
448
|
-
}
|
|
449
|
-
if (Date.now() > deadline) {
|
|
450
|
-
throw new Error(`sendAndWaitForReply timed out after ${waitTimeout}ms`);
|
|
451
|
-
}
|
|
452
|
-
if (isStreamEvent(item)) {
|
|
453
|
-
const evt = item.event;
|
|
454
|
-
if (evt.kind === 'chat.message') {
|
|
455
|
-
const data = evt.data;
|
|
456
|
-
if (data['reply_to'] === sentEventId) {
|
|
457
|
-
return evt;
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
finally {
|
|
464
|
-
stream.return(undefined);
|
|
465
|
-
}
|
|
466
|
-
throw new Error('sendAndWaitForReply: stream ended without reply');
|
|
467
|
-
}
|
|
468
697
|
/**
|
|
469
698
|
* Acknowledge chat message
|
|
470
699
|
*/
|
|
@@ -476,56 +705,6 @@ export class CCCCClient {
|
|
|
476
705
|
by: by ?? actorId,
|
|
477
706
|
});
|
|
478
707
|
}
|
|
479
|
-
/**
|
|
480
|
-
* Send a file as a chat attachment.
|
|
481
|
-
* @param options - File path (relative to scope root), optional caption and recipients.
|
|
482
|
-
* @returns The created event and optional ack event.
|
|
483
|
-
*/
|
|
484
|
-
async fileSend(options) {
|
|
485
|
-
const args = {
|
|
486
|
-
group_id: options.groupId,
|
|
487
|
-
path: options.path,
|
|
488
|
-
by: options.by ?? 'user',
|
|
489
|
-
priority: options.priority ?? 'normal',
|
|
490
|
-
reply_required: options.replyRequired ?? false,
|
|
491
|
-
};
|
|
492
|
-
if (options.text)
|
|
493
|
-
args['text'] = options.text;
|
|
494
|
-
if (options.to)
|
|
495
|
-
args['to'] = options.to;
|
|
496
|
-
return this.call('file_send', args);
|
|
497
|
-
}
|
|
498
|
-
/**
|
|
499
|
-
* Get recent chat messages from the ledger.
|
|
500
|
-
* @param options - Group ID, limit, and max chars.
|
|
501
|
-
* @returns The ledger tail result.
|
|
502
|
-
*/
|
|
503
|
-
async ledgerTail(options) {
|
|
504
|
-
const args = {
|
|
505
|
-
group_id: options.groupId,
|
|
506
|
-
by: options.by ?? 'user',
|
|
507
|
-
};
|
|
508
|
-
if (options.limit !== undefined)
|
|
509
|
-
args['limit'] = options.limit;
|
|
510
|
-
if (options.maxChars !== undefined)
|
|
511
|
-
args['max_chars'] = options.maxChars;
|
|
512
|
-
return this.call('ledger_tail', args);
|
|
513
|
-
}
|
|
514
|
-
/**
|
|
515
|
-
* Get recent terminal output from an actor.
|
|
516
|
-
* @param options - Group ID, actor ID, and line count.
|
|
517
|
-
* @returns The terminal tail result.
|
|
518
|
-
*/
|
|
519
|
-
async terminalTail(options) {
|
|
520
|
-
const args = {
|
|
521
|
-
group_id: options.groupId,
|
|
522
|
-
actor_id: options.actorId,
|
|
523
|
-
by: options.by ?? 'user',
|
|
524
|
-
};
|
|
525
|
-
if (options.lines !== undefined)
|
|
526
|
-
args['lines'] = options.lines;
|
|
527
|
-
return this.call('terminal_tail', args);
|
|
528
|
-
}
|
|
529
708
|
// ============================================================
|
|
530
709
|
// Convenience methods: inbox
|
|
531
710
|
// ============================================================
|
|
@@ -598,20 +777,219 @@ export class CCCCClient {
|
|
|
598
777
|
});
|
|
599
778
|
}
|
|
600
779
|
// ============================================================
|
|
780
|
+
// Convenience methods: Group Space
|
|
781
|
+
// ============================================================
|
|
782
|
+
/**
|
|
783
|
+
* Read Group Space provider and binding status.
|
|
784
|
+
*/
|
|
785
|
+
async groupSpaceStatus(options) {
|
|
786
|
+
return this.call('group_space_status', {
|
|
787
|
+
group_id: options.groupId,
|
|
788
|
+
provider: options.provider ?? 'notebooklm',
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* List available remote spaces for binding.
|
|
793
|
+
*/
|
|
794
|
+
async groupSpaceSpaces(options) {
|
|
795
|
+
return this.call('group_space_spaces', {
|
|
796
|
+
group_id: options.groupId,
|
|
797
|
+
provider: options.provider ?? 'notebooklm',
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Read the provider capability matrix for a group.
|
|
802
|
+
*/
|
|
803
|
+
async groupSpaceCapabilities(options) {
|
|
804
|
+
return this.call('group_space_capabilities', {
|
|
805
|
+
group_id: options.groupId,
|
|
806
|
+
provider: options.provider ?? 'notebooklm',
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Bind or unbind one Group Space lane.
|
|
811
|
+
*/
|
|
812
|
+
async groupSpaceBind(options) {
|
|
813
|
+
const args = {
|
|
814
|
+
group_id: options.groupId,
|
|
815
|
+
provider: options.provider ?? 'notebooklm',
|
|
816
|
+
lane: options.lane,
|
|
817
|
+
action: options.action ?? 'bind',
|
|
818
|
+
by: options.by ?? 'user',
|
|
819
|
+
};
|
|
820
|
+
if (options.remoteSpaceId)
|
|
821
|
+
args['remote_space_id'] = options.remoteSpaceId;
|
|
822
|
+
return this.call('group_space_bind', args);
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Enqueue one Group Space ingest action.
|
|
826
|
+
*/
|
|
827
|
+
async groupSpaceIngest(options) {
|
|
828
|
+
const args = {
|
|
829
|
+
group_id: options.groupId,
|
|
830
|
+
provider: options.provider ?? 'notebooklm',
|
|
831
|
+
lane: options.lane,
|
|
832
|
+
kind: options.kind ?? 'context_sync',
|
|
833
|
+
by: options.by ?? 'user',
|
|
834
|
+
};
|
|
835
|
+
if (options.payload)
|
|
836
|
+
args['payload'] = options.payload;
|
|
837
|
+
if (options.idempotencyKey)
|
|
838
|
+
args['idempotency_key'] = options.idempotencyKey;
|
|
839
|
+
return this.call('group_space_ingest', args);
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Query Group Space knowledge for one lane.
|
|
843
|
+
*/
|
|
844
|
+
async groupSpaceQuery(options) {
|
|
845
|
+
const args = {
|
|
846
|
+
group_id: options.groupId,
|
|
847
|
+
provider: options.provider ?? 'notebooklm',
|
|
848
|
+
lane: options.lane,
|
|
849
|
+
query: options.query,
|
|
850
|
+
};
|
|
851
|
+
if (options.options)
|
|
852
|
+
args['options'] = options.options;
|
|
853
|
+
return this.call('group_space_query', args);
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Manage remote sources in the bound Group Space lane.
|
|
857
|
+
*/
|
|
858
|
+
async groupSpaceSources(options) {
|
|
859
|
+
const args = {
|
|
860
|
+
group_id: options.groupId,
|
|
861
|
+
provider: options.provider ?? 'notebooklm',
|
|
862
|
+
lane: options.lane,
|
|
863
|
+
action: options.action ?? 'list',
|
|
864
|
+
by: options.by ?? 'user',
|
|
865
|
+
};
|
|
866
|
+
if (options.sourceId)
|
|
867
|
+
args['source_id'] = options.sourceId;
|
|
868
|
+
if (options.newTitle)
|
|
869
|
+
args['new_title'] = options.newTitle;
|
|
870
|
+
return this.call('group_space_sources', args);
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* List, generate, or download Group Space artifacts.
|
|
874
|
+
*/
|
|
875
|
+
async groupSpaceArtifact(options) {
|
|
876
|
+
const args = {
|
|
877
|
+
group_id: options.groupId,
|
|
878
|
+
provider: options.provider ?? 'notebooklm',
|
|
879
|
+
lane: options.lane,
|
|
880
|
+
action: options.action ?? 'list',
|
|
881
|
+
by: options.by ?? 'user',
|
|
882
|
+
};
|
|
883
|
+
if (options.kind)
|
|
884
|
+
args['kind'] = options.kind;
|
|
885
|
+
if (options.options)
|
|
886
|
+
args['options'] = options.options;
|
|
887
|
+
if (options.wait !== undefined)
|
|
888
|
+
args['wait'] = options.wait;
|
|
889
|
+
if (options.saveToSpace !== undefined)
|
|
890
|
+
args['save_to_space'] = options.saveToSpace;
|
|
891
|
+
if (options.outputPath)
|
|
892
|
+
args['output_path'] = options.outputPath;
|
|
893
|
+
if (options.outputFormat)
|
|
894
|
+
args['output_format'] = options.outputFormat;
|
|
895
|
+
if (options.artifactId)
|
|
896
|
+
args['artifact_id'] = options.artifactId;
|
|
897
|
+
if (options.timeoutSeconds !== undefined)
|
|
898
|
+
args['timeout_seconds'] = options.timeoutSeconds;
|
|
899
|
+
if (options.initialInterval !== undefined)
|
|
900
|
+
args['initial_interval'] = options.initialInterval;
|
|
901
|
+
if (options.maxInterval !== undefined)
|
|
902
|
+
args['max_interval'] = options.maxInterval;
|
|
903
|
+
return this.call('group_space_artifact', args);
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* List or manage Group Space jobs.
|
|
907
|
+
*/
|
|
908
|
+
async groupSpaceJobs(options) {
|
|
909
|
+
const args = {
|
|
910
|
+
group_id: options.groupId,
|
|
911
|
+
provider: options.provider ?? 'notebooklm',
|
|
912
|
+
lane: options.lane,
|
|
913
|
+
action: options.action ?? 'list',
|
|
914
|
+
by: options.by ?? 'user',
|
|
915
|
+
};
|
|
916
|
+
if (options.jobId)
|
|
917
|
+
args['job_id'] = options.jobId;
|
|
918
|
+
if (options.state)
|
|
919
|
+
args['state'] = options.state;
|
|
920
|
+
if (options.limit !== undefined)
|
|
921
|
+
args['limit'] = options.limit;
|
|
922
|
+
return this.call('group_space_jobs', args);
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Read or run Group Space synchronization for one lane.
|
|
926
|
+
*/
|
|
927
|
+
async groupSpaceSync(options) {
|
|
928
|
+
return this.call('group_space_sync', {
|
|
929
|
+
group_id: options.groupId,
|
|
930
|
+
provider: options.provider ?? 'notebooklm',
|
|
931
|
+
lane: options.lane,
|
|
932
|
+
action: options.action ?? 'status',
|
|
933
|
+
force: options.force ?? false,
|
|
934
|
+
by: options.by ?? 'user',
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Read provider credential status.
|
|
939
|
+
*/
|
|
940
|
+
async groupSpaceProviderCredentialStatus(options = {}) {
|
|
941
|
+
return this.call('group_space_provider_credential_status', {
|
|
942
|
+
provider: options.provider ?? 'notebooklm',
|
|
943
|
+
by: options.by ?? 'user',
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Update provider credentials.
|
|
948
|
+
*/
|
|
949
|
+
async groupSpaceProviderCredentialUpdate(options = {}) {
|
|
950
|
+
const args = {
|
|
951
|
+
provider: options.provider ?? 'notebooklm',
|
|
952
|
+
by: options.by ?? 'user',
|
|
953
|
+
clear: options.clear ?? false,
|
|
954
|
+
};
|
|
955
|
+
if (options.authJson)
|
|
956
|
+
args['auth_json'] = options.authJson;
|
|
957
|
+
return this.call('group_space_provider_credential_update', args);
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Run provider health check.
|
|
961
|
+
*/
|
|
962
|
+
async groupSpaceProviderHealthCheck(options = {}) {
|
|
963
|
+
return this.call('group_space_provider_health_check', {
|
|
964
|
+
provider: options.provider ?? 'notebooklm',
|
|
965
|
+
by: options.by ?? 'user',
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Control provider auth flow.
|
|
970
|
+
*/
|
|
971
|
+
async groupSpaceProviderAuth(options = {}) {
|
|
972
|
+
const args = {
|
|
973
|
+
provider: options.provider ?? 'notebooklm',
|
|
974
|
+
action: options.action ?? 'status',
|
|
975
|
+
by: options.by ?? 'user',
|
|
976
|
+
};
|
|
977
|
+
if (options.timeoutSeconds !== undefined)
|
|
978
|
+
args['timeout_seconds'] = options.timeoutSeconds;
|
|
979
|
+
return this.call('group_space_provider_auth', args);
|
|
980
|
+
}
|
|
981
|
+
// ============================================================
|
|
601
982
|
// Event stream
|
|
602
983
|
// ============================================================
|
|
603
984
|
/**
|
|
604
985
|
* Subscribe to the group event stream (Server-Sent Events style, long-lived connection).
|
|
605
986
|
* Yields {@link EventStreamItem} objects as they arrive. The socket is destroyed
|
|
606
987
|
* when the generator is returned or thrown.
|
|
607
|
-
* @param options - Group ID, event filters, optional since cursor
|
|
988
|
+
* @param options - Group ID, event filters, and optional since cursor.
|
|
608
989
|
* @yields {EventStreamItem} Each event or heartbeat from the stream.
|
|
609
990
|
* @throws {DaemonAPIError} If the handshake fails.
|
|
610
991
|
*/
|
|
611
992
|
async *eventsStream(options) {
|
|
612
|
-
// Check abort before connecting
|
|
613
|
-
if (options.signal?.aborted)
|
|
614
|
-
return;
|
|
615
993
|
const args = {
|
|
616
994
|
group_id: options.groupId,
|
|
617
995
|
by: options.by ?? 'user',
|
|
@@ -637,37 +1015,20 @@ export class CCCCClient {
|
|
|
637
1015
|
socket.destroy();
|
|
638
1016
|
throw new DaemonAPIError(handshake.error?.code ?? 'unknown', handshake.error?.message ?? 'Handshake failed', handshake.error?.details, handshake);
|
|
639
1017
|
}
|
|
640
|
-
// Wire up AbortSignal to destroy the socket
|
|
641
|
-
const onAbort = () => { socket.destroy(); };
|
|
642
|
-
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
643
1018
|
try {
|
|
644
1019
|
for await (const line of readLines(socket, initialBuffer)) {
|
|
645
|
-
if (options.signal?.aborted)
|
|
646
|
-
return;
|
|
647
|
-
let parsed;
|
|
648
1020
|
try {
|
|
649
|
-
parsed = JSON.parse(line);
|
|
650
|
-
|
|
651
|
-
catch {
|
|
652
|
-
// Non-JSON line from daemon; surface as an error so callers know
|
|
653
|
-
// something unexpected happened instead of silently losing data.
|
|
654
|
-
throw new DaemonAPIError('invalid_stream_data', `Unparseable event stream line: ${line.slice(0, 200)}`, {});
|
|
655
|
-
}
|
|
656
|
-
if (parsed !== null && typeof parsed === 'object') {
|
|
657
|
-
const obj = parsed;
|
|
658
|
-
// Daemon may send error objects in the stream (ok: false).
|
|
659
|
-
if ('ok' in obj && obj['ok'] === false) {
|
|
660
|
-
const err = obj['error'];
|
|
661
|
-
throw new DaemonAPIError(err?.['code'] ?? 'stream_error', err?.['message'] ?? 'Daemon sent error in event stream', err?.['details'] ?? {}, obj);
|
|
662
|
-
}
|
|
663
|
-
if ('t' in obj) {
|
|
1021
|
+
const parsed = JSON.parse(line);
|
|
1022
|
+
if (parsed !== null && typeof parsed === 'object' && 't' in parsed) {
|
|
664
1023
|
yield parsed;
|
|
665
1024
|
}
|
|
666
1025
|
}
|
|
1026
|
+
catch {
|
|
1027
|
+
// Skip invalid JSON lines.
|
|
1028
|
+
}
|
|
667
1029
|
}
|
|
668
1030
|
}
|
|
669
1031
|
finally {
|
|
670
|
-
options.signal?.removeEventListener('abort', onAbort);
|
|
671
1032
|
socket.destroy();
|
|
672
1033
|
}
|
|
673
1034
|
}
|