@ran-sh/dsh-crew 1.0.1 → 1.0.3

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.
@@ -509,6 +509,15 @@ const COPY = {
509
509
  providerState: (state) => `状态:${state}`,
510
510
  providerModels: (count) => `${count} 个模型`,
511
511
  providerJobs: (count) => `${count} 个运行任务`,
512
+ providerOfficialBuiltin: "· 官方内置(不可删除)",
513
+ providerSourceUnresolved: "· 来源未解析(安全锁定)",
514
+ providerLayerMigration: "检测到旧版 profile Provider",
515
+ providerLayerMigrationHint: "这些 Provider 仍在 Crew profile/base 层,所以 3210 原生页面不会显示删除。请先按迁移计划提升到 Harness 用户设置层;不会复制密钥。",
516
+ providerLayerMigrationPlan: "迁移计划",
517
+ providerLayerMigrate: "迁移到用户层",
518
+ providerLayerMigrating: "迁移中…",
519
+ providerLayerMigrationConfirm: "确认把这个 Provider 移到 Harness 用户设置层?会先保存可回滚快照,不会复制密钥值。",
520
+ providerLayerRollback: "回滚迁移",
512
521
  providerDeletePlan: "生成删除计划",
513
522
  providerDelete: "确认删除",
514
523
  providerPlanReady: "计划已生成;再次确认后才会修改 3210 配置。",
@@ -519,6 +528,12 @@ const COPY = {
519
528
  providerDeleteConfirm: "确认按计划删除这个 Harness Provider?会清理路由引用并重启 3210。",
520
529
  providerDeleteBlocked: "当前 Provider 不能删除(内置、在用、已删除或缺少可用替换)。",
521
530
  providerLifecycleError: "Provider 生命周期操作失败",
531
+ providerDeletePending: "配置可能已变更,事务仍待重启/验证;请刷新状态后选择回滚。",
532
+ providerRollbackContinue: "继续回滚验证",
533
+ recoveryUnresolved: "发现不可自动恢复的事务",
534
+ recoveryStorage: "存储项",
535
+ recoveryQuarantine: "隔离",
536
+ recoveryQuarantineConfirm: "该事务清单无法安全恢复。将其移入隔离区并阻止自动使用,继续?",
522
537
  providerNoInventory: "暂时无法读取 3210 Provider inventory。",
523
538
  credentialReferences: "Credential 引用",
524
539
  credentialReferenceHint: "仅显示引用名、归属和孤儿状态;不会读取或删除密钥。",
@@ -529,8 +544,8 @@ const COPY = {
529
544
  credentialPurging: "清理中…",
530
545
  credentialPurgeUnavailable: "凭据清理不可用",
531
546
  credentialPurgeUnverified: "已执行清理,但验证失败;引用仍保留用于恢复检查",
532
- customProviders: "自定义 Provider",
533
- addProvider: "+ 添加 Provider",
547
+ customProviders: "多模态适配器",
548
+ addProvider: "+ 添加多模态适配器",
534
549
  noCustomProviders: "暂无。添加后会出现在上方的视觉 / 生图 provider 选择里。",
535
550
  providerName: "名称",
536
551
  modelsField: "模型列表(逗号分隔)",
@@ -832,6 +847,15 @@ const COPY = {
832
847
  providerState: (state) => `State: ${state}`,
833
848
  providerModels: (count) => `${count} models`,
834
849
  providerJobs: (count) => `${count} running jobs`,
850
+ providerOfficialBuiltin: "· official built-in (immutable)",
851
+ providerSourceUnresolved: "· source unresolved (locked)",
852
+ providerLayerMigration: "Legacy profile Providers detected",
853
+ providerLayerMigrationHint: "These Providers still exist in the Crew profile/base layer, so the native 3210 page cannot show Delete. Promote them to Harness user settings with an explicit plan; keys are never copied.",
854
+ providerLayerMigrationPlan: "Migration plan",
855
+ providerLayerMigrate: "Migrate to user layer",
856
+ providerLayerMigrating: "Migrating…",
857
+ providerLayerMigrationConfirm: "Move this Provider into Harness user settings? A rollback snapshot is saved first; credential values are never copied.",
858
+ providerLayerRollback: "Rollback migration",
835
859
  providerDeletePlan: "Plan deletion",
836
860
  providerDelete: "Confirm delete",
837
861
  providerPlanReady: "Plan ready; a second confirmation is required before changing 3210 config.",
@@ -842,6 +866,12 @@ const COPY = {
842
866
  providerDeleteConfirm: "Delete this Harness Provider per the plan? Routing refs will be scrubbed and 3210 restarted.",
843
867
  providerDeleteBlocked: "This Provider cannot be deleted (built-in, in use, already absent, or missing a valid replacement).",
844
868
  providerLifecycleError: "Provider lifecycle operation failed",
869
+ providerDeletePending: "Configuration may already be changed; the transaction still needs restart/verification. Refresh status and use rollback if needed.",
870
+ providerRollbackContinue: "Continue rollback verification",
871
+ recoveryUnresolved: "An unrecoverable recovery transaction is blocking deletion",
872
+ recoveryStorage: "storage entry",
873
+ recoveryQuarantine: "Quarantine",
874
+ recoveryQuarantineConfirm: "This recovery manifest cannot be safely reopened. Move it into the quarantine area and keep it out of automatic recovery?",
845
875
  providerNoInventory: "3210 Provider inventory is temporarily unavailable.",
846
876
  credentialReferences: "Credential references",
847
877
  credentialReferenceHint: "Names, ownership, and orphan status only; values are never read or deleted.",
@@ -852,8 +882,8 @@ const COPY = {
852
882
  credentialPurging: "Purging…",
853
883
  credentialPurgeUnavailable: "Credential purge unavailable",
854
884
  credentialPurgeUnverified: "Purge ran but verification failed; the reference remains visible for recovery",
855
- customProviders: "Custom providers",
856
- addProvider: "+ Add provider",
885
+ customProviders: "Multimodal adapters",
886
+ addProvider: "+ Add multimodal adapter",
857
887
  noCustomProviders: "None yet. Added providers appear in the vision / image-gen selects above.",
858
888
  providerName: "Name",
859
889
  modelsField: "Models (comma-separated)",
@@ -1959,13 +1989,13 @@ function WorkersPanel({ ctx }) {
1959
1989
  applyPatch(patch);
1960
1990
  };
1961
1991
  const deleteHarnessProvider = async (record) => {
1962
- if (!record || record.ownership === "harness" || record.desired_state === "absent" || Number(record.references?.active_jobs ?? 0) > 0) {
1992
+ if (!record || record.delete_capability !== "supported" || record.desired_state === "absent" || Number(record.references?.active_jobs ?? 0) > 0) {
1963
1993
  setNotice(copy.providerDeleteBlocked);
1964
1994
  return;
1965
1995
  }
1966
1996
  let replacementDefault;
1967
1997
  if (record.references?.harness_default === true) {
1968
- const candidate = (providerInventory?.records ?? []).find((item) => item.id !== record.id && item.desired_state !== "absent" && item.ownership !== "harness" && (item.models?.length ?? 0) > 0)?.id ?? "";
1998
+ const candidate = (providerInventory?.records ?? []).find((item) => item.id === "deepseek-official" && item.id !== record.id && item.desired_state !== "absent" && (item.models?.length ?? 0) > 0)?.id ?? (providerInventory?.records ?? []).find((item) => item.id !== record.id && item.desired_state !== "absent" && item.delete_capability !== "source-unresolved" && (item.models?.length ?? 0) > 0)?.id ?? "";
1969
1999
  const entered = window.prompt(copy.providerReplacement, candidate);
1970
2000
  if (!entered?.trim()) return;
1971
2001
  replacementDefault = entered.trim();
@@ -2005,14 +2035,106 @@ function WorkersPanel({ ctx }) {
2005
2035
  }
2006
2036
  setNotice(copy.saved);
2007
2037
  await refreshProviderInventory();
2038
+ } catch (error) {
2039
+ try {
2040
+ await refreshProviderInventory();
2041
+ } catch {}
2042
+ setNotice(`${String(error?.message ?? error)} · ${copy.providerDeletePending}`);
2043
+ } finally {
2044
+ setProviderLifecycleBusy(null);
2045
+ }
2046
+ };
2047
+ const migrateHarnessProvider = async (entry) => {
2048
+ if (!entry || !["promote-existing-user", "materialize-user"].includes(entry.action)) {
2049
+ setNotice(entry?.blocked_reason ?? copy.providerLayerMigrationHint);
2050
+ return;
2051
+ }
2052
+ if (!window.confirm(copy.providerLayerMigrationConfirm)) return;
2053
+ const id = String(entry.provider_id ?? "");
2054
+ if (!id) return;
2055
+ setProviderLifecycleBusy(`migrate:${id}`);
2056
+ setNotice(copy.providerLayerMigrating);
2057
+ try {
2058
+ const encoded = encodeURIComponent(id);
2059
+ const planned = await post(`/providers/${encoded}/migrate-plan`, {});
2060
+ if (!planned.ok || !planned.plan) throw new Error(planned.code ?? copy.providerLifecycleError);
2061
+ if (!window.confirm(`${copy.providerLayerMigrationConfirm}\n\n${id}`)) return;
2062
+ const result = await post(`/providers/${encoded}/migrate`, {
2063
+ plan_id: planned.plan.plan_id,
2064
+ confirm: true
2065
+ });
2066
+ if (!result.ok) throw new Error(result.code ?? result.error ?? copy.providerLifecycleError);
2067
+ if (result.restart_required === true && result.result?.state === "RESTART_PENDING") {
2068
+ const restartPath = "http://127.0.0.1:3080/_dsh/dsh-crew/supervisor/restart";
2069
+ const restarted = await readJson(await fetch(restartPath, {
2070
+ method: "POST",
2071
+ headers: { "content-type": "application/json" },
2072
+ body: JSON.stringify({ confirm: true })
2073
+ }), restartPath);
2074
+ if (!restarted.ok) throw new Error(restarted.code ?? restarted.error ?? copy.providerLifecycleError);
2075
+ const verified = await post(`/providers/${encoded}/verify-migration`, {
2076
+ transaction_id: planned.plan.plan_id,
2077
+ confirm: true
2078
+ });
2079
+ if (!verified.ok || verified.state !== "VERIFIED") throw new Error(verified.code ?? verified.error ?? copy.providerLifecycleError);
2080
+ }
2081
+ setNotice(copy.saved);
2082
+ await refreshProviderInventory();
2083
+ } catch (error) {
2084
+ try {
2085
+ await refreshProviderInventory();
2086
+ } catch {}
2087
+ setNotice(String(error?.message ?? error));
2088
+ } finally {
2089
+ setProviderLifecycleBusy(null);
2090
+ }
2091
+ };
2092
+ const rollbackHarnessMigration = async (record, transactionId) => {
2093
+ if (!record?.id || !transactionId || !window.confirm(copy.providerRollbackConfirm)) return;
2094
+ setProviderLifecycleBusy(`rollback-migrate:${record.id}`);
2095
+ setNotice(copy.working);
2096
+ try {
2097
+ const encoded = encodeURIComponent(record.id);
2098
+ const result = await post(`/providers/${encoded}/rollback-migration`, {
2099
+ transaction_id: transactionId,
2100
+ confirm: true
2101
+ });
2102
+ if (!result.ok) throw new Error(result.code ?? result.error ?? copy.providerLifecycleError);
2103
+ if (result.restart_required === true) {
2104
+ const restartPath = "http://127.0.0.1:3080/_dsh/dsh-crew/supervisor/restart";
2105
+ const restarted = await readJson(await fetch(restartPath, {
2106
+ method: "POST",
2107
+ headers: { "content-type": "application/json" },
2108
+ body: JSON.stringify({ confirm: true })
2109
+ }), restartPath);
2110
+ if (!restarted.ok) throw new Error(restarted.code ?? restarted.error ?? copy.providerLifecycleError);
2111
+ }
2112
+ setNotice(copy.saved);
2113
+ await refreshProviderInventory();
2008
2114
  } catch (error) {
2009
2115
  setNotice(String(error?.message ?? error));
2116
+ try {
2117
+ await refreshProviderInventory();
2118
+ } catch {}
2010
2119
  } finally {
2011
2120
  setProviderLifecycleBusy(null);
2012
2121
  }
2013
2122
  };
2014
- const rollbackHarnessProvider = async (record, transactionId) => {
2015
- if (!record || !transactionId || record.desired_state !== "absent") {
2123
+ const rollbackHarnessProvider = async (record, transactionId, transactionState = "VERIFIED") => {
2124
+ const continuing = ["ROLLBACK_PENDING", "ROLLBACK_RESTART_PENDING"].includes(transactionState);
2125
+ const recoverable = [
2126
+ "PLANNED",
2127
+ "TOMBSTONE_APPLYING",
2128
+ "TOMBSTONE_APPLIED",
2129
+ "REFERENCES_APPLYING",
2130
+ "REFERENCES_APPLIED",
2131
+ "DECLARATIONS_APPLYING",
2132
+ "DECLARATIONS_APPLIED",
2133
+ "DELETE_RESTART_PENDING",
2134
+ "ROLLBACK_APPLYING",
2135
+ "ROLLBACK_RESTORED"
2136
+ ].includes(transactionState);
2137
+ if (!record || !transactionId || !continuing && !recoverable && record.desired_state !== "absent") {
2016
2138
  setNotice(copy.providerRollbackUnavailable);
2017
2139
  return;
2018
2140
  }
@@ -2021,7 +2143,11 @@ function WorkersPanel({ ctx }) {
2021
2143
  setNotice(copy.working);
2022
2144
  try {
2023
2145
  const encoded = encodeURIComponent(record.id);
2024
- const result = await post(`/providers/${encoded}/rollback`, {
2146
+ const result = continuing ? {
2147
+ ok: true,
2148
+ restart_required: true,
2149
+ state: "ROLLBACK_PENDING"
2150
+ } : await post(`/providers/${encoded}/rollback`, {
2025
2151
  transaction_id: transactionId,
2026
2152
  confirm: true
2027
2153
  });
@@ -2043,6 +2169,32 @@ function WorkersPanel({ ctx }) {
2043
2169
  setNotice(copy.saved);
2044
2170
  await refreshProviderInventory();
2045
2171
  } catch (error) {
2172
+ try {
2173
+ await refreshProviderInventory();
2174
+ } catch {}
2175
+ setNotice(`${String(error?.message ?? error)} · ${copy.providerDeletePending}`);
2176
+ } finally {
2177
+ setProviderLifecycleBusy(null);
2178
+ }
2179
+ };
2180
+ const quarantineRecoveryTransaction = async (entry) => {
2181
+ const actionId = typeof entry?.action_id === "string" ? entry.action_id : "";
2182
+ if (!actionId || entry?.recoverable === true) return;
2183
+ if (!window.confirm(copy.recoveryQuarantineConfirm)) return;
2184
+ setProviderLifecycleBusy(`quarantine:${actionId}`);
2185
+ setNotice(copy.working);
2186
+ try {
2187
+ const result = await post("/providers/_recovery/quarantine", {
2188
+ action_id: actionId,
2189
+ confirm: true
2190
+ });
2191
+ if (!result.ok) throw new Error(result.code ?? result.error ?? copy.providerLifecycleError);
2192
+ setNotice(copy.saved);
2193
+ await refreshProviderInventory();
2194
+ } catch (error) {
2195
+ try {
2196
+ await refreshProviderInventory();
2197
+ } catch {}
2046
2198
  setNotice(String(error?.message ?? error));
2047
2199
  } finally {
2048
2200
  setProviderLifecycleBusy(null);
@@ -3040,7 +3192,7 @@ function WorkersPanel({ ctx }) {
3040
3192
  style: S.fieldLabel,
3041
3193
  children: copy.workerProvider
3042
3194
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
3043
- value: config.worker_provider_mode ?? "follow-dsh",
3195
+ value: config.worker_provider_mode ?? "deepseek-official",
3044
3196
  onChange: (v) => field("worker_provider_mode", v),
3045
3197
  options: ["follow-dsh", "deepseek-official"].map((m) => ({
3046
3198
  value: m,
@@ -3580,6 +3732,78 @@ function WorkersPanel({ ctx }) {
3580
3732
  },
3581
3733
  children: providerInventoryError
3582
3734
  }),
3735
+ !providerInventoryError && (providerInventory?.migration?.providers?.length ?? 0) > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3736
+ style: {
3737
+ border: "1px solid rgba(201,135,53,0.45)",
3738
+ borderRadius: 7,
3739
+ padding: "8px 10px",
3740
+ marginBottom: 8
3741
+ },
3742
+ children: [
3743
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3744
+ style: {
3745
+ fontWeight: 650,
3746
+ fontSize: 12.5
3747
+ },
3748
+ children: copy.providerLayerMigration
3749
+ }),
3750
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3751
+ style: {
3752
+ fontSize: 11.5,
3753
+ opacity: .7,
3754
+ marginTop: 3
3755
+ },
3756
+ children: copy.providerLayerMigrationHint
3757
+ }),
3758
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3759
+ style: {
3760
+ fontSize: 11,
3761
+ marginTop: 6,
3762
+ fontFamily: "monospace"
3763
+ },
3764
+ children: (providerInventory.migration.providers ?? []).map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3765
+ style: {
3766
+ display: "flex",
3767
+ alignItems: "center",
3768
+ gap: 6,
3769
+ marginTop: 3
3770
+ },
3771
+ children: [
3772
+ entry.provider_id,
3773
+ " · ",
3774
+ entry.action,
3775
+ entry.harness_default ? " · Harness Default" : "",
3776
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
3777
+ ["promote-existing-user", "materialize-user"].includes(entry.action) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3778
+ type: "button",
3779
+ style: {
3780
+ ...S.btn,
3781
+ padding: "2px 7px"
3782
+ },
3783
+ disabled: providerLifecycleBusy !== null,
3784
+ onClick: () => {
3785
+ migrateHarnessProvider(entry);
3786
+ },
3787
+ children: providerLifecycleBusy === `migrate:${entry.provider_id}` ? copy.providerLayerMigrating : copy.providerLayerMigrate
3788
+ })
3789
+ ]
3790
+ }, entry.provider_id))
3791
+ }),
3792
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
3793
+ href: "http://127.0.0.1:3210/",
3794
+ target: "_blank",
3795
+ rel: "noopener noreferrer",
3796
+ style: {
3797
+ ...S.btn,
3798
+ display: "inline-block",
3799
+ textDecoration: "none",
3800
+ marginTop: 7,
3801
+ padding: "3px 8px"
3802
+ },
3803
+ children: [copy.providerLayerMigrationPlan, " → 3210"]
3804
+ })
3805
+ ]
3806
+ }),
3583
3807
  !providerInventoryError && (providerInventory?.records?.length ?? 0) === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3584
3808
  style: {
3585
3809
  fontSize: 12,
@@ -3588,9 +3812,34 @@ function WorkersPanel({ ctx }) {
3588
3812
  children: copy.providerNoInventory
3589
3813
  }),
3590
3814
  (providerInventory?.records ?? []).map((record) => {
3591
- const blocked = record.ownership === "harness" || record.desired_state === "absent" || Number(record.references?.active_jobs ?? 0) > 0;
3815
+ const immutable = record.delete_capability === "immutable-builtin";
3816
+ const unresolved = record.delete_capability === "source-unresolved";
3817
+ const blocked = immutable || unresolved || record.desired_state === "absent" || Number(record.references?.active_jobs ?? 0) > 0;
3592
3818
  const lifecycle = record.desired_state === "absent" ? "absent" : record.lifecycle?.enabled === false ? "disabled" : record.lifecycle?.catalogued ? "catalogued" : "configured";
3593
- const rollbackTransaction = (providerInventory?.lifecycle_transactions ?? []).find((entry) => entry.provider_id === record.id && ["VERIFIED", "RESTART_PENDING"].includes(entry.state));
3819
+ const lifecycleTransactions = providerInventory?.lifecycle_transactions ?? [];
3820
+ const recoveryTransactions = (providerInventory?.recovery_transactions ?? []).map((entry) => ({
3821
+ ...entry,
3822
+ state: entry.phase
3823
+ }));
3824
+ const rollbackTransaction = [...lifecycleTransactions, ...recoveryTransactions].filter((entry) => entry.provider_id === record.id && [
3825
+ "VERIFIED",
3826
+ "RESTART_PENDING",
3827
+ "ROLLBACK_PENDING",
3828
+ "PLANNED",
3829
+ "TOMBSTONE_APPLYING",
3830
+ "TOMBSTONE_APPLIED",
3831
+ "REFERENCES_APPLYING",
3832
+ "REFERENCES_APPLIED",
3833
+ "DECLARATIONS_APPLYING",
3834
+ "DECLARATIONS_APPLIED",
3835
+ "DELETE_RESTART_PENDING",
3836
+ "ROLLBACK_RESTART_PENDING",
3837
+ "ROLLBACK_RESTORED",
3838
+ "ROLLBACK_APPLYING",
3839
+ "PREPARED",
3840
+ "USER_MATERIALIZED",
3841
+ "BASE_REMOVED"
3842
+ ].includes(entry.state)).sort((a, b) => String(a.updated_at ?? "").localeCompare(String(b.updated_at ?? ""))).at(-1);
3594
3843
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3595
3844
  style: {
3596
3845
  display: "flex",
@@ -3619,12 +3868,20 @@ function WorkersPanel({ ctx }) {
3619
3868
  style: { opacity: .65 },
3620
3869
  children: copy.providerModels(record.models?.length ?? 0)
3621
3870
  }),
3871
+ immutable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3872
+ style: { opacity: .65 },
3873
+ children: copy.providerOfficialBuiltin
3874
+ }),
3875
+ unresolved && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3876
+ style: { color: "#c98735" },
3877
+ children: copy.providerSourceUnresolved
3878
+ }),
3622
3879
  Number(record.references?.active_jobs ?? 0) > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3623
3880
  style: { color: "#c98735" },
3624
3881
  children: copy.providerJobs(record.references.active_jobs)
3625
3882
  }),
3626
3883
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
3627
- record.ownership !== "harness" && record.desired_state !== "absent" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3884
+ record.delete_capability === "supported" && record.desired_state !== "absent" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3628
3885
  type: "button",
3629
3886
  style: {
3630
3887
  ...S.btn,
@@ -3636,7 +3893,32 @@ function WorkersPanel({ ctx }) {
3636
3893
  },
3637
3894
  children: providerLifecycleBusy === record.id ? copy.working : copy.providerDeletePlan
3638
3895
  }),
3639
- record.desired_state === "absent" && rollbackTransaction && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3896
+ (record.desired_state === "absent" || [
3897
+ "PLANNED",
3898
+ "TOMBSTONE_APPLYING",
3899
+ "TOMBSTONE_APPLIED",
3900
+ "REFERENCES_APPLYING",
3901
+ "REFERENCES_APPLIED",
3902
+ "DECLARATIONS_APPLYING",
3903
+ "DECLARATIONS_APPLIED",
3904
+ "DELETE_RESTART_PENDING",
3905
+ "ROLLBACK_PENDING",
3906
+ "ROLLBACK_RESTART_PENDING",
3907
+ "ROLLBACK_RESTORED",
3908
+ "ROLLBACK_APPLYING"
3909
+ ].includes(rollbackTransaction?.state)) && rollbackTransaction && rollbackTransaction.source !== "provider-layer-migration" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3910
+ type: "button",
3911
+ style: {
3912
+ ...S.btn,
3913
+ padding: "2px 8px"
3914
+ },
3915
+ disabled: providerLifecycleBusy !== null,
3916
+ onClick: () => {
3917
+ rollbackHarnessProvider(record, rollbackTransaction.transaction_id, rollbackTransaction.state);
3918
+ },
3919
+ children: providerLifecycleBusy === `rollback:${record.id}` ? copy.working : rollbackTransaction.state === "ROLLBACK_PENDING" ? copy.providerRollbackContinue : copy.providerRollback
3920
+ }),
3921
+ rollbackTransaction?.source === "provider-layer-migration" && rollbackTransaction.transaction_id && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3640
3922
  type: "button",
3641
3923
  style: {
3642
3924
  ...S.btn,
@@ -3644,13 +3926,51 @@ function WorkersPanel({ ctx }) {
3644
3926
  },
3645
3927
  disabled: providerLifecycleBusy !== null,
3646
3928
  onClick: () => {
3647
- rollbackHarnessProvider(record, rollbackTransaction.transaction_id);
3929
+ rollbackHarnessMigration(record, rollbackTransaction.transaction_id);
3648
3930
  },
3649
- children: providerLifecycleBusy === `rollback:${record.id}` ? copy.working : copy.providerRollback
3931
+ children: providerLifecycleBusy === `rollback-migrate:${record.id}` ? copy.working : copy.providerLayerRollback
3650
3932
  })
3651
3933
  ]
3652
3934
  }, record.id);
3653
3935
  }),
3936
+ (providerInventory?.recovery_transactions ?? []).filter((entry) => entry?.unresolved === true || !entry?.provider_id).map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3937
+ style: {
3938
+ display: "flex",
3939
+ alignItems: "center",
3940
+ gap: 8,
3941
+ fontSize: 12,
3942
+ flexWrap: "wrap",
3943
+ color: "#c98735",
3944
+ marginTop: 6
3945
+ },
3946
+ children: [
3947
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3948
+ style: { fontWeight: 600 },
3949
+ children: copy.recoveryUnresolved
3950
+ }),
3951
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3952
+ style: S.mono,
3953
+ children: entry.storage_id ?? entry.transaction_id ?? "unknown"
3954
+ }),
3955
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3956
+ style: { opacity: .7 },
3957
+ children: entry.phase
3958
+ }),
3959
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
3960
+ entry.action_id && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3961
+ type: "button",
3962
+ style: {
3963
+ ...S.btn,
3964
+ padding: "2px 8px"
3965
+ },
3966
+ disabled: providerLifecycleBusy !== null,
3967
+ onClick: () => {
3968
+ quarantineRecoveryTransaction(entry);
3969
+ },
3970
+ children: providerLifecycleBusy === `quarantine:${entry.action_id}` ? copy.working : copy.recoveryQuarantine
3971
+ })
3972
+ ]
3973
+ }, `recovery:${entry.storage_id ?? entry.transaction_id ?? "unknown"}`)),
3654
3974
  (credentialRefs.length > 0 || credentialUnverified.length > 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3655
3975
  style: {
3656
3976
  marginTop: 10,