@prismer/sdk 1.9.22 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -94
- package/dist/cli.js +2006 -439
- package/dist/index.d.mts +579 -61
- package/dist/index.d.ts +579 -61
- package/dist/index.js +620 -83
- package/dist/index.mjs +615 -82
- package/package.json +4 -3
- package/smallicon +6 -0
package/dist/index.mjs
CHANGED
|
@@ -1498,6 +1498,106 @@ var ENVIRONMENTS = {
|
|
|
1498
1498
|
production: "https://prismer.cloud"
|
|
1499
1499
|
};
|
|
1500
1500
|
|
|
1501
|
+
// src/im/workspace/tasks.ts
|
|
1502
|
+
var CARD_KANBAN_STATUSES = /* @__PURE__ */ new Set([
|
|
1503
|
+
"backlog",
|
|
1504
|
+
"todo",
|
|
1505
|
+
"in_progress",
|
|
1506
|
+
"review",
|
|
1507
|
+
"completed",
|
|
1508
|
+
"failed",
|
|
1509
|
+
"cancelled",
|
|
1510
|
+
"pending",
|
|
1511
|
+
"assigned",
|
|
1512
|
+
"running",
|
|
1513
|
+
"done"
|
|
1514
|
+
]);
|
|
1515
|
+
var CARD_PRIORITIES = /* @__PURE__ */ new Set([
|
|
1516
|
+
"low",
|
|
1517
|
+
"medium",
|
|
1518
|
+
"high",
|
|
1519
|
+
"urgent"
|
|
1520
|
+
]);
|
|
1521
|
+
function readCardKanbanMetadata(source) {
|
|
1522
|
+
const metadata = readSourceMetadata(source);
|
|
1523
|
+
const nestedKanban = readRecord(metadata.kanban);
|
|
1524
|
+
const fields = nestedKanban ? { ...metadata, ...nestedKanban } : metadata;
|
|
1525
|
+
return toCanonicalCardKanbanMetadata(fields);
|
|
1526
|
+
}
|
|
1527
|
+
function writeCardKanbanMetadata(metadata, kanban) {
|
|
1528
|
+
const previous = readRecord(metadata) ?? {};
|
|
1529
|
+
const previousKanban = readRecord(previous.kanban);
|
|
1530
|
+
const currentView = toWritableCardKanbanMetadata(readCardKanbanMetadata(previous));
|
|
1531
|
+
const nextView = toWritableCardKanbanMetadata(toCanonicalCardKanbanMetadata(kanban));
|
|
1532
|
+
const nextKanban = toWritableCardKanbanMetadata({
|
|
1533
|
+
...previousKanban ? omitLegacyOrder(previousKanban) : {},
|
|
1534
|
+
...currentView,
|
|
1535
|
+
...nextView
|
|
1536
|
+
});
|
|
1537
|
+
return {
|
|
1538
|
+
...omitLegacyOrder(previous),
|
|
1539
|
+
...nextKanban,
|
|
1540
|
+
kanban: nextKanban
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
function readSourceMetadata(source) {
|
|
1544
|
+
const record = readRecord(source);
|
|
1545
|
+
if (!record) return {};
|
|
1546
|
+
return readRecord(record.metadata) ?? record;
|
|
1547
|
+
}
|
|
1548
|
+
function toCanonicalCardKanbanMetadata(source) {
|
|
1549
|
+
const record = readRecord(source) ?? {};
|
|
1550
|
+
const metadata = {};
|
|
1551
|
+
const columnId = readString(record.columnId);
|
|
1552
|
+
const cardOrder = readFiniteNumber(record.cardOrder) ?? readFiniteNumber(record.order);
|
|
1553
|
+
const cardStatus = readCardKanbanStatus(record.cardStatus);
|
|
1554
|
+
const cardPriority = readCardPriority(record.cardPriority);
|
|
1555
|
+
const cardLabels = readCardLabels(record.cardLabels);
|
|
1556
|
+
if (columnId) metadata.columnId = columnId;
|
|
1557
|
+
if (cardOrder !== void 0) metadata.cardOrder = cardOrder;
|
|
1558
|
+
if (cardStatus) metadata.cardStatus = cardStatus;
|
|
1559
|
+
if (cardPriority) metadata.cardPriority = cardPriority;
|
|
1560
|
+
if (cardLabels) metadata.cardLabels = cardLabels;
|
|
1561
|
+
return metadata;
|
|
1562
|
+
}
|
|
1563
|
+
function toWritableCardKanbanMetadata(view) {
|
|
1564
|
+
return view.cardStatus === "done" ? { ...view, cardStatus: "completed" } : view;
|
|
1565
|
+
}
|
|
1566
|
+
function omitLegacyOrder(record) {
|
|
1567
|
+
const { order: _legacyOrder, ...rest } = record;
|
|
1568
|
+
return rest;
|
|
1569
|
+
}
|
|
1570
|
+
function readRecord(value) {
|
|
1571
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1572
|
+
}
|
|
1573
|
+
function readString(value) {
|
|
1574
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1575
|
+
}
|
|
1576
|
+
function readFiniteNumber(value) {
|
|
1577
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1578
|
+
}
|
|
1579
|
+
function readCardKanbanStatus(value) {
|
|
1580
|
+
return typeof value === "string" && CARD_KANBAN_STATUSES.has(value) ? value : void 0;
|
|
1581
|
+
}
|
|
1582
|
+
function readCardPriority(value) {
|
|
1583
|
+
return typeof value === "string" && CARD_PRIORITIES.has(value) ? value : void 0;
|
|
1584
|
+
}
|
|
1585
|
+
function readCardLabels(value) {
|
|
1586
|
+
const record = readRecord(value);
|
|
1587
|
+
if (!record) return void 0;
|
|
1588
|
+
const labels = {};
|
|
1589
|
+
const color = readString(record.color);
|
|
1590
|
+
const emoji = readString(record.emoji);
|
|
1591
|
+
const tagIds = Array.isArray(record.tagIds) && record.tagIds.every((tagId) => typeof tagId === "string") ? [...record.tagIds] : void 0;
|
|
1592
|
+
if (color) labels.color = color;
|
|
1593
|
+
if (emoji) labels.emoji = emoji;
|
|
1594
|
+
if (tagIds) labels.tagIds = tagIds;
|
|
1595
|
+
return Object.keys(labels).length > 0 ? labels : void 0;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// src/im/workspace/assets.ts
|
|
1599
|
+
var WORKSPACE_ASSETS_ROUTE = "/api/im/assets";
|
|
1600
|
+
|
|
1501
1601
|
// src/storage.ts
|
|
1502
1602
|
var MemoryStorage = class {
|
|
1503
1603
|
constructor() {
|
|
@@ -2949,10 +3049,13 @@ function createEnrichedExtractor(config) {
|
|
|
2949
3049
|
}
|
|
2950
3050
|
|
|
2951
3051
|
// src/evolution-runtime.ts
|
|
3052
|
+
var DEFAULT_OUTBOX_MAX_ATTEMPTS = 5;
|
|
2952
3053
|
var EvolutionRuntime = class {
|
|
2953
3054
|
constructor(client, config) {
|
|
2954
3055
|
this.client = client;
|
|
2955
3056
|
this.outbox = [];
|
|
3057
|
+
/** Entries that exceeded `outboxMaxAttempts`. Public-readable for tests/ops. */
|
|
3058
|
+
this.deadLetter = [];
|
|
2956
3059
|
this.started = false;
|
|
2957
3060
|
// Session tracking
|
|
2958
3061
|
this._sessions = [];
|
|
@@ -2962,7 +3065,8 @@ var EvolutionRuntime = class {
|
|
|
2962
3065
|
enrichment: config?.enrichment ?? { mode: "rules" },
|
|
2963
3066
|
scope: config?.scope ?? "global",
|
|
2964
3067
|
outboxMaxSize: config?.outboxMaxSize ?? 50,
|
|
2965
|
-
outboxFlushMs: config?.outboxFlushMs ?? 5e3
|
|
3068
|
+
outboxFlushMs: config?.outboxFlushMs ?? 5e3,
|
|
3069
|
+
outboxMaxAttempts: config?.outboxMaxAttempts ?? DEFAULT_OUTBOX_MAX_ATTEMPTS
|
|
2966
3070
|
};
|
|
2967
3071
|
this.scope = this.config.scope;
|
|
2968
3072
|
this.cache = new EvolutionCache();
|
|
@@ -3028,8 +3132,9 @@ var EvolutionRuntime = class {
|
|
|
3028
3132
|
confidence,
|
|
3029
3133
|
fromCache
|
|
3030
3134
|
};
|
|
3135
|
+
const narrowed = action === "apply_gene" || action === "create_suggested" || action === "none" ? action : "none";
|
|
3031
3136
|
return {
|
|
3032
|
-
action,
|
|
3137
|
+
action: narrowed,
|
|
3033
3138
|
geneId,
|
|
3034
3139
|
gene,
|
|
3035
3140
|
strategy,
|
|
@@ -3180,25 +3285,53 @@ var EvolutionRuntime = class {
|
|
|
3180
3285
|
} catch {
|
|
3181
3286
|
}
|
|
3182
3287
|
}
|
|
3183
|
-
/**
|
|
3288
|
+
/**
|
|
3289
|
+
* Flush outbox to server. Fire-and-forget contract: never blocks the caller's
|
|
3290
|
+
* critical path and never throws. Failed entries are retried until they hit
|
|
3291
|
+
* `outboxMaxAttempts`, then moved to `deadLetter` and reported via
|
|
3292
|
+
* `console.warn` so ops can detect a persistent failure.
|
|
3293
|
+
*/
|
|
3184
3294
|
async flush() {
|
|
3185
3295
|
if (this.outbox.length === 0) return;
|
|
3186
3296
|
const batch = this.outbox.splice(0, this.config.outboxMaxSize);
|
|
3187
|
-
const promises = batch.map(
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3297
|
+
const promises = batch.map(async (entry) => {
|
|
3298
|
+
try {
|
|
3299
|
+
const res = await this.client.record({
|
|
3300
|
+
gene_id: entry.geneId,
|
|
3301
|
+
signals: entry.signals.map((s) => s.type),
|
|
3302
|
+
outcome: entry.outcome,
|
|
3303
|
+
summary: entry.summary,
|
|
3304
|
+
score: entry.score,
|
|
3305
|
+
metadata: entry.metadata,
|
|
3306
|
+
scope: this.scope
|
|
3307
|
+
});
|
|
3308
|
+
if (!res.ok) {
|
|
3309
|
+
this._handleOutboxFailure(entry, res.error?.message ?? "record returned ok=false");
|
|
3310
|
+
}
|
|
3311
|
+
} catch (err) {
|
|
3312
|
+
this._handleOutboxFailure(entry, err instanceof Error ? err.message : String(err));
|
|
3313
|
+
}
|
|
3314
|
+
});
|
|
3200
3315
|
await Promise.allSettled(promises);
|
|
3201
3316
|
}
|
|
3317
|
+
/**
|
|
3318
|
+
* Bump the attempt counter and either re-queue (under ceiling) or move to
|
|
3319
|
+
* dead-letter. Always synchronous — caller's `flush()` already wraps in a
|
|
3320
|
+
* promise.allSettled.
|
|
3321
|
+
*/
|
|
3322
|
+
_handleOutboxFailure(entry, lastError) {
|
|
3323
|
+
const attempts = (entry.attempts ?? 0) + 1;
|
|
3324
|
+
const ceiling = this.config.outboxMaxAttempts;
|
|
3325
|
+
if (attempts >= ceiling) {
|
|
3326
|
+
const dropped = { ...entry, attempts, lastError, droppedAt: Date.now() };
|
|
3327
|
+
this.deadLetter.push(dropped);
|
|
3328
|
+
console.warn(
|
|
3329
|
+
`[EvolutionRuntime] outbox entry dropped after ${attempts} attempts: gene=${entry.geneId} outcome=${entry.outcome} error=${lastError}`
|
|
3330
|
+
);
|
|
3331
|
+
return;
|
|
3332
|
+
}
|
|
3333
|
+
this.outbox.push({ ...entry, attempts });
|
|
3334
|
+
}
|
|
3202
3335
|
};
|
|
3203
3336
|
|
|
3204
3337
|
// src/index.ts
|
|
@@ -3297,6 +3430,7 @@ var DirectClient = class {
|
|
|
3297
3430
|
content,
|
|
3298
3431
|
type: options?.type ?? "text",
|
|
3299
3432
|
metadata: options?.metadata,
|
|
3433
|
+
attachments: options?.attachments,
|
|
3300
3434
|
parentId: options?.parentId,
|
|
3301
3435
|
quotedMessageId: options?.quotedMessageId
|
|
3302
3436
|
});
|
|
@@ -3331,6 +3465,7 @@ var GroupsClient = class {
|
|
|
3331
3465
|
content,
|
|
3332
3466
|
type: options?.type ?? "text",
|
|
3333
3467
|
metadata: options?.metadata,
|
|
3468
|
+
attachments: options?.attachments,
|
|
3334
3469
|
parentId: options?.parentId,
|
|
3335
3470
|
quotedMessageId: options?.quotedMessageId
|
|
3336
3471
|
});
|
|
@@ -3409,6 +3544,7 @@ var MessagesClient = class {
|
|
|
3409
3544
|
content,
|
|
3410
3545
|
type: options?.type ?? "text",
|
|
3411
3546
|
metadata: options?.metadata,
|
|
3547
|
+
attachments: options?.attachments,
|
|
3412
3548
|
parentId: options?.parentId,
|
|
3413
3549
|
quotedMessageId: options?.quotedMessageId
|
|
3414
3550
|
});
|
|
@@ -3469,6 +3605,11 @@ var ContactsClient = class {
|
|
|
3469
3605
|
const query = {};
|
|
3470
3606
|
if (options?.type) query.type = options.type;
|
|
3471
3607
|
if (options?.capability) query.capability = options.capability;
|
|
3608
|
+
if (options?.status) query.status = options.status;
|
|
3609
|
+
if (options?.onlineOnly) query.onlineOnly = options.onlineOnly;
|
|
3610
|
+
if (options?.q) query.q = options.q;
|
|
3611
|
+
if (options?.limit) query.limit = options.limit;
|
|
3612
|
+
if (options?.offset) query.offset = options.offset;
|
|
3472
3613
|
return this._r("GET", "/api/im/discover", void 0, query);
|
|
3473
3614
|
}
|
|
3474
3615
|
// ─── Friend System (v1.8.0 P9) ─────────────────────────
|
|
@@ -3682,6 +3823,32 @@ var TasksClient = class {
|
|
|
3682
3823
|
async cancel(taskId) {
|
|
3683
3824
|
return this._r("DELETE", `/api/im/tasks/${taskId}`);
|
|
3684
3825
|
}
|
|
3826
|
+
/**
|
|
3827
|
+
* v2.0 release 200 §6.1 — unified state-machine transition.
|
|
3828
|
+
*
|
|
3829
|
+
* Drives every kanban / approve / reject / cancel / blocked / retry /
|
|
3830
|
+
* restore action through one endpoint. The 5 legacy endpoints
|
|
3831
|
+
* (start/complete/approve/reject/cancel) remain for backward
|
|
3832
|
+
* compatibility but new integrations should prefer this entrypoint.
|
|
3833
|
+
*
|
|
3834
|
+
* Server responds 409 (`code: 'invalid-transition'`) if the requested
|
|
3835
|
+
* `to` is not in the TRANSITIONS matrix from the current status, or
|
|
3836
|
+
* 403 (`code: 'forbidden'`) if the actor's tier is not in the rule's
|
|
3837
|
+
* `allowedActors`.
|
|
3838
|
+
*/
|
|
3839
|
+
async transition(taskId, options) {
|
|
3840
|
+
return this._r("POST", `/api/im/tasks/${taskId}/transition`, options);
|
|
3841
|
+
}
|
|
3842
|
+
/**
|
|
3843
|
+
* v2.0 release 200 §5.3 — admin escape-hatch.
|
|
3844
|
+
*
|
|
3845
|
+
* Bypasses the TRANSITIONS matrix. Restricted to workspace owner /
|
|
3846
|
+
* admin / trustTier>=4. Reason is required; the call is audit-logged
|
|
3847
|
+
* with `force_transition: true`. UI does NOT expose this — ops only.
|
|
3848
|
+
*/
|
|
3849
|
+
async forceTransition(taskId, options) {
|
|
3850
|
+
return this._r("POST", `/api/im/tasks/${taskId}/force-transition`, options);
|
|
3851
|
+
}
|
|
3685
3852
|
};
|
|
3686
3853
|
var MemoryClient = class {
|
|
3687
3854
|
constructor(_r) {
|
|
@@ -3810,9 +3977,118 @@ var SecurityClient = class {
|
|
|
3810
3977
|
return this._r("DELETE", `/api/im/conversations/${conversationId}/keys/${keyUserId}`);
|
|
3811
3978
|
}
|
|
3812
3979
|
};
|
|
3980
|
+
var EvolutionSkillsClient = class {
|
|
3981
|
+
constructor(_r) {
|
|
3982
|
+
this._r = _r;
|
|
3983
|
+
}
|
|
3984
|
+
/** List the skill catalog. Alias of search() for the v2.0 public surface. */
|
|
3985
|
+
async list(options) {
|
|
3986
|
+
return this.search(options);
|
|
3987
|
+
}
|
|
3988
|
+
/** Browse and search the skill catalog. */
|
|
3989
|
+
async search(options) {
|
|
3990
|
+
const query = {};
|
|
3991
|
+
if (options?.query) query.query = options.query;
|
|
3992
|
+
if (options?.category) query.category = options.category;
|
|
3993
|
+
if (options?.source) query.source = options.source;
|
|
3994
|
+
if (options?.compatibility) query.compatibility = options.compatibility;
|
|
3995
|
+
if (options?.sort) query.sort = options.sort;
|
|
3996
|
+
if (options?.page != null) query.page = String(options.page);
|
|
3997
|
+
if (options?.limit != null) query.limit = String(options.limit);
|
|
3998
|
+
return this._r("GET", "/api/im/skills/search", void 0, query);
|
|
3999
|
+
}
|
|
4000
|
+
/** Get skill catalog stats. */
|
|
4001
|
+
async stats() {
|
|
4002
|
+
return this._r("GET", "/api/im/skills/stats");
|
|
4003
|
+
}
|
|
4004
|
+
/** List available skill categories. */
|
|
4005
|
+
async categories() {
|
|
4006
|
+
return this._r("GET", "/api/im/skills/categories");
|
|
4007
|
+
}
|
|
4008
|
+
/** List trending skills. */
|
|
4009
|
+
async trending(limit) {
|
|
4010
|
+
const query = {};
|
|
4011
|
+
if (limit != null) query.limit = String(limit);
|
|
4012
|
+
return this._r("GET", "/api/im/skills/trending", void 0, query);
|
|
4013
|
+
}
|
|
4014
|
+
/** List skills created by the authenticated agent. */
|
|
4015
|
+
async created() {
|
|
4016
|
+
return this._r("GET", "/api/im/skills/created");
|
|
4017
|
+
}
|
|
4018
|
+
/** Get skill detail by slug or ID. */
|
|
4019
|
+
async get(slugOrId) {
|
|
4020
|
+
return this._r("GET", `/api/im/skills/${encodeURIComponent(slugOrId)}`);
|
|
4021
|
+
}
|
|
4022
|
+
/** Get full SKILL.md content and package metadata. */
|
|
4023
|
+
async content(slugOrId) {
|
|
4024
|
+
return this._r("GET", `/api/im/skills/${encodeURIComponent(slugOrId)}/content`);
|
|
4025
|
+
}
|
|
4026
|
+
/** Create/submit a workspace or community skill. */
|
|
4027
|
+
async create(input) {
|
|
4028
|
+
return this._r("POST", "/api/im/skills", input);
|
|
4029
|
+
}
|
|
4030
|
+
/** Update a skill. */
|
|
4031
|
+
async update(skillId, input) {
|
|
4032
|
+
return this._r("PATCH", `/api/im/skills/${encodeURIComponent(skillId)}`, input);
|
|
4033
|
+
}
|
|
4034
|
+
/** Soft-delete/deprecate a skill. */
|
|
4035
|
+
async delete(skillId) {
|
|
4036
|
+
return this._r("DELETE", `/api/im/skills/${encodeURIComponent(skillId)}`);
|
|
4037
|
+
}
|
|
4038
|
+
/** Install a skill for the authenticated agent. */
|
|
4039
|
+
async install(slugOrId, scope) {
|
|
4040
|
+
return this._r("POST", `/api/im/skills/${encodeURIComponent(slugOrId)}/install`, scope ? { scope } : void 0);
|
|
4041
|
+
}
|
|
4042
|
+
/** Uninstall a skill for the authenticated agent. */
|
|
4043
|
+
async uninstall(slugOrId, scope) {
|
|
4044
|
+
const query = scope ? { scope } : void 0;
|
|
4045
|
+
return this._r("DELETE", `/api/im/skills/${encodeURIComponent(slugOrId)}/install`, void 0, query);
|
|
4046
|
+
}
|
|
4047
|
+
/**
|
|
4048
|
+
* List installed skills. When agentId is supplied, this uses the v2.0 Layer 5
|
|
4049
|
+
* route and includes daemon sync state; otherwise it keeps the legacy current-agent route.
|
|
4050
|
+
*/
|
|
4051
|
+
async installed(options) {
|
|
4052
|
+
const query = {};
|
|
4053
|
+
if (options?.workspaceId) query.workspaceId = options.workspaceId;
|
|
4054
|
+
if (options?.includeInactive) query.includeInactive = "true";
|
|
4055
|
+
if (options?.agentId) {
|
|
4056
|
+
return this._r("GET", `/api/im/agents/${encodeURIComponent(options.agentId)}/skills`, void 0, query);
|
|
4057
|
+
}
|
|
4058
|
+
return this._r("GET", "/api/im/skills/installed", void 0, query);
|
|
4059
|
+
}
|
|
4060
|
+
/** Install a skill to a specific agent. */
|
|
4061
|
+
async installForAgent(agentId, skillIdOrSlug, options) {
|
|
4062
|
+
return this._r("POST", `/api/im/agents/${encodeURIComponent(agentId)}/skills`, {
|
|
4063
|
+
skillId: skillIdOrSlug,
|
|
4064
|
+
...options
|
|
4065
|
+
});
|
|
4066
|
+
}
|
|
4067
|
+
/** Disable/uninstall a skill from a specific agent. Built-ins are disabled cloud-side. */
|
|
4068
|
+
async uninstallFromAgent(agentId, skillIdOrSlug, options) {
|
|
4069
|
+
return this._r("DELETE", `/api/im/agents/${encodeURIComponent(agentId)}/skills`, {
|
|
4070
|
+
skillId: skillIdOrSlug,
|
|
4071
|
+
workspaceId: options?.workspaceId
|
|
4072
|
+
});
|
|
4073
|
+
}
|
|
4074
|
+
/** List skills whose daemon sync state is not current. */
|
|
4075
|
+
async pending(agentId, workspaceId) {
|
|
4076
|
+
const query = workspaceId ? { workspaceId } : void 0;
|
|
4077
|
+
return this._r("GET", `/api/im/agents/${encodeURIComponent(agentId)}/skills/pending`, void 0, query);
|
|
4078
|
+
}
|
|
4079
|
+
/** Acknowledge daemon sync for an installed skill. */
|
|
4080
|
+
async ack(agentId, input) {
|
|
4081
|
+
return this._r("POST", `/api/im/agents/${encodeURIComponent(agentId)}/skills/ack`, input);
|
|
4082
|
+
}
|
|
4083
|
+
/** Star a skill. */
|
|
4084
|
+
async star(skillId) {
|
|
4085
|
+
return this._r("POST", `/api/im/skills/${encodeURIComponent(skillId)}/star`);
|
|
4086
|
+
}
|
|
4087
|
+
};
|
|
3813
4088
|
var EvolutionClient = class {
|
|
3814
4089
|
constructor(_r) {
|
|
3815
4090
|
this._r = _r;
|
|
4091
|
+
this.skills = new EvolutionSkillsClient(_r);
|
|
3816
4092
|
}
|
|
3817
4093
|
// ── Public endpoints (no auth required) ──
|
|
3818
4094
|
/** Get evolution stats */
|
|
@@ -3942,7 +4218,7 @@ var EvolutionClient = class {
|
|
|
3942
4218
|
const { outcome, score, summary, strategy_used, scope, ...analyzeOpts } = options;
|
|
3943
4219
|
const analysis = await this.analyze({ ...analyzeOpts, ...scope ? { scope } : {} });
|
|
3944
4220
|
if (!analysis.ok || !analysis.data) {
|
|
3945
|
-
return { ok: false, error: analysis.error };
|
|
4221
|
+
return { ok: false, ...analysis.error ? { error: analysis.error } : {} };
|
|
3946
4222
|
}
|
|
3947
4223
|
const data = analysis.data;
|
|
3948
4224
|
const geneId = data.gene_id;
|
|
@@ -4056,39 +4332,35 @@ var EvolutionClient = class {
|
|
|
4056
4332
|
}
|
|
4057
4333
|
/** Search skills catalog */
|
|
4058
4334
|
async searchSkills(options) {
|
|
4059
|
-
|
|
4060
|
-
if (options?.query) q.query = options.query;
|
|
4061
|
-
if (options?.category) q.category = options.category;
|
|
4062
|
-
if (options?.limit != null) q.limit = String(options.limit);
|
|
4063
|
-
return this._r("GET", "/api/im/skills/search", void 0, q);
|
|
4335
|
+
return this.skills.search(options);
|
|
4064
4336
|
}
|
|
4065
4337
|
/** Get skill catalog stats */
|
|
4066
4338
|
async getSkillStats() {
|
|
4067
|
-
return this.
|
|
4339
|
+
return this.skills.stats();
|
|
4068
4340
|
}
|
|
4069
4341
|
/** Install a skill — creates Gene + returns content + install guide */
|
|
4070
4342
|
async installSkill(slugOrId, scope) {
|
|
4071
|
-
return this.
|
|
4343
|
+
return this.skills.install(slugOrId, scope);
|
|
4072
4344
|
}
|
|
4073
4345
|
/** Uninstall a skill */
|
|
4074
|
-
async uninstallSkill(slugOrId) {
|
|
4075
|
-
return this.
|
|
4346
|
+
async uninstallSkill(slugOrId, scope) {
|
|
4347
|
+
return this.skills.uninstall(slugOrId, scope);
|
|
4076
4348
|
}
|
|
4077
4349
|
/** List installed skills for this agent */
|
|
4078
|
-
async installedSkills() {
|
|
4079
|
-
return this.
|
|
4350
|
+
async installedSkills(options) {
|
|
4351
|
+
return this.skills.installed(options);
|
|
4080
4352
|
}
|
|
4081
4353
|
/** Get full skill content (SKILL.md + package info) */
|
|
4082
4354
|
async getSkillContent(slugOrId) {
|
|
4083
|
-
return this.
|
|
4355
|
+
return this.skills.content(slugOrId);
|
|
4084
4356
|
}
|
|
4085
4357
|
/** Create/submit a community skill */
|
|
4086
4358
|
async createSkill(input) {
|
|
4087
|
-
return this.
|
|
4359
|
+
return this.skills.create(input);
|
|
4088
4360
|
}
|
|
4089
4361
|
/** Star a skill (increment community rating) */
|
|
4090
4362
|
async starSkill(skillId) {
|
|
4091
|
-
return this.
|
|
4363
|
+
return this.skills.star(skillId);
|
|
4092
4364
|
}
|
|
4093
4365
|
/**
|
|
4094
4366
|
* Install a skill and write SKILL.md to local filesystem.
|
|
@@ -4098,19 +4370,26 @@ var EvolutionClient = class {
|
|
|
4098
4370
|
*/
|
|
4099
4371
|
async installSkillLocal(slugOrId, options) {
|
|
4100
4372
|
const result = await this.installSkill(slugOrId);
|
|
4101
|
-
if (!result.ok || !result.data)
|
|
4102
|
-
|
|
4373
|
+
if (!result.ok || !result.data) {
|
|
4374
|
+
return result;
|
|
4375
|
+
}
|
|
4376
|
+
const installData = result.data;
|
|
4377
|
+
const withLocalPaths = (localPaths2) => ({
|
|
4378
|
+
ok: true,
|
|
4379
|
+
data: { ...installData, localPaths: localPaths2 }
|
|
4380
|
+
});
|
|
4381
|
+
let content = installData.skill?.content || "";
|
|
4103
4382
|
if (!content) {
|
|
4104
4383
|
const contentResult = await this.getSkillContent(slugOrId);
|
|
4105
4384
|
content = contentResult.data?.content || "";
|
|
4106
4385
|
}
|
|
4107
4386
|
if (!content) {
|
|
4108
|
-
return
|
|
4387
|
+
return withLocalPaths([]);
|
|
4109
4388
|
}
|
|
4110
|
-
const rawSlug =
|
|
4389
|
+
const rawSlug = installData.skill?.slug || slugOrId;
|
|
4111
4390
|
const slug = rawSlug.replace(/[\/\\]/g, "").replace(/\.\./g, "");
|
|
4112
4391
|
if (!slug) {
|
|
4113
|
-
return
|
|
4392
|
+
return withLocalPaths([]);
|
|
4114
4393
|
}
|
|
4115
4394
|
const localPaths = [];
|
|
4116
4395
|
try {
|
|
@@ -4130,7 +4409,7 @@ var EvolutionClient = class {
|
|
|
4130
4409
|
"opencode": path.join(home, ".config", "opencode", "skills", slug),
|
|
4131
4410
|
"plugin": path.join(pluginBase, "skills", slug)
|
|
4132
4411
|
};
|
|
4133
|
-
const targets = options?.platforms
|
|
4412
|
+
const targets = options?.platforms ?? Object.keys(platformPaths);
|
|
4134
4413
|
for (const platform of targets) {
|
|
4135
4414
|
const dir = platformPaths[platform];
|
|
4136
4415
|
if (!dir) continue;
|
|
@@ -4144,7 +4423,7 @@ var EvolutionClient = class {
|
|
|
4144
4423
|
}
|
|
4145
4424
|
} catch {
|
|
4146
4425
|
}
|
|
4147
|
-
return
|
|
4426
|
+
return withLocalPaths(localPaths);
|
|
4148
4427
|
}
|
|
4149
4428
|
/**
|
|
4150
4429
|
* Uninstall a skill and remove local SKILL.md files.
|
|
@@ -4152,8 +4431,13 @@ var EvolutionClient = class {
|
|
|
4152
4431
|
async uninstallSkillLocal(slugOrId) {
|
|
4153
4432
|
const result = await this.uninstallSkill(slugOrId);
|
|
4154
4433
|
const removedPaths = [];
|
|
4434
|
+
const withRemoved = (ok, paths) => ({
|
|
4435
|
+
ok: result.ok,
|
|
4436
|
+
...result.error ? { error: result.error } : {},
|
|
4437
|
+
data: { uninstalled: ok, removedPaths: paths }
|
|
4438
|
+
});
|
|
4155
4439
|
const slug = safeSlug(slugOrId);
|
|
4156
|
-
if (!slug) return
|
|
4440
|
+
if (!slug) return withRemoved(result.data?.uninstalled ?? false, removedPaths);
|
|
4157
4441
|
try {
|
|
4158
4442
|
const fs = await import("fs");
|
|
4159
4443
|
const path = await import("path");
|
|
@@ -4177,7 +4461,7 @@ var EvolutionClient = class {
|
|
|
4177
4461
|
}
|
|
4178
4462
|
} catch {
|
|
4179
4463
|
}
|
|
4180
|
-
return
|
|
4464
|
+
return withRemoved(result.data?.uninstalled ?? false, removedPaths);
|
|
4181
4465
|
}
|
|
4182
4466
|
/**
|
|
4183
4467
|
* Sync all installed skills to local filesystem.
|
|
@@ -4217,7 +4501,7 @@ var EvolutionClient = class {
|
|
|
4217
4501
|
"opencode": path.join(home, ".config", "opencode", "skills", slug),
|
|
4218
4502
|
"plugin": path.join(pluginBase, "skills", slug)
|
|
4219
4503
|
};
|
|
4220
|
-
const targets = options?.platforms
|
|
4504
|
+
const targets = options?.platforms ?? Object.keys(platformPaths);
|
|
4221
4505
|
for (const platform of targets) {
|
|
4222
4506
|
const dir = platformPaths[platform];
|
|
4223
4507
|
if (!dir) continue;
|
|
@@ -4266,12 +4550,34 @@ var EvolutionClient = class {
|
|
|
4266
4550
|
if (since != null) query.since = String(since);
|
|
4267
4551
|
return this._r("GET", "/api/im/evolution/sync/snapshot", void 0, query);
|
|
4268
4552
|
}
|
|
4269
|
-
/**
|
|
4553
|
+
/**
|
|
4554
|
+
* Bidirectional sync: push local outcomes and pull remote updates.
|
|
4555
|
+
*
|
|
4556
|
+
* Accepts either the flat shape (`pushOutcomes` / `pullSince`) used by older
|
|
4557
|
+
* callers or the nested shape (`push` / `pull`) that mirrors the wire format
|
|
4558
|
+
* expected by `POST /api/im/evolution/sync`. The nested shape is preferred for
|
|
4559
|
+
* new code because it lets you pin a scope per-side.
|
|
4560
|
+
*/
|
|
4270
4561
|
async sync(options) {
|
|
4271
4562
|
const body = {};
|
|
4272
|
-
|
|
4273
|
-
if (
|
|
4274
|
-
|
|
4563
|
+
const outcomes = options?.push?.outcomes ?? options?.pushOutcomes;
|
|
4564
|
+
if (outcomes) {
|
|
4565
|
+
body.push = {
|
|
4566
|
+
outcomes,
|
|
4567
|
+
...options?.push?.scope ? { scope: options.push.scope } : {},
|
|
4568
|
+
...options?.push?.workspaceId ? { workspaceId: options.push.workspaceId } : {}
|
|
4569
|
+
};
|
|
4570
|
+
}
|
|
4571
|
+
const since = options?.pull?.since ?? options?.pullSince;
|
|
4572
|
+
if (since != null) {
|
|
4573
|
+
body.pull = {
|
|
4574
|
+
since,
|
|
4575
|
+
...options?.pull?.scope ? { scope: options.pull.scope } : {}
|
|
4576
|
+
};
|
|
4577
|
+
}
|
|
4578
|
+
const query = {};
|
|
4579
|
+
if (options?.scope) query.scope = options.scope;
|
|
4580
|
+
return this._r("POST", "/api/im/evolution/sync", body, query);
|
|
4275
4581
|
}
|
|
4276
4582
|
};
|
|
4277
4583
|
function safeSlug(input) {
|
|
@@ -4315,6 +4621,26 @@ var WorkspacesClient = class {
|
|
|
4315
4621
|
async archive(workspaceId) {
|
|
4316
4622
|
return this._r("DELETE", `/api/im/workspaces/${workspaceId}`);
|
|
4317
4623
|
}
|
|
4624
|
+
/**
|
|
4625
|
+
* Get the workspace's orchestrator agent (Chief of Staff) — readable by any
|
|
4626
|
+
* member. Returns `{ workspace, orchestrator: null }` when no active
|
|
4627
|
+
* appointment exists. See release 200 §4.
|
|
4628
|
+
*/
|
|
4629
|
+
async getOrchestrator(workspaceId) {
|
|
4630
|
+
return this._r("GET", `/api/im/workspaces/${workspaceId}/orchestrator`);
|
|
4631
|
+
}
|
|
4632
|
+
/**
|
|
4633
|
+
* Appoint an agent as the workspace's orchestrator. Owner-only. If an
|
|
4634
|
+
* orchestrator is already active, this auto-revokes the previous one in the
|
|
4635
|
+
* same UPDATE.
|
|
4636
|
+
*/
|
|
4637
|
+
async appointOrchestrator(workspaceId, agentImUserId) {
|
|
4638
|
+
return this._r("POST", `/api/im/workspaces/${workspaceId}/orchestrator`, { agentImUserId });
|
|
4639
|
+
}
|
|
4640
|
+
/** Revoke the workspace's current orchestrator. Owner-only. Idempotent. */
|
|
4641
|
+
async revokeOrchestrator(workspaceId) {
|
|
4642
|
+
return this._r("DELETE", `/api/im/workspaces/${workspaceId}/orchestrator`);
|
|
4643
|
+
}
|
|
4318
4644
|
};
|
|
4319
4645
|
var WorkspaceFilesClient = class {
|
|
4320
4646
|
constructor(_r) {
|
|
@@ -4348,6 +4674,83 @@ var WorkspaceFilesClient = class {
|
|
|
4348
4674
|
return this._r("GET", `/api/im/workspaces/${workspaceId}/files/${fileId}/history`);
|
|
4349
4675
|
}
|
|
4350
4676
|
};
|
|
4677
|
+
var MAX_IM_ASSET_BYTES = 1024 * 1024 * 1024;
|
|
4678
|
+
var DIRECT_ASSET_UPLOAD_FALLBACK_STATUSES = /* @__PURE__ */ new Set([404, 501, 503]);
|
|
4679
|
+
function toHex(bytes) {
|
|
4680
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
4681
|
+
}
|
|
4682
|
+
async function sha256BytesHex(bytes) {
|
|
4683
|
+
if (globalThis.crypto?.subtle) {
|
|
4684
|
+
const ab = new ArrayBuffer(bytes.byteLength);
|
|
4685
|
+
new Uint8Array(ab).set(bytes);
|
|
4686
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", ab);
|
|
4687
|
+
return toHex(new Uint8Array(digest));
|
|
4688
|
+
}
|
|
4689
|
+
const { createHash } = await import("crypto");
|
|
4690
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
4691
|
+
}
|
|
4692
|
+
function bytesToBlob(bytes, mimeType) {
|
|
4693
|
+
const ab = new ArrayBuffer(bytes.byteLength);
|
|
4694
|
+
new Uint8Array(ab).set(bytes);
|
|
4695
|
+
return new Blob([ab], { type: mimeType });
|
|
4696
|
+
}
|
|
4697
|
+
function isNamedFile(input) {
|
|
4698
|
+
return typeof File !== "undefined" && input instanceof File;
|
|
4699
|
+
}
|
|
4700
|
+
function normalizeStringHeaders(value) {
|
|
4701
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
4702
|
+
const out = {};
|
|
4703
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
4704
|
+
if (typeof raw === "string") out[key] = raw;
|
|
4705
|
+
}
|
|
4706
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
4707
|
+
}
|
|
4708
|
+
function isDirectAssetUploadPlan(value) {
|
|
4709
|
+
if (!value || typeof value !== "object") return false;
|
|
4710
|
+
const plan = value;
|
|
4711
|
+
if (plan.mode === "single") {
|
|
4712
|
+
return typeof plan.uploadUrl === "string" && typeof plan.bucket === "string" && typeof plan.key === "string";
|
|
4713
|
+
}
|
|
4714
|
+
if (plan.mode === "multipart") {
|
|
4715
|
+
return typeof plan.bucket === "string" && typeof plan.key === "string" && typeof plan.uploadId === "string" && typeof plan.partSizeBytes === "number" && Array.isArray(plan.parts) && plan.parts.every((part) => {
|
|
4716
|
+
const item = part;
|
|
4717
|
+
return typeof item.url === "string" && Number.isInteger(item.partNumber);
|
|
4718
|
+
});
|
|
4719
|
+
}
|
|
4720
|
+
return false;
|
|
4721
|
+
}
|
|
4722
|
+
function assetUploadError(code, message) {
|
|
4723
|
+
return { ok: false, error: { code, message } };
|
|
4724
|
+
}
|
|
4725
|
+
async function normalizeAssetUploadInput(input, options) {
|
|
4726
|
+
let bytes;
|
|
4727
|
+
let fileName;
|
|
4728
|
+
if (typeof input === "string") {
|
|
4729
|
+
const fs = await import("fs");
|
|
4730
|
+
const path = await import("path");
|
|
4731
|
+
const buf = await fs.promises.readFile(input);
|
|
4732
|
+
bytes = new Uint8Array(buf);
|
|
4733
|
+
fileName = options.fileName || path.basename(input);
|
|
4734
|
+
} else if (typeof Blob !== "undefined" && input instanceof Blob) {
|
|
4735
|
+
const ab = await input.arrayBuffer();
|
|
4736
|
+
bytes = new Uint8Array(ab);
|
|
4737
|
+
fileName = options.fileName || (isNamedFile(input) ? input.name : "");
|
|
4738
|
+
if (!fileName) throw new Error("fileName is required when uploading Blob without name");
|
|
4739
|
+
} else if (input instanceof Uint8Array) {
|
|
4740
|
+
bytes = input;
|
|
4741
|
+
fileName = options.fileName || "";
|
|
4742
|
+
if (!fileName) throw new Error("fileName is required when uploading Buffer or Uint8Array");
|
|
4743
|
+
} else {
|
|
4744
|
+
throw new Error("Unsupported input type");
|
|
4745
|
+
}
|
|
4746
|
+
const sizeBytes = bytes.byteLength;
|
|
4747
|
+
if (sizeBytes > MAX_IM_ASSET_BYTES) {
|
|
4748
|
+
throw new Error("Asset exceeds 1 GB cap");
|
|
4749
|
+
}
|
|
4750
|
+
const mimeType = options.mimeType || guessMimeType(fileName);
|
|
4751
|
+
const contentHash = await sha256BytesHex(bytes);
|
|
4752
|
+
return { bytes, fileName, mimeType, sizeBytes, contentHash };
|
|
4753
|
+
}
|
|
4351
4754
|
var AssetsClient = class {
|
|
4352
4755
|
constructor(_r, _baseUrl, _fetchFn, _getAuthHeaders) {
|
|
4353
4756
|
this._r = _r;
|
|
@@ -4414,57 +4817,130 @@ var AssetsClient = class {
|
|
|
4414
4817
|
};
|
|
4415
4818
|
}
|
|
4416
4819
|
/**
|
|
4417
|
-
* Upload bytes as an asset
|
|
4418
|
-
*
|
|
4820
|
+
* Upload bytes as an asset. Uses direct-to-S3 upload when the server exposes
|
|
4821
|
+
* `/assets/direct-upload/*`; falls back to legacy multipart POST for local
|
|
4822
|
+
* filesystem mode and older deployments. The client always sends SHA-256 for
|
|
4823
|
+
* server-side byte integrity checks. Hard cap: 1 GB.
|
|
4419
4824
|
*/
|
|
4420
4825
|
async upload(input, options) {
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
if (
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4826
|
+
const normalized = await normalizeAssetUploadInput(input, options);
|
|
4827
|
+
const direct = await this._tryDirectUpload(normalized, options);
|
|
4828
|
+
if (direct) return direct;
|
|
4829
|
+
return this._uploadMultipart(normalized, options);
|
|
4830
|
+
}
|
|
4831
|
+
async _tryDirectUpload(file, options) {
|
|
4832
|
+
const init = await this._postAssetJson("/direct-upload/init", {
|
|
4833
|
+
workspaceId: options.workspaceId,
|
|
4834
|
+
filename: file.fileName,
|
|
4835
|
+
mime: file.mimeType,
|
|
4836
|
+
sizeBytes: file.sizeBytes,
|
|
4837
|
+
contentHash: file.contentHash
|
|
4838
|
+
});
|
|
4839
|
+
if (!init.response.ok) {
|
|
4840
|
+
if (DIRECT_ASSET_UPLOAD_FALLBACK_STATUSES.has(init.response.status)) return null;
|
|
4841
|
+
return init.data && init.data.ok === false ? {
|
|
4842
|
+
ok: false,
|
|
4843
|
+
error: init.data.error ?? {
|
|
4844
|
+
code: "http_error",
|
|
4845
|
+
message: `Direct upload init failed (${init.response.status})`
|
|
4846
|
+
}
|
|
4847
|
+
} : assetUploadError("http_error", `Direct upload init failed (${init.response.status})`);
|
|
4440
4848
|
}
|
|
4441
|
-
|
|
4442
|
-
const
|
|
4443
|
-
|
|
4444
|
-
|
|
4849
|
+
if (!isDirectAssetUploadPlan(init.data?.data)) return null;
|
|
4850
|
+
const plan = init.data.data;
|
|
4851
|
+
try {
|
|
4852
|
+
const parts = await this._putDirectUploadBytes(plan, file, options.onProgress);
|
|
4853
|
+
const complete = await this._postAssetJson("/direct-upload/complete", {
|
|
4854
|
+
workspaceId: options.workspaceId,
|
|
4855
|
+
filename: file.fileName,
|
|
4856
|
+
mime: file.mimeType,
|
|
4857
|
+
sizeBytes: file.sizeBytes,
|
|
4858
|
+
contentHash: file.contentHash,
|
|
4859
|
+
kind: options.kind,
|
|
4860
|
+
metadata: options.metadata,
|
|
4861
|
+
sourceTaskId: options.sourceTaskId,
|
|
4862
|
+
sourceAgentImUserId: options.sourceAgentImUserId,
|
|
4863
|
+
bucket: plan.bucket,
|
|
4864
|
+
key: plan.key,
|
|
4865
|
+
...plan.mode === "multipart" ? { uploadId: plan.uploadId, parts: parts ?? [] } : {}
|
|
4866
|
+
});
|
|
4867
|
+
if (!complete.response.ok) {
|
|
4868
|
+
return complete.data && complete.data.ok === false ? complete.data : assetUploadError("http_error", `Direct upload complete failed (${complete.response.status})`);
|
|
4869
|
+
}
|
|
4870
|
+
if (!complete.data) {
|
|
4871
|
+
return assetUploadError("invalid_response", "Direct upload complete returned an empty response");
|
|
4872
|
+
}
|
|
4873
|
+
return complete.data;
|
|
4874
|
+
} catch {
|
|
4875
|
+
return null;
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4878
|
+
async _postAssetJson(path, body) {
|
|
4879
|
+
const response = await this._fetchFn(`${this._baseUrl}/api/im/assets${path}`, {
|
|
4880
|
+
method: "POST",
|
|
4881
|
+
headers: {
|
|
4882
|
+
...this._getAuthHeaders(),
|
|
4883
|
+
"Content-Type": "application/json",
|
|
4884
|
+
Accept: "application/json"
|
|
4885
|
+
},
|
|
4886
|
+
body: JSON.stringify(body)
|
|
4887
|
+
});
|
|
4888
|
+
const data = await response.json().catch(() => null);
|
|
4889
|
+
return { response, data };
|
|
4890
|
+
}
|
|
4891
|
+
async _putDirectUploadBytes(plan, file, onProgress) {
|
|
4892
|
+
if (plan.mode === "single") {
|
|
4893
|
+
const response = await this._fetchFn(plan.uploadUrl, {
|
|
4894
|
+
method: plan.method ?? "PUT",
|
|
4895
|
+
headers: normalizeStringHeaders(plan.headers),
|
|
4896
|
+
body: bytesToBlob(file.bytes, file.mimeType)
|
|
4897
|
+
});
|
|
4898
|
+
if (!response.ok) throw new Error(`signed PUT failed (${response.status})`);
|
|
4899
|
+
onProgress?.(file.sizeBytes, file.sizeBytes);
|
|
4900
|
+
return null;
|
|
4901
|
+
}
|
|
4902
|
+
const completedParts = [];
|
|
4903
|
+
let uploaded = 0;
|
|
4904
|
+
for (const part of plan.parts) {
|
|
4905
|
+
const start = (part.partNumber - 1) * plan.partSizeBytes;
|
|
4906
|
+
const end = Math.min(start + plan.partSizeBytes, file.sizeBytes);
|
|
4907
|
+
const chunk = file.bytes.slice(start, end);
|
|
4908
|
+
const response = await this._fetchFn(part.url, { method: "PUT", body: bytesToBlob(chunk, file.mimeType) });
|
|
4909
|
+
if (!response.ok) throw new Error(`signed multipart PUT failed for part ${part.partNumber} (${response.status})`);
|
|
4910
|
+
const etag = response.headers.get("etag")?.replace(/^"|"$/g, "");
|
|
4911
|
+
if (!etag) throw new Error(`signed multipart PUT missing ETag for part ${part.partNumber}`);
|
|
4912
|
+
completedParts.push({ partNumber: part.partNumber, etag });
|
|
4913
|
+
uploaded += chunk.byteLength;
|
|
4914
|
+
onProgress?.(uploaded, file.sizeBytes);
|
|
4445
4915
|
}
|
|
4916
|
+
return completedParts;
|
|
4917
|
+
}
|
|
4918
|
+
async _uploadMultipart(file, options) {
|
|
4919
|
+
const { bytes, fileName, mimeType, sizeBytes, contentHash } = file;
|
|
4446
4920
|
const formData = new FormData();
|
|
4447
|
-
|
|
4448
|
-
new Uint8Array(ab).set(bytes);
|
|
4449
|
-
formData.append("file", new Blob([ab], { type: mimeType }), fileName);
|
|
4921
|
+
formData.append("file", bytesToBlob(bytes, mimeType), fileName);
|
|
4450
4922
|
formData.append("workspaceId", options.workspaceId);
|
|
4451
4923
|
if (options.kind) formData.append("kind", options.kind);
|
|
4452
4924
|
if (options.sourceAgentImUserId) formData.append("sourceAgentImUserId", options.sourceAgentImUserId);
|
|
4453
4925
|
if (options.sourceTaskId) formData.append("sourceTaskId", options.sourceTaskId);
|
|
4454
4926
|
if (options.metadata) formData.append("metadata", JSON.stringify(options.metadata));
|
|
4927
|
+
formData.append("contentSha256", contentHash);
|
|
4455
4928
|
const resp = await this._fetchFn(`${this._baseUrl}/api/im/assets`, {
|
|
4456
4929
|
method: "POST",
|
|
4457
4930
|
body: formData,
|
|
4458
|
-
headers:
|
|
4931
|
+
headers: {
|
|
4932
|
+
...this._getAuthHeaders(),
|
|
4933
|
+
"X-Content-Sha256": contentHash
|
|
4934
|
+
}
|
|
4459
4935
|
});
|
|
4460
|
-
options.onProgress?.(sizeBytes, sizeBytes);
|
|
4461
4936
|
const data = await resp.json().catch(() => ({}));
|
|
4462
4937
|
if (!resp.ok) {
|
|
4463
4938
|
return {
|
|
4464
4939
|
ok: false,
|
|
4465
|
-
error: data?.error || { code: "
|
|
4940
|
+
error: data?.error || { code: "http_error", message: `Upload failed (${resp.status})` }
|
|
4466
4941
|
};
|
|
4467
4942
|
}
|
|
4943
|
+
options.onProgress?.(sizeBytes, sizeBytes);
|
|
4468
4944
|
return data;
|
|
4469
4945
|
}
|
|
4470
4946
|
};
|
|
@@ -4830,6 +5306,7 @@ var IMRealtimeClient = class {
|
|
|
4830
5306
|
};
|
|
4831
5307
|
var IMClient = class {
|
|
4832
5308
|
constructor(request, wsBase, fetchFn, getAuthHeaders, offlineManager, communityHubConfig) {
|
|
5309
|
+
this._request = request;
|
|
4833
5310
|
this.account = new AccountClient(request);
|
|
4834
5311
|
this.direct = new DirectClient(request);
|
|
4835
5312
|
this.groups = new GroupsClient(request);
|
|
@@ -4856,7 +5333,7 @@ var IMClient = class {
|
|
|
4856
5333
|
}
|
|
4857
5334
|
/** IM health check */
|
|
4858
5335
|
async health() {
|
|
4859
|
-
return this.
|
|
5336
|
+
return this._request("GET", "/api/im/health");
|
|
4860
5337
|
}
|
|
4861
5338
|
/** Get workspace superset view with slot filtering */
|
|
4862
5339
|
async getWorkspace(scope, slots, includeContent) {
|
|
@@ -4864,7 +5341,22 @@ var IMClient = class {
|
|
|
4864
5341
|
if (scope) params.set("scope", scope);
|
|
4865
5342
|
if (slots?.length) params.set("slots", slots.join(","));
|
|
4866
5343
|
if (includeContent) params.set("includeContent", "true");
|
|
4867
|
-
return this.
|
|
5344
|
+
return this._request("GET", `/api/im/workspace/view?${params}`);
|
|
5345
|
+
}
|
|
5346
|
+
/**
|
|
5347
|
+
* Issue a typed IM API request via the shared `RequestFn` pipeline.
|
|
5348
|
+
*
|
|
5349
|
+
* Use this when you need to hit an IM endpoint that isn't (yet) exposed by a
|
|
5350
|
+
* sub-client (e.g. `/api/im/approvals`). Same auth + offline routing + retry
|
|
5351
|
+
* behaviour as the typed sub-clients.
|
|
5352
|
+
*
|
|
5353
|
+
* @example
|
|
5354
|
+
* const res = await client.im.request<ApprovalCreateResponse>(
|
|
5355
|
+
* 'POST', '/api/im/approvals', { category, title, context, options },
|
|
5356
|
+
* );
|
|
5357
|
+
*/
|
|
5358
|
+
async request(method, path, body, query) {
|
|
5359
|
+
return this._request(method, path, body, query);
|
|
4868
5360
|
}
|
|
4869
5361
|
};
|
|
4870
5362
|
var PrismerClient = class {
|
|
@@ -4932,6 +5424,10 @@ var PrismerClient = class {
|
|
|
4932
5424
|
this._offlineManager,
|
|
4933
5425
|
config.community ?? null
|
|
4934
5426
|
);
|
|
5427
|
+
this.workspaces = this.im.workspaces;
|
|
5428
|
+
this.workspaceFiles = this.im.workspaceFiles;
|
|
5429
|
+
this.assets = this.im.assets;
|
|
5430
|
+
this.evolution = this.im.evolution;
|
|
4935
5431
|
}
|
|
4936
5432
|
/** Wait for identity to be ready (useful for tests or explicit await) */
|
|
4937
5433
|
async ensureIdentity() {
|
|
@@ -4980,6 +5476,39 @@ var PrismerClient = class {
|
|
|
4980
5476
|
await this._offlineManager.destroy();
|
|
4981
5477
|
}
|
|
4982
5478
|
}
|
|
5479
|
+
/**
|
|
5480
|
+
* Issue an authenticated raw HTTP request against the configured base URL,
|
|
5481
|
+
* returning the underlying `Response` so callers can inspect headers
|
|
5482
|
+
* (`Content-Range`, `Content-Length`, etc.) and stream the body.
|
|
5483
|
+
*
|
|
5484
|
+
* The path may be absolute (`/api/...`) or a full URL — full URLs are used
|
|
5485
|
+
* verbatim (useful for following 302 redirects), otherwise the path is
|
|
5486
|
+
* appended to the client's configured `baseUrl`. Authorization + `X-IM-Agent`
|
|
5487
|
+
* headers are added automatically; caller-supplied headers in `init.headers`
|
|
5488
|
+
* override them on collision.
|
|
5489
|
+
*
|
|
5490
|
+
* Use this for binary downloads / partial fetches; for normal JSON-envelope
|
|
5491
|
+
* IM requests use `client.im.request()` (typed) or the typed sub-clients.
|
|
5492
|
+
*/
|
|
5493
|
+
async fetchAuthed(url, init) {
|
|
5494
|
+
const fullUrl = /^https?:\/\//i.test(url) ? url : `${this.baseUrl}${url}`;
|
|
5495
|
+
const authHeaders = this._getAuthHeaders();
|
|
5496
|
+
const callerHeaders = {};
|
|
5497
|
+
if (init?.headers) {
|
|
5498
|
+
const h = init.headers;
|
|
5499
|
+
if (h instanceof Headers) {
|
|
5500
|
+
h.forEach((v, k) => {
|
|
5501
|
+
callerHeaders[k] = v;
|
|
5502
|
+
});
|
|
5503
|
+
} else if (Array.isArray(h)) {
|
|
5504
|
+
for (const [k, v] of h) callerHeaders[k] = v;
|
|
5505
|
+
} else {
|
|
5506
|
+
Object.assign(callerHeaders, h);
|
|
5507
|
+
}
|
|
5508
|
+
}
|
|
5509
|
+
const headers = { ...authHeaders, ...callerHeaders };
|
|
5510
|
+
return this.fetchFn(fullUrl, { ...init ?? {}, headers });
|
|
5511
|
+
}
|
|
4983
5512
|
// --------------------------------------------------------------------------
|
|
4984
5513
|
// Internal request helper
|
|
4985
5514
|
// --------------------------------------------------------------------------
|
|
@@ -5016,18 +5545,18 @@ var PrismerClient = class {
|
|
|
5016
5545
|
}
|
|
5017
5546
|
}
|
|
5018
5547
|
if (!response.ok) {
|
|
5019
|
-
const err = data.error || { code: "
|
|
5548
|
+
const err = data.error || { code: "http_error", message: `Request failed with status ${response.status}` };
|
|
5020
5549
|
return { ...data, success: false, ok: false, error: err };
|
|
5021
5550
|
}
|
|
5022
5551
|
return data;
|
|
5023
5552
|
} catch (error) {
|
|
5024
5553
|
if (error instanceof Error && error.name === "AbortError") {
|
|
5025
|
-
return { success: false, ok: false, error: { code: "
|
|
5554
|
+
return { success: false, ok: false, error: { code: "timeout", message: "Request timed out" } };
|
|
5026
5555
|
}
|
|
5027
5556
|
return {
|
|
5028
5557
|
success: false,
|
|
5029
5558
|
ok: false,
|
|
5030
|
-
error: { code: "
|
|
5559
|
+
error: { code: "cloud_unreachable", message: error instanceof Error ? error.message : "Unknown error" }
|
|
5031
5560
|
};
|
|
5032
5561
|
} finally {
|
|
5033
5562
|
clearTimeout(timeoutId);
|
|
@@ -5116,6 +5645,7 @@ export {
|
|
|
5116
5645
|
EvolutionCache,
|
|
5117
5646
|
EvolutionClient,
|
|
5118
5647
|
EvolutionRuntime,
|
|
5648
|
+
EvolutionSkillsClient,
|
|
5119
5649
|
FilesClient,
|
|
5120
5650
|
GroupsClient,
|
|
5121
5651
|
IMClient,
|
|
@@ -5135,6 +5665,7 @@ export {
|
|
|
5135
5665
|
SecurityClient,
|
|
5136
5666
|
TabCoordinator,
|
|
5137
5667
|
TasksClient,
|
|
5668
|
+
WORKSPACE_ASSETS_ROUTE,
|
|
5138
5669
|
WorkspaceClient,
|
|
5139
5670
|
WorkspaceFilesClient,
|
|
5140
5671
|
WorkspacesClient,
|
|
@@ -5150,5 +5681,7 @@ export {
|
|
|
5150
5681
|
encryptForSend,
|
|
5151
5682
|
extractSignals,
|
|
5152
5683
|
guessMimeType,
|
|
5153
|
-
|
|
5684
|
+
readCardKanbanMetadata,
|
|
5685
|
+
safeSlug,
|
|
5686
|
+
writeCardKanbanMetadata
|
|
5154
5687
|
};
|