@prismer/sdk 1.9.24 → 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.js
CHANGED
|
@@ -45,6 +45,7 @@ __export(index_exports, {
|
|
|
45
45
|
EvolutionCache: () => EvolutionCache,
|
|
46
46
|
EvolutionClient: () => EvolutionClient,
|
|
47
47
|
EvolutionRuntime: () => EvolutionRuntime,
|
|
48
|
+
EvolutionSkillsClient: () => EvolutionSkillsClient,
|
|
48
49
|
FilesClient: () => FilesClient,
|
|
49
50
|
GroupsClient: () => GroupsClient,
|
|
50
51
|
IMClient: () => IMClient,
|
|
@@ -64,6 +65,7 @@ __export(index_exports, {
|
|
|
64
65
|
SecurityClient: () => SecurityClient,
|
|
65
66
|
TabCoordinator: () => TabCoordinator,
|
|
66
67
|
TasksClient: () => TasksClient,
|
|
68
|
+
WORKSPACE_ASSETS_ROUTE: () => WORKSPACE_ASSETS_ROUTE,
|
|
67
69
|
WorkspaceClient: () => WorkspaceClient,
|
|
68
70
|
WorkspaceFilesClient: () => WorkspaceFilesClient,
|
|
69
71
|
WorkspacesClient: () => WorkspacesClient,
|
|
@@ -79,7 +81,9 @@ __export(index_exports, {
|
|
|
79
81
|
encryptForSend: () => encryptForSend,
|
|
80
82
|
extractSignals: () => extractSignals,
|
|
81
83
|
guessMimeType: () => guessMimeType,
|
|
82
|
-
|
|
84
|
+
readCardKanbanMetadata: () => readCardKanbanMetadata,
|
|
85
|
+
safeSlug: () => safeSlug,
|
|
86
|
+
writeCardKanbanMetadata: () => writeCardKanbanMetadata
|
|
83
87
|
});
|
|
84
88
|
module.exports = __toCommonJS(index_exports);
|
|
85
89
|
|
|
@@ -1563,6 +1567,106 @@ var ENVIRONMENTS = {
|
|
|
1563
1567
|
production: "https://prismer.cloud"
|
|
1564
1568
|
};
|
|
1565
1569
|
|
|
1570
|
+
// src/im/workspace/tasks.ts
|
|
1571
|
+
var CARD_KANBAN_STATUSES = /* @__PURE__ */ new Set([
|
|
1572
|
+
"backlog",
|
|
1573
|
+
"todo",
|
|
1574
|
+
"in_progress",
|
|
1575
|
+
"review",
|
|
1576
|
+
"completed",
|
|
1577
|
+
"failed",
|
|
1578
|
+
"cancelled",
|
|
1579
|
+
"pending",
|
|
1580
|
+
"assigned",
|
|
1581
|
+
"running",
|
|
1582
|
+
"done"
|
|
1583
|
+
]);
|
|
1584
|
+
var CARD_PRIORITIES = /* @__PURE__ */ new Set([
|
|
1585
|
+
"low",
|
|
1586
|
+
"medium",
|
|
1587
|
+
"high",
|
|
1588
|
+
"urgent"
|
|
1589
|
+
]);
|
|
1590
|
+
function readCardKanbanMetadata(source) {
|
|
1591
|
+
const metadata = readSourceMetadata(source);
|
|
1592
|
+
const nestedKanban = readRecord(metadata.kanban);
|
|
1593
|
+
const fields = nestedKanban ? { ...metadata, ...nestedKanban } : metadata;
|
|
1594
|
+
return toCanonicalCardKanbanMetadata(fields);
|
|
1595
|
+
}
|
|
1596
|
+
function writeCardKanbanMetadata(metadata, kanban) {
|
|
1597
|
+
const previous = readRecord(metadata) ?? {};
|
|
1598
|
+
const previousKanban = readRecord(previous.kanban);
|
|
1599
|
+
const currentView = toWritableCardKanbanMetadata(readCardKanbanMetadata(previous));
|
|
1600
|
+
const nextView = toWritableCardKanbanMetadata(toCanonicalCardKanbanMetadata(kanban));
|
|
1601
|
+
const nextKanban = toWritableCardKanbanMetadata({
|
|
1602
|
+
...previousKanban ? omitLegacyOrder(previousKanban) : {},
|
|
1603
|
+
...currentView,
|
|
1604
|
+
...nextView
|
|
1605
|
+
});
|
|
1606
|
+
return {
|
|
1607
|
+
...omitLegacyOrder(previous),
|
|
1608
|
+
...nextKanban,
|
|
1609
|
+
kanban: nextKanban
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
function readSourceMetadata(source) {
|
|
1613
|
+
const record = readRecord(source);
|
|
1614
|
+
if (!record) return {};
|
|
1615
|
+
return readRecord(record.metadata) ?? record;
|
|
1616
|
+
}
|
|
1617
|
+
function toCanonicalCardKanbanMetadata(source) {
|
|
1618
|
+
const record = readRecord(source) ?? {};
|
|
1619
|
+
const metadata = {};
|
|
1620
|
+
const columnId = readString(record.columnId);
|
|
1621
|
+
const cardOrder = readFiniteNumber(record.cardOrder) ?? readFiniteNumber(record.order);
|
|
1622
|
+
const cardStatus = readCardKanbanStatus(record.cardStatus);
|
|
1623
|
+
const cardPriority = readCardPriority(record.cardPriority);
|
|
1624
|
+
const cardLabels = readCardLabels(record.cardLabels);
|
|
1625
|
+
if (columnId) metadata.columnId = columnId;
|
|
1626
|
+
if (cardOrder !== void 0) metadata.cardOrder = cardOrder;
|
|
1627
|
+
if (cardStatus) metadata.cardStatus = cardStatus;
|
|
1628
|
+
if (cardPriority) metadata.cardPriority = cardPriority;
|
|
1629
|
+
if (cardLabels) metadata.cardLabels = cardLabels;
|
|
1630
|
+
return metadata;
|
|
1631
|
+
}
|
|
1632
|
+
function toWritableCardKanbanMetadata(view) {
|
|
1633
|
+
return view.cardStatus === "done" ? { ...view, cardStatus: "completed" } : view;
|
|
1634
|
+
}
|
|
1635
|
+
function omitLegacyOrder(record) {
|
|
1636
|
+
const { order: _legacyOrder, ...rest } = record;
|
|
1637
|
+
return rest;
|
|
1638
|
+
}
|
|
1639
|
+
function readRecord(value) {
|
|
1640
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1641
|
+
}
|
|
1642
|
+
function readString(value) {
|
|
1643
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1644
|
+
}
|
|
1645
|
+
function readFiniteNumber(value) {
|
|
1646
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1647
|
+
}
|
|
1648
|
+
function readCardKanbanStatus(value) {
|
|
1649
|
+
return typeof value === "string" && CARD_KANBAN_STATUSES.has(value) ? value : void 0;
|
|
1650
|
+
}
|
|
1651
|
+
function readCardPriority(value) {
|
|
1652
|
+
return typeof value === "string" && CARD_PRIORITIES.has(value) ? value : void 0;
|
|
1653
|
+
}
|
|
1654
|
+
function readCardLabels(value) {
|
|
1655
|
+
const record = readRecord(value);
|
|
1656
|
+
if (!record) return void 0;
|
|
1657
|
+
const labels = {};
|
|
1658
|
+
const color = readString(record.color);
|
|
1659
|
+
const emoji = readString(record.emoji);
|
|
1660
|
+
const tagIds = Array.isArray(record.tagIds) && record.tagIds.every((tagId) => typeof tagId === "string") ? [...record.tagIds] : void 0;
|
|
1661
|
+
if (color) labels.color = color;
|
|
1662
|
+
if (emoji) labels.emoji = emoji;
|
|
1663
|
+
if (tagIds) labels.tagIds = tagIds;
|
|
1664
|
+
return Object.keys(labels).length > 0 ? labels : void 0;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
// src/im/workspace/assets.ts
|
|
1668
|
+
var WORKSPACE_ASSETS_ROUTE = "/api/im/assets";
|
|
1669
|
+
|
|
1566
1670
|
// src/storage.ts
|
|
1567
1671
|
var MemoryStorage = class {
|
|
1568
1672
|
constructor() {
|
|
@@ -3014,10 +3118,13 @@ function createEnrichedExtractor(config) {
|
|
|
3014
3118
|
}
|
|
3015
3119
|
|
|
3016
3120
|
// src/evolution-runtime.ts
|
|
3121
|
+
var DEFAULT_OUTBOX_MAX_ATTEMPTS = 5;
|
|
3017
3122
|
var EvolutionRuntime = class {
|
|
3018
3123
|
constructor(client, config) {
|
|
3019
3124
|
this.client = client;
|
|
3020
3125
|
this.outbox = [];
|
|
3126
|
+
/** Entries that exceeded `outboxMaxAttempts`. Public-readable for tests/ops. */
|
|
3127
|
+
this.deadLetter = [];
|
|
3021
3128
|
this.started = false;
|
|
3022
3129
|
// Session tracking
|
|
3023
3130
|
this._sessions = [];
|
|
@@ -3027,7 +3134,8 @@ var EvolutionRuntime = class {
|
|
|
3027
3134
|
enrichment: config?.enrichment ?? { mode: "rules" },
|
|
3028
3135
|
scope: config?.scope ?? "global",
|
|
3029
3136
|
outboxMaxSize: config?.outboxMaxSize ?? 50,
|
|
3030
|
-
outboxFlushMs: config?.outboxFlushMs ?? 5e3
|
|
3137
|
+
outboxFlushMs: config?.outboxFlushMs ?? 5e3,
|
|
3138
|
+
outboxMaxAttempts: config?.outboxMaxAttempts ?? DEFAULT_OUTBOX_MAX_ATTEMPTS
|
|
3031
3139
|
};
|
|
3032
3140
|
this.scope = this.config.scope;
|
|
3033
3141
|
this.cache = new EvolutionCache();
|
|
@@ -3093,8 +3201,9 @@ var EvolutionRuntime = class {
|
|
|
3093
3201
|
confidence,
|
|
3094
3202
|
fromCache
|
|
3095
3203
|
};
|
|
3204
|
+
const narrowed = action === "apply_gene" || action === "create_suggested" || action === "none" ? action : "none";
|
|
3096
3205
|
return {
|
|
3097
|
-
action,
|
|
3206
|
+
action: narrowed,
|
|
3098
3207
|
geneId,
|
|
3099
3208
|
gene,
|
|
3100
3209
|
strategy,
|
|
@@ -3245,25 +3354,53 @@ var EvolutionRuntime = class {
|
|
|
3245
3354
|
} catch {
|
|
3246
3355
|
}
|
|
3247
3356
|
}
|
|
3248
|
-
/**
|
|
3357
|
+
/**
|
|
3358
|
+
* Flush outbox to server. Fire-and-forget contract: never blocks the caller's
|
|
3359
|
+
* critical path and never throws. Failed entries are retried until they hit
|
|
3360
|
+
* `outboxMaxAttempts`, then moved to `deadLetter` and reported via
|
|
3361
|
+
* `console.warn` so ops can detect a persistent failure.
|
|
3362
|
+
*/
|
|
3249
3363
|
async flush() {
|
|
3250
3364
|
if (this.outbox.length === 0) return;
|
|
3251
3365
|
const batch = this.outbox.splice(0, this.config.outboxMaxSize);
|
|
3252
|
-
const promises = batch.map(
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3366
|
+
const promises = batch.map(async (entry) => {
|
|
3367
|
+
try {
|
|
3368
|
+
const res = await this.client.record({
|
|
3369
|
+
gene_id: entry.geneId,
|
|
3370
|
+
signals: entry.signals.map((s) => s.type),
|
|
3371
|
+
outcome: entry.outcome,
|
|
3372
|
+
summary: entry.summary,
|
|
3373
|
+
score: entry.score,
|
|
3374
|
+
metadata: entry.metadata,
|
|
3375
|
+
scope: this.scope
|
|
3376
|
+
});
|
|
3377
|
+
if (!res.ok) {
|
|
3378
|
+
this._handleOutboxFailure(entry, res.error?.message ?? "record returned ok=false");
|
|
3379
|
+
}
|
|
3380
|
+
} catch (err) {
|
|
3381
|
+
this._handleOutboxFailure(entry, err instanceof Error ? err.message : String(err));
|
|
3382
|
+
}
|
|
3383
|
+
});
|
|
3265
3384
|
await Promise.allSettled(promises);
|
|
3266
3385
|
}
|
|
3386
|
+
/**
|
|
3387
|
+
* Bump the attempt counter and either re-queue (under ceiling) or move to
|
|
3388
|
+
* dead-letter. Always synchronous — caller's `flush()` already wraps in a
|
|
3389
|
+
* promise.allSettled.
|
|
3390
|
+
*/
|
|
3391
|
+
_handleOutboxFailure(entry, lastError) {
|
|
3392
|
+
const attempts = (entry.attempts ?? 0) + 1;
|
|
3393
|
+
const ceiling = this.config.outboxMaxAttempts;
|
|
3394
|
+
if (attempts >= ceiling) {
|
|
3395
|
+
const dropped = { ...entry, attempts, lastError, droppedAt: Date.now() };
|
|
3396
|
+
this.deadLetter.push(dropped);
|
|
3397
|
+
console.warn(
|
|
3398
|
+
`[EvolutionRuntime] outbox entry dropped after ${attempts} attempts: gene=${entry.geneId} outcome=${entry.outcome} error=${lastError}`
|
|
3399
|
+
);
|
|
3400
|
+
return;
|
|
3401
|
+
}
|
|
3402
|
+
this.outbox.push({ ...entry, attempts });
|
|
3403
|
+
}
|
|
3267
3404
|
};
|
|
3268
3405
|
|
|
3269
3406
|
// src/index.ts
|
|
@@ -3362,6 +3499,7 @@ var DirectClient = class {
|
|
|
3362
3499
|
content,
|
|
3363
3500
|
type: options?.type ?? "text",
|
|
3364
3501
|
metadata: options?.metadata,
|
|
3502
|
+
attachments: options?.attachments,
|
|
3365
3503
|
parentId: options?.parentId,
|
|
3366
3504
|
quotedMessageId: options?.quotedMessageId
|
|
3367
3505
|
});
|
|
@@ -3396,6 +3534,7 @@ var GroupsClient = class {
|
|
|
3396
3534
|
content,
|
|
3397
3535
|
type: options?.type ?? "text",
|
|
3398
3536
|
metadata: options?.metadata,
|
|
3537
|
+
attachments: options?.attachments,
|
|
3399
3538
|
parentId: options?.parentId,
|
|
3400
3539
|
quotedMessageId: options?.quotedMessageId
|
|
3401
3540
|
});
|
|
@@ -3474,6 +3613,7 @@ var MessagesClient = class {
|
|
|
3474
3613
|
content,
|
|
3475
3614
|
type: options?.type ?? "text",
|
|
3476
3615
|
metadata: options?.metadata,
|
|
3616
|
+
attachments: options?.attachments,
|
|
3477
3617
|
parentId: options?.parentId,
|
|
3478
3618
|
quotedMessageId: options?.quotedMessageId
|
|
3479
3619
|
});
|
|
@@ -3534,6 +3674,11 @@ var ContactsClient = class {
|
|
|
3534
3674
|
const query = {};
|
|
3535
3675
|
if (options?.type) query.type = options.type;
|
|
3536
3676
|
if (options?.capability) query.capability = options.capability;
|
|
3677
|
+
if (options?.status) query.status = options.status;
|
|
3678
|
+
if (options?.onlineOnly) query.onlineOnly = options.onlineOnly;
|
|
3679
|
+
if (options?.q) query.q = options.q;
|
|
3680
|
+
if (options?.limit) query.limit = options.limit;
|
|
3681
|
+
if (options?.offset) query.offset = options.offset;
|
|
3537
3682
|
return this._r("GET", "/api/im/discover", void 0, query);
|
|
3538
3683
|
}
|
|
3539
3684
|
// ─── Friend System (v1.8.0 P9) ─────────────────────────
|
|
@@ -3747,6 +3892,32 @@ var TasksClient = class {
|
|
|
3747
3892
|
async cancel(taskId) {
|
|
3748
3893
|
return this._r("DELETE", `/api/im/tasks/${taskId}`);
|
|
3749
3894
|
}
|
|
3895
|
+
/**
|
|
3896
|
+
* v2.0 release 200 §6.1 — unified state-machine transition.
|
|
3897
|
+
*
|
|
3898
|
+
* Drives every kanban / approve / reject / cancel / blocked / retry /
|
|
3899
|
+
* restore action through one endpoint. The 5 legacy endpoints
|
|
3900
|
+
* (start/complete/approve/reject/cancel) remain for backward
|
|
3901
|
+
* compatibility but new integrations should prefer this entrypoint.
|
|
3902
|
+
*
|
|
3903
|
+
* Server responds 409 (`code: 'invalid-transition'`) if the requested
|
|
3904
|
+
* `to` is not in the TRANSITIONS matrix from the current status, or
|
|
3905
|
+
* 403 (`code: 'forbidden'`) if the actor's tier is not in the rule's
|
|
3906
|
+
* `allowedActors`.
|
|
3907
|
+
*/
|
|
3908
|
+
async transition(taskId, options) {
|
|
3909
|
+
return this._r("POST", `/api/im/tasks/${taskId}/transition`, options);
|
|
3910
|
+
}
|
|
3911
|
+
/**
|
|
3912
|
+
* v2.0 release 200 §5.3 — admin escape-hatch.
|
|
3913
|
+
*
|
|
3914
|
+
* Bypasses the TRANSITIONS matrix. Restricted to workspace owner /
|
|
3915
|
+
* admin / trustTier>=4. Reason is required; the call is audit-logged
|
|
3916
|
+
* with `force_transition: true`. UI does NOT expose this — ops only.
|
|
3917
|
+
*/
|
|
3918
|
+
async forceTransition(taskId, options) {
|
|
3919
|
+
return this._r("POST", `/api/im/tasks/${taskId}/force-transition`, options);
|
|
3920
|
+
}
|
|
3750
3921
|
};
|
|
3751
3922
|
var MemoryClient = class {
|
|
3752
3923
|
constructor(_r) {
|
|
@@ -3875,9 +4046,118 @@ var SecurityClient = class {
|
|
|
3875
4046
|
return this._r("DELETE", `/api/im/conversations/${conversationId}/keys/${keyUserId}`);
|
|
3876
4047
|
}
|
|
3877
4048
|
};
|
|
4049
|
+
var EvolutionSkillsClient = class {
|
|
4050
|
+
constructor(_r) {
|
|
4051
|
+
this._r = _r;
|
|
4052
|
+
}
|
|
4053
|
+
/** List the skill catalog. Alias of search() for the v2.0 public surface. */
|
|
4054
|
+
async list(options) {
|
|
4055
|
+
return this.search(options);
|
|
4056
|
+
}
|
|
4057
|
+
/** Browse and search the skill catalog. */
|
|
4058
|
+
async search(options) {
|
|
4059
|
+
const query = {};
|
|
4060
|
+
if (options?.query) query.query = options.query;
|
|
4061
|
+
if (options?.category) query.category = options.category;
|
|
4062
|
+
if (options?.source) query.source = options.source;
|
|
4063
|
+
if (options?.compatibility) query.compatibility = options.compatibility;
|
|
4064
|
+
if (options?.sort) query.sort = options.sort;
|
|
4065
|
+
if (options?.page != null) query.page = String(options.page);
|
|
4066
|
+
if (options?.limit != null) query.limit = String(options.limit);
|
|
4067
|
+
return this._r("GET", "/api/im/skills/search", void 0, query);
|
|
4068
|
+
}
|
|
4069
|
+
/** Get skill catalog stats. */
|
|
4070
|
+
async stats() {
|
|
4071
|
+
return this._r("GET", "/api/im/skills/stats");
|
|
4072
|
+
}
|
|
4073
|
+
/** List available skill categories. */
|
|
4074
|
+
async categories() {
|
|
4075
|
+
return this._r("GET", "/api/im/skills/categories");
|
|
4076
|
+
}
|
|
4077
|
+
/** List trending skills. */
|
|
4078
|
+
async trending(limit) {
|
|
4079
|
+
const query = {};
|
|
4080
|
+
if (limit != null) query.limit = String(limit);
|
|
4081
|
+
return this._r("GET", "/api/im/skills/trending", void 0, query);
|
|
4082
|
+
}
|
|
4083
|
+
/** List skills created by the authenticated agent. */
|
|
4084
|
+
async created() {
|
|
4085
|
+
return this._r("GET", "/api/im/skills/created");
|
|
4086
|
+
}
|
|
4087
|
+
/** Get skill detail by slug or ID. */
|
|
4088
|
+
async get(slugOrId) {
|
|
4089
|
+
return this._r("GET", `/api/im/skills/${encodeURIComponent(slugOrId)}`);
|
|
4090
|
+
}
|
|
4091
|
+
/** Get full SKILL.md content and package metadata. */
|
|
4092
|
+
async content(slugOrId) {
|
|
4093
|
+
return this._r("GET", `/api/im/skills/${encodeURIComponent(slugOrId)}/content`);
|
|
4094
|
+
}
|
|
4095
|
+
/** Create/submit a workspace or community skill. */
|
|
4096
|
+
async create(input) {
|
|
4097
|
+
return this._r("POST", "/api/im/skills", input);
|
|
4098
|
+
}
|
|
4099
|
+
/** Update a skill. */
|
|
4100
|
+
async update(skillId, input) {
|
|
4101
|
+
return this._r("PATCH", `/api/im/skills/${encodeURIComponent(skillId)}`, input);
|
|
4102
|
+
}
|
|
4103
|
+
/** Soft-delete/deprecate a skill. */
|
|
4104
|
+
async delete(skillId) {
|
|
4105
|
+
return this._r("DELETE", `/api/im/skills/${encodeURIComponent(skillId)}`);
|
|
4106
|
+
}
|
|
4107
|
+
/** Install a skill for the authenticated agent. */
|
|
4108
|
+
async install(slugOrId, scope) {
|
|
4109
|
+
return this._r("POST", `/api/im/skills/${encodeURIComponent(slugOrId)}/install`, scope ? { scope } : void 0);
|
|
4110
|
+
}
|
|
4111
|
+
/** Uninstall a skill for the authenticated agent. */
|
|
4112
|
+
async uninstall(slugOrId, scope) {
|
|
4113
|
+
const query = scope ? { scope } : void 0;
|
|
4114
|
+
return this._r("DELETE", `/api/im/skills/${encodeURIComponent(slugOrId)}/install`, void 0, query);
|
|
4115
|
+
}
|
|
4116
|
+
/**
|
|
4117
|
+
* List installed skills. When agentId is supplied, this uses the v2.0 Layer 5
|
|
4118
|
+
* route and includes daemon sync state; otherwise it keeps the legacy current-agent route.
|
|
4119
|
+
*/
|
|
4120
|
+
async installed(options) {
|
|
4121
|
+
const query = {};
|
|
4122
|
+
if (options?.workspaceId) query.workspaceId = options.workspaceId;
|
|
4123
|
+
if (options?.includeInactive) query.includeInactive = "true";
|
|
4124
|
+
if (options?.agentId) {
|
|
4125
|
+
return this._r("GET", `/api/im/agents/${encodeURIComponent(options.agentId)}/skills`, void 0, query);
|
|
4126
|
+
}
|
|
4127
|
+
return this._r("GET", "/api/im/skills/installed", void 0, query);
|
|
4128
|
+
}
|
|
4129
|
+
/** Install a skill to a specific agent. */
|
|
4130
|
+
async installForAgent(agentId, skillIdOrSlug, options) {
|
|
4131
|
+
return this._r("POST", `/api/im/agents/${encodeURIComponent(agentId)}/skills`, {
|
|
4132
|
+
skillId: skillIdOrSlug,
|
|
4133
|
+
...options
|
|
4134
|
+
});
|
|
4135
|
+
}
|
|
4136
|
+
/** Disable/uninstall a skill from a specific agent. Built-ins are disabled cloud-side. */
|
|
4137
|
+
async uninstallFromAgent(agentId, skillIdOrSlug, options) {
|
|
4138
|
+
return this._r("DELETE", `/api/im/agents/${encodeURIComponent(agentId)}/skills`, {
|
|
4139
|
+
skillId: skillIdOrSlug,
|
|
4140
|
+
workspaceId: options?.workspaceId
|
|
4141
|
+
});
|
|
4142
|
+
}
|
|
4143
|
+
/** List skills whose daemon sync state is not current. */
|
|
4144
|
+
async pending(agentId, workspaceId) {
|
|
4145
|
+
const query = workspaceId ? { workspaceId } : void 0;
|
|
4146
|
+
return this._r("GET", `/api/im/agents/${encodeURIComponent(agentId)}/skills/pending`, void 0, query);
|
|
4147
|
+
}
|
|
4148
|
+
/** Acknowledge daemon sync for an installed skill. */
|
|
4149
|
+
async ack(agentId, input) {
|
|
4150
|
+
return this._r("POST", `/api/im/agents/${encodeURIComponent(agentId)}/skills/ack`, input);
|
|
4151
|
+
}
|
|
4152
|
+
/** Star a skill. */
|
|
4153
|
+
async star(skillId) {
|
|
4154
|
+
return this._r("POST", `/api/im/skills/${encodeURIComponent(skillId)}/star`);
|
|
4155
|
+
}
|
|
4156
|
+
};
|
|
3878
4157
|
var EvolutionClient = class {
|
|
3879
4158
|
constructor(_r) {
|
|
3880
4159
|
this._r = _r;
|
|
4160
|
+
this.skills = new EvolutionSkillsClient(_r);
|
|
3881
4161
|
}
|
|
3882
4162
|
// ── Public endpoints (no auth required) ──
|
|
3883
4163
|
/** Get evolution stats */
|
|
@@ -4007,7 +4287,7 @@ var EvolutionClient = class {
|
|
|
4007
4287
|
const { outcome, score, summary, strategy_used, scope, ...analyzeOpts } = options;
|
|
4008
4288
|
const analysis = await this.analyze({ ...analyzeOpts, ...scope ? { scope } : {} });
|
|
4009
4289
|
if (!analysis.ok || !analysis.data) {
|
|
4010
|
-
return { ok: false, error: analysis.error };
|
|
4290
|
+
return { ok: false, ...analysis.error ? { error: analysis.error } : {} };
|
|
4011
4291
|
}
|
|
4012
4292
|
const data = analysis.data;
|
|
4013
4293
|
const geneId = data.gene_id;
|
|
@@ -4121,39 +4401,35 @@ var EvolutionClient = class {
|
|
|
4121
4401
|
}
|
|
4122
4402
|
/** Search skills catalog */
|
|
4123
4403
|
async searchSkills(options) {
|
|
4124
|
-
|
|
4125
|
-
if (options?.query) q.query = options.query;
|
|
4126
|
-
if (options?.category) q.category = options.category;
|
|
4127
|
-
if (options?.limit != null) q.limit = String(options.limit);
|
|
4128
|
-
return this._r("GET", "/api/im/skills/search", void 0, q);
|
|
4404
|
+
return this.skills.search(options);
|
|
4129
4405
|
}
|
|
4130
4406
|
/** Get skill catalog stats */
|
|
4131
4407
|
async getSkillStats() {
|
|
4132
|
-
return this.
|
|
4408
|
+
return this.skills.stats();
|
|
4133
4409
|
}
|
|
4134
4410
|
/** Install a skill — creates Gene + returns content + install guide */
|
|
4135
4411
|
async installSkill(slugOrId, scope) {
|
|
4136
|
-
return this.
|
|
4412
|
+
return this.skills.install(slugOrId, scope);
|
|
4137
4413
|
}
|
|
4138
4414
|
/** Uninstall a skill */
|
|
4139
|
-
async uninstallSkill(slugOrId) {
|
|
4140
|
-
return this.
|
|
4415
|
+
async uninstallSkill(slugOrId, scope) {
|
|
4416
|
+
return this.skills.uninstall(slugOrId, scope);
|
|
4141
4417
|
}
|
|
4142
4418
|
/** List installed skills for this agent */
|
|
4143
|
-
async installedSkills() {
|
|
4144
|
-
return this.
|
|
4419
|
+
async installedSkills(options) {
|
|
4420
|
+
return this.skills.installed(options);
|
|
4145
4421
|
}
|
|
4146
4422
|
/** Get full skill content (SKILL.md + package info) */
|
|
4147
4423
|
async getSkillContent(slugOrId) {
|
|
4148
|
-
return this.
|
|
4424
|
+
return this.skills.content(slugOrId);
|
|
4149
4425
|
}
|
|
4150
4426
|
/** Create/submit a community skill */
|
|
4151
4427
|
async createSkill(input) {
|
|
4152
|
-
return this.
|
|
4428
|
+
return this.skills.create(input);
|
|
4153
4429
|
}
|
|
4154
4430
|
/** Star a skill (increment community rating) */
|
|
4155
4431
|
async starSkill(skillId) {
|
|
4156
|
-
return this.
|
|
4432
|
+
return this.skills.star(skillId);
|
|
4157
4433
|
}
|
|
4158
4434
|
/**
|
|
4159
4435
|
* Install a skill and write SKILL.md to local filesystem.
|
|
@@ -4163,19 +4439,26 @@ var EvolutionClient = class {
|
|
|
4163
4439
|
*/
|
|
4164
4440
|
async installSkillLocal(slugOrId, options) {
|
|
4165
4441
|
const result = await this.installSkill(slugOrId);
|
|
4166
|
-
if (!result.ok || !result.data)
|
|
4167
|
-
|
|
4442
|
+
if (!result.ok || !result.data) {
|
|
4443
|
+
return result;
|
|
4444
|
+
}
|
|
4445
|
+
const installData = result.data;
|
|
4446
|
+
const withLocalPaths = (localPaths2) => ({
|
|
4447
|
+
ok: true,
|
|
4448
|
+
data: { ...installData, localPaths: localPaths2 }
|
|
4449
|
+
});
|
|
4450
|
+
let content = installData.skill?.content || "";
|
|
4168
4451
|
if (!content) {
|
|
4169
4452
|
const contentResult = await this.getSkillContent(slugOrId);
|
|
4170
4453
|
content = contentResult.data?.content || "";
|
|
4171
4454
|
}
|
|
4172
4455
|
if (!content) {
|
|
4173
|
-
return
|
|
4456
|
+
return withLocalPaths([]);
|
|
4174
4457
|
}
|
|
4175
|
-
const rawSlug =
|
|
4458
|
+
const rawSlug = installData.skill?.slug || slugOrId;
|
|
4176
4459
|
const slug = rawSlug.replace(/[\/\\]/g, "").replace(/\.\./g, "");
|
|
4177
4460
|
if (!slug) {
|
|
4178
|
-
return
|
|
4461
|
+
return withLocalPaths([]);
|
|
4179
4462
|
}
|
|
4180
4463
|
const localPaths = [];
|
|
4181
4464
|
try {
|
|
@@ -4195,7 +4478,7 @@ var EvolutionClient = class {
|
|
|
4195
4478
|
"opencode": path.join(home, ".config", "opencode", "skills", slug),
|
|
4196
4479
|
"plugin": path.join(pluginBase, "skills", slug)
|
|
4197
4480
|
};
|
|
4198
|
-
const targets = options?.platforms
|
|
4481
|
+
const targets = options?.platforms ?? Object.keys(platformPaths);
|
|
4199
4482
|
for (const platform of targets) {
|
|
4200
4483
|
const dir = platformPaths[platform];
|
|
4201
4484
|
if (!dir) continue;
|
|
@@ -4209,7 +4492,7 @@ var EvolutionClient = class {
|
|
|
4209
4492
|
}
|
|
4210
4493
|
} catch {
|
|
4211
4494
|
}
|
|
4212
|
-
return
|
|
4495
|
+
return withLocalPaths(localPaths);
|
|
4213
4496
|
}
|
|
4214
4497
|
/**
|
|
4215
4498
|
* Uninstall a skill and remove local SKILL.md files.
|
|
@@ -4217,8 +4500,13 @@ var EvolutionClient = class {
|
|
|
4217
4500
|
async uninstallSkillLocal(slugOrId) {
|
|
4218
4501
|
const result = await this.uninstallSkill(slugOrId);
|
|
4219
4502
|
const removedPaths = [];
|
|
4503
|
+
const withRemoved = (ok, paths) => ({
|
|
4504
|
+
ok: result.ok,
|
|
4505
|
+
...result.error ? { error: result.error } : {},
|
|
4506
|
+
data: { uninstalled: ok, removedPaths: paths }
|
|
4507
|
+
});
|
|
4220
4508
|
const slug = safeSlug(slugOrId);
|
|
4221
|
-
if (!slug) return
|
|
4509
|
+
if (!slug) return withRemoved(result.data?.uninstalled ?? false, removedPaths);
|
|
4222
4510
|
try {
|
|
4223
4511
|
const fs = await import("fs");
|
|
4224
4512
|
const path = await import("path");
|
|
@@ -4242,7 +4530,7 @@ var EvolutionClient = class {
|
|
|
4242
4530
|
}
|
|
4243
4531
|
} catch {
|
|
4244
4532
|
}
|
|
4245
|
-
return
|
|
4533
|
+
return withRemoved(result.data?.uninstalled ?? false, removedPaths);
|
|
4246
4534
|
}
|
|
4247
4535
|
/**
|
|
4248
4536
|
* Sync all installed skills to local filesystem.
|
|
@@ -4282,7 +4570,7 @@ var EvolutionClient = class {
|
|
|
4282
4570
|
"opencode": path.join(home, ".config", "opencode", "skills", slug),
|
|
4283
4571
|
"plugin": path.join(pluginBase, "skills", slug)
|
|
4284
4572
|
};
|
|
4285
|
-
const targets = options?.platforms
|
|
4573
|
+
const targets = options?.platforms ?? Object.keys(platformPaths);
|
|
4286
4574
|
for (const platform of targets) {
|
|
4287
4575
|
const dir = platformPaths[platform];
|
|
4288
4576
|
if (!dir) continue;
|
|
@@ -4331,12 +4619,34 @@ var EvolutionClient = class {
|
|
|
4331
4619
|
if (since != null) query.since = String(since);
|
|
4332
4620
|
return this._r("GET", "/api/im/evolution/sync/snapshot", void 0, query);
|
|
4333
4621
|
}
|
|
4334
|
-
/**
|
|
4622
|
+
/**
|
|
4623
|
+
* Bidirectional sync: push local outcomes and pull remote updates.
|
|
4624
|
+
*
|
|
4625
|
+
* Accepts either the flat shape (`pushOutcomes` / `pullSince`) used by older
|
|
4626
|
+
* callers or the nested shape (`push` / `pull`) that mirrors the wire format
|
|
4627
|
+
* expected by `POST /api/im/evolution/sync`. The nested shape is preferred for
|
|
4628
|
+
* new code because it lets you pin a scope per-side.
|
|
4629
|
+
*/
|
|
4335
4630
|
async sync(options) {
|
|
4336
4631
|
const body = {};
|
|
4337
|
-
|
|
4338
|
-
if (
|
|
4339
|
-
|
|
4632
|
+
const outcomes = options?.push?.outcomes ?? options?.pushOutcomes;
|
|
4633
|
+
if (outcomes) {
|
|
4634
|
+
body.push = {
|
|
4635
|
+
outcomes,
|
|
4636
|
+
...options?.push?.scope ? { scope: options.push.scope } : {},
|
|
4637
|
+
...options?.push?.workspaceId ? { workspaceId: options.push.workspaceId } : {}
|
|
4638
|
+
};
|
|
4639
|
+
}
|
|
4640
|
+
const since = options?.pull?.since ?? options?.pullSince;
|
|
4641
|
+
if (since != null) {
|
|
4642
|
+
body.pull = {
|
|
4643
|
+
since,
|
|
4644
|
+
...options?.pull?.scope ? { scope: options.pull.scope } : {}
|
|
4645
|
+
};
|
|
4646
|
+
}
|
|
4647
|
+
const query = {};
|
|
4648
|
+
if (options?.scope) query.scope = options.scope;
|
|
4649
|
+
return this._r("POST", "/api/im/evolution/sync", body, query);
|
|
4340
4650
|
}
|
|
4341
4651
|
};
|
|
4342
4652
|
function safeSlug(input) {
|
|
@@ -4380,6 +4690,26 @@ var WorkspacesClient = class {
|
|
|
4380
4690
|
async archive(workspaceId) {
|
|
4381
4691
|
return this._r("DELETE", `/api/im/workspaces/${workspaceId}`);
|
|
4382
4692
|
}
|
|
4693
|
+
/**
|
|
4694
|
+
* Get the workspace's orchestrator agent (Chief of Staff) — readable by any
|
|
4695
|
+
* member. Returns `{ workspace, orchestrator: null }` when no active
|
|
4696
|
+
* appointment exists. See release 200 §4.
|
|
4697
|
+
*/
|
|
4698
|
+
async getOrchestrator(workspaceId) {
|
|
4699
|
+
return this._r("GET", `/api/im/workspaces/${workspaceId}/orchestrator`);
|
|
4700
|
+
}
|
|
4701
|
+
/**
|
|
4702
|
+
* Appoint an agent as the workspace's orchestrator. Owner-only. If an
|
|
4703
|
+
* orchestrator is already active, this auto-revokes the previous one in the
|
|
4704
|
+
* same UPDATE.
|
|
4705
|
+
*/
|
|
4706
|
+
async appointOrchestrator(workspaceId, agentImUserId) {
|
|
4707
|
+
return this._r("POST", `/api/im/workspaces/${workspaceId}/orchestrator`, { agentImUserId });
|
|
4708
|
+
}
|
|
4709
|
+
/** Revoke the workspace's current orchestrator. Owner-only. Idempotent. */
|
|
4710
|
+
async revokeOrchestrator(workspaceId) {
|
|
4711
|
+
return this._r("DELETE", `/api/im/workspaces/${workspaceId}/orchestrator`);
|
|
4712
|
+
}
|
|
4383
4713
|
};
|
|
4384
4714
|
var WorkspaceFilesClient = class {
|
|
4385
4715
|
constructor(_r) {
|
|
@@ -4413,6 +4743,83 @@ var WorkspaceFilesClient = class {
|
|
|
4413
4743
|
return this._r("GET", `/api/im/workspaces/${workspaceId}/files/${fileId}/history`);
|
|
4414
4744
|
}
|
|
4415
4745
|
};
|
|
4746
|
+
var MAX_IM_ASSET_BYTES = 1024 * 1024 * 1024;
|
|
4747
|
+
var DIRECT_ASSET_UPLOAD_FALLBACK_STATUSES = /* @__PURE__ */ new Set([404, 501, 503]);
|
|
4748
|
+
function toHex(bytes) {
|
|
4749
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
4750
|
+
}
|
|
4751
|
+
async function sha256BytesHex(bytes) {
|
|
4752
|
+
if (globalThis.crypto?.subtle) {
|
|
4753
|
+
const ab = new ArrayBuffer(bytes.byteLength);
|
|
4754
|
+
new Uint8Array(ab).set(bytes);
|
|
4755
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", ab);
|
|
4756
|
+
return toHex(new Uint8Array(digest));
|
|
4757
|
+
}
|
|
4758
|
+
const { createHash } = await import("crypto");
|
|
4759
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
4760
|
+
}
|
|
4761
|
+
function bytesToBlob(bytes, mimeType) {
|
|
4762
|
+
const ab = new ArrayBuffer(bytes.byteLength);
|
|
4763
|
+
new Uint8Array(ab).set(bytes);
|
|
4764
|
+
return new Blob([ab], { type: mimeType });
|
|
4765
|
+
}
|
|
4766
|
+
function isNamedFile(input) {
|
|
4767
|
+
return typeof File !== "undefined" && input instanceof File;
|
|
4768
|
+
}
|
|
4769
|
+
function normalizeStringHeaders(value) {
|
|
4770
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
4771
|
+
const out = {};
|
|
4772
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
4773
|
+
if (typeof raw === "string") out[key] = raw;
|
|
4774
|
+
}
|
|
4775
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
4776
|
+
}
|
|
4777
|
+
function isDirectAssetUploadPlan(value) {
|
|
4778
|
+
if (!value || typeof value !== "object") return false;
|
|
4779
|
+
const plan = value;
|
|
4780
|
+
if (plan.mode === "single") {
|
|
4781
|
+
return typeof plan.uploadUrl === "string" && typeof plan.bucket === "string" && typeof plan.key === "string";
|
|
4782
|
+
}
|
|
4783
|
+
if (plan.mode === "multipart") {
|
|
4784
|
+
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) => {
|
|
4785
|
+
const item = part;
|
|
4786
|
+
return typeof item.url === "string" && Number.isInteger(item.partNumber);
|
|
4787
|
+
});
|
|
4788
|
+
}
|
|
4789
|
+
return false;
|
|
4790
|
+
}
|
|
4791
|
+
function assetUploadError(code, message) {
|
|
4792
|
+
return { ok: false, error: { code, message } };
|
|
4793
|
+
}
|
|
4794
|
+
async function normalizeAssetUploadInput(input, options) {
|
|
4795
|
+
let bytes;
|
|
4796
|
+
let fileName;
|
|
4797
|
+
if (typeof input === "string") {
|
|
4798
|
+
const fs = await import("fs");
|
|
4799
|
+
const path = await import("path");
|
|
4800
|
+
const buf = await fs.promises.readFile(input);
|
|
4801
|
+
bytes = new Uint8Array(buf);
|
|
4802
|
+
fileName = options.fileName || path.basename(input);
|
|
4803
|
+
} else if (typeof Blob !== "undefined" && input instanceof Blob) {
|
|
4804
|
+
const ab = await input.arrayBuffer();
|
|
4805
|
+
bytes = new Uint8Array(ab);
|
|
4806
|
+
fileName = options.fileName || (isNamedFile(input) ? input.name : "");
|
|
4807
|
+
if (!fileName) throw new Error("fileName is required when uploading Blob without name");
|
|
4808
|
+
} else if (input instanceof Uint8Array) {
|
|
4809
|
+
bytes = input;
|
|
4810
|
+
fileName = options.fileName || "";
|
|
4811
|
+
if (!fileName) throw new Error("fileName is required when uploading Buffer or Uint8Array");
|
|
4812
|
+
} else {
|
|
4813
|
+
throw new Error("Unsupported input type");
|
|
4814
|
+
}
|
|
4815
|
+
const sizeBytes = bytes.byteLength;
|
|
4816
|
+
if (sizeBytes > MAX_IM_ASSET_BYTES) {
|
|
4817
|
+
throw new Error("Asset exceeds 1 GB cap");
|
|
4818
|
+
}
|
|
4819
|
+
const mimeType = options.mimeType || guessMimeType(fileName);
|
|
4820
|
+
const contentHash = await sha256BytesHex(bytes);
|
|
4821
|
+
return { bytes, fileName, mimeType, sizeBytes, contentHash };
|
|
4822
|
+
}
|
|
4416
4823
|
var AssetsClient = class {
|
|
4417
4824
|
constructor(_r, _baseUrl, _fetchFn, _getAuthHeaders) {
|
|
4418
4825
|
this._r = _r;
|
|
@@ -4479,57 +4886,130 @@ var AssetsClient = class {
|
|
|
4479
4886
|
};
|
|
4480
4887
|
}
|
|
4481
4888
|
/**
|
|
4482
|
-
* Upload bytes as an asset
|
|
4483
|
-
*
|
|
4889
|
+
* Upload bytes as an asset. Uses direct-to-S3 upload when the server exposes
|
|
4890
|
+
* `/assets/direct-upload/*`; falls back to legacy multipart POST for local
|
|
4891
|
+
* filesystem mode and older deployments. The client always sends SHA-256 for
|
|
4892
|
+
* server-side byte integrity checks. Hard cap: 1 GB.
|
|
4484
4893
|
*/
|
|
4485
4894
|
async upload(input, options) {
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
if (
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4895
|
+
const normalized = await normalizeAssetUploadInput(input, options);
|
|
4896
|
+
const direct = await this._tryDirectUpload(normalized, options);
|
|
4897
|
+
if (direct) return direct;
|
|
4898
|
+
return this._uploadMultipart(normalized, options);
|
|
4899
|
+
}
|
|
4900
|
+
async _tryDirectUpload(file, options) {
|
|
4901
|
+
const init = await this._postAssetJson("/direct-upload/init", {
|
|
4902
|
+
workspaceId: options.workspaceId,
|
|
4903
|
+
filename: file.fileName,
|
|
4904
|
+
mime: file.mimeType,
|
|
4905
|
+
sizeBytes: file.sizeBytes,
|
|
4906
|
+
contentHash: file.contentHash
|
|
4907
|
+
});
|
|
4908
|
+
if (!init.response.ok) {
|
|
4909
|
+
if (DIRECT_ASSET_UPLOAD_FALLBACK_STATUSES.has(init.response.status)) return null;
|
|
4910
|
+
return init.data && init.data.ok === false ? {
|
|
4911
|
+
ok: false,
|
|
4912
|
+
error: init.data.error ?? {
|
|
4913
|
+
code: "http_error",
|
|
4914
|
+
message: `Direct upload init failed (${init.response.status})`
|
|
4915
|
+
}
|
|
4916
|
+
} : assetUploadError("http_error", `Direct upload init failed (${init.response.status})`);
|
|
4505
4917
|
}
|
|
4506
|
-
|
|
4507
|
-
const
|
|
4508
|
-
|
|
4509
|
-
|
|
4918
|
+
if (!isDirectAssetUploadPlan(init.data?.data)) return null;
|
|
4919
|
+
const plan = init.data.data;
|
|
4920
|
+
try {
|
|
4921
|
+
const parts = await this._putDirectUploadBytes(plan, file, options.onProgress);
|
|
4922
|
+
const complete = await this._postAssetJson("/direct-upload/complete", {
|
|
4923
|
+
workspaceId: options.workspaceId,
|
|
4924
|
+
filename: file.fileName,
|
|
4925
|
+
mime: file.mimeType,
|
|
4926
|
+
sizeBytes: file.sizeBytes,
|
|
4927
|
+
contentHash: file.contentHash,
|
|
4928
|
+
kind: options.kind,
|
|
4929
|
+
metadata: options.metadata,
|
|
4930
|
+
sourceTaskId: options.sourceTaskId,
|
|
4931
|
+
sourceAgentImUserId: options.sourceAgentImUserId,
|
|
4932
|
+
bucket: plan.bucket,
|
|
4933
|
+
key: plan.key,
|
|
4934
|
+
...plan.mode === "multipart" ? { uploadId: plan.uploadId, parts: parts ?? [] } : {}
|
|
4935
|
+
});
|
|
4936
|
+
if (!complete.response.ok) {
|
|
4937
|
+
return complete.data && complete.data.ok === false ? complete.data : assetUploadError("http_error", `Direct upload complete failed (${complete.response.status})`);
|
|
4938
|
+
}
|
|
4939
|
+
if (!complete.data) {
|
|
4940
|
+
return assetUploadError("invalid_response", "Direct upload complete returned an empty response");
|
|
4941
|
+
}
|
|
4942
|
+
return complete.data;
|
|
4943
|
+
} catch {
|
|
4944
|
+
return null;
|
|
4945
|
+
}
|
|
4946
|
+
}
|
|
4947
|
+
async _postAssetJson(path, body) {
|
|
4948
|
+
const response = await this._fetchFn(`${this._baseUrl}/api/im/assets${path}`, {
|
|
4949
|
+
method: "POST",
|
|
4950
|
+
headers: {
|
|
4951
|
+
...this._getAuthHeaders(),
|
|
4952
|
+
"Content-Type": "application/json",
|
|
4953
|
+
Accept: "application/json"
|
|
4954
|
+
},
|
|
4955
|
+
body: JSON.stringify(body)
|
|
4956
|
+
});
|
|
4957
|
+
const data = await response.json().catch(() => null);
|
|
4958
|
+
return { response, data };
|
|
4959
|
+
}
|
|
4960
|
+
async _putDirectUploadBytes(plan, file, onProgress) {
|
|
4961
|
+
if (plan.mode === "single") {
|
|
4962
|
+
const response = await this._fetchFn(plan.uploadUrl, {
|
|
4963
|
+
method: plan.method ?? "PUT",
|
|
4964
|
+
headers: normalizeStringHeaders(plan.headers),
|
|
4965
|
+
body: bytesToBlob(file.bytes, file.mimeType)
|
|
4966
|
+
});
|
|
4967
|
+
if (!response.ok) throw new Error(`signed PUT failed (${response.status})`);
|
|
4968
|
+
onProgress?.(file.sizeBytes, file.sizeBytes);
|
|
4969
|
+
return null;
|
|
4970
|
+
}
|
|
4971
|
+
const completedParts = [];
|
|
4972
|
+
let uploaded = 0;
|
|
4973
|
+
for (const part of plan.parts) {
|
|
4974
|
+
const start = (part.partNumber - 1) * plan.partSizeBytes;
|
|
4975
|
+
const end = Math.min(start + plan.partSizeBytes, file.sizeBytes);
|
|
4976
|
+
const chunk = file.bytes.slice(start, end);
|
|
4977
|
+
const response = await this._fetchFn(part.url, { method: "PUT", body: bytesToBlob(chunk, file.mimeType) });
|
|
4978
|
+
if (!response.ok) throw new Error(`signed multipart PUT failed for part ${part.partNumber} (${response.status})`);
|
|
4979
|
+
const etag = response.headers.get("etag")?.replace(/^"|"$/g, "");
|
|
4980
|
+
if (!etag) throw new Error(`signed multipart PUT missing ETag for part ${part.partNumber}`);
|
|
4981
|
+
completedParts.push({ partNumber: part.partNumber, etag });
|
|
4982
|
+
uploaded += chunk.byteLength;
|
|
4983
|
+
onProgress?.(uploaded, file.sizeBytes);
|
|
4510
4984
|
}
|
|
4985
|
+
return completedParts;
|
|
4986
|
+
}
|
|
4987
|
+
async _uploadMultipart(file, options) {
|
|
4988
|
+
const { bytes, fileName, mimeType, sizeBytes, contentHash } = file;
|
|
4511
4989
|
const formData = new FormData();
|
|
4512
|
-
|
|
4513
|
-
new Uint8Array(ab).set(bytes);
|
|
4514
|
-
formData.append("file", new Blob([ab], { type: mimeType }), fileName);
|
|
4990
|
+
formData.append("file", bytesToBlob(bytes, mimeType), fileName);
|
|
4515
4991
|
formData.append("workspaceId", options.workspaceId);
|
|
4516
4992
|
if (options.kind) formData.append("kind", options.kind);
|
|
4517
4993
|
if (options.sourceAgentImUserId) formData.append("sourceAgentImUserId", options.sourceAgentImUserId);
|
|
4518
4994
|
if (options.sourceTaskId) formData.append("sourceTaskId", options.sourceTaskId);
|
|
4519
4995
|
if (options.metadata) formData.append("metadata", JSON.stringify(options.metadata));
|
|
4996
|
+
formData.append("contentSha256", contentHash);
|
|
4520
4997
|
const resp = await this._fetchFn(`${this._baseUrl}/api/im/assets`, {
|
|
4521
4998
|
method: "POST",
|
|
4522
4999
|
body: formData,
|
|
4523
|
-
headers:
|
|
5000
|
+
headers: {
|
|
5001
|
+
...this._getAuthHeaders(),
|
|
5002
|
+
"X-Content-Sha256": contentHash
|
|
5003
|
+
}
|
|
4524
5004
|
});
|
|
4525
|
-
options.onProgress?.(sizeBytes, sizeBytes);
|
|
4526
5005
|
const data = await resp.json().catch(() => ({}));
|
|
4527
5006
|
if (!resp.ok) {
|
|
4528
5007
|
return {
|
|
4529
5008
|
ok: false,
|
|
4530
|
-
error: data?.error || { code: "
|
|
5009
|
+
error: data?.error || { code: "http_error", message: `Upload failed (${resp.status})` }
|
|
4531
5010
|
};
|
|
4532
5011
|
}
|
|
5012
|
+
options.onProgress?.(sizeBytes, sizeBytes);
|
|
4533
5013
|
return data;
|
|
4534
5014
|
}
|
|
4535
5015
|
};
|
|
@@ -4895,6 +5375,7 @@ var IMRealtimeClient = class {
|
|
|
4895
5375
|
};
|
|
4896
5376
|
var IMClient = class {
|
|
4897
5377
|
constructor(request, wsBase, fetchFn, getAuthHeaders, offlineManager, communityHubConfig) {
|
|
5378
|
+
this._request = request;
|
|
4898
5379
|
this.account = new AccountClient(request);
|
|
4899
5380
|
this.direct = new DirectClient(request);
|
|
4900
5381
|
this.groups = new GroupsClient(request);
|
|
@@ -4921,7 +5402,7 @@ var IMClient = class {
|
|
|
4921
5402
|
}
|
|
4922
5403
|
/** IM health check */
|
|
4923
5404
|
async health() {
|
|
4924
|
-
return this.
|
|
5405
|
+
return this._request("GET", "/api/im/health");
|
|
4925
5406
|
}
|
|
4926
5407
|
/** Get workspace superset view with slot filtering */
|
|
4927
5408
|
async getWorkspace(scope, slots, includeContent) {
|
|
@@ -4929,7 +5410,22 @@ var IMClient = class {
|
|
|
4929
5410
|
if (scope) params.set("scope", scope);
|
|
4930
5411
|
if (slots?.length) params.set("slots", slots.join(","));
|
|
4931
5412
|
if (includeContent) params.set("includeContent", "true");
|
|
4932
|
-
return this.
|
|
5413
|
+
return this._request("GET", `/api/im/workspace/view?${params}`);
|
|
5414
|
+
}
|
|
5415
|
+
/**
|
|
5416
|
+
* Issue a typed IM API request via the shared `RequestFn` pipeline.
|
|
5417
|
+
*
|
|
5418
|
+
* Use this when you need to hit an IM endpoint that isn't (yet) exposed by a
|
|
5419
|
+
* sub-client (e.g. `/api/im/approvals`). Same auth + offline routing + retry
|
|
5420
|
+
* behaviour as the typed sub-clients.
|
|
5421
|
+
*
|
|
5422
|
+
* @example
|
|
5423
|
+
* const res = await client.im.request<ApprovalCreateResponse>(
|
|
5424
|
+
* 'POST', '/api/im/approvals', { category, title, context, options },
|
|
5425
|
+
* );
|
|
5426
|
+
*/
|
|
5427
|
+
async request(method, path, body, query) {
|
|
5428
|
+
return this._request(method, path, body, query);
|
|
4933
5429
|
}
|
|
4934
5430
|
};
|
|
4935
5431
|
var PrismerClient = class {
|
|
@@ -4997,6 +5493,10 @@ var PrismerClient = class {
|
|
|
4997
5493
|
this._offlineManager,
|
|
4998
5494
|
config.community ?? null
|
|
4999
5495
|
);
|
|
5496
|
+
this.workspaces = this.im.workspaces;
|
|
5497
|
+
this.workspaceFiles = this.im.workspaceFiles;
|
|
5498
|
+
this.assets = this.im.assets;
|
|
5499
|
+
this.evolution = this.im.evolution;
|
|
5000
5500
|
}
|
|
5001
5501
|
/** Wait for identity to be ready (useful for tests or explicit await) */
|
|
5002
5502
|
async ensureIdentity() {
|
|
@@ -5045,6 +5545,39 @@ var PrismerClient = class {
|
|
|
5045
5545
|
await this._offlineManager.destroy();
|
|
5046
5546
|
}
|
|
5047
5547
|
}
|
|
5548
|
+
/**
|
|
5549
|
+
* Issue an authenticated raw HTTP request against the configured base URL,
|
|
5550
|
+
* returning the underlying `Response` so callers can inspect headers
|
|
5551
|
+
* (`Content-Range`, `Content-Length`, etc.) and stream the body.
|
|
5552
|
+
*
|
|
5553
|
+
* The path may be absolute (`/api/...`) or a full URL — full URLs are used
|
|
5554
|
+
* verbatim (useful for following 302 redirects), otherwise the path is
|
|
5555
|
+
* appended to the client's configured `baseUrl`. Authorization + `X-IM-Agent`
|
|
5556
|
+
* headers are added automatically; caller-supplied headers in `init.headers`
|
|
5557
|
+
* override them on collision.
|
|
5558
|
+
*
|
|
5559
|
+
* Use this for binary downloads / partial fetches; for normal JSON-envelope
|
|
5560
|
+
* IM requests use `client.im.request()` (typed) or the typed sub-clients.
|
|
5561
|
+
*/
|
|
5562
|
+
async fetchAuthed(url, init) {
|
|
5563
|
+
const fullUrl = /^https?:\/\//i.test(url) ? url : `${this.baseUrl}${url}`;
|
|
5564
|
+
const authHeaders = this._getAuthHeaders();
|
|
5565
|
+
const callerHeaders = {};
|
|
5566
|
+
if (init?.headers) {
|
|
5567
|
+
const h = init.headers;
|
|
5568
|
+
if (h instanceof Headers) {
|
|
5569
|
+
h.forEach((v, k) => {
|
|
5570
|
+
callerHeaders[k] = v;
|
|
5571
|
+
});
|
|
5572
|
+
} else if (Array.isArray(h)) {
|
|
5573
|
+
for (const [k, v] of h) callerHeaders[k] = v;
|
|
5574
|
+
} else {
|
|
5575
|
+
Object.assign(callerHeaders, h);
|
|
5576
|
+
}
|
|
5577
|
+
}
|
|
5578
|
+
const headers = { ...authHeaders, ...callerHeaders };
|
|
5579
|
+
return this.fetchFn(fullUrl, { ...init ?? {}, headers });
|
|
5580
|
+
}
|
|
5048
5581
|
// --------------------------------------------------------------------------
|
|
5049
5582
|
// Internal request helper
|
|
5050
5583
|
// --------------------------------------------------------------------------
|
|
@@ -5081,18 +5614,18 @@ var PrismerClient = class {
|
|
|
5081
5614
|
}
|
|
5082
5615
|
}
|
|
5083
5616
|
if (!response.ok) {
|
|
5084
|
-
const err = data.error || { code: "
|
|
5617
|
+
const err = data.error || { code: "http_error", message: `Request failed with status ${response.status}` };
|
|
5085
5618
|
return { ...data, success: false, ok: false, error: err };
|
|
5086
5619
|
}
|
|
5087
5620
|
return data;
|
|
5088
5621
|
} catch (error) {
|
|
5089
5622
|
if (error instanceof Error && error.name === "AbortError") {
|
|
5090
|
-
return { success: false, ok: false, error: { code: "
|
|
5623
|
+
return { success: false, ok: false, error: { code: "timeout", message: "Request timed out" } };
|
|
5091
5624
|
}
|
|
5092
5625
|
return {
|
|
5093
5626
|
success: false,
|
|
5094
5627
|
ok: false,
|
|
5095
|
-
error: { code: "
|
|
5628
|
+
error: { code: "cloud_unreachable", message: error instanceof Error ? error.message : "Unknown error" }
|
|
5096
5629
|
};
|
|
5097
5630
|
} finally {
|
|
5098
5631
|
clearTimeout(timeoutId);
|
|
@@ -5182,6 +5715,7 @@ function createClient(config) {
|
|
|
5182
5715
|
EvolutionCache,
|
|
5183
5716
|
EvolutionClient,
|
|
5184
5717
|
EvolutionRuntime,
|
|
5718
|
+
EvolutionSkillsClient,
|
|
5185
5719
|
FilesClient,
|
|
5186
5720
|
GroupsClient,
|
|
5187
5721
|
IMClient,
|
|
@@ -5201,6 +5735,7 @@ function createClient(config) {
|
|
|
5201
5735
|
SecurityClient,
|
|
5202
5736
|
TabCoordinator,
|
|
5203
5737
|
TasksClient,
|
|
5738
|
+
WORKSPACE_ASSETS_ROUTE,
|
|
5204
5739
|
WorkspaceClient,
|
|
5205
5740
|
WorkspaceFilesClient,
|
|
5206
5741
|
WorkspacesClient,
|
|
@@ -5215,5 +5750,7 @@ function createClient(config) {
|
|
|
5215
5750
|
encryptForSend,
|
|
5216
5751
|
extractSignals,
|
|
5217
5752
|
guessMimeType,
|
|
5218
|
-
|
|
5753
|
+
readCardKanbanMetadata,
|
|
5754
|
+
safeSlug,
|
|
5755
|
+
writeCardKanbanMetadata
|
|
5219
5756
|
});
|