@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/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
- safeSlug: () => safeSlug
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
- /** Flush outbox to server */
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
- (entry) => this.client.record({
3254
- gene_id: entry.geneId,
3255
- signals: entry.signals.map((s) => s.type),
3256
- outcome: entry.outcome,
3257
- summary: entry.summary,
3258
- score: entry.score,
3259
- metadata: entry.metadata,
3260
- scope: this.scope
3261
- }).catch(() => {
3262
- this.outbox.push(entry);
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
- const q = {};
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._r("GET", "/api/im/skills/stats");
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._r("POST", `/api/im/skills/${encodeURIComponent(slugOrId)}/install`, scope ? { scope } : void 0);
4412
+ return this.skills.install(slugOrId, scope);
4137
4413
  }
4138
4414
  /** Uninstall a skill */
4139
- async uninstallSkill(slugOrId) {
4140
- return this._r("DELETE", `/api/im/skills/${encodeURIComponent(slugOrId)}/install`);
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._r("GET", "/api/im/skills/installed");
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._r("GET", `/api/im/skills/${encodeURIComponent(slugOrId)}/content`);
4424
+ return this.skills.content(slugOrId);
4149
4425
  }
4150
4426
  /** Create/submit a community skill */
4151
4427
  async createSkill(input) {
4152
- return this._r("POST", "/api/im/skills", input);
4428
+ return this.skills.create(input);
4153
4429
  }
4154
4430
  /** Star a skill (increment community rating) */
4155
4431
  async starSkill(skillId) {
4156
- return this._r("POST", `/api/im/skills/${encodeURIComponent(skillId)}/star`);
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) return result;
4167
- let content = result.data.skill?.content || "";
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 { ...result, data: { ...result.data, localPaths: [] } };
4456
+ return withLocalPaths([]);
4174
4457
  }
4175
- const rawSlug = result.data.skill?.slug || slugOrId;
4458
+ const rawSlug = installData.skill?.slug || slugOrId;
4176
4459
  const slug = rawSlug.replace(/[\/\\]/g, "").replace(/\.\./g, "");
4177
4460
  if (!slug) {
4178
- return { ...result, data: { ...result.data, localPaths: [] } };
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 || Object.keys(platformPaths);
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 { ...result, data: { ...result.data, localPaths } };
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 { ...result, data: { uninstalled: result.data?.uninstalled ?? false, removedPaths } };
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 { ...result, data: { uninstalled: result.data?.uninstalled ?? false, removedPaths } };
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 || Object.keys(platformPaths);
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
- /** Bidirectional sync: push local outcomes and pull remote updates */
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
- if (options?.pushOutcomes) body.push = { outcomes: options.pushOutcomes };
4338
- if (options?.pullSince != null) body.pull = { since: options.pullSince };
4339
- return this._r("POST", "/api/im/evolution/sync", body);
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 (multipart). 100 MB hard cap; >50 MB returns
4483
- * 413 with `USE_PRESIGNED` use S3 presign flow for those (not yet wrapped).
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
- let bytes;
4487
- let fileName;
4488
- if (typeof input === "string") {
4489
- const fs = await import("fs");
4490
- const path = await import("path");
4491
- const buf = await fs.promises.readFile(input);
4492
- bytes = new Uint8Array(buf);
4493
- fileName = options.fileName || path.basename(input);
4494
- } else if (typeof Blob !== "undefined" && input instanceof Blob) {
4495
- const ab2 = await input.arrayBuffer();
4496
- bytes = new Uint8Array(ab2);
4497
- fileName = options.fileName || (input instanceof File ? input.name : "");
4498
- if (!fileName) throw new Error("fileName is required when uploading Blob without name");
4499
- } else if (input instanceof Uint8Array) {
4500
- bytes = input;
4501
- fileName = options.fileName || "";
4502
- if (!fileName) throw new Error("fileName is required when uploading Buffer or Uint8Array");
4503
- } else {
4504
- throw new Error("Unsupported input type");
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
- const mimeType = options.mimeType || guessMimeType(fileName);
4507
- const sizeBytes = bytes.byteLength;
4508
- if (sizeBytes > 100 * 1024 * 1024) {
4509
- throw new Error("Asset exceeds 100 MB cap");
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
- const ab = new ArrayBuffer(bytes.byteLength);
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: this._getAuthHeaders()
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: "HTTP_ERROR", message: `Upload failed (${resp.status})` }
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.account["_r"]("GET", "/api/im/health");
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.workspace["_r"]("GET", `/api/im/workspace/view?${params}`);
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: "HTTP_ERROR", message: `Request failed with status ${response.status}` };
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: "TIMEOUT", message: "Request timed out" } };
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: "NETWORK_ERROR", message: error instanceof Error ? error.message : "Unknown error" }
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
- safeSlug
5753
+ readCardKanbanMetadata,
5754
+ safeSlug,
5755
+ writeCardKanbanMetadata
5219
5756
  });