unique-sdk 2026.26.0.dev10__tar.gz → 2026.26.0.dev11__tar.gz

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 (84) hide show
  1. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/PKG-INFO +1 -1
  2. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/pyproject.toml +1 -1
  3. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/cli.py +8 -12
  4. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/commands/elicitation.py +31 -3
  5. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/shell.py +15 -7
  6. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/skills/unique-cli-elicitation/SKILL.md +34 -8
  7. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/README.md +0 -0
  8. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/__init__.py +0 -0
  9. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/_api_requestor.py +0 -0
  10. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/_api_resource.py +0 -0
  11. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/_api_version.py +0 -0
  12. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/_error.py +0 -0
  13. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/_http_client.py +0 -0
  14. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/_list_object.py +0 -0
  15. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/_object_classes.py +0 -0
  16. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/_request_options.py +0 -0
  17. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/_unique_object.py +0 -0
  18. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/_unique_ql.py +0 -0
  19. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/_unique_response.py +0 -0
  20. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/_util.py +0 -0
  21. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/_version.py +0 -0
  22. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/_webhook.py +0 -0
  23. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/__init__.py +0 -0
  24. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_acronyms.py +0 -0
  25. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_agentic_table.py +0 -0
  26. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_analytics_order.py +0 -0
  27. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_benchmarking.py +0 -0
  28. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_briefing.py +0 -0
  29. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_chat_completion.py +0 -0
  30. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_content.py +0 -0
  31. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_dynamic_frontend.py +0 -0
  32. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_elicitation.py +0 -0
  33. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_embedding.py +0 -0
  34. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_event.py +0 -0
  35. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_folder.py +0 -0
  36. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_group.py +0 -0
  37. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_integrated.py +0 -0
  38. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_llm_models.py +0 -0
  39. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_mcp.py +0 -0
  40. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_message.py +0 -0
  41. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_message_assessment.py +0 -0
  42. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_message_execution.py +0 -0
  43. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_message_log.py +0 -0
  44. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_message_tool.py +0 -0
  45. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_module.py +0 -0
  46. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_scheduled_task.py +0 -0
  47. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_search.py +0 -0
  48. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_search_string.py +0 -0
  49. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_short_term_memory.py +0 -0
  50. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_space.py +0 -0
  51. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_user.py +0 -0
  52. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/api_resources/_web_search.py +0 -0
  53. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/__init__.py +0 -0
  54. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/__main__.py +0 -0
  55. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/commands/__init__.py +0 -0
  56. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/commands/_citation_manifest.py +0 -0
  57. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/commands/cite_file.py +0 -0
  58. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/commands/dynamic_frontend.py +0 -0
  59. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/commands/files.py +0 -0
  60. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/commands/folders.py +0 -0
  61. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/commands/mcp.py +0 -0
  62. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/commands/navigation.py +0 -0
  63. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/commands/read.py +0 -0
  64. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/commands/scheduled_tasks.py +0 -0
  65. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/commands/search.py +0 -0
  66. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/commands/subagent.py +0 -0
  67. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/commands/web_search.py +0 -0
  68. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/commands/web_search_config.py +0 -0
  69. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/config.py +0 -0
  70. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/formatting.py +0 -0
  71. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/skills/unique-cli-file-management/SKILL.md +0 -0
  72. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/skills/unique-cli-mcp/SKILL.md +0 -0
  73. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/skills/unique-cli-scheduled-tasks/SKILL.md +0 -0
  74. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/skills/unique-cli-search/SKILL.md +0 -0
  75. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/skills/unique-cli-subagent/SKILL.md +0 -0
  76. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/skills/unique-cli-web-search/SKILL.md +0 -0
  77. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/cli/state.py +0 -0
  78. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/utils/analytics_order_run.py +0 -0
  79. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/utils/benchmarking_run.py +0 -0
  80. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/utils/chat_history.py +0 -0
  81. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/utils/chat_in_space.py +0 -0
  82. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/utils/file_io.py +0 -0
  83. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/utils/sources.py +0 -0
  84. {unique_sdk-2026.26.0.dev10 → unique_sdk-2026.26.0.dev11}/unique_sdk/utils/token.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-sdk
3
- Version: 2026.26.0.dev10
3
+ Version: 2026.26.0.dev11
4
4
  Summary:
5
5
  Author: Martin Fadler, Konstantin Krauss, Andreas Hauri
6
6
  Author-email: Martin Fadler <martin.fadler@unique.ch>, Konstantin Krauss <konstantin@unique.ch>, Andreas Hauri <andreas@unique.ch>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique_sdk"
3
- version = "2026.26.0.dev10"
3
+ version = "2026.26.0.dev11"
4
4
  description = ""
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -13,6 +13,7 @@ from unique_sdk.cli.commands.dynamic_frontend import (
13
13
  cmd_dynamic_frontend_list,
14
14
  )
15
15
  from unique_sdk.cli.commands.elicitation import (
16
+ DEFAULT_WAIT_TIMEOUT_SECONDS,
16
17
  cmd_elicit_ask,
17
18
  cmd_elicit_create,
18
19
  cmd_elicit_get,
@@ -1059,19 +1060,16 @@ def elicit() -> None:
1059
1060
  )
1060
1061
  @click.option("--chat-id", "-c", default=None, help="Associated chat ID.")
1061
1062
  @click.option("--message-id", "-m", default=None, help="Associated message ID.")
1062
- @click.option(
1063
- "--expires-in",
1064
- "expires_in_seconds",
1065
- type=int,
1066
- default=None,
1067
- help="Expire the elicitation after N seconds if not answered.",
1068
- )
1069
1063
  @click.option(
1070
1064
  "--timeout",
1071
1065
  type=int,
1072
- default=300,
1066
+ default=DEFAULT_WAIT_TIMEOUT_SECONDS,
1073
1067
  show_default=True,
1074
- help="Max seconds to block waiting for the user's response.",
1068
+ help=(
1069
+ "Max seconds to block waiting for the user's response. This also "
1070
+ "sets when the elicitation expires, so the request expires exactly "
1071
+ "when we stop waiting and the chat UI can offer a way to continue."
1072
+ ),
1075
1073
  )
1076
1074
  @click.option(
1077
1075
  "--poll-interval",
@@ -1130,7 +1128,6 @@ def elicit_ask(
1130
1128
  schema: str | None,
1131
1129
  chat_id: str | None,
1132
1130
  message_id: str | None,
1133
- expires_in_seconds: int | None,
1134
1131
  timeout: int,
1135
1132
  poll_interval: float,
1136
1133
  metadata: tuple[str, ...],
@@ -1167,7 +1164,6 @@ def elicit_ask(
1167
1164
  "schema": schema,
1168
1165
  "chat_id": chat_id,
1169
1166
  "message_id": message_id,
1170
- "expires_in_seconds": expires_in_seconds,
1171
1167
  "timeout": timeout,
1172
1168
  "poll_interval": poll_interval,
1173
1169
  "metadata": parsed_metadata or None,
@@ -1351,7 +1347,7 @@ def elicit_get(ctx: click.Context, elicitation_id: str) -> None:
1351
1347
  @click.option(
1352
1348
  "--timeout",
1353
1349
  type=int,
1354
- default=300,
1350
+ default=DEFAULT_WAIT_TIMEOUT_SECONDS,
1355
1351
  show_default=True,
1356
1352
  help="Max seconds to wait for a terminal state.",
1357
1353
  )
@@ -26,7 +26,7 @@ from unique_sdk.cli.formatting import (
26
26
  from unique_sdk.cli.state import ShellState
27
27
 
28
28
  DEFAULT_POLL_INTERVAL_SECONDS = 2.0
29
- DEFAULT_WAIT_TIMEOUT_SECONDS = 300
29
+ DEFAULT_WAIT_TIMEOUT_SECONDS = 7200
30
30
  TERMINAL_STATUSES = {
31
31
  "RESPONDED",
32
32
  "ACCEPTED",
@@ -669,9 +669,27 @@ def cmd_elicit_wait(
669
669
  terminal_status = status
670
670
  return format_elicitation(elicitation)
671
671
  if time.monotonic() >= deadline:
672
+ # One last read on the deadline. When the caller coupled
673
+ # ``--expires-in`` to ``--timeout`` (the default for
674
+ # ``elicit ask``), the record's ``expiresAt`` lands at this
675
+ # same instant; this fetch forces the backend's lazy expiry to
676
+ # run so we report a clean EXPIRED — and publish it to the chat
677
+ # subscription — instead of a stale PENDING.
678
+ final = dict(
679
+ unique_sdk.Elicitation.get_elicitation(
680
+ user_id=state.config.user_id,
681
+ company_id=state.config.company_id,
682
+ elicitation_id=elicitation_id,
683
+ )
684
+ )
685
+ last = final
686
+ final_status = str(final.get("status", "")).upper()
687
+ if final_status in TERMINAL_STATUSES:
688
+ terminal_status = final_status
689
+ return format_elicitation(final)
672
690
  return (
673
691
  f"elicit: timed out after {timeout}s waiting for "
674
- f"{elicitation_id} (last status: {status or 'UNKNOWN'})\n\n"
692
+ f"{elicitation_id} (last status: {final_status or 'UNKNOWN'})\n\n"
675
693
  f"{format_elicitation(last)}"
676
694
  )
677
695
  time.sleep(poll_interval)
@@ -715,7 +733,6 @@ def cmd_elicit_ask(
715
733
  schema: str | None = None,
716
734
  chat_id: str | None = None,
717
735
  message_id: str | None = None,
718
- expires_in_seconds: int | None = None,
719
736
  timeout: int = DEFAULT_WAIT_TIMEOUT_SECONDS,
720
737
  poll_interval: float = DEFAULT_POLL_INTERVAL_SECONDS,
721
738
  metadata: list[tuple[str, str]] | None = None,
@@ -751,6 +768,17 @@ def cmd_elicit_ask(
751
768
  "required": ["answer"],
752
769
  }
753
770
 
771
+ # `ask` exposes a single knob: `--timeout` is both how long we wait and
772
+ # when the record expires. The two are always the same here, so the
773
+ # backend's default (5 minutes) never leaves the record PENDING after
774
+ # we have stopped waiting — which would otherwise prevent the chat UI
775
+ # from flipping the prompt to EXPIRED and offering the user a way to
776
+ # continue. The poll loop below reads the freshly EXPIRED record and the
777
+ # elicitation subscription delivers the terminal status to the chat.
778
+ # (Use `elicit create --expires-in` if you need expiry decoupled from a
779
+ # local wait.)
780
+ expires_in_seconds = timeout
781
+
754
782
  user_metadata = _parse_metadata_pairs(metadata)
755
783
  effective_message_id = message_id
756
784
  placeholder_message_id: str | None = None
@@ -9,6 +9,7 @@ from typing import Any
9
9
 
10
10
  from unique_sdk.cli import __version__
11
11
  from unique_sdk.cli.commands.elicitation import (
12
+ DEFAULT_WAIT_TIMEOUT_SECONDS,
12
13
  cmd_elicit_ask,
13
14
  cmd_elicit_create,
14
15
  cmd_elicit_get,
@@ -87,8 +88,8 @@ OVERVIEW_HELP = textwrap.dedent("""\
87
88
  --schema <json> JSON schema (default: single 'answer' field)
88
89
  --chat-id / -c <id> Associated chat ID
89
90
  --message-id / -m <id> Associated message ID
90
- --expires-in <seconds> Auto-expire the request
91
- --timeout <seconds> Max wait time (default: 300)
91
+ --timeout <seconds> Max wait time, also sets when the
92
+ request expires (default: 7200)
92
93
  --poll-interval <seconds> Poll frequency (default: 2.0)
93
94
  --metadata key=value Metadata (repeatable)
94
95
  --no-visible Skip the UN-19815 visibility workaround
@@ -112,7 +113,7 @@ OVERVIEW_HELP = textwrap.dedent("""\
112
113
  elicit pending List pending elicitations
113
114
  elicit get <id> Show one elicitation
114
115
  elicit wait <id> [opts] Poll until answered / expired
115
- --timeout <seconds> Max wait (default: 300)
116
+ --timeout <seconds> Max wait (default: 7200)
116
117
  --poll-interval <seconds> Poll frequency (default: 2.0)
117
118
  elicit respond <id> [opts] Respond on behalf of the user
118
119
  --action ACCEPT|DECLINE|CANCEL|REJECT Response action (required)
@@ -788,8 +789,9 @@ class UniqueShell(cmd.Cmd):
788
789
  --mode FORM|URL Display mode (create only, required)
789
790
  --chat-id / -c <id> Associated chat ID
790
791
  --message-id / -m <id> Associated message ID
791
- --expires-in <seconds> Auto-expire the request
792
- --timeout <seconds> (ask / wait) max wait time, default 300
792
+ --expires-in <seconds> Auto-expire the request (create only)
793
+ --timeout <seconds> (ask / wait) max wait time, default 7200;
794
+ for ask this also sets when it expires
793
795
  --poll-interval <seconds> (ask / wait) poll frequency, default 2
794
796
  --external-id <id> External identifier (create only)
795
797
  --metadata key=value Metadata (repeatable)
@@ -863,7 +865,7 @@ class UniqueShell(cmd.Cmd):
863
865
  "message_id": None,
864
866
  "expires_in_seconds": None,
865
867
  "external_elicitation_id": None,
866
- "timeout": 300,
868
+ "timeout": DEFAULT_WAIT_TIMEOUT_SECONDS,
867
869
  "poll_interval": 2.0,
868
870
  "action": None,
869
871
  "content": None,
@@ -972,6 +974,13 @@ class UniqueShell(cmd.Cmd):
972
974
  if not message:
973
975
  self._print("Usage: elicit ask <message> [options]")
974
976
  return
977
+ if opts["expires_in_seconds"] is not None:
978
+ self._print(
979
+ "No such option: --expires-in for 'elicit ask'. Use --timeout "
980
+ "(it also sets when the request expires). For expiry decoupled "
981
+ "from a local wait, use 'elicit create --expires-in'."
982
+ )
983
+ return
975
984
 
976
985
  ask_kwargs: dict[str, Any] = {
977
986
  "message": message,
@@ -979,7 +988,6 @@ class UniqueShell(cmd.Cmd):
979
988
  "schema": opts["schema"],
980
989
  "chat_id": opts["chat_id"],
981
990
  "message_id": opts["message_id"],
982
- "expires_in_seconds": opts["expires_in_seconds"],
983
991
  "timeout": opts["timeout"],
984
992
  "poll_interval": opts["poll_interval"],
985
993
  "metadata": opts["metadata"] or None,
@@ -133,8 +133,7 @@ unique-cli elicit ask "Please provide report settings" \
133
133
  | `--message-id` | `-m` | none | **MANDATORY.** The current assistant message ID. Always pass `"$UNIQUE_MESSAGE_ID"`. Anchors the elicitation to the correct message in the conversation thread. |
134
134
  | `--tool-name` | `-t` | `agent_question` | Short snake_case label shown to the user (e.g. `clarify`, `confirm_delete`, `choose_report`). |
135
135
  | `--schema` | | single `answer` string | JSON Schema for the form body. |
136
- | `--expires-in` | | none | Seconds before the request auto-expires on the platform. |
137
- | `--timeout` | | `300` | Max seconds to block locally before giving up. |
136
+ | `--timeout` | | `7200` | Max seconds to block locally before giving up. This is the single knob for `ask`: it also sets when the request expires on the platform, so the prompt expires exactly when you stop waiting and the chat UI can offer the user a way to continue. |
138
137
  | `--poll-interval` | | `2.0` | Seconds between status polls. |
139
138
  | `--metadata` | | none | `key=value` metadata (repeatable). |
140
139
  | `--assistant-id` | | `$UNIQUE_ASSISTANT_ID`, else latest assistant in chat | Assistant id for the placeholder message created by the visibility workaround. Set this (or export `UNIQUE_ASSISTANT_ID`) only if the chat is brand-new with no prior assistant messages. |
@@ -159,9 +158,34 @@ Terminal statuses:
159
158
  | `RESPONDED` / `COMPLETED` | User answered | Parse `Response:` JSON and proceed. |
160
159
  | `DECLINED` | User explicitly declined | Do not proceed. Acknowledge and stop. |
161
160
  | `CANCELLED` | Cancelled (by user or system) | Do not proceed. |
162
- | `EXPIRED` | Timed out on the platform (via `--expires-in`) | Ask again only if the task still needs it. |
161
+ | `EXPIRED` | Timed out on the platform the user did not answer within `--timeout` | Ask again only if the task still needs it; do not treat the expiry as approval. |
163
162
 
164
- If the CLI itself times out locally (`elicit: timed out after Ns ...`), raise `--timeout` and try again. If this happens repeatedly, double-check that you passed `--chat-id` and did not pass `--no-visible` — an invisible elicitation is the most common cause of a local timeout.
163
+ Because `ask` derives the request's expiry from `--timeout`, when the user does not answer in time the platform expires the request and `elicit ask` returns a clean `EXPIRED` status (rather than a local-only timeout). If you instead see `elicit: timed out after Ns ...`, raise `--timeout` and try again. If this happens repeatedly, double-check that you passed `--chat-id` and did not pass `--no-visible` — an invisible elicitation is the most common cause of a local timeout.
164
+
165
+ ### Repeat the answer back in chat
166
+
167
+ After a `RESPONDED` / `COMPLETED` elicitation, always repeat the user's answer back in the normal chat before you continue. This keeps the decision in the chat history and makes it clear what the user said.
168
+
169
+ Write this as a user-readable summary, not as raw JSON. Use the field descriptions and option labels from the schema to translate the response into plain language:
170
+
171
+ ```markdown
172
+ Got it — you chose Markdown for the report format and asked me to include the appendix.
173
+ ```
174
+
175
+ If the exact structured response is useful for auditing or debugging, put it behind a collapsed details block after the readable summary instead of leading with it:
176
+
177
+ ````markdown
178
+ <details>
179
+ <summary>Structured elicitation response</summary>
180
+
181
+ ```json
182
+ {"format":"Markdown","include_appendix":true}
183
+ ```
184
+
185
+ </details>
186
+ ````
187
+
188
+ Do not expose raw JSON by default when a natural-language confirmation would be clearer.
165
189
 
166
190
  ## Scripting pattern
167
191
 
@@ -192,7 +216,7 @@ esac
192
216
  - Use `enum` for closed choices so the UI can render a selector.
193
217
  - Use `"type": "boolean"` for confirmations; treat `true` as "go ahead", everything else as "stop".
194
218
  - Add short `description` strings -- they are shown as help text next to each field.
195
- - Keep schemas small. Break long flows into several sequential `elicit ask` calls instead of one giant form.
219
+ - Keep schemas small. Ask at most 5 questions in a single elicitation; if you need more, split the flow so the user is not confused by an oversized form.
196
220
 
197
221
  ## Agent workflow rules
198
222
 
@@ -203,9 +227,11 @@ esac
203
227
  5. **Never run destructive CLI commands without a confirmation elicitation.** This includes `rm`, `rmdir -r`, bulk renames, large uploads, schedule deletion, etc.
204
228
  6. **Pick a meaningful `--tool-name`.** `confirm_delete`, `choose_region`, `pick_report` -- short snake_case describing the intent.
205
229
  7. **Constrain answers with a schema** whenever the valid set is finite -- don't rely on parsing free text when `enum` is an option.
206
- 8. **Handle non-`RESPONDED` outcomes explicitly.** If the status is `DECLINED` / `CANCELLED` / `EXPIRED`, tell the user you stopped and ask what they want to do next instead of silently proceeding.
207
- 9. **Don't spam elicitations.** One well-designed form with several fields is better than five sequential yes/no questions.
208
- 10. **Respect timeouts.** The default `--timeout` is 5 minutes -- raise it only if you genuinely expect the user to take longer.
230
+ 8. **Repeat answered elicitations back in chat.** Summarize what the user chose in natural language before acting on it; hide raw JSON in a collapsible details block only when it adds value.
231
+ 9. **Handle non-`RESPONDED` outcomes explicitly.** If the status is `DECLINED` / `CANCELLED` / `EXPIRED`, tell the user you stopped and ask what they want to do next instead of silently proceeding.
232
+ 10. **Don't spam elicitations.** One well-designed form with a few related fields is better than five sequential yes/no questions.
233
+ 11. **Cap each elicitation at 5 questions.** If you need more than 5 answers, split them into multiple focused elicitations so the user can respond confidently.
234
+ 12. **Respect timeouts.** The default `--timeout` is 2 hours -- override it only when the task needs a shorter or longer wait.
209
235
 
210
236
  ## Prerequisites
211
237