@useorgx/openclaw-plugin 0.4.5 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +333 -26
- package/dashboard/dist/assets/B3ziCA02.js +8 -0
- package/dashboard/dist/assets/BNeJ0kpF.js +1 -0
- package/dashboard/dist/assets/BzkiMPmM.js +215 -0
- package/dashboard/dist/assets/CUV9IHHi.js +1 -0
- package/dashboard/dist/assets/CpJsfbXo.js +9 -0
- package/dashboard/dist/assets/Ie7d9Iq2.css +1 -0
- package/dashboard/dist/assets/sAhvFnpk.js +4 -0
- package/dashboard/dist/index.html +5 -5
- package/dist/activity-actor-fields.d.ts +3 -0
- package/dist/activity-actor-fields.js +128 -0
- package/dist/activity-store.d.ts +28 -0
- package/dist/activity-store.js +257 -0
- package/dist/agent-context-store.d.ts +19 -0
- package/dist/agent-context-store.js +60 -3
- package/dist/agent-suite.d.ts +83 -0
- package/dist/agent-suite.js +615 -0
- package/dist/artifacts/register-artifact.d.ts +47 -0
- package/dist/artifacts/register-artifact.js +271 -0
- package/dist/auth-store.js +8 -13
- package/dist/contracts/client.d.ts +23 -1
- package/dist/contracts/client.js +127 -8
- package/dist/contracts/types.d.ts +194 -1
- package/dist/entity-comment-store.d.ts +29 -0
- package/dist/entity-comment-store.js +190 -0
- package/dist/hooks/post-reporting-event.mjs +326 -0
- package/dist/http-handler.d.ts +7 -1
- package/dist/http-handler.js +4500 -534
- package/dist/index.js +1078 -68
- package/dist/local-openclaw.js +8 -0
- package/dist/mcp-client-setup.js +145 -28
- package/dist/mcp-http-handler.d.ts +17 -0
- package/dist/mcp-http-handler.js +144 -3
- package/dist/next-up-queue-store.d.ts +31 -0
- package/dist/next-up-queue-store.js +169 -0
- package/dist/openclaw.plugin.json +1 -1
- package/dist/outbox.d.ts +1 -1
- package/dist/runtime-instance-store.d.ts +1 -1
- package/dist/runtime-instance-store.js +19 -2
- package/dist/skill-pack-state.d.ts +69 -0
- package/dist/skill-pack-state.js +232 -0
- package/dist/worker-supervisor.d.ts +25 -0
- package/dist/worker-supervisor.js +77 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +15 -1
- package/skills/orgx-design-agent/SKILL.md +38 -0
- package/skills/orgx-engineering-agent/SKILL.md +55 -0
- package/skills/orgx-marketing-agent/SKILL.md +40 -0
- package/skills/orgx-operations-agent/SKILL.md +40 -0
- package/skills/orgx-orchestrator-agent/SKILL.md +45 -0
- package/skills/orgx-product-agent/SKILL.md +39 -0
- package/skills/orgx-sales-agent/SKILL.md +40 -0
- package/skills/ship/SKILL.md +63 -0
- package/dashboard/dist/assets/B68j2crt.js +0 -1
- package/dashboard/dist/assets/BZZ-fiJx.js +0 -32
- package/dashboard/dist/assets/BoXlCHKa.js +0 -9
- package/dashboard/dist/assets/Bq9x_Xyh.css +0 -1
- package/dashboard/dist/assets/DBhrRVdp.js +0 -1
- package/dashboard/dist/assets/DD1jv1Hd.js +0 -8
- package/dashboard/dist/assets/DNjbmawF.js +0 -214
|
@@ -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
|
+
}
|
package/dist/auth-store.js
CHANGED
|
@@ -14,6 +14,9 @@ function installationFile() {
|
|
|
14
14
|
function isUserScopedApiKey(apiKey) {
|
|
15
15
|
return apiKey.trim().toLowerCase().startsWith('oxk_');
|
|
16
16
|
}
|
|
17
|
+
function isUuid(value) {
|
|
18
|
+
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);
|
|
19
|
+
}
|
|
17
20
|
function ensureAuthDir() {
|
|
18
21
|
const dir = authDir();
|
|
19
22
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
@@ -49,17 +52,6 @@ export function readPersistedAuth() {
|
|
|
49
52
|
if (!parsed || typeof parsed.apiKey !== 'string' || parsed.apiKey.trim().length === 0) {
|
|
50
53
|
return null;
|
|
51
54
|
}
|
|
52
|
-
if (isUserScopedApiKey(parsed.apiKey) &&
|
|
53
|
-
typeof parsed.userId === 'string' &&
|
|
54
|
-
parsed.userId.trim().length > 0) {
|
|
55
|
-
const sanitized = {
|
|
56
|
-
...parsed,
|
|
57
|
-
userId: null,
|
|
58
|
-
updatedAt: new Date().toISOString(),
|
|
59
|
-
};
|
|
60
|
-
writeJsonFileAtomicSync(file, sanitized, 0o600);
|
|
61
|
-
return sanitized;
|
|
62
|
-
}
|
|
63
55
|
return parsed;
|
|
64
56
|
}
|
|
65
57
|
catch {
|
|
@@ -70,9 +62,12 @@ export function writePersistedAuth(input) {
|
|
|
70
62
|
ensureAuthDir();
|
|
71
63
|
const now = new Date().toISOString();
|
|
72
64
|
const existing = readPersistedAuth();
|
|
73
|
-
const
|
|
65
|
+
const rawUserId = typeof input.userId === 'string' ? input.userId.trim() : '';
|
|
66
|
+
const normalizedUserId = rawUserId.length === 0
|
|
74
67
|
? null
|
|
75
|
-
: input.
|
|
68
|
+
: isUserScopedApiKey(input.apiKey)
|
|
69
|
+
? (isUuid(rawUserId) ? rawUserId : null)
|
|
70
|
+
: rawUserId;
|
|
76
71
|
const next = {
|
|
77
72
|
apiKey: input.apiKey,
|
|
78
73
|
source: input.source,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Uses native fetch — no external dependencies.
|
|
8
8
|
*/
|
|
9
|
-
import type { OrgSnapshot, SyncPayload, SyncResponse, SpawnGuardResult, QualityScore, Entity, EntityListFilters, EmitActivityRequest, EmitActivityResponse, ApplyChangesetRequest, ApplyChangesetResponse, LiveActivityItem, SessionTreeResponse, HandoffSummary, CheckpointSummary, RestoreRequest, DelegationPreflightResult, BillingStatus, BillingCheckoutRequest, BillingUrlResult } from "./types.js";
|
|
9
|
+
import type { OrgSnapshot, SyncPayload, SyncResponse, SpawnGuardResult, QualityScore, Entity, EntityListFilters, EmitActivityRequest, EmitActivityResponse, ApplyChangesetRequest, ApplyChangesetResponse, RecordRunOutcomeRequest, RecordRunOutcomeResponse, RecordRunRetroRequest, RecordRunRetroResponse, LiveActivityItem, SessionTreeResponse, HandoffSummary, CheckpointSummary, RestoreRequest, DelegationPreflightResult, BillingStatus, BillingCheckoutRequest, BillingUrlResult, KickoffContextRequest, KickoffContextResponse, SkillPack } from "./types.js";
|
|
10
10
|
export type DecisionAction = "approve" | "reject";
|
|
11
11
|
export type RunAction = "pause" | "resume" | "cancel" | "rollback";
|
|
12
12
|
export interface DecisionActionResult {
|
|
@@ -26,6 +26,7 @@ export declare class OrgXClient {
|
|
|
26
26
|
baseUrl?: string;
|
|
27
27
|
}): void;
|
|
28
28
|
getBaseUrl(): string;
|
|
29
|
+
getUserId(): string;
|
|
29
30
|
/**
|
|
30
31
|
* Low-level authenticated request helper.
|
|
31
32
|
*
|
|
@@ -41,6 +42,25 @@ export declare class OrgXClient {
|
|
|
41
42
|
private buildQuery;
|
|
42
43
|
getOrgSnapshot(): Promise<OrgSnapshot>;
|
|
43
44
|
syncMemory(payload: SyncPayload): Promise<SyncResponse>;
|
|
45
|
+
getKickoffContext(payload: KickoffContextRequest): Promise<KickoffContextResponse>;
|
|
46
|
+
getSkillPack(input?: {
|
|
47
|
+
name?: string;
|
|
48
|
+
ifNoneMatch?: string | null;
|
|
49
|
+
}): Promise<{
|
|
50
|
+
ok: true;
|
|
51
|
+
notModified: true;
|
|
52
|
+
etag: string | null;
|
|
53
|
+
pack: null;
|
|
54
|
+
} | {
|
|
55
|
+
ok: true;
|
|
56
|
+
notModified: false;
|
|
57
|
+
etag: string | null;
|
|
58
|
+
pack: SkillPack;
|
|
59
|
+
} | {
|
|
60
|
+
ok: false;
|
|
61
|
+
status: number;
|
|
62
|
+
error: string;
|
|
63
|
+
}>;
|
|
44
64
|
delegationPreflight(payload: {
|
|
45
65
|
intent: string;
|
|
46
66
|
acceptanceCriteria?: string[];
|
|
@@ -80,6 +100,8 @@ export declare class OrgXClient {
|
|
|
80
100
|
createBillingPortal(): Promise<BillingUrlResult>;
|
|
81
101
|
emitActivity(payload: EmitActivityRequest): Promise<EmitActivityResponse>;
|
|
82
102
|
applyChangeset(payload: ApplyChangesetRequest): Promise<ApplyChangesetResponse>;
|
|
103
|
+
recordRunOutcome(payload: RecordRunOutcomeRequest): Promise<RecordRunOutcomeResponse>;
|
|
104
|
+
recordRunRetro(payload: RecordRunRetroRequest): Promise<RecordRunRetroResponse>;
|
|
83
105
|
getLiveSessions(params?: {
|
|
84
106
|
limit?: number;
|
|
85
107
|
initiative?: string | null;
|
package/dist/contracts/client.js
CHANGED
|
@@ -6,13 +6,43 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Uses native fetch — no external dependencies.
|
|
8
8
|
*/
|
|
9
|
-
const
|
|
9
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 25_000;
|
|
10
|
+
const DEFAULT_LIVE_TIMEOUT_MS = 30_000;
|
|
11
|
+
const DEFAULT_SYNC_TIMEOUT_MS = 45_000;
|
|
10
12
|
const USER_AGENT = "OrgX-Clawdbot-Plugin/1.0";
|
|
11
13
|
const DECISION_MUTATION_CONCURRENCY = 6;
|
|
12
14
|
const DEFAULT_CLIENT_BASE_URL = "https://www.useorgx.com";
|
|
13
15
|
function isUserScopedApiKey(apiKey) {
|
|
14
16
|
return apiKey.trim().toLowerCase().startsWith("oxk_");
|
|
15
17
|
}
|
|
18
|
+
function parseTimeoutMsEnv(name) {
|
|
19
|
+
const raw = process.env[name];
|
|
20
|
+
if (!raw)
|
|
21
|
+
return null;
|
|
22
|
+
const parsed = Number(raw);
|
|
23
|
+
if (!Number.isFinite(parsed))
|
|
24
|
+
return null;
|
|
25
|
+
// Keep it sane: timeouts below 1s tend to create pathological "offline" loops.
|
|
26
|
+
return Math.max(1_000, Math.floor(parsed));
|
|
27
|
+
}
|
|
28
|
+
function resolveRequestTimeoutMs(path) {
|
|
29
|
+
const configured = parseTimeoutMsEnv("ORGX_HTTP_TIMEOUT_MS") ??
|
|
30
|
+
parseTimeoutMsEnv("ORGX_API_TIMEOUT_MS");
|
|
31
|
+
const base = configured ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
32
|
+
// Live endpoints frequently return larger payloads (sessions, activity, agents).
|
|
33
|
+
if (path.startsWith("/api/client/live/")) {
|
|
34
|
+
return Math.max(base, DEFAULT_LIVE_TIMEOUT_MS);
|
|
35
|
+
}
|
|
36
|
+
// Sync can include a full org snapshot and may take longer than typical CRUD.
|
|
37
|
+
if (path === "/api/client/sync") {
|
|
38
|
+
return Math.max(base, DEFAULT_SYNC_TIMEOUT_MS);
|
|
39
|
+
}
|
|
40
|
+
// Handoffs can require server-side aggregation.
|
|
41
|
+
if (path === "/api/client/handoffs") {
|
|
42
|
+
return Math.max(base, DEFAULT_LIVE_TIMEOUT_MS);
|
|
43
|
+
}
|
|
44
|
+
return base;
|
|
45
|
+
}
|
|
16
46
|
function normalizeHost(value) {
|
|
17
47
|
return value.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
18
48
|
}
|
|
@@ -50,17 +80,16 @@ export class OrgXClient {
|
|
|
50
80
|
constructor(apiKey, baseUrl, userId) {
|
|
51
81
|
this.apiKey = apiKey;
|
|
52
82
|
this.baseUrl = normalizeClientBaseUrl(baseUrl, DEFAULT_CLIENT_BASE_URL);
|
|
53
|
-
|
|
83
|
+
// Keep userId available even for oxk_ keys (it can be used as created_by_id for certain writes),
|
|
84
|
+
// but only send it as a header for non-user-scoped keys.
|
|
85
|
+
this.userId = userId || "";
|
|
54
86
|
}
|
|
55
87
|
setCredentials(input) {
|
|
56
88
|
if (typeof input.apiKey === "string") {
|
|
57
89
|
this.apiKey = input.apiKey;
|
|
58
|
-
if (isUserScopedApiKey(this.apiKey)) {
|
|
59
|
-
this.userId = "";
|
|
60
|
-
}
|
|
61
90
|
}
|
|
62
91
|
if (typeof input.userId === "string") {
|
|
63
|
-
this.userId =
|
|
92
|
+
this.userId = input.userId;
|
|
64
93
|
}
|
|
65
94
|
if (typeof input.baseUrl === "string" && input.baseUrl.trim().length > 0) {
|
|
66
95
|
this.baseUrl = normalizeClientBaseUrl(input.baseUrl, this.baseUrl);
|
|
@@ -69,6 +98,9 @@ export class OrgXClient {
|
|
|
69
98
|
getBaseUrl() {
|
|
70
99
|
return this.baseUrl;
|
|
71
100
|
}
|
|
101
|
+
getUserId() {
|
|
102
|
+
return this.userId;
|
|
103
|
+
}
|
|
72
104
|
// ===========================================================================
|
|
73
105
|
// HTTP helpers
|
|
74
106
|
// ===========================================================================
|
|
@@ -85,7 +117,8 @@ export class OrgXClient {
|
|
|
85
117
|
async request(method, path, body) {
|
|
86
118
|
const url = `${this.baseUrl}${path}`;
|
|
87
119
|
const controller = new AbortController();
|
|
88
|
-
const
|
|
120
|
+
const timeoutMs = resolveRequestTimeoutMs(path);
|
|
121
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
89
122
|
try {
|
|
90
123
|
const headers = {
|
|
91
124
|
"Content-Type": "application/json",
|
|
@@ -124,7 +157,7 @@ export class OrgXClient {
|
|
|
124
157
|
}
|
|
125
158
|
catch (err) {
|
|
126
159
|
if (err instanceof DOMException && err.name === "AbortError") {
|
|
127
|
-
throw new Error(`OrgX API ${method} ${path} timed out after ${
|
|
160
|
+
throw new Error(`OrgX API ${method} ${path} timed out after ${timeoutMs}ms`);
|
|
128
161
|
}
|
|
129
162
|
throw err;
|
|
130
163
|
}
|
|
@@ -194,6 +227,72 @@ export class OrgXClient {
|
|
|
194
227
|
}
|
|
195
228
|
return response;
|
|
196
229
|
}
|
|
230
|
+
// ===========================================================================
|
|
231
|
+
// Kickoff Context
|
|
232
|
+
// ===========================================================================
|
|
233
|
+
async getKickoffContext(payload) {
|
|
234
|
+
return await this.post("/api/client/kickoff-context", payload ?? {});
|
|
235
|
+
}
|
|
236
|
+
// ===========================================================================
|
|
237
|
+
// Skill Packs (ETag-aware)
|
|
238
|
+
// ===========================================================================
|
|
239
|
+
async getSkillPack(input) {
|
|
240
|
+
const name = (input?.name ?? "").trim() || "orgx-agent-suite";
|
|
241
|
+
const url = `${this.baseUrl}/api/client/skill-pack?name=${encodeURIComponent(name)}`;
|
|
242
|
+
const controller = new AbortController();
|
|
243
|
+
const timeoutMs = resolveRequestTimeoutMs("/api/client/skill-pack");
|
|
244
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
245
|
+
try {
|
|
246
|
+
const headers = {
|
|
247
|
+
"Content-Type": "application/json",
|
|
248
|
+
"User-Agent": USER_AGENT,
|
|
249
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
250
|
+
};
|
|
251
|
+
if (this.userId && !isUserScopedApiKey(this.apiKey)) {
|
|
252
|
+
headers["X-Orgx-User-Id"] = this.userId;
|
|
253
|
+
}
|
|
254
|
+
const ifNoneMatch = (input?.ifNoneMatch ?? "").trim();
|
|
255
|
+
if (ifNoneMatch) {
|
|
256
|
+
headers["If-None-Match"] = ifNoneMatch;
|
|
257
|
+
}
|
|
258
|
+
const response = await fetch(url, {
|
|
259
|
+
method: "GET",
|
|
260
|
+
headers,
|
|
261
|
+
signal: controller.signal,
|
|
262
|
+
});
|
|
263
|
+
const etag = response.headers.get("etag");
|
|
264
|
+
if (response.status === 304) {
|
|
265
|
+
return { ok: true, notModified: true, etag, pack: null };
|
|
266
|
+
}
|
|
267
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
268
|
+
const payload = contentType.includes("application/json")
|
|
269
|
+
? (await response.json().catch(() => null))
|
|
270
|
+
: null;
|
|
271
|
+
if (!response.ok) {
|
|
272
|
+
const detail = payload && typeof payload === "object" && "error" in payload && typeof payload.error === "string"
|
|
273
|
+
? payload.error
|
|
274
|
+
: `${response.status} ${response.statusText}`;
|
|
275
|
+
return { ok: false, status: response.status, error: detail };
|
|
276
|
+
}
|
|
277
|
+
if (payload && typeof payload === "object" && payload.ok === true && payload.data) {
|
|
278
|
+
return { ok: true, notModified: false, etag, pack: payload.data };
|
|
279
|
+
}
|
|
280
|
+
return { ok: false, status: 502, error: "SkillPack response was invalid" };
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
284
|
+
return {
|
|
285
|
+
ok: false,
|
|
286
|
+
status: 504,
|
|
287
|
+
error: `OrgX API GET /api/client/skill-pack timed out after ${timeoutMs}ms`,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
return { ok: false, status: 502, error: err instanceof Error ? err.message : "SkillPack request failed" };
|
|
291
|
+
}
|
|
292
|
+
finally {
|
|
293
|
+
clearTimeout(timeout);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
197
296
|
async delegationPreflight(payload) {
|
|
198
297
|
return this.post("/api/client/delegation/preflight", payload);
|
|
199
298
|
}
|
|
@@ -320,6 +419,26 @@ export class OrgXClient {
|
|
|
320
419
|
}
|
|
321
420
|
return response;
|
|
322
421
|
}
|
|
422
|
+
async recordRunOutcome(payload) {
|
|
423
|
+
const response = await this.post("/api/client/live/runs/outcomes/record", payload);
|
|
424
|
+
if (response &&
|
|
425
|
+
typeof response === "object" &&
|
|
426
|
+
"data" in response &&
|
|
427
|
+
response.data) {
|
|
428
|
+
return response.data;
|
|
429
|
+
}
|
|
430
|
+
return response;
|
|
431
|
+
}
|
|
432
|
+
async recordRunRetro(payload) {
|
|
433
|
+
const response = await this.post("/api/client/live/runs/retro", payload);
|
|
434
|
+
if (response &&
|
|
435
|
+
typeof response === "object" &&
|
|
436
|
+
"data" in response &&
|
|
437
|
+
response.data) {
|
|
438
|
+
return response.data;
|
|
439
|
+
}
|
|
440
|
+
return response;
|
|
441
|
+
}
|
|
323
442
|
// ===========================================================================
|
|
324
443
|
// Live Sessions + Activity + Handoffs
|
|
325
444
|
// ===========================================================================
|