@lesliechan721/aw 0.1.0-beta.27 → 0.1.0-beta.28

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.
@@ -436,6 +436,7 @@ export type OperationExecuteResult = {
436
436
  }>['action'];
437
437
  pluginId?: string;
438
438
  originIssue?: IssueOrigin;
439
+ portableIntent?: PortableResultIntentMetadata;
439
440
  returnRoute?: TuiRoute;
440
441
  operationRecordId?: string;
441
442
  diagnosticIds?: string[];
@@ -450,6 +451,7 @@ export type OperationExecuteResult = {
450
451
  }>['action'];
451
452
  pluginId?: string;
452
453
  originIssue?: IssueOrigin;
454
+ portableIntent?: PortableResultIntentMetadata;
453
455
  returnRoute?: TuiRoute;
454
456
  operationRecordId?: string;
455
457
  diagnosticIds?: string[];
@@ -463,6 +465,7 @@ export type OperationExecuteResult = {
463
465
  }>['action'];
464
466
  review: OperationReviewModel;
465
467
  originIssue?: IssueOrigin;
468
+ portableIntent?: PortableResultIntentMetadata;
466
469
  operationRecordId?: string;
467
470
  diagnosticIds?: string[];
468
471
  effects?: OperationEffectFacts;
@@ -478,6 +481,7 @@ export type OperationExecuteResult = {
478
481
  pluginId?: string;
479
482
  returnRoute?: TuiRoute;
480
483
  originIssue?: IssueOrigin;
484
+ portableIntent?: PortableResultIntentMetadata;
481
485
  writeMayHaveCompleted?: boolean;
482
486
  verification?: 'issueStillPresent' | 'verificationFailed';
483
487
  summary?: OperationResultSummary;
@@ -485,6 +489,10 @@ export type OperationExecuteResult = {
485
489
  diagnosticIds?: string[];
486
490
  effects?: OperationEffectFacts;
487
491
  };
492
+ export interface PortableResultIntentMetadata {
493
+ scope: Scope;
494
+ agentMode: 'automatic' | 'explicit';
495
+ }
488
496
  export interface OperationResultSummary {
489
497
  code: 'operationSucceeded' | 'operationPartial' | 'operationFailed';
490
498
  message: string;
@@ -536,6 +544,10 @@ export interface OperationResultSummary {
536
544
  selectedSkills: string[];
537
545
  agentMode: 'automatic' | 'explicit';
538
546
  requestedAgents: string[];
547
+ attemptedTargets?: Array<{
548
+ skill: string;
549
+ agent?: string;
550
+ }>;
539
551
  scope: Scope;
540
552
  materializationDigest: string;
541
553
  skillsCliVersion: string;
@@ -603,6 +615,20 @@ export interface PluginOperationProgressLine {
603
615
  }
604
616
  export interface TuiStatusLoadResult {
605
617
  projectRoot?: string;
618
+ projectIntent?: {
619
+ state: 'ready';
620
+ roots: Array<{
621
+ pluginId: string;
622
+ platforms: PlatformName[];
623
+ }>;
624
+ } | {
625
+ state: 'absent';
626
+ } | {
627
+ state: 'invalid' | 'unavailable';
628
+ code: string;
629
+ message: string;
630
+ };
631
+ /** Compatibility input for older injected status fixtures. Production uses projectIntent. */
606
632
  projectIntentRoots?: Array<{
607
633
  pluginId: string;
608
634
  platforms: PlatformName[];
@@ -742,6 +768,11 @@ export interface TuiControllerSnapshot {
742
768
  operationRecordId?: string;
743
769
  };
744
770
  projectIntentReminder?: ProjectIntentReminder;
771
+ projectIntentIssue?: {
772
+ state: 'invalid' | 'unavailable';
773
+ code: string;
774
+ message: string;
775
+ };
745
776
  }
746
777
  export declare class TuiOperationBlockedError extends Error {
747
778
  readonly code: string;
@@ -90,10 +90,13 @@ function projectReminderTargetKey(target) {
90
90
  return [target.pluginId, target.platform, target.scope].join('\0');
91
91
  }
92
92
  export function projectCurrentIntentReminder(status) {
93
+ if (status.projectIntent && status.projectIntent.state !== 'ready')
94
+ return undefined;
93
95
  const actionableTargets = [];
94
96
  const blockedTargets = [];
95
97
  const unconfirmedTargets = [];
96
- const projectRoots = new Map((status.projectIntentRoots ?? []).map((root) => [
98
+ const projectRoots = new Map((status.projectIntent?.state === 'ready'
99
+ ? status.projectIntent.roots : status.projectIntentRoots ?? []).map((root) => [
97
100
  root.pluginId,
98
101
  new Set(root.platforms),
99
102
  ]));
@@ -724,6 +727,8 @@ export class TuiController {
724
727
  #portableResultSkills;
725
728
  #portableResultReceipt;
726
729
  #portableResultIntent;
730
+ #portableResultLifecycle = 0;
731
+ #portableResultRecheck;
727
732
  #completionNotice;
728
733
  #projectIntentReminder;
729
734
  #loadError;
@@ -839,6 +844,10 @@ export class TuiController {
839
844
  ...(this.#projectIntentReminder
840
845
  ? { projectIntentReminder: this.#projectIntentReminder }
841
846
  : {}),
847
+ ...(this.#status?.projectIntent?.state === 'invalid'
848
+ || this.#status?.projectIntent?.state === 'unavailable'
849
+ ? { projectIntentIssue: this.#status.projectIntent }
850
+ : {}),
842
851
  });
843
852
  }
844
853
  subscribe(listener) {
@@ -921,6 +930,7 @@ export class TuiController {
921
930
  }
922
931
  beginRetirement() {
923
932
  this.#retiring = true;
933
+ this.#invalidatePortableResultRecheck();
924
934
  }
925
935
  async waitForWrites() {
926
936
  this.beginRetirement();
@@ -952,6 +962,7 @@ export class TuiController {
952
962
  goHome(options = {}) {
953
963
  if (this.hasSessionDraft && !options.discardDrafts)
954
964
  return false;
965
+ this.#invalidatePortableResultRecheck();
955
966
  this.#cancelReviewPreparation();
956
967
  this.#cancelPortableRequest();
957
968
  this.#cancelInstallPlacementRequest();
@@ -1330,6 +1341,7 @@ export class TuiController {
1330
1341
  return { name: 'sourceList' };
1331
1342
  }
1332
1343
  #showResult(result, intent) {
1344
+ this.#invalidatePortableResultRecheck();
1333
1345
  const terminal = { name: 'result', operationId: result.operationId };
1334
1346
  const target = this.#stableResultRoute(intent, result);
1335
1347
  this.#resultAnchor = structuredClone(target);
@@ -1521,6 +1533,9 @@ export class TuiController {
1521
1533
  this.#publish();
1522
1534
  }
1523
1535
  push(route, context) {
1536
+ if (this.#router.snapshot.current.route.name === 'result') {
1537
+ this.#invalidatePortableResultRecheck();
1538
+ }
1524
1539
  this.#cancelReviewPreparation();
1525
1540
  this.#cancelPortableRequest();
1526
1541
  this.#cancelInstallPlacementRequest();
@@ -1709,26 +1724,86 @@ export class TuiController {
1709
1724
  || !this.dependencies.recheckPortableInstall) {
1710
1725
  throw new TuiOperationBlockedError('portableRecheckUnavailable', '当前结果不能执行只读复查。');
1711
1726
  }
1712
- const checked = await this.#trackRead(this.dependencies.recheckPortableInstall(structuredClone(this.#portableResultReceipt), this.#abort.signal));
1713
- this.#portableResultReceipt = structuredClone(checked);
1714
- const summary = operationResultSummary(this.#portableResultIntent, checked);
1715
- const previous = this.#result;
1716
- this.#result = {
1717
- state: checked.status === 'succeeded' ? 'succeeded' : 'partial',
1718
- operationId: previous.operationId,
1719
- summary: {
1727
+ const currentResult = this.#result;
1728
+ if (currentResult.state !== 'succeeded' && currentResult.state !== 'partial') {
1729
+ throw new TuiOperationBlockedError('portableRecheckUnavailable', '当前结果不能执行只读复查。');
1730
+ }
1731
+ const previous = structuredClone(currentResult);
1732
+ const originalReceipt = structuredClone(this.#portableResultReceipt);
1733
+ const originalIntent = structuredClone(this.#portableResultIntent);
1734
+ const lifecycle = this.#portableResultLifecycle;
1735
+ if (this.#portableResultRecheck?.lifecycle === lifecycle
1736
+ && this.#portableResultRecheck.operationId === previous.operationId) {
1737
+ return await this.#portableResultRecheck.promise;
1738
+ }
1739
+ const abort = new AbortController();
1740
+ const signal = AbortSignal.any([this.#abort.signal, abort.signal]);
1741
+ const isCurrent = () => !this.#disposed
1742
+ && !this.#retiring
1743
+ && !signal.aborted
1744
+ && lifecycle === this.#portableResultLifecycle
1745
+ && this.#router.snapshot.current.route.name === 'result'
1746
+ && this.#result?.operationId === previous.operationId
1747
+ && this.#result.operationRecordId === previous.operationRecordId;
1748
+ const promise = (async () => {
1749
+ let checked;
1750
+ try {
1751
+ checked = await this.#trackRead(this.dependencies.recheckPortableInstall(structuredClone(originalReceipt), signal));
1752
+ }
1753
+ catch (error) {
1754
+ if (!isCurrent())
1755
+ return cloneFrozen(originalReceipt);
1756
+ throw error;
1757
+ }
1758
+ if (!isCurrent())
1759
+ return cloneFrozen(originalReceipt);
1760
+ this.#portableResultReceipt = structuredClone(checked);
1761
+ const summary = operationResultSummary(originalIntent, checked);
1762
+ const verificationState = summary.portable?.verificationUnknown
1763
+ ? 'unknown' : 'completed';
1764
+ const effects = previous.effects
1765
+ ? { ...previous.effects, finalVerification: verificationState }
1766
+ : undefined;
1767
+ const updatedSummary = {
1720
1768
  ...summary,
1721
- elapsedMs: 'summary' in previous && previous.summary?.elapsedMs !== undefined
1722
- ? previous.summary.elapsedMs : summary.elapsedMs,
1723
- },
1724
- operationKind: 'portableInstall',
1725
- pluginId: this.#portableResultIntent.pluginId,
1726
- returnRoute: 'returnRoute' in previous && previous.returnRoute
1727
- ? structuredClone(previous.returnRoute)
1728
- : { name: 'pluginDetail', pluginId: this.#portableResultIntent.pluginId },
1769
+ ...(previous.summary.elapsedMs !== undefined
1770
+ ? { elapsedMs: previous.summary.elapsedMs } : {}),
1771
+ ...(previous.summary.diagnosticIds
1772
+ ? { diagnosticIds: structuredClone(previous.summary.diagnosticIds) } : {}),
1773
+ ...(effects ? { effects } : {}),
1774
+ };
1775
+ const updated = checked.status === 'succeeded'
1776
+ ? { ...previous, state: 'succeeded', summary: updatedSummary, ...(effects ? { effects } : {}) }
1777
+ : { ...previous, state: 'partial', summary: updatedSummary, ...(effects ? { effects } : {}) };
1778
+ this.#result = updated;
1779
+ if (updated.operationRecordId) {
1780
+ const record = this.#operationHistory.get(updated.operationRecordId);
1781
+ if (record) {
1782
+ record.summary = {
1783
+ ...record.summary,
1784
+ state: updated.state,
1785
+ code: updated.summary.code,
1786
+ message: updated.summary.message,
1787
+ effects: effects ?? record.summary.effects,
1788
+ };
1789
+ this.#operationHistory.record(record);
1790
+ }
1791
+ }
1792
+ this.#publish();
1793
+ return cloneFrozen(checked);
1794
+ })();
1795
+ this.#portableResultRecheck = {
1796
+ lifecycle,
1797
+ operationId: previous.operationId,
1798
+ abort,
1799
+ promise,
1729
1800
  };
1730
- this.#publish();
1731
- return cloneFrozen(checked);
1801
+ void promise.finally(() => {
1802
+ if (this.#portableResultRecheck?.promise === promise) {
1803
+ this.#portableResultRecheck = undefined;
1804
+ }
1805
+ }).catch(() => undefined);
1806
+ return await promise;
1732
1807
  }
1733
1808
  startPortableRetryReviewPreparation() {
1734
1809
  this.#assertAcceptingActions();
@@ -1763,6 +1838,9 @@ export class TuiController {
1763
1838
  this.#publish();
1764
1839
  }
1765
1840
  replace(route, context) {
1841
+ if (this.#router.snapshot.current.route.name === 'result') {
1842
+ this.#invalidatePortableResultRecheck();
1843
+ }
1766
1844
  this.#cancelReviewPreparation();
1767
1845
  this.#cancelPortableRequest();
1768
1846
  this.#cancelInstallPlacementRequest();
@@ -1882,6 +1960,8 @@ export class TuiController {
1882
1960
  }
1883
1961
  back() {
1884
1962
  const route = this.#router.snapshot.current.route;
1963
+ if (route.name === 'result')
1964
+ this.#invalidatePortableResultRecheck();
1885
1965
  const activeFlow = this.#router.activeFlow();
1886
1966
  this.#cancelReviewPreparation();
1887
1967
  this.#cancelPortableRequest();
@@ -1925,6 +2005,7 @@ export class TuiController {
1925
2005
  leaveResult(target) {
1926
2006
  if (this.#router.snapshot.current.route.name !== 'result')
1927
2007
  return;
2008
+ this.#invalidatePortableResultRecheck();
1928
2009
  if (target?.name === 'home') {
1929
2010
  this.#router.resetToRoot();
1930
2011
  this.#portableDraft = undefined;
@@ -1973,6 +2054,7 @@ export class TuiController {
1973
2054
  if (this.#router.snapshot.current.route.name !== 'result' || !result
1974
2055
  || !('returnRoute' in result) || !result.returnRoute)
1975
2056
  return;
2057
+ this.#invalidatePortableResultRecheck();
1976
2058
  const returnRoute = this.#normalizeRoute(result.returnRoute);
1977
2059
  this.#router.back();
1978
2060
  const kind = result.operationKind === 'configSave' || result.operationKind === 'configSaveBatch'
@@ -2024,6 +2106,7 @@ export class TuiController {
2024
2106
  }
2025
2107
  }
2026
2108
  resetPortableFlowToPreview(pluginId, target, preserveDraft = false) {
2109
+ this.#invalidatePortableResultRecheck();
2027
2110
  this.#cancelPortableRequest();
2028
2111
  if (preserveDraft && !this.#portableDraft) {
2029
2112
  this.#portableDraft = this.#recoverablePortableDraft;
@@ -2055,6 +2138,11 @@ export class TuiController {
2055
2138
  this.#portableRequestAbort = undefined;
2056
2139
  this.#portablePending = undefined;
2057
2140
  }
2141
+ #invalidatePortableResultRecheck() {
2142
+ this.#portableResultLifecycle += 1;
2143
+ this.#portableResultRecheck?.abort.abort();
2144
+ this.#portableResultRecheck = undefined;
2145
+ }
2058
2146
  #runPortableRequest(key, action) {
2059
2147
  if (this.#portablePending?.key === key) {
2060
2148
  return this.#portablePending.promise;
@@ -2766,6 +2854,8 @@ export class TuiController {
2766
2854
  this.#ensureOperationFlow(intent);
2767
2855
  const inheritedReturnRoute = this.#reviewPreparation?.returnRoute;
2768
2856
  const current = this.#router.snapshot.current.route;
2857
+ if (current.name === 'result')
2858
+ this.#invalidatePortableResultRecheck();
2769
2859
  const navigation = current.name === 'result' || current.name === 'reviewPreparing'
2770
2860
  ? 'replace' : 'push';
2771
2861
  const resolvedReturnRoute = returnRoute
@@ -3836,6 +3926,12 @@ export class TuiController {
3836
3926
  operationKind: intent.kind,
3837
3927
  ...(intent.kind === 'sourceMutation' ? { sourceAction: intent.action } : {}),
3838
3928
  ...('pluginId' in intent ? { pluginId: intent.pluginId } : {}),
3929
+ ...(intent.kind === 'portableInstall' ? {
3930
+ portableIntent: {
3931
+ scope: intent.scope,
3932
+ agentMode: intent.requestedAgents === undefined ? 'automatic' : 'explicit',
3933
+ },
3934
+ } : {}),
3839
3935
  ...(intent.kind === 'pluginWrite' && intent.originIssue
3840
3936
  ? { originIssue: intent.originIssue }
3841
3937
  : {}),
@@ -3845,6 +3941,7 @@ export class TuiController {
3845
3941
  if (this.#disposed)
3846
3942
  return;
3847
3943
  this.#disposed = true;
3944
+ this.#invalidatePortableResultRecheck();
3848
3945
  this.#cancelReviewPreparation();
3849
3946
  this.#cancelPortableRequest();
3850
3947
  this.#cancelInstallPlacementRequest();
@@ -3938,6 +4035,9 @@ function operationResultSummaryUnsafe(intent, execution) {
3938
4035
  selectedSkills: receipt.selectedSkills.map((skill) => skill.name),
3939
4036
  agentMode: receipt.agentMode,
3940
4037
  requestedAgents: receipt.requestedAgents,
4038
+ ...(receipt.attemptedTargets
4039
+ ? { attemptedTargets: structuredClone(receipt.attemptedTargets) }
4040
+ : {}),
3941
4041
  scope: receipt.scope,
3942
4042
  materializationDigest: receipt.materializationDigest,
3943
4043
  skillsCliVersion: receipt.skillsCliVersion,