@akagilnc/pi-workflow-roles 0.1.4659 → 0.1.4673

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.
@@ -1778,10 +1778,10 @@ __export(session_assistant_usage_exports, {
1778
1778
  });
1779
1779
  import { join as join6 } from "node:path";
1780
1780
  async function readAssistantUsageFromSessionFile(sessionFile) {
1781
- const { readFile: readFile24 } = await import("node:fs/promises");
1781
+ const { readFile: readFile25 } = await import("node:fs/promises");
1782
1782
  let text;
1783
1783
  try {
1784
- text = await readFile24(sessionFile, "utf8");
1784
+ text = await readFile25(sessionFile, "utf8");
1785
1785
  } catch (error) {
1786
1786
  if (error?.code === "ENOENT") return void 0;
1787
1787
  throw error;
@@ -11423,10 +11423,10 @@ function pairGateRounds(volumes) {
11423
11423
  return rounds.sort((a, b) => a.officerStartedAt.localeCompare(b.officerStartedAt)).map((round, index) => ({ ...round, roundIndex: index + 1 }));
11424
11424
  }
11425
11425
  async function resolveOfficerSessionFromPointerFile(pointerPath) {
11426
- const { readFile: readFile24 } = await import("node:fs/promises");
11426
+ const { readFile: readFile25 } = await import("node:fs/promises");
11427
11427
  let raw;
11428
11428
  try {
11429
- raw = JSON.parse(await readFile24(pointerPath, "utf8"));
11429
+ raw = JSON.parse(await readFile25(pointerPath, "utf8"));
11430
11430
  } catch (error) {
11431
11431
  throw new Error(
11432
11432
  `direct officer run pointer unreadable in ${pointerPath}: ${error instanceof Error ? error.message : String(error)}`,
@@ -22739,7 +22739,6 @@ function createNativeNavigatorSessionFactory(deps) {
22739
22739
  let providerFailure;
22740
22740
  let noReceipt;
22741
22741
  let disposed = false;
22742
- let inFlightPrompt;
22743
22742
  let hostRunId = readNavigatorHostRunPointer(sessionManager.getEntries());
22744
22743
  const summon = deps?.summonPublicRole ?? (async (options) => {
22745
22744
  const { summonPublicRole: summonPublicRole2 } = await Promise.resolve().then(() => (init_public_role_summons(), public_role_summons_exports));
@@ -22752,85 +22751,87 @@ function createNativeNavigatorSessionFactory(deps) {
22752
22751
  }
22753
22752
  providerFailure = void 0;
22754
22753
  noReceipt = void 0;
22755
- const run = (async () => {
22756
- try {
22757
- const summonHome = await resolveNavigatorLedgerHome(context);
22758
- const resumeRunId = hostRunId;
22759
- const baseSummon = {
22760
- role: "navigator",
22761
- argv: [text],
22762
- cwd: context.cwd,
22763
- ...summonHome === void 0 ? {} : { home: summonHome }
22764
- };
22765
- const resumable = deps?.hostRunResumable ?? navigatorHostRunResumable;
22766
- let summoned;
22767
- if (resumeRunId === void 0 || summonHome === void 0) {
22768
- summoned = await summon(baseSummon);
22769
- } else if (await resumable(summonHome, resumeRunId)) {
22754
+ try {
22755
+ const summonHome = await resolveNavigatorLedgerHome(context);
22756
+ if (disposed) return;
22757
+ const resumeRunId = hostRunId;
22758
+ const baseSummon = {
22759
+ role: "navigator",
22760
+ argv: [text],
22761
+ cwd: context.cwd,
22762
+ ...summonHome === void 0 ? {} : { home: summonHome },
22763
+ ...context.signal === void 0 ? {} : { signal: context.signal }
22764
+ };
22765
+ const resumable = deps?.hostRunResumable ?? navigatorHostRunResumable;
22766
+ let summoned;
22767
+ if (resumeRunId === void 0 || summonHome === void 0) {
22768
+ if (disposed) return;
22769
+ summoned = await summon(baseSummon);
22770
+ } else {
22771
+ const canResume = await resumable(summonHome, resumeRunId);
22772
+ if (disposed) return;
22773
+ if (canResume) {
22770
22774
  summoned = await summon({ ...baseSummon, resumeRunId });
22771
22775
  } else {
22772
22776
  hostRunId = void 0;
22773
22777
  summoned = await summon(baseSummon);
22774
22778
  }
22775
- const outcome = summoned.terminal?.roleOutcome;
22776
- if (outcome === void 0) {
22777
- const detail = summoned.stderr?.trim() || `exit ${summoned.exitCode}`;
22778
- providerFailure = { source: "transport", cause: "unknown" };
22779
- throw navigatorUnavailableError(
22780
- providerFailure.source,
22781
- new Error(`Navigator public summon produced no terminal (${detail})`),
22782
- providerFailure.cause
22783
- );
22784
- }
22785
- if (outcome.kind === "failure") {
22786
- providerFailure = navigatorProviderFailureFromPublicTerminal(outcome);
22787
- throw navigatorUnavailableError(
22788
- providerFailure.source,
22789
- new Error(outcome.diagnostic),
22790
- providerFailure.cause
22791
- );
22792
- }
22793
- if (outcome.kind === "no_receipt") {
22794
- noReceipt = outcome;
22795
- return;
22796
- }
22797
- const runDirectory = summoned.runDirectory ?? (typeof summoned.admitted?.runDirectory === "string" ? summoned.admitted.runDirectory : void 0);
22798
- if (typeof runDirectory === "string" && runDirectory.trim() !== "") {
22799
- const nextRunId = runIdFromNavigatorDirectory(runDirectory);
22800
- if (nextRunId !== void 0) {
22801
- hostRunId = nextRunId;
22802
- sessionManager.appendCustomEntry(NAVIGATOR_HOST_RUN_POINTER_ENTRY, { runId: nextRunId });
22803
- }
22804
- }
22805
- if (outcome.kind !== "accepted") {
22806
- return;
22807
- }
22808
- const { navigatorProseFromUnknown: navigatorProseFromUnknown2 } = await Promise.resolve().then(() => (init_navigator_output(), navigator_output_exports));
22809
- const proseParts = [];
22810
- for (const payload of outcome.payloads ?? []) {
22811
- const prose = navigatorProseFromUnknown2(payload);
22812
- if (prose !== void 0) proseParts.push(prose);
22813
- }
22814
- if (proseParts.length === 0) return;
22815
- await tool.execute(
22816
- "navigator-public-prepare",
22817
- { prose: proseParts.join("\n\n") },
22818
- void 0,
22819
- void 0,
22820
- context
22779
+ }
22780
+ if (disposed) return;
22781
+ const outcome = summoned.terminal?.roleOutcome;
22782
+ if (outcome === void 0) {
22783
+ const detail = summoned.stderr?.trim() || `exit ${summoned.exitCode}`;
22784
+ providerFailure = { source: "transport", cause: "unknown" };
22785
+ throw navigatorUnavailableError(
22786
+ providerFailure.source,
22787
+ new Error(`Navigator public summon produced no terminal (${detail})`),
22788
+ providerFailure.cause
22821
22789
  );
22822
- } catch (error) {
22823
- if (error instanceof NavigatorUnavailableError) throw error;
22824
- const fact = navigatorProviderFailureFromError(error);
22825
- providerFailure = fact ?? { source: "transport", cause: "unknown" };
22826
- throw navigatorUnavailableError(providerFailure.source, error, providerFailure.cause);
22827
22790
  }
22828
- })();
22829
- inFlightPrompt = run;
22830
- try {
22831
- await run;
22832
- } finally {
22833
- if (inFlightPrompt === run) inFlightPrompt = void 0;
22791
+ if (outcome.kind === "failure") {
22792
+ providerFailure = navigatorProviderFailureFromPublicTerminal(outcome);
22793
+ throw navigatorUnavailableError(
22794
+ providerFailure.source,
22795
+ new Error(outcome.diagnostic),
22796
+ providerFailure.cause
22797
+ );
22798
+ }
22799
+ if (outcome.kind === "no_receipt") {
22800
+ noReceipt = outcome;
22801
+ return;
22802
+ }
22803
+ const runDirectory = summoned.runDirectory ?? (typeof summoned.admitted?.runDirectory === "string" ? summoned.admitted.runDirectory : void 0);
22804
+ if (typeof runDirectory === "string" && runDirectory.trim() !== "") {
22805
+ const nextRunId = runIdFromNavigatorDirectory(runDirectory);
22806
+ if (nextRunId !== void 0) {
22807
+ hostRunId = nextRunId;
22808
+ sessionManager.appendCustomEntry(NAVIGATOR_HOST_RUN_POINTER_ENTRY, { runId: nextRunId });
22809
+ }
22810
+ }
22811
+ if (outcome.kind !== "accepted") {
22812
+ return;
22813
+ }
22814
+ const { navigatorProseFromUnknown: navigatorProseFromUnknown2 } = await Promise.resolve().then(() => (init_navigator_output(), navigator_output_exports));
22815
+ const proseParts = [];
22816
+ for (const payload of outcome.payloads ?? []) {
22817
+ const prose = navigatorProseFromUnknown2(payload);
22818
+ if (prose !== void 0) proseParts.push(prose);
22819
+ }
22820
+ if (proseParts.length === 0) return;
22821
+ if (disposed) return;
22822
+ await tool.execute(
22823
+ "navigator-public-prepare",
22824
+ { prose: proseParts.join("\n\n") },
22825
+ void 0,
22826
+ void 0,
22827
+ context
22828
+ );
22829
+ } catch (error) {
22830
+ if (disposed) return;
22831
+ if (error instanceof NavigatorUnavailableError) throw error;
22832
+ const fact = navigatorProviderFailureFromError(error);
22833
+ providerFailure = fact ?? { source: "transport", cause: "unknown" };
22834
+ throw navigatorUnavailableError(providerFailure.source, error, providerFailure.cause);
22834
22835
  }
22835
22836
  },
22836
22837
  providerFailure: () => providerFailure,
@@ -22863,8 +22864,6 @@ function createNativeNavigatorSessionFactory(deps) {
22863
22864
  recordPointer: () => sessionManager.getSessionDir(),
22864
22865
  dispose: async () => {
22865
22866
  disposed = true;
22866
- const pending = inFlightPrompt;
22867
- if (pending !== void 0) await pending.catch(() => void 0);
22868
22867
  }
22869
22868
  };
22870
22869
  };
@@ -22976,7 +22975,14 @@ function createNavigatorAttendance(options) {
22976
22975
  let preparationFailure;
22977
22976
  let routePlaybookReadFailure;
22978
22977
  let disposed = false;
22978
+ let closing;
22979
+ const nestCancel = new AbortController();
22979
22980
  let warmedHelp;
22981
+ const sessionHostContext = () => {
22982
+ const parentSignal = options.context.signal;
22983
+ const signal = parentSignal === void 0 ? nestCancel.signal : AbortSignal.any([nestCancel.signal, parentSignal]);
22984
+ return { ...options.context, signal };
22985
+ };
22980
22986
  const loadLiveHelp = async () => {
22981
22987
  try {
22982
22988
  return await Promise.all(
@@ -23066,7 +23072,7 @@ ${text}
23066
23072
  let created;
23067
23073
  try {
23068
23074
  created = await options.createSession({
23069
- context: options.context,
23075
+ context: sessionHostContext(),
23070
23076
  subject: subjectKey,
23071
23077
  ...options.modelSettingPath === void 0 ? {} : { modelSettingPath: options.modelSettingPath },
23072
23078
  tool
@@ -23263,17 +23269,17 @@ ${helpContext}
23263
23269
  };
23264
23270
  return {
23265
23271
  setWorkContext(next) {
23266
- let closing;
23272
+ let closing2;
23267
23273
  if (next.subjectKey !== subjectKey && session !== void 0) {
23268
23274
  const previous = session;
23269
23275
  session = void 0;
23270
- closing = previous.dispose();
23276
+ closing2 = previous.dispose();
23271
23277
  }
23272
23278
  subjectKey = next.subjectKey;
23273
23279
  subject = next.subject;
23274
23280
  authority = next.authority;
23275
23281
  contextError = next.contextError;
23276
- return closing;
23282
+ return closing2;
23277
23283
  },
23278
23284
  /**
23279
23285
  * Start live-help subprocesses during activation without beginning full
@@ -23311,10 +23317,19 @@ ${helpContext}
23311
23317
  },
23312
23318
  dispose() {
23313
23319
  disposed = true;
23314
- const current = session;
23315
- session = void 0;
23320
+ if (!nestCancel.signal.aborted) {
23321
+ nestCancel.abort(navigatorUnavailableError(
23322
+ "session",
23323
+ new Error("Navigator attendance was disposed")
23324
+ ));
23325
+ }
23316
23326
  activeInvocationId = void 0;
23317
- return current?.dispose();
23327
+ if (closing === void 0) {
23328
+ const current = session;
23329
+ session = void 0;
23330
+ closing = Promise.resolve(current?.dispose()).then(() => void 0);
23331
+ }
23332
+ return closing;
23318
23333
  }
23319
23334
  };
23320
23335
  async function settleOnce(settlement) {
@@ -26903,6 +26918,38 @@ function createRoleRuntimeExtension(dependencies) {
26903
26918
  let noReceiptRecorded = false;
26904
26919
  let priorFetch;
26905
26920
  let fetchWrapped = false;
26921
+ const disposeNavigatorAttendanceNonBlocking = (attendance) => {
26922
+ if (attendance === void 0) return;
26923
+ const recordDisposeFailure = (error) => {
26924
+ const diagnostic = error instanceof Error ? error.message : String(error);
26925
+ try {
26926
+ sitianReport({
26927
+ level: "event",
26928
+ kind: "navigator-dispose-failure",
26929
+ cwd: navigatorCwd,
26930
+ sessionParent: navigatorSessionParent,
26931
+ payload: { diagnostic },
26932
+ source: "role-runtime"
26933
+ });
26934
+ } catch (recordError) {
26935
+ try {
26936
+ envelopeHost.appendEntry?.("ak-navigator-dispose-failure", {
26937
+ diagnostic,
26938
+ recordFailure: recordError instanceof Error ? recordError.message : String(recordError)
26939
+ });
26940
+ } catch {
26941
+ }
26942
+ }
26943
+ };
26944
+ let pending;
26945
+ try {
26946
+ pending = attendance.dispose();
26947
+ } catch (error) {
26948
+ recordDisposeFailure(error);
26949
+ return;
26950
+ }
26951
+ void Promise.resolve(pending).then(void 0, recordDisposeFailure);
26952
+ };
26906
26953
  const settleNavigatorProjection = async (settlement) => {
26907
26954
  const attendance = navigatorAttendance;
26908
26955
  if (settlement === void 0 || attendance === void 0) return;
@@ -26935,27 +26982,7 @@ function createRoleRuntimeExtension(dependencies) {
26935
26982
  };
26936
26983
  pendingNavigatorPresentation = { event, report };
26937
26984
  }
26938
- void Promise.resolve(attendance.dispose()).then(
26939
- void 0,
26940
- (error) => {
26941
- const diagnostic = error instanceof Error ? error.message : String(error);
26942
- try {
26943
- sitianReport({
26944
- level: "event",
26945
- kind: "navigator-dispose-failure",
26946
- cwd: navigatorCwd,
26947
- sessionParent: navigatorSessionParent,
26948
- payload: { diagnostic },
26949
- source: "role-runtime"
26950
- });
26951
- } catch (recordError) {
26952
- envelopeHost.appendEntry?.("ak-navigator-dispose-failure", {
26953
- diagnostic,
26954
- recordFailure: recordError instanceof Error ? recordError.message : String(recordError)
26955
- });
26956
- }
26957
- }
26958
- );
26985
+ disposeNavigatorAttendanceNonBlocking(attendance);
26959
26986
  })();
26960
26987
  pendingNavigatorSettlement = pending;
26961
26988
  await pending;
@@ -27237,9 +27264,10 @@ function createRoleRuntimeExtension(dependencies) {
27237
27264
  } catch {
27238
27265
  }
27239
27266
  }
27240
- await navigatorAttendance?.dispose();
27267
+ const attendanceToDispose = navigatorAttendance;
27241
27268
  navigatorAttendance = void 0;
27242
27269
  pendingNavigatorSettlement = void 0;
27270
+ disposeNavigatorAttendanceNonBlocking(attendanceToDispose);
27243
27271
  pendingInfrastructureFailures.clear();
27244
27272
  pendingSubmissionNonPassByToolCallId.clear();
27245
27273
  observationFace.reset();
@@ -27910,7 +27938,7 @@ async function requireGatekeeperPass(options) {
27910
27938
  }
27911
27939
 
27912
27940
  // src/host-native-method.ts
27913
- import { lstat as lstat7, mkdir as mkdir6, readlink, realpath as realpath8, symlink } from "node:fs/promises";
27941
+ import { access as access4, lstat as lstat7, mkdir as mkdir6, readdir as readdir9, readFile as readFile24, readlink, realpath as realpath8, symlink } from "node:fs/promises";
27914
27942
  import { basename as basename10, dirname as dirname22, join as join41 } from "node:path";
27915
27943
  var HOST_METHOD_PLUGIN_NAME = "ak-methods";
27916
27944
  var packagedMethodsDir = (root) => join41(root, "resources", "methods");
@@ -27942,25 +27970,103 @@ function applyCodexSkillInvocation(methods, prompt) {
27942
27970
  prompt
27943
27971
  );
27944
27972
  }
27973
+ var isEnoent4 = (error) => error.code === "ENOENT";
27974
+ async function packagedMethodSkillNames(packagedMethodsRealpath) {
27975
+ const entries = await readdir9(packagedMethodsRealpath, { withFileTypes: true });
27976
+ const names = [];
27977
+ for (const entry of entries) {
27978
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
27979
+ try {
27980
+ await access4(join41(packagedMethodsRealpath, entry.name, "SKILL.md"));
27981
+ } catch (error) {
27982
+ if (isEnoent4(error)) continue;
27983
+ throw error;
27984
+ }
27985
+ names.push(entry.name);
27986
+ }
27987
+ return names;
27988
+ }
27989
+ async function packagedSkillFileBytes(skillDir) {
27990
+ try {
27991
+ await access4(join41(skillDir, "SKILL.md"));
27992
+ } catch (error) {
27993
+ if (isEnoent4(error)) return void 0;
27994
+ throw error;
27995
+ }
27996
+ const files = /* @__PURE__ */ new Map();
27997
+ async function walk(dir, prefix) {
27998
+ const entries = await readdir9(dir, { withFileTypes: true });
27999
+ for (const entry of entries) {
28000
+ const rel = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
28001
+ const full = join41(dir, entry.name);
28002
+ if (entry.isDirectory()) {
28003
+ await walk(full, rel);
28004
+ continue;
28005
+ }
28006
+ if (entry.isFile()) {
28007
+ files.set(rel, await readFile24(full));
28008
+ }
28009
+ }
28010
+ }
28011
+ await walk(skillDir, "");
28012
+ return files;
28013
+ }
28014
+ async function catalogPublishesPackagedSkill(catalogSkillDir, required) {
28015
+ for (const [rel, bytes] of required) {
28016
+ let other;
28017
+ try {
28018
+ other = await readFile24(join41(catalogSkillDir, rel));
28019
+ } catch (error) {
28020
+ if (isEnoent4(error)) return false;
28021
+ throw error;
28022
+ }
28023
+ if (!bytes.equals(other)) return false;
28024
+ }
28025
+ return true;
28026
+ }
28027
+ async function isCompatibleMethodCatalog(link, packagedMethodsRealpath) {
28028
+ const stat2 = await lstat7(link);
28029
+ if (!stat2.isSymbolicLink()) return false;
28030
+ let resolved;
28031
+ try {
28032
+ resolved = await realpath8(link);
28033
+ } catch (error) {
28034
+ if (isEnoent4(error)) return false;
28035
+ throw error;
28036
+ }
28037
+ if (resolved === packagedMethodsRealpath) return true;
28038
+ const names = await packagedMethodSkillNames(packagedMethodsRealpath);
28039
+ if (names.length === 0) return false;
28040
+ for (const name of names) {
28041
+ const required = await packagedSkillFileBytes(join41(packagedMethodsRealpath, name));
28042
+ if (required === void 0) return false;
28043
+ if (!await catalogPublishesPackagedSkill(join41(resolved, name), required)) return false;
28044
+ }
28045
+ return true;
28046
+ }
27945
28047
  async function installWorkspaceMethodSkills(cwd, packageRoot) {
27946
28048
  const target = await realpath8(packagedMethodsDir(packageRoot));
27947
28049
  const link = join41(cwd, ".agents", "skills");
28050
+ let present;
27948
28051
  try {
27949
- const stat2 = await lstat7(link);
27950
- if (!stat2.isSymbolicLink() || await realpath8(link) !== target) {
27951
- const detail = stat2.isSymbolicLink() ? `symlink to ${await readlink(link)}` : "non-symlink entry";
27952
- throw new Error(`workspace method catalog conflict at ${link}: ${detail}`);
27953
- }
27954
- return;
28052
+ await lstat7(link);
28053
+ present = true;
27955
28054
  } catch (error) {
27956
- if (error.code !== "ENOENT") throw error;
28055
+ if (!isEnoent4(error)) throw error;
28056
+ present = false;
28057
+ }
28058
+ if (present) {
28059
+ if (await isCompatibleMethodCatalog(link, target)) return;
28060
+ const stat2 = await lstat7(link);
28061
+ const detail = stat2.isSymbolicLink() ? `symlink to ${await readlink(link)}` : "non-symlink entry";
28062
+ throw new Error(`workspace method catalog conflict at ${link}: ${detail}`);
27957
28063
  }
27958
28064
  await mkdir6(dirname22(link), { recursive: true });
27959
28065
  try {
27960
28066
  await symlink(target, link);
27961
28067
  } catch (error) {
27962
28068
  if (error.code !== "EEXIST") throw error;
27963
- if (await realpath8(link).catch(() => "") !== target) {
28069
+ if (!await isCompatibleMethodCatalog(link, target)) {
27964
28070
  throw new Error(`workspace method catalog conflict at ${link}`);
27965
28071
  }
27966
28072
  }
@@ -138,7 +138,14 @@ function createNavigatorAttendance(options) {
138
138
  let preparationFailure;
139
139
  let routePlaybookReadFailure;
140
140
  let disposed = false;
141
+ let closing;
142
+ const nestCancel = new AbortController();
141
143
  let warmedHelp;
144
+ const sessionHostContext = () => {
145
+ const parentSignal = options.context.signal;
146
+ const signal = parentSignal === void 0 ? nestCancel.signal : AbortSignal.any([nestCancel.signal, parentSignal]);
147
+ return { ...options.context, signal };
148
+ };
142
149
  const loadLiveHelp = async () => {
143
150
  try {
144
151
  return await Promise.all(
@@ -228,7 +235,7 @@ ${text}
228
235
  let created;
229
236
  try {
230
237
  created = await options.createSession({
231
- context: options.context,
238
+ context: sessionHostContext(),
232
239
  subject: subjectKey,
233
240
  ...options.modelSettingPath === void 0 ? {} : { modelSettingPath: options.modelSettingPath },
234
241
  tool
@@ -425,17 +432,17 @@ ${helpContext}
425
432
  };
426
433
  return {
427
434
  setWorkContext(next) {
428
- let closing;
435
+ let closing2;
429
436
  if (next.subjectKey !== subjectKey && session !== void 0) {
430
437
  const previous = session;
431
438
  session = void 0;
432
- closing = previous.dispose();
439
+ closing2 = previous.dispose();
433
440
  }
434
441
  subjectKey = next.subjectKey;
435
442
  subject = next.subject;
436
443
  authority = next.authority;
437
444
  contextError = next.contextError;
438
- return closing;
445
+ return closing2;
439
446
  },
440
447
  /**
441
448
  * Start live-help subprocesses during activation without beginning full
@@ -473,10 +480,19 @@ ${helpContext}
473
480
  },
474
481
  dispose() {
475
482
  disposed = true;
476
- const current = session;
477
- session = void 0;
483
+ if (!nestCancel.signal.aborted) {
484
+ nestCancel.abort(navigatorUnavailableError(
485
+ "session",
486
+ new Error("Navigator attendance was disposed")
487
+ ));
488
+ }
478
489
  activeInvocationId = void 0;
479
- return current?.dispose();
490
+ if (closing === void 0) {
491
+ const current = session;
492
+ session = void 0;
493
+ closing = Promise.resolve(current?.dispose()).then(() => void 0);
494
+ }
495
+ return closing;
480
496
  }
481
497
  };
482
498
  async function settleOnce(settlement) {