cccc-sdk 0.4.3 → 0.4.4
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 +53 -3
- package/dist/client.d.ts +92 -23
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +386 -67
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +31 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +31 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +3 -0
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +334 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +16 -1
- package/dist/types.js.map +1 -1
- package/package.json +3 -3
package/dist/client.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CCCC SDK client
|
|
3
3
|
*/
|
|
4
|
-
import { DaemonAPIError, IncompatibleDaemonError, } from './errors.js';
|
|
4
|
+
import { DaemonAPIError, IncompatibleDaemonError, ErrorCodes, } from './errors.js';
|
|
5
|
+
import { isStreamEvent } from './types.js';
|
|
5
6
|
import { discoverEndpoint, callDaemon, openEventsStream, readLines, } from './transport.js';
|
|
7
|
+
function compactRecord(input) {
|
|
8
|
+
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
|
|
9
|
+
}
|
|
6
10
|
/**
|
|
7
11
|
* Client for communicating with the CCCC daemon over IPC (Unix socket or TCP).
|
|
8
12
|
*
|
|
@@ -50,22 +54,23 @@ export class CCCCClient {
|
|
|
50
54
|
args: args ?? {},
|
|
51
55
|
};
|
|
52
56
|
const response = await callDaemon(this._endpoint, request, this._timeoutMs);
|
|
53
|
-
if (!response.ok
|
|
54
|
-
throw new DaemonAPIError(response.error
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw new DaemonAPIError(response.error?.code ?? 'error', response.error?.message ?? 'daemon error', response.error?.details ?? {}, response);
|
|
55
59
|
}
|
|
56
60
|
return response;
|
|
57
61
|
}
|
|
58
62
|
/**
|
|
59
63
|
* Send an IPC request and return only the result payload.
|
|
64
|
+
* @typeParam T - Expected result type (defaults to `Record<string, unknown>`).
|
|
60
65
|
* @param op - The IPC operation name.
|
|
61
66
|
* @param args - Operation arguments.
|
|
62
|
-
* @returns The `result` field from the daemon response
|
|
67
|
+
* @returns The `result` field from the daemon response, cast to `T`.
|
|
63
68
|
* @throws {DaemonAPIError} If the daemon returns an error.
|
|
64
69
|
* @throws {DaemonUnavailableError} If the connection fails.
|
|
65
70
|
*/
|
|
66
71
|
async call(op, args) {
|
|
67
72
|
const response = await this.callRaw(op, args);
|
|
68
|
-
return response.result ?? {};
|
|
73
|
+
return (response.result ?? {});
|
|
69
74
|
}
|
|
70
75
|
/**
|
|
71
76
|
* Assert that the connected daemon meets the caller's compatibility requirements.
|
|
@@ -77,12 +82,8 @@ export class CCCCClient {
|
|
|
77
82
|
*/
|
|
78
83
|
async assertCompatible(options = {}) {
|
|
79
84
|
const pingResult = await this.ping();
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const rawCaps = pingResult['capabilities'];
|
|
83
|
-
const capabilities = (rawCaps !== null && typeof rawCaps === 'object' && !Array.isArray(rawCaps))
|
|
84
|
-
? rawCaps
|
|
85
|
-
: {};
|
|
85
|
+
const ipcV = pingResult.ipc_v ?? 0;
|
|
86
|
+
const capabilities = pingResult.capabilities ?? {};
|
|
86
87
|
// Check IPC version
|
|
87
88
|
const requiredV = options.requireIpcV ?? 1;
|
|
88
89
|
if (ipcV < requiredV) {
|
|
@@ -103,7 +104,7 @@ export class CCCCClient {
|
|
|
103
104
|
await this.callRaw(op, {});
|
|
104
105
|
}
|
|
105
106
|
catch (e) {
|
|
106
|
-
if (e instanceof DaemonAPIError && e.code ===
|
|
107
|
+
if (e instanceof DaemonAPIError && e.code === ErrorCodes.UNKNOWN_OP) {
|
|
107
108
|
throw new IncompatibleDaemonError(`Operation not supported: ${op}`);
|
|
108
109
|
}
|
|
109
110
|
// Other errors (e.g. missing_group_id) imply the operation exists.
|
|
@@ -207,7 +208,7 @@ export class CCCCClient {
|
|
|
207
208
|
async groupAutomationManage(options) {
|
|
208
209
|
const actions = options.actions;
|
|
209
210
|
if (actions.length === 0) {
|
|
210
|
-
throw new DaemonAPIError(
|
|
211
|
+
throw new DaemonAPIError(ErrorCodes.INVALID_ARGS, 'groupAutomationManage requires a non-empty actions array', {});
|
|
211
212
|
}
|
|
212
213
|
const args = {
|
|
213
214
|
group_id: options.groupId,
|
|
@@ -314,10 +315,25 @@ export class CCCCClient {
|
|
|
314
315
|
return this.call('actor_remove', { group_id: groupId, actor_id: actorId, by });
|
|
315
316
|
}
|
|
316
317
|
/**
|
|
317
|
-
* Start actor
|
|
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).
|
|
318
324
|
*/
|
|
319
|
-
async actorStart(groupId, actorId,
|
|
320
|
-
|
|
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
|
+
}
|
|
321
337
|
}
|
|
322
338
|
/**
|
|
323
339
|
* Stop actor
|
|
@@ -466,48 +482,35 @@ export class CCCCClient {
|
|
|
466
482
|
/**
|
|
467
483
|
* Search the capability registry for one group/caller scope.
|
|
468
484
|
*/
|
|
469
|
-
async capabilitySearch(options) {
|
|
470
|
-
|
|
485
|
+
async capabilitySearch(options = {}) {
|
|
486
|
+
return this.call('capability_search', compactRecord({
|
|
471
487
|
group_id: options.groupId,
|
|
472
|
-
by: options.by
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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);
|
|
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
|
+
}));
|
|
491
498
|
}
|
|
492
499
|
/**
|
|
493
500
|
* Enable or disable a capability.
|
|
494
501
|
*/
|
|
495
502
|
async capabilityEnable(options) {
|
|
496
|
-
|
|
503
|
+
return this.call('capability_enable', compactRecord({
|
|
497
504
|
group_id: options.groupId,
|
|
498
505
|
capability_id: options.capabilityId,
|
|
499
|
-
scope: options.scope
|
|
500
|
-
enabled: options.enabled
|
|
501
|
-
cleanup: options.cleanup
|
|
502
|
-
by: options.by
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
args['ttl_seconds'] = options.ttlSeconds;
|
|
508
|
-
if (options.actorId)
|
|
509
|
-
args['actor_id'] = options.actorId;
|
|
510
|
-
return this.call('capability_enable', args);
|
|
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
|
+
}));
|
|
511
514
|
}
|
|
512
515
|
/**
|
|
513
516
|
* Block or unblock a capability.
|
|
@@ -531,14 +534,15 @@ export class CCCCClient {
|
|
|
531
534
|
/**
|
|
532
535
|
* Read effective capability exposure for one caller scope.
|
|
533
536
|
*/
|
|
534
|
-
async capabilityState(options) {
|
|
535
|
-
const args =
|
|
536
|
-
group_id: options
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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));
|
|
542
546
|
}
|
|
543
547
|
/**
|
|
544
548
|
* Read capability allowlist default, overlay, and effective snapshots.
|
|
@@ -645,7 +649,7 @@ export class CCCCClient {
|
|
|
645
649
|
/**
|
|
646
650
|
* Send a chat message to a group.
|
|
647
651
|
* @param options - Message content, recipients, and priority.
|
|
648
|
-
* @returns The
|
|
652
|
+
* @returns The created event and optional ack event.
|
|
649
653
|
* @throws {DaemonAPIError} On invalid group, missing permissions, etc.
|
|
650
654
|
*/
|
|
651
655
|
async send(options) {
|
|
@@ -663,7 +667,8 @@ export class CCCCClient {
|
|
|
663
667
|
return this.call('send', args);
|
|
664
668
|
}
|
|
665
669
|
/**
|
|
666
|
-
* Send message across groups
|
|
670
|
+
* Send message across groups.
|
|
671
|
+
* @returns The created event and optional ack event.
|
|
667
672
|
*/
|
|
668
673
|
async sendCrossGroup(options) {
|
|
669
674
|
const args = {
|
|
@@ -679,7 +684,30 @@ export class CCCCClient {
|
|
|
679
684
|
return this.call('send_cross_group', args);
|
|
680
685
|
}
|
|
681
686
|
/**
|
|
682
|
-
*
|
|
687
|
+
* Create a durable task and send one linked visible message.
|
|
688
|
+
*/
|
|
689
|
+
async trackedSend(options) {
|
|
690
|
+
const args = compactRecord({
|
|
691
|
+
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);
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Reply to a message.
|
|
710
|
+
* @returns The created event and optional ack event.
|
|
683
711
|
*/
|
|
684
712
|
async reply(options) {
|
|
685
713
|
const args = {
|
|
@@ -694,6 +722,60 @@ export class CCCCClient {
|
|
|
694
722
|
args['to'] = options.to;
|
|
695
723
|
return this.call('reply', args);
|
|
696
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
|
+
}
|
|
697
779
|
/**
|
|
698
780
|
* Acknowledge chat message
|
|
699
781
|
*/
|
|
@@ -705,6 +787,56 @@ export class CCCCClient {
|
|
|
705
787
|
by: by ?? actorId,
|
|
706
788
|
});
|
|
707
789
|
}
|
|
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 = {
|
|
797
|
+
group_id: options.groupId,
|
|
798
|
+
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);
|
|
808
|
+
}
|
|
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 = {
|
|
816
|
+
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);
|
|
824
|
+
}
|
|
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 = {
|
|
832
|
+
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);
|
|
839
|
+
}
|
|
708
840
|
// ============================================================
|
|
709
841
|
// Convenience methods: inbox
|
|
710
842
|
// ============================================================
|
|
@@ -776,6 +908,173 @@ export class CCCCClient {
|
|
|
776
908
|
dry_run: options.dryRun ?? false,
|
|
777
909
|
});
|
|
778
910
|
}
|
|
911
|
+
contextOp(groupId, op, by = 'system', dryRun = false) {
|
|
912
|
+
return this.contextSync({ groupId, by, dryRun, ops: [op] });
|
|
913
|
+
}
|
|
914
|
+
/** Update the shared coordination brief (Context Ops v3). */
|
|
915
|
+
async coordinationBriefUpdate(options) {
|
|
916
|
+
return this.contextOp(options.groupId, compactRecord({
|
|
917
|
+
op: 'coordination.brief.update',
|
|
918
|
+
objective: options.objective,
|
|
919
|
+
current_focus: options.currentFocus,
|
|
920
|
+
constraints: options.constraints,
|
|
921
|
+
project_brief: options.projectBrief,
|
|
922
|
+
project_brief_stale: options.projectBriefStale,
|
|
923
|
+
}), options.by, options.dryRun);
|
|
924
|
+
}
|
|
925
|
+
/** Add a compact coordination decision or handoff note. */
|
|
926
|
+
async coordinationNoteAdd(options) {
|
|
927
|
+
return this.contextOp(options.groupId, compactRecord({
|
|
928
|
+
op: 'coordination.note.add',
|
|
929
|
+
kind: options.kind,
|
|
930
|
+
summary: options.summary,
|
|
931
|
+
task_id: options.taskId,
|
|
932
|
+
}), options.by, options.dryRun);
|
|
933
|
+
}
|
|
934
|
+
/** Create a Context Ops v3 task. */
|
|
935
|
+
async taskCreate(options) {
|
|
936
|
+
return this.contextOp(options.groupId, compactRecord({
|
|
937
|
+
op: 'task.create',
|
|
938
|
+
title: options.title,
|
|
939
|
+
outcome: options.outcome,
|
|
940
|
+
status: options.status,
|
|
941
|
+
parent_id: options.parentId,
|
|
942
|
+
assignee: options.assignee,
|
|
943
|
+
priority: options.priority,
|
|
944
|
+
blocked_by: options.blockedBy,
|
|
945
|
+
waiting_on: options.waitingOn,
|
|
946
|
+
handoff_to: options.handoffTo,
|
|
947
|
+
task_type: options.taskType,
|
|
948
|
+
notes: options.notes,
|
|
949
|
+
checklist: options.checklist,
|
|
950
|
+
}), options.by, options.dryRun);
|
|
951
|
+
}
|
|
952
|
+
/** Update a Context Ops v3 task. */
|
|
953
|
+
async taskUpdate(options) {
|
|
954
|
+
return this.contextOp(options.groupId, compactRecord({
|
|
955
|
+
op: 'task.update',
|
|
956
|
+
task_id: options.taskId,
|
|
957
|
+
title: options.title,
|
|
958
|
+
outcome: options.outcome,
|
|
959
|
+
status: options.status,
|
|
960
|
+
assignee: options.assignee,
|
|
961
|
+
priority: options.priority,
|
|
962
|
+
blocked_by: options.blockedBy,
|
|
963
|
+
waiting_on: options.waitingOn,
|
|
964
|
+
handoff_to: options.handoffTo,
|
|
965
|
+
notes: options.notes,
|
|
966
|
+
checklist: options.checklist,
|
|
967
|
+
}), options.by, options.dryRun);
|
|
968
|
+
}
|
|
969
|
+
/** Move a task through the canonical lifecycle operation. */
|
|
970
|
+
async taskMove(options) {
|
|
971
|
+
return this.contextOp(options.groupId, {
|
|
972
|
+
op: 'task.move',
|
|
973
|
+
task_id: options.taskId,
|
|
974
|
+
status: options.status,
|
|
975
|
+
}, options.by, options.dryRun);
|
|
976
|
+
}
|
|
977
|
+
/** Restore an archived task. */
|
|
978
|
+
async taskRestore(options) {
|
|
979
|
+
return this.contextOp(options.groupId, {
|
|
980
|
+
op: 'task.restore',
|
|
981
|
+
task_id: options.taskId,
|
|
982
|
+
}, options.by, options.dryRun);
|
|
983
|
+
}
|
|
984
|
+
/** Update per-actor working memory. */
|
|
985
|
+
async agentStateUpdate(options) {
|
|
986
|
+
return this.contextOp(options.groupId, compactRecord({
|
|
987
|
+
op: 'agent_state.update',
|
|
988
|
+
actor_id: options.actorId,
|
|
989
|
+
active_task_id: options.activeTaskId,
|
|
990
|
+
focus: options.focus,
|
|
991
|
+
next_action: options.nextAction,
|
|
992
|
+
what_changed: options.whatChanged,
|
|
993
|
+
blockers: options.blockers,
|
|
994
|
+
open_loops: options.openLoops,
|
|
995
|
+
commitments: options.commitments,
|
|
996
|
+
environment_summary: options.environmentSummary,
|
|
997
|
+
user_model: options.userModel,
|
|
998
|
+
persona_notes: options.personaNotes,
|
|
999
|
+
resume_hint: options.resumeHint,
|
|
1000
|
+
}), options.by, options.dryRun);
|
|
1001
|
+
}
|
|
1002
|
+
/** Clear per-actor working memory. */
|
|
1003
|
+
async agentStateClear(options) {
|
|
1004
|
+
return this.contextOp(options.groupId, {
|
|
1005
|
+
op: 'agent_state.clear',
|
|
1006
|
+
actor_id: options.actorId,
|
|
1007
|
+
}, options.by, options.dryRun);
|
|
1008
|
+
}
|
|
1009
|
+
/** Merge restricted projection metadata. */
|
|
1010
|
+
async metaMerge(options) {
|
|
1011
|
+
return this.contextOp(options.groupId, {
|
|
1012
|
+
op: 'meta.merge',
|
|
1013
|
+
data: options.data,
|
|
1014
|
+
}, options.by, options.dryRun);
|
|
1015
|
+
}
|
|
1016
|
+
// ============================================================
|
|
1017
|
+
// Convenience methods: capability use and memory
|
|
1018
|
+
// ============================================================
|
|
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({
|
|
1033
|
+
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
|
+
}));
|
|
1040
|
+
}
|
|
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
|
+
}));
|
|
1051
|
+
}
|
|
1052
|
+
async memoryWrite(options) {
|
|
1053
|
+
return this.call('memory_write', compactRecord({
|
|
1054
|
+
group_id: options.groupId,
|
|
1055
|
+
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
|
+
}));
|
|
1069
|
+
}
|
|
1070
|
+
async memoryProfileGet(options) {
|
|
1071
|
+
return this.call('memory_profile_get', compactRecord({
|
|
1072
|
+
group_id: options.groupId,
|
|
1073
|
+
actor_id: options.actorId,
|
|
1074
|
+
user_id: options.userId,
|
|
1075
|
+
tags: options.tags,
|
|
1076
|
+
}));
|
|
1077
|
+
}
|
|
779
1078
|
// ============================================================
|
|
780
1079
|
// Convenience methods: Group Space
|
|
781
1080
|
// ============================================================
|
|
@@ -985,11 +1284,14 @@ export class CCCCClient {
|
|
|
985
1284
|
* Subscribe to the group event stream (Server-Sent Events style, long-lived connection).
|
|
986
1285
|
* Yields {@link EventStreamItem} objects as they arrive. The socket is destroyed
|
|
987
1286
|
* when the generator is returned or thrown.
|
|
988
|
-
* @param options - Group ID, event filters,
|
|
1287
|
+
* @param options - Group ID, event filters, optional since cursor, and optional AbortSignal.
|
|
989
1288
|
* @yields {EventStreamItem} Each event or heartbeat from the stream.
|
|
990
1289
|
* @throws {DaemonAPIError} If the handshake fails.
|
|
991
1290
|
*/
|
|
992
1291
|
async *eventsStream(options) {
|
|
1292
|
+
// Check abort before connecting
|
|
1293
|
+
if (options.signal?.aborted)
|
|
1294
|
+
return;
|
|
993
1295
|
const args = {
|
|
994
1296
|
group_id: options.groupId,
|
|
995
1297
|
by: options.by ?? 'user',
|
|
@@ -1015,20 +1317,37 @@ export class CCCCClient {
|
|
|
1015
1317
|
socket.destroy();
|
|
1016
1318
|
throw new DaemonAPIError(handshake.error?.code ?? 'unknown', handshake.error?.message ?? 'Handshake failed', handshake.error?.details, handshake);
|
|
1017
1319
|
}
|
|
1320
|
+
// Wire up AbortSignal to destroy the socket
|
|
1321
|
+
const onAbort = () => { socket.destroy(); };
|
|
1322
|
+
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
1018
1323
|
try {
|
|
1019
1324
|
for await (const line of readLines(socket, initialBuffer)) {
|
|
1325
|
+
if (options.signal?.aborted)
|
|
1326
|
+
return;
|
|
1327
|
+
let parsed;
|
|
1020
1328
|
try {
|
|
1021
|
-
|
|
1022
|
-
if (parsed !== null && typeof parsed === 'object' && 't' in parsed) {
|
|
1023
|
-
yield parsed;
|
|
1024
|
-
}
|
|
1329
|
+
parsed = JSON.parse(line);
|
|
1025
1330
|
}
|
|
1026
1331
|
catch {
|
|
1027
|
-
//
|
|
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) {
|
|
1344
|
+
yield parsed;
|
|
1345
|
+
}
|
|
1028
1346
|
}
|
|
1029
1347
|
}
|
|
1030
1348
|
}
|
|
1031
1349
|
finally {
|
|
1350
|
+
options.signal?.removeEventListener('abort', onAbort);
|
|
1032
1351
|
socket.destroy();
|
|
1033
1352
|
}
|
|
1034
1353
|
}
|