yarramate 0.18.0 → 0.19.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 (65) hide show
  1. package/README.md +2 -1
  2. package/catalogues/core-enrichment.yaml +52 -1
  3. package/dist/adapters/graphify-cli.js +0 -0
  4. package/dist/adapters/likec4-cli.js +0 -0
  5. package/dist/adapters/mcp-cli.js +0 -0
  6. package/dist/adapters/visual/client.js +0 -1
  7. package/dist/adapters/visual/protocol-contract.d.ts +118 -35
  8. package/dist/adapters/visual/protocol-contract.js +1 -11
  9. package/dist/adapters/visual/protocol.d.ts +8 -1
  10. package/dist/adapters/visual/protocol.js +86 -46
  11. package/dist/adapters/visual/request.d.ts +37 -0
  12. package/dist/adapters/visual/request.js +131 -0
  13. package/dist/adapters/visual/session-server.d.ts +12 -14
  14. package/dist/adapters/visual/session-server.js +641 -283
  15. package/dist/adapters/visual/session-store.d.ts +28 -28
  16. package/dist/adapters/visual/session-store.js +32 -93
  17. package/dist/adapters/visual/wire.d.ts +36 -10
  18. package/dist/adapters/visual-cli.d.ts +3 -0
  19. package/dist/adapters/visual-cli.js +66 -1
  20. package/dist/apply-command.d.ts +10 -0
  21. package/dist/apply-command.js +399 -247
  22. package/dist/cli.js +0 -0
  23. package/dist/compiler.d.ts +13 -0
  24. package/dist/compiler.js +36 -2
  25. package/dist/graph-projection.d.ts +62 -0
  26. package/dist/graph-projection.js +222 -0
  27. package/dist/interrogate-command.d.ts +2 -0
  28. package/dist/interrogate-command.js +22 -0
  29. package/dist/kind-label.d.ts +8 -0
  30. package/dist/kind-label.js +8 -0
  31. package/dist/operations.d.ts +139 -0
  32. package/dist/operations.js +11 -0
  33. package/dist/projection.d.ts +2 -0
  34. package/dist/projection.js +8 -2
  35. package/dist/reconciliation.js +8 -7
  36. package/dist/source-document.d.ts +10 -0
  37. package/dist/source-document.js +21 -1
  38. package/dist/visual-app/assets/index-6oSR9RGM.js +354 -0
  39. package/dist/visual-app/assets/index-dwA9AOr5.css +1 -0
  40. package/dist/visual-app/index.html +2 -2
  41. package/docs/CONSUMING-YARRAMATE.md +25 -19
  42. package/package.json +6 -1
  43. package/schema/yarramate-adapter-mapping.schema.json +1 -1
  44. package/schema/yarramate-apply-result.schema.json +8 -2
  45. package/schema/yarramate-document.schema.json +1 -1
  46. package/schema/yarramate-operations.schema.json +203 -1
  47. package/schema/yarramate-projection.schema.json +12 -3
  48. package/schema/yarramate-question-catalogue.schema.json +15 -2
  49. package/schema/yarramate-visual-event.schema.json +143 -0
  50. package/schema/yarramate-visual-graph.schema.json +272 -0
  51. package/schema/yarramate-visual-layout.schema.json +41 -0
  52. package/schema/yarramate-visual-model.schema.json +6 -44
  53. package/schema/yarramate-visual-response.schema.json +170 -19
  54. package/schema/yarramate-visual-session-descriptor.schema.json +1 -1
  55. package/schema/yarramate-visual-session-request.schema.json +0 -70
  56. package/schema/yarramate-visual-session-started.schema.json +2 -9
  57. package/schema/yarramate-visual-status.schema.json +3 -2
  58. package/skills/yarramate-architecture/SKILL.md +20 -13
  59. package/skills/yarramate-architecture/references/journey-checklists.md +4 -2
  60. package/skills/yarramate-architecture/references/native-authoring.md +30 -0
  61. package/skills/yarramate-architecture/references/visual-conversations.md +149 -150
  62. package/dist/adapters/visual/likec4-compiler.d.ts +0 -55
  63. package/dist/adapters/visual/likec4-compiler.js +0 -460
  64. package/dist/visual-app/assets/index-CGvC8e3R.css +0 -1
  65. package/dist/visual-app/assets/index-CXuW1tOj.js +0 -209
@@ -1,6 +1,7 @@
1
- import { type VisualAuthority, type VisualDiagnostic, type VisualEvent, type VisualFreezeReason, type VisualHandoff, type VisualModel, type VisualResponse, type VisualSessionDescriptor, type VisualSessionRequest, type VisualTerminationReason } from './protocol.js';
1
+ import { type VisualAuthority, type VisualDiagnostic, type VisualEvent, type VisualFreezeReason, type VisualHandoff, type VisualResponse, type VisualSessionDescriptor, type VisualSessionRequest, type VisualTerminationReason } from './protocol.js';
2
+ import { type WorkspaceSource } from '../../compiler.js';
3
+ import { type CanvasGraph } from '../../graph-projection.js';
2
4
  export declare const VISUAL_SESSION_MARKER_FORMAT: 'yarramate/visual-session-marker/v1';
3
- export declare const VISUAL_ACTIVE_MODEL_FORMAT: 'yarramate/visual-active-model/v1';
4
5
  /**
5
6
  * Upper bound on how many orphaned sessions one `start` prunes. Cleanup runs on
6
7
  * the critical path of a new session, so a directory full of orphans must cost
@@ -19,8 +20,6 @@ export interface VisualSessionPaths {
19
20
  readonly marker: string;
20
21
  readonly descriptor: string;
21
22
  readonly journal: string;
22
- readonly candidates: string;
23
- readonly activeModel: string;
24
23
  }
25
24
  export interface SessionDependencies {
26
25
  readonly baseDir: string;
@@ -45,17 +44,6 @@ export interface VisualSessionMarker {
45
44
  readonly createdAt: string;
46
45
  readonly authority: VisualAuthority;
47
46
  }
48
- /**
49
- * Last-good rendering pointer. It names a staged candidate directory instead of
50
- * embedding the model so the swap is a single rename of a small file.
51
- */
52
- export interface VisualActiveModel {
53
- readonly format: typeof VISUAL_ACTIVE_MODEL_FORMAT;
54
- readonly candidate: string;
55
- readonly authority: VisualAuthority;
56
- readonly initialView: string;
57
- readonly promotedAt: string;
58
- }
59
47
  export interface VisualSessionCreated {
60
48
  readonly paths: VisualSessionPaths;
61
49
  readonly browserToken: string;
@@ -73,17 +61,6 @@ export interface VisualAppendRejected {
73
61
  readonly diagnostics: readonly VisualDiagnostic[];
74
62
  }
75
63
  export type VisualAppendResult = VisualAppendAccepted | VisualAppendRejected;
76
- export interface ModelPromotion {
77
- readonly model: VisualModel;
78
- readonly now: () => Date;
79
- readonly compile: (candidateDir: string) => Promise<readonly VisualDiagnostic[]>;
80
- }
81
- export interface ModelPromotionResult {
82
- readonly promoted: boolean;
83
- readonly candidate: string;
84
- readonly candidateDir: string;
85
- readonly diagnostics: readonly VisualDiagnostic[];
86
- }
87
64
  export declare const visualSessionPaths: (root: string) => VisualSessionPaths;
88
65
  export declare const createVisualSession: (request: VisualSessionRequest, deps: SessionDependencies) => Promise<VisualSessionCreated>;
89
66
  /**
@@ -105,8 +82,31 @@ export declare const appendVisualResponse: (paths: VisualSessionPaths, response:
105
82
  export declare const appendTerminalEvent: (paths: VisualSessionPaths, reason: VisualTerminationReason, deps: TerminalEventDependencies) => Promise<VisualEvent>;
106
83
  export declare const readActionableEventsAfter: (paths: VisualSessionPaths, sequence: number) => Promise<readonly VisualEvent[]>;
107
84
  export declare const recoverVisualSession: (paths: VisualSessionPaths, includeTranscript?: boolean) => Promise<VisualHandoff>;
108
- export declare const readActiveVisualModel: (paths: VisualSessionPaths) => Promise<VisualActiveModel | undefined>;
109
- export declare const promoteCompiledModel: (paths: VisualSessionPaths, promotion: ModelPromotion) => Promise<ModelPromotionResult>;
85
+ export type VisualModelGraphResult = {
86
+ readonly ok: true;
87
+ readonly graph: CanvasGraph;
88
+ } | {
89
+ readonly ok: false;
90
+ readonly diagnostics: readonly VisualDiagnostic[];
91
+ };
92
+ /**
93
+ * Builds the `graph` a session's `VisualModel` renders, from the workspace's
94
+ * own source documents. This is the whole construction step a visual session
95
+ * needs at start: no subprocess, no staged candidate directory, no on-disk
96
+ * pointer file — `compileWorkspaceWithProfileContext` and
97
+ * `projectGraphForCanvas` are both pure, synchronous functions over this
98
+ * repo's native compiler, so the caller assembles the result directly into
99
+ * the full `VisualModel` (format, authority, initialView, sourceDigests,
100
+ * graph) without any session-store I/O.
101
+ *
102
+ * A compile failure here is not a runtime defect — it is exactly the
103
+ * diagnostic-shaped rejection the browser already expects, so the workspace
104
+ * compiler's own diagnostics are returned unchanged rather than resynthesised.
105
+ * `Diagnostic` (compiler.ts) and `VisualDiagnostic` (protocol-contract.ts)
106
+ * already share one shape: severity, code, message, path, pointer, line,
107
+ * column.
108
+ */
109
+ export declare const buildVisualModelGraph: (sources: readonly WorkspaceSource[]) => VisualModelGraphResult;
110
110
  /**
111
111
  * Recovers the handoff and only then deletes the session, so cleanup can never
112
112
  * be the step that loses confirmed state. Returns `undefined` when the session
@@ -1,16 +1,16 @@
1
1
  import { constants } from 'node:fs';
2
2
  import { lstat, mkdir, open, readFile, readdir, rename, rm, stat, truncate, writeFile, } from 'node:fs/promises';
3
- import { basename, dirname, join, resolve, sep } from 'node:path';
4
- import { VISUAL_LIMITS, parseVisualEvent, parseVisualHandoff, parseVisualModel, parseVisualResponse, parseVisualSessionDescriptor, parseVisualSessionRequest, } from './protocol.js';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
+ import { VISUAL_LIMITS, parseVisualEvent, parseVisualHandoff, parseVisualResponse, parseVisualSessionDescriptor, parseVisualSessionRequest, } from './protocol.js';
5
+ import { compileWorkspaceWithProfileContext, } from '../../compiler.js';
6
+ import { projectGraphForCanvas } from '../../graph-projection.js';
5
7
  export const VISUAL_SESSION_MARKER_FORMAT = 'yarramate/visual-session-marker/v1';
6
- export const VISUAL_ACTIVE_MODEL_FORMAT = 'yarramate/visual-active-model/v1';
7
8
  /**
8
9
  * Upper bound on how many orphaned sessions one `start` prunes. Cleanup runs on
9
10
  * the critical path of a new session, so a directory full of orphans must cost
10
11
  * a bounded amount of work; the remainder is collected by the next start.
11
12
  */
12
13
  export const VISUAL_SESSION_PRUNE_LIMIT = 64;
13
- const CANDIDATE_NAME = /^[0-9]{6}$/;
14
14
  const IDENTIFIER = /^[0-9a-f]{32}$/;
15
15
  const TIMESTAMP = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$/;
16
16
  const NEWLINE = 0x0a;
@@ -58,7 +58,6 @@ const serialize = (key, action) => {
58
58
  const forget = (paths) => {
59
59
  states.delete(paths.journal);
60
60
  queues.delete(paths.journal);
61
- queues.delete(paths.candidates);
62
61
  };
63
62
  const drawHex = (randomBytes, size) => {
64
63
  const drawn = randomBytes(size);
@@ -113,8 +112,6 @@ export const visualSessionPaths = (root) => {
113
112
  marker: join(base, 'session.json'),
114
113
  descriptor: join(base, 'descriptor.json'),
115
114
  journal: join(base, 'journal.jsonl'),
116
- candidates: join(base, 'candidates'),
117
- activeModel: join(base, 'active-model.json'),
118
115
  };
119
116
  };
120
117
  const readSessionMarker = async (paths) => {
@@ -141,7 +138,7 @@ const readSessionMarker = async (paths) => {
141
138
  !IDENTIFIER.test(id) ||
142
139
  typeof createdAt !== 'string' ||
143
140
  !TIMESTAMP.test(createdAt) ||
144
- (authority !== 'canonical' && authority !== 'ad-hoc')) {
141
+ authority !== 'canonical') {
145
142
  throw storeError('YMVS124', `Session marker "${paths.marker}" is not ${VISUAL_SESSION_MARKER_FORMAT}`);
146
143
  }
147
144
  // A marker that names another directory cannot authorise destructive work on
@@ -277,7 +274,6 @@ export const createVisualSession = async (request, deps) => {
277
274
  createdAt: deps.now().toISOString(),
278
275
  authority: validated.value.authority,
279
276
  });
280
- await mkdir(paths.candidates, { recursive: false, mode: 0o700 });
281
277
  await writeFile(paths.journal, '', { mode: 0o600 });
282
278
  await syncDirectory(root);
283
279
  return {
@@ -559,87 +555,33 @@ export const recoverVisualSession = async (paths, includeTranscript = false) =>
559
555
  }
560
556
  return handoff;
561
557
  };
562
- export const readActiveVisualModel = async (paths) => {
563
- let raw = '';
564
- try {
565
- raw = await readFile(paths.activeModel, 'utf8');
566
- }
567
- catch {
568
- return undefined;
569
- }
570
- let parsed;
571
- try {
572
- parsed = JSON.parse(raw);
573
- }
574
- catch {
575
- throw storeError('YMVS128', `Active model pointer "${paths.activeModel}" is not JSON`);
576
- }
577
- const fields = documentFields(parsed);
578
- const candidate = fields.candidate;
579
- const authority = fields.authority;
580
- const initialView = fields.initialView;
581
- const promotedAt = fields.promotedAt;
582
- if (fields.format !== VISUAL_ACTIVE_MODEL_FORMAT ||
583
- typeof candidate !== 'string' ||
584
- !CANDIDATE_NAME.test(candidate) ||
585
- (authority !== 'canonical' && authority !== 'ad-hoc') ||
586
- typeof initialView !== 'string' ||
587
- typeof promotedAt !== 'string' ||
588
- !TIMESTAMP.test(promotedAt)) {
589
- throw storeError('YMVS128', `Active model pointer "${paths.activeModel}" is not ${VISUAL_ACTIVE_MODEL_FORMAT}`);
558
+ /**
559
+ * Builds the `graph` a session's `VisualModel` renders, from the workspace's
560
+ * own source documents. This is the whole construction step a visual session
561
+ * needs at start: no subprocess, no staged candidate directory, no on-disk
562
+ * pointer file — `compileWorkspaceWithProfileContext` and
563
+ * `projectGraphForCanvas` are both pure, synchronous functions over this
564
+ * repo's native compiler, so the caller assembles the result directly into
565
+ * the full `VisualModel` (format, authority, initialView, sourceDigests,
566
+ * graph) without any session-store I/O.
567
+ *
568
+ * A compile failure here is not a runtime defect — it is exactly the
569
+ * diagnostic-shaped rejection the browser already expects, so the workspace
570
+ * compiler's own diagnostics are returned unchanged rather than resynthesised.
571
+ * `Diagnostic` (compiler.ts) and `VisualDiagnostic` (protocol-contract.ts)
572
+ * already share one shape: severity, code, message, path, pointer, line,
573
+ * column.
574
+ */
575
+ export const buildVisualModelGraph = (sources) => {
576
+ const compiled = compileWorkspaceWithProfileContext(sources);
577
+ if (!compiled.ok) {
578
+ return { ok: false, diagnostics: compiled.diagnostics };
590
579
  }
591
580
  return {
592
- format: VISUAL_ACTIVE_MODEL_FORMAT,
593
- candidate,
594
- authority,
595
- initialView,
596
- promotedAt,
581
+ ok: true,
582
+ graph: projectGraphForCanvas(compiled.graph, compiled.profileContext),
597
583
  };
598
584
  };
599
- export const promoteCompiledModel = async (paths, promotion) => {
600
- // The runtime has already parsed the `model.replace` response that carried
601
- // this model, so a violation here is a runtime defect rather than untrusted
602
- // input, and must not be reported to the browser as a candidate diagnostic.
603
- const validated = parseVisualModel(promotion.model);
604
- if (!validated.ok) {
605
- const first = validated.diagnostics[0];
606
- throw storeError(first?.code ?? 'YMVS106', `Candidate model is invalid: ${first?.message ?? 'unknown violation'}`);
607
- }
608
- const model = validated.value;
609
- return serialize(paths.candidates, async () => {
610
- await readSessionMarker(paths);
611
- const existing = await readdir(paths.candidates);
612
- const highest = existing
613
- .filter((entry) => CANDIDATE_NAME.test(entry))
614
- .reduce((max, entry) => Math.max(max, Number(entry)), 0);
615
- const candidate = String(highest + 1).padStart(6, '0');
616
- const candidateDir = join(paths.candidates, candidate);
617
- await mkdir(candidateDir, { recursive: false, mode: 0o700 });
618
- for (const [file, contents] of Object.entries(model.files)) {
619
- const target = resolve(candidateDir, file);
620
- // Confinement is already enforced by the model schema and by the protocol
621
- // path check; re-checking the resolved path means neither of those alone
622
- // is load-bearing for a write outside the candidate directory.
623
- if (!target.startsWith(`${candidateDir}${sep}`)) {
624
- throw storeError('YMVS125', `Candidate file "${file}" resolves outside "${candidateDir}"`);
625
- }
626
- await mkdir(dirname(target), { recursive: true, mode: 0o700 });
627
- await writePrivateFile(target, contents);
628
- }
629
- const diagnostics = await promotion.compile(candidateDir);
630
- if (diagnostics.length > 0) {
631
- return { promoted: false, candidate, candidateDir, diagnostics };
632
- }
633
- await writePrivateJson(paths.activeModel, {
634
- format: VISUAL_ACTIVE_MODEL_FORMAT,
635
- candidate,
636
- authority: model.authority,
637
- initialView: model.initialView,
638
- promotedAt: promotion.now().toISOString(),
639
- });
640
- return { promoted: true, candidate, candidateDir, diagnostics: [] };
641
- });
642
- };
643
585
  /**
644
586
  * Recovers the handoff and only then deletes the session, so cleanup can never
645
587
  * be the step that loses confirmed state. Returns `undefined` when the session
@@ -668,11 +610,10 @@ export const removeVisualSession = async (paths, includeTranscript = false) => {
668
610
  * none of them could be measured.
669
611
  *
670
612
  * POSIX updates a directory's modification time only when its own entries
671
- * change, so the session root records nothing about an appended journal, and
672
- * the candidates directory records nothing about a promoted pointer. Every
673
- * artefact that can carry activity is therefore measured directly; the
674
- * optional ones are simply absent until a session produces them. `lstat`
675
- * keeps a symlinked artefact from reporting some other file's activity.
613
+ * change, so the session root records nothing about an appended journal.
614
+ * Every artefact that can carry activity is therefore measured directly;
615
+ * `lstat` keeps a symlinked artefact from reporting some other file's
616
+ * activity.
676
617
  */
677
618
  const lastActivityMs = async (paths) => {
678
619
  let newest;
@@ -681,8 +622,6 @@ const lastActivityMs = async (paths) => {
681
622
  paths.marker,
682
623
  paths.descriptor,
683
624
  paths.journal,
684
- paths.candidates,
685
- paths.activeModel,
686
625
  ]) {
687
626
  let entry;
688
627
  try {
@@ -1,4 +1,5 @@
1
- import type { VISUAL_PROTOCOL_VERSION, VisualAuthority, VisualCapabilities, VisualChoicePresentPayload, VisualDiagnostic, VisualFreezeReason, VisualResponse, VisualTerminationReason } from './protocol-contract.js';
1
+ import type { CanvasGraph } from '../../graph-projection.js';
2
+ import type { VISUAL_PROTOCOL_VERSION, VisualApplyResultPayload, VisualAuthority, VisualBrowserInput, VisualCapabilities, VisualChoicePresentPayload, VisualDiagnostic, VisualFilterResultPayload, VisualFreezeReason, VisualKindOption, VisualLayoutPositions, VisualLayoutSaveResultPayload, VisualResponse, VisualTerminationReason, VisualViewSaveResultPayload, VisualViewSummary } from './protocol-contract.js';
2
3
  /**
3
4
  * Transport shapes the session server and the browser application both speak.
4
5
  *
@@ -9,18 +10,21 @@ import type { VISUAL_PROTOCOL_VERSION, VisualAuthority, VisualCapabilities, Visu
9
10
  * that serves it — this module holds types only, and every dependency it has on
10
11
  * the protocol is type-only too.
11
12
  */
12
- /** One promoted candidate as the browser receives it. */
13
+ /** The resolved graph a session renders, as the browser receives it. */
13
14
  export interface VisualRenderedModel {
14
- readonly candidate: string;
15
15
  readonly authority: VisualAuthority;
16
16
  readonly initialView: string;
17
- readonly views: readonly string[];
18
- /**
19
- * The trusted compiler's own LikeC4 export for this candidate, the document
20
- * `createLikeC4Model` consumes. Its internals belong to LikeC4, so the
21
- * transport carries it verbatim instead of restating a shape it does not own.
22
- */
23
- readonly compiled: unknown;
17
+ readonly graph: CanvasGraph;
18
+ /** Manifest-relative document paths — the add-concept target dropdown. */
19
+ readonly documents: readonly string[];
20
+ readonly vocabulary: {
21
+ readonly conceptKinds: readonly VisualKindOption[];
22
+ readonly relationshipKinds: readonly VisualKindOption[];
23
+ };
24
+ /** Every saved layout sidecar, keyed by projection id (Plan-level decision 1). */
25
+ readonly layouts: {
26
+ readonly [projectionId: string]: VisualLayoutPositions;
27
+ };
24
28
  }
25
29
  /**
26
30
  * One line of the conversation, as plain text.
@@ -47,6 +51,8 @@ export interface VisualSessionSnapshot {
47
51
  readonly webSocketUrl: string;
48
52
  readonly model: VisualRenderedModel;
49
53
  readonly transcript: readonly VisualTranscriptRecord[];
54
+ /** The saved views the reviewer can switch this session's diagram to. */
55
+ readonly views: readonly VisualViewSummary[];
50
56
  /**
51
57
  * Whether the agent still owes an answer to something the reviewer sent. A
52
58
  * browser that reconnects mid-turn reads this rather than inferring the turn
@@ -79,6 +85,14 @@ export type VisualServerFrame = {
79
85
  readonly eventId: string;
80
86
  } | {
81
87
  readonly kind: 'rejected';
88
+ /**
89
+ * The input this refusal is about, as the frame that carried it named
90
+ * itself. A browser with a commit or a save in flight can only retire
91
+ * the right control if the refusal says which one died, and a refusal
92
+ * that never got as far as reading a type - a binary frame, a document
93
+ * that is not JSON - names nothing rather than guessing.
94
+ */
95
+ readonly refused?: VisualBrowserInput['type'];
82
96
  readonly diagnostics: readonly VisualDiagnostic[];
83
97
  readonly frozen?: VisualFreezeReason;
84
98
  } | {
@@ -87,6 +101,18 @@ export type VisualServerFrame = {
87
101
  } | {
88
102
  readonly kind: 'model';
89
103
  readonly model: VisualRenderedModel;
104
+ } | {
105
+ readonly kind: 'filter-result';
106
+ readonly result: VisualFilterResultPayload;
107
+ } | {
108
+ readonly kind: 'view-save-result';
109
+ readonly result: VisualViewSaveResultPayload;
110
+ } | {
111
+ readonly kind: 'apply-result';
112
+ readonly result: VisualApplyResultPayload;
113
+ } | {
114
+ readonly kind: 'layout-save-result';
115
+ readonly result: VisualLayoutSaveResultPayload;
90
116
  } | {
91
117
  readonly kind: 'closing';
92
118
  readonly reason: VisualTerminationReason;
@@ -10,6 +10,9 @@ export declare const VISUAL_SESSION_DIRECTORY: string;
10
10
  /**
11
11
  * Every command an agent can run to completion. `start` is not one of them: it
12
12
  * blocks for the life of the session and is dispatched by `runVisualCli`.
13
+ *
14
+ * `request` is the one verb that takes no descriptor - it runs before any
15
+ * session exists - so it is dispatched ahead of the descriptor requirement.
13
16
  */
14
17
  export declare function runVisualClientCli(args: readonly string[], cwd?: string): Promise<CliResult>;
15
18
  /** Where a foreground session publishes, and where it observes its signals. */
@@ -3,9 +3,12 @@ import { join } from 'node:path';
3
3
  import { isMainModule, versionResult } from '../cli-support.js';
4
4
  import { fetchVisualStatus, readVisualJsonDocument, readVisualSessionDescriptor, recoverVisualSessionClient, sendVisualResponse, stopVisualSessionClient, visualClientDiagnostic, visualFailureDiagnostics, visualSessionAlreadyStopped, waitForVisualEvent, } from './visual/client.js';
5
5
  import { parseVisualResponse, parseVisualSessionRequest, } from './visual/protocol.js';
6
+ import { buildVisualSessionRequest } from './visual/request.js';
6
7
  import { startVisualServer } from './visual/session-server.js';
7
8
  import { pruneStaleVisualSessions } from './visual/session-store.js';
8
9
  export const visualUsage = 'Usage:\n' +
10
+ ' yarramate-visual request [--view <id>] [--title <text>]\n' +
11
+ ' [--description <text>] [--chat]\n' +
9
12
  ' yarramate-visual start <request.json>\n' +
10
13
  ' yarramate-visual wait <descriptor.json> [--after <sequence>]\n' +
11
14
  ' yarramate-visual respond <descriptor.json> <response.json>\n' +
@@ -66,6 +69,63 @@ const transcriptFlag = (rest) => rest.length === 0
66
69
  : rest.length === 1 && rest[0] === '--transcript'
67
70
  ? true
68
71
  : undefined;
72
+ /**
73
+ * Strict because the document this builds is a session's whole input: a
74
+ * misspelled flag that was silently ignored would open a session on the wrong
75
+ * view, or with chat off when the caller asked for it. A repeated flag is a
76
+ * caller that means two things at once, and a value beginning with `--` is a
77
+ * missing value rather than a title, so both refuse with usage.
78
+ */
79
+ const requestFlags = (rest) => {
80
+ let view;
81
+ let title;
82
+ let description;
83
+ let chatEnabled = false;
84
+ for (let index = 0; index < rest.length; index += 1) {
85
+ const flag = rest[index];
86
+ if (flag === '--chat') {
87
+ if (chatEnabled)
88
+ return undefined;
89
+ chatEnabled = true;
90
+ continue;
91
+ }
92
+ const value = rest[index + 1];
93
+ if (value === undefined || value.startsWith('--'))
94
+ return undefined;
95
+ index += 1;
96
+ if (flag === '--view' && view === undefined)
97
+ view = value;
98
+ else if (flag === '--title' && title === undefined)
99
+ title = value;
100
+ else if (flag === '--description' && description === undefined) {
101
+ description = value;
102
+ }
103
+ else
104
+ return undefined;
105
+ }
106
+ return { view, title, description, chatEnabled };
107
+ };
108
+ /**
109
+ * Transcribes the workspace into the request document `start` consumes. It is
110
+ * the only visual command that reads the repository rather than a session:
111
+ * every other one addresses a descriptor, and this one runs before a session
112
+ * exists.
113
+ */
114
+ const requestCliCommand = (rest, cwd) => {
115
+ const flags = requestFlags(rest);
116
+ if (flags === undefined)
117
+ return usageResult;
118
+ const built = buildVisualSessionRequest({
119
+ cwd,
120
+ initialView: flags.view,
121
+ title: flags.title,
122
+ description: flags.description,
123
+ chatEnabled: flags.chatEnabled,
124
+ });
125
+ if (!built.ok)
126
+ return refusalResult(built.diagnostics);
127
+ return { exitCode: 0, stdout: documentLine(built.request), stderr: '' };
128
+ };
69
129
  const waitCliCommand = async (descriptorPath, rest, cwd) => {
70
130
  const after = sequenceFlag(rest);
71
131
  if (after === undefined)
@@ -164,11 +224,16 @@ const stopCliCommand = async (descriptorPath, rest, cwd) => {
164
224
  /**
165
225
  * Every command an agent can run to completion. `start` is not one of them: it
166
226
  * blocks for the life of the session and is dispatched by `runVisualCli`.
227
+ *
228
+ * `request` is the one verb that takes no descriptor - it runs before any
229
+ * session exists - so it is dispatched ahead of the descriptor requirement.
167
230
  */
168
231
  export async function runVisualClientCli(args, cwd = process.cwd()) {
169
232
  const [command, descriptor, ...rest] = args;
170
233
  if (command === '--version')
171
234
  return versionResult('yarramate-visual');
235
+ if (command === 'request')
236
+ return requestCliCommand(args.slice(1), cwd);
172
237
  if (descriptor === undefined)
173
238
  return usageResult;
174
239
  switch (command) {
@@ -215,7 +280,7 @@ export const runVisualStart = async (requestPath, cwd = process.cwd(), io = proc
215
280
  // A previous runtime's orphans are collected on the critical path of a new
216
281
  // session, bounded per pass so a full directory cannot stall this one.
217
282
  await pruneStaleVisualSessions(baseDir, new Date());
218
- handle = await startVisualServer({ request: request.value, baseDir });
283
+ handle = await startVisualServer({ request: request.value, baseDir, cwd });
219
284
  }
220
285
  catch (cause) {
221
286
  // A failed start has already recovered and removed whatever it created.
@@ -1,2 +1,12 @@
1
1
  import { type CliResult } from './cli-support.js';
2
+ import { type Diagnostic, type WorkspaceSource } from './compiler.js';
3
+ import type { YarramateApplyResult } from './operations.js';
4
+ export type ApplyOutcome = {
5
+ readonly ok: true;
6
+ readonly result: YarramateApplyResult;
7
+ } | {
8
+ readonly ok: false;
9
+ readonly diagnostics: readonly Diagnostic[];
10
+ };
11
+ export declare const applyOperations: (operations: WorkspaceSource, workspace: WorkspaceSource, cwd: string) => ApplyOutcome;
2
12
  export declare function runApplyCommand(options: readonly string[], cwd: string): CliResult;