@geoly-ai/social-hub-sdk 0.1.0 → 0.1.2

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.d.ts CHANGED
@@ -75,6 +75,43 @@ export type UpdateCalendarEntryBody = {
75
75
  deliveryMode?: CalendarEntryDeliveryMode;
76
76
  permalink?: string;
77
77
  failureReason?: string;
78
+ /**
79
+ * R7 · 抢占执行权时的乐观版本钉子(只对 `scheduled → running` 有效)。
80
+ * 🔴 终态回写**不要带**:抢占那一次自己会把 revision 推到 N+1,终态再带 N 必然失败
81
+ * (得到 `expected_revision_not_applicable`)。
82
+ */
83
+ expectedRevision?: number;
84
+ /**
85
+ * R7 · 服务端签发的执行 capability token。终态回写必带(只要条目有在途授权);
86
+ * 初次抢占**不得**带。⚠️ bearer 凭据,别打日志。
87
+ */
88
+ executionToken?: string;
89
+ /**
90
+ * R7 · 抢占的幂等键(客户端生成,建议 UUID)。
91
+ * **强烈建议每次抢占都带** —— 它是「claim 已提交但响应丢失」的唯一补救。
92
+ */
93
+ claimRequestId?: string;
94
+ /**
95
+ * R7 · 强制终结:宿主机死在 `running` 中途时的逃生口。
96
+ * 仅 manager/admin、仅 `failed`/`cancelled`、必须同时给 `failureReason`。
97
+ * ⚠️ 不是把执行权让给下一台机 —— 条目就此终结。
98
+ */
99
+ forceTerminalize?: boolean;
100
+ };
101
+ /**
102
+ * R7 · 抢占成功时服务端回的执行授权。
103
+ * 🔴 `token` 是 bearer 凭据:存本机 state 文件,别打日志、别回显。
104
+ */
105
+ export type CalendarEntryExecutionGrant = {
106
+ /** 单调递增的执行代数,仅供审计对账;授权判据是 `token`。 */
107
+ epoch: number;
108
+ token: string;
109
+ /**
110
+ * 抢占**之后**的 directiveRevision(= 抢占前 + 1)。
111
+ * ⚠️ 抢占会 append 一条 `cancel` 指令并广播给**包括赢家在内**的全部机器 ——
112
+ * 协议规定它只删**未来的** cron,**绝不中止已持 token 的在途执行**。
113
+ */
114
+ directiveRevision: number;
78
115
  };
79
116
  export type ListRedditPostSnapshotsParams = {
80
117
  limit?: number;
@@ -386,6 +423,22 @@ export type CreateContentDraftBody = {
386
423
  * ⚠️ **不参与发布执行** —— 系统没有 Reddit link post 概念,执行只冻结 title+body。
387
424
  */
388
425
  referenceUrl?: string | null;
426
+ /**
427
+ * R2:**创建幂等键**(可选)。同一个 `(team, sourceRef)` 重复创建会复用首次那篇,
428
+ * 返回 `{ reused: true }` 而不是再造一篇。
429
+ *
430
+ * 用它包住会重试的调用(cron 重跑、超时重发、回执丢失后补发)。key 由调用方拥有,
431
+ * 服务端只做 NFKC + trim + 长度 1..200 + 拒控制字符,**不限制字符集** ——
432
+ * `agent:reports/2026-08.json#3` 这类带 `#` 的键是合法的。
433
+ *
434
+ * 🔴 唯一例外:**`openclaw:` 前缀是导入器专用的保留命名空间,写入面直接 400**
435
+ * (导入器有权用源文件覆盖同 key 的草稿;你占用它,你的草稿会在下次 ingest 被覆盖)。
436
+ * 换个自己的前缀即可。按 `openclaw:` 反查**不受限制**。
437
+ *
438
+ * ⚠️ **同 key 换内容 = 409**(`CONTENT_DRAFT_SOURCE_REF_CONFLICT`),不是静默覆盖。
439
+ * 改稿走 `PATCH`,或换一个新 key。删除会释放 key。
440
+ */
441
+ sourceRef?: string;
389
442
  };
390
443
  /** `POST /publishing-plans` 里的单条待排期条目。 */
391
444
  export type CreatePublishingPlanEntryInput = {
@@ -1146,9 +1199,31 @@ export declare class SocialHubClient {
1146
1199
  listCalendarEntries(teamId: string, params?: ListCalendarEntriesParams): Promise<{
1147
1200
  items: unknown[];
1148
1201
  }>;
1202
+ /**
1203
+ * PATCH 一条日历条目。
1204
+ *
1205
+ * ## R7 · 执行权排他(只对 `external_agent` 条目)
1206
+ * 协议是四步,**顺序不能换**:
1207
+ * 1. `calendar preflight`(可以两台都拿到 ok,它是纯读);
1208
+ * 2. **抢 `status:"running"`** —— 这一步才是排他。赢家在响应里拿到
1209
+ * `execution.token`;输的那台拿 **409 `EXECUTION_NOT_ACQUIRED`**,
1210
+ * `details.reason = "execution_already_acquired"`,应当安静退出并删本机 cron;
1211
+ * 3. 提交到 Reddit;
1212
+ * 4. 带 `executionToken` 回写 `succeeded` / `failed`。
1213
+ *
1214
+ * ⛔ **绝不能先提交再抢 running** —— 那样排他就完全失效了。
1215
+ *
1216
+ * 🔴 抢占时**务必带 `claimRequestId`**(客户端生成的幂等键)。没有它,
1217
+ * 「claim 已提交但 HTTP 响应丢失」会留下一条永久卡 `running`、
1218
+ * 却没有任何人握着 token 的条目,只能人工强制终结。带上它重试即可拿回原 token。
1219
+ *
1220
+ * ⚠️ `execution.token` 是 **bearer 凭据**:写进本机 state 文件,别打日志、别回显。
1221
+ */
1149
1222
  updateCalendarEntry(teamId: string, entryId: string, body: UpdateCalendarEntryBody): Promise<{
1150
1223
  ok: boolean;
1151
1224
  warning?: string;
1225
+ /** 仅在本次真正取得(或幂等重取)执行权时出现。老服务端不会回这个字段。 */
1226
+ execution?: CalendarEntryExecutionGrant;
1152
1227
  }>;
1153
1228
  /**
1154
1229
  * 与 {@link SocialHubClient.listCalendarEntries} **同一个端点**,只是把返回体
@@ -1308,6 +1383,11 @@ export declare class SocialHubClient {
1308
1383
  status?: string | string[];
1309
1384
  /** 目标板块过滤(单值或数组;数组按逗号拼,同字段内 OR)。 */
1310
1385
  subreddit?: string | string[];
1386
+ /**
1387
+ * R2:按创建幂等键反查(单值或数组;数组按逗号拼,同字段内 OR)。
1388
+ * 迁移期用它把本地 legacy key 映射到 Hub 草稿,不必靠标题去猜。
1389
+ */
1390
+ sourceRef?: string | string[];
1311
1391
  search?: string;
1312
1392
  } | number): Promise<{
1313
1393
  items: unknown[];
@@ -1315,8 +1395,19 @@ export declare class SocialHubClient {
1315
1395
  limit: number;
1316
1396
  offset: number;
1317
1397
  }>;
1398
+ /**
1399
+ * `POST /content-drafts`。
1400
+ *
1401
+ * R2:传了 `body.sourceRef` 时,`reused` 表示这次是**复用**了既有草稿(HTTP 200)
1402
+ * 而不是新建(HTTP 201)。
1403
+ *
1404
+ * ⚠️ 没传 `sourceRef` 时服务端**不返回** `reused`(响应保持 R2 之前的 `{ id }`),
1405
+ * 所以这里的类型是可选的 —— 别写 `if (!res.reused)` 当作「新建」的判据,
1406
+ * 那在没传 key 时恒真但毫无意义;要判复用请先确认自己传了 key。
1407
+ */
1318
1408
  createContentDraft(teamId: string, body: CreateContentDraftBody): Promise<{
1319
1409
  id: string;
1410
+ reused?: boolean;
1320
1411
  }>;
1321
1412
  /**
1322
1413
  * GET /content-review-targets — team-scoped 审核目标列表(status/contentType 过滤 +
@@ -2986,6 +3077,121 @@ export declare class SocialHubClient {
2986
3077
  count: number;
2987
3078
  runs: unknown[];
2988
3079
  }>;
3080
+ /**
3081
+ * POST subreddits — 只登记 canonical 板块身份,**不建资产**(幂等)。
3082
+ *
3083
+ * 「r/foo 这个板块存在」与「r/foo 是我们的」是两件事:后者会让绑定到它的账号在
3084
+ * enforce 档下被全面阻断。分开是为了能先把 20 个板块名录进来核对拼写,
3085
+ * 再逐个确认所有权。
3086
+ */
3087
+ registerSubredditIdentity(body: {
3088
+ name: string;
3089
+ displayName?: string;
3090
+ }): Promise<{
3091
+ subredditId: string;
3092
+ normalizedName: string;
3093
+ created: boolean;
3094
+ }>;
3095
+ /** POST — 登记一条自建板块资产(幂等:同板块已有资产则返回 created=false)。 */
3096
+ registerOwnedSubreddit(body: {
3097
+ name: string;
3098
+ displayName?: string;
3099
+ /** 可为空:板块是公司资产,不必挂品牌(代价由行级授权承担)。 */
3100
+ brandId?: string | null;
3101
+ assetStatus?: "active" | "archived" | "lost";
3102
+ notes?: string | null;
3103
+ }): Promise<{
3104
+ ownedSubredditId: string;
3105
+ created: boolean;
3106
+ }>;
3107
+ /**
3108
+ * GET — 列出自建板块资产(含版主绑定与只读风险投影)。
3109
+ *
3110
+ * `moderatorRisk=zero|single` 是**零/单版主的发现入口**:存量 20 条全是单版主,
3111
+ * 这是查出「哪些板块只剩一个版主、而那个号刚被封」的地方。
3112
+ * ⛔ 它只做发现,不做写入硬拒绝。
3113
+ */
3114
+ listOwnedSubreddits(params?: {
3115
+ assetStatus?: "active" | "archived" | "lost";
3116
+ moderatorRisk?: "zero" | "single" | "redundant";
3117
+ brandId?: string;
3118
+ limit?: number;
3119
+ offset?: number;
3120
+ }): Promise<{
3121
+ items: unknown[];
3122
+ }>;
3123
+ /** GET :id — 单条资产详情(不可见时返 404,不返 403:避免变成枚举面)。 */
3124
+ getOwnedSubreddit(ownedSubredditId: string): Promise<unknown>;
3125
+ /**
3126
+ * PATCH :id/status — 改资产状态(CAS,rowVersion 漂移返 409)。
3127
+ *
3128
+ * ⚠️ `lost` **解除**该板块所有绑定账号的阻断并停止核验;`archived` **保留**阻断。
3129
+ */
3130
+ updateOwnedSubredditStatus(ownedSubredditId: string, body: {
3131
+ assetStatus: "active" | "archived" | "lost";
3132
+ rowVersion: number;
3133
+ reason?: string | null;
3134
+ }): Promise<{
3135
+ rowVersion: number;
3136
+ }>;
3137
+ /**
3138
+ * POST :id/moderators — 给板块加一个版主绑定。
3139
+ *
3140
+ * 🔴 返回的 `accountAtRisk=true` 表示**这个账号已经是 banned/restricted 了**。
3141
+ * 建绑不构成状态转变,所以封禁告警那条链一条都不会发 —— 服务端另落了一条
3142
+ * critical 风险事件,而这个返回值是人工录入 20 条存量时当场能看见的信号。
3143
+ */
3144
+ addSubredditModerator(ownedSubredditId: string, body: {
3145
+ socialAccountId: string;
3146
+ isPrimary?: boolean;
3147
+ declaredPermissions?: string[];
3148
+ notes?: string | null;
3149
+ }): Promise<{
3150
+ bindingId: string;
3151
+ accountAtRisk: boolean;
3152
+ }>;
3153
+ /** POST moderators/:id/revoke — 撤销绑定(CAS)。行永久保留,历史不可抹。 */
3154
+ revokeSubredditModerator(bindingId: string, body: {
3155
+ rowVersion: number;
3156
+ reason?: string | null;
3157
+ }): Promise<{
3158
+ rowVersion: number;
3159
+ }>;
3160
+ /** POST moderators/replace — 更替版主 = **关旧建新**(不是就地改 accountId)。 */
3161
+ replaceSubredditModerator(body: {
3162
+ bindingId: string;
3163
+ rowVersion: number;
3164
+ nextSocialAccountId: string;
3165
+ declaredPermissions?: string[];
3166
+ reason?: string | null;
3167
+ }): Promise<{
3168
+ revokedBindingId: string;
3169
+ bindingId: string;
3170
+ }>;
3171
+ /** GET — 读全局版主保护档位(off / shadow / enforce)。 */
3172
+ getModeratorProtection(): Promise<{
3173
+ mode: "off" | "shadow" | "enforce";
3174
+ rowVersion: number;
3175
+ note: string | null;
3176
+ updatedAt: string;
3177
+ updatedByUserId: string | null;
3178
+ }>;
3179
+ /**
3180
+ * PATCH — 改全局版主保护档位(CAS + 审计)。
3181
+ *
3182
+ * 🔴 这是**全局**开关,无个体 override:`enforce` 之下所有持有 active 绑定的账号,
3183
+ * 四类自动化动作(publish_post / publish_comment / warmup / engage)一律阻断。
3184
+ * 切档前请先跑完 `shadow` 的验证窗口,并暂停 dispatcher 与外部 agent cron
3185
+ * (已在外部执行中的动作不可撤回,那是 cutover 前的已知风险)。
3186
+ */
3187
+ setModeratorProtection(body: {
3188
+ mode: "off" | "shadow" | "enforce";
3189
+ rowVersion: number;
3190
+ note?: string | null;
3191
+ }): Promise<{
3192
+ mode: string;
3193
+ rowVersion: number;
3194
+ }>;
2989
3195
  private subscriptionsBase;
2990
3196
  private eventsBase;
2991
3197
  private sourcesBase;