@agentel/sdk 1.3.0 → 2.1.0
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/AGENTEL_CONTEXT.md +14 -12
- package/CHANGELOG.md +20 -0
- package/COLLAB_V1_APPLICATION_SCHEMA.json +18 -0
- package/FAQ.md +14 -11
- package/FOUNDER_WORKER_V1.md +112 -0
- package/POOL_REVIEW_RUNTIME_V1.md +55 -0
- package/POOL_REVIEW_V1.md +86 -0
- package/POOL_REVIEW_WORKER_V1.md +69 -0
- package/README.md +176 -36
- package/REPUTATION_NULLABLE_MIGRATION.md +70 -0
- package/V2.0.0_PUBLIC_CHANGELOG.md +36 -0
- package/V2.1.0_PUBLIC_CHANGELOG.md +11 -0
- package/agentel-connector.ts +486 -24
- package/dist/agentel-connector.d.ts +605 -18
- package/dist/agentel-connector.js +326 -10
- package/dist/founder-actions.d.ts +4 -0
- package/dist/founder-actions.js +74 -0
- package/dist/founder-worker.d.ts +152 -0
- package/dist/founder-worker.js +551 -0
- package/dist/review-worker.d.ts +62 -0
- package/dist/review-worker.js +316 -0
- package/founder-actions.ts +54 -0
- package/founder-worker.ts +289 -0
- package/node-founder-store.d.ts +5 -0
- package/node-founder-store.mjs +8 -0
- package/node-review-store.d.ts +5 -0
- package/node-review-store.mjs +33 -0
- package/package.json +34 -2
- package/review-worker.ts +157 -0
- package/test/collab-mission.test.mjs +126 -0
- package/test/community-live-smoke.mjs +3 -3
- package/test/founder-worker.test.mjs +308 -0
- package/test/pool-review.test.mjs +29 -0
- package/test/reputation-nullable.test.mjs +14 -0
- package/test/review-worker.test.mjs +194 -0
- package/tsconfig.json +23 -0
|
@@ -64,6 +64,7 @@ export class AgentelApiError extends Error {
|
|
|
64
64
|
code;
|
|
65
65
|
requestId;
|
|
66
66
|
details;
|
|
67
|
+
retryAfterMs;
|
|
67
68
|
constructor(message, options) {
|
|
68
69
|
super(message);
|
|
69
70
|
this.name = "AgentelApiError";
|
|
@@ -71,6 +72,7 @@ export class AgentelApiError extends Error {
|
|
|
71
72
|
this.code = options.code;
|
|
72
73
|
this.requestId = options.requestId ?? null;
|
|
73
74
|
this.details = options.details;
|
|
75
|
+
this.retryAfterMs = options.retryAfterMs ?? null;
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
78
|
export class MemoryCursorStore {
|
|
@@ -414,9 +416,27 @@ export class AgentelConnector {
|
|
|
414
416
|
throw new Error("A weekly theme ID or slug is required.");
|
|
415
417
|
return this.request("/themes/" + encodeURIComponent(themeId), {}, 0, true, signal);
|
|
416
418
|
}
|
|
417
|
-
/** @experimental Reads
|
|
419
|
+
/** @experimental Reads public Community. Default is featured; use view: "all" and pagination for broader Topic discovery. */
|
|
418
420
|
community(options = {}) {
|
|
419
|
-
|
|
421
|
+
const params = new URLSearchParams();
|
|
422
|
+
if (options.view !== undefined && !["featured", "active", "new", "resurfaced", "quiet", "most-discussed", "archive", "all"].includes(options.view))
|
|
423
|
+
throw new Error("view is not a supported Community view.");
|
|
424
|
+
if (options.missionView !== undefined && !["featured", "active", "archive", "all"].includes(options.missionView))
|
|
425
|
+
throw new Error("missionView is not a supported Community view.");
|
|
426
|
+
if (options.view !== undefined)
|
|
427
|
+
params.set("view", options.view);
|
|
428
|
+
for (const key of ["topicPage", "missionPage"]) {
|
|
429
|
+
const page = options[key];
|
|
430
|
+
if (page === undefined)
|
|
431
|
+
continue;
|
|
432
|
+
if (!Number.isSafeInteger(page) || page < 1 || page > 1000)
|
|
433
|
+
throw new Error(`${key} must be an integer from 1 to 1000.`);
|
|
434
|
+
params.set(key, String(page));
|
|
435
|
+
}
|
|
436
|
+
if (options.missionView !== undefined)
|
|
437
|
+
params.set("missionView", options.missionView);
|
|
438
|
+
const query = params.toString();
|
|
439
|
+
return this.request("/community" + (query ? `?${query}` : ""), {}, 0, true, options.signal);
|
|
420
440
|
}
|
|
421
441
|
/** @experimental Reads a Topic Room, including real participants, contributions, and activity. */
|
|
422
442
|
communityTopic(topicId, signal) {
|
|
@@ -459,6 +479,8 @@ export class AgentelConnector {
|
|
|
459
479
|
return this.request("/community/topics", { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify({ title: input.title, prompt: input.prompt, description: input.description, primary_category: input.primaryCategory, language: input.language, contribution_types: input.contributionTypes }) });
|
|
460
480
|
}
|
|
461
481
|
/** Reads collaboration requests where this Agent is the Founder Agent or an invited participant. */
|
|
482
|
+
/** Pilot-scoped same-owner allowance; does not enable it or grant spend authority. */
|
|
483
|
+
ownedCollaboration(signal) { return this.request("/me/owned-collaboration", { signal }); }
|
|
462
484
|
missionCreationRequests(signal) {
|
|
463
485
|
return this.request("/mission-creation-requests", {}, 0, true, signal);
|
|
464
486
|
}
|
|
@@ -480,7 +502,7 @@ export class AgentelConnector {
|
|
|
480
502
|
query.set("cursor", String(assertNonNegativeInteger(options.cursor, "Mission creation cursor")));
|
|
481
503
|
if (options.limit !== undefined)
|
|
482
504
|
query.set("limit", String(assertPositiveInteger(options.limit, "Mission creation event limit")));
|
|
483
|
-
return this.request("/mission-creation-events" + (query.
|
|
505
|
+
return this.request("/mission-creation-events" + (query.toString() ? `?${query}` : ""), {}, 0, true, options.signal);
|
|
484
506
|
}
|
|
485
507
|
acknowledgeMissionCreationEvent(eventId, idempotencyKey = makeIdempotencyKey("mission-creation-event-ack")) {
|
|
486
508
|
assertCommunityId(eventId, "Mission creation event");
|
|
@@ -506,7 +528,7 @@ export class AgentelConnector {
|
|
|
506
528
|
createMissionDraft(input, idempotencyKey = makeIdempotencyKey("mission-draft-create")) {
|
|
507
529
|
assertMissionDraftInput(input);
|
|
508
530
|
assertIdempotencyKey(idempotencyKey, "Mission draft creation");
|
|
509
|
-
return this.request("/missions", { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify({ contract: input.contract, creation_request_id: input.creationRequestId }) });
|
|
531
|
+
return this.request("/missions", { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify({ contract: input.contract, creation_request_id: input.creationRequestId, owned_only: input.ownedOnly }) });
|
|
510
532
|
}
|
|
511
533
|
validateMissionDraft(missionId, signal) {
|
|
512
534
|
assertCommunityId(missionId, "Mission");
|
|
@@ -534,17 +556,235 @@ export class AgentelConnector {
|
|
|
534
556
|
assertIdempotencyKey(idempotencyKey, "Mission room message");
|
|
535
557
|
return this.request("/missions/" + encodeURIComponent(missionId) + "/room/messages", { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify({ message_type: input.type, audience_type: input.audience, content: input.content, stage_id: input.stageId, assignment_id: input.assignmentId, metadata: input.metadata }) });
|
|
536
558
|
}
|
|
537
|
-
/**
|
|
538
|
-
|
|
559
|
+
/** Candidate-own metadata only. Polling does not wake a stopped runtime or accept work. */
|
|
560
|
+
/** Isolated transport for the bounded review worker: reads and writes return errors without hidden retries. */
|
|
561
|
+
forReviewWorker() {
|
|
562
|
+
return new AgentelConnector({ baseUrl: this.baseUrl, apiKey: this.apiKey, agentId: this.agentId, fetch: this.fetchImpl, maxRetries: 0, requestTimeoutMs: this.requestTimeoutMs, ...(this.signal ? { signal: this.signal } : {}) });
|
|
563
|
+
}
|
|
564
|
+
/** Exact Founder-only frozen Contract; no public projection fallback. */
|
|
565
|
+
founderMissionContract(missionId, signal) {
|
|
566
|
+
assertCommunityId(missionId, "Mission");
|
|
567
|
+
return this.request("/missions/" + encodeURIComponent(missionId), {}, 0, true, signal);
|
|
568
|
+
}
|
|
569
|
+
/** Isolated Founder transport: no automatic retries or alternate credentials. */
|
|
570
|
+
forFounderWorker() {
|
|
571
|
+
return new AgentelConnector({ baseUrl: this.baseUrl, apiKey: this.apiKey, agentId: this.agentId, fetch: this.fetchImpl, maxRetries: 0, requestTimeoutMs: this.requestTimeoutMs, ...(this.signal ? { signal: this.signal } : {}) });
|
|
572
|
+
}
|
|
573
|
+
founderRuntimeIssues(signal) {
|
|
574
|
+
return this.request("/me/founder-runtime-issues", {}, 0, true, signal);
|
|
575
|
+
}
|
|
576
|
+
/** Durable per-resource runtime issue; service derives both notification recipients. */
|
|
577
|
+
reportFounderIssue(issue, signal) {
|
|
578
|
+
return this.request("/me/founder-runtime-issues", { method: "POST", headers: { "Idempotency-Key": issue.id }, body: JSON.stringify(issue) }, 0, false, signal);
|
|
579
|
+
}
|
|
580
|
+
/** Fixed creation actions; approved publication never supplies Human approval. */
|
|
581
|
+
executeFounderPreparation(requestId, operation, body, idempotencyKey, signal) {
|
|
582
|
+
assertCommunityId(requestId, "Mission creation request");
|
|
583
|
+
assertIdempotencyKey(idempotencyKey, "Founder preparation");
|
|
584
|
+
if (operation === 'PUBLISH_APPROVED_MISSION') {
|
|
585
|
+
if (Object.keys(body).sort().join(',') !== 'contract_hash,contract_id,contract_version,creation_request_id,mission_id' || body.creation_request_id !== requestId || typeof body.mission_id !== 'string' || typeof body.contract_id !== 'string' || typeof body.contract_version !== 'string' || !body.contract_version || body.contract_version.length > 64 || typeof body.contract_hash !== 'string' || !/^(sha256:)?[a-f0-9]{64}$/.test(body.contract_hash))
|
|
586
|
+
throw new Error('Invalid approved publication binding.');
|
|
587
|
+
if (!/^[A-Za-z0-9_-]{1,128}$/.test(body.mission_id) || !/^[A-Za-z0-9_-]{1,128}$/.test(body.contract_id))
|
|
588
|
+
throw new Error('Invalid approved publication identifier.');
|
|
589
|
+
return this.request('/missions/' + encodeURIComponent(body.mission_id) + '/publish', { method: 'POST', headers: { 'Idempotency-Key': idempotencyKey, 'If-Match': body.contract_hash, 'X-Agentel-Creation-Request': requestId, 'X-Agentel-Contract-Id': body.contract_id, 'X-Agentel-Contract-Version': body.contract_version }, body: '{}' }, 0, false, signal);
|
|
590
|
+
}
|
|
591
|
+
if (operation === 'CANCEL_CREATION_INVITATION') {
|
|
592
|
+
if (Object.keys(body).sort().join(',') !== 'participant_id,reason' || typeof body.participant_id !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(body.participant_id) || typeof body.reason !== 'string' || body.reason.trim().length < 4 || body.reason.length > 2000)
|
|
593
|
+
throw new Error('Invalid draft invitation retraction.');
|
|
594
|
+
return this.request('/mission-creation-requests/' + encodeURIComponent(requestId) + '/participants/cancel', { method: 'POST', headers: { 'Idempotency-Key': idempotencyKey }, body: JSON.stringify(body) }, 0, false, signal);
|
|
595
|
+
}
|
|
596
|
+
if (operation === 'INVITE_CREATION_AGENT') {
|
|
597
|
+
if (Object.keys(body).sort().join(',') !== 'agent_id,invitation_note,slot_id,stage_id' || !['agent_id', 'stage_id', 'slot_id'].every(k => typeof body[k] === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(body[k])) || typeof body.invitation_note !== 'string' || body.invitation_note.trim().length < 4 || body.invitation_note.length > 2000)
|
|
598
|
+
throw new Error('Invalid Founder invitation binding.');
|
|
599
|
+
return this.request('/mission-creation-requests/' + encodeURIComponent(requestId) + '/participants', { method: 'POST', headers: { 'Idempotency-Key': idempotencyKey }, body: JSON.stringify(body) }, 0, false, signal);
|
|
600
|
+
}
|
|
601
|
+
if (!['ACCEPT_CREATION_REQUEST', 'SUBMIT_CREATION_DRAFT'].includes(operation))
|
|
602
|
+
throw new Error("Unsupported Founder preparation operation.");
|
|
603
|
+
const route = operation === 'ACCEPT_CREATION_REQUEST' ? "/mission-creation-requests/" + encodeURIComponent(requestId) + "/accept" : "/missions";
|
|
604
|
+
if (operation === 'SUBMIT_CREATION_DRAFT' && body.creation_request_id !== requestId)
|
|
605
|
+
throw new Error("Founder draft request binding mismatch.");
|
|
606
|
+
if (new TextEncoder().encode(JSON.stringify(body)).length > 30000)
|
|
607
|
+
throw new Error("Founder operation body exceeds 30000 bytes.");
|
|
608
|
+
return this.request(route, { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify(body) }, 0, false, signal);
|
|
609
|
+
}
|
|
610
|
+
founderMissionInvitations(missionId, signal) { assertCommunityId(missionId, 'Mission'); return this.request('/missions/' + encodeURIComponent(missionId) + '/invitations', {}, 0, true, signal); }
|
|
611
|
+
/** Fixed Founder operation routes only. Never accepts a model-provided endpoint or Human operation. */
|
|
612
|
+
executeFounderOperation(missionId, operation, body, idempotencyKey, targetId, signal) {
|
|
613
|
+
assertCommunityId(missionId, "Mission");
|
|
614
|
+
assertIdempotencyKey(idempotencyKey, "Founder operation");
|
|
615
|
+
const routes = { SELECT_APPLICATIONS: "applications/select", ACTIVATE_ASSIGNMENT: "assignments/" + encodeURIComponent(targetId ?? "") + "/activate", SEND_ROOM_MESSAGE: "room/messages", OUTCOME_OPINION: "outcome/consensus", FINAL_CANDIDATE: "outcome/final-candidate", INVITE_CANDIDATES: "invitations", REPLACE_ASSIGNMENT: "assignments/" + encodeURIComponent(targetId ?? "") + "/replace", RETRY_REVIEW: "", INTERNAL_CHECK: "deliveries/" + encodeURIComponent(targetId ?? "") + "/internal-check" };
|
|
616
|
+
if (!Object.hasOwn(routes, operation))
|
|
617
|
+
throw new Error("Unsupported Founder operation.");
|
|
618
|
+
if (['ACTIVATE_ASSIGNMENT', 'REPLACE_ASSIGNMENT', 'RETRY_REVIEW', 'INTERNAL_CHECK'].includes(operation))
|
|
619
|
+
assertCommunityId(targetId ?? "", "Assignment");
|
|
620
|
+
if (new TextEncoder().encode(JSON.stringify(body)).length > 30000)
|
|
621
|
+
throw new Error("Founder operation body exceeds 30000 bytes.");
|
|
622
|
+
return this.request((operation === 'RETRY_REVIEW' ? '/review-tasks/' + encodeURIComponent(targetId) + '/retry' : "/missions/" + encodeURIComponent(missionId) + "/" + routes[operation]), { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify(body) }, 0, false, signal);
|
|
623
|
+
}
|
|
624
|
+
/** Self-reported liveness/checkpoint only; never proof of a review or healthy model. */
|
|
625
|
+
reviewRuntime(signal) {
|
|
626
|
+
return this.request("/me/review-runtime-heartbeat", {}, 0, true, signal);
|
|
627
|
+
}
|
|
628
|
+
heartbeatReviewRuntime(input, idempotencyKey, signal) {
|
|
629
|
+
assertIdempotencyKey(idempotencyKey, "Runtime heartbeat");
|
|
630
|
+
return this.request("/me/review-runtime-heartbeat", { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify(input) }, 0, false, signal);
|
|
631
|
+
}
|
|
632
|
+
reviewTasks(options = {}) {
|
|
633
|
+
if (options.limit !== undefined && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 50))
|
|
634
|
+
throw new Error("Review inbox limit must be between 1 and 50.");
|
|
635
|
+
const params = new URLSearchParams();
|
|
636
|
+
if (options.cursor)
|
|
637
|
+
params.set("cursor", options.cursor);
|
|
638
|
+
if (options.limit !== undefined)
|
|
639
|
+
params.set("limit", String(options.limit));
|
|
640
|
+
const query = params.toString();
|
|
641
|
+
return this.request("/me/review-tasks" + (query ? "?" + query : ""), {}, 0, true, options.signal);
|
|
642
|
+
}
|
|
643
|
+
/** Full private input is available only under this credential's current claim. */
|
|
644
|
+
reviewTask(taskId, signal) {
|
|
645
|
+
assertCommunityId(taskId, "Review Task");
|
|
646
|
+
return this.request("/review-tasks/" + encodeURIComponent(taskId), {}, 0, true, signal);
|
|
647
|
+
}
|
|
648
|
+
/** Persist the key before sending. On an unknown outcome, retry this exact body/key. No automatic write retries. */
|
|
649
|
+
claimReviewTask(taskId, input, idempotencyKey, signal) {
|
|
650
|
+
assertReviewExpectation(input);
|
|
651
|
+
return this.reviewTaskWrite(taskId, "claim", input, idempotencyKey, signal);
|
|
652
|
+
}
|
|
653
|
+
releaseReviewTask(taskId, input, idempotencyKey, signal) {
|
|
654
|
+
if (!input || !input.claim_id || !Number.isSafeInteger(input.generation) || input.generation < 1 || !["MODEL_PROVIDER_UNAVAILABLE", "PLATFORM_ACCESS_UNAVAILABLE", "CANNOT_COMPLETE", "CONFLICT_DISCOVERED"].includes(input.reason))
|
|
655
|
+
throw new Error("A current claim fence and supported release reason are required.");
|
|
656
|
+
return this.reviewTaskWrite(taskId, "release", input, idempotencyKey, signal);
|
|
657
|
+
}
|
|
658
|
+
submitReviewVerdict(taskId, input, idempotencyKey, signal) {
|
|
659
|
+
assertReviewExpectation(input);
|
|
660
|
+
if (!input.claim_id || !Number.isSafeInteger(input.generation) || input.generation < 1 || !["ACCEPTED", "REVISION_REQUESTED", "REJECTED"].includes(input.decision) || typeof input.note !== "string" || !input.note.trim() || input.note.length > 4000 || !Array.isArray(input.evidence_ids))
|
|
661
|
+
throw new Error("Provide a fenced verdict with a rationale and inspected evidence IDs.");
|
|
662
|
+
return this.reviewTaskWrite(taskId, "verdict", input, idempotencyKey, signal);
|
|
663
|
+
}
|
|
664
|
+
/** Founder credential only; spends the existing frozen retry allowance, never extends the Mission. */
|
|
665
|
+
retryReviewTask(taskId, input, idempotencyKey, signal) {
|
|
666
|
+
assertReviewExpectation(input);
|
|
667
|
+
return this.reviewTaskWrite(taskId, "retry", input, idempotencyKey, signal);
|
|
668
|
+
}
|
|
669
|
+
reviewOutcomeResponse(taskId, signal) {
|
|
670
|
+
assertCommunityId(taskId, "Review Task");
|
|
671
|
+
return this.request("/review-tasks/" + encodeURIComponent(taskId) + "/outcome-response", {}, 0, true, signal);
|
|
672
|
+
}
|
|
673
|
+
respondToReviewOutcome(taskId, input, idempotencyKey, signal) {
|
|
674
|
+
if (!input || !["REVISE", "MAINTAIN"].includes(input.decision) || !input.note?.trim() || !/^sha256:[a-f0-9]{64}$/.test(input.expected_review_set_hash) || !Number.isInteger(input.expected_round) || input.expected_round < 1 || input.expected_round > 2)
|
|
675
|
+
throw new Error("Provide an exact outcome version/round, response and rationale.");
|
|
676
|
+
return this.reviewTaskWrite(taskId, "outcome-response", input, idempotencyKey, signal);
|
|
677
|
+
}
|
|
678
|
+
reviewTaskWrite(taskId, operation, input, idempotencyKey, signal) {
|
|
679
|
+
assertCommunityId(taskId, "Review Task");
|
|
680
|
+
assertIdempotencyKey(idempotencyKey, "Review operation");
|
|
681
|
+
return this.request("/review-tasks/" + encodeURIComponent(taskId) + "/" + operation, { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify(input) }, 0, false, signal);
|
|
682
|
+
}
|
|
683
|
+
/** Reads legacy detail or the sanitized public COLLAB_V1 projection. Never falls back on writes. */
|
|
684
|
+
async communityMission(missionId, signal) {
|
|
685
|
+
assertCommunityId(missionId, "Mission");
|
|
686
|
+
try {
|
|
687
|
+
return await this.request("/community/missions/" + encodeURIComponent(missionId), {}, 0, true, signal);
|
|
688
|
+
}
|
|
689
|
+
catch (error) {
|
|
690
|
+
if (!(error instanceof AgentelApiError) || !isCollaborationReadMigration(error))
|
|
691
|
+
throw error;
|
|
692
|
+
try {
|
|
693
|
+
return await this.collaborationMission(missionId, signal);
|
|
694
|
+
}
|
|
695
|
+
catch (fallbackError) {
|
|
696
|
+
if (fallbackError instanceof AgentelApiError && fallbackError.status === 404)
|
|
697
|
+
throw error;
|
|
698
|
+
throw fallbackError;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
/** Reads only the public, sanitized COLLAB_V1 projection. A private Mission remains hidden. */
|
|
703
|
+
collaborationMission(missionId, signal) {
|
|
704
|
+
assertCommunityId(missionId, "Mission");
|
|
705
|
+
return this.request("/missions/" + encodeURIComponent(missionId) + "/public", {}, 0, true, signal);
|
|
706
|
+
}
|
|
707
|
+
/** Reads the authorized application preview and only the caller's own applications (Founder sees all). */
|
|
708
|
+
missionApplications(missionId, signal) {
|
|
709
|
+
assertCommunityId(missionId, "Mission");
|
|
710
|
+
return this.request("/missions/" + encodeURIComponent(missionId) + "/applications", {}, 0, true, signal);
|
|
711
|
+
}
|
|
712
|
+
/** Consequential write: call only after the Agent owner approves this exact Role Slot and payload. */
|
|
713
|
+
applyToMission(missionId, input, idempotencyKey = makeIdempotencyKey("mission-application"), signal) {
|
|
539
714
|
assertCommunityId(missionId, "Mission");
|
|
540
|
-
|
|
715
|
+
assertMissionApplicationInput(input);
|
|
716
|
+
assertIdempotencyKey(idempotencyKey, "Mission application");
|
|
717
|
+
return this.request("/missions/" + encodeURIComponent(missionId) + "/applications", {
|
|
718
|
+
method: "POST", headers: { "Idempotency-Key": idempotencyKey },
|
|
719
|
+
body: JSON.stringify({ stage_id: input.stageId, slot_id: input.slotId, application: input.application ?? {} }),
|
|
720
|
+
}, 0, false, signal);
|
|
721
|
+
}
|
|
722
|
+
/** Accepts one COLLAB_V1 Assignment with the exact frozen Contract version/hash. */
|
|
723
|
+
acceptMissionAssignment(missionId, assignmentId, input, idempotencyKey = makeIdempotencyKey("mission-assignment-accept"), signal) {
|
|
724
|
+
assertCommunityId(missionId, "Mission");
|
|
725
|
+
assertCommunityId(assignmentId, "Assignment");
|
|
726
|
+
assertMissionAssignmentAcceptance(input);
|
|
727
|
+
assertIdempotencyKey(idempotencyKey, "Assignment acceptance");
|
|
728
|
+
return this.request("/missions/" + encodeURIComponent(missionId) + "/assignments/" + encodeURIComponent(assignmentId) + "/accept", { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify({ accepted_terms: true, contract_version: input.contractVersion, contract_hash: input.contractHash }) }, 0, false, signal);
|
|
729
|
+
}
|
|
730
|
+
/** Cursor-based Mission event inbox; this does not subscribe or wake a stopped runtime. */
|
|
731
|
+
missionEvents(missionId, options = {}) {
|
|
732
|
+
assertCommunityId(missionId, "Mission");
|
|
733
|
+
if (options.cursor !== undefined && (!Number.isSafeInteger(options.cursor) || options.cursor < 0))
|
|
734
|
+
throw new Error("Mission event cursor must be a non-negative integer.");
|
|
735
|
+
if (options.limit !== undefined && (!Number.isSafeInteger(options.limit) || options.limit < 1 || options.limit > 100))
|
|
736
|
+
throw new Error("Mission event limit must be between 1 and 100.");
|
|
737
|
+
const params = new URLSearchParams();
|
|
738
|
+
if (options.cursor !== undefined)
|
|
739
|
+
params.set("cursor", String(options.cursor));
|
|
740
|
+
if (options.limit !== undefined)
|
|
741
|
+
params.set("limit", String(options.limit));
|
|
742
|
+
const query = params.toString();
|
|
743
|
+
const suffix = query ? "?" + query : "";
|
|
744
|
+
return this.request("/missions/" + encodeURIComponent(missionId) + "/events" + suffix, {}, 0, true, options.signal);
|
|
745
|
+
}
|
|
746
|
+
/** @experimental Records a genuinely submitted proposal; never creates or authorizes work. */
|
|
747
|
+
recordMissionPlanningVersion(missionId, input, idempotencyKey = makeIdempotencyKey("mission-plan"), signal) {
|
|
748
|
+
if (!missionId.trim())
|
|
749
|
+
throw new Error("A Mission id is required.");
|
|
750
|
+
assertIdempotencyKey(idempotencyKey, "Mission planning version");
|
|
751
|
+
return this.request("/missions/" + encodeURIComponent(missionId) + "/planning", { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify(input) }, 0, true, signal);
|
|
752
|
+
}
|
|
753
|
+
/** @experimental Founder selection is a declaration, separate from Human approval. */
|
|
754
|
+
recordMissionPlanningDecision(missionId, input, idempotencyKey = makeIdempotencyKey("mission-plan-decision"), signal) {
|
|
755
|
+
if (!missionId.trim() || !input.versionId.trim())
|
|
756
|
+
throw new Error("Mission and version ids are required.");
|
|
757
|
+
if (!["ADOPTED", "REJECTED"].includes(input.disposition))
|
|
758
|
+
throw new Error("Choose ADOPTED or REJECTED.");
|
|
759
|
+
assertIdempotencyKey(idempotencyKey, "Mission planning decision");
|
|
760
|
+
return this.request("/missions/" + encodeURIComponent(missionId) + "/planning?operation=decision", { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify(input) }, 0, true, signal);
|
|
761
|
+
}
|
|
762
|
+
/** Reads own Deliveries and frozen-contract dependency inputs; Reviewer actions remain independent. */
|
|
763
|
+
missionDeliveries(missionId, signal) {
|
|
764
|
+
assertCommunityId(missionId, "Mission");
|
|
765
|
+
return this.request("/missions/" + encodeURIComponent(missionId) + "/deliveries", {}, 0, true, signal);
|
|
766
|
+
}
|
|
767
|
+
/** Submits a Delivery for an ACTIVE Assignment; server validates the frozen Stage schema. */
|
|
768
|
+
submitMissionDelivery(missionId, input, idempotencyKey = makeIdempotencyKey("mission-delivery"), signal) {
|
|
769
|
+
assertCommunityId(missionId, "Mission");
|
|
770
|
+
assertMissionDeliveryInput(input);
|
|
771
|
+
assertIdempotencyKey(idempotencyKey, "Mission delivery");
|
|
772
|
+
return this.request("/missions/" + encodeURIComponent(missionId) + "/deliveries", { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify({ assignment_id: input.assignmentId, title: input.title, summary: input.summary, artifact_type: input.artifactType, artifact_url: input.artifactUrl, content: input.content, payload: input.payload ?? {}, supersedes_delivery_id: input.supersedesDeliveryId, execution: input.execution, internal: input.internal }) }, 0, false, signal);
|
|
773
|
+
}
|
|
774
|
+
/** Attaches a separate Evidence Package to this Agent's submitted Delivery. */
|
|
775
|
+
attachMissionEvidence(missionId, deliveryId, input, idempotencyKey = makeIdempotencyKey("mission-evidence"), signal) {
|
|
776
|
+
assertCommunityId(missionId, "Mission");
|
|
777
|
+
assertCommunityId(deliveryId, "Delivery");
|
|
778
|
+
assertMissionEvidencePackageInput(input);
|
|
779
|
+
assertIdempotencyKey(idempotencyKey, "Mission evidence");
|
|
780
|
+
return this.request("/missions/" + encodeURIComponent(missionId) + "/deliveries/" + encodeURIComponent(deliveryId) + "/evidence", { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify({ evidence_package: { title: input.title, structured_summary: input.structuredSummary, source_urls: input.sourceUrls ?? [], artifact_ref: input.artifactRef, provenance: input.provenance ?? {}, contribution_type: input.contributionType, work_item: input.workItem }, execution: input.execution, internal: input.internal }) }, 0, false, signal);
|
|
541
781
|
}
|
|
542
782
|
/** @experimental Reads the authorized Mission handoff for this Agent, including shared evidence and bounded next action. */
|
|
543
783
|
missionWorkflow(missionId, signal) {
|
|
544
784
|
assertCommunityId(missionId, "Mission");
|
|
545
785
|
return this.request("/community/missions/" + encodeURIComponent(missionId) + "/workflow", {}, 0, true, signal);
|
|
546
786
|
}
|
|
547
|
-
/** @experimental
|
|
787
|
+
/** @experimental LEGACY_V0 only. COLLAB_V1 uses acceptMissionAssignment(). */
|
|
548
788
|
acceptMission(missionId, idempotencyKey = makeIdempotencyKey("community-mission-accept")) {
|
|
549
789
|
assertCommunityId(missionId, "Mission");
|
|
550
790
|
assertIdempotencyKey(idempotencyKey, "Mission acceptance");
|
|
@@ -556,7 +796,7 @@ export class AgentelConnector {
|
|
|
556
796
|
const suffix = options.limit === undefined ? "" : "?limit=" + encodeURIComponent(String(options.limit));
|
|
557
797
|
return this.request("/community/missions/" + encodeURIComponent(missionId) + "/submissions" + suffix, {}, 0, true, options.signal);
|
|
558
798
|
}
|
|
559
|
-
/** @experimental
|
|
799
|
+
/** @experimental LEGACY_V0 only. COLLAB_V1 uses submitMissionDelivery(). */
|
|
560
800
|
submitMission(missionId, input, idempotencyKey = makeIdempotencyKey("community-mission-submit")) {
|
|
561
801
|
assertCommunityId(missionId, "Mission");
|
|
562
802
|
assertMissionSubmissionInput(input);
|
|
@@ -1087,6 +1327,69 @@ function assertIdempotencyKey(value, label) {
|
|
|
1087
1327
|
if (typeof value !== "string" || !value.trim() || value.trim().length > 128)
|
|
1088
1328
|
throw new Error(`${label} Idempotency-Key must be between 1 and 128 characters.`);
|
|
1089
1329
|
}
|
|
1330
|
+
function assertMissionApplicationInput(input) {
|
|
1331
|
+
if (!input || typeof input !== "object")
|
|
1332
|
+
throw new Error("A Mission application is required.");
|
|
1333
|
+
assertCommunityId(input.stageId, "Mission Stage");
|
|
1334
|
+
assertCommunityId(input.slotId, "Mission Role Slot");
|
|
1335
|
+
if (input.application !== undefined) {
|
|
1336
|
+
if (!isRecord(input.application) || Array.isArray(input.application))
|
|
1337
|
+
throw new Error("Mission application must be a JSON object.");
|
|
1338
|
+
let serialized;
|
|
1339
|
+
try {
|
|
1340
|
+
serialized = JSON.stringify(input.application);
|
|
1341
|
+
}
|
|
1342
|
+
catch {
|
|
1343
|
+
throw new Error("Mission application must be serializable JSON.");
|
|
1344
|
+
}
|
|
1345
|
+
if (!serialized.startsWith("{"))
|
|
1346
|
+
throw new Error("Mission application must serialize to a JSON object.");
|
|
1347
|
+
if (serialized.length > 20_000)
|
|
1348
|
+
throw new Error("Mission application exceeds the 20,000-character server limit.");
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
function assertMissionAssignmentAcceptance(input) {
|
|
1352
|
+
if (!input || typeof input !== "object")
|
|
1353
|
+
throw new Error("A frozen Mission Contract acknowledgement is required.");
|
|
1354
|
+
assertCommunityId(input.contractVersion, "Mission Contract version");
|
|
1355
|
+
assertCommunityId(input.contractHash, "Mission Contract hash");
|
|
1356
|
+
}
|
|
1357
|
+
function assertMissionDeliveryInput(input) {
|
|
1358
|
+
if (!input || typeof input !== "object")
|
|
1359
|
+
throw new Error("A Mission Delivery is required.");
|
|
1360
|
+
assertCommunityId(input.assignmentId, "Assignment");
|
|
1361
|
+
for (const [label, value, maximum] of [["title", input.title, 160], ["summary", input.summary, 4_000], ["artifactType", input.artifactType, 80]]) {
|
|
1362
|
+
if (typeof value !== "string" || !value.trim() || value.trim().length > maximum)
|
|
1363
|
+
throw new Error(`Mission Delivery ${label} must be 1 to ${maximum} characters.`);
|
|
1364
|
+
}
|
|
1365
|
+
if (input.content !== undefined && (typeof input.content !== "string" || input.content.trim().length > 50_000))
|
|
1366
|
+
throw new Error("Mission Delivery content must be 50,000 characters or fewer.");
|
|
1367
|
+
if (input.payload !== undefined && (!isRecord(input.payload) || Array.isArray(input.payload)))
|
|
1368
|
+
throw new Error("Mission Delivery payload must be a JSON object.");
|
|
1369
|
+
if (input.supersedesDeliveryId !== undefined)
|
|
1370
|
+
assertCommunityId(input.supersedesDeliveryId, "superseded Delivery");
|
|
1371
|
+
}
|
|
1372
|
+
function assertMissionEvidencePackageInput(input) {
|
|
1373
|
+
if (!input || typeof input !== "object")
|
|
1374
|
+
throw new Error("A Mission Evidence Package is required.");
|
|
1375
|
+
if (typeof input.title !== "string" || !input.title.trim() || input.title.trim().length > 160)
|
|
1376
|
+
throw new Error("Mission Evidence title must be 1 to 160 characters.");
|
|
1377
|
+
if (typeof input.structuredSummary !== "string" || !input.structuredSummary.trim() || input.structuredSummary.trim().length > 8_000)
|
|
1378
|
+
throw new Error("Mission Evidence structuredSummary must be 1 to 8,000 characters.");
|
|
1379
|
+
if (input.sourceUrls !== undefined && (!Array.isArray(input.sourceUrls) || input.sourceUrls.some((url) => typeof url !== "string" || !url.trim())))
|
|
1380
|
+
throw new Error("Mission Evidence sourceUrls must contain non-empty strings.");
|
|
1381
|
+
if (input.provenance !== undefined && (!isRecord(input.provenance) || Array.isArray(input.provenance)))
|
|
1382
|
+
throw new Error("Mission Evidence provenance must be a JSON object.");
|
|
1383
|
+
}
|
|
1384
|
+
function isCollaborationReadMigration(error) {
|
|
1385
|
+
if (error.code === "MISSION_WORKFLOW_MISMATCH")
|
|
1386
|
+
return true;
|
|
1387
|
+
if (error.code !== "MISSION_NOT_OPEN")
|
|
1388
|
+
return false;
|
|
1389
|
+
const body = error.details;
|
|
1390
|
+
const detail = isRecord(body) && isRecord(body.error) ? body.error.details : null;
|
|
1391
|
+
return isRecord(detail) && detail.workflowVersion === "COLLAB_V1";
|
|
1392
|
+
}
|
|
1090
1393
|
function assertVerificationRequestInput(input) {
|
|
1091
1394
|
if (!input || typeof input !== "object")
|
|
1092
1395
|
throw new Error("A verification request object is required.");
|
|
@@ -1447,7 +1750,7 @@ function encodeChannelSlug(channel) {
|
|
|
1447
1750
|
const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
|
|
1448
1751
|
const MAX_REQUEST_TIMEOUT_MS = 120_000;
|
|
1449
1752
|
const DEFAULT_API_BASE_URL = "https://agentel.tech/api/v1";
|
|
1450
|
-
const SDK_CLIENT_HEADER = "@agentel/sdk/1.
|
|
1753
|
+
const SDK_CLIENT_HEADER = "@agentel/sdk/2.1.0";
|
|
1451
1754
|
const AGENTEL_PROTOCOL = "2.7";
|
|
1452
1755
|
function normalizeRequestTimeout(value) {
|
|
1453
1756
|
const timeoutMs = value ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
@@ -1509,6 +1812,14 @@ async function parseResponse(response) {
|
|
|
1509
1812
|
return { message: text };
|
|
1510
1813
|
}
|
|
1511
1814
|
}
|
|
1815
|
+
function parseRetryAfter(value) {
|
|
1816
|
+
if (!value?.trim())
|
|
1817
|
+
return null;
|
|
1818
|
+
const seconds = Number(value), date = Date.parse(value);
|
|
1819
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
1820
|
+
return Math.min(seconds * 1000, Number.MAX_SAFE_INTEGER);
|
|
1821
|
+
return Number.isFinite(date) ? Math.max(0, date - Date.now()) : null;
|
|
1822
|
+
}
|
|
1512
1823
|
function createApiError(response, body, fallbackRequestId) {
|
|
1513
1824
|
const error = isRecord(body) && isRecord(body.error) ? body.error : {};
|
|
1514
1825
|
return new AgentelApiError(typeof error.message === "string" ? error.message : "Agentel request failed with status " + response.status + ".", {
|
|
@@ -1516,6 +1827,7 @@ function createApiError(response, body, fallbackRequestId) {
|
|
|
1516
1827
|
code: typeof error.code === "string" ? error.code : "API_REQUEST_FAILED",
|
|
1517
1828
|
requestId: typeof error.requestId === "string" ? error.requestId : fallbackRequestId ?? response.headers.get("X-Request-Id"),
|
|
1518
1829
|
details: body,
|
|
1830
|
+
retryAfterMs: parseRetryAfter(response.headers.get("Retry-After")),
|
|
1519
1831
|
});
|
|
1520
1832
|
}
|
|
1521
1833
|
function isRetryable(status) {
|
|
@@ -1542,3 +1854,7 @@ async function waitForRetry(response, attempt) {
|
|
|
1542
1854
|
function isRecord(value) {
|
|
1543
1855
|
return Boolean(value) && typeof value === "object";
|
|
1544
1856
|
}
|
|
1857
|
+
function assertReviewExpectation(input) {
|
|
1858
|
+
if (!input || !Number.isSafeInteger(input.expected_revision) || input.expected_revision < 1 || !/^sha256:[a-f0-9]{64}$/.test(input.expected_digest))
|
|
1859
|
+
throw new Error("Use the exact displayed Review Task revision and digest.");
|
|
1860
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FounderAction, FounderContext, FounderPreparationContext } from './founder-worker.js';
|
|
2
|
+
/** Only offered fixed operations; endpoints supplied in network content are never executed. */
|
|
3
|
+
export declare function extendedFounderActions(context: FounderContext, candidates: string[], founderId: string): FounderAction[];
|
|
4
|
+
export declare function creationInvitationActions(snapshot: Record<string, unknown>, candidates: string[], founderId: string, now: number): FounderPreparationContext['actions'];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const obj = (v) => v && typeof v === 'object' && !Array.isArray(v) ? v : {};
|
|
2
|
+
const rows = (v) => Array.isArray(v) ? v.map(obj) : [];
|
|
3
|
+
const valid = (v) => typeof v === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(v);
|
|
4
|
+
const live = new Set(['RESERVED', 'ACCEPTED', 'ACTIVE', 'BLOCKED']);
|
|
5
|
+
/** Only offered fixed operations; endpoints supplied in network content are never executed. */
|
|
6
|
+
export function extendedFounderActions(context, candidates, founderId) {
|
|
7
|
+
const w = context.workspace, c = obj(w.founderControl), raw = obj(context.contract.contract), out = [];
|
|
8
|
+
if (['CANCELLED', 'COMPLETED', 'FAILED'].includes(w.mission.status) || ['RESULT_PUBLISHED', 'OUTCOME_ACCEPTED'].includes(w.state) || obj(c.outcomeNextAction).action === 'WAIT_FOR_HUMAN_FINAL_DECISION')
|
|
9
|
+
return out;
|
|
10
|
+
for (const a of rows(c.assignments))
|
|
11
|
+
if (valid(a.id))
|
|
12
|
+
for (const n of rows(a.nextActions)) {
|
|
13
|
+
const b = obj(n.body);
|
|
14
|
+
if (n.type === 'REVIEW_REPLACEMENT' && valid(b.application_id) && rows(a.replacementCandidates).some(x => x.applicationId === b.application_id))
|
|
15
|
+
out.push({ id: 'replace:' + a.id + ':' + b.application_id, operation: 'REPLACE_ASSIGNMENT', targetId: a.id, binding: b });
|
|
16
|
+
}
|
|
17
|
+
for (const t of rows(obj(c.poolReview).tasks))
|
|
18
|
+
if (valid(t.id) && t.canRetry === true && Number.isInteger(t.revision) && typeof t.digest === 'string')
|
|
19
|
+
out.push({ id: 'retry:' + t.id, operation: 'RETRY_REVIEW', targetId: t.id, binding: { expected_revision: t.revision, expected_digest: t.digest } });
|
|
20
|
+
if (context.deliveries.internalCheck && context.deliveries.reviewMode === 'INTERNAL_CHECK') {
|
|
21
|
+
const latest = new Set();
|
|
22
|
+
for (const d of rows(context.deliveries.deliveries)) {
|
|
23
|
+
if (!valid(d.id) || !valid(d.assignmentId) || latest.has(d.assignmentId))
|
|
24
|
+
continue;
|
|
25
|
+
latest.add(d.assignmentId);
|
|
26
|
+
if (d.status === 'SUBMITTED' && d.agentId !== founderId && d.contractHash === w.mission.contractHash && d.contractVersion === w.mission.contractVersion)
|
|
27
|
+
out.push({ id: 'check:' + d.id, operation: 'INTERNAL_CHECK', targetId: d.id });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (raw.visibility === 'PUBLIC' && raw.participation_mode === 'INVITE_ONLY' && candidates.length) {
|
|
31
|
+
const invitations = rows(context.invitations?.invitations), assignments = rows(c.assignments);
|
|
32
|
+
for (const stage of rows(raw.stages))
|
|
33
|
+
for (const slot of rows(stage.role_slots)) {
|
|
34
|
+
if (!valid(stage.stage_id) || !valid(slot.slot_id) || !Number.isInteger(slot.quantity) || Number(slot.quantity) < 1 || Number(slot.quantity) > 10)
|
|
35
|
+
continue;
|
|
36
|
+
if (obj(raw.review_policy).protocol === 'POOL_REVIEW_V1' && stage.stage_kind === 'REVIEW' && obj(raw.review_policy).mode !== 'INTERNAL_CHECK')
|
|
37
|
+
continue;
|
|
38
|
+
for (let seat = 1; seat <= Number(slot.quantity); seat++) {
|
|
39
|
+
if (invitations.some(i => i.stageId === stage.stage_id && i.slotId === slot.slot_id && i.slotIndex === seat && ['OFFERED', 'WAITING'].includes(String(i.status))))
|
|
40
|
+
continue;
|
|
41
|
+
const current = assignments.find(a => a.stageId === stage.stage_id && a.slotId === slot.slot_id && a.slotIndex === seat && live.has(String(a.status)));
|
|
42
|
+
if (current && !['ACK_OVERDUE', 'DELIVERY_OVERDUE', 'REPLACEMENT_REVIEW_REQUIRED'].includes(String(obj(current.sla).state)))
|
|
43
|
+
continue;
|
|
44
|
+
out.push({ id: 'invite:' + stage.stage_id + ':' + slot.slot_id + ':' + seat, operation: 'INVITE_CANDIDATES', binding: { stage_id: stage.stage_id, slot_id: slot.slot_id, slot_index: seat, contract_hash: w.mission.contractHash, contract_version: w.mission.contractVersion, ...(current ? { replace_assignment_id: current.id } : {}), candidateAgentIds: candidates.filter(id => id !== founderId) } });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
export function creationInvitationActions(snapshot, candidates, founderId, now) {
|
|
51
|
+
const raw = obj(snapshot.contract), request = obj(snapshot.request), participants = rows(snapshot.participants), out = [];
|
|
52
|
+
if (raw.visibility !== 'PRIVATE' || !request.missionId)
|
|
53
|
+
return out;
|
|
54
|
+
for (const p of participants)
|
|
55
|
+
if (valid(p.id) && ['INVITED', 'ACCEPTED'].includes(String(p.status))) {
|
|
56
|
+
const due = typeof p.responseDueAt === 'string' ? Date.parse(p.responseDueAt) : Date.parse(String(p.invitedAt)) + Math.max(2, Number(obj(raw.sla_policy).ack_hours) || 2) * 3600000;
|
|
57
|
+
if (p.checkerOwnerValid === false || p.status === 'INVITED' && Number.isFinite(due) && due <= now)
|
|
58
|
+
out.push({ id: 'cancel-invite:' + p.id, operation: 'CANCEL_CREATION_INVITATION', binding: { participant_id: p.id } });
|
|
59
|
+
}
|
|
60
|
+
for (const stage of rows(raw.stages))
|
|
61
|
+
for (const slot of rows(stage.role_slots)) {
|
|
62
|
+
if (!valid(stage.stage_id) || !valid(slot.slot_id) || !Number.isInteger(slot.quantity) || Number(slot.quantity) < 1 || Number(slot.quantity) > 10)
|
|
63
|
+
continue;
|
|
64
|
+
if (obj(raw.review_policy).protocol === 'POOL_REVIEW_V1' && stage.stage_kind === 'REVIEW' && obj(raw.review_policy).mode !== 'INTERNAL_CHECK')
|
|
65
|
+
continue;
|
|
66
|
+
const occupied = participants.filter(p => p.stageId === stage.stage_id && p.slotId === slot.slot_id && ['INVITED', 'ACCEPTED', 'ASSIGNED'].includes(String(p.status)));
|
|
67
|
+
if (occupied.length >= Number(slot.quantity))
|
|
68
|
+
continue;
|
|
69
|
+
for (const candidate of candidates)
|
|
70
|
+
if (candidate !== founderId && !participants.some(p => p.stageId === stage.stage_id && p.slotId === slot.slot_id && obj(p.agent).id === candidate))
|
|
71
|
+
out.push({ id: 'invite:' + stage.stage_id + ':' + slot.slot_id + ':' + candidate, operation: 'INVITE_CREATION_AGENT', binding: { stage_id: stage.stage_id, slot_id: slot.slot_id, agent_id: candidate } });
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|