chatroom-cli 1.91.1 → 1.91.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.
package/dist/index.js CHANGED
@@ -29879,6 +29879,60 @@ function resolveCodexSdkPackageJson(chatroomCliRoot) {
29879
29879
  throw new CodexSdkPackageError(`Could not locate @openai/codex-sdk package.json from ${chatroomCliRoot}. ${REINSTALL_HINT2}`);
29880
29880
  }
29881
29881
  }
29882
+ function resolveTargetTriple() {
29883
+ const { platform, arch } = process;
29884
+ if (platform === "linux" && arch === "x64")
29885
+ return "x86_64-unknown-linux-musl";
29886
+ if (platform === "linux" && arch === "arm64")
29887
+ return "aarch64-unknown-linux-musl";
29888
+ if (platform === "darwin" && arch === "x64")
29889
+ return "x86_64-apple-darwin";
29890
+ if (platform === "darwin" && arch === "arm64")
29891
+ return "aarch64-apple-darwin";
29892
+ if (platform === "win32" && arch === "x64")
29893
+ return "x86_64-pc-windows-msvc";
29894
+ if (platform === "win32" && arch === "arm64")
29895
+ return "aarch64-pc-windows-msvc";
29896
+ throw new CodexSdkPackageError(`Unsupported platform for ${CODEX_NPM_NAME}: ${platform}-${arch}. ${REINSTALL_HINT2}`);
29897
+ }
29898
+ function resolvePlatformPackageName2(targetTriple) {
29899
+ const pkg = PLATFORM_PACKAGE_BY_TARGET[targetTriple];
29900
+ if (!pkg) {
29901
+ throw new CodexSdkPackageError(`Unsupported platform for ${CODEX_NPM_NAME}: ${targetTriple}. ${REINSTALL_HINT2}`);
29902
+ }
29903
+ return pkg;
29904
+ }
29905
+ function resolveCodexExecutablePath(moduleRef = import.meta.url) {
29906
+ if (cachedExecutablePath2)
29907
+ return cachedExecutablePath2;
29908
+ const chatroomCliRoot = resolveChatroomCliRoot2(moduleRef);
29909
+ const require3 = createRequire4(join8(chatroomCliRoot, "package.json"));
29910
+ try {
29911
+ require3.resolve(`${CODEX_NPM_NAME}/package.json`, { paths: [chatroomCliRoot] });
29912
+ } catch {
29913
+ throw new CodexSdkPackageError(`${CODEX_NPM_NAME} is not installed. Ensure chatroom-cli was installed with optional dependencies. ${REINSTALL_HINT2}`);
29914
+ }
29915
+ const targetTriple = resolveTargetTriple();
29916
+ const platformPkg = resolvePlatformPackageName2(targetTriple);
29917
+ const codexBinaryName = process.platform === "win32" ? "codex.exe" : "codex";
29918
+ let platformPkgDir;
29919
+ try {
29920
+ platformPkgDir = dirname3(require3.resolve(`${platformPkg}/package.json`, { paths: [chatroomCliRoot] }));
29921
+ } catch {
29922
+ throw new CodexSdkPackageError(`Native Codex CLI package ${platformPkg} is not installed. Ensure ${CODEX_NPM_NAME} is installed with optional dependencies. ${REINSTALL_HINT2}`);
29923
+ }
29924
+ const packageBinaryPath = join8(platformPkgDir, "vendor", targetTriple, "bin", codexBinaryName);
29925
+ const legacyBinaryPath = join8(platformPkgDir, "vendor", targetTriple, "codex", codexBinaryName);
29926
+ if (existsSync2(packageBinaryPath)) {
29927
+ cachedExecutablePath2 = packageBinaryPath;
29928
+ return cachedExecutablePath2;
29929
+ }
29930
+ if (existsSync2(legacyBinaryPath)) {
29931
+ cachedExecutablePath2 = legacyBinaryPath;
29932
+ return cachedExecutablePath2;
29933
+ }
29934
+ throw new CodexSdkPackageError(`Unable to locate Codex CLI binaries for ${targetTriple}. Ensure ${CODEX_NPM_NAME} is installed with optional dependencies. ${REINSTALL_HINT2}`);
29935
+ }
29882
29936
  async function importBundledCodexSdk(moduleRef = import.meta.url) {
29883
29937
  const chatroomCliRoot = resolveChatroomCliRoot2(moduleRef);
29884
29938
  const pinnedVersion = readPinnedSdkVersion2(chatroomCliRoot);
@@ -29887,11 +29941,11 @@ async function importBundledCodexSdk(moduleRef = import.meta.url) {
29887
29941
  if (installedVersion !== pinnedVersion) {
29888
29942
  throw new CodexSdkPackageError(`@openai/codex-sdk@${installedVersion} does not match chatroom-cli pin (${pinnedVersion}). ${REINSTALL_HINT2}`);
29889
29943
  }
29890
- const entryPath = join8(dirname3(packageJsonPath), "dist", "index.js");
29891
- if (!existsSync2(entryPath)) {
29892
- throw new CodexSdkPackageError(`@openai/codex-sdk entry file is missing: ${entryPath}. ${REINSTALL_HINT2}`);
29944
+ const distEntryPath = join8(dirname3(packageJsonPath), "dist", "index.js");
29945
+ if (!existsSync2(distEntryPath)) {
29946
+ throw new CodexSdkPackageError(`@openai/codex-sdk entry file is missing: ${distEntryPath}. ${REINSTALL_HINT2}`);
29893
29947
  }
29894
- return import(pathToFileURL2(entryPath).href);
29948
+ return import(pathToFileURL2(distEntryPath).href);
29895
29949
  }
29896
29950
  function getBundledCodexSdkVersion(moduleRef = import.meta.url) {
29897
29951
  const chatroomCliRoot = resolveChatroomCliRoot2(moduleRef);
@@ -29908,12 +29962,28 @@ function formatCodexSdkError(err) {
29908
29962
  }
29909
29963
  function formatCodexSdkLoadError(err) {
29910
29964
  if (err instanceof CodexSdkPackageError) {
29911
- return err.message;
29965
+ const message2 = err.message;
29966
+ if ((message2.includes("Codex CLI") || message2.includes("optional dependencies")) && !message2.includes(REINSTALL_HINT2)) {
29967
+ return `${message2} ${REINSTALL_HINT2}`;
29968
+ }
29969
+ return message2;
29970
+ }
29971
+ const message = err instanceof Error ? err.message : String(err);
29972
+ if (message.includes("Codex CLI") || message.includes("optional dependencies")) {
29973
+ return `${message} ${REINSTALL_HINT2}`;
29912
29974
  }
29913
29975
  return formatCodexSdkError(err);
29914
29976
  }
29915
- var REINSTALL_HINT2 = "Reinstall chatroom-cli: npm install -g chatroom-cli@latest", CodexSdkPackageError;
29977
+ var REINSTALL_HINT2 = "Reinstall chatroom-cli: npm install -g chatroom-cli@latest", CODEX_NPM_NAME = "@openai/codex", PLATFORM_PACKAGE_BY_TARGET, CodexSdkPackageError, cachedExecutablePath2;
29916
29978
  var init_codex_sdk_package = __esm(() => {
29979
+ PLATFORM_PACKAGE_BY_TARGET = {
29980
+ "x86_64-unknown-linux-musl": "@openai/codex-linux-x64",
29981
+ "aarch64-unknown-linux-musl": "@openai/codex-linux-arm64",
29982
+ "x86_64-apple-darwin": "@openai/codex-darwin-x64",
29983
+ "aarch64-apple-darwin": "@openai/codex-darwin-arm64",
29984
+ "x86_64-pc-windows-msvc": "@openai/codex-win32-x64",
29985
+ "aarch64-pc-windows-msvc": "@openai/codex-win32-arm64"
29986
+ };
29917
29987
  CodexSdkPackageError = class CodexSdkPackageError extends Error {
29918
29988
  code = "CODEX_SDK_PACKAGE_INCOMPLETE";
29919
29989
  constructor(message) {
@@ -30354,6 +30424,7 @@ var init_codex_sdk_agent_service = __esm(() => {
30354
30424
  async isInstalled() {
30355
30425
  try {
30356
30426
  await loadSdk2();
30427
+ resolveCodexExecutablePath();
30357
30428
  return true;
30358
30429
  } catch (err) {
30359
30430
  console.warn(`[codex-sdk] unavailable: ${formatCodexSdkLoadError(err)}`);
@@ -30697,7 +30768,11 @@ ${options.prompt}`;
30697
30768
  let thread;
30698
30769
  try {
30699
30770
  const { Codex } = await loadSdk2();
30700
- codex = new Codex({ env: buildCodexEnv(options.resolvedConvexUrl) });
30771
+ const codexPath = resolveCodexExecutablePath();
30772
+ codex = new Codex({
30773
+ codexPathOverride: codexPath,
30774
+ env: buildCodexEnv(options.resolvedConvexUrl)
30775
+ });
30701
30776
  thread = codex.startThread(buildThreadOptions(options.workingDir, variant));
30702
30777
  } catch (err) {
30703
30778
  writeSpawnError2(buildAgentLogPrefix("codex-sdk", context5), err);
@@ -30724,7 +30799,11 @@ ${options.prompt}`;
30724
30799
  const pid = keeper.pid;
30725
30800
  const variant = decodeCodexVariant(options.model ?? stored.model);
30726
30801
  const { Codex } = await loadSdk2();
30727
- const codex = new Codex({ env: buildCodexEnv(options.resolvedConvexUrl) });
30802
+ const codexPath = resolveCodexExecutablePath();
30803
+ const codex = new Codex({
30804
+ codexPathOverride: codexPath,
30805
+ env: buildCodexEnv(options.resolvedConvexUrl)
30806
+ });
30728
30807
  const thread = codex.resumeThread(stored.harnessSessionId, buildThreadOptions(stored.workingDir, variant));
30729
30808
  return this.startRunningSession({
30730
30809
  pid,
@@ -108117,7 +108196,7 @@ var init_start_subscriptions = __esm(() => {
108117
108196
  });
108118
108197
 
108119
108198
  // src/daemon/entry/enhancer/constants.ts
108120
- var ENHANCER_AGENT_ROLE = "enhancer", ENHANCER_AGENT_END_GRACE_MS = 3000, ENHANCER_JOB_POLL_INTERVAL_MS = 500;
108199
+ var ENHANCER_AGENT_ROLE = "enhancer", ENHANCER_AGENT_END_GRACE_MS = 3000;
108121
108200
 
108122
108201
  // src/daemon/entry/enhancer/enhancer-log.ts
108123
108202
  function formatEnhancerLogLine(message) {
@@ -108132,100 +108211,143 @@ function writeEnhancerLog(message) {
108132
108211
  }
108133
108212
  var ENHANCER_LOG_PREFIX = "[enhancer]";
108134
108213
 
108214
+ // src/daemon/entry/enhancer/job-outcome-subscription.ts
108215
+ function subscribeToEnhancerJobOutcome(args2) {
108216
+ let currentState = null;
108217
+ let resolveOutcome = null;
108218
+ let rejectOutcome = null;
108219
+ let stopped = false;
108220
+ const outcome = new Promise((resolve5, reject) => {
108221
+ resolveOutcome = resolve5;
108222
+ rejectOutcome = reject;
108223
+ });
108224
+ const unsub = args2.wsClient.onUpdate(api.web.enhancer.index.getJobOutcome, {
108225
+ sessionId: args2.sessionId,
108226
+ chatroomId: args2.chatroomId,
108227
+ jobId: args2.jobId
108228
+ }, (state) => {
108229
+ if (stopped)
108230
+ return;
108231
+ if (!state) {
108232
+ rejectOutcome?.(new Error("Enhancer job not found"));
108233
+ resolveOutcome = null;
108234
+ rejectOutcome = null;
108235
+ return;
108236
+ }
108237
+ currentState = state;
108238
+ if (state.status === "complete" || state.status === "failed" || state.status === "cancelled") {
108239
+ resolveOutcome?.(state);
108240
+ resolveOutcome = null;
108241
+ rejectOutcome = null;
108242
+ }
108243
+ }, (error51) => {
108244
+ if (stopped)
108245
+ return;
108246
+ rejectOutcome?.(error51);
108247
+ resolveOutcome = null;
108248
+ rejectOutcome = null;
108249
+ });
108250
+ return {
108251
+ outcome,
108252
+ getCurrentState: () => currentState,
108253
+ stop: () => {
108254
+ if (stopped)
108255
+ return;
108256
+ stopped = true;
108257
+ unsub();
108258
+ resolveOutcome = null;
108259
+ rejectOutcome = null;
108260
+ }
108261
+ };
108262
+ }
108263
+ var init_job_outcome_subscription = __esm(() => {
108264
+ init_api3();
108265
+ });
108266
+
108135
108267
  // src/daemon/entry/enhancer/wait-for-enhancer-job.ts
108136
108268
  async function waitForEnhancerJobResolution(params) {
108137
- const { sessionId, chatroomId, jobId, backend: backend2, onFailure, onSalvageComplete } = params;
108269
+ const { sessionId, chatroomId, jobId, wsClient: wsClient2, onFailure, onSalvageComplete } = params;
108138
108270
  let outcome = null;
108139
108271
  let salvagedText = "";
108140
- const pollInterval = setInterval(async () => {
108272
+ let agentEndTimer = null;
108273
+ let resolveWait = null;
108274
+ const waitPromise = new Promise((resolve5) => {
108275
+ resolveWait = resolve5;
108276
+ });
108277
+ const subscription = subscribeToEnhancerJobOutcome({
108278
+ wsClient: wsClient2,
108279
+ sessionId,
108280
+ chatroomId,
108281
+ jobId
108282
+ });
108283
+ const finish = (resolution) => {
108141
108284
  if (outcome)
108142
108285
  return;
108143
- try {
108144
- const status3 = await backend2.query(api.web.enhancer.index.getJob, {
108145
- sessionId,
108146
- chatroomId,
108147
- jobId
108148
- });
108149
- if (status3?.status === "complete") {
108150
- outcome = "complete";
108151
- writeEnhancerLog(`completed job=${jobId}`);
108152
- }
108153
- } catch {}
108154
- }, ENHANCER_JOB_POLL_INTERVAL_MS);
108286
+ outcome = resolution;
108287
+ resolveWait?.();
108288
+ resolveWait = null;
108289
+ };
108290
+ const failAfterAgentEnd = () => {
108291
+ writeEnhancerLog("agent_end: turn ended without complete — failing terminal");
108292
+ onFailure("Agent exited without completing enhancer job", true);
108293
+ finish("failed");
108294
+ };
108295
+ subscription.outcome.then((state) => {
108296
+ if (outcome)
108297
+ return;
108298
+ const resolution = state.status === "complete" ? "complete" : "failed";
108299
+ if (resolution === "complete")
108300
+ writeEnhancerLog(`completed job=${jobId}`);
108301
+ finish(resolution);
108302
+ }).catch((error51) => {
108303
+ if (outcome)
108304
+ return;
108305
+ writeEnhancerLog(`enhancer outcome subscription failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
108306
+ onFailure("Enhancer job outcome subscription failed", false);
108307
+ finish("failed");
108308
+ });
108155
108309
  params.onAssistantText?.((text) => {
108156
108310
  salvagedText += text;
108157
108311
  });
108158
108312
  params.onAgentEnd?.(() => {
108159
108313
  if (outcome)
108160
108314
  return;
108161
- const check4 = () => {
108315
+ agentEndTimer = setTimeout(() => {
108162
108316
  if (outcome)
108163
108317
  return;
108164
- backend2.query(api.web.enhancer.index.getJob, {
108165
- sessionId,
108166
- chatroomId,
108167
- jobId
108168
- }).then((status3) => {
108169
- if (outcome)
108170
- return;
108171
- if (status3?.status === "complete") {
108172
- outcome = "complete";
108173
- return;
108174
- }
108175
- if (status3?.status === "running") {
108176
- const trimmed = salvagedText.trim();
108177
- if (trimmed && onSalvageComplete) {
108178
- onSalvageComplete(trimmed).then(() => {
108179
- if (outcome)
108180
- return;
108181
- backend2.query(api.web.enhancer.index.getJob, {
108182
- sessionId,
108183
- chatroomId,
108184
- jobId
108185
- }).then((afterSalvage) => {
108186
- if (afterSalvage?.status === "complete") {
108187
- outcome = "complete";
108188
- writeEnhancerLog("agent_end: salvaged assistant text via complete");
108189
- return;
108190
- }
108191
- outcome = "failed";
108192
- writeEnhancerLog("agent_end: turn ended without complete — failing terminal");
108193
- onFailure("Agent exited without completing enhancer job", true);
108194
- });
108195
- }).catch(() => {
108196
- outcome = "failed";
108197
- writeEnhancerLog("agent_end: turn ended without complete — failing terminal");
108198
- onFailure("Agent exited without completing enhancer job", true);
108199
- });
108200
- } else {
108201
- outcome = "failed";
108202
- writeEnhancerLog("agent_end: turn ended without complete — failing terminal");
108203
- onFailure("Agent exited without completing enhancer job", true);
108204
- }
108205
- }
108318
+ const state = subscription.getCurrentState();
108319
+ if (state?.status === "complete") {
108320
+ finish("complete");
108321
+ return;
108322
+ }
108323
+ if (state?.status !== "running") {
108324
+ failAfterAgentEnd();
108325
+ return;
108326
+ }
108327
+ const trimmed = salvagedText.trim();
108328
+ if (!trimmed || !onSalvageComplete) {
108329
+ failAfterAgentEnd();
108330
+ return;
108331
+ }
108332
+ onSalvageComplete(trimmed).catch(() => {
108333
+ failAfterAgentEnd();
108206
108334
  });
108207
- };
108208
- setTimeout(check4, ENHANCER_AGENT_END_GRACE_MS);
108335
+ }, ENHANCER_AGENT_END_GRACE_MS);
108209
108336
  });
108210
108337
  params.onExit(() => {
108211
108338
  if (outcome)
108212
108339
  return;
108213
- outcome = "failed";
108214
108340
  onFailure("Agent process exited without completing enhancer job", false);
108341
+ finish("failed");
108215
108342
  });
108216
- await new Promise((resolve5) => {
108217
- const check4 = setInterval(() => {
108218
- if (outcome) {
108219
- clearInterval(check4);
108220
- resolve5();
108221
- }
108222
- }, 100);
108223
- });
108224
- clearInterval(pollInterval);
108343
+ await waitPromise;
108344
+ if (agentEndTimer)
108345
+ clearTimeout(agentEndTimer);
108346
+ subscription.stop();
108225
108347
  return outcome ?? "failed";
108226
108348
  }
108227
108349
  var init_wait_for_enhancer_job = __esm(() => {
108228
- init_api3();
108350
+ init_job_outcome_subscription();
108229
108351
  });
108230
108352
 
108231
108353
  // src/daemon/entry/enhancer-inbound-registry.ts
@@ -108242,7 +108364,7 @@ async function dispatchEnhancerInboundEvent(event) {
108242
108364
  var handler3;
108243
108365
 
108244
108366
  // src/daemon/entry/enhancer/job-subscriber.ts
108245
- async function processEnhancerJobForSpawn(sessionId, machineId, convexUrl, backend2, agentServices, job, inFlight2) {
108367
+ async function processEnhancerJobForSpawn(sessionId, machineId, convexUrl, wsClient2, backend2, agentServices, job, inFlight2) {
108246
108368
  if (inFlight2.has(job.jobId))
108247
108369
  return;
108248
108370
  inFlight2.add(job.jobId);
@@ -108298,7 +108420,7 @@ async function processEnhancerJobForSpawn(sessionId, machineId, convexUrl, backe
108298
108420
  sessionId,
108299
108421
  chatroomId: payload.chatroomId,
108300
108422
  jobId: payload.jobId,
108301
- backend: backend2,
108423
+ wsClient: wsClient2,
108302
108424
  onAssistantText: spawned.onAssistantText ? (cb) => spawned.onAssistantText?.(cb) : undefined,
108303
108425
  onAgentEnd: spawned.onAgentEnd ? (cb) => spawned.onAgentEnd?.(cb) : undefined,
108304
108426
  onExit: (cb) => spawned.onExit(() => cb()),
@@ -108340,30 +108462,30 @@ async function processEnhancerJobForSpawn(sessionId, machineId, convexUrl, backe
108340
108462
  }
108341
108463
  }
108342
108464
  }
108343
- function processEnhancerJobs(sessionId, machineId, convexUrl, backend2, agentServices, jobs, inFlight2) {
108465
+ function processEnhancerJobs(sessionId, machineId, convexUrl, wsClient2, backend2, agentServices, jobs, inFlight2) {
108344
108466
  for (const job of jobs ?? []) {
108345
- processEnhancerJobForSpawn(sessionId, machineId, convexUrl, backend2, agentServices, job, inFlight2);
108467
+ processEnhancerJobForSpawn(sessionId, machineId, convexUrl, wsClient2, backend2, agentServices, job, inFlight2);
108346
108468
  }
108347
108469
  }
108348
- async function drainPendingEnhancerJobs(sessionId, machineId, convexUrl, backend2, agentServices, inFlight2) {
108470
+ async function drainPendingEnhancerJobs(sessionId, machineId, convexUrl, wsClient2, backend2, agentServices, inFlight2) {
108349
108471
  const jobs = await backend2.query(api.daemon.enhancer.index.pendingForMachine, {
108350
108472
  sessionId,
108351
108473
  machineId
108352
108474
  });
108353
108475
  if (!jobs?.length)
108354
108476
  return;
108355
- processEnhancerJobs(sessionId, machineId, convexUrl, backend2, agentServices, jobs, inFlight2);
108477
+ processEnhancerJobs(sessionId, machineId, convexUrl, wsClient2, backend2, agentServices, jobs, inFlight2);
108356
108478
  }
108357
- function startEnhancerJobSubscriber(sessionId, machineId, convexUrl, backend2, agentServices) {
108479
+ function startEnhancerJobSubscriber(sessionId, machineId, convexUrl, wsClient2, backend2, agentServices) {
108358
108480
  const inFlight2 = new Set;
108359
108481
  registerEnhancerInboundHandler(async () => {
108360
- await drainPendingEnhancerJobs(sessionId, machineId, convexUrl, backend2, agentServices, inFlight2);
108482
+ await drainPendingEnhancerJobs(sessionId, machineId, convexUrl, wsClient2, backend2, agentServices, inFlight2);
108361
108483
  });
108362
108484
  return {
108363
108485
  stop: () => {
108364
108486
  unregisterEnhancerInboundHandler();
108365
108487
  },
108366
- drainPendingEnhancerJobs: () => drainPendingEnhancerJobs(sessionId, machineId, convexUrl, backend2, agentServices, inFlight2)
108488
+ drainPendingEnhancerJobs: () => drainPendingEnhancerJobs(sessionId, machineId, convexUrl, wsClient2, backend2, agentServices, inFlight2)
108367
108489
  };
108368
108490
  }
108369
108491
  var init_job_subscriber = __esm(() => {
@@ -108372,8 +108494,8 @@ var init_job_subscriber = __esm(() => {
108372
108494
  });
108373
108495
 
108374
108496
  // src/daemon/entry/enhancer/start-subscriptions.ts
108375
- function startEnhancerSubscriptions(sessionId, machineId, convexUrl, backend2, agentServices) {
108376
- return startEnhancerJobSubscriber(sessionId, machineId, convexUrl, backend2, agentServices);
108497
+ function startEnhancerSubscriptions(sessionId, machineId, convexUrl, wsClient2, backend2, agentServices) {
108498
+ return startEnhancerJobSubscriber(sessionId, machineId, convexUrl, wsClient2, backend2, agentServices);
108377
108499
  }
108378
108500
  var init_start_subscriptions2 = __esm(() => {
108379
108501
  init_job_subscriber();
@@ -114660,7 +114782,7 @@ function createDaemonRuntime(deps) {
114660
114782
  backend: session2.backend,
114661
114783
  convexUrl: session2.convexUrl
114662
114784
  }, activeSessions, harnesses);
114663
- enhancerWorkerHandle = startEnhancerSubscriptions(session2.sessionId, session2.machineId, session2.convexUrl, session2.backend, session2.agentServices);
114785
+ enhancerWorkerHandle = startEnhancerSubscriptions(session2.sessionId, session2.machineId, session2.convexUrl, deps.wsClient, session2.backend, session2.agentServices);
114664
114786
  }
114665
114787
  console.log(`
114666
114788
  Listening for commands...`);
@@ -117671,4 +117793,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
117671
117793
  });
117672
117794
  program2.parse();
117673
117795
 
117674
- //# debugId=D279A4D8F6CF2F1C64756E2164756E21
117796
+ //# debugId=7232F20998B29AC964756E2164756E21