@crowdedkingdoms/crowdyjs 11.0.0 → 12.0.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/MIGRATION.md +118 -0
- package/README.md +208 -0
- package/dist/crowdy-agent/browser-dispatcher.d.ts +41 -0
- package/dist/crowdy-agent/browser-dispatcher.d.ts.map +1 -0
- package/dist/crowdy-agent/browser-dispatcher.js +264 -0
- package/dist/crowdy-agent/controller.d.ts +144 -0
- package/dist/crowdy-agent/controller.d.ts.map +1 -0
- package/dist/crowdy-agent/controller.js +1239 -0
- package/dist/crowdy-agent/errors.d.ts +37 -0
- package/dist/crowdy-agent/errors.d.ts.map +1 -0
- package/dist/crowdy-agent/errors.js +107 -0
- package/dist/crowdy-agent/graphql-transport.d.ts +154 -0
- package/dist/crowdy-agent/graphql-transport.d.ts.map +1 -0
- package/dist/crowdy-agent/graphql-transport.js +854 -0
- package/dist/crowdy-agent/index.d.ts +16 -0
- package/dist/crowdy-agent/index.d.ts.map +1 -0
- package/dist/crowdy-agent/index.js +15 -0
- package/dist/crowdy-agent/registry.d.ts +29 -0
- package/dist/crowdy-agent/registry.d.ts.map +1 -0
- package/dist/crowdy-agent/registry.js +281 -0
- package/dist/crowdy-agent/schema.d.ts +72 -0
- package/dist/crowdy-agent/schema.d.ts.map +1 -0
- package/dist/crowdy-agent/schema.js +467 -0
- package/dist/crowdy-agent/studio-tools.d.ts +15 -0
- package/dist/crowdy-agent/studio-tools.d.ts.map +1 -0
- package/dist/crowdy-agent/studio-tools.js +280 -0
- package/dist/crowdy-agent/tool-descriptors.d.ts +4 -0
- package/dist/crowdy-agent/tool-descriptors.d.ts.map +1 -0
- package/dist/crowdy-agent/tool-descriptors.js +1049 -0
- package/dist/crowdy-agent/transport.d.ts +152 -0
- package/dist/crowdy-agent/transport.d.ts.map +1 -0
- package/dist/crowdy-agent/transport.js +27 -0
- package/dist/crowdy-agent/types.d.ts +328 -0
- package/dist/crowdy-agent/types.d.ts.map +1 -0
- package/dist/crowdy-agent/types.js +37 -0
- package/dist/crowdy-client.d.ts +3 -0
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/crowdy-client.js +6 -0
- package/dist/crowdy-studio/agent-dom-shell.d.ts +40 -0
- package/dist/crowdy-studio/agent-dom-shell.d.ts.map +1 -0
- package/dist/crowdy-studio/agent-dom-shell.js +354 -0
- package/dist/crowdy-studio/controller.d.ts +74 -4
- package/dist/crowdy-studio/controller.d.ts.map +1 -1
- package/dist/crowdy-studio/controller.js +450 -17
- package/dist/crowdy-studio/dom-shell.d.ts +4 -1
- package/dist/crowdy-studio/dom-shell.d.ts.map +1 -1
- package/dist/crowdy-studio/dom-shell.js +6 -1
- package/dist/crowdy-studio/index.d.ts +6 -3
- package/dist/crowdy-studio/index.d.ts.map +1 -1
- package/dist/crowdy-studio/index.js +3 -0
- package/dist/crowdy-studio/models.d.ts +67 -0
- package/dist/crowdy-studio/models.d.ts.map +1 -1
- package/dist/crowdy-studio/mount.d.ts +22 -1
- package/dist/crowdy-studio/mount.d.ts.map +1 -1
- package/dist/crowdy-studio/mount.js +163 -2
- package/dist/crowdy-studio/styles.d.ts +1 -1
- package/dist/crowdy-studio/styles.d.ts.map +1 -1
- package/dist/crowdy-studio/styles.js +5 -3
- package/dist/generated/graphql.d.ts +3753 -1
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/generated/graphql.js +296 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/kit/npcs.d.ts.map +1 -1
- package/dist/kit/social.d.ts.map +1 -1
- package/dist/player-host/index.d.ts +5 -0
- package/dist/player-host/index.d.ts.map +1 -0
- package/dist/player-host/index.js +3 -0
- package/dist/player-host/lease-manager.d.ts +66 -0
- package/dist/player-host/lease-manager.d.ts.map +1 -0
- package/dist/player-host/lease-manager.js +428 -0
- package/dist/player-host/schemas.d.ts +9 -0
- package/dist/player-host/schemas.d.ts.map +1 -0
- package/dist/player-host/schemas.js +347 -0
- package/dist/player-host/tools.d.ts +13 -0
- package/dist/player-host/tools.d.ts.map +1 -0
- package/dist/player-host/tools.js +79 -0
- package/dist/player-host/types.d.ts +203 -0
- package/dist/player-host/types.d.ts.map +1 -0
- package/dist/player-host/types.js +1 -0
- package/package.json +12 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PlayerCodeBroker, } from '../player-runtime/player-code-broker.js';
|
|
2
|
+
import { digestCanonicalJson, sha256Digest, } from '../crowdy-agent/schema.js';
|
|
2
3
|
import { parseRustcDiagnostics } from './diagnostics.js';
|
|
3
4
|
import { cloneCrowdyStudioProject, crowdyStudioFileKey, normalizeCrowdyStudioPath, projectTargets, CrowdyStudioOfflineError, CrowdyStudioRevisionConflictError, } from './models.js';
|
|
4
5
|
import { createCrowdyStudioStarterProject, } from './starter-projects.js';
|
|
@@ -21,6 +22,9 @@ export class CrowdyStudioController {
|
|
|
21
22
|
activeFile: null,
|
|
22
23
|
saveState: 'SAVED',
|
|
23
24
|
runtime: { phase: 'IDLE' },
|
|
25
|
+
runtimeSync: { state: 'NEVER_RUN' },
|
|
26
|
+
agentActivity: 'IDLE',
|
|
27
|
+
checkpoints: [],
|
|
24
28
|
buildOutput: '',
|
|
25
29
|
authoritativeDiagnostics: [],
|
|
26
30
|
localDiagnostics: [],
|
|
@@ -31,6 +35,7 @@ export class CrowdyStudioController {
|
|
|
31
35
|
invokeResult: null,
|
|
32
36
|
};
|
|
33
37
|
this.listeners = new Set();
|
|
38
|
+
this.humanEditListeners = new Set();
|
|
34
39
|
this.autosaveTimer = null;
|
|
35
40
|
this.retryTimer = null;
|
|
36
41
|
this.savePromise = null;
|
|
@@ -39,6 +44,7 @@ export class CrowdyStudioController {
|
|
|
39
44
|
this.conflictRemote = null;
|
|
40
45
|
this.broker = null;
|
|
41
46
|
this.operationGeneration = 0;
|
|
47
|
+
this.agentOperationGeneration = 0;
|
|
42
48
|
this.visibleSurfaces = new Set();
|
|
43
49
|
this.surfaceTimers = new Map();
|
|
44
50
|
this.pageVisible = true;
|
|
@@ -54,6 +60,51 @@ export class CrowdyStudioController {
|
|
|
54
60
|
listener(this.state);
|
|
55
61
|
return () => this.listeners.delete(listener);
|
|
56
62
|
}
|
|
63
|
+
/** Subscribe to synchronous human-edit preemption signals. */
|
|
64
|
+
onHumanEdit(listener) {
|
|
65
|
+
this.humanEditListeners.add(listener);
|
|
66
|
+
return () => this.humanEditListeners.delete(listener);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Flush autosave before a durable agent turn. Conflict/offline state fails
|
|
70
|
+
* closed so Build never starts from an uncommitted browser snapshot.
|
|
71
|
+
*/
|
|
72
|
+
async prepareForAgentWork() {
|
|
73
|
+
this.ensureAlive();
|
|
74
|
+
this.update({ agentActivity: 'PREPARING' });
|
|
75
|
+
const saved = await this.saveNow();
|
|
76
|
+
if (!saved || this.state.saveState !== 'SAVED') {
|
|
77
|
+
this.update({ agentActivity: 'PAUSED' });
|
|
78
|
+
throw new Error('Resolve the project save before starting agent work');
|
|
79
|
+
}
|
|
80
|
+
const project = this.state.project;
|
|
81
|
+
this.update({ agentActivity: 'WORKING' });
|
|
82
|
+
return {
|
|
83
|
+
...(project
|
|
84
|
+
? {
|
|
85
|
+
projectId: project.projectId,
|
|
86
|
+
projectRevisionId: project.revision.id,
|
|
87
|
+
}
|
|
88
|
+
: {}),
|
|
89
|
+
saveState: 'SAVED',
|
|
90
|
+
runtimeSync: { ...this.state.runtimeSync },
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
finishAgentWork(paused = false) {
|
|
94
|
+
this.update({ agentActivity: paused ? 'PAUSED' : 'IDLE' });
|
|
95
|
+
}
|
|
96
|
+
beginAgentOperation() {
|
|
97
|
+
return ++this.agentOperationGeneration;
|
|
98
|
+
}
|
|
99
|
+
/** Synchronously fence an in-flight agent compile/deploy/invoke operation. */
|
|
100
|
+
cancelAgentOperation(message = 'Agent operation cancelled') {
|
|
101
|
+
++this.agentOperationGeneration;
|
|
102
|
+
++this.operationGeneration;
|
|
103
|
+
this.update({
|
|
104
|
+
agentActivity: 'PAUSED',
|
|
105
|
+
runtime: { phase: 'IDLE', message },
|
|
106
|
+
});
|
|
107
|
+
}
|
|
57
108
|
canTarget(target, action) {
|
|
58
109
|
const permission = this.options.targetPermissions?.[target];
|
|
59
110
|
return permission
|
|
@@ -62,6 +113,32 @@ export class CrowdyStudioController {
|
|
|
62
113
|
: permission.canRun
|
|
63
114
|
: true;
|
|
64
115
|
}
|
|
116
|
+
/** Credential-free context projection used by exact browser agent tools. */
|
|
117
|
+
getAgentContext() {
|
|
118
|
+
const project = this.state.project;
|
|
119
|
+
return {
|
|
120
|
+
appRef: this.options.appId,
|
|
121
|
+
...(project ? { projectRef: project.projectId } : {}),
|
|
122
|
+
gridRef: this.options.gridId,
|
|
123
|
+
contextVersion: digestCanonicalJson({
|
|
124
|
+
contract: 'crowdy.studio-context/1',
|
|
125
|
+
appRef: this.options.appId,
|
|
126
|
+
gridRef: this.options.gridId,
|
|
127
|
+
...(project
|
|
128
|
+
? {
|
|
129
|
+
projectRef: project.projectId,
|
|
130
|
+
projectRevisionId: project.revision.id,
|
|
131
|
+
projectContentHash: projectContentHash(project),
|
|
132
|
+
}
|
|
133
|
+
: {}),
|
|
134
|
+
saveState: this.state.saveState,
|
|
135
|
+
runtimeSync: this.state.runtimeSync,
|
|
136
|
+
}),
|
|
137
|
+
...(project
|
|
138
|
+
? { projectContentHash: projectContentHash(project) }
|
|
139
|
+
: {}),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
65
142
|
async initialize() {
|
|
66
143
|
this.ensureAlive();
|
|
67
144
|
const scope = this.scope();
|
|
@@ -128,6 +205,9 @@ export class CrowdyStudioController {
|
|
|
128
205
|
projectId,
|
|
129
206
|
});
|
|
130
207
|
this.installProject(project);
|
|
208
|
+
if (this.options.synchronizationProvider) {
|
|
209
|
+
await this.refreshCheckpoints();
|
|
210
|
+
}
|
|
131
211
|
}
|
|
132
212
|
installProject(project) {
|
|
133
213
|
++this.operationGeneration;
|
|
@@ -148,6 +228,12 @@ export class CrowdyStudioController {
|
|
|
148
228
|
saveState: 'SAVED',
|
|
149
229
|
saveMessage: undefined,
|
|
150
230
|
runtime: { phase: 'IDLE' },
|
|
231
|
+
runtimeSync: {
|
|
232
|
+
state: 'NEVER_RUN',
|
|
233
|
+
savedRevisionId: clone.revision.id,
|
|
234
|
+
},
|
|
235
|
+
agentActivity: 'IDLE',
|
|
236
|
+
checkpoints: [],
|
|
151
237
|
buildOutput: '',
|
|
152
238
|
authoritativeDiagnostics: [],
|
|
153
239
|
localDiagnostics: [],
|
|
@@ -352,19 +438,183 @@ export class CrowdyStudioController {
|
|
|
352
438
|
this.update({ saveState: 'SAVING', saveMessage: undefined });
|
|
353
439
|
return this.saveNow();
|
|
354
440
|
}
|
|
355
|
-
async
|
|
356
|
-
|
|
441
|
+
async refreshCheckpoints() {
|
|
442
|
+
const project = this.requireProject();
|
|
443
|
+
const checkpoints = this.options.synchronizationProvider
|
|
444
|
+
? await this.options.synchronizationProvider.listCheckpoints({
|
|
445
|
+
...this.scope(),
|
|
446
|
+
projectId: project.projectId,
|
|
447
|
+
})
|
|
448
|
+
: this.state.checkpoints;
|
|
449
|
+
this.update({ checkpoints: [...checkpoints] });
|
|
450
|
+
return checkpoints;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Validate every change against one immutable baseline, then persist and
|
|
454
|
+
* synchronize all files or none. Routine agent patches cannot delete/rename.
|
|
455
|
+
*/
|
|
456
|
+
async applyAtomicPatch(input) {
|
|
457
|
+
if (!(await this.saveNow())) {
|
|
458
|
+
throw new Error('Resolve the current project save before applying an agent patch');
|
|
459
|
+
}
|
|
460
|
+
const baseline = cloneCrowdyStudioProject(this.requireProject());
|
|
461
|
+
if (baseline.revision.id !== input.expectedRevisionId) {
|
|
462
|
+
throw new CrowdyStudioRevisionConflictError(`Expected revision ${input.expectedRevisionId}, found ${baseline.revision.id}`, baseline);
|
|
463
|
+
}
|
|
464
|
+
applyValidatedPatch(baseline, input);
|
|
465
|
+
if (!this.options.synchronizationProvider) {
|
|
466
|
+
throw new Error('Atomic agent patches require a durable synchronization provider');
|
|
467
|
+
}
|
|
468
|
+
const result = await this.options.synchronizationProvider.applyAtomicPatch({
|
|
469
|
+
...this.scope(),
|
|
470
|
+
projectId: baseline.projectId,
|
|
471
|
+
expectedRevisionId: input.expectedRevisionId,
|
|
472
|
+
changes: input.changes,
|
|
473
|
+
});
|
|
474
|
+
if (result.project.projectId !== baseline.projectId) {
|
|
475
|
+
throw new Error('Atomic patch returned a different project');
|
|
476
|
+
}
|
|
477
|
+
if (result.project.revision.id === baseline.revision.id ||
|
|
478
|
+
result.checkpoint.projectRevisionId !== baseline.revision.id) {
|
|
479
|
+
throw new Error('Atomic patch returned invalid revision/checkpoint metadata');
|
|
480
|
+
}
|
|
481
|
+
for (const change of input.changes) {
|
|
482
|
+
const persisted = result.project.files.find((file) => file.target === change.target &&
|
|
483
|
+
file.path === normalizeCrowdyStudioPath(change.path));
|
|
484
|
+
if (!persisted || persisted.content !== change.content) {
|
|
485
|
+
throw new Error(`Atomic patch did not synchronize ${change.target}:${change.path}`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
this.synchronizeProject(result.project, {
|
|
489
|
+
source: 'AGENT',
|
|
490
|
+
expectedPreviousRevisionId: baseline.revision.id,
|
|
491
|
+
checkpoint: result.checkpoint,
|
|
492
|
+
});
|
|
493
|
+
return result;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Apply a server-published project revision to Monaco/kernel state. Pending
|
|
497
|
+
* human edits win and turn the update into an explicit conflict.
|
|
498
|
+
*/
|
|
499
|
+
synchronizeProject(project, synchronization) {
|
|
500
|
+
const current = this.requireProject();
|
|
501
|
+
if (project.projectId !== current.projectId) {
|
|
502
|
+
throw new Error('Project synchronization target does not match the open project');
|
|
503
|
+
}
|
|
504
|
+
if (synchronization.expectedPreviousRevisionId &&
|
|
505
|
+
synchronization.expectedPreviousRevisionId !== current.revision.id) {
|
|
506
|
+
throw new CrowdyStudioRevisionConflictError('Project synchronization started from a stale revision', project);
|
|
507
|
+
}
|
|
508
|
+
if (this.persistedGeneration !== this.editGeneration) {
|
|
509
|
+
this.conflictRemote = cloneCrowdyStudioProject(project);
|
|
510
|
+
this.update({
|
|
511
|
+
saveState: 'CONFLICT',
|
|
512
|
+
saveMessage: 'Human edits preempted an incoming agent project revision',
|
|
513
|
+
agentActivity: 'PAUSED',
|
|
514
|
+
});
|
|
515
|
+
throw new CrowdyStudioRevisionConflictError('Human edits preempted the agent project synchronization', project);
|
|
516
|
+
}
|
|
517
|
+
this.clearSaveTimers();
|
|
518
|
+
const clone = cloneCrowdyStudioProject(project);
|
|
519
|
+
this.editGeneration = 0;
|
|
520
|
+
this.persistedGeneration = 0;
|
|
521
|
+
this.conflictRemote = null;
|
|
522
|
+
const openFiles = this.state.openFiles.filter((ref) => fileRefExists(clone, this.state, ref));
|
|
523
|
+
const activeFile = this.state.activeFile &&
|
|
524
|
+
openFiles.some((ref) => sameFileRef(ref, this.state.activeFile))
|
|
525
|
+
? this.state.activeFile
|
|
526
|
+
: openFiles.at(-1) ?? null;
|
|
527
|
+
const checkpoint = synchronization.checkpoint;
|
|
528
|
+
this.update({
|
|
529
|
+
project: clone,
|
|
530
|
+
projects: upsertSummary(this.state.projects, summaryOf(clone)),
|
|
531
|
+
openFiles,
|
|
532
|
+
activeFile,
|
|
533
|
+
saveState: 'SAVED',
|
|
534
|
+
saveMessage: undefined,
|
|
535
|
+
checkpoints: checkpoint
|
|
536
|
+
? upsertCheckpoint(this.state.checkpoints, checkpoint)
|
|
537
|
+
: this.state.checkpoints,
|
|
538
|
+
runtimeSync: {
|
|
539
|
+
...this.state.runtimeSync,
|
|
540
|
+
savedRevisionId: clone.revision.id,
|
|
541
|
+
state: this.state.runtimeSync.state === 'RUNNING_SAVED' ||
|
|
542
|
+
this.state.runtimeSync.state === 'RUNNING_STALE'
|
|
543
|
+
? this.state.runtimeSync.runningRevisionId === clone.revision.id
|
|
544
|
+
? 'RUNNING_SAVED'
|
|
545
|
+
: 'RUNNING_STALE'
|
|
546
|
+
: this.state.runtimeSync.state,
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
this.options.onProjectSynchronized?.(cloneCrowdyStudioProject(clone), synchronization);
|
|
550
|
+
}
|
|
551
|
+
async restoreCheckpoint(checkpointId, approvalGrant, expectedRevisionId = this.requireProject().revision.id) {
|
|
552
|
+
if (approvalGrant.trim().length < 8) {
|
|
553
|
+
throw new Error('Checkpoint restore requires an opaque exact approval grant');
|
|
554
|
+
}
|
|
555
|
+
if (!(await this.saveNow())) {
|
|
556
|
+
throw new Error('Resolve the current project save before restoring a checkpoint');
|
|
557
|
+
}
|
|
558
|
+
const current = cloneCrowdyStudioProject(this.requireProject());
|
|
559
|
+
if (current.revision.id !== expectedRevisionId) {
|
|
560
|
+
throw new CrowdyStudioRevisionConflictError(`Expected revision ${expectedRevisionId}, found ${current.revision.id}`, current);
|
|
561
|
+
}
|
|
562
|
+
let restoredProject;
|
|
563
|
+
let preRestoreCheckpoint;
|
|
564
|
+
if (!this.options.synchronizationProvider) {
|
|
565
|
+
throw new Error('Checkpoint restore requires a durable synchronization provider');
|
|
566
|
+
}
|
|
567
|
+
const restored = await this.options.synchronizationProvider.restoreCheckpoint({
|
|
568
|
+
...this.scope(),
|
|
569
|
+
projectId: current.projectId,
|
|
570
|
+
checkpointId,
|
|
571
|
+
expectedRevisionId,
|
|
572
|
+
approvalGrant,
|
|
573
|
+
});
|
|
574
|
+
restoredProject = restored.project;
|
|
575
|
+
preRestoreCheckpoint = restored.preRestoreCheckpoint;
|
|
576
|
+
if (restoredProject.projectId !== current.projectId ||
|
|
577
|
+
restoredProject.revision.id === current.revision.id ||
|
|
578
|
+
preRestoreCheckpoint.projectRevisionId !== current.revision.id) {
|
|
579
|
+
throw new Error('Checkpoint restore returned invalid synchronization metadata');
|
|
580
|
+
}
|
|
581
|
+
this.synchronizeProject(restoredProject, {
|
|
582
|
+
source: 'AGENT',
|
|
583
|
+
expectedPreviousRevisionId: expectedRevisionId,
|
|
584
|
+
checkpoint: preRestoreCheckpoint,
|
|
585
|
+
});
|
|
586
|
+
return preRestoreCheckpoint;
|
|
587
|
+
}
|
|
588
|
+
async testDraft(agentOperation) {
|
|
589
|
+
return this.deployProject(true, agentOperation);
|
|
357
590
|
}
|
|
358
|
-
async
|
|
359
|
-
|
|
591
|
+
async testDraftPlan(plan, agentOperation) {
|
|
592
|
+
return this.deployProject(true, agentOperation, plan);
|
|
360
593
|
}
|
|
361
|
-
async
|
|
594
|
+
async deployLive(agentOperation) {
|
|
595
|
+
return this.deployProject(false, agentOperation);
|
|
596
|
+
}
|
|
597
|
+
async deployLivePlan(plan, agentOperation) {
|
|
598
|
+
return this.deployProject(false, agentOperation, plan);
|
|
599
|
+
}
|
|
600
|
+
async deployProject(draft, agentOperation, plan) {
|
|
601
|
+
this.checkAgentOperation(agentOperation);
|
|
362
602
|
this.requireProject();
|
|
363
603
|
if (!(await this.saveNow())) {
|
|
364
604
|
this.setRuntime('ERROR', 'Project must be saved before it can be built');
|
|
365
|
-
return
|
|
605
|
+
return {
|
|
606
|
+
deployment: draft ? 'DRAFT' : 'LIVE',
|
|
607
|
+
status: 'FAILED',
|
|
608
|
+
projectRevisionId: this.requireProject().revision.id,
|
|
609
|
+
targets: projectTargets(this.requireProject().kind),
|
|
610
|
+
message: 'Project must be saved before it can be built',
|
|
611
|
+
};
|
|
366
612
|
}
|
|
613
|
+
this.checkAgentOperation(agentOperation);
|
|
367
614
|
const project = this.requireProject();
|
|
615
|
+
if (plan)
|
|
616
|
+
this.assertDeploymentPlan(project, plan, draft);
|
|
617
|
+
const targets = plan ? [...plan.targets] : projectTargets(project.kind);
|
|
368
618
|
const operation = ++this.operationGeneration;
|
|
369
619
|
this.stopSurfacePolling();
|
|
370
620
|
this.update({
|
|
@@ -373,12 +623,18 @@ export class CrowdyStudioController {
|
|
|
373
623
|
authoritativeDiagnostics: [],
|
|
374
624
|
});
|
|
375
625
|
try {
|
|
376
|
-
const targets = projectTargets(project.kind);
|
|
377
626
|
if (targets.length === 1) {
|
|
378
627
|
const target = targets[0];
|
|
379
628
|
const compiled = await this.compileTarget(project, target, draft, operation);
|
|
380
|
-
if (!compiled)
|
|
381
|
-
return
|
|
629
|
+
if (!compiled) {
|
|
630
|
+
return {
|
|
631
|
+
deployment: draft ? 'DRAFT' : 'LIVE',
|
|
632
|
+
status: 'COMPILE_FAILED',
|
|
633
|
+
projectRevisionId: project.revision.id,
|
|
634
|
+
targets,
|
|
635
|
+
message: this.state.runtime.message ?? 'Compilation failed',
|
|
636
|
+
};
|
|
637
|
+
}
|
|
382
638
|
if (target === 'SERVER') {
|
|
383
639
|
await this.enableServer(compiled.name, operation);
|
|
384
640
|
}
|
|
@@ -390,11 +646,25 @@ export class CrowdyStudioController {
|
|
|
390
646
|
// Compile the client first so a client failure never publishes a new
|
|
391
647
|
// server version or alters the currently live pairing requirement.
|
|
392
648
|
const client = await this.compileTarget(project, 'CLIENT', draft, operation);
|
|
393
|
-
if (!client)
|
|
394
|
-
return
|
|
649
|
+
if (!client) {
|
|
650
|
+
return {
|
|
651
|
+
deployment: draft ? 'DRAFT' : 'LIVE',
|
|
652
|
+
status: 'COMPILE_FAILED',
|
|
653
|
+
projectRevisionId: project.revision.id,
|
|
654
|
+
targets,
|
|
655
|
+
message: this.state.runtime.message ?? 'Client compilation failed',
|
|
656
|
+
};
|
|
657
|
+
}
|
|
395
658
|
const server = await this.compileTarget(project, 'SERVER', draft, operation);
|
|
396
|
-
if (!server)
|
|
397
|
-
return
|
|
659
|
+
if (!server) {
|
|
660
|
+
return {
|
|
661
|
+
deployment: draft ? 'DRAFT' : 'LIVE',
|
|
662
|
+
status: 'COMPILE_FAILED',
|
|
663
|
+
projectRevisionId: project.revision.id,
|
|
664
|
+
targets,
|
|
665
|
+
message: this.state.runtime.message ?? 'Server compilation failed',
|
|
666
|
+
};
|
|
667
|
+
}
|
|
398
668
|
this.checkOperation(operation);
|
|
399
669
|
const requiredClientName = project.metadata.pairingPreference === 'REQUIRED'
|
|
400
670
|
? client.name
|
|
@@ -413,13 +683,41 @@ export class CrowdyStudioController {
|
|
|
413
683
|
phase: 'RUNNING',
|
|
414
684
|
message: draft ? 'Draft test is running' : 'Project is live',
|
|
415
685
|
},
|
|
686
|
+
runtimeSync: {
|
|
687
|
+
state: 'RUNNING_SAVED',
|
|
688
|
+
savedRevisionId: project.revision.id,
|
|
689
|
+
runningRevisionId: project.revision.id,
|
|
690
|
+
deployment: draft ? 'DRAFT' : 'LIVE',
|
|
691
|
+
startedAt: new Date().toISOString(),
|
|
692
|
+
},
|
|
416
693
|
});
|
|
417
694
|
await this.refreshSurface('usage').catch(() => { });
|
|
695
|
+
return {
|
|
696
|
+
deployment: draft ? 'DRAFT' : 'LIVE',
|
|
697
|
+
status: 'RUNNING',
|
|
698
|
+
projectRevisionId: project.revision.id,
|
|
699
|
+
targets,
|
|
700
|
+
message: draft ? 'Draft test is running' : 'Project is live',
|
|
701
|
+
};
|
|
418
702
|
}
|
|
419
703
|
catch (error) {
|
|
420
|
-
if (error instanceof OperationCancelledError)
|
|
421
|
-
return
|
|
704
|
+
if (error instanceof OperationCancelledError) {
|
|
705
|
+
return {
|
|
706
|
+
deployment: draft ? 'DRAFT' : 'LIVE',
|
|
707
|
+
status: 'FAILED',
|
|
708
|
+
projectRevisionId: project.revision.id,
|
|
709
|
+
targets,
|
|
710
|
+
message: 'Deployment was cancelled',
|
|
711
|
+
};
|
|
712
|
+
}
|
|
422
713
|
this.setRuntime('ERROR', errorMessage(error));
|
|
714
|
+
return {
|
|
715
|
+
deployment: draft ? 'DRAFT' : 'LIVE',
|
|
716
|
+
status: 'FAILED',
|
|
717
|
+
projectRevisionId: project.revision.id,
|
|
718
|
+
targets,
|
|
719
|
+
message: errorMessage(error),
|
|
720
|
+
};
|
|
423
721
|
}
|
|
424
722
|
finally {
|
|
425
723
|
if (operation === this.operationGeneration) {
|
|
@@ -591,10 +889,15 @@ export class CrowdyStudioController {
|
|
|
591
889
|
phase: 'PARTIAL_FAILURE',
|
|
592
890
|
message: failures.join(' · '),
|
|
593
891
|
},
|
|
892
|
+
runtimeSync: {
|
|
893
|
+
...this.state.runtimeSync,
|
|
894
|
+
state: 'STOPPED',
|
|
895
|
+
},
|
|
594
896
|
});
|
|
595
897
|
return result;
|
|
596
898
|
}
|
|
597
|
-
async invoke(exportName, paramsJson) {
|
|
899
|
+
async invoke(exportName, paramsJson, agentOperation) {
|
|
900
|
+
this.checkAgentOperation(agentOperation);
|
|
598
901
|
const project = this.requireProject();
|
|
599
902
|
if (!projectTargets(project.kind).includes('SERVER')) {
|
|
600
903
|
throw new Error('Invoke requires a SERVER target');
|
|
@@ -605,6 +908,7 @@ export class CrowdyStudioController {
|
|
|
605
908
|
exportName: exportName.trim() || 'invoke',
|
|
606
909
|
paramsJson: paramsJson?.trim() || null,
|
|
607
910
|
});
|
|
911
|
+
this.checkAgentOperation(agentOperation);
|
|
608
912
|
this.update({ invokeResult: result });
|
|
609
913
|
return result;
|
|
610
914
|
}
|
|
@@ -671,6 +975,7 @@ export class CrowdyStudioController {
|
|
|
671
975
|
this.broker?.stop();
|
|
672
976
|
this.broker = null;
|
|
673
977
|
this.listeners.clear();
|
|
978
|
+
this.humanEditListeners.clear();
|
|
674
979
|
}
|
|
675
980
|
async performSaveLoop() {
|
|
676
981
|
while (this.persistedGeneration !== this.editGeneration) {
|
|
@@ -701,6 +1006,16 @@ export class CrowdyStudioController {
|
|
|
701
1006
|
projects: upsertSummary(this.state.projects, summaryOf(saved)),
|
|
702
1007
|
saveState: this.persistedGeneration === this.editGeneration ? 'SAVED' : 'SAVING',
|
|
703
1008
|
saveMessage: undefined,
|
|
1009
|
+
runtimeSync: {
|
|
1010
|
+
...this.state.runtimeSync,
|
|
1011
|
+
savedRevisionId: saved.revision.id,
|
|
1012
|
+
state: this.state.runtimeSync.state === 'RUNNING_SAVED' ||
|
|
1013
|
+
this.state.runtimeSync.state === 'RUNNING_STALE'
|
|
1014
|
+
? this.state.runtimeSync.runningRevisionId === saved.revision.id
|
|
1015
|
+
? 'RUNNING_SAVED'
|
|
1016
|
+
: 'RUNNING_STALE'
|
|
1017
|
+
: this.state.runtimeSync.state,
|
|
1018
|
+
},
|
|
704
1019
|
});
|
|
705
1020
|
}
|
|
706
1021
|
catch (error) {
|
|
@@ -723,8 +1038,19 @@ export class CrowdyStudioController {
|
|
|
723
1038
|
return true;
|
|
724
1039
|
}
|
|
725
1040
|
markEdited() {
|
|
1041
|
+
for (const listener of this.humanEditListeners)
|
|
1042
|
+
listener();
|
|
726
1043
|
this.editGeneration++;
|
|
727
|
-
this.update({
|
|
1044
|
+
this.update({
|
|
1045
|
+
saveState: 'SAVING',
|
|
1046
|
+
saveMessage: undefined,
|
|
1047
|
+
agentActivity: this.state.agentActivity === 'WORKING'
|
|
1048
|
+
? 'PAUSED'
|
|
1049
|
+
: this.state.agentActivity,
|
|
1050
|
+
runtimeSync: this.state.runtimeSync.state === 'RUNNING_SAVED'
|
|
1051
|
+
? { ...this.state.runtimeSync, state: 'RUNNING_STALE' }
|
|
1052
|
+
: this.state.runtimeSync,
|
|
1053
|
+
});
|
|
728
1054
|
this.clearTimer('autosave');
|
|
729
1055
|
this.autosaveTimer = setTimeout(() => {
|
|
730
1056
|
this.autosaveTimer = null;
|
|
@@ -797,11 +1123,41 @@ export class CrowdyStudioController {
|
|
|
797
1123
|
onHostCall: this.options.onHostCall,
|
|
798
1124
|
};
|
|
799
1125
|
}
|
|
1126
|
+
assertDeploymentPlan(project, plan, draft) {
|
|
1127
|
+
if (project.revision.id !== plan.expectedRevisionId) {
|
|
1128
|
+
throw new CrowdyStudioRevisionConflictError(`Expected revision ${plan.expectedRevisionId}, found ${project.revision.id}`, project);
|
|
1129
|
+
}
|
|
1130
|
+
const authoritativeTargets = [...projectTargets(project.kind)].sort();
|
|
1131
|
+
const requestedTargets = [...new Set(plan.targets)].sort();
|
|
1132
|
+
if (requestedTargets.length !== authoritativeTargets.length ||
|
|
1133
|
+
requestedTargets.some((target, index) => target !== authoritativeTargets[index])) {
|
|
1134
|
+
throw new Error(`Deployment targets must exactly match ${authoritativeTargets.join(', ')}`);
|
|
1135
|
+
}
|
|
1136
|
+
if (plan.pairingPreference !== undefined &&
|
|
1137
|
+
plan.pairingPreference !== project.metadata.pairingPreference) {
|
|
1138
|
+
throw new Error('Deployment pairing preference changed after approval');
|
|
1139
|
+
}
|
|
1140
|
+
if (plan.projectContentHash !== undefined &&
|
|
1141
|
+
plan.projectContentHash !== projectContentHash(project)) {
|
|
1142
|
+
throw new Error('Deployment project content changed after approval');
|
|
1143
|
+
}
|
|
1144
|
+
if (!draft &&
|
|
1145
|
+
(plan.pairingPreference === undefined ||
|
|
1146
|
+
plan.projectContentHash === undefined)) {
|
|
1147
|
+
throw new Error('Live deployment requires exact pairing and project content bindings');
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
800
1150
|
checkOperation(generation) {
|
|
801
1151
|
if (generation !== this.operationGeneration || this.destroyed) {
|
|
802
1152
|
throw new OperationCancelledError();
|
|
803
1153
|
}
|
|
804
1154
|
}
|
|
1155
|
+
checkAgentOperation(generation) {
|
|
1156
|
+
if (generation !== undefined &&
|
|
1157
|
+
(generation !== this.agentOperationGeneration || this.destroyed)) {
|
|
1158
|
+
throw new OperationCancelledError();
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
805
1161
|
sleep(ms) {
|
|
806
1162
|
return (this.options.sleep ??
|
|
807
1163
|
((delay) => new Promise((resolve) => setTimeout(resolve, delay))))(ms);
|
|
@@ -910,6 +1266,83 @@ function normalizeUsage(value) {
|
|
|
910
1266
|
gateReason: value.gateReason ?? null,
|
|
911
1267
|
};
|
|
912
1268
|
}
|
|
1269
|
+
function applyValidatedPatch(baseline, input) {
|
|
1270
|
+
if (input.changes.length < 1 || input.changes.length > 16) {
|
|
1271
|
+
throw new Error('Atomic patch must contain 1 to 16 file changes');
|
|
1272
|
+
}
|
|
1273
|
+
const next = cloneCrowdyStudioProject(baseline);
|
|
1274
|
+
const seen = new Set();
|
|
1275
|
+
for (const change of input.changes) {
|
|
1276
|
+
const path = normalizeCrowdyStudioPath(change.path);
|
|
1277
|
+
const key = crowdyStudioFileKey(change.target, path);
|
|
1278
|
+
if (seen.has(key))
|
|
1279
|
+
throw new Error(`Atomic patch repeats ${key}`);
|
|
1280
|
+
seen.add(key);
|
|
1281
|
+
if (!projectTargets(next.kind).includes(change.target)) {
|
|
1282
|
+
throw new Error(`${next.kind} projects do not have a ${change.target} target`);
|
|
1283
|
+
}
|
|
1284
|
+
const bytes = new TextEncoder().encode(change.content).byteLength;
|
|
1285
|
+
if (bytes > 65536)
|
|
1286
|
+
throw new Error(`${key} exceeds the 65536-byte file limit`);
|
|
1287
|
+
const index = next.files.findIndex((file) => file.target === change.target && file.path === path);
|
|
1288
|
+
if (change.operation === 'CREATE') {
|
|
1289
|
+
if (change.expectedContentHash !== 'ABSENT' || index >= 0) {
|
|
1290
|
+
throw new CrowdyStudioRevisionConflictError(`${key} was expected to be absent`, baseline);
|
|
1291
|
+
}
|
|
1292
|
+
next.files.push({ target: change.target, path, content: change.content });
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
if (index < 0) {
|
|
1296
|
+
throw new CrowdyStudioRevisionConflictError(`${key} no longer exists`, baseline);
|
|
1297
|
+
}
|
|
1298
|
+
const currentHash = sha256Digest(next.files[index].content);
|
|
1299
|
+
if (change.expectedContentHash !== currentHash) {
|
|
1300
|
+
throw new CrowdyStudioRevisionConflictError(`${key} content hash changed`, baseline);
|
|
1301
|
+
}
|
|
1302
|
+
next.files[index] = {
|
|
1303
|
+
target: change.target,
|
|
1304
|
+
path,
|
|
1305
|
+
content: change.content,
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
if (next.files.length > 128) {
|
|
1309
|
+
throw new Error('Project exceeds the 128-file limit');
|
|
1310
|
+
}
|
|
1311
|
+
next.files.sort(compareProjectFile);
|
|
1312
|
+
return next;
|
|
1313
|
+
}
|
|
1314
|
+
function projectContentHash(project) {
|
|
1315
|
+
return digestCanonicalJson({
|
|
1316
|
+
contract: 'crowdy.studio-project-content/1',
|
|
1317
|
+
projectId: project.projectId,
|
|
1318
|
+
metadata: project.metadata,
|
|
1319
|
+
files: project.files
|
|
1320
|
+
.map((file) => ({
|
|
1321
|
+
target: file.target,
|
|
1322
|
+
path: file.path,
|
|
1323
|
+
contentHash: sha256Digest(file.content),
|
|
1324
|
+
}))
|
|
1325
|
+
.sort((left, right) => crowdyStudioFileKey(left.target, left.path).localeCompare(crowdyStudioFileKey(right.target, right.path))),
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
function upsertCheckpoint(checkpoints, checkpoint) {
|
|
1329
|
+
return [
|
|
1330
|
+
checkpoint,
|
|
1331
|
+
...checkpoints.filter((entry) => entry.checkpointId !== checkpoint.checkpointId),
|
|
1332
|
+
];
|
|
1333
|
+
}
|
|
1334
|
+
function fileRefExists(project, state, ref) {
|
|
1335
|
+
if (ref.source === 'PROJECT') {
|
|
1336
|
+
return project.files.some((file) => file.target === ref.target &&
|
|
1337
|
+
file.path === normalizeCrowdyStudioPath(ref.path));
|
|
1338
|
+
}
|
|
1339
|
+
const references = ref.source === 'PERSONAL_LIBRARY'
|
|
1340
|
+
? state.personalLibraryFiles
|
|
1341
|
+
: state.commonFiles;
|
|
1342
|
+
return references.some((file) => ref.referenceId
|
|
1343
|
+
? file.id === ref.referenceId
|
|
1344
|
+
: file.target === ref.target && file.path === ref.path);
|
|
1345
|
+
}
|
|
913
1346
|
function errorMessage(error) {
|
|
914
1347
|
return error instanceof Error ? error.message : String(error);
|
|
915
1348
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type CrowdyStudioController, type CrowdyStudioState } from './controller.js';
|
|
2
|
+
import type { CrowdyStudioAgentController } from '../crowdy-agent/controller.js';
|
|
3
|
+
import { type CrowdyStudioAgentDomShellOptions } from './agent-dom-shell.js';
|
|
2
4
|
/** Modular DOM shell; editor implementations mount into `editorHost`. */
|
|
3
5
|
export declare class CrowdyStudioDomShell {
|
|
4
6
|
private readonly controller;
|
|
@@ -32,9 +34,10 @@ export declare class CrowdyStudioDomShell {
|
|
|
32
34
|
private readonly invokeResult;
|
|
33
35
|
private readonly panelButtons;
|
|
34
36
|
private readonly panels;
|
|
37
|
+
private readonly agentShell;
|
|
35
38
|
private activePanel;
|
|
36
39
|
private disposed;
|
|
37
|
-
constructor(host: HTMLElement, controller: CrowdyStudioController);
|
|
40
|
+
constructor(host: HTMLElement, controller: CrowdyStudioController, agentController?: CrowdyStudioAgentController, agentOptions?: CrowdyStudioAgentDomShellOptions);
|
|
38
41
|
render(state: CrowdyStudioState): void;
|
|
39
42
|
dispose(): void;
|
|
40
43
|
private createPanel;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom-shell.d.ts","sourceRoot":"","sources":["../../src/crowdy-studio/dom-shell.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,sBAAsB,EAE3B,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"dom-shell.d.ts","sourceRoot":"","sources":["../../src/crowdy-studio/dom-shell.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,sBAAsB,EAE3B,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AACjF,OAAO,EAEL,KAAK,gCAAgC,EACtC,MAAM,sBAAsB,CAAC;AAY9B,yEAAyE;AACzE,qBAAa,oBAAoB;IAqC7B,OAAO,CAAC,QAAQ,CAAC,UAAU;IApC7B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;IACjD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;IACjD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAc;IAC5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAmB;IAChD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAmB;IACvD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAc;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAc;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAmB;IAChD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;IACnD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA2C;IACxE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqC;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmC;IAC9D,OAAO,CAAC,WAAW,CAAyB;IAC5C,OAAO,CAAC,QAAQ,CAAS;gBAGvB,IAAI,EAAE,WAAW,EACA,UAAU,EAAE,sBAAsB,EACnD,eAAe,CAAC,EAAE,2BAA2B,EAC7C,YAAY,GAAE,gCAAqC;IA2MrD,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;IA+BtC,OAAO,IAAI,IAAI;IAUf,OAAO,CAAC,WAAW;IAgBnB,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,cAAc;IAoBtB,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,cAAc;IAyBtB,OAAO,CAAC,cAAc;IAyBtB,OAAO,CAAC,cAAc;IAkDtB,OAAO,CAAC,gBAAgB;IAuCxB,OAAO,CAAC,UAAU;IAwBlB,OAAO,CAAC,cAAc;IAgCtB,OAAO,CAAC,cAAc;IAgCtB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,iBAAiB;YAgBX,GAAG;CAQlB"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { CrowdyStudioAgentDomShell, } from './agent-dom-shell.js';
|
|
1
2
|
import { projectTargets, } from './models.js';
|
|
2
3
|
import { CROWDY_STUDIO_STYLES } from './styles.js';
|
|
3
4
|
/** Modular DOM shell; editor implementations mount into `editorHost`. */
|
|
4
5
|
export class CrowdyStudioDomShell {
|
|
5
|
-
constructor(host, controller) {
|
|
6
|
+
constructor(host, controller, agentController, agentOptions = {}) {
|
|
6
7
|
this.controller = controller;
|
|
7
8
|
this.panelButtons = new Map();
|
|
8
9
|
this.panels = new Map();
|
|
@@ -99,6 +100,9 @@ export class CrowdyStudioDomShell {
|
|
|
99
100
|
this.invokePanel.append(invokeControls, this.invokeResult);
|
|
100
101
|
this.root.append(toolbar, this.newForm, main, bottom);
|
|
101
102
|
host.appendChild(this.root);
|
|
103
|
+
this.agentShell = agentController
|
|
104
|
+
? new CrowdyStudioAgentDomShell(this.root, agentController, agentOptions)
|
|
105
|
+
: null;
|
|
102
106
|
newButton.addEventListener('click', () => {
|
|
103
107
|
this.newForm.dataset.open =
|
|
104
108
|
this.newForm.dataset.open === 'true' ? 'false' : 'true';
|
|
@@ -184,6 +188,7 @@ export class CrowdyStudioDomShell {
|
|
|
184
188
|
this.controller.setSurfaceVisible('usage', false);
|
|
185
189
|
this.controller.setSurfaceVisible('logs', false);
|
|
186
190
|
this.controller.setSurfaceVisible('runs', false);
|
|
191
|
+
this.agentShell?.dispose();
|
|
187
192
|
this.root.remove();
|
|
188
193
|
}
|
|
189
194
|
createPanel(name, label, tabs) {
|