open-research-protocol 0.4.14 → 0.4.15

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 (52) hide show
  1. package/AGENT_INTEGRATION.md +50 -0
  2. package/README.md +273 -144
  3. package/bin/orp.js +14 -1
  4. package/cli/orp.py +14846 -9925
  5. package/docs/AGENT_LOOP.md +13 -0
  6. package/docs/AGENT_MODES.md +79 -0
  7. package/docs/CANONICAL_CLI_BOUNDARY.md +15 -0
  8. package/docs/EXCHANGE.md +94 -0
  9. package/docs/LAUNCH_KIT.md +107 -0
  10. package/docs/ORP_HOSTED_WORKSPACE_CONTRACT.md +295 -0
  11. package/docs/ORP_PUBLIC_LAUNCH_CHECKLIST.md +5 -0
  12. package/docs/START_HERE.md +567 -0
  13. package/package.json +4 -2
  14. package/packages/lifeops-orp/README.md +67 -0
  15. package/packages/lifeops-orp/package.json +48 -0
  16. package/packages/lifeops-orp/src/index.d.ts +106 -0
  17. package/packages/lifeops-orp/src/index.js +7 -0
  18. package/packages/lifeops-orp/src/mapping.js +309 -0
  19. package/packages/lifeops-orp/src/workspace.js +108 -0
  20. package/packages/lifeops-orp/test/orp.test.js +187 -0
  21. package/packages/orp-workspace-launcher/README.md +82 -0
  22. package/packages/orp-workspace-launcher/package.json +39 -0
  23. package/packages/orp-workspace-launcher/src/commands.js +77 -0
  24. package/packages/orp-workspace-launcher/src/core-plan.js +506 -0
  25. package/packages/orp-workspace-launcher/src/hosted-state.js +208 -0
  26. package/packages/orp-workspace-launcher/src/index.js +82 -0
  27. package/packages/orp-workspace-launcher/src/ledger.js +745 -0
  28. package/packages/orp-workspace-launcher/src/list.js +488 -0
  29. package/packages/orp-workspace-launcher/src/orp-command.js +126 -0
  30. package/packages/orp-workspace-launcher/src/orp.js +912 -0
  31. package/packages/orp-workspace-launcher/src/registry.js +558 -0
  32. package/packages/orp-workspace-launcher/src/slot.js +188 -0
  33. package/packages/orp-workspace-launcher/src/sync.js +363 -0
  34. package/packages/orp-workspace-launcher/src/tabs.js +166 -0
  35. package/packages/orp-workspace-launcher/test/commands.test.js +164 -0
  36. package/packages/orp-workspace-launcher/test/core-plan.test.js +253 -0
  37. package/packages/orp-workspace-launcher/test/fixtures/smoke-notes.txt +2 -0
  38. package/packages/orp-workspace-launcher/test/fixtures/workspace-manifest.json +17 -0
  39. package/packages/orp-workspace-launcher/test/ledger.test.js +244 -0
  40. package/packages/orp-workspace-launcher/test/list.test.js +299 -0
  41. package/packages/orp-workspace-launcher/test/orp-command.test.js +44 -0
  42. package/packages/orp-workspace-launcher/test/orp.test.js +224 -0
  43. package/packages/orp-workspace-launcher/test/tabs.test.js +168 -0
  44. package/scripts/orp-kernel-agent-pilot.py +10 -1
  45. package/scripts/orp-kernel-agent-replication.py +10 -1
  46. package/scripts/orp-kernel-canonical-continuation.py +10 -1
  47. package/scripts/orp-kernel-continuation-pilot.py +10 -1
  48. package/scripts/render-terminal-demo.py +416 -0
  49. package/spec/v1/exchange-report.schema.json +105 -0
  50. package/spec/v1/hosted-workspace-event.schema.json +102 -0
  51. package/spec/v1/hosted-workspace.schema.json +332 -0
  52. package/spec/v1/workspace.schema.json +108 -0
@@ -0,0 +1,416 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import math
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ from PIL import Image, ImageDraw, ImageFont
9
+
10
+
11
+ ROOT = Path(__file__).resolve().parents[1]
12
+ ASSETS = ROOT / "assets"
13
+ GIF_PATH = ASSETS / "terminal-demo.gif"
14
+ POSTER_PATH = ASSETS / "terminal-demo-poster.png"
15
+ STORYBOARD_PATH = ASSETS / "terminal-demo-storyboard.png"
16
+
17
+ WIDTH = 1360
18
+ HEIGHT = 840
19
+ SIDE_MARGIN = 72
20
+ SHELL_TOP = 132
21
+ BOTTOM_MARGIN = 72
22
+ WINDOW_PADDING = 28
23
+
24
+ OUTER_BG = "#07131F"
25
+ WINDOW_BG = "#0A1728"
26
+ WINDOW_EDGE = "#16314D"
27
+ TITLEBAR_BG = "#10243A"
28
+ TERMINAL_BG = "#0C1B2E"
29
+ INK = "#E7F4F1"
30
+ MUTED = "#8FA7BF"
31
+ DIM = "#6C819A"
32
+ ACCENT = "#6EE7D8"
33
+ SKY = "#8DBEFF"
34
+ SOFT = "#F1D6B8"
35
+ CORAL = "#FF9A86"
36
+ LIME = "#BDEB9B"
37
+ GOLD = "#F6D98D"
38
+
39
+ FONT_MONO = "/System/Library/Fonts/Menlo.ttc"
40
+ FONT_SANS_BOLD = "/System/Library/Fonts/Supplemental/Arial Bold.ttf"
41
+
42
+
43
+ def load_font(path: str, size: int) -> ImageFont.ImageFont:
44
+ try:
45
+ return ImageFont.truetype(path, size)
46
+ except OSError:
47
+ return ImageFont.load_default()
48
+
49
+
50
+ MONO_18 = load_font(FONT_MONO, 18)
51
+ MONO_20 = load_font(FONT_MONO, 20)
52
+ MONO_24 = load_font(FONT_MONO, 24)
53
+ MONO_28 = load_font(FONT_MONO, 28)
54
+ MONO_32 = load_font(FONT_MONO, 32)
55
+ SANS_22 = load_font(FONT_SANS_BOLD, 22)
56
+ SANS_26 = load_font(FONT_SANS_BOLD, 26)
57
+ SANS_34 = load_font(FONT_SANS_BOLD, 34)
58
+ SANS_52 = load_font(FONT_SANS_BOLD, 52)
59
+
60
+ CONTENT_X = SIDE_MARGIN + WINDOW_PADDING + 46
61
+ CONTENT_Y = SHELL_TOP + 138
62
+ CONTENT_WIDTH = (WIDTH - SIDE_MARGIN - WINDOW_PADDING) - CONTENT_X - 20
63
+ LINE_HEIGHT = 34
64
+ TYPING_MS = 75
65
+ COMMAND_SETTLE_MS = 320
66
+ OUTPUT_STEP_MS = 240
67
+ SCENE_HOLD_MS = 33000
68
+
69
+ SCENES = [
70
+ {
71
+ "id": "home",
72
+ "label": "home",
73
+ "headline": "discover the surface",
74
+ "command": "orp home",
75
+ "output_font": "small",
76
+ "line_height": 30,
77
+ "output": [
78
+ ("ORP 0.4.13", ACCENT),
79
+ ("Agent-first CLI for workspace ledgers, secrets, scheduling, and research workflows.", INK),
80
+ ("Repo", SKY),
81
+ (" root: /Volumes/Code_2TB/code/orp", INK),
82
+ (" config: orp.yml (missing)", ACCENT),
83
+ (" git: yes, branch=main, commit=4cde66c", SOFT),
84
+ ("Daily Loop", SKY),
85
+ (" orp workspace tabs main", ACCENT),
86
+ (' orp secrets add --alias <alias> --label "<label>" --provider <provider>', INK),
87
+ (' orp checkpoint create -m "capture loop state"', SOFT),
88
+ ],
89
+ },
90
+ {
91
+ "id": "hosted",
92
+ "label": "hosted",
93
+ "headline": "see the control plane",
94
+ "command": "orp workspaces list",
95
+ "output": [
96
+ ("workspaces.count=2", ACCENT),
97
+ ("cursor=", INK),
98
+ ("has_more=false", SKY),
99
+ ("source=idea_bridge", ACCENT),
100
+ ("---", DIM),
101
+ ("workspace.id=main-cody-1", INK),
102
+ ("workspace.title=main-cody-1", SKY),
103
+ ("workspace.tab_count=18", SOFT),
104
+ ],
105
+ },
106
+ {
107
+ "id": "secrets",
108
+ "label": "secrets",
109
+ "headline": "save the key once, reuse it later",
110
+ "command": 'orp secrets add --alias openai-primary --label "OpenAI Primary" --provider openai',
111
+ "output": [
112
+ ("Secret value:", ACCENT),
113
+ (" sk-...", INK),
114
+ ("secret.alias=openai-primary", SKY),
115
+ ("secret.provider=openai", ACCENT),
116
+ ("secret.kind=api_key", INK),
117
+ ("secret.status=active", SKY),
118
+ ("next: orp secrets resolve openai-primary --reveal", SOFT),
119
+ ],
120
+ },
121
+ {
122
+ "id": "workspace",
123
+ "label": "workspace",
124
+ "headline": "keep the workspace ledger",
125
+ "command": "orp workspace tabs main",
126
+ "output": [
127
+ ("saved tabs: 11", ACCENT),
128
+ ("ledger: hosted canonical + local cache", INK),
129
+ ("recovery: copy the exact cd && resume command you need", SKY),
130
+ ("tools: codex resume and claude --resume are both tracked", ACCENT),
131
+ ("tail tabs: orp · orp-web-app · RigidityCore · frg-site", SOFT),
132
+ ],
133
+ },
134
+ {
135
+ "id": "schedule",
136
+ "label": "schedule",
137
+ "headline": "automate the next loop",
138
+ "command": 'orp schedule add codex --name morning-summary --prompt "Summarize this repo"',
139
+ "output": [
140
+ ("ORP Scheduled Job Created", ACCENT),
141
+ ("Name: morning-summary", INK),
142
+ ("Kind: codex", SKY),
143
+ ("Schedule: daily at 09:00", ACCENT),
144
+ ("Prompt source: inline", INK),
145
+ ("Codex session id: none", SKY),
146
+ ("Next steps:", ACCENT),
147
+ (" orp schedule run morning-summary", SOFT),
148
+ ],
149
+ },
150
+ {
151
+ "id": "governance",
152
+ "label": "governance",
153
+ "headline": "checkpoint the repo safely",
154
+ "command": 'orp checkpoint create -m "capture loop state"',
155
+ "output": [
156
+ ("commit=7f3c2a1", ACCENT),
157
+ ("branch=work/release-hardening", INK),
158
+ ("message=checkpoint: capture loop state", SKY),
159
+ ("checkpoint_log=orp/checkpoints/CHECKPOINT_LOG.md", ACCENT),
160
+ ("git_runtime=orp/git/runtime.json", SOFT),
161
+ ],
162
+ },
163
+ {
164
+ "id": "planning",
165
+ "label": "planning",
166
+ "headline": "track the live point",
167
+ "command": "orp frontier state",
168
+ "output": [
169
+ ("program_id=sunflower-coda", ACCENT),
170
+ ("active_version=v10", INK),
171
+ ("active_milestone=v10.3", SKY),
172
+ ("active_phase=395", ACCENT),
173
+ ("band=verification", INK),
174
+ ("next_action=Execute Phase 395", SKY),
175
+ ("blocked_by=(none)", SOFT),
176
+ ],
177
+ },
178
+ {
179
+ "id": "synthesis",
180
+ "label": "synthesis",
181
+ "headline": "scan, synthesize, collaborate",
182
+ "command": "orp exchange repo synthesize /path/to/source",
183
+ "output": [
184
+ ("exchange_id=exc_20260331_001", ACCENT),
185
+ ("source.mode=local_path", INK),
186
+ ("source.local_path=/path/to/source", SKY),
187
+ ("source.git_present=true", ACCENT),
188
+ ("artifacts.exchange_json=orp/exchange/exc_20260331_001/EXCHANGE.json", INK),
189
+ ("artifacts.summary_md=orp/exchange/exc_20260331_001/SUMMARY.md", SKY),
190
+ ("artifacts.transfer_map_md=orp/exchange/exc_20260331_001/TRANSFER_MAP.md", SOFT),
191
+ ],
192
+ },
193
+ {
194
+ "id": "mode",
195
+ "label": "mode",
196
+ "headline": "change the lens",
197
+ "command": "orp mode nudge sleek-minimal-progressive",
198
+ "output": [
199
+ ("mode.id=sleek-minimal-progressive", ACCENT),
200
+ ("mode.label=Sleek Minimal Progressive", INK),
201
+ ("nudge.title=Subtractive Spark", SKY),
202
+ ("nudge.prompt=remove one thing before adding one surprising move", ACCENT),
203
+ ("nudge.twist=keep the architecture cleaner than the idea feels", INK),
204
+ ("nudge.release=drop the first obvious framing", SOFT),
205
+ ("nudge.micro_loop:", ACCENT),
206
+ ("- zoom out, rotate, re-enter deliberately", SKY),
207
+ ],
208
+ },
209
+ ]
210
+
211
+
212
+ def wrap_line(text: str, font: ImageFont.ImageFont, max_width: int) -> list[str]:
213
+ words = text.split(" ")
214
+ lines: list[str] = []
215
+ current = ""
216
+ for word in words:
217
+ trial = word if not current else f"{current} {word}"
218
+ trial_box = font.getbbox(trial)
219
+ trial_width = trial_box[2] - trial_box[0]
220
+ if trial_width <= max_width or not current:
221
+ current = trial
222
+ else:
223
+ lines.append(current)
224
+ current = word
225
+ if current:
226
+ lines.append(current)
227
+ return lines
228
+
229
+
230
+ def wrap_command(command: str, font: ImageFont.ImageFont, max_width: int) -> list[str]:
231
+ words = command.split(" ")
232
+ lines: list[str] = []
233
+ current = "$"
234
+ for word in words:
235
+ trial = f"{current} {word}".strip()
236
+ trial_box = font.getbbox(trial)
237
+ trial_width = trial_box[2] - trial_box[0]
238
+ if trial_width <= max_width or current == "$":
239
+ current = trial
240
+ else:
241
+ lines.append(current)
242
+ current = f" {word}"
243
+ if current:
244
+ lines.append(current)
245
+ return lines
246
+
247
+
248
+ def draw_background(draw: ImageDraw.ImageDraw) -> None:
249
+ draw.rectangle((0, 0, WIDTH, HEIGHT), fill=OUTER_BG)
250
+ orbit_colors = [ACCENT, SKY, SOFT, ACCENT, SKY]
251
+ orbit_points = [
252
+ (84, 70),
253
+ (WIDTH - 88, 66),
254
+ (106, HEIGHT - 118),
255
+ (232, HEIGHT - 176),
256
+ (WIDTH - 210, HEIGHT - 162),
257
+ (WIDTH - 116, HEIGHT - 110),
258
+ ]
259
+ for idx, (x, y) in enumerate(orbit_points):
260
+ r = 8 if idx % 2 == 0 else 6
261
+ color = orbit_colors[idx % len(orbit_colors)]
262
+ draw.ellipse((x - r, y - r, x + r, y + r), fill=color)
263
+ draw.line((106, HEIGHT - 118, 232, HEIGHT - 176), fill="#17324D", width=2)
264
+ draw.line((WIDTH - 210, HEIGHT - 162, WIDTH - 116, HEIGHT - 110), fill="#17324D", width=2)
265
+
266
+
267
+ def draw_terminal_shell(draw: ImageDraw.ImageDraw) -> tuple[int, int, int, int]:
268
+ left = SIDE_MARGIN
269
+ top = SHELL_TOP
270
+ right = WIDTH - SIDE_MARGIN
271
+ bottom = HEIGHT - BOTTOM_MARGIN
272
+ draw.rounded_rectangle((left, top, right, bottom), radius=36, fill=WINDOW_BG, outline=WINDOW_EDGE, width=2)
273
+ draw.rounded_rectangle(
274
+ (left + WINDOW_PADDING, top + WINDOW_PADDING, right - WINDOW_PADDING, bottom - WINDOW_PADDING),
275
+ radius=24,
276
+ fill=TERMINAL_BG,
277
+ outline="#1A3A5A",
278
+ width=2,
279
+ )
280
+ title_bottom = top + WINDOW_PADDING + 62
281
+ draw.rounded_rectangle(
282
+ (left + WINDOW_PADDING, top + WINDOW_PADDING, right - WINDOW_PADDING, title_bottom),
283
+ radius=24,
284
+ fill=TITLEBAR_BG,
285
+ )
286
+ draw.rectangle((left + WINDOW_PADDING, title_bottom - 24, right - WINDOW_PADDING, title_bottom), fill=TITLEBAR_BG)
287
+ for idx, color in enumerate((CORAL, GOLD, LIME)):
288
+ x = left + WINDOW_PADDING + 22 + (idx * 28)
289
+ y = top + WINDOW_PADDING + 20
290
+ draw.ellipse((x, y, x + 14, y + 14), fill=color)
291
+ draw.text((left + WINDOW_PADDING + 112, top + WINDOW_PADDING + 12), "open-research-protocol demo", font=SANS_22, fill=INK)
292
+ right_label = "agent-first research loop"
293
+ right_box = draw.textbbox((0, 0), right_label, font=MONO_18)
294
+ right_x = right - WINDOW_PADDING - 22 - (right_box[2] - right_box[0])
295
+ draw.text((right_x, top + WINDOW_PADDING + 18), right_label, font=MONO_18, fill=DIM)
296
+ return (left, top, right, bottom)
297
+
298
+
299
+ def draw_scene_header(draw: ImageDraw.ImageDraw, scene: dict) -> None:
300
+ label = scene["label"].upper()
301
+ label_box = draw.textbbox((0, 0), label, font=MONO_20)
302
+ label_x = (WIDTH - (label_box[2] - label_box[0])) // 2
303
+ draw.text((label_x, 18), label, font=MONO_20, fill=DIM)
304
+ headline = scene["headline"]
305
+ headline_box = draw.textbbox((0, 0), headline, font=SANS_52)
306
+ headline_x = (WIDTH - (headline_box[2] - headline_box[0])) // 2
307
+ draw.text((headline_x, 42), headline, font=SANS_52, fill=ACCENT)
308
+
309
+
310
+ def render_scene(scene: dict, typed_chars: Optional[int] = None, shown_lines: int = 0, cursor: bool = False) -> Image.Image:
311
+ image = Image.new("RGBA", (WIDTH, HEIGHT), OUTER_BG)
312
+ draw = ImageDraw.Draw(image)
313
+ draw_background(draw)
314
+ draw_scene_header(draw, scene)
315
+ draw_terminal_shell(draw)
316
+
317
+ command = scene["command"] if typed_chars is None else scene["command"][:typed_chars]
318
+ command_lines = wrap_command(command, MONO_32, CONTENT_WIDTH)
319
+ if cursor and command_lines:
320
+ command_lines[-1] = f"{command_lines[-1]}_"
321
+ command_y = CONTENT_Y + 14
322
+ for line in command_lines:
323
+ draw.text((CONTENT_X, command_y), line, font=MONO_32, fill=ACCENT)
324
+ command_y += 40
325
+
326
+ output_font = MONO_24 if scene.get("output_font") != "small" else MONO_20
327
+ line_height = int(scene.get("line_height", LINE_HEIGHT))
328
+ content_max_y = HEIGHT - BOTTOM_MARGIN - WINDOW_PADDING - 20
329
+ y = command_y + 38
330
+ for text, color in scene["output"][:shown_lines]:
331
+ if y > content_max_y:
332
+ break
333
+ for line in wrap_line(text, output_font, CONTENT_WIDTH):
334
+ if y > content_max_y:
335
+ break
336
+ draw.text((CONTENT_X, y), line, font=output_font, fill=color)
337
+ y += line_height
338
+
339
+ footer = "npm install -g open-research-protocol"
340
+ footer_box = draw.textbbox((0, 0), footer, font=MONO_24)
341
+ footer_width = (footer_box[2] - footer_box[0]) + 46
342
+ footer_x = (WIDTH - footer_width) // 2
343
+ footer_y = HEIGHT - 118
344
+ draw.rounded_rectangle((footer_x, footer_y, footer_x + footer_width, footer_y + 54), radius=15, fill=ACCENT)
345
+ draw.text((footer_x + 23, footer_y + 14), footer, font=MONO_24, fill=WINDOW_BG)
346
+ return image
347
+
348
+
349
+ def build_storyboard(final_frames: list[Image.Image]) -> Image.Image:
350
+ thumb_width = 560
351
+ thumb_height = int(thumb_width * HEIGHT / WIDTH)
352
+ cols = 3 if len(final_frames) > 6 else 2
353
+ rows = max(1, math.ceil(len(final_frames) / cols))
354
+ gutter = 24
355
+ board_width = thumb_width * cols + gutter * (cols + 1)
356
+ board_height = thumb_height * rows + gutter * (rows + 1) + 72
357
+ storyboard = Image.new("RGBA", (board_width, board_height), OUTER_BG)
358
+ draw = ImageDraw.Draw(storyboard)
359
+ draw.text((gutter, 20), "ORP terminal walkthrough storyboard", font=SANS_34, fill=INK)
360
+ for idx, frame in enumerate(final_frames):
361
+ thumb = frame.copy()
362
+ thumb.thumbnail((thumb_width, thumb_height))
363
+ row = idx // cols
364
+ col = idx % cols
365
+ x = gutter + col * (thumb_width + gutter)
366
+ y = 72 + gutter + row * (thumb_height + gutter)
367
+ storyboard.alpha_composite(thumb, (x, y))
368
+ return storyboard
369
+
370
+
371
+ def main() -> None:
372
+ ASSETS.mkdir(parents=True, exist_ok=True)
373
+ frames: list[Image.Image] = []
374
+ durations: list[int] = []
375
+ final_scene_frames: list[Image.Image] = []
376
+
377
+ for index, scene in enumerate(SCENES, start=1):
378
+ command = scene["command"]
379
+ step = 2 if len(command) < 28 else 4
380
+ for typed in range(1, len(command) + 1, step):
381
+ frames.append(render_scene(scene, typed_chars=typed, shown_lines=0, cursor=True))
382
+ durations.append(TYPING_MS)
383
+ frames.append(render_scene(scene, typed_chars=len(command), shown_lines=0, cursor=False))
384
+ durations.append(COMMAND_SETTLE_MS)
385
+ for shown_lines in range(1, len(scene["output"]) + 1):
386
+ frame = render_scene(scene, typed_chars=len(command), shown_lines=shown_lines, cursor=False)
387
+ frames.append(frame)
388
+ durations.append(OUTPUT_STEP_MS if shown_lines < len(scene["output"]) else SCENE_HOLD_MS)
389
+ if shown_lines == len(scene["output"]):
390
+ final_scene_frames.append(frame.copy())
391
+ scene_path = ASSETS / f"terminal-scene-{index:02d}-{scene['id']}.png"
392
+ frame.save(scene_path)
393
+
394
+ poster = final_scene_frames[2] if len(final_scene_frames) >= 3 else final_scene_frames[0]
395
+ poster.save(POSTER_PATH)
396
+ storyboard = build_storyboard(final_scene_frames)
397
+ storyboard.save(STORYBOARD_PATH)
398
+ frames[0].save(
399
+ GIF_PATH,
400
+ save_all=True,
401
+ append_images=frames[1:],
402
+ duration=durations,
403
+ loop=0,
404
+ optimize=True,
405
+ disposal=2,
406
+ )
407
+ print(f"WROTE {GIF_PATH}")
408
+ print(f"WROTE {POSTER_PATH}")
409
+ print(f"WROTE {STORYBOARD_PATH}")
410
+ for index, scene in enumerate(SCENES, start=1):
411
+ scene_name = f"terminal-scene-{index:02d}-{scene['id']}.png"
412
+ print(f"WROTE {ASSETS / scene_name}")
413
+
414
+
415
+ if __name__ == "__main__":
416
+ main()
@@ -0,0 +1,105 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openresearchprotocol.com/spec/v1/exchange-report.schema.json",
4
+ "title": "ORP Exchange Report",
5
+ "description": "Machine-readable ORP knowledge exchange artifact for structured synthesis of another repository or project directory.",
6
+ "type": "object",
7
+ "additionalProperties": true,
8
+ "required": [
9
+ "schema_version",
10
+ "kind",
11
+ "exchange_id",
12
+ "generated_at_utc",
13
+ "current_project_root",
14
+ "source",
15
+ "inventory",
16
+ "relation",
17
+ "suggested_focus",
18
+ "artifacts",
19
+ "notes"
20
+ ],
21
+ "properties": {
22
+ "schema_version": {
23
+ "const": "1.0.0"
24
+ },
25
+ "kind": {
26
+ "const": "exchange_report"
27
+ },
28
+ "exchange_id": {
29
+ "type": "string",
30
+ "minLength": 1
31
+ },
32
+ "generated_at_utc": {
33
+ "type": "string",
34
+ "minLength": 1
35
+ },
36
+ "current_project_root": {
37
+ "type": "string",
38
+ "minLength": 1
39
+ },
40
+ "source": {
41
+ "type": "object",
42
+ "required": [
43
+ "requested",
44
+ "mode",
45
+ "label",
46
+ "local_path",
47
+ "remote_url",
48
+ "git_present",
49
+ "git_initialized_by_orp",
50
+ "cloned_by_orp",
51
+ "git"
52
+ ],
53
+ "properties": {
54
+ "requested": { "type": "string" },
55
+ "mode": { "type": "string" },
56
+ "label": { "type": "string" },
57
+ "local_path": { "type": "string" },
58
+ "remote_url": { "type": "string" },
59
+ "git_present": { "type": "boolean" },
60
+ "git_initialized_by_orp": { "type": "boolean" },
61
+ "cloned_by_orp": { "type": "boolean" },
62
+ "git": { "type": "object" }
63
+ }
64
+ },
65
+ "inventory": {
66
+ "type": "object",
67
+ "required": [
68
+ "top_level_entries",
69
+ "manifest_files",
70
+ "docs_paths",
71
+ "test_paths",
72
+ "languages",
73
+ "files_scanned",
74
+ "dirs_scanned"
75
+ ]
76
+ },
77
+ "relation": {
78
+ "type": "object",
79
+ "required": [
80
+ "host_repo_name",
81
+ "source_repo_name",
82
+ "same_root",
83
+ "shared_languages",
84
+ "shared_manifest_types",
85
+ "shared_top_level_entries"
86
+ ]
87
+ },
88
+ "suggested_focus": {
89
+ "type": "array",
90
+ "items": { "type": "string" }
91
+ },
92
+ "artifacts": {
93
+ "type": "object",
94
+ "required": [
95
+ "exchange_json",
96
+ "summary_md",
97
+ "transfer_map_md"
98
+ ]
99
+ },
100
+ "notes": {
101
+ "type": "array",
102
+ "items": { "type": "string" }
103
+ }
104
+ }
105
+ }
@@ -0,0 +1,102 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openresearchprotocol.com/spec/v1/hosted-workspace-event.schema.json",
4
+ "title": "ORP Hosted Workspace Event",
5
+ "description": "Timeline event for one hosted ORP workspace, used to build the detail-page activity stream and state history.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "schema_version",
10
+ "event_id",
11
+ "workspace_id",
12
+ "event_type",
13
+ "actor",
14
+ "created_at_utc"
15
+ ],
16
+ "properties": {
17
+ "schema_version": {
18
+ "type": "string",
19
+ "const": "1.0.0"
20
+ },
21
+ "event_id": {
22
+ "type": "string",
23
+ "minLength": 1
24
+ },
25
+ "workspace_id": {
26
+ "type": "string",
27
+ "minLength": 1
28
+ },
29
+ "event_type": {
30
+ "type": "string",
31
+ "enum": [
32
+ "workspace.created",
33
+ "workspace.updated",
34
+ "workspace.opened",
35
+ "snapshot.created",
36
+ "tab.added",
37
+ "tab.removed",
38
+ "tab.updated",
39
+ "focus.updated",
40
+ "trajectory.updated",
41
+ "summary.updated",
42
+ "bridge.synced"
43
+ ]
44
+ },
45
+ "snapshot_id": {
46
+ "type": "string"
47
+ },
48
+ "actor": {
49
+ "type": "object",
50
+ "additionalProperties": false,
51
+ "required": [
52
+ "type"
53
+ ],
54
+ "properties": {
55
+ "type": {
56
+ "type": "string",
57
+ "enum": [
58
+ "user",
59
+ "agent",
60
+ "system"
61
+ ]
62
+ },
63
+ "id": {
64
+ "type": "string"
65
+ },
66
+ "label": {
67
+ "type": "string"
68
+ }
69
+ }
70
+ },
71
+ "summary": {
72
+ "type": "string"
73
+ },
74
+ "tab_ids": {
75
+ "type": "array",
76
+ "items": {
77
+ "type": "string",
78
+ "minLength": 1
79
+ }
80
+ },
81
+ "state_excerpt": {
82
+ "type": "object",
83
+ "additionalProperties": false,
84
+ "properties": {
85
+ "tab_count": {
86
+ "type": "integer",
87
+ "minimum": 0
88
+ },
89
+ "current_focus": {
90
+ "type": "string"
91
+ },
92
+ "trajectory": {
93
+ "type": "string"
94
+ }
95
+ }
96
+ },
97
+ "created_at_utc": {
98
+ "type": "string",
99
+ "format": "date-time"
100
+ }
101
+ }
102
+ }