@useorgx/openclaw-plugin 0.4.9 → 0.7.2

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 (224) hide show
  1. package/README.md +77 -11
  2. package/dashboard/dist/assets/6mILZQ2a.js +1 -0
  3. package/dashboard/dist/assets/6mILZQ2a.js.br +0 -0
  4. package/dashboard/dist/assets/6mILZQ2a.js.gz +0 -0
  5. package/dashboard/dist/assets/8dksYiq4.js +2 -0
  6. package/dashboard/dist/assets/8dksYiq4.js.br +0 -0
  7. package/dashboard/dist/assets/8dksYiq4.js.gz +0 -0
  8. package/dashboard/dist/assets/B5zYRHc3.js +1 -0
  9. package/dashboard/dist/assets/B5zYRHc3.js.br +0 -0
  10. package/dashboard/dist/assets/B5zYRHc3.js.gz +0 -0
  11. package/dashboard/dist/assets/B6wPWJ35.js +1 -0
  12. package/dashboard/dist/assets/B6wPWJ35.js.br +0 -0
  13. package/dashboard/dist/assets/B6wPWJ35.js.gz +0 -0
  14. package/dashboard/dist/assets/BJgZIVUQ.js +53 -0
  15. package/dashboard/dist/assets/BJgZIVUQ.js.br +0 -0
  16. package/dashboard/dist/assets/BJgZIVUQ.js.gz +0 -0
  17. package/dashboard/dist/assets/BWEwjt1W.js +1 -0
  18. package/dashboard/dist/assets/BWEwjt1W.js.br +0 -0
  19. package/dashboard/dist/assets/BWEwjt1W.js.gz +0 -0
  20. package/dashboard/dist/assets/BgOYB78t.js +4 -0
  21. package/dashboard/dist/assets/BgOYB78t.js.br +0 -0
  22. package/dashboard/dist/assets/BgOYB78t.js.gz +0 -0
  23. package/dashboard/dist/assets/BzRbDCAD.css +1 -0
  24. package/dashboard/dist/assets/BzRbDCAD.css.br +0 -0
  25. package/dashboard/dist/assets/BzRbDCAD.css.gz +0 -0
  26. package/dashboard/dist/assets/C-KIc3Wc.js.br +0 -0
  27. package/dashboard/dist/assets/C-KIc3Wc.js.gz +0 -0
  28. package/dashboard/dist/assets/C8uM3AX8.js +1 -0
  29. package/dashboard/dist/assets/C8uM3AX8.js.br +0 -0
  30. package/dashboard/dist/assets/C8uM3AX8.js.gz +0 -0
  31. package/dashboard/dist/assets/C9jy61eu.js +212 -0
  32. package/dashboard/dist/assets/C9jy61eu.js.br +0 -0
  33. package/dashboard/dist/assets/C9jy61eu.js.gz +0 -0
  34. package/dashboard/dist/assets/CC63EwFD.js +1 -0
  35. package/dashboard/dist/assets/CC63EwFD.js.br +0 -0
  36. package/dashboard/dist/assets/CC63EwFD.js.gz +0 -0
  37. package/dashboard/dist/assets/CL_wXqR7.js +1 -0
  38. package/dashboard/dist/assets/CL_wXqR7.js.br +0 -0
  39. package/dashboard/dist/assets/CL_wXqR7.js.gz +0 -0
  40. package/dashboard/dist/assets/CZaT3ob_.js +1 -0
  41. package/dashboard/dist/assets/CZaT3ob_.js.br +0 -0
  42. package/dashboard/dist/assets/CZaT3ob_.js.gz +0 -0
  43. package/dashboard/dist/assets/CgaottFX.js +1 -0
  44. package/dashboard/dist/assets/CgaottFX.js.br +0 -0
  45. package/dashboard/dist/assets/CgaottFX.js.gz +0 -0
  46. package/dashboard/dist/assets/{CpJsfbXo.js → CxQ08qFN.js} +2 -2
  47. package/dashboard/dist/assets/CxQ08qFN.js.br +0 -0
  48. package/dashboard/dist/assets/CxQ08qFN.js.gz +0 -0
  49. package/dashboard/dist/assets/CzCxAZlW.js +1 -0
  50. package/dashboard/dist/assets/CzCxAZlW.js.br +0 -0
  51. package/dashboard/dist/assets/CzCxAZlW.js.gz +0 -0
  52. package/dashboard/dist/assets/D3iMTYEj.js +1 -0
  53. package/dashboard/dist/assets/D3iMTYEj.js.br +0 -0
  54. package/dashboard/dist/assets/D3iMTYEj.js.gz +0 -0
  55. package/dashboard/dist/assets/D8JNX8kq.js +2 -0
  56. package/dashboard/dist/assets/D8JNX8kq.js.br +0 -0
  57. package/dashboard/dist/assets/D8JNX8kq.js.gz +0 -0
  58. package/dashboard/dist/assets/DnA8dpj6.js +1 -0
  59. package/dashboard/dist/assets/DnA8dpj6.js.br +0 -0
  60. package/dashboard/dist/assets/DnA8dpj6.js.gz +0 -0
  61. package/dashboard/dist/assets/IUexzymk.js +1 -0
  62. package/dashboard/dist/assets/IUexzymk.js.br +0 -0
  63. package/dashboard/dist/assets/IUexzymk.js.gz +0 -0
  64. package/dashboard/dist/assets/cNrhgGc1.js +8 -0
  65. package/dashboard/dist/assets/cNrhgGc1.js.br +0 -0
  66. package/dashboard/dist/assets/cNrhgGc1.js.gz +0 -0
  67. package/dashboard/dist/assets/ic2FaMnh.js +1 -0
  68. package/dashboard/dist/assets/ic2FaMnh.js.br +0 -0
  69. package/dashboard/dist/assets/ic2FaMnh.js.gz +0 -0
  70. package/dashboard/dist/assets/qm8xLgv-.css +1 -0
  71. package/dashboard/dist/assets/qm8xLgv-.css.br +0 -0
  72. package/dashboard/dist/assets/qm8xLgv-.css.gz +0 -0
  73. package/dashboard/dist/assets/rttbDbEx.js +1 -0
  74. package/dashboard/dist/assets/rttbDbEx.js.br +0 -0
  75. package/dashboard/dist/assets/rttbDbEx.js.gz +0 -0
  76. package/dashboard/dist/brand/anthropic-mark.svg.br +0 -0
  77. package/dashboard/dist/brand/anthropic-mark.svg.gz +0 -0
  78. package/dashboard/dist/brand/openai-mark.svg.br +0 -0
  79. package/dashboard/dist/brand/openai-mark.svg.gz +0 -0
  80. package/dashboard/dist/brand/openclaw-mark.svg.br +0 -0
  81. package/dashboard/dist/brand/openclaw-mark.svg.gz +0 -0
  82. package/dashboard/dist/brand/xandy-orchestrator.png +0 -0
  83. package/dashboard/dist/index.html +7 -5
  84. package/dashboard/dist/index.html.br +0 -0
  85. package/dashboard/dist/index.html.gz +0 -0
  86. package/dist/activity-actor-fields.js +26 -4
  87. package/dist/activity-store.js +34 -8
  88. package/dist/agent-context-store.js +79 -17
  89. package/dist/agent-run-store.js +44 -3
  90. package/dist/agent-suite.d.ts +9 -0
  91. package/dist/agent-suite.js +149 -9
  92. package/dist/artifacts/artifact-domain-schemas.d.ts +66 -0
  93. package/dist/artifacts/artifact-domain-schemas.js +357 -0
  94. package/dist/artifacts/register-artifact.d.ts +4 -3
  95. package/dist/artifacts/register-artifact.js +170 -57
  96. package/dist/chat-store.d.ts +157 -0
  97. package/dist/chat-store.js +586 -0
  98. package/dist/cli/orgx.js +11 -0
  99. package/dist/contracts/client.d.ts +43 -3
  100. package/dist/contracts/client.js +159 -30
  101. package/dist/contracts/practice-exercise-schema.d.ts +216 -0
  102. package/dist/contracts/practice-exercise-schema.js +314 -0
  103. package/dist/contracts/retro-schema.d.ts +81 -0
  104. package/dist/contracts/retro-schema.js +80 -0
  105. package/dist/contracts/shared-types.d.ts +159 -0
  106. package/dist/contracts/shared-types.js +199 -1
  107. package/dist/contracts/skill-pack-schema.d.ts +192 -0
  108. package/dist/contracts/skill-pack-schema.js +180 -0
  109. package/dist/contracts/types.d.ts +247 -2
  110. package/dist/entities/auto-assignment.js +43 -17
  111. package/dist/event-sanitization.d.ts +11 -0
  112. package/dist/event-sanitization.js +113 -0
  113. package/dist/gateway-watchdog.d.ts +5 -0
  114. package/dist/gateway-watchdog.js +50 -0
  115. package/dist/hooks/post-reporting-event.mjs +1 -5
  116. package/dist/http/helpers/activity-headline.js +13 -132
  117. package/dist/http/helpers/auto-continue-engine.d.ts +198 -10
  118. package/dist/http/helpers/auto-continue-engine.js +3145 -186
  119. package/dist/http/helpers/autopilot-operations.d.ts +19 -0
  120. package/dist/http/helpers/autopilot-operations.js +182 -31
  121. package/dist/http/helpers/autopilot-runtime.d.ts +1 -0
  122. package/dist/http/helpers/autopilot-runtime.js +328 -25
  123. package/dist/http/helpers/autopilot-slice-utils.d.ts +18 -0
  124. package/dist/http/helpers/autopilot-slice-utils.js +514 -93
  125. package/dist/http/helpers/decision-mapper.d.ts +40 -0
  126. package/dist/http/helpers/decision-mapper.js +223 -7
  127. package/dist/http/helpers/dispatch-lifecycle.d.ts +19 -2
  128. package/dist/http/helpers/dispatch-lifecycle.js +242 -37
  129. package/dist/http/helpers/kickoff-context.js +104 -0
  130. package/dist/http/helpers/llm-client.d.ts +47 -0
  131. package/dist/http/helpers/llm-client.js +256 -0
  132. package/dist/http/helpers/mission-control.d.ts +102 -3
  133. package/dist/http/helpers/mission-control.js +498 -9
  134. package/dist/http/helpers/sentinel-catalog.d.ts +23 -0
  135. package/dist/http/helpers/sentinel-catalog.js +193 -0
  136. package/dist/http/helpers/session-classification.d.ts +9 -0
  137. package/dist/http/helpers/session-classification.js +564 -0
  138. package/dist/http/helpers/slice-experience-v2.d.ts +137 -0
  139. package/dist/http/helpers/slice-experience-v2.js +677 -0
  140. package/dist/http/helpers/slice-run-projections.d.ts +72 -0
  141. package/dist/http/helpers/slice-run-projections.js +877 -0
  142. package/dist/http/helpers/triage-mapper.d.ts +43 -0
  143. package/dist/http/helpers/triage-mapper.js +549 -0
  144. package/dist/http/helpers/value-utils.js +7 -2
  145. package/dist/http/helpers/workspace-scope.d.ts +15 -0
  146. package/dist/http/helpers/workspace-scope.js +170 -0
  147. package/dist/http/index.js +1420 -105
  148. package/dist/http/routes/agent-suite.d.ts +9 -0
  149. package/dist/http/routes/agent-suite.js +294 -8
  150. package/dist/http/routes/agents-catalog.js +64 -19
  151. package/dist/http/routes/chat.d.ts +19 -0
  152. package/dist/http/routes/chat.js +522 -0
  153. package/dist/http/routes/decision-actions.d.ts +8 -1
  154. package/dist/http/routes/decision-actions.js +42 -5
  155. package/dist/http/routes/dispatch-gateway-envelope.d.ts +25 -0
  156. package/dist/http/routes/dispatch-gateway-envelope.js +26 -0
  157. package/dist/http/routes/entities.d.ts +16 -0
  158. package/dist/http/routes/entities.js +232 -6
  159. package/dist/http/routes/live-legacy.d.ts +5 -0
  160. package/dist/http/routes/live-legacy.js +23 -509
  161. package/dist/http/routes/live-misc.d.ts +12 -0
  162. package/dist/http/routes/live-misc.js +251 -31
  163. package/dist/http/routes/live-snapshot.d.ts +49 -2
  164. package/dist/http/routes/live-snapshot.js +653 -23
  165. package/dist/http/routes/live-terminal.d.ts +11 -0
  166. package/dist/http/routes/live-terminal.js +154 -0
  167. package/dist/http/routes/live-triage.d.ts +61 -0
  168. package/dist/http/routes/live-triage.js +192 -0
  169. package/dist/http/routes/mission-control-actions.d.ts +49 -1
  170. package/dist/http/routes/mission-control-actions.js +1246 -84
  171. package/dist/http/routes/mission-control-read.d.ts +48 -3
  172. package/dist/http/routes/mission-control-read.js +1658 -20
  173. package/dist/http/routes/realtime-orchestrator.d.ts +10 -0
  174. package/dist/http/routes/realtime-orchestrator.js +74 -0
  175. package/dist/http/routes/run-control.d.ts +5 -2
  176. package/dist/http/routes/run-control.js +10 -0
  177. package/dist/http/routes/sentinels-catalog.d.ts +7 -0
  178. package/dist/http/routes/sentinels-catalog.js +24 -0
  179. package/dist/http/routes/summary.js +10 -3
  180. package/dist/http/routes/usage.d.ts +24 -0
  181. package/dist/http/routes/usage.js +362 -0
  182. package/dist/http/routes/work-artifacts.js +28 -9
  183. package/dist/index.js +165 -27
  184. package/dist/local-openclaw.js +29 -6
  185. package/dist/mcp-client-setup.js +3 -3
  186. package/dist/mcp-http-handler.d.ts +3 -0
  187. package/dist/mcp-http-handler.js +34 -60
  188. package/dist/next-up-queue-store.d.ts +16 -1
  189. package/dist/next-up-queue-store.js +89 -7
  190. package/dist/outbox.d.ts +5 -0
  191. package/dist/outbox.js +113 -9
  192. package/dist/paths.js +36 -5
  193. package/dist/reporting/rollups.d.ts +41 -0
  194. package/dist/reporting/rollups.js +113 -0
  195. package/dist/retro/domain-templates.d.ts +45 -0
  196. package/dist/retro/domain-templates.js +297 -0
  197. package/dist/retro/quality-rubric.d.ts +33 -0
  198. package/dist/retro/quality-rubric.js +213 -0
  199. package/dist/runtime-cleanup.d.ts +18 -0
  200. package/dist/runtime-cleanup.js +87 -0
  201. package/dist/services/background.d.ts +11 -0
  202. package/dist/services/background.js +22 -0
  203. package/dist/services/experiment-randomization.d.ts +21 -0
  204. package/dist/services/experiment-randomization.js +63 -0
  205. package/dist/skill-pack-state.d.ts +36 -5
  206. package/dist/skill-pack-state.js +273 -29
  207. package/dist/sync/local-agent-telemetry.d.ts +13 -0
  208. package/dist/sync/local-agent-telemetry.js +128 -0
  209. package/dist/sync/outbox-replay.js +131 -24
  210. package/dist/team-context-store.d.ts +23 -0
  211. package/dist/team-context-store.js +116 -0
  212. package/dist/telemetry/posthog.js +4 -2
  213. package/dist/tools/core-tools.d.ts +10 -14
  214. package/dist/tools/core-tools.js +1289 -24
  215. package/dist/types.d.ts +2 -0
  216. package/dist/types.js +2 -0
  217. package/dist/worker-supervisor.js +23 -0
  218. package/package.json +20 -6
  219. package/dashboard/dist/assets/B3ziCA02.js +0 -8
  220. package/dashboard/dist/assets/B5NEElEI.css +0 -1
  221. package/dashboard/dist/assets/BhapSNAs.js +0 -215
  222. package/dashboard/dist/assets/iFdvE7lx.js +0 -1
  223. package/dashboard/dist/assets/jRJsmpYM.js +0 -1
  224. package/dashboard/dist/assets/sAhvFnpk.js +0 -4
@@ -17,101 +17,136 @@ function ensurePrivateDirForFile(pathname) {
17
17
  }
18
18
  function autopilotSliceSchema() {
19
19
  // Strict enough to keep outputs predictable, but tolerant of older agents.
20
- return {
21
- type: "object",
22
- additionalProperties: false,
23
- required: ["status", "summary", "workstream_id"],
24
- properties: {
25
- status: {
26
- type: "string",
27
- enum: ["completed", "blocked", "needs_decision", "error"],
20
+ const artifactProperties = {
21
+ name: { type: "string", minLength: 1 },
22
+ artifact_type: {
23
+ type: "string",
24
+ enum: ["pr", "commit", "document", "config", "report", "design", "retro", "other"],
25
+ },
26
+ confidence_score: { type: ["number", "null"], minimum: 0, maximum: 1 },
27
+ description: { type: ["string", "null"] },
28
+ url: { type: ["string", "null"] },
29
+ verification_steps: { type: ["array", "null"], items: { type: "string" } },
30
+ milestone_id: { type: ["string", "null"] },
31
+ task_ids: { type: ["array", "null"], items: { type: "string" } },
32
+ };
33
+ const decisionProperties = {
34
+ question: { type: "string", minLength: 1 },
35
+ summary: { type: ["string", "null"] },
36
+ options: { type: ["array", "null"], items: { type: "string" } },
37
+ urgency: {
38
+ type: ["string", "null"],
39
+ enum: ["low", "medium", "high", "urgent", null],
40
+ },
41
+ blocking: { type: "boolean" },
42
+ };
43
+ const skillEvidenceProperties = {
44
+ skill: { type: "string", minLength: 1 },
45
+ skill_file: { type: ["string", "null"] },
46
+ skill_sha256: { type: ["string", "null"] },
47
+ skill_heading: { type: ["string", "null"] },
48
+ };
49
+ const taskUpdateProperties = {
50
+ task_id: { type: "string", minLength: 1 },
51
+ status: { type: "string", enum: ["todo", "in_progress", "done", "blocked"] },
52
+ reason: { type: ["string", "null"] },
53
+ };
54
+ const milestoneUpdateProperties = {
55
+ milestone_id: { type: "string", minLength: 1 },
56
+ status: {
57
+ type: "string",
58
+ enum: ["planned", "in_progress", "completed", "at_risk", "cancelled"],
59
+ },
60
+ reason: { type: ["string", "null"] },
61
+ };
62
+ const topLevelProperties = {
63
+ status: {
64
+ type: "string",
65
+ enum: ["completed", "blocked", "needs_decision", "error"],
66
+ },
67
+ summary: { type: "string", minLength: 1 },
68
+ workstream_id: { type: "string", minLength: 1 },
69
+ workstream_title: { type: ["string", "null"] },
70
+ slice_id: { type: ["string", "null"] },
71
+ artifacts: {
72
+ type: ["array", "null"],
73
+ items: {
74
+ type: "object",
75
+ additionalProperties: false,
76
+ required: Object.keys(artifactProperties),
77
+ properties: artifactProperties,
28
78
  },
29
- summary: { type: "string", minLength: 1 },
30
- workstream_id: { type: "string", minLength: 1 },
31
- workstream_title: { type: ["string", "null"] },
32
- slice_id: { type: ["string", "null"] },
33
- artifacts: {
34
- type: ["array", "null"],
35
- items: {
36
- type: "object",
37
- additionalProperties: false,
38
- required: ["name", "artifact_type"],
39
- properties: {
40
- name: { type: "string", minLength: 1 },
41
- artifact_type: {
42
- type: "string",
43
- enum: ["pr", "commit", "document", "config", "report", "design", "other"],
44
- },
45
- description: { type: ["string", "null"] },
46
- url: { type: ["string", "null"] },
47
- verification_steps: { type: ["array", "null"], items: { type: "string" } },
48
- milestone_id: { type: ["string", "null"] },
49
- task_ids: { type: ["array", "null"], items: { type: "string" } },
50
- },
51
- },
79
+ },
80
+ decisions_needed: {
81
+ type: ["array", "null"],
82
+ items: {
83
+ type: "object",
84
+ additionalProperties: false,
85
+ required: Object.keys(decisionProperties),
86
+ properties: decisionProperties,
52
87
  },
53
- decisions_needed: {
54
- type: ["array", "null"],
55
- items: {
56
- type: "object",
57
- additionalProperties: false,
58
- required: ["question"],
59
- properties: {
60
- question: { type: "string", minLength: 1 },
61
- summary: { type: ["string", "null"] },
62
- options: { type: ["array", "null"], items: { type: "string" } },
63
- urgency: {
64
- type: ["string", "null"],
65
- enum: ["low", "medium", "high", "urgent", null],
66
- },
67
- blocking: { type: ["boolean", "null"] },
68
- },
69
- },
88
+ },
89
+ skill_evidence: {
90
+ type: ["array", "null"],
91
+ items: {
92
+ type: "object",
93
+ additionalProperties: false,
94
+ required: Object.keys(skillEvidenceProperties),
95
+ properties: skillEvidenceProperties,
70
96
  },
71
- task_updates: {
72
- type: ["array", "null"],
73
- items: {
74
- type: "object",
75
- additionalProperties: false,
76
- required: ["task_id", "status"],
77
- properties: {
78
- task_id: { type: "string", minLength: 1 },
79
- status: { type: "string", minLength: 1 },
80
- reason: { type: ["string", "null"] },
81
- },
82
- },
97
+ },
98
+ task_updates: {
99
+ type: ["array", "null"],
100
+ items: {
101
+ type: "object",
102
+ additionalProperties: false,
103
+ required: Object.keys(taskUpdateProperties),
104
+ properties: taskUpdateProperties,
83
105
  },
84
- milestone_updates: {
85
- type: ["array", "null"],
86
- items: {
87
- type: "object",
88
- additionalProperties: false,
89
- required: ["milestone_id", "status"],
90
- properties: {
91
- milestone_id: { type: "string", minLength: 1 },
92
- status: { type: "string", minLength: 1 },
93
- reason: { type: ["string", "null"] },
94
- },
95
- },
106
+ },
107
+ milestone_updates: {
108
+ type: ["array", "null"],
109
+ items: {
110
+ type: "object",
111
+ additionalProperties: false,
112
+ required: Object.keys(milestoneUpdateProperties),
113
+ properties: milestoneUpdateProperties,
96
114
  },
97
- next_actions: { type: ["array", "null"], items: { type: "string" } },
98
115
  },
116
+ next_actions: { type: ["array", "null"], items: { type: "string" } },
117
+ };
118
+ return {
119
+ type: "object",
120
+ additionalProperties: false,
121
+ required: Object.keys(topLevelProperties),
122
+ properties: topLevelProperties,
123
+ // Keep schema within Codex structured-output subset (no combinators like allOf/if/then).
124
+ // Status/decision consistency is enforced by coordinator post-parse.
99
125
  };
100
126
  }
101
127
  export function ensureAutopilotSliceSchemaPath(schemaFilename) {
102
128
  const file = join(getOrgxPluginConfigDir(), schemaFilename);
129
+ const nextSchemaRaw = JSON.stringify(autopilotSliceSchema(), null, 2);
103
130
  try {
104
- if (existsSync(file))
105
- return file;
131
+ if (existsSync(file)) {
132
+ try {
133
+ const existingRaw = readFileSync(file, "utf8").trim();
134
+ if (existingRaw === nextSchemaRaw)
135
+ return file;
136
+ }
137
+ catch {
138
+ // continue and rewrite
139
+ }
140
+ }
106
141
  ensurePrivateDirForFile(file);
107
- writeFileAtomicSync(file, JSON.stringify(autopilotSliceSchema(), null, 2), { mode: 0o600 });
142
+ writeFileAtomicSync(file, nextSchemaRaw, { mode: 0o600 });
108
143
  return file;
109
144
  }
110
145
  catch {
111
146
  // Fall back to best-effort write.
112
147
  try {
113
148
  ensurePrivateDirForFile(file);
114
- writeFileSync(file, `${JSON.stringify(autopilotSliceSchema(), null, 2)}\n`, {
149
+ writeFileSync(file, `${nextSchemaRaw}\n`, {
115
150
  encoding: "utf8",
116
151
  mode: 0o600,
117
152
  });
@@ -123,19 +158,249 @@ export function ensureAutopilotSliceSchemaPath(schemaFilename) {
123
158
  }
124
159
  }
125
160
  export function parseSliceResult(raw) {
126
- const trimmed = raw.trim();
161
+ const allowedStatuses = new Set(["completed", "blocked", "needs_decision", "error"]);
162
+ const stripUtf8Bom = (text) => text.replace(/^\uFEFF/, "");
163
+ const extractMarkdownJsonFences = (text) => {
164
+ const matches = text.matchAll(/```(?:json)?\s*([\s\S]*?)\s*```/gi);
165
+ const fences = [];
166
+ for (const match of matches) {
167
+ const inner = typeof match[1] === "string" ? match[1].trim() : "";
168
+ if (inner.length > 0)
169
+ fences.push(inner);
170
+ }
171
+ return fences;
172
+ };
173
+ const stripMarkdownJsonFence = (text) => {
174
+ const fenceCount = text.match(/```/g)?.length ?? 0;
175
+ if (fenceCount !== 2)
176
+ return text;
177
+ const fenced = text.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
178
+ if (!fenced || typeof fenced[1] !== "string")
179
+ return text;
180
+ return fenced[1].trim();
181
+ };
182
+ const parseSliceJsonText = (text) => {
183
+ const normalized = stripUtf8Bom(stripMarkdownJsonFence(text.trim()));
184
+ const parsed = parseJsonSafe(normalized);
185
+ if (parsed && typeof parsed === "object" && isLikelySliceResult(parsed)) {
186
+ return normalizeSliceResult(parsed);
187
+ }
188
+ const fencedCandidates = extractMarkdownJsonFences(normalized);
189
+ for (let i = fencedCandidates.length - 1; i >= 0; i -= 1) {
190
+ const parsedInner = parseJsonSafe(stripUtf8Bom(fencedCandidates[i]));
191
+ if (parsedInner && typeof parsedInner === "object" && isLikelySliceResult(parsedInner)) {
192
+ return normalizeSliceResult(parsedInner);
193
+ }
194
+ }
195
+ return null;
196
+ };
197
+ const isLikelySliceResult = (value) => {
198
+ if (!value || typeof value !== "object")
199
+ return false;
200
+ const record = value;
201
+ const status = typeof record.status === "string" ? record.status : "";
202
+ const workstreamId = typeof record.workstream_id === "string" ? record.workstream_id : "";
203
+ const summary = typeof record.summary === "string" ? record.summary : "";
204
+ return (allowedStatuses.has(status) &&
205
+ summary.trim().length > 0 &&
206
+ workstreamId.trim().length > 0);
207
+ };
208
+ const normalizeSliceResult = (value) => {
209
+ const record = value;
210
+ const status = typeof record.status === "string" ? record.status : "";
211
+ const decisions = record.decisions_needed;
212
+ let changed = false;
213
+ let nextRecord = record;
214
+ if (Array.isArray(decisions)) {
215
+ const normalized = decisions.map((decision) => {
216
+ if (!decision || typeof decision !== "object")
217
+ return decision;
218
+ const decisionRecord = decision;
219
+ if (typeof decisionRecord.blocking === "boolean")
220
+ return decision;
221
+ changed = true;
222
+ return { ...decisionRecord, blocking: false };
223
+ });
224
+ if (changed)
225
+ nextRecord = { ...nextRecord, decisions_needed: normalized };
226
+ }
227
+ const hasBlockingDecision = (input) => Array.isArray(input.decisions_needed) &&
228
+ input.decisions_needed.some((decision) => {
229
+ if (!decision || typeof decision !== "object")
230
+ return false;
231
+ return decision.blocking === true;
232
+ });
233
+ if (hasBlockingDecision(nextRecord) && status === "completed") {
234
+ changed = true;
235
+ nextRecord = { ...nextRecord, status: "needs_decision" };
236
+ }
237
+ const normalizedStatus = typeof nextRecord.status === "string" ? nextRecord.status : status;
238
+ if (normalizedStatus === "completed") {
239
+ const hasExplicitOutcomeArrays = Array.isArray(record.artifacts) ||
240
+ Array.isArray(record.task_updates) ||
241
+ Array.isArray(record.milestone_updates) ||
242
+ Array.isArray(record.decisions_needed);
243
+ const artifactsCount = Array.isArray(nextRecord.artifacts) ? nextRecord.artifacts.length : 0;
244
+ const taskUpdatesCount = Array.isArray(nextRecord.task_updates)
245
+ ? nextRecord.task_updates.length
246
+ : 0;
247
+ const milestoneUpdatesCount = Array.isArray(nextRecord.milestone_updates)
248
+ ? nextRecord.milestone_updates.length
249
+ : 0;
250
+ const hasOutcomes = artifactsCount > 0 ||
251
+ taskUpdatesCount > 0 ||
252
+ milestoneUpdatesCount > 0;
253
+ if (hasExplicitOutcomeArrays && !hasOutcomes) {
254
+ changed = true;
255
+ nextRecord = { ...nextRecord, status: "error" };
256
+ }
257
+ }
258
+ const finalStatus = typeof nextRecord.status === "string" ? nextRecord.status : "";
259
+ const requiresBlockingDecision = finalStatus === "blocked" || finalStatus === "needs_decision" || finalStatus === "error";
260
+ if (requiresBlockingDecision && !hasBlockingDecision(nextRecord)) {
261
+ const nextDecisions = Array.isArray(nextRecord.decisions_needed)
262
+ ? [...nextRecord.decisions_needed]
263
+ : [];
264
+ nextDecisions.push({
265
+ question: "Missing required blocking decision for non-completed status",
266
+ summary: "Parser inserted a blocking decision because blocked/needs_decision/error statuses require blocking=true.",
267
+ options: null,
268
+ urgency: "medium",
269
+ blocking: true,
270
+ });
271
+ changed = true;
272
+ nextRecord = { ...nextRecord, decisions_needed: nextDecisions };
273
+ }
274
+ return changed ? nextRecord : value;
275
+ };
276
+ const unwrapStructuredOutput = (value) => {
277
+ if (!value || typeof value !== "object")
278
+ return null;
279
+ const record = value;
280
+ const parseEmbeddedText = (candidate) => {
281
+ if (typeof candidate === "string") {
282
+ return parseSliceJsonText(candidate);
283
+ }
284
+ if (Array.isArray(candidate)) {
285
+ for (let i = candidate.length - 1; i >= 0; i -= 1) {
286
+ const parsed = parseEmbeddedText(candidate[i]);
287
+ if (parsed)
288
+ return parsed;
289
+ }
290
+ return null;
291
+ }
292
+ if (!candidate || typeof candidate !== "object")
293
+ return null;
294
+ const candidateRecord = candidate;
295
+ if (typeof candidateRecord.value === "string") {
296
+ return parseSliceJsonText(candidateRecord.value);
297
+ }
298
+ if (typeof candidateRecord.text === "string") {
299
+ return parseSliceJsonText(candidateRecord.text);
300
+ }
301
+ const fromValue = parseEmbeddedText(candidateRecord.value);
302
+ if (fromValue)
303
+ return fromValue;
304
+ const fromText = parseEmbeddedText(candidateRecord.text);
305
+ if (fromText)
306
+ return fromText;
307
+ const fromOutputText = parseEmbeddedText(candidateRecord.output_text);
308
+ if (fromOutputText)
309
+ return fromOutputText;
310
+ return null;
311
+ };
312
+ const finalOutput = record.final_output;
313
+ if (finalOutput && typeof finalOutput === "object") {
314
+ if (isLikelySliceResult(finalOutput))
315
+ return normalizeSliceResult(finalOutput);
316
+ const parsedFinalOutput = parseEmbeddedText(finalOutput);
317
+ if (parsedFinalOutput)
318
+ return parsedFinalOutput;
319
+ }
320
+ if (typeof finalOutput === "string") {
321
+ const parsedFinalOutput = parseSliceJsonText(finalOutput);
322
+ if (parsedFinalOutput)
323
+ return parsedFinalOutput;
324
+ }
325
+ const structured = record.structured_output;
326
+ if (structured && typeof structured === "object") {
327
+ if (isLikelySliceResult(structured))
328
+ return normalizeSliceResult(structured);
329
+ const parsedStructured = parseEmbeddedText(structured);
330
+ if (parsedStructured)
331
+ return parsedStructured;
332
+ }
333
+ if (typeof structured === "string") {
334
+ const parsedStructured = parseSliceJsonText(structured);
335
+ if (parsedStructured)
336
+ return parsedStructured;
337
+ }
338
+ // Claude text-mode envelopes can sometimes return JSON in `result`.
339
+ const parsedResultObject = parseEmbeddedText(record.result);
340
+ if (parsedResultObject)
341
+ return parsedResultObject;
342
+ if (typeof record.result === "string") {
343
+ const parsedResult = parseSliceJsonText(record.result);
344
+ if (parsedResult)
345
+ return parsedResult;
346
+ }
347
+ if (typeof record.output_text === "string") {
348
+ const parsedOutputText = parseSliceJsonText(record.output_text);
349
+ if (parsedOutputText)
350
+ return parsedOutputText;
351
+ }
352
+ // Responses-style envelopes can return text in output/message/content arrays.
353
+ const output = record.output;
354
+ if (Array.isArray(output)) {
355
+ for (let i = output.length - 1; i >= 0; i -= 1) {
356
+ const item = output[i];
357
+ if (!item || typeof item !== "object")
358
+ continue;
359
+ const itemRecord = item;
360
+ const directText = parseEmbeddedText(itemRecord.text);
361
+ if (directText)
362
+ return directText;
363
+ const outputText = parseEmbeddedText(itemRecord.output_text);
364
+ if (outputText)
365
+ return outputText;
366
+ if (!Array.isArray(itemRecord.content))
367
+ continue;
368
+ for (let j = itemRecord.content.length - 1; j >= 0; j -= 1) {
369
+ const content = itemRecord.content[j];
370
+ if (!content || typeof content !== "object")
371
+ continue;
372
+ const contentRecord = content;
373
+ const contentDirect = parseEmbeddedText(contentRecord);
374
+ if (contentDirect)
375
+ return contentDirect;
376
+ const contentText = parseEmbeddedText(contentRecord.text);
377
+ if (contentText)
378
+ return contentText;
379
+ const contentOutputText = parseEmbeddedText(contentRecord.output_text);
380
+ if (contentOutputText)
381
+ return contentOutputText;
382
+ }
383
+ }
384
+ }
385
+ return isLikelySliceResult(record) ? normalizeSliceResult(record) : null;
386
+ };
387
+ const trimmed = stripUtf8Bom(raw).trim();
127
388
  if (!trimmed)
128
389
  return null;
129
- const direct = parseJsonSafe(trimmed);
130
- if (direct && typeof direct === "object")
131
- return direct;
390
+ const direct = parseJsonSafe(stripMarkdownJsonFence(trimmed));
391
+ const directUnwrapped = unwrapStructuredOutput(direct);
392
+ if (directUnwrapped && typeof directUnwrapped === "object")
393
+ return directUnwrapped;
394
+ const directTextParsed = parseSliceJsonText(trimmed);
395
+ if (directTextParsed && typeof directTextParsed === "object")
396
+ return directTextParsed;
132
397
  // Tolerant parse: extract the last complete top-level JSON object from mixed logs.
133
- const extractLastTopLevelObject = (text) => {
398
+ const extractTopLevelObjects = (text) => {
134
399
  let inString = false;
135
400
  let escaped = false;
136
401
  let depth = 0;
137
402
  let start = -1;
138
- let lastObject = null;
403
+ const objects = [];
139
404
  for (let i = 0; i < text.length; i += 1) {
140
405
  const ch = text[i];
141
406
  if (inString) {
@@ -167,18 +432,20 @@ export function parseSliceResult(raw) {
167
432
  continue;
168
433
  depth -= 1;
169
434
  if (depth === 0 && start >= 0) {
170
- lastObject = text.slice(start, i + 1);
435
+ objects.push(text.slice(start, i + 1));
171
436
  start = -1;
172
437
  }
173
438
  }
174
439
  }
175
- return lastObject;
440
+ return objects;
176
441
  };
177
- const candidate = extractLastTopLevelObject(trimmed);
178
- if (candidate) {
442
+ const candidates = extractTopLevelObjects(trimmed);
443
+ for (let i = candidates.length - 1; i >= 0; i -= 1) {
444
+ const candidate = candidates[i];
179
445
  const parsed = parseJsonSafe(candidate);
180
- if (parsed && typeof parsed === "object")
181
- return parsed;
446
+ const unwrapped = unwrapStructuredOutput(parsed);
447
+ if (unwrapped && typeof unwrapped === "object")
448
+ return unwrapped;
182
449
  }
183
450
  return null;
184
451
  }
@@ -237,7 +504,13 @@ export function normalizeCodexArgs(args) {
237
504
  normalized.unshift("exec");
238
505
  }
239
506
  if (!normalized.includes("--skip-git-repo-check")) {
240
- normalized.push("--skip-git-repo-check");
507
+ const passthroughIndex = normalized.indexOf("--");
508
+ if (passthroughIndex > -1) {
509
+ normalized.splice(passthroughIndex, 0, "--skip-git-repo-check");
510
+ }
511
+ else {
512
+ normalized.push("--skip-git-repo-check");
513
+ }
241
514
  }
242
515
  return normalized;
243
516
  }
@@ -423,7 +696,94 @@ export function createCodexBinResolver() {
423
696
  getCachedCodexProbeSummary,
424
697
  };
425
698
  }
699
+ function normalizeSkillName(skill) {
700
+ return skill.replace(/^\$/, "").trim();
701
+ }
702
+ function skillAliasesFor(skill) {
703
+ const normalized = normalizeSkillName(skill);
704
+ if (!normalized)
705
+ return [];
706
+ const aliases = [normalized];
707
+ if (normalized.startsWith("orgx-"))
708
+ aliases.push(normalized.slice("orgx-".length));
709
+ if (normalized.endsWith("-agent"))
710
+ aliases.push(normalized.slice(0, -"-agent".length));
711
+ return Array.from(new Set(aliases.filter(Boolean)));
712
+ }
713
+ function buildSkillHints(requiredSkills) {
714
+ return requiredSkills
715
+ .map((skill) => {
716
+ const normalized = normalizeSkillName(skill);
717
+ const aliases = skillAliasesFor(normalized);
718
+ const hintPaths = aliases
719
+ .flatMap((alias) => [
720
+ join(homedir(), ".codex", "skills", alias, "SKILL.md"),
721
+ join(homedir(), ".agents", "skills", alias, "SKILL.md"),
722
+ ])
723
+ .filter(Boolean);
724
+ return {
725
+ skill: normalized,
726
+ hintPaths: Array.from(new Set(hintPaths)),
727
+ };
728
+ })
729
+ .filter((entry) => entry.skill.length > 0);
730
+ }
731
+ export function buildSliceOutputInstructions(input) {
732
+ const skillHints = buildSkillHints(input.requiredSkills);
733
+ return [
734
+ "# Slice Execution",
735
+ "",
736
+ `Slice run: ${input.runId}`,
737
+ "",
738
+ "Reporting:",
739
+ "- You MUST emit progress at least twice (start + completion) using an OrgX progress tool.",
740
+ "- Preferred tool: orgx_report_progress. Equivalent aliases are valid (for example mcp__orgx__update_stream_progress).",
741
+ "- If no OrgX progress tool is available, include a blocking decisions_needed entry describing the missing tool.",
742
+ "- Do NOT hunt for OrgX mutation tools to mark tasks done. Instead, request status changes in your FINAL JSON via task_updates/milestone_updates; the coordinator will apply them.",
743
+ "",
744
+ "What to do:",
745
+ "- Choose a coherent slice of work you can complete end-to-end in this run.",
746
+ "- Execute the work (code/docs/config) and produce verifiable outcomes.",
747
+ "- Self-assess confidence when saving artifacts and include `confidence_score` in [0,1].",
748
+ "- If blocked, be explicit about what decision/info is required.",
749
+ "- Keep this run focused: stay inside the current repository/workdir and avoid unrelated exploration.",
750
+ "- Execution budget: prefer <=12 shell commands and <=6 minutes wall time.",
751
+ "- Verification budget: run only targeted checks for changed files. Avoid full-suite commands (for example `npm run test:hooks`, `npm test`, `npm run build`) unless the task explicitly requires them.",
752
+ "- If you hit sandbox/env blockers after one retry, stop and return `status=needs_decision` with the blocker and the smallest unblocking action.",
753
+ "- For each required skill, read the skill document and collect proof (path + sha256 + heading).",
754
+ "",
755
+ "Output requirements:",
756
+ "- Print ONLY a single JSON object as the final output (no interim JSON status messages).",
757
+ `- Your JSON MUST conform to this schema file: ${input.schemaPath}`,
758
+ "- Artifacts must be verifiable: include URLs or local paths, plus verification steps.",
759
+ "- Include `confidence_score` for each artifact (`0` to `1`; use `null` when unknown).",
760
+ "- If you need a human decision, include it in decisions_needed.",
761
+ "- For every decisions_needed entry, ALWAYS set blocking explicitly (true or false).",
762
+ "- If status is blocked, needs_decision, or error: include at least one decisions_needed entry with blocking=true.",
763
+ "- Status/decision consistency is strict:",
764
+ " - If any decision is blocking=true, status MUST be needs_decision or blocked (never completed).",
765
+ " - Only use status=completed when all listed decisions are non-blocking follow-ups.",
766
+ "- Never return status=completed with zero artifacts and zero task/milestone updates.",
767
+ "- skill_evidence is mandatory. Include one object per required skill with:",
768
+ " - skill (exact required skill id without leading $)",
769
+ " - skill_file (absolute SKILL.md path used)",
770
+ " - skill_sha256 (lowercase SHA-256 hex of that file)",
771
+ " - skill_heading (first markdown heading or first non-empty line)",
772
+ "- If you cannot locate/verify a required skill file, return status=needs_decision and a blocking decisions_needed entry.",
773
+ "Skill file hints:",
774
+ ...(skillHints.length > 0
775
+ ? skillHints.flatMap((entry) => [
776
+ `- ${entry.skill}:`,
777
+ ...entry.hintPaths.map((path) => ` - ${path}`),
778
+ ])
779
+ : ["- (none)"]),
780
+ "- If you are confident OrgX statuses should change, include task_updates and/or milestone_updates (with a short reason).",
781
+ " - task_updates.status must be one of: todo, in_progress, done, blocked",
782
+ " - milestone_updates.status must be one of: planned, in_progress, completed, at_risk, cancelled",
783
+ ].join("\n");
784
+ }
426
785
  export function buildWorkstreamSlicePrompt(input) {
786
+ const skillHints = buildSkillHints(input.executionPolicy.requiredSkills);
427
787
  const milestones = input.milestoneSummaries
428
788
  .map((m) => `- ${m.title} (${m.status}) [${m.id}]`)
429
789
  .slice(0, 10)
@@ -435,8 +795,25 @@ export function buildWorkstreamSlicePrompt(input) {
435
795
  })
436
796
  .slice(0, 18)
437
797
  .join("\n");
798
+ const behaviorConfigLines = [
799
+ input.behaviorConfig?.configId
800
+ ? `- behavior_config_id: ${input.behaviorConfig.configId}`
801
+ : null,
802
+ input.behaviorConfig?.version
803
+ ? `- behavior_config_version: ${input.behaviorConfig.version}`
804
+ : null,
805
+ input.behaviorConfig?.hash
806
+ ? `- behavior_config_hash: ${input.behaviorConfig.hash}`
807
+ : null,
808
+ input.behaviorConfig?.policySource
809
+ ? `- policy_source: ${input.behaviorConfig.policySource}`
810
+ : null,
811
+ input.behaviorConfig?.context
812
+ ? `- behavior_context: ${input.behaviorConfig.context}`
813
+ : null,
814
+ ].filter((line) => Boolean(line));
438
815
  return [
439
- "You are an OrgX execution agent running ONE workstream slice in a background codex session.",
816
+ "You are an OrgX execution agent running ONE workstream slice in a background autonomous session.",
440
817
  "",
441
818
  `Execution policy: ${input.executionPolicy.domain}`,
442
819
  `Required skills: ${input.executionPolicy.requiredSkills.map((s) => (s.startsWith("$") ? s : `$${s}`)).join(", ")}`,
@@ -444,6 +821,13 @@ export function buildWorkstreamSlicePrompt(input) {
444
821
  `Initiative: ${input.initiativeTitle} [${input.initiativeId}]`,
445
822
  `Workstream: ${input.workstreamTitle} [${input.workstreamId}]`,
446
823
  `Slice run: ${input.runId}`,
824
+ ...(behaviorConfigLines.length > 0
825
+ ? [
826
+ "",
827
+ "Behavior config (plugin-injected context):",
828
+ ...behaviorConfigLines,
829
+ ]
830
+ : []),
447
831
  "",
448
832
  "Milestones (context):",
449
833
  milestones || "- (none found)",
@@ -452,25 +836,62 @@ export function buildWorkstreamSlicePrompt(input) {
452
836
  tasks || "- (none found)",
453
837
  "",
454
838
  "Reporting:",
455
- "- Prefer using the MCP tool orgx_report_progress for progress updates (if it is available in your tool list).",
839
+ "- You MUST emit progress at least twice (start + completion) using an OrgX progress tool.",
840
+ "- Preferred tool: orgx_report_progress. Equivalent aliases are valid (for example mcp__orgx__update_stream_progress).",
841
+ "- If no OrgX progress tool is available, include a blocking decisions_needed entry describing the missing tool.",
456
842
  "- Do NOT hunt for OrgX mutation tools to mark tasks done. Instead, request status changes in your FINAL JSON via task_updates/milestone_updates; the coordinator will apply them.",
457
843
  "",
458
844
  "What to do:",
459
845
  "- Choose a coherent slice of work you can complete end-to-end in this run.",
460
846
  "- Execute the work (code/docs/config) and produce verifiable outcomes.",
847
+ "- Self-assess confidence when saving artifacts and include `confidence_score` in [0,1].",
461
848
  "- If blocked, be explicit about what decision/info is required.",
849
+ "- Keep this run focused: stay inside the current repository/workdir and avoid unrelated exploration.",
850
+ "- Execution budget: prefer <=12 shell commands and <=6 minutes wall time.",
851
+ "- Verification budget: run only targeted checks for changed files. Avoid full-suite commands (for example `npm run test:hooks`, `npm test`, `npm run build`) unless the task explicitly requires them.",
852
+ "- If you hit sandbox/env blockers after one retry, stop and return `status=needs_decision` with the blocker and the smallest unblocking action.",
853
+ "- For each required skill, read the skill document and collect proof (path + sha256 + heading).",
462
854
  "",
463
855
  "Output requirements:",
464
- "- Print ONLY a single JSON object as the final output.",
856
+ "- Print ONLY a single JSON object as the final output (no interim JSON status messages).",
465
857
  `- Your JSON MUST conform to this schema file: ${input.schemaPath}`,
466
858
  "- Artifacts must be verifiable: include URLs or local paths, plus verification steps.",
859
+ "- Include `confidence_score` for each artifact (`0` to `1`; use `null` when unknown).",
467
860
  "- If you need a human decision, include it in decisions_needed.",
468
861
  "- For every decisions_needed entry, ALWAYS set blocking explicitly (true or false).",
862
+ "- If status is blocked, needs_decision, or error: include at least one decisions_needed entry with blocking=true.",
469
863
  "- Status/decision consistency is strict:",
470
864
  " - If any decision is blocking=true, status MUST be needs_decision or blocked (never completed).",
471
865
  " - Only use status=completed when all listed decisions are non-blocking follow-ups.",
866
+ "- Never return status=completed with zero artifacts and zero task/milestone updates.",
867
+ "- skill_evidence is mandatory. Include one object per required skill with:",
868
+ " - skill (exact required skill id without leading $)",
869
+ " - skill_file (absolute SKILL.md path used)",
870
+ " - skill_sha256 (lowercase SHA-256 hex of that file)",
871
+ " - skill_heading (first markdown heading or first non-empty line)",
872
+ "- If you cannot locate/verify a required skill file, return status=needs_decision and a blocking decisions_needed entry.",
873
+ "Skill file hints:",
874
+ ...(skillHints.length > 0
875
+ ? skillHints.flatMap((entry) => [
876
+ `- ${entry.skill}:`,
877
+ ...entry.hintPaths.map((path) => ` - ${path}`),
878
+ ])
879
+ : ["- (none)"]),
472
880
  "- If you are confident OrgX statuses should change, include task_updates and/or milestone_updates (with a short reason).",
473
881
  " - task_updates.status must be one of: todo, in_progress, done, blocked",
474
882
  " - milestone_updates.status must be one of: planned, in_progress, completed, at_risk, cancelled",
475
883
  ].join("\n");
476
884
  }
885
+ export function buildScopeDirective(scope, meta) {
886
+ switch (scope) {
887
+ case "milestone":
888
+ return (`You are completing milestone "${meta.milestoneTitles?.[0] ?? "Unknown"}" ` +
889
+ `(${meta.taskCount} tasks). Complete all tasks to close the milestone. Report per-task progress.`);
890
+ case "workstream":
891
+ return (`You are advancing the entire "${meta.workstreamTitle ?? "Unknown"}" workstream ` +
892
+ `(${meta.taskCount} tasks across ${meta.milestoneTitles?.length ?? 0} milestones). ` +
893
+ `Prioritize by dependency order. Report per-milestone progress.`);
894
+ default:
895
+ return "";
896
+ }
897
+ }