@crowdedkingdoms/crowdyjs 11.1.0 → 12.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.
Files changed (108) hide show
  1. package/MIGRATION.md +152 -0
  2. package/README.md +262 -0
  3. package/dist/crowdy-agent/browser-dispatcher.d.ts +41 -0
  4. package/dist/crowdy-agent/browser-dispatcher.d.ts.map +1 -0
  5. package/dist/crowdy-agent/browser-dispatcher.js +264 -0
  6. package/dist/crowdy-agent/controller.d.ts +144 -0
  7. package/dist/crowdy-agent/controller.d.ts.map +1 -0
  8. package/dist/crowdy-agent/controller.js +1239 -0
  9. package/dist/crowdy-agent/errors.d.ts +37 -0
  10. package/dist/crowdy-agent/errors.d.ts.map +1 -0
  11. package/dist/crowdy-agent/errors.js +107 -0
  12. package/dist/crowdy-agent/graphql-transport.d.ts +154 -0
  13. package/dist/crowdy-agent/graphql-transport.d.ts.map +1 -0
  14. package/dist/crowdy-agent/graphql-transport.js +854 -0
  15. package/dist/crowdy-agent/index.d.ts +16 -0
  16. package/dist/crowdy-agent/index.d.ts.map +1 -0
  17. package/dist/crowdy-agent/index.js +15 -0
  18. package/dist/crowdy-agent/registry.d.ts +29 -0
  19. package/dist/crowdy-agent/registry.d.ts.map +1 -0
  20. package/dist/crowdy-agent/registry.js +281 -0
  21. package/dist/crowdy-agent/schema.d.ts +72 -0
  22. package/dist/crowdy-agent/schema.d.ts.map +1 -0
  23. package/dist/crowdy-agent/schema.js +467 -0
  24. package/dist/crowdy-agent/studio-tools.d.ts +15 -0
  25. package/dist/crowdy-agent/studio-tools.d.ts.map +1 -0
  26. package/dist/crowdy-agent/studio-tools.js +280 -0
  27. package/dist/crowdy-agent/tool-descriptors.d.ts +4 -0
  28. package/dist/crowdy-agent/tool-descriptors.d.ts.map +1 -0
  29. package/dist/crowdy-agent/tool-descriptors.js +1049 -0
  30. package/dist/crowdy-agent/transport.d.ts +152 -0
  31. package/dist/crowdy-agent/transport.d.ts.map +1 -0
  32. package/dist/crowdy-agent/transport.js +27 -0
  33. package/dist/crowdy-agent/types.d.ts +328 -0
  34. package/dist/crowdy-agent/types.d.ts.map +1 -0
  35. package/dist/crowdy-agent/types.js +37 -0
  36. package/dist/crowdy-client.d.ts +3 -0
  37. package/dist/crowdy-client.d.ts.map +1 -1
  38. package/dist/crowdy-client.js +6 -0
  39. package/dist/crowdy-studio/agent-dom-shell.d.ts +68 -0
  40. package/dist/crowdy-studio/agent-dom-shell.d.ts.map +1 -0
  41. package/dist/crowdy-studio/agent-dom-shell.js +418 -0
  42. package/dist/crowdy-studio/controller.d.ts +74 -4
  43. package/dist/crowdy-studio/controller.d.ts.map +1 -1
  44. package/dist/crowdy-studio/controller.js +450 -17
  45. package/dist/crowdy-studio/dom-shell.d.ts +50 -4
  46. package/dist/crowdy-studio/dom-shell.d.ts.map +1 -1
  47. package/dist/crowdy-studio/dom-shell.js +554 -123
  48. package/dist/crowdy-studio/embed/dock.d.ts +56 -0
  49. package/dist/crowdy-studio/embed/dock.d.ts.map +1 -0
  50. package/dist/crowdy-studio/embed/dock.js +231 -0
  51. package/dist/crowdy-studio/embed/embed-styles.d.ts +12 -0
  52. package/dist/crowdy-studio/embed/embed-styles.d.ts.map +1 -0
  53. package/dist/crowdy-studio/embed/embed-styles.js +549 -0
  54. package/dist/crowdy-studio/embed/hud-layer.d.ts +40 -0
  55. package/dist/crowdy-studio/embed/hud-layer.d.ts.map +1 -0
  56. package/dist/crowdy-studio/embed/hud-layer.js +100 -0
  57. package/dist/crowdy-studio/embed/index.d.ts +5 -0
  58. package/dist/crowdy-studio/embed/index.d.ts.map +1 -0
  59. package/dist/crowdy-studio/embed/index.js +4 -0
  60. package/dist/crowdy-studio/embed/panel.d.ts +162 -0
  61. package/dist/crowdy-studio/embed/panel.d.ts.map +1 -0
  62. package/dist/crowdy-studio/embed/panel.js +642 -0
  63. package/dist/crowdy-studio/index.d.ts +9 -3
  64. package/dist/crowdy-studio/index.d.ts.map +1 -1
  65. package/dist/crowdy-studio/index.js +6 -0
  66. package/dist/crowdy-studio/layout.d.ts +50 -0
  67. package/dist/crowdy-studio/layout.d.ts.map +1 -0
  68. package/dist/crowdy-studio/layout.js +155 -0
  69. package/dist/crowdy-studio/models.d.ts +67 -0
  70. package/dist/crowdy-studio/models.d.ts.map +1 -1
  71. package/dist/crowdy-studio/mount.d.ts +15 -0
  72. package/dist/crowdy-studio/mount.d.ts.map +1 -1
  73. package/dist/crowdy-studio/mount.js +132 -3
  74. package/dist/crowdy-studio/splitter.d.ts +33 -0
  75. package/dist/crowdy-studio/splitter.d.ts.map +1 -0
  76. package/dist/crowdy-studio/splitter.js +133 -0
  77. package/dist/crowdy-studio/styles.d.ts +1 -1
  78. package/dist/crowdy-studio/styles.d.ts.map +1 -1
  79. package/dist/crowdy-studio/styles.js +128 -11
  80. package/dist/generated/graphql.d.ts +3753 -1
  81. package/dist/generated/graphql.d.ts.map +1 -1
  82. package/dist/generated/graphql.js +296 -0
  83. package/dist/index.d.ts +1 -1
  84. package/dist/index.js +1 -1
  85. package/dist/kit/npcs.d.ts.map +1 -1
  86. package/dist/kit/social.d.ts.map +1 -1
  87. package/dist/player-host/control-banner.d.ts +40 -0
  88. package/dist/player-host/control-banner.d.ts.map +1 -0
  89. package/dist/player-host/control-banner.js +252 -0
  90. package/dist/player-host/control-gate.d.ts +77 -0
  91. package/dist/player-host/control-gate.d.ts.map +1 -0
  92. package/dist/player-host/control-gate.js +197 -0
  93. package/dist/player-host/index.d.ts +7 -0
  94. package/dist/player-host/index.d.ts.map +1 -0
  95. package/dist/player-host/index.js +5 -0
  96. package/dist/player-host/lease-manager.d.ts +66 -0
  97. package/dist/player-host/lease-manager.d.ts.map +1 -0
  98. package/dist/player-host/lease-manager.js +428 -0
  99. package/dist/player-host/schemas.d.ts +9 -0
  100. package/dist/player-host/schemas.d.ts.map +1 -0
  101. package/dist/player-host/schemas.js +347 -0
  102. package/dist/player-host/tools.d.ts +13 -0
  103. package/dist/player-host/tools.d.ts.map +1 -0
  104. package/dist/player-host/tools.js +79 -0
  105. package/dist/player-host/types.d.ts +203 -0
  106. package/dist/player-host/types.d.ts.map +1 -0
  107. package/dist/player-host/types.js +1 -0
  108. package/package.json +17 -2
@@ -0,0 +1,68 @@
1
+ import { type CrowdyStudioAgentController, type CrowdyStudioAgentStateV1 } from '../crowdy-agent/controller.js';
2
+ import type { StudioLayoutController } from './layout.js';
3
+ export interface CrowdyStudioAgentDomShellOptions {
4
+ getPlayLeaseContext?: () => {
5
+ controlledEntityId: string;
6
+ hostCapabilityRevision: string;
7
+ } | null;
8
+ /**
9
+ * Optional studio layout controller. When present the dock participates in
10
+ * the shared pane layout and auto-reveals itself for safety-critical
11
+ * moments (pending exact approvals, an activating Play lease).
12
+ */
13
+ layout?: StudioLayoutController;
14
+ }
15
+ /**
16
+ * Accessible, text-only rendering for the integrated durable agent dock.
17
+ *
18
+ * The conversation is the primary surface. Play-control, activity, and
19
+ * change-tracking sections are collapsible disclosures so the dock stays
20
+ * minimal, with one deliberate exception: pending exact approvals and the
21
+ * active Play lease countdown always render prominently.
22
+ */
23
+ export declare class CrowdyStudioAgentDomShell {
24
+ private readonly controller;
25
+ private readonly options;
26
+ readonly root: HTMLElement;
27
+ private readonly status;
28
+ private readonly budget;
29
+ private readonly lease;
30
+ private readonly messages;
31
+ private readonly stream;
32
+ private readonly timeline;
33
+ private readonly approvals;
34
+ private readonly checkpoints;
35
+ private readonly composer;
36
+ private readonly send;
37
+ private readonly playDisclosure;
38
+ private readonly playSummary;
39
+ private readonly activityDisclosure;
40
+ private readonly activitySummary;
41
+ private readonly changesDisclosure;
42
+ private readonly changesSummary;
43
+ private readonly modeButtons;
44
+ private readonly scopeInputs;
45
+ private readonly unsubscribe;
46
+ private readonly countdownTimer;
47
+ private lastState;
48
+ private lastPendingApprovals;
49
+ private lastPlayLeaseActive;
50
+ private disposed;
51
+ constructor(host: HTMLElement, controller: CrowdyStudioAgentController, options?: CrowdyStudioAgentDomShellOptions);
52
+ render(state: CrowdyStudioAgentStateV1): void;
53
+ dispose(): void;
54
+ /**
55
+ * Safety-critical auto-reveal: a newly pending exact approval or a Play
56
+ * lease activation opens the dock and the Play disclosure so the human is
57
+ * always shown what needs their decision or is acting on their behalf.
58
+ */
59
+ private autoReveal;
60
+ private renderBudget;
61
+ private renderLease;
62
+ private renderMessages;
63
+ private renderTimeline;
64
+ private renderApprovals;
65
+ private renderCheckpoints;
66
+ private run;
67
+ }
68
+ //# sourceMappingURL=agent-dom-shell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-dom-shell.d.ts","sourceRoot":"","sources":["../../src/crowdy-studio/agent-dom-shell.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC9B,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAY1D,MAAM,WAAW,gCAAgC;IAC/C,mBAAmB,CAAC,EAAE,MAAM;QAC1B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,sBAAsB,EAAE,MAAM,CAAC;KAChC,GAAG,IAAI,CAAC;IACT;;;;OAIG;IACH,MAAM,CAAC,EAAE,sBAAsB,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,qBAAa,yBAAyB;IA6BlC,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO;IA7B1B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;IAC/C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoB;IACzC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAqB;IACxD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAc;IAC9C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAqB;IACvD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAc;IAC7C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAiD;IAC7E,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAuC;IACnE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IACzC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiC;IAChE,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,oBAAoB,CAAK;IACjC,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAS;gBAGvB,IAAI,EAAE,WAAW,EACA,UAAU,EAAE,2BAA2B,EACvC,OAAO,GAAE,gCAAqC;IAmLjE,MAAM,CAAC,KAAK,EAAE,wBAAwB,GAAG,IAAI;IA0B7C,OAAO,IAAI,IAAI;IAQf;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAqBlB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,WAAW;IAwBnB,OAAO,CAAC,cAAc;IAiBtB,OAAO,CAAC,cAAc;IA8DtB,OAAO,CAAC,eAAe;IAgDvB,OAAO,CAAC,iBAAiB;YA0CX,GAAG;CAQlB"}
@@ -0,0 +1,418 @@
1
+ const PLAY_SCOPES = [
2
+ 'observe',
3
+ 'locomotion',
4
+ 'interact',
5
+ 'craft',
6
+ 'combat',
7
+ 'communicate',
8
+ 'travel',
9
+ ];
10
+ /**
11
+ * Accessible, text-only rendering for the integrated durable agent dock.
12
+ *
13
+ * The conversation is the primary surface. Play-control, activity, and
14
+ * change-tracking sections are collapsible disclosures so the dock stays
15
+ * minimal, with one deliberate exception: pending exact approvals and the
16
+ * active Play lease countdown always render prominently.
17
+ */
18
+ export class CrowdyStudioAgentDomShell {
19
+ constructor(host, controller, options = {}) {
20
+ this.controller = controller;
21
+ this.options = options;
22
+ this.modeButtons = new Map();
23
+ this.scopeInputs = new Map();
24
+ this.lastPendingApprovals = 0;
25
+ this.lastPlayLeaseActive = false;
26
+ this.disposed = false;
27
+ this.lastState = controller.getState();
28
+ this.root = document.createElement('aside');
29
+ this.root.className = 'ck-crowdy-studio-agent-dock';
30
+ this.root.setAttribute('aria-label', 'Crowdy Studio agent');
31
+ const header = document.createElement('header');
32
+ const title = document.createElement('h2');
33
+ title.textContent = 'Crowdy Agent';
34
+ this.status = document.createElement('span');
35
+ this.status.className = 'ck-crowdy-studio-agent-status';
36
+ this.status.setAttribute('role', 'status');
37
+ this.status.setAttribute('aria-live', 'polite');
38
+ header.append(title, this.status);
39
+ const controlsRow = document.createElement('div');
40
+ controlsRow.className = 'ck-crowdy-studio-agent-controls-row';
41
+ const modeGroup = document.createElement('div');
42
+ modeGroup.className = 'ck-crowdy-studio-agent-modes';
43
+ modeGroup.setAttribute('role', 'group');
44
+ modeGroup.setAttribute('aria-label', 'Agent mode');
45
+ for (const mode of ['ASK', 'BUILD', 'PLAY']) {
46
+ const control = button(titleCase(mode));
47
+ control.addEventListener('click', () => {
48
+ void this.run(() => this.controller.setMode(mode));
49
+ });
50
+ control.setAttribute('aria-pressed', 'false');
51
+ this.modeButtons.set(mode, control);
52
+ modeGroup.append(control);
53
+ }
54
+ const controls = document.createElement('div');
55
+ controls.className = 'ck-crowdy-studio-agent-controls';
56
+ const pause = button('Pause');
57
+ pause.addEventListener('click', () => void this.run(() => this.controller.pause()));
58
+ const resume = button('Resume');
59
+ resume.addEventListener('click', () => void this.run(() => this.controller.resume()));
60
+ const stop = button('Stop');
61
+ stop.className = 'ck-crowdy-studio-agent-stop';
62
+ stop.addEventListener('click', () => void this.run(() => this.controller.stop()));
63
+ controls.append(pause, resume, stop);
64
+ controlsRow.append(modeGroup, controls);
65
+ // Safety surfaces: approvals and the active Play lease stay prominent and
66
+ // are never hidden behind a disclosure.
67
+ this.approvals = document.createElement('section');
68
+ this.approvals.className = 'ck-crowdy-studio-agent-approvals';
69
+ this.approvals.setAttribute('aria-label', 'Pending exact approvals');
70
+ this.lease = document.createElement('div');
71
+ this.lease.className = 'ck-crowdy-studio-agent-lease';
72
+ this.lease.setAttribute('role', 'status');
73
+ this.lease.setAttribute('aria-label', 'Agent control lease');
74
+ this.messages = document.createElement('div');
75
+ this.messages.className = 'ck-crowdy-studio-agent-messages';
76
+ this.messages.setAttribute('role', 'log');
77
+ this.messages.setAttribute('aria-live', 'polite');
78
+ this.messages.setAttribute('aria-relevant', 'additions text');
79
+ this.stream = document.createElement('div');
80
+ this.stream.className = 'ck-crowdy-studio-agent-stream';
81
+ this.stream.setAttribute('aria-label', 'Streaming assistant response');
82
+ this.messages.append(this.stream);
83
+ const form = document.createElement('form');
84
+ form.className = 'ck-crowdy-studio-agent-composer';
85
+ this.composer = document.createElement('textarea');
86
+ this.composer.maxLength = 32768;
87
+ this.composer.rows = 3;
88
+ this.composer.placeholder = 'Ask, build, or play…';
89
+ this.composer.setAttribute('aria-label', 'Message Crowdy Agent');
90
+ this.send = button('Send', 'submit');
91
+ form.append(this.composer, this.send);
92
+ form.addEventListener('submit', (event) => {
93
+ event.preventDefault();
94
+ const content = this.composer.value;
95
+ if (!content.trim())
96
+ return;
97
+ void this.run(async () => {
98
+ await this.controller.sendMessage(content);
99
+ this.composer.value = '';
100
+ });
101
+ });
102
+ this.composer.addEventListener('keydown', (event) => {
103
+ if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
104
+ event.preventDefault();
105
+ form.requestSubmit();
106
+ }
107
+ });
108
+ // Collapsible secondary sections.
109
+ const playBody = document.createElement('div');
110
+ const leaseControls = document.createElement('fieldset');
111
+ leaseControls.className = 'ck-crowdy-studio-agent-lease-controls';
112
+ const leaseLegend = document.createElement('legend');
113
+ leaseLegend.textContent = 'Play lease scopes';
114
+ leaseControls.append(leaseLegend);
115
+ for (const scope of PLAY_SCOPES) {
116
+ const label = document.createElement('label');
117
+ const input = document.createElement('input');
118
+ input.type = 'checkbox';
119
+ input.value = scope;
120
+ input.checked = scope === 'observe';
121
+ this.scopeInputs.set(scope, input);
122
+ const text = document.createElement('span');
123
+ text.textContent = scope.replace('_', ' ');
124
+ label.append(input, text);
125
+ leaseControls.append(label);
126
+ }
127
+ const grant = button('Grant 60 second Play lease');
128
+ grant.addEventListener('click', () => {
129
+ const context = this.options.getPlayLeaseContext?.();
130
+ if (!context) {
131
+ this.status.textContent =
132
+ 'The game host must advertise a controlled entity before Play.';
133
+ return;
134
+ }
135
+ const scopes = [...this.scopeInputs]
136
+ .filter(([, input]) => input.checked)
137
+ .map(([scope]) => scope);
138
+ void this.run(() => this.controller.grantPlayLease({
139
+ scopes,
140
+ durationSeconds: 60,
141
+ ...context,
142
+ }));
143
+ });
144
+ leaseControls.append(grant);
145
+ playBody.append(leaseControls);
146
+ [this.playDisclosure, this.playSummary] = disclosure('Play control', playBody);
147
+ this.timeline = document.createElement('ol');
148
+ this.timeline.className = 'ck-crowdy-studio-agent-timeline';
149
+ this.timeline.setAttribute('aria-label', 'Agent plan and tool timeline');
150
+ [this.activityDisclosure, this.activitySummary] = disclosure('Activity', this.timeline);
151
+ this.checkpoints = document.createElement('section');
152
+ this.checkpoints.className = 'ck-crowdy-studio-agent-checkpoints';
153
+ this.checkpoints.setAttribute('aria-label', 'Agent diffs and checkpoints');
154
+ [this.changesDisclosure, this.changesSummary] = disclosure('Changes', this.checkpoints);
155
+ this.budget = document.createElement('div');
156
+ this.budget.className = 'ck-crowdy-studio-agent-budget';
157
+ this.budget.setAttribute('aria-label', 'Agent budget');
158
+ this.root.append(header, controlsRow, this.approvals, this.lease, this.messages, form, this.playDisclosure, this.activityDisclosure, this.changesDisclosure, this.budget);
159
+ host.dataset.agent = 'true';
160
+ host.append(this.root);
161
+ this.unsubscribe = controller.subscribe((state) => this.render(state));
162
+ this.countdownTimer = setInterval(() => this.renderLease(this.lastState), 1000);
163
+ this.render(this.lastState);
164
+ }
165
+ render(state) {
166
+ if (this.disposed)
167
+ return;
168
+ this.lastState = state;
169
+ this.status.textContent = state.lastError
170
+ ? `${state.connection} · ${state.lastError.code}: ${state.lastError.message}`
171
+ : state.reconnectRequired
172
+ ? `${state.connection} · explicit resume required`
173
+ : state.connection;
174
+ const mode = state.session?.mode;
175
+ for (const [candidate, control] of this.modeButtons) {
176
+ control.setAttribute('aria-pressed', String(candidate === mode));
177
+ control.dataset.active = String(candidate === mode);
178
+ control.disabled = state.connection !== 'CONNECTED';
179
+ }
180
+ this.send.disabled =
181
+ state.connection !== 'CONNECTED' ||
182
+ state.session?.status !== 'ACTIVE';
183
+ this.renderBudget(state);
184
+ this.renderLease(state);
185
+ this.renderMessages(state);
186
+ this.renderTimeline(state);
187
+ this.renderApprovals(state);
188
+ this.renderCheckpoints(state);
189
+ this.autoReveal(state);
190
+ }
191
+ dispose() {
192
+ if (this.disposed)
193
+ return;
194
+ this.disposed = true;
195
+ clearInterval(this.countdownTimer);
196
+ this.unsubscribe();
197
+ this.root.remove();
198
+ }
199
+ /**
200
+ * Safety-critical auto-reveal: a newly pending exact approval or a Play
201
+ * lease activation opens the dock and the Play disclosure so the human is
202
+ * always shown what needs their decision or is acting on their behalf.
203
+ */
204
+ autoReveal(state) {
205
+ const pending = state.approvals.filter((approval) => approval.status === 'PENDING').length;
206
+ const playLeaseActive = state.leases.some((lease) => lease.kind === 'PLAY' && lease.status === 'ACTIVE');
207
+ if (pending > this.lastPendingApprovals) {
208
+ this.options.layout?.setVisible('agent', true);
209
+ }
210
+ if (playLeaseActive && !this.lastPlayLeaseActive) {
211
+ this.options.layout?.setVisible('agent', true);
212
+ this.playDisclosure.open = true;
213
+ }
214
+ if (state.session?.mode === 'PLAY' && !playLeaseActive) {
215
+ this.playDisclosure.open = true;
216
+ }
217
+ this.lastPendingApprovals = pending;
218
+ this.lastPlayLeaseActive = playLeaseActive;
219
+ }
220
+ renderBudget(state) {
221
+ if (!state.budget) {
222
+ this.budget.textContent = 'Budget unavailable';
223
+ return;
224
+ }
225
+ const dimensions = state.budget.dimensions.map((dimension) => `${dimension.name.toLowerCase().replace(/_/gu, ' ')} ${dimension.consumed}/${dimension.limit} ${dimension.unit}`);
226
+ this.budget.textContent = [
227
+ state.budget.platformFunded ? 'Platform-funded pilot' : state.budget.payer,
228
+ ...dimensions,
229
+ ].join(' · ');
230
+ }
231
+ renderLease(state) {
232
+ const lease = state.leases.find((entry) => entry.kind === 'PLAY' && entry.status === 'ACTIVE');
233
+ if (!lease) {
234
+ this.lease.textContent = 'No active Play lease';
235
+ this.lease.dataset.active = 'false';
236
+ this.playSummary.textContent = 'Play control';
237
+ return;
238
+ }
239
+ const remaining = Math.max(0, Math.ceil((Date.parse(lease.expiresAt) - Date.now()) / 1000));
240
+ this.lease.dataset.active = 'true';
241
+ this.lease.textContent = [
242
+ `Play lease: ${remaining}s`,
243
+ `holder ${lease.holder}`,
244
+ `entity ${lease.controlledEntityId ?? 'none'}`,
245
+ `scopes ${lease.scopes.join(', ')}`,
246
+ ].join(' · ');
247
+ this.playSummary.textContent = `Play control · lease ${remaining}s`;
248
+ }
249
+ renderMessages(state) {
250
+ const rows = state.messages.map((message) => {
251
+ const article = document.createElement('article');
252
+ article.className = 'ck-crowdy-studio-agent-message';
253
+ article.dataset.role = message.role;
254
+ const author = document.createElement('strong');
255
+ author.textContent = message.role === 'USER' ? 'You' : 'Crowdy Agent';
256
+ const content = document.createElement('p');
257
+ content.textContent = message.content;
258
+ article.append(author, content);
259
+ return article;
260
+ });
261
+ this.messages.replaceChildren(...rows, this.stream);
262
+ this.stream.textContent = state.streamingText;
263
+ this.stream.hidden = state.streamingText.length === 0;
264
+ }
265
+ renderTimeline(state) {
266
+ const runItems = state.events
267
+ .filter((event) => event.type.startsWith('RUN_'))
268
+ .map((event) => {
269
+ const item = document.createElement('li');
270
+ item.dataset.status = event.type;
271
+ const title = document.createElement('strong');
272
+ title.textContent = event.type.replace(/_/gu, ' ').toLowerCase();
273
+ const timestamp = document.createElement('span');
274
+ timestamp.textContent = event.createdAt;
275
+ item.append(title, timestamp);
276
+ const payload = event.payload;
277
+ if (payload.code || payload.reason || payload.error) {
278
+ const detail = document.createElement('p');
279
+ detail.textContent = [
280
+ payload.code,
281
+ payload.reason,
282
+ payload.error
283
+ ? `${payload.error.code}: ${payload.error.message}`
284
+ : '',
285
+ ]
286
+ .filter(Boolean)
287
+ .join(' · ');
288
+ item.append(detail);
289
+ }
290
+ return item;
291
+ });
292
+ const toolItems = state.tools.map((tool) => {
293
+ const item = document.createElement('li');
294
+ item.dataset.status = tool.status;
295
+ const title = document.createElement('strong');
296
+ title.textContent = `${tool.name} ${tool.version}`;
297
+ const status = document.createElement('span');
298
+ status.textContent = tool.status.replace(/_/gu, ' ').toLowerCase();
299
+ item.append(title, status);
300
+ if (tool.safeSummary) {
301
+ const summary = document.createElement('p');
302
+ summary.textContent = tool.safeSummary;
303
+ item.append(summary);
304
+ }
305
+ if (tool.error) {
306
+ const error = document.createElement('p');
307
+ error.textContent = `${tool.error.code}: ${tool.error.message}`;
308
+ item.append(error);
309
+ }
310
+ return item;
311
+ });
312
+ const items = [...runItems, ...toolItems];
313
+ this.timeline.replaceChildren(...items);
314
+ this.activitySummary.textContent =
315
+ items.length > 0 ? `Activity (${items.length})` : 'Activity';
316
+ if (items.length === 0) {
317
+ const empty = document.createElement('li');
318
+ empty.textContent = 'No tool activity.';
319
+ this.timeline.append(empty);
320
+ }
321
+ }
322
+ renderApprovals(state) {
323
+ const pending = state.approvals.filter((approval) => approval.status === 'PENDING');
324
+ const cards = pending.map((approval) => {
325
+ const card = document.createElement('article');
326
+ card.className = 'ck-crowdy-studio-agent-approval';
327
+ const tool = state.tools.find((entry) => entry.toolCallId === approval.toolCallId);
328
+ const title = heading(tool
329
+ ? `Exact approval: ${tool.name} ${tool.version}`
330
+ : 'Exact approval required', 3);
331
+ const summary = document.createElement('p');
332
+ summary.textContent = `Server-verified execution plan: ${approval.safeSummary}`;
333
+ const call = document.createElement('p');
334
+ call.textContent = `Tool call ${approval.toolCallId}`;
335
+ const reasons = document.createElement('p');
336
+ reasons.textContent = approval.reasons.join(' · ');
337
+ const hash = document.createElement('code');
338
+ hash.textContent = approval.argumentHash;
339
+ const expiry = document.createElement('p');
340
+ expiry.textContent = `Expires ${approval.expiresAt}`;
341
+ const actions = document.createElement('div');
342
+ const approve = button('Approve exact call');
343
+ approve.addEventListener('click', () => void this.run(() => this.controller.approveTool(approval.toolCallId, approval.argumentHash)));
344
+ const deny = button('Deny');
345
+ deny.addEventListener('click', () => void this.run(() => this.controller.rejectTool(approval.toolCallId)));
346
+ actions.append(approve, deny);
347
+ card.append(title, call, summary, reasons, hash, expiry, actions);
348
+ return card;
349
+ });
350
+ this.approvals.replaceChildren(...cards);
351
+ this.approvals.hidden = cards.length === 0;
352
+ }
353
+ renderCheckpoints(state) {
354
+ const diffTools = state.tools.filter((tool) => tool.name === 'workspace.file.patch' ||
355
+ tool.name === 'workspace.conflict.resolve');
356
+ const cards = diffTools.map((tool) => {
357
+ const card = document.createElement('article');
358
+ card.className = 'ck-crowdy-studio-agent-diff';
359
+ const title = heading('Project diff', 3);
360
+ const summary = document.createElement('p');
361
+ summary.textContent =
362
+ tool.safeSummary ?? `${tool.name} · ${tool.status.toLowerCase()}`;
363
+ card.append(title, summary);
364
+ return card;
365
+ });
366
+ for (const checkpoint of state.checkpoints) {
367
+ const card = document.createElement('article');
368
+ card.className = 'ck-crowdy-studio-agent-checkpoint';
369
+ const title = heading('Checkpoint', 3);
370
+ const detail = document.createElement('p');
371
+ detail.textContent = `${checkpoint.checkpointId} · revision ${checkpoint.projectRevision} · ${checkpoint.reason.toLowerCase().replace(/_/gu, ' ')}`;
372
+ const restore = button('Restore checkpoint');
373
+ restore.addEventListener('click', () => void this.run(() => this.controller.restoreCheckpoint(checkpoint.checkpointId)));
374
+ card.append(title, detail, restore);
375
+ cards.push(card);
376
+ }
377
+ this.checkpoints.replaceChildren(...cards);
378
+ this.changesSummary.textContent =
379
+ cards.length > 0 ? `Changes (${cards.length})` : 'Changes';
380
+ if (cards.length === 0) {
381
+ const none = document.createElement('p');
382
+ none.className = 'ck-crowdy-studio-empty';
383
+ none.textContent = 'No agent edits or checkpoints yet.';
384
+ this.checkpoints.append(none);
385
+ }
386
+ }
387
+ async run(operation) {
388
+ try {
389
+ await operation();
390
+ }
391
+ catch (error) {
392
+ this.status.textContent =
393
+ error instanceof Error ? error.message : 'Agent action failed';
394
+ }
395
+ }
396
+ }
397
+ function disclosure(label, body) {
398
+ const details = document.createElement('details');
399
+ details.className = 'ck-crowdy-studio-agent-disclosure';
400
+ const summary = document.createElement('summary');
401
+ summary.textContent = label;
402
+ details.append(summary, body);
403
+ return [details, summary];
404
+ }
405
+ function button(label, type = 'button') {
406
+ const control = document.createElement('button');
407
+ control.type = type;
408
+ control.textContent = label;
409
+ return control;
410
+ }
411
+ function heading(text, level = 2) {
412
+ const value = document.createElement(`h${level}`);
413
+ value.textContent = text;
414
+ return value;
415
+ }
416
+ function titleCase(value) {
417
+ return `${value[0]}${value.slice(1).toLowerCase()}`;
418
+ }
@@ -2,7 +2,7 @@ import { type PlayerCodeBrokerOptions, type PlayerCodeGridBounds } from '../play
2
2
  import type { PlayerComputeAPI } from '../domains/playerCompute.js';
3
3
  import type { PlayerWalletAPI } from '../domains/playerWallet.js';
4
4
  import { type CrowdyStudioDiagnostic } from './diagnostics.js';
5
- import { type CrowdyStudioFileRef, type CrowdyStudioPairingPreference, type CrowdyStudioProject, type CrowdyStudioProjectMetadata, type CrowdyStudioProjectProvider, type CrowdyStudioProjectSummary, type CrowdyStudioReferenceFile, type CrowdyStudioSaveState, type CrowdyStudioTarget } from './models.js';
5
+ import { type CrowdyStudioAtomicPatchInput, type CrowdyStudioAtomicPatchResult, type CrowdyStudioCheckpointMetadata, type CrowdyStudioFileRef, type CrowdyStudioPairingPreference, type CrowdyStudioProject, type CrowdyStudioProjectMetadata, type CrowdyStudioProjectProvider, type CrowdyStudioProjectSummary, type CrowdyStudioReferenceFile, type CrowdyStudioSaveState, type CrowdyStudioSynchronizationProvider, type CrowdyStudioTarget, type CrowdyStudioProjectSynchronization } from './models.js';
6
6
  import { type CrowdyStudioNewProjectOptions } from './starter-projects.js';
7
7
  export type CrowdyStudioPhase = 'IDLE' | 'TESTING_DRAFT' | 'DEPLOYING_LIVE' | 'COMPILING' | 'ENABLING' | 'RUNNING' | 'COMPILE_FAILED' | 'STOPPING' | 'STOPPED' | 'PARTIAL_FAILURE' | 'ERROR';
8
8
  export type CrowdyStudioPolledSurface = 'runs' | 'logs' | 'usage';
@@ -11,6 +11,33 @@ export interface CrowdyStudioRuntimeStatus {
11
11
  target?: CrowdyStudioTarget;
12
12
  message?: string;
13
13
  }
14
+ export type CrowdyStudioRuntimeSyncState = 'NEVER_RUN' | 'RUNNING_SAVED' | 'RUNNING_STALE' | 'STOPPED';
15
+ export interface CrowdyStudioRuntimeSync {
16
+ state: CrowdyStudioRuntimeSyncState;
17
+ savedRevisionId?: string;
18
+ runningRevisionId?: string;
19
+ deployment?: 'DRAFT' | 'LIVE';
20
+ startedAt?: string;
21
+ }
22
+ export interface CrowdyStudioDeployResult {
23
+ deployment: 'DRAFT' | 'LIVE';
24
+ status: 'RUNNING' | 'COMPILE_FAILED' | 'FAILED';
25
+ projectRevisionId: string;
26
+ targets: readonly CrowdyStudioTarget[];
27
+ message: string;
28
+ }
29
+ export interface CrowdyStudioDeploymentPlan {
30
+ expectedRevisionId: string;
31
+ targets: readonly CrowdyStudioTarget[];
32
+ pairingPreference?: CrowdyStudioPairingPreference;
33
+ projectContentHash?: string;
34
+ }
35
+ export interface CrowdyStudioAgentWorkContext {
36
+ projectId?: string;
37
+ projectRevisionId?: string;
38
+ saveState: 'SAVED';
39
+ runtimeSync: CrowdyStudioRuntimeSync;
40
+ }
14
41
  export interface CrowdyStudioUsageSnapshot {
15
42
  hourUnitsUsed: string;
16
43
  dayUnitsUsed: string;
@@ -51,6 +78,9 @@ export interface CrowdyStudioState {
51
78
  saveState: CrowdyStudioSaveState;
52
79
  saveMessage?: string;
53
80
  runtime: CrowdyStudioRuntimeStatus;
81
+ runtimeSync: CrowdyStudioRuntimeSync;
82
+ agentActivity: 'IDLE' | 'PREPARING' | 'WORKING' | 'PAUSED';
83
+ checkpoints: readonly CrowdyStudioCheckpointMetadata[];
54
84
  buildOutput: string;
55
85
  authoritativeDiagnostics: readonly CrowdyStudioDiagnostic[];
56
86
  localDiagnostics: readonly CrowdyStudioDiagnostic[];
@@ -91,6 +121,9 @@ export interface CrowdyStudioControllerOptions {
91
121
  compilePollMs?: number;
92
122
  compilePollLimit?: number;
93
123
  monitorPollMs?: number;
124
+ /** Durable atomic-patch and checkpoint adapter, independent of GraphQL types. */
125
+ synchronizationProvider?: CrowdyStudioSynchronizationProvider;
126
+ onProjectSynchronized?: (project: CrowdyStudioProject, synchronization: CrowdyStudioProjectSynchronization) => void;
94
127
  sleep?: (ms: number) => Promise<void>;
95
128
  brokerFactory?: (options: PlayerCodeBrokerOptions) => CrowdyStudioBroker;
96
129
  isOnline?: () => boolean;
@@ -110,6 +143,7 @@ export declare class CrowdyStudioController {
110
143
  private readonly options;
111
144
  private state;
112
145
  private readonly listeners;
146
+ private readonly humanEditListeners;
113
147
  private autosaveTimer;
114
148
  private retryTimer;
115
149
  private savePromise;
@@ -118,6 +152,7 @@ export declare class CrowdyStudioController {
118
152
  private conflictRemote;
119
153
  private broker;
120
154
  private operationGeneration;
155
+ private agentOperationGeneration;
121
156
  private readonly visibleSurfaces;
122
157
  private readonly surfaceTimers;
123
158
  private pageVisible;
@@ -125,7 +160,26 @@ export declare class CrowdyStudioController {
125
160
  constructor(options: CrowdyStudioControllerOptions);
126
161
  getState(): CrowdyStudioState;
127
162
  subscribe(listener: (state: CrowdyStudioState) => void): () => void;
163
+ /** Subscribe to synchronous human-edit preemption signals. */
164
+ onHumanEdit(listener: () => void): () => void;
165
+ /**
166
+ * Flush autosave before a durable agent turn. Conflict/offline state fails
167
+ * closed so Build never starts from an uncommitted browser snapshot.
168
+ */
169
+ prepareForAgentWork(): Promise<CrowdyStudioAgentWorkContext>;
170
+ finishAgentWork(paused?: boolean): void;
171
+ beginAgentOperation(): number;
172
+ /** Synchronously fence an in-flight agent compile/deploy/invoke operation. */
173
+ cancelAgentOperation(message?: string): void;
128
174
  canTarget(target: CrowdyStudioTarget, action: 'write' | 'run'): boolean;
175
+ /** Credential-free context projection used by exact browser agent tools. */
176
+ getAgentContext(): {
177
+ appRef: string;
178
+ projectRef?: string;
179
+ gridRef: string;
180
+ contextVersion: string;
181
+ projectContentHash?: string;
182
+ };
129
183
  initialize(): Promise<void>;
130
184
  createProject(options: Omit<CrowdyStudioNewProjectOptions, 'appId' | 'gridId'>): Promise<CrowdyStudioProject>;
131
185
  switchProject(projectId: string): Promise<void>;
@@ -152,15 +206,29 @@ export declare class CrowdyStudioController {
152
206
  retrySave(): Promise<boolean>;
153
207
  acceptRemoteConflict(): Promise<void>;
154
208
  overwriteConflict(): Promise<boolean>;
155
- testDraft(): Promise<void>;
156
- deployLive(): Promise<void>;
209
+ refreshCheckpoints(): Promise<readonly CrowdyStudioCheckpointMetadata[]>;
210
+ /**
211
+ * Validate every change against one immutable baseline, then persist and
212
+ * synchronize all files or none. Routine agent patches cannot delete/rename.
213
+ */
214
+ applyAtomicPatch(input: CrowdyStudioAtomicPatchInput): Promise<CrowdyStudioAtomicPatchResult>;
215
+ /**
216
+ * Apply a server-published project revision to Monaco/kernel state. Pending
217
+ * human edits win and turn the update into an explicit conflict.
218
+ */
219
+ synchronizeProject(project: CrowdyStudioProject, synchronization: CrowdyStudioProjectSynchronization): void;
220
+ restoreCheckpoint(checkpointId: string, approvalGrant: string, expectedRevisionId?: string): Promise<CrowdyStudioCheckpointMetadata>;
221
+ testDraft(agentOperation?: number): Promise<CrowdyStudioDeployResult>;
222
+ testDraftPlan(plan: CrowdyStudioDeploymentPlan, agentOperation?: number): Promise<CrowdyStudioDeployResult>;
223
+ deployLive(agentOperation?: number): Promise<CrowdyStudioDeployResult>;
224
+ deployLivePlan(plan: CrowdyStudioDeploymentPlan, agentOperation?: number): Promise<CrowdyStudioDeployResult>;
157
225
  private deployProject;
158
226
  private compileTarget;
159
227
  private recordBuild;
160
228
  private enableServer;
161
229
  private runClient;
162
230
  stopProject(): Promise<CrowdyStudioStopResult>;
163
- invoke(exportName: string, paramsJson?: string): Promise<CrowdyStudioInvokeResult>;
231
+ invoke(exportName: string, paramsJson?: string, agentOperation?: number): Promise<CrowdyStudioInvokeResult>;
164
232
  setSurfaceVisible(surface: CrowdyStudioPolledSurface, visible: boolean): void;
165
233
  setPageVisible(visible: boolean): void;
166
234
  refreshSurface(surface: CrowdyStudioPolledSurface): Promise<void>;
@@ -175,7 +243,9 @@ export declare class CrowdyStudioController {
175
243
  private clearSaveTimers;
176
244
  private clearTimer;
177
245
  private clientRuntimeOptions;
246
+ private assertDeploymentPlan;
178
247
  private checkOperation;
248
+ private checkAgentOperation;
179
249
  private sleep;
180
250
  private requireProject;
181
251
  private requireFile;