@oh-my-pi/pi-coding-agent 15.9.1 → 15.9.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/dist/types/cli/dry-balance-cli.d.ts +104 -0
  3. package/dist/types/commands/dry-balance.d.ts +31 -0
  4. package/dist/types/config/model-registry.d.ts +2 -0
  5. package/dist/types/config/models-config-schema.d.ts +3 -0
  6. package/dist/types/config/settings.d.ts +11 -0
  7. package/dist/types/discovery/helpers.d.ts +1 -0
  8. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -3
  9. package/dist/types/hindsight/bank.d.ts +17 -9
  10. package/dist/types/hindsight/mental-models.d.ts +1 -1
  11. package/dist/types/hindsight/state.d.ts +9 -3
  12. package/dist/types/mcp/manager.d.ts +1 -1
  13. package/dist/types/modes/components/transcript-container.d.ts +3 -2
  14. package/dist/types/session/agent-session.d.ts +9 -0
  15. package/dist/types/session/auth-storage.d.ts +2 -2
  16. package/dist/types/task/types.d.ts +2 -0
  17. package/dist/types/tools/index.d.ts +16 -0
  18. package/dist/types/tools/path-utils.d.ts +11 -0
  19. package/package.json +9 -9
  20. package/src/cli/dry-balance-cli.ts +823 -0
  21. package/src/cli-commands.ts +1 -0
  22. package/src/commands/dry-balance.ts +43 -0
  23. package/src/config/model-registry.ts +6 -0
  24. package/src/config/models-config-schema.ts +2 -0
  25. package/src/config/settings.ts +38 -0
  26. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +1 -0
  27. package/src/discovery/github.ts +37 -1
  28. package/src/discovery/helpers.ts +3 -1
  29. package/src/extensibility/plugins/legacy-pi-compat.ts +245 -25
  30. package/src/hindsight/backend.ts +184 -35
  31. package/src/hindsight/bank.ts +32 -22
  32. package/src/hindsight/mental-models.ts +1 -1
  33. package/src/hindsight/state.ts +21 -7
  34. package/src/internal-urls/docs-index.generated.ts +4 -4
  35. package/src/internal-urls/omp-protocol.ts +8 -2
  36. package/src/mcp/manager.ts +40 -21
  37. package/src/modes/components/transcript-container.ts +14 -3
  38. package/src/modes/components/tree-selector.ts +29 -2
  39. package/src/modes/controllers/input-controller.ts +8 -2
  40. package/src/modes/setup-wizard/scenes/sign-in.ts +27 -7
  41. package/src/prompts/agents/explore.md +1 -0
  42. package/src/prompts/agents/librarian.md +1 -0
  43. package/src/prompts/dry-balance-bench.md +8 -0
  44. package/src/sdk.ts +82 -9
  45. package/src/session/agent-session.ts +66 -7
  46. package/src/session/auth-storage.ts +4 -0
  47. package/src/task/executor.ts +6 -2
  48. package/src/task/index.ts +8 -7
  49. package/src/task/types.ts +2 -0
  50. package/src/tools/bash.ts +3 -4
  51. package/src/tools/index.ts +16 -0
  52. package/src/tools/job.ts +3 -3
  53. package/src/tools/memory-reflect.ts +2 -2
  54. package/src/tools/path-utils.ts +21 -0
  55. package/src/tools/search.ts +18 -1
  56. package/src/utils/file-mentions.ts +7 -107
  57. package/src/utils/title-generator.ts +58 -37
@@ -9,10 +9,10 @@
9
9
 
10
10
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
11
11
  import { logger } from "@oh-my-pi/pi-utils";
12
- import type { Settings } from "../config/settings";
12
+ import { onHindsightScopeChanged, type Settings } from "../config/settings";
13
13
  import type { MemoryBackend, MemoryBackendStartOptions } from "../memory-backend/types";
14
14
  import type { AgentSession } from "../session/agent-session";
15
- import { computeBankScope } from "./bank";
15
+ import { type BankScope, computeBankScope } from "./bank";
16
16
  import { createHindsightClient } from "./client";
17
17
  import { isHindsightConfigured, loadHindsightConfig } from "./config";
18
18
  import type { HindsightMessage } from "./content";
@@ -60,12 +60,16 @@ export const hindsightBackend: MemoryBackend = {
60
60
  recallTagsMatch: parent.recallTagsMatch,
61
61
  config: parent.config,
62
62
  session,
63
- missionsSet: parent.missionsSet,
63
+ banksSet: parent.banksSet,
64
64
  lastRetainedTurn: 0,
65
65
  hasRecalledForFirstTurn: true,
66
66
  aliasOf: parent,
67
67
  }),
68
68
  );
69
+ // Aliases don't run auto-recall/auto-retain, so any pending retain
70
+ // queue belongs to the previous alias and is safe to drop after a
71
+ // best-effort flush (`flushRetainQueue` is no-op when empty).
72
+ await previous?.flushRetainQueue();
69
73
  previous?.dispose();
70
74
  return;
71
75
  }
@@ -76,38 +80,7 @@ export const hindsightBackend: MemoryBackend = {
76
80
  return;
77
81
  }
78
82
 
79
- const client = createHindsightClient(config);
80
- const scope = computeBankScope(config, session.sessionManager.getCwd());
81
-
82
- const state = new HindsightSessionState({
83
- sessionId,
84
- client,
85
- bankId: scope.bankId,
86
- retainTags: scope.retainTags,
87
- recallTags: scope.recallTags,
88
- recallTagsMatch: scope.recallTagsMatch,
89
- config,
90
- session,
91
- missionsSet: new Set(),
92
- lastRetainedTurn: 0,
93
- hasRecalledForFirstTurn: false,
94
- });
95
-
96
- // Cleanup any stale state for this session (defensive — prevents leaks
97
- // when a session is reused without going through dispose).
98
- const previous = session.setHindsightSessionState(state);
99
- previous?.dispose();
100
- state.attachSessionListeners();
101
-
102
- // Kick off mental-model bootstrap. Resolves asynchronously; the first
103
- // turn races and is covered in `beforeAgentStartPrompt` via
104
- // `mentalModelsLoadPromise`. Subsequent turns see the populated cache
105
- // because `runMentalModelLoad` calls `refreshBaseSystemPrompt`.
106
- if (config.mentalModelsEnabled) {
107
- state.mentalModelsLoadPromise = state.runMentalModelLoad(scope).catch(err => {
108
- logger.debug("Hindsight: mental-model bootstrap failed", { bankId: state.bankId, error: String(err) });
109
- });
110
- }
83
+ await installPrimaryState(session, settings, new Set());
111
84
  },
112
85
 
113
86
  async buildDeveloperInstructions(_agentDir, settings, session): Promise<string | undefined> {
@@ -173,6 +146,182 @@ export const hindsightBackend: MemoryBackend = {
173
146
  return await state.recallForCompaction(flat);
174
147
  },
175
148
  };
149
+ interface PrimaryRebuildTask {
150
+ pending: boolean;
151
+ }
152
+
153
+ const primaryRebuildTasks = new WeakMap<AgentSession, PrimaryRebuildTask>();
154
+
155
+ /**
156
+ * Coalesce and serialize live scope rebuilds for one session. Cwd reloads fire
157
+ * all settings hooks synchronously; running every callback immediately would
158
+ * let multiple rebuilds capture the same old state and leak the fresh states
159
+ * installed by earlier continuations.
160
+ */
161
+ function schedulePrimaryStateRebuild(session: AgentSession): void {
162
+ const task = primaryRebuildTasks.get(session);
163
+ if (task) {
164
+ task.pending = true;
165
+ return;
166
+ }
167
+
168
+ const nextTask: PrimaryRebuildTask = { pending: true };
169
+ primaryRebuildTasks.set(session, nextTask);
170
+ void Promise.resolve()
171
+ .then(async () => {
172
+ while (nextTask.pending) {
173
+ nextTask.pending = false;
174
+ try {
175
+ await rebuildPrimaryStateOnScopeChange(session);
176
+ } catch (err) {
177
+ logger.warn("Hindsight: scope rebuild failed", { error: String(err) });
178
+ }
179
+ }
180
+ })
181
+ .finally(() => {
182
+ if (primaryRebuildTasks.get(session) === nextTask) {
183
+ primaryRebuildTasks.delete(session);
184
+ }
185
+ });
186
+ }
187
+
188
+ /**
189
+ * Build (or rebuild) the primary `HindsightSessionState` for `session` from
190
+ * the current settings and install it. Disposes any previous primary state
191
+ * after flushing its retain queue so in-flight tool-initiated retains land in
192
+ * the bank that was selected when they were enqueued, not in the new bank.
193
+ *
194
+ * The created state takes ownership of the `onHindsightScopeChanged`
195
+ * subscription so subsequent `hindsight.bankId` / `bankIdPrefix` / `scoping`
196
+ * edits trigger another rebuild from the same wiring.
197
+ */
198
+ async function installPrimaryState(
199
+ session: AgentSession,
200
+ settings: Settings,
201
+ banksSet: Set<string>,
202
+ ): Promise<HindsightSessionState | undefined> {
203
+ const sessionId = session.sessionId;
204
+ if (!sessionId) return undefined;
205
+
206
+ const config = loadHindsightConfig(settings);
207
+ if (!isHindsightConfigured(config)) return undefined;
208
+
209
+ const client = createHindsightClient(config);
210
+ const scope = computeBankScope(config, session.sessionManager.getCwd());
211
+
212
+ // Cleanup any stale state for this session (defensive — prevents leaks
213
+ // when a session is reused without going through dispose). Flush the
214
+ // previous state's retain queue BEFORE clearing it, otherwise
215
+ // `HindsightRetainQueue.#doFlush` sees `session.getHindsightSessionState()
216
+ // !== state` and drops the batch. Re-read after the await so a concurrent
217
+ // owner cannot leave the actual current state undisposed.
218
+ let previous = session.getHindsightSessionState();
219
+ if (previous) {
220
+ await previous.flushRetainQueue();
221
+ }
222
+ const latest = session.getHindsightSessionState();
223
+ if (latest && latest !== previous) {
224
+ previous?.dispose();
225
+ previous = latest;
226
+ await previous.flushRetainQueue();
227
+ }
228
+
229
+ const state = new HindsightSessionState({
230
+ sessionId,
231
+ client,
232
+ bankId: scope.bankId,
233
+ retainTags: scope.retainTags,
234
+ recallTags: scope.recallTags,
235
+ recallTagsMatch: scope.recallTagsMatch,
236
+ config,
237
+ session,
238
+ banksSet,
239
+ lastRetainedTurn: 0,
240
+ hasRecalledForFirstTurn: false,
241
+ });
242
+
243
+ // Subscribe BEFORE installing: if the operator manages to flip another
244
+ // setting between install and subscribe, we'd miss the edge.
245
+ state.unsubscribeScope = onHindsightScopeChanged(() => {
246
+ schedulePrimaryStateRebuild(session);
247
+ });
248
+
249
+ const displaced = session.setHindsightSessionState(state);
250
+ if (displaced && displaced !== previous) {
251
+ await displaced.flushRetainQueue();
252
+ displaced.dispose();
253
+ }
254
+ previous?.dispose();
255
+ state.attachSessionListeners();
256
+
257
+ // Kick off mental-model bootstrap. Resolves asynchronously; the first
258
+ // turn races and is covered in `beforeAgentStartPrompt` via
259
+ // `mentalModelsLoadPromise`. Subsequent turns see the populated cache
260
+ // because `runMentalModelLoad` calls `refreshBaseSystemPrompt`.
261
+ if (config.mentalModelsEnabled) {
262
+ state.mentalModelsLoadPromise = state.runMentalModelLoad(scope).catch(err => {
263
+ logger.debug("Hindsight: mental-model bootstrap failed", { bankId: state.bankId, error: String(err) });
264
+ });
265
+ }
266
+
267
+ return state;
268
+ }
269
+
270
+ /**
271
+ * `onHindsightScopeChanged` handler: re-evaluate the bank scope from current
272
+ * settings and rebuild the primary state when it has actually drifted. No-op
273
+ * when the scope is unchanged or the session is no longer hosting a primary
274
+ * state (e.g. it was wiped to `undefined`, or this is a subagent alias).
275
+ */
276
+ async function rebuildPrimaryStateOnScopeChange(session: AgentSession): Promise<void> {
277
+ const current = session.getHindsightSessionState();
278
+ if (!current || current.aliasOf) return;
279
+
280
+ const settings = session.settings;
281
+ const config = loadHindsightConfig(settings);
282
+ if (!isHindsightConfigured(config)) {
283
+ // Hindsight effectively unwired mid-session. Flush before clearing so
284
+ // queued retains don't get dropped by `HindsightRetainQueue.#doFlush`.
285
+ await current.flushRetainQueue();
286
+ const previous = session.setHindsightSessionState(undefined);
287
+ previous?.dispose();
288
+ return;
289
+ }
290
+
291
+ const next = computeBankScope(config, session.sessionManager.getCwd());
292
+ if (bankScopesEqual(next, current)) return;
293
+
294
+ // Preserve the banksSet so we don't re-PUT banks we've already confirmed.
295
+ await installPrimaryState(session, settings, current.banksSet);
296
+ }
297
+
298
+ /** Tag-array equality: order matters because we never reorder on the way in. */
299
+ function stringArraysEqual(a: string[] | undefined, b: string[] | undefined): boolean {
300
+ if (a === b) return true;
301
+ if (!a || !b) return false;
302
+ if (a.length !== b.length) return false;
303
+ for (let i = 0; i < a.length; i++) {
304
+ if (a[i] !== b[i]) return false;
305
+ }
306
+ return true;
307
+ }
308
+
309
+ /**
310
+ * Structural compare of a freshly resolved `BankScope` against a live state's
311
+ * bank routing. Used by the scope-change handler to skip rebuilds that don't
312
+ * actually move the bank or its tag filters.
313
+ */
314
+ function bankScopesEqual(
315
+ scope: BankScope,
316
+ state: Pick<HindsightSessionState, "bankId" | "retainTags" | "recallTags" | "recallTagsMatch">,
317
+ ): boolean {
318
+ return (
319
+ scope.bankId === state.bankId &&
320
+ stringArraysEqual(scope.retainTags, state.retainTags) &&
321
+ stringArraysEqual(scope.recallTags, state.recallTags) &&
322
+ scope.recallTagsMatch === state.recallTagsMatch
323
+ );
324
+ }
176
325
 
177
326
  /** Reduce arbitrary AgentMessages into the Hindsight flat-text shape. */
178
327
  function flattenMessagesForRecall(messages: AgentMessage[]): HindsightMessage[] {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Bank ID derivation, project-tag scoping, and first-use mission setup.
2
+ * Bank ID derivation, project-tag scoping, and first-use bank setup.
3
3
  *
4
4
  * Three scoping modes (`HindsightConfig.scoping`):
5
5
  * - `global` — single shared bank, no per-project filter.
@@ -11,10 +11,13 @@
11
11
  * The base bank id is `bankIdPrefix-bankId` (default `omp`). Per-project mode
12
12
  * appends `-<project>`; tagged mode leaves the bank untouched and uses tags.
13
13
  *
14
- * Mission setup is idempotent at module level — a missionsSet keeps track of
15
- * banks we've already POSTed to so each session boundary doesn't fire a fresh
16
- * `createBank` call. Failures are swallowed: missions are an optimisation, not
17
- * a precondition for retain/recall.
14
+ * Bank existence is idempotent at module level — a banksSet keeps track of
15
+ * banks we've already PUT so each session boundary doesn't fire a fresh
16
+ * `createBank` call. The PUT is idempotent server-side, so re-firing on a hot
17
+ * path would only burn round-trips. Failures are swallowed: missing the
18
+ * mission patch is an optimisation, but the bank ITSELF must exist before
19
+ * mental-model bootstrap or the first retain, otherwise the very first POST
20
+ * lands against a missing bank.
18
21
  */
19
22
 
20
23
  import * as path from "node:path";
@@ -93,39 +96,46 @@ export function deriveBankId(config: HindsightConfig, directory: string): string
93
96
  }
94
97
 
95
98
  /**
96
- * Ensure a bank's reflect/retain mission is set, exactly once per process.
99
+ * Ensure a bank exists, and patch its reflect/retain mission on first use.
97
100
  *
98
- * Tracked via the supplied set; on overflow we drop the oldest half so the set
99
- * cannot grow unboundedly across long-lived processes.
101
+ * Idempotent: skips the PUT when the bank id is already in the supplied set.
102
+ * The mission body is optional — when `bankMission` is blank we still PUT to
103
+ * make sure the bank itself is created, so mental-model bootstrap and the
104
+ * first retain don't land against a non-existent bank.
105
+ *
106
+ * The set is capped; on overflow we drop the oldest half so it cannot grow
107
+ * unboundedly across long-lived processes.
100
108
  */
101
- export async function ensureBankMission(
109
+ export async function ensureBankExists(
102
110
  client: HindsightApi,
103
111
  bankId: string,
104
112
  config: HindsightConfig,
105
- missionsSet: Set<string>,
113
+ banksSet: Set<string>,
106
114
  ): Promise<void> {
115
+ if (banksSet.has(bankId)) return;
116
+
107
117
  const mission = config.bankMission?.trim();
108
- if (!mission) return;
109
- if (missionsSet.has(bankId)) return;
118
+ const retainMission = config.retainMission?.trim();
110
119
 
111
120
  try {
112
121
  await client.createBank(bankId, {
113
- reflectMission: mission,
114
- retainMission: config.retainMission?.trim() || undefined,
122
+ reflectMission: mission || undefined,
123
+ retainMission: retainMission || undefined,
115
124
  });
116
- missionsSet.add(bankId);
117
- if (missionsSet.size > MISSION_SET_CAP) {
118
- const keys = [...missionsSet].sort();
125
+ banksSet.add(bankId);
126
+ if (banksSet.size > MISSION_SET_CAP) {
127
+ const keys = [...banksSet].sort();
119
128
  for (const key of keys.slice(0, keys.length >> 1)) {
120
- missionsSet.delete(key);
129
+ banksSet.delete(key);
121
130
  }
122
131
  }
123
132
  if (config.debug) {
124
- logger.debug("Hindsight: set mission for bank", { bankId });
133
+ logger.debug("Hindsight: ensured bank", { bankId, mission: Boolean(mission) });
125
134
  }
126
135
  } catch (err) {
127
- // Mission set is best-effort; the bank may not exist yet, or the API may
128
- // reject the call. Either way, retain/recall still work, so swallow.
129
- logger.debug("Hindsight: ensureBankMission failed", { bankId, error: String(err) });
136
+ // Bank creation is best-effort; the server may already have it, or the
137
+ // API may reject the call. Either way, downstream retain/recall calls
138
+ // will surface a clearer error if the bank really is missing.
139
+ logger.debug("Hindsight: ensureBankExists failed", { bankId, error: String(err) });
130
140
  }
131
141
  }
@@ -112,7 +112,7 @@ function dedupe<T>(items: T[]): T[] {
112
112
  * Idempotently create any seed mental models that don't already exist on the
113
113
  * bank. Best-effort: a list/create failure does not throw — mental models are
114
114
  * an optimization, not a precondition for retain/recall, and we mirror the
115
- * swallow-on-failure pattern used by `ensureBankMission`.
115
+ * swallow-on-failure pattern used by `ensureBankExists`.
116
116
  *
117
117
  * Existing models are NEVER modified. See module docstring.
118
118
  */
@@ -1,6 +1,6 @@
1
1
  import { logger } from "@oh-my-pi/pi-utils";
2
2
  import type { AgentSession } from "../session/agent-session";
3
- import { type BankScope, ensureBankMission } from "./bank";
3
+ import { type BankScope, ensureBankExists } from "./bank";
4
4
  import type { HindsightApi, MemoryItemInput } from "./client";
5
5
  import type { HindsightConfig } from "./config";
6
6
  import {
@@ -45,12 +45,12 @@ export interface HindsightSessionStateOptions {
45
45
  recallTagsMatch?: "any" | "all" | "any_strict" | "all_strict";
46
46
  config: HindsightConfig;
47
47
  session: AgentSession;
48
- missionsSet: Set<string>;
48
+ banksSet: Set<string>;
49
49
  lastRetainedTurn?: number;
50
50
  hasRecalledForFirstTurn?: boolean;
51
51
  /**
52
52
  * When set, this entry is a subagent alias that reuses the parent's bank,
53
- * scope, config, client, and missionsSet. Aliases skip auto-recall and
53
+ * scope, config, client, and banksSet. Aliases skip auto-recall and
54
54
  * auto-retain — those run on the parent only — but the recall/retain/reflect
55
55
  * tools resolve via the alias so they persist to the same bank as the parent.
56
56
  */
@@ -148,7 +148,7 @@ export class HindsightRetainQueue {
148
148
  }
149
149
 
150
150
  try {
151
- await ensureBankMission(state.client, state.bankId, state.config, state.missionsSet);
151
+ await ensureBankExists(state.client, state.bankId, state.config, state.banksSet);
152
152
  const batch: MemoryItemInput[] = items.map(item => ({
153
153
  content: item.content,
154
154
  context: item.context ?? state.config.retainContext,
@@ -198,7 +198,7 @@ export class HindsightSessionState {
198
198
  recallTagsMatch?: "any" | "all" | "any_strict" | "all_strict";
199
199
  config: HindsightConfig;
200
200
  session: AgentSession;
201
- missionsSet: Set<string>;
201
+ banksSet: Set<string>;
202
202
  lastRetainedTurn: number;
203
203
  hasRecalledForFirstTurn: boolean;
204
204
  lastRecallSnippet?: string;
@@ -213,6 +213,12 @@ export class HindsightSessionState {
213
213
  */
214
214
  mentalModelsLoadPromise?: Promise<void>;
215
215
  unsubscribe?: () => void;
216
+ /**
217
+ * Releases the `onHindsightScopeChanged` subscription that drives live
218
+ * rebuilds when `hindsight.bankId` / `bankIdPrefix` / `scoping` change.
219
+ * Only set on primary states; aliases inherit the parent's subscription.
220
+ */
221
+ unsubscribeScope?: () => void;
216
222
  /** Alias states delegate persistence config to a primary parent state. */
217
223
  aliasOf?: HindsightSessionState;
218
224
  readonly retainQueue: HindsightRetainQueue;
@@ -226,7 +232,7 @@ export class HindsightSessionState {
226
232
  this.recallTagsMatch = options.recallTagsMatch;
227
233
  this.config = options.config;
228
234
  this.session = options.session;
229
- this.missionsSet = options.missionsSet;
235
+ this.banksSet = options.banksSet;
230
236
  this.lastRetainedTurn = options.lastRetainedTurn ?? 0;
231
237
  this.hasRecalledForFirstTurn = options.hasRecalledForFirstTurn ?? false;
232
238
  this.aliasOf = options.aliasOf;
@@ -291,7 +297,7 @@ export class HindsightSessionState {
291
297
  const { transcript } = prepareRetentionTranscript(target, true);
292
298
  if (!transcript) return;
293
299
 
294
- await ensureBankMission(this.client, this.bankId, this.config, this.missionsSet);
300
+ await ensureBankExists(this.client, this.bankId, this.config, this.banksSet);
295
301
  await this.client.retain(this.bankId, transcript, {
296
302
  documentId,
297
303
  context: this.config.retainContext,
@@ -398,6 +404,12 @@ export class HindsightSessionState {
398
404
  async runMentalModelLoad(scope: BankScope): Promise<void> {
399
405
  if (!this.config.mentalModelsEnabled) return;
400
406
 
407
+ // Create/ensure the bank BEFORE the first mental-model POST so we don't
408
+ // land `createMentalModel` against a bank the server has never seen —
409
+ // that surfaces as a FK / 404 on Hindsight's side. `ensureBankExists`
410
+ // is idempotent (PUT) and skips after the first call via `banksSet`.
411
+ await ensureBankExists(this.client, this.bankId, this.config, this.banksSet);
412
+
401
413
  // Seeding is opt-in (`hindsight.mentalModelAutoSeed`). Default behaviour is
402
414
  // read-only: we surface whatever models the operator has curated on the
403
415
  // bank, but we do NOT POST to create new ones unless they explicitly
@@ -456,6 +468,8 @@ export class HindsightSessionState {
456
468
  dispose(): void {
457
469
  this.unsubscribe?.();
458
470
  this.unsubscribe = undefined;
471
+ this.unsubscribeScope?.();
472
+ this.unsubscribeScope = undefined;
459
473
  this.retainQueue.dispose();
460
474
  }
461
475