oasis_test_v2 2.2.36 → 2.2.37

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.
Files changed (2) hide show
  1. package/dist/index.js +310 -116
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -5645,7 +5645,6 @@ CREATE TABLE IF NOT EXISTS ${q2}.workorders (
5645
5645
  paused_reason text,
5646
5646
  done_at timestamptz,
5647
5647
  structural_version integer NOT NULL DEFAULT 0,
5648
- row_version integer NOT NULL DEFAULT 0,
5649
5648
  created_at timestamptz NOT NULL,
5650
5649
  created_by text NOT NULL,
5651
5650
  updated_at timestamptz NOT NULL
@@ -5660,7 +5659,6 @@ CREATE TABLE IF NOT EXISTS ${q2}.workflow_nodes (
5660
5659
  spec text,
5661
5660
  version integer NOT NULL DEFAULT 0,
5662
5661
  assignee_actor_id text,
5663
- assignee_role text,
5664
5662
  latest_work_id text,
5665
5663
  latest_proposed_work_id text,
5666
5664
  latest_accept_id text,
@@ -5670,7 +5668,6 @@ CREATE TABLE IF NOT EXISTS ${q2}.workflow_nodes (
5670
5668
  cancelled_at timestamptz,
5671
5669
  -- \u5C01\u5B58\u539F\u56E0\uFF08accepted/cancelled/frozen\uFF09\u3002NULL = \u5B58\u91CF\u884C\uFF0C\u8BFB\u6A21\u578B\u6309 cancelled \u515C\u5E95\u3002
5672
5670
  seal_reason text,
5673
- row_version integer NOT NULL DEFAULT 0,
5674
5671
  created_at timestamptz NOT NULL,
5675
5672
  updated_at timestamptz NOT NULL
5676
5673
  );
@@ -5684,7 +5681,6 @@ CREATE TABLE IF NOT EXISTS ${q2}.workflow_edges (
5684
5681
  kind text NOT NULL CHECK (kind IN ('data','order')),
5685
5682
  required boolean NOT NULL DEFAULT true,
5686
5683
  pinned_work_id text,
5687
- consumed_work_id text,
5688
5684
  PRIMARY KEY (from_node_id, to_node_id)
5689
5685
  );
5690
5686
  CREATE INDEX IF NOT EXISTS edges_to_idx ON ${q2}.workflow_edges (to_node_id);
@@ -5886,7 +5882,6 @@ CREATE TABLE IF NOT EXISTS ${q2}.events (
5886
5882
  origin text NOT NULL CHECK (origin IN ('command','apply','effect')),
5887
5883
  dedup_key text,
5888
5884
  status text NOT NULL CHECK (status IN ('pending','applied','done','failed')),
5889
- attempts integer NOT NULL DEFAULT 0,
5890
5885
  available_at timestamptz,
5891
5886
  failed_reason text,
5892
5887
  created_at timestamptz NOT NULL,
@@ -6077,6 +6072,31 @@ BEGIN
6077
6072
  END;
6078
6073
  END $$;
6079
6074
 
6075
+ -- ADR 0564\uFF1A\u9000\u5F79\u672A\u6D88\u8D39\u7684\u8BA1\u6570\u5668\u548C\u5355\u5411\u526F\u672C\uFF1B\u5386\u53F2 events.payload \u539F\u6587\u4E0D\u6539\u3002
6076
+ -- \u90E8\u7F72\u524D\u5907\u4EFD\u5B58\u91CF\u5E93\uFF08\u542B\u975E\u7A7A consumed_work_id / assignee_role\uFF09\u3002\u6309\u5217\u9694\u79BB\u6E05\u7406\uFF0C
6077
+ -- \u5916\u90E8\u4F9D\u8D56\u53EA\u63A8\u8FDF\u8BE5\u5217\uFF0C\u4E0D CASCADE\u3001\u4E0D\u963B\u65AD\u542F\u52A8\uFF08\u6CBF\u7528 ADR 0512 \u7684\u53EF\u7528\u6027\u7EA6\u675F\uFF09\u3002
6078
+ -- \u81EA\u6709 node_view \u5DF2\u5728\u4E0A\u65B9\u5220\u9664\uFF0C\u540E\u7EED ENGINE_VIEWS \u6309\u65B0\u5217\u96C6\u91CD\u5EFA\uFF1B\u4ECD\u88AB\u4FDD\u7559\u7684\u65E7
6079
+ -- workorder_view \u6309\u5916\u90E8\u4F9D\u8D56\u5904\u7406\uFF0C\u4E0D\u80FD\u4E3A\u672C\u6B21\u9000\u5F79\u7834\u574F ADR 0512 \u7684\u5386\u53F2\u503C\u4FDD\u7559\u3002
6080
+ DO $$
6081
+ DECLARE obsolete record;
6082
+ BEGIN
6083
+ FOR obsolete IN SELECT * FROM (VALUES
6084
+ ('workorders', 'row_version'),
6085
+ ('workflow_nodes', 'row_version'),
6086
+ ('workflow_nodes', 'assignee_role'),
6087
+ ('workflow_edges', 'consumed_work_id'),
6088
+ ('events', 'attempts')
6089
+ ) AS retired(table_name, column_name) LOOP
6090
+ BEGIN
6091
+ EXECUTE format('ALTER TABLE %I.%I DROP COLUMN IF EXISTS %I',
6092
+ ${lit}, obsolete.table_name, obsolete.column_name);
6093
+ EXCEPTION WHEN dependent_objects_still_exist THEN
6094
+ RAISE WARNING 'Retaining retired engine field %.%.%: external dependencies require reconciliation (ADR 0564)',
6095
+ ${lit}, obsolete.table_name, obsolete.column_name;
6096
+ END;
6097
+ END LOOP;
6098
+ END $$;
6099
+
6080
6100
  -- \u8C03\u5EA6\u5668\u5B8C\u7ED3\u6807\u8BB0\uFF08mark_done/unmark_done \u843D\u70B9\uFF0C2026-08-12\uFF09\u3002
6081
6101
  ALTER TABLE ${q2}.workorders ADD COLUMN IF NOT EXISTS done_at timestamptz;
6082
6102
  -- \u2605 \u5DE5\u5355\u7EA7\u5C01\u5B58\u6574\u4F53\u79FB\u9664\uFF08ADR 0137\uFF09\uFF1Alifecycle \u53EA\u5269 draft/active\uFF0Cseal_reason \u5217\u5220\u9664\u3002
@@ -11329,7 +11349,6 @@ function toRecord(r) {
11329
11349
  origin: r.origin,
11330
11350
  dedupKey: r.dedup_key ?? null,
11331
11351
  status: r.status,
11332
- attempts: Number(r.attempts ?? 0),
11333
11352
  availableAt: iso(r.available_at),
11334
11353
  failedReason: r.failed_reason ?? null,
11335
11354
  createdAt: iso(r.created_at),
@@ -11558,7 +11577,6 @@ var init_store_postgres = __esm({
11558
11577
  pausedReason: r.paused_reason,
11559
11578
  doneAt: iso(r.done_at),
11560
11579
  structuralVersion: r.structural_version,
11561
- rowVersion: r.row_version,
11562
11580
  createdAt: iso(r.created_at),
11563
11581
  createdBy: r.created_by,
11564
11582
  updatedAt: iso(r.updated_at)
@@ -11571,7 +11589,6 @@ var init_store_postgres = __esm({
11571
11589
  spec: n.spec,
11572
11590
  version: n.version ?? 0,
11573
11591
  assigneeActorId: n.assignee_actor_id,
11574
- assigneeRole: n.assignee_role,
11575
11592
  latestWorkId: n.latest_work_id,
11576
11593
  latestProposedWorkId: n.latest_proposed_work_id ?? null,
11577
11594
  latestAcceptId: n.latest_accept_id,
@@ -11581,7 +11598,6 @@ var init_store_postgres = __esm({
11581
11598
  sealReason: n.seal_reason ?? null,
11582
11599
  fields: n.fields ?? void 0,
11583
11600
  continuationBudget: n.continuation_budget ?? null,
11584
- rowVersion: n.row_version,
11585
11601
  createdAt: iso(n.created_at),
11586
11602
  updatedAt: iso(n.updated_at)
11587
11603
  })),
@@ -11591,8 +11607,7 @@ var init_store_postgres = __esm({
11591
11607
  toNodeId: e.to_node_id,
11592
11608
  kind: e.kind,
11593
11609
  required: e.required,
11594
- pinnedWorkId: e.pinned_work_id,
11595
- consumedWorkId: e.consumed_work_id
11610
+ pinnedWorkId: e.pinned_work_id
11596
11611
  })),
11597
11612
  requirements: reqs.rows.map((q2) => ({
11598
11613
  nodeId: q2.node_id,
@@ -11731,8 +11746,8 @@ var init_store_postgres = __esm({
11731
11746
  await this.c.query(
11732
11747
  `INSERT INTO ${this.s}.workorders
11733
11748
  (id,company_id,title,owner_actor_id,lifecycle,dispatch_paused,paused_by,paused_reason,
11734
- done_at,structural_version,row_version,created_at,created_by,updated_at)
11735
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)
11749
+ done_at,structural_version,created_at,created_by,updated_at)
11750
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)
11736
11751
  ON CONFLICT (id) DO NOTHING`,
11737
11752
  [
11738
11753
  r.id,
@@ -11745,7 +11760,6 @@ var init_store_postgres = __esm({
11745
11760
  r.pausedReason,
11746
11761
  r.doneAt,
11747
11762
  r.structuralVersion,
11748
- r.rowVersion,
11749
11763
  r.createdAt,
11750
11764
  r.createdBy,
11751
11765
  r.updatedAt
@@ -11760,10 +11774,10 @@ var init_store_postgres = __esm({
11760
11774
  const n = m2.row;
11761
11775
  await this.c.query(
11762
11776
  `INSERT INTO ${this.s}.workflow_nodes
11763
- (id,workorder_id,type,title,spec,version,assignee_actor_id,assignee_role,
11777
+ (id,workorder_id,type,title,spec,version,assignee_actor_id,
11764
11778
  latest_work_id,latest_proposed_work_id,latest_accept_id,is_final_output,
11765
- fields,cancelled_at,seal_reason,row_version,created_at,updated_at,continuation_budget)
11766
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19)
11779
+ fields,cancelled_at,seal_reason,created_at,updated_at,continuation_budget)
11780
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17)
11767
11781
  ON CONFLICT (id) DO NOTHING`,
11768
11782
  [
11769
11783
  n.id,
@@ -11773,7 +11787,6 @@ var init_store_postgres = __esm({
11773
11787
  n.spec,
11774
11788
  n.version,
11775
11789
  n.assigneeActorId,
11776
- n.assigneeRole,
11777
11790
  n.latestWorkId,
11778
11791
  n.latestProposedWorkId,
11779
11792
  n.latestAcceptId,
@@ -11781,7 +11794,6 @@ var init_store_postgres = __esm({
11781
11794
  n.fields ? JSON.stringify(n.fields) : null,
11782
11795
  n.cancelledAt,
11783
11796
  n.sealReason ?? null,
11784
- n.rowVersion,
11785
11797
  n.createdAt,
11786
11798
  n.updatedAt,
11787
11799
  n.continuationBudget ? JSON.stringify(n.continuationBudget) : null
@@ -11796,9 +11808,9 @@ var init_store_postgres = __esm({
11796
11808
  const e = m2.row;
11797
11809
  await this.c.query(
11798
11810
  `INSERT INTO ${this.s}.workflow_edges
11799
- (workorder_id,from_node_id,to_node_id,kind,required,pinned_work_id,consumed_work_id)
11800
- VALUES ($1,$2,$3,$4,$5,$6,$7) ON CONFLICT (from_node_id,to_node_id) DO NOTHING`,
11801
- [e.workorderId, e.fromNodeId, e.toNodeId, e.kind, e.required, e.pinnedWorkId, e.consumedWorkId]
11811
+ (workorder_id,from_node_id,to_node_id,kind,required,pinned_work_id)
11812
+ VALUES ($1,$2,$3,$4,$5,$6) ON CONFLICT (from_node_id,to_node_id) DO NOTHING`,
11813
+ [e.workorderId, e.fromNodeId, e.toNodeId, e.kind, e.required, e.pinnedWorkId]
11802
11814
  );
11803
11815
  break;
11804
11816
  }
@@ -12017,8 +12029,8 @@ var init_store_postgres = __esm({
12017
12029
  const res = await this.c.query(
12018
12030
  `INSERT INTO ${this.s}.events
12019
12031
  (company_id,workorder_id,node_id,work_id,actor_id,hand_actor_id,kind,payload,caused_by_seq,
12020
- origin,dedup_key,status,attempts,created_at)
12021
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,'pending',0,$12)
12032
+ origin,dedup_key,status,created_at)
12033
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,'pending',$12)
12022
12034
  ON CONFLICT (workorder_id, dedup_key) WHERE dedup_key IS NOT NULL DO NOTHING
12023
12035
  RETURNING seq`,
12024
12036
  [
@@ -12203,13 +12215,11 @@ function newNode(id, workorderId, at, version2) {
12203
12215
  spec: null,
12204
12216
  version: version2,
12205
12217
  assigneeActorId: null,
12206
- assigneeRole: null,
12207
12218
  latestWorkId: null,
12208
12219
  latestProposedWorkId: null,
12209
12220
  latestAcceptId: null,
12210
12221
  isFinalOutput: false,
12211
12222
  cancelledAt: null,
12212
- rowVersion: 0,
12213
12223
  createdAt: at,
12214
12224
  updatedAt: at
12215
12225
  };
@@ -12280,7 +12290,6 @@ var init_plan = __esm({
12280
12290
  title: e.title,
12281
12291
  spec: e.spec,
12282
12292
  assigneeActorId: e.assigneeActorId,
12283
- assigneeRole: e.assigneeRole,
12284
12293
  isFinalOutput: e.isFinalOutput,
12285
12294
  ...e.fields ? { fields: e.fields } : {}
12286
12295
  });
@@ -12332,8 +12341,7 @@ var init_plan = __esm({
12332
12341
  toNodeId: e.toNodeId,
12333
12342
  kind: e.edgeKind,
12334
12343
  required: e.required,
12335
- pinnedWorkId: upstream?.latestAcceptId ?? null,
12336
- consumedWorkId: null
12344
+ pinnedWorkId: upstream?.latestAcceptId ?? null
12337
12345
  });
12338
12346
  const dn = state.node(e.toNodeId);
12339
12347
  if (dn) state.updateNode(e.toNodeId, { version: dn.version + 1 });
@@ -12362,7 +12370,6 @@ var init_plan = __esm({
12362
12370
  }
12363
12371
  state.updateNode(e.nodeId, {
12364
12372
  assigneeActorId: e.assigneeActorId,
12365
- assigneeRole: e.assigneeRole,
12366
12373
  ...changed && !settled ? { version: node2.version + 1 } : {},
12367
12374
  updatedAt: ctx.at
12368
12375
  });
@@ -12458,7 +12465,6 @@ var init_plan = __esm({
12458
12465
  title: spec.title,
12459
12466
  spec: spec.spec,
12460
12467
  assigneeActorId: spec.assigneeActorId,
12461
- assigneeRole: spec.assigneeRole,
12462
12468
  isFinalOutput: spec.isFinalOutput,
12463
12469
  reviewers: spec.reviewers,
12464
12470
  ...spec.fields ? { fields: spec.fields } : {}
@@ -12528,7 +12534,6 @@ var init_workorder = __esm({
12528
12534
  pausedReason: null,
12529
12535
  doneAt: null,
12530
12536
  structuralVersion: 0,
12531
- rowVersion: 0,
12532
12537
  createdAt: ctx.at,
12533
12538
  createdBy: ctx.actorId,
12534
12539
  updatedAt: ctx.at
@@ -12543,7 +12548,7 @@ var init_workorder = __esm({
12543
12548
  ([key]) => Object.hasOwn(state.workorder, key)
12544
12549
  ));
12545
12550
  if (Object.keys(patch).length === 0) return;
12546
- state.updateWorkorder({ ...patch, updatedAt: ctx.at, rowVersion: state.workorder.rowVersion + 1 });
12551
+ state.updateWorkorder({ ...patch, updatedAt: ctx.at });
12547
12552
  }
12548
12553
  };
12549
12554
  workorderPause = {
@@ -13347,11 +13352,6 @@ var init_review = __esm({
13347
13352
  for (const r of state.openReviewsOf(e.workId)) {
13348
13353
  state.updateReview(r.id, { cancelledAt: ctx.at, status: "dead" });
13349
13354
  }
13350
- const inputs = state.workInputsOf(e.workId);
13351
- for (const edge of state.edgesInto(w2.nodeId)) {
13352
- const real = inputs.find((i) => i.upstreamNodeId === edge.fromNodeId);
13353
- state.updateEdge(edge.fromNodeId, edge.toNodeId, { consumedWorkId: real?.upstreamWorkId ?? edge.pinnedWorkId });
13354
- }
13355
13355
  for (const edge of state.edgesFrom(w2.nodeId)) {
13356
13356
  state.updateEdge(edge.fromNodeId, edge.toNodeId, { pinnedWorkId: e.workId });
13357
13357
  }
@@ -13614,7 +13614,6 @@ function emptySnapshotPlaceholder(record8) {
13614
13614
  pausedReason: null,
13615
13615
  doneAt: null,
13616
13616
  structuralVersion: 0,
13617
- rowVersion: 0,
13618
13617
  createdAt: "",
13619
13618
  createdBy: "",
13620
13619
  updatedAt: ""
@@ -20236,7 +20235,6 @@ var init_kernel_bridge = __esm({
20236
20235
  title: args.title ?? args.description ?? args.type,
20237
20236
  spec: args.description ?? null,
20238
20237
  assigneeActorId: owner,
20239
- assigneeRole: null,
20240
20238
  isFinalOutput: args.isFinalOutput ?? false,
20241
20239
  // ★ docType(projects 域的具体文档类型)随节点 fields 落库(同 reviewRoles 的做法,jsonb 免加列);
20242
20240
  // bridge 投影时从 fields 剥出成 artifact.docType、不混进用户自定义字段。
@@ -21101,8 +21099,7 @@ var init_kernel_bridge = __esm({
21101
21099
  kind: "plan.assign_actor",
21102
21100
  workorderId: wid,
21103
21101
  nodeId: args.artifactId,
21104
- assigneeActorId: args.owner,
21105
- assigneeRole: null
21102
+ assigneeActorId: args.owner
21106
21103
  }
21107
21104
  });
21108
21105
  }
@@ -21628,7 +21625,6 @@ var init_kernel_bridge = __esm({
21628
21625
  title: op.title ?? op.type,
21629
21626
  spec: op.description ?? null,
21630
21627
  assigneeActorId: spawnOwner,
21631
- assigneeRole: null,
21632
21628
  isFinalOutput: op.isFinalOutput ?? false,
21633
21629
  ...allReviewers.length > 0 ? { reviewers: allReviewers } : {},
21634
21630
  ...mergedFields ? { fields: mergedFields } : {}
@@ -21675,7 +21671,7 @@ var init_kernel_bridge = __esm({
21675
21671
  break;
21676
21672
  }
21677
21673
  case "assign":
21678
- await enqueue({ kind: "plan.assign_actor", workorderId: woFor(op.artifactId), nodeId: op.artifactId, assigneeActorId: op.owner, assigneeRole: null });
21674
+ await enqueue({ kind: "plan.assign_actor", workorderId: woFor(op.artifactId), nodeId: op.artifactId, assigneeActorId: op.owner });
21679
21675
  applied++;
21680
21676
  break;
21681
21677
  case "seal":
@@ -31180,6 +31176,9 @@ var init_chat_turn_gate = __esm({
31180
31176
  function isDeletedActor(actor) {
31181
31177
  return actor?.status === "disabled";
31182
31178
  }
31179
+ function hideDeleted(rows) {
31180
+ return rows.filter((row) => row.status !== "disabled");
31181
+ }
31183
31182
  var ACTOR_DELETED_CHAT_MESSAGE, ACTOR_DELETED_ERROR_CODE, ActorAlreadyDeletedError;
31184
31183
  var init_deletion = __esm({
31185
31184
  "../server/src/domains/actors/deletion.ts"() {
@@ -210253,6 +210252,11 @@ var init_memory_registry_store = __esm({
210253
210252
  ...v2.updatedBy ? { updatedBy: v2.updatedBy } : {}
210254
210253
  });
210255
210254
  }
210255
+ async insertVariable(v2) {
210256
+ if (this.variables.has(this._varKey(v2.key, v2.actorId, v2.projectId))) return false;
210257
+ await this.putVariable(v2);
210258
+ return true;
210259
+ }
210256
210260
  async touchVariableUsage(key, actorId, projectId2, when) {
210257
210261
  const mk = this._varKey(key, actorId, projectId2);
210258
210262
  const ex = this.variables.get(mk);
@@ -216681,6 +216685,9 @@ var init_company_scoped_registry = __esm({
216681
216685
  async putVariable(...args) {
216682
216686
  return this.base.putVariable(...args);
216683
216687
  }
216688
+ async insertVariable(...args) {
216689
+ return this.base.insertVariable(...args);
216690
+ }
216684
216691
  async patchVariableValue(...args) {
216685
216692
  return this.base.patchVariableValue(...args);
216686
216693
  }
@@ -221846,6 +221853,9 @@ function computeEffectiveEntries(config2, connections, connectors) {
221846
221853
  }
221847
221854
  return out;
221848
221855
  }
221856
+ function personalConnectionPairs(vars) {
221857
+ return vars.flatMap((v2) => v2.scope === "personal" && v2.actorId && v2.connectorId ? [{ actorId: v2.actorId, connectorId: v2.connectorId }] : []);
221858
+ }
221849
221859
  function variableKeyFromEnv(env = process.env) {
221850
221860
  const hex = env.OASIS_VAR_KEY;
221851
221861
  if (hex) {
@@ -222587,35 +222597,43 @@ ${input.description}
222587
222597
  }
222588
222598
  }
222589
222599
  /**
222590
- * 连接器 + 分配派生:按每个 AI 员工的**有效启用**口径聚合「此连接器对谁生效」(决策 0080:
222591
- * 连接记录 > 旧 connectorIds > 组织默认,与运行时注入同一判定)。此前只扫旧 connectorIds,
222592
- * 组织页显示的员工集合与真正生效的注入口径脱节。
222600
+ * 管理页连接器列表的「N 个 Agent 已连接」:每个连接器上**建立过 Agent 层连接**的 Agent
222601
+ * (判据见 {@link personalConnectionPairs},与类型详情页的行、组织页 tab 同一份)。
222602
+ *
222603
+ * 口径与原型帧 `1496:5390` / `1551:767` 对齐:卡片上的数 = 点进去那一页的行数——
222604
+ * 开关关着的、凭据不全或解不开的都是一行,也都算一个;从没连过的不算。
222605
+ *
222606
+ * 两处刻意的范围:
222607
+ * · 人群 = 花名册(`GET /api/actors?kind=agent`)那一群:**已删除的 Agent 不算**——详情页压根列不出他们,
222608
+ * 算进来就是「卡片 5、点进去 4」。
222609
+ * · 不以连接器表为界:2026-09-09 起连接器类型对所有组织默认可用、个人授权又不碰组织行,
222610
+ * 连接器表**天然是空的**(二期生产 09-15 实测 `items: []`)。此前只对表里有行的连接器聚合,
222611
+ * 卡片恒为「暂无连接的 Agent」、点进去却有好几位。没有组织行的连接器单列在
222612
+ * `connectedActorIdsByConnector` 里,**不**编一条 Connector 记录塞进 `items`(没有的 name/mode 不造)。
222613
+ *
222614
+ * 这里**不再**按 `effective` 数(此前的「对谁生效」口径):那是注入闸,含组织默认与旧 connectorIds 两档,
222615
+ * 数出来的人与详情行、组织页 tab 都对不上。注入闸本身(`computeEffectiveEntries`)不动。
222593
222616
  */
222594
222617
  async listConnectorsWithAssignments() {
222595
- const [connectors, agents] = await Promise.all([
222618
+ const [connectors, agents, vars] = await Promise.all([
222596
222619
  this.opts.store.listConnectors(),
222597
- this.opts.store.listActors({ kind: "agent" })
222620
+ this.opts.store.listActors({ kind: "agent" }),
222621
+ this.opts.store.listVariables()
222598
222622
  ]);
222599
- const agentIds = agents.map((a) => a.id);
222600
- const configs = this.opts.store.latestConfigs ? await this.opts.store.latestConfigs(agentIds) : new Map(await Promise.all(agentIds.map(async (id) => [id, await this.opts.store.latestConfig(id)])));
222601
- const allConns = this.opts.store.listAllActorConnectorConnections ? await this.opts.store.listAllActorConnectorConnections() : (await Promise.all(agentIds.map((id) => this.opts.store.listActorConnectorConnections(id)))).flat();
222602
- const connsByActor = /* @__PURE__ */ new Map();
222603
- for (const c of allConns) {
222604
- const arr = connsByActor.get(c.actorId) ?? [];
222605
- arr.push(c);
222606
- connsByActor.set(c.actorId, arr);
222607
- }
222623
+ const roster = new Set(hideDeleted(agents).map((a) => a.id));
222608
222624
  const byConn = /* @__PURE__ */ new Map();
222609
- for (const a of agents) {
222610
- const entries = computeEffectiveEntries(configs.get(a.id) ?? null, connsByActor.get(a.id) ?? [], connectors);
222611
- for (const e of entries) {
222612
- if (!e.enabled) continue;
222613
- const list2 = byConn.get(e.connectorId) ?? [];
222614
- list2.push(a.id);
222615
- byConn.set(e.connectorId, list2);
222616
- }
222617
- }
222618
- return connectors.map((c) => ({ ...c, connectedActorIds: [...new Set(byConn.get(c.id) ?? [])] }));
222625
+ for (const { actorId, connectorId } of personalConnectionPairs(vars)) {
222626
+ if (!roster.has(actorId)) continue;
222627
+ const set = byConn.get(connectorId) ?? /* @__PURE__ */ new Set();
222628
+ set.add(actorId);
222629
+ byConn.set(connectorId, set);
222630
+ }
222631
+ const connectedActorIdsByConnector = {};
222632
+ for (const [connectorId, set] of byConn) connectedActorIdsByConnector[connectorId] = [...set];
222633
+ return {
222634
+ items: connectors.map((c) => ({ ...c, connectedActorIds: connectedActorIdsByConnector[c.id] ?? [] })),
222635
+ connectedActorIdsByConnector
222636
+ };
222619
222637
  }
222620
222638
  /** 该员工最新配置里启用的 connector id 集合——与 resolveActorEnv 同一闸门(connectorIds 成员资格)。 */
222621
222639
  async activeConnectorIdsForActor(actorId, cfg) {
@@ -222712,17 +222730,94 @@ ${input.description}
222712
222730
  ]);
222713
222731
  const globalConnected = connectors.filter((c) => c.status === "connected").map((c) => c.id);
222714
222732
  const actorConnected = [...new Set(
222715
- vars.filter((v2) => v2.scope === "personal" && v2.actorId === actorId && v2.connectorId).map((v2) => v2.connectorId)
222733
+ personalConnectionPairs(vars).filter((p2) => p2.actorId === actorId).map((p2) => p2.connectorId)
222716
222734
  )];
222717
222735
  const effective = await this.effectiveConnectorEntries(actorId, config2, connectors);
222736
+ const { ready, assessed, undecryptable } = this.connectorCredentialReadiness(actorId, vars);
222737
+ const credentialReady = [...ready];
222738
+ const credentialUndecryptable = [...undecryptable];
222739
+ const credentialMissing = actorConnected.filter(
222740
+ (id) => undecryptable.has(id) || assessed.has(id) && !ready.has(id)
222741
+ );
222742
+ return { connections, globalConnected, actorConnected, effective, credentialReady, credentialMissing, credentialUndecryptable };
222743
+ }
222744
+ /**
222745
+ * 这名员工每个连接器的凭据齐不齐——**派单那条路的等价判定**:
222746
+ * ① `collectAllConnectorCredentials`(派单与会话中途续期共用的那个函数)现在就收得齐;或
222747
+ * ② 连接器声明了续期能力,且续期材料里 required 的几项都在(`orgDefault` 的可回落组织级,
222748
+ * 与 `readRefreshMaterials` 同口径)——`resolveActorEnv` 注入前会先跑
222749
+ * `refreshEnabledConnectorCredentials`,把短命凭据铸出来再收。
222750
+ * GitHub App 就是这一类:组织级连接只存 App 材料、token 每次派单现铸(server.ts 的 manifest
222751
+ * 回调刻意不存组织级 token)。只看 ① 会把它一刀切报成「凭据不全」。
222752
+ *
222753
+ * 续期**会不会成功**(私钥被吊销、App 被卸载、飞书 refresh_token 过期、平台不可达 / 限流)这里判不了——
222754
+ * 要真打一次平台才知道,PRD「不能获取时不伪造」,所以不算异常。
222755
+ * `assessed` = 本服务判得了的连接器(注册表里有的)。
222756
+ *
222757
+ * **解不开的密文**(变量密钥换过、坏行)不能当「没有」处理,要记成那个连接器的异常证据(`undecryptable`)。
222758
+ * 派单那条路(serve.ts `buildActorProvision` → `resolveActorProvisionEntries`;`resolveActorEnv` 同规则)
222759
+ * 对「无 connectorId 的行 + 启用中连接器的行」**逐行**解密,任何一行解不开就抛,dispatcher 记 spawn-failed
222760
+ * ——整单派不出去,不是只少一个连接器:
222761
+ * · 不回落同 key 的组织值(个人行存在就解个人行,解不开就抛;续期读材料的 `revealResolvedVariable` 同理);
222762
+ * · 不跳过可选项(用户 token、只在服务端用的 refresh_token 也逐行解);
222763
+ * · 续期读材料撞上坏行 → 该连接器本轮不续(best-effort catch),坏行原样留着,下一步解密照样抛。
222764
+ * 所以这里若把坏行当「没有」再回落组织值、或因为它可选就放过,界面会说凭据齐全而派单整单失败
222765
+ * (2026-09-15 验收时补的回归:connector-connection-consistency.test.ts「解不开的密文」一组)。
222766
+ * 反过来**不改运行时**:派单遇坏行就失败是既有的身份策略,不为了显示去动它。
222767
+ *
222768
+ * 归属:行上有 `connectorId` 就归它;没有(密钥页手录的那种)就归「声明了这个变量名的连接器」——
222769
+ * 与这里收凭据时不看 `connectorId`、只按变量名取值是同一口径。归属不到任何连接器的坏行(如组织级
222770
+ * OPENAI_API_KEY)一样会让派单失败,但它不是哪条连接的事,不拿它把无关连接器标坏。
222771
+ * 此前(更早)这里直接抛,整个 `GET /api/actors/:id/connectors` 500;现在接口照样可读。
222772
+ */
222773
+ connectorCredentialReadiness(actorId, vars) {
222774
+ const connectors = createAllConnectors();
222775
+ const declaredBy = /* @__PURE__ */ new Map();
222776
+ for (const connector of connectors) {
222777
+ for (const s2 of [...connector.credentialSources, ...connector.refreshCredentialSources ?? []]) {
222778
+ for (const key of [s2.from, ...s2.fromAny ?? []]) {
222779
+ const slugs = declaredBy.get(key) ?? [];
222780
+ if (!slugs.includes(connector.config.slug)) declaredBy.set(key, [...slugs, connector.config.slug]);
222781
+ }
222782
+ }
222783
+ }
222784
+ const undecryptable = /* @__PURE__ */ new Set();
222785
+ const decode2 = (v2) => {
222786
+ try {
222787
+ return this.decodeCiphertext(v2.valueEncrypted);
222788
+ } catch {
222789
+ for (const slug6 of v2.connectorId ? [v2.connectorId] : declaredBy.get(v2.key) ?? []) undecryptable.add(slug6);
222790
+ return void 0;
222791
+ }
222792
+ };
222718
222793
  const flat = {};
222719
- for (const v2 of vars) if (v2.scope === "global" && !v2.actorId) flat[v2.key] = this.decodeCiphertext(v2.valueEncrypted);
222720
- for (const v2 of vars) if (v2.scope === "personal" && (!v2.actorId || v2.actorId === actorId)) flat[v2.key] = this.decodeCiphertext(v2.valueEncrypted);
222721
- const credentialReady = collectAllConnectorCredentials({
222794
+ const own3 = {};
222795
+ for (const v2 of vars) {
222796
+ if (v2.scope !== "global" || v2.actorId) continue;
222797
+ const value2 = decode2(v2);
222798
+ if (value2 !== void 0) flat[v2.key] = value2;
222799
+ }
222800
+ for (const v2 of vars) {
222801
+ if (v2.scope !== "personal" || v2.actorId && v2.actorId !== actorId) continue;
222802
+ const value2 = decode2(v2);
222803
+ if (value2 === void 0) continue;
222804
+ flat[v2.key] = value2;
222805
+ if (v2.actorId === actorId) own3[v2.key] = value2;
222806
+ }
222807
+ const ready = new Set(collectAllConnectorCredentials({
222722
222808
  vars: flat,
222723
222809
  actorName: flat["GIT_AUTHOR_NAME"] ?? actorId.split(":").pop() ?? "oasis-agent"
222724
- }).map((c) => c.slug);
222725
- return { connections, globalConnected, actorConnected, effective, credentialReady };
222810
+ }).map((c) => c.slug));
222811
+ const assessed = /* @__PURE__ */ new Set();
222812
+ for (const connector of connectors) {
222813
+ const slug6 = connector.config.slug;
222814
+ assessed.add(slug6);
222815
+ if (ready.has(slug6) || !connector.refreshCredentials) continue;
222816
+ const materials = (connector.refreshCredentialSources ?? []).filter((s2) => s2.required);
222817
+ if (materials.length > 0 && materials.every((s2) => Boolean((s2.orgDefault ? flat : own3)[s2.from]))) ready.add(slug6);
222818
+ }
222819
+ for (const slug6 of undecryptable) ready.delete(slug6);
222820
+ return { ready, assessed, undecryptable };
222726
222821
  }
222727
222822
  /**
222728
222823
  * 该员工每个连接器的「有效启用」集合(变量/技能注入闸门):
@@ -222815,6 +222910,23 @@ ${input.description}
222815
222910
  if (v2.value === void 0 || v2.value === "") {
222816
222911
  return;
222817
222912
  }
222913
+ await this.opts.store.putVariable(this.toStoredVariableRow(v2, v2.value));
222914
+ }
222915
+ /**
222916
+ * **新建**一条变量(决策 0565):这一行(组织变量按 key、个人变量按 key + actorId、项目变量按 key + projectId)
222917
+ * 已经存在就返回 `"exists"`、什么都不写——不像 `putVariable` 那样悄悄覆盖。
222918
+ *
222919
+ * 「管理页新建组织变量」「Agent 详情新建个人变量」都走这里:同一组织第二条同名组织变量、同一 Agent
222920
+ * 第二条同名个人变量在存储层当场被挡下;不同 Agent 的同名个人变量、以及个人变量与同名组织变量
222921
+ * 是不同的唯一键,照常共存。值必填(新建出一条没有值的空壳没有意义)。
222922
+ */
222923
+ async createVariable(v2) {
222924
+ if (!v2.value) throw new ApiError(400, "BAD_REQUEST", "\u65B0\u5EFA\u53D8\u91CF\u5FC5\u987B\u63D0\u4F9B\u53D8\u91CF\u503C");
222925
+ const inserted = await this.opts.store.insertVariable(this.toStoredVariableRow(v2, v2.value));
222926
+ return inserted ? "created" : "exists";
222927
+ }
222928
+ /** `putVariable` / `createVariable` 共用:校验 + 加密 + 摊成存储行。两条写路径的落行口径必须逐字一致。 */
222929
+ toStoredVariableRow(v2, value2) {
222818
222930
  if (v2.deliveryMode === void 0 && v2.connectorId === void 0) {
222819
222931
  throw new ApiError(
222820
222932
  400,
@@ -222822,7 +222934,7 @@ ${input.description}
222822
222934
  "\u5199\u53D8\u91CF\u5FC5\u987B\u663E\u5F0F\u63D0\u4F9B deliveryMode\uFF08env | ref | broker\uFF09\u2014\u2014\u6295\u9012\u6863\u51B3\u5B9A\u660E\u6587\u662F\u5426\u8FDB agent env\uFF0C\u4E0D\u8BBE\u7F3A\u7701"
222823
222935
  );
222824
222936
  }
222825
- await this.opts.store.putVariable({
222937
+ return {
222826
222938
  key: v2.key,
222827
222939
  // 展示名(子 PRD《密钥管理》§2)。没传就不传下去——存储层按「不动」处理,不抹既有名字。
222828
222940
  ...v2.name !== void 0 && v2.name !== "" ? { name: v2.name } : {},
@@ -222833,7 +222945,7 @@ ${input.description}
222833
222945
  ...v2.overrides !== void 0 ? { overrides: v2.overrides } : {},
222834
222946
  // 非连接器写入到此 deliveryMode 必非空(上面已拦);连接器写入沿用 env(其值经 sensitiveVars 剔除,不进 env)。
222835
222947
  deliveryMode: v2.deliveryMode ?? "env",
222836
- valueEncrypted: v2.encrypted === false ? `plain:${v2.value}` : this.encrypt(v2.value),
222948
+ valueEncrypted: v2.encrypted === false ? `plain:${value2}` : this.encrypt(value2),
222837
222949
  updatedAt: this.now(),
222838
222950
  /* 创建者 / 创建时间 / 更新者:**由服务端落,不接受调用方自报**。
222839
222951
  createdBy/createdAt 两格存储层按「第一次落行才写」处理(COALESCE 旧值),所以这里每次都传,
@@ -222846,7 +222958,7 @@ ${input.description}
222846
222958
  // 决策 0556:发给谁只对全局行有意义;个人档 / 项目档一律不带(存储层 COALESCE:不带 = 不动)。
222847
222959
  ...v2.scope === "global" && v2.audience !== void 0 ? { audience: v2.audience } : {},
222848
222960
  ...v2.scope === "global" && v2.enabledActorIds !== void 0 ? { enabledActorIds: dedupe(v2.enabledActorIds) } : {}
222849
- });
222961
+ };
222850
222962
  }
222851
222963
  /**
222852
222964
  * 只改一行变量的元数据、不碰值(决策 0556)。
@@ -222906,15 +223018,18 @@ ${input.description}
222906
223018
  /** 前端/接口可见的列表。
222907
223019
  * - actorId 指定时:返回全局 + 该员工个人变量,按 key 去重(个人覆盖全局);
222908
223020
  * 个人变量如覆盖同名全局则 overrides 字段填 key 名,告知前端。
223021
+ * `opts.includeShadowed`(决策 0565):**不去重**——被同名个人变量覆盖的那条组织变量也带上,
223022
+ * Agent 详情要把「组织变量」与「个人变量」分开摆,覆盖关系由调用方按同名个人行判。
223023
+ * 发给谁(audience)的过滤仍由存储层那一条查询做,这里不另判。
222909
223024
  * - 不指定时:返回全部(管理员视图,不去重)。
222910
223025
  */
222911
- async listVariables(actorId) {
223026
+ async listVariables(actorId, opts) {
222912
223027
  const rows = await this.opts.store.listVariables(actorId);
222913
223028
  let source = rows;
222914
223029
  if (actorId) {
222915
223030
  const personalKeys = new Set(rows.filter((r) => r.actorId).map((r) => r.key));
222916
223031
  const globalKeys = new Set(rows.filter((r) => !r.actorId).map((r) => r.key));
222917
- source = rows.filter((r) => r.actorId !== void 0 || !personalKeys.has(r.key));
223032
+ if (!opts?.includeShadowed) source = rows.filter((r) => r.actorId !== void 0 || !personalKeys.has(r.key));
222918
223033
  source = source.map(
222919
223034
  (r) => r.actorId && globalKeys.has(r.key) && !r.overrides ? { ...r, overrides: r.key } : r
222920
223035
  );
@@ -222956,11 +223071,19 @@ ${input.description}
222956
223071
  * 或行是 2026-09-10 之前落的(那时还没有 created_by 这一格)。
222957
223072
  */
222958
223073
  async variableCreatedBy(key, selector = {}) {
223074
+ return (await this.variableRowMeta(key, selector))?.createdBy;
223075
+ }
223076
+ /** 按存储唯一键 (key, actorId?, projectId?) 找那一行的元数据(**不解密**)。没有这一行返回 undefined。 */
223077
+ async variableRowMeta(key, selector = {}) {
222959
223078
  const rows = await this.opts.store.listVariables();
222960
223079
  const hit = rows.find(
222961
223080
  (r) => r.key === key && (r.actorId ?? "") === (selector.actorId ?? "") && (r.projectId ?? "") === (selector.projectId ?? "")
222962
223081
  );
222963
- return hit?.createdBy;
223082
+ if (!hit) return void 0;
223083
+ return {
223084
+ ...hit.createdBy !== void 0 ? { createdBy: hit.createdBy } : {},
223085
+ ...hit.connectorId !== void 0 ? { connectorId: hit.connectorId } : {}
223086
+ };
222964
223087
  }
222965
223088
  async revealVariable(key, actorId) {
222966
223089
  const ct = await this.opts.store.getVariableCiphertext(key, actorId);
@@ -224316,7 +224439,8 @@ function actorsDomain(opts) {
224316
224439
  });
224317
224440
  router.get("/api/connectors", async (req) => {
224318
224441
  const { service } = await resolveCtx(req.auth.companyId);
224319
- return { status: 200, body: { items: await service.listConnectorsWithAssignments() } };
224442
+ const { items, connectedActorIdsByConnector } = await service.listConnectorsWithAssignments();
224443
+ return { status: 200, body: { items, connectedActorIdsByConnector } };
224320
224444
  });
224321
224445
  router.post("/api/connectors", async (req) => {
224322
224446
  const { service } = await resolveCtx(req.auth.companyId);
@@ -224367,9 +224491,39 @@ function actorsDomain(opts) {
224367
224491
  throw new ApiError(
224368
224492
  403,
224369
224493
  "SECRET_NOT_OWNED",
224370
- "\u8FD9\u4EFD\u5BC6\u94A5\u7531\u522B\u4EBA\u521B\u5EFA\uFF0C\u53EA\u6709\u521B\u5EFA\u8005\u672C\u4EBA\u6216\u7EC4\u7EC7\u7BA1\u7406\u8005\u53EF\u4EE5\u4FEE\u6539 / \u5220\u9664"
224494
+ "\u8FD9\u6761\u53D8\u91CF\u7531\u522B\u4EBA\u521B\u5EFA\uFF0C\u53EA\u6709\u521B\u5EFA\u8005\u672C\u4EBA\u6216\u7EC4\u7EC7\u7BA1\u7406\u8005\u53EF\u4EE5\u4FEE\u6539 / \u5220\u9664"
224371
224495
  );
224372
224496
  };
224497
+ const variableExistsError = async (service, key, selector) => {
224498
+ const meta = await service.variableRowMeta(key, selector).catch(() => void 0);
224499
+ if (meta?.connectorId) {
224500
+ return new ApiError(
224501
+ 409,
224502
+ "VARIABLE_ALREADY_EXISTS",
224503
+ `\u53D8\u91CF\u540D ${key} \u5DF2\u88AB\u8FDE\u63A5\u5668\u300C${meta.connectorId}\u300D\u7684\u51ED\u636E\u5360\u7528\uFF1B\u8FDE\u63A5\u5668\u51ED\u636E\u8BF7\u901A\u8FC7\u8FDE\u63A5\u5668\u6388\u6743\u6D41\u7A0B\u7EF4\u62A4`
224504
+ );
224505
+ }
224506
+ if (selector.actorId) {
224507
+ return new ApiError(
224508
+ 409,
224509
+ "VARIABLE_ALREADY_EXISTS",
224510
+ `\u8FD9\u4F4D Agent \u5DF2\u7ECF\u6709\u53EB ${key} \u7684\u4E2A\u4EBA\u53D8\u91CF\u4E86\u2014\u2014\u540C\u4E00 Agent \u540C\u4E00\u4E2A\u53D8\u91CF\u540D\u53EA\u80FD\u6709\u4E00\u6761\u4E2A\u4EBA\u53D8\u91CF\uFF0C\u8981\u6539\u8BF7\u7F16\u8F91\u90A3\u4E00\u6761`
224511
+ );
224512
+ }
224513
+ if (selector.projectId) {
224514
+ return new ApiError(409, "VARIABLE_ALREADY_EXISTS", `\u9879\u76EE ${selector.projectId} \u5DF2\u7ECF\u6709\u53EB ${key} \u7684\u9879\u76EE\u53D8\u91CF\u4E86`);
224515
+ }
224516
+ return new ApiError(
224517
+ 409,
224518
+ "VARIABLE_ALREADY_EXISTS",
224519
+ `\u7EC4\u7EC7\u91CC\u5DF2\u7ECF\u6709\u53EB ${key} \u7684\u7EC4\u7EC7\u53D8\u91CF\u4E86\u2014\u2014\u540C\u4E00\u4E2A\u53D8\u91CF\u540D\u5728\u7EC4\u7EC7\u91CC\u53EA\u80FD\u6709\u4E00\u6761\u7EC4\u7EC7\u53D8\u91CF\uFF0C\u8981\u6539\u8BF7\u7F16\u8F91\u90A3\u4E00\u6761`
224520
+ );
224521
+ };
224522
+ const parseCreateFlag = (raw) => {
224523
+ if (raw === void 0 || raw === false) return false;
224524
+ if (raw === true) return true;
224525
+ throw new ApiError(400, "BAD_REQUEST", "create \u53EA\u80FD\u662F\u5E03\u5C14\u503C true / false");
224526
+ };
224373
224527
  const auditVariableChange = async (req, kind, variableKey, targetScope) => {
224374
224528
  await opts.credentialAudit?.({
224375
224529
  kind,
@@ -224391,6 +224545,7 @@ function actorsDomain(opts) {
224391
224545
  const { service } = await resolveCtx(req.auth.companyId);
224392
224546
  const b2 = req.body;
224393
224547
  if (!b2?.key) throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11 key");
224548
+ const create = parseCreateFlag(b2.create);
224394
224549
  const expiresAt2 = normalizeExpiresAt(b2.expiresAt);
224395
224550
  const audienceMeta = parseAudienceMeta(b2);
224396
224551
  if ((audienceMeta.audience !== void 0 || audienceMeta.enabledActorIds !== void 0) && b2.scope !== "global") {
@@ -224407,7 +224562,7 @@ function actorsDomain(opts) {
224407
224562
  if (scope === "project" && !b2.projectId) {
224408
224563
  throw new ApiError(400, "BAD_REQUEST", "scope=project \u5FC5\u987B\u63D0\u4F9B projectId");
224409
224564
  }
224410
- if (scope === "project" && opts.projectExists && !await opts.projectExists(b2.projectId)) {
224565
+ if (scope === "project" && opts.projectExists && !await opts.projectExists(b2.projectId, req.auth.companyId)) {
224411
224566
  throw new ApiError(400, "BAD_REQUEST", `\u9879\u76EE\u4E0D\u5B58\u5728\uFF1A${b2.projectId}\uFF08scope=project \u53D8\u91CF\u7684 projectId \u5FC5\u987B\u662F\u771F\u5B9E\u9879\u76EE\uFF09`);
224412
224567
  }
224413
224568
  if (b2.deliveryMode !== void 0 && b2.deliveryMode !== "env" && b2.deliveryMode !== "ref" && b2.deliveryMode !== "broker") {
@@ -224427,11 +224582,9 @@ function actorsDomain(opts) {
224427
224582
  "personal \u53D8\u91CF\u8BF7\u8D70 POST /api/actors/:id/variables\uFF08\u672C\u7AEF\u70B9\u65E0 actorId\uFF0C\u5199\u5165\u4F1A\u843D\u4E0B\u65E0\u5F52\u5C5E\u7684\u7578\u5F62\u884C\uFF09"
224428
224583
  );
224429
224584
  }
224430
- if (b2.value === void 0 || b2.value === "") return { status: 200, body: { ok: true } };
224431
- await requireSecretOwner(req, service, b2.key, scope === "project" ? { projectId: b2.projectId } : {});
224432
- await service.putVariable({
224585
+ const selector = scope === "project" ? { projectId: b2.projectId } : {};
224586
+ const input = {
224433
224587
  key: b2.key,
224434
- value: b2.value,
224435
224588
  scope,
224436
224589
  // 展示名(子 PRD《密钥管理》§2「名称」);不传 = 不改既有名字。
224437
224590
  ...typeof b2.name === "string" && b2.name.trim() ? { name: b2.name.trim() } : {},
@@ -224444,7 +224597,16 @@ function actorsDomain(opts) {
224444
224597
  // 创建者/更新者取**当前登录人**,不接受 body 传入——自报的「谁建的」等于没有这一格。
224445
224598
  by: req.auth.actor,
224446
224599
  expiresAt: expiresAt2
224447
- });
224600
+ };
224601
+ if (create) {
224602
+ if (b2.value === void 0 || b2.value === "") throw new ApiError(400, "BAD_REQUEST", "\u65B0\u5EFA\u53D8\u91CF\u5FC5\u987B\u63D0\u4F9B\u53D8\u91CF\u503C");
224603
+ const outcome = await service.createVariable({ ...input, value: b2.value });
224604
+ if (outcome === "exists") throw await variableExistsError(service, b2.key, selector);
224605
+ } else {
224606
+ if (b2.value === void 0 || b2.value === "") return { status: 200, body: { ok: true } };
224607
+ await requireSecretOwner(req, service, b2.key, selector);
224608
+ await service.putVariable({ ...input, value: b2.value });
224609
+ }
224448
224610
  await auditVariableChange(
224449
224611
  req,
224450
224612
  "credential_write",
@@ -224486,7 +224648,8 @@ function actorsDomain(opts) {
224486
224648
  router.get("/api/actors/:id/variables", async (req) => {
224487
224649
  requireVariableManager(req);
224488
224650
  const { service } = await resolveCtx(req.auth.companyId);
224489
- return { status: 200, body: { items: await service.listVariables(req.params.id) } };
224651
+ const includeShadowed = ["1", "true"].includes(req.query.get("includeShadowed") ?? "");
224652
+ return { status: 200, body: { items: await service.listVariables(req.params.id, includeShadowed ? { includeShadowed } : void 0) } };
224490
224653
  });
224491
224654
  router.post("/api/actors/:id/variables", async (req) => {
224492
224655
  requireVariableManager(req);
@@ -224494,6 +224657,12 @@ function actorsDomain(opts) {
224494
224657
  const actorId = req.params.id;
224495
224658
  const b2 = req.body;
224496
224659
  if (!b2?.key) throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11 key");
224660
+ const create = parseCreateFlag(b2.create);
224661
+ if (create) {
224662
+ const owner = await service.getActor(actorId);
224663
+ if (!owner) throw new ApiError(404, "NOT_FOUND", `\u6CA1\u6709\u8FD9\u4E2A Agent\uFF1A${actorId}`);
224664
+ if (owner.kind !== "agent") throw new ApiError(400, "BAD_REQUEST", "\u4E2A\u4EBA\u53D8\u91CF\u53EA\u80FD\u5EFA\u5728 Agent \u4E0A");
224665
+ }
224497
224666
  const expiresAt2 = normalizeExpiresAt(b2.expiresAt);
224498
224667
  if (b2.deliveryMode !== void 0 && b2.deliveryMode !== "env" && b2.deliveryMode !== "ref" && b2.deliveryMode !== "broker") {
224499
224668
  throw new ApiError(400, "BAD_REQUEST", "deliveryMode \u53EA\u80FD\u662F env\u3001ref \u6216 broker");
@@ -224505,6 +224674,7 @@ function actorsDomain(opts) {
224505
224674
  "\u5FC5\u987B\u663E\u5F0F\u63D0\u4F9B deliveryMode\uFF08env | ref | broker\uFF09\u2014\u2014\u6295\u9012\u6863\u51B3\u5B9A\u660E\u6587\u662F\u5426\u8FDB agent env\uFF0C\u4E0D\u8BBE\u7F3A\u7701"
224506
224675
  );
224507
224676
  }
224677
+ if (create && (b2.value === void 0 || b2.value === "")) throw new ApiError(400, "BAD_REQUEST", "\u65B0\u5EFA\u53D8\u91CF\u5FC5\u987B\u63D0\u4F9B\u53D8\u91CF\u503C");
224508
224678
  if (b2.value === void 0 || b2.value === "") return { status: 200, body: { ok: true } };
224509
224679
  if (b2.connectorId === void 0) {
224510
224680
  const existing = await service.listVariables(actorId);
@@ -224517,13 +224687,12 @@ function actorsDomain(opts) {
224517
224687
  );
224518
224688
  }
224519
224689
  }
224520
- await requireSecretOwner(req, service, b2.key, { actorId });
224521
- await service.putVariable({
224690
+ const input = {
224522
224691
  key: b2.key,
224523
224692
  value: b2.value,
224524
224693
  actorId,
224525
224694
  scope: "personal",
224526
- // 展示名(同上)。组织页「添加密钥 → 新建」走的就是这条端点。
224695
+ // 展示名(同上)。组织页「新建变量」走的就是这条端点。
224527
224696
  ...typeof b2.name === "string" && b2.name.trim() ? { name: b2.name.trim() } : {},
224528
224697
  ...b2.deliveryMode !== void 0 ? { deliveryMode: b2.deliveryMode } : {},
224529
224698
  ...b2.connectorId !== void 0 ? { connectorId: b2.connectorId } : {},
@@ -224531,7 +224700,14 @@ function actorsDomain(opts) {
224531
224700
  ...b2.encrypted !== void 0 ? { encrypted: b2.encrypted } : {},
224532
224701
  by: req.auth.actor,
224533
224702
  expiresAt: expiresAt2
224534
- });
224703
+ };
224704
+ if (create) {
224705
+ const outcome = await service.createVariable(input);
224706
+ if (outcome === "exists") throw await variableExistsError(service, b2.key, { actorId });
224707
+ } else {
224708
+ await requireSecretOwner(req, service, b2.key, { actorId });
224709
+ await service.putVariable(input);
224710
+ }
224535
224711
  await auditVariableChange(req, "credential_write", b2.key, `personal:${actorId}`);
224536
224712
  return { status: 201, body: { ok: true } };
224537
224713
  });
@@ -244949,6 +245125,26 @@ var init_postgres = __esm({
244949
245125
  });
244950
245126
 
244951
245127
  // ../storage/src/postgres-registry.ts
245128
+ function variableInsertParams(v2) {
245129
+ return [
245130
+ v2.key,
245131
+ v2.scope,
245132
+ v2.actorId ?? null,
245133
+ v2.projectId ?? null,
245134
+ v2.connectorId ?? null,
245135
+ v2.overrides ?? null,
245136
+ v2.deliveryMode ?? null,
245137
+ v2.valueEncrypted,
245138
+ v2.updatedAt,
245139
+ v2.expiresAt ?? null,
245140
+ v2.name ?? null,
245141
+ v2.createdBy ?? null,
245142
+ v2.createdAt ?? null,
245143
+ v2.updatedBy ?? null,
245144
+ v2.audience ?? null,
245145
+ v2.enabledActorIds ?? null
245146
+ ];
245147
+ }
244952
245148
  function rowToVariable(row) {
244953
245149
  return {
244954
245150
  key: row.key,
@@ -244976,7 +245172,7 @@ function rowToVariable(row) {
244976
245172
  ...Array.isArray(row.enabled_actor_ids) ? { enabledActorIds: row.enabled_actor_ids } : {}
244977
245173
  };
244978
245174
  }
244979
- var ident5, PostgresRegistryStore, rowToActor, rowToConfig, rowToAgentPrefs;
245175
+ var ident5, PostgresRegistryStore, VARIABLE_INSERT_COLUMNS, rowToActor, rowToConfig, rowToAgentPrefs;
244980
245176
  var init_postgres_registry = __esm({
244981
245177
  "../storage/src/postgres-registry.ts"() {
244982
245178
  "use strict";
@@ -245657,7 +245853,7 @@ var init_postgres_registry = __esm({
245657
245853
  updated_by 反过来:每次都覆盖。三者与 name 的 COALESCE 方向相反,别照抄。 */
245658
245854
  /* audience / enabled_actor_ids(决策 0556)走 COALESCE(**新值**, 旧值):不传 = 不动——
245659
245855
  改默认值时手上不带「发给谁」,普通赋值会把「仅指定 Agent」悄悄打回全员生效。 */
245660
- `INSERT INTO ${this.s}.variables (key, scope, actor_id, project_id, connector_id, overrides, delivery_mode, value_encrypted, updated_at, expires_at, name, created_by, created_at, updated_by, audience, enabled_actor_ids)
245856
+ `INSERT INTO ${this.s}.variables (${VARIABLE_INSERT_COLUMNS})
245661
245857
  VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)
245662
245858
  ON CONFLICT (key, COALESCE(actor_id, ''), COALESCE(project_id, '')) DO UPDATE
245663
245859
  SET scope=$2, actor_id=$3, project_id=$4, connector_id=$5, overrides=$6, delivery_mode=$7, value_encrypted=$8, updated_at=$9, expires_at=$10,
@@ -245667,25 +245863,17 @@ var init_postgres_registry = __esm({
245667
245863
  updated_by=$14,
245668
245864
  audience=COALESCE($15, ${this.s}.variables.audience),
245669
245865
  enabled_actor_ids=COALESCE($16, ${this.s}.variables.enabled_actor_ids)`,
245670
- [
245671
- v2.key,
245672
- v2.scope,
245673
- v2.actorId ?? null,
245674
- v2.projectId ?? null,
245675
- v2.connectorId ?? null,
245676
- v2.overrides ?? null,
245677
- v2.deliveryMode ?? null,
245678
- v2.valueEncrypted,
245679
- v2.updatedAt,
245680
- v2.expiresAt ?? null,
245681
- v2.name ?? null,
245682
- v2.createdBy ?? null,
245683
- v2.createdAt ?? null,
245684
- v2.updatedBy ?? null,
245685
- v2.audience ?? null,
245686
- v2.enabledActorIds ?? null
245687
- ]
245866
+ variableInsertParams(v2)
245867
+ );
245868
+ }
245869
+ async insertVariable(v2) {
245870
+ const r = await this.pool.query(
245871
+ `INSERT INTO ${this.s}.variables (${VARIABLE_INSERT_COLUMNS})
245872
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)
245873
+ ON CONFLICT (key, COALESCE(actor_id, ''), COALESCE(project_id, '')) DO NOTHING`,
245874
+ variableInsertParams(v2)
245688
245875
  );
245876
+ return (r.rowCount ?? 0) > 0;
245689
245877
  }
245690
245878
  async touchVariableUsage(key, actorId, projectId2, when) {
245691
245879
  await this.pool.query(
@@ -245773,6 +245961,7 @@ var init_postgres_registry = __esm({
245773
245961
  );
245774
245962
  }
245775
245963
  };
245964
+ VARIABLE_INSERT_COLUMNS = "key, scope, actor_id, project_id, connector_id, overrides, delivery_mode, value_encrypted, updated_at, expires_at, name, created_by, created_at, updated_by, audience, enabled_actor_ids";
245776
245965
  rowToActor = (row) => ({
245777
245966
  id: row.id,
245778
245967
  kind: row.kind,
@@ -257049,6 +257238,9 @@ var init_cutover_aware_registry = __esm({
257049
257238
  async putVariable(...args) {
257050
257239
  return (await this.orgStoreForWrite()).putVariable(...args);
257051
257240
  }
257241
+ async insertVariable(...args) {
257242
+ return (await this.orgStoreForWrite()).insertVariable(...args);
257243
+ }
257052
257244
  async patchVariableValue(...args) {
257053
257245
  return (await this.orgStoreForWrite()).patchVariableValue(...args);
257054
257246
  }
@@ -263398,8 +263590,10 @@ async function startServe(opts) {
263398
263590
  credentialAudit,
263399
263591
  readCredentialAudit,
263400
263592
  // ADR 0125:写 scope=project 变量时校验 projectId 真实存在(getProject 直查,含临时项目)。
263401
- // projectStateStore 在下方 ~行 1023 才赋值——本箭头只在请求时跑,那时已就绪(同 resolveDispatchScope 惰性模式)。
263402
- projectExists: async (id) => await projectStateStore.getProject(id) !== null,
263593
+ // 按请求所属公司查(同 PR #870 的剧本/自动化):此前固定查 projectStateStore(默认公司那一份),
263594
+ // 非默认公司的项目一律报「项目不存在」。未解析出公司 = 默认公司,与 experiments 同一口径。
263595
+ // projectExistsIn 在下方才声明——本箭头只在请求时跑,那时已就绪(同 resolveDispatchScope 惰性模式)。
263596
+ projectExists: async (id, companyId) => projectExistsIn(id, companyId ?? defaultCompanyId),
263403
263597
  /* 子 PRD《密钥管理》§1 的权限判据:密钥的写 / 删要认「创建者本人或组织管理者」。
263404
263598
  owner 与 admin 都算组织管理者(与组织页 `orgIdentityOf` 同一口径)。
263405
263599
  `controlPlaneStore` 在下方才赋值——本箭头只在请求时跑,那时已就绪(同 projectExists 的惰性模式)。
@@ -276452,7 +276646,7 @@ function shimScript() {
276452
276646
  }
276453
276647
 
276454
276648
  // src/index.ts
276455
- var PKG_VERSION = true ? "2.2.36" : "dev";
276649
+ var PKG_VERSION = true ? "2.2.37" : "dev";
276456
276650
  var LOCAL_BIN = localBin();
276457
276651
  var NPM_PREFIX = npmPrefix();
276458
276652
  var INSTANCE = DEFAULT_INSTANCE;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oasis_test_v2",
3
- "version": "2.2.36",
3
+ "version": "2.2.37",
4
4
  "description": "Oasis node daemon + CLI — background daemon, auto-start, full server CLI",
5
5
  "bin": {
6
6
  "oasis": "./dist/index.js"
@@ -26,6 +26,6 @@
26
26
  "node": ">=20"
27
27
  },
28
28
  "oasisRelease": {
29
- "sourceHead": "696858b581661883b2eb278fb40c793f3ecf70da"
29
+ "sourceHead": "e9498c25c512b5edf75fde28c132f60d079b20a0"
30
30
  }
31
31
  }