@penclipai/plugin-sdk 2026.505.0 → 2026.506.0-canary.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/README.md +180 -8
- package/dist/bundlers.d.ts.map +1 -1
- package/dist/bundlers.js +2 -1
- package/dist/bundlers.js.map +1 -1
- package/dist/host-client-factory.d.ts +23 -0
- package/dist/host-client-factory.d.ts.map +1 -1
- package/dist/host-client-factory.js +70 -0
- package/dist/host-client-factory.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/protocol.d.ts +138 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +538 -1
- package/dist/testing.js.map +1 -1
- package/dist/types.d.ts +107 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/components.d.ts +254 -0
- package/dist/ui/components.d.ts.map +1 -1
- package/dist/ui/components.js +38 -0
- package/dist/ui/components.js.map +1 -1
- package/dist/ui/hooks.d.ts +36 -1
- package/dist/ui/hooks.d.ts.map +1 -1
- package/dist/ui/hooks.js +47 -0
- package/dist/ui/hooks.js.map +1 -1
- package/dist/ui/index.d.ts +7 -3
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +4 -1
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/types.d.ts +80 -0
- package/dist/ui/types.d.ts.map +1 -1
- package/dist/worker-rpc-host.d.ts.map +1 -1
- package/dist/worker-rpc-host.js +84 -0
- package/dist/worker-rpc-host.js.map +1 -1
- package/package.json +3 -3
package/dist/testing.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { pluginOperationIssueOriginKind } from "@penclipai/shared";
|
|
2
3
|
// ---------------------------------------------------------------------------
|
|
3
4
|
// Environment event assertion helpers
|
|
4
5
|
// ---------------------------------------------------------------------------
|
|
@@ -199,6 +200,8 @@ export function createTestHarness(options) {
|
|
|
199
200
|
const entityExternalIndex = new Map();
|
|
200
201
|
const companies = new Map();
|
|
201
202
|
const projects = new Map();
|
|
203
|
+
const routines = new Map();
|
|
204
|
+
const routineRuns = new Map();
|
|
202
205
|
const issues = new Map();
|
|
203
206
|
const blockedByIssueIds = new Map();
|
|
204
207
|
const issueComments = new Map();
|
|
@@ -243,6 +246,43 @@ export function createTestHarness(options) {
|
|
|
243
246
|
return { blockedBy, blocks };
|
|
244
247
|
}
|
|
245
248
|
const defaultPluginOriginKind = `plugin:${manifest.id}`;
|
|
249
|
+
function managedAgentDeclaration(agentKey) {
|
|
250
|
+
const declaration = manifest.agents?.find((agent) => agent.agentKey === agentKey);
|
|
251
|
+
if (!declaration)
|
|
252
|
+
throw new Error(`Managed agent declaration not found: ${agentKey}`);
|
|
253
|
+
return declaration;
|
|
254
|
+
}
|
|
255
|
+
function isManagedAgent(agent, agentKey) {
|
|
256
|
+
const marker = agent.metadata?.paperclipManagedResource;
|
|
257
|
+
return Boolean(marker
|
|
258
|
+
&& typeof marker === "object"
|
|
259
|
+
&& !Array.isArray(marker)
|
|
260
|
+
&& marker.pluginKey === manifest.id
|
|
261
|
+
&& marker.resourceKind === "agent"
|
|
262
|
+
&& marker.resourceKey === agentKey);
|
|
263
|
+
}
|
|
264
|
+
function managedAgentMetadata(agentKey, existing) {
|
|
265
|
+
return {
|
|
266
|
+
...(existing ?? {}),
|
|
267
|
+
paperclipManagedResource: {
|
|
268
|
+
pluginKey: manifest.id,
|
|
269
|
+
resourceKind: "agent",
|
|
270
|
+
resourceKey: agentKey,
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function managedResolution(agentKey, companyId, agent, status) {
|
|
275
|
+
return {
|
|
276
|
+
pluginKey: manifest.id,
|
|
277
|
+
resourceKind: "agent",
|
|
278
|
+
resourceKey: agentKey,
|
|
279
|
+
companyId,
|
|
280
|
+
agentId: agent?.id ?? null,
|
|
281
|
+
agent,
|
|
282
|
+
status,
|
|
283
|
+
approvalId: null,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
246
286
|
function normalizePluginOriginKind(originKind = defaultPluginOriginKind) {
|
|
247
287
|
if (originKind == null || originKind === "")
|
|
248
288
|
return defaultPluginOriginKind;
|
|
@@ -260,6 +300,81 @@ export function createTestHarness(options) {
|
|
|
260
300
|
return { ...currentConfig };
|
|
261
301
|
},
|
|
262
302
|
},
|
|
303
|
+
localFolders: {
|
|
304
|
+
declarations() {
|
|
305
|
+
return manifest.localFolders ?? [];
|
|
306
|
+
},
|
|
307
|
+
async configure(input) {
|
|
308
|
+
requireCapability(manifest, capabilitySet, "local.folders");
|
|
309
|
+
return {
|
|
310
|
+
folderKey: input.folderKey,
|
|
311
|
+
configured: true,
|
|
312
|
+
path: input.path,
|
|
313
|
+
realPath: input.path,
|
|
314
|
+
access: input.access ?? "readWrite",
|
|
315
|
+
readable: true,
|
|
316
|
+
writable: input.access === "read" ? false : true,
|
|
317
|
+
requiredDirectories: input.requiredDirectories ?? [],
|
|
318
|
+
requiredFiles: input.requiredFiles ?? [],
|
|
319
|
+
missingDirectories: [],
|
|
320
|
+
missingFiles: [],
|
|
321
|
+
healthy: true,
|
|
322
|
+
problems: [],
|
|
323
|
+
checkedAt: new Date().toISOString(),
|
|
324
|
+
};
|
|
325
|
+
},
|
|
326
|
+
async status(_companyId, folderKey) {
|
|
327
|
+
requireCapability(manifest, capabilitySet, "local.folders");
|
|
328
|
+
return {
|
|
329
|
+
folderKey,
|
|
330
|
+
configured: false,
|
|
331
|
+
path: null,
|
|
332
|
+
realPath: null,
|
|
333
|
+
access: "readWrite",
|
|
334
|
+
readable: false,
|
|
335
|
+
writable: false,
|
|
336
|
+
requiredDirectories: [],
|
|
337
|
+
requiredFiles: [],
|
|
338
|
+
missingDirectories: [],
|
|
339
|
+
missingFiles: [],
|
|
340
|
+
healthy: false,
|
|
341
|
+
problems: [{ code: "not_configured", message: "No local folder path is configured." }],
|
|
342
|
+
checkedAt: new Date().toISOString(),
|
|
343
|
+
};
|
|
344
|
+
},
|
|
345
|
+
async list(_companyId, folderKey, options) {
|
|
346
|
+
requireCapability(manifest, capabilitySet, "local.folders");
|
|
347
|
+
return {
|
|
348
|
+
folderKey,
|
|
349
|
+
relativePath: options?.relativePath ?? null,
|
|
350
|
+
entries: [],
|
|
351
|
+
truncated: false,
|
|
352
|
+
};
|
|
353
|
+
},
|
|
354
|
+
async readText() {
|
|
355
|
+
requireCapability(manifest, capabilitySet, "local.folders");
|
|
356
|
+
throw new Error("Test harness local folder readText is not implemented");
|
|
357
|
+
},
|
|
358
|
+
async writeTextAtomic(_companyId, folderKey) {
|
|
359
|
+
requireCapability(manifest, capabilitySet, "local.folders");
|
|
360
|
+
return {
|
|
361
|
+
folderKey,
|
|
362
|
+
configured: false,
|
|
363
|
+
path: null,
|
|
364
|
+
realPath: null,
|
|
365
|
+
access: "readWrite",
|
|
366
|
+
readable: false,
|
|
367
|
+
writable: false,
|
|
368
|
+
requiredDirectories: [],
|
|
369
|
+
requiredFiles: [],
|
|
370
|
+
missingDirectories: [],
|
|
371
|
+
missingFiles: [],
|
|
372
|
+
healthy: false,
|
|
373
|
+
problems: [{ code: "not_configured", message: "No local folder path is configured." }],
|
|
374
|
+
checkedAt: new Date().toISOString(),
|
|
375
|
+
};
|
|
376
|
+
},
|
|
377
|
+
},
|
|
263
378
|
events: {
|
|
264
379
|
on(name, filterOrFn, maybeFn) {
|
|
265
380
|
requireCapability(manifest, capabilitySet, "events.subscribe");
|
|
@@ -443,6 +558,316 @@ export function createTestHarness(options) {
|
|
|
443
558
|
const workspaces = projectWorkspaces.get(projectId) ?? [];
|
|
444
559
|
return workspaces.find((workspace) => workspace.isPrimary) ?? null;
|
|
445
560
|
},
|
|
561
|
+
managed: {
|
|
562
|
+
async get(projectKey, companyId) {
|
|
563
|
+
requireCapability(manifest, capabilitySet, "projects.managed");
|
|
564
|
+
const declaration = manifest.projects?.find((project) => project.projectKey === projectKey);
|
|
565
|
+
if (!declaration) {
|
|
566
|
+
return {
|
|
567
|
+
pluginKey: manifest.id,
|
|
568
|
+
resourceKind: "project",
|
|
569
|
+
resourceKey: projectKey,
|
|
570
|
+
companyId,
|
|
571
|
+
projectId: null,
|
|
572
|
+
project: null,
|
|
573
|
+
status: "missing",
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
const externalId = `${manifest.id}:project:${projectKey}`;
|
|
577
|
+
const existingEntity = [...entities.values()].find((entity) => entity.entityType === "managed_resource"
|
|
578
|
+
&& entity.scopeKind === "company"
|
|
579
|
+
&& entity.scopeId === companyId
|
|
580
|
+
&& entity.externalId === externalId);
|
|
581
|
+
const existingProject = existingEntity ? projects.get(String(existingEntity.data?.projectId ?? "")) : null;
|
|
582
|
+
if (existingProject && isInCompany(existingProject, companyId)) {
|
|
583
|
+
return {
|
|
584
|
+
pluginKey: manifest.id,
|
|
585
|
+
resourceKind: "project",
|
|
586
|
+
resourceKey: projectKey,
|
|
587
|
+
companyId,
|
|
588
|
+
projectId: existingProject.id,
|
|
589
|
+
project: existingProject,
|
|
590
|
+
status: "resolved",
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
const now = new Date();
|
|
594
|
+
const project = {
|
|
595
|
+
id: `project-${projects.size + 1}`,
|
|
596
|
+
companyId,
|
|
597
|
+
urlKey: declaration.projectKey,
|
|
598
|
+
goalId: null,
|
|
599
|
+
goalIds: [],
|
|
600
|
+
goals: [],
|
|
601
|
+
name: declaration.displayName,
|
|
602
|
+
description: declaration.description ?? null,
|
|
603
|
+
status: declaration.status ?? "in_progress",
|
|
604
|
+
leadAgentId: null,
|
|
605
|
+
targetDate: null,
|
|
606
|
+
color: declaration.color ?? null,
|
|
607
|
+
env: null,
|
|
608
|
+
pauseReason: null,
|
|
609
|
+
pausedAt: null,
|
|
610
|
+
executionWorkspacePolicy: null,
|
|
611
|
+
codebase: {
|
|
612
|
+
workspaceId: null,
|
|
613
|
+
repoUrl: null,
|
|
614
|
+
repoRef: null,
|
|
615
|
+
defaultRef: null,
|
|
616
|
+
repoName: null,
|
|
617
|
+
localFolder: null,
|
|
618
|
+
managedFolder: `/tmp/${declaration.projectKey}`,
|
|
619
|
+
effectiveLocalFolder: `/tmp/${declaration.projectKey}`,
|
|
620
|
+
origin: "managed_checkout",
|
|
621
|
+
},
|
|
622
|
+
workspaces: [],
|
|
623
|
+
primaryWorkspace: null,
|
|
624
|
+
managedByPlugin: {
|
|
625
|
+
id: `managed-${projects.size + 1}`,
|
|
626
|
+
pluginId: manifest.id,
|
|
627
|
+
pluginKey: manifest.id,
|
|
628
|
+
pluginDisplayName: manifest.displayName,
|
|
629
|
+
resourceKind: "project",
|
|
630
|
+
resourceKey: projectKey,
|
|
631
|
+
defaultsJson: { displayName: declaration.displayName, settings: declaration.settings ?? {} },
|
|
632
|
+
createdAt: now,
|
|
633
|
+
updatedAt: now,
|
|
634
|
+
},
|
|
635
|
+
archivedAt: null,
|
|
636
|
+
createdAt: now,
|
|
637
|
+
updatedAt: now,
|
|
638
|
+
};
|
|
639
|
+
projects.set(project.id, project);
|
|
640
|
+
const externalKey = `managed_resource|company|${companyId}|${externalId}`;
|
|
641
|
+
const nowIso = now.toISOString();
|
|
642
|
+
const record = {
|
|
643
|
+
id: randomUUID(),
|
|
644
|
+
entityType: "managed_resource",
|
|
645
|
+
scopeKind: "company",
|
|
646
|
+
scopeId: companyId,
|
|
647
|
+
externalId,
|
|
648
|
+
title: declaration.displayName,
|
|
649
|
+
status: null,
|
|
650
|
+
data: { resourceKind: "project", resourceKey: projectKey, projectId: project.id },
|
|
651
|
+
createdAt: nowIso,
|
|
652
|
+
updatedAt: nowIso,
|
|
653
|
+
};
|
|
654
|
+
entities.set(record.id, record);
|
|
655
|
+
entityExternalIndex.set(externalKey, record.id);
|
|
656
|
+
return {
|
|
657
|
+
pluginKey: manifest.id,
|
|
658
|
+
resourceKind: "project",
|
|
659
|
+
resourceKey: projectKey,
|
|
660
|
+
companyId,
|
|
661
|
+
projectId: project.id,
|
|
662
|
+
project,
|
|
663
|
+
status: "created",
|
|
664
|
+
};
|
|
665
|
+
},
|
|
666
|
+
async reconcile(projectKey, companyId) {
|
|
667
|
+
return this.get(projectKey, companyId);
|
|
668
|
+
},
|
|
669
|
+
async reset(projectKey, companyId) {
|
|
670
|
+
const resolved = await this.get(projectKey, companyId);
|
|
671
|
+
return { ...resolved, status: resolved.project ? "reset" : resolved.status };
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
routines: {
|
|
676
|
+
managed: {
|
|
677
|
+
async get(routineKey, companyId) {
|
|
678
|
+
requireCapability(manifest, capabilitySet, "routines.managed");
|
|
679
|
+
const declaration = manifest.routines?.find((routine) => routine.routineKey === routineKey);
|
|
680
|
+
if (!declaration) {
|
|
681
|
+
return {
|
|
682
|
+
pluginKey: manifest.id,
|
|
683
|
+
resourceKind: "routine",
|
|
684
|
+
resourceKey: routineKey,
|
|
685
|
+
companyId,
|
|
686
|
+
routineId: null,
|
|
687
|
+
routine: null,
|
|
688
|
+
status: "missing",
|
|
689
|
+
missingRefs: [],
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
const externalId = `${manifest.id}:routine:${routineKey}`;
|
|
693
|
+
const existingEntity = [...entities.values()].find((entity) => entity.entityType === "managed_resource"
|
|
694
|
+
&& entity.scopeKind === "company"
|
|
695
|
+
&& entity.scopeId === companyId
|
|
696
|
+
&& entity.externalId === externalId);
|
|
697
|
+
const existingRoutine = existingEntity ? routines.get(String(existingEntity.data?.routineId ?? "")) : null;
|
|
698
|
+
if (existingRoutine && isInCompany(existingRoutine, companyId)) {
|
|
699
|
+
return {
|
|
700
|
+
pluginKey: manifest.id,
|
|
701
|
+
resourceKind: "routine",
|
|
702
|
+
resourceKey: routineKey,
|
|
703
|
+
companyId,
|
|
704
|
+
routineId: existingRoutine.id,
|
|
705
|
+
routine: existingRoutine,
|
|
706
|
+
status: "resolved",
|
|
707
|
+
missingRefs: [],
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
return {
|
|
711
|
+
pluginKey: manifest.id,
|
|
712
|
+
resourceKind: "routine",
|
|
713
|
+
resourceKey: routineKey,
|
|
714
|
+
companyId,
|
|
715
|
+
routineId: null,
|
|
716
|
+
routine: null,
|
|
717
|
+
status: "missing",
|
|
718
|
+
missingRefs: [],
|
|
719
|
+
};
|
|
720
|
+
},
|
|
721
|
+
async reconcile(routineKey, companyId, overrides) {
|
|
722
|
+
const existing = await this.get(routineKey, companyId);
|
|
723
|
+
if (existing.routine)
|
|
724
|
+
return existing;
|
|
725
|
+
const declaration = manifest.routines?.find((routine) => routine.routineKey === routineKey);
|
|
726
|
+
if (!declaration)
|
|
727
|
+
return existing;
|
|
728
|
+
const now = new Date();
|
|
729
|
+
const agentRef = declaration.assigneeRef;
|
|
730
|
+
const projectRef = declaration.projectRef;
|
|
731
|
+
const assigneeAgentId = overrides?.assigneeAgentId
|
|
732
|
+
?? (agentRef?.resourceKind === "agent"
|
|
733
|
+
? [...agents.values()].find((agent) => isInCompany(agent, companyId) && isManagedAgent(agent, agentRef.resourceKey))?.id
|
|
734
|
+
: null)
|
|
735
|
+
?? null;
|
|
736
|
+
const projectId = overrides?.projectId
|
|
737
|
+
?? (projectRef?.resourceKind === "project"
|
|
738
|
+
? [...projects.values()].find((project) => (isInCompany(project, companyId)
|
|
739
|
+
&& project.managedByPlugin?.pluginKey === manifest.id
|
|
740
|
+
&& project.managedByPlugin?.resourceKey === projectRef.resourceKey))?.id
|
|
741
|
+
: null)
|
|
742
|
+
?? null;
|
|
743
|
+
const missingRefs = [];
|
|
744
|
+
if (agentRef && !assigneeAgentId)
|
|
745
|
+
missingRefs.push({ ...agentRef, pluginKey: manifest.id });
|
|
746
|
+
if (projectRef && !projectId)
|
|
747
|
+
missingRefs.push({ ...projectRef, pluginKey: manifest.id });
|
|
748
|
+
if (missingRefs.length > 0) {
|
|
749
|
+
return {
|
|
750
|
+
pluginKey: manifest.id,
|
|
751
|
+
resourceKind: "routine",
|
|
752
|
+
resourceKey: routineKey,
|
|
753
|
+
companyId,
|
|
754
|
+
routineId: null,
|
|
755
|
+
routine: null,
|
|
756
|
+
status: "missing_refs",
|
|
757
|
+
missingRefs,
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
const routine = {
|
|
761
|
+
id: `routine-${routines.size + 1}`,
|
|
762
|
+
companyId,
|
|
763
|
+
projectId,
|
|
764
|
+
goalId: declaration.goalId ?? null,
|
|
765
|
+
parentIssueId: null,
|
|
766
|
+
title: declaration.title,
|
|
767
|
+
description: declaration.description ?? null,
|
|
768
|
+
assigneeAgentId,
|
|
769
|
+
priority: declaration.priority ?? "medium",
|
|
770
|
+
status: declaration.status ?? (assigneeAgentId ? "active" : "paused"),
|
|
771
|
+
concurrencyPolicy: declaration.concurrencyPolicy ?? "coalesce_if_active",
|
|
772
|
+
catchUpPolicy: declaration.catchUpPolicy ?? "skip_missed",
|
|
773
|
+
variables: declaration.variables ?? [],
|
|
774
|
+
createdByAgentId: null,
|
|
775
|
+
createdByUserId: null,
|
|
776
|
+
updatedByAgentId: null,
|
|
777
|
+
updatedByUserId: null,
|
|
778
|
+
lastTriggeredAt: null,
|
|
779
|
+
lastEnqueuedAt: null,
|
|
780
|
+
latestRevisionId: null,
|
|
781
|
+
latestRevisionNumber: 1,
|
|
782
|
+
createdAt: now,
|
|
783
|
+
updatedAt: now,
|
|
784
|
+
managedByPlugin: {
|
|
785
|
+
id: `managed-routine-${routines.size + 1}`,
|
|
786
|
+
pluginId: manifest.id,
|
|
787
|
+
pluginKey: manifest.id,
|
|
788
|
+
pluginDisplayName: manifest.displayName,
|
|
789
|
+
resourceKind: "routine",
|
|
790
|
+
resourceKey: routineKey,
|
|
791
|
+
defaultsJson: { title: declaration.title, issueTemplate: declaration.issueTemplate ?? null },
|
|
792
|
+
createdAt: now,
|
|
793
|
+
updatedAt: now,
|
|
794
|
+
},
|
|
795
|
+
};
|
|
796
|
+
routines.set(routine.id, routine);
|
|
797
|
+
const nowIso = now.toISOString();
|
|
798
|
+
const record = {
|
|
799
|
+
id: randomUUID(),
|
|
800
|
+
entityType: "managed_resource",
|
|
801
|
+
scopeKind: "company",
|
|
802
|
+
scopeId: companyId,
|
|
803
|
+
externalId: `${manifest.id}:routine:${routineKey}`,
|
|
804
|
+
title: declaration.title,
|
|
805
|
+
status: null,
|
|
806
|
+
data: { resourceKind: "routine", resourceKey: routineKey, routineId: routine.id },
|
|
807
|
+
createdAt: nowIso,
|
|
808
|
+
updatedAt: nowIso,
|
|
809
|
+
};
|
|
810
|
+
entities.set(record.id, record);
|
|
811
|
+
return {
|
|
812
|
+
pluginKey: manifest.id,
|
|
813
|
+
resourceKind: "routine",
|
|
814
|
+
resourceKey: routineKey,
|
|
815
|
+
companyId,
|
|
816
|
+
routineId: routine.id,
|
|
817
|
+
routine,
|
|
818
|
+
status: "created",
|
|
819
|
+
missingRefs: [],
|
|
820
|
+
};
|
|
821
|
+
},
|
|
822
|
+
async reset(routineKey, companyId, overrides) {
|
|
823
|
+
const resolved = await this.reconcile(routineKey, companyId, overrides);
|
|
824
|
+
return { ...resolved, status: resolved.routine ? "reset" : resolved.status };
|
|
825
|
+
},
|
|
826
|
+
async update(routineKey, companyId, patch) {
|
|
827
|
+
const resolved = await this.get(routineKey, companyId);
|
|
828
|
+
if (!resolved.routine)
|
|
829
|
+
throw new Error(`Managed routine not found: ${routineKey}`);
|
|
830
|
+
const next = {
|
|
831
|
+
...resolved.routine,
|
|
832
|
+
...(patch.status !== undefined ? { status: patch.status } : {}),
|
|
833
|
+
updatedAt: new Date(),
|
|
834
|
+
};
|
|
835
|
+
routines.set(next.id, next);
|
|
836
|
+
return next;
|
|
837
|
+
},
|
|
838
|
+
async run(routineKey, companyId) {
|
|
839
|
+
const resolved = await this.get(routineKey, companyId);
|
|
840
|
+
if (!resolved.routine)
|
|
841
|
+
throw new Error(`Managed routine not found: ${routineKey}`);
|
|
842
|
+
const now = new Date();
|
|
843
|
+
const run = {
|
|
844
|
+
id: `routine-run-${routineRuns.size + 1}`,
|
|
845
|
+
companyId,
|
|
846
|
+
routineId: resolved.routine.id,
|
|
847
|
+
triggerId: null,
|
|
848
|
+
source: "manual",
|
|
849
|
+
status: "queued",
|
|
850
|
+
triggeredAt: now,
|
|
851
|
+
idempotencyKey: null,
|
|
852
|
+
triggerPayload: null,
|
|
853
|
+
dispatchFingerprint: null,
|
|
854
|
+
linkedIssueId: null,
|
|
855
|
+
coalescedIntoRunId: null,
|
|
856
|
+
failureReason: null,
|
|
857
|
+
completedAt: null,
|
|
858
|
+
createdAt: now,
|
|
859
|
+
updatedAt: now,
|
|
860
|
+
};
|
|
861
|
+
routineRuns.set(run.id, run);
|
|
862
|
+
routines.set(resolved.routine.id, {
|
|
863
|
+
...resolved.routine,
|
|
864
|
+
lastTriggeredAt: now,
|
|
865
|
+
lastEnqueuedAt: now,
|
|
866
|
+
updatedAt: now,
|
|
867
|
+
});
|
|
868
|
+
return run;
|
|
869
|
+
},
|
|
870
|
+
},
|
|
446
871
|
},
|
|
447
872
|
companies: {
|
|
448
873
|
async list(input) {
|
|
@@ -474,6 +899,10 @@ export function createTestHarness(options) {
|
|
|
474
899
|
normalizePluginOriginKind(input.originKind);
|
|
475
900
|
out = out.filter((issue) => issue.originKind === input.originKind);
|
|
476
901
|
}
|
|
902
|
+
if (input?.originKindPrefix) {
|
|
903
|
+
const prefix = input.originKindPrefix;
|
|
904
|
+
out = out.filter((issue) => typeof issue.originKind === "string" && issue.originKind.startsWith(prefix));
|
|
905
|
+
}
|
|
477
906
|
if (input?.originId)
|
|
478
907
|
out = out.filter((issue) => issue.originId === input.originId);
|
|
479
908
|
if (input?.status)
|
|
@@ -492,6 +921,9 @@ export function createTestHarness(options) {
|
|
|
492
921
|
async create(input) {
|
|
493
922
|
requireCapability(manifest, capabilitySet, "issues.create");
|
|
494
923
|
const now = new Date();
|
|
924
|
+
const originKind = normalizePluginOriginKind(input.surfaceVisibility === "plugin_operation" && !input.originKind
|
|
925
|
+
? pluginOperationIssueOriginKind(manifest.id)
|
|
926
|
+
: input.originKind);
|
|
495
927
|
const record = {
|
|
496
928
|
id: randomUUID(),
|
|
497
929
|
companyId: input.companyId,
|
|
@@ -513,7 +945,7 @@ export function createTestHarness(options) {
|
|
|
513
945
|
createdByUserId: null,
|
|
514
946
|
issueNumber: null,
|
|
515
947
|
identifier: null,
|
|
516
|
-
originKind
|
|
948
|
+
originKind,
|
|
517
949
|
originId: input.originId ?? null,
|
|
518
950
|
originRunId: input.originRunId ?? null,
|
|
519
951
|
requestDepth: input.requestDepth ?? 0,
|
|
@@ -891,6 +1323,111 @@ export function createTestHarness(options) {
|
|
|
891
1323
|
}
|
|
892
1324
|
return { runId: randomUUID() };
|
|
893
1325
|
},
|
|
1326
|
+
managed: {
|
|
1327
|
+
async get(agentKey, companyId) {
|
|
1328
|
+
requireCapability(manifest, capabilitySet, "agents.managed");
|
|
1329
|
+
const cid = requireCompanyId(companyId);
|
|
1330
|
+
managedAgentDeclaration(agentKey);
|
|
1331
|
+
const agent = [...agents.values()].find((candidate) => candidate.companyId === cid &&
|
|
1332
|
+
candidate.status !== "terminated" &&
|
|
1333
|
+
isManagedAgent(candidate, agentKey)) ?? null;
|
|
1334
|
+
return managedResolution(agentKey, cid, agent, agent ? "resolved" : "missing");
|
|
1335
|
+
},
|
|
1336
|
+
async reconcile(agentKey, companyId) {
|
|
1337
|
+
requireCapability(manifest, capabilitySet, "agents.managed");
|
|
1338
|
+
const cid = requireCompanyId(companyId);
|
|
1339
|
+
const declaration = managedAgentDeclaration(agentKey);
|
|
1340
|
+
const existingAgent = [...agents.values()].find((candidate) => candidate.companyId === cid &&
|
|
1341
|
+
candidate.status !== "terminated" &&
|
|
1342
|
+
isManagedAgent(candidate, agentKey)) ?? null;
|
|
1343
|
+
const existing = managedResolution(agentKey, cid, existingAgent, existingAgent ? "resolved" : "missing");
|
|
1344
|
+
if (existing.agent)
|
|
1345
|
+
return existing;
|
|
1346
|
+
const now = new Date();
|
|
1347
|
+
const created = {
|
|
1348
|
+
id: randomUUID(),
|
|
1349
|
+
companyId: cid,
|
|
1350
|
+
name: declaration.displayName,
|
|
1351
|
+
urlKey: declaration.displayName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""),
|
|
1352
|
+
role: (declaration.role ?? "general"),
|
|
1353
|
+
title: declaration.title ?? null,
|
|
1354
|
+
icon: declaration.icon ?? null,
|
|
1355
|
+
status: declaration.status ?? "idle",
|
|
1356
|
+
reportsTo: null,
|
|
1357
|
+
capabilities: declaration.capabilities ?? null,
|
|
1358
|
+
adapterType: (declaration.adapterType ?? "process"),
|
|
1359
|
+
adapterConfig: declaration.adapterConfig ?? {},
|
|
1360
|
+
runtimeConfig: declaration.runtimeConfig ?? {},
|
|
1361
|
+
budgetMonthlyCents: declaration.budgetMonthlyCents ?? 0,
|
|
1362
|
+
spentMonthlyCents: 0,
|
|
1363
|
+
pauseReason: null,
|
|
1364
|
+
pausedAt: null,
|
|
1365
|
+
permissions: { canCreateAgents: Boolean(declaration.permissions?.canCreateAgents) },
|
|
1366
|
+
lastHeartbeatAt: null,
|
|
1367
|
+
metadata: managedAgentMetadata(agentKey),
|
|
1368
|
+
createdAt: now,
|
|
1369
|
+
updatedAt: now,
|
|
1370
|
+
};
|
|
1371
|
+
agents.set(created.id, created);
|
|
1372
|
+
return managedResolution(agentKey, cid, created, "created");
|
|
1373
|
+
},
|
|
1374
|
+
async reset(agentKey, companyId) {
|
|
1375
|
+
requireCapability(manifest, capabilitySet, "agents.managed");
|
|
1376
|
+
const cid = requireCompanyId(companyId);
|
|
1377
|
+
const declaration = managedAgentDeclaration(agentKey);
|
|
1378
|
+
let agent = [...agents.values()].find((candidate) => candidate.companyId === cid &&
|
|
1379
|
+
candidate.status !== "terminated" &&
|
|
1380
|
+
isManagedAgent(candidate, agentKey)) ?? null;
|
|
1381
|
+
if (!agent) {
|
|
1382
|
+
const now = new Date();
|
|
1383
|
+
agent = {
|
|
1384
|
+
id: randomUUID(),
|
|
1385
|
+
companyId: cid,
|
|
1386
|
+
name: declaration.displayName,
|
|
1387
|
+
urlKey: declaration.displayName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""),
|
|
1388
|
+
role: (declaration.role ?? "general"),
|
|
1389
|
+
title: declaration.title ?? null,
|
|
1390
|
+
icon: declaration.icon ?? null,
|
|
1391
|
+
status: declaration.status ?? "idle",
|
|
1392
|
+
reportsTo: null,
|
|
1393
|
+
capabilities: declaration.capabilities ?? null,
|
|
1394
|
+
adapterType: (declaration.adapterType ?? "process"),
|
|
1395
|
+
adapterConfig: declaration.adapterConfig ?? {},
|
|
1396
|
+
runtimeConfig: declaration.runtimeConfig ?? {},
|
|
1397
|
+
budgetMonthlyCents: declaration.budgetMonthlyCents ?? 0,
|
|
1398
|
+
spentMonthlyCents: 0,
|
|
1399
|
+
pauseReason: null,
|
|
1400
|
+
pausedAt: null,
|
|
1401
|
+
permissions: { canCreateAgents: Boolean(declaration.permissions?.canCreateAgents) },
|
|
1402
|
+
lastHeartbeatAt: null,
|
|
1403
|
+
metadata: managedAgentMetadata(agentKey),
|
|
1404
|
+
createdAt: now,
|
|
1405
|
+
updatedAt: now,
|
|
1406
|
+
};
|
|
1407
|
+
agents.set(agent.id, agent);
|
|
1408
|
+
}
|
|
1409
|
+
const resolved = managedResolution(agentKey, cid, agent, "resolved");
|
|
1410
|
+
if (!resolved.agent)
|
|
1411
|
+
return resolved;
|
|
1412
|
+
const updated = {
|
|
1413
|
+
...resolved.agent,
|
|
1414
|
+
name: declaration.displayName,
|
|
1415
|
+
role: (declaration.role ?? "general"),
|
|
1416
|
+
title: declaration.title ?? null,
|
|
1417
|
+
icon: declaration.icon ?? null,
|
|
1418
|
+
capabilities: declaration.capabilities ?? null,
|
|
1419
|
+
adapterType: (declaration.adapterType ?? "process"),
|
|
1420
|
+
adapterConfig: declaration.adapterConfig ?? {},
|
|
1421
|
+
runtimeConfig: declaration.runtimeConfig ?? {},
|
|
1422
|
+
budgetMonthlyCents: declaration.budgetMonthlyCents ?? 0,
|
|
1423
|
+
permissions: { canCreateAgents: Boolean(declaration.permissions?.canCreateAgents) },
|
|
1424
|
+
metadata: managedAgentMetadata(agentKey, resolved.agent.metadata),
|
|
1425
|
+
updatedAt: new Date(),
|
|
1426
|
+
};
|
|
1427
|
+
agents.set(updated.id, updated);
|
|
1428
|
+
return managedResolution(agentKey, cid, updated, "reset");
|
|
1429
|
+
},
|
|
1430
|
+
},
|
|
894
1431
|
sessions: {
|
|
895
1432
|
async create(agentId, companyId, opts) {
|
|
896
1433
|
requireCapability(manifest, capabilitySet, "agent.sessions.create");
|