@useorgx/openclaw-plugin 0.4.6 → 0.4.9

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 (137) hide show
  1. package/README.md +310 -24
  2. package/dashboard/dist/assets/B5NEElEI.css +1 -0
  3. package/dashboard/dist/assets/BhapSNAs.js +215 -0
  4. package/dashboard/dist/assets/iFdvE7lx.js +1 -0
  5. package/dashboard/dist/assets/jRJsmpYM.js +1 -0
  6. package/dashboard/dist/index.html +2 -2
  7. package/dist/activity-actor-fields.d.ts +3 -0
  8. package/dist/activity-actor-fields.js +128 -0
  9. package/dist/activity-store.js +12 -19
  10. package/dist/agent-context-store.js +5 -25
  11. package/dist/agent-run-store.js +5 -25
  12. package/dist/agent-suite.js +1 -8
  13. package/dist/artifacts/register-artifact.d.ts +47 -0
  14. package/dist/artifacts/register-artifact.js +271 -0
  15. package/dist/auth/flows.d.ts +47 -0
  16. package/dist/auth/flows.js +169 -0
  17. package/dist/auth-store.js +14 -39
  18. package/dist/byok-store.js +5 -19
  19. package/dist/cli/orgx.d.ts +66 -0
  20. package/dist/cli/orgx.js +91 -0
  21. package/dist/config/refresh.d.ts +32 -0
  22. package/dist/config/refresh.js +55 -0
  23. package/dist/config/resolution.d.ts +37 -0
  24. package/dist/config/resolution.js +178 -0
  25. package/dist/contracts/client.d.ts +1 -0
  26. package/dist/contracts/client.js +7 -5
  27. package/dist/contracts/shared-types.d.ts +147 -0
  28. package/dist/contracts/shared-types.js +3 -0
  29. package/dist/contracts/types.d.ts +1 -130
  30. package/dist/contracts/types.js +5 -0
  31. package/dist/entities/auto-assignment.d.ts +36 -0
  32. package/dist/entities/auto-assignment.js +115 -0
  33. package/dist/entity-comment-store.js +5 -25
  34. package/dist/hash-utils.d.ts +2 -0
  35. package/dist/hash-utils.js +12 -0
  36. package/dist/http/helpers/activity-headline.d.ts +10 -0
  37. package/dist/http/helpers/activity-headline.js +192 -0
  38. package/dist/http/helpers/artifact-fallback.d.ts +13 -0
  39. package/dist/http/helpers/artifact-fallback.js +148 -0
  40. package/dist/http/helpers/auto-continue-engine.d.ts +298 -0
  41. package/dist/http/helpers/auto-continue-engine.js +1218 -0
  42. package/dist/http/helpers/autopilot-operations.d.ts +157 -0
  43. package/dist/http/helpers/autopilot-operations.js +403 -0
  44. package/dist/http/helpers/autopilot-runtime.d.ts +42 -0
  45. package/dist/http/helpers/autopilot-runtime.js +319 -0
  46. package/dist/http/helpers/autopilot-slice-utils.d.ts +38 -0
  47. package/dist/http/helpers/autopilot-slice-utils.js +476 -0
  48. package/dist/http/helpers/decision-mapper.d.ts +12 -0
  49. package/dist/http/helpers/decision-mapper.js +44 -0
  50. package/dist/http/helpers/dispatch-lifecycle.d.ts +102 -0
  51. package/dist/http/helpers/dispatch-lifecycle.js +604 -0
  52. package/dist/http/helpers/hash-utils.d.ts +1 -0
  53. package/dist/http/helpers/hash-utils.js +1 -0
  54. package/dist/http/helpers/kickoff-context.d.ts +12 -0
  55. package/dist/http/helpers/kickoff-context.js +154 -0
  56. package/dist/http/helpers/mission-control.d.ts +94 -0
  57. package/dist/http/helpers/mission-control.js +894 -0
  58. package/dist/http/helpers/openclaw-cli.d.ts +37 -0
  59. package/dist/http/helpers/openclaw-cli.js +283 -0
  60. package/dist/http/helpers/runtime-sse.d.ts +20 -0
  61. package/dist/http/helpers/runtime-sse.js +110 -0
  62. package/dist/http/helpers/value-utils.d.ts +6 -0
  63. package/dist/http/helpers/value-utils.js +67 -0
  64. package/dist/http/index.d.ts +88 -0
  65. package/dist/http/index.js +2353 -0
  66. package/dist/http/router.d.ts +23 -0
  67. package/dist/http/router.js +23 -0
  68. package/dist/http/routes/agent-control.d.ts +79 -0
  69. package/dist/http/routes/agent-control.js +684 -0
  70. package/dist/http/routes/agent-suite.d.ts +29 -0
  71. package/dist/http/routes/agent-suite.js +198 -0
  72. package/dist/http/routes/agents-catalog.d.ts +40 -0
  73. package/dist/http/routes/agents-catalog.js +83 -0
  74. package/dist/http/routes/billing.d.ts +23 -0
  75. package/dist/http/routes/billing.js +55 -0
  76. package/dist/http/routes/debug.d.ts +14 -0
  77. package/dist/http/routes/debug.js +21 -0
  78. package/dist/http/routes/decision-actions.d.ts +13 -0
  79. package/dist/http/routes/decision-actions.js +66 -0
  80. package/dist/http/routes/delegation.d.ts +19 -0
  81. package/dist/http/routes/delegation.js +32 -0
  82. package/dist/http/routes/entities.d.ts +47 -0
  83. package/dist/http/routes/entities.js +152 -0
  84. package/dist/http/routes/entity-dynamic.d.ts +25 -0
  85. package/dist/http/routes/entity-dynamic.js +191 -0
  86. package/dist/http/routes/health.d.ts +22 -0
  87. package/dist/http/routes/health.js +49 -0
  88. package/dist/http/routes/live-legacy.d.ts +110 -0
  89. package/dist/http/routes/live-legacy.js +598 -0
  90. package/dist/http/routes/live-misc.d.ts +69 -0
  91. package/dist/http/routes/live-misc.js +206 -0
  92. package/dist/http/routes/live-snapshot.d.ts +90 -0
  93. package/dist/http/routes/live-snapshot.js +297 -0
  94. package/dist/http/routes/mission-control-actions.d.ts +83 -0
  95. package/dist/http/routes/mission-control-actions.js +541 -0
  96. package/dist/http/routes/mission-control-read.d.ts +28 -0
  97. package/dist/http/routes/mission-control-read.js +67 -0
  98. package/dist/http/routes/onboarding.d.ts +34 -0
  99. package/dist/http/routes/onboarding.js +101 -0
  100. package/dist/http/routes/run-control.d.ts +24 -0
  101. package/dist/http/routes/run-control.js +86 -0
  102. package/dist/http/routes/runtime-hooks.d.ts +69 -0
  103. package/dist/http/routes/runtime-hooks.js +437 -0
  104. package/dist/http/routes/settings-byok.d.ts +23 -0
  105. package/dist/http/routes/settings-byok.js +163 -0
  106. package/dist/http/routes/summary.d.ts +18 -0
  107. package/dist/http/routes/summary.js +42 -0
  108. package/dist/http/routes/work-artifacts.d.ts +9 -0
  109. package/dist/http/routes/work-artifacts.js +36 -0
  110. package/dist/http/shared-state.d.ts +16 -0
  111. package/dist/http/shared-state.js +1 -0
  112. package/dist/http-handler.d.ts +1 -88
  113. package/dist/http-handler.js +1 -9664
  114. package/dist/index.js +122 -2121
  115. package/dist/json-utils.d.ts +1 -0
  116. package/dist/json-utils.js +8 -0
  117. package/dist/local-openclaw.js +8 -0
  118. package/dist/mcp-client-setup.js +75 -90
  119. package/dist/next-up-queue-store.js +4 -18
  120. package/dist/runtime-instance-store.js +8 -34
  121. package/dist/services/background.d.ts +23 -0
  122. package/dist/services/background.js +23 -0
  123. package/dist/services/instrumentation.d.ts +29 -0
  124. package/dist/services/instrumentation.js +136 -0
  125. package/dist/snapshot-store.js +5 -25
  126. package/dist/stores/json-store.d.ts +11 -0
  127. package/dist/stores/json-store.js +42 -0
  128. package/dist/sync/outbox-replay.d.ts +55 -0
  129. package/dist/sync/outbox-replay.js +514 -0
  130. package/dist/tools/core-tools.d.ts +76 -0
  131. package/dist/tools/core-tools.js +1005 -0
  132. package/dist/worker-supervisor.js +15 -0
  133. package/package.json +6 -1
  134. package/dashboard/dist/assets/0tOC3wSN.js +0 -214
  135. package/dashboard/dist/assets/Bm8QnMJ_.js +0 -1
  136. package/dashboard/dist/assets/CyxZio4Y.js +0 -1
  137. package/dashboard/dist/assets/DaAIOik3.css +0 -1
@@ -0,0 +1,128 @@
1
+ function asRecord(value) {
2
+ if (!value || typeof value !== "object" || Array.isArray(value))
3
+ return null;
4
+ return value;
5
+ }
6
+ function nonEmptyString(value) {
7
+ if (typeof value !== "string")
8
+ return null;
9
+ const trimmed = value.trim();
10
+ return trimmed.length > 0 ? trimmed : null;
11
+ }
12
+ function firstString(record, keys) {
13
+ if (!record)
14
+ return null;
15
+ for (const key of keys) {
16
+ const candidate = nonEmptyString(record[key]);
17
+ if (candidate)
18
+ return candidate;
19
+ }
20
+ return null;
21
+ }
22
+ function normalizedMetadata(item) {
23
+ const metadata = asRecord(item.metadata);
24
+ if (!metadata)
25
+ return null;
26
+ const nested = asRecord(metadata.metadata);
27
+ if (!nested)
28
+ return metadata;
29
+ return { ...metadata, ...nested };
30
+ }
31
+ function hasActorValue(id, name) {
32
+ return Boolean((id && id.trim().length > 0) || (name && name.trim().length > 0));
33
+ }
34
+ export function enrichActivityActorFields(item) {
35
+ const metadata = normalizedMetadata(item);
36
+ let requesterAgentId = nonEmptyString(item.requesterAgentId) ??
37
+ firstString(metadata, [
38
+ "requested_by_agent_id",
39
+ "requestedByAgentId",
40
+ "requester_agent_id",
41
+ "requesterAgentId",
42
+ "requester_id",
43
+ "requesterId",
44
+ "runner_agent_id",
45
+ "runnerAgentId",
46
+ "handoff_from_agent_id",
47
+ "handoffFromAgentId",
48
+ "from_agent_id",
49
+ "fromAgentId",
50
+ "source_agent_id",
51
+ "sourceAgentId",
52
+ ]);
53
+ let requesterAgentName = nonEmptyString(item.requesterAgentName) ??
54
+ firstString(metadata, [
55
+ "requested_by_agent_name",
56
+ "requestedByAgentName",
57
+ "requester_agent_name",
58
+ "requesterAgentName",
59
+ "requester_name",
60
+ "requesterName",
61
+ "runner_agent_name",
62
+ "runnerAgentName",
63
+ "handoff_from_agent_name",
64
+ "handoffFromAgentName",
65
+ "from_agent_name",
66
+ "fromAgentName",
67
+ "source_agent_name",
68
+ "sourceAgentName",
69
+ ]);
70
+ const executorAgentId = nonEmptyString(item.executorAgentId) ??
71
+ firstString(metadata, [
72
+ "executed_by_agent_id",
73
+ "executedByAgentId",
74
+ "executor_agent_id",
75
+ "executorAgentId",
76
+ "executor_id",
77
+ "executorId",
78
+ "delegated_to_agent_id",
79
+ "delegatedToAgentId",
80
+ "handoff_to_agent_id",
81
+ "handoffToAgentId",
82
+ "target_agent_id",
83
+ "targetAgentId",
84
+ "agent_id",
85
+ "agentId",
86
+ ]) ??
87
+ nonEmptyString(item.agentId);
88
+ const executorAgentName = nonEmptyString(item.executorAgentName) ??
89
+ firstString(metadata, [
90
+ "executed_by_agent_name",
91
+ "executedByAgentName",
92
+ "executor_agent_name",
93
+ "executorAgentName",
94
+ "executor_name",
95
+ "executorName",
96
+ "delegated_to_agent_name",
97
+ "delegatedToAgentName",
98
+ "handoff_to_agent_name",
99
+ "handoffToAgentName",
100
+ "target_agent_name",
101
+ "targetAgentName",
102
+ "agent_name",
103
+ "agentName",
104
+ ]) ??
105
+ nonEmptyString(item.agentName);
106
+ if (!hasActorValue(requesterAgentId, requesterAgentName) && hasActorValue(executorAgentId, executorAgentName)) {
107
+ requesterAgentId = executorAgentId;
108
+ requesterAgentName = executorAgentName;
109
+ }
110
+ const unchanged = nonEmptyString(item.requesterAgentId) === requesterAgentId &&
111
+ nonEmptyString(item.requesterAgentName) === requesterAgentName &&
112
+ nonEmptyString(item.executorAgentId) === executorAgentId &&
113
+ nonEmptyString(item.executorAgentName) === executorAgentName;
114
+ if (unchanged)
115
+ return item;
116
+ return {
117
+ ...item,
118
+ requesterAgentId: requesterAgentId ?? null,
119
+ requesterAgentName: requesterAgentName ?? null,
120
+ executorAgentId: executorAgentId ?? null,
121
+ executorAgentName: executorAgentName ?? null,
122
+ };
123
+ }
124
+ export function enrichActivityActorFieldsList(items) {
125
+ if (!Array.isArray(items) || items.length === 0)
126
+ return [];
127
+ return items.map((item) => enrichActivityActorFields(item));
128
+ }
@@ -1,7 +1,9 @@
1
- import { chmodSync, existsSync, mkdirSync, readFileSync, } from "node:fs";
1
+ import { existsSync, readFileSync, } from "node:fs";
2
2
  import { Buffer } from "node:buffer";
3
+ import { enrichActivityActorFieldsList } from "./activity-actor-fields.js";
3
4
  import { getOrgxPluginConfigDir, getOrgxPluginConfigPath } from "./paths.js";
4
5
  import { backupCorruptFileSync, writeJsonFileAtomicSync } from "./fs-utils.js";
6
+ import { ensureStoreDirSync, parseJsonSafe } from "./stores/json-store.js";
5
7
  const STORE_VERSION = 1;
6
8
  const STORE_FILENAME = "activity-store.json";
7
9
  const MAX_ITEMS = 50_000;
@@ -9,26 +11,11 @@ const RETENTION_DAYS = 45;
9
11
  const FLUSH_DEBOUNCE_MS = 1_250;
10
12
  let cached = null;
11
13
  function ensureDir() {
12
- const dir = getOrgxPluginConfigDir();
13
- mkdirSync(dir, { recursive: true, mode: 0o700 });
14
- try {
15
- chmodSync(dir, 0o700);
16
- }
17
- catch {
18
- // best effort
19
- }
14
+ ensureStoreDirSync(getOrgxPluginConfigDir());
20
15
  }
21
16
  function storePath() {
22
17
  return getOrgxPluginConfigPath(STORE_FILENAME);
23
18
  }
24
- function parseJson(value) {
25
- try {
26
- return JSON.parse(value);
27
- }
28
- catch {
29
- return null;
30
- }
31
- }
32
19
  function toEpoch(value) {
33
20
  if (!value)
34
21
  return 0;
@@ -62,7 +49,9 @@ function normalizeItems(source) {
62
49
  continue;
63
50
  byId.set(id, item);
64
51
  }
65
- return Array.from(byId.values()).sort(compareActivity).slice(0, MAX_ITEMS);
52
+ return enrichActivityActorFieldsList(Array.from(byId.values()))
53
+ .sort(compareActivity)
54
+ .slice(0, MAX_ITEMS);
66
55
  }
67
56
  function readPersistedStore() {
68
57
  ensureDir();
@@ -72,7 +61,7 @@ function readPersistedStore() {
72
61
  }
73
62
  try {
74
63
  const raw = readFileSync(file, "utf8");
75
- const parsed = parseJson(raw);
64
+ const parsed = parseJsonSafe(raw);
76
65
  if (!parsed || parsed.version !== STORE_VERSION || !Array.isArray(parsed.items)) {
77
66
  backupCorruptFileSync(file);
78
67
  return { version: STORE_VERSION, updatedAt: new Date().toISOString(), items: [] };
@@ -181,6 +170,10 @@ export function appendActivityItems(items) {
181
170
  existing.type !== item.type ||
182
171
  existing.title !== item.title ||
183
172
  existing.description !== item.description ||
173
+ (existing.requesterAgentId ?? null) !== (item.requesterAgentId ?? null) ||
174
+ (existing.requesterAgentName ?? null) !== (item.requesterAgentName ?? null) ||
175
+ (existing.executorAgentId ?? null) !== (item.executorAgentId ?? null) ||
176
+ (existing.executorAgentName ?? null) !== (item.executorAgentName ?? null) ||
184
177
  existing.summary !== item.summary ||
185
178
  JSON.stringify(existing.metadata ?? null) !== JSON.stringify(item.metadata ?? null)) {
186
179
  state.byId.set(id, item);
@@ -1,6 +1,7 @@
1
- import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, } from "node:fs";
1
+ import { existsSync, readFileSync, } from "node:fs";
2
2
  import { getOrgxPluginConfigDir, getOrgxPluginConfigPath } from "./paths.js";
3
3
  import { backupCorruptFileSync, writeJsonFileAtomicSync } from "./fs-utils.js";
4
+ import { clearStoreFileSync, ensureStoreDirSync, parseJsonSafe, } from "./stores/json-store.js";
4
5
  const MAX_AGENTS = 120;
5
6
  const MAX_RUNS = 480;
6
7
  function contextDir() {
@@ -10,22 +11,7 @@ function contextFile() {
10
11
  return getOrgxPluginConfigPath("agent-contexts.json");
11
12
  }
12
13
  function ensureContextDir() {
13
- const dir = contextDir();
14
- mkdirSync(dir, { recursive: true, mode: 0o700 });
15
- try {
16
- chmodSync(dir, 0o700);
17
- }
18
- catch {
19
- // best effort
20
- }
21
- }
22
- function parseJson(value) {
23
- try {
24
- return JSON.parse(value);
25
- }
26
- catch {
27
- return null;
28
- }
14
+ ensureStoreDirSync(contextDir());
29
15
  }
30
16
  function normalizeContext(input) {
31
17
  return {
@@ -55,7 +41,7 @@ export function readAgentContexts() {
55
41
  return { updatedAt: new Date().toISOString(), agents: {}, runs: {} };
56
42
  }
57
43
  const raw = readFileSync(file, "utf8");
58
- const parsed = parseJson(raw);
44
+ const parsed = parseJsonSafe(raw);
59
45
  if (!parsed || typeof parsed !== "object") {
60
46
  backupCorruptFileSync(file);
61
47
  return { updatedAt: new Date().toISOString(), agents: {}, runs: {} };
@@ -157,11 +143,5 @@ export function upsertRunContext(input) {
157
143
  return next;
158
144
  }
159
145
  export function clearAgentContexts() {
160
- const file = contextFile();
161
- try {
162
- rmSync(file, { force: true });
163
- }
164
- catch {
165
- // best effort
166
- }
146
+ clearStoreFileSync(contextFile());
167
147
  }
@@ -1,6 +1,7 @@
1
- import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, } from "node:fs";
1
+ import { existsSync, readFileSync, } from "node:fs";
2
2
  import { getOrgxPluginConfigDir, getOrgxPluginConfigPath } from "./paths.js";
3
3
  import { backupCorruptFileSync, writeJsonFileAtomicSync } from "./fs-utils.js";
4
+ import { clearStoreFileSync, ensureStoreDirSync, parseJsonSafe, } from "./stores/json-store.js";
4
5
  const MAX_RUNS = 240;
5
6
  function runDir() {
6
7
  return getOrgxPluginConfigDir();
@@ -9,22 +10,7 @@ function runFile() {
9
10
  return getOrgxPluginConfigPath("agent-runs.json");
10
11
  }
11
12
  function ensureRunDir() {
12
- const dir = runDir();
13
- mkdirSync(dir, { recursive: true, mode: 0o700 });
14
- try {
15
- chmodSync(dir, 0o700);
16
- }
17
- catch {
18
- // best effort
19
- }
20
- }
21
- function parseJson(value) {
22
- try {
23
- return JSON.parse(value);
24
- }
25
- catch {
26
- return null;
27
- }
13
+ ensureStoreDirSync(runDir());
28
14
  }
29
15
  function normalizeNullableString(value) {
30
16
  if (typeof value !== "string")
@@ -56,7 +42,7 @@ export function readAgentRuns() {
56
42
  return { updatedAt: new Date().toISOString(), runs: {} };
57
43
  }
58
44
  const raw = readFileSync(file, "utf8");
59
- const parsed = parseJson(raw);
45
+ const parsed = parseJsonSafe(raw);
60
46
  if (!parsed || typeof parsed !== "object") {
61
47
  backupCorruptFileSync(file);
62
48
  return { updatedAt: new Date().toISOString(), runs: {} };
@@ -148,11 +134,5 @@ export function markAgentRunStopped(runId) {
148
134
  return updated ? normalizeRecord(updated) : null;
149
135
  }
150
136
  export function clearAgentRuns() {
151
- const file = runFile();
152
- try {
153
- rmSync(file, { force: true });
154
- }
155
- catch {
156
- // best effort
157
- }
137
+ clearStoreFileSync(runFile());
158
138
  }
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, join } from "node:path";
5
5
  import { writeFileAtomicSync } from "./fs-utils.js";
6
+ import { parseJsonSafe } from "./json-utils.js";
6
7
  import { getOpenClawDir } from "./paths.js";
7
8
  export const ORGX_AGENT_SUITE_PACK_ID = "orgx-agent-suite";
8
9
  export const ORGX_AGENT_SUITE_AGENTS = [
@@ -29,14 +30,6 @@ const SUITE_FILES = [
29
30
  function isRecord(value) {
30
31
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
31
32
  }
32
- function parseJsonSafe(raw) {
33
- try {
34
- return JSON.parse(raw);
35
- }
36
- catch {
37
- return null;
38
- }
39
- }
40
33
  function isSafeAgentId(value) {
41
34
  const trimmed = value.trim();
42
35
  if (!trimmed)
@@ -0,0 +1,47 @@
1
+ import type { OrgXClient } from "../contracts/client.js";
2
+ export type ArtifactEntityType = "initiative" | "milestone" | "task" | "decision" | "project";
3
+ export interface RegisterArtifactInput {
4
+ /** Optional deterministic artifact id (UUID). Enables idempotent retries/outbox replay. */
5
+ artifact_id?: string | null;
6
+ entity_type: ArtifactEntityType;
7
+ entity_id: string;
8
+ name: string;
9
+ artifact_type: string;
10
+ /**
11
+ * Required by OrgX work_artifacts schema.
12
+ * If omitted, defaults to "human" (safe default for user-scoped oxk_ keys).
13
+ */
14
+ created_by_type?: "human" | "agent" | null;
15
+ /**
16
+ * Optional explicit creator id. OrgX can typically infer this from auth context,
17
+ * but allow callers to supply it when they have a stable UUID.
18
+ */
19
+ created_by_id?: string | null;
20
+ description?: string | null;
21
+ external_url?: string | null;
22
+ preview_markdown?: string | null;
23
+ status?: string;
24
+ metadata?: Record<string, unknown> | null;
25
+ /**
26
+ * When true, do a read-after-write check against:
27
+ * - GET /api/artifacts/:id
28
+ * - GET /api/work-artifacts/by-entity
29
+ */
30
+ validate_persistence?: boolean;
31
+ }
32
+ export interface RegisterArtifactResult {
33
+ ok: boolean;
34
+ artifact_id: string | null;
35
+ artifact_url: string | null;
36
+ created: boolean;
37
+ persistence: {
38
+ checked: boolean;
39
+ artifact_detail_ok: boolean;
40
+ linked_ok: boolean;
41
+ attempts: number;
42
+ last_error: string | null;
43
+ };
44
+ warnings: string[];
45
+ }
46
+ export declare function validateRegisterArtifactInput(input: RegisterArtifactInput): string[];
47
+ export declare function registerArtifact(client: OrgXClient, baseUrl: string, input: RegisterArtifactInput): Promise<RegisterArtifactResult>;
@@ -0,0 +1,271 @@
1
+ import { randomUUID } from "node:crypto";
2
+ const MAX_PREVIEW_MARKDOWN = 25_000;
3
+ function normalizeText(value) {
4
+ return typeof value === "string" ? value.trim() : "";
5
+ }
6
+ function isUuid(value) {
7
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
8
+ }
9
+ function resolveCreatedById(client, input) {
10
+ const explicit = normalizeText(input.created_by_id);
11
+ if (explicit && isUuid(explicit))
12
+ return explicit;
13
+ const fromClient = typeof client?.getUserId === "function"
14
+ ? normalizeText(client.getUserId())
15
+ : "";
16
+ if (fromClient && isUuid(fromClient))
17
+ return fromClient;
18
+ const fromEnv = normalizeText(process.env.ORGX_CREATED_BY_ID);
19
+ if (fromEnv && isUuid(fromEnv))
20
+ return fromEnv;
21
+ return null;
22
+ }
23
+ export function validateRegisterArtifactInput(input) {
24
+ const errors = [];
25
+ if (!input || typeof input !== "object") {
26
+ return ["input must be an object"];
27
+ }
28
+ const entityType = normalizeText(input.entity_type);
29
+ if (!entityType)
30
+ errors.push("entity_type is required");
31
+ const entityId = normalizeText(input.entity_id);
32
+ if (!entityId)
33
+ errors.push("entity_id is required");
34
+ // In production OrgX uses UUIDs, but tests/mocks sometimes use short ids like "init-1".
35
+ // Keep this as a soft constraint (persistence validation will catch mismatches upstream).
36
+ const name = normalizeText(input.name);
37
+ if (!name)
38
+ errors.push("name is required");
39
+ const artifactType = normalizeText(input.artifact_type);
40
+ if (!artifactType)
41
+ errors.push("artifact_type is required");
42
+ const createdByType = normalizeText(input.created_by_type);
43
+ if (createdByType && createdByType !== "human" && createdByType !== "agent") {
44
+ errors.push("created_by_type must be 'human' or 'agent' when provided");
45
+ }
46
+ const externalUrl = normalizeText(input.external_url);
47
+ const preview = normalizeText(input.preview_markdown);
48
+ if (!externalUrl && !preview) {
49
+ errors.push("at least one of external_url or preview_markdown is required");
50
+ }
51
+ return errors;
52
+ }
53
+ function safeErrorMessage(err) {
54
+ if (err instanceof Error)
55
+ return err.message;
56
+ return String(err);
57
+ }
58
+ function isArtifactTypeConstraintError(err) {
59
+ const msg = safeErrorMessage(err).toLowerCase();
60
+ return (msg.includes("artifact_type") &&
61
+ (msg.includes("constraint") || msg.includes("foreign") || msg.includes("violat")));
62
+ }
63
+ function normalizeBaseUrl(baseUrl) {
64
+ return baseUrl.replace(/\/+$/, "");
65
+ }
66
+ async function sleep(ms) {
67
+ await new Promise((r) => setTimeout(r, ms));
68
+ }
69
+ async function validateArtifactPersistence(client, input) {
70
+ // Use API-key compatible entity CRUD endpoints for validation.
71
+ // The web UI endpoints (/api/artifacts/*, /api/work-artifacts/*) are session-authenticated (401 for API keys).
72
+ const detail = await client.rawRequest("GET", `/api/entities?type=artifact&id=${encodeURIComponent(input.artifactId)}`);
73
+ const rows = detail && typeof detail === "object" ? detail.data : null;
74
+ const artifact = Array.isArray(rows) ? rows.find((r) => r && typeof r === "object" && r.id === input.artifactId) : null;
75
+ const artifactOk = artifact && typeof artifact === "object" && typeof artifact.id === "string" && artifact.id === input.artifactId;
76
+ const linkedOk = artifactOk &&
77
+ typeof artifact.entity_type === "string" &&
78
+ typeof artifact.entity_id === "string" &&
79
+ artifact.entity_type === input.entity_type &&
80
+ artifact.entity_id === input.entity_id;
81
+ return { artifact_detail_ok: Boolean(artifactOk), linked_ok: Boolean(linkedOk) };
82
+ }
83
+ export async function registerArtifact(client, baseUrl, input) {
84
+ const warnings = [];
85
+ const errors = validateRegisterArtifactInput(input);
86
+ if (errors.length > 0) {
87
+ return {
88
+ ok: false,
89
+ artifact_id: null,
90
+ artifact_url: null,
91
+ created: false,
92
+ persistence: {
93
+ checked: false,
94
+ artifact_detail_ok: false,
95
+ linked_ok: false,
96
+ attempts: 0,
97
+ last_error: errors.join("; "),
98
+ },
99
+ warnings: [],
100
+ };
101
+ }
102
+ const requestedId = normalizeText(input.artifact_id);
103
+ const desiredId = requestedId && isUuid(requestedId) ? requestedId : randomUUID();
104
+ const artifactUrl = `${normalizeBaseUrl(baseUrl)}/artifacts/${desiredId}`;
105
+ const status = normalizeText(input.status) || "draft";
106
+ const createdByType = normalizeText(input.created_by_type) || "human";
107
+ const createdById = resolveCreatedById(client, input);
108
+ const createdBy = createdById ? { created_by_type: createdByType, created_by_id: createdById } : { created_by_type: createdByType };
109
+ const metadata = {
110
+ ...(input.metadata && typeof input.metadata === "object" && !Array.isArray(input.metadata)
111
+ ? input.metadata
112
+ : {}),
113
+ };
114
+ if (input.external_url)
115
+ metadata.external_url = String(input.external_url);
116
+ if (input.preview_markdown) {
117
+ const preview = String(input.preview_markdown);
118
+ metadata.preview_markdown =
119
+ preview.length > MAX_PREVIEW_MARKDOWN
120
+ ? preview.slice(0, MAX_PREVIEW_MARKDOWN)
121
+ : preview;
122
+ if (preview.length > MAX_PREVIEW_MARKDOWN)
123
+ metadata.preview_truncated = true;
124
+ }
125
+ const metadataInitiativeId = typeof metadata.initiative_id === "string" && metadata.initiative_id.trim().length > 0
126
+ ? metadata.initiative_id.trim()
127
+ : null;
128
+ const initiativeIdHint = input.entity_type === "initiative" ? input.entity_id : metadataInitiativeId;
129
+ let entity = null;
130
+ let created = false;
131
+ // Attempt idempotent create using a client-provided UUID id (preferred).
132
+ try {
133
+ try {
134
+ entity = await client.createEntity("artifact", {
135
+ id: desiredId,
136
+ name: input.name,
137
+ description: input.description ?? undefined,
138
+ artifact_type: input.artifact_type,
139
+ entity_type: input.entity_type,
140
+ entity_id: input.entity_id,
141
+ ...(initiativeIdHint ? { initiative_id: initiativeIdHint } : {}),
142
+ artifact_url: artifactUrl,
143
+ status,
144
+ metadata,
145
+ ...createdBy,
146
+ });
147
+ created = true;
148
+ }
149
+ catch (err) {
150
+ if (!isArtifactTypeConstraintError(err)) {
151
+ throw err;
152
+ }
153
+ warnings.push(`artifact_type rejected; retrying with shared.project_handbook`);
154
+ metadata.requested_artifact_type = input.artifact_type;
155
+ entity = await client.createEntity("artifact", {
156
+ id: desiredId,
157
+ name: input.name,
158
+ description: input.description ?? undefined,
159
+ artifact_type: "shared.project_handbook",
160
+ entity_type: input.entity_type,
161
+ entity_id: input.entity_id,
162
+ ...(initiativeIdHint ? { initiative_id: initiativeIdHint } : {}),
163
+ artifact_url: artifactUrl,
164
+ status,
165
+ metadata,
166
+ ...createdBy,
167
+ });
168
+ created = true;
169
+ }
170
+ }
171
+ catch (err) {
172
+ warnings.push(`artifact create with explicit id failed: ${safeErrorMessage(err)}`);
173
+ // Fallback: create without id, then patch artifact_url once we know the server id.
174
+ try {
175
+ entity = await client.createEntity("artifact", {
176
+ name: input.name,
177
+ description: input.description ?? undefined,
178
+ artifact_type: input.artifact_type,
179
+ entity_type: input.entity_type,
180
+ entity_id: input.entity_id,
181
+ ...(initiativeIdHint ? { initiative_id: initiativeIdHint } : {}),
182
+ artifact_url: input.external_url ?? `${normalizeBaseUrl(baseUrl)}/artifacts/pending`,
183
+ status,
184
+ metadata,
185
+ ...createdBy,
186
+ });
187
+ created = true;
188
+ }
189
+ catch (inner) {
190
+ if (!isArtifactTypeConstraintError(inner)) {
191
+ throw inner;
192
+ }
193
+ warnings.push(`artifact_type rejected; retrying with shared.project_handbook`);
194
+ metadata.requested_artifact_type = input.artifact_type;
195
+ entity = await client.createEntity("artifact", {
196
+ name: input.name,
197
+ description: input.description ?? undefined,
198
+ artifact_type: "shared.project_handbook",
199
+ entity_type: input.entity_type,
200
+ entity_id: input.entity_id,
201
+ ...(initiativeIdHint ? { initiative_id: initiativeIdHint } : {}),
202
+ artifact_url: input.external_url ?? `${normalizeBaseUrl(baseUrl)}/artifacts/pending`,
203
+ status,
204
+ metadata,
205
+ ...createdBy,
206
+ });
207
+ created = true;
208
+ }
209
+ }
210
+ const artifactId = entity && typeof entity === "object" && typeof entity.id === "string"
211
+ ? String(entity.id)
212
+ : null;
213
+ let finalArtifactUrl = artifactId
214
+ ? `${normalizeBaseUrl(baseUrl)}/artifacts/${artifactId}`
215
+ : null;
216
+ if (artifactId) {
217
+ try {
218
+ await client.updateEntity("artifact", artifactId, {
219
+ artifact_url: finalArtifactUrl,
220
+ });
221
+ }
222
+ catch (err) {
223
+ warnings.push(`artifact_url patch failed: ${safeErrorMessage(err)}`);
224
+ // Keep whatever we sent originally.
225
+ finalArtifactUrl = typeof entity?.artifact_url === "string" ? entity.artifact_url : finalArtifactUrl;
226
+ }
227
+ }
228
+ const shouldValidate = input.validate_persistence === true;
229
+ const persistence = {
230
+ checked: false,
231
+ artifact_detail_ok: false,
232
+ linked_ok: false,
233
+ attempts: 0,
234
+ last_error: null,
235
+ };
236
+ if (shouldValidate && artifactId) {
237
+ persistence.checked = true;
238
+ const maxAttempts = 4;
239
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
240
+ persistence.attempts = attempt;
241
+ try {
242
+ const result = await validateArtifactPersistence(client, {
243
+ artifactId,
244
+ entity_type: input.entity_type,
245
+ entity_id: input.entity_id,
246
+ });
247
+ persistence.artifact_detail_ok = result.artifact_detail_ok;
248
+ persistence.linked_ok = result.linked_ok;
249
+ if (result.artifact_detail_ok && result.linked_ok) {
250
+ break;
251
+ }
252
+ }
253
+ catch (err) {
254
+ persistence.last_error = safeErrorMessage(err);
255
+ }
256
+ // quick backoff for eventual consistency
257
+ await sleep(250 * attempt);
258
+ }
259
+ if (!persistence.artifact_detail_ok || !persistence.linked_ok) {
260
+ warnings.push(`artifact persistence check failed (detail_ok=${persistence.artifact_detail_ok}, linked_ok=${persistence.linked_ok})`);
261
+ }
262
+ }
263
+ return {
264
+ ok: Boolean(artifactId),
265
+ artifact_id: artifactId,
266
+ artifact_url: finalArtifactUrl,
267
+ created,
268
+ persistence,
269
+ warnings,
270
+ };
271
+ }