@1agh/maude 0.55.0 → 0.57.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/apps/studio/acp/bridge.ts +385 -26
- package/apps/studio/acp/index.ts +498 -102
- package/apps/studio/acp/running.ts +97 -0
- package/apps/studio/acp/transcript.ts +64 -0
- package/apps/studio/acp/write-scope.ts +459 -0
- package/apps/studio/bin/_smart-frames.mjs +187 -20
- package/apps/studio/bin/_smart-frames.test.mjs +59 -4
- package/apps/studio/bin/_transcribe.mjs +40 -3
- package/apps/studio/bin/smoke.sh +7 -1
- package/apps/studio/build.ts +28 -1
- package/apps/studio/client/app.jsx +78 -33
- package/apps/studio/client/panels/ChatPanel.jsx +19 -1
- package/apps/studio/client/panels/CloudBar.jsx +104 -6
- package/apps/studio/client/panels/GitPanel.jsx +38 -25
- package/apps/studio/client/panels/PermissionPrompt.jsx +146 -6
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +145 -6
- package/apps/studio/client/panels/SettingsPanel.jsx +239 -27
- package/apps/studio/client/panels/acp-runtime.js +96 -5
- package/apps/studio/client/styles/3-shell-maude.css +11 -2
- package/apps/studio/client/styles/4-components.css +72 -0
- package/apps/studio/client/styles/6-acp-chat.css +51 -0
- package/apps/studio/cloud/endpoints.ts +56 -3
- package/apps/studio/collab/persistence.ts +29 -2
- package/apps/studio/config.schema.json +3 -3
- package/apps/studio/context.ts +41 -0
- package/apps/studio/dist/client.bundle.js +1330 -1330
- package/apps/studio/dist/comment-mount.js +2 -2
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/generation/gemma-models.ts +312 -14
- package/apps/studio/generation/prefs.ts +7 -2
- package/apps/studio/generation/runtime-probe.ts +50 -0
- package/apps/studio/generation/whisper-models.ts +124 -0
- package/apps/studio/hmr-broadcast.ts +67 -0
- package/apps/studio/http.ts +247 -111
- package/apps/studio/input-router.tsx +55 -2
- package/apps/studio/server.ts +22 -9
- package/apps/studio/sync/autocommit.ts +61 -2
- package/apps/studio/sync/cell-pairing.ts +174 -0
- package/apps/studio/sync/codec.ts +11 -5
- package/apps/studio/sync/connection-state.ts +116 -6
- package/apps/studio/sync/index.ts +288 -26
- package/apps/studio/sync/limits.ts +49 -0
- package/apps/studio/sync/loopback.ts +21 -0
- package/apps/studio/sync/presentation.ts +273 -0
- package/apps/studio/sync/projection.ts +47 -12
- package/apps/studio/sync/remote-docs.ts +191 -0
- package/apps/studio/sync/status.ts +4 -1
- package/apps/studio/sync/supervisor.ts +178 -0
- package/apps/studio/test/acp-activity-endpoint.test.ts +183 -0
- package/apps/studio/test/acp-branch-guard.test.ts +123 -0
- package/apps/studio/test/acp-bridge-lifetime.test.ts +369 -0
- package/apps/studio/test/acp-caps-bridge.test.ts +5 -0
- package/apps/studio/test/acp-commands.test.ts +5 -0
- package/apps/studio/test/acp-elicitation-bridge.test.ts +5 -0
- package/apps/studio/test/acp-permission-prompt.test.ts +175 -1
- package/apps/studio/test/acp-permission.test.ts +18 -2
- package/apps/studio/test/acp-session-allowed-tools.test.ts +62 -14
- package/apps/studio/test/acp-usage-bridge.test.ts +5 -0
- package/apps/studio/test/acp-write-gate.test.ts +323 -0
- package/apps/studio/test/acp-write-scope.test.ts +533 -0
- package/apps/studio/test/bundle-smoke.test.ts +35 -18
- package/apps/studio/test/canvas-origin-gate.test.ts +7 -0
- package/apps/studio/test/cloud-connect-note.test.ts +135 -0
- package/apps/studio/test/cloud-endpoints.test.ts +102 -0
- package/apps/studio/test/cloud-shell-surfaces.test.ts +5 -2
- package/apps/studio/test/csrf-write-guard.test.ts +19 -2
- package/apps/studio/test/fixtures/mock-acp-agent-slow.mjs +45 -0
- package/apps/studio/test/fixtures/mock-acp-agent-write.mjs +118 -0
- package/apps/studio/test/gemma-models.test.ts +245 -0
- package/apps/studio/test/hmr-broadcast.test.ts +57 -1
- package/apps/studio/test/input-router.test.ts +95 -0
- package/apps/studio/test/shared-doc-cell-pairing.test.ts +639 -0
- package/apps/studio/test/sync-autocommit.test.ts +47 -0
- package/apps/studio/test/sync-connect-honesty.test.ts +114 -0
- package/apps/studio/test/sync-connection-state.test.ts +45 -1
- package/apps/studio/test/sync-presentation.test.ts +185 -0
- package/apps/studio/test/sync-remote-docs.test.ts +171 -0
- package/apps/studio/test/sync-supervisor.test.ts +212 -0
- package/apps/studio/test/trusted-request-host.test.ts +66 -0
- package/apps/studio/test/whisper-setup.test.ts +97 -0
- package/apps/studio/whats-new.json +45 -0
- package/apps/studio/ws.ts +9 -1
- package/cli/commands/kg.mjs +9 -2
- package/package.json +8 -8
- package/plugins/design/dependencies.json +21 -3
|
@@ -880,6 +880,57 @@
|
|
|
880
880
|
gap: var(--space-2);
|
|
881
881
|
}
|
|
882
882
|
|
|
883
|
+
/* feature-acp-write-path-scope Task 4 — the out-of-project WRITE variant.
|
|
884
|
+
Distinguished from an ordinary permission card because the two ask
|
|
885
|
+
materially different questions: the ordinary one is "do you want this
|
|
886
|
+
tool to run", this one is "do you want a file written somewhere your
|
|
887
|
+
project's history stack cannot roll back". The danger tint is the whole
|
|
888
|
+
point — a card that looks identical to the routine one gets clicked
|
|
889
|
+
through at the same rate. */
|
|
890
|
+
.chat-perm--outside {
|
|
891
|
+
border-color: var(--danger, #d9534f);
|
|
892
|
+
}
|
|
893
|
+
.chat-perm--outside .chat-perm-hd b {
|
|
894
|
+
color: var(--danger, #d9534f);
|
|
895
|
+
}
|
|
896
|
+
.chat-perm-outside {
|
|
897
|
+
display: flex;
|
|
898
|
+
flex-direction: column;
|
|
899
|
+
gap: var(--space-1);
|
|
900
|
+
}
|
|
901
|
+
/* The resolved absolute paths. Monospaced + wrapping at any character: these
|
|
902
|
+
are the one string on the card the user actually has to READ, and a path
|
|
903
|
+
that overflows or ellipsizes is a path whose dangerous tail is hidden —
|
|
904
|
+
the same failure the .chat-perm-hd b clamp above exists to prevent, but
|
|
905
|
+
with the opposite fix, because here truncation is what would be unsafe. */
|
|
906
|
+
.chat-perm-paths {
|
|
907
|
+
margin: 0;
|
|
908
|
+
padding: 0;
|
|
909
|
+
list-style: none;
|
|
910
|
+
display: flex;
|
|
911
|
+
flex-direction: column;
|
|
912
|
+
gap: var(--space-1);
|
|
913
|
+
}
|
|
914
|
+
.chat-perm-path {
|
|
915
|
+
padding: var(--space-1) var(--space-2);
|
|
916
|
+
border-radius: var(--radius-xs);
|
|
917
|
+
background: var(--bg-1);
|
|
918
|
+
color: var(--fg-0);
|
|
919
|
+
font-family: var(--font-mono);
|
|
920
|
+
font-size: var(--type-xs);
|
|
921
|
+
overflow-wrap: anywhere;
|
|
922
|
+
}
|
|
923
|
+
.chat-perm-scope,
|
|
924
|
+
.chat-perm-note {
|
|
925
|
+
margin: 0;
|
|
926
|
+
font-size: var(--type-xs);
|
|
927
|
+
color: var(--fg-3);
|
|
928
|
+
}
|
|
929
|
+
.chat-perm-root {
|
|
930
|
+
font-family: var(--font-mono);
|
|
931
|
+
overflow-wrap: anywhere;
|
|
932
|
+
}
|
|
933
|
+
|
|
883
934
|
/* Form-elicitation card (feature-acp-ask-user-question — AskUserQuestion +
|
|
884
935
|
generic MCP form input). Same slot + shape as .chat-perm, mutually
|
|
885
936
|
exclusive with it — see the mount site in ChatPanel.jsx. */
|
|
@@ -109,6 +109,19 @@ async function cloudFetch(
|
|
|
109
109
|
|
|
110
110
|
interface Ctx {
|
|
111
111
|
paths: { repoRoot: string; designRoot: string };
|
|
112
|
+
/**
|
|
113
|
+
* The live sync runtime's owner (server.ts → sync/supervisor.ts). Optional:
|
|
114
|
+
* unit tests and any non-serving embedder construct a Ctx without one, and a
|
|
115
|
+
* missing supervisor degrades to exactly the old behaviour — linked on disk,
|
|
116
|
+
* syncing after the next start.
|
|
117
|
+
*/
|
|
118
|
+
syncControl?: {
|
|
119
|
+
restart(linkedHub?: {
|
|
120
|
+
url: string;
|
|
121
|
+
linkedAt: number;
|
|
122
|
+
syncTsx?: boolean;
|
|
123
|
+
}): Promise<{ syncing: boolean; canvases: number; reason?: string; detail?: string }>;
|
|
124
|
+
};
|
|
112
125
|
}
|
|
113
126
|
|
|
114
127
|
export function createCloudEndpoints(ctx: Ctx) {
|
|
@@ -225,7 +238,9 @@ export function createCloudEndpoints(ctx: Ctx) {
|
|
|
225
238
|
/**
|
|
226
239
|
* Attach THIS project to a cloud workspace: open → exchange at the cell →
|
|
227
240
|
* store the hub credential + linkedHub, exactly as `maude design link`
|
|
228
|
-
* would
|
|
241
|
+
* would — and then START SYNCING (linkToWorkspace cycles the live runtime
|
|
242
|
+
* through ctx.syncControl). Connecting is the whole gesture; there is no
|
|
243
|
+
* second step for the person to discover.
|
|
229
244
|
*/
|
|
230
245
|
async attach(projectId: string): Promise<CloudEndpointResult> {
|
|
231
246
|
const file = readCloudFile();
|
|
@@ -380,9 +395,42 @@ export function createCloudEndpoints(ctx: Ctx) {
|
|
|
380
395
|
} catch {
|
|
381
396
|
/* absent/malformed → start minimal */
|
|
382
397
|
}
|
|
383
|
-
|
|
398
|
+
const linkedHub: { url: string; linkedAt: number; syncTsx?: boolean } = {
|
|
399
|
+
url: norm,
|
|
400
|
+
linkedAt: Date.now(),
|
|
401
|
+
};
|
|
402
|
+
// Carry a project-wide TSX opt-out across the re-link. Only the RESTRICTIVE
|
|
403
|
+
// direction is carried (`false`, never `true`): somebody who turned canvas
|
|
404
|
+
// BODIES off (DDR-072/DDR-079) did so deliberately, and silently re-enabling
|
|
405
|
+
// them on the next Connect would start uploading source they opted out of.
|
|
406
|
+
// It never mattered while nothing synced until a restart; it does now.
|
|
407
|
+
const prior = (cfg as { linkedHub?: { syncTsx?: boolean } }).linkedHub;
|
|
408
|
+
if (prior?.syncTsx === false) linkedHub.syncTsx = false;
|
|
409
|
+
cfg.linkedHub = linkedHub;
|
|
384
410
|
writeFileSync(cfgPath, `${JSON.stringify(cfg, null, 2)}\n`, 'utf8');
|
|
385
411
|
|
|
412
|
+
// START SYNCING — do not hand the person a "restart the server" note and
|
|
413
|
+
// call the job done. The runtime captures linkedHub once at boot, so this
|
|
414
|
+
// cycles it in place, with the value we JUST wrote (never re-read from the
|
|
415
|
+
// committed config file). Best-effort: a supervisor-less embedder, or a
|
|
416
|
+
// runtime that declines, still leaves a correctly linked project behind.
|
|
417
|
+
let sync: { syncing: boolean; canvases: number; reason?: string; detail?: string } = {
|
|
418
|
+
syncing: false,
|
|
419
|
+
canvases: 0,
|
|
420
|
+
reason: 'no-supervisor',
|
|
421
|
+
detail: 'Restart Maude to start syncing.',
|
|
422
|
+
};
|
|
423
|
+
try {
|
|
424
|
+
sync = (await ctx.syncControl?.restart(linkedHub)) ?? sync;
|
|
425
|
+
} catch (err) {
|
|
426
|
+
sync = {
|
|
427
|
+
syncing: false,
|
|
428
|
+
canvases: 0,
|
|
429
|
+
reason: 'error',
|
|
430
|
+
detail: `Linked, but syncing could not start: ${(err as Error).message}`,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
386
434
|
return {
|
|
387
435
|
status: 200,
|
|
388
436
|
json: {
|
|
@@ -390,7 +438,12 @@ export function createCloudEndpoints(ctx: Ctx) {
|
|
|
390
438
|
url: norm,
|
|
391
439
|
role: role ?? null,
|
|
392
440
|
project: project ?? null,
|
|
393
|
-
|
|
441
|
+
sync,
|
|
442
|
+
// Kept for older clients (and the CLI) that read `note` — now it
|
|
443
|
+
// reports what actually happened instead of assigning homework.
|
|
444
|
+
note: sync.syncing
|
|
445
|
+
? `Linked — syncing ${sync.canvases} canvas${sync.canvases === 1 ? '' : 'es'}.`
|
|
446
|
+
: `Linked. ${sync.detail ?? 'Restart Maude to start syncing.'}`,
|
|
394
447
|
},
|
|
395
448
|
};
|
|
396
449
|
}
|
|
@@ -8,6 +8,9 @@ import * as Y from 'yjs';
|
|
|
8
8
|
|
|
9
9
|
import type { Api } from '../api.ts';
|
|
10
10
|
import type { Context } from '../context.ts';
|
|
11
|
+
// From the LEAF, never from `sync/codec.ts` — codec imports `Y_TYPES` from this
|
|
12
|
+
// file, so reaching for it here would close a cycle (see sync/limits.ts).
|
|
13
|
+
import { MAX_ANNOTATIONS_BYTES, MAX_COMMENTS_BYTES, withinByteCap } from '../sync/limits.ts';
|
|
11
14
|
import { ensureStateDir, type RoomCallbacks } from './room.ts';
|
|
12
15
|
|
|
13
16
|
/**
|
|
@@ -55,6 +58,26 @@ export interface PersistenceDeps {
|
|
|
55
58
|
* skipped when the Y type is empty / unset — the JSON file stays whatever
|
|
56
59
|
* the prior legacy write produced.
|
|
57
60
|
*/
|
|
61
|
+
/**
|
|
62
|
+
* DDR-064 pre-cutover checklist — cap the doc→disk lane for comments and
|
|
63
|
+
* annotations.
|
|
64
|
+
*
|
|
65
|
+
* The codec's `MAX_*_BYTES` guard the FILE→DOC direction. This is the other
|
|
66
|
+
* one, and until shared-doc it barely mattered: the room's doc was populated
|
|
67
|
+
* only by browsers on this machine. Under a shared doc it is populated by the
|
|
68
|
+
* hub, so an oversized array pushed by a peer (or by a hostile hub — DDR-054's
|
|
69
|
+
* threat model, §2d) would be materialized to this disk unbounded. Same ceiling
|
|
70
|
+
* as the import lane, so a value that could never be imported can never be
|
|
71
|
+
* written either.
|
|
72
|
+
*
|
|
73
|
+
* Refuses the WRITE, not the sync: the doc keeps the value and the peers keep
|
|
74
|
+
* converging. What is withheld is turning somebody else's blob into our disk.
|
|
75
|
+
* Shared with `sync/projection.ts`'s equivalent guard via `sync/limits.ts`.
|
|
76
|
+
*/
|
|
77
|
+
function withinCap(slug: string, lane: string, value: string, max: number): boolean {
|
|
78
|
+
return withinByteCap(`collab/${slug}`, lane, Buffer.byteLength(value, 'utf8'), max);
|
|
79
|
+
}
|
|
80
|
+
|
|
58
81
|
export function createPersistence(deps: PersistenceDeps): RoomCallbacks {
|
|
59
82
|
const { ctx, api, fileForSlug } = deps;
|
|
60
83
|
const stateDir = ensureStateDir(ctx.paths.designRoot);
|
|
@@ -135,14 +158,18 @@ export function createPersistence(deps: PersistenceDeps): RoomCallbacks {
|
|
|
135
158
|
const list = arr.toArray() as Parameters<Api['saveCommentsForFile']>[1];
|
|
136
159
|
if (list.length > 0) seenComments.add(slug);
|
|
137
160
|
if (list.length > 0 || seenComments.has(slug)) {
|
|
138
|
-
|
|
161
|
+
if (withinCap(slug, 'comments', JSON.stringify(list), MAX_COMMENTS_BYTES)) {
|
|
162
|
+
await api.saveCommentsForFile(file, list);
|
|
163
|
+
}
|
|
139
164
|
}
|
|
140
165
|
|
|
141
166
|
// Annotations — Y.Map.svg → annotations.svg file. Task 5.
|
|
142
167
|
const map = doc.getMap<unknown>(Y_TYPES.annotations);
|
|
143
168
|
const svg = map.get('svg');
|
|
144
169
|
if (typeof svg === 'string' && svg) {
|
|
145
|
-
|
|
170
|
+
if (withinCap(slug, 'annotations', svg, MAX_ANNOTATIONS_BYTES)) {
|
|
171
|
+
await api.saveAnnotations(file, svg);
|
|
172
|
+
}
|
|
146
173
|
}
|
|
147
174
|
}
|
|
148
175
|
|
|
@@ -259,12 +259,12 @@
|
|
|
259
259
|
},
|
|
260
260
|
"transcription": {
|
|
261
261
|
"type": "object",
|
|
262
|
-
"description": "Speech-to-text / subtitle engine preference (Task 2.6, DDR-164). The engine is
|
|
262
|
+
"description": "Speech-to-text / subtitle engine preference (Task 2.6, DDR-164). The engine is never switched behind your back: a chosen-but-unavailable engine errors clearly, and `auto` — a mode you SELECT — resolves to the best engine this machine is set up for while stating which one it picked. `maude design transcribe --provider` overrides this per-call.",
|
|
263
263
|
"properties": {
|
|
264
264
|
"provider": {
|
|
265
265
|
"type": "string",
|
|
266
|
-
"enum": ["whisper", "elevenlabs", "groq"],
|
|
267
|
-
"description": "Which engine transcribes when no --provider flag is given. `whisper` = local whisper.cpp (free, offline, no key); `elevenlabs` = ElevenLabs Scribe (cloud, key); `groq` = Groq Whisper (cloud, key). Absent ⇒ whisper.",
|
|
266
|
+
"enum": ["auto", "whisper", "elevenlabs", "groq"],
|
|
267
|
+
"description": "Which engine transcribes when no --provider flag is given. `auto` = use the cloud engine whose key is set (ElevenLabs Scribe first), else local whisper — the UI and the CLI both state what it resolved to; `whisper` = local whisper.cpp (free, offline, no key); `elevenlabs` = ElevenLabs Scribe (cloud, key, audio leaves the machine); `groq` = Groq Whisper (cloud, key). Absent ⇒ whisper.",
|
|
268
268
|
"default": "whisper"
|
|
269
269
|
},
|
|
270
270
|
"model": {
|
package/apps/studio/context.ts
CHANGED
|
@@ -182,6 +182,22 @@ export interface Context {
|
|
|
182
182
|
* gates behavior starting in Phase B.
|
|
183
183
|
*/
|
|
184
184
|
sharedDoc?: boolean;
|
|
185
|
+
/**
|
|
186
|
+
* The live sync runtime's owner, set by server.ts once it exists. Present so
|
|
187
|
+
* the cloud attach lane can START SYNCING the moment a person links a
|
|
188
|
+
* project, instead of leaving them holding a "restart the studio server"
|
|
189
|
+
* note with no button behind it. Structural type (not the SyncSupervisor
|
|
190
|
+
* import) so context.ts stays free of the sync module graph — tests and the
|
|
191
|
+
* non-serving entry points construct a Context without one.
|
|
192
|
+
*/
|
|
193
|
+
syncControl?: {
|
|
194
|
+
restart(linkedHub?: LinkedHub): Promise<{
|
|
195
|
+
syncing: boolean;
|
|
196
|
+
canvases: number;
|
|
197
|
+
reason?: string;
|
|
198
|
+
detail?: string;
|
|
199
|
+
}>;
|
|
200
|
+
};
|
|
185
201
|
}
|
|
186
202
|
|
|
187
203
|
function resolveRepoRoot(): string {
|
|
@@ -364,6 +380,12 @@ export function reloadConfig(ctx: Context): boolean {
|
|
|
364
380
|
// (syncTsx gating) drift out of step with the hub the socket is actually
|
|
365
381
|
// attached to — and a poisoned config must never re-point sync without a
|
|
366
382
|
// restart (DDR-149 fan-out review).
|
|
383
|
+
//
|
|
384
|
+
// This is a rule about the FILE, and it stays. The one path that may change
|
|
385
|
+
// the live link is `adoptLinkedHub` below: a person pressing Connect in
|
|
386
|
+
// trusted app chrome, whose hub credential this same process just stored.
|
|
387
|
+
// That path cycles the runtime itself (sync/supervisor.ts) instead of hoping
|
|
388
|
+
// a watcher notices.
|
|
367
389
|
if (JSON.stringify(next.linkedHub) !== JSON.stringify(ctx.cfg.linkedHub)) {
|
|
368
390
|
console.warn(' warn: linkedHub changed — not hot-reloadable, restart the server to apply.');
|
|
369
391
|
if (ctx.cfg.linkedHub === undefined) delete next.linkedHub;
|
|
@@ -389,3 +411,22 @@ export function reloadConfig(ctx: Context): boolean {
|
|
|
389
411
|
ctx.cfg.canvasGroups.find((g) => /system/i.test(g.path))?.path ?? 'system';
|
|
390
412
|
return true;
|
|
391
413
|
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Adopt a link the person just authorized — the ONE way `cfg.linkedHub` may
|
|
417
|
+
* change on a running server.
|
|
418
|
+
*
|
|
419
|
+
* The value is passed BY VALUE from the attach lane (cloud/endpoints.ts), which
|
|
420
|
+
* exchanged it against the configured cloud address and stored the matching hub
|
|
421
|
+
* credential moments earlier. It is deliberately NOT re-read from
|
|
422
|
+
* `.design/config.json`: that file is committed, shared, and writable by
|
|
423
|
+
* anything else on the machine, and re-reading it here would hand the
|
|
424
|
+
* file-watcher's refused capability (reloadConfig above) back through a side
|
|
425
|
+
* door. What the person consented to is what gets applied.
|
|
426
|
+
*
|
|
427
|
+
* Callers must cycle the sync runtime afterwards — `createSyncSupervisor`
|
|
428
|
+
* (sync/supervisor.ts) does both in one step, and is the only intended caller.
|
|
429
|
+
*/
|
|
430
|
+
export function adoptLinkedHub(ctx: Context, linkedHub: LinkedHub): void {
|
|
431
|
+
ctx.cfg.linkedHub = linkedHub;
|
|
432
|
+
}
|