@gajae-code/coding-agent 0.18.5 → 0.18.7

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 (133) hide show
  1. package/CHANGELOG.md +140 -0
  2. package/dist/types/capability/types.d.ts +4 -0
  3. package/dist/types/cli-commands.d.ts +10 -0
  4. package/dist/types/cli-ordinary.d.ts +13 -0
  5. package/dist/types/cli.d.ts +12 -3
  6. package/dist/types/commands/contribution-prep.d.ts +0 -3
  7. package/dist/types/commands/sdk.d.ts +3 -0
  8. package/dist/types/commands/update.d.ts +1 -0
  9. package/dist/types/config/model-profile-activation.d.ts +19 -0
  10. package/dist/types/config/settings.d.ts +4 -0
  11. package/dist/types/coordinator-mcp/session-reaper.d.ts +5 -0
  12. package/dist/types/discovery/helpers.d.ts +2 -2
  13. package/dist/types/edit/modes/replace.d.ts +3 -3
  14. package/dist/types/edit/streaming.d.ts +2 -2
  15. package/dist/types/exec/bash-shell-supervisor.d.ts +12 -0
  16. package/dist/types/runtime-mcp/elicitation.d.ts +12 -0
  17. package/dist/types/sdk/broker/discovery.d.ts +8 -0
  18. package/dist/types/sdk/broker/ensure.d.ts +1 -0
  19. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +4 -0
  20. package/dist/types/sdk/broker/lifecycle.d.ts +9 -0
  21. package/dist/types/sdk/broker/runtime.d.ts +1 -1
  22. package/dist/types/sdk/broker/task-artifact-owner-validation.d.ts +55 -0
  23. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  24. package/dist/types/sdk/bus/telegram-daemon.d.ts +4 -2
  25. package/dist/types/sdk/cli/diagnostics-cli.d.ts +18 -0
  26. package/dist/types/sdk/diagnostics/observe-broker.d.ts +185 -0
  27. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  28. package/dist/types/sdk/host/host.d.ts +6 -0
  29. package/dist/types/sdk/index.d.ts +1 -0
  30. package/dist/types/sdk/router/session-router.d.ts +9 -0
  31. package/dist/types/sdk/session.d.ts +13 -0
  32. package/dist/types/session/agent-session.d.ts +5 -1
  33. package/dist/types/session/internal/managed-session-storage.d.ts +11 -1
  34. package/dist/types/session/internal/task-artifact-owner-access.d.ts +17 -0
  35. package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +3 -0
  36. package/dist/types/session/session-retirement.d.ts +47 -0
  37. package/dist/types/session/session-storage.d.ts +0 -46
  38. package/dist/types/session/session-transcript-header.d.ts +2 -0
  39. package/dist/types/session/task-artifact-owner-codec.d.ts +107 -0
  40. package/dist/types/session/task-artifact-owner-retirement.d.ts +7 -0
  41. package/dist/types/session/task-artifact-owner.d.ts +10 -0
  42. package/dist/types/tools/ask-contract.d.ts +75 -254
  43. package/dist/types/tools/bash.d.ts +1 -1
  44. package/package.json +7 -7
  45. package/scripts/build-sdk-package-smoke.ts +5 -0
  46. package/scripts/resident-memory-bench.ts +174 -20
  47. package/src/capability/index.ts +1 -0
  48. package/src/capability/types.ts +4 -0
  49. package/src/cli/config-cli.ts +8 -18
  50. package/src/cli/public-command-entry.ts +5 -0
  51. package/src/cli/ssh-cli.ts +6 -3
  52. package/src/cli/web-search-cli.ts +9 -3
  53. package/src/cli-commands.ts +14 -0
  54. package/src/cli-main.ts +7 -8
  55. package/src/cli-ordinary.ts +197 -0
  56. package/src/cli.ts +25 -182
  57. package/src/commands/contribution-prep.ts +1 -3
  58. package/src/commands/sdk.ts +171 -21
  59. package/src/commands/update.ts +15 -1
  60. package/src/config/file-lock.ts +91 -2
  61. package/src/config/model-profile-activation.ts +70 -4
  62. package/src/config/model-profile-contract.ts +4 -1
  63. package/src/config/model-profiles.ts +23 -31
  64. package/src/config/model-resolver.ts +9 -1
  65. package/src/config/settings.ts +44 -4
  66. package/src/coordinator-mcp/event-webhook.ts +19 -14
  67. package/src/coordinator-mcp/policy.ts +14 -4
  68. package/src/coordinator-mcp/server.ts +142 -7
  69. package/src/coordinator-mcp/session-reaper.ts +9 -1
  70. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  71. package/src/discovery/helpers.ts +9 -3
  72. package/src/discovery/ssh.ts +8 -3
  73. package/src/edit/modes/replace.ts +6 -6
  74. package/src/edit/streaming.ts +15 -15
  75. package/src/exec/bash-shell-supervisor.ts +60 -3
  76. package/src/gjc-runtime/gc-runtime.ts +2 -1
  77. package/src/hindsight/config.ts +9 -3
  78. package/src/internal-urls/docs-index.generated.ts +11 -10
  79. package/src/memories/index.ts +8 -2
  80. package/src/modes/acp/acp-agent.ts +724 -370
  81. package/src/modes/components/tool-execution.ts +1 -1
  82. package/src/prompts/tools/ask.md +2 -0
  83. package/src/runtime-mcp/client.ts +13 -4
  84. package/src/runtime-mcp/elicitation.ts +142 -0
  85. package/src/sdk/broker/broker.ts +54 -1
  86. package/src/sdk/broker/daemon-entry.ts +67 -6
  87. package/src/sdk/broker/discovery.ts +8 -0
  88. package/src/sdk/broker/ensure.ts +156 -20
  89. package/src/sdk/broker/lifecycle-ledger.ts +110 -12
  90. package/src/sdk/broker/lifecycle.ts +334 -348
  91. package/src/sdk/broker/runtime.ts +5 -1
  92. package/src/sdk/broker/task-artifact-owner-validation.ts +259 -0
  93. package/src/sdk/broker/transport.ts +5 -1
  94. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  95. package/src/sdk/bus/chat-daemon-runtime.ts +3 -0
  96. package/src/sdk/bus/index.ts +3 -3
  97. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  98. package/src/sdk/bus/telegram-daemon-control.ts +15 -12
  99. package/src/sdk/bus/telegram-daemon.ts +38 -3
  100. package/src/sdk/cli/diagnostics-cli.ts +247 -0
  101. package/src/sdk/diagnostics/observe-broker.ts +654 -0
  102. package/src/sdk/host/control/dispatch.ts +52 -4
  103. package/src/sdk/host/control/operations.ts +1 -1
  104. package/src/sdk/host/host.ts +6 -0
  105. package/src/sdk/host/session-runtime.test.ts +83 -4
  106. package/src/sdk/host/session-runtime.ts +15 -7
  107. package/src/sdk/index.ts +1 -0
  108. package/src/sdk/lifecycle-session.ts +1 -0
  109. package/src/sdk/mcp/server.ts +15 -3
  110. package/src/sdk/protocol/operation-registry.ts +1 -1
  111. package/src/sdk/router/session-router.ts +26 -3
  112. package/src/sdk/session.ts +51 -8
  113. package/src/session/agent-session.ts +169 -43
  114. package/src/session/blob-store.ts +78 -33
  115. package/src/session/internal/managed-session-storage.ts +203 -4
  116. package/src/session/internal/task-artifact-owner-access.ts +229 -0
  117. package/src/session/internal/task-artifact-owner-transcript.ts +68 -0
  118. package/src/session/session-retirement.ts +155 -0
  119. package/src/session/session-storage.ts +1 -160
  120. package/src/session/session-transcript-header.ts +17 -0
  121. package/src/session/task-artifact-owner-codec.ts +578 -0
  122. package/src/session/task-artifact-owner-retirement.ts +475 -0
  123. package/src/session/task-artifact-owner.ts +289 -0
  124. package/src/setup/hermes/templates/operator-instructions.v1.md +19 -1
  125. package/src/setup/hermes-setup.ts +4 -1
  126. package/src/slash-commands/builtin-registry.ts +8 -5
  127. package/src/tools/ask-contract.test.ts +76 -1
  128. package/src/tools/ask-contract.ts +57 -6
  129. package/src/tools/ask.ts +16 -7
  130. package/src/tools/bash.ts +38 -29
  131. package/src/tools/descriptors.test.ts +29 -0
  132. package/src/tools/tool-catalog.generated.ts +7 -4
  133. package/src/web/search/index.ts +2 -2
package/src/tools/bash.ts CHANGED
@@ -19,8 +19,10 @@ import {
19
19
  } from "../gjc-runtime/managed-owner-admission";
20
20
  import {
21
21
  MANAGED_OWNER_CHILD_TOKEN_ENV,
22
+ MANAGED_OWNER_COMMAND_ENV,
22
23
  MANAGED_OWNER_GENERATION_ENV,
23
24
  MANAGED_OWNER_INCARNATION_ENV,
25
+ MANAGED_OWNER_REDACT_COMMAND_ENV,
24
26
  MANAGED_OWNER_RUN_ID_ENV,
25
27
  MANAGED_OWNER_STATE_DIR_ENV,
26
28
  } from "../gjc-runtime/managed-owner-supervisor";
@@ -118,6 +120,8 @@ export const MANAGED_OWNER_BASH_ENV = [
118
120
  MANAGED_OWNER_RUN_ID_ENV,
119
121
  MANAGED_OWNER_INCARNATION_ENV,
120
122
  MANAGED_OWNER_CHILD_TOKEN_ENV,
123
+ MANAGED_OWNER_COMMAND_ENV,
124
+ MANAGED_OWNER_REDACT_COMMAND_ENV,
121
125
  MANAGED_OWNER_PREDECESSOR_TOKEN_ENV,
122
126
  MANAGED_OWNER_PREDECESSOR_GENERATION_ENV,
123
127
  MANAGED_OWNER_PREDECESSOR_RUN_ID_ENV,
@@ -1278,7 +1282,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
1278
1282
  */
1279
1283
  async #waitForManagedBashJob(
1280
1284
  job: ManagedBashJobHandle,
1281
- thresholdMs: number,
1285
+ thresholdMs: number | undefined,
1282
1286
  signal?: AbortSignal,
1283
1287
  backgroundRequest?: Promise<FoldReason>,
1284
1288
  foldAdapter?: FoldAdapter,
@@ -1288,31 +1292,36 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
1288
1292
  }
1289
1293
 
1290
1294
  const startedAt = Date.now();
1291
- const threshold = Promise.withResolvers<{ kind: "running"; reason: FoldReason }>();
1292
- const thresholdTimer = setTimeout(
1293
- () => {
1294
- const requestFold = this.session.requestForegroundBashBackground;
1295
- if (!foldAdapter || !requestFold) {
1296
- threshold.resolve({ kind: "running", reason: "timer" });
1297
- return;
1298
- }
1299
- void requestFold("timer", foldAdapter)
1300
- .then(folded => {
1301
- if (!folded) threshold.resolve({ kind: "running", reason: "timer" });
1302
- })
1303
- .catch(error => {
1304
- logger.warn("Timer-triggered fold failed", {
1305
- jobId: foldAdapter.jobId,
1306
- error: error instanceof Error ? error.message : String(error),
1307
- });
1308
- threshold.resolve({ kind: "running", reason: "timer" });
1309
- });
1310
- },
1311
- Math.max(0, thresholdMs),
1312
- );
1295
+ const threshold =
1296
+ thresholdMs === undefined ? undefined : Promise.withResolvers<{ kind: "running"; reason: FoldReason }>();
1297
+ const thresholdTimer =
1298
+ threshold === undefined
1299
+ ? undefined
1300
+ : setTimeout(
1301
+ () => {
1302
+ const requestFold = this.session.requestForegroundBashBackground;
1303
+ if (!foldAdapter || !requestFold) {
1304
+ threshold.resolve({ kind: "running", reason: "timer" });
1305
+ return;
1306
+ }
1307
+ void requestFold("timer", foldAdapter)
1308
+ .then(folded => {
1309
+ if (!folded) threshold.resolve({ kind: "running", reason: "timer" });
1310
+ })
1311
+ .catch(error => {
1312
+ logger.warn("Timer-triggered fold failed", {
1313
+ jobId: foldAdapter.jobId,
1314
+ error: error instanceof Error ? error.message : String(error),
1315
+ });
1316
+ threshold.resolve({ kind: "running", reason: "timer" });
1317
+ });
1318
+ },
1319
+ Math.max(0, thresholdMs ?? 0),
1320
+ );
1313
1321
  const waiters: Array<
1314
1322
  Promise<ManagedBashJobCompletion | { kind: "running"; reason: FoldReason } | { kind: "aborted" }>
1315
- > = [job.completion, threshold.promise];
1323
+ > = [job.completion];
1324
+ if (threshold) waiters.push(threshold.promise);
1316
1325
  if (backgroundRequest) {
1317
1326
  waiters.push(backgroundRequest.then(reason => ({ kind: "running" as const, reason })));
1318
1327
  }
@@ -1333,7 +1342,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
1333
1342
  return await Promise.race(waiters);
1334
1343
  } finally {
1335
1344
  stopSteerWatch();
1336
- clearTimeout(thresholdTimer);
1345
+ if (thresholdTimer !== undefined) clearTimeout(thresholdTimer);
1337
1346
  if (signal && onAbort) signal.removeEventListener("abort", onAbort);
1338
1347
  }
1339
1348
  }
@@ -1894,11 +1903,11 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
1894
1903
  // longer has to be bypassed to make folding work. A capable ACP session keeps
1895
1904
  // its terminal contract and still folds.
1896
1905
  if (!pty && ownedManager && !clientTerminalActive) {
1897
- // With auto-background off, wait past the command's own timeout so the job only
1898
- // leaves the foreground on an explicit Ctrl+B fold, never on an auto-background timer.
1906
+ // With auto-background off, the foreground wait has no timer threshold, so the job
1907
+ // only leaves the foreground on an explicit Ctrl+B or steer fold.
1899
1908
  const autoBackgroundWaitMs = this.#autoBackgroundEnabled
1900
1909
  ? this.#resolveAutoBackgroundWaitMs(timeoutMs)
1901
- : timeoutMs + 1_000;
1910
+ : undefined;
1902
1911
  const startBackgrounded = autoBackgroundWaitMs === 0;
1903
1912
  let managedForegroundSettled = false;
1904
1913
  const job = this.#startManagedBashJob({
@@ -2600,7 +2609,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
2600
2609
  try {
2601
2610
  bridgeWait = await this.#waitForManagedBashJob(
2602
2611
  bridgeHandle,
2603
- this.#autoBackgroundEnabled ? this.#resolveAutoBackgroundWaitMs(timeoutMs) : timeoutMs + 1_000,
2612
+ this.#autoBackgroundEnabled ? this.#resolveAutoBackgroundWaitMs(timeoutMs) : undefined,
2604
2613
  signal,
2605
2614
  bridgeFoldRequest.promise,
2606
2615
  bridgeFoldAdapter,
@@ -736,6 +736,35 @@ describe("tool descriptor compatibility gate", () => {
736
736
  }),
737
737
  ).toThrow('Validation failed for tool "ask"');
738
738
  });
739
+ test("loaded and deferred asks reject missing interview metadata and empty siblings with actionable corrections", async () => {
740
+ for (const stage of ["topology", "post-topology"] as const) {
741
+ const session = makeSession({ "tools.discoveryMode": "all" });
742
+ session.hasUI = true;
743
+ session.getDeepInterviewAskStage = () => stage;
744
+ const eager = await BUILTIN_TOOL_DESCRIPTORS.ask.load(session);
745
+ const lazy = (await createTools(session)).find(tool => tool.name === "ask");
746
+ if (!eager || !lazy) throw new Error("expected eager and deferred asks");
747
+ const ordinary = {
748
+ id: "dot-gjc-topology",
749
+ question: "Round 0 | Topology confirmation\n\nDoes this scope match?",
750
+ options: [{ label: "Looks right" }],
751
+ workflowGate: { stage: "deep-interview", kind: "question" },
752
+ };
753
+ for (const [arguments_, correction] of [
754
+ [{ questions: [ordinary] }, "require structured deepInterview metadata"],
755
+ [{ questions: [{ ...ordinary, deepInterview: null }] }, "require structured deepInterview metadata"],
756
+ [{ questions: [ordinary, { id: "metadata", question: "", options: [] }] }, "remove empty extra questions"],
757
+ [{ questions: [ordinary, ordinary] }, "require exactly one question"],
758
+ ] as const) {
759
+ for (const tool of [eager, lazy]) {
760
+ expect(() =>
761
+ validateToolArguments(tool, { type: "toolCall", id: "incident", name: "ask", arguments: arguments_ }),
762
+ ).toThrow(correction);
763
+ }
764
+ }
765
+ }
766
+ });
767
+
739
768
  test("deferred ask validation matches eager AskTool on adversarial contracts", async () => {
740
769
  const session = makeSession({ "tools.discoveryMode": "all" });
741
770
  session.hasUI = true;
@@ -310,7 +310,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
310
310
  "ask": {
311
311
  "name": "ask",
312
312
  "label": "Ask",
313
- "description": "Asks user when you need clarification or input during task execution.\n\n<conditions>\n- Multiple approaches exist with significantly different tradeoffs user should weigh\n</conditions>\n\n<instruction>\n- Use `recommended: <index>` to mark default (0-indexed); \" (Recommended)\" added automatically\n- Use `questions` for multiple related questions instead of asking one at a time\n- Set `multi: true` on question to allow multiple selections\n</instruction>\n\n<caution>\n- Provide 2-5 concise, distinct options\n</caution>\n\n<critical>\n- **Default to action.** Resolve ambiguity yourself using repo conventions, existing patterns, and reasonable defaults. Exhaust existing sources (code, configs, docs, history) before asking. Only ask when options have materially different tradeoffs the user must decide.\n- **If multiple choices are acceptable**, pick the most conservative/standard option and proceed; state the choice.\n- **Do NOT include the automatic custom-input control or labels that imitate it** such as \"Other (type your own)\", \"Other (specify)\", \"type your own\", \"custom input\", or \"직접 입력\" — UI automatically adds \"Other (type your own)\" to every question. A plain domain value such as \"Other\" is allowed when it is a genuine answer, not a request for free text. To collect free text, ask for it in the question body and rely on the automatic Other/custom-input control instead of adding a fake option.\n</critical>\n\n<examples>\n# Single question\nquestions: [{\"id\": \"auth_method\", \"question\": \"Which authentication method should this API use?\", \"options\": [{\"label\": \"JWT\"}, {\"label\": \"OAuth2\"}, {\"label\": \"Session cookies\"}], \"recommended\": 0}]\n\n# Multiple questions\nquestions: [{\"id\": \"storage_type\", \"question\": \"Which storage backend?\", \"options\": [{\"label\": \"SQLite\"}, {\"label\": \"PostgreSQL\"}]}, {\"id\": \"auth_method\", \"question\": \"Which auth method?\", \"options\": [{\"label\": \"JWT\"}, {\"label\": \"Session cookies\"}]}]\n</examples>",
313
+ "description": "Asks user when you need clarification or input during task execution.\n\n<conditions>\n- Multiple approaches exist with significantly different tradeoffs user should weigh\n</conditions>\n\n<instruction>\n- Use `recommended: <index>` to mark default (0-indexed); \" (Recommended)\" added automatically\n- Use `questions` for multiple related questions instead of asking one at a time\n- Set `multi: true` on question to allow multiple selections\n- Every question body must contain meaningful, non-whitespace text. Never add empty `metadata`/`ignore` questions.\n- While deep-interview is in the `interviewing` phase, submit exactly one question with the required `deepInterview` metadata on that question. Round 0 needs `round: 0`, `component: \"review-topology\"`, `dimension: \"topology\"`, `ambiguity`, and `intent_contract` containing the displayed items and affirmative labels. Later questions need a positive round, component, dimension, and ambiguity. Question prose and `workflowGate` alone cannot record an interview answer. After final-spec persistence transitions to `handoff`, next-workflow/approval choices are ordinary asks without interview round metadata.\n</instruction>\n\n<caution>\n- Provide 2-5 concise, distinct options\n</caution>\n\n<critical>\n- **Default to action.** Resolve ambiguity yourself using repo conventions, existing patterns, and reasonable defaults. Exhaust existing sources (code, configs, docs, history) before asking. Only ask when options have materially different tradeoffs the user must decide.\n- **If multiple choices are acceptable**, pick the most conservative/standard option and proceed; state the choice.\n- **Do NOT include the automatic custom-input control or labels that imitate it** such as \"Other (type your own)\", \"Other (specify)\", \"type your own\", \"custom input\", or \"직접 입력\" — UI automatically adds \"Other (type your own)\" to every question. A plain domain value such as \"Other\" is allowed when it is a genuine answer, not a request for free text. To collect free text, ask for it in the question body and rely on the automatic Other/custom-input control instead of adding a fake option.\n</critical>\n\n<examples>\n# Single question\nquestions: [{\"id\": \"auth_method\", \"question\": \"Which authentication method should this API use?\", \"options\": [{\"label\": \"JWT\"}, {\"label\": \"OAuth2\"}, {\"label\": \"Session cookies\"}], \"recommended\": 0}]\n\n# Multiple questions\nquestions: [{\"id\": \"storage_type\", \"question\": \"Which storage backend?\", \"options\": [{\"label\": \"SQLite\"}, {\"label\": \"PostgreSQL\"}]}, {\"id\": \"auth_method\", \"question\": \"Which auth method?\", \"options\": [{\"label\": \"JWT\"}, {\"label\": \"Session cookies\"}]}]\n</examples>",
314
314
  "parameters": {
315
315
  "type": "object",
316
316
  "properties": {
@@ -326,7 +326,8 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
326
326
  },
327
327
  "question": {
328
328
  "type": "string",
329
- "description": "question text"
329
+ "minLength": 1,
330
+ "description": "non-empty question text"
330
331
  },
331
332
  "options": {
332
333
  "type": "array",
@@ -2660,7 +2661,8 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
2660
2661
  "description": "recency filter"
2661
2662
  },
2662
2663
  "limit": {
2663
- "type": "number",
2664
+ "type": "integer",
2665
+ "minimum": 1,
2664
2666
  "description": "max results"
2665
2667
  },
2666
2668
  "max_tokens": {
@@ -2672,7 +2674,8 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
2672
2674
  "description": "sampling temperature"
2673
2675
  },
2674
2676
  "num_search_results": {
2675
- "type": "number",
2677
+ "type": "integer",
2678
+ "minimum": 1,
2676
2679
  "description": "number of search results"
2677
2680
  },
2678
2681
  "xai_search_mode": {
@@ -44,10 +44,10 @@ import {
44
44
  export const webSearchSchema = z.object({
45
45
  query: z.string().describe("search query"),
46
46
  recency: z.enum(["day", "week", "month", "year"]).describe("recency filter").optional(),
47
- limit: z.number().describe("max results").optional(),
47
+ limit: z.number().int().min(1).describe("max results").optional(),
48
48
  max_tokens: z.number().describe("max output tokens").optional(),
49
49
  temperature: z.number().describe("sampling temperature").optional(),
50
- num_search_results: z.number().describe("number of search results").optional(),
50
+ num_search_results: z.number().int().min(1).describe("number of search results").optional(),
51
51
  xai_search_mode: z
52
52
  .enum(["web", "x", "web_and_x"])
53
53
  .describe("xAI only: use web_search, x_search, or both")